i2c: exynos5: add support for atomic transfers
authorMarek Szyprowski <m.szyprowski@samsung.com>
Fri, 6 Oct 2023 15:08:03 +0000 (17:08 +0200)
committerWolfram Sang <wsa@kernel.org>
Sat, 21 Oct 2023 18:45:41 +0000 (20:45 +0200)
Add support for atomic transfers using polling mode with interrupts
intentionally disabled. This removes the warning introduced by commit
63b96983a5dd ("i2c: core: introduce callbacks for atomic transfers")
during system reboot and power-off.

Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
Reviewed-by: Andi Shyti <andi.shyti@kernel.org>
Signed-off-by: Wolfram Sang <wsa@kernel.org>
drivers/i2c/busses/i2c-exynos5.c

index 2b0b9cdffa861bc123f240d9401df62048167f5f..65cb06ec38042727b9252b2e391ebe830dc88f2a 100644 (file)
@@ -194,6 +194,11 @@ struct exynos5_i2c {
         */
        int                     trans_done;
 
+       /*
+        * Called from atomic context, don't use interrupts.
+        */
+       unsigned int            atomic;
+
        /* Controller operating frequency */
        unsigned int            op_clock;
 
@@ -711,6 +716,22 @@ static void exynos5_i2c_message_start(struct exynos5_i2c *i2c, int stop)
        spin_unlock_irqrestore(&i2c->lock, flags);
 }
 
+static bool exynos5_i2c_poll_irqs_timeout(struct exynos5_i2c *i2c,
+                                         unsigned long timeout)
+{
+       unsigned long time_left = jiffies + timeout;
+
+       while (time_before(jiffies, time_left) &&
+              !((i2c->trans_done && (i2c->msg->len == i2c->msg_ptr)) ||
+                (i2c->state < 0))) {
+               while (readl(i2c->regs + HSI2C_INT_ENABLE) &
+                      readl(i2c->regs + HSI2C_INT_STATUS))
+                       exynos5_i2c_irq(i2c->irq, i2c);
+               usleep_range(100, 200);
+       }
+       return time_before(jiffies, time_left);
+}
+
 static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
                              struct i2c_msg *msgs, int stop)
 {
@@ -725,8 +746,13 @@ static int exynos5_i2c_xfer_msg(struct exynos5_i2c *i2c,
 
        exynos5_i2c_message_start(i2c, stop);
 
-       timeout = wait_for_completion_timeout(&i2c->msg_complete,
-                                             EXYNOS5_I2C_TIMEOUT);
+       if (!i2c->atomic)
+               timeout = wait_for_completion_timeout(&i2c->msg_complete,
+                                                     EXYNOS5_I2C_TIMEOUT);
+       else
+               timeout = exynos5_i2c_poll_irqs_timeout(i2c,
+                                                       EXYNOS5_I2C_TIMEOUT);
+
        if (timeout == 0)
                ret = -ETIMEDOUT;
        else
@@ -777,6 +803,21 @@ err_pclk:
        return ret ?: num;
 }
 
+static int exynos5_i2c_xfer_atomic(struct i2c_adapter *adap,
+                                  struct i2c_msg *msgs, int num)
+{
+       struct exynos5_i2c *i2c = adap->algo_data;
+       int ret;
+
+       disable_irq(i2c->irq);
+       i2c->atomic = true;
+       ret = exynos5_i2c_xfer(adap, msgs, num);
+       i2c->atomic = false;
+       enable_irq(i2c->irq);
+
+       return ret;
+}
+
 static u32 exynos5_i2c_func(struct i2c_adapter *adap)
 {
        return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
@@ -784,6 +825,7 @@ static u32 exynos5_i2c_func(struct i2c_adapter *adap)
 
 static const struct i2c_algorithm exynos5_i2c_algorithm = {
        .master_xfer            = exynos5_i2c_xfer,
+       .master_xfer_atomic     = exynos5_i2c_xfer_atomic,
        .functionality          = exynos5_i2c_func,
 };