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

Commit 36350577 authored by Chandana Kishori Chiluveru's avatar Chandana Kishori Chiluveru Committed by Manu Gautam
Browse files

usb: dwc3-msm: Allow PM suspend in host mode irrespective of runtimePM



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 and its driver doesn't
support runtime or selective suspend.
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.
Also, super-speed PHY cannot be suspended in SS mode bus-suspend.
Allowing PM suspend without SS PHY wouldn't give much power
savings as XO and VMIN can't happen. For this reason force host
mode disconnect if SS host mode so that SS PHY can be suspended.
On high level below are the changes in default behavior:
 -Do not use wakeup_source for DWC3 when operating in host mode.
 -For HS host mode, devices will suspend upon PM suspend and resumed
  after PM resume in case not runtime suspended already.
 -For SS host mode stop host before PM suspend start host on PM resume.

CRs-fixed: 2009301
Change-Id: I3749b96296cfc86428bdd4da4ce9c70fb4e4b98c
Signed-off-by: default avatarChandana Kishori Chiluveru <cchiluve@codeaurora.org>
Signed-off-by: default avatarManu Gautam <mgautam@codeaurora.org>
parent 20059604
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -47,6 +47,9 @@ Optional properties :
- qcom,pm-qos-latency: This property represents the maximum tolerable CPU latency in
  microsecs, which is used as a vote to keep the CPUs in a high enough power state when
  USB bus is in use (not suspended).
- qcom,no-wakeup-src-in-hostmode: If present then driver doesn't use wakeup_source APIs e.g.
  pm_stay_awake and pm_relax in host mode. This allows PM suspend to happen irrespective of
  runtimePM state of host.

Sub nodes:
- Sub node for "DWC3- USB3 controller".
+123 −26
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@

#define DWC3_IDEV_CHG_MAX 1500
#define DWC3_HVDCP_CHG_MAX 1800
#define DWC3_WAKEUP_SRC_TIMEOUT 5000

/* AHB2PHY register offsets */
#define PERIPH_SS_AHB2PHY_TOP_CFG 0x10
@@ -216,7 +217,8 @@ struct dwc3_msm {
	struct delayed_work	resume_work;
	struct work_struct	restart_usb_work;
	bool			in_restart;
	struct workqueue_struct *dwc3_wq;
	struct workqueue_struct *dwc3_resume_wq;
	struct workqueue_struct *sm_usb_wq;
	struct delayed_work	sm_work;
	unsigned long		inputs;
	enum dwc3_chg_type	chg_type;
@@ -252,6 +254,9 @@ struct dwc3_msm {
	struct notifier_block	dwc3_cpu_notifier;
	struct notifier_block	usbdev_nb;
	bool			hc_died;
	bool			stop_host;
	bool			no_wakeup_src_in_hostmode;
	bool			host_only_mode;

	int  pwr_event_irq;
	atomic_t                in_p3;
@@ -1602,7 +1607,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;
}

@@ -1821,7 +1826,7 @@ static void dwc3_msm_notify_event(struct dwc3 *dwc, unsigned event,
		dev_dbg(mdwc->dev, "DWC3_CONTROLLER_NOTIFY_OTG_EVENT received\n");
		if (dwc->enable_bus_suspend) {
			mdwc->suspend = dwc->b_suspend;
			queue_delayed_work(mdwc->dwc3_wq,
			queue_delayed_work(mdwc->dwc3_resume_wq,
					&mdwc->resume_work, 0);
		}
		break;
@@ -1950,7 +1955,7 @@ static int dwc3_msm_prepare_suspend(struct dwc3_msm *mdwc)
		/* Mark fatal error for host mode or USB bus suspend case */
		if (mdwc->in_host_mode || (mdwc->vbus_active
			&& mdwc->otg_state == OTG_STATE_B_SUSPEND)) {
			queue_delayed_work(mdwc->dwc3_wq,
			queue_delayed_work(mdwc->dwc3_resume_wq,
					&mdwc->resume_work, 0);
			return -EBUSY;
		}
@@ -2134,9 +2139,10 @@ static int dwc3_msm_suspend(struct dwc3_msm *mdwc)
	 * with DCP or during cable disconnect, we dont require wakeup
	 * using HS_PHY_IRQ or SS_PHY_IRQ. Hence enable wakeup only in
	 * case of host bus suspend and device bus suspend.
	 * In host mode if wakeup_sources are not used then don't enable.
	 */
	if ((mdwc->vbus_active && mdwc->otg_state == OTG_STATE_B_SUSPEND)
				|| mdwc->in_host_mode) {
		  || (mdwc->in_host_mode && !mdwc->no_wakeup_src_in_hostmode)) {
		enable_irq_wake(mdwc->hs_phy_irq);
		enable_irq(mdwc->hs_phy_irq);
		if (mdwc->ss_phy_irq) {
@@ -2312,11 +2318,12 @@ static void dwc3_ext_event_notify(struct dwc3_msm *mdwc)
		pm_runtime_set_autosuspend_delay(mdwc->dev, 1000);
		pm_runtime_use_autosuspend(mdwc->dev);
		if (!work_busy(&mdwc->sm_work.work))
			schedule_delayed_work(&mdwc->sm_work, 0);
			queue_delayed_work(mdwc->sm_usb_wq, &mdwc->sm_work, 0);
		return;
	}

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

static void dwc3_resume_work(struct work_struct *w)
@@ -2437,7 +2444,9 @@ static irqreturn_t msm_dwc3_pwr_irq(int irq, void *data)
	 * all other power events.
	 */
	if (atomic_read(&dwc->in_lpm)) {
		if (!mdwc->no_wakeup_src_in_hostmode || !mdwc->in_host_mode)
			pm_stay_awake(mdwc->dev);

		/* set this to call dwc3_msm_resume() */
		mdwc->resume_pending = true;
		return IRQ_WAKE_THREAD;
@@ -2512,7 +2521,7 @@ static int dwc3_msm_power_set_property_usb(struct power_supply *psy,
		if (dwc->is_drd) {
			dbg_event(0xFF, "stayID", 0);
			pm_stay_awake(mdwc->dev);
			queue_delayed_work(mdwc->dwc3_wq,
			queue_delayed_work(mdwc->dwc3_resume_wq,
					&mdwc->resume_work, 0);
		}
		break;
@@ -2537,7 +2546,7 @@ static int dwc3_msm_power_set_property_usb(struct power_supply *psy,
		 */
		if (mdwc->otg_state == OTG_STATE_UNDEFINED) {
			mdwc->vbus_active = val->intval;
			queue_delayed_work(mdwc->dwc3_wq,
			queue_delayed_work(mdwc->dwc3_resume_wq,
					&mdwc->resume_work, 0);
			break;
		}
@@ -2549,10 +2558,13 @@ static int dwc3_msm_power_set_property_usb(struct power_supply *psy,
		if (dwc->is_drd && !mdwc->in_restart) {
			dbg_event(0xFF, "Q RW (vbus)", val->intval);
			dbg_event(0xFF, "stayVbus", 0);
			/* Ignore !vbus on stop_host */
			if (mdwc->vbus_active || test_bit(ID, &mdwc->inputs)) {
				pm_stay_awake(mdwc->dev);
			queue_delayed_work(mdwc->dwc3_wq,
				queue_delayed_work(mdwc->dwc3_resume_wq,
					&mdwc->resume_work, 0);
			}
		}
		break;
	case POWER_SUPPLY_PROP_ONLINE:
		mdwc->online = val->intval;
@@ -2663,7 +2675,7 @@ static irqreturn_t dwc3_pmic_id_irq(int irq, void *data)
		mdwc->id_state = id;
		dbg_event(0xFF, "stayIDIRQ", 0);
		pm_stay_awake(mdwc->dev);
		queue_delayed_work(mdwc->dwc3_wq, &mdwc->resume_work, 0);
		queue_delayed_work(mdwc->dwc3_resume_wq, &mdwc->resume_work, 0);
	}

	return IRQ_HANDLED;
@@ -2823,9 +2835,15 @@ static int dwc3_msm_probe(struct platform_device *pdev)
	INIT_DELAYED_WORK(&mdwc->sm_work, dwc3_msm_otg_sm_work);
	INIT_DELAYED_WORK(&mdwc->perf_vote_work, dwc3_msm_otg_perf_vote_work);

	mdwc->dwc3_wq = alloc_ordered_workqueue("dwc3_wq", 0);
	if (!mdwc->dwc3_wq) {
		pr_err("%s: Unable to create workqueue dwc3_wq\n", __func__);
	mdwc->sm_usb_wq = create_freezable_workqueue("k_sm_usb");
	 if (!mdwc->sm_usb_wq) {
		pr_err("Failed to create workqueue for sm_usb");
		return -ENOMEM;
	}

	mdwc->dwc3_resume_wq = alloc_ordered_workqueue("dwc3_resume_wq", 0);
	if (!mdwc->dwc3_resume_wq) {
		pr_err("%s: Unable to create dwc3_resume_wq\n", __func__);
		return -ENOMEM;
	}

@@ -3049,6 +3067,11 @@ static int dwc3_msm_probe(struct platform_device *pdev)
	mdwc->disable_host_mode_pm = of_property_read_bool(node,
				"qcom,disable-host-mode-pm");

	mdwc->no_wakeup_src_in_hostmode = of_property_read_bool(node,
				"qcom,no-wakeup-src-in-hostmode");
	if (mdwc->no_wakeup_src_in_hostmode)
		dev_dbg(&pdev->dev, "dwc3 host not using wakeup source\n");

	mdwc->detect_dpdm_floating = of_property_read_bool(node,
				"qcom,detect-dpdm-floating");

@@ -3160,6 +3183,7 @@ static int dwc3_msm_probe(struct platform_device *pdev)

	if (!dwc->is_drd && host_mode) {
		dev_dbg(&pdev->dev, "DWC3 in host only mode\n");
		mdwc->host_only_mode = true;
		mdwc->id_state = DWC3_ID_GROUND;
		dwc3_ext_event_notify(mdwc);
	}
@@ -3497,9 +3521,12 @@ static int dwc3_otg_start_host(struct dwc3_msm *mdwc, int on)
		mdwc->in_host_mode = false;

		/* re-init core and OTG registers as block reset clears these */
		if (!mdwc->host_only_mode)
			dwc3_post_host_reset_core_init(dwc);

		pm_runtime_mark_last_busy(mdwc->dev);
		pm_runtime_put_sync_autosuspend(mdwc->dev);

		dbg_event(0xFF, "StopHost psync",
			atomic_read(&mdwc->dev->power.usage_count));
	}
@@ -3756,8 +3783,16 @@ static void dwc3_msm_otg_sm_work(struct work_struct *w)
			mdwc->otg_state = OTG_STATE_A_HOST;
			ret = dwc3_otg_start_host(mdwc, 1);
			pm_runtime_put_noidle(mdwc->dev);
			if (ret != -EPROBE_DEFER)
			/*
			 * call pm_relax to match with pm_stay_wake
			 * in probe for host mode.
			 */
			if (ret != -EPROBE_DEFER) {
				if (mdwc->no_wakeup_src_in_hostmode
						&& mdwc->in_host_mode)
					pm_relax(mdwc->dev);
				return;
			}
			/*
			 * regulator_get for VBUS may fail with -EPROBE_DEFER.
			 * Set the state as A_IDLE which will re-schedule
@@ -3958,22 +3993,33 @@ static void dwc3_msm_otg_sm_work(struct work_struct *w)
				mdwc->otg_state = OTG_STATE_A_IDLE;
				goto ret;
			}
			if (mdwc->no_wakeup_src_in_hostmode &&
						mdwc->in_host_mode)
				pm_wakeup_event(mdwc->dev,
						DWC3_WAKEUP_SRC_TIMEOUT);
		}
		break;

	case OTG_STATE_A_HOST:
		if (test_bit(ID, &mdwc->inputs) || mdwc->hc_died) {
			dbg_event(0xFF, "id || hc_died", 0);
		if (test_bit(ID, &mdwc->inputs) || mdwc->hc_died
				|| mdwc->stop_host) {
			dbg_event(0xFF, "id || hc_died || stop_host", 0);
			dev_dbg(mdwc->dev, "%s state id || hc_died\n", state);
			dwc3_otg_start_host(mdwc, 0);
			mdwc->otg_state = OTG_STATE_B_IDLE;
			mdwc->vbus_retry_count = 0;
			mdwc->hc_died = false;
			if (!mdwc->stop_host)
				work = 1;
			mdwc->stop_host = false;
		} else {
			dev_dbg(mdwc->dev, "still in a_host state. Resuming root hub.\n");
			dbg_event(0xFF, "XHCIResume", 0);
			if (dwc)
				pm_runtime_resume(&dwc->xhci->dev);
			if (mdwc->no_wakeup_src_in_hostmode)
				pm_wakeup_event(mdwc->dev,
						DWC3_WAKEUP_SRC_TIMEOUT);
		}
		break;

@@ -3983,12 +4029,58 @@ static void dwc3_msm_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;
}

static int dwc3_msm_pm_prepare(struct device *dev)
{
	struct dwc3_msm *mdwc = dev_get_drvdata(dev);
	struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3);
	struct usb_hcd	*hcd;
	struct xhci_hcd	*xhci;
	bool stop_ss_host = false;

	dev_dbg(dev, "dwc3-msm PM prepare,lpm:%u\n", atomic_read(&dwc->in_lpm));

	if (!mdwc->in_host_mode || !mdwc->no_wakeup_src_in_hostmode)
		return 0;

	hcd = dev_get_drvdata(&dwc->xhci->dev);
	xhci = hcd_to_xhci(hcd);
	/*
	 * If SS PHY is not suspended then we need to stop host before
	 * suspending ssphy
	 */
	flush_workqueue(mdwc->sm_usb_wq);
	if (atomic_read(&dwc->in_lpm)) {
		if (!(mdwc->lpm_flags & MDWC3_SS_PHY_SUSPEND))
			stop_ss_host = true;
	} else {
		stop_ss_host = dwc3_msm_is_superspeed(mdwc);
	}
	if (stop_ss_host) {
		dbg_event(0xFF, "pm_prep stop_host", mdwc->lpm_flags);
		mdwc->stop_host = true;
		queue_delayed_work(mdwc->dwc3_resume_wq, &mdwc->resume_work, 0);
		/* pm_relax will happen at the end of stop_host */
		pm_stay_awake(mdwc->dev);
		return -EBUSY;
	}
	/* If in lpm then prevent usb core to runtime_resume from pm_suspend */
	if (atomic_read(&dwc->in_lpm)) {
		hcd_to_bus(hcd)->skip_resume = true;
		hcd_to_bus(xhci->shared_hcd)->skip_resume = true;
	} else {
		hcd_to_bus(hcd)->skip_resume = false;
		hcd_to_bus(xhci->shared_hcd)->skip_resume = false;
	}

	return 0;
}

#ifdef CONFIG_PM_SLEEP
static int dwc3_msm_pm_suspend(struct device *dev)
{
@@ -3999,8 +4091,8 @@ static int dwc3_msm_pm_suspend(struct device *dev)
	dev_dbg(dev, "dwc3-msm PM suspend\n");
	dbg_event(0xFF, "PM Sus", 0);

	flush_workqueue(mdwc->dwc3_wq);
	if (!atomic_read(&dwc->in_lpm)) {
	flush_workqueue(mdwc->dwc3_resume_wq);
	if (!atomic_read(&dwc->in_lpm) && !mdwc->no_wakeup_src_in_hostmode) {
		dev_err(mdwc->dev, "Abort PM suspend!! (USB is outside LPM)\n");
		return -EBUSY;
	}
@@ -4024,11 +4116,15 @@ static int dwc3_msm_pm_resume(struct device *dev)
	dbg_event(0xFF, "PM Res", 0);

	/* flush to avoid race in read/write of pm_suspended */
	flush_workqueue(mdwc->dwc3_wq);
	flush_workqueue(mdwc->dwc3_resume_wq);
	atomic_set(&mdwc->pm_suspended, 0);

	/* Resume h/w in host mode as it may not be runtime suspended */
	if (mdwc->no_wakeup_src_in_hostmode && !test_bit(ID, &mdwc->inputs))
		dwc3_msm_resume(mdwc);

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

	dbg_event(0xFF, "PMResComplete", 0);
	return 0;
@@ -4071,6 +4167,7 @@ static int dwc3_msm_runtime_resume(struct device *dev)
#endif

static const struct dev_pm_ops dwc3_msm_dev_pm_ops = {
	.prepare =	dwc3_msm_pm_prepare,
	SET_SYSTEM_SLEEP_PM_OPS(dwc3_msm_pm_suspend, dwc3_msm_pm_resume)
	SET_RUNTIME_PM_OPS(dwc3_msm_runtime_suspend, dwc3_msm_runtime_resume,
				dwc3_msm_runtime_idle)