i2c: mv64xxx: Add atomic_xfer method to driver
authorChris Morgan <macromorgan@hotmail.com>
Wed, 30 Mar 2022 17:16:57 +0000 (12:16 -0500)
committerWolfram Sang <wsa@kernel.org>
Tue, 14 Jun 2022 20:10:10 +0000 (22:10 +0200)
Add an atomic_xfer method to the driver so that it behaves correctly
when controlling a PMIC that is responsible for device shutdown.

The atomic_xfer method added is similar to the one from the i2c-rk3x
driver. When running an atomic_xfer a bool flag in the driver data is
set, the interrupt is not unmasked on transfer start, and the IRQ
handler is manually invoked while waiting for pending transfers to
complete.

Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
Acked-by: Gregory CLEMENT <gregory.clement@bootlin.com>
Signed-off-by: Wolfram Sang <wsa@kernel.org>
drivers/i2c/busses/i2c-mv64xxx.c

index 424c53e..103a05e 100644 (file)
@@ -150,6 +150,7 @@ struct mv64xxx_i2c_data {
        /* Clk div is 2 to the power n, not 2 to the power n + 1 */
        bool                    clk_n_base_0;
        struct i2c_bus_recovery_info    rinfo;
+       bool                    atomic;
 };
 
 static struct mv64xxx_i2c_regs mv64xxx_i2c_regs_mv64xxx = {
@@ -179,7 +180,10 @@ mv64xxx_i2c_prepare_for_io(struct mv64xxx_i2c_data *drv_data,
        u32     dir = 0;
 
        drv_data->cntl_bits = MV64XXX_I2C_REG_CONTROL_ACK |
-               MV64XXX_I2C_REG_CONTROL_INTEN | MV64XXX_I2C_REG_CONTROL_TWSIEN;
+                             MV64XXX_I2C_REG_CONTROL_TWSIEN;
+
+       if (!drv_data->atomic)
+               drv_data->cntl_bits |= MV64XXX_I2C_REG_CONTROL_INTEN;
 
        if (msg->flags & I2C_M_RD)
                dir = 1;
@@ -409,7 +413,8 @@ mv64xxx_i2c_do_action(struct mv64xxx_i2c_data *drv_data)
        case MV64XXX_I2C_ACTION_RCV_DATA_STOP:
                drv_data->msg->buf[drv_data->byte_posn++] =
                        readl(drv_data->reg_base + drv_data->reg_offsets.data);
-               drv_data->cntl_bits &= ~MV64XXX_I2C_REG_CONTROL_INTEN;
+               if (!drv_data->atomic)
+                       drv_data->cntl_bits &= ~MV64XXX_I2C_REG_CONTROL_INTEN;
                writel(drv_data->cntl_bits | MV64XXX_I2C_REG_CONTROL_STOP,
                        drv_data->reg_base + drv_data->reg_offsets.control);
                drv_data->block = 0;
@@ -427,7 +432,8 @@ mv64xxx_i2c_do_action(struct mv64xxx_i2c_data *drv_data)
                drv_data->rc = -EIO;
                fallthrough;
        case MV64XXX_I2C_ACTION_SEND_STOP:
-               drv_data->cntl_bits &= ~MV64XXX_I2C_REG_CONTROL_INTEN;
+               if (!drv_data->atomic)
+                       drv_data->cntl_bits &= ~MV64XXX_I2C_REG_CONTROL_INTEN;
                writel(drv_data->cntl_bits | MV64XXX_I2C_REG_CONTROL_STOP,
                        drv_data->reg_base + drv_data->reg_offsets.control);
                drv_data->block = 0;
@@ -575,6 +581,17 @@ mv64xxx_i2c_wait_for_completion(struct mv64xxx_i2c_data *drv_data)
                spin_unlock_irqrestore(&drv_data->lock, flags);
 }
 
+static void mv64xxx_i2c_wait_polling(struct mv64xxx_i2c_data *drv_data)
+{
+       ktime_t timeout = ktime_add_ms(ktime_get(), drv_data->adapter.timeout);
+
+       while (READ_ONCE(drv_data->block) &&
+              ktime_compare(ktime_get(), timeout) < 0) {
+               udelay(5);
+               mv64xxx_i2c_intr(0, drv_data);
+       }
+}
+
 static int
 mv64xxx_i2c_execute_msg(struct mv64xxx_i2c_data *drv_data, struct i2c_msg *msg,
                                int is_last)
@@ -590,7 +607,11 @@ mv64xxx_i2c_execute_msg(struct mv64xxx_i2c_data *drv_data, struct i2c_msg *msg,
        mv64xxx_i2c_send_start(drv_data);
        spin_unlock_irqrestore(&drv_data->lock, flags);
 
-       mv64xxx_i2c_wait_for_completion(drv_data);
+       if (!drv_data->atomic)
+               mv64xxx_i2c_wait_for_completion(drv_data);
+       else
+               mv64xxx_i2c_wait_polling(drv_data);
+
        return drv_data->rc;
 }
 
@@ -717,7 +738,7 @@ mv64xxx_i2c_functionality(struct i2c_adapter *adap)
 }
 
 static int
-mv64xxx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
+mv64xxx_i2c_xfer_core(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
 {
        struct mv64xxx_i2c_data *drv_data = i2c_get_adapdata(adap);
        int rc, ret = num;
@@ -730,7 +751,7 @@ mv64xxx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
        drv_data->msgs = msgs;
        drv_data->num_msgs = num;
 
-       if (mv64xxx_i2c_can_offload(drv_data))
+       if (mv64xxx_i2c_can_offload(drv_data) && !drv_data->atomic)
                rc = mv64xxx_i2c_offload_xfer(drv_data);
        else
                rc = mv64xxx_i2c_execute_msg(drv_data, &msgs[0], num == 1);
@@ -747,8 +768,27 @@ mv64xxx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
        return ret;
 }
 
+static int
+mv64xxx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
+{
+       struct mv64xxx_i2c_data *drv_data = i2c_get_adapdata(adap);
+
+       drv_data->atomic = 0;
+       return mv64xxx_i2c_xfer_core(adap, msgs, num);
+}
+
+static int mv64xxx_i2c_xfer_atomic(struct i2c_adapter *adap,
+                                  struct i2c_msg msgs[], int num)
+{
+       struct mv64xxx_i2c_data *drv_data = i2c_get_adapdata(adap);
+
+       drv_data->atomic = 1;
+       return mv64xxx_i2c_xfer_core(adap, msgs, num);
+}
+
 static const struct i2c_algorithm mv64xxx_i2c_algo = {
        .master_xfer = mv64xxx_i2c_xfer,
+       .master_xfer_atomic = mv64xxx_i2c_xfer_atomic,
        .functionality = mv64xxx_i2c_functionality,
 };