diff options
author | Alok Chauhan <alokc@nvidia.com> | 2011-07-07 14:30:39 +0530 |
---|---|---|
committer | Manish Tuteja <mtuteja@nvidia.com> | 2011-07-11 02:41:24 -0700 |
commit | 266d1b7397284505e55d06254b497cb32be07b69 (patch) | |
tree | 9668c914981f72e446f2e4c81bcd68a63306eeae /drivers | |
parent | 6887d313f33745a4870a5c58cdffab1aa7d6f593 (diff) |
i2c: tegra: Avoid duplicate write into Tx Fifo
Dvc I2C_DONE_INTR_EN interrupt bit is always enable into dvc
control register3.During normal transaction on dvc i2c bus
sometimes one transaction written two times in TX fifo buffer
because of triggered dvc interrupt.This is causing to corrupt
the next transaction header and send wrong address over dvc
i2c bus.To solve this issue dvc i2c interrupt has to disable
during filling of Tx fifo and enable after that.
Writing last packet into Tx Fifo is generating i2c interrupt
immediately if IE bit is enable in Packet header. Data shared
between isr and normal thread are not in sync. So alway update
these data before writing into Tx fifo.
Updated the following things in code:
(1) Add the code to mask/unmask I2C_DONE_INTR_EN into dvc control reg3
(2) Always updates the i2c driver required field structure data before
writing into Tx Fifo register.
(3) Add the code to handle tx fifo overflow condition also.
(4) Put delay before resetting the controller
BUG 839528
Change-Id: I7780411b474a20f985e1f7993e5ccccbab619bbc
Reviewed-on: http://git-master/r/39985
Reviewed-by: Alok Chauhan <alokc@nvidia.com>
Tested-by: Alok Chauhan <alokc@nvidia.com>
Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/i2c/busses/i2c-tegra.c | 79 |
1 files changed, 67 insertions, 12 deletions
diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index 0a6b21f8f077..d50c5e5db862 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -19,6 +19,7 @@ #include <linux/init.h> #include <linux/platform_device.h> #include <linux/clk.h> +#include <linux/err.h> #include <linux/i2c.h> #include <linux/io.h> #include <linux/interrupt.h> @@ -44,8 +45,8 @@ #define I2C_SL_CNFG 0x020 #define I2C_SL_CNFG_NACK (1<<1) #define I2C_SL_CNFG_NEWSL (1<<2) -#define I2C_SL_ADDR1 0x02c -#define I2C_SL_ADDR2 0x030 +#define I2C_SL_ADDR1 0x02c +#define I2C_SL_ADDR2 0x030 #define I2C_TX_FIFO 0x050 #define I2C_RX_FIFO 0x054 #define I2C_PACKET_TRANSFER_STATUS 0x058 @@ -136,6 +137,7 @@ struct tegra_i2c_dev { int msg_read; int msg_transfer_complete; struct i2c_msg *msgs; + int msg_add; int msgs_num; bool is_suspended; int bus_count; @@ -156,6 +158,20 @@ static u32 dvc_readl(struct tegra_i2c_dev *i2c_dev, unsigned long reg) return readl(i2c_dev->base + reg); } +static void dvc_i2c_mask_irq(struct tegra_i2c_dev *i2c_dev, u32 mask) +{ + u32 int_mask = dvc_readl(i2c_dev, DVC_CTRL_REG3); + int_mask &= ~mask; + dvc_writel(i2c_dev, int_mask, DVC_CTRL_REG3); +} + +static void dvc_i2c_unmask_irq(struct tegra_i2c_dev *i2c_dev, u32 mask) +{ + u32 int_mask = dvc_readl(i2c_dev, DVC_CTRL_REG3); + int_mask |= mask; + dvc_writel(i2c_dev, int_mask, DVC_CTRL_REG3); +} + /* i2c_writel and i2c_readl will offset the register if necessary to talk * to the I2C block inside the DVC block */ @@ -272,10 +288,15 @@ static int tegra_i2c_fill_tx_fifo(struct tegra_i2c_dev *i2c_dev) for (word = 0; word < words_to_transfer; word++) { val = get_unaligned_le32(buf); - i2c_writel(i2c_dev, val, I2C_TX_FIFO); + + /* Update the field before writing into Tx Fifo */ buf += BYTES_PER_FIFO_WORD; buf_remaining -= BYTES_PER_FIFO_WORD; tx_fifo_avail--; + i2c_dev->msg_buf_remaining = buf_remaining; + i2c_dev->msg_buf = buf; + + i2c_writel(i2c_dev, val, I2C_TX_FIFO); } if (tx_fifo_avail > 0 && buf_remaining > 0) { @@ -285,9 +306,14 @@ static int tegra_i2c_fill_tx_fifo(struct tegra_i2c_dev *i2c_dev) val = 0; for (byte = 0; byte < bytes_to_transfer; byte++) val |= (*buf++) << (byte * 8); - i2c_writel(i2c_dev, val, I2C_TX_FIFO); + + /* Update the field before writing into Tx Fifo */ buf_remaining -= bytes_to_transfer; tx_fifo_avail--; + i2c_dev->msg_buf_remaining = buf_remaining; + i2c_dev->msg_buf = buf; + + i2c_writel(i2c_dev, val, I2C_TX_FIFO); } BUG_ON(tx_fifo_avail > 0 && buf_remaining > 0); i2c_dev->msg_buf_remaining = buf_remaining; @@ -306,7 +332,6 @@ static void tegra_dvc_init(struct tegra_i2c_dev *i2c_dev) u32 val = 0; val = dvc_readl(i2c_dev, DVC_CTRL_REG3); val |= DVC_CTRL_REG3_SW_PROG; - val |= DVC_CTRL_REG3_I2C_DONE_INTR_EN; dvc_writel(i2c_dev, val, DVC_CTRL_REG3); val = dvc_readl(i2c_dev, DVC_CTRL_REG1); @@ -335,6 +360,10 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) clk_enable(i2c_dev->clk); + /* Interrupt generated before sending stop signal so + * wait for some time so that stop signal can be send proerly */ + mdelay(1); + tegra_periph_reset_assert(i2c_dev->clk); udelay(2); tegra_periph_reset_deassert(i2c_dev->clk); @@ -370,13 +399,14 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) static irqreturn_t tegra_i2c_isr(int irq, void *dev_id) { u32 status; - const u32 status_err = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST; + const u32 status_err = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST | I2C_INT_TX_FIFO_OVERFLOW; struct tegra_i2c_dev *i2c_dev = dev_id; status = i2c_readl(i2c_dev, I2C_INT_STATUS); if (status == 0) { - dev_warn(i2c_dev->dev, "unknown interrupt\n"); + dev_err(i2c_dev->dev, "unknown interrupt Add 0x%02x\n", + i2c_dev->msg_add); i2c_dev->msg_err |= I2C_ERR_UNKNOWN_INTERRUPT; if (!i2c_dev->irq_disabled) { @@ -389,16 +419,30 @@ static irqreturn_t tegra_i2c_isr(int irq, void *dev_id) } if (unlikely(status & status_err)) { + dev_err(i2c_dev->dev, "I2c error status 0x%08x\n", status); if (status & I2C_INT_NO_ACK) { i2c_dev->msg_err |= I2C_ERR_NO_ACK; - dev_warn(i2c_dev->dev, "no acknowledge\n"); + dev_err(i2c_dev->dev, "no acknowledge from address" + " 0x%x\n", i2c_dev->msg_add); + dev_err(i2c_dev->dev, "Packet status 0x%08x\n", + i2c_readl(i2c_dev, I2C_PACKET_TRANSFER_STATUS)); } if (status & I2C_INT_ARBITRATION_LOST) { i2c_dev->msg_err |= I2C_ERR_ARBITRATION_LOST; - dev_warn(i2c_dev->dev, "arbitration lost\n"); + dev_err(i2c_dev->dev, "arbitration lost during " + " communicate to add 0x%x\n", i2c_dev->msg_add); + dev_err(i2c_dev->dev, "Packet status 0x%08x\n", + i2c_readl(i2c_dev, I2C_PACKET_TRANSFER_STATUS)); } + if (status & I2C_INT_TX_FIFO_OVERFLOW) { + i2c_dev->msg_err |= I2C_INT_TX_FIFO_OVERFLOW; + dev_warn(i2c_dev->dev, "Tx fifo overflow during " + " communicate to add 0x%x\n", i2c_dev->msg_add); + dev_warn(i2c_dev->dev, "Packet status 0x%08x\n", + i2c_readl(i2c_dev, I2C_PACKET_TRANSFER_STATUS)); + } complete(&i2c_dev->msg_complete); goto err; } @@ -407,7 +451,7 @@ static irqreturn_t tegra_i2c_isr(int irq, void *dev_id) && (status == I2C_INT_TX_FIFO_DATA_REQ) && i2c_dev->msg_read && i2c_dev->msg_buf_remaining)) { - dev_warn(i2c_dev->dev, "unexpected status\n"); + dev_err(i2c_dev->dev, "unexpected status\n"); i2c_dev->msg_err |= I2C_ERR_UNEXPECTED_STATUS; if (!i2c_dev->irq_disabled) { @@ -470,10 +514,14 @@ err: /* 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_INT_RX_FIFO_DATA_REQ | I2C_INT_TX_FIFO_OVERFLOW); i2c_writel(i2c_dev, status, I2C_INT_STATUS); + /* An error occured, mask dvc interrupt */ + if (i2c_dev->is_dvc) + dvc_i2c_mask_irq(i2c_dev, DVC_CTRL_REG3_I2C_DONE_INTR_EN); + if (i2c_dev->is_dvc) dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, DVC_STATUS); @@ -499,6 +547,7 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_bus *i2c_bus, i2c_dev->msg_transfer_complete = 0; i2c_dev->msg_read = (msg->flags & I2C_M_RD); INIT_COMPLETION(i2c_dev->msg_complete); + i2c_dev->msg_add = msg->addr; i2c_dev->packet_header = (0 << PACKET_HEADER0_HEADER_SIZE_SHIFT) | PACKET_HEADER0_PROTOCOL_I2C | @@ -524,7 +573,10 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_bus *i2c_bus, if (!(msg->flags & I2C_M_RD)) tegra_i2c_fill_tx_fifo(i2c_dev); - int_mask = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST; + if (i2c_dev->is_dvc) + dvc_i2c_unmask_irq(i2c_dev, DVC_CTRL_REG3_I2C_DONE_INTR_EN); + + int_mask = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST | I2C_INT_TX_FIFO_OVERFLOW; if (msg->flags & I2C_M_RD) int_mask |= I2C_INT_RX_FIFO_DATA_REQ; else if (i2c_dev->msg_buf_remaining) @@ -536,6 +588,9 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_bus *i2c_bus, TEGRA_I2C_TIMEOUT); tegra_i2c_mask_irq(i2c_dev, int_mask); + if (i2c_dev->is_dvc) + dvc_i2c_mask_irq(i2c_dev, DVC_CTRL_REG3_I2C_DONE_INTR_EN); + if (WARN_ON(ret == 0)) { dev_err(i2c_dev->dev, "i2c transfer timed out, addr 0x%04x, data 0x%02x\n", |