]> git.proxmox.com Git - mirror_ubuntu-focal-kernel.git/commitdiff
i2c-davinci: Fix signal handling bug
authorTroy Kisky <troy.kisky@boundarydevices.com>
Mon, 14 Jul 2008 20:38:21 +0000 (22:38 +0200)
committerJean Delvare <khali@mahadeva.delvare>
Mon, 14 Jul 2008 20:38:21 +0000 (22:38 +0200)
If wait_for_completion_interruptible_timeout exits due
to a signal, the i2c bus was locking up.

Signed-off-by: Troy Kisky <troy.kisky@boundarydevices.com>
Signed-off-by: Kevin Hilman <khilman@mvista.com>
Signed-off-by: Jean Delvare <khali@linux-fr.org>
drivers/i2c/busses/i2c-davinci.c

index c56f8fe4efe5408bf200b8d55ceeb5abb20a63b5..160857296952fffb3b55a61b074c7bc6d364218b 100644 (file)
@@ -85,6 +85,7 @@
 #define DAVINCI_I2C_MDR_MST    (1 << 10)
 #define DAVINCI_I2C_MDR_TRX    (1 << 9)
 #define DAVINCI_I2C_MDR_XA     (1 << 8)
+#define DAVINCI_I2C_MDR_RM     (1 << 7)
 #define DAVINCI_I2C_MDR_IRS    (1 << 5)
 
 #define DAVINCI_I2C_IMR_AAS    (1 << 6)
@@ -112,6 +113,7 @@ struct davinci_i2c_dev {
        u8                      *buf;
        size_t                  buf_len;
        int                     irq;
+       u8                      terminate;
        struct i2c_adapter      adapter;
 };
 
@@ -283,20 +285,34 @@ i2c_davinci_xfer_msg(struct i2c_adapter *adap, struct i2c_msg *msg, int stop)
                MOD_REG_BIT(w, DAVINCI_I2C_IMR_XRDY, 1);
        davinci_i2c_write_reg(dev, DAVINCI_I2C_IMR_REG, w);
 
+       dev->terminate = 0;
        /* write the data into mode register */
        davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, flag);
 
        r = wait_for_completion_interruptible_timeout(&dev->cmd_complete,
                                                      DAVINCI_I2C_TIMEOUT);
-       dev->buf_len = 0;
-       if (r < 0)
-               return r;
-
        if (r == 0) {
                dev_err(dev->dev, "controller timed out\n");
                i2c_davinci_init(dev);
+               dev->buf_len = 0;
                return -ETIMEDOUT;
        }
+       if (dev->buf_len) {
+               /* This should be 0 if all bytes were transferred
+                * or dev->cmd_err denotes an error.
+                * A signal may have aborted the transfer.
+                */
+               if (r >= 0) {
+                       dev_err(dev->dev, "abnormal termination buf_len=%i\n",
+                               dev->buf_len);
+                       r = -EREMOTEIO;
+               }
+               dev->terminate = 1;
+               wmb();
+               dev->buf_len = 0;
+       }
+       if (r < 0)
+               return r;
 
        /* no error */
        if (likely(!dev->cmd_err))
@@ -354,6 +370,27 @@ static u32 i2c_davinci_func(struct i2c_adapter *adap)
        return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
 }
 
+static void terminate_read(struct davinci_i2c_dev *dev)
+{
+       u16 w = davinci_i2c_read_reg(dev, DAVINCI_I2C_MDR_REG);
+       w |= DAVINCI_I2C_MDR_NACK;
+       davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, w);
+
+       /* Throw away data */
+       davinci_i2c_read_reg(dev, DAVINCI_I2C_DRR_REG);
+       if (!dev->terminate)
+               dev_err(dev->dev, "RDR IRQ while no data requested\n");
+}
+static void terminate_write(struct davinci_i2c_dev *dev)
+{
+       u16 w = davinci_i2c_read_reg(dev, DAVINCI_I2C_MDR_REG);
+       w |= DAVINCI_I2C_MDR_RM | DAVINCI_I2C_MDR_STP;
+       davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, w);
+
+       if (!dev->terminate)
+               dev_err(dev->dev, "TDR IRQ while no data to send\n");
+}
+
 /*
  * Interrupt service routine. This gets called whenever an I2C interrupt
  * occurs.
@@ -374,12 +411,15 @@ static irqreturn_t i2c_davinci_isr(int this_irq, void *dev_id)
 
                switch (stat) {
                case DAVINCI_I2C_IVR_AL:
+                       /* Arbitration lost, must retry */
                        dev->cmd_err |= DAVINCI_I2C_STR_AL;
+                       dev->buf_len = 0;
                        complete(&dev->cmd_complete);
                        break;
 
                case DAVINCI_I2C_IVR_NACK:
                        dev->cmd_err |= DAVINCI_I2C_STR_NACK;
+                       dev->buf_len = 0;
                        complete(&dev->cmd_complete);
                        break;
 
@@ -401,9 +441,10 @@ static irqreturn_t i2c_davinci_isr(int this_irq, void *dev_id)
                                davinci_i2c_write_reg(dev,
                                        DAVINCI_I2C_STR_REG,
                                        DAVINCI_I2C_IMR_RRDY);
-                       } else
-                               dev_err(dev->dev, "RDR IRQ while no "
-                                       "data requested\n");
+                       } else {
+                               /* signal can terminate transfer */
+                               terminate_read(dev);
+                       }
                        break;
 
                case DAVINCI_I2C_IVR_XRDY:
@@ -420,9 +461,10 @@ static irqreturn_t i2c_davinci_isr(int this_irq, void *dev_id)
                                davinci_i2c_write_reg(dev,
                                                      DAVINCI_I2C_IMR_REG,
                                                      w);
-                       } else
-                               dev_err(dev->dev, "TDR IRQ while no data to "
-                                       "send\n");
+                       } else {
+                               /* signal can terminate transfer */
+                               terminate_write(dev);
+                       }
                        break;
 
                case DAVINCI_I2C_IVR_SCD: