Skip to content

Commit 0e49686

Browse files
pelwellpopcornmix
authored andcommitted
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: #6057 Signed-off-by: Phil Elwell <[email protected]>
1 parent 5aa553a commit 0e49686

File tree

3 files changed

+38
-1
lines changed

3 files changed

+38
-1
lines changed

drivers/i2c/busses/i2c-designware-common.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ static char *abort_sources[] = {
5757
"slave lost the bus while transmitting data to a remote master",
5858
[ABRT_SLAVE_RD_INTX] =
5959
"incorrect slave-transmitter mode configuration",
60+
[ABRT_SLAVE_SDA_STUCK_AT_LOW] =
61+
"SDA stuck at low",
6062
};
6163

6264
static int dw_reg_read(void *context, unsigned int reg, unsigned int *val)
@@ -609,8 +611,16 @@ int i2c_dw_wait_bus_not_busy(struct dw_i2c_dev *dev)
609611
int i2c_dw_handle_tx_abort(struct dw_i2c_dev *dev)
610612
{
611613
unsigned long abort_source = dev->abort_source;
614+
unsigned int reg;
612615
int i;
613616

617+
if (abort_source & DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW) {
618+
regmap_write(dev->map, DW_IC_ENABLE,
619+
DW_IC_ENABLE_ENABLE | DW_IC_ENABLE_BUS_RECOVERY);
620+
regmap_read_poll_timeout(dev->map, DW_IC_ENABLE, reg,
621+
!(reg & DW_IC_ENABLE_BUS_RECOVERY),
622+
1100, 200000);
623+
}
614624
if (abort_source & DW_IC_TX_ABRT_NOACK) {
615625
for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources))
616626
dev_dbg(dev->dev,
@@ -625,6 +635,8 @@ int i2c_dw_handle_tx_abort(struct dw_i2c_dev *dev)
625635
return -EAGAIN;
626636
else if (abort_source & DW_IC_TX_ABRT_GCALL_READ)
627637
return -EINVAL; /* wrong msgs[] data */
638+
else if (abort_source & DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW)
639+
return -EREMOTEIO;
628640
else
629641
return -EIO;
630642
}

drivers/i2c/busses/i2c-designware-core.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,12 @@
7979
#define DW_IC_TX_ABRT_SOURCE 0x80
8080
#define DW_IC_ENABLE_STATUS 0x9c
8181
#define DW_IC_CLR_RESTART_DET 0xa8
82+
#define DW_IC_SCL_STUCK_AT_LOW_TIMEOUT 0xac
83+
#define DW_IC_SDA_STUCK_AT_LOW_TIMEOUT 0xb0
8284
#define DW_IC_COMP_PARAM_1 0xf4
8385
#define DW_IC_COMP_VERSION 0xf8
8486
#define DW_IC_SDA_HOLD_MIN_VERS 0x3131312A /* "111*" == v1.11* */
87+
#define DW_IC_BUS_CLEAR_MIN_VERS 0x3230302A /* "200*" == v2.00* */
8588
#define DW_IC_COMP_TYPE 0xfc
8689
#define DW_IC_COMP_TYPE_VALUE 0x44570140 /* "DW" + 0x0140 */
8790

@@ -109,14 +112,17 @@
109112
DW_IC_INTR_RX_UNDER | \
110113
DW_IC_INTR_RD_REQ)
111114

115+
#define DW_IC_ENABLE_ENABLE BIT(0)
112116
#define DW_IC_ENABLE_ABORT BIT(1)
117+
#define DW_IC_ENABLE_BUS_RECOVERY BIT(3)
113118

114119
#define DW_IC_STATUS_ACTIVITY BIT(0)
115120
#define DW_IC_STATUS_TFE BIT(2)
116121
#define DW_IC_STATUS_RFNE BIT(3)
117122
#define DW_IC_STATUS_MASTER_ACTIVITY BIT(5)
118123
#define DW_IC_STATUS_SLAVE_ACTIVITY BIT(6)
119124
#define DW_IC_STATUS_MASTER_HOLD_TX_FIFO_EMPTY BIT(7)
125+
#define DW_IC_STATUS_SDA_STUCK_NOT_RECOVERED BIT(11)
120126

