aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/i2c
diff options
context:
space:
mode:
authorPaul Walmsley <paul@pwsan.com>2008-11-21 16:39:45 -0500
committerTony Lindgren <tony@atomide.com>2008-11-21 16:39:45 -0500
commit0cbbcffdf5f30ef60d918549014684eada4f5b3f (patch)
tree9fb736ff6863927629172179f756d014fe8273e8 /drivers/i2c
parentb7af349b175af45f9d87b3bf3f0a221e1831ed39 (diff)
i2c-omap: Close suspected race between omap_i2c_idle() and omap_i2c_isr()
omap_i2c_idle() sets an internal flag, "dev->idle", instructing its ISR to decline interrupts. It sets this flag before it actually masks the interrupts on the I2C controller. This is problematic, since an I2C interrupt could arrive after dev->idle is set, but before the interrupt source is masked. When this happens, Linux disables the I2C controller's IRQ, causing all future transactions on the bus to fail. Symptoms, happening on about 7% of boots: irq 56: nobody cared (try booting with the "irqpoll" option) <warning traceback here> Disabling IRQ #56 i2c_omap i2c_omap.1: controller timed out In omap_i2c_idle(), this patch sets dev->idle only after the interrupt mask write to the I2C controller has left the ARM write buffer. That's probably the major offender. For additional prophylaxis, in omap_i2c_unidle(), the patch clears the dev->idle flag before interrupts are enabled, rather than afterwards. The patch has survived twenty-two reboots on the 3430SDP here without wedging I2C1. Not absolutely dispositive, but promising! Signed-off-by: Paul Walmsley <paul@pwsan.com> Signed-off-by: Tony Lindgren <tony@atomide.com>
Diffstat (limited to 'drivers/i2c')
-rw-r--r--drivers/i2c/busses/i2c-omap.c12
1 files changed, 8 insertions, 4 deletions
diff --git a/drivers/i2c/busses/i2c-omap.c b/drivers/i2c/busses/i2c-omap.c
index 17476ecd9782..5ca0e0010a0e 100644
--- a/drivers/i2c/busses/i2c-omap.c
+++ b/drivers/i2c/busses/i2c-omap.c
@@ -181,22 +181,26 @@ static void omap_i2c_unidle(struct omap_i2c_dev *dev)
181 if (dev->iclk != NULL) 181 if (dev->iclk != NULL)
182 clk_enable(dev->iclk); 182 clk_enable(dev->iclk);
183 clk_enable(dev->fclk); 183 clk_enable(dev->fclk);
184 dev->idle = 0;
184 if (dev->iestate) 185 if (dev->iestate)
185 omap_i2c_write_reg(dev, OMAP_I2C_IE_REG, dev->iestate); 186 omap_i2c_write_reg(dev, OMAP_I2C_IE_REG, dev->iestate);
186 dev->idle = 0;
187} 187}
188 188
189static void omap_i2c_idle(struct omap_i2c_dev *dev) 189static void omap_i2c_idle(struct omap_i2c_dev *dev)
190{ 190{
191 u16 iv; 191 u16 iv;
192 192
193 dev->idle = 1;
194 dev->iestate = omap_i2c_read_reg(dev, OMAP_I2C_IE_REG); 193 dev->iestate = omap_i2c_read_reg(dev, OMAP_I2C_IE_REG);
195 omap_i2c_write_reg(dev, OMAP_I2C_IE_REG, 0); 194 omap_i2c_write_reg(dev, OMAP_I2C_IE_REG, 0);
196 if (dev->rev1) 195 if (dev->rev1) {
197 iv = omap_i2c_read_reg(dev, OMAP_I2C_IV_REG); /* Read clears */ 196 iv = omap_i2c_read_reg(dev, OMAP_I2C_IV_REG); /* Read clears */
198 else 197 } else {
199 omap_i2c_write_reg(dev, OMAP_I2C_STAT_REG, dev->iestate); 198 omap_i2c_write_reg(dev, OMAP_I2C_STAT_REG, dev->iestate);
199
200 /* Flush posted write before the dev->idle store occurs */
201 omap_i2c_read_reg(dev, OMAP_I2C_STAT_REG);
202 }
203 dev->idle = 1;
200 clk_disable(dev->fclk); 204 clk_disable(dev->fclk);
201 if (dev->iclk != NULL) 205 if (dev->iclk != NULL)
202 clk_disable(dev->iclk); 206 clk_disable(dev->iclk);