diff --git a/drivers/pmdomain/apple/pmgr-pwrstate.c b/drivers/pmdomain/apple/pmgr-pwrstate.c index c307eba0c8cc9..335f4f4f74f7f 100644 --- a/drivers/pmdomain/apple/pmgr-pwrstate.c +++ b/drivers/pmdomain/apple/pmgr-pwrstate.c @@ -79,6 +79,10 @@ struct apple_pmgr_ps { bool strict_state; bool wait_parent; u32 reset_time_us; + /* Off in genpd but on in hardware as the system slept: someone else raised it. */ + bool sleep_raised_by_other; + /* That domain's target as the system slept. */ + u32 sleep_target; /* * apple,consumer-switched: firmware-owned, but powered off and on by its * consumer (apple_pmgr_ps_consumer_off()). The state word before @@ -448,6 +452,32 @@ static int apple_pmgr_ps_resume_early(struct device *dev) return 0; } +/* + * By the noirq phase genpd has powered off what it is going to. A domain it has off + * whose target is still on was raised by a coprocessor's firmware: on T8140 the AOP + * raises audio domains of its own in the always-on PMGR, and the sleep keeps them. + */ +static int apple_pmgr_ps_suspend_noirq(struct device *dev) +{ + struct apple_pmgr_ps *ps = dev_get_drvdata(dev); + unsigned long flags; + u32 reg; + + ps->sleep_raised_by_other = false; + if (ps->firmware_owned || ps->inherited_on) + return 0; + + spin_lock_irqsave(&ps->genpd.slock, flags); + if (!regmap_read(ps->regmap, ps->offset, ®)) { + ps->sleep_target = FIELD_GET(APPLE_PMGR_PS_TARGET, reg); + ps->sleep_raised_by_other = ps->genpd.status == GENPD_STATE_OFF && + ps->sleep_target != APPLE_PMGR_PS_PWRGATE; + } + spin_unlock_irqrestore(&ps->genpd.slock, flags); + + return 0; +} + /* * A firmware-driven suspend powers the SoC down and its sleep engine leaves the * domains in states of its own: AUTO_ENABLE cleared, some at clock gating. genpd @@ -458,7 +488,8 @@ static int apple_pmgr_ps_resume_early(struct device *dev) * 30 others (measured under aura). Which of them Linux needs isn't established, * so this follows genpd's books rather than a list. A domain whose driver took it * down for the sleep, as the USB host's PHY does, is that driver's to raise, in - * its own order. + * its own order. A domain someone else raised before the sleep, and still at that + * target, stays theirs. */ static int apple_pmgr_ps_resume_noirq(struct device *dev) { @@ -475,7 +506,14 @@ static int apple_pmgr_ps_resume_noirq(struct device *dev) ret = regmap_read(ps->regmap, ps->offset, ®); if (!ret) { if (ps->genpd.status == GENPD_STATE_OFF) { - if (FIELD_GET(APPLE_PMGR_PS_TARGET, reg) != APPLE_PMGR_PS_PWRGATE) + u32 target = FIELD_GET(APPLE_PMGR_PS_TARGET, reg); + + /* + * Lowering an AOP-raised audio domain here leaves the internal + * microphone array's data path dark: the AOP never raises it again. + */ + if (target != APPLE_PMGR_PS_PWRGATE && + !(ps->sleep_raised_by_other && target == ps->sleep_target)) ret = apple_pmgr_ps_set(&ps->genpd, APPLE_PMGR_PS_PWRGATE, false); } else { @@ -1138,7 +1176,7 @@ MODULE_DEVICE_TABLE(of, apple_pmgr_ps_of_match); static const struct dev_pm_ops apple_pmgr_ps_pm_ops = { LATE_SYSTEM_SLEEP_PM_OPS(apple_pmgr_ps_suspend_late, apple_pmgr_ps_resume_early) - NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, apple_pmgr_ps_resume_noirq) + NOIRQ_SYSTEM_SLEEP_PM_OPS(apple_pmgr_ps_suspend_noirq, apple_pmgr_ps_resume_noirq) }; static struct platform_driver apple_pmgr_ps_driver = {