mvebu: fix PXA I2C bus with recovery
authorRobert Marko <robert.marko@sartura.hr>
Fri, 10 Nov 2023 12:42:34 +0000 (13:42 +0100)
committerHauke Mehrtens <hauke@hauke-m.de>
Sun, 12 Nov 2023 13:28:21 +0000 (14:28 +0100)
Since kernel 5.11, the PXA I2C driver has been converted to generic I2C
recovery, which makes the I2C bus completely lock up if recovery pinctrl
is present in the DT and I2C recovery is enabled.

This effectively completely broke I2C on Methode uDPU and eDPU boards
as both of them rely on I2C recovery.

After a discussion upstream, it was concluded that there is no simple fix
and that the blamed upstream commit:
0b01392c18b9993a584f36ace1d61118772ad0ca ("i2c: pxa: move to generic GPIO
recovery") should be reverted.
I have sent the revert upstream, it should be merged soon so lets "fix"
OpenWrt as well.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>
target/linux/mvebu/patches-5.15/106-Revert-i2c-pxa-move-to-generic-GPIO-recovery.patch [new file with mode: 0644]
target/linux/mvebu/patches-6.1/106-Revert-i2c-pxa-move-to-generic-GPIO-recovery.patch [new file with mode: 0644]

diff --git a/target/linux/mvebu/patches-5.15/106-Revert-i2c-pxa-move-to-generic-GPIO-recovery.patch b/target/linux/mvebu/patches-5.15/106-Revert-i2c-pxa-move-to-generic-GPIO-recovery.patch
new file mode 100644 (file)
index 0000000..7a0dc15
--- /dev/null
@@ -0,0 +1,139 @@
+From ea8444b6fa5955c16b713dc83310882b93b44e62 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robert.marko@sartura.hr>
+Date: Fri, 10 Nov 2023 10:10:29 +0100
+Subject: [PATCH] Revert "i2c: pxa: move to generic GPIO recovery"
+
+This reverts commit 0b01392c18b9993a584f36ace1d61118772ad0ca.
+
+Conversion of PXA to generic I2C recovery, makes the I2C bus completely
+lock up if recovery pinctrl is present in the DT and I2C recovery is
+enabled.
+
+So, until the generic I2C recovery can also work with PXA lets revert
+to have working I2C and I2C recovery again.
+
+Signed-off-by: Robert Marko <robert.marko@sartura.hr>
+Cc: stable@vger.kernel.org # 5.11+
+---
+ drivers/i2c/busses/i2c-pxa.c | 76 ++++++++++++++++++++++++++++++++----
+ 1 file changed, 68 insertions(+), 8 deletions(-)
+
+--- a/drivers/i2c/busses/i2c-pxa.c
++++ b/drivers/i2c/busses/i2c-pxa.c
+@@ -264,6 +264,9 @@ struct pxa_i2c {
+       u32                     hs_mask;
+       struct i2c_bus_recovery_info recovery;
++      struct pinctrl          *pinctrl;
++      struct pinctrl_state    *pinctrl_default;
++      struct pinctrl_state    *pinctrl_recovery;
+ };
+ #define _IBMR(i2c)    ((i2c)->reg_ibmr)
+@@ -1302,12 +1305,13 @@ static void i2c_pxa_prepare_recovery(str
+        */
+       gpiod_set_value(i2c->recovery.scl_gpiod, ibmr & IBMR_SCLS);
+       gpiod_set_value(i2c->recovery.sda_gpiod, ibmr & IBMR_SDAS);
++
++      WARN_ON(pinctrl_select_state(i2c->pinctrl, i2c->pinctrl_recovery));
+ }
+ static void i2c_pxa_unprepare_recovery(struct i2c_adapter *adap)
+ {
+       struct pxa_i2c *i2c = adap->algo_data;
+-      struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;
+       u32 isr;
+       /*
+@@ -1321,7 +1325,7 @@ static void i2c_pxa_unprepare_recovery(s
+               i2c_pxa_do_reset(i2c);
+       }
+-      WARN_ON(pinctrl_select_state(bri->pinctrl, bri->pins_default));
++      WARN_ON(pinctrl_select_state(i2c->pinctrl, i2c->pinctrl_default));
+       dev_dbg(&i2c->adap.dev, "recovery: IBMR 0x%08x ISR 0x%08x\n",
+               readl(_IBMR(i2c)), readl(_ISR(i2c)));
+@@ -1343,20 +1347,76 @@ static int i2c_pxa_init_recovery(struct
+       if (IS_ENABLED(CONFIG_I2C_PXA_SLAVE))
+               return 0;
+-      bri->pinctrl = devm_pinctrl_get(dev);
+-      if (PTR_ERR(bri->pinctrl) == -ENODEV) {
+-              bri->pinctrl = NULL;
++      i2c->pinctrl = devm_pinctrl_get(dev);
++      if (PTR_ERR(i2c->pinctrl) == -ENODEV)
++              i2c->pinctrl = NULL;
++      if (IS_ERR(i2c->pinctrl))
++              return PTR_ERR(i2c->pinctrl);
++
++      if (!i2c->pinctrl)
++              return 0;
++
++      i2c->pinctrl_default = pinctrl_lookup_state(i2c->pinctrl,
++                                                  PINCTRL_STATE_DEFAULT);
++      i2c->pinctrl_recovery = pinctrl_lookup_state(i2c->pinctrl, "recovery");
++
++      if (IS_ERR(i2c->pinctrl_default) || IS_ERR(i2c->pinctrl_recovery)) {
++              dev_info(dev, "missing pinmux recovery information: %ld %ld\n",
++                       PTR_ERR(i2c->pinctrl_default),
++                       PTR_ERR(i2c->pinctrl_recovery));
++              return 0;
++      }
++
++      /*
++       * Claiming GPIOs can influence the pinmux state, and may glitch the
++       * I2C bus. Do this carefully.
++       */
++      bri->scl_gpiod = devm_gpiod_get(dev, "scl", GPIOD_OUT_HIGH_OPEN_DRAIN);
++      if (bri->scl_gpiod == ERR_PTR(-EPROBE_DEFER))
++              return -EPROBE_DEFER;
++      if (IS_ERR(bri->scl_gpiod)) {
++              dev_info(dev, "missing scl gpio recovery information: %pe\n",
++                       bri->scl_gpiod);
++              return 0;
++      }
++
++      /*
++       * We have SCL. Pull SCL low and wait a bit so that SDA glitches
++       * have no effect.
++       */
++      gpiod_direction_output(bri->scl_gpiod, 0);
++      udelay(10);
++      bri->sda_gpiod = devm_gpiod_get(dev, "sda", GPIOD_OUT_HIGH_OPEN_DRAIN);
++
++      /* Wait a bit in case of a SDA glitch, and then release SCL. */
++      udelay(10);
++      gpiod_direction_output(bri->scl_gpiod, 1);
++
++      if (bri->sda_gpiod == ERR_PTR(-EPROBE_DEFER))
++              return -EPROBE_DEFER;
++
++      if (IS_ERR(bri->sda_gpiod)) {
++              dev_info(dev, "missing sda gpio recovery information: %pe\n",
++                       bri->sda_gpiod);
+               return 0;
+       }
+-      if (IS_ERR(bri->pinctrl))
+-              return PTR_ERR(bri->pinctrl);
+       bri->prepare_recovery = i2c_pxa_prepare_recovery;
+       bri->unprepare_recovery = i2c_pxa_unprepare_recovery;
++      bri->recover_bus = i2c_generic_scl_recovery;
+       i2c->adap.bus_recovery_info = bri;
+-      return 0;
++      /*
++       * Claiming GPIOs can change the pinmux state, which confuses the
++       * pinctrl since pinctrl's idea of the current setting is unaffected
++       * by the pinmux change caused by claiming the GPIO. Work around that
++       * by switching pinctrl to the GPIO state here. We do it this way to
++       * avoid glitching the I2C bus.
++       */
++      pinctrl_select_state(i2c->pinctrl, i2c->pinctrl_recovery);
++
++      return pinctrl_select_state(i2c->pinctrl, i2c->pinctrl_default);
+ }
+ static int i2c_pxa_probe(struct platform_device *dev)
diff --git a/target/linux/mvebu/patches-6.1/106-Revert-i2c-pxa-move-to-generic-GPIO-recovery.patch b/target/linux/mvebu/patches-6.1/106-Revert-i2c-pxa-move-to-generic-GPIO-recovery.patch
new file mode 100644 (file)
index 0000000..7a0dc15
--- /dev/null
@@ -0,0 +1,139 @@
+From ea8444b6fa5955c16b713dc83310882b93b44e62 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robert.marko@sartura.hr>
+Date: Fri, 10 Nov 2023 10:10:29 +0100
+Subject: [PATCH] Revert "i2c: pxa: move to generic GPIO recovery"
+
+This reverts commit 0b01392c18b9993a584f36ace1d61118772ad0ca.
+
+Conversion of PXA to generic I2C recovery, makes the I2C bus completely
+lock up if recovery pinctrl is present in the DT and I2C recovery is
+enabled.
+
+So, until the generic I2C recovery can also work with PXA lets revert
+to have working I2C and I2C recovery again.
+
+Signed-off-by: Robert Marko <robert.marko@sartura.hr>
+Cc: stable@vger.kernel.org # 5.11+
+---
+ drivers/i2c/busses/i2c-pxa.c | 76 ++++++++++++++++++++++++++++++++----
+ 1 file changed, 68 insertions(+), 8 deletions(-)
+
+--- a/drivers/i2c/busses/i2c-pxa.c
++++ b/drivers/i2c/busses/i2c-pxa.c
+@@ -264,6 +264,9 @@ struct pxa_i2c {
+       u32                     hs_mask;
+       struct i2c_bus_recovery_info recovery;
++      struct pinctrl          *pinctrl;
++      struct pinctrl_state    *pinctrl_default;
++      struct pinctrl_state    *pinctrl_recovery;
+ };
+ #define _IBMR(i2c)    ((i2c)->reg_ibmr)
+@@ -1302,12 +1305,13 @@ static void i2c_pxa_prepare_recovery(str
+        */
+       gpiod_set_value(i2c->recovery.scl_gpiod, ibmr & IBMR_SCLS);
+       gpiod_set_value(i2c->recovery.sda_gpiod, ibmr & IBMR_SDAS);
++
++      WARN_ON(pinctrl_select_state(i2c->pinctrl, i2c->pinctrl_recovery));
+ }
+ static void i2c_pxa_unprepare_recovery(struct i2c_adapter *adap)
+ {
+       struct pxa_i2c *i2c = adap->algo_data;
+-      struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;
+       u32 isr;
+       /*
+@@ -1321,7 +1325,7 @@ static void i2c_pxa_unprepare_recovery(s
+               i2c_pxa_do_reset(i2c);
+       }
+-      WARN_ON(pinctrl_select_state(bri->pinctrl, bri->pins_default));
++      WARN_ON(pinctrl_select_state(i2c->pinctrl, i2c->pinctrl_default));
+       dev_dbg(&i2c->adap.dev, "recovery: IBMR 0x%08x ISR 0x%08x\n",
+               readl(_IBMR(i2c)), readl(_ISR(i2c)));
+@@ -1343,20 +1347,76 @@ static int i2c_pxa_init_recovery(struct
+       if (IS_ENABLED(CONFIG_I2C_PXA_SLAVE))
+               return 0;
+-      bri->pinctrl = devm_pinctrl_get(dev);
+-      if (PTR_ERR(bri->pinctrl) == -ENODEV) {
+-              bri->pinctrl = NULL;
++      i2c->pinctrl = devm_pinctrl_get(dev);
++      if (PTR_ERR(i2c->pinctrl) == -ENODEV)
++              i2c->pinctrl = NULL;
++      if (IS_ERR(i2c->pinctrl))
++              return PTR_ERR(i2c->pinctrl);
++
++      if (!i2c->pinctrl)
++              return 0;
++
++      i2c->pinctrl_default = pinctrl_lookup_state(i2c->pinctrl,
++                                                  PINCTRL_STATE_DEFAULT);
++      i2c->pinctrl_recovery = pinctrl_lookup_state(i2c->pinctrl, "recovery");
++
++      if (IS_ERR(i2c->pinctrl_default) || IS_ERR(i2c->pinctrl_recovery)) {
++              dev_info(dev, "missing pinmux recovery information: %ld %ld\n",
++                       PTR_ERR(i2c->pinctrl_default),
++                       PTR_ERR(i2c->pinctrl_recovery));
++              return 0;
++      }
++
++      /*
++       * Claiming GPIOs can influence the pinmux state, and may glitch the
++       * I2C bus. Do this carefully.
++       */
++      bri->scl_gpiod = devm_gpiod_get(dev, "scl", GPIOD_OUT_HIGH_OPEN_DRAIN);
++      if (bri->scl_gpiod == ERR_PTR(-EPROBE_DEFER))
++              return -EPROBE_DEFER;
++      if (IS_ERR(bri->scl_gpiod)) {
++              dev_info(dev, "missing scl gpio recovery information: %pe\n",
++                       bri->scl_gpiod);
++              return 0;
++      }
++
++      /*
++       * We have SCL. Pull SCL low and wait a bit so that SDA glitches
++       * have no effect.
++       */
++      gpiod_direction_output(bri->scl_gpiod, 0);
++      udelay(10);
++      bri->sda_gpiod = devm_gpiod_get(dev, "sda", GPIOD_OUT_HIGH_OPEN_DRAIN);
++
++      /* Wait a bit in case of a SDA glitch, and then release SCL. */
++      udelay(10);
++      gpiod_direction_output(bri->scl_gpiod, 1);
++
++      if (bri->sda_gpiod == ERR_PTR(-EPROBE_DEFER))
++              return -EPROBE_DEFER;
++
++      if (IS_ERR(bri->sda_gpiod)) {
++              dev_info(dev, "missing sda gpio recovery information: %pe\n",
++                       bri->sda_gpiod);
+               return 0;
+       }
+-      if (IS_ERR(bri->pinctrl))
+-              return PTR_ERR(bri->pinctrl);
+       bri->prepare_recovery = i2c_pxa_prepare_recovery;
+       bri->unprepare_recovery = i2c_pxa_unprepare_recovery;
++      bri->recover_bus = i2c_generic_scl_recovery;
+       i2c->adap.bus_recovery_info = bri;
+-      return 0;
++      /*
++       * Claiming GPIOs can change the pinmux state, which confuses the
++       * pinctrl since pinctrl's idea of the current setting is unaffected
++       * by the pinmux change caused by claiming the GPIO. Work around that
++       * by switching pinctrl to the GPIO state here. We do it this way to
++       * avoid glitching the I2C bus.
++       */
++      pinctrl_select_state(i2c->pinctrl, i2c->pinctrl_recovery);
++
++      return pinctrl_select_state(i2c->pinctrl, i2c->pinctrl_default);
+ }
+ static int i2c_pxa_probe(struct platform_device *dev)