|
@@ -26,6 +26,7 @@
|
|
|
#include <linux/module.h>
|
|
|
#include <linux/moduleparam.h>
|
|
|
#include <linux/slab.h>
|
|
|
+#include <linux/dmi.h>
|
|
|
|
|
|
#include "xhci.h"
|
|
|
|
|
@@ -398,6 +399,95 @@ static void xhci_msix_sync_irqs(struct xhci_hcd *xhci)
|
|
|
|
|
|
#endif
|
|
|
|
|
|
+static void compliance_mode_recovery(unsigned long arg)
|
|
|
+{
|
|
|
+ struct xhci_hcd *xhci;
|
|
|
+ struct usb_hcd *hcd;
|
|
|
+ u32 temp;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ xhci = (struct xhci_hcd *)arg;
|
|
|
+
|
|
|
+ for (i = 0; i < xhci->num_usb3_ports; i++) {
|
|
|
+ temp = xhci_readl(xhci, xhci->usb3_ports[i]);
|
|
|
+ if ((temp & PORT_PLS_MASK) == USB_SS_PORT_LS_COMP_MOD) {
|
|
|
+ /*
|
|
|
+ * Compliance Mode Detected. Letting USB Core
|
|
|
+ * handle the Warm Reset
|
|
|
+ */
|
|
|
+ xhci_dbg(xhci, "Compliance Mode Detected->Port %d!\n",
|
|
|
+ i + 1);
|
|
|
+ xhci_dbg(xhci, "Attempting Recovery routine!\n");
|
|
|
+ hcd = xhci->shared_hcd;
|
|
|
+
|
|
|
+ if (hcd->state == HC_STATE_SUSPENDED)
|
|
|
+ usb_hcd_resume_root_hub(hcd);
|
|
|
+
|
|
|
+ usb_hcd_poll_rh_status(hcd);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (xhci->port_status_u0 != ((1 << xhci->num_usb3_ports)-1))
|
|
|
+ mod_timer(&xhci->comp_mode_recovery_timer,
|
|
|
+ jiffies + msecs_to_jiffies(COMP_MODE_RCVRY_MSECS));
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Quirk to work around issue generated by the SN65LVPE502CP USB3.0 re-driver
|
|
|
+ * that causes ports behind that hardware to enter compliance mode sometimes.
|
|
|
+ * The quirk creates a timer that polls every 2 seconds the link state of
|
|
|
+ * each host controller's port and recovers it by issuing a Warm reset
|
|
|
+ * if Compliance mode is detected, otherwise the port will become "dead" (no
|
|
|
+ * device connections or disconnections will be detected anymore). Becasue no
|
|
|
+ * status event is generated when entering compliance mode (per xhci spec),
|
|
|
+ * this quirk is needed on systems that have the failing hardware installed.
|
|
|
+ */
|
|
|
+static void compliance_mode_recovery_timer_init(struct xhci_hcd *xhci)
|
|
|
+{
|
|
|
+ xhci->port_status_u0 = 0;
|
|
|
+ init_timer(&xhci->comp_mode_recovery_timer);
|
|
|
+
|
|
|
+ xhci->comp_mode_recovery_timer.data = (unsigned long) xhci;
|
|
|
+ xhci->comp_mode_recovery_timer.function = compliance_mode_recovery;
|
|
|
+ xhci->comp_mode_recovery_timer.expires = jiffies +
|
|
|
+ msecs_to_jiffies(COMP_MODE_RCVRY_MSECS);
|
|
|
+
|
|
|
+ set_timer_slack(&xhci->comp_mode_recovery_timer,
|
|
|
+ msecs_to_jiffies(COMP_MODE_RCVRY_MSECS));
|
|
|
+ add_timer(&xhci->comp_mode_recovery_timer);
|
|
|
+ xhci_dbg(xhci, "Compliance Mode Recovery Timer Initialized.\n");
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * This function identifies the systems that have installed the SN65LVPE502CP
|
|
|
+ * USB3.0 re-driver and that need the Compliance Mode Quirk.
|
|
|
+ * Systems:
|
|
|
+ * Vendor: Hewlett-Packard -> System Models: Z420, Z620 and Z820
|
|
|
+ */
|
|
|
+static bool compliance_mode_recovery_timer_quirk_check(void)
|
|
|
+{
|
|
|
+ const char *dmi_product_name, *dmi_sys_vendor;
|
|
|
+
|
|
|
+ dmi_product_name = dmi_get_system_info(DMI_PRODUCT_NAME);
|
|
|
+ dmi_sys_vendor = dmi_get_system_info(DMI_SYS_VENDOR);
|
|
|
+
|
|
|
+ if (!(strstr(dmi_sys_vendor, "Hewlett-Packard")))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ if (strstr(dmi_product_name, "Z420") ||
|
|
|
+ strstr(dmi_product_name, "Z620") ||
|
|
|
+ strstr(dmi_product_name, "Z820"))
|
|
|
+ return true;
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static int xhci_all_ports_seen_u0(struct xhci_hcd *xhci)
|
|
|
+{
|
|
|
+ return (xhci->port_status_u0 == ((1 << xhci->num_usb3_ports)-1));
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
/*
|
|
|
* Initialize memory for HCD and xHC (one-time init).
|
|
|
*
|
|
@@ -421,6 +511,12 @@ int xhci_init(struct usb_hcd *hcd)
|
|
|
retval = xhci_mem_init(xhci, GFP_KERNEL);
|
|
|
xhci_dbg(xhci, "Finished xhci_init\n");
|
|
|
|
|
|
+ /* Initializing Compliance Mode Recovery Data If Needed */
|
|
|
+ if (compliance_mode_recovery_timer_quirk_check()) {
|
|
|
+ xhci->quirks |= XHCI_COMP_MODE_QUIRK;
|
|
|
+ compliance_mode_recovery_timer_init(xhci);
|
|
|
+ }
|
|
|
+
|
|
|
return retval;
|
|
|
}
|
|
|
|
|
@@ -629,6 +725,11 @@ void xhci_stop(struct usb_hcd *hcd)
|
|
|
del_timer_sync(&xhci->event_ring_timer);
|
|
|
#endif
|
|
|
|
|
|
+ /* Deleting Compliance Mode Recovery Timer */
|
|
|
+ if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) &&
|
|
|
+ (!(xhci_all_ports_seen_u0(xhci))))
|
|
|
+ del_timer_sync(&xhci->comp_mode_recovery_timer);
|
|
|
+
|
|
|
if (xhci->quirks & XHCI_AMD_PLL_FIX)
|
|
|
usb_amd_dev_put();
|
|
|
|
|
@@ -806,6 +907,16 @@ int xhci_suspend(struct xhci_hcd *xhci)
|
|
|
}
|
|
|
spin_unlock_irq(&xhci->lock);
|
|
|
|
|
|
+ /*
|
|
|
+ * Deleting Compliance Mode Recovery Timer because the xHCI Host
|
|
|
+ * is about to be suspended.
|
|
|
+ */
|
|
|
+ if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) &&
|
|
|
+ (!(xhci_all_ports_seen_u0(xhci)))) {
|
|
|
+ del_timer_sync(&xhci->comp_mode_recovery_timer);
|
|
|
+ xhci_dbg(xhci, "Compliance Mode Recovery Timer Deleted!\n");
|
|
|
+ }
|
|
|
+
|
|
|
/* step 5: remove core well power */
|
|
|
/* synchronize irq when using MSI-X */
|
|
|
xhci_msix_sync_irqs(xhci);
|
|
@@ -938,6 +1049,16 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated)
|
|
|
usb_hcd_resume_root_hub(hcd);
|
|
|
usb_hcd_resume_root_hub(xhci->shared_hcd);
|
|
|
}
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If system is subject to the Quirk, Compliance Mode Timer needs to
|
|
|
+ * be re-initialized Always after a system resume. Ports are subject
|
|
|
+ * to suffer the Compliance Mode issue again. It doesn't matter if
|
|
|
+ * ports have entered previously to U0 before system's suspension.
|
|
|
+ */
|
|
|
+ if (xhci->quirks & XHCI_COMP_MODE_QUIRK)
|
|
|
+ compliance_mode_recovery_timer_init(xhci);
|
|
|
+
|
|
|
return retval;
|
|
|
}
|
|
|
#endif /* CONFIG_PM */
|