Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0949fa1b authored by Ajay Agarwal's avatar Ajay Agarwal Committed by Jack Pham
Browse files

usb: dwc3: Add support for PM suspend and hibernation in host mode



DWC3 driver uses wakeup_source and allows pm_suspend only after
entering low power mode as part of runtime suspend. This prevents
PM suspend in host mode if connected device's driver doesn't
support runtime or selective suspend.
For platforms such as automotive, add support in driver where
user can specify using dtsi attribute to not use wakeup source in
host mode. It will allow system to enter deep sleep or pm_suspend
irrespective of runtime PM state of XHCI. The connected devices
are also reset-resumed on PM resume.
On high level below are the changes in default behavior:
 -Do not use wakeup_source for DWC3 when operating in host mode.
 -For host mode, devices will suspend upon PM suspend and
  reset-resumed on PM resume.

Change-Id: I8a3d744a1d9d714f17a0cfe4ff5b69b04d9f763f
Signed-off-by: default avatarAjay Agarwal <ajaya@codeaurora.org>
parent 21f76b3f
Loading
Loading
Loading
Loading
+12 −1
Original line number Diff line number Diff line
@@ -1853,8 +1853,18 @@ static int dwc3_resume(struct device *dev)
	int		ret;

	/* Check if platform glue driver handling PM, if not then handle here */
	if (!dwc3_notify_event(dwc, DWC3_CORE_PM_RESUME_EVENT, 0))
	if (!dwc3_notify_event(dwc, DWC3_CORE_PM_RESUME_EVENT, 0)) {
		/*
		 * If the core was in host mode during suspend, then set the
		 * runtime PM state as active to reflect actual state of device
		 * which is now out of LPM. This allows runtime_suspend later.
		 */
		if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST &&
		    dwc->host_poweroff_in_pm_suspend)
			goto runtime_set_active;

		return 0;
	}

	pinctrl_pm_select_default_state(dev);

@@ -1862,6 +1872,7 @@ static int dwc3_resume(struct device *dev)
	if (ret)
		return ret;

runtime_set_active:
	pm_runtime_disable(dev);
	pm_runtime_set_active(dev);
	pm_runtime_enable(dev);
+6 −0
Original line number Diff line number Diff line
@@ -1334,6 +1334,12 @@ struct dwc3 {
	unsigned int		vbus_active:1;
	/* Indicate if software connect was issued by the usb_gadget_driver */
	unsigned int		softconnect:1;
	/*
	 * If true, PM suspend allowed irrespective of host runtimePM state
	 * and core will power collapse. This also leads to reset-resume of
	 * connected devices on PM resume.
	 */
	bool			host_poweroff_in_pm_suspend;
};

#define INCRX_BURST_MODE 0
+107 −30
Original line number Diff line number Diff line
@@ -237,6 +237,7 @@ struct dwc3_msm {
	struct work_struct	restart_usb_work;
	bool			in_restart;
	struct workqueue_struct *dwc3_wq;
	struct workqueue_struct *sm_usb_wq;
	struct delayed_work	sm_work;
	unsigned long		inputs;
	unsigned int		max_power;
@@ -1697,7 +1698,7 @@ static int msm_dwc3_usbdev_notify(struct notifier_block *self,
	}

	mdwc->hc_died = true;
	schedule_delayed_work(&mdwc->sm_work, 0);
	queue_delayed_work(mdwc->sm_usb_wq, &mdwc->sm_work, 0);
	return 0;
}

@@ -2058,15 +2059,34 @@ static void dwc3_msm_power_collapse_por(struct dwc3_msm *mdwc)
	if (ret)
		dev_err(mdwc->dev, "%s: dwc3_core init failed (%d)\n",
							__func__, ret);

	/* Get initial P3 status and enable IN_P3 event */
	if (dwc3_is_usb31(dwc))
		val = dwc3_msm_read_reg_field(mdwc->base,
			DWC31_LINK_GDBGLTSSM,
			DWC3_GDBGLTSSM_LINKSTATE_MASK);
	else
		val = dwc3_msm_read_reg_field(mdwc->base,
			DWC3_GDBGLTSSM, DWC3_GDBGLTSSM_LINKSTATE_MASK);
	atomic_set(&mdwc->in_p3, val == DWC3_LINK_STATE_U3);
	dwc3_msm_write_reg_field(mdwc->base, PWR_EVNT_IRQ_MASK_REG,
				PWR_EVNT_POWERDOWN_IN_P3_MASK, 1);

	/* Set the core in host mode if it was in host mode during pm_suspend */
	if (mdwc->in_host_mode) {
		dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST);
		dwc3_en_sleep_mode(dwc);
	}

static int dwc3_msm_prepare_suspend(struct dwc3_msm *mdwc)
}

