[PATCH] i2c: iproc: recover the bus on transfer timeouts
Jonas Gorski <[email protected]> Tue, 4 Aug 2026 11:14:05 +0200
| Newsgroups | org.kernel.vger.linux-i2c,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
Just resetting the controller is not always enough to recover from a timed out transfer, the bus may need to be cleared as well. So add support for generic SCL recovery by switching to bit-banged mode for manual control of SCL and SDA signals, and using it when SDA is still low after a timeout. Clearing SDA does not reset START_BUSY, so regardless of clearing SDA, the controller might still need to be reset as well. This is loosely inspired by iproc_smb_sda_low_recovery() from the non-upstream iproc smbus driver implementation [1]. [1] https://github.com/opencomputeproject/onie/blob/master/patches/kernel/3= .2.69/driver-iproc-smbus.patch Signed-off-by: Jonas Gorski <[email protected]> --- drivers/i2c/busses/i2c-bcm-iproc.c | 92 ++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/drivers/i2c/busses/i2c-bcm-iproc.c b/drivers/i2c/busses/i2c-bc= m-iproc.c index 66bb3bfdaa4c..1d3732c76c09 100644 --- a/drivers/i2c/busses/i2c-bcm-iproc.c +++ b/drivers/i2c/busses/i2c-bcm-iproc.c @@ -15,6 +15,7 @@ #define CFG_OFFSET 0x00 #define CFG_RESET_SHIFT 31 #define CFG_EN_SHIFT 30 +#define CFG_BITBANG_EN_SHIFT 29 #define CFG_SLAVE_ADDR_0_SHIFT 28 #define CFG_M_RETRY_CNT_SHIFT 16 #define CFG_M_RETRY_CNT_MASK 0x0f @@ -56,6 +57,12 @@ #define S_FIFO_RX_THLD_SHIFT 8 #define S_FIFO_RX_THLD_MASK 0x3f =20 +#define BITBANG_CTRL_OFFSET 0x14 +#define BITBANG_CLK_IN_SHIFT 31 +#define BITBANG_CLK_OUT_EN_SHIFT 30 +#define BITBANG_DATA_IN_SHIFT 29 +#define BITBANG_DATA_OUT_EN_SHIFT 28 + #define M_CMD_OFFSET 0x30 #define M_CMD_START_BUSY_SHIFT 31 #define M_CMD_STATUS_SHIFT 25 @@ -715,6 +722,76 @@ static void bcm_iproc_i2c_init(struct bcm_iproc_i2c_de= v *iproc_i2c) iproc_i2c_wr_reg(iproc_i2c, IS_OFFSET, 0xffffffff); } =20 +static void bcm_iproc_i2c_prepare_recovery(struct i2c_adapter *adapter) +{ + struct bcm_iproc_i2c_dev *iproc_i2c =3D i2c_get_adapdata(adapter); + u32 val; + + /* enable bit-bang mode to allow manually driving SDA/SCL */ + val =3D iproc_i2c_rd_reg(iproc_i2c, CFG_OFFSET); + val |=3D BIT(CFG_BITBANG_EN_SHIFT); + iproc_i2c_wr_reg(iproc_i2c, CFG_OFFSET, val); + usleep_range(50, 75); +} + +static void bcm_iproc_i2c_unprepare_recovery(struct i2c_adapter *adapter) +{ + struct bcm_iproc_i2c_dev *iproc_i2c =3D i2c_get_adapdata(adapter); + u32 val; + + /* disable bit-bang mode again */ + val =3D iproc_i2c_rd_reg(iproc_i2c, CFG_OFFSET); + val &=3D ~BIT(CFG_BITBANG_EN_SHIFT); + iproc_i2c_wr_reg(iproc_i2c, CFG_OFFSET, val); + usleep_range(10, 15); +} + +static int bcm_iproc_i2c_get_scl(struct i2c_adapter *adapter) +{ + struct bcm_iproc_i2c_dev *iproc_i2c =3D i2c_get_adapdata(adapter); + u32 val; + + val =3D iproc_i2c_rd_reg(iproc_i2c, BITBANG_CTRL_OFFSET); + + return !!(val & BIT(BITBANG_CLK_IN_SHIFT)); +} + +static void bcm_iproc_i2c_set_scl(struct i2c_adapter *adapter, int scl) +{ + struct bcm_iproc_i2c_dev *iproc_i2c =3D i2c_get_adapdata(adapter); + u32 val; + + val =3D iproc_i2c_rd_reg(iproc_i2c, BITBANG_CTRL_OFFSET); + if (scl) + val |=3D BIT(BITBANG_CLK_OUT_EN_SHIFT); + else + val &=3D ~BIT(BITBANG_CLK_OUT_EN_SHIFT); + iproc_i2c_wr_reg(iproc_i2c, BITBANG_CTRL_OFFSET, val); +} + +static int bcm_iproc_i2c_get_sda(struct i2c_adapter *adapter) +{ + struct bcm_iproc_i2c_dev *iproc_i2c =3D i2c_get_adapdata(adapter); + u32 val; + + val =3D iproc_i2c_rd_reg(iproc_i2c, BITBANG_CTRL_OFFSET); + + return !!(val & BIT(BITBANG_DATA_IN_SHIFT)); +} + +static void bcm_iproc_i2c_set_sda(struct i2c_adapter *adapter, int sda) +{ + struct bcm_iproc_i2c_dev *iproc_i2c =3D i2c_get_adapdata(adapter); + u32 val; + + val =3D iproc_i2c_rd_reg(iproc_i2c, BITBANG_CTRL_OFFSET); + if (sda) + val |=3D BIT(BITBANG_DATA_OUT_EN_SHIFT); + else + val &=3D ~BIT(BITBANG_DATA_OUT_EN_SHIFT); + iproc_i2c_wr_reg(iproc_i2c, BITBANG_CTRL_OFFSET, val); +} + static int bcm_iproc_i2c_check_status(struct bcm_iproc_i2c_dev *iproc_i2c, struct i2c_msg *msg) { @@ -803,6 +880,10 @@ static int bcm_iproc_i2c_xfer_wait(struct bcm_iproc_i2= c_dev *iproc_i2c, } =20 if (!time_left && !iproc_i2c->xfer_is_done) { + /* Recover bus if SDA is held low */ + if (bcm_iproc_i2c_get_sda(&iproc_i2c->adapter) =3D=3D 0) + i2c_recover_bus(&iproc_i2c->adapter); + /* * The controller may fail to clear START_BUSY after a timeout, * reset the controller to recover in that case. @@ -1064,6 +1145,16 @@ static const struct i2c_adapter_quirks bcm_iproc_i2c= _quirks =3D { .max_read_len =3D M_RX_MAX_READ_LEN, }; =20 +static struct i2c_bus_recovery_info bcm_iproc_i2c_recovery_info =3D { + .recover_bus =3D i2c_generic_scl_recovery, + .get_scl =3D bcm_iproc_i2c_get_scl, + .set_scl =3D bcm_iproc_i2c_set_scl, + .get_sda =3D bcm_iproc_i2c_get_sda, + .set_sda =3D bcm_iproc_i2c_set_sda, + .prepare_recovery =3D bcm_iproc_i2c_prepare_recovery, + .unprepare_recovery =3D bcm_iproc_i2c_unprepare_recovery, +}; + static int bcm_iproc_i2c_cfg_speed(struct bcm_iproc_i2c_dev *iproc_i2c) { unsigned int bus_speed; @@ -1163,6 +1254,7 @@ static int bcm_iproc_i2c_probe(struct platform_device= *pdev) of_node_full_name(iproc_i2c->device->of_node)); adap->algo =3D &bcm_iproc_algo; adap->quirks =3D &bcm_iproc_i2c_quirks; + adap->bus_recovery_info =3D &bcm_iproc_i2c_recovery_info; adap->dev.parent =3D &pdev->dev; adap->dev.of_node =3D pdev->dev.of_node; =20 --=20 2.55.0 --=20 BISDN GmbH K=F6rnerstra=DFe 7-10 10785 Berlin Germany Phone: +49 30 610 816 100 Managing Directors:=A0Dr.-Ing. Hagen Woesner, Andreas K=F6psel Commercial=20 register:=A0 Amtsgericht Berlin-Charlottenburg HRB 141569 B VAT ID No:=A0 DE283257294 <http://goog_1008408251>