Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

i2c: tegra: recover from spurious interrupt storm

Re-init the I2C controller when an IRQ arrives with no
I2C_INT_STATUS bits set to indicate why the interrupt was sent.
Storms of such mystery interrupts are infrequently seen.

Dump some more status when these interrupts arrive. Set an error
for the current request and wake up the requester (rather than
timing out the request or possibly silently ignoring the interrupts).

If the I2C block is inside the DVC, also ACK the DVC I2C transfer
done interrupt in the ISR error return path, as is done for the
normal return path.

Signed-off-by: Todd Poynor <toddpoynor@google.com>
[swarren: Fix minor checkpatch whitespace issue, commit tag]
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Signed-off-by: Ben Dooks <ben-linux@fluff.org>

authored by

Todd Poynor and committed by
Ben Dooks
cb63c62d 65a1a0ac

+24 -2
+24 -2
drivers/i2c/busses/i2c-tegra.c
··· 37 37 #define I2C_CNFG 0x000 38 38 #define I2C_CNFG_PACKET_MODE_EN (1<<10) 39 39 #define I2C_CNFG_NEW_MASTER_FSM (1<<11) 40 + #define I2C_STATUS 0x01C 40 41 #define I2C_SL_CNFG 0x020 41 42 #define I2C_SL_CNFG_NEWSL (1<<2) 42 43 #define I2C_SL_ADDR1 0x02c ··· 78 77 #define I2C_ERR_NONE 0x00 79 78 #define I2C_ERR_NO_ACK 0x01 80 79 #define I2C_ERR_ARBITRATION_LOST 0x02 80 + #define I2C_ERR_UNKNOWN_INTERRUPT 0x04 81 81 82 82 #define PACKET_HEADER0_HEADER_SIZE_SHIFT 28 83 83 #define PACKET_HEADER0_PACKET_ID_SHIFT 16 ··· 123 121 void __iomem *base; 124 122 int cont_id; 125 123 int irq; 124 + bool irq_disabled; 126 125 int is_dvc; 127 126 struct completion msg_complete; 128 127 int msg_err; ··· 346 343 err = -ETIMEDOUT; 347 344 348 345 clk_disable(i2c_dev->clk); 346 + 347 + if (i2c_dev->irq_disabled) { 348 + i2c_dev->irq_disabled = 0; 349 + enable_irq(i2c_dev->irq); 350 + } 351 + 349 352 return err; 350 353 } 351 354 ··· 364 355 status = i2c_readl(i2c_dev, I2C_INT_STATUS); 365 356 366 357 if (status == 0) { 367 - dev_warn(i2c_dev->dev, "interrupt with no status\n"); 368 - return IRQ_NONE; 358 + dev_warn(i2c_dev->dev, "irq status 0 %08x %08x %08x\n", 359 + i2c_readl(i2c_dev, I2C_PACKET_TRANSFER_STATUS), 360 + i2c_readl(i2c_dev, I2C_STATUS), 361 + i2c_readl(i2c_dev, I2C_CNFG)); 362 + i2c_dev->msg_err |= I2C_ERR_UNKNOWN_INTERRUPT; 363 + 364 + if (!i2c_dev->irq_disabled) { 365 + disable_irq_nosync(i2c_dev->irq); 366 + i2c_dev->irq_disabled = 1; 367 + } 368 + 369 + complete(&i2c_dev->msg_complete); 370 + goto err; 369 371 } 370 372 371 373 if (unlikely(status & status_err)) { ··· 416 396 I2C_INT_PACKET_XFER_COMPLETE | I2C_INT_TX_FIFO_DATA_REQ | 417 397 I2C_INT_RX_FIFO_DATA_REQ); 418 398 i2c_writel(i2c_dev, status, I2C_INT_STATUS); 399 + if (i2c_dev->is_dvc) 400 + dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, DVC_STATUS); 419 401 return IRQ_HANDLED; 420 402 } 421 403