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);
* 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;