diff options
author | Stefan Wahren <wahrenst@gmx.net> | 2025-02-17 14:41:32 +0100 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2025-02-19 15:15:54 +0100 |
commit | ba6e518d136b25b340ddedb97a79db5642e4ed7f (patch) | |
tree | 124618833a10c1a1830df5a9c42c6bb70357bd46 | |
parent | 8b7a1b3da2e290bcbe8024519c86ddf1aa2096e8 (diff) |
usb: dwc2: Implement recovery after PM domain off
According to the dt-bindings there are some platforms, which have a
dedicated USB power domain for DWC2 IP core supply. If the power domain
is switched off during system suspend then all USB register will lose
their settings.
Use GUSBCFG_TOUTCAL as a canary to detect that the power domain has
been powered off during suspend. Since the GOTGCTL_CURMODE_HOST doesn't
match on all platform with the current mode, additionally backup
GINTSTS. This works reliable to decide which registers should be
restored.
Signed-off-by: Stefan Wahren <wahrenst@gmx.net>
Reviewed-by: Douglas Anderson <dianders@chromium.org>
Link: https://lore.kernel.org/r/20250217134132.36786-4-wahrenst@gmx.net
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/usb/dwc2/core.c | 1 | ||||
-rw-r--r-- | drivers/usb/dwc2/core.h | 2 | ||||
-rw-r--r-- | drivers/usb/dwc2/platform.c | 38 |
3 files changed, 41 insertions, 0 deletions
diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 9919ab725d54..c3d24312db0f 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -43,6 +43,7 @@ int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg) /* Backup global regs */ gr = &hsotg->gr_backup; + gr->gintsts = dwc2_readl(hsotg, GINTSTS); gr->gotgctl = dwc2_readl(hsotg, GOTGCTL); gr->gintmsk = dwc2_readl(hsotg, GINTMSK); gr->gahbcfg = dwc2_readl(hsotg, GAHBCFG); diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 265791fbe87f..34127b890b2a 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -667,6 +667,7 @@ struct dwc2_hw_params { /** * struct dwc2_gregs_backup - Holds global registers state before * entering partial power down + * @gintsts: Backup of GINTSTS register * @gotgctl: Backup of GOTGCTL register * @gintmsk: Backup of GINTMSK register * @gahbcfg: Backup of GAHBCFG register @@ -683,6 +684,7 @@ struct dwc2_hw_params { * @valid: True if registers values backuped. */ struct dwc2_gregs_backup { + u32 gintsts; u32 gotgctl; u32 gintmsk; u32 gahbcfg; diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 91c80a92d9b8..12b4dc07d08a 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -685,6 +685,14 @@ static int __maybe_unused dwc2_suspend(struct device *dev) regulator_disable(dwc2->usb33d); } + if (is_device_mode) + ret = dwc2_gadget_backup_critical_registers(dwc2); + else + ret = dwc2_host_backup_critical_registers(dwc2); + + if (ret) + return ret; + if (dwc2->ll_hw_enabled && (is_device_mode || dwc2_host_can_poweroff_phy(dwc2))) { ret = __dwc2_lowlevel_hw_disable(dwc2); @@ -694,6 +702,24 @@ static int __maybe_unused dwc2_suspend(struct device *dev) return ret; } +static int dwc2_restore_critical_registers(struct dwc2_hsotg *hsotg) +{ + struct dwc2_gregs_backup *gr; + + gr = &hsotg->gr_backup; + + if (!gr->valid) { + dev_err(hsotg->dev, "No valid register backup, failed to restore\n"); + return -EINVAL; + } + + if (gr->gintsts & GINTSTS_CURMODE_HOST) + return dwc2_host_restore_critical_registers(hsotg); + + return dwc2_gadget_restore_critical_registers(hsotg, DWC2_RESTORE_DCTL | + DWC2_RESTORE_DCFG); +} + static int __maybe_unused dwc2_resume(struct device *dev) { struct dwc2_hsotg *dwc2 = dev_get_drvdata(dev); @@ -706,6 +732,18 @@ static int __maybe_unused dwc2_resume(struct device *dev) } dwc2->phy_off_for_suspend = false; + /* + * During suspend it's possible that the power domain for the + * DWC2 controller is disabled and all register values get lost. + * In case the GUSBCFG register is not initialized, it's clear the + * registers must be restored. + */ + if (!(dwc2_readl(dwc2, GUSBCFG) & GUSBCFG_TOUTCAL_MASK)) { + ret = dwc2_restore_critical_registers(dwc2); + if (ret) + return ret; + } + if (dwc2->params.activate_stm_id_vb_detection) { unsigned long flags; u32 ggpio, gotgctl; |