diff options
Diffstat (limited to 'src/soc/samsung/exynos5250')
-rw-r--r-- | src/soc/samsung/exynos5250/i2c.c | 447 | ||||
-rw-r--r-- | src/soc/samsung/exynos5250/i2c.h | 16 |
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(®s->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(®s->con) & ~I2cConIntPending, ®s->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(®s->con) | I2cConAckGen, ®s->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(®s->con) & ~I2cConAckGen, ®s->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(®s->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(®s->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(®s->stat) & (I2cStatModeMask); + writeb(mode | I2cStatEnable, ®s->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, ®s->ds); + uint8_t mode = read ? I2cStatMasterRecv : I2cStatMasterXmit; + writeb(mode | I2cStatStartStop | I2cStatEnable, ®s->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], ®s->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(®s->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, ®s->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 */ |