]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
i2c: microchip-core: actually use repeated sends
authorConor Dooley <conor.dooley@microchip.com>
Wed, 18 Dec 2024 12:07:40 +0000 (12:07 +0000)
committerAndi Shyti <andi.shyti@kernel.org>
Thu, 26 Dec 2024 00:54:47 +0000 (01:54 +0100)
At present, where repeated sends are intended to be used, the
i2c-microchip-core driver sends a stop followed by a start. Lots of i2c
devices must not malfunction in the face of this behaviour, because the
driver has operated like this for years! Try to keep track of whether or
not a repeated send is required, and suppress sending a stop in these
cases.

CC: stable@vger.kernel.org
Fixes: 64a6f1c4987e ("i2c: add support for microchip fpga i2c controllers")
Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
Reviewed-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20241218-football-composure-e56df2461461@spud
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
drivers/i2c/busses/i2c-microchip-corei2c.c

index d1543e7d838086858fb53e0a1dcfcc29d5e46d76..6a124e903c666c2dbedf85538e315fc5e8324ef5 100644 (file)
  * @base:              pointer to register struct
  * @dev:               device reference
  * @i2c_clk:           clock reference for i2c input clock
+ * @msg_queue:         pointer to the messages requiring sending
  * @buf:               pointer to msg buffer for easier use
  * @msg_complete:      xfer completion object
  * @adapter:           core i2c abstraction
  * @msg_err:           error code for completed message
  * @bus_clk_rate:      current i2c bus clock rate
  * @isr_status:                cached copy of local ISR status
+ * @total_num:         total number of messages to be sent/received
+ * @current_num:       index of the current message being sent/received
  * @msg_len:           number of bytes transferred in msg
  * @addr:              address of the current slave
+ * @restart_needed:    whether or not a repeated start is required after current message
  */
 struct mchp_corei2c_dev {
        void __iomem *base;
        struct device *dev;
        struct clk *i2c_clk;
+       struct i2c_msg *msg_queue;
        u8 *buf;
        struct completion msg_complete;
        struct i2c_adapter adapter;
        int msg_err;
+       int total_num;
+       int current_num;
        u32 bus_clk_rate;
        u32 isr_status;
        u16 msg_len;
        u8 addr;
+       bool restart_needed;
 };
 
 static void mchp_corei2c_core_disable(struct mchp_corei2c_dev *idev)
@@ -222,6 +230,47 @@ static int mchp_corei2c_fill_tx(struct mchp_corei2c_dev *idev)
        return 0;
 }
 
+static void mchp_corei2c_next_msg(struct mchp_corei2c_dev *idev)
+{
+       struct i2c_msg *this_msg;
+       u8 ctrl;
+
+       if (idev->current_num >= idev->total_num) {
+               complete(&idev->msg_complete);
+               return;
+       }
+
+       /*
+        * If there's been an error, the isr needs to return control
+        * to the "main" part of the driver, so as not to keep sending
+        * messages once it completes and clears the SI bit.
+        */
+       if (idev->msg_err) {
+               complete(&idev->msg_complete);
+               return;
+       }
+
+       this_msg = idev->msg_queue++;
+
+       if (idev->current_num < (idev->total_num - 1)) {
+               struct i2c_msg *next_msg = idev->msg_queue;
+
+               idev->restart_needed = next_msg->flags & I2C_M_RD;
+       } else {
+               idev->restart_needed = false;
+       }
+
+       idev->addr = i2c_8bit_addr_from_msg(this_msg);
+       idev->msg_len = this_msg->len;
+       idev->buf = this_msg->buf;
+
+       ctrl = readb(idev->base + CORE_I2C_CTRL);
+       ctrl |= CTRL_STA;
+       writeb(ctrl, idev->base + CORE_I2C_CTRL);
+
+       idev->current_num++;
+}
+
 static irqreturn_t mchp_corei2c_handle_isr(struct mchp_corei2c_dev *idev)
 {
        u32 status = idev->isr_status;
@@ -247,10 +296,14 @@ static irqreturn_t mchp_corei2c_handle_isr(struct mchp_corei2c_dev *idev)
                break;
        case STATUS_M_SLAW_ACK:
        case STATUS_M_TX_DATA_ACK:
-               if (idev->msg_len > 0)
+               if (idev->msg_len > 0) {
                        mchp_corei2c_fill_tx(idev);
-               else
-                       last_byte = true;
+               } else {
+                       if (idev->restart_needed)
+                               finished = true;
+                       else
+                               last_byte = true;
+               }
                break;
        case STATUS_M_TX_DATA_NACK:
        case STATUS_M_SLAR_NACK:
@@ -287,7 +340,7 @@ static irqreturn_t mchp_corei2c_handle_isr(struct mchp_corei2c_dev *idev)
                mchp_corei2c_stop(idev);
 
        if (last_byte || finished)
-               complete(&idev->msg_complete);
+               mchp_corei2c_next_msg(idev);
 
        return IRQ_HANDLED;
 }
@@ -311,21 +364,48 @@ static irqreturn_t mchp_corei2c_isr(int irq, void *_dev)
        return ret;
 }
 
-static int mchp_corei2c_xfer_msg(struct mchp_corei2c_dev *idev,
-                                struct i2c_msg *msg)
+static int mchp_corei2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+                            int num)
 {
-       u8 ctrl;
+       struct mchp_corei2c_dev *idev = i2c_get_adapdata(adap);
+       struct i2c_msg *this_msg = msgs;
        unsigned long time_left;
+       u8 ctrl;
+
+       mchp_corei2c_core_enable(idev);
+
+       /*
+        * The isr controls the flow of a transfer, this info needs to be saved
+        * to a location that it can access the queue information from.
+        */
+       idev->restart_needed = false;
+       idev->msg_queue = msgs;
+       idev->total_num = num;
+       idev->current_num = 0;
 
-       idev->addr = i2c_8bit_addr_from_msg(msg);
-       idev->msg_len = msg->len;
-       idev->buf = msg->buf;
+       /*
+        * But the first entry to the isr is triggered by the start in this
+        * function, so the first message needs to be "dequeued".
+        */
+       idev->addr = i2c_8bit_addr_from_msg(this_msg);
+       idev->msg_len = this_msg->len;
+       idev->buf = this_msg->buf;
        idev->msg_err = 0;
 
-       reinit_completion(&idev->msg_complete);
+       if (idev->total_num > 1) {
+               struct i2c_msg *next_msg = msgs + 1;
 
-       mchp_corei2c_core_enable(idev);
+               idev->restart_needed = next_msg->flags & I2C_M_RD;
+       }
 
+       idev->current_num++;
+       idev->msg_queue++;
+
+       reinit_completion(&idev->msg_complete);
+
+       /*
+        * Send the first start to pass control to the isr
+        */
        ctrl = readb(idev->base + CORE_I2C_CTRL);
        ctrl |= CTRL_STA;
        writeb(ctrl, idev->base + CORE_I2C_CTRL);
@@ -335,20 +415,8 @@ static int mchp_corei2c_xfer_msg(struct mchp_corei2c_dev *idev,
        if (!time_left)
                return -ETIMEDOUT;
 
-       return idev->msg_err;
-}
-
-static int mchp_corei2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
-                            int num)
-{
-       struct mchp_corei2c_dev *idev = i2c_get_adapdata(adap);
-       int i, ret;
-
-       for (i = 0; i < num; i++) {
-               ret = mchp_corei2c_xfer_msg(idev, msgs++);
-               if (ret)
-                       return ret;
-       }
+       if (idev->msg_err)
+               return idev->msg_err;
 
        return num;
 }