From: Abhishek Paliwal <abhishek.pali...@aricent.com> From: David Daney <david.da...@cavium.com>
Combined several changes: Add workaround for chips with broken irqs. Use High Level Controller when possible. Retry more situations where arbitration is lost. Clean up resource allocation code. Signed-off-by: David Daney <david.da...@cavium.com> Signed-off-by: Abhishek Paliwal <abhishek.pali...@aricent.com> --- drivers/i2c/busses/i2c-octeon.c | 446 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 420 insertions(+), 26 deletions(-) diff --git a/drivers/i2c/busses/i2c-octeon.c b/drivers/i2c/busses/i2c-octeon.c index 81042b0..96d88d0 100644 --- a/drivers/i2c/busses/i2c-octeon.c +++ b/drivers/i2c/busses/i2c-octeon.c @@ -2,7 +2,7 @@ * (C) Copyright 2009-2010 * Nokia Siemens Networks, michael.lawnick....@nsn.com * - * Portions Copyright (C) 2010, 2011 Cavium Networks, Inc. + * Portions Copyright (C) 2010 - 2013 Cavium, Inc. * * This is a driver for the i2c adapter in Cavium Networks' OCTEON processors. * @@ -27,21 +27,31 @@ #define DRV_NAME "i2c-octeon" /* The previous out-of-tree version was implicitly version 1.0. */ -#define DRV_VERSION "2.0" +#define DRV_VERSION "2.5" /* register offsets */ -#define SW_TWSI 0x00 -#define TWSI_INT 0x10 +#define SW_TWSI 0x00 +#define TWSI_INT 0x10 +#define SW_TWSI_EXT 0x18 /* Controller command patterns */ #define SW_TWSI_V 0x8000000000000000ull +#define SW_TWSI_EIA (1ull << 61) +#define SW_TWSI_R (1ull << 56) +#define SW_TWSI_SOVR (1ull << 55) +#define SW_TWSI_OP_7 (0ull << 57) +#define SW_TWSI_OP_7_IA (1ull << 57) +#define SW_TWSI_OP_10 (2ull << 57) +#define SW_TWSI_OP_10_IA (3ull << 57) +#define SW_TWSI_SIZE_SHIFT 52 +#define SW_TWSI_A_SHIFT 40 +#define SW_TWSI_IA_SHIFT 32 #define SW_TWSI_EOP_TWSI_DATA 0x0C00000100000000ull #define SW_TWSI_EOP_TWSI_CTL 0x0C00000200000000ull #define SW_TWSI_EOP_TWSI_CLKCTL 0x0C00000300000000ull #define SW_TWSI_EOP_TWSI_STAT 0x0C00000300000000ull #define SW_TWSI_EOP_TWSI_RST 0x0C00000700000000ull #define SW_TWSI_OP_TWSI_CLK 0x0800000000000000ull -#define SW_TWSI_R 0x0100000000000000ull /* Controller command and status bits */ #define TWSI_CTL_CE 0x80 @@ -66,10 +76,10 @@ struct octeon_i2c { int irq; u32 twsi_freq; int sys_freq; - resource_size_t twsi_phys; void __iomem *twsi_base; - resource_size_t regsize; struct device *dev; + int broken_irq_mode; + bool octeon_i2c_hlc_enabled; }; /** @@ -202,6 +212,20 @@ static int octeon_i2c_wait(struct octeon_i2c *i2c) { int result; + if (i2c->broken_irq_mode) { + /* + * Some chip revisions seem to not assert the irq in + * the interrupt controller. So we must poll for the + * IFLG change. + */ + u64 end = get_jiffies_64() + i2c->adap.timeout; + + while (!octeon_i2c_test_iflg(i2c) && get_jiffies_64() <= end) + udelay(50); + + return octeon_i2c_test_iflg(i2c) ? 0 : -ETIMEDOUT; + } + octeon_i2c_int_enable(i2c); result = wait_event_timeout(i2c->queue, @@ -210,6 +234,13 @@ static int octeon_i2c_wait(struct octeon_i2c *i2c) octeon_i2c_int_disable(i2c); + + if (result <= 0 && octeon_i2c_test_iflg(i2c)) { + dev_err(i2c->dev, "broken irq connection detected, switching to polling mode.\n"); + i2c->broken_irq_mode = 1; + return 0; + } + if (result < 0) { dev_dbg(i2c->dev, "%s: wait interrupted\n", __func__); return result; @@ -221,6 +252,44 @@ static int octeon_i2c_wait(struct octeon_i2c *i2c) return 0; } +static int octeon_i2c_enable_hlc(struct octeon_i2c *i2c) +{ + if (i2c->octeon_i2c_hlc_enabled) + return 0; + + octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, + TWSI_CTL_CE | TWSI_CTL_ENAB); + + i2c->octeon_i2c_hlc_enabled = true; + return 0; +} + +static bool octeon_i2c_lost_arb(u64 code) +{ + switch (code & 0xffull) { + /* Arbitration lost in address or data byte */ + case 0x38: + /* + * Arbitration lost in address as master, slave address + + * write bit received, ACK transmitted. + */ + case 0x68: + /* + * Arbitration lost in address as master, general call address + * received, ACK transmitted. + */ + case 0x78: + /* + * Arbitration lost in address as master, slave address + read + * bit received, ACK transmitted. + */ + case 0xb0: + return true; + default: + return false; + } +} + /** * octeon_i2c_start - send START to the bus. * @i2c: The struct octeon_i2c. @@ -232,6 +301,8 @@ static int octeon_i2c_start(struct octeon_i2c *i2c) u8 data; int result; + i2c->octeon_i2c_hlc_enabled = false; + octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, TWSI_CTL_ENAB | TWSI_CTL_STA); @@ -290,17 +361,19 @@ static int octeon_i2c_stop(struct octeon_i2c *i2c) * @target: Target address. * @data: Pointer to the data to be sent. * @length: Length of the data. + * @phase: which phase of a combined operation. * * The address is sent over the bus, then the data. * * Returns 0 on success, otherwise a negative errno. */ static int octeon_i2c_write(struct octeon_i2c *i2c, int target, - const u8 *data, int length) + const u8 *data, int length, int phase) { int i, result; u8 tmp; +restart: result = octeon_i2c_start(i2c); if (result) return result; @@ -314,6 +387,9 @@ static int octeon_i2c_write(struct octeon_i2c *i2c, int target, for (i = 0; i < length; i++) { tmp = octeon_i2c_read_sw(i2c, SW_TWSI_EOP_TWSI_STAT); + if (phase == 0 && octeon_i2c_lost_arb(tmp)) + goto restart; + if ((tmp != STAT_TXADDR_ACK) && (tmp != STAT_TXDATA_ACK)) { dev_err(i2c->dev, "%s: bad status before write (0x%x)\n", @@ -338,13 +414,14 @@ static int octeon_i2c_write(struct octeon_i2c *i2c, int target, * @target: Target address. * @data: Pointer to the location to store the datae . * @length: Length of the data. + * @phase: which phase of a combined operation. * * The address is sent over the bus, then the data is read. * * Returns 0 on success, otherwise a negative errno. */ static int octeon_i2c_read(struct octeon_i2c *i2c, int target, - u8 *data, int length) + u8 *data, int length, int phase) { int i, result; u8 tmp; @@ -352,6 +429,7 @@ static int octeon_i2c_read(struct octeon_i2c *i2c, int target, if (length < 1) return -EINVAL; +restart: result = octeon_i2c_start(i2c); if (result) return result; @@ -365,6 +443,9 @@ static int octeon_i2c_read(struct octeon_i2c *i2c, int target, for (i = 0; i < length; i++) { tmp = octeon_i2c_read_sw(i2c, SW_TWSI_EOP_TWSI_STAT); + if (phase == 0 && octeon_i2c_lost_arb(tmp)) + goto restart; + if ((tmp != STAT_RXDATA_ACK) && (tmp != STAT_RXADDR_ACK)) { dev_err(i2c->dev, "%s: bad status before read (0x%x)\n", @@ -388,6 +469,294 @@ static int octeon_i2c_read(struct octeon_i2c *i2c, int target, return 0; } +static bool octeon_i2c_hlc_test_ready(struct octeon_i2c *i2c) +{ + u64 v = __raw_readq(i2c->twsi_base + SW_TWSI); + return (v & SW_TWSI_V) == 0; +} + +static void octeon_i2c_hlc_int_enable(struct octeon_i2c *i2c) +{ + octeon_i2c_write_int(i2c, 0x10); +} + +static void octeon_i2c_hlc_int_clear(struct octeon_i2c *i2c) +{ + octeon_i2c_write_int(i2c, 0x3); +} + +/** + * octeon_i2c_hlc_wait - wait for an HLC operation to complete. + * @i2c: The struct octeon_i2c. + * + * Returns 0 on success, otherwise a negative errno. + */ +static int octeon_i2c_hlc_wait(struct octeon_i2c *i2c) +{ + int result; + + if (i2c->broken_irq_mode) { + /* + * Some chip revisions seem to not assert the irq in + * the interrupt controller. So we must poll for the + * IFLG change. + */ + u64 end = get_jiffies_64() + i2c->adap.timeout; + + while (!octeon_i2c_hlc_test_ready(i2c) && get_jiffies_64() <= end) + udelay(50); + + return octeon_i2c_hlc_test_ready(i2c) ? 0 : -ETIMEDOUT; + } + + octeon_i2c_hlc_int_enable(i2c); + + result = wait_event_interruptible_timeout(i2c->queue, + octeon_i2c_hlc_test_ready(i2c), + i2c->adap.timeout); + + octeon_i2c_int_disable(i2c); + + + if (result <= 0 && octeon_i2c_hlc_test_ready(i2c)) { + dev_err(i2c->dev, "broken irq connection detected, switching to polling mode.\n"); + i2c->broken_irq_mode = 1; + return 0; + } + + if (result < 0) { + dev_dbg(i2c->dev, "%s: wait interrupted\n", __func__); + return result; + } else if (result == 0) { + dev_dbg(i2c->dev, "%s: timeout\n", __func__); + return -ETIMEDOUT; + } + + return 0; +} + +static int octeon_i2c_simple_read(struct octeon_i2c *i2c, struct i2c_msg *msgs) +{ + u64 cmd; + int i, j; + int ret = 0; + + octeon_i2c_enable_hlc(i2c); +retry: + cmd = SW_TWSI_V | SW_TWSI_R | SW_TWSI_SOVR; + /* SIZE */ + cmd |= (u64)(msgs[0].len - 1) << SW_TWSI_SIZE_SHIFT; + /* A */ + cmd |= (u64)(msgs[0].addr & 0x7full) << SW_TWSI_A_SHIFT; + + if (msgs[0].flags & I2C_M_TEN) + cmd |= SW_TWSI_OP_10; + else + cmd |= SW_TWSI_OP_7; + + octeon_i2c_hlc_int_clear(i2c); + __raw_writeq(cmd, i2c->twsi_base + SW_TWSI); + + ret = octeon_i2c_hlc_wait(i2c); + + if (ret) + goto err; + + cmd = __raw_readq(i2c->twsi_base + SW_TWSI); + + if ((cmd & SW_TWSI_R) == 0) { + if (octeon_i2c_lost_arb(cmd)) + goto retry; + ret = -EIO; + goto err; + } + + for (i = 0, j = msgs[0].len - 1; i < msgs[0].len && i < 4; i++, j--) + msgs[0].buf[j] = (cmd >> (8 * i)) & 0xff; + + if (msgs[0].len >= 4) { + cmd = __raw_readq(i2c->twsi_base + SW_TWSI_EXT); + for (i = 0; i < msgs[0].len - 4 && i < 4; i++, j--) + msgs[0].buf[j] = (cmd >> (8 * i)) & 0xff; + } + +err: + return ret; +} + +static int octeon_i2c_simple_write(struct octeon_i2c *i2c, struct i2c_msg *msgs) +{ + u64 cmd; + int i, j; + int ret = 0; + + octeon_i2c_enable_hlc(i2c); + +retry: + cmd = SW_TWSI_V | SW_TWSI_SOVR; + /* SIZE */ + cmd |= (u64)(msgs[0].len - 1) << SW_TWSI_SIZE_SHIFT; + /* A */ + cmd |= (u64)(msgs[0].addr & 0x7full) << SW_TWSI_A_SHIFT; + + if (msgs[0].flags & I2C_M_TEN) + cmd |= SW_TWSI_OP_10; + else + cmd |= SW_TWSI_OP_7; + + for (i = 0, j = msgs[0].len - 1; i < msgs[0].len && i < 4; i++, j--) + cmd |= (u64)msgs[0].buf[j] << (8 * i); + + if (msgs[0].len >= 4) { + u64 ext = 0; + for (i = 0; i < msgs[0].len - 4 && i < 4; i++, j--) + ext |= (u64)msgs[0].buf[j] << (8 * i); + __raw_writeq(ext, i2c->twsi_base + SW_TWSI_EXT); + } + + octeon_i2c_hlc_int_clear(i2c); + __raw_writeq(cmd, i2c->twsi_base + SW_TWSI); + + ret = octeon_i2c_hlc_wait(i2c); + + if (ret) + goto err; + + cmd = __raw_readq(i2c->twsi_base + SW_TWSI); + + if ((cmd & SW_TWSI_R) == 0) { + if (octeon_i2c_lost_arb(cmd)) + goto retry; + ret = -EIO; + goto err; + } + +err: + return ret; +} + +static int octeon_i2c_ia_read(struct octeon_i2c *i2c, struct i2c_msg *msgs) +{ + u64 cmd; + int i, j; + int ret = 0; + + octeon_i2c_enable_hlc(i2c); + +retry: + cmd = SW_TWSI_V | SW_TWSI_R | SW_TWSI_SOVR; + /* SIZE */ + cmd |= (u64)(msgs[1].len - 1) << SW_TWSI_SIZE_SHIFT; + /* A */ + cmd |= (u64)(msgs[0].addr & 0x7full) << SW_TWSI_A_SHIFT; + + if (msgs[0].flags & I2C_M_TEN) + cmd |= SW_TWSI_OP_10_IA; + else + cmd |= SW_TWSI_OP_7_IA; + + if (msgs[0].len == 2) { + u64 ext = 0; + cmd |= SW_TWSI_EIA; + ext = (u64)msgs[0].buf[0] << SW_TWSI_IA_SHIFT; + cmd |= (u64)msgs[0].buf[1] << SW_TWSI_IA_SHIFT; + __raw_writeq(ext, i2c->twsi_base + SW_TWSI_EXT); + } else { + cmd |= (u64)msgs[0].buf[0] << SW_TWSI_IA_SHIFT; + } + + octeon_i2c_hlc_int_clear(i2c); + __raw_writeq(cmd, i2c->twsi_base + SW_TWSI); + + ret = octeon_i2c_hlc_wait(i2c); + + if (ret) + goto err; + + cmd = __raw_readq(i2c->twsi_base + SW_TWSI); + + if ((cmd & SW_TWSI_R) == 0) { + if (octeon_i2c_lost_arb(cmd)) + goto retry; + ret = -EIO; + goto err; + } + + for (i = 0, j = msgs[1].len - 1; i < msgs[1].len && i < 4; i++, j--) + msgs[1].buf[j] = (cmd >> (8 * i)) & 0xff; + + if (msgs[1].len >= 4) { + cmd = __raw_readq(i2c->twsi_base + SW_TWSI_EXT); + for (i = 0; i < msgs[1].len - 4 && i < 4; i++, j--) + msgs[1].buf[j] = (cmd >> (8 * i)) & 0xff; + } + +err: + return ret; +} + +static int octeon_i2c_ia_write(struct octeon_i2c *i2c, struct i2c_msg *msgs) +{ + u64 cmd; + int i, j; + int ret = 0; + u64 ext = 0; + bool set_ext = false; + + octeon_i2c_enable_hlc(i2c); + +retry: + cmd = SW_TWSI_V | SW_TWSI_SOVR; + /* SIZE */ + cmd |= (u64)(msgs[1].len - 1) << SW_TWSI_SIZE_SHIFT; + /* A */ + cmd |= (u64)(msgs[0].addr & 0x7full) << SW_TWSI_A_SHIFT; + + if (msgs[0].flags & I2C_M_TEN) + cmd |= SW_TWSI_OP_10_IA; + else + cmd |= SW_TWSI_OP_7_IA; + + if (msgs[0].len == 2) { + cmd |= SW_TWSI_EIA; + ext |= (u64)msgs[0].buf[0] << SW_TWSI_IA_SHIFT; + set_ext = true; + cmd |= (u64)msgs[0].buf[1] << SW_TWSI_IA_SHIFT; + } else { + cmd |= (u64)msgs[0].buf[0] << SW_TWSI_IA_SHIFT; + } + for (i = 0, j = msgs[1].len - 1; i < msgs[1].len && i < 4; i++, j--) + cmd |= (u64)msgs[1].buf[j] << (8 * i); + + if (msgs[1].len >= 4) { + for (i = 0; i < msgs[1].len - 4 && i < 4; i++, j--) + ext |= (u64)msgs[1].buf[j] << (8 * i); + set_ext = true; + } + if (set_ext) + __raw_writeq(ext, i2c->twsi_base + SW_TWSI_EXT); + + octeon_i2c_hlc_int_clear(i2c); + __raw_writeq(cmd, i2c->twsi_base + SW_TWSI); + + ret = octeon_i2c_hlc_wait(i2c); + + if (ret) + goto err; + + cmd = __raw_readq(i2c->twsi_base + SW_TWSI); + + if ((cmd & SW_TWSI_R) == 0) { + if (octeon_i2c_lost_arb(cmd)) + goto retry; + ret = -EIO; + goto err; + } + +err: + return ret; +} + /** * octeon_i2c_xfer - The driver's master_xfer function. * @adap: Pointer to the i2c_adapter structure. @@ -406,6 +775,27 @@ static int octeon_i2c_xfer(struct i2c_adapter *adap, int ret = 0; struct octeon_i2c *i2c = i2c_get_adapdata(adap); + if (num == 1) { + if (msgs[0].len > 0 && msgs[0].len <= 8) { + if (msgs[0].flags & I2C_M_RD) + ret = octeon_i2c_simple_read(i2c, msgs); + else + ret = octeon_i2c_simple_write(i2c, msgs); + goto out; + } + } else if (num == 2) { + if ((msgs[0].flags & I2C_M_RD) == 0 && + msgs[0].len > 0 && msgs[0].len <= 2 && + msgs[1].len > 0 && msgs[1].len <= 8 && + msgs[0].addr == msgs[1].addr) { + if (msgs[1].flags & I2C_M_RD) + ret = octeon_i2c_ia_read(i2c, msgs); + else + ret = octeon_i2c_ia_write(i2c, msgs); + goto out; + } + } + for (i = 0; ret == 0 && i < num; i++) { pmsg = &msgs[i]; dev_dbg(i2c->dev, @@ -414,13 +804,13 @@ static int octeon_i2c_xfer(struct i2c_adapter *adap, pmsg->len, pmsg->addr, i + 1, num); if (pmsg->flags & I2C_M_RD) ret = octeon_i2c_read(i2c, pmsg->addr, pmsg->buf, - pmsg->len); + pmsg->len, i); else ret = octeon_i2c_write(i2c, pmsg->addr, pmsg->buf, - pmsg->len); + pmsg->len, i); } octeon_i2c_stop(i2c); - +out: return (ret != 0) ? ret : num; } @@ -438,7 +828,6 @@ static struct i2c_adapter octeon_i2c_ops = { .owner = THIS_MODULE, .name = "OCTEON adapter", .algo = &octeon_i2c_algo, - .timeout = HZ / 50, }; /** @@ -492,20 +881,26 @@ static int octeon_i2c_initlowlevel(struct octeon_i2c *i2c) u8 status; int tries; - /* disable high level controller, enable bus access */ - octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, TWSI_CTL_ENAB); - /* reset controller */ octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_RST, 0); - for (tries = 10; tries; tries--) { + status = 0; + for (tries = 10; tries && status != STAT_IDLE; tries--) { udelay(1); status = octeon_i2c_read_sw(i2c, SW_TWSI_EOP_TWSI_STAT); - if (status == STAT_IDLE) - return 0; } - dev_err(i2c->dev, "%s: TWSI_RST failed! (0x%x)\n", __func__, status); - return -EIO; + + if (status != STAT_IDLE) { + dev_err(i2c->dev, "%s: TWSI_RST failed! (0x%x)\n", + __func__, status); + return -EIO; + } + + + /* disable high level controller, enable bus access */ + octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, TWSI_CTL_ENAB); + + return 0; } static int octeon_i2c_probe(struct platform_device *pdev) @@ -534,8 +929,6 @@ static int octeon_i2c_probe(struct platform_device *pdev) result = -ENXIO; goto out; } - i2c->twsi_phys = res_mem->start; - i2c->regsize = resource_size(res_mem); /* * "clock-rate" is a legacy binding, the official binding is @@ -554,12 +947,12 @@ static int octeon_i2c_probe(struct platform_device *pdev) i2c->sys_freq = octeon_get_io_clock_rate(); - if (!devm_request_mem_region(&pdev->dev, i2c->twsi_phys, i2c->regsize, - res_mem->name)) { + if (!devm_request_mem_region(&pdev->dev, res_mem->start, resource_size(res_mem), + res_mem->name)) { dev_err(i2c->dev, "request_mem_region failed\n"); goto out; } - i2c->twsi_base = devm_ioremap(&pdev->dev, i2c->twsi_phys, i2c->regsize); + i2c->twsi_base = devm_ioremap(&pdev->dev, res_mem->start, resource_size(res_mem)); init_waitqueue_head(&i2c->queue); @@ -585,6 +978,7 @@ static int octeon_i2c_probe(struct platform_device *pdev) } i2c->adap = octeon_i2c_ops; + i2c->adap.timeout = msecs_to_jiffies(50); i2c->adap.dev.parent = &pdev->dev; i2c->adap.dev.of_node = pdev->dev.of_node; i2c_set_adapdata(&i2c->adap, i2c); -- 1.8.1.4 -- _______________________________________________ linux-yocto mailing list linux-yocto@yoctoproject.org https://lists.yoctoproject.org/listinfo/linux-yocto