* [PATCH] i2c: designware: Handle active slave and shutdown cleanly
@ 2026-04-23 0:28 William A. Kennington III
2026-04-23 0:51 ` [PATCH v2] " William A. Kennington III
0 siblings, 1 reply; 4+ messages in thread
From: William A. Kennington III @ 2026-04-23 0:28 UTC (permalink / raw)
To: Mika Westerberg, Andy Shevchenko, Jan Dabros, Andi Shyti
Cc: William A. Kennington III, linux-i2c, linux-kernel
From: "William A. Kennington III" <wak@google.com>
When the I2C master attempts a new transaction while the slave
controller is shutting down or restarting, it can lead to bus lockups
and system bootloops if the hardware enters an inconsistent state.
Address this by ensuring that the internal state machines are properly
cleared when disabling the controller if slave activity is detected.
Additionally, add a shutdown hook that gracefully sets the slave
disable bit before disabling the controller. This guarantees that any
incoming requests from the master are immediately NACKed during
shutdown, preventing the bus from hanging.
Signed-off-by: William A. Kennington III <wak@google.com>
(cherry picked from src/hw/icebreaker commit 2e825fbffc3b20b5148dde046cb56346ffbe301b)
---
drivers/i2c/busses/i2c-designware-common.c | 32 +++++++++++++++++++++
drivers/i2c/busses/i2c-designware-core.h | 1 +
drivers/i2c/busses/i2c-designware-master.c | 32 ++++++++++++---------
drivers/i2c/busses/i2c-designware-pcidrv.c | 15 +++++++++-
drivers/i2c/busses/i2c-designware-platdrv.c | 13 +++++++++
5 files changed, 79 insertions(+), 14 deletions(-)
diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c
index 4dc57fd56170..31394d8fe612 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 slave 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);
@@ -1028,5 +1036,29 @@ EXPORT_GPL_DEV_PM_OPS(i2c_dw_dev_pm_ops) = {
RUNTIME_PM_OPS(i2c_dw_runtime_suspend, i2c_dw_runtime_resume, NULL)
};
+void i2c_dw_shutdown(struct dw_i2c_dev *dev)
+{
+ unsigned int con;
+
+ /*
+ * We only need to handle shutdown for slave mode to ensure
+ * we NACK any incoming master requests. Master mode cleanup
+ * is handled after each transfer in i2c_dw_xfer.
+ */
+ if (dev->mode != DW_IC_SLAVE)
+ return;
+
+ /*
+ * To quickly NACK the master during shutdown, we set the slave
+ * disable bit while the controller is still enabled.
+ */
+ regmap_read(dev->map, DW_IC_CON, &con);
+ con |= DW_IC_CON_SLAVE_DISABLE;
+ regmap_write(dev->map, DW_IC_CON, con);
+
+ i2c_dw_disable(dev);
+}
+EXPORT_SYMBOL_GPL(i2c_dw_shutdown);
+
MODULE_DESCRIPTION("Synopsys DesignWare I2C bus adapter core");
MODULE_LICENSE("GPL");
diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h
index 9d8d104cc391..8b422249acbd 100644
--- a/drivers/i2c/busses/i2c-designware-core.h
+++ b/drivers/i2c/busses/i2c-designware-core.h
@@ -393,6 +393,7 @@ static inline void __i2c_dw_read_intr_mask(struct dw_i2c_dev *dev,
void __i2c_dw_disable(struct dw_i2c_dev *dev);
void i2c_dw_disable(struct dw_i2c_dev *dev);
+void i2c_dw_shutdown(struct dw_i2c_dev *dev);
extern void i2c_dw_configure_master(struct dw_i2c_dev *dev);
extern int i2c_dw_probe_master(struct dw_i2c_dev *dev);
diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c
index de929b91d5ea..5b3505faa352 100644
--- a/drivers/i2c/busses/i2c-designware-master.c
+++ b/drivers/i2c/busses/i2c-designware-master.c
@@ -785,19 +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;
diff --git a/drivers/i2c/busses/i2c-designware-pcidrv.c b/drivers/i2c/busses/i2c-designware-pcidrv.c
index f21f9877c040..87074655c0e7 100644
--- a/drivers/i2c/busses/i2c-designware-pcidrv.c
+++ b/drivers/i2c/busses/i2c-designware-pcidrv.c
@@ -356,11 +356,24 @@ static const struct pci_device_id i2c_designware_pci_ids[] = {
};
MODULE_DEVICE_TABLE(pci, i2c_designware_pci_ids);
+static void i2c_dw_pci_shutdown(struct pci_dev *pdev)
+{
+ struct dw_i2c_dev *i_dev = pci_get_drvdata(pdev);
+
+ if (!i_dev)
+ return;
+
+ pm_runtime_disable(&pdev->dev);
+ if (!pm_runtime_status_suspended(&pdev->dev))
+ i2c_dw_shutdown(i_dev);
+}
+
static struct pci_driver dw_i2c_driver = {
.name = DRIVER_NAME,
.probe = i2c_dw_pci_probe,
.remove = i2c_dw_pci_remove,
- .driver = {
+ .shutdown = i2c_dw_pci_shutdown,
+ .driver = {
.pm = pm_ptr(&i2c_dw_dev_pm_ops),
},
.id_table = i2c_designware_pci_ids,
diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c
index 3351c4a9ef11..7ff7c1631e64 100644
--- a/drivers/i2c/busses/i2c-designware-platdrv.c
+++ b/drivers/i2c/busses/i2c-designware-platdrv.c
@@ -289,9 +289,22 @@ static const struct platform_device_id dw_i2c_platform_ids[] = {
};
MODULE_DEVICE_TABLE(platform, dw_i2c_platform_ids);
+static void dw_i2c_plat_shutdown(struct platform_device *pdev)
+{
+ struct dw_i2c_dev *i_dev = platform_get_drvdata(pdev);
+
+ if (!i_dev)
+ return;
+
+ pm_runtime_disable(&pdev->dev);
+ if (!pm_runtime_status_suspended(&pdev->dev))
+ i2c_dw_shutdown(i_dev);
+}
+
static struct platform_driver dw_i2c_driver = {
.probe = dw_i2c_plat_probe,
.remove = dw_i2c_plat_remove,
+ .shutdown = dw_i2c_plat_shutdown,
.driver = {
.name = "i2c_designware",
.of_match_table = dw_i2c_of_match,
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH v2] i2c: designware: Handle active slave and shutdown cleanly
2026-04-23 0:28 [PATCH] i2c: designware: Handle active slave and shutdown cleanly William A. Kennington III
@ 2026-04-23 0:51 ` William A. Kennington III
2026-04-23 6:13 ` Mika Westerberg
2026-04-23 7:43 ` Andy Shevchenko
0 siblings, 2 replies; 4+ messages in thread
From: William A. Kennington III @ 2026-04-23 0:51 UTC (permalink / raw)
To: Mika Westerberg, Andy Shevchenko, Jan Dabros, Andi Shyti
Cc: William A. Kennington III, linux-i2c, linux-kernel
When the I2C master attempts a new transaction while the slave
controller is shutting down or restarting, it can lead to bus lockups
and system bootloops if the hardware enters an inconsistent state.
Address this by ensuring that the internal state machines are properly
cleared when disabling the controller if slave activity is detected.
Additionally, add a shutdown hook that gracefully sets the slave
disable bit before disabling the controller. This guarantees that any
incoming requests from the master are immediately NACKed during
shutdown, preventing the bus from hanging.
Signed-off-by: William A. Kennington III <william@wkennington.com>
---
Changes since V1:
- Fix description footers
- Fix emails
drivers/i2c/busses/i2c-designware-common.c | 32 +++++++++++++++++++++
drivers/i2c/busses/i2c-designware-core.h | 1 +
drivers/i2c/busses/i2c-designware-master.c | 32 ++++++++++++---------
drivers/i2c/busses/i2c-designware-pcidrv.c | 15 +++++++++-
drivers/i2c/busses/i2c-designware-platdrv.c | 13 +++++++++
5 files changed, 79 insertions(+), 14 deletions(-)
diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c
index 4dc57fd56170..31394d8fe612 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 slave 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);
@@ -1028,5 +1036,29 @@ EXPORT_GPL_DEV_PM_OPS(i2c_dw_dev_pm_ops) = {
RUNTIME_PM_OPS(i2c_dw_runtime_suspend, i2c_dw_runtime_resume, NULL)
};
+void i2c_dw_shutdown(struct dw_i2c_dev *dev)
+{
+ unsigned int con;
+
+ /*
+ * We only need to handle shutdown for slave mode to ensure
+ * we NACK any incoming master requests. Master mode cleanup
+ * is handled after each transfer in i2c_dw_xfer.
+ */
+ if (dev->mode != DW_IC_SLAVE)
+ return;
+
+ /*
+ * To quickly NACK the master during shutdown, we set the slave
+ * disable bit while the controller is still enabled.
+ */
+ regmap_read(dev->map, DW_IC_CON, &con);
+ con |= DW_IC_CON_SLAVE_DISABLE;
+ regmap_write(dev->map, DW_IC_CON, con);
+
+ i2c_dw_disable(dev);
+}
+EXPORT_SYMBOL_GPL(i2c_dw_shutdown);
+
MODULE_DESCRIPTION("Synopsys DesignWare I2C bus adapter core");
MODULE_LICENSE("GPL");
diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h
index 9d8d104cc391..8b422249acbd 100644
--- a/drivers/i2c/busses/i2c-designware-core.h
+++ b/drivers/i2c/busses/i2c-designware-core.h
@@ -393,6 +393,7 @@ static inline void __i2c_dw_read_intr_mask(struct dw_i2c_dev *dev,
void __i2c_dw_disable(struct dw_i2c_dev *dev);
void i2c_dw_disable(struct dw_i2c_dev *dev);
+void i2c_dw_shutdown(struct dw_i2c_dev *dev);
extern void i2c_dw_configure_master(struct dw_i2c_dev *dev);
extern int i2c_dw_probe_master(struct dw_i2c_dev *dev);
diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c
index de929b91d5ea..5b3505faa352 100644
--- a/drivers/i2c/busses/i2c-designware-master.c
+++ b/drivers/i2c/busses/i2c-designware-master.c
@@ -785,19 +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;
diff --git a/drivers/i2c/busses/i2c-designware-pcidrv.c b/drivers/i2c/busses/i2c-designware-pcidrv.c
index f21f9877c040..87074655c0e7 100644
--- a/drivers/i2c/busses/i2c-designware-pcidrv.c
+++ b/drivers/i2c/busses/i2c-designware-pcidrv.c
@@ -356,11 +356,24 @@ static const struct pci_device_id i2c_designware_pci_ids[] = {
};
MODULE_DEVICE_TABLE(pci, i2c_designware_pci_ids);
+static void i2c_dw_pci_shutdown(struct pci_dev *pdev)
+{
+ struct dw_i2c_dev *i_dev = pci_get_drvdata(pdev);
+
+ if (!i_dev)
+ return;
+
+ pm_runtime_disable(&pdev->dev);
+ if (!pm_runtime_status_suspended(&pdev->dev))
+ i2c_dw_shutdown(i_dev);
+}
+
static struct pci_driver dw_i2c_driver = {
.name = DRIVER_NAME,
.probe = i2c_dw_pci_probe,
.remove = i2c_dw_pci_remove,
- .driver = {
+ .shutdown = i2c_dw_pci_shutdown,
+ .driver = {
.pm = pm_ptr(&i2c_dw_dev_pm_ops),
},
.id_table = i2c_designware_pci_ids,
diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c
index 3351c4a9ef11..7ff7c1631e64 100644
--- a/drivers/i2c/busses/i2c-designware-platdrv.c
+++ b/drivers/i2c/busses/i2c-designware-platdrv.c
@@ -289,9 +289,22 @@ static const struct platform_device_id dw_i2c_platform_ids[] = {
};
MODULE_DEVICE_TABLE(platform, dw_i2c_platform_ids);
+static void dw_i2c_plat_shutdown(struct platform_device *pdev)
+{
+ struct dw_i2c_dev *i_dev = platform_get_drvdata(pdev);
+
+ if (!i_dev)
+ return;
+
+ pm_runtime_disable(&pdev->dev);
+ if (!pm_runtime_status_suspended(&pdev->dev))
+ i2c_dw_shutdown(i_dev);
+}
+
static struct platform_driver dw_i2c_driver = {
.probe = dw_i2c_plat_probe,
.remove = dw_i2c_plat_remove,
+ .shutdown = dw_i2c_plat_shutdown,
.driver = {
.name = "i2c_designware",
.of_match_table = dw_i2c_of_match,
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related [flat|nested] 4+ messages in thread* Re: [PATCH v2] i2c: designware: Handle active slave and shutdown cleanly
2026-04-23 0:51 ` [PATCH v2] " William A. Kennington III
@ 2026-04-23 6:13 ` Mika Westerberg
2026-04-23 7:43 ` Andy Shevchenko
1 sibling, 0 replies; 4+ messages in thread
From: Mika Westerberg @ 2026-04-23 6:13 UTC (permalink / raw)
To: William A. Kennington III
Cc: Andy Shevchenko, Jan Dabros, Andi Shyti, linux-i2c, linux-kernel
Hi,
On Wed, Apr 22, 2026 at 05:51:05PM -0700, William A. Kennington III wrote:
> When the I2C master attempts a new transaction while the slave
> controller is shutting down or restarting, it can lead to bus lockups
> and system bootloops if the hardware enters an inconsistent state.
>
> Address this by ensuring that the internal state machines are properly
> cleared when disabling the controller if slave activity is detected.
>
> Additionally, add a shutdown hook that gracefully sets the slave
> disable bit before disabling the controller. This guarantees that any
> incoming requests from the master are immediately NACKed during
> shutdown, preventing the bus from hanging.
Can you split this into two patches? One that deals with the host side and
the other that deals with the target shutdown.
The code itself looks good to me.
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2] i2c: designware: Handle active slave and shutdown cleanly
2026-04-23 0:51 ` [PATCH v2] " William A. Kennington III
2026-04-23 6:13 ` Mika Westerberg
@ 2026-04-23 7:43 ` Andy Shevchenko
1 sibling, 0 replies; 4+ messages in thread
From: Andy Shevchenko @ 2026-04-23 7:43 UTC (permalink / raw)
To: William A. Kennington III
Cc: Mika Westerberg, Jan Dabros, Andi Shyti, linux-i2c, linux-kernel
On Wed, Apr 22, 2026 at 05:51:05PM -0700, William A. Kennington III wrote:
> When the I2C master attempts a new transaction while the slave
> controller is shutting down or restarting, it can lead to bus lockups
> and system bootloops if the hardware enters an inconsistent state.
>
> Address this by ensuring that the internal state machines are properly
> cleared when disabling the controller if slave activity is detected.
>
> Additionally, add a shutdown hook that gracefully sets the slave
> disable bit before disabling the controller. This guarantees that any
> incoming requests from the master are immediately NACKed during
> shutdown, preventing the bus from hanging.
Do not send a new version:
- inside the same email thread as the previous one(s)
- too early, give 24h+ _at least_ to others to have a chance to look into it
According to the split I think you can do it in a way that it goes like this
- introducing shutdown exported function (and use it as is)
- convert PCI driver to use it
- convert platform driver to use it
- do something for target case
...
> static struct pci_driver dw_i2c_driver = {
> .name = DRIVER_NAME,
> .probe = i2c_dw_pci_probe,
> .remove = i2c_dw_pci_remove,
> - .driver = {
> + .driver = {
Stray change.
...
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-04-23 7:43 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-23 0:28 [PATCH] i2c: designware: Handle active slave and shutdown cleanly William A. Kennington III
2026-04-23 0:51 ` [PATCH v2] " William A. Kennington III
2026-04-23 6:13 ` Mika Westerberg
2026-04-23 7:43 ` Andy Shevchenko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox