diff options
author | Jin Park <jinyoungp@nvidia.com> | 2011-01-19 14:41:42 +0900 |
---|---|---|
committer | Niket Sirsi <nsirsi@nvidia.com> | 2011-01-28 19:36:49 -0800 |
commit | 6bab778a4b0caf58e464847c827d988a566bd434 (patch) | |
tree | 1cb945fbcf00f89cada57f0fa618399a12f1009f | |
parent | 8197cb15eaf0640d13c816926f65972ca6cbba84 (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.c | 121 | ||||
-rw-r--r-- | include/linux/i2c-tegra.h | 2 |
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 */ |