summaryrefslogtreecommitdiff
path: root/src/soc/samsung/exynos5250
diff options
context:
space:
mode:
Diffstat (limited to 'src/soc/samsung/exynos5250')
-rw-r--r--src/soc/samsung/exynos5250/i2c.c447
-rw-r--r--src/soc/samsung/exynos5250/i2c.h16
2 files changed, 170 insertions, 293 deletions
diff --git a/src/soc/samsung/exynos5250/i2c.c b/src/soc/samsung/exynos5250/i2c.c
index e83ab57014..d5d83b393a 100644
--- a/src/soc/samsung/exynos5250/i2c.c
+++ b/src/soc/samsung/exynos5250/i2c.c
@@ -18,386 +18,279 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#include <assert.h>
#include <console/console.h>
#include <delay.h>
#include <arch/io.h>
#include <device/i2c.h>
#include "clk.h"
#include "i2c.h"
+#include "periph.h"
+
+struct __attribute__ ((packed)) i2c_regs
+{
+ uint8_t con;
+ uint8_t _1[3];
+ uint8_t stat;
+ uint8_t _2[3];
+ uint8_t add;
+ uint8_t _3[3];
+ uint8_t ds;
+ uint8_t _4[3];
+ uint8_t lc;
+ uint8_t _5[3];
+};
+
+struct s3c24x0_i2c_bus {
+ int bus_num;
+ struct i2c_regs *regs;
+ enum periph_id periph_id;
+};
-#define I2C_WRITE 0
-#define I2C_READ 1
-
-#define I2C_OK 0
-#define I2C_NOK 1
-#define I2C_NACK 2
-#define I2C_NOK_LA 3 /* Lost arbitration */
-#define I2C_NOK_TOUT 4 /* time out */
-
-#define I2CSTAT_BSY 0x20 /* Busy bit */
-#define I2CSTAT_NACK 0x01 /* Nack bit */
-#define I2CCON_ACKGEN 0x80 /* Acknowledge generation */
-#define I2CCON_IRPND 0x10 /* Interrupt pending bit */
-#define I2C_MODE_MT 0xC0 /* Master Transmit Mode */
-#define I2C_MODE_MR 0x80 /* Master Receive Mode */
-#define I2C_START_STOP 0x20 /* START / STOP */
-#define I2C_TXRX_ENA 0x10 /* I2C Tx/Rx enable */
-
-/* The timeouts we live by */
enum {
- I2C_XFER_TIMEOUT_MS = 35, /* xfer to complete */
- I2C_INIT_TIMEOUT_MS = 1000, /* bus free on init */
- I2C_IDLE_TIMEOUT_MS = 100, /* waiting for bus idle */
- I2C_STOP_TIMEOUT_US = 200, /* waiting for stop events */
+ I2cConIntPending = 0x1 << 4,
+ I2cConIntEn = 0x1 << 5,
+ I2cConAckGen = 0x1 << 7
};
-static struct s3c24x0_i2c_bus i2c_buses[] = {
+enum {
+ I2cStatAck = 0x1 << 0,
+ I2cStatAddrZero = 0x1 << 1,
+ I2cStatAddrSlave = 0x1 << 2,
+ I2cStatArb = 0x1 << 3,
+ I2cStatEnable = 0x1 << 4,
+ I2cStatStartStop = 0x1 << 5,
+ I2cStatBusy = 0x1 << 5,
+
+ I2cStatModeMask = 0x3 << 6,
+ I2cStatSlaveRecv = 0x0 << 6,
+ I2cStatSlaveXmit = 0x1 << 6,
+ I2cStatMasterRecv = 0x2 << 6,
+ I2cStatMasterXmit = 0x3 << 6
+};
+
+static struct s3c24x0_i2c_bus i2c_busses[] = {
{
.bus_num = 0,
- .regs = (struct s3c24x0_i2c *)0x12c60000,
+ .regs = (void *)0x12c60000,
.periph_id = PERIPH_ID_I2C0,
},
{
.bus_num = 1,
- .regs = (struct s3c24x0_i2c *)0x12c70000,
+ .regs = (void *)0x12c70000,
.periph_id = PERIPH_ID_I2C1,
},
{
.bus_num = 2,
- .regs = (struct s3c24x0_i2c *)0x12c80000,
+ .regs = (void *)0x12c80000,
.periph_id = PERIPH_ID_I2C2,
},
{
.bus_num = 3,
- .regs = (struct s3c24x0_i2c *)0x12c90000,
+ .regs = (void *)0x12c90000,
.periph_id = PERIPH_ID_I2C3,
},
{
.bus_num = 4,
- .regs = (struct s3c24x0_i2c *)0x12ca0000,
+ .regs = (void *)0x12ca0000,
.periph_id = PERIPH_ID_I2C4,
},
{
.bus_num = 5,
- .regs = (struct s3c24x0_i2c *)0x12cb0000,
+ .regs = (void *)0x12cb0000,
.periph_id = PERIPH_ID_I2C5,
},
{
.bus_num = 6,
- .regs = (struct s3c24x0_i2c *)0x12cc0000,
+ .regs = (void *)0x12cc0000,
.periph_id = PERIPH_ID_I2C6,
},
{
.bus_num = 7,
- .regs = (struct s3c24x0_i2c *)0x12cd0000,
+ .regs = (void *)0x12cd0000,
.periph_id = PERIPH_ID_I2C7,
},
};
-static int WaitForXfer(struct s3c24x0_i2c *i2c)
-{
- int i;
- i = I2C_XFER_TIMEOUT_MS * 20;
- while (!(readl(&i2c->iiccon) & I2CCON_IRPND)) {
- if (i == 0) {
- printk(BIOS_ERR, "%s: i2c xfer timeout\n", __func__);
- return I2C_NOK_TOUT;
- }
- udelay(50);
- i--;
- }
- return I2C_OK;
-}
-static int IsACK(struct s3c24x0_i2c *i2c)
+static int i2c_int_pending(struct i2c_regs *regs)
{
- return !(readl(&i2c->iicstat) & I2CSTAT_NACK);
+ return readb(&regs->con) & I2cConIntPending;
}
-static void ReadWriteByte(struct s3c24x0_i2c *i2c)
+static void i2c_clear_int(struct i2c_regs *regs)
{
- uint32_t x;
-
- x = readl(&i2c->iiccon);
- writel(x & ~I2CCON_IRPND, &i2c->iiccon);
+ writeb(readb(&regs->con) & ~I2cConIntPending, &regs->con);
}
-static void i2c_ch_init(struct s3c24x0_i2c_bus *bus, int speed, int slaveadd)
+static void i2c_ack_enable(struct i2c_regs *regs)
{
- unsigned long freq, pres = 16, div;
- unsigned long val;
-
- freq = clock_get_periph_rate(bus->periph_id);
- /* calculate prescaler and divisor values */
- if ((freq / pres / (16 + 1)) > speed)
- /* set prescaler to 512 */
- pres = 512;
-
- div = 0;
-
- while ((freq / pres / (div + 1)) > speed)
- div++;
-
- /* set prescaler, divisor according to freq, also set ACKGEN, IRQ */
- val = (div & 0x0F) | 0xA0 | ((pres == 512) ? 0x40 : 0);
- writel(val, &bus->regs->iiccon);
+ writeb(readb(&regs->con) | I2cConAckGen, &regs->con);
+}
- /* init to SLAVE RECEIVE mode and clear I2CADDn */
- writel(0, &bus->regs->iicstat);
- writel(slaveadd, &bus->regs->iicadd);
- /* program Master Transmit (and implicit STOP) */
- writel(I2C_MODE_MT | I2C_TXRX_ENA, &bus->regs->iicstat);
+static void i2c_ack_disable(struct i2c_regs *regs)
+{
+ writeb(readb(&regs->con) & ~I2cConAckGen, &regs->con);
}
-/*
- * MULTI BUS I2C support
- */
-static void i2c_bus_init(struct s3c24x0_i2c_bus *bus, int speed, int slaveadd)
+static int i2c_got_ack(struct i2c_regs *regs)
{
- i2c_ch_init(bus, speed, slaveadd);
+ return !(readb(&regs->stat) & I2cStatAck);
}
-/*
- * Verify the whether I2C ACK was received or not
- *
- * @param i2c pointer to I2C register base
- * @param buf array of data
- * @param len length of data
- * return I2C_OK when transmission done
- * I2C_NACK otherwise
- */
-static int i2c_send_verify(struct s3c24x0_i2c *i2c, unsigned char buf[],
- unsigned char len)
+static int i2c_wait_for_idle(struct i2c_regs *regs)
{
- int i, result = I2C_OK;
-
- if (IsACK(i2c)) {
- for (i = 0; (i < len) && (result == I2C_OK); i++) {
- writel(buf[i], &i2c->iicds);
- ReadWriteByte(i2c);
- result = WaitForXfer(i2c);
- if (result == I2C_OK && !IsACK(i2c))
- result = I2C_NACK;
- }
- } else {
- result = I2C_NACK;
+ int timeout = 1000 * 100; // 1s.
+ while (timeout--) {
+ if (!(readb(&regs->stat) & I2cStatBusy))
+ return 0;
+ udelay(10);
}
-
- return result;
+ printk(BIOS_ERR, "I2C timeout waiting for idle.\n");
+ return 1;
}
-void i2c_init(unsigned bus_num, int speed, int slaveadd)
+static int i2c_wait_for_int(struct i2c_regs *regs)
{
- struct s3c24x0_i2c_bus *i2c;
- int i;
+ int timeout = 1000 * 100; // 1s.
+ while (timeout--) {
+ if (i2c_int_pending(regs))
+ return 0;
+ udelay(10);
+ }
+ printk(BIOS_ERR, "I2C timeout waiting for I2C interrupt.\n");
+ return 1;
+}
+
- i2c = &i2c_buses[bus_num];
- i2c_bus_init(i2c, speed, slaveadd);
- /* wait for some time to give previous transfer a chance to finish */
- i = I2C_INIT_TIMEOUT_MS * 20;
- while ((readl(&i2c->regs->iicstat) & I2CSTAT_BSY) && (i > 0)) {
- udelay(50);
- i--;
- }
- i2c_ch_init(i2c, speed, slaveadd);
+static int i2c_send_stop(struct i2c_regs *regs)
+{
+ uint8_t mode = readb(&regs->stat) & (I2cStatModeMask);
+ writeb(mode | I2cStatEnable, &regs->stat);
+ i2c_clear_int(regs);
+ return i2c_wait_for_idle(regs);
}
-/*
- * Send a STOP event and wait for it to have completed
- *
- * @param mode If it is a master transmitter or receiver
- * @return I2C_OK if the line became idle before timeout I2C_NOK_TOUT otherwise
- */
-static int i2c_send_stop(struct s3c24x0_i2c *i2c, int mode)
+static int i2c_send_start(struct i2c_regs *regs, int read, int chip)
{
- int timeout;
+ writeb(chip << 1, &regs->ds);
+ uint8_t mode = read ? I2cStatMasterRecv : I2cStatMasterXmit;
+ writeb(mode | I2cStatStartStop | I2cStatEnable, &regs->stat);
+ i2c_clear_int(regs);
- /* Setting the STOP event to fire */
- writel(mode | I2C_TXRX_ENA, &i2c->iicstat);
- ReadWriteByte(i2c);
+ if (i2c_wait_for_int(regs))
+ return 1;
- /* Wait for the STOP to send and the bus to go idle */
- for (timeout = I2C_STOP_TIMEOUT_US; timeout > 0; timeout -= 5) {
- if (!(readl(&i2c->iicstat) & I2CSTAT_BSY))
- return I2C_OK;
- udelay(5);
+ if (!i2c_got_ack(regs)) {
+ // Nobody home, but they may just be asleep.
+ return 1;
}
- return I2C_NOK_TOUT;
+ return 0;
}
-/*
- * cmd_type is 0 for write, 1 for read.
- *
- * addr_len can take any value from 0-255, it is only limited
- * by the char, we could make it larger if needed. If it is
- * 0 we skip the address write cycle.
- */
-static int i2c_transfer(struct s3c24x0_i2c *i2c,
- unsigned char cmd_type,
- unsigned char chip,
- unsigned char addr[],
- unsigned char addr_len,
- unsigned char data[],
- unsigned short data_len)
+static int i2c_xmit_buf(struct i2c_regs *regs, uint8_t *data, int len)
{
- int i, result, stop_bit_result;
- uint32_t x;
+ ASSERT(len);
- if (data == 0 || data_len == 0) {
- /* Don't support data transfer of no length or to address 0 */
- printk(BIOS_ERR, "i2c_transfer: bad call\n");
- return I2C_NOK;
- }
+ i2c_ack_enable(regs);
- /* Check I2C bus idle */
- i = I2C_IDLE_TIMEOUT_MS * 20;
- while ((readl(&i2c->iicstat) & I2CSTAT_BSY) && (i > 0)) {
- udelay(50);
- i--;
- }
-
- if (readl(&i2c->iicstat) & I2CSTAT_BSY) {
- printk(BIOS_ERR, "%s: bus busy\n", __func__);
- return I2C_NOK_TOUT;
- }
+ int i;
+ for (i = 0; i < len; i++) {
+ writeb(data[i], &regs->ds);
- x = readl(&i2c->iiccon);
- writel(x | I2CCON_ACKGEN, &i2c->iiccon);
+ i2c_clear_int(regs);
+ if (i2c_wait_for_int(regs))
+ return 1;
- if (addr && addr_len) {
- writel(chip, &i2c->iicds);
- /* send START */
- writel(I2C_MODE_MT | I2C_TXRX_ENA | I2C_START_STOP,
- &i2c->iicstat);
- if (WaitForXfer(i2c) == I2C_OK)
- result = i2c_send_verify(i2c, addr, addr_len);
- else
- result = I2C_NACK;
- } else
- result = I2C_NACK;
-
- switch (cmd_type) {
- case I2C_WRITE:
- if (result == I2C_OK)
- result = i2c_send_verify(i2c, data, data_len);
- else {
- writel(chip, &i2c->iicds);
- /* send START */
- writel(I2C_MODE_MT | I2C_TXRX_ENA | I2C_START_STOP,
- &i2c->iicstat);
- if (WaitForXfer(i2c) == I2C_OK)
- result = i2c_send_verify(i2c, data, data_len);
+ if (!i2c_got_ack(regs)) {
+ printk(BIOS_INFO, "I2c nacked.\n");
+ return 1;
}
+ }
- if (result == I2C_OK)
- result = WaitForXfer(i2c);
+ return 0;
+}
- stop_bit_result = i2c_send_stop(i2c, I2C_MODE_MT);
- break;
+static int i2c_recv_buf(struct i2c_regs *regs, uint8_t *data, int len)
+{
+ ASSERT(len);
- case I2C_READ:
- {
- int was_ok = (result == I2C_OK);
-
- writel(chip, &i2c->iicds);
- /* resend START */
- writel(I2C_MODE_MR | I2C_TXRX_ENA |
- I2C_START_STOP, &i2c->iicstat);
- ReadWriteByte(i2c);
- result = WaitForXfer(i2c);
-
- if (was_ok || IsACK(i2c)) {
- i = 0;
- while ((i < data_len) && (result == I2C_OK)) {
- /* disable ACK for final READ */
- if (i == data_len - 1) {
- x = readl(&i2c->iiccon) & ~I2CCON_ACKGEN;
- writel(x, &i2c->iiccon);
- }
- ReadWriteByte(i2c);
- result = WaitForXfer(i2c);
- data[i] = readl(&i2c->iicds);
- i++;
- }
- } else {
- result = I2C_NACK;
- }
+ i2c_ack_enable(regs);
- stop_bit_result = i2c_send_stop(i2c, I2C_MODE_MR);
- break;
- }
+ int i;
+ for (i = 0; i < len; i++) {
+ if (i == len - 1)
+ i2c_ack_disable(regs);
+
+ i2c_clear_int(regs);
+ if (i2c_wait_for_int(regs))
+ return 1;
- default:
- printk(BIOS_ERR, "i2c_transfer: bad call\n");
- result = stop_bit_result = I2C_NOK;
- break;
+ data[i] = readb(&regs->ds);
}
- /*
- * If the transmission went fine, then only the stop bit was left to
- * fail. Otherwise, the real failure we're interested in came before
- * that, during the actual transmission.
- */
- return (result == I2C_OK) ? stop_bit_result : result;
+ return 0;
}
-int i2c_read(unsigned bus, unsigned chip, unsigned addr,
- unsigned alen, uint8_t *buf, unsigned len)
+int i2c_transfer(unsigned bus, struct i2c_seg *segments, int seg_count)
{
- struct s3c24x0_i2c_bus *i2c;
- unsigned char xaddr[4];
- int ret;
+ struct s3c24x0_i2c_bus *i2c = &i2c_busses[bus];
+ struct i2c_regs *regs = i2c->regs;
+ int res = 0;
- if (alen > 4) {
- printk(BIOS_ERR, "I2C read: addr len %d not supported\n", alen);
+ if (!regs || i2c_wait_for_idle(regs))
return 1;
- }
- if (alen > 0) {
- xaddr[0] = (addr >> 24) & 0xFF;
- xaddr[1] = (addr >> 16) & 0xFF;
- xaddr[2] = (addr >> 8) & 0xFF;
- xaddr[3] = addr & 0xFF;
- }
+ writeb(I2cStatMasterXmit | I2cStatEnable, &regs->stat);
- i2c = &i2c_buses[bus];
- ret = i2c_transfer(i2c->regs, I2C_READ, chip << 1, &xaddr[4 - alen],
- alen, buf, len);
- if (ret) {
- printk(BIOS_ERR, "I2c read: failed %d\n", ret);
- return 1;
+ int i;
+ for (i = 0; i < seg_count; i++) {
+ struct i2c_seg *seg = &segments[i];
+
+ res = i2c_send_start(regs, seg->read, seg->chip);
+ if (res)
+ break;
+ if (seg->read)
+ res = i2c_recv_buf(regs, seg->buf, seg->len);
+ else
+ res = i2c_xmit_buf(regs, seg->buf, seg->len);
+ if (res)
+ break;
}
- return 0;
+
+ return i2c_send_stop(regs) || res;
}
-int i2c_write(unsigned bus, unsigned chip, unsigned addr,
- unsigned alen, const uint8_t *buf, unsigned len)
+void i2c_init(unsigned bus, int speed, int slaveadd)
{
- struct s3c24x0_i2c_bus *i2c;
- unsigned char xaddr[4];
- int ret;
+ struct s3c24x0_i2c_bus *i2c = &i2c_busses[bus];
- if (alen > 4) {
- printk(BIOS_ERR, "I2C write: addr len %d not supported\n",
- alen);
- return 1;
- }
+ unsigned long freq, pres = 16, div;
+ unsigned long val;
- if (alen > 0) {
- xaddr[0] = (addr >> 24) & 0xFF;
- xaddr[1] = (addr >> 16) & 0xFF;
- xaddr[2] = (addr >> 8) & 0xFF;
- xaddr[3] = addr & 0xFF;
- }
+ freq = clock_get_periph_rate(i2c->periph_id);
+ // Calculate prescaler and divisor values.
+ if ((freq / pres / (16 + 1)) > speed)
+ /* set prescaler to 512 */
+ pres = 512;
+
+ div = 0;
+
+ while ((freq / pres / (div + 1)) > speed)
+ div++;
- i2c = &i2c_buses[bus];
- ret = i2c_transfer(i2c->regs, I2C_WRITE, chip << 1, &xaddr[4 - alen],
- alen, (void *)buf, len);
+ // Set prescaler, divisor according to freq, also set ACKGEN, IRQ.
+ val = (div & 0x0f) | 0xa0 | ((pres == 512) ? 0x40 : 0);
+ writel(val, &i2c->regs->con);
- return ret != 0;
+ // Init to SLAVE RECEIVE mode and clear I2CADDn.
+ writel(0, &i2c->regs->stat);
+ writel(slaveadd, &i2c->regs->add);
+ // program Master Transmit (and implicit STOP).
+ writel(I2cStatMasterXmit | I2cStatEnable, &i2c->regs->stat);
}
diff --git a/src/soc/samsung/exynos5250/i2c.h b/src/soc/samsung/exynos5250/i2c.h
index a1d8bc1dcd..af4f2160ab 100644
--- a/src/soc/samsung/exynos5250/i2c.h
+++ b/src/soc/samsung/exynos5250/i2c.h
@@ -20,22 +20,6 @@
#ifndef CPU_SAMSUNG_EXYNOS5250_I2C_H
#define CPU_SAMSUNG_EXYNOS5250_I2C_H
-#include "periph.h"
-
-struct s3c24x0_i2c {
- u32 iiccon;
- u32 iicstat;
- u32 iicadd;
- u32 iicds;
- u32 iiclc;
-};
-
-struct s3c24x0_i2c_bus {
- int bus_num;
- struct s3c24x0_i2c *regs;
- enum periph_id periph_id;
-};
-
void i2c_init(unsigned bus, int speed, int slaveadd);
#endif /* CPU_SAMSUNG_EXYNOS5250_I2C_H */