i2c: wmt: fix a bug when thread blocked
authorHans Hu <hanshu-oc@zhaoxin.com>
Mon, 8 Apr 2024 02:54:46 +0000 (10:54 +0800)
committerAndi Shyti <andi.shyti@kernel.org>
Sun, 5 May 2024 22:56:45 +0000 (00:56 +0200)
During each byte access, the host performs clock stretching.

To reduce the host performs clock stretching, move most of
the per-msg processing to the interrupt context.

Suggested-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Reviewed-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Signed-off-by: Hans Hu <hanshu-oc@zhaoxin.com>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
drivers/i2c/busses/i2c-viai2c-common.c
drivers/i2c/busses/i2c-viai2c-common.h

index 59901c53ad48ca3307407af2f9fb3c9d491badb8..1b39cf4f0d6c838c8a21f814b53812316f62a3bb 100644 (file)
@@ -18,37 +18,18 @@ int viai2c_wait_bus_not_busy(struct viai2c *i2c)
        return 0;
 }
 
-int viai2c_check_status(struct viai2c *i2c)
-{
-       int ret = 0;
-       unsigned long time_left;
-
-       time_left = wait_for_completion_timeout(&i2c->complete,
-                                               msecs_to_jiffies(500));
-       if (!time_left)
-               return -ETIMEDOUT;
-
-       if (i2c->cmd_status & VIAI2C_ISR_NACK_ADDR)
-               ret = -EIO;
-
-       if (i2c->cmd_status & VIAI2C_ISR_SCL_TIMEOUT)
-               ret = -ETIMEDOUT;
-
-       return ret;
-}
-
 static int viai2c_write(struct viai2c *i2c, struct i2c_msg *pmsg, int last)
 {
        u16 val, tcr_val = i2c->tcr;
-       int ret;
-       int xfer_len = 0;
+
+       i2c->last = last;
 
        if (pmsg->len == 0) {
                /*
                 * We still need to run through the while (..) once, so
                 * start at -1 and break out early from the loop
                 */
-               xfer_len = -1;
+               i2c->xfered_len = -1;
                writew(0, i2c->base + VIAI2C_REG_CDR);
        } else {
                writew(pmsg->buf[0] & 0xFF, i2c->base + VIAI2C_REG_CDR);
@@ -73,42 +54,15 @@ static int viai2c_write(struct viai2c *i2c, struct i2c_msg *pmsg, int last)
                writew(val, i2c->base + VIAI2C_REG_CR);
        }
 
-       while (xfer_len < pmsg->len) {
-               ret = viai2c_check_status(i2c);
-               if (ret)
-                       return ret;
-
-               xfer_len++;
-
-               val = readw(i2c->base + VIAI2C_REG_CSR);
-               if (val & VIAI2C_CSR_RCV_NOT_ACK) {
-                       dev_dbg(i2c->dev, "write RCV NACK error\n");
-                       return -EIO;
-               }
-
-               if (pmsg->len == 0) {
-                       val = VIAI2C_CR_TX_END | VIAI2C_CR_CPU_RDY | VIAI2C_CR_ENABLE;
-                       writew(val, i2c->base + VIAI2C_REG_CR);
-                       break;
-               }
-
-               if (xfer_len == pmsg->len) {
-                       if (last != 1)
-                               writew(VIAI2C_CR_ENABLE, i2c->base + VIAI2C_REG_CR);
-               } else {
-                       writew(pmsg->buf[xfer_len] & 0xFF, i2c->base + VIAI2C_REG_CDR);
-                       writew(VIAI2C_CR_CPU_RDY | VIAI2C_CR_ENABLE, i2c->base + VIAI2C_REG_CR);
-               }
-       }
+       if (!wait_for_completion_timeout(&i2c->complete, VIAI2C_TIMEOUT))
+               return -ETIMEDOUT;
 
-       return 0;
+       return i2c->ret;
 }
 
 static int viai2c_read(struct viai2c *i2c, struct i2c_msg *pmsg)
 {
        u16 val, tcr_val = i2c->tcr;
-       int ret;
-       u32 xfer_len = 0;
 
        val = readw(i2c->base + VIAI2C_REG_CR);
        val &= ~(VIAI2C_CR_TX_END | VIAI2C_CR_RX_END);
@@ -133,21 +87,10 @@ static int viai2c_read(struct viai2c *i2c, struct i2c_msg *pmsg)
                writew(val, i2c->base + VIAI2C_REG_CR);
        }
 
