* [PATCH] usb: dwc3: qcom: Restore UTMI PIPE clock on resume
@ 2026-10-27 1:21 Filippo Falezza
2026-10-27 5:01 ` Prashanth K
0 siblings, 1 reply; 2+ messages in thread
From: Filippo Falezza @ 2026-10-27 1:21 UTC (permalink / raw)
To: Thinh Nguyen, Greg Kroah-Hartman, Manu Gautam, Felipe Balbi
Cc: Bjorn Andersson, linux-arm-msm, linux-usb, linux-kernel, Filippo Falezza
Boards whose controller has no SuperSpeed PHY set
qcom,select-utmi-as-pipe-clk, and the glue then programs QSCRATCH
GENERAL_CFG so that the core's PIPE clock domain runs from the UTMI
clock. This is only done at probe.
When the SoC reaches its deepest state in suspend-to-idle (CX power
collapse), the QSCRATCH registers lose their contents. On resume the
core is reinitialised with its PIPE domain unclocked, and the gadget
does not come back:
dwc3-qcom a600000.usb: failed to enable ep0out
The link stays down until the driver is unbound and bound again.
Remember the property at probe and select the UTMI clock again in
dwc3_qcom_resume(), after the clocks and interconnect paths are enabled
and before the core is resumed. Move dwc3_qcom_select_utmi_clk() up so
that it is defined before its new caller.
Fixes: a4333c3a6ba9 ("usb: dwc3: Add Qualcomm DWC3 glue driver")
Signed-off-by: Filippo Falezza <effeffe99@gmail.com>
---
Hello there,
this fixes USB peripheral mode after suspend on a Motorola Moto G 5G
Plus (SM7250), a phone with a USB 2.0-only port that I'm bringing up on
mainline. Its controller needs qcom,select-utmi-as-pipe-clk. The first
s2idle that reached CX power collapse broke the gadget: "failed to
enable ep0out" on resume, and USB networking stayed down until I
rebound dwc3-qcom by hand. Rebinding works because probe programs
GENERAL_CFG again, which is what this patch does in the resume path.
With the patch, the gadget (NCM plus ACM) reconnects after s2idle, and
the SSH session over the cable that I use for testing survives
suspend; the phone has run with it on linux-next since 11 October.
The other boards that use
qcom,select-utmi-as-pipe-clk should benefit as well, but I have only
tested this one, in peripheral mode. The legacy glue driver
(dwc3-qcom-legacy.c) has the same probe-only programming; I left it
alone because I can't test it.
The patch is on top of usb-linus (6b4b543fa187) and builds with W=1 for
arm64.
I wrote this patch and drafted this note with the help of an AI coding
assistant (Claude Code). The testing above is on the phone.
Thanks,
Filippo
---
drivers/usb/dwc3/dwc3-qcom.c | 45 +++++++++++++++++++++++++++-----------------
1 file changed, 28 insertions(+), 17 deletions(-)
diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c
index ac68b4218b56..6334ca7ba30f 100644
--- a/drivers/usb/dwc3/dwc3-qcom.c
+++ b/drivers/usb/dwc3/dwc3-qcom.c
@@ -81,6 +81,7 @@ struct dwc3_qcom {
enum usb_dr_mode mode;
bool is_suspended;
bool pm_suspended;
+ bool utmi_as_pipe_clk;
struct icc_path *icc_path_ddr;
struct icc_path *icc_path_apps;
@@ -335,6 +336,23 @@ static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom)
dwc3_qcom_enable_port_interrupts(&qcom->ports[i]);
}
+static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom)
+{
+ /* Configure dwc3 to use UTMI clock as PIPE clock not present */
+ dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
+ PIPE_UTMI_CLK_DIS);
+
+ usleep_range(100, 1000);
+
+ dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
+ PIPE_UTMI_CLK_SEL | PIPE3_PHYSTATUS_SW);
+
+ usleep_range(100, 1000);
+
+ dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
+ PIPE_UTMI_CLK_DIS);
+}
+
static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, bool wakeup)
{
u32 val;
@@ -388,6 +406,15 @@ static int dwc3_qcom_resume(struct dwc3_qcom *qcom, bool wakeup)
if (ret)
dev_warn(qcom->dev, "failed to enable interconnect: %d\n", ret);
+ /*
+ * The QSCRATCH registers lose their state when the controller's power
+ * domain is collapsed during system suspend: select the UTMI clock
+ * for the PIPE domain again, or the core can't run without a PIPE
+ * clock ("failed to enable ep0out").
+ */
+ if (qcom->utmi_as_pipe_clk)
+ dwc3_qcom_select_utmi_clk(qcom);
+
/* Clear existing events from PHY related to L2 in/out */
for (i = 0; i < qcom->num_ports; i++) {
dwc3_qcom_setbits(qcom->qscratch_base,
@@ -419,23 +446,6 @@ static irqreturn_t qcom_dwc3_resume_irq(int irq, void *data)
return IRQ_HANDLED;
}
-static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom)
-{
- /* Configure dwc3 to use UTMI clock as PIPE clock not present */
- dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
- PIPE_UTMI_CLK_DIS);
-
- usleep_range(100, 1000);
-
- dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
- PIPE_UTMI_CLK_SEL | PIPE3_PHYSTATUS_SW);
-
- usleep_range(100, 1000);
-
- dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
- PIPE_UTMI_CLK_DIS);
-}
-
static int dwc3_qcom_request_irq(struct dwc3_qcom *qcom, int irq,
const char *name)
{
@@ -680,6 +690,7 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
*/
ignore_pipe_clk = device_property_read_bool(dev,
"qcom,select-utmi-as-pipe-clk");
+ qcom->utmi_as_pipe_clk = ignore_pipe_clk;
if (ignore_pipe_clk)
dwc3_qcom_select_utmi_clk(qcom);
---
base-commit: 6b4b543fa18757297ac756a6fe6e64f506b7844a
change-id: 20261027-dwc3-utmi-resume-5ff551b1ed1d
Best regards,
--
Filippo Falezza <effeffe99@gmail.com>
^ permalink raw reply [flat|nested] 2+ messages in thread
* Re: [PATCH] usb: dwc3: qcom: Restore UTMI PIPE clock on resume
2026-10-27 1:21 [PATCH] usb: dwc3: qcom: Restore UTMI PIPE clock on resume Filippo Falezza
@ 2026-10-27 5:01 ` Prashanth K
0 siblings, 0 replies; 2+ messages in thread
From: Prashanth K @ 2026-10-27 5:01 UTC (permalink / raw)
To: Filippo Falezza
Cc: Bjorn Andersson, linux-arm-msm, linux-usb, linux-kernel,
Thinh Nguyen, Greg Kroah-Hartman, Krishna Kurapati
On 10/27/2026 6:51 AM, Filippo Falezza wrote:
> Boards whose controller has no SuperSpeed PHY set
> qcom,select-utmi-as-pipe-clk, and the glue then programs QSCRATCH
> GENERAL_CFG so that the core's PIPE clock domain runs from the UTMI
> clock. This is only done at probe.
>
> When the SoC reaches its deepest state in suspend-to-idle (CX power
> collapse), the QSCRATCH registers lose their contents. On resume the
> core is reinitialised with its PIPE domain unclocked, and the gadget
> does not come back:
>
> dwc3-qcom a600000.usb: failed to enable ep0out
>
> The link stays down until the driver is unbound and bound again.
>
> Remember the property at probe and select the UTMI clock again in
> dwc3_qcom_resume(), after the clocks and interconnect paths are enabled
> and before the core is resumed. Move dwc3_qcom_select_utmi_clk() up so
> that it is defined before its new caller.
>
> Fixes: a4333c3a6ba9 ("usb: dwc3: Add Qualcomm DWC3 glue driver")
> Signed-off-by: Filippo Falezza <effeffe99@gmail.com>
> ---
> Hello there,
>
> this fixes USB peripheral mode after suspend on a Motorola Moto G 5G
> Plus (SM7250), a phone with a USB 2.0-only port that I'm bringing up on
> mainline. Its controller needs qcom,select-utmi-as-pipe-clk. The first
> s2idle that reached CX power collapse broke the gadget: "failed to
> enable ep0out" on resume, and USB networking stayed down until I
> rebound dwc3-qcom by hand. Rebinding works because probe programs
> GENERAL_CFG again, which is what this patch does in the resume path.
>
> With the patch, the gadget (NCM plus ACM) reconnects after s2idle, and
> the SSH session over the cable that I use for testing survives
> suspend; the phone has run with it on linux-next since 11 October.
> The other boards that use
> qcom,select-utmi-as-pipe-clk should benefit as well, but I have only
> tested this one, in peripheral mode. The legacy glue driver
> (dwc3-qcom-legacy.c) has the same probe-only programming; I left it
> alone because I can't test it.
>
> The patch is on top of usb-linus (6b4b543fa187) and builds with W=1 for
> arm64.
>
> I wrote this patch and drafted this note with the help of an AI coding
> assistant (Claude Code). The testing above is on the phone.
>
The patch looks good to me, did you test this with host mode and RT
suspend/resume?
> Thanks,
> Filippo
> ---
> drivers/usb/dwc3/dwc3-qcom.c | 45 +++++++++++++++++++++++++++-----------------
> 1 file changed, 28 insertions(+), 17 deletions(-)
>
> diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c
> index ac68b4218b56..6334ca7ba30f 100644
> --- a/drivers/usb/dwc3/dwc3-qcom.c
> +++ b/drivers/usb/dwc3/dwc3-qcom.c
> @@ -81,6 +81,7 @@ struct dwc3_qcom {
> enum usb_dr_mode mode;
> bool is_suspended;
> bool pm_suspended;
> + bool utmi_as_pipe_clk;
> struct icc_path *icc_path_ddr;
> struct icc_path *icc_path_apps;
>
> @@ -335,6 +336,23 @@ static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom)
> dwc3_qcom_enable_port_interrupts(&qcom->ports[i]);
> }
>
> +static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom)
> +{
> + /* Configure dwc3 to use UTMI clock as PIPE clock not present */
> + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
> + PIPE_UTMI_CLK_DIS);
> +
> + usleep_range(100, 1000);
> +
> + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
> + PIPE_UTMI_CLK_SEL | PIPE3_PHYSTATUS_SW);
> +
> + usleep_range(100, 1000);
> +
> + dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
> + PIPE_UTMI_CLK_DIS);
> +}
> +
> static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, bool wakeup)
> {
> u32 val;
> @@ -388,6 +406,15 @@ static int dwc3_qcom_resume(struct dwc3_qcom *qcom, bool wakeup)
> if (ret)
> dev_warn(qcom->dev, "failed to enable interconnect: %d\n", ret);
>
> + /*
> + * The QSCRATCH registers lose their state when the controller's power
> + * domain is collapsed during system suspend: select the UTMI clock
> + * for the PIPE domain again, or the core can't run without a PIPE
> + * clock ("failed to enable ep0out").
> + */
> + if (qcom->utmi_as_pipe_clk)
> + dwc3_qcom_select_utmi_clk(qcom);
> +
> /* Clear existing events from PHY related to L2 in/out */
> for (i = 0; i < qcom->num_ports; i++) {
> dwc3_qcom_setbits(qcom->qscratch_base,
> @@ -419,23 +446,6 @@ static irqreturn_t qcom_dwc3_resume_irq(int irq, void *data)
> return IRQ_HANDLED;
> }
>
> -static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom)
> -{
> - /* Configure dwc3 to use UTMI clock as PIPE clock not present */
> - dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
> - PIPE_UTMI_CLK_DIS);
> -
> - usleep_range(100, 1000);
> -
> - dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
> - PIPE_UTMI_CLK_SEL | PIPE3_PHYSTATUS_SW);
> -
> - usleep_range(100, 1000);
> -
> - dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
> - PIPE_UTMI_CLK_DIS);
> -}
> -
> static int dwc3_qcom_request_irq(struct dwc3_qcom *qcom, int irq,
> const char *name)
> {
> @@ -680,6 +690,7 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
> */
> ignore_pipe_clk = device_property_read_bool(dev,
> "qcom,select-utmi-as-pipe-clk");
Minor nit, You can use directly qcom->utmi_as_pipe_clk instead of
ignore_pipe_clk.
> + qcom->utmi_as_pipe_clk = ignore_pipe_clk;
> if (ignore_pipe_clk)
> dwc3_qcom_select_utmi_clk(qcom);
>
>
> ---
> base-commit: 6b4b543fa18757297ac756a6fe6e64f506b7844a
> change-id: 20261027-dwc3-utmi-resume-5ff551b1ed1d
>
> Best regards,
> --
> Filippo Falezza <effeffe99@gmail.com>
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-27 5:01 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-27 1:21 [PATCH] usb: dwc3: qcom: Restore UTMI PIPE clock on resume Filippo Falezza
2026-10-27 5:01 ` Prashanth K
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®