OSDN Git Service

usb: dwc3: Add changes to support dual-role switching
authorJack Pham <jackp@codeaurora.org>
Tue, 2 Feb 2016 20:57:37 +0000 (12:57 -0800)
committerDavid Keitel <dkeitel@codeaurora.org>
Tue, 22 Mar 2016 18:07:11 +0000 (11:07 -0700)
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>
drivers/usb/dwc3/core.c
drivers/usb/dwc3/core.h
drivers/usb/dwc3/gadget.c
drivers/usb/dwc3/host.c
drivers/usb/host/xhci-plat.c

index 67f058c..deef432 100644 (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;
-
-       ret = phy_power_on(dwc->usb3_generic_phy);
-       if (ret < 0)
-               goto err3;
+       /* Hardcode number of eps */
+       dwc->num_in_eps = 16;
+       dwc->num_out_eps = 16;
 
-       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
index 6886b14..2e1a2a3 100644 (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);
 
index 377282f..3e5b4ea 100644 (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);
 
-       spin_unlock_irqrestore(&dwc->lock, flags);
+       return ret;
+}
 
-       return 0;
+/* Required gadget re-initialization before switching to gadget in OTG mode */
+void dwc3_gadget_restart(struct dwc3 *dwc)
+{
+       __dwc3_gadget_start(dwc);
+}
 
-err3:
-       __dwc3_gadget_ep_disable(dwc->eps[0]);
+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;
 
-err2:
-       dwc->gadget_driver = NULL;
+       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;
+       }
 
-err1:
+       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;
 
+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);
index c679f63..e0db4d6 100644 (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);
 }
index 05647e6..7c3bdce 100644 (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);
+
+       if (!xhci)
+               return 0;
+
+       dev_dbg(dev, "xhci-plat runtime suspend\n");
 
-       return xhci_resume(xhci, 0);
+       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