-       while (xfer_len < pmsg->len) {
-               ret = viai2c_check_status(i2c);
-               if (ret)
-                       return ret;
-
-               pmsg->buf[xfer_len] = readw(i2c->base + VIAI2C_REG_CDR) >> 8;
-               xfer_len++;
-
-               val = readw(i2c->base + VIAI2C_REG_CR) | VIAI2C_CR_CPU_RDY;
-               if (xfer_len == pmsg->len - 1)
-                       val |= VIAI2C_CR_RX_END;
-               writew(val, i2c->base + VIAI2C_REG_CR);
-       }
+       if (!wait_for_completion_timeout(&i2c->complete, VIAI2C_TIMEOUT))
+               return -ETIMEDOUT;
 
-       return 0;
+       return i2c->ret;
 }
 
 int viai2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
@@ -165,6 +108,9 @@ int viai2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
                                return ret;
                }
 
+               i2c->msg = pmsg;
+               i2c->xfered_len = 0;
+
                if (pmsg->flags & I2C_M_RD)
                        ret = viai2c_read(i2c, pmsg);
                else
@@ -174,15 +120,76 @@ int viai2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
        return (ret < 0) ? ret : i;
 }
 
+/*
+ * Main process of the byte mode xfer
+ *
+ * Return value indicates whether the transfer is complete
+ *  1: all the data has been successfully transferred
+ *  0: there is still data that needs to be transferred
+ *  -EIO: error occurred
+ */
+static int viai2c_irq_xfer(struct viai2c *i2c)
+{
+       u16 val;
+       struct i2c_msg *msg = i2c->msg;
+       u8 read = msg->flags & I2C_M_RD;
+       void __iomem *base = i2c->base;
+
+       if (read) {
+               msg->buf[i2c->xfered_len] = readw(base + VIAI2C_REG_CDR) >> 8;
+
+               val = readw(base + VIAI2C_REG_CR) | VIAI2C_CR_CPU_RDY;
+               if (i2c->xfered_len == msg->len - 2)
+                       val |= VIAI2C_CR_RX_END;
+               writew(val, base + VIAI2C_REG_CR);
+       } else {
+               val = readw(base + VIAI2C_REG_CSR);
+               if (val & VIAI2C_CSR_RCV_NOT_ACK)
+                       return -EIO;
+
+               /* I2C_SMBUS_QUICK */
+               if (msg->len == 0) {
+                       val = VIAI2C_CR_TX_END | VIAI2C_CR_CPU_RDY | VIAI2C_CR_ENABLE;
+                       writew(val, base + VIAI2C_REG_CR);
+                       return 1;
+               }
+
+               if ((i2c->xfered_len + 1) == msg->len) {
+                       if (!i2c->last)
+                               writew(VIAI2C_CR_ENABLE, base + VIAI2C_REG_CR);
+               } else {
+                       writew(msg->buf[i2c->xfered_len + 1] & 0xFF, base + VIAI2C_REG_CDR);
+                       writew(VIAI2C_CR_CPU_RDY | VIAI2C_CR_ENABLE, base + VIAI2C_REG_CR);
+               }
+       }
+
+       i2c->xfered_len++;
+
+       return i2c->xfered_len == msg->len;
+}
+
 static irqreturn_t viai2c_isr(int irq, void *data)
 {
        struct viai2c *i2c = data;
+       u8 status;
 
        /* save the status and write-clear it */
-       i2c->cmd_status = readw(i2c->base + VIAI2C_REG_ISR);
-       writew(i2c->cmd_status, i2c->base + VIAI2C_REG_ISR);
+       status = readw(i2c->base + VIAI2C_REG_ISR);
+       writew(status, i2c->base + VIAI2C_REG_ISR);
+
+       i2c->ret = 0;
+       if (status & VIAI2C_ISR_NACK_ADDR)
+               i2c->ret = -EIO;
+
+       if (status & VIAI2C_ISR_SCL_TIMEOUT)
+               i2c->ret = -ETIMEDOUT;
+
+       if (!i2c->ret)
+               i2c->ret = viai2c_irq_xfer(i2c);
 
-       complete(&i2c->complete);
+       /* All the data has been successfully transferred or error occurred */
+       if (i2c->ret)
+               complete(&i2c->complete);
 
        return IRQ_HANDLED;
 }
index 28799e7e97f0be50ea8ddd1f871b23de036e9ec5..c92e054ac7e7385e41d4eeaacf1dbc522e7f9706 100644 (file)
@@ -58,11 +58,13 @@ struct viai2c {
        struct clk              *clk;
        u16                     tcr;
        int                     irq;
-       u16                     cmd_status;
+       u16                     xfered_len;
+       struct i2c_msg          *msg;
+       int                     ret;
+       bool                    last;
 };
 
 int viai2c_wait_bus_not_busy(struct viai2c *i2c);
-int viai2c_check_status(struct viai2c *i2c);
 int viai2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num);
 int viai2c_init(struct platform_device *pdev, struct viai2c **pi2c);