[PATCH v2 6/7] usb: dwc3: qcom: Support broken suspend for SA8255P
From: Faisal Hassan
Date: Thu Sep 03 2026 - 13:04:22 EST
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.
Co-developed-by: Sriram Dash <sriram.dash@xxxxxxxxxxxxxxxx>
Signed-off-by: Sriram Dash <sriram.dash@xxxxxxxxxxxxxxxx>
Signed-off-by: Faisal Hassan <faisal.hassan@xxxxxxxxxxxxxxxx>
---
drivers/usb/dwc3/dwc3-qcom.c | 128 +++++++++++++++++++++++++++--------
1 file changed, 100 insertions(+), 28 deletions(-)
diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c
index 6d25f81800a7..e58a9ca23b00 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;
@@ -85,6 +89,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)
@@ -335,6 +346,23 @@ static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom)
dwc3_qcom_enable_port_interrupts(&qcom->ports[i]);
}
+static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom)
+{
+ /* Configure dwc3 to use UTMI clock as PIPE clock not present */
+ dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
+ PIPE_UTMI_CLK_DIS);
+
+ usleep_range(100, 1000);
+
+ dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
+ PIPE_UTMI_CLK_SEL | PIPE3_PHYSTATUS_SW);
+
+ usleep_range(100, 1000);
+
+ dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
+ PIPE_UTMI_CLK_DIS);
+}
+
static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, pm_message_t msg)
{
u32 val;
@@ -388,6 +416,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;
+ }
+ }
+
/*
* For runtime resume, always assume wakeup was enabled.
* For system resume, check device wakeup capability.
@@ -415,6 +465,14 @@ 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);
}
+ if (qcom->broken_suspend) {
+ if (!wakeup && 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) && wakeup))
+ dwc3_qcom_vbus_override_enable(qcom, true);
+ }
+
qcom->is_suspended = false;
return 0;
@@ -449,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)
{
@@ -637,6 +678,15 @@ static struct dwc3_glue_ops dwc3_qcom_glue_ops = {
.pre_run_stop = dwc3_qcom_run_stop_notifier,
};
+static const struct property_entry dwc3_qcom_props_broken_suspend[] = {
+ PROPERTY_ENTRY_BOOL("xhci-reset-on-resume"),
+ { }
+};
+
+static const struct software_node dwc3_qcom_swnode_prop_broken_suspend = {
+ .properties = dwc3_qcom_props_broken_suspend,
+};
+
static int dwc3_qcom_probe(struct platform_device *pdev)
{
struct dwc3_probe_data probe_data = {};
@@ -644,8 +694,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);
@@ -654,21 +704,33 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
qcom->dev = &pdev->dev;
+ priv_data = of_device_get_match_data(dev);
+ if (priv_data && priv_data->broken_suspend) {
+ ret = device_add_software_node(&pdev->dev,
+ &dwc3_qcom_swnode_prop_broken_suspend);
+ if (ret)
+ return ret;
+ 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),
- "failed to get resets\n");
+ ret = dev_err_probe(&pdev->dev, PTR_ERR(qcom->resets),
+ "failed to get resets\n");
+ goto err_remove_swnode;
}
ret = devm_clk_bulk_get_all(&pdev->dev, &qcom->clks);
- if (ret < 0)
- return dev_err_probe(dev, ret, "failed to get clocks\n");
+ if (ret < 0) {
+ dev_err_probe(dev, ret, "failed to get clocks\n");
+ goto err_remove_swnode;
+ }
qcom->num_clocks = ret;
ret = reset_control_assert(qcom->resets);
if (ret) {
dev_err(&pdev->dev, "failed to assert resets, err=%d\n", ret);
- return ret;
+ goto err_remove_swnode;
}
usleep_range(10, 1000);
@@ -676,12 +738,12 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
ret = reset_control_deassert(qcom->resets);
if (ret) {
dev_err(&pdev->dev, "failed to deassert resets, err=%d\n", ret);
- return ret;
+ goto err_remove_swnode;
}
ret = clk_bulk_prepare_enable(qcom->num_clocks, qcom->clks);
if (ret < 0)
- return ret;
+ goto err_remove_swnode;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r) {
@@ -708,9 +770,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);
@@ -757,6 +819,9 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
clk_disable:
clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks);
+err_remove_swnode:
+ if (qcom->broken_suspend)
+ device_remove_software_node(&pdev->dev);
return ret;
}
@@ -773,6 +838,9 @@ static void dwc3_qcom_remove(struct platform_device *pdev)
dwc3_qcom_interconnect_exit(qcom);
pm_runtime_put_noidle(qcom->dev);
+
+ if (qcom->broken_suspend)
+ device_remove_software_node(&pdev->dev);
}
static int dwc3_qcom_pm_suspend(struct device *dev)
@@ -867,6 +935,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" },
{ }
};
--
2.34.1