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

Commit c071aedb authored by qctecmdr Service's avatar qctecmdr Service Committed by Gerrit - the friendly Code Review server
Browse files

Merge "usb: xhci-plat: Add support for PM suspend and hibernation"

parents 67942dbf 25f18f33
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -79,6 +79,9 @@ Optional properties :
	are supported. If omitted, assume HW supports "1.5".
- qcom,reset-ep-after-lpm-resume: If present, dbm requires ep reset after
	going to lpm
- qcom,host-poweroff-in-pm-suspend: If present, allow PM suspend to happen
	irrespective of runtimePM state of host and power collapse the core.
	This also leads to reset-resume of connected devices on PM resume.

Sub nodes:
- Sub node for "DWC3- USB3 controller".
+3 −0
Original line number Diff line number Diff line
@@ -29,6 +29,9 @@ Optional properties:
  - clocks: reference to a clock
  - usb3-lpm-capable: determines if platform is USB3 LPM capable
  - quirk-broken-port-ped: set if the controller has broken port disable mechanism
  - host-poweroff-in-pm-suspend: If set, allow PM suspend to happen irrespective
    of runtimePM state of host and power collapse the core. This also leads to
    reset-resume of connected devices on PM resume.

Example:
	usb@f0931000 {
+13 −1
Original line number Diff line number Diff line
@@ -130,6 +130,7 @@ void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode)
	reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG));
	reg |= DWC3_GCTL_PRTCAPDIR(mode);
	dwc3_writel(dwc->regs, DWC3_GCTL, reg);
	dwc->current_dr_role = mode;
}

void dwc3_en_sleep_mode(struct dwc3 *dwc)
@@ -1536,8 +1537,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);

@@ -1545,6 +1556,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
@@ -1190,6 +1190,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 work_to_dwc(w)		(container_of((w), struct dwc3, drd_work))
+107 −30
Original line number Diff line number Diff line
@@ -238,6 +238,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;
@@ -1705,7 +1706,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;
}

@@ -2060,15 +2061,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;
@@ -2267,7 +2287,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);
@@ -2329,7 +2349,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;
@@ -2407,8 +2427,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);
@@ -2568,8 +2588,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 = arm_iommu_attach_device(mdwc->dev,
					mdwc->iommu_map);
@@ -2584,18 +2602,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;
	}

@@ -2678,7 +2684,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)
@@ -3376,6 +3382,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) {
@@ -3627,6 +3645,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")) {
@@ -3665,6 +3689,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;
}
@@ -3745,6 +3770,9 @@ static int dwc3_msm_remove(struct platform_device *pdev)
		arm_iommu_release_mapping(mdwc->iommu_map);
	}

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

	return 0;
}

@@ -4385,7 +4413,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;
@@ -4394,6 +4422,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);

@@ -4401,6 +4430,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;
@@ -4411,6 +4446,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);
@@ -4423,6 +4478,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);

@@ -4450,7 +4527,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)
Loading