* [PATCH v3 0/7] usb: dwc3: qcom: Add firmware-managed resource support for SA8255P
@ 2026-10-10 5:32 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
` (6 more replies)
0 siblings, 7 replies; 9+ 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 SoCs such as SA8255P, the USB controller runs in
a Linux VM while the platform resources that the controller depends on
(clocks, interconnects, resets, regulators and the GDSC) are owned and
sequenced by firmware running in a separate VM:
+----------------+ SCMI +------------------+
| Linux VM | <------------------> | Firmware VM |
| | | |
| dwc3-qcom.c | | clocks |
| "power" PD |----------------------->| interconnects |
| "bus" PD |----------------------->| resets |
| | | regulators/GDSC |
+----------------+ +------------------+
Linux has no direct access to these resources; it can only vote for
two abstracted power domains exposed over the SCMI power-domain
protocol:
- "power": gates the power supply (GDSC) to the USB controller
- "bus": gates the clocks and interconnects used for data transfer
Register accesses through the controller require "power" to be voted
on; "bus" additionally requires "power". The vote policy implemented
in this series is:
- Enable order: power, then bus
- Disable order: bus, then power
- Runtime suspend/resume vote only "bus" -- "power" stays on so that
accesses that can legitimately happen while runtime suspended
keep working.
- System suspend/resume vote both domains.
The two domains are attached with PD_FLAG_NO_DEV_LINK because runtime
suspend and system suspend require different supplier states for the
same consumer, which a plain device-link cannot express; the driver
manages the runtime PM references for each attached domain explicitly
instead.
SA8255P also requires a few platform-specific fixups on top of this,
handled by this series:
- "Broken suspend": APPS/system suspend causes the clock controller
to cut all clocks to the controller, putting it into a Power-On
Reset state and losing Qscratch programming. Resume must reset the
controller, reselect the UTMI clock as the PIPE clock when no SS
PHY is present, and reassert VBUS_VALID so the PHYs don't fall
back into suspend.
- The loss of power across system suspend also leaves the xHC in an
error state on resume, which the existing XHCI_RESET_ON_RESUME
quirk handles once dwc3 is taught to request it via a device
property.
Dependency: this series applies on top of usb-next plus
"usb: dwc3: qcom: Add support to skip phy management by USB core" [2]
from Krishna Kurapati (Acked-by: Thinh Nguyen, not yet applied). Patch 5
sets the skip_phy_init property introduced there and will not build
without it, so that patch needs to be applied first.
Patch overview:
Patch 1: Track whether the wakeup interrupts are armed so enable and
disable stay balanced across runtime and system PM, and
always disarm them on resume. They stay disarmed if the
resume fails.
Patch 2: Refactor dwc3_qcom_suspend()/dwc3_qcom_resume() to take a
pm_message_t instead of a bool, so the runtime-PM vs
system-PM path can be told apart with PMSG_IS_AUTO(), which
later patches rely on.
Patch 3: New devicetree binding, qcom,sa8255p-dwc3.yaml, requiring
"power"/"bus" power-domains and excluding clocks/
interconnects for firmware-managed platforms.
Patch 4: Teach xhci-plat to set XHCI_RESET_ON_RESUME from a device
property.
Patch 5: SA8255P broken-suspend handling (reset + PIPE_UTMI_CLK_SEL +
VBUS_VALID fixups on resume). The xhci_reset_on_resume
property is added to struct dwc3_properties and forwarded to
xhci-plat, and the glue driver sets it, together with
needs_full_reinit, through its probe data.
Patch 6: Firmware-managed "power"/"bus" domain voting for SA8255P,
wired into probe/remove/suspend/resume, with the probe
error paths and the system-suspend-after-runtime-suspend
case keeping the votes and reset balanced.
Patch 7: Enable the VBUS override at probe when the default role is
peripheral.
Testing:
Tested on SA8255P Ride:
- Driver probe, unbind and rebind
- Runtime suspend and resume
- System suspend and resume
- Host mode enumeration across system suspend/resume
- 20x system suspend/resume with a host attached to the peripheral
port: UDC returns to configured, no soft reset failures
- 20x driver unbind/bind with the gadget enumerated and on the host
port
Changes in v3:
- Dropped the software node used to pass xhci-reset-on-resume from
dwc3-qcom to the dwc3 core, together with its probe/remove
handling. The flag is now a field in struct dwc3_properties
(xhci_reset_on_resume), set through the glue driver's probe data
and forwarded to xhci-plat by dwc3 host.c. This avoids the software
node lifetime problems that led to the revert of the earlier
approach. The old patch 5 is merged into the broken-suspend patch.
[Thinh]
- Renamed the flag to xhci_reset_on_resume and placed it with the
other quirk bits in struct dwc3. [Thinh]
- Depend on Krishna's skip-phy patch [2]; the property array in dwc3
host.c now has 9 slots (8 properties plus the terminator) to cover
all quirks that can be set together.
- Patch 1: changed approach. Instead of re-enabling the wakeup
interrupts when resume fails, track whether they are armed
(wakeup_irqs_armed) so enable/disable are idempotent, and always
disarm them on resume, not only for host mode with wakeup enabled.
On a failed resume they stay disarmed, since a level-triggered
wakeup line could otherwise keep firing.
- Patch 2: dropped the wakeup value derived from the PM message in
the resume path, as patch 1 now disarms the interrupts
unconditionally.
- Patch 3: interrupts were minItems 1/maxItems 6 and are now
minItems 5 with the names listed in the schema; added dma-coherent;
dropped the free-form descriptions of power-domains,
power-domain-names and resets.
- Patch 5: also set needs_full_reinit so dwc3 core re-initializes the
controller on system resume even in host mode with wakeup enabled;
restore Qscratch (PIPE_UTMI_CLK_SEL, VBUS_VALID) on every system
resume rather than depending on wakeup, only forcing VBUS_VALID
outside host mode, and never on runtime resume; skip
dwc3_pm_prepare() so the resume callback always runs.
- Patch 6: dwc3_qcom_domains_put() no longer returns an error: the
runtime PM usage count is dropped even when put_sync() fails, so
failures are only warned about and cannot unbalance the next vote.
System suspend of a runtime-suspended device now also drops the
"power" vote that runtime suspend keeps, so system resume stays
balanced. Probe error paths now assert the reset again. The
remove path is simplified accordingly.
- Patch 7: new.
- Link to v2: https://lore.kernel.org/all/20260903153827.3463313-1-faisal.hassan@oss.qualcomm.com/
Changes in v2:
- Reworked the resource model from OPP-based performance-state
voting to plain power-domain ON/OFF voting via "power" and "bus"
domains, addressing Bjorn's objection that OPP levels conflate
performance-state selection with power-state signaling. [Bjorn]
- Documented the PD_FLAG_NO_DEV_LINK rationale (asymmetric runtime
suspend vs system suspend supplier state) in the firmware-managed
patch.
- Validate the number of attached domains against what was
requested instead of assuming attach always returns exactly two.
- Fixed an unwind bug where a "bus" domain enable failure during
probe would return without dropping the "power" domain vote.
- Added the new qcom,sa8255p-dwc3.yaml binding.
- Terminated the broken-suspend software node's property array and
fixed a bug where the software-property walk in dwc3 core did not
continue past the first match.
- Fixed dwc3_qcom_resume()'s broken-suspend reset to only fire on
system resume, not runtime resume.
- Link to v1: https://lore.kernel.org/r/20251127-controller_scmi_upstream-v1-0-38bcca513c28@oss.qualcomm.com
[1] v2: https://lore.kernel.org/all/20260903153827.3463313-1-faisal.hassan@oss.qualcomm.com/
[2] https://lore.kernel.org/all/20260914-xhci-skip-phy-init-v1-1-9c46b31a5a39@oss.qualcomm.com/
Faisal Hassan (7):
usb: dwc3: qcom: Keep wakeup IRQ enable/disable balanced on failed
resume
usb: dwc3: qcom: Distinguish PM and runtime suspend/resume paths
dt-bindings: usb: Add qcom,sa8255p-dwc3 for firmware-managed resources
usb: host: xhci-plat: Support XHCI_RESET_ON_RESUME via device property
usb: dwc3: qcom: Support broken suspend for SA8255P
usb: dwc3: qcom: Support firmware-managed resource states for power
management
usb: dwc3: qcom: Enable VBUS override at probe for default peripheral
role
.../bindings/usb/qcom,sa8255p-dwc3.yaml | 119 +++++
drivers/usb/dwc3/core.c | 3 +
drivers/usb/dwc3/core.h | 3 +
drivers/usb/dwc3/dwc3-qcom.c | 411 +++++++++++++++---
drivers/usb/dwc3/glue.h | 2 +
drivers/usb/dwc3/host.c | 5 +-
drivers/usb/host/xhci-plat.c | 3 +
7 files changed, 487 insertions(+), 59 deletions(-)
create mode 100644 Documentation/devicetree/bindings/usb/qcom,sa8255p-dwc3.yaml
--
2.34.1
^ permalink raw reply [flat|nested] 9+ messages in thread
* [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; 9+ 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] 9+ 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; 9+ 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] 9+ 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; 9+ 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] 9+ 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; 9+ 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] 9+ 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; 9+ 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] 9+ 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; 9+ 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] 9+ 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; 9+ 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] 9+ 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
0 siblings, 0 replies; 9+ 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] 9+ messages in thread
end of thread, other threads:[~2026-10-13 7:20 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [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-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 ` [PATCH v3 5/7] usb: dwc3: qcom: Support broken suspend for SA8255P 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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox