OSDN Git Service

usb: dwc3-msm: Allow PM suspend in host mode irrespective of runtimePM
authorAjay Agarwal <ajaya@codeaurora.org>
Thu, 17 Aug 2017 13:17:19 +0000 (18:47 +0530)
committerAjay Agarwal <ajaya@codeaurora.org>
Wed, 27 Sep 2017 05:43:37 +0000 (11:13 +0530)
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.
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.
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 resumed
  after PM resume.

Change-Id: Ia1d450e6dd99e094e48caf9723a355b7870b54dd
Signed-off-by: Ajay Agarwal <ajaya@codeaurora.org>
Documentation/devicetree/bindings/usb/msm-ssusb.txt
drivers/usb/dwc3/dwc3-msm.c
drivers/usb/host/xhci-plat.c
drivers/usb/host/xhci.c
drivers/usb/host/xhci.h

index 47fad8a..5479233 100644 (file)
@@ -64,6 +64,8 @@ Optional properties :
          device provides both "USB" and "USB-HOST" events.
 - qcom,pm-qos-latency: This represents max tolerable CPU latency in microsecs,
        which is used as a vote by driver to get max performance in perf mode.
+- qcom,no-wakeup-src-in-hostmode: If present then driver doesn't use wakeup_source APIs
+  in host mode. This allows PM suspend to happen irrespective of runtimePM state of host.
 
 Sub nodes:
 - Sub node for "DWC3- USB3 controller".
index 5ad68df..010331b 100644 (file)
@@ -58,7 +58,7 @@
 
 /* time out to wait for USB cable status notification (in ms)*/
 #define SM_INIT_TIMEOUT 30000
-
+#define DWC3_WAKEUP_SRC_TIMEOUT 5000
 /* AHB2PHY register offsets */
 #define PERIPH_SS_AHB2PHY_TOP_CFG 0x10
 
@@ -216,6 +216,7 @@ struct dwc3_msm {
        struct notifier_block   usbdev_nb;
        bool                    hc_died;
        bool                    xhci_ss_compliance_enable;
+       bool                    no_wakeup_src_in_hostmode;
 
        struct extcon_dev       *extcon_vbus;
        struct extcon_dev       *extcon_id;
@@ -2338,6 +2339,7 @@ static void dwc3_ext_event_notify(struct dwc3_msm *mdwc)
                clear_bit(B_SUSPEND, &mdwc->inputs);
        }
 
+       pm_stay_awake(mdwc->dev);
        schedule_delayed_work(&mdwc->sm_work, 0);
 }
 
