Skip to content

Commit 2409205

Browse files
fallenwsakernel
authored andcommitted
i2c: designware: fix __i2c_dw_disable() in case master is holding SCL low
The DesignWare IP can be synthesized with the IC_EMPTYFIFO_HOLD_MASTER_EN parameter. In this case, when the TX FIFO gets empty and the last command didn't have the STOP bit (IC_DATA_CMD[9]), the controller will hold SCL low until a new command is pushed into the TX FIFO or the transfer is aborted. When the controller is holding SCL low, it cannot be disabled. The transfer must first be aborted. Also, the bus recovery won't work because SCL is held low by the master. Check if the master is holding SCL low in __i2c_dw_disable() before trying to disable the controller. If SCL is held low, an abort is initiated. When the abort is done, then proceed with disabling the controller. This whole situation can happen for instance during SMBus read data block if the slave just responds with "byte count == 0". This puts the driver in an unrecoverable state, because the controller is holding SCL low and the current __i2c_dw_disable() procedure is not working. In this situation only a SoC reset can fix the i2c bus. Co-developed-by: Jonathan Borne <[email protected]> Signed-off-by: Jonathan Borne <[email protected]> Signed-off-by: Yann Sionneau <[email protected]> Acked-by: Jarkko Nikula <[email protected]> Signed-off-by: Wolfram Sang <[email protected]>
1 parent 3914784 commit 2409205

File tree

2 files changed

+20
-0
lines changed

2 files changed

+20
-0
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,25 @@ int i2c_dw_set_sda_hold(struct dw_i2c_dev *dev)
441441

442442
void __i2c_dw_disable(struct dw_i2c_dev *dev)
443443
{
444+
unsigned int raw_intr_stats;
445+
unsigned int enable;
444446
int timeout = 100;
447+
bool abort_needed;
445448
unsigned int status;
449+
int ret;
450+
451+
regmap_read(dev->map, DW_IC_RAW_INTR_STAT, &raw_intr_stats);
452+
regmap_read(dev->map, DW_IC_ENABLE, &enable);
453+
454+
abort_needed = raw_intr_stats & DW_IC_INTR_MST_ON_HOLD;
455+
if (abort_needed) {
456+
regmap_write(dev->map, DW_IC_ENABLE, enable | DW_IC_ENABLE_ABORT);
457+
ret = regmap_read_poll_timeout(dev->map, DW_IC_ENABLE, enable,
458+
!(enable & DW_IC_ENABLE_ABORT), 10,
459+
100);
460+
if (ret)
461+
dev_err(dev->dev, "timeout while trying to abort current transfer\n");
462+
}
446463

447464
do {
448465
__i2c_dw_disable_nowait(dev);

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
#define DW_IC_INTR_START_DET BIT(10)
9999
#define DW_IC_INTR_GEN_CALL BIT(11)
100100
#define DW_IC_INTR_RESTART_DET BIT(12)
101+
#define DW_IC_INTR_MST_ON_HOLD BIT(13)
101102

102103
#define DW_IC_INTR_DEFAULT_MASK (DW_IC_INTR_RX_FULL | \
103104
DW_IC_INTR_TX_ABRT | \
@@ -108,6 +109,8 @@
108109
DW_IC_INTR_RX_UNDER | \
109110
DW_IC_INTR_RD_REQ)
110111

112+
#define DW_IC_ENABLE_ABORT BIT(1)
113+
111114
#define DW_IC_STATUS_ACTIVITY BIT(0)
112115
#define DW_IC_STATUS_TFE BIT(2)
113116
#define DW_IC_STATUS_RFNE BIT(3)

0 commit comments

Comments
 (0)