[PATCH] usb: typec: tipd: mask interrupt across suspend to fix IRQ storm
From: jianfeng . gao
Date: Fri Sep 18 2026 - 01:24:38 EST
From: Gao Jianfeng <jianfeng.gao@xxxxxxxxx>
When tps->wakeup is false, tps6598x_suspend() left the IRQ enabled
because disable_irq() was gated inside the "if (tps->wakeup)" branch.
During hibernation the parent I2C (designware) adapter suspends right
after the child's freeze callback, so with the IRQ still enabled and the
type-C line still asserted, tps6598x_interrupt() keeps re-firing. Its
register reads now fail with -ESHUTDOWN, the handler bails with
event1/event2 == 0 and returns IRQ_NONE, so the line is never acked and
the handler loops, flooding the log with "failed to read ..." errors
until the noirq phase finally masks interrupts.
Mask the interrupt unconditionally in suspend and re-enable it in
resume, keeping enable_irq()/disable_irq() balanced. The wake-source
arming stays conditional on tps->wakeup.
The resume error paths jump to a common exit so the IRQ is re-enabled
(and the wake source disarmed) even when the controller fails to come
back. The line is requested with IRQF_SHARED, so a leaked disable_irq()
would mask it for every other device sharing it, and the imbalance would
never be recovered by a later resume.
Fixes: 481735d64794 ("usb: typec: tipd: Support wakeup")
Cc: stable@xxxxxxxxxxxxxxx
Co-developed-by: Li Yifan <yifan2.li@xxxxxxxxx>
Signed-off-by: Li Yifan <yifan2.li@xxxxxxxxx>
Signed-off-by: Gao Jianfeng <jianfeng.gao@xxxxxxxxx>
Acked-by: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
Tested-by: Munirah Izyani Mohammad Amin <munirah.izyani.mohammad.amin@xxxxxxxxx>
---
drivers/usb/typec/tipd/core.c | 38 ++++++++++++++++++++++++++---------
1 file changed, 28 insertions(+), 10 deletions(-)
diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c
index 9248f0dd3f49..5ed573e30479 100644
--- a/drivers/usb/typec/tipd/core.c
+++ b/drivers/usb/typec/tipd/core.c
@@ -1972,12 +1972,19 @@ static int __maybe_unused tps6598x_suspend(struct device *dev)
struct i2c_client *client = to_i2c_client(dev);
struct tps6598x *tps = i2c_get_clientdata(client);
- if (tps->wakeup) {
+ /*
+ * Mask the interrupt before the parent I2C controller is suspended:
+ * otherwise a still-asserted level IRQ keeps re-entering the handler,
+ * whose register reads then fail with -ESHUTDOWN, producing an
+ * interrupt storm until the noirq phase globally disables interrupts.
+ */
+ if (client->irq)
disable_irq(client->irq);
+
+ if (tps->wakeup)
enable_irq_wake(client->irq);
- } else if (tps->reset) {
+ else if (tps->reset)
gpiod_set_value_cansleep(tps->reset, 1);
- }
if (!client->irq)
cancel_delayed_work_sync(&tps->wq_poll);
@@ -1993,18 +2000,16 @@ static int __maybe_unused tps6598x_resume(struct device *dev)
ret = tps6598x_check_mode(tps);
if (ret < 0)
- return ret;
+ goto out_enable_irq;
if (ret == TPS_MODE_PTCH) {
ret = tps->data->init(tps);
if (ret)
- return ret;
+ goto out_enable_irq;
}
+ ret = 0;
- if (tps->wakeup) {
- disable_irq_wake(client->irq);
- enable_irq(client->irq);
- } else if (tps->reset) {
+ if (!tps->wakeup && tps->reset) {
gpiod_set_value_cansleep(tps->reset, 0);
msleep(TPS_SETUP_MS);
}
@@ -2013,7 +2018,20 @@ static int __maybe_unused tps6598x_resume(struct device *dev)
queue_delayed_work(system_power_efficient_wq, &tps->wq_poll,
msecs_to_jiffies(POLL_INTERVAL));
- return 0;
+out_enable_irq:
+ /*
+ * Unwind suspend unconditionally, including on the error paths above:
+ * the line is requested with IRQF_SHARED, so a leaked disable_irq()
+ * would mask it for every other device on it, and the imbalance would
+ * never be recovered by a later resume.
+ */
+ if (tps->wakeup)
+ disable_irq_wake(client->irq);
+
+ if (client->irq)
+ enable_irq(client->irq);
+
+ return ret;
}
static const struct dev_pm_ops tps6598x_pm_ops = {
--
2.34.1