summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/dwc3/core.c118
-rw-r--r--drivers/usb/dwc3/core.h33
-rw-r--r--drivers/usb/dwc3/gadget.c61
3 files changed, 212 insertions, 0 deletions
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 47435086058b..75a9f88a5ad6 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -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,
},
};
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 35e4d3c01ed0..52e48e21c82f 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -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 */
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 04e4812fe2f9..73b0b7fc77f1 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -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;
+}