| 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; |
| } |