* [PATCH v3 1/7] usb: dwc3: qcom: Keep wakeup IRQ enable/disable balanced on failed resume
2026-10-10 5:32 [PATCH v3 0/7] usb: dwc3: qcom: Add firmware-managed resource support for SA8255P Faisal Hassan
@ 2026-10-10 5:32 ` Faisal Hassan
2026-10-10 5:32 ` [PATCH v3 2/7] usb: dwc3: qcom: Distinguish PM and runtime suspend/resume paths Faisal Hassan
` (5 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Faisal Hassan @ 2026-10-10 5:32 UTC (permalink / raw)
To: Thinh.Nguyen, gregkh, mathias.nyman, robh, krzk+dt, conor+dt,
p.zabel, wesley.cheng
Cc: sriram.dash, jack.pham, krishna.kurapati, shazad.hussain,
linux-arm-msm, linux-usb, devicetree, linux-kernel, Faisal Hassan
dwc3_qcom_resume() disables the wakeup interrupts before enabling the
clocks. If clk_bulk_prepare_enable() fails, it returns with them
disabled. The next suspend returns early on is_suspended without
re-enabling them, and the next resume disables them again, so every
failed resume leaves the disable depth one level deeper and host-mode
wakeup is lost for good.
Re-enabling them on the error path is not safe either: the
level-triggered ss_phy interrupt can stay asserted, and the handler
can't make progress while the controller is not resumed, so it would
fire continuously.
Track whether the wakeup interrupts are armed and make enabling and
disabling them idempotent. A failed resume now leaves them disarmed and
balanced, and the next suspend arms them again. Disarm them in resume
regardless of the wakeup setting, so interrupts armed by a runtime
suspend are also disarmed when system resume runs with device wakeup
disabled.
Signed-off-by: Faisal Hassan <faisal.hassan@oss.qualcomm.com>
---
drivers/usb/dwc3/dwc3-qcom.c | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c
index d44f093ac243..d4fda4dd81f6 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 wakeup_irqs_armed;
struct icc_path *icc_path_ddr;
struct icc_path *icc_path_apps;
@@ -323,16 +324,26 @@ static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom)
{
int i;
+ if (!qcom->wakeup_irqs_armed)
+ return;
+
for (i = 0; i < qcom->num_ports; i++)
dwc3_qcom_disable_port_interrupts(&qcom->ports[i]);
+
+ qcom->wakeup_irqs_armed = false;
}
static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom)
{
int i;
+ if (qcom->wakeup_irqs_armed)
+ return;
+
for (i = 0; i < qcom->num_ports; i++)
dwc3_qcom_enable_port_interrupts(&qcom->ports[i]);
+
+ qcom->wakeup_irqs_armed = true;
}
static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, bool wakeup)
@@ -377,8 +388,13 @@ static int dwc3_qcom_resume(struct dwc3_qcom *qcom, bool wakeup)
if (!qcom->is_suspended)
return 0;
- if (dwc3_qcom_is_host(qcom) && wakeup)
- dwc3_qcom_disable_interrupts(qcom);
+ /*
+ * Wakeup interrupts may have been armed by an earlier runtime suspend
+ * even if wakeup is now disabled, so always disarm them. On failure
+ * they stay disarmed, as a level-triggered wakeup line could otherwise
+ * keep firing while the controller can't be resumed.
+ */
+ dwc3_qcom_disable_interrupts(qcom);
ret = clk_bulk_prepare_enable(qcom->num_clocks, qcom->clks);
if (ret < 0)
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v3 2/7] usb: dwc3: qcom: Distinguish PM and runtime suspend/resume paths
2026-10-10 5:32 [PATCH v3 0/7] usb: dwc3: qcom: Add firmware-managed resource support for SA8255P Faisal Hassan
2026-10-10 5:32 ` [PATCH v3 1/7] usb: dwc3: qcom: Keep wakeup IRQ enable/disable balanced on failed resume Faisal Hassan
@ 2026-10-10 5:32 ` Faisal Hassan
2026-10-10 5:32 ` [PATCH v3 3/7] dt-bindings: usb: Add qcom,sa8255p-dwc3 for firmware-managed resources Faisal Hassan
` (4 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Faisal Hassan @ 2026-10-10 5:32 UTC (permalink / raw)
To: Thinh.Nguyen, gregkh, mathias.nyman, robh, krzk+dt, conor+dt,
p.zabel, wesley.cheng
Cc: sriram.dash, jack.pham, krishna.kurapati, shazad.hussain,
linux-arm-msm, linux-usb, devicetree, linux-kernel, Faisal Hassan
The dwc3_qcom_suspend() and dwc3_qcom_resume() functions
handle both system PM and runtime PM paths but cannot
differentiate between them, preventing distinct power
management strategies.
Refactor suspend/resume functions to accept pm_message_t
instead of boolean wakeup flags. Use PMSG_IS_AUTO()
to identify runtime PM (PMSG_AUTO_SUSPEND/RESUME) versus
system PM (PMSG_SUSPEND/RESUME) paths.
Preserve existing wakeup behavior: always enable wakeup
for runtime suspend, check device_may_wakeup() for system
suspend.
Follow dwc3/core.c pattern to enable future per-path
strategies like differentiated power states.
Co-developed-by: Sriram Dash <sriram.dash@oss.qualcomm.com>
Signed-off-by: Sriram Dash <sriram.dash@oss.qualcomm.com>
Signed-off-by: Faisal Hassan <faisal.hassan@oss.qualcomm.com>
Acked-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
---
drivers/usb/dwc3/dwc3-qcom.c | 24 ++++++++++++++++--------
1 file changed, 16 insertions(+), 8 deletions(-)
diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c
index d4fda4dd81f6..508b1ee7cd0f 100644
--- a/drivers/usb/dwc3/dwc3-qcom.c
+++ b/drivers/usb/dwc3/dwc3-qcom.c
@@ -346,14 +346,24 @@ static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom)
qcom->wakeup_irqs_armed = true;
}
-static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, bool wakeup)
+static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, pm_message_t msg)
{
u32 val;
int i, ret;
+ bool wakeup;
if (qcom->is_suspended)
return 0;
+ /*
+ * For runtime suspend, always enable wakeup.
+ * For system suspend, check device wakeup capability.
+ */
+ if (PMSG_IS_AUTO(msg))
+ wakeup = true;
+ else
+ wakeup = device_may_wakeup(qcom->dev);
+
for (i = 0; i < qcom->num_ports; i++) {
val = readl(qcom->qscratch_base + pwr_evnt_irq_stat_reg[i]);
if (!(val & PWR_EVNT_LPM_IN_L2_MASK))
@@ -380,7 +390,7 @@ static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, bool wakeup)
return 0;
}
-static int dwc3_qcom_resume(struct dwc3_qcom *qcom, bool wakeup)
+static int dwc3_qcom_resume(struct dwc3_qcom *qcom, pm_message_t msg)
{
int ret;
int i;
@@ -766,14 +776,13 @@ static int dwc3_qcom_pm_suspend(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
struct dwc3_qcom *qcom = to_dwc3_qcom(dwc);
- bool wakeup = device_may_wakeup(dev);
int ret;
ret = dwc3_pm_suspend(&qcom->dwc);
if (ret)
return ret;
- ret = dwc3_qcom_suspend(qcom, wakeup);
+ ret = dwc3_qcom_suspend(qcom, PMSG_SUSPEND);
if (ret)
return ret;
@@ -786,10 +795,9 @@ static int dwc3_qcom_pm_resume(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
struct dwc3_qcom *qcom = to_dwc3_qcom(dwc);
- bool wakeup = device_may_wakeup(dev);
int ret;
- ret = dwc3_qcom_resume(qcom, wakeup);
+ ret = dwc3_qcom_resume(qcom, PMSG_RESUME);
if (ret)
return ret;
@@ -826,7 +834,7 @@ static int dwc3_qcom_runtime_suspend(struct device *dev)
if (ret)
return ret;
- return dwc3_qcom_suspend(qcom, true);
+ return dwc3_qcom_suspend(qcom, PMSG_AUTO_SUSPEND);
}
static int dwc3_qcom_runtime_resume(struct device *dev)
@@ -835,7 +843,7 @@ static int dwc3_qcom_runtime_resume(struct device *dev)
struct dwc3_qcom *qcom = to_dwc3_qcom(dwc);
int ret;
- ret = dwc3_qcom_resume(qcom, true);
+ ret = dwc3_qcom_resume(qcom, PMSG_AUTO_RESUME);
if (ret)
return ret;
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v3 3/7] dt-bindings: usb: Add qcom,sa8255p-dwc3 for firmware-managed resources
2026-10-10 5:32 [PATCH v3 0/7] usb: dwc3: qcom: Add firmware-managed resource support for SA8255P Faisal Hassan
2026-10-10 5:32 ` [PATCH v3 1/7] usb: dwc3: qcom: Keep wakeup IRQ enable/disable balanced on failed resume Faisal Hassan
2026-10-10 5:32 ` [PATCH v3 2/7] usb: dwc3: qcom: Distinguish PM and runtime suspend/resume paths Faisal Hassan
@ 2026-10-10 5:32 ` Faisal Hassan
2026-10-13 7:20 ` Krzysztof Kozlowski
2026-10-10 5:32 ` [PATCH v3 4/7] usb: host: xhci-plat: Support XHCI_RESET_ON_RESUME via device property Faisal Hassan
` (3 subsequent siblings)
6 siblings, 1 reply; 10+ messages in thread
From: Faisal Hassan @ 2026-10-10 5:32 UTC (permalink / raw)
To: Thinh.Nguyen, gregkh, mathias.nyman, robh, krzk+dt, conor+dt,
p.zabel, wesley.cheng
Cc: sriram.dash, jack.pham, krishna.kurapati, shazad.hussain,
linux-arm-msm, linux-usb, devicetree, linux-kernel, Faisal Hassan
On Qualcomm automotive SoC sa8255p, platform resources like clocks,
interconnects, resets, regulators and GDSC are abstracted and managed
collectively by firmware.
Add a new binding file for firmware-managed USB controllers where
access to these resources is exposed as two power domains:
- Power domain: Controls power supply (GDSC) to the USB controller
- Bus domain: Controls clocks and interconnects for data transfer
The binding makes power-domains and power-domain-names properties mandatory
for firmware-managed platforms, while excluding the clocks property since
clocks are managed through the power domains.
Co-developed-by: Sriram Dash <sriram.dash@oss.qualcomm.com>
Signed-off-by: Sriram Dash <sriram.dash@oss.qualcomm.com>
Signed-off-by: Faisal Hassan <faisal.hassan@oss.qualcomm.com>
---
.../bindings/usb/qcom,sa8255p-dwc3.yaml | 119 ++++++++++++++++++
1 file changed, 119 insertions(+)
create mode 100644 Documentation/devicetree/bindings/usb/qcom,sa8255p-dwc3.yaml
diff --git a/Documentation/devicetree/bindings/usb/qcom,sa8255p-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,sa8255p-dwc3.yaml
new file mode 100644
index 000000000000..87f426fdbd70
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/qcom,sa8255p-dwc3.yaml
@@ -0,0 +1,119 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/qcom,sa8255p-dwc3.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm SA8255P USB3 Controller with Firmware-Managed Resources
+
+maintainers:
+ - Wesley Cheng <wesley.cheng@oss.qualcomm.com>
+ - Faisal Hassan <faisal.hassan@oss.qualcomm.com>
+
+description:
+ USB controller on Qualcomm SA8255P automotive SoC where platform resources
+ such as clocks, interconnects, resets, regulators and the GDSC are
+ abstracted and managed collectively by firmware through SCMI (System
+ Control and Management Interface).
+
+ Access to these firmware-managed resources is exposed as two power
+ domains, one gating the power supply (GDSC) to the controller, and one
+ gating the clocks and interconnects used for data transfer.
+
+properties:
+ compatible:
+ const: qcom,sa8255p-dwc3
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ minItems: 5
+ maxItems: 6
+
+ interrupt-names:
+ minItems: 5
+ items:
+ - const: dwc_usb3
+ - const: pwr_event
+ - const: hs_phy_irq
+ - const: dp_hs_phy_irq
+ - const: dm_hs_phy_irq
+ - const: ss_phy_irq
+
+ power-domains:
+ minItems: 2
+ maxItems: 2
+
+ power-domain-names:
+ items:
+ - const: power
+ - const: bus
+
+ resets:
+ maxItems: 1
+
+ iommus:
+ maxItems: 1
+
+ phys:
+ minItems: 1
+ maxItems: 2
+
+ phy-names:
+ minItems: 1
+ maxItems: 2
+
+ dma-coherent: true
+
+ # Firmware-managed platforms do not use direct clock/interconnect control
+ clocks: false
+ clock-names: false
+ interconnects: false
+ interconnect-names: false
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - interrupt-names
+ - power-domains
+ - power-domain-names
+ - resets
+
+allOf:
+ - $ref: snps,dwc3-common.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ usb@a800000 {
+ compatible = "qcom,sa8255p-dwc3";
+ reg = <0x0a800000 0x10000>;
+
+ interrupts-extended = <&intc GIC_SPI 349 IRQ_TYPE_LEVEL_HIGH>,
+ <&intc GIC_SPI 352 IRQ_TYPE_LEVEL_HIGH>,
+ <&intc GIC_SPI 351 IRQ_TYPE_LEVEL_HIGH>,
+ <&pdc 8 IRQ_TYPE_EDGE_BOTH>,
+ <&pdc 7 IRQ_TYPE_EDGE_BOTH>,
+ <&pdc 13 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "dwc_usb3",
+ "pwr_event",
+ "hs_phy_irq",
+ "dp_hs_phy_irq",
+ "dm_hs_phy_irq",
+ "ss_phy_irq";
+
+ power-domains = <&scmi_pd 1>, <&scmi_pd 2>;
+ power-domain-names = "power", "bus";
+ resets = <&scmi_rst 0>;
+
+ iommus = <&apps_smmu 0x0a0 0x0>;
+ phys = <&usb_1_hsphy>, <&usb_1_qmpphy>;
+ phy-names = "usb2-phy", "usb3-phy";
+ };
+...
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH v3 3/7] dt-bindings: usb: Add qcom,sa8255p-dwc3 for firmware-managed resources
2026-10-10 5:32 ` [PATCH v3 3/7] dt-bindings: usb: Add qcom,sa8255p-dwc3 for firmware-managed resources Faisal Hassan
@ 2026-10-13 7:20 ` Krzysztof Kozlowski
2026-10-13 13:39 ` Faisal Hassan
0 siblings, 1 reply; 10+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-13 7:20 UTC (permalink / raw)
To: Faisal Hassan
Cc: Thinh.Nguyen, gregkh, mathias.nyman, robh, krzk+dt, conor+dt,
p.zabel, wesley.cheng, sriram.dash, jack.pham, krishna.kurapati,
shazad.hussain, linux-arm-msm, linux-usb, devicetree,
linux-kernel
On Sat, Oct 10, 2026 at 11:02:42AM +0530, Faisal Hassan wrote:
> On Qualcomm automotive SoC sa8255p, platform resources like clocks,
> interconnects, resets, regulators and GDSC are abstracted and managed
> collectively by firmware.
>
> Add a new binding file for firmware-managed USB controllers where
> access to these resources is exposed as two power domains:
> - Power domain: Controls power supply (GDSC) to the USB controller
> - Bus domain: Controls clocks and interconnects for data transfer
>
> The binding makes power-domains and power-domain-names properties mandatory
> for firmware-managed platforms, while excluding the clocks property since
> clocks are managed through the power domains.
>
> Co-developed-by: Sriram Dash <sriram.dash@oss.qualcomm.com>
> Signed-off-by: Sriram Dash <sriram.dash@oss.qualcomm.com>
> Signed-off-by: Faisal Hassan <faisal.hassan@oss.qualcomm.com>
> ---
> .../bindings/usb/qcom,sa8255p-dwc3.yaml | 119 ++++++++++++++++++
> 1 file changed, 119 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/usb/qcom,sa8255p-dwc3.yaml
>
> diff --git a/Documentation/devicetree/bindings/usb/qcom,sa8255p-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,sa8255p-dwc3.yaml
> new file mode 100644
> index 000000000000..87f426fdbd70
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/usb/qcom,sa8255p-dwc3.yaml
> @@ -0,0 +1,119 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/usb/qcom,sa8255p-dwc3.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Qualcomm SA8255P USB3 Controller with Firmware-Managed Resources
> +
> +maintainers:
> + - Wesley Cheng <wesley.cheng@oss.qualcomm.com>
> + - Faisal Hassan <faisal.hassan@oss.qualcomm.com>
> +
> +description:
> + USB controller on Qualcomm SA8255P automotive SoC where platform resources
> + such as clocks, interconnects, resets, regulators and the GDSC are
> + abstracted and managed collectively by firmware through SCMI (System
> + Control and Management Interface).
> +
> + Access to these firmware-managed resources is exposed as two power
> + domains, one gating the power supply (GDSC) to the controller, and one
> + gating the clocks and interconnects used for data transfer.
> +
> +properties:
> + compatible:
> + const: qcom,sa8255p-dwc3
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + minItems: 5
> + maxItems: 6
> +
> + interrupt-names:
> + minItems: 5
> + items:
> + - const: dwc_usb3
> + - const: pwr_event
> + - const: hs_phy_irq
> + - const: dp_hs_phy_irq
> + - const: dm_hs_phy_irq
> + - const: ss_phy_irq
That's still unexpectedly flexible.
> +
> + power-domains:
> + minItems: 2
Drop
> + maxItems: 2
> +
> + power-domain-names:
> + items:
> + - const: power
> + - const: bus
I don't see how did this improve. I asked to use existing bindings as
reference, so why aren't these power and perf? You wrote that bus
controls clocks and interconnect, so is this something different than
perf domain is doing for sa8255?
> +
> + resets:
> + maxItems: 1
> +
> + iommus:
> + maxItems: 1
> +
> + phys:
> + minItems: 1
> + maxItems: 2
phys should be afixed, constrained.
> +
> + phy-names:
> + minItems: 1
> + maxItems: 2
This as well.
> +
> + dma-coherent: true
> +
> + # Firmware-managed platforms do not use direct clock/interconnect control
> + clocks: false
> + clock-names: false
> + interconnects: false
> + interconnect-names: false
Drop all four above. Not sure why do you need them.
> +
> +required:
> + - compatible
> + - reg
> + - interrupts
> + - interrupt-names
> + - power-domains
> + - power-domain-names
> + - resets
I guess phys and phy-names as well.
> +
> +allOf:
> + - $ref: snps,dwc3-common.yaml#
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 3/7] dt-bindings: usb: Add qcom,sa8255p-dwc3 for firmware-managed resources
2026-10-13 7:20 ` Krzysztof Kozlowski
@ 2026-10-13 13:39 ` Faisal Hassan
0 siblings, 0 replies; 10+ messages in thread
From: Faisal Hassan @ 2026-10-13 13:39 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Thinh.Nguyen, gregkh, mathias.nyman, robh, krzk+dt, conor+dt,
p.zabel, wesley.cheng, sriram.dash, jack.pham, krishna.kurapati,
shazad.hussain, linux-arm-msm, linux-usb, devicetree,
linux-kernel
On 13-10-2026 12:50 pm, Krzysztof Kozlowski wrote:
> On Sat, Oct 10, 2026 at 11:02:42AM +0530, Faisal Hassan wrote:
>> On Qualcomm automotive SoC sa8255p, platform resources like clocks,
>> interconnects, resets, regulators and GDSC are abstracted and managed
>> collectively by firmware.
>>
>> Add a new binding file for firmware-managed USB controllers where
>> access to these resources is exposed as two power domains:
>> - Power domain: Controls power supply (GDSC) to the USB controller
>> - Bus domain: Controls clocks and interconnects for data transfer
>>
>> The binding makes power-domains and power-domain-names properties mandatory
>> for firmware-managed platforms, while excluding the clocks property since
>> clocks are managed through the power domains.
>>
>> Co-developed-by: Sriram Dash <sriram.dash@oss.qualcomm.com>
>> Signed-off-by: Sriram Dash <sriram.dash@oss.qualcomm.com>
>> Signed-off-by: Faisal Hassan <faisal.hassan@oss.qualcomm.com>
>> ---
>> .../bindings/usb/qcom,sa8255p-dwc3.yaml | 119 ++++++++++++++++++
>> 1 file changed, 119 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/usb/qcom,sa8255p-dwc3.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/usb/qcom,sa8255p-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,sa8255p-dwc3.yaml
>> new file mode 100644
>> index 000000000000..87f426fdbd70
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/usb/qcom,sa8255p-dwc3.yaml
>> @@ -0,0 +1,119 @@
>> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/usb/qcom,sa8255p-dwc3.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Qualcomm SA8255P USB3 Controller with Firmware-Managed Resources
>> +
>> +maintainers:
>> + - Wesley Cheng <wesley.cheng@oss.qualcomm.com>
>> + - Faisal Hassan <faisal.hassan@oss.qualcomm.com>
>> +
>> +description:
>> + USB controller on Qualcomm SA8255P automotive SoC where platform resources
>> + such as clocks, interconnects, resets, regulators and the GDSC are
>> + abstracted and managed collectively by firmware through SCMI (System
>> + Control and Management Interface).
>> +
>> + Access to these firmware-managed resources is exposed as two power
>> + domains, one gating the power supply (GDSC) to the controller, and one
>> + gating the clocks and interconnects used for data transfer.
>> +
>> +properties:
>> + compatible:
>> + const: qcom,sa8255p-dwc3
>> +
>> + reg:
>> + maxItems: 1
>> +
>> + interrupts:
>> + minItems: 5
>> + maxItems: 6
>> +
>> + interrupt-names:
>> + minItems: 5
>> + items:
>> + - const: dwc_usb3
>> + - const: pwr_event
>> + - const: hs_phy_irq
>> + - const: dp_hs_phy_irq
>> + - const: dm_hs_phy_irq
>> + - const: ss_phy_irq
>
> That's still unexpectedly flexible.
>
Ok, I'll drop minItems so the list is fixed at 6 entries:
interrupts:
maxItems: 6
interrupt-names:
items:
- const: dwc_usb3
- const: pwr_event
- const: hs_phy_irq
- const: dp_hs_phy_irq
- const: dm_hs_phy_irq
- const: ss_phy_irq
>> +
>> + power-domains:
>> + minItems: 2
>
> Drop
>
>> + maxItems: 2
>> +
>> + power-domain-names:
>> + items:
>> + - const: power
>> + - const: bus
>
> I don't see how did this improve. I asked to use existing bindings as
> reference, so why aren't these power and perf? You wrote that bus
> controls clocks and interconnect, so is this something different than
> perf domain is doing for sa8255?
>
The sa8255 "perf" domains (e.g. GENI) are SCMI performance-protocol
domains with OPP levels. USB "bus" is a plain SCMI power domain with no
levels, only on/off, so "perf" would be misleading. I'd like to keep
"bus", and will drop the redundant minItems.
>> +
>> + resets:
>> + maxItems: 1
>> +
>> + iommus:
>> + maxItems: 1
>> +
>> + phys:
>> + minItems: 1
>> + maxItems: 2
>
> phys should be afixed, constrained.
>
>> +
>> + phy-names:
>> + minItems: 1
>> + maxItems: 2
>
> This as well.
>
Sure, will modify as below.
phys:
maxItems: 2
phy-names:
items:
- const: usb2-phy
- const: usb3-phy
>> +
>> + dma-coherent: true
>> +
>> + # Firmware-managed platforms do not use direct clock/interconnect control
>> + clocks: false
>> + clock-names: false
>> + interconnects: false
>> + interconnect-names: false
>
> Drop all four above. Not sure why do you need them.
>
Sure.
>> +
>> +required:
>> + - compatible
>> + - reg
>> + - interrupts
>> + - interrupt-names
>> + - power-domains
>> + - power-domain-names
>> + - resets
>
> I guess phys and phy-names as well.
>
Sure, will add both.
>> +
>> +allOf:
>> + - $ref: snps,dwc3-common.yaml#
>
> Best regards,
> Krzysztof
Let me know if these changes look OK, and I'll send v4 with them.
Thanks,
Faisal
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 4/7] usb: host: xhci-plat: Support XHCI_RESET_ON_RESUME via device property
2026-10-10 5:32 [PATCH v3 0/7] usb: dwc3: qcom: Add firmware-managed resource support for SA8255P Faisal Hassan
` (2 preceding siblings ...)
2026-10-10 5:32 ` [PATCH v3 3/7] dt-bindings: usb: Add qcom,sa8255p-dwc3 for firmware-managed resources Faisal Hassan
@ 2026-10-10 5:32 ` Faisal Hassan
2026-10-10 5:32 ` [PATCH v3 5/7] usb: dwc3: qcom: Support broken suspend for SA8255P Faisal Hassan
` (2 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Faisal Hassan @ 2026-10-10 5:32 UTC (permalink / raw)
To: Thinh.Nguyen, gregkh, mathias.nyman, robh, krzk+dt, conor+dt,
p.zabel, wesley.cheng
Cc: sriram.dash, jack.pham, krishna.kurapati, shazad.hussain,
linux-arm-msm, linux-usb, devicetree, linux-kernel, Faisal Hassan
When the USB controller loses power during PM suspend (as occurs
on targets like SA8255P that fully power off the controller),
it generates a Host Controller Error (HCE) on resume requiring
full reinitialization. This is handled by the XHCI_RESET_ON_RESUME
quirk (introduced upstream in 8b328f8002bc).
Read the "xhci-reset-on-resume-quirk" device property to set
XHCI_RESET_ON_RESUME. This allows parent drivers (e.g., dwc3)
to enable the quirk via software nodes.
Co-developed-by: Sriram Dash <sriram.dash@oss.qualcomm.com>
Signed-off-by: Sriram Dash <sriram.dash@oss.qualcomm.com>
Signed-off-by: Faisal Hassan <faisal.hassan@oss.qualcomm.com>
---
drivers/usb/host/xhci-plat.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index 6fd595f81a30..0093ed24ac3f 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -277,6 +277,9 @@ int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const s
if (device_property_read_bool(tmpdev, "xhci-skip-phy-init-quirk"))
xhci->quirks |= XHCI_SKIP_PHY_INIT;
+ if (device_property_read_bool(tmpdev, "xhci-reset-on-resume-quirk"))
+ xhci->quirks |= XHCI_RESET_ON_RESUME;
+
device_property_read_u32(tmpdev, "imod-interval-ns",
&xhci->imod_interval);
device_property_read_u16(tmpdev, "num-hc-interrupters",
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v3 5/7] usb: dwc3: qcom: Support broken suspend for SA8255P
2026-10-10 5:32 [PATCH v3 0/7] usb: dwc3: qcom: Add firmware-managed resource support for SA8255P Faisal Hassan
` (3 preceding siblings ...)
2026-10-10 5:32 ` [PATCH v3 4/7] usb: host: xhci-plat: Support XHCI_RESET_ON_RESUME via device property Faisal Hassan
@ 2026-10-10 5:32 ` Faisal Hassan
2026-10-10 5:32 ` [PATCH v3 6/7] usb: dwc3: qcom: Support firmware-managed resource states for power management Faisal Hassan
2026-10-10 5:32 ` [PATCH v3 7/7] usb: dwc3: qcom: Enable VBUS override at probe for default peripheral role Faisal Hassan
6 siblings, 0 replies; 10+ messages in thread
From: Faisal Hassan @ 2026-10-10 5:32 UTC (permalink / raw)
To: Thinh.Nguyen, gregkh, mathias.nyman, robh, krzk+dt, conor+dt,
p.zabel, wesley.cheng
Cc: sriram.dash, jack.pham, krishna.kurapati, shazad.hussain,
linux-arm-msm, linux-usb, devicetree, linux-kernel, Faisal Hassan
During APPS Suspend, the clock controller unit disables all clocks
to the CPU and subsystems, causing the controller to enter the
Power-On Reset (POR) state. This results in the loss of
Qscratch programming.
Upon APPS Resume, the USB controller does not have the proper
PIPE_UTMI_CLK_SEL or VBUS_VALID. Hence, if only the High-Speed
PHY is used for a controller capable of SuperSpeed operations,
the controller will assume it should use the USB3 PHY and not
the HS PHY as configured before APPS Suspend. This behavior
disrupts the normal operation of the USB controller. Similarly,
if the controller was operating in device mode prior to
APPS suspend, and VBUS_VALID signal is not set after APPS resume,
this will cause the PHYs to transition into the suspend state.
This behavior disrupts the normal operation of the USB controller.
Address the issues by ensuring that the PIPE_UTMI_CLK_SEL and
VBUS_VALID are handled properly after the system resumes from
APPS Suspend. This is crucial for maintaining the correct
operational state of the USB controller and preventing
unintended suspensions of the PHYs.
Also, as the controller is starting from POR state, make
sure the controller Reset is performed.
Because the controller is reset on every system resume, the
Qscratch restore is done whether or not wakeup was enabled, and
dwc3 core is told via needs_full_reinit to fully re-initialize the
core on system resume even in host mode with wakeup enabled. Wakeup
on this platform comes from the PHY interrupts routed through PDC,
not from the controller staying powered. VBUS_VALID is only forced
outside host mode, matching what probe does.
The XHCI controller is reset by the same event, so also set the
xhci_reset_on_resume dwc3 property. dwc3 passes it on to the xhci-plat
device as xhci-reset-on-resume-quirk, which makes xhci-plat set
XHCI_RESET_ON_RESUME.
Also make the .prepare callback return 0 on these platforms. A
runtime-suspended device in device mode would otherwise be left in
place across system sleep (direct_complete), skipping the system
resume callback and with it the reset and Qscratch restore.
Co-developed-by: Sriram Dash <sriram.dash@oss.qualcomm.com>
Signed-off-by: Sriram Dash <sriram.dash@oss.qualcomm.com>
Signed-off-by: Faisal Hassan <faisal.hassan@oss.qualcomm.com>
---
drivers/usb/dwc3/core.c | 3 +
drivers/usb/dwc3/core.h | 3 +
drivers/usb/dwc3/dwc3-qcom.c | 107 ++++++++++++++++++++++++++++-------
drivers/usb/dwc3/glue.h | 2 +
drivers/usb/dwc3/host.c | 5 +-
5 files changed, 98 insertions(+), 22 deletions(-)
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index d9f4e4109abb..d782d5ef9c57 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -1697,6 +1697,9 @@ static void dwc3_get_software_properties(struct dwc3 *dwc,
if (properties->skip_phy_init)
dwc->skip_phy_init = true;
+ if (properties->xhci_reset_on_resume)
+ dwc->xhci_reset_on_resume = true;
+
dwc->gsbuscfg0_reqinfo = DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED;
if (properties->gsbuscfg0_reqinfo !=
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 1c8d441ea9f8..3cbe4eeb0a5a 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -1177,6 +1177,7 @@ struct dwc3_glue_ops {
* 3 - Reserved
* @dis_metastability_quirk: set to disable metastability quirk.
* @dis_split_quirk: set to disable split boundary.
+ * @xhci_reset_on_resume: set to reset the xHCI host controller on system resume.
* @enable_usb2_transceiver_delay: Set to insert a delay before the
* assertion of the TxValid signal during a HS Chirp.
* @sys_wakeup: set if the device may do system wakeup.
@@ -1433,6 +1434,8 @@ struct dwc3 {
unsigned dis_metastability_quirk:1;
unsigned dis_split_quirk:1;
+ unsigned xhci_reset_on_resume:1;
+
unsigned enable_usb2_transceiver_delay:1;
unsigned async_callbacks:1;
unsigned sys_wakeup:1;
diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c
index 508b1ee7cd0f..3860ab83c28c 100644
--- a/drivers/usb/dwc3/dwc3-qcom.c
+++ b/drivers/usb/dwc3/dwc3-qcom.c
@@ -68,6 +68,10 @@ struct dwc3_qcom_port {
enum usb_device_speed usb2_speed;
};
+struct dwc3_qcom_priv_data {
+ bool broken_suspend;
+};
+
struct dwc3_qcom {
struct device *dev;
void __iomem *qscratch_base;
@@ -86,6 +90,13 @@ struct dwc3_qcom {
struct icc_path *icc_path_apps;
enum usb_role current_role;
+
+ bool broken_suspend;
+ bool ignore_pipe_clk;
+};
+
+static const struct dwc3_qcom_priv_data sa8255p_dwc3_qcom_priv_data = {
+ .broken_suspend = true,
};
#define to_dwc3_qcom(d) container_of((d), struct dwc3_qcom, dwc)
@@ -346,6 +357,23 @@ static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom)
qcom->wakeup_irqs_armed = true;
}
+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, pm_message_t msg)
{
u32 val;
@@ -398,6 +426,28 @@ static int dwc3_qcom_resume(struct dwc3_qcom *qcom, pm_message_t msg)
if (!qcom->is_suspended)
return 0;
+ if (qcom->broken_suspend && !PMSG_IS_AUTO(msg)) {
+ /*
+ * Only system suspend fully powers off the controller and
+ * puts it into POR state. Runtime suspend does not, so skip
+ * the reset on runtime resume to avoid needlessly clobbering
+ * state that was never lost.
+ */
+ ret = reset_control_assert(qcom->resets);
+ if (ret) {
+ dev_err(qcom->dev, "failed to assert resets, err=%d\n", ret);
+ return ret;
+ }
+
+ usleep_range(10, 1000);
+
+ ret = reset_control_deassert(qcom->resets);
+ if (ret) {
+ dev_err(qcom->dev, "failed to deassert resets, err=%d\n", ret);
+ return ret;
+ }
+ }
+
/*
* Wakeup interrupts may have been armed by an earlier runtime suspend
* even if wakeup is now disabled, so always disarm them. On failure
@@ -421,6 +471,18 @@ static int dwc3_qcom_resume(struct dwc3_qcom *qcom, pm_message_t msg)
PWR_EVNT_LPM_IN_L2_MASK | PWR_EVNT_LPM_OUT_L2_MASK);
}
+ /*
+ * The reset above returned Qscratch to its POR defaults regardless of
+ * wakeup, so restore the configuration done at probe time.
+ */
+ if (qcom->broken_suspend && !PMSG_IS_AUTO(msg)) {
+ if (qcom->ignore_pipe_clk)
+ dwc3_qcom_select_utmi_clk(qcom);
+ /* Make sure vbus valid is set for PHYs after PM resume */
+ if (!dwc3_qcom_is_host(qcom))
+ dwc3_qcom_vbus_override_enable(qcom, true);
+ }
+
qcom->is_suspended = false;
return 0;
@@ -445,23 +507,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)
{
@@ -640,8 +685,8 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
struct dwc3_qcom *qcom;
struct resource res;
struct resource *r;
+ const struct dwc3_qcom_priv_data *priv_data;
int ret;
- bool ignore_pipe_clk;
bool wakeup_source;
qcom = devm_kzalloc(&pdev->dev, sizeof(*qcom), GFP_KERNEL);
@@ -650,6 +695,10 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
qcom->dev = &pdev->dev;
+ priv_data = of_device_get_match_data(dev);
+ if (priv_data)
+ qcom->broken_suspend = priv_data->broken_suspend;
+
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),
@@ -704,9 +753,9 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
* Disable pipe_clk requirement if specified. Used when dwc3
* operates without SSPHY and only HS/FS/LS modes are supported.
*/
- ignore_pipe_clk = device_property_read_bool(dev,
- "qcom,select-utmi-as-pipe-clk");
- if (ignore_pipe_clk)
+ qcom->ignore_pipe_clk = device_property_read_bool(dev,
+ "qcom,select-utmi-as-pipe-clk");
+ if (qcom->ignore_pipe_clk)
dwc3_qcom_select_utmi_clk(qcom);
qcom->mode = usb_get_dr_mode(dev);
@@ -732,6 +781,9 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
probe_data.ignore_clocks_and_resets = true;
probe_data.properties = DWC3_DEFAULT_PROPERTIES;
probe_data.properties.skip_phy_init = true;
+ /* Controller is reset on system resume, even with wakeup enabled */
+ probe_data.properties.needs_full_reinit = qcom->broken_suspend;
+ probe_data.properties.xhci_reset_on_resume = qcom->broken_suspend;
ret = dwc3_core_probe(&probe_data);
if (ret) {
ret = dev_err_probe(dev, ret, "failed to register DWC3 Core\n");
@@ -820,6 +872,15 @@ static void dwc3_qcom_complete(struct device *dev)
static int dwc3_qcom_prepare(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
+ struct dwc3_qcom *qcom = to_dwc3_qcom(dwc);
+
+ /*
+ * The controller loses power across system suspend, so the system
+ * resume callback must always run to reset and reprogram it. Don't
+ * let the PM core leave a runtime-suspended device as-is.
+ */
+ if (qcom->broken_suspend)
+ return 0;
return dwc3_pm_prepare(dwc);
}
@@ -864,6 +925,10 @@ static const struct dev_pm_ops dwc3_qcom_dev_pm_ops = {
};
static const struct of_device_id dwc3_qcom_of_match[] = {
+ {
+ .compatible = "qcom,sa8255p-dwc3",
+ .data = &sa8255p_dwc3_qcom_priv_data,
+ },
{ .compatible = "qcom,snps-dwc3" },
{ }
};
diff --git a/drivers/usb/dwc3/glue.h b/drivers/usb/dwc3/glue.h
index 72c5593e657a..028a4f8a4fac 100644
--- a/drivers/usb/dwc3/glue.h
+++ b/drivers/usb/dwc3/glue.h
@@ -15,11 +15,13 @@
* @needs_full_reinit: indicate the controller may not remain power during system
* pm and need full initialization
* @skip_phy_init: skip xHCI PHY initialization on host init
+ * @xhci_reset_on_resume: reset the xHCI host controller on system resume
*/
struct dwc3_properties {
u32 gsbuscfg0_reqinfo;
unsigned needs_full_reinit:1;
unsigned skip_phy_init:1;
+ unsigned xhci_reset_on_resume:1;
};
#define DWC3_DEFAULT_PROPERTIES ((struct dwc3_properties){ \
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index 3fb8be8be590..a9c81def13c2 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -130,7 +130,7 @@ static int dwc3_host_get_irq(struct dwc3 *dwc)
int dwc3_host_init(struct dwc3 *dwc)
{
- struct property_entry props[7];
+ struct property_entry props[9];
struct platform_device *xhci;
int ret, irq;
int prop_idx = 0;
@@ -177,6 +177,9 @@ int dwc3_host_init(struct dwc3 *dwc)
if (dwc->skip_phy_init)
props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-skip-phy-init-quirk");
+ if (dwc->xhci_reset_on_resume)
+ props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-reset-on-resume-quirk");
+
/**
* WORKAROUND: dwc3 revisions <=3.00a have a limitation
* where Port Disable command doesn't work.
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v3 6/7] usb: dwc3: qcom: Support firmware-managed resource states for power management
2026-10-10 5:32 [PATCH v3 0/7] usb: dwc3: qcom: Add firmware-managed resource support for SA8255P Faisal Hassan
` (4 preceding siblings ...)
2026-10-10 5:32 ` [PATCH v3 5/7] usb: dwc3: qcom: Support broken suspend for SA8255P Faisal Hassan
@ 2026-10-10 5:32 ` Faisal Hassan
2026-10-10 5:32 ` [PATCH v3 7/7] usb: dwc3: qcom: Enable VBUS override at probe for default peripheral role Faisal Hassan
6 siblings, 0 replies; 10+ messages in thread
From: Faisal Hassan @ 2026-10-10 5:32 UTC (permalink / raw)
To: Thinh.Nguyen, gregkh, mathias.nyman, robh, krzk+dt, conor+dt,
p.zabel, wesley.cheng
Cc: sriram.dash, jack.pham, krishna.kurapati, shazad.hussain,
linux-arm-msm, linux-usb, devicetree, linux-kernel, Faisal Hassan
Add support for firmware-managed resource handling in the Qualcomm
DWC3 USB controller driver. On platforms such as SA8255P, controller
resources are abstracted and managed collectively by firmware, and are
exposed to Linux through SCMI-backed power domains.
On SA8255P, resources such as clocks, regulators, resets, TLMM and
interconnects are controlled by firmware. The driver communicates with
firmware through two power domains:
- "power": controls power supply (GDSC) to the USB controller
- "bus": controls clocks and interconnects used for data transfer.
Register accesses through the controller require the "power"
domain to be enabled.
The two domains are voted on and off directly from the PM callbacks
through dwc3_qcom_domains_get() and dwc3_qcom_domains_put(). Runtime
PM usage counting keeps each domain enabled for as long as it is
required; the driver does not attempt to track or infer any aggregate
device power state, nor does it assume what state firmware or the
bootloader left the domains in before probe.
Runtime suspend/resume vote only the "bus" domain, since the "power"
domain must remain enabled for hardware accesses that can occur while
runtime suspended. System suspend/resume vote both domains.
If system suspend is entered while the device is already runtime
suspended, only the "power" vote is still held, so system suspend
drops just that one. System resume then votes both domains back on,
which keeps the votes balanced across the cycle.
The domains are attached with PD_FLAG_NO_DEV_LINK because runtime
suspend and system suspend require different supplier states. Runtime
suspend releases only the "bus" domain while retaining the "power"
domain, whereas system suspend releases both domains. Ordinary
device-links would couple both suppliers to the same consumer runtime
PM state and therefore cannot express the required policy. The driver
consequently manages the runtime PM references for the attached domains
explicitly.
These votes are taken during probe, remove, suspend and resume,
enabling coordinated management of grouped resources according to the
controller's operational requirements.
Enable firmware-managed resource handling for the
"qcom,sa8255p-dwc3" compatible while maintaining backward
compatibility with existing platforms that use direct resource
control.
pm_runtime_put_sync() drops the domain usage count even when it
returns an error, so failures to vote a domain off are only reported.
Failing suspend on them would leave the driver thinking the votes are
still held and unbalance the next resume/suspend pair.
While here, assert the controller reset in dwc3_qcom_remove() and on
the probe error-unwind path once the controller is powered down. These
paths are shared by every qcom,snps-dwc3 platform, not only the
firmware-managed ones added here. That is safe for existing platforms,
because remove() already fully powers the controller down and a probe
failure already leaves it unclocked.
Co-developed-by: Shazad Hussain <shazad.hussain@oss.qualcomm.com>
Signed-off-by: Shazad Hussain <shazad.hussain@oss.qualcomm.com>
Co-developed-by: Sriram Dash <sriram.dash@oss.qualcomm.com>
Signed-off-by: Sriram Dash <sriram.dash@oss.qualcomm.com>
Signed-off-by: Faisal Hassan <faisal.hassan@oss.qualcomm.com>
---
drivers/usb/dwc3/dwc3-qcom.c | 252 +++++++++++++++++++++++++++++++----
1 file changed, 228 insertions(+), 24 deletions(-)
diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c
index 3860ab83c28c..0896a323bdda 100644
--- a/drivers/usb/dwc3/dwc3-qcom.c
+++ b/drivers/usb/dwc3/dwc3-qcom.c
@@ -13,6 +13,7 @@
#include <linux/kernel.h>
#include <linux/interconnect.h>
#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
#include <linux/phy/phy.h>
#include <linux/usb/of.h>
#include <linux/reset.h>
@@ -70,6 +71,7 @@ struct dwc3_qcom_port {
struct dwc3_qcom_priv_data {
bool broken_suspend;
+ bool fw_managed;
};
struct dwc3_qcom {
@@ -93,14 +95,142 @@ struct dwc3_qcom {
bool broken_suspend;
bool ignore_pipe_clk;
+
+ bool fw_managed;
+ struct dev_pm_domain_list *pd_list;
};
static const struct dwc3_qcom_priv_data sa8255p_dwc3_qcom_priv_data = {
.broken_suspend = true,
+ .fw_managed = true,
};
#define to_dwc3_qcom(d) container_of((d), struct dwc3_qcom, dwc)
+/*
+ * Firmware-managed resource handling
+ *
+ * On platforms where clocks, interconnects, resets and the GDSC for the USB
+ * controller are owned and sequenced by firmware, access to those resources
+ * is exposed to Linux as two power domains:
+ * - pd_list->pd_devs[0] ("power"): the GDSC supplying the controller
+ * - pd_list->pd_devs[1] ("bus"): the clocks and interconnects used for data
+ * transfer, which additionally require "power" to be on for register
+ * accesses to succeed
+ *
+ * The two domains are voted on/off directly from each PM callback that needs
+ * them; there's no attempt to track or name an aggregate device power state,
+ * since the actual state lives in firmware and is queried by voting through
+ * runtime PM, not by mirroring it in the driver.
+ */
+
+/**
+ * dwc3_qcom_domain_detach() - Detach power domains
+ * @qcom: Pointer to the dwc3_qcom structure
+ *
+ * Detaches all power domains.
+ */
+static void dwc3_qcom_domain_detach(struct dwc3_qcom *qcom)
+{
+ if (qcom->pd_list)
+ dev_pm_domain_detach_list(qcom->pd_list);
+}
+
+/**
+ * dwc3_qcom_domain_attach() - Attach power domains
+ * @qcom: Pointer to the dwc3_qcom structure
+ *
+ * Attaches power domains for firmware managed resource handling.
+ * Returns 0 on success, negative error code on failure.
+ */
+static int dwc3_qcom_domain_attach(struct dwc3_qcom *qcom)
+{
+ struct dev_pm_domain_attach_data pd_data = {
+ .pd_flags = PD_FLAG_NO_DEV_LINK,
+ .pd_names = (const char*[]) { "power", "bus" },
+ .num_pd_names = 2,
+ };
+ struct device *dev = qcom->dev;
+ int ret;
+
+ ret = dev_pm_domain_attach_list(dev, &pd_data, &qcom->pd_list);
+ if (ret != pd_data.num_pd_names) {
+ dev_err(dev, "domain attach failed (%d)\n", ret);
+ return ret < 0 ? ret : -ENODEV;
+ }
+
+ return 0;
+}
+
+/**
+ * dwc3_qcom_domains_get() - Vote the firmware-managed domains on
+ * @qcom: Pointer to the dwc3_qcom structure
+ * @bus_only: If true, leave the power domain untouched and only vote bus
+ *
+ * Votes runtime PM "on" for the domains this call is responsible for. Each
+ * call has exactly one matching dwc3_qcom_domains_put() call with the same
+ * @bus_only value on the corresponding disable path; runtime PM's own usage
+ * counting is what keeps power on for as long as bus needs it, without the
+ * driver tracking or assuming any prior domain state itself.
+ * Returns 0 on success, negative error code on failure.
+ */
+static int dwc3_qcom_domains_get(struct dwc3_qcom *qcom, bool bus_only)
+{
+ struct device *power_dev = qcom->pd_list->pd_devs[0];
+ struct device *bus_dev = qcom->pd_list->pd_devs[1];
+ int ret;
+
+ if (!bus_only) {
+ ret = pm_runtime_resume_and_get(power_dev);
+ if (ret) {
+ dev_err(qcom->dev, "failed to enable power domain: %d\n", ret);
+ return ret;
+ }
+ }
+
+ ret = pm_runtime_resume_and_get(bus_dev);
+ if (ret) {
+ dev_err(qcom->dev, "failed to enable bus domain: %d\n", ret);
+ if (!bus_only)
+ pm_runtime_put_sync(power_dev);
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * dwc3_qcom_domains_put() - Vote the firmware-managed domains off
+ * @qcom: Pointer to the dwc3_qcom structure
+ * @bus_only: If true, leave the power domain untouched and only vote bus
+ *
+ * Releases the votes taken by the matching dwc3_qcom_domains_get() call.
+ * Bus is released before power since bus register accesses require power
+ * to still be on.
+ *
+ * pm_runtime_put_sync() drops the usage count even when it returns an error,
+ * so the votes are released regardless and errors are only reported. Treating
+ * them as failures would make callers believe the votes are still held and
+ * unbalance the next dwc3_qcom_domains_get()/put() pair.
+ */
+static void dwc3_qcom_domains_put(struct dwc3_qcom *qcom, bool bus_only)
+{
+ struct device *power_dev = qcom->pd_list->pd_devs[0];
+ struct device *bus_dev = qcom->pd_list->pd_devs[1];
+ int ret;
+
+ ret = pm_runtime_put_sync(bus_dev);
+ if (ret < 0)
+ dev_warn(qcom->dev, "failed to disable bus domain: %d\n", ret);
+
+ if (bus_only)
+ return;
+
+ ret = pm_runtime_put_sync(power_dev);
+ if (ret < 0)
+ dev_warn(qcom->dev, "failed to disable power domain: %d\n", ret);
+}
+
static inline void dwc3_qcom_setbits(void __iomem *base, u32 offset, u32 val)
{
u32 reg;
@@ -380,8 +510,20 @@ static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, pm_message_t msg)
int i, ret;
bool wakeup;
- if (qcom->is_suspended)
+ if (qcom->is_suspended) {
+ /*
+ * Runtime suspend dropped only the bus domain. System suspend
+ * must drop the power domain too, so that system resume, which
+ * votes both domains back on, stays balanced. The usage count
+ * is dropped even if this fails, so don't fail the suspend.
+ */
+ if (qcom->fw_managed && !PMSG_IS_AUTO(msg)) {
+ ret = pm_runtime_put_sync(qcom->pd_list->pd_devs[0]);
+ if (ret < 0)
+ dev_warn(qcom->dev, "failed to disable power domain: %d\n", ret);
+ }
return 0;
+ }
/*
* For runtime suspend, always enable wakeup.
@@ -397,11 +539,17 @@ static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, pm_message_t msg)
if (!(val & PWR_EVNT_LPM_IN_L2_MASK))
dev_err(qcom->dev, "port-%d HS-PHY not in L2\n", i + 1);
}
- clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks);
- ret = dwc3_qcom_interconnect_disable(qcom);
- if (ret)
- dev_warn(qcom->dev, "failed to disable interconnect: %d\n", ret);
+ if (!qcom->fw_managed) {
+ clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks);
+
+ ret = dwc3_qcom_interconnect_disable(qcom);
+ if (ret)
+ dev_warn(qcom->dev, "failed to disable interconnect: %d\n", ret);
+ } else {
+ /* Runtime suspend keeps power voted on; system suspend drops both. */
+ dwc3_qcom_domains_put(qcom, PMSG_IS_AUTO(msg));
+ }
/*
* The role is stable during suspend as role switching is done from a
@@ -456,13 +604,22 @@ static int dwc3_qcom_resume(struct dwc3_qcom *qcom, pm_message_t msg)
*/
dwc3_qcom_disable_interrupts(qcom);
- ret = clk_bulk_prepare_enable(qcom->num_clocks, qcom->clks);
- if (ret < 0)
- return ret;
+ if (!qcom->fw_managed) {
+ ret = clk_bulk_prepare_enable(qcom->num_clocks, qcom->clks);
+ if (ret < 0)
+ return ret;
- ret = dwc3_qcom_interconnect_enable(qcom);
- if (ret)
- dev_warn(qcom->dev, "failed to enable interconnect: %d\n", ret);
+ ret = dwc3_qcom_interconnect_enable(qcom);
+ if (ret)
+ dev_warn(qcom->dev, "failed to enable interconnect: %d\n", ret);
+ } else {
+ /* Runtime resume re-votes bus only; system resume votes both. */
+ ret = dwc3_qcom_domains_get(qcom, PMSG_IS_AUTO(msg));
+ if (ret) {
+ dev_err(qcom->dev, "failed to enable power domains: %d\n", ret);
+ return ret;
+ }
+ }
/* Clear existing events from PHY related to L2 in/out */
for (i = 0; i < qcom->num_ports; i++) {
@@ -699,16 +856,21 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
if (priv_data)
qcom->broken_suspend = priv_data->broken_suspend;
+ if (priv_data && priv_data->fw_managed)
+ qcom->fw_managed = priv_data->fw_managed;
+
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),
"failed to get resets\n");
}
- ret = devm_clk_bulk_get_all(&pdev->dev, &qcom->clks);
- if (ret < 0)
- return dev_err_probe(dev, ret, "failed to get clocks\n");
- qcom->num_clocks = ret;
+ if (!qcom->fw_managed) {
+ ret = devm_clk_bulk_get_all(&pdev->dev, &qcom->clks);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to get clocks\n");
+ qcom->num_clocks = ret;
+ }
ret = reset_control_assert(qcom->resets);
if (ret) {
@@ -724,9 +886,33 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
return ret;
}
- ret = clk_bulk_prepare_enable(qcom->num_clocks, qcom->clks);
- if (ret < 0)
- return ret;
+ if (!qcom->fw_managed) {
+ ret = clk_bulk_prepare_enable(qcom->num_clocks, qcom->clks);
+ if (ret < 0) {
+ reset_control_assert(qcom->resets);
+ return ret;
+ }
+ } else {
+ ret = dwc3_qcom_domain_attach(qcom);
+ if (ret) {
+ dev_err(dev, "Failed to attach domains (%d).\n", ret);
+ reset_control_assert(qcom->resets);
+ return ret;
+ }
+
+ /*
+ * pm_runtime_resume_and_get() is correct here regardless of
+ * whatever state firmware or the bootloader left the domains
+ * in beforehand.
+ */
+ ret = dwc3_qcom_domains_get(qcom, false /* bus_only */);
+ if (ret) {
+ dev_err(dev, "Failed to enable power domains (%d)\n", ret);
+ dwc3_qcom_domain_detach(qcom);
+ reset_control_assert(qcom->resets);
+ return ret;
+ }
+ }
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r) {
@@ -790,9 +976,11 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
goto clk_disable;
}
- ret = dwc3_qcom_interconnect_init(qcom);
- if (ret)
- goto remove_core;
+ if (!qcom->fw_managed) {
+ ret = dwc3_qcom_interconnect_init(qcom);
+ if (ret)
+ goto remove_core;
+ }
wakeup_source = of_property_read_bool(dev->of_node, "wakeup-source");
device_init_wakeup(&pdev->dev, wakeup_source);
@@ -804,7 +992,14 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
remove_core:
dwc3_core_remove(&qcom->dwc);
clk_disable:
- clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks);
+ if (!qcom->fw_managed) {
+ clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks);
+ } else {
+ dwc3_qcom_domains_put(qcom, false /* bus_only */);
+ dwc3_qcom_domain_detach(qcom);
+ }
+
+ reset_control_assert(qcom->resets);
return ret;
}
@@ -817,9 +1012,18 @@ static void dwc3_qcom_remove(struct platform_device *pdev)
if (pm_runtime_resume_and_get(qcom->dev) < 0)
return;
+ /* Also disables runtime PM and marks the device suspended. */
dwc3_core_remove(&qcom->dwc);
- clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks);
- dwc3_qcom_interconnect_exit(qcom);
+
+ if (!qcom->fw_managed) {
+ clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks);
+ dwc3_qcom_interconnect_exit(qcom);
+ } else {
+ dwc3_qcom_domains_put(qcom, false /* bus_only */);
+ dwc3_qcom_domain_detach(qcom);
+ }
+
+ reset_control_assert(qcom->resets);
pm_runtime_put_noidle(qcom->dev);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v3 7/7] usb: dwc3: qcom: Enable VBUS override at probe for default peripheral role
2026-10-10 5:32 [PATCH v3 0/7] usb: dwc3: qcom: Add firmware-managed resource support for SA8255P Faisal Hassan
` (5 preceding siblings ...)
2026-10-10 5:32 ` [PATCH v3 6/7] usb: dwc3: qcom: Support firmware-managed resource states for power management Faisal Hassan
@ 2026-10-10 5:32 ` Faisal Hassan
6 siblings, 0 replies; 10+ messages in thread
From: Faisal Hassan @ 2026-10-10 5:32 UTC (permalink / raw)
To: Thinh.Nguyen, gregkh, mathias.nyman, robh, krzk+dt, conor+dt,
p.zabel, wesley.cheng
Cc: sriram.dash, jack.pham, krishna.kurapati, shazad.hussain,
linux-arm-msm, linux-usb, devicetree, linux-kernel, Faisal Hassan
When the controller is in OTG mode with usb-role-switch and
role-switch-default-mode set to peripheral, the role notifier never sees
a transition at probe, so the QSCRATCH VBUS override is not programmed
and the device never gets a session-valid indication.
Enable the VBUS override at probe whenever the initial role is
USB_ROLE_DEVICE.
Validated on SA8255P with an HS host attached: 20 unbind/bind and 20
suspend/resume iterations completed with no failures.
Signed-off-by: Faisal Hassan <faisal.hassan@oss.qualcomm.com>
---
drivers/usb/dwc3/dwc3-qcom.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c
index 0896a323bdda..92f1223f7b21 100644
--- a/drivers/usb/dwc3/dwc3-qcom.c
+++ b/drivers/usb/dwc3/dwc3-qcom.c
@@ -953,10 +953,12 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
dwc3_qcom_vbus_override_enable(qcom, true);
} else {
if ((device_property_read_bool(dev, "usb-role-switch")) &&
- (usb_get_role_switch_default_mode(dev) == USB_DR_MODE_HOST))
+ (usb_get_role_switch_default_mode(dev) == USB_DR_MODE_HOST)) {
qcom->current_role = USB_ROLE_HOST;
- else
+ } else {
qcom->current_role = USB_ROLE_DEVICE;
+ dwc3_qcom_vbus_override_enable(qcom, true);
+ }
}
qcom->dwc.glue_ops = &dwc3_qcom_glue_ops;
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread