[PATCH v2] mfd: mt6397-irq: Fix PM notifier and IRQ domain teardown
Yuho Choi
dbgh9129 at gmail.com
Fri Jun 19 09:56:49 PDT 2026
mt6397_irq_init() registers a PM notifier and creates an irq_domain. Both
objects point back to the devm-managed mt6397_chip.
The notifier was not unregistered on probe failure or unbind, so a later PM
transition could dereference freed chip state. The irq_domain had the
opposite lifetime problem: it could be removed while the devm-managed
parent IRQ was still active, but it was leaked on successful unbind.
Manage both lifetimes with devm actions. Register the domain cleanup before
requesting the parent IRQ, so devres frees the parent IRQ before removing
child mappings and the irq_domain. This keeps the domain alive while the
parent IRQ handler can still run.
Also serialize PM notifier mask writes with irqlock. While suspend is in
progress, keep wake_mask programmed in hardware even if a concurrent IRQ
sync updates irq_masks_cur in software.
Fixes: a4872e80ce7d ("mfd: mt6397: Extract IRQ related code from core driver")
Fixes: 4e2e7cfec13a ("mfd: mt6397: Modify suspend/resume behavior")
Signed-off-by: Yuho Choi <dbgh9129 at gmail.com>
---
Changes in v2:
- Use devm actions for PM notifier and irq_domain cleanup.
- Remove direct irq_domain_remove() calls from failure paths.
- Serialize PM notifier mask writes with irqlock.
---
drivers/mfd/mt6397-core.c | 4 +-
drivers/mfd/mt6397-irq.c | 85 +++++++++++++++++++++++----------
include/linux/mfd/mt6397/core.h | 1 +
3 files changed, 63 insertions(+), 27 deletions(-)
diff --git a/drivers/mfd/mt6397-core.c b/drivers/mfd/mt6397-core.c
index 1bdacda9a933..ff1e393202ee 100644
--- a/drivers/mfd/mt6397-core.c
+++ b/drivers/mfd/mt6397-core.c
@@ -389,10 +389,8 @@ static int mt6397_probe(struct platform_device *pdev)
ret = devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE,
pmic_core->cells, pmic_core->cell_size,
NULL, 0, pmic->irq_domain);
- if (ret) {
- irq_domain_remove(pmic->irq_domain);
+ if (ret)
dev_err(&pdev->dev, "failed to add child devices: %d\n", ret);
- }
return ret;
}
diff --git a/drivers/mfd/mt6397-irq.c b/drivers/mfd/mt6397-irq.c
index 5d2e5459f744..4be74d8325cd 100644
--- a/drivers/mfd/mt6397-irq.c
+++ b/drivers/mfd/mt6397-irq.c
@@ -25,17 +25,23 @@ static void mt6397_irq_lock(struct irq_data *data)
mutex_lock(&mt6397->irqlock);
}
+static void mt6397_irq_write_masks(struct mt6397_chip *mt6397,
+ const u16 *masks)
+{
+ regmap_write(mt6397->regmap, mt6397->int_con[0], masks[0]);
+ regmap_write(mt6397->regmap, mt6397->int_con[1], masks[1]);
+ if (mt6397->int_con[2])
+ regmap_write(mt6397->regmap, mt6397->int_con[2], masks[2]);
+}
+
static void mt6397_irq_sync_unlock(struct irq_data *data)
{
struct mt6397_chip *mt6397 = irq_data_get_irq_chip_data(data);
+ const u16 *masks;
- regmap_write(mt6397->regmap, mt6397->int_con[0],
- mt6397->irq_masks_cur[0]);
- regmap_write(mt6397->regmap, mt6397->int_con[1],
- mt6397->irq_masks_cur[1]);
- if (mt6397->int_con[2])
- regmap_write(mt6397->regmap, mt6397->int_con[2],
- mt6397->irq_masks_cur[2]);
+ masks = mt6397->irq_suspended ? mt6397->wake_mask :
+ mt6397->irq_masks_cur;
+ mt6397_irq_write_masks(mt6397, masks);
mutex_unlock(&mt6397->irqlock);
}
@@ -141,24 +147,20 @@ static int mt6397_irq_pm_notifier(struct notifier_block *notifier,
switch (pm_event) {
case PM_SUSPEND_PREPARE:
- regmap_write(chip->regmap,
- chip->int_con[0], chip->wake_mask[0]);
- regmap_write(chip->regmap,
- chip->int_con[1], chip->wake_mask[1]);
- if (chip->int_con[2])
- regmap_write(chip->regmap,
- chip->int_con[2], chip->wake_mask[2]);
+ mutex_lock(&chip->irqlock);
+ chip->irq_suspended = true;
+ mt6397_irq_write_masks(chip, chip->wake_mask);
+ mutex_unlock(&chip->irqlock);
+
enable_irq_wake(chip->irq);
break;
case PM_POST_SUSPEND:
- regmap_write(chip->regmap,
- chip->int_con[0], chip->irq_masks_cur[0]);
- regmap_write(chip->regmap,
- chip->int_con[1], chip->irq_masks_cur[1]);
- if (chip->int_con[2])
- regmap_write(chip->regmap,
- chip->int_con[2], chip->irq_masks_cur[2]);
+ mutex_lock(&chip->irqlock);
+ chip->irq_suspended = false;
+ mt6397_irq_write_masks(chip, chip->irq_masks_cur);
+ mutex_unlock(&chip->irqlock);
+
disable_irq_wake(chip->irq);
break;
@@ -169,6 +171,29 @@ static int mt6397_irq_pm_notifier(struct notifier_block *notifier,
return NOTIFY_DONE;
}
+static void mt6397_irq_pm_notifier_unregister(void *data)
+{
+ struct mt6397_chip *chip = data;
+
+ unregister_pm_notifier(&chip->pm_nb);
+}
+
+static void mt6397_irq_domain_remove(void *data)
+{
+ struct mt6397_chip *chip = data;
+ unsigned int hwirq;
+ unsigned int virq;
+
+ for (hwirq = 0; hwirq < MT6397_IRQ_NR; hwirq++) {
+ virq = irq_find_mapping(chip->irq_domain, hwirq);
+ if (virq)
+ irq_dispose_mapping(virq);
+ }
+
+ irq_domain_remove(chip->irq_domain);
+ chip->irq_domain = NULL;
+}
+
int mt6397_irq_init(struct mt6397_chip *chip)
{
int ret;
@@ -223,16 +248,28 @@ int mt6397_irq_init(struct mt6397_chip *chip)
return -ENOMEM;
}
+ ret = devm_add_action_or_reset(chip->dev, mt6397_irq_domain_remove,
+ chip);
+ if (ret)
+ return ret;
+
ret = devm_request_threaded_irq(chip->dev, chip->irq, NULL,
mt6397_irq_thread, IRQF_ONESHOT,
"mt6397-pmic", chip);
if (ret) {
dev_err(chip->dev, "failed to register irq=%d; err: %d\n",
chip->irq, ret);
- irq_domain_remove(chip->irq_domain);
return ret;
}
- register_pm_notifier(&chip->pm_nb);
- return 0;
+ ret = register_pm_notifier(&chip->pm_nb);
+ if (ret) {
+ dev_err(chip->dev, "failed to register PM notifier: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(chip->dev,
+ mt6397_irq_pm_notifier_unregister, chip);
+
+ return ret;
}
diff --git a/include/linux/mfd/mt6397/core.h b/include/linux/mfd/mt6397/core.h
index 340fc72e22aa..f9c2cbd17c5c 100644
--- a/include/linux/mfd/mt6397/core.h
+++ b/include/linux/mfd/mt6397/core.h
@@ -66,6 +66,7 @@ struct mt6397_chip {
int irq;
struct irq_domain *irq_domain;
struct mutex irqlock;
+ bool irq_suspended;
u16 wake_mask[3];
u16 irq_masks_cur[3];
u16 irq_masks_cache[3];
--
2.43.0
More information about the Linux-mediatek
mailing list