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>
This commit is contained in:
Phil Elwell
2024-03-26 15:57:46 +00:00
committed by Dom Cobley
parent 0f08ad2e53
commit c2fdf1dad5
3 changed files with 38 additions and 1 deletions

View File

@@ -57,6 +57,8 @@ static char *abort_sources[] = {
"slave lost the bus while transmitting data to a remote master", "slave lost the bus while transmitting data to a remote master",
[ABRT_SLAVE_RD_INTX] = [ABRT_SLAVE_RD_INTX] =
"incorrect slave-transmitter mode configuration", "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) static int dw_reg_read(void *context, unsigned int reg, unsigned int *val)
@@ -593,8 +595,16 @@ int i2c_dw_wait_bus_not_busy(struct dw_i2c_dev *dev)
int i2c_dw_handle_tx_abort(struct dw_i2c_dev *dev) int i2c_dw_handle_tx_abort(struct dw_i2c_dev *dev)
{ {
unsigned long abort_source = dev->abort_source; unsigned long abort_source = dev->abort_source;
unsigned int reg;
int i; 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) { if (abort_source & DW_IC_TX_ABRT_NOACK) {
for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources)) for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources))
dev_dbg(dev->dev, dev_dbg(dev->dev,
@@ -609,6 +619,8 @@ int i2c_dw_handle_tx_abort(struct dw_i2c_dev *dev)
return -EAGAIN; return -EAGAIN;
else if (abort_source & DW_IC_TX_ABRT_GCALL_READ) else if (abort_source & DW_IC_TX_ABRT_GCALL_READ)
return -EINVAL; /* wrong msgs[] data */ return -EINVAL; /* wrong msgs[] data */
else if (abort_source & DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW)
return -EREMOTEIO;
else else
return -EIO; return -EIO;
} }

View File

@@ -79,9 +79,12 @@
#define DW_IC_TX_ABRT_SOURCE 0x80 #define DW_IC_TX_ABRT_SOURCE 0x80
#define DW_IC_ENABLE_STATUS 0x9c #define DW_IC_ENABLE_STATUS 0x9c
#define DW_IC_CLR_RESTART_DET 0xa8 #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_PARAM_1 0xf4
#define DW_IC_COMP_VERSION 0xf8 #define DW_IC_COMP_VERSION 0xf8
#define DW_IC_SDA_HOLD_MIN_VERS 0x3131312A /* "111*" == v1.11* */ #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 0xfc
#define DW_IC_COMP_TYPE_VALUE 0x44570140 /* "DW" + 0x0140 */ #define DW_IC_COMP_TYPE_VALUE 0x44570140 /* "DW" + 0x0140 */
@@ -109,13 +112,16 @@
DW_IC_INTR_RX_UNDER | \ DW_IC_INTR_RX_UNDER | \
DW_IC_INTR_RD_REQ) DW_IC_INTR_RD_REQ)
#define DW_IC_ENABLE_ENABLE BIT(0)
#define DW_IC_ENABLE_ABORT BIT(1) #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_ACTIVITY BIT(0)
#define DW_IC_STATUS_TFE BIT(2) #define DW_IC_STATUS_TFE BIT(2)
#define DW_IC_STATUS_RFNE BIT(3) #define DW_IC_STATUS_RFNE BIT(3)
#define DW_IC_STATUS_MASTER_ACTIVITY BIT(5) #define DW_IC_STATUS_MASTER_ACTIVITY BIT(5)
#define DW_IC_STATUS_SLAVE_ACTIVITY BIT(6) #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_SHIFT 16
#define DW_IC_SDA_HOLD_RX_MASK GENMASK(23, 16) #define DW_IC_SDA_HOLD_RX_MASK GENMASK(23, 16)
@@ -163,6 +169,7 @@
#define ABRT_SLAVE_FLUSH_TXFIFO 13 #define ABRT_SLAVE_FLUSH_TXFIFO 13
#define ABRT_SLAVE_ARBLOST 14 #define ABRT_SLAVE_ARBLOST 14
#define ABRT_SLAVE_RD_INTX 15 #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_7B_ADDR_NOACK BIT(ABRT_7B_ADDR_NOACK)
#define DW_IC_TX_ABRT_10ADDR1_NOACK BIT(ABRT_10ADDR1_NOACK) #define DW_IC_TX_ABRT_10ADDR1_NOACK BIT(ABRT_10ADDR1_NOACK)
@@ -178,6 +185,7 @@
#define DW_IC_RX_ABRT_SLAVE_RD_INTX BIT(ABRT_SLAVE_RD_INTX) #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_ARBLOST BIT(ABRT_SLAVE_ARBLOST)
#define DW_IC_RX_ABRT_SLAVE_FLUSH_TXFIFO BIT(ABRT_SLAVE_FLUSH_TXFIFO) #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 | \ #define DW_IC_TX_ABRT_NOACK (DW_IC_TX_ABRT_7B_ADDR_NOACK | \
DW_IC_TX_ABRT_10ADDR1_NOACK | \ DW_IC_TX_ABRT_10ADDR1_NOACK | \

View File

@@ -212,6 +212,7 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev)
*/ */
static int i2c_dw_init_master(struct dw_i2c_dev *dev) static int i2c_dw_init_master(struct dw_i2c_dev *dev)
{ {
unsigned int timeout = 0;
int ret; int ret;
ret = i2c_dw_acquire_lock(dev); ret = i2c_dw_acquire_lock(dev);
@@ -235,6 +236,17 @@ static int i2c_dw_init_master(struct dw_i2c_dev *dev)
regmap_write(dev->map, DW_IC_HS_SCL_LCNT, dev->hs_lcnt); 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 */ /* Write SDA hold time if supported */
if (dev->sda_hold_time) if (dev->sda_hold_time)
regmap_write(dev->map, DW_IC_SDA_HOLD, dev->sda_hold_time); regmap_write(dev->map, DW_IC_SDA_HOLD, dev->sda_hold_time);
@@ -976,6 +988,7 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev)
struct i2c_adapter *adap = &dev->adapter; struct i2c_adapter *adap = &dev->adapter;
unsigned long irq_flags; unsigned long irq_flags;
unsigned int ic_con; unsigned int ic_con;
unsigned int id_ver;
int ret; int ret;
init_completion(&dev->cmd_complete); init_completion(&dev->cmd_complete);
@@ -1011,7 +1024,11 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev)
if (ret) if (ret)
return 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; dev->master_cfg |= DW_IC_CON_BUS_CLEAR_CTRL;
ret = dev->init(dev); ret = dev->init(dev);