]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
usb: dwc2: Implement recovery after PM domain off
authorStefan Wahren <wahrenst@gmx.net>
Mon, 17 Feb 2025 13:41:32 +0000 (14:41 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 19 Feb 2025 14:15:54 +0000 (15:15 +0100)
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>
drivers/usb/dwc2/core.c
drivers/usb/dwc2/core.h
drivers/usb/dwc2/platform.c

index 9919ab725d544caacce42aa25971151f50d060ec..c3d24312db0fec3766c3069afd393af64ea4047b 100644 (file)
@@ -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);
index 265791fbe87f643bfa546ea80227f0f5f82e26b0..34127b890b2a7f87ede1676fe0e27e7ec4767848 100644 (file)
@@ -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;
index 91c80a92d9b8beafe071c06c14f7de7e8c733cf4..12b4dc07d08a47fe8ecf47d158b46b9bf63da18c 100644 (file)
@@ -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;