[linux-yocto] [PATCH 02/12] i2c: i2c-octeon: broken irqs, high-level controller, retry lost arbitration
Chandrakala Chavva
cchavva.cavm at gmail.com
Thu Jan 29 07:32:01 PST 2015
From: Abhishek Paliwal <abhishek.paliwal at aricent.com>
From: David Daney <david.daney at 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.daney at cavium.com>
Signed-off-by: Abhishek Paliwal <abhishek.paliwal at 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.ext at 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
More information about the linux-yocto
mailing list