121127
#define DW_IC_SDA_HOLD_RX_SHIFT 16
122128
#define DW_IC_SDA_HOLD_RX_MASK GENMASK(23, 16)
@@ -164,6 +170,7 @@
164170
#define ABRT_SLAVE_FLUSH_TXFIFO 13
165171
#define ABRT_SLAVE_ARBLOST 14
166172
#define ABRT_SLAVE_RD_INTX 15
173+
#define ABRT_SLAVE_SDA_STUCK_AT_LOW 17
167174

168175
#define DW_IC_TX_ABRT_7B_ADDR_NOACK BIT(ABRT_7B_ADDR_NOACK)
169176
#define DW_IC_TX_ABRT_10ADDR1_NOACK BIT(ABRT_10ADDR1_NOACK)
@@ -179,6 +186,7 @@
179186
#define DW_IC_RX_ABRT_SLAVE_RD_INTX BIT(ABRT_SLAVE_RD_INTX)
180187
#define DW_IC_RX_ABRT_SLAVE_ARBLOST BIT(ABRT_SLAVE_ARBLOST)
181188
#define DW_IC_RX_ABRT_SLAVE_FLUSH_TXFIFO BIT(ABRT_SLAVE_FLUSH_TXFIFO)
189+
#define DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW BIT(ABRT_SLAVE_SDA_STUCK_AT_LOW)
182190

183191
#define DW_IC_TX_ABRT_NOACK (DW_IC_TX_ABRT_7B_ADDR_NOACK | \
184192
DW_IC_TX_ABRT_10ADDR1_NOACK | \

drivers/i2c/busses/i2c-designware-master.c

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev)
215215
*/
216216
static int i2c_dw_init_master(struct dw_i2c_dev *dev)
217217
{
218+
unsigned int timeout = 0;
218219
int ret;
219220

220221
ret = i2c_dw_acquire_lock(dev);
@@ -238,6 +239,17 @@ static int i2c_dw_init_master(struct dw_i2c_dev *dev)
238239
regmap_write(dev->map, DW_IC_HS_SCL_LCNT, dev->hs_lcnt);
239240
}
240241

242+
if (dev->master_cfg & DW_IC_CON_BUS_CLEAR_CTRL) {
243+
/* Set a sensible timeout if not already configured */
244+
regmap_read(dev->map, DW_IC_SDA_STUCK_AT_LOW_TIMEOUT, &timeout);
245+
if (timeout == ~0) {
246+
/* Use 10ms as a timeout, which is 1000 cycles at 100kHz */
247+
timeout = i2c_dw_clk_rate(dev) * 10; /* clock rate is in kHz */
248+
regmap_write(dev->map, DW_IC_SDA_STUCK_AT_LOW_TIMEOUT, timeout);
249+
regmap_write(dev->map, DW_IC_SCL_STUCK_AT_LOW_TIMEOUT, timeout);
250+
}
251+
}
252+
241253
/* Write SDA hold time if supported */
242254
if (dev->sda_hold_time)
243255
regmap_write(dev->map, DW_IC_SDA_HOLD, dev->sda_hold_time);
@@ -1074,6 +1086,7 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev)
10741086
struct i2c_adapter *adap = &dev->adapter;
10751087
unsigned long irq_flags;
10761088
unsigned int ic_con;
1089+
unsigned int id_ver;
10771090
int ret;
10781091

10791092
init_completion(&dev->cmd_complete);
@@ -1109,7 +1122,11 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev)
11091122
if (ret)
11101123
return ret;
11111124

1112-
if (ic_con & DW_IC_CON_BUS_CLEAR_CTRL)
1125+
ret = regmap_read(dev->map, DW_IC_COMP_VERSION, &id_ver);
1126+
if (ret)
1127+
return ret;
1128+
1129+
if (ic_con & DW_IC_CON_BUS_CLEAR_CTRL || id_ver >= DW_IC_BUS_CLEAR_MIN_VERS)
11131130
dev->master_cfg |= DW_IC_CON_BUS_CLEAR_CTRL;
11141131

11151132
ret = dev->init(dev);

0 commit comments

Comments
 (0)