static int dwc3_msm_prepare_suspend(struct dwc3_msm *mdwc, bool ignore_p3_state)
{
	unsigned long timeout;
	u32 reg = 0;

	if ((mdwc->in_host_mode || mdwc->in_device_mode)
			&& dwc3_msm_is_superspeed(mdwc) && !mdwc->in_restart) {
	if (!ignore_p3_state && ((mdwc->in_host_mode || mdwc->in_device_mode)
			&& dwc3_msm_is_superspeed(mdwc) && !mdwc->in_restart)) {
		if (!atomic_read(&mdwc->in_p3)) {
			dev_err(mdwc->dev, "Not in P3,aborting LPM sequence\n");
			return -EBUSY;
@@ -2265,7 +2285,7 @@ static int dwc3_msm_update_bus_bw(struct dwc3_msm *mdwc, enum bus_vote bv)
	return ret;
}

static int dwc3_msm_suspend(struct dwc3_msm *mdwc)
static int dwc3_msm_suspend(struct dwc3_msm *mdwc, bool force_power_collapse)
{
	int ret;
	struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3);
@@ -2327,7 +2347,7 @@ static int dwc3_msm_suspend(struct dwc3_msm *mdwc)
		return -EBUSY;
	}

	ret = dwc3_msm_prepare_suspend(mdwc);
	ret = dwc3_msm_prepare_suspend(mdwc, force_power_collapse);
	if (ret) {
		mutex_unlock(&mdwc->suspend_resume_mutex);
		return ret;
@@ -2405,8 +2425,8 @@ static int dwc3_msm_suspend(struct dwc3_msm *mdwc)
	clk_disable_unprepare(mdwc->xo_clk);

	/* Perform controller power collapse */
	if (!mdwc->in_host_mode && (!mdwc->in_device_mode ||
					mdwc->in_restart)) {
	if (!(mdwc->in_host_mode || mdwc->in_device_mode) ||
	      mdwc->in_restart || force_power_collapse) {
		mdwc->lpm_flags |= MDWC3_POWER_COLLAPSE;
		dev_dbg(mdwc->dev, "%s: power collapse\n", __func__);
		dwc3_msm_config_gdsc(mdwc, 0);
@@ -2566,8 +2586,6 @@ static int dwc3_msm_resume(struct dwc3_msm *mdwc)

	/* Recover from controller power collapse */
	if (mdwc->lpm_flags & MDWC3_POWER_COLLAPSE) {
		u32 tmp;

		if (mdwc->iommu_map) {
			ret = __depr_arm_iommu_attach_device(mdwc->dev,
					mdwc->iommu_map);
@@ -2582,18 +2600,6 @@ static int dwc3_msm_resume(struct dwc3_msm *mdwc)

		dwc3_msm_power_collapse_por(mdwc);

		/* Get initial P3 status and enable IN_P3 event */
		if (dwc3_is_usb31(dwc))
			tmp = dwc3_msm_read_reg_field(mdwc->base,
				DWC31_LINK_GDBGLTSSM,
				DWC3_GDBGLTSSM_LINKSTATE_MASK);
		else
			tmp = dwc3_msm_read_reg_field(mdwc->base,
				DWC3_GDBGLTSSM, DWC3_GDBGLTSSM_LINKSTATE_MASK);
		atomic_set(&mdwc->in_p3, tmp == DWC3_LINK_STATE_U3);
		dwc3_msm_write_reg_field(mdwc->base, PWR_EVNT_IRQ_MASK_REG,
					PWR_EVNT_POWERDOWN_IN_P3_MASK, 1);

		mdwc->lpm_flags &= ~MDWC3_POWER_COLLAPSE;
	}

@@ -2676,7 +2682,7 @@ static void dwc3_ext_event_notify(struct dwc3_msm *mdwc)
		clear_bit(B_SUSPEND, &mdwc->inputs);
	}

	schedule_delayed_work(&mdwc->sm_work, 0);
	queue_delayed_work(mdwc->sm_usb_wq, &mdwc->sm_work, 0);
}

static void dwc3_resume_work(struct work_struct *w)
@@ -3381,6 +3387,18 @@ static int dwc3_msm_probe(struct platform_device *pdev)
		return -ENOMEM;
	}

	/*
	 * Create freezable workqueue for sm_work so that it gets scheduled only
	 * after pm_resume has happened completely. This helps in avoiding race
	 * conditions between xhci_plat_resume and xhci_runtime_resume; and also
	 * between hcd disconnect and xhci_resume.
	 */
	mdwc->sm_usb_wq = create_freezable_workqueue("k_sm_usb");
	if (!mdwc->sm_usb_wq) {
		destroy_workqueue(mdwc->dwc3_wq);
		return -ENOMEM;
	}

	/* Get all clks and gdsc reference */
	ret = dwc3_msm_get_clk_gdsc(mdwc);
	if (ret) {
@@ -3632,6 +3650,12 @@ static int dwc3_msm_probe(struct platform_device *pdev)
		mdwc->pm_qos_latency = 0;
	}

	if (of_property_read_bool(node, "qcom,host-poweroff-in-pm-suspend")) {
		dwc->host_poweroff_in_pm_suspend = true;
		dev_dbg(mdwc->dev, "%s: Core power collapse on host PM suspend\n",
								__func__);
	}

	mutex_init(&mdwc->suspend_resume_mutex);

	if (of_property_read_bool(node, "extcon")) {
@@ -3670,6 +3694,7 @@ static int dwc3_msm_probe(struct platform_device *pdev)
	}
	of_platform_depopulate(&pdev->dev);
err:
	destroy_workqueue(mdwc->sm_usb_wq);
	destroy_workqueue(mdwc->dwc3_wq);
	return ret;
}
@@ -3750,6 +3775,9 @@ static int dwc3_msm_remove(struct platform_device *pdev)
		__depr_arm_iommu_release_mapping(mdwc->iommu_map);
	}

	destroy_workqueue(mdwc->sm_usb_wq);
	destroy_workqueue(mdwc->dwc3_wq);

	return 0;
}

@@ -4381,7 +4409,7 @@ static void dwc3_otg_sm_work(struct work_struct *w)
	}

	if (work)
		schedule_delayed_work(&mdwc->sm_work, delay);
		queue_delayed_work(mdwc->sm_usb_wq, &mdwc->sm_work, delay);

ret:
	return;
@@ -4390,6 +4418,7 @@ static void dwc3_otg_sm_work(struct work_struct *w)
#ifdef CONFIG_PM_SLEEP
static int dwc3_msm_pm_suspend(struct device *dev)
{
	int ret = 0;
	struct dwc3_msm *mdwc = dev_get_drvdata(dev);
	struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3);

@@ -4397,6 +4426,12 @@ static int dwc3_msm_pm_suspend(struct device *dev)
	dbg_event(0xFF, "PM Sus", 0);

	flush_workqueue(mdwc->dwc3_wq);

	/*
	 * Check if pm_suspend can proceed irrespective of runtimePM state of
	 * host.
	 */
	if (!dwc->host_poweroff_in_pm_suspend || !mdwc->in_host_mode) {
		if (!atomic_read(&dwc->in_lpm)) {
			dev_err(mdwc->dev, "Abort PM suspend!! (USB is outside LPM)\n");
			return -EBUSY;
@@ -4407,6 +4442,26 @@ static int dwc3_msm_pm_suspend(struct device *dev)
		return 0;
	}

	/*
	 * PHYs also need to be power collapsed, so call notify_disconnect
	 * before suspend to ensure it.
	 */
	usb_phy_notify_disconnect(mdwc->hs_phy, USB_SPEED_HIGH);
	mdwc->hs_phy->flags &= ~PHY_HOST_MODE;
	usb_phy_notify_disconnect(mdwc->ss_phy, USB_SPEED_SUPER);
	mdwc->ss_phy->flags &= ~PHY_HOST_MODE;

	/*
	 * Power collapse the core. Hence call dwc3_msm_suspend with
	 * 'force_power_collapse' set to 'true'.
	 */
	ret = dwc3_msm_suspend(mdwc, true);
	if (!ret)
		atomic_set(&mdwc->pm_suspended, 1);

	return ret;
}

static int dwc3_msm_pm_resume(struct device *dev)
{
	struct dwc3_msm *mdwc = dev_get_drvdata(dev);
@@ -4419,6 +4474,28 @@ static int dwc3_msm_pm_resume(struct device *dev)
	flush_workqueue(mdwc->dwc3_wq);
	atomic_set(&mdwc->pm_suspended, 0);

	if (!dwc->host_poweroff_in_pm_suspend || !mdwc->in_host_mode) {
		/* kick in otg state machine */
		queue_work(mdwc->dwc3_wq, &mdwc->resume_work);

		return 0;
	}

	/* Resume dwc to avoid unclocked access by xhci_plat_resume */
	dwc3_msm_resume(mdwc);
	pm_runtime_disable(dev);
	pm_runtime_set_active(dev);
	pm_runtime_enable(dev);

	/* Restore PHY flags if hibernated in host mode */
	mdwc->hs_phy->flags |= PHY_HOST_MODE;
	usb_phy_notify_connect(mdwc->hs_phy, USB_SPEED_HIGH);
	if (dwc->maximum_speed >= USB_SPEED_SUPER) {
		mdwc->ss_phy->flags |= PHY_HOST_MODE;
		usb_phy_notify_connect(mdwc->ss_phy,
					USB_SPEED_SUPER);
	}

	/* kick in otg state machine */
	queue_work(mdwc->dwc3_wq, &mdwc->resume_work);

@@ -4446,7 +4523,7 @@ static int dwc3_msm_runtime_suspend(struct device *dev)
	dev_dbg(dev, "DWC3-msm runtime suspend\n");
	dbg_event(0xFF, "RT Sus", 0);

	return dwc3_msm_suspend(mdwc);
	return dwc3_msm_suspend(mdwc, false);
}

static int dwc3_msm_runtime_resume(struct device *dev)
+3 −0
Original line number Diff line number Diff line
@@ -126,6 +126,9 @@ int dwc3_host_init(struct dwc3 *dwc)
	if (dwc->revision <= DWC3_REVISION_300A)
		props[prop_idx++].name = "quirk-broken-port-ped";

	if (dwc->host_poweroff_in_pm_suspend)
		props[prop_idx++].name = "host-poweroff-in-pm-suspend";

	if (prop_idx) {
		ret = platform_device_add_properties(xhci, props);
		if (ret) {