Commit 7415f17c authored by Felipe Balbi's avatar Felipe Balbi

usb: dwc3: core: add power management support

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: default avatarVivek Gautam <gautam.vivek@samsung.com>
Signed-off-by: default avatarFelipe Balbi <balbi@ti.com>
parent 9fcb3bd8
...@@ -591,6 +591,123 @@ static int dwc3_remove(struct platform_device *pdev) ...@@ -591,6 +591,123 @@ static int dwc3_remove(struct platform_device *pdev)
return 0; 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 #ifdef CONFIG_OF
static const struct of_device_id of_dwc3_match[] = { static const struct of_device_id of_dwc3_match[] = {
{ {
...@@ -607,6 +724,7 @@ static struct platform_driver dwc3_driver = { ...@@ -607,6 +724,7 @@ static struct platform_driver dwc3_driver = {
.driver = { .driver = {
.name = "dwc3", .name = "dwc3",
.of_match_table = of_match_ptr(of_dwc3_match), .of_match_table = of_match_ptr(of_dwc3_match),
.pm = DWC3_PM_OPS,
}, },
}; };
......
...@@ -626,6 +626,8 @@ struct dwc3_scratchpad_array { ...@@ -626,6 +626,8 @@ struct dwc3_scratchpad_array {
* @mode: mode of operation * @mode: mode of operation
* @usb2_phy: pointer to USB2 PHY * @usb2_phy: pointer to USB2 PHY
* @usb3_phy: pointer to USB3 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 * @is_selfpowered: true when we are selfpowered
* @three_stage_setup: set if we perform a three phase setup * @three_stage_setup: set if we perform a three phase setup
* @ep0_bounced: true when we used bounce buffer * @ep0_bounced: true when we used bounce buffer
...@@ -675,6 +677,10 @@ struct dwc3 { ...@@ -675,6 +677,10 @@ struct dwc3 {
void __iomem *regs; void __iomem *regs;
size_t regs_size; size_t regs_size;
/* used for suspend/resume */
u32 dcfg;
u32 gctl;
u32 num_event_buffers; u32 num_event_buffers;
u32 u1u2; u32 u1u2;
u32 maximum_speed; u32 maximum_speed;
...@@ -885,4 +891,31 @@ static inline void dwc3_gadget_exit(struct dwc3 *dwc) ...@@ -885,4 +891,31 @@ static inline void dwc3_gadget_exit(struct dwc3 *dwc)
{ } { }
#endif #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 */ #endif /* __DRIVERS_USB_DWC3_CORE_H */
...@@ -2594,6 +2594,8 @@ int dwc3_gadget_init(struct dwc3 *dwc) ...@@ -2594,6 +2594,8 @@ int dwc3_gadget_init(struct dwc3 *dwc)
return ret; return ret;
} }
/* -------------------------------------------------------------------------- */
void dwc3_gadget_exit(struct dwc3 *dwc) void dwc3_gadget_exit(struct dwc3 *dwc)
{ {
usb_del_gadget_udc(&dwc->gadget); usb_del_gadget_udc(&dwc->gadget);
...@@ -2611,3 +2613,62 @@ void dwc3_gadget_exit(struct dwc3 *dwc) ...@@ -2611,3 +2613,62 @@ void dwc3_gadget_exit(struct dwc3 *dwc)
dma_free_coherent(dwc->dev, sizeof(*dwc->ctrl_req), dma_free_coherent(dwc->dev, sizeof(*dwc->ctrl_req),
dwc->ctrl_req, dwc->ctrl_req_addr); 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;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment