From: Vladyslav Tymoshchuk <tvlad744@gmail.com>
To: Thinh Nguyen <Thinh.Nguyen@synopsys.com>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Krishna Kurapati <krishna.kurapati@oss.qualcomm.com>,
Faisal Hassan <faisal.hassan@oss.qualcomm.com>,
Abel Vesa <abel.vesa@oss.qualcomm.com>,
linux-arm-msm@vger.kernel.org, linux-usb@vger.kernel.org,
linux-kernel@vger.kernel.org,
Vladyslav Tymoshchuk <tvlad744@gmail.com>
Subject: [PATCH] usb: dwc3: qcom: Defer VBUS override update during system suspend
Date: Sun, 18 Oct 2026 17:31:26 +0300 [thread overview]
Message-ID: <20261018143126.3978-1-tvlad744@gmail.com> (raw)
dwc3_qcom_set_role_notifier() updates the VBUS override in QSCRATCH
synchronously, from the context of the usb_role_switch_set_role()
caller. The glue assumes that role switches only happen from a
freezable workqueue, but that holds for the dwc3 core's mode switch,
not for this notifier: the UCSI connector work, for one, runs on the
non-freezable system workqueue.
On SM8750, when the USB cable is unplugged while the system is in
s2idle, the UCSI connector work for the change runs before the devices
have resumed. It switches the role to USB_ROLE_NONE, and the notifier
accesses QSCRATCH while the glue clocks are still off, before
dwc3_qcom_pm_resume() turns them back on. The access does not complete
and the SoC resets. pm_runtime_resume_and_get() does not prevent this:
runtime PM is disabled at that point and the device is still marked
active, so it returns success without resuming anything.
While the glue is suspended for system sleep, only record the VBUS
override update and apply it from dwc3_qcom_pm_resume() once the
clocks are on. Apply it after the core has resumed, since resuming the
gadget enables the override and the role switch is the more recent
event. Serialize the notifier against system suspend and resume with a
mutex, so that a role switch can't race with the clocks being turned
off.
Fixes: 21188e8d6d75 ("usb: dwc3: qcom: Implement glue callbacks to facilitate runtime suspend")
Cc: stable@vger.kernel.org
Signed-off-by: Vladyslav Tymoshchuk <tvlad744@gmail.com>
---
Tested on SM8750 (OnePlus Pad 3; the board DT is not upstream yet),
v7.3-rc5 plus board patches, with the UCSI connector work still on
system_wq. The test kernel also has local ucsi_glink and qcom_battmgr
changes that make a cable plug or unplug wake the system from s2idle,
similar to Jishnu Prakash's ucsi_glink patch [3]:
- cable unplugged during s2idle: resumed in 2 of 2 runs; without this
patch the SoC reset in all 6 earlier runs. The switch to
USB_ROLE_NONE came 14-17 ms after the start of the resume, during
the noirq phase, and was only recorded; dwc3_qcom_pm_resume()
cleared the override about a second later.
- cable plugged in during s2idle, s2idle with the cable connected,
and unplug/replug while awake: no change in behaviour.
I have not tested this patch without the wakeup changes.
The UCSI side is discussed in Abel Vesa's RFC to queue the connector
work on system_freezable_wq [1]. This patch is needed either way, as
not every caller of usb_role_switch_set_role() is frozen during system
sleep.
Faisal Hassan's SA8255P series [2] also changes dwc3_qcom_pm_suspend()
and dwc3_qcom_pm_resume() (the wakeup argument becomes a pm_message_t);
whichever goes in second needs a small rebase.
I used an AI assistant (Claude) for the analysis and to draft this
patch and its description; I reviewed the result and ran the tests on
my device.
[1] https://lore.kernel.org/all/20250205-ucsi-schedule-conn-worker-on-freezable-wq-v1-1-107d1356b77b@linaro.org/
[2] https://lore.kernel.org/all/20261010053246.2991984-3-faisal.hassan@oss.qualcomm.com/
[3] https://lore.kernel.org/all/20260722-ucsi_glink_wakeup-v2-1-b43dee56df24@oss.qualcomm.com/
drivers/usb/dwc3/dwc3-qcom.c | 67 ++++++++++++++++++++++++++++--------
1 file changed, 52 insertions(+), 15 deletions(-)
diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c
index ac68b4218b56..2997479988c7 100644
--- a/drivers/usb/dwc3/dwc3-qcom.c
+++ b/drivers/usb/dwc3/dwc3-qcom.c
@@ -10,6 +10,7 @@
#include <linux/irq.h>
#include <linux/of_clk.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/kernel.h>
#include <linux/interconnect.h>
#include <linux/platform_device.h>
@@ -85,6 +86,11 @@ struct dwc3_qcom {
struct icc_path *icc_path_apps;
enum usb_role current_role;
+
+ /* Serializes the role notifier against system suspend and resume */
+ struct mutex role_lock;
+ bool vbus_override_pending;
+ bool vbus_override;
};
#define to_dwc3_qcom(d) container_of((d), struct dwc3_qcom, dwc)
@@ -561,27 +567,40 @@ static int dwc3_qcom_setup_irq(struct dwc3_qcom *qcom, struct platform_device *p
static void dwc3_qcom_set_role_notifier(struct dwc3 *dwc, enum usb_role next_role)
{
struct dwc3_qcom *qcom = to_dwc3_qcom(dwc);
+ bool enable;
+
+ guard(mutex)(&qcom->role_lock);
if (qcom->current_role == next_role)
return;
+ enable = qcom->current_role != USB_ROLE_DEVICE;
+
+ /*
+ * Not every caller of usb_role_switch_set_role() is frozen during
+ * system sleep, e.g. the UCSI connector work can run in the noirq
+ * resume phase. The glue clocks are off then and runtime PM can't
+ * turn them on, so leave QSCRATCH alone and let
+ * dwc3_qcom_pm_resume() apply the update.
+ */
+ if (qcom->pm_suspended) {
+ qcom->vbus_override_pending = true;
+ qcom->vbus_override = enable;
+ qcom->current_role = next_role;
+ return;
+ }
+
if (pm_runtime_resume_and_get(qcom->dev)) {
dev_dbg(qcom->dev, "Failed to resume device\n");
return;
}
- if (qcom->current_role == USB_ROLE_DEVICE)
- dwc3_qcom_vbus_override_enable(qcom, false);
- else if (qcom->current_role != USB_ROLE_DEVICE)
- dwc3_qcom_vbus_override_enable(qcom, true);
+ qcom->vbus_override_pending = false;
+ dwc3_qcom_vbus_override_enable(qcom, enable);
pm_runtime_mark_last_busy(qcom->dev);
pm_runtime_put_sync(qcom->dev);
- /*
- * Current role changes via usb_role_switch_set_role callback protected
- * internally by mutex lock.
- */
qcom->current_role = next_role;
}
@@ -624,6 +643,10 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
qcom->dev = &pdev->dev;
+ ret = devm_mutex_init(dev, &qcom->role_lock);
+ if (ret)
+ return ret;
+
qcom->resets = devm_reset_control_array_get_optional_exclusive(dev);
if (IS_ERR(qcom->resets)) {
return dev_err_probe(&pdev->dev, PTR_ERR(qcom->resets),
@@ -756,6 +779,8 @@ static int dwc3_qcom_pm_suspend(struct device *dev)
if (ret)
return ret;
+ guard(mutex)(&qcom->role_lock);
+
ret = dwc3_qcom_suspend(qcom, wakeup);
if (ret)
return ret;
@@ -772,17 +797,29 @@ static int dwc3_qcom_pm_resume(struct device *dev)
bool wakeup = device_may_wakeup(dev);
int ret;
- ret = dwc3_qcom_resume(qcom, wakeup);
- if (ret)
- return ret;
+ scoped_guard(mutex, &qcom->role_lock) {
+ ret = dwc3_qcom_resume(qcom, wakeup);
+ if (ret)
+ return ret;
- qcom->pm_suspended = false;
+ qcom->pm_suspended = false;
+ }
ret = dwc3_pm_resume(&qcom->dwc);
- if (ret)
- return ret;
- return 0;
+ /*
+ * Apply a VBUS override update from a role switch that came in while
+ * suspended. Do it after the core has resumed, as resuming the gadget
+ * enables the override and the role switch is the more recent event.
+ */
+ scoped_guard(mutex, &qcom->role_lock) {
+ if (qcom->vbus_override_pending) {
+ dwc3_qcom_vbus_override_enable(qcom, qcom->vbus_override);
+ qcom->vbus_override_pending = false;
+ }
+ }
+
+ return ret;
}
static void dwc3_qcom_complete(struct device *dev)
base-commit: a90ee4305c4a5df72c11b31dacfdc76e00fcf78a
--
2.43.0
next reply other threads:[~2026-10-18 14:31 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-18 14:31 Vladyslav Tymoshchuk [this message]
2026-10-18 14:54 ` [PATCH] usb: dwc3: qcom: Defer VBUS override update during system suspend sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261018143126.3978-1-tvlad744@gmail.com \
--to=tvlad744@gmail.com \
--cc=Thinh.Nguyen@synopsys.com \
--cc=abel.vesa@oss.qualcomm.com \
--cc=faisal.hassan@oss.qualcomm.com \
--cc=gregkh@linuxfoundation.org \
--cc=krishna.kurapati@oss.qualcomm.com \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox