From 7690f65fb72f743d496fb75818fec558b01b9b50 Mon Sep 17 00:00:00 2001 From: Diljit Singh Date: Mon, 9 Nov 2026 17:31:57 -0500 Subject: [PATCH 1/4] cpufreq: apple: give the PMP its wait again after a firmware sleep A firmware-driven sleep stops the PMP, and its driver starts it again only after the CPUs are back online. The fast die-temperature samples taken in between find it down. More than 30 s after boot that reads as a PMP that never came up: every wake logged "PMP not running after 30 s", and the sampling slowed to its give-up rate, holding the fallback limit (2688 MHz on the J700's P cluster) until the next slow sample. Restart the 30 s window at the suspend, as at boot. Jiffies don't count the sleep, so the PMP gets 30 s after the wake. Signed-off-by: Diljit Singh --- drivers/cpufreq/apple-soc-cpufreq.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/cpufreq/apple-soc-cpufreq.c b/drivers/cpufreq/apple-soc-cpufreq.c index 0603a40d865b6..379fa031ce599 100644 --- a/drivers/cpufreq/apple-soc-cpufreq.c +++ b/drivers/cpufreq/apple-soc-cpufreq.c @@ -838,6 +838,19 @@ static unsigned int apple_soc_cpufreq_pstate_freq(struct cpufreq_policy *policy, static int apple_soc_cpufreq_suspend(struct cpufreq_policy *policy) { struct apple_cpu_priv *priv = policy->driver_data; + struct apple_fast_die *fd = priv->fast_die; + + /* + * A firmware-driven sleep stops the PMP, and its driver starts it again only after the + * CPUs are back: the first samples after the wake find it down. Give it the same time to + * come back as at boot. Jiffies don't count the sleep. + */ + if (fd && fd->report && pm_suspend_via_firmware()) { + spin_lock_bh(&fd->lock); + fd->start = jiffies; + fd->warned = false; + spin_unlock_bh(&fd->lock); + } if (priv->info->lazy_verify) { int pstate = apple_soc_cpufreq_drain(priv); From 1782f53bf26a9efa1ecd229d3243a46a522ac52c Mon Sep 17 00:00:00 2001 From: Diljit Singh Date: Mon, 9 Nov 2026 17:48:47 -0500 Subject: [PATCH 2/4] usb: typec: tipd: cd321x: get the contract back before rearming the debug link A sleep woken by its own debug-link cut sleeps again as a dark wake, with the link left down. When that second sleep was refused (an early wake event), the full wake's rearm failed all 40 tries at the debug VDM: the partner had dropped the PD contract, both ends sat at default USB power, and the Debug USB never came back until a replug. The debug VDM needs a contract. After a few failed tries, if the port has none, do what the cut does when the contract doesn't come back: reset the controller (Gaid), wait for the contract, at most three times, and set the controller up again as after the cut before the next try. Signed-off-by: Diljit Singh --- drivers/usb/typec/tipd/core.c | 60 ++++++++++++++++++++++++++++--- drivers/usb/typec/tipd/tps6598x.h | 2 ++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 0f581faf056e5..b0d5d0954e5df 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -1695,14 +1695,65 @@ static void cd321x_rearm_debug_link(struct cd321x *cd321x) cd321x->rearm_pending = false; } +/* The cut's cold reset, and how it waits for the contract to come back, see apple_sleep_usb_down(). */ +#define CD321X_REARM_RESET_AFTER 4 +#define CD321X_REARM_RESETS 3 +#define CD321X_RESET_MS 1000 +#define CD321X_CONTRACT_MS 5000 + +static bool cd321x_has_contract(struct tps6598x *tps) +{ + u16 pwr; + + return !tps6598x_read16(tps, TPS_REG_POWER_STATUS, &pwr) && + TPS_POWER_STATUS_CONNECTION(pwr) && + TPS_POWER_STATUS_PWROPMODE(pwr) == TYPEC_PWR_MODE_PD; +} + +/* + * The debug VDM needs a PD contract. After a cut whose sleep was refused, the partner was + * found to have dropped it, both ends at default USB power, and no try could rearm the link. + * Do what the cut does when the contract doesn't come back: reset the controller again and + * wait for it. The controller then needs setting up as after the cut. + */ +static void cd321x_rearm_recover_contract(struct cd321x *cd321x) +{ + struct tps6598x *tps = &cd321x->tps; + int ms, ret; + + lockdep_assert_held(&tps->lock); + cd321x->rearm_resets++; + dev_warn(tps->dev, "debug link: no contract with the partner, reset %u\n", + cd321x->rearm_resets); + ret = tps6598x_exec_cmd_tmo(tps, "Gaid", 0, NULL, 0, NULL, 2000, 0); + if (ret) + dev_dbg(tps->dev, "reset command: %d\n", ret); + msleep(CD321X_RESET_MS); + for (ms = 0; ms < CD321X_CONTRACT_MS && !cd321x_has_contract(tps); ms += 50) + msleep(50); + if (ms < CD321X_CONTRACT_MS) + dev_info(tps->dev, "debug link: contract back %d ms after reset %u\n", + CD321X_RESET_MS + ms, cd321x->rearm_resets); + cd321x->pm.setup_pending = true; + cd321x->reset_by_cut = true; + cd321x->state_valid = false; + mod_delayed_work(system_freezable_wq, &cd321x->resume_work, 0); + cd321x->rearm_tries = 0; +} + static void cd321x_rearm_work(struct work_struct *work) { struct cd321x *cd321x = container_of(to_delayed_work(work), struct cd321x, rearm_work); + struct tps6598x *tps = &cd321x->tps; - guard(mutex)(&cd321x->tps.lock); - if (cd321x->rearm_pending && cd321x->pm.phase != CD321X_PM_PREPARED && - cd321x->pm.phase != CD321X_PM_REMOVED) - cd321x_rearm_debug_link(cd321x); + guard(mutex)(&tps->lock); + if (!cd321x->rearm_pending || cd321x->pm.phase == CD321X_PM_PREPARED || + cd321x->pm.phase == CD321X_PM_REMOVED) + return; + if (cd321x->rearm_tries >= CD321X_REARM_RESET_AFTER && + cd321x->rearm_resets < CD321X_REARM_RESETS && !cd321x_has_contract(tps)) + cd321x_rearm_recover_contract(cd321x); + cd321x_rearm_debug_link(cd321x); } static void cd321x_complete(struct tps6598x *tps) @@ -1728,6 +1779,7 @@ static void cd321x_complete(struct tps6598x *tps) cd321x->rearm_pending = true; if (cd321x->rearm_pending && !pm_dark_wake()) { cd321x->rearm_tries = 0; + cd321x->rearm_resets = 0; cd321x_rearm_debug_link(cd321x); } if (cd321x_pm_resume(&cd321x->pm)) diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index 1b811bfe8d96a..a5512eea4205f 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -473,6 +473,8 @@ struct cd321x { /* The debug link is still to be rearmed after a cut, and the tries so far */ bool rearm_pending; unsigned int rearm_tries; + /* Resets taken since the cut for a contract the partner dropped, see cd321x_rearm_work() */ + unsigned int rearm_resets; bool dp_sink_reported; bool dp_irq_hpd; From 99696ed7aaf7dbae16933f7ae387ff9a1b46aad5 Mon Sep 17 00:00:00 2001 From: Diljit Singh Date: Mon, 9 Nov 2026 18:44:13 -0500 Subject: [PATCH 3/4] spmi: apple: clear pending sources nothing uses After every firmware-driven sleep the Neo's USB-PD SPMI controller raised its interrupt twice with nothing of ours to run, and the chained handler reported a bad IRQ (bad_chained_irq dump for apple_spmi_irq_handler). Read at those interrupts: - the PD controllers' even lines, next to each odd one tipd uses, were pending: ACK 0xd1c00 and 0x1450 against enables 0x22000 and 0x28a0; - another queue's response vector was pending in the vectors' word (0x80 next to ours), the sleep's own commands at the debug link's cut; - tipd's own line was pending while still masked by the resume. Nothing maps the first two, so nothing ever clears them. Track which sources a driver has mapped and clear the pending ones nobody has, and the other vectors' responses, as probe does for every source. A used source pending while masked waits for its unmask and isn't reported. Signed-off-by: Diljit Singh --- drivers/spmi/spmi-apple-controller.c | 34 ++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/drivers/spmi/spmi-apple-controller.c b/drivers/spmi/spmi-apple-controller.c index e655fad881fac..9fce26242183d 100644 --- a/drivers/spmi/spmi-apple-controller.c +++ b/drivers/spmi/spmi-apple-controller.c @@ -100,6 +100,8 @@ struct apple_spmi { struct irq_domain *irqd; raw_spinlock_t irq_mask_lock; DECLARE_BITMAP(irq_mask_cache, SPMI_MAX_PERIPHERAL_IRQS); + /* The sources a driver uses, enabled or not. */ + DECLARE_BITMAP(irq_used, SPMI_MAX_PERIPHERAL_IRQS); int irq; bool notify_irq; bool notify_parked; @@ -344,8 +346,11 @@ static struct irq_chip apple_spmi_irq_chip = { static int apple_spmi_irq_domain_map(struct irq_domain *irqd, unsigned int irq, irq_hw_number_t hw) { + struct apple_spmi *spmi = irqd->host_data; + irq_domain_set_info(irqd, irq, hw, &apple_spmi_irq_chip, irqd->host_data, handle_edge_irq, NULL, NULL); + set_bit(hw, spmi->irq_used); return 0; } @@ -399,7 +404,9 @@ static void apple_spmi_irq_domain_free(struct irq_domain *irqd, unsigned int vir for (i = 0; i < nr_irqs; i++) { struct irq_data *d = irq_domain_get_irq_data(irqd, virq + i); + struct apple_spmi *spmi = irqd->host_data; + clear_bit(d->hwirq, spmi->irq_used); irq_set_handler(virq + i, NULL); irq_domain_reset_irq_data(d); } @@ -416,7 +423,7 @@ static void apple_spmi_irq_handler(struct irq_desc *desc) struct apple_spmi *spmi = irq_desc_get_handler_data(desc); struct irq_chip *chip = irq_desc_get_chip(desc); u32 notify = spmi->hw->num_irqs; - bool handled = false; + bool handled = false, held = false; unsigned long val, offset, bit; chained_irq_enter(chip, desc); @@ -426,9 +433,32 @@ static void apple_spmi_irq_handler(struct irq_desc *desc) complete(&spmi->fifo_rx); handled = true; } + /* Other queues' responses, such as the sleep's own commands at the debug link's cut. */ + if (val & ~BIT(notify % 32)) { + writel(val & ~BIT(notify % 32), spmi->regs + SPMI_IRQ_ACK_BASE + (notify / 32) * 4); + handled = true; + } for (offset = 0; offset < notify / 8; offset += sizeof(val)) { + unsigned long stray; + val = readq(spmi->regs + SPMI_IRQ_ACK_BASE + offset); + /* + * After a real sleep, sources no driver uses come up pending (on the Neo the + * USB-PD controllers' even lines, latched by the debug link's cut), and the + * interrupt fires for them with nothing of ours to run. Clear them, as probe does. + * A used source that is masked, as all are until the resume enables them, stays + * pending for its unmask: it isn't a bad interrupt either. + */ + stray = val & ~spmi->irq_used[offset / sizeof(val)]; + if (stray) { + writel(lower_32_bits(stray), spmi->regs + SPMI_IRQ_ACK_BASE + offset); + writel(upper_32_bits(stray), spmi->regs + SPMI_IRQ_ACK_BASE + offset + 4); + handled = true; + } + if (val & spmi->irq_used[offset / sizeof(val)] & + ~spmi->irq_mask_cache[offset / sizeof(val)]) + held = true; /** * because of other masters in the bus, we're going to get a multitude of * interrupts we're not interested in. irq_resolve_mapping isn't very @@ -441,7 +471,7 @@ static void apple_spmi_irq_handler(struct irq_desc *desc) handled = true; } } - if (!handled) + if (!handled && !held) handle_bad_irq(desc); chained_irq_exit(chip, desc); } From c81000fb2880e6a284f9a4209a670c084b091302 Mon Sep 17 00:00:00 2001 From: Diljit Singh Date: Mon, 9 Nov 2026 19:13:44 -0500 Subject: [PATCH 4/4] spmi: apple: read the acks back before the parent interrupt is unmasked The acks the chained handler writes are posted, and the parent is unmasked as the handler returns, before they may have reached the controller. Read the ACK block back first, so a cleared source can't look pending to the parent again. On the Neo this didn't remove the one bad_chained_irq that is left after each firmware-driven sleep (down from two with stray sources cleared); that one is still open. Signed-off-by: Diljit Singh --- drivers/spmi/spmi-apple-controller.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/spmi/spmi-apple-controller.c b/drivers/spmi/spmi-apple-controller.c index 9fce26242183d..f4124d0ae83d2 100644 --- a/drivers/spmi/spmi-apple-controller.c +++ b/drivers/spmi/spmi-apple-controller.c @@ -473,6 +473,12 @@ static void apple_spmi_irq_handler(struct irq_desc *desc) } if (!handled && !held) handle_bad_irq(desc); + /* + * The acks are posted writes: let them reach the controller before the parent is + * unmasked, or it fires again for what was just cleared. + */ + if (handled) + readl(spmi->regs + SPMI_IRQ_ACK_BASE); chained_irq_exit(chip, desc); }