Merge 3.7-rc3 into usb-next.
[firefly-linux-kernel-4.4.55.git] / drivers / usb / core / hub.c
index 1af04bdeaf0c1884487ed830bcee7dd32ee720b6..5b131b6477db060d37fb9c347736b1aad5a3f415 100644 (file)
@@ -39,6 +39,9 @@
 #endif
 #endif
 
+#define USB_VENDOR_GENESYS_LOGIC               0x05e3
+#define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND       0x01
+
 struct usb_port {
        struct usb_device *child;
        struct device dev;
@@ -86,6 +89,8 @@ struct usb_hub {
        unsigned                quiescing:1;
        unsigned                disconnected:1;
 
+       unsigned                quirk_check_port_auto_suspend:1;
+
        unsigned                has_indicators:1;
        u8                      indicator[USB_MAXCHILDREN];
        struct delayed_work     leds;
@@ -1612,6 +1617,41 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
        desc = intf->cur_altsetting;
        hdev = interface_to_usbdev(intf);
 
+       /*
+        * Set default autosuspend delay as 0 to speedup bus suspend,
+        * based on the below considerations:
+        *
+        * - Unlike other drivers, the hub driver does not rely on the
+        *   autosuspend delay to provide enough time to handle a wakeup
+        *   event, and the submitted status URB is just to check future
+        *   change on hub downstream ports, so it is safe to do it.
+        *
+        * - The patch might cause one or more auto supend/resume for
+        *   below very rare devices when they are plugged into hub
+        *   first time:
+        *
+        *      devices having trouble initializing, and disconnect
+        *      themselves from the bus and then reconnect a second
+        *      or so later
+        *
+        *      devices just for downloading firmware, and disconnects
+        *      themselves after completing it
+        *
+        *   For these quite rare devices, their drivers may change the
+        *   autosuspend delay of their parent hub in the probe() to one
+        *   appropriate value to avoid the subtle problem if someone
+        *   does care it.
+        *
+        * - The patch may cause one or more auto suspend/resume on
+        *   hub during running 'lsusb', but it is probably too
+        *   infrequent to worry about.
+        *
+        * - Change autosuspend delay of hub can avoid unnecessary auto
+        *   suspend timer for hub, also may decrease power consumption
+        *   of USB bus.
+        */
+       pm_runtime_set_autosuspend_delay(&hdev->dev, 0);
+
        /* Hubs have proper suspend/resume support. */
        usb_enable_autosuspend(hdev);
 
@@ -1670,6 +1710,9 @@ descriptor_error:
        if (hdev->speed == USB_SPEED_HIGH)
                highspeed_hubs++;
 
+       if (id->driver_info & HUB_QUIRK_CHECK_PORT_AUTOSUSPEND)
+               hub->quirk_check_port_auto_suspend = 1;
+
        if (hub_configure(hub, endpoint) >= 0)
                return 0;
 
@@ -2879,6 +2922,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
                                (PMSG_IS_AUTO(msg) ? "auto-" : ""),
                                udev->do_remote_wakeup);
                usb_set_device_state(udev, USB_STATE_SUSPENDED);
+               udev->port_is_suspended = 1;
                msleep(10);
        }
        usb_mark_last_busy(hub->hdev);
@@ -3043,6 +3087,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
 
  SuspendCleared:
        if (status == 0) {
+               udev->port_is_suspended = 0;
                if (hub_is_superspeed(hub->hdev)) {
                        if (portchange & USB_PORT_STAT_C_LINK_STATE)
                                clear_port_feature(hub->hdev, port1,
@@ -3126,6 +3171,21 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
 
 #endif
 
+static int check_ports_changed(struct usb_hub *hub)
+{
+       int port1;
+
+       for (port1 = 1; port1 <= hub->hdev->maxchild; ++port1) {
+               u16 portstatus, portchange;
+               int status;
+
+               status = hub_port_status(hub, port1, &portstatus, &portchange);
+               if (!status && portchange)
+                       return 1;
+       }
+       return 0;
+}
+
 static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
 {
        struct usb_hub          *hub = usb_get_intfdata (intf);
@@ -3144,6 +3204,16 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
                                return -EBUSY;
                }
        }
+
+       if (hdev->do_remote_wakeup && hub->quirk_check_port_auto_suspend) {
+               /* check if there are changes pending on hub ports */
+               if (check_ports_changed(hub)) {
+                       if (PMSG_IS_AUTO(msg))
+                               return -EBUSY;
+                       pm_wakeup_event(&hdev->dev, 2000);
+               }
+       }
+
        if (hub_is_superspeed(hdev) && hdev->do_remote_wakeup) {
                /* Enable hub to send remote wakeup for all ports. */
                for (port1 = 1; port1 <= hdev->maxchild; port1++) {
@@ -4648,6 +4718,11 @@ static int hub_thread(void *__unused)
 }
 
 static const struct usb_device_id hub_id_table[] = {
+    { .match_flags = USB_DEVICE_ID_MATCH_VENDOR
+                  | USB_DEVICE_ID_MATCH_INT_CLASS,
+      .idVendor = USB_VENDOR_GENESYS_LOGIC,
+      .bInterfaceClass = USB_CLASS_HUB,
+      .driver_info = HUB_QUIRK_CHECK_PORT_AUTOSUSPEND},
     { .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS,
       .bDeviceClass = USB_CLASS_HUB},
     { .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,