summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJin Park <jinyoungp@nvidia.com>2011-01-19 14:41:42 +0900
committerNiket Sirsi <nsirsi@nvidia.com>2011-01-28 19:36:49 -0800
commit6bab778a4b0caf58e464847c827d988a566bd434 (patch)
tree1cb945fbcf00f89cada57f0fa618399a12f1009f
parent8197cb15eaf0640d13c816926f65972ca6cbba84 (diff)
i2c-tegra: Retry transfer when unexpected status is detected
Sometimes unexpected status like I2C busy status, Tx FIFO interrupted and wait on Rx data etc is seen. Add a code to detect such conditions and return -EAGAIN from driver. This will cause the i2c-core to retry the transmission as per the retry count and time-out specified by the platform data of the adapter. Bug ID: 777455 Change-Id: Iac5971bca4d760d93cd2ed147f78fc2807315b4e Signed-off-by: Jin Park <jinyoungp@nvidia.com> Reviewed-on: http://git-master/r/16212 Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>
-rw-r--r--drivers/i2c/busses/i2c-tegra.c121
-rw-r--r--include/linux/i2c-tegra.h2
2 files changed, 99 insertions, 24 deletions
diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c
index 14ef9f3a8f02..8a7c909f5fc3 100644
--- a/drivers/i2c/busses/i2c-tegra.c
+++ b/drivers/i2c/busses/i2c-tegra.c
@@ -31,14 +31,16 @@
#include <mach/clk.h>
#include <mach/pinmux.h>
-#define TEGRA_I2C_TIMEOUT (msecs_to_jiffies(1000))
-#define BYTES_PER_FIFO_WORD 4
+#define TEGRA_I2C_TIMEOUT (msecs_to_jiffies(1000))
+#define TEGRA_I2C_RETRIES 3
+#define BYTES_PER_FIFO_WORD 4
#define I2C_CNFG 0x000
#define I2C_CNFG_DEBOUNCE_CNT_SHIFT 12
#define I2C_CNFG_PACKET_MODE_EN (1<<10)
#define I2C_CNFG_NEW_MASTER_FSM (1<<11)
#define I2C_STATUS 0x01C
+#define I2C_STATUS_BUSY (1<<8)
#define I2C_SL_CNFG 0x020
#define I2C_SL_CNFG_NEWSL (1<<2)
#define I2C_SL_ADDR1 0x02c
@@ -80,6 +82,7 @@
#define I2C_ERR_NO_ACK 0x01
#define I2C_ERR_ARBITRATION_LOST 0x02
#define I2C_ERR_UNKNOWN_INTERRUPT 0x04
+#define I2C_ERR_UNEXPECTED_STATUS 0x08
#define PACKET_HEADER0_HEADER_SIZE_SHIFT 28
#define PACKET_HEADER0_PACKET_ID_SHIFT 16
@@ -120,9 +123,14 @@ struct tegra_i2c_dev {
struct completion msg_complete;
int msg_err;
u8 *msg_buf;
+ u32 packet_header;
+ u32 payload_size;
+ u32 io_header;
size_t msg_buf_remaining;
int msg_read;
int msg_transfer_complete;
+ struct i2c_msg *msgs;
+ int msgs_num;
bool is_suspended;
int bus_count;
const struct tegra_pingroup_config *last_mux;
@@ -344,13 +352,10 @@ static irqreturn_t tegra_i2c_isr(int irq, void *dev_id)
status = i2c_readl(i2c_dev, I2C_INT_STATUS);
if (status == 0) {
- dev_warn(i2c_dev->dev, "irq status 0 %08x %08x %08x\n",
- i2c_readl(i2c_dev, I2C_PACKET_TRANSFER_STATUS),
- i2c_readl(i2c_dev, I2C_STATUS),
- i2c_readl(i2c_dev, I2C_CNFG));
+ dev_warn(i2c_dev->dev, "unknown interrupt\n");
i2c_dev->msg_err |= I2C_ERR_UNKNOWN_INTERRUPT;
- if (! i2c_dev->irq_disabled) {
+ if (!i2c_dev->irq_disabled) {
disable_irq_nosync(i2c_dev->irq);
i2c_dev->irq_disabled = 1;
}
@@ -362,12 +367,30 @@ static irqreturn_t tegra_i2c_isr(int irq, void *dev_id)
if (unlikely(status & status_err)) {
if (status & I2C_INT_NO_ACK) {
i2c_dev->msg_err |= I2C_ERR_NO_ACK;
- dev_warn(i2c_dev->dev, " no acknowledge\n");
+ dev_warn(i2c_dev->dev, "no acknowledge\n");
}
+
if (status & I2C_INT_ARBITRATION_LOST) {
i2c_dev->msg_err |= I2C_ERR_ARBITRATION_LOST;
- dev_warn(i2c_dev->dev, " arbitration lost\n");
+ dev_warn(i2c_dev->dev, "arbitration lost\n");
}
+
+ complete(&i2c_dev->msg_complete);
+ goto err;
+ }
+
+ if (unlikely((i2c_readl(i2c_dev, I2C_STATUS) & I2C_STATUS_BUSY)
+ && (status == I2C_INT_TX_FIFO_DATA_REQ)
+ && i2c_dev->msg_read
+ && i2c_dev->msg_buf_remaining)) {
+ dev_warn(i2c_dev->dev, "unexpected status\n");
+ i2c_dev->msg_err |= I2C_ERR_UNEXPECTED_STATUS;
+
+ if (!i2c_dev->irq_disabled) {
+ disable_irq_nosync(i2c_dev->irq);
+ i2c_dev->irq_disabled = 1;
+ }
+
complete(&i2c_dev->msg_complete);
goto err;
}
@@ -391,18 +414,45 @@ static irqreturn_t tegra_i2c_isr(int irq, void *dev_id)
if (i2c_dev->msg_transfer_complete && !i2c_dev->msg_buf_remaining)
complete(&i2c_dev->msg_complete);
+
i2c_writel(i2c_dev, status, I2C_INT_STATUS);
+
if (i2c_dev->is_dvc)
dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, DVC_STATUS);
+
return IRQ_HANDLED;
+
err:
+ dev_dbg(i2c_dev->dev, "reg: 0x%08x 0x%08x 0x%08x 0x%08x\n",
+ i2c_readl(i2c_dev, I2C_CNFG), i2c_readl(i2c_dev, I2C_STATUS),
+ i2c_readl(i2c_dev, I2C_INT_STATUS),
+ i2c_readl(i2c_dev, I2C_PACKET_TRANSFER_STATUS));
+
+ dev_dbg(i2c_dev->dev, "packet: 0x%08x %u 0x%08x\n",
+ i2c_dev->packet_header, i2c_dev->payload_size,
+ i2c_dev->io_header);
+
+ if (i2c_dev->msgs) {
+ struct i2c_msg *msgs = i2c_dev->msgs;
+ int i;
+
+ for (i = 0; i < i2c_dev->msgs_num; i++)
+ dev_dbg(i2c_dev->dev,
+ "msgs[%d] %c, addr=0x%04x, len=%d\n",
+ i, (msgs[i].flags & I2C_M_RD) ? 'R' : 'W',
+ msgs[i].addr, msgs[i].len);
+ }
+
/* An error occured, mask all interrupts */
tegra_i2c_mask_irq(i2c_dev, I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST |
I2C_INT_PACKET_XFER_COMPLETE | I2C_INT_TX_FIFO_DATA_REQ |
I2C_INT_RX_FIFO_DATA_REQ);
+
i2c_writel(i2c_dev, status, I2C_INT_STATUS);
+
if (i2c_dev->is_dvc)
dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, DVC_STATUS);
+
return IRQ_HANDLED;
}
@@ -410,7 +460,6 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_bus *i2c_bus,
struct i2c_msg *msg, int stop)
{
struct tegra_i2c_dev *i2c_dev = i2c_bus->dev;
- u32 packet_header;
u32 int_mask;
int ret;
@@ -427,26 +476,26 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_bus *i2c_bus,
i2c_dev->msg_read = (msg->flags & I2C_M_RD);
INIT_COMPLETION(i2c_dev->msg_complete);
- packet_header = (0 << PACKET_HEADER0_HEADER_SIZE_SHIFT) |
+ i2c_dev->packet_header = (0 << PACKET_HEADER0_HEADER_SIZE_SHIFT) |
PACKET_HEADER0_PROTOCOL_I2C |
(i2c_dev->cont_id << PACKET_HEADER0_CONT_ID_SHIFT) |
(1 << PACKET_HEADER0_PACKET_ID_SHIFT);
- i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO);
+ i2c_writel(i2c_dev, i2c_dev->packet_header, I2C_TX_FIFO);
- packet_header = msg->len - 1;
- i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO);
+ i2c_dev->payload_size = msg->len - 1;
+ i2c_writel(i2c_dev, i2c_dev->payload_size, I2C_TX_FIFO);
- packet_header = msg->addr << I2C_HEADER_SLAVE_ADDR_SHIFT;
- packet_header |= I2C_HEADER_IE_ENABLE;
+ i2c_dev->io_header = msg->addr << I2C_HEADER_SLAVE_ADDR_SHIFT;
+ i2c_dev->io_header |= I2C_HEADER_IE_ENABLE;
if (!stop)
- packet_header |= I2C_HEADER_REPEAT_START;
+ i2c_dev->io_header |= I2C_HEADER_REPEAT_START;
if (msg->flags & I2C_M_TEN)
- packet_header |= I2C_HEADER_10BIT_ADDR;
+ i2c_dev->io_header |= I2C_HEADER_10BIT_ADDR;
if (msg->flags & I2C_M_IGNORE_NAK)
- packet_header |= I2C_HEADER_CONT_ON_NAK;
+ i2c_dev->io_header |= I2C_HEADER_CONT_ON_NAK;
if (msg->flags & I2C_M_RD)
- packet_header |= I2C_HEADER_READ;
- i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO);
+ i2c_dev->io_header |= I2C_HEADER_READ;
+ i2c_writel(i2c_dev, i2c_dev->io_header, I2C_TX_FIFO);
if (!(msg->flags & I2C_M_RD))
tegra_i2c_fill_tx_fifo(i2c_dev);
@@ -459,17 +508,21 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_bus *i2c_bus,
tegra_i2c_unmask_irq(i2c_dev, int_mask);
pr_debug("unmasked irq: %02x\n", i2c_readl(i2c_dev, I2C_INT_MASK));
- ret = wait_for_completion_timeout(&i2c_dev->msg_complete, TEGRA_I2C_TIMEOUT);
+ ret = wait_for_completion_timeout(&i2c_dev->msg_complete,
+ TEGRA_I2C_TIMEOUT);
tegra_i2c_mask_irq(i2c_dev, int_mask);
if (WARN_ON(ret == 0)) {
- dev_err(i2c_dev->dev, "i2c transfer timed out\n");
+ dev_err(i2c_dev->dev,
+ "i2c transfer timed out, addr 0x%04x, data 0x%02x\n",
+ msg->addr, msg->buf[0]);
tegra_i2c_init(i2c_dev);
return -ETIMEDOUT;
}
- pr_debug("transfer complete: %d %d %d\n", ret, completion_done(&i2c_dev->msg_complete), i2c_dev->msg_err);
+ pr_debug("transfer complete: %d %d %d\n", ret,
+ completion_done(&i2c_dev->msg_complete), i2c_dev->msg_err);
if (likely(i2c_dev->msg_err == I2C_ERR_NONE))
return 0;
@@ -481,6 +534,9 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_bus *i2c_bus,
return -EREMOTEIO;
}
+ if (i2c_dev->msg_err & I2C_ERR_UNEXPECTED_STATUS)
+ return -EAGAIN;
+
return -EIO;
}
@@ -511,6 +567,9 @@ static int tegra_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],
i2c_dev->last_bus_clk = i2c_bus->bus_clk_rate;
}
+ i2c_dev->msgs = msgs;
+ i2c_dev->msgs_num = num;
+
clk_enable(i2c_dev->clk);
for (i = 0; i < num; i++) {
int stop = (i == (num - 1)) ? 1 : 0;
@@ -525,6 +584,9 @@ out:
rt_mutex_unlock(&i2c_dev->dev_lock);
+ i2c_dev->msgs = NULL;
+ i2c_dev->msgs_num = 0;
+
return ret;
}
@@ -619,6 +681,8 @@ static int tegra_i2c_probe(struct platform_device *pdev)
i2c_dev->cont_id = pdev->id;
i2c_dev->dev = &pdev->dev;
i2c_dev->last_bus_clk = plat->bus_clk_rate[0] ?: 100000;
+ i2c_dev->msgs = NULL;
+ i2c_dev->msgs_num = 0;
rt_mutex_init(&i2c_dev->dev_lock);
i2c_dev->is_dvc = plat->is_dvc;
@@ -655,6 +719,15 @@ static int tegra_i2c_probe(struct platform_device *pdev)
sizeof(i2c_bus->adapter.name));
i2c_bus->adapter.dev.parent = &pdev->dev;
i2c_bus->adapter.nr = plat->adapter_nr + i;
+
+ if (plat->retries)
+ i2c_bus->adapter.retries = plat->retries;
+ else
+ i2c_bus->adapter.retries = TEGRA_I2C_RETRIES;
+
+ if (plat->timeout)
+ i2c_bus->adapter.timeout = plat->timeout;
+
ret = i2c_add_numbered_adapter(&i2c_bus->adapter);
if (ret) {
dev_err(&pdev->dev, "Failed to add I2C adapter\n");
diff --git a/include/linux/i2c-tegra.h b/include/linux/i2c-tegra.h
index 6123502b8ddd..f92fe26c7535 100644
--- a/include/linux/i2c-tegra.h
+++ b/include/linux/i2c-tegra.h
@@ -29,6 +29,8 @@ struct tegra_i2c_platform_data {
int bus_mux_len[TEGRA_I2C_MAX_BUS];
unsigned long bus_clk_rate[TEGRA_I2C_MAX_BUS];
bool is_dvc;
+ int retries;
+ int timeout; /* in jiffies */
};
#endif /* _LINUX_I2C_TEGRA_H */