usb: dwc3-rockchip: delay suspend until gadget finish transfer
[firefly-linux-kernel-4.4.55.git] / drivers / usb / dwc3 / dwc3-rockchip.c
index 1bb95df6e7d6dd9f25bbb0bcedb52d5a2d40f065..b9f4d6f532794c71c705e5a3286d4aefbfaf5d93 100644 (file)
@@ -28,6 +28,7 @@
 #include <linux/pm_runtime.h>
 #include <linux/extcon.h>
 #include <linux/freezer.h>
+#include <linux/iopoll.h>
 #include <linux/reset.h>
 #include <linux/usb.h>
 #include <linux/usb/hcd.h>
 #include "../host/xhci.h"
 
 #define DWC3_ROCKCHIP_AUTOSUSPEND_DELAY  500 /* ms */
+#define PERIPHERAL_DISCONNECT_TIMEOUT 1000000 /* us */
 
 struct dwc3_rockchip {
        int                     num_clocks;
        bool                    connected;
+       bool                    skip_suspend;
        bool                    suspended;
        struct device           *dev;
        struct clk              **clks;
@@ -87,6 +90,7 @@ static void dwc3_rockchip_otg_extcon_evt_work(struct work_struct *work)
        struct xhci_hcd         *xhci;
        unsigned long           flags;
        int                     ret;
+       int                     val;
        u32                     reg, count;
 
        mutex_lock(&rockchip->lock);
@@ -115,12 +119,16 @@ static void dwc3_rockchip_otg_extcon_evt_work(struct work_struct *work)
                 * the dwc3 core code could at least in theory access chip
                 * registers while the reset is asserted, with unknown impact.
                 */
-               reset_control_assert(rockchip->otg_rst);
-               usleep_range(1000, 1200);
-               reset_control_deassert(rockchip->otg_rst);
-
-               pm_runtime_get_sync(rockchip->dev);
-               pm_runtime_get_sync(dwc->dev);
+               if (!rockchip->skip_suspend) {
+                       reset_control_assert(rockchip->otg_rst);
+                       usleep_range(1000, 1200);
+                       reset_control_deassert(rockchip->otg_rst);
+
+                       pm_runtime_get_sync(rockchip->dev);
+                       pm_runtime_get_sync(dwc->dev);
+               } else {
+                       rockchip->skip_suspend = false;
+               }
 
                spin_lock_irqsave(&dwc->lock, flags);
                dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
@@ -132,6 +140,12 @@ static void dwc3_rockchip_otg_extcon_evt_work(struct work_struct *work)
                if (rockchip->connected)
                        goto out;
 
+               if (rockchip->skip_suspend) {
+                       pm_runtime_put(dwc->dev);
+                       pm_runtime_put(rockchip->dev);
+                       rockchip->skip_suspend = false;
+               }
+
                /*
                 * If dr_mode is device only, never to
                 * set the mode to the host mode.
@@ -269,8 +283,23 @@ static void dwc3_rockchip_otg_extcon_evt_work(struct work_struct *work)
                        phy_power_off(dwc->usb3_generic_phy);
                }
 
-               pm_runtime_put_sync(rockchip->dev);
-               pm_runtime_put_sync_suspend(dwc->dev);
+               if (DWC3_GCTL_PRTCAP(reg) == DWC3_GCTL_PRTCAP_DEVICE) {
+                       ret = readx_poll_timeout(atomic_read,
+                                                &dwc->dev->power.usage_count,
+                                                val,
+                                                val < 2 && !dwc->connected,
+                                                1000,
+                                                PERIPHERAL_DISCONNECT_TIMEOUT);
+                       if (ret < 0) {
+                               rockchip->skip_suspend = true;
+                               dev_warn(rockchip->dev, "Peripheral disconnect timeout\n");
+                       }
+               }
+
+               if (!rockchip->skip_suspend) {
+                       pm_runtime_put_sync_suspend(dwc->dev);
+                       pm_runtime_put_sync_suspend(rockchip->dev);
+               }
 
                rockchip->connected = false;
                dev_info(rockchip->dev, "USB unconnected\n");