diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c index d792c65d46ae..e4dfa2ec58bb 100644 --- a/drivers/i2c/busses/i2c-designware-common.c +++ b/drivers/i2c/busses/i2c-designware-common.c @@ -633,6 +633,14 @@ void __i2c_dw_disable(struct dw_i2c_dev *dev) abort_needed = (raw_intr_stats & DW_IC_INTR_MST_ON_HOLD) || (ic_stats & DW_IC_STATUS_MASTER_HOLD_TX_FIFO_EMPTY); + + /* + * If we are in target mode and there is activity, we should also + * trigger an abort to clear the internal state machines. + */ + if (dev->mode == DW_IC_SLAVE && (ic_stats & DW_IC_STATUS_SLAVE_ACTIVITY)) + abort_needed = true; + if (abort_needed) { if (!(enable & DW_IC_ENABLE_ENABLE)) { regmap_write(dev->map, DW_IC_ENABLE, DW_IC_ENABLE_ENABLE); diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index de929b91d5ea..7a301c8b604e 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -785,18 +785,25 @@ __i2c_dw_xfer_one_part(struct dw_i2c_dev *dev, struct i2c_msg *msgs, size_t num) * IC_RAW_INTR_STAT.MASTER_ON_HOLD holding SCL low. Check if * controller is still ACTIVE before disabling I2C. */ - if (i2c_dw_is_controller_active(dev)) - dev_err(dev->dev, "controller active\n"); - - /* - * We must disable the adapter before returning and signaling the end - * of the current transfer. Otherwise the hardware might continue - * generating interrupts which in turn causes a race condition with - * the following transfer. Needs some more investigation if the - * additional interrupts are a hardware bug or this driver doesn't - * handle them correctly yet. - */ - __i2c_dw_disable_nowait(dev); + if (i2c_dw_is_controller_active(dev)) { + /* + * If the controller is still active after the timeout, attempt a + * bus recovery to clear any potentially locked state. + */ + dev_err(dev->dev, "controller active after xfer, recovering\n"); + i2c_recover_bus(&dev->adapter); + i2c_dw_init(dev); + } else { + /* + * We must disable the adapter before returning and signaling the end + * of the current transfer. Otherwise the hardware might continue + * generating interrupts which in turn causes a race condition with + * the following transfer. Needs some more investigation if the + * additional interrupts are a hardware bug or this driver doesn't + * handle them correctly yet. + */ + __i2c_dw_disable_nowait(dev); + } if (dev->msg_err) return dev->msg_err;