blob: 4f259d0417d460ed0917e08b2763decd12ad5ee1 [file] [log] [blame]
From 605c0d7db526bd4dd6e8efd0dcddbf35b62bfb9e Mon Sep 17 00:00:00 2001
From: Christian Ruppert <christian.ruppert@abilis.com>
Date: Fri, 7 Jun 2013 10:51:23 +0200
Subject: i2c: designware: fix race between subsequent xfers
The designware block is not always properly disabled in the case of
transfer errors. Interrupts from aborted transfers might be handled
after the data structures for the following transfer are initialised but
before the hardware is set up. This can corrupt the data structures to
the point that the system is stuck in an infinite interrupt loop (where
FIFOs are never emptied because dev->msg_read_idx == dev->msgs_num).
This patch cleanly disables the designware-i2c hardware at the end of
every transfer, be it successful or not.
Signed-off-by: Christian Ruppert <christian.ruppert@abilis.com>
[wsa: extended the comment]
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
(cherry picked from commit 38d7fadef4973bb94e36897fcb6bb6a12fdd10c9)
Signed-off-by: Darren Hart <dvhart@linux.intel.com>
---
drivers/i2c/busses/i2c-designware-core.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
--- a/drivers/i2c/busses/i2c-designware-core.c
+++ b/drivers/i2c/busses/i2c-designware-core.c
@@ -586,11 +586,21 @@ i2c_dw_xfer(struct i2c_adapter *adap, st
ret = wait_for_completion_timeout(&dev->cmd_complete, HZ);
if (ret == 0) {
dev_err(dev->dev, "controller timed out\n");
+ /* i2c_dw_init implicitly disables the adapter */
i2c_dw_init(dev);
ret = -ETIMEDOUT;
goto done;
}
+ /*
+ * We must disable the adapter before unlocking the &dev->lock mutex
+ * below. Otherwise the hardware might continue generating interrupts
+ * which in turn causes a race condition with the following transfer.
+ * Needs some more investigation if the additional interrupts are
+ * a hardware bug or this driver doesn't handle them correctly yet.
+ */
+ __i2c_dw_enable(dev, false);
+
if (dev->msg_err) {
ret = dev->msg_err;
goto done;
@@ -598,8 +608,6 @@ i2c_dw_xfer(struct i2c_adapter *adap, st
/* no error */
if (likely(!dev->cmd_err)) {
- /* Disable the adapter */
- __i2c_dw_enable(dev, false);
ret = num;
goto done;
}