diff options
author | Gabe Black <gabeblack@google.com> | 2014-04-07 18:45:14 -0700 |
---|---|---|
committer | Marc Jones <marc.jones@se-eng.com> | 2014-12-16 00:02:43 +0100 |
commit | cdb61a6f5d2268b059ac56da3b69ad0313f3fb90 (patch) | |
tree | 57dcfe80e68d872d1080cfc168abc866f0cbffe7 /src/soc/samsung/exynos5250/i2c.c | |
parent | be6f8cb0f0a2873a9c16dc454cde1f29f4fab765 (diff) |
i2c: Replace the i2c API.
The new API is in use in depthcharge and is based around the "i2c_transfer"
function instead of i2c_read and i2c_write. The new function takes an array of
i2c_seg structures which represent each portion of the transfer after a start
bit and before the stop bit. If there's more than one segment, they're
seperated by repeated starts.
Some wrapper functions have also been added which make certain common
operations easy. These include reading or writing a byte from a register or
reading or writing a blob of raw data. The i2c device drivers generally use
these wrappers but can call the i2c_transfer function directly if the need
something different.
The tegra i2c driver was very similar to the one in depthcharge and was simple
to convert. The Exynos 5250 and 5420 drivers were ported from depthcharge and
replace the ones in coreboot. The Exynos 5420 driver was ported from the high
speed portion of the one in coreboot and was straightforward to port back. The
low speed portion and the Exynos 5250 drivers had been transplanted from U-Boot
and were replaced with the depthcharge implementation.
BUG=None
TEST=Built and booted on nyan with and without EFS. Built and booted on, pit
and daisy.
BRANCH=None
Original-Change-Id: I1e98c3fa2560be25444ab3d0394bb214b9d56e93
Original-Signed-off-by: Gabe Black <gabeblack@google.com>
Original-Reviewed-on: https://chromium-review.googlesource.com/193561
Original-Reviewed-by: David Hendricks <dhendrix@chromium.org>
Original-Reviewed-by: Jimmy Zhang <jimmzhang@nvidia.com>
Original-Tested-by: Jimmy Zhang <jimmzhang@nvidia.com>
Original-Reviewed-by: Hung-Te Lin <hungte@chromium.org>
Original-Commit-Queue: Gabe Black <gabeblack@chromium.org>
Original-Tested-by: Gabe Black <gabeblack@chromium.org>
(cherry picked from commit 00c423fb2c06c69d580ee3ec0a3892ebf164a5fe)
This cherry-pick required additional changes to the following:
src/cpu/allwinner/a10/twi.c
src/drivers/xpowers/axp209/axp209.c
Signed-off-by: Marc Jones <marc.jones@se-eng.com>
Change-Id: I691959c66308eeeec219b1bec463b8b365a246d7
Reviewed-on: http://review.coreboot.org/7751
Tested-by: build bot (Jenkins)
Reviewed-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
Reviewed-by: Patrick Georgi <pgeorgi@google.com>
Diffstat (limited to 'src/soc/samsung/exynos5250/i2c.c')
-rw-r--r-- | src/soc/samsung/exynos5250/i2c.c | 447 |
1 files changed, 170 insertions, 277 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); } |