diff --git a/drivers/input/misc/macsmc-input.c b/drivers/input/misc/macsmc-input.c index fb3fe3fdb6920..109af27bbb11b 100644 --- a/drivers/input/misc/macsmc-input.c +++ b/drivers/input/misc/macsmc-input.c @@ -270,10 +270,14 @@ static int macsmc_input_pm_prepare(struct device *dev) static void macsmc_input_pm_complete(struct device *dev) { struct macsmc_input *smcin = dev_get_drvdata(dev); + u8 lid; - /* Still armed: a lid opened or a button pressed before the next .prepare wakes. */ + /* + * Still armed: a lid opened or a button pressed before the next .prepare wakes. A dark + * wake with the lid already open (for the debug link's cut) has no lid to open. + */ if (pm_dark_wake()) { - smcin->dark = true; + smcin->dark = apple_smc_read_u8(smcin->smc, SMC_KEY(MSLD), &lid) < 0 || lid; return; } smcin->wakeup_mode = false; diff --git a/drivers/mfd/macsmc-test.c b/drivers/mfd/macsmc-test.c index 16d6fdae7c12e..d6ccf8a3970a2 100644 --- a/drivers/mfd/macsmc-test.c +++ b/drivers/mfd/macsmc-test.c @@ -246,6 +246,30 @@ static void macsmc_sleep_order_test(struct kunit *test) KUNIT_EXPECT_EQ(test, ctx->seen[6], SMC_KEY(waka)); } +static void macsmc_test_outbox_poll(struct apple_smc *smc, const char *after, + unsigned int min_ms, unsigned int quiet_ms) +{ +} + +static void macsmc_sleep_warn_late_test(struct kunit *test) +{ + struct macsmc_test_context *ctx = test->priv; + + kunit_activate_static_stub(test, apple_smc_ap_power, macsmc_test_ap_power); + kunit_activate_static_stub(test, apple_smc_outbox_poll, macsmc_test_outbox_poll); + ctx->result = 8; + + /* A sleep that cuts the debug link: slpw comes at the noirq suspend, before slpa. */ + ctx->smc.sleep_warn_late = true; + KUNIT_EXPECT_EQ(test, apple_smc_sleep_enter(&ctx->smc), 0); + KUNIT_EXPECT_FALSE(test, ctx->smc.sleep_warn_late); + KUNIT_EXPECT_TRUE(test, ctx->smc.sleep_warned); + KUNIT_ASSERT_EQ(test, ctx->seen_count, 3U); + KUNIT_EXPECT_EQ(test, ctx->seen[0], SMC_KEY(slpw)); + KUNIT_EXPECT_EQ(test, ctx->seen[1], SMC_KEY(slpa)); + KUNIT_EXPECT_EQ(test, ctx->seen[2], (u32)MACSMC_TEST_AP_IDLE); +} + static void macsmc_sleep_refused_test(struct kunit *test) { struct macsmc_test_context *ctx = test->priv; @@ -332,6 +356,19 @@ static void macsmc_wake_reason_test(struct kunit *test) KUNIT_EXPECT_STREQ(test, names, "wifibt USB-C_plug"); } +static void macsmc_port_reasons_test(struct kunit *test) +{ + /* The debug link's cut: USB-C_plug, acdetach, or both. */ + KUNIT_EXPECT_TRUE(test, apple_smc_port_reasons_only(BIT_ULL(42))); + KUNIT_EXPECT_TRUE(test, apple_smc_port_reasons_only(BIT_ULL(41) | BIT_ULL(42))); + /* Anything else, with it or alone, is a real wake. */ + KUNIT_EXPECT_FALSE(test, apple_smc_port_reasons_only(0)); + KUNIT_EXPECT_FALSE(test, apple_smc_port_reasons_only(BIT_ULL(42) | BIT_ULL(0))); + KUNIT_EXPECT_FALSE(test, apple_smc_port_reasons_only(BIT_ULL(42) | BIT_ULL(10))); + KUNIT_EXPECT_FALSE(test, apple_smc_port_reasons_only(BIT_ULL(23))); + KUNIT_EXPECT_FALSE(test, apple_smc_port_reasons_only(BIT_ULL(43))); +} + static struct kunit_case macsmc_parser_cases[] = { KUNIT_CASE(macsmc_key_info_lock_test), KUNIT_CASE(macsmc_key_index_test), @@ -343,10 +380,12 @@ static struct kunit_case macsmc_parser_cases[] = { KUNIT_CASE(macsmc_sram_value_test), KUNIT_CASE(macsmc_key_info_test), KUNIT_CASE(macsmc_sleep_order_test), + KUNIT_CASE(macsmc_sleep_warn_late_test), KUNIT_CASE(macsmc_sleep_refused_test), KUNIT_CASE(macsmc_notice_test), KUNIT_CASE(macsmc_lid_wake_test), KUNIT_CASE(macsmc_wake_reason_test), + KUNIT_CASE(macsmc_port_reasons_test), {} }; diff --git a/drivers/mfd/macsmc.c b/drivers/mfd/macsmc.c index 801718b9b24d4..1d0e4d58b68a6 100644 --- a/drivers/mfd/macsmc.c +++ b/drivers/mfd/macsmc.c @@ -672,19 +672,21 @@ MODULE_DEVICE_TABLE(of, apple_smc_of_match); * from their letters. * * SMC.OutboxNotEmpty wakes the SoC while anything waits in the outbox. An event that arrives - * during the sleep's entry (the lid, a key) is left there, so the sleep returns at once. The - * debug link's cut fills it too, and the SMC counts what it reports after the notice as wake - * reasons, so the cut comes before the notice. + * during the sleep's entry (the lid, a key) is left there, so the sleep returns at once. + * + * The debug link's cut, on a debug cable only, makes the SMC see a cable pulled and plugged + * back. With the cut after slpw, the SMC woke the AP right after the commit, for USB-C_plug + * or acdetach, in every such sleep (J700, 223 of 223), even with its outbox quiet for 500 ms + * before the notice. A sleep that cuts the link warns the SMC only after the cut. */ /* The gaps macOS leaves before the AP idle notice and after it. */ #define SMC_SLEEP_ARM_MS 6 #define SMC_SLEEP_IDLE_MS 30 +/* The shortest gap macOS left between slpw and slpa, in two traced sleeps. */ +#define SMC_SLEEP_WARN_MS 770 -/* - * After the debug link's cut. Its port and charger events keep coming for over a second, - * and any that lands after the AP idle notice wakes the SoC at once. - */ +/* After the debug link's cut. Its port and charger events keep coming for over a second. */ #define SMC_OUTBOX_QUIET_MS 500 #define SMC_OUTBOX_SETTLE_MS 5000 @@ -849,12 +851,30 @@ static void apple_smc_lid_wake(struct apple_smc *smc) apple_sleep_lid_closed_wake(); } +/* Only what a cable pulled and plugged back reports: what the debug link's cut makes. */ +static bool apple_smc_port_reasons_only(u64 record) +{ + static const char * const names[] = { "acattach", "acdetach", "USB-C_plug" }; + u64 port = 0; + int i, bit; + + for (i = 0; i < ARRAY_SIZE(names); i++) { + bit = apple_smc_wake_reason_bit(names[i]); + if (bit >= 0) + port |= BIT_ULL(bit); + } + + return record && !(record & ~port); +} + static void apple_smc_sleep_wake(struct apple_smc *smc) { apple_smc_notice_on(smc); apple_smc_write_mbse(smc, SMC_KEY(waka)); smc->sleep_warned = false; apple_smc_lid_wake(smc); + if (apple_smc_port_reasons_only(smc->wake_record)) + apple_sleep_debug_link_cut_wake(); /* A dark wake isn't one: with wakeup_count in use, it would abort the sleep after it. */ if (smc->wake_record && !pm_dark_wake()) pm_wakeup_event(smc->dev, 0); @@ -864,7 +884,11 @@ static int apple_smc_prepare(struct device *dev) { struct apple_smc *smc = dev_get_drvdata(dev); - if (pm_suspend_via_firmware()) + if (!pm_suspend_via_firmware()) + return 0; + if (apple_sleep_debug_link_up()) + smc->sleep_warn_late = true; + else apple_smc_sleep_warn(smc); return 0; @@ -875,19 +899,27 @@ static void apple_smc_complete(struct device *dev) { struct apple_smc *smc = dev_get_drvdata(dev); + smc->sleep_warn_late = false; if (smc->sleep_warned) { apple_smc_write_mbse(smc, SMC_KEY(waka)); smc->sleep_warned = false; } } -/* The events go to their handlers as usual, through the RTKit worker. */ -static void apple_smc_outbox_settle(struct apple_smc *smc) +/* + * For at least @min_ms, then until the outbox is quiet for @quiet_ms. The events go to their + * handlers as usual, through the RTKit worker. + */ +static void apple_smc_outbox_poll(struct apple_smc *smc, const char *after, + unsigned int min_ms, unsigned int quiet_ms) { unsigned int ms, quiet = 0, count = 0, last = 0; int ret; - for (ms = 0; ms < SMC_OUTBOX_SETTLE_MS && quiet < SMC_OUTBOX_QUIET_MS; ms++) { + if (IS_ENABLED(CONFIG_MFD_MACSMC_KUNIT_TEST)) + KUNIT_STATIC_STUB_REDIRECT(apple_smc_outbox_poll, smc, after, min_ms, quiet_ms); + + for (ms = 0; ms < SMC_OUTBOX_SETTLE_MS && (ms < min_ms || quiet < quiet_ms); ms++) { ret = apple_rtkit_poll(smc->rtk); if (ret > 0) { count += ret; @@ -898,8 +930,23 @@ static void apple_smc_outbox_settle(struct apple_smc *smc) } usleep_range(1000, 1200); } - dev_dbg(smc->dev, "debug link cut: %u messages, the last at %u ms, %s after %u ms\n", - count, last, quiet >= SMC_OUTBOX_QUIET_MS ? "quiet" : "still busy", ms); + /* A debug cable only. */ + dev_info(smc->dev, "%s: %u messages in %u ms, the last at %u ms%s\n", after, count, ms, + last, ms >= SMC_OUTBOX_SETTLE_MS ? ", still busy" : ""); +} + +/* The noirq suspend's part, polled: the cut, a late warning, the commit's notices. */ +static int apple_smc_sleep_enter(struct apple_smc *smc) +{ + if (apple_sleep_debug_link_cut()) + apple_smc_outbox_poll(smc, "debug link cut", 0, SMC_OUTBOX_QUIET_MS); + if (smc->sleep_warn_late) { + smc->sleep_warn_late = false; + apple_smc_sleep_warn(smc); + apple_smc_outbox_poll(smc, "late sleep warning", SMC_SLEEP_WARN_MS, 0); + } + + return apple_smc_sleep_commit(smc); } static int apple_smc_suspend_noirq(struct device *dev) @@ -907,13 +954,11 @@ static int apple_smc_suspend_noirq(struct device *dev) struct apple_smc *smc = dev_get_drvdata(dev); int ret; - if (!smc->sleep_warned) + if (!smc->sleep_warned && !smc->sleep_warn_late) return 0; apple_smc_set_polled(smc, true); - if (apple_sleep_debug_link_cut()) - apple_smc_outbox_settle(smc); - ret = apple_smc_sleep_commit(smc); + ret = apple_smc_sleep_enter(smc); apple_smc_set_polled(smc, false); return ret; diff --git a/drivers/soc/apple/sleep.c b/drivers/soc/apple/sleep.c index 3838cedf471ef..91ccd2386e44c 100644 --- a/drivers/soc/apple/sleep.c +++ b/drivers/soc/apple/sleep.c @@ -1859,13 +1859,19 @@ static int sleep_hpm_wait_contract(struct apple_sleep *s) return -ETIMEDOUT; } +/* A live debug link holds the debug switch domain on. */ +static bool sleep_debug_link_up(struct apple_sleep *s) +{ + return s->hpm_spmi && + FIELD_GET(PS_ACTUAL, readl(s->ps_aon + s->soc->ps_aon_debug_switch)); +} + /* * The debug link stays up with the ATC0 domains off, and it holds the debug domains * active, which macOS has idle. The only known way to end it is the port controller's - * cold reset. To the SMC that's a cable pulled: it queues the port's events and records - * them as wake reasons. So it has to come before the SMC's AP idle notice, not after, or - * SMC.OutboxNotEmpty is up at the commit, and so does the partner's new contract. - * tipd rearms the link after a refusal. + * cold reset. To the SMC that's a cable pulled: it records the port's events as wake + * reasons once it has been warned of the sleep, and wakes the AP right after the commit. + * So the SMC's driver warns it only after this. tipd rearms the link after a refusal. */ static bool apple_sleep_usb_down(struct apple_sleep *s) { @@ -1877,8 +1883,7 @@ static bool apple_sleep_usb_down(struct apple_sleep *s) return false; s->usb_down_ran = true; - if (!s->hpm_spmi || - !FIELD_GET(PS_ACTUAL, readl(s->ps_aon + s->soc->ps_aon_debug_switch))) + if (!sleep_debug_link_up(s)) return false; dev_info(s->dev, "debug link: cutting it for the sleep\n"); @@ -1918,6 +1923,14 @@ bool apple_sleep_debug_link_cut(void) } EXPORT_SYMBOL_GPL(apple_sleep_debug_link_cut); +bool apple_sleep_debug_link_up(void) +{ + struct apple_sleep *s = apple_sleep; + + return s && !s->usb_down_ran && sleep_debug_link_up(s); +} +EXPORT_SYMBOL_GPL(apple_sleep_debug_link_up); + /* * macOS takes the wake for a lid closing during the sleep as a dark one: every driver wakes, * the display stays off, and the root domain sleeps again as soon as it sees the lid closed. @@ -1933,6 +1946,22 @@ void apple_sleep_lid_closed_wake(void) } EXPORT_SYMBOL_GPL(apple_sleep_lid_closed_wake); +/* + * The SMC woke the AP for the port alone, and this sleep cut the debug link: the cut's own + * events. Sleep again. The link stays down until tipd rearms it at a full wake, so the next + * entry doesn't cut it. + */ +void apple_sleep_debug_link_cut_wake(void) +{ + struct apple_sleep *s = apple_sleep; + + if (!s || !s->smc_woke_alone || !READ_ONCE(sleep_debug_link_cut)) + return; + dev_info(s->dev, "debug link: its cut woke the sleep: dark wake\n"); + pm_set_dark_wake(); +} +EXPORT_SYMBOL_GPL(apple_sleep_debug_link_cut_wake); + /* * The radio's link and the engine that starts it, in macOS's order, measured on one * sleep: the link into L2, the engine stopped, PERST# asserted, the PHY's clocks off. diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 99cd947f790cb..0f581faf056e5 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -1720,10 +1720,13 @@ static void cd321x_complete(struct tps6598x *tps) } if (pm_suspend_via_firmware() && device_property_read_bool(tps->dev, "apple,sleep-ssps")) tps->data->switch_power_state(tps, TPS_SYSTEM_POWER_STATE_S0); - /* A link still down from an earlier sleep isn't cut again, so it stays pending. */ + /* + * A link still down from an earlier sleep isn't cut again, so it stays pending. A dark + * wake sleeps again at once: the link stays down for it, and the full wake rearms it. + */ if (apple_sleep_debug_link_was_cut(tps->dev)) cd321x->rearm_pending = true; - if (cd321x->rearm_pending) { + if (cd321x->rearm_pending && !pm_dark_wake()) { cd321x->rearm_tries = 0; cd321x_rearm_debug_link(cd321x); } diff --git a/include/linux/mfd/macsmc.h b/include/linux/mfd/macsmc.h index bf8781ba4c52e..d0cc7ae5f504a 100644 --- a/include/linux/mfd/macsmc.h +++ b/include/linux/mfd/macsmc.h @@ -116,6 +116,8 @@ struct apple_smc { bool ap_notified; /* Told of a coming sleep, and not yet of its end. */ bool sleep_warned; + /* To be told at the noirq suspend, after the debug link's cut. */ + bool sleep_warn_late; /* In a noirq phase: replies are polled for. */ bool polled; /* MBSW at the last wake: what the SMC had woken the AP for. */ diff --git a/include/linux/soc/apple/sleep.h b/include/linux/soc/apple/sleep.h index 1766f865b64d2..301d955cfe678 100644 --- a/include/linux/soc/apple/sleep.h +++ b/include/linux/soc/apple/sleep.h @@ -108,6 +108,9 @@ bool apple_sleep_debug_link_cut_peek(struct device *dev); */ bool apple_sleep_debug_link_cut(void); +/* This sleep will end the debug link: for the SMC's driver, which warns the SMC after it. */ +bool apple_sleep_debug_link_up(void); + /* * From the SMC's driver, in its noirq resume: the SMC woke the AP for the lid alone, and the * lid reads closed. If nothing else woke the SoC, the PM core goes back to sleep once every @@ -115,6 +118,12 @@ bool apple_sleep_debug_link_cut(void); */ void apple_sleep_lid_closed_wake(void); +/* + * From the SMC's driver, in its noirq resume: the SMC woke the AP for the port alone. If + * this sleep cut the debug link and nothing else woke the SoC, it's a dark wake. + */ +void apple_sleep_debug_link_cut_wake(void); + /* The radio's host has its port back after a real sleep, so the next commit may stop it. */ void apple_sleep_radio_port_up(void); @@ -227,10 +236,19 @@ static inline bool apple_sleep_debug_link_cut(void) return false; } +static inline bool apple_sleep_debug_link_up(void) +{ + return false; +} + static inline void apple_sleep_lid_closed_wake(void) { } +static inline void apple_sleep_debug_link_cut_wake(void) +{ +} + static inline void apple_sleep_radio_port_up(void) { }