From f0703a06027df01ed331067e00093f395c43afc2 Mon Sep 17 00:00:00 2001 From: Anze Date: Sun, 19 Jul 2026 00:28:58 +0200 Subject: [PATCH 1/2] SM8750: fix suspend wedge from ucsi USB role on charger unplug Add a SM8750 kernel patch reverting the u_role gating introduced by 0c8ee850572 ("usb: typec: ucsi: Add UCSI_USB4_IMPLIES_USB quirk for X1E80100"). Without that quirk the USB data role is never cleared for a charger-only partner and stays attached across suspend, keeping the USB power island up; the firmware power-collapse then hard-hangs the SoC when the cable is unplugged while suspended. Device-tested on the AYN Odin 3. --- .../0509-usb-typec-ucsi-clear-USB-role.patch | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 projects/ROCKNIX/devices/SM8750/patches/linux/0509-usb-typec-ucsi-clear-USB-role.patch diff --git a/projects/ROCKNIX/devices/SM8750/patches/linux/0509-usb-typec-ucsi-clear-USB-role.patch b/projects/ROCKNIX/devices/SM8750/patches/linux/0509-usb-typec-ucsi-clear-USB-role.patch new file mode 100644 index 0000000000..ba52de19a7 --- /dev/null +++ b/projects/ROCKNIX/devices/SM8750/patches/linux/0509-usb-typec-ucsi-clear-USB-role.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Anze +Date: Sun, 19 Jul 2026 00:30:00 +0200 +Subject: [PATCH] usb: typec: ucsi: clear USB role when partner lacks USB + data + +Commit 0c8ee850572 ("usb: typec: ucsi: Add UCSI_USB4_IMPLIES_USB quirk +for X1E80100") gated the "partner advertises no USB data -> +USB_ROLE_NONE" decision behind the UCSI_USB4_IMPLIES_USB quirk. On +ports without that quirk (e.g. SM8750 / AYN Odin 3, pmic_glink UCSI) +the USB data role is then never cleared for a charger-only partner, so +it stays attached across system suspend. That keeps the USB power +island up, and the deep-suspend firmware power-collapse hard-hangs the +SoC when the cable is unplugged while suspended: dead screen, the SoC +runs hot, only a forced reset recovers (7.0.x is fine, 7.1.x hangs). + +Restore the pre-0c8ee850572 behaviour: clear the USB role whenever the +partner advertises no USB data. + +Signed-off-by: Anze +--- +--- a/drivers/usb/typec/ucsi/ucsi.c ++++ b/drivers/usb/typec/ucsi/ucsi.c +@@ -1196,10 +1196,8 @@ + typec_partner_set_usb_mode(con->partner, USB_MODE_USB4); + } + +- if ((!UCSI_CONSTAT(con, PARTNER_FLAG_USB)) && +- ((con->ucsi->quirks & UCSI_USB4_IMPLIES_USB) && +- (!(UCSI_CONSTAT(con, PARTNER_FLAG_USB4_GEN3) || +- UCSI_CONSTAT(con, PARTNER_FLAG_USB4_GEN4))))) ++ /* Only notify USB controller if partner supports USB data */ ++ if (!(UCSI_CONSTAT(con, PARTNER_FLAG_USB))) + u_role = USB_ROLE_NONE; + + ret = usb_role_switch_set_role(con->usb_role_sw, u_role); From 7ba91dd7dab495e427abcbdc00048886e94879fb Mon Sep 17 00:00:00 2001 From: Anze Date: Tue, 21 Jul 2026 16:52:02 +0200 Subject: [PATCH 2/2] SM8750: drop USB data roles over suspend, re-derive at resume Companion to the charger-only fix (0509): a dock DOES advertise USB data, so its role survives suspend, the USB power island stays up and the SM8750 firmware power-collapse hard-hangs on cable events while asleep. Kernel patch 0510 quiesces the USB role on every UCSI connector at suspend entry and re-derives it from fresh PPM state at resume: - per-instance state: the PM notifier block lives in struct ucsi (container_of in the callback), no globals - symmetric teardown: registered in ucsi_register(), synchronously unregistered first thing in ucsi_unregister() -- the path the pmic_glink PDR/SSR teardown takes -- so an ADSP restart cannot leave a notifier pointing at freed connectors; ucsi->connector is NULLed after the kfree - synchronization: role writes happen under con->lock and a ucsi->usb_role_suspended flag makes every role-setting path compute USB_ROLE_NONE while suspend is in progress, closing the race with the connector workqueue - no blind restore: at PM_POST_SUSPEND a partner task re-reads GET_CONNECTOR_STATUS and re-derives the role from fresh state, covering partners that stayed, vanished or appeared during sleep Device-tested on AYN Odin 3: dock attached -> suspend -> unplug while asleep -> clean resume; suspend/resume with charger and with dock kept attached also verified (charging intact after resume). --- ...ec-ucsi-drop-usb-role-during-suspend.patch | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 projects/ROCKNIX/devices/SM8750/patches/linux/0510-usb-typec-ucsi-drop-usb-role-during-suspend.patch diff --git a/projects/ROCKNIX/devices/SM8750/patches/linux/0510-usb-typec-ucsi-drop-usb-role-during-suspend.patch b/projects/ROCKNIX/devices/SM8750/patches/linux/0510-usb-typec-ucsi-drop-usb-role-during-suspend.patch new file mode 100644 index 0000000000..997e7c84c7 --- /dev/null +++ b/projects/ROCKNIX/devices/SM8750/patches/linux/0510-usb-typec-ucsi-drop-usb-role-during-suspend.patch @@ -0,0 +1,206 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:01 2001 +From: Anze +Date: Mon, 21 Jul 2026 13:30:00 +0200 +Subject: [PATCH] usb: typec: ucsi: quiesce USB data roles over system suspend + +The charger-unplug wedge fix (0509) only clears the USB data role for +partners that advertise no USB data. A dock DOES advertise USB data, so +the role stays attached across suspend, the USB power island stays up, +and the SM8750 firmware power-collapse hard-hangs on cable events while +asleep, exactly like the charger case. + +Quiesce the USB role on every connector at suspend entry and re-derive +it from fresh PPM state at resume: + +- Per-instance state: the PM notifier block lives in struct ucsi + (container_of in the callback), no globals, any number of UCSI + instances and connectors. +- Symmetric teardown: the notifier is registered in ucsi_register() and + synchronously unregistered first thing in ucsi_unregister(), which is + exactly the path the pmic_glink PDR/SSR teardown takes, so an ADSP + restart cannot leave a notifier pointing at freed connectors. + ucsi->connector is also NULLed after the kfree so the re-registered + instance cannot iterate a stale pointer before init completes. +- Synchronization: role changes are made under con->lock, the same + mutex the connector event handler and partner tasks hold, and + ucsi->usb_role_suspended makes every role-setting path compute + USB_ROLE_NONE while suspend is in progress, so a connector event + racing suspend entry cannot re-raise the role. +- No blind restore: nothing is saved. At PM_POST_SUSPEND a partner task + re-reads GET_CONNECTOR_STATUS and re-derives the role from fresh + state (ucsi_check_connection() plus an explicit ucsi_partner_change() + for the state-unchanged case), covering partners that stayed, + vanished or appeared while the system was asleep. + +A PM notifier is used instead of dev_pm_ops on purpose: at +PM_SUSPEND_PREPARE the system is fully awake, so the USB controller +processes the switch to USB_ROLE_NONE and drops the power island before +any device suspends; a device PM callback would give no ordering +guarantee against the USB controller's own suspend. + +Device-tested on AYN Odin 3: dock attached -> suspend -> dock unplugged +while asleep -> clean resume (previously a hard wedge). + +Signed-off-by: Anze +--- +--- a/drivers/usb/typec/ucsi/ucsi.c ++++ b/drivers/usb/typec/ucsi/ucsi.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -1200,6 +1201,10 @@ + if (!(UCSI_CONSTAT(con, PARTNER_FLAG_USB))) + u_role = USB_ROLE_NONE; + ++ /* USB roles are quiesced over system suspend, see ucsi_pm_notify() */ ++ if (READ_ONCE(con->ucsi->usb_role_suspended)) ++ u_role = USB_ROLE_NONE; ++ + ret = usb_role_switch_set_role(con->usb_role_sw, u_role); + if (ret) + dev_err(con->ucsi->dev, "con:%d: failed to set usb role:%d\n", +@@ -1781,6 +1786,10 @@ + if (!(UCSI_CONSTAT(con, PARTNER_FLAG_USB))) + u_role = USB_ROLE_NONE; + ++ /* USB roles are quiesced over system suspend, see ucsi_pm_notify() */ ++ if (READ_ONCE(ucsi->usb_role_suspended)) ++ u_role = USB_ROLE_NONE; ++ + ret = usb_role_switch_set_role(con->usb_role_sw, u_role); + if (ret) { + dev_err(ucsi->dev, "con:%d: failed to set usb role:%d\n", +@@ -1954,6 +1963,79 @@ + return ret; + } + ++/* ++ * On Qualcomm SM8750 the firmware hangs in power collapse when a cable ++ * event arrives while a USB data role is still attached: the active role ++ * keeps the USB power island up across suspend, and losing the partner ++ * while the SoC is asleep wedges the firmware (hard hang, forced reset). ++ * ++ * Quiesce the USB role on every connector at suspend entry. A PM ++ * notifier is used instead of a device PM callback on purpose: it runs ++ * while the system is still fully awake, so the USB controller processes ++ * the switch to USB_ROLE_NONE and drops the power island before any ++ * device starts suspending (a dev_pm_ops callback would give no ordering ++ * guarantee against the USB controller's own suspend). ++ * ++ * Nothing is saved or blindly restored: ucsi->usb_role_suspended makes ++ * every role-setting path compute USB_ROLE_NONE while set, and at resume ++ * the connector status is re-read from the PPM so the role is re-derived ++ * from fresh state. That covers partners that stayed, vanished or ++ * appeared while the system was asleep. ++ */ ++static int ucsi_pm_resync_connection(struct ucsi_connector *con) ++{ ++ bool was_connected = UCSI_CONSTAT(con, CONNECTED); ++ int ret; ++ ++ ret = ucsi_check_connection(con); ++ if (ret) ++ return ret; ++ ++ /* ++ * ucsi_check_connection() leaves the USB role alone when the ++ * connection state did not change; the role was quiesced for ++ * suspend, so re-apply it from the fresh status here as well. ++ */ ++ if (was_connected && UCSI_CONSTAT(con, CONNECTED)) ++ ucsi_partner_change(con); ++ ++ return 0; ++} ++ ++static int ucsi_pm_notify(struct notifier_block *nb, unsigned long action, ++ void *data) ++{ ++ struct ucsi *ucsi = container_of(nb, struct ucsi, pm_nb); ++ struct ucsi_connector *connector = READ_ONCE(ucsi->connector); ++ struct ucsi_connector *con; ++ ++ if (!connector) ++ return NOTIFY_DONE; ++ ++ switch (action) { ++ case PM_SUSPEND_PREPARE: ++ WRITE_ONCE(ucsi->usb_role_suspended, true); ++ for (con = connector; con->port; con++) { ++ mutex_lock(&con->lock); ++ if (con->usb_role_sw) ++ usb_role_switch_set_role(con->usb_role_sw, ++ USB_ROLE_NONE); ++ mutex_unlock(&con->lock); ++ } ++ break; ++ case PM_POST_SUSPEND: ++ WRITE_ONCE(ucsi->usb_role_suspended, false); ++ for (con = connector; con->port; con++) { ++ mutex_lock(&con->lock); ++ ucsi_partner_task(con, ucsi_pm_resync_connection, 1, 0); ++ mutex_unlock(&con->lock); ++ } ++ break; ++ } ++ ++ return NOTIFY_DONE; ++} ++ + static void ucsi_resume_work(struct work_struct *work) + { + struct ucsi *ucsi = container_of(work, struct ucsi, resume_work); +@@ -2123,6 +2205,9 @@ + UCSI_BCD_GET_MINOR(ucsi->version), + UCSI_BCD_GET_SUBMINOR(ucsi->version)); + ++ ucsi->pm_nb.notifier_call = ucsi_pm_notify; ++ register_pm_notifier(&ucsi->pm_nb); ++ + queue_delayed_work(system_long_wq, &ucsi->work, 0); + + ucsi_debugfs_register(ucsi); +@@ -2141,6 +2226,9 @@ + u64 cmd = UCSI_SET_NOTIFICATION_ENABLE; + int i; + ++ /* Synchronous: no PM notifier runs beyond this point */ ++ unregister_pm_notifier(&ucsi->pm_nb); ++ + /* Make sure that we are not in the middle of driver initialization */ + cancel_delayed_work_sync(&ucsi->work); + cancel_work_sync(&ucsi->resume_work); +@@ -2183,6 +2271,7 @@ + } + + kfree(ucsi->connector); ++ ucsi->connector = NULL; + } + EXPORT_SYMBOL_GPL(ucsi_unregister); + +--- a/drivers/usb/typec/ucsi/ucsi.h ++++ b/drivers/usb/typec/ucsi/ucsi.h +@@ -7,6 +7,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -477,6 +478,10 @@ + struct work_struct resume_work; + struct delayed_work work; + int work_count; ++ ++ /* USB roles are quiesced over system suspend, see ucsi_pm_notify() */ ++ struct notifier_block pm_nb; ++ bool usb_role_suspended; + #define UCSI_ROLE_SWITCH_RETRY_PER_HZ 10 + #define UCSI_ROLE_SWITCH_INTERVAL (HZ / UCSI_ROLE_SWITCH_RETRY_PER_HZ) + #define UCSI_ROLE_SWITCH_WAIT_COUNT (10 * UCSI_ROLE_SWITCH_RETRY_PER_HZ)