]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
i2c: spacemit: ensure SDA is released after bus reset
authorTroy Mitchell <troy.mitchell@linux.spacemit.com>
Thu, 25 Sep 2025 02:02:29 +0000 (10:02 +0800)
committerWolfram Sang <wsa+renesas@sang-engineering.com>
Thu, 25 Sep 2025 21:49:08 +0000 (23:49 +0200)
After performing a conditional bus reset, the controller must ensure
that the SDA line is actually released.

Previously, the reset routine only performed a single check,
which could leave the bus in a locked state in some situations.

This patch introduces a loop that toggles the reset cycle and issues
a reset request up to SPACEMIT_BUS_RESET_CLK_CNT_MAX times, checking
SDA after each attempt. If SDA is released before the maximum count,
the function returns early. Otherwise, a warning is emitted.

This change improves bus recovery reliability.

Fixes: 5ea558473fa31 ("i2c: spacemit: add support for SpacemiT K1 SoC")
Signed-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>
Reviewed-by: Aurelien Jarno <aurelien@aurel32.net>
Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
drivers/i2c/busses/i2c-k1.c

index 848dfaf634f63021bc565f2c0a1c93f9f33665dd..6b918770e612e098b8ad17418f420d87c94df166 100644 (file)
@@ -3,6 +3,7 @@
  * Copyright (C) 2024-2025 Troy Mitchell <troymitchell988@gmail.com>
  */
 
+#include <linux/bitfield.h>
  #include <linux/clk.h>
  #include <linux/i2c.h>
  #include <linux/iopoll.h>
@@ -26,7 +27,8 @@
 #define SPACEMIT_CR_MODE_FAST    BIT(8)                /* bus mode (master operation) */
 /* Bit 9 is reserved */
 #define SPACEMIT_CR_UR           BIT(10)       /* unit reset */
-/* Bits 11-12 are reserved */
+#define SPACEMIT_CR_RSTREQ      BIT(11)        /* i2c bus reset request */
+/* Bit 12 is reserved */
 #define SPACEMIT_CR_SCLE         BIT(13)       /* master clock enable */
 #define SPACEMIT_CR_IUE          BIT(14)       /* unit enable */
 /* Bits 15-17 are reserved */
@@ -78,6 +80,8 @@
                                        SPACEMIT_SR_ALD)
 
 #define SPACEMIT_RCR_SDA_GLITCH_NOFIX          BIT(7)          /* bypass the SDA glitch fix */
+/* the cycles of SCL during bus reset */
+#define SPACEMIT_RCR_FIELD_RST_CYC             GENMASK(3, 0)
 
 /* SPACEMIT_IBMR register fields */
 #define SPACEMIT_BMR_SDA         BIT(0)                /* SDA line level */
@@ -91,6 +95,8 @@
 
 #define SPACEMIT_SR_ERR        (SPACEMIT_SR_BED | SPACEMIT_SR_RXOV | SPACEMIT_SR_ALD)
 
+#define SPACEMIT_BUS_RESET_CLK_CNT_MAX         9
+
 enum spacemit_i2c_state {
        SPACEMIT_STATE_IDLE,
        SPACEMIT_STATE_START,
@@ -163,6 +169,7 @@ static int spacemit_i2c_handle_err(struct spacemit_i2c_dev *i2c)
 static void spacemit_i2c_conditionally_reset_bus(struct spacemit_i2c_dev *i2c)
 {
        u32 status;
+       u8 clk_cnt;
 
        /* if bus is locked, reset unit. 0: locked */
        status = readl(i2c->base + SPACEMIT_IBMR);
@@ -172,6 +179,18 @@ static void spacemit_i2c_conditionally_reset_bus(struct spacemit_i2c_dev *i2c)
        spacemit_i2c_reset(i2c);
        usleep_range(10, 20);
 
+       for (clk_cnt = 0; clk_cnt < SPACEMIT_BUS_RESET_CLK_CNT_MAX; clk_cnt++) {
+               status = readl(i2c->base + SPACEMIT_IBMR);
+               if (status & SPACEMIT_BMR_SDA)
+                       return;
+
+               /* There's nothing left to save here, we are about to exit */
+               writel(FIELD_PREP(SPACEMIT_RCR_FIELD_RST_CYC, 1),
+                      i2c->base + SPACEMIT_IRCR);
+               writel(SPACEMIT_CR_RSTREQ, i2c->base + SPACEMIT_ICR);
+               usleep_range(20, 30);
+       }
+
        /* check sda again here */
        status = readl(i2c->base + SPACEMIT_IBMR);
        if (!(status & SPACEMIT_BMR_SDA))