usb: dwc3: core: add power management support
authorFelipe Balbi <balbi@ti.com>
Mon, 30 Apr 2012 11:56:33 +0000 (14:56 +0300)
committerFelipe Balbi <balbi@ti.com>
Mon, 18 Mar 2013 09:17:01 +0000 (11:17 +0200)
Add support for basic power management on
the dwc3 driver. While there is still lots
to improve for full PM support, this minimal
patch will already make sure that we survive
suspend-to-ram and suspend-to-disk without
major issues.

Cc: Vikas C Sajjan <vikas.sajjan@linaro.org>
Tested-by: Vivek Gautam <gautam.vivek@samsung.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
drivers/usb/dwc3/core.c
drivers/usb/dwc3/core.h
drivers/usb/dwc3/gadget.c

index 47435086058b1db2b0dfc87fd6f77f80c7d528f5..75a9f88a5ad6882888a3c8cf696b1b8b97a174c1 100644 (file)
@@ -591,6 +591,123 @@ static int dwc3_remove(struct platform_device *pdev)
        return 0;
 }
 
+#ifdef CONFIG_PM
+static int dwc3_prepare(struct device *dev)
+{
+       struct dwc3     *dwc = dev_get_drvdata(dev);
+       unsigned long   flags;
+
+       spin_lock_irqsave(&dwc->lock, flags);
+
+       switch (dwc->mode) {
+       case DWC3_MODE_DEVICE:
+       case DWC3_MODE_DRD:
+               dwc3_gadget_prepare(dwc);
+               /* FALLTHROUGH */
+       case DWC3_MODE_HOST:
+       default:
+               dwc3_event_buffers_cleanup(dwc);
+               break;
+       }
+
+       spin_unlock_irqrestore(&dwc->lock, flags);
+
+       return 0;
+}
+
+static void dwc3_complete(struct device *dev)
+{
+       struct dwc3     *dwc = dev_get_drvdata(dev);
+       unsigned long   flags;
+
+       spin_lock_irqsave(&dwc->lock, flags);
+
+       switch (dwc->mode) {
+       case DWC3_MODE_DEVICE:
+       case DWC3_MODE_DRD:
+               dwc3_gadget_complete(dwc);
+               /* FALLTHROUGH */
+       case DWC3_MODE_HOST:
+       default:
+               dwc3_event_buffers_setup(dwc);
+               break;
+       }
+
+       spin_unlock_irqrestore(&dwc->lock, flags);
+}
+
+static int dwc3_suspend(struct device *dev)
+{
+       struct dwc3     *dwc = dev_get_drvdata(dev);
+       unsigned long   flags;
+
+       spin_lock_irqsave(&dwc->lock, flags);
+
+       switch (dwc->mode) {
+       case DWC3_MODE_DEVICE:
+       case DWC3_MODE_DRD:
+               dwc3_gadget_suspend(dwc);
+               /* FALLTHROUGH */
+       case DWC3_MODE_HOST:
+       default:
+               /* do nothing */
+               break;
+       }
+
+       dwc->gctl = dwc3_readl(dwc->regs, DWC3_GCTL);
+       spin_unlock_irqrestore(&dwc->lock, flags);
+
+       usb_phy_shutdown(dwc->usb3_phy);
+       usb_phy_shutdown(dwc->usb2_phy);
+
+       return 0;
+}
+
+static int dwc3_resume(struct device *dev)
+{
+       struct dwc3     *dwc = dev_get_drvdata(dev);
+       unsigned long   flags;
+
+       usb_phy_init(dwc->usb3_phy);
+       usb_phy_init(dwc->usb2_phy);
+       msleep(100);
+
+       spin_lock_irqsave(&dwc->lock, flags);
+
+       dwc3_writel(dwc->regs, DWC3_GCTL, dwc->gctl);
+
+       switch (dwc->mode) {
+       case DWC3_MODE_DEVICE:
+       case DWC3_MODE_DRD:
+               dwc3_gadget_resume(dwc);
+               /* FALLTHROUGH */
+       case DWC3_MODE_HOST:
+       default:
+               /* do nothing */
+               break;
+       }
+
+       spin_unlock_irqrestore(&dwc->lock, flags);
+
+       pm_runtime_disable(dev);
+       pm_runtime_set_active(dev);
+       pm_runtime_enable(dev);
+
+       return 0;
+}
+
+static const struct dev_pm_ops dwc3_dev_pm_ops = {
+       .prepare        = dwc3_prepare,
+       .complete       = dwc3_complete,
+
+       SET_SYSTEM_SLEEP_PM_OPS(dwc3_suspend, dwc3_resume)
+};
+
+#define DWC3_PM_OPS    &(dwc3_dev_pm_ops)
+#else
+#define DWC3_PM_OPS    NULL
+#endif
+
 #ifdef CONFIG_OF
 static const struct of_device_id of_dwc3_match[] = {
        {
@@ -607,6 +724,7 @@ static struct platform_driver dwc3_driver = {
        .driver         = {
                .name   = "dwc3",
                .of_match_table = of_match_ptr(of_dwc3_match),
+               .pm     = DWC3_PM_OPS,
        },
 };
 
index 35e4d3c01ed0328dc380ed41df07d5b555e2b072..52e48e21c82fb4293816bcc52fd64b19faf74e86 100644 (file)
@@ -626,6 +626,8 @@ struct dwc3_scratchpad_array {
  * @mode: mode of operation
  * @usb2_phy: pointer to USB2 PHY
  * @usb3_phy: pointer to USB3 PHY
+ * @dcfg: saved contents of DCFG register
+ * @gctl: saved contents of GCTL register
  * @is_selfpowered: true when we are selfpowered
  * @three_stage_setup: set if we perform a three phase setup
  * @ep0_bounced: true when we used bounce buffer
@@ -675,6 +677,10 @@ struct dwc3 {
        void __iomem            *regs;
        size_t                  regs_size;
 
+       /* used for suspend/resume */
+       u32                     dcfg;
+       u32                     gctl;
+
        u32                     num_event_buffers;
        u32                     u1u2;
        u32                     maximum_speed;
@@ -885,4 +891,31 @@ static inline void dwc3_gadget_exit(struct dwc3 *dwc)
 { }
 #endif
 
+/* power management interface */
+#if !IS_ENABLED(CONFIG_USB_DWC3_HOST)
+int dwc3_gadget_prepare(struct dwc3 *dwc);
+void dwc3_gadget_complete(struct dwc3 *dwc);
+int dwc3_gadget_suspend(struct dwc3 *dwc);
+int dwc3_gadget_resume(struct dwc3 *dwc);
+#else
+static inline int dwc3_gadget_prepare(struct dwc3 *dwc)
+{
+       return 0;
+}
+
+static inline void dwc3_gadget_complete(struct dwc3 *dwc)
+{
+}
+
+static inline int dwc3_gadget_suspend(struct dwc3 *dwc)
+{
+       return 0;
+}
+
+static inline int dwc3_gadget_resume(struct dwc3 *dwc)
+{
+       return 0;
+}
+#endif /* !IS_ENABLED(CONFIG_USB_DWC3_HOST) */
+
 #endif /* __DRIVERS_USB_DWC3_CORE_H */
index 04e4812fe2f9faf3d1328450af26eb694bc5b381..73b0b7fc77f13e0e90a01f7c53c10b75087061f6 100644 (file)
@@ -2594,6 +2594,8 @@ err0:
        return ret;
 }
 
+/* -------------------------------------------------------------------------- */
+
 void dwc3_gadget_exit(struct dwc3 *dwc)
 {
        usb_del_gadget_udc(&dwc->gadget);
@@ -2611,3 +2613,62 @@ void dwc3_gadget_exit(struct dwc3 *dwc)
        dma_free_coherent(dwc->dev, sizeof(*dwc->ctrl_req),
                        dwc->ctrl_req, dwc->ctrl_req_addr);
 }
+
+int dwc3_gadget_prepare(struct dwc3 *dwc)
+{
+       if (dwc->pullups_connected)
+               dwc3_gadget_disable_irq(dwc);
+
+       return 0;
+}
+
+void dwc3_gadget_complete(struct dwc3 *dwc)
+{
+       if (dwc->pullups_connected) {
+               dwc3_gadget_enable_irq(dwc);
+               dwc3_gadget_run_stop(dwc, true);
+       }
+}
+
+int dwc3_gadget_suspend(struct dwc3 *dwc)
+{
+       __dwc3_gadget_ep_disable(dwc->eps[0]);
+       __dwc3_gadget_ep_disable(dwc->eps[1]);
+
+       dwc->dcfg = dwc3_readl(dwc->regs, DWC3_DCFG);
+
+       return 0;
+}
+
+int dwc3_gadget_resume(struct dwc3 *dwc)
+{
+       struct dwc3_ep          *dep;
+       int                     ret;
+
+       /* Start with SuperSpeed Default */
+       dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
+
+       dep = dwc->eps[0];
+       ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false);
+       if (ret)
+               goto err0;
+
+       dep = dwc->eps[1];
+       ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false);
+       if (ret)
+               goto err1;
+
+       /* begin to receive SETUP packets */
+       dwc->ep0state = EP0_SETUP_PHASE;
+       dwc3_ep0_out_start(dwc);
+
+       dwc3_writel(dwc->regs, DWC3_DCFG, dwc->dcfg);
+
+       return 0;
+
+err1:
+       __dwc3_gadget_ep_disable(dwc->eps[0]);
+
+err0:
+       return ret;
+}