USB: Ignore xHCI Reset Device status.
[cascardo/linux.git] / drivers / usb / core / hub.c
index 1af04bd..3bc50fc 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;
@@ -736,7 +741,6 @@ static void hub_tt_work(struct work_struct *work)
        struct usb_hub          *hub =
                container_of(work, struct usb_hub, tt.clear_work);
        unsigned long           flags;
-       int                     limit = 100;
 
        spin_lock_irqsave (&hub->tt.lock, flags);
        while (!list_empty(&hub->tt.clear_list)) {
@@ -746,9 +750,6 @@ static void hub_tt_work(struct work_struct *work)
                const struct hc_driver  *drv;
                int                     status;
 
-               if (!hub->quiescing && --limit < 0)
-                       break;
-
                next = hub->tt.clear_list.next;
                clear = list_entry (next, struct usb_tt_clear, clear_list);
                list_del (&clear->clear_list);
@@ -1612,6 +1613,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 +1706,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;
 
@@ -2012,7 +2051,7 @@ static void show_string(struct usb_device *udev, char *id, char *string)
 {
        if (!string)
                return;
-       dev_printk(KERN_INFO, &udev->dev, "%s: %s\n", id, string);
+       dev_info(&udev->dev, "%s: %s\n", id, string);
 }
 
 static void announce_device(struct usb_device *udev)
@@ -2526,14 +2565,11 @@ static void hub_port_finish_reset(struct usb_hub *hub, int port1,
                        msleep(10 + 40);
                        update_devnum(udev, 0);
                        hcd = bus_to_hcd(udev->bus);
-                       if (hcd->driver->reset_device) {
-                               *status = hcd->driver->reset_device(hcd, udev);
-                               if (*status < 0) {
-                                       dev_err(&udev->dev, "Cannot reset "
-                                                       "HCD device state\n");
-                                       break;
-                               }
-                       }
+                       /* The xHC may think the device is already reset,
+                        * so ignore the status.
+                        */
+                       if (hcd->driver->reset_device)
+                               hcd->driver->reset_device(hcd, udev);
                }
                /* FALL THROUGH */
        case -ENOTCONN:
@@ -2541,16 +2577,16 @@ static void hub_port_finish_reset(struct usb_hub *hub, int port1,
                clear_port_feature(hub->hdev,
                                port1, USB_PORT_FEAT_C_RESET);
                /* FIXME need disconnect() for NOTATTACHED device */
-               if (warm) {
+               if (hub_is_superspeed(hub->hdev)) {
                        clear_port_feature(hub->hdev, port1,
                                        USB_PORT_FEAT_C_BH_PORT_RESET);
                        clear_port_feature(hub->hdev, port1,
                                        USB_PORT_FEAT_C_PORT_LINK_STATE);
-               } else {
+               }
+               if (!warm)
                        usb_set_device_state(udev, *status
                                        ? USB_STATE_NOTATTACHED
                                        : USB_STATE_DEFAULT);
-               }
                break;
        }
 }
@@ -2879,6 +2915,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 +3080,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 +3164,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 +3197,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++) {
@@ -3972,6 +4035,9 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
        if (retval)
                goto fail;
 
+       if (hcd->phy && !hdev->parent)
+               usb_phy_notify_connect(hcd->phy, udev->speed);
+
        /*
         * Some superspeed devices have finished the link training process
         * and attached to a superspeed hub port, but the device descriptor
@@ -4166,8 +4232,12 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
        }
 
        /* Disconnect any existing devices under this port */
-       if (udev)
+       if (udev) {
+               if (hcd->phy && !hdev->parent &&
+                               !(portstatus & USB_PORT_STAT_CONNECTION))
+                       usb_phy_notify_disconnect(hcd->phy, udev->speed);
                usb_disconnect(&hub->ports[port1 - 1]->child);
+       }
        clear_bit(port1, hub->change_bits);
 
        /* We can forget about a "removed" device when there's a physical
@@ -4190,13 +4260,6 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
                }
        }
 
-       if (hcd->phy && !hdev->parent) {
-               if (portstatus & USB_PORT_STAT_CONNECTION)
-                       usb_phy_notify_connect(hcd->phy, port1);
-               else
-                       usb_phy_notify_disconnect(hcd->phy, port1);
-       }
-
        /* Return now if debouncing failed or nothing is connected or
         * the device was "removed".
         */
@@ -4648,6 +4711,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,