diff options
Diffstat (limited to 'target/linux/bcm27xx/patches-6.6/950-0998-i2c-designware-Add-support-for-bus-clear-feature.patch')
-rw-r--r-- | target/linux/bcm27xx/patches-6.6/950-0998-i2c-designware-Add-support-for-bus-clear-feature.patch | 156 |
1 files changed, 156 insertions, 0 deletions
diff --git a/target/linux/bcm27xx/patches-6.6/950-0998-i2c-designware-Add-support-for-bus-clear-feature.patch b/target/linux/bcm27xx/patches-6.6/950-0998-i2c-designware-Add-support-for-bus-clear-feature.patch new file mode 100644 index 0000000000..5eb4bcc5d2 --- /dev/null +++ b/target/linux/bcm27xx/patches-6.6/950-0998-i2c-designware-Add-support-for-bus-clear-feature.patch @@ -0,0 +1,156 @@ +From 24cb07b0c0724a22e474d12e7c2d5b834bf3b076 Mon Sep 17 00:00:00 2001 +From: Phil Elwell <phil@raspberrypi.com> +Date: Tue, 26 Mar 2024 15:57:46 +0000 +Subject: [PATCH 0998/1085] i2c: designware: Add support for bus clear feature + +Newer versions of the DesignWare I2C block support the detection of +stuck signals, and a mechanism to recover from them. Add the required +software support to the driver. + +This change was prompted by the observation that reading a single byte +from register 0 of a VEML7700 seems to cause it to issue an ACK too +early, and the controller to complain about losing arbitration. There +is a suspicion that this may be a more widespread problem, but at least +this patch prevents the bus from locking up. + +See: https://github.com/raspberrypi/linux/issues/6057 + +Signed-off-by: Phil Elwell <phil@raspberrypi.com> +--- + drivers/i2c/busses/i2c-designware-common.c | 12 ++++++++++++ + drivers/i2c/busses/i2c-designware-core.h | 8 ++++++++ + drivers/i2c/busses/i2c-designware-master.c | 19 ++++++++++++++++++- + 3 files changed, 38 insertions(+), 1 deletion(-) + +--- a/drivers/i2c/busses/i2c-designware-common.c ++++ b/drivers/i2c/busses/i2c-designware-common.c +@@ -57,6 +57,8 @@ static char *abort_sources[] = { + "slave lost the bus while transmitting data to a remote master", + [ABRT_SLAVE_RD_INTX] = + "incorrect slave-transmitter mode configuration", ++ [ABRT_SLAVE_SDA_STUCK_AT_LOW] = ++ "SDA stuck at low", + }; + + static int dw_reg_read(void *context, unsigned int reg, unsigned int *val) +@@ -607,8 +609,16 @@ int i2c_dw_wait_bus_not_busy(struct dw_i + int i2c_dw_handle_tx_abort(struct dw_i2c_dev *dev) + { + unsigned long abort_source = dev->abort_source; ++ unsigned int reg; + int i; + ++ if (abort_source & DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW) { ++ regmap_write(dev->map, DW_IC_ENABLE, ++ DW_IC_ENABLE_ENABLE | DW_IC_ENABLE_BUS_RECOVERY); ++ regmap_read_poll_timeout(dev->map, DW_IC_ENABLE, reg, ++ !(reg & DW_IC_ENABLE_BUS_RECOVERY), ++ 1100, 200000); ++ } + if (abort_source & DW_IC_TX_ABRT_NOACK) { + for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources)) + dev_dbg(dev->dev, +@@ -623,6 +633,8 @@ int i2c_dw_handle_tx_abort(struct dw_i2c + return -EAGAIN; + else if (abort_source & DW_IC_TX_ABRT_GCALL_READ) + return -EINVAL; /* wrong msgs[] data */ ++ else if (abort_source & DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW) ++ return -EREMOTEIO; + else + return -EIO; + } +--- a/drivers/i2c/busses/i2c-designware-core.h ++++ b/drivers/i2c/busses/i2c-designware-core.h +@@ -79,9 +79,12 @@ + #define DW_IC_TX_ABRT_SOURCE 0x80 + #define DW_IC_ENABLE_STATUS 0x9c + #define DW_IC_CLR_RESTART_DET 0xa8 ++#define DW_IC_SCL_STUCK_AT_LOW_TIMEOUT 0xac ++#define DW_IC_SDA_STUCK_AT_LOW_TIMEOUT 0xb0 + #define DW_IC_COMP_PARAM_1 0xf4 + #define DW_IC_COMP_VERSION 0xf8 + #define DW_IC_SDA_HOLD_MIN_VERS 0x3131312A /* "111*" == v1.11* */ ++#define DW_IC_BUS_CLEAR_MIN_VERS 0x3230302A /* "200*" == v2.00* */ + #define DW_IC_COMP_TYPE 0xfc + #define DW_IC_COMP_TYPE_VALUE 0x44570140 /* "DW" + 0x0140 */ + +@@ -111,12 +114,14 @@ + + #define DW_IC_ENABLE_ENABLE BIT(0) + #define DW_IC_ENABLE_ABORT BIT(1) ++#define DW_IC_ENABLE_BUS_RECOVERY BIT(3) + + #define DW_IC_STATUS_ACTIVITY BIT(0) + #define DW_IC_STATUS_TFE BIT(2) + #define DW_IC_STATUS_RFNE BIT(3) + #define DW_IC_STATUS_MASTER_ACTIVITY BIT(5) + #define DW_IC_STATUS_SLAVE_ACTIVITY BIT(6) ++#define DW_IC_STATUS_SDA_STUCK_NOT_RECOVERED BIT(11) + + #define DW_IC_SDA_HOLD_RX_SHIFT 16 + #define DW_IC_SDA_HOLD_RX_MASK GENMASK(23, 16) +@@ -164,6 +169,7 @@ + #define ABRT_SLAVE_FLUSH_TXFIFO 13 + #define ABRT_SLAVE_ARBLOST 14 + #define ABRT_SLAVE_RD_INTX 15 ++#define ABRT_SLAVE_SDA_STUCK_AT_LOW 17 + + #define DW_IC_TX_ABRT_7B_ADDR_NOACK BIT(ABRT_7B_ADDR_NOACK) + #define DW_IC_TX_ABRT_10ADDR1_NOACK BIT(ABRT_10ADDR1_NOACK) +@@ -179,6 +185,7 @@ + #define DW_IC_RX_ABRT_SLAVE_RD_INTX BIT(ABRT_SLAVE_RD_INTX) + #define DW_IC_RX_ABRT_SLAVE_ARBLOST BIT(ABRT_SLAVE_ARBLOST) + #define DW_IC_RX_ABRT_SLAVE_FLUSH_TXFIFO BIT(ABRT_SLAVE_FLUSH_TXFIFO) ++#define DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW BIT(ABRT_SLAVE_SDA_STUCK_AT_LOW) + + #define DW_IC_TX_ABRT_NOACK (DW_IC_TX_ABRT_7B_ADDR_NOACK | \ + DW_IC_TX_ABRT_10ADDR1_NOACK | \ +--- a/drivers/i2c/busses/i2c-designware-master.c ++++ b/drivers/i2c/busses/i2c-designware-master.c +@@ -212,6 +212,7 @@ static int i2c_dw_set_timings_master(str + */ + static int i2c_dw_init_master(struct dw_i2c_dev *dev) + { ++ unsigned int timeout = 0; + int ret; + + ret = i2c_dw_acquire_lock(dev); +@@ -235,6 +236,17 @@ static int i2c_dw_init_master(struct dw_ + regmap_write(dev->map, DW_IC_HS_SCL_LCNT, dev->hs_lcnt); + } + ++ if (dev->master_cfg & DW_IC_CON_BUS_CLEAR_CTRL) { ++ /* Set a sensible timeout if not already configured */ ++ regmap_read(dev->map, DW_IC_SDA_STUCK_AT_LOW_TIMEOUT, &timeout); ++ if (timeout == ~0) { ++ /* Use 10ms as a timeout, which is 1000 cycles at 100kHz */ ++ timeout = i2c_dw_clk_rate(dev) * 10; /* clock rate is in kHz */ ++ regmap_write(dev->map, DW_IC_SDA_STUCK_AT_LOW_TIMEOUT, timeout); ++ regmap_write(dev->map, DW_IC_SCL_STUCK_AT_LOW_TIMEOUT, timeout); ++ } ++ } ++ + /* Write SDA hold time if supported */ + if (dev->sda_hold_time) + regmap_write(dev->map, DW_IC_SDA_HOLD, dev->sda_hold_time); +@@ -1071,6 +1083,7 @@ int i2c_dw_probe_master(struct dw_i2c_de + struct i2c_adapter *adap = &dev->adapter; + unsigned long irq_flags; + unsigned int ic_con; ++ unsigned int id_ver; + int ret; + + init_completion(&dev->cmd_complete); +@@ -1106,7 +1119,11 @@ int i2c_dw_probe_master(struct dw_i2c_de + if (ret) + return ret; + +- if (ic_con & DW_IC_CON_BUS_CLEAR_CTRL) ++ ret = regmap_read(dev->map, DW_IC_COMP_VERSION, &id_ver); ++ if (ret) ++ return ret; ++ ++ if (ic_con & DW_IC_CON_BUS_CLEAR_CTRL || id_ver >= DW_IC_BUS_CLEAR_MIN_VERS) + dev->master_cfg |= DW_IC_CON_BUS_CLEAR_CTRL; + + ret = dev->init(dev); |