@@ -2626,6 +2628,7 @@ static int dwc3_msm_id_notifier(struct notifier_block *nb,
        if (mdwc->id_state != id) {
                mdwc->id_state = id;
                dbg_event(0xFF, "id_state", mdwc->id_state);
+               pm_stay_awake(mdwc->dev);
                queue_work(mdwc->dwc3_wq, &mdwc->resume_work);
        }
 
@@ -2688,6 +2691,7 @@ static int dwc3_msm_vbus_notifier(struct notifier_block *nb,
        mdwc->vbus_active = event;
        if (dwc->is_drd && !mdwc->in_restart) {
                dbg_event(0xFF, "Q RW (vbus)", mdwc->vbus_active);
+               pm_stay_awake(mdwc->dev);
                queue_work(mdwc->dwc3_wq, &mdwc->resume_work);
        }
 done:
@@ -3087,6 +3091,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");
+
        dwc3_set_notifier(&dwc3_msm_notify_event);
 
        /* Assumes dwc3 is the first DT child of dwc3-msm */
@@ -3879,12 +3888,14 @@ static void dwc3_otg_sm_work(struct work_struct *w)
                                mdwc->otg_state = OTG_STATE_A_IDLE;
                                goto ret;
                        }
+                       pm_wakeup_event(mdwc->dev, DWC3_WAKEUP_SRC_TIMEOUT);
                }
                break;
 
        case OTG_STATE_A_HOST:
                if (test_bit(ID, &mdwc->inputs) || mdwc->hc_died) {
-                       dev_dbg(mdwc->dev, "id || hc_died\n");
+                       dbg_event(0xFF, "id || hc_died", 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;
@@ -3895,6 +3906,7 @@ static void dwc3_otg_sm_work(struct work_struct *w)
                        dbg_event(0xFF, "XHCIResume", 0);
                        if (dwc)
                                pm_runtime_resume(&dwc->xhci->dev);
+                       pm_wakeup_event(mdwc->dev, DWC3_WAKEUP_SRC_TIMEOUT);
                }
                break;
 
@@ -3910,6 +3922,34 @@ 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;
+
+       dev_dbg(dev, "dwc3-msm PM prepare,lpm:%u\n", atomic_read(&dwc->in_lpm));
+       dbg_event(0xFF, "PM Prep", 0);
+       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);
+       flush_delayed_work(&mdwc->sm_work);
+
+       /* 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)
 {
@@ -3921,7 +3961,7 @@ static int dwc3_msm_pm_suspend(struct device *dev)
        dbg_event(0xFF, "PM Sus", 0);
 
        flush_workqueue(mdwc->dwc3_wq);
-       if (!atomic_read(&dwc->in_lpm)) {
+       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;
        }
@@ -3945,8 +3985,13 @@ static int dwc3_msm_pm_resume(struct device *dev)
        flush_workqueue(mdwc->dwc3_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_work(mdwc->dwc3_wq, &mdwc->resume_work);
+       if (mdwc->vbus_active || !mdwc->id_state)
+               queue_work(mdwc->dwc3_wq, &mdwc->resume_work);
 
        return 0;
 }
@@ -3983,6 +4028,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)
index 56a9cd6..c6998f0 100644 (file)
@@ -325,6 +325,34 @@ static int xhci_plat_remove(struct platform_device *dev)
        return 0;
 }
 
+#ifdef CONFIG_PM_SLEEP
+static int xhci_plat_suspend(struct device *dev)
+{
+       struct usb_hcd *hcd = dev_get_drvdata(dev);
+       struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+       if (!xhci)
+               return 0;
+
+       dev_dbg(dev, "xhci-plat PM suspend\n");
+
+       return xhci_suspend(xhci, true);
+}
+
+static int xhci_plat_resume(struct device *dev)
+{
+       struct usb_hcd *hcd = dev_get_drvdata(dev);
+       struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+       if (!xhci)
+               return 0;
+
+       dev_dbg(dev, "xhci-plat PM resume\n");
+
+       return xhci_resume(xhci, false);
+}
+#endif
+
 #ifdef CONFIG_PM
 static int xhci_plat_runtime_idle(struct device *dev)
 {
@@ -373,7 +401,7 @@ static int xhci_plat_runtime_resume(struct device *dev)
 }
 
 static const struct dev_pm_ops xhci_plat_pm_ops = {
-       SET_SYSTEM_SLEEP_PM_OPS(NULL, NULL)
+       SET_SYSTEM_SLEEP_PM_OPS(xhci_plat_suspend, xhci_plat_resume)
        SET_RUNTIME_PM_OPS(xhci_plat_runtime_suspend, xhci_plat_runtime_resume,
                           xhci_plat_runtime_idle)
 };
index aab1c79..641e028 100644 (file)
@@ -918,7 +918,7 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup)
        struct usb_hcd          *hcd = xhci_to_hcd(xhci);
        u32                     command;
 
-       if (!hcd->state)
+       if (!hcd->state || xhci->suspended)
                return 0;
 
        if (hcd->state != HC_STATE_SUSPENDED ||
@@ -988,6 +988,7 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup)
        /* step 5: remove core well power */
        /* synchronize irq when using MSI-X */
        xhci_msix_sync_irqs(xhci);
+       xhci->suspended = true;
 
        return rc;
 }
@@ -1007,7 +1008,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated)
        int                     retval = 0;
        bool                    comp_timer_running = false;
 
-       if (!hcd->state)
+       if (!hcd->state || !xhci->suspended)
                return 0;
 
        /* Wait a bit if either of the roothubs need to settle from the
@@ -1141,6 +1142,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated)
 
        /* Re-enable port polling. */
        xhci_dbg(xhci, "%s: starting port polling.\n", __func__);
+       xhci->suspended = false;
        set_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags);
        usb_hcd_poll_rh_status(xhci->shared_hcd);
        set_bit(HCD_FLAG_POLL_RH, &hcd->flags);
index 8fcec1b..7fc97d9 100644 (file)
@@ -1667,6 +1667,7 @@ struct xhci_hcd {
        /* Compliance Mode Recovery Data */
        struct timer_list       comp_mode_recovery_timer;
        u32                     port_status_u0;
+       bool                    suspended;
 /* Compliance Mode Timer Triggered every 2 seconds */
 #define COMP_MODE_RCVRY_MSECS 2000
 };