Commit ba6e518d authored by Stefan Wahren's avatar Stefan Wahren Committed by Greg Kroah-Hartman
Browse files

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: default avatarStefan Wahren <wahrenst@gmx.net>
Reviewed-by: default avatarDouglas Anderson <dianders@chromium.org>
Link: https://lore.kernel.org/r/20250217134132.36786-4-wahrenst@gmx.net


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 8b7a1b3d
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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);
+2 −0
Original line number Diff line number Diff line
@@ -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;
+38 −0
Original line number Diff line number Diff line
@@ -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;