diff options
| author | Mohsan Habibi <mohsan@motorola.com> | 2014-06-26 02:38:27 -0400 |
|---|---|---|
| committer | James Wylder <jwylder@motorola.com> | 2014-06-27 20:53:05 +0000 |
| commit | 2f3dcb081e91c074580fcd3da1ca1495634a8378 (patch) | |
| tree | 8dbfdc56d917552fc94a969c778e9a66f4406544 | |
| parent | 897f938e4fa27d760203591122375df39003b763 (diff) | |
| download | olio-linux-3.10-2f3dcb081e91c074580fcd3da1ca1495634a8378.tar.xz olio-linux-3.10-2f3dcb081e91c074580fcd3da1ca1495634a8378.zip | |
IKXCLOCK-2530 i2c: omap: enable irq only during transfer
Interrupts should be enabled only during a transfer for several
reasons:
- During bus busy recovery (omap_bus_clear), the recovery mechanism
might generate spurious and unexpected interrupts, which won't be
handled properly.
- Inside omap_i2c_xfer, there is a race condition where an interrupt
could trigger while calling pm_runtime_put, which would schedule
the ISR thread, but the clocks would be disabled before the thread
gets a chance to run.
- Prevent a misbehaving slave from causing unexpected interrupts if
it starts driving the lines in an incorrect way.
Additionally, move the recovery procedure to its own bus_clear function
for clarity, and also perform a reset after the recovery to ensure the
module is in a defined state.
Change-Id: I9b78884e7ad5407c2c09bc6af0fa982d7e7b2158
Signed-off-by: Mohsan Habibi <mohsan@motorola.com>
| -rw-r--r-- | drivers/i2c/busses/i2c-omap.c | 125 |
1 files changed, 83 insertions, 42 deletions
diff --git a/drivers/i2c/busses/i2c-omap.c b/drivers/i2c/busses/i2c-omap.c index ca702aa7f81..ef08e8b72bf 100644 --- a/drivers/i2c/busses/i2c-omap.c +++ b/drivers/i2c/busses/i2c-omap.c @@ -108,6 +108,7 @@ enum { #define OMAP_I2C_STAT_ARDY (1 << 2) /* Register access ready */ #define OMAP_I2C_STAT_NACK (1 << 1) /* No ack interrupt enable */ #define OMAP_I2C_STAT_AL (1 << 0) /* Arbitration lost int ena */ +#define OMAP_I2C_STAT_CLEAR_ALL 0x6377 /* Mask to clear stat bits */ /* I2C WE wakeup enable register */ #define OMAP_I2C_WE_XDR_WE (1 << 14) /* TX drain wakup */ @@ -153,7 +154,8 @@ enum { #define OMAP_I2C_SYSTEST_ST_EN (1 << 15) /* System test enable */ #define OMAP_I2C_SYSTEST_FREE (1 << 14) /* Free running mode */ #define OMAP_I2C_SYSTEST_TMODE_MASK (3 << 12) /* Test mode select */ -#define OMAP_I2C_SYSTEST_TMODE_SHIFT (12) /* Test mode select */ +#define OMAP_I2C_SYSTEST_TMODE_TEST (2 << 12) /* Test mode select */ +#define OMAP_I2C_SYSTEST_TMODE_LOOP (3 << 12) /* Test mode select */ #define OMAP_I2C_SYSTEST_SCL_I (1 << 3) /* SCL line sense in */ #define OMAP_I2C_SYSTEST_SCL_O (1 << 2) /* SCL line drive out */ #define OMAP_I2C_SYSTEST_SDA_I (1 << 1) /* SDA line sense in */ @@ -272,6 +274,38 @@ static inline u16 omap_i2c_read_reg(struct omap_i2c_dev *i2c_dev, int reg) (i2c_dev->regs[reg] << i2c_dev->reg_shift)); } +/* + * Disable all interrupts in i2c module + */ +static inline void omap_i2c_disable_interrupts(struct omap_i2c_dev *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + + omap_i2c_write_reg(dev, OMAP_I2C_IE_REG, 0); + + if (dev->rev < OMAP_I2C_OMAP1_REV_2) { + omap_i2c_read_reg(dev, OMAP_I2C_IV_REG); /* Read clears */ + } else { + omap_i2c_write_reg(dev, OMAP_I2C_STAT_REG, + OMAP_I2C_STAT_CLEAR_ALL); + + /* Flush posted write */ + omap_i2c_read_reg(dev, OMAP_I2C_STAT_REG); + } + + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* + * Enable interrupts in i2c module + */ +static inline void omap_i2c_enable_interrupts(struct omap_i2c_dev *dev) +{ + omap_i2c_write_reg(dev, OMAP_I2C_IE_REG, dev->iestate); +} + static void __omap_i2c_init(struct omap_i2c_dev *dev) { @@ -288,13 +322,6 @@ static void __omap_i2c_init(struct omap_i2c_dev *dev) /* Take the I2C module out of reset: */ omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, OMAP_I2C_CON_EN); - - /* - * Don't write to this register if the IE state is 0 as it can - * cause deadlock. - */ - if (dev->iestate) - omap_i2c_write_reg(dev, OMAP_I2C_IE_REG, dev->iestate); } static int omap_i2c_reset(struct omap_i2c_dev *dev) @@ -618,6 +645,33 @@ static int omap_i2c_xfer_msg(struct i2c_adapter *adap, return -EIO; } +/* + * Bus Clear + * Note: interrupts must be disabled before calling this function, otherwise + * unexpected interrupts could occur while operating in test mode. + */ +static int omap_i2c_bus_clear(struct omap_i2c_dev *dev) +{ + int r; + + /* + * Per the I2C specification, if we are stuck in a bus busy state + * we can attempt a bus clear to try and recover the bus by sending + * at least 9 clock pulses on SCL. Put the I2C in a test mode so it + * will output a continuous clock on SCL. + */ + omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, OMAP_I2C_CON_EN); + omap_i2c_write_reg(dev, OMAP_I2C_SYSTEST_REG, + (OMAP_I2C_SYSTEST_ST_EN | + OMAP_I2C_SYSTEST_TMODE_TEST)); + usleep_range(1000, 2000); + omap_i2c_write_reg(dev, OMAP_I2C_SYSTEST_REG, 0); + omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, 0); + omap_i2c_reset(dev); + omap_i2c_init(dev); + r = omap_i2c_wait_for_bb(dev); + return r; +} /* * Prepare controller for a transaction and call omap_i2c_xfer_msg @@ -629,24 +683,15 @@ omap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) struct omap_i2c_dev *dev = i2c_get_adapdata(adap); int i; int r; - u16 val; r = pm_runtime_get_sync(dev->dev); if (IS_ERR_VALUE(r)) goto out; r = omap_i2c_wait_for_bb(dev); - /* If timeout, try to again check after soft reset of I2C block */ + /* If timeout, try to again check after attempting to clear the bus */ if (WARN_ON(r == -ETIMEDOUT)) { - /* Provide a permanent clock to recover the peripheral */ - val = omap_i2c_read_reg(dev, OMAP_I2C_SYSTEST_REG); - val |= (OMAP_I2C_SYSTEST_ST_EN | - OMAP_I2C_SYSTEST_FREE | - (2 << OMAP_I2C_SYSTEST_TMODE_SHIFT)); - omap_i2c_write_reg(dev, OMAP_I2C_SYSTEST_REG, val); - usleep_range(1000, 2000); - omap_i2c_init(dev); - r = omap_i2c_wait_for_bb(dev); + r = omap_i2c_bus_clear(dev); if (r < 0) { dev_err(dev->dev, "Unable to recover i2c bus from bb\n"); goto out; @@ -661,12 +706,30 @@ omap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) */ pm_qos_update_request(&dev->pm_qos_request, dev->latency); + /* A misbehaving slave could potentially put the I2C lines in a state + * that triggers an unexpected interrupt, so lets enable interrupt only + * when needed which is when the master initiates a transfer. Ideally, + * the local omap_i2c_runtime functions would be masking the interrupts + * directly in the i2c registers, but there exists a race condition + * where an interrupt could trigger just before the interrupts are + * masked inside omap_i2c_runtime_suspend, which would schedule the + * ISR thread to run. Later when the thread is scheduled to run, a data + * abort would result since clocks have already been disabled. + */ + omap_i2c_enable_interrupts(dev); + for (i = 0; i < num; i++) { r = omap_i2c_xfer_msg(adap, &msgs[i], (i == (num - 1))); if (r != 0) break; } + /* Disable interrupts when transaction completes */ + omap_i2c_disable_interrupts(dev); + + /* Make sure any pending interrupts get serviced completely */ + synchronize_irq(dev->irq); + if (r == 0) r = num; @@ -1294,27 +1357,6 @@ static int omap_i2c_remove(struct platform_device *pdev) #ifdef CONFIG_PM #ifdef CONFIG_PM_RUNTIME -static int omap_i2c_runtime_suspend(struct device *dev) -{ - struct platform_device *pdev = to_platform_device(dev); - struct omap_i2c_dev *_dev = platform_get_drvdata(pdev); - - _dev->iestate = omap_i2c_read_reg(_dev, OMAP_I2C_IE_REG); - - omap_i2c_write_reg(_dev, OMAP_I2C_IE_REG, 0); - - if (_dev->rev < OMAP_I2C_OMAP1_REV_2) { - omap_i2c_read_reg(_dev, OMAP_I2C_IV_REG); /* Read clears */ - } else { - omap_i2c_write_reg(_dev, OMAP_I2C_STAT_REG, _dev->iestate); - - /* Flush posted write */ - omap_i2c_read_reg(_dev, OMAP_I2C_STAT_REG); - } - - return 0; -} - static int omap_i2c_runtime_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); @@ -1330,8 +1372,7 @@ static int omap_i2c_runtime_resume(struct device *dev) #endif /* CONFIG_PM_RUNTIME */ static struct dev_pm_ops omap_i2c_pm_ops = { - SET_RUNTIME_PM_OPS(omap_i2c_runtime_suspend, - omap_i2c_runtime_resume, NULL) + SET_RUNTIME_PM_OPS(NULL, omap_i2c_runtime_resume, NULL) }; #define OMAP_I2C_PM_OPS (&omap_i2c_pm_ops) #else |