i2c: bcm2835: Handle untimely DONE signal
authorPhil Elwell <phil@raspberrypi.com>
Fri, 5 Feb 2021 09:20:31 +0000 (09:20 +0000)
committerPhil Elwell <phil@raspberrypi.com>
Fri, 5 Feb 2021 10:26:16 +0000 (10:26 +0000)
Under certain circumstance the DONE flag can appear to be set early.
Fortunately the TA flag is often still set at that time, and it can be
used as an indication that DONE is invalid.

Handle the other cases - when TA is not set but not all data is
available - by silently accepting it and hoping for a second DONE.

See: https://github.com/raspberrypi/linux/issues/3064

Signed-off-by: Phil Elwell <phil@raspberrypi.com>
drivers/i2c/busses/i2c-bcm2835.c

index 5b2589b..90ca593 100644 (file)
@@ -373,6 +373,10 @@ static irqreturn_t bcm2835_i2c_isr(int this_irq, void *data)
        u32 val, err;
 
        val = bcm2835_i2c_readl(i2c_dev, BCM2835_I2C_S);
+       bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_S,
+                          BCM2835_I2C_S_CLKT |
+                          BCM2835_I2C_S_ERR |
+                          BCM2835_I2C_S_DONE);
        bcm2835_debug_add(i2c_dev, val);
 
        err = val & (BCM2835_I2C_S_CLKT | BCM2835_I2C_S_ERR);
@@ -382,6 +386,9 @@ static irqreturn_t bcm2835_i2c_isr(int this_irq, void *data)
        }
 
        if (val & BCM2835_I2C_S_DONE) {
+               if (val & BCM2835_I2C_S_TA)
+                       return IRQ_HANDLED;
+
                if (!i2c_dev->curr_msg) {
                        dev_err(i2c_dev->dev, "Got unexpected interrupt (from firmware?)\n");
                } else if (i2c_dev->curr_msg->flags & I2C_M_RD) {
@@ -389,7 +396,10 @@ static irqreturn_t bcm2835_i2c_isr(int this_irq, void *data)
                        val = bcm2835_i2c_readl(i2c_dev, BCM2835_I2C_S);
                }
 
-               if ((val & BCM2835_I2C_S_RXD) || i2c_dev->msg_buf_remaining)
+               if (i2c_dev->msg_buf_remaining)
+                       return IRQ_HANDLED;
+
+               if (val & BCM2835_I2C_S_RXD)
                        i2c_dev->msg_err = BCM2835_I2C_S_LEN;
                else
                        i2c_dev->msg_err = 0;
@@ -426,8 +436,6 @@ static irqreturn_t bcm2835_i2c_isr(int this_irq, void *data)
 
 complete:
        bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_C, BCM2835_I2C_C_CLEAR);
-       bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_S, BCM2835_I2C_S_CLKT |
-                          BCM2835_I2C_S_ERR | BCM2835_I2C_S_DONE);
        complete(&i2c_dev->completion);
 
        return IRQ_HANDLED;