diff --git a/Documentation/devicetree/bindings/usb/apple,sn201202x.yaml b/Documentation/devicetree/bindings/usb/apple,sn201202x.yaml index 88ee208e102ef..c2d2eb1068ea8 100644 --- a/Documentation/devicetree/bindings/usb/apple,sn201202x.yaml +++ b/Documentation/devicetree/bindings/usb/apple,sn201202x.yaml @@ -39,6 +39,8 @@ properties: minItems: 3 maxItems: 4 + wakeup-source: true + connector: $ref: /schemas/connector/usb-connector.yaml# diff --git a/arch/arm64/boot/dts/apple/t8132-j713.dts b/arch/arm64/boot/dts/apple/t8132-j713.dts index 2bf6dbe86337a..896d677323103 100644 --- a/arch/arm64/boot/dts/apple/t8132-j713.dts +++ b/arch/arm64/boot/dts/apple/t8132-j713.dts @@ -311,6 +311,7 @@ <43 IRQ_TYPE_EDGE_RISING>, <45 IRQ_TYPE_EDGE_RISING>; interrupt-names = "irq", "sleep", "wake"; + wakeup-source; typec1: connector { compatible = "usb-c-connector"; @@ -345,6 +346,7 @@ <17 IRQ_TYPE_EDGE_RISING>, <19 IRQ_TYPE_EDGE_RISING>; interrupt-names = "irq", "sleep", "wake"; + wakeup-source; typec0: connector { compatible = "usb-c-connector"; diff --git a/drivers/phy/apple/atc-test.c b/drivers/phy/apple/atc-test.c index b64bcdbee1bb6..682e5a3809aa8 100644 --- a/drivers/phy/apple/atc-test.c +++ b/drivers/phy/apple/atc-test.c @@ -1158,6 +1158,41 @@ static void atcphy_test_t8132_rpm_busy(struct kunit *test) KUNIT_EXPECT_TRUE(test, atcphy_rpm_busy(atcphy)); } +#ifdef CONFIG_SUSPEND +/* An epoch with a host survives suspend-to-idle only; without one, or incomplete, never. */ +static void atcphy_test_t8132_system_suspend(struct kunit *test) +{ + struct apple_atcphy *atcphy = atcphy_test_alloc_powered_off(test); + suspend_state_t target = pm_suspend_target_state; + + atcphy->hw = &atcphy_hw_t8132; + atcphy->t8132_ss = true; + dev_set_drvdata(atcphy->dev, atcphy); + + pm_suspend_target_state = PM_SUSPEND_TO_IDLE; + atcphy->mode_dirty = true; + KUNIT_EXPECT_EQ(test, atcphy_suspend(atcphy->dev), -EBUSY); + atcphy->mode_dirty = false; + + /* Without a host, as in a USB3 device role, nothing holds the domain */ + atcphy->t8132_common_owned = true; + KUNIT_EXPECT_EQ(test, atcphy_suspend(atcphy->dev), -EBUSY); + KUNIT_EXPECT_FALSE(test, atcphy->t8132_suspended); + + atcphy->host_active = true; + KUNIT_EXPECT_EQ(test, atcphy_suspend(atcphy->dev), 0); + KUNIT_EXPECT_TRUE(test, atcphy->t8132_suspended); + KUNIT_EXPECT_EQ(test, atcphy_resume(atcphy->dev), 0); + KUNIT_EXPECT_FALSE(test, atcphy->t8132_suspended); + + pm_suspend_target_state = PM_SUSPEND_MEM; + KUNIT_EXPECT_EQ(test, atcphy_suspend(atcphy->dev), -EBUSY); + KUNIT_EXPECT_FALSE(test, atcphy->t8132_suspended); + + pm_suspend_target_state = target; +} +#endif + /* The hold follows the busy state, with one reference at most. */ static void atcphy_test_t8132_rpm_hold(struct kunit *test) { @@ -1356,6 +1391,9 @@ static struct kunit_case atcphy_t8132_pm_test_cases[] = { KUNIT_CASE(atcphy_test_t8132_rpm_hold), KUNIT_CASE(atcphy_test_t8132_restore_idle), KUNIT_CASE(atcphy_test_t8132_restore_mode), +#ifdef CONFIG_SUSPEND + KUNIT_CASE(atcphy_test_t8132_system_suspend), +#endif {} }; diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index b4c91cf4d373c..9520c96b7e136 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -6152,8 +6153,16 @@ static int atcphy_suspend(struct device *dev) return 0; guard(mutex)(&atcphy->lock); - /* Active and incomplete common epochs have no qualified sleep handoff. */ - if (atcphy->t8132_common_owned || atcphy->mode_dirty) + /* An incomplete common epoch has no qualified sleep handoff. */ + if (atcphy->mode_dirty) + return -EBUSY; + /* + * An active one survives suspend-to-idle while a host runs on it: DWC3 + * then keeps the power domain on across system sleep, so the link stays + * as it is. Without a host, or in a deeper sleep state, it is lost. + */ + if (atcphy->t8132_common_owned && + (!atcphy->host_active || pm_suspend_target_state != PM_SUSPEND_TO_IDLE)) return -EBUSY; /* Do not admit a new epoch after this check and before resume. */ atcphy->t8132_suspended = true; @@ -6172,6 +6181,9 @@ static int atcphy_resume(struct device *dev) guard(mutex)(&atcphy->lock); atcphy->t8132_suspended = false; + if (atcphy->t8132_common_owned && atcphy->host_active && + READ_ONCE(atcphy->pd_was_off)) + dev_warn(dev, "power domain went off under an active USB3 link\n"); /* * A suspended PHY holds nothing, and its domain may drop again once system * resume completes: leave the restore to its next operation. diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 50c564c46b48a..a374ff625a24c 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -592,6 +592,10 @@ static const struct usb_device_id usb_quirk_list[] = { /* VIA Labs, Inc. USB2.0 Hub */ { USB_DEVICE(0x2109, 0x2817), .driver_info = USB_QUIRK_NO_LPM }, + /* VIA Labs, Inc. VL822 USB2.0 Hub */ + { USB_DEVICE(0x2109, 0x2822), .driver_info = + USB_QUIRK_HUB_NO_SUSPENDED_WAKEUP }, + /* Raydium Touchscreen */ { USB_DEVICE(0x2386, 0x3114), .driver_info = USB_QUIRK_NO_LPM }, diff --git a/drivers/usb/core/usb-quirks-test.c b/drivers/usb/core/usb-quirks-test.c index 5587e1d4cfa0c..9073f6bd3149e 100644 --- a/drivers/usb/core/usb-quirks-test.c +++ b/drivers/usb/core/usb-quirks-test.c @@ -53,6 +53,19 @@ static void usb_quirks_vl122_test(struct kunit *test) KUNIT_EXPECT_TRUE(test, usb_hub_id_quirks(&t->intf) & HUB_QUIRK_DISABLE_AUTOSUSPEND); } +/* + * The VIA Labs VL822 USB 2.0 hub passes on no remote wakeup from below while it + * is suspended. It is kept awake in suspend-to-idle and keeps its autosuspend. + */ +static void usb_quirks_vl822_test(struct kunit *test) +{ + struct usb_quirks_test *t = usb_quirks_test_hub(test, 0x2109, 0x2822); + + KUNIT_EXPECT_EQ(test, usb_detect_device_list_quirks(&t->udev), + USB_QUIRK_HUB_NO_SUSPENDED_WAKEUP); + KUNIT_EXPECT_EQ(test, usb_hub_id_quirks(&t->intf), 0UL); +} + /* Another VIA Labs hub keeps its own device quirk and no hub quirk. */ static void usb_quirks_other_via_hub_test(struct kunit *test) { @@ -73,6 +86,7 @@ static void usb_quirks_generic_hub_test(struct kunit *test) static struct kunit_case usb_quirks_test_cases[] = { KUNIT_CASE(usb_quirks_vl122_test), + KUNIT_CASE(usb_quirks_vl822_test), KUNIT_CASE(usb_quirks_other_via_hub_test), KUNIT_CASE(usb_quirks_generic_hub_test), {} diff --git a/drivers/usb/dwc3/dwc3-apple.c b/drivers/usb/dwc3/dwc3-apple.c index c11d299840d80..b45776269a087 100644 --- a/drivers/usb/dwc3/dwc3-apple.c +++ b/drivers/usb/dwc3/dwc3-apple.c @@ -125,6 +125,7 @@ * @idle_rpm: Let the controller runtime-suspend while the core is down * @rpm_held: The glue holds a runtime PM reference for the running core * @pre_core_rpm: Runtime PM is the glue's own until the first core bring-up + * @dt_dis_u3_susphy: The device tree keeps USB3 PHY suspend off itself * @drd_cfg: The controller's configuration block (optional, for its tunable) * @tunables: Controller tunables from the device tree, see dwc3_apple_tunable_list */ @@ -154,6 +155,7 @@ struct dwc3_apple { bool idle_rpm; bool rpm_held; bool pre_core_rpm; + bool dt_dis_u3_susphy; void __iomem *drd_cfg; struct apple_tunable *tunables[DWC3_APPLE_NR_TUNABLES]; @@ -435,6 +437,8 @@ static int dwc3_apple_core_stop(struct dwc3_apple *appledwc) lockdep_assert_held(&appledwc->lock); if (appledwc->core_active) { if (!appledwc->core_exit_pending) { + /* The PHY hands the PIPE back in P3, as it took it over */ + dwc->dis_u3_susphy_quirk = appledwc->dt_dis_u3_susphy; dwc3_enable_susphy(dwc, true); dwc3_event_buffers_cleanup(dwc); appledwc->core_exit_pending = true; @@ -619,6 +623,7 @@ static int dwc3_apple_set_role(struct dwc3_apple *appledwc, enum dwc3_apple_stat * device mode and the T8132 host role were selected in dwc3_apple_core_start(); * other host roles are selected by the xHCI root hub as it starts. */ + appledwc->dwc.dis_u3_susphy_quirk = appledwc->dt_dis_u3_susphy; dwc3_enable_susphy(&appledwc->dwc, true); ret = phy_set_mode(appledwc->usb3_phy, host ? PHY_MODE_USB_HOST : PHY_MODE_USB_DEVICE); if (ret) { @@ -626,6 +631,14 @@ static int dwc3_apple_set_role(struct dwc3_apple *appledwc, enum dwc3_apple_stat if (of_device_is_compatible(appledwc->dev->of_node, "apple,t8132-dwc3")) return ret; dev_warn(appledwc->dev, "USB3 PHY setup failed, USB2 only, err=%d\n", ret); + } else if (host && appledwc->usb3_phy && + of_device_is_compatible(appledwc->dev->of_node, "apple,t8132-dwc3")) { + /* + * The T8132 PHY does not bring a SuperSpeed link back from U3 once + * the core has suspended it: keep it out of P3 from here on. + */ + appledwc->dwc.dis_u3_susphy_quirk = true; + dwc3_enable_susphy(&appledwc->dwc, true); } else if (appledwc->usb3_phy && of_device_is_compatible(appledwc->dev->of_node, "apple,t6040-dwc3")) { dwc3_apple_usb3_pipe_reset(appledwc); @@ -728,6 +741,8 @@ static int dwc3_apple_exit(struct dwc3_apple *appledwc) appledwc->state = DWC3_APPLE_NO_CABLE; break; case DWC3_APPLE_HOST: + /* dwc3_host_exit() lets the USB3 PHY into P3 for the PIPE handback */ + appledwc->dwc.dis_u3_susphy_quirk = appledwc->dt_dis_u3_susphy; dwc3_host_exit(&appledwc->dwc); appledwc->state = DWC3_APPLE_NO_CABLE; break; @@ -855,6 +870,7 @@ static int dwc3_apple_probe(struct platform_device *pdev) appledwc->dev = &pdev->dev; appledwc->fixed_hub = dwc3_apple_has_fixed_hub(dev); appledwc->host_only = dwc3_apple_host_only(dev); + appledwc->dt_dis_u3_susphy = device_property_read_bool(dev, "snps,dis_u3_susphy_quirk"); /* The PHY driver and the DARTs follow runtime PM on these SoCs. */ appledwc->idle_rpm = of_device_is_compatible(dev->of_node, "apple,t6040-dwc3") || of_device_is_compatible(dev->of_node, "apple,t8132-dwc3"); diff --git a/drivers/usb/typec/tipd/Kconfig b/drivers/usb/typec/tipd/Kconfig index 6cc0e1d860704..83cd877951886 100644 --- a/drivers/usb/typec/tipd/Kconfig +++ b/drivers/usb/typec/tipd/Kconfig @@ -75,5 +75,6 @@ config TYPEC_SN201202X_KUNIT_TEST help Test that the SN201202x events are not handled while the controller sleeps, and that a system suspend that fails leaves the controller - awake. These tests run on a simulated SPMI bus, without accessing a + awake. Also test which data status and status count as a link + that wakes the system from suspend-to-idle. These tests run on a simulated SPMI bus, without accessing a physical Type-C controller. diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index defc67404b6ad..2f1b3f4d4ff31 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include #include @@ -46,6 +48,7 @@ #define TPS_REG_SYSTEM_CONF 0x28 #define TPS_REG_CTRL_CONF 0x29 #define TPS_REG_BOOT_STATUS 0x2D +#define TPS_REG_RX_SOURCE_CAPS 0x30 #define TPS_REG_POWER_STATUS 0x3f #define TPS_REG_PD_STATUS 0x40 #define TPS_REG_RX_IDENTITY_SOP 0x48 @@ -1532,10 +1535,11 @@ static void cd321x_resume_work(struct work_struct *work) if (!tps6598x_read_status(tps, &status)) goto retry; + /* Without a plug too, or a power supply removed in sleep stays online */ + if (!tps6598x_read_power_status(tps)) + goto retry; if (status & TPS_STATUS_PLUG_PRESENT) { - if (!tps6598x_read_power_status(tps)) - goto retry; if (TPS_POWER_STATUS_PWROPMODE(tps->pwr_status) == TYPEC_PWR_MODE_PD && tps6598x_read_partner_identity(tps)) goto retry; @@ -1645,6 +1649,46 @@ static int cd321x_check_setup(struct cd321x *cd321x, u64 event) return 0; } +/** + * cd321x_sleep_events - Acknowledge the events of a controller in system sleep + * @tps: The controller, woken for this + * @st: Its data status and status, and for a power sink whether its source is + * capable of USB communication + * + * The resume revalidates the whole port state, so nothing else is read. + * + * Context: tps->lock held. + * Return: 0, or a negative error code. + */ +int cd321x_sleep_events(struct tps6598x *tps, struct cd321x_sleep_state *st) +{ + /* The PDO count, then the first PDO, vSafe5V, which carries the flags */ + u8 caps[1 + sizeof(u32)]; + u64 event; + int ret; + + lockdep_assert_held(&tps->lock); + st->usb_comm = false; + ret = tps6598x_read64(tps, TPS_REG_INT_EVENT1, &event); + if (!ret && event) + ret = tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event); + if (ret) + return ret; + /* A controller that was reset is set up again on resume */ + cd321x_check_setup(container_of(tps, struct cd321x, tps), event); + + ret = tps6598x_read32(tps, TPS_REG_DATA_STATUS, &st->data_status); + if (!ret) + ret = tps6598x_read32(tps, TPS_REG_STATUS, &st->status); + if (!ret && !(st->status & TPS_STATUS_PORTROLE)) { + ret = tps6598x_block_read(tps, TPS_REG_RX_SOURCE_CAPS, caps, sizeof(caps)); + if (!ret && (caps[0] & GENMASK(2, 0))) + st->usb_comm = get_unaligned_le32(&caps[1]) & PDO_FIXED_USB_COMM; + } + return ret; +} +EXPORT_SYMBOL_GPL(cd321x_sleep_events); + static irqreturn_t cd321x_interrupt(int irq, void *data) { struct tps6598x *tps = data; @@ -1653,6 +1697,9 @@ static irqreturn_t cd321x_interrupt(int irq, void *data) u32 status; int ret; + if (tps->sleep_event && tps->sleep_event(tps)) + return IRQ_HANDLED; + mutex_lock(&tps->lock); ret = tps6598x_read64(tps, TPS_REG_INT_EVENT1, &event); @@ -2951,10 +2998,17 @@ int tipd_init(struct tps6598x *tps) } } + tps->wakeup = device_property_read_bool(tps->dev, "wakeup-source"); if (tps->irq) { + /* + * A variant that filters its events in system sleep keeps the + * interrupt live and decides itself whether to wake the system. + */ ret = devm_request_threaded_irq(tps->dev, tps->irq, NULL, tps->data->irq_handler, - IRQF_SHARED | IRQF_ONESHOT, + IRQF_SHARED | IRQF_ONESHOT | + (tps->wakeup && tps->sleep_event ? + IRQF_NO_SUSPEND : 0), dev_name(tps->dev), tps); } else { dev_dbg(tps->dev, "no IRQ specified, using polling mode\n"); @@ -2968,10 +3022,10 @@ int tipd_init(struct tps6598x *tps) fwnode_handle_put(fwnode); - tps->wakeup = device_property_read_bool(tps->dev, "wakeup-source"); if (tps->wakeup && tps->irq) { devm_device_init_wakeup(tps->dev); - enable_irq_wake(tps->irq); + if (!tps->sleep_event) + enable_irq_wake(tps->irq); } tipd_debugfs_register(tps); @@ -3040,8 +3094,10 @@ EXPORT_SYMBOL_GPL(tipd_remove); int tipd_suspend(struct tps6598x *tps) { if (tps->wakeup) { - disable_irq(tps->irq); - enable_irq_wake(tps->irq); + if (!tps->sleep_event) { + disable_irq(tps->irq); + enable_irq_wake(tps->irq); + } } else if (tps->reset) { gpiod_set_value_cansleep(tps->reset, 1); } @@ -3071,8 +3127,10 @@ int tipd_resume(struct tps6598x *tps) } if (tps->wakeup) { - disable_irq_wake(tps->irq); - enable_irq(tps->irq); + if (!tps->sleep_event) { + disable_irq_wake(tps->irq); + enable_irq(tps->irq); + } } else if (tps->reset) { gpiod_set_value_cansleep(tps->reset, 0); msleep(TPS_SETUP_MS); diff --git a/drivers/usb/typec/tipd/sn201202x-pm-test.c b/drivers/usb/typec/tipd/sn201202x-pm-test.c index 1082774dba56f..1d0efec7fa9a3 100644 --- a/drivers/usb/typec/tipd/sn201202x-pm-test.c +++ b/drivers/usb/typec/tipd/sn201202x-pm-test.c @@ -220,12 +220,55 @@ static void sn201202x_unconfirmed_sleep_failed_wakeup_test(struct kunit *test) KUNIT_EXPECT_TRUE(test, sn201202x_test_irq_enabled(t)); } +/* As read on a J713, the dock aside */ +static const struct cd321x_sleep_state sn201202x_test_charger_attach = { + /* Attached as a USB3 device, before the data role swap */ + .data_status = 0x800000e1, .status = 0x1000600d, +}; + +static const struct cd321x_sleep_state sn201202x_test_charger_swapped = { + /* A USB3 link as host for a moment after it, then none */ + .data_status = 0x80000061, .status = 0x1000904d, +}; + +static const struct cd321x_sleep_state sn201202x_test_charger = { + .data_status = 0x80000001, .status = 0x100c90cd, +}; + +static const struct cd321x_sleep_state sn201202x_test_keyboard = { + /* Powered by this port, as are hubs */ + .data_status = 0x00000073, .status = 0x100000fd, +}; + +static const struct cd321x_sleep_state sn201202x_test_dock = { + /* Powers this port and passes USB: a USB capable source */ + .data_status = 0x80000073, .status = 0x100c90cd, .usb_comm = true, +}; + +/* In system sleep a charger is no link; a USB device, dock or display is. */ +static void sn201202x_sleep_links_test(struct kunit *test) +{ + struct cd321x_sleep_state dp = { + .data_status = TPS_DATA_STATUS_DATA_CONNECTION | + TPS_DATA_STATUS_DP_CONNECTION, + .status = 0x100c90cd, + }; + + KUNIT_EXPECT_EQ(test, sn201202x_sleep_links(&sn201202x_test_charger_attach), 0); + KUNIT_EXPECT_EQ(test, sn201202x_sleep_links(&sn201202x_test_charger_swapped), 0); + KUNIT_EXPECT_EQ(test, sn201202x_sleep_links(&sn201202x_test_charger), 0); + KUNIT_EXPECT_NE(test, sn201202x_sleep_links(&sn201202x_test_keyboard), 0); + KUNIT_EXPECT_NE(test, sn201202x_sleep_links(&sn201202x_test_dock), 0); + KUNIT_EXPECT_EQ(test, sn201202x_sleep_links(&dp), TPS_DATA_STATUS_DP_CONNECTION); +} + static struct kunit_case sn201202x_pm_cases[] = { KUNIT_CASE(sn201202x_sleep_cycle_test), KUNIT_CASE(sn201202x_failed_wakeup_test), KUNIT_CASE(sn201202x_rejected_sleep_test), KUNIT_CASE(sn201202x_unconfirmed_sleep_test), KUNIT_CASE(sn201202x_unconfirmed_sleep_failed_wakeup_test), + KUNIT_CASE(sn201202x_sleep_links_test), {} }; diff --git a/drivers/usb/typec/tipd/spmi.c b/drivers/usb/typec/tipd/spmi.c index 4f0ceca25ee7f..65b630d76371a 100644 --- a/drivers/usb/typec/tipd/spmi.c +++ b/drivers/usb/typec/tipd/spmi.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include #include #include #include @@ -8,12 +9,16 @@ #include #include #include +#include #include #include "tps6598x.h" #define tps_to_sn(tps) container_of_const((tps), struct sn201202x, cd.tps) +/* A sleeping controller does not answer a select; give up on it sooner. */ +#define SN201202X_ATOMIC_SELECT_MS 10 + static int regmap_sn201202x_select_reg(struct spmi_device *sdev, u8 reg) { int err; @@ -35,9 +40,15 @@ static int regmap_sn201202x_select_reg(struct spmi_device *sdev, u8 reg) if (sn->polled || sn->select_polled) { int poll_err; - poll_err = read_poll_timeout(spmi_register_read, err, - err || val == reg, 100, 100000, - false, sdev, 0, &val); + if (sn->atomic) + poll_err = read_poll_timeout_atomic(spmi_register_read, err, + err || val == reg, 100, + SN201202X_ATOMIC_SELECT_MS * 1000, + false, sdev, 0, &val); + else + poll_err = read_poll_timeout(spmi_register_read, err, + err || val == reg, 100, 100000, + false, sdev, 0, &val); return err ?: poll_err; } @@ -190,8 +201,15 @@ static const struct of_device_id sn201202x_of_match[] = { {} }; -static int sn201202x_wake(struct sn201202x *sn) +/* + * In system sleep the wake interrupt is suspended, so the controller is + * polled as without interrupts. + */ +static int sn201202x_wake_poll(struct sn201202x *sn, bool poll) { + /* Busy-waiting has no clock to go by: each step is charged instead */ + int budget_ms = 500; + unsigned int tries = 0; unsigned long deadline; u8 size; int ret; @@ -200,16 +218,24 @@ static int sn201202x_wake(struct sn201202x *sn) ret = spmi_command_wakeup(sn->sdev); if (ret) return ret; - if (!sn->polled) - return wait_for_completion_timeout(&sn->wake_completion, - msecs_to_jiffies(100)) ? 0 : -ETIMEDOUT; + /* A WAKEUP sent while it was still going to sleep may go unanswered */ + if (!poll && wait_for_completion_timeout(&sn->wake_completion, + msecs_to_jiffies(100))) + return 0; /* A sleeping ACE3 ACKs commands but returns zeros until it is awake. */ deadline = jiffies + msecs_to_jiffies(500); do { + if (++tries % 20 == 0) { + ret = spmi_command_wakeup(sn->sdev); + if (ret) + return ret; + } ret = regmap_sn201202x_select_reg(sn->sdev, TPS_REG_MODE); - if (ret == -ETIMEDOUT) + if (ret == -ETIMEDOUT) { + budget_ms -= SN201202X_ATOMIC_SELECT_MS; continue; + } if (ret) return ret; ret = spmi_register_read(sn->sdev, 0x1f, &size); @@ -217,12 +243,77 @@ static int sn201202x_wake(struct sn201202x *sn) return ret; if (size) return 0; - usleep_range(1000, 2000); - } while (time_before(jiffies, deadline)); + if (sn->atomic) + mdelay(1); + else + usleep_range(1000, 2000); + budget_ms--; + } while (sn->atomic ? budget_ms > 0 : time_before(jiffies, deadline)); return -ETIMEDOUT; } +static int sn201202x_wake(struct sn201202x *sn) +{ + return sn201202x_wake_poll(sn, sn->polled); +} + +/* + * The links that wake the system when they appear in system sleep. A port + * attaches as a USB device before any data role swap, as it does to a + * charger, and a charger may still show a USB link for a moment after one: + * a USB link counts only with this port as the host, and from a power source + * only if the source is capable of USB communication. + */ +VISIBLE_IF_KUNIT u32 sn201202x_sleep_links(const struct cd321x_sleep_state *st) +{ + u32 links = st->data_status & (TPS_DATA_STATUS_DP_CONNECTION | + TPS_DATA_STATUS_TBT_CONNECTION | + CD321X_DATA_STATUS_USB4_CONNECTION); + + if (st->data_status & TPS_DATA_STATUS_USB_DATA_ROLE) + return links; + if (!(st->status & TPS_STATUS_PORTROLE) && !st->usb_comm) + return links; + return links | (st->data_status & (TPS_DATA_STATUS_USB2_CONNECTION | + TPS_DATA_STATUS_USB3_CONNECTION)); +} +EXPORT_SYMBOL_IF_KUNIT(sn201202x_sleep_links); + +/* + * The event interrupt stays live in system sleep (IRQF_NO_SUSPEND). Wake the + * controller to acknowledge the event, and wake the system only for a new + * data connection, or if the controller cannot be read. It goes back to sleep + * either way, as the resume expects. + */ +static bool sn201202x_sleep_event(struct tps6598x *tps) +{ + struct sn201202x *sn = tps_to_sn(tps); + struct cd321x_sleep_state st; + u32 links = 0; + bool wake; + int ret; + + guard(mutex)(&tps->lock); + if (!READ_ONCE(sn->sleep_watch)) + return false; + + sn->atomic = true; + ret = sn201202x_wake_poll(sn, true); + if (!ret) + ret = cd321x_sleep_events(tps, &st); + if (!ret) + links = sn201202x_sleep_links(&st); + wake = ret || (links & ~sn->sleep_links); + if (!ret) + sn->sleep_links = links; + spmi_command_sleep(sn->sdev); + sn->atomic = false; + if (wake) + pm_wakeup_dev_event(tps->dev, 0, true); + return true; +} + static int sn201202x_probe(struct spmi_device *device) { const struct of_device_id *match; @@ -308,6 +399,13 @@ static int sn201202x_probe(struct spmi_device *device) return ret; } + /* + * In system sleep only the event interrupt stays live. Register access + * then has to poll, which a select notification would not. + */ + if (!sn->polled && sn->select_polled) + tps->sleep_event = sn201202x_sleep_event; + spmi_device_set_drvdata(device, tps); tps->regmap = devm_regmap_init_sn201202x(device, &tps6598x_regmap_config); if (IS_ERR(tps->regmap)) @@ -343,6 +441,11 @@ VISIBLE_IF_KUNIT int __maybe_unused sn201202x_resume(struct device *dev) struct sn201202x *sn = tps_to_sn(tps); int err; + if (READ_ONCE(sn->sleep_watch)) { + /* Waits for a running sleep_event() */ + disable_irq(tps->irq); + WRITE_ONCE(sn->sleep_watch, false); + } err = sn201202x_wake(sn); if (!err) err = tipd_resume(tps); @@ -357,6 +460,7 @@ VISIBLE_IF_KUNIT int __maybe_unused sn201202x_suspend(struct device *dev) { struct tps6598x *tps = dev_get_drvdata(dev); struct sn201202x *sn = tps_to_sn(tps); + bool watch; int err, ret; /* @@ -369,6 +473,19 @@ VISIBLE_IF_KUNIT int __maybe_unused sn201202x_suspend(struct device *dev) err = tipd_suspend(tps); if (err) goto out_enable; + watch = tps->sleep_event && device_may_wakeup(dev) && + pm_suspend_target_state == PM_SUSPEND_TO_IDLE; + if (watch) { + struct cd321x_sleep_state st; + + guard(mutex)(&tps->lock); + /* Without a baseline, a plug-in in sleep cannot be told from a charger */ + watch = !cd321x_sleep_events(tps, &st); + if (watch) + sn->sleep_links = sn201202x_sleep_links(&st); + else + dev_warn(dev, "port state unreadable, no wakeup for an attach\n"); + } reinit_completion(&sn->sleep_completion); err = spmi_command_sleep(sn->sdev); if (err) @@ -378,6 +495,10 @@ VISIBLE_IF_KUNIT int __maybe_unused sn201202x_suspend(struct device *dev) err = -ETIMEDOUT; goto out_wake; } + if (watch) { + WRITE_ONCE(sn->sleep_watch, true); + enable_irq(tps->irq); + } return 0; out_wake: diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index 5a1b73a6d4601..1fa0a4f67258f 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -363,6 +363,8 @@ struct tps6598x { enum power_supply_usb_type usb_type; int wakeup; + /* Handles events while the system sleeps, if the variant can filter them */ + bool (*sleep_event)(struct tps6598x *tps); u32 status; /* status reg */ u32 data_status; u16 pwr_status; @@ -464,6 +466,11 @@ struct sn201202x { struct completion sleep_completion; struct completion wake_completion; struct spmi_device *sdev; + /* Links that wake the system, while the controller's events are filtered */ + u32 sleep_links; + bool sleep_watch; + /* Busy-wait: in suspend-to-idle a sleeping wait outlasts the timers */ + bool atomic; }; extern const struct tipd_data *tipd_hw_variants[]; @@ -478,6 +485,15 @@ int tipd_resume(struct tps6598x *tps); /* Internal to tps6598x-core */ int tps6598x_block_read(struct tps6598x *tps, u8 reg, void *val, size_t len); +/* What the system-sleep event filter of a port reads */ +struct cd321x_sleep_state { + u32 data_status; + u32 status; + /* A power source this port sinks from is capable of USB communication */ + bool usb_comm; +}; + +int cd321x_sleep_events(struct tps6598x *tps, struct cd321x_sleep_state *st); int tps6598x_exec_cmd_tmo(struct tps6598x *tps, const char *cmd, size_t in_len, const u8 *in_data, size_t out_len, u8 *out_data, @@ -504,6 +520,7 @@ cd321x_tbt_switch_get(struct device *dev, struct fwnode_handle *fwnode, int cd321x_tbt_watch_late(struct cd321x *cd321x, struct fwnode_handle *fwnode); int sn201202x_suspend(struct device *dev); int sn201202x_resume(struct device *dev); +u32 sn201202x_sleep_links(const struct cd321x_sleep_state *st); #endif #endif /* __TPS6598X_H__ */ diff --git a/tools/asahi/neo-stability-tests.py b/tools/asahi/neo-stability-tests.py index a8319e71b33d5..067a9d502801c 100644 --- a/tools/asahi/neo-stability-tests.py +++ b/tools/asahi/neo-stability-tests.py @@ -79,8 +79,8 @@ def function(source, name): assert(h==dockchannel_rx_irq&&t==dockchannel_rx_irq_thread&&!strcmp(n,"apple-dockchannel-rx"));rx_irq_requested=irq;rx_flags=f;return 0;} enum dwc3_apple_state {DWC3_APPLE_PROBE_PENDING,DWC3_APPLE_NO_CABLE,DWC3_APPLE_HOST,DWC3_APPLE_DEVICE,DWC3_APPLE_SUSPENDED}; #define DWC3_GCTL_PRTCAP_HOST 1 -struct dwc3 {int num_usb3_ports,num_usb2_ports;int usb3_generic_phy[2],usb2_generic_phy[2],usb3_phy,usb2_phy;int current_dr_role;}; -struct dwc3_apple {struct device*dev;struct dwc3 dwc;int lock,reset;enum dwc3_apple_state state;bool fixed_hub,host_only,rpm_held,core_active,core_exit_pending,reset_pending;unsigned usb3_power_off_pending,usb2_power_off_pending;}; +struct dwc3 {int num_usb3_ports,num_usb2_ports;int usb3_generic_phy[2],usb2_generic_phy[2],usb3_phy,usb2_phy;int current_dr_role;bool dis_u3_susphy_quirk;}; +struct dwc3_apple {struct device*dev;struct dwc3 dwc;int lock,reset;enum dwc3_apple_state state;bool fixed_hub,host_only,rpm_held,core_active,core_exit_pending,reset_pending,dt_dis_u3_susphy;unsigned usb3_power_off_pending,usb2_power_off_pending;}; static int core_error,host_error,gadget_error,reset_error,role_error,start_role; static unsigned rpm_puts; /* A started core holds the glue runtime PM reference until core_stop drops it. */ diff --git a/tools/testing/selftests/drivers/apple/sn201202x-transport.py b/tools/testing/selftests/drivers/apple/sn201202x-transport.py index 8f80c82ead71c..1197e8ab80de2 100755 --- a/tools/testing/selftests/drivers/apple/sn201202x-transport.py +++ b/tools/testing/selftests/drivers/apple/sn201202x-transport.py @@ -43,7 +43,7 @@ def main(): struct completion { int unused; }; struct device { void *of_node; }; struct tps6598x { struct device *dev; }; -struct sn201202x { bool polled, select_polled; struct completion select_completion; }; +struct sn201202x { bool polled, select_polled, atomic; struct completion select_completion; }; struct spmi_device { struct device dev; struct tps6598x tps; }; static struct sn201202x sn; static u8 selected, advertised; @@ -90,6 +90,8 @@ def main(): for(unsigned attempt=0;attempt<8;attempt++) { \ (ret)=op(__VA_ARGS__); if(condition) { poll_ret=0; break; } \ } poll_ret; }) +#define SN201202X_ATOMIC_SELECT_MS 10 +#define read_poll_timeout_atomic read_poll_timeout ''' + function(source, "regmap_sn201202x_select_reg") + function(source, "regmap_sn201202x_read") + r''' static void clean(struct spmi_device *s) { @@ -108,6 +110,10 @@ def main(): assert(!regmap_sn201202x_read(&s,®,1,out,4)); assert(writes==1 && polls==3 && completed && length_reads==1 && payload_reads==1); assert(bytes==4 && out[0]==0 && out[3]==3 && out[4]==0xa5); + clean(&s); sn.atomic=true; memset(out,0xa5,sizeof(out)); + assert(!regmap_sn201202x_read(&s,®,1,out,4)); + assert(writes==1 && polls==3 && completed && length_reads==1 && payload_reads==1); + assert(bytes==4 && out[0]==0 && out[3]==3 && out[4]==0xa5); puts("ok 1 - exact selector completion precedes length and payload"); for(unsigned length=0;length<=255;length++) for(unsigned request=0;request<=64;request++) { clean(&s); advertised=length; memset(out,0xa5,sizeof(out)); @@ -147,7 +153,7 @@ def main(): return 0; } ''' - busy = program.replace("err || val == reg", "err || (val & 0x7f) == reg", 1) + busy = program.replace("err || val == reg", "err || (val & 0x7f) == reg") unchecked = program.replace( 'if (of_device_is_compatible(sdev->dev.of_node, "apple,t8132-sn201202x"))', 'if (false)', 1) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/mocks.c b/tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/mocks.c index 22e01ce7be38d..7d643a83eedd8 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/mocks.c +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/mocks.c @@ -74,6 +74,7 @@ struct dwc3 { int num_usb2_ports, num_usb3_ports, usb2_phy, usb3_phy; struct reset_controller_dev *reset; int dev, usb_psy, vbus_draw_work, dr_mode, current_dr_role; + bool dis_u3_susphy_quirk; struct { void *notifier_call; } psy_nb; }; struct dwc3_apple { @@ -87,6 +88,7 @@ struct dwc3_apple { bool rpm_held, host_only; /* T8132 only: runtime PM before the first core probe; never set here. */ bool pre_core_rpm; + bool dt_dis_u3_susphy; unsigned long usb2_power_off_pending, usb3_power_off_pending; }; static unsigned int polls, fail_poll, reset_calls, reset_writes, core_writes; diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/teardown_pm.py b/tools/testing/selftests/drivers/gpu/apple-dcp/teardown_pm.py index 7381092399252..b293087d4917c 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/teardown_pm.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/teardown_pm.py @@ -92,15 +92,15 @@ def unit_for(source): static struct xhci_hcd xhci_context; #define XHCI_STATE_REMOVING 1 #define hcd_to_xhci(h) (&xhci_context) -struct dwc3 { bool sys_wakeup; struct platform_device *xhci; }; +struct dwc3 { bool sys_wakeup, dis_u3_susphy_quirk; struct platform_device *xhci; }; enum { DWC3_APPLE_PROBE_PENDING, DWC3_APPLE_NO_CABLE, DWC3_APPLE_HOST, DWC3_APPLE_DEVICE }; /* pre_core_rpm is T8132 only: runtime PM before the first core probe; never set here. */ -struct dwc3_apple { struct dwc3 dwc; struct device *dev; int lock,state; void *role_sw; bool pre_core_rpm; }; +struct dwc3_apple { struct dwc3 dwc; struct device *dev; int lock,state; void *role_sw; bool pre_core_rpm, dt_dis_u3_susphy; }; static struct dwc3_apple apple_context; #define to_dwc3_apple(d) container_of(d,struct dwc3_apple,dwc) static bool inside_host_remove; static void *platform_get_drvdata(struct platform_device *d) { return d==&controller?(void *)&apple_context.dwc:(void *)&hcd; } -static void dwc3_enable_susphy(struct dwc3 *d,bool v) { (void)d;assert(v);susphy_calls++;observed_controller_write(); } +static void dwc3_enable_susphy(struct dwc3 *d,bool v) { assert(v&&d->dis_u3_susphy_quirk==apple_context.dt_dis_u3_susphy);susphy_calls++;observed_controller_write(); } static void device_init_wakeup(struct device *d,bool b) { (void)d;(void)b; } static void platform_device_unregister(struct platform_device *d); static void dwc3_gadget_exit(struct dwc3 *d) { (void)d;assert(0); } @@ -158,7 +158,7 @@ def unit_for(source): struct apple_atcphy p;prepare(&p,true,true,false); struct device_private private={0};controller.dev.p=&private;host_device.p=&private; controller_driver.remove=controller_remove;host_driver.remove=host_remove; - apple_context=(struct dwc3_apple){.dwc={.xhci=&host_platform_device},.dev=&controller.dev,.state=DWC3_APPLE_HOST}; + apple_context=(struct dwc3_apple){.dwc={.xhci=&host_platform_device,.dis_u3_susphy_quirk=true},.dev=&controller.dev,.state=DWC3_APPLE_HOST}; xhci_context=(struct xhci_hcd){0}; inject_remove=true;removal_started=blocked_attempt=release_remove=false; atomic_store(&early_write,false);atomic_store(&handoff_active,true); diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py b/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py index 6c40adf5d3a85..f3890d43e1b07 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py @@ -341,7 +341,7 @@ def probe(root): unit = quiesce.make_unit(root) unit = unit.replace(block(unit, 'int main('), block(unit, 'int main(').replace('int main(', 'int __attribute__((unused)) prior_main(', 1), 1) unit = unit.replace('struct tipd_data {', 'struct fwnode_handle;\nstruct tipd_data { unsigned irq_mask1; int (*init)(struct tps6598x *); int (*switch_power_state)(struct tps6598x *, int); int (*register_port)(struct tps6598x *, struct fwnode_handle *); bool (*read_data_status)(struct tps6598x *); bool (*read_power_status)(struct tps6598x *); int (*connect)(struct tps6598x *,u32); void (*ready)(struct tps6598x *); void *irq_handler;') - unit = unit.replace('struct tps6598x {', 'struct tps6598x { bool dbma_at_probe,wakeup; u32 status;') + unit = unit.replace('struct tps6598x {', 'struct tps6598x { bool dbma_at_probe,wakeup; u32 status; bool (*sleep_event)(struct tps6598x *);') helpers = r""" #undef dev_err #define dev_err(...) ((void)0) @@ -359,6 +359,7 @@ def probe(root): #define PTR_ERR(p) ((int)(intptr_t)(p)) #define IRQF_SHARED 0 #define IRQF_ONESHOT 0 +#define IRQF_NO_SUSPEND 0 #define POLL_INTERVAL 1000 #define INIT_DELAYED_WORK(...) ((void)0) #define tps6598x_poll_work 0