[PATCH] usb: dwc3: Support the dwc3 host suspend/resume

From: Baolin Wang
Date: Fri Jul 21 2017 - 02:59:18 EST


For some mobile devices with strict power management, we also want to
suspend the host when the slave was detached for power saving. Thus
adding the host suspend/resume functions to support this requirement.

We will issue the pm_suspend_ignore_children() for the dwc3 device,
since we will resume the child device (xHCI device) in runtime resume
callback (dwc3_host_resume()) of dwc3 device, now the dwc3 device's
runtime state is not RPM_ACTIVE, which will block to resume its
child device (xHCI device). Add ignore children flag can avoid this
situation.

Signed-off-by: Baolin Wang <baolin.wang@xxxxxxxxxx>
---
drivers/usb/dwc3/core.c | 26 +++++++++++++++++++++++++-
drivers/usb/dwc3/core.h | 7 +++++++
drivers/usb/dwc3/host.c | 15 +++++++++++++++
3 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 326b302..2be7ddc 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -1193,6 +1193,7 @@ static int dwc3_probe(struct platform_device *pdev)
pm_runtime_use_autosuspend(dev);
pm_runtime_set_autosuspend_delay(dev, DWC3_DEFAULT_AUTOSUSPEND_DELAY);
pm_runtime_enable(dev);
+ pm_suspend_ignore_children(dev, true);
ret = pm_runtime_get_sync(dev);
if (ret < 0)
goto err1;
@@ -1292,15 +1293,27 @@ static int dwc3_remove(struct platform_device *pdev)
static int dwc3_suspend_common(struct dwc3 *dwc)
{
unsigned long flags;
+ int ret;

switch (dwc->dr_mode) {
case USB_DR_MODE_PERIPHERAL:
+ spin_lock_irqsave(&dwc->lock, flags);
+ dwc3_gadget_suspend(dwc);
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ break;
case USB_DR_MODE_OTG:
+ ret = dwc3_host_suspend(dwc);
+ if (ret)
+ return ret;
+
spin_lock_irqsave(&dwc->lock, flags);
dwc3_gadget_suspend(dwc);
spin_unlock_irqrestore(&dwc->lock, flags);
break;
case USB_DR_MODE_HOST:
+ ret = dwc3_host_suspend(dwc);
+ if (ret)
+ return ret;
default:
/* do nothing */
break;
@@ -1322,12 +1335,23 @@ static int dwc3_resume_common(struct dwc3 *dwc)

switch (dwc->dr_mode) {
case USB_DR_MODE_PERIPHERAL:
+ spin_lock_irqsave(&dwc->lock, flags);
+ dwc3_gadget_resume(dwc);
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ break;
case USB_DR_MODE_OTG:
+ ret = dwc3_host_resume(dwc);
+ if (ret)
+ return ret;
+
spin_lock_irqsave(&dwc->lock, flags);
dwc3_gadget_resume(dwc);
spin_unlock_irqrestore(&dwc->lock, flags);
- /* FALLTHROUGH */
+ break;
case USB_DR_MODE_HOST:
+ ret = dwc3_host_resume(dwc);
+ if (ret)
+ return ret;
default:
/* do nothing */
break;
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index ea910ac..9b5a4eb 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -1193,11 +1193,18 @@ static inline bool dwc3_is_usb31(struct dwc3 *dwc)
#if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
int dwc3_host_init(struct dwc3 *dwc);
void dwc3_host_exit(struct dwc3 *dwc);
+int dwc3_host_suspend(struct dwc3 *dwc);
+int dwc3_host_resume(struct dwc3 *dwc);
#else
static inline int dwc3_host_init(struct dwc3 *dwc)
{ return 0; }
static inline void dwc3_host_exit(struct dwc3 *dwc)
{ }
+
+static inline int dwc3_host_suspend(struct dwc3 *dwc)
+{ return 0; }
+static inline int dwc3_host_resume(struct dwc3 *dwc)
+{ return 0; }
#endif

#if IS_ENABLED(CONFIG_USB_DWC3_GADGET) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index 76f0b0d..3fa4414 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -16,6 +16,7 @@
*/

#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>

#include "core.h"

@@ -151,3 +152,17 @@ void dwc3_host_exit(struct dwc3 *dwc)
dev_name(dwc->dev));
platform_device_unregister(dwc->xhci);
}
+
+int dwc3_host_suspend(struct dwc3 *dwc)
+{
+ struct device *xhci = &dwc->xhci->dev;
+
+ return pm_runtime_suspend(xhci);
+}
+
+int dwc3_host_resume(struct dwc3 *dwc)
+{
+ struct device *xhci = &dwc->xhci->dev;
+
+ return pm_runtime_resume(xhci);
+}
--
1.7.9.5