USB: Fix persist resume of some SS USB devices
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / drivers / usb / core / hub.c
index 63c217053668ae79f3dce71144678287abec9471..7e90d146d7dd8eaa7db65adf5c7ae886f4d72398 100644 (file)
@@ -3165,6 +3165,43 @@ static int finish_port_resume(struct usb_device *udev)
        return status;
 }
 
+/*
+ * There are some SS USB devices which take longer time for link training.
+ * XHCI specs 4.19.4 says that when Link training is successful, port
+ * sets CSC bit to 1. So if SW reads port status before successful link
+ * training, then it will not find device to be present.
+ * USB Analyzer log with such buggy devices show that in some cases
+ * device switch on the RX termination after long delay of host enabling
+ * the VBUS. In few other cases it has been seen that device fails to
+ * negotiate link training in first attempt. It has been
+ * reported till now that few devices take as long as 2000 ms to train
+ * the link after host enabling its VBUS and termination. Following
+ * routine implements a 2000 ms timeout for link training. If in a case
+ * link trains before timeout, loop will exit earlier.
+ *
+ * FIXME: If a device was connected before suspend, but was removed
+ * while system was asleep, then the loop in the following routine will
+ * only exit at timeout.
+ *
+ * This routine should only be called when persist is enabled for a SS
+ * device.
+ */
+static int wait_for_ss_port_enable(struct usb_device *udev,
+               struct usb_hub *hub, int *port1,
+               u16 *portchange, u16 *portstatus)
+{
+       int status = 0, delay_ms = 0;
+
+       while (delay_ms < 2000) {
+               if (status || *portstatus & USB_PORT_STAT_CONNECTION)
+                       break;
+               msleep(20);
+               delay_ms += 20;
+               status = hub_port_status(hub, *port1, portstatus, portchange);
+       }
+       return status;
+}
+
 /*
  * usb_port_resume - re-activate a suspended usb device's upstream port
  * @udev: device to re-activate, not a root hub
@@ -3267,6 +3304,10 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
 
        clear_bit(port1, hub->busy_bits);
 
+       if (udev->persist_enabled && hub_is_superspeed(hub->hdev))
+               status = wait_for_ss_port_enable(udev, hub, &port1, &portchange,
+                               &portstatus);
+
        status = check_port_resume_type(udev,
                        hub, port1, status, portchange, portstatus);
        if (status == 0)