usb: dwc3: Add changes to support dual-role switching

This patch is a squash of several commits from msm-3.18 that
add support for dual-role switching (formerly known as "OTG")
to the DWC3 and XHCI platform drivers.

Based on the following commits:

usb: dwc3: Introduce OTG driver for dwc3
usb: dwc3-msm: Add support for LPM on cable disconnect
DWC3: Enable XHCI host in OTG mode
USB: dwc3: Add support for host bus suspend
usb: dwc3 / xhci_plat: Call xhci_suspend/resume when entering/exiting LPM
USB: dwc3: gadget: Implement gadget_vbus_draw() API
USB: gadget: dwc3: Fix composition switch issue during cable disconnect
usb: dwc3: notify gadget disconnect upon VBUS low
usb: dwc3: msm: Remove last of dwc3_otg
dwc3: Reset USB controller/PHY after psy connect indication at bootup
dwc3: Use otg_sm_work state machine for host and device only mode

Signed-off-by: Jack Pham <jackp@codeaurora.org>
This commit is contained in:
Jack Pham 2016-02-02 12:57:37 -08:00 committed by David Keitel
parent 92f418332c
commit 7b131410f5
5 changed files with 312 additions and 143 deletions

View file

@ -822,38 +822,16 @@ static int dwc3_core_get_phy(struct dwc3 *dwc)
static int dwc3_core_init_mode(struct dwc3 *dwc)
{
struct device *dev = dwc->dev;
int ret;
switch (dwc->dr_mode) {
case USB_DR_MODE_PERIPHERAL:
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
ret = dwc3_gadget_init(dwc);
if (ret) {
dev_err(dev, "failed to initialize gadget\n");
return ret;
}
break;
case USB_DR_MODE_HOST:
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);
ret = dwc3_host_init(dwc);
if (ret) {
dev_err(dev, "failed to initialize host\n");
return ret;
}
break;
case USB_DR_MODE_OTG:
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
ret = dwc3_host_init(dwc);
if (ret) {
dev_err(dev, "failed to initialize host\n");
return ret;
}
ret = dwc3_gadget_init(dwc);
if (ret) {
dev_err(dev, "failed to initialize gadget\n");
return ret;
}
break;
default:
dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode);
@ -882,6 +860,13 @@ static void dwc3_core_exit_mode(struct dwc3 *dwc)
}
}
/* XHCI reset, resets other CORE registers as well, re-init those */
void dwc3_post_host_reset_core_init(struct dwc3 *dwc)
{
dwc3_core_init(dwc);
dwc3_gadget_restart(dwc);
}
static void (*notify_event) (struct dwc3 *, unsigned);
void dwc3_set_notifier(void (*notify)(struct dwc3 *, unsigned))
{
@ -902,6 +887,69 @@ int dwc3_notify_event(struct dwc3 *dwc, unsigned event)
}
EXPORT_SYMBOL(dwc3_notify_event);
int dwc3_core_pre_init(struct dwc3 *dwc)
{
int ret;
dwc3_cache_hwparams(dwc);
ret = dwc3_phy_setup(dwc);
if (ret)
goto err0;
if (!dwc->ev_buffs) {
ret = dwc3_alloc_event_buffers(dwc, DWC3_EVENT_BUFFERS_SIZE);
if (ret) {
dev_err(dwc->dev, "failed to allocate event buffers\n");
ret = -ENOMEM;
goto err1;
}
}
ret = dwc3_core_init(dwc);
if (ret) {
dev_err(dwc->dev, "failed to initialize core\n");
goto err2;
}
ret = phy_power_on(dwc->usb2_generic_phy);
if (ret < 0)
goto err3;
ret = phy_power_on(dwc->usb3_generic_phy);
if (ret < 0)
goto err4;
ret = dwc3_event_buffers_setup(dwc);
if (ret) {
dev_err(dwc->dev, "failed to setup event buffers\n");
goto err5;
}
ret = dwc3_core_init_mode(dwc);
if (ret) {
dev_err(dwc->dev, "failed to set mode with dwc3 core\n");
goto err6;
}
return ret;
err6:
dwc3_event_buffers_cleanup(dwc);
err5:
phy_power_off(dwc->usb3_generic_phy);
err4:
phy_power_off(dwc->usb2_generic_phy);
err3:
dwc3_core_exit(dwc);
err2:
dwc3_free_event_buffers(dwc);
err1:
dwc3_ulpi_exit(dwc);
err0:
return ret;
}
#define DWC3_ALIGN_MASK (16 - 1)
static int dwc3_probe(struct platform_device *pdev)
@ -1086,12 +1134,6 @@ static int dwc3_probe(struct platform_device *pdev)
init_waitqueue_head(&dwc->wait_linkstate);
platform_set_drvdata(pdev, dwc);
dwc3_cache_hwparams(dwc);
ret = dwc3_phy_setup(dwc);
if (ret)
goto err0;
ret = dwc3_core_get_phy(dwc);
if (ret)
goto err0;
@ -1104,85 +1146,64 @@ static int dwc3_probe(struct platform_device *pdev)
dma_set_coherent_mask(dev, dev->parent->coherent_dma_mask);
}
pm_runtime_no_callbacks(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
pm_runtime_forbid(dev);
ret = dwc3_alloc_event_buffers(dwc, DWC3_EVENT_BUFFERS_SIZE);
if (ret) {
dev_err(dwc->dev, "failed to allocate event buffers\n");
ret = -ENOMEM;
goto err1;
}
if (IS_ENABLED(CONFIG_USB_DWC3_HOST))
dwc->dr_mode = USB_DR_MODE_HOST;
else if (IS_ENABLED(CONFIG_USB_DWC3_GADGET))
dwc->dr_mode = USB_DR_MODE_PERIPHERAL;
if (dwc->dr_mode == USB_DR_MODE_UNKNOWN)
if (dwc->dr_mode == USB_DR_MODE_UNKNOWN) {
dwc->dr_mode = USB_DR_MODE_OTG;
ret = dwc3_core_init(dwc);
if (ret) {
dev_err(dev, "failed to initialize core\n");
goto err1;
dwc->is_drd = true;
}
/* Adjust Frame Length */
dwc3_frame_length_adjustment(dwc, fladj);
usb_phy_set_suspend(dwc->usb2_phy, 0);
usb_phy_set_suspend(dwc->usb3_phy, 0);
ret = phy_power_on(dwc->usb2_generic_phy);
if (ret < 0)
goto err2;
/* Hardcode number of eps */
dwc->num_in_eps = 16;
dwc->num_out_eps = 16;
ret = phy_power_on(dwc->usb3_generic_phy);
if (ret < 0)
goto err3;
ret = dwc3_event_buffers_setup(dwc);
if (ret) {
dev_err(dwc->dev, "failed to setup event buffers\n");
goto err4;
if (dwc->dr_mode == USB_DR_MODE_OTG ||
dwc->dr_mode == USB_DR_MODE_PERIPHERAL) {
ret = dwc3_gadget_init(dwc);
if (ret) {
dev_err(dev, "failed to initialize gadget\n");
goto err0;
}
}
ret = dwc3_core_init_mode(dwc);
if (ret)
goto err5;
if (dwc->dr_mode == USB_DR_MODE_OTG ||
dwc->dr_mode == USB_DR_MODE_HOST) {
ret = dwc3_host_init(dwc);
if (ret) {
dev_err(dev, "failed to initialize host\n");
goto err_gadget;
}
}
ret = dwc3_debugfs_init(dwc);
if (ret) {
dev_err(dev, "failed to initialize debugfs\n");
goto err6;
goto err_host;
}
pm_runtime_allow(dev);
return 0;
err6:
dwc3_core_exit_mode(dwc);
err5:
dwc3_event_buffers_cleanup(dwc);
err4:
phy_power_off(dwc->usb3_generic_phy);
err3:
phy_power_off(dwc->usb2_generic_phy);
err2:
usb_phy_set_suspend(dwc->usb2_phy, 1);
usb_phy_set_suspend(dwc->usb3_phy, 1);
dwc3_core_exit(dwc);
err1:
dwc3_free_event_buffers(dwc);
dwc3_ulpi_exit(dwc);
err_host:
if (dwc->dr_mode == USB_DR_MODE_OTG ||
dwc->dr_mode == USB_DR_MODE_HOST)
dwc3_host_exit(dwc);
err_gadget:
if (dwc->dr_mode == USB_DR_MODE_OTG ||
dwc->dr_mode == USB_DR_MODE_PERIPHERAL)
dwc3_gadget_exit(dwc);
err0:
/*
* restore res->start back to its original value so that, in case the

View file

@ -716,7 +716,8 @@ struct dwc3_scratchpad_array {
#define DWC3_CORE_PM_RESUME_EVENT 4
#define DWC3_CONTROLLER_CONNDONE_EVENT 5
#define DWC3_CONTROLLER_NOTIFY_OTG_EVENT 6
#define DWC3_CONTROLLER_RESTART_USB_SESSION 10
#define DWC3_CONTROLLER_SET_CURRENT_DRAW_EVENT 7
#define DWC3_CONTROLLER_RESTART_USB_SESSION 8
#define MAX_INTR_STATS 10
/**
@ -807,6 +808,7 @@ struct dwc3_scratchpad_array {
* 1 - -3.5dB de-emphasis
* 2 - No de-emphasis
* 3 - Reserved
* @is_drd: device supports dual-role or not
* @err_evt_seen: previous event in queue was erratic error
* @usb3_u1u2_disable: if true, disable U1U2 low power modes in Superspeed mode.
* @in_lpm: indicates if controller is in low power mode (no clocks)
@ -818,6 +820,7 @@ struct dwc3_scratchpad_array {
* @bh_handled_evt_cnt: no. of events handled by tasklet per interrupt
* @bh_dbg_index: index for capturing bh_completion_time and bh_handled_evt_cnt
* @wait_linkstate: waitqueue for waiting LINK to move into required state
* @vbus_draw: current to be drawn from USB
*/
struct dwc3 {
struct usb_ctrlrequest *ctrl_req;
@ -965,6 +968,11 @@ struct dwc3 {
unsigned tx_de_emphasis_quirk:1;
unsigned tx_de_emphasis:2;
unsigned is_drd:1;
/* Indicate if the gadget was powered by the otg driver */
unsigned vbus_active:1;
/* Indicate if software connect was issued by the usb_gadget_driver */
unsigned softconnect:1;
unsigned nominal_elastic_buffer:1;
unsigned err_evt_seen:1;
unsigned usb3_u1u2_disable:1;
@ -977,6 +985,7 @@ struct dwc3 {
atomic_t in_lpm;
int tx_fifo_size;
bool b_suspend;
unsigned vbus_draw;
/* IRQ timing statistics */
int irq;
@ -1156,6 +1165,7 @@ static inline void dwc3_host_exit(struct dwc3 *dwc)
#if IS_ENABLED(CONFIG_USB_DWC3_GADGET) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
int dwc3_gadget_init(struct dwc3 *dwc);
void dwc3_gadget_exit(struct dwc3 *dwc);
void dwc3_gadget_restart(struct dwc3 *dwc);
int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode);
int dwc3_gadget_get_link_state(struct dwc3 *dwc);
int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state);
@ -1167,6 +1177,8 @@ static inline int dwc3_gadget_init(struct dwc3 *dwc)
{ return 0; }
static inline void dwc3_gadget_exit(struct dwc3 *dwc)
{ }
static inline void dwc3_gadget_restart(struct dwc3 *dwc)
{ }
static inline int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode)
{ return 0; }
static inline int dwc3_gadget_get_link_state(struct dwc3 *dwc)
@ -1212,6 +1224,8 @@ static inline void dwc3_ulpi_exit(struct dwc3 *dwc)
int dwc3_core_init(struct dwc3 *dwc);
int dwc3_core_pre_init(struct dwc3 *dwc);
void dwc3_post_host_reset_core_init(struct dwc3 *dwc);
int dwc3_event_buffers_setup(struct dwc3 *dwc);
void dwc3_usb3_phy_suspend(struct dwc3 *dwc, int suspend);

View file

@ -1808,6 +1808,16 @@ static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend)
return 0;
}
static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned mA)
{
struct dwc3 *dwc = gadget_to_dwc(g);
dwc->vbus_draw = mA;
dev_dbg(dwc->dev, "Notify controller from %s. mA = %d\n", __func__, mA);
dwc3_notify_event(dwc, DWC3_CONTROLLER_SET_CURRENT_DRAW_EVENT);
return 0;
}
static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on)
{
struct dwc3 *dwc = gadget_to_dwc(g);
@ -1816,6 +1826,16 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on)
is_on = !!is_on;
dwc->softconnect = is_on;
if ((dwc->is_drd && !dwc->vbus_active) || !dwc->gadget_driver) {
/*
* Need to wait for vbus_session(on) from otg driver or to
* the udc_start.
*/
return 0;
}
pm_runtime_get_sync(dwc->dev);
dbg_event(0xFF, "Pullup gsync",
atomic_read(&dwc->dev->power.usage_count));
@ -1875,37 +1895,57 @@ void dwc3_gadget_disable_irq(struct dwc3 *dwc)
static irqreturn_t dwc3_interrupt(int irq, void *_dwc);
static irqreturn_t dwc3_thread_interrupt(int irq, void *_dwc);
static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc);
static int dwc3_gadget_start(struct usb_gadget *g,
struct usb_gadget_driver *driver)
static int dwc3_gadget_vbus_session(struct usb_gadget *_gadget, int is_active)
{
struct dwc3 *dwc = gadget_to_dwc(g);
struct dwc3_ep *dep;
unsigned long flags;
int ret = 0;
int irq;
u32 reg;
struct dwc3 *dwc = gadget_to_dwc(_gadget);
unsigned long flags;
irq = platform_get_irq(to_platform_device(dwc->dev), 0);
dwc->irq = irq;
ret = request_irq(irq, dwc3_interrupt, IRQF_SHARED, "dwc3", dwc);
if (ret) {
dev_err(dwc->dev, "failed to request irq #%d --> %d\n",
irq, ret);
goto err0;
}
if (!dwc->is_drd)
return -EPERM;
is_active = !!is_active;
spin_lock_irqsave(&dwc->lock, flags);
if (dwc->gadget_driver) {
dev_err(dwc->dev, "%s is already bound to %s\n",
dwc->gadget.name,
dwc->gadget_driver->driver.name);
ret = -EBUSY;
goto err1;
/* Mark that the vbus was powered */
dwc->vbus_active = is_active;
/*
* Check if upper level usb_gadget_driver was already registerd with
* this udc controller driver (if dwc3_gadget_start was called)
*/
if (dwc->gadget_driver && dwc->softconnect) {
if (dwc->vbus_active) {
/*
* Both vbus was activated by otg and pullup was
* signaled by the gadget driver.
*/
dwc3_gadget_run_stop(dwc, 1, false);
} else {
dwc3_gadget_run_stop(dwc, 0, false);
}
}
dwc->gadget_driver = driver;
/*
* Clearing run/stop bit might occur before disconnect event is seen.
* Make sure to let gadget driver know in that case.
*/
if (!dwc->vbus_active) {
dev_dbg(dwc->dev, "calling disconnect from %s\n", __func__);
dwc3_gadget_disconnect_interrupt(dwc);
}
spin_unlock_irqrestore(&dwc->lock, flags);
return 0;
}
static int __dwc3_gadget_start(struct dwc3 *dwc)
{
struct dwc3_ep *dep;
int ret = 0;
u32 reg;
reg = dwc3_readl(dwc->regs, DWC3_DCFG);
reg &= ~(DWC3_DCFG_SPEED_MASK);
@ -1959,20 +1999,27 @@ static int dwc3_gadget_start(struct usb_gadget *g,
/* Start with SuperSpeed Default */
dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
dwc->delayed_status = false;
/* reinitialize physical ep0-1 */
dep = dwc->eps[0];
dep->flags = 0;
dep->endpoint.maxburst = 1;
ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false,
false);
if (ret) {
dev_err(dwc->dev, "failed to enable %s\n", dep->name);
goto err2;
return ret;
}
dep = dwc->eps[1];
dep->flags = 0;
dep->endpoint.maxburst = 1;
ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false,
false);
if (ret) {
dev_err(dwc->dev, "failed to enable %s\n", dep->name);
goto err3;
__dwc3_gadget_ep_disable(dwc->eps[0]);
return ret;
}
/* begin to receive SETUP packets */
@ -1981,19 +2028,55 @@ static int dwc3_gadget_start(struct usb_gadget *g,
dwc3_gadget_enable_irq(dwc);
return ret;
}
/* Required gadget re-initialization before switching to gadget in OTG mode */
void dwc3_gadget_restart(struct dwc3 *dwc)
{
__dwc3_gadget_start(dwc);
}
static int dwc3_gadget_start(struct usb_gadget *g,
struct usb_gadget_driver *driver)
{
struct dwc3 *dwc = gadget_to_dwc(g);
unsigned long flags;
int ret = 0;
int irq;
irq = platform_get_irq(to_platform_device(dwc->dev), 0);
dwc->irq = irq;
ret = request_irq(irq, dwc3_interrupt, IRQF_SHARED, "dwc3", dwc);
if (ret) {
dev_err(dwc->dev, "failed to request irq #%d --> %d\n",
irq, ret);
goto err0;
}
spin_lock_irqsave(&dwc->lock, flags);
if (dwc->gadget_driver) {
dev_err(dwc->dev, "%s is already bound to %s\n",
dwc->gadget.name,
dwc->gadget_driver->driver.name);
ret = -EBUSY;
goto err1;
}
dwc->gadget_driver = driver;
/*
* For DRD, this might get called by gadget driver during bootup
* even though host mode might be active. Don't actually perform
* device-specific initialization until device mode is activated.
* In that case dwc3_gadget_restart() will handle it.
*/
spin_unlock_irqrestore(&dwc->lock, flags);
return 0;
err3:
__dwc3_gadget_ep_disable(dwc->eps[0]);
err2:
dwc->gadget_driver = NULL;
err1:
spin_unlock_irqrestore(&dwc->lock, flags);
free_irq(irq, dwc);
err0:
@ -2037,6 +2120,8 @@ static const struct usb_gadget_ops dwc3_gadget_ops = {
.wakeup = dwc3_gadget_wakeup,
.func_wakeup = dwc_gadget_func_wakeup,
.set_selfpowered = dwc3_gadget_set_selfpowered,
.vbus_session = dwc3_gadget_vbus_session,
.vbus_draw = dwc3_gadget_vbus_draw,
.pullup = dwc3_gadget_pullup,
.udc_start = dwc3_gadget_start,
.udc_stop = dwc3_gadget_stop,
@ -2646,6 +2731,7 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc)
dwc3_notify_event(dwc, DWC3_CONTROLLER_NOTIFY_OTG_EVENT);
dwc3_usb3_phy_suspend(dwc, false);
usb_gadget_vbus_draw(&dwc->gadget, 0);
dwc3_reset_gadget(dwc);
dbg_event(0xFF, "BUS RST", 0);
@ -3114,6 +3200,12 @@ static void dwc3_process_event_entry(struct dwc3 *dwc,
{
trace_dwc3_event(event->raw);
/* skip event processing in absence of vbus */
if (!dwc->vbus_active) {
dbg_print_reg("SKIP EVT", event->raw);
return;
}
/* If run/stop is cleared don't process any more events */
if (!dwc->pullups_connected) {
dbg_print_reg("SKIP_EVT_PULLUP", event->raw);
@ -3399,6 +3491,13 @@ int dwc3_gadget_init(struct dwc3 *dwc)
goto err4;
}
if (!dwc->is_drd) {
pm_runtime_no_callbacks(&dwc->gadget.dev);
pm_runtime_set_active(&dwc->gadget.dev);
pm_runtime_enable(&dwc->gadget.dev);
pm_runtime_get(&dwc->gadget.dev);
}
return 0;
err4:
@ -3425,6 +3524,11 @@ err0:
void dwc3_gadget_exit(struct dwc3 *dwc)
{
if (dwc->is_drd) {
pm_runtime_put(&dwc->gadget.dev);
pm_runtime_disable(&dwc->gadget.dev);
}
usb_del_gadget_udc(&dwc->gadget);
dwc3_gadget_free_endpoints(dwc);

View file

@ -62,18 +62,9 @@ int dwc3_host_init(struct dwc3 *dwc)
phy_create_lookup(dwc->usb3_generic_phy, "usb3-phy",
dev_name(&xhci->dev));
ret = platform_device_add(xhci);
if (ret) {
dev_err(dwc->dev, "failed to register xHCI device\n");
goto err2;
}
/* Platform device gets added as part of state machine */
return 0;
err2:
phy_remove_lookup(dwc->usb2_generic_phy, "usb2-phy",
dev_name(&xhci->dev));
phy_remove_lookup(dwc->usb3_generic_phy, "usb3-phy",
dev_name(&xhci->dev));
err1:
platform_device_put(xhci);
return ret;
@ -85,5 +76,6 @@ void dwc3_host_exit(struct dwc3 *dwc)
dev_name(&dwc->xhci->dev));
phy_remove_lookup(dwc->usb3_generic_phy, "usb3-phy",
dev_name(&dwc->xhci->dev));
platform_device_unregister(dwc->xhci);
if (!dwc->is_drd)
platform_device_unregister(dwc->xhci);
}

View file

@ -134,6 +134,15 @@ static int xhci_plat_probe(struct platform_device *pdev)
goto put_hcd;
}
if (pdev->dev.parent)
pm_runtime_resume(pdev->dev.parent);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, 1000);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
if (of_device_is_compatible(pdev->dev.of_node,
"marvell,armada-375-xhci") ||
of_device_is_compatible(pdev->dev.of_node,
@ -182,6 +191,9 @@ static int xhci_plat_probe(struct platform_device *pdev)
if (ret)
goto dealloc_usb2_hcd;
pm_runtime_mark_last_busy(&pdev->dev);
pm_runtime_put_autosuspend(&pdev->dev);
return 0;
@ -210,6 +222,8 @@ static int xhci_plat_remove(struct platform_device *dev)
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct clk *clk = xhci->clk;
pm_runtime_disable(&dev->dev);
usb_remove_hcd(xhci->shared_hcd);
usb_phy_shutdown(hcd->usb_phy);
@ -223,33 +237,57 @@ static int xhci_plat_remove(struct platform_device *dev)
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int xhci_plat_suspend(struct device *dev)
#ifdef CONFIG_PM
static int xhci_plat_runtime_idle(struct device *dev)
{
struct usb_hcd *hcd = dev_get_drvdata(dev);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
/*
* xhci_suspend() needs `do_wakeup` to know whether host is allowed
* to do wakeup during suspend. Since xhci_plat_suspend is currently
* only designed for system suspend, device_may_wakeup() is enough
* to dertermine whether host is allowed to do wakeup. Need to
* reconsider this when xhci_plat_suspend enlarges its scope, e.g.,
* also applies to runtime suspend.
* When pm_runtime_put_autosuspend() is called on this device,
* after this idle callback returns the PM core will schedule the
* autosuspend if there is any remaining time until expiry. However,
* when reaching this point because the child_count becomes 0, the
* core does not honor autosuspend in that case and results in
* idle/suspend happening immediately. In order to have a delay
* before suspend we have to call pm_runtime_autosuspend() manually.
*/
return xhci_suspend(xhci, device_may_wakeup(dev));
pm_runtime_mark_last_busy(dev);
pm_runtime_autosuspend(dev);
return -EBUSY;
}
static int xhci_plat_resume(struct device *dev)
static int xhci_plat_runtime_suspend(struct device *dev)
{
struct usb_hcd *hcd = dev_get_drvdata(dev);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct usb_hcd *hcd = dev_get_drvdata(dev);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
return xhci_resume(xhci, 0);
if (!xhci)
return 0;
dev_dbg(dev, "xhci-plat runtime suspend\n");
return xhci_suspend(xhci, true);
}
static int xhci_plat_runtime_resume(struct device *dev)
{
struct usb_hcd *hcd = dev_get_drvdata(dev);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
int ret;
if (!xhci)
return 0;
dev_dbg(dev, "xhci-plat runtime resume\n");
ret = xhci_resume(xhci, false);
pm_runtime_mark_last_busy(dev);
return ret;
}
static const struct dev_pm_ops xhci_plat_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(xhci_plat_suspend, xhci_plat_resume)
SET_SYSTEM_SLEEP_PM_OPS(NULL, NULL)
SET_RUNTIME_PM_OPS(xhci_plat_runtime_suspend, xhci_plat_runtime_resume,
xhci_plat_runtime_idle)
};
#define DEV_PM_OPS (&xhci_plat_pm_ops)
#else