summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorAlok Chauhan <alokc@nvidia.com>2011-07-07 15:39:19 +0530
committerVarun Colbert <vcolbert@nvidia.com>2011-07-11 17:07:01 -0700
commit141524a2a2cdf3fc7c2e872d0bf8b93b5f86e433 (patch)
treed5d824431b587a039e1d87bee17efc56b73b7e5b /drivers
parent72f29850f1f356e58bb3282b0b069443bbfc6bc9 (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: I8cf297a03c0c4e98806ab6eaf46bdf83d11e031e Reviewed-on: http://git-master/r/39997 Reviewed-by: Varun Colbert <vcolbert@nvidia.com> Tested-by: Varun Colbert <vcolbert@nvidia.com>
Diffstat (limited to 'drivers')
-rwxr-xr-x[-rw-r--r--]drivers/i2c/busses/i2c-tegra.c41
1 files changed, 37 insertions, 4 deletions
diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c
index f9a14a75563c..3064fc4863ad 100644..100755
--- a/drivers/i2c/busses/i2c-tegra.c
+++ b/drivers/i2c/busses/i2c-tegra.c
@@ -166,6 +166,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
*/
@@ -282,10 +296,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) {
@@ -295,9 +314,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;
@@ -316,7 +340,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);
@@ -348,7 +371,7 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev)
/* Interrupt generated before sending stop signal so
* wait for some time so that stop signal can be send proerly */
- udelay(100);
+ mdelay(1);
tegra_periph_reset_assert(i2c_dev->clk);
udelay(2);
@@ -511,6 +534,10 @@ err:
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);
@@ -565,6 +592,9 @@ 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);
+ 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;
@@ -577,6 +607,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",