diff options
-rw-r--r-- | drivers/usb/host/xhci-hub.c | 42 | ||||
-rw-r--r-- | drivers/usb/host/xhci.c | 121 | ||||
-rw-r--r-- | drivers/usb/host/xhci.h | 6 |
3 files changed, 169 insertions, 0 deletions
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 74bfc868b7ad..d5eb357aa5c4 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c | |||
@@ -493,11 +493,48 @@ static void xhci_hub_report_link_state(u32 *status, u32 status_reg) | |||
493 | * when this bit is set. | 493 | * when this bit is set. |
494 | */ | 494 | */ |
495 | pls |= USB_PORT_STAT_CONNECTION; | 495 | pls |= USB_PORT_STAT_CONNECTION; |
496 | } else { | ||
497 | /* | ||
498 | * If CAS bit isn't set but the Port is already at | ||
499 | * Compliance Mode, fake a connection so the USB core | ||
500 | * notices the Compliance state and resets the port. | ||
501 | * This resolves an issue generated by the SN65LVPE502CP | ||
502 | * in which sometimes the port enters compliance mode | ||
503 | * caused by a delay on the host-device negotiation. | ||
504 | */ | ||
505 | if (pls == USB_SS_PORT_LS_COMP_MOD) | ||
506 | pls |= USB_PORT_STAT_CONNECTION; | ||
496 | } | 507 | } |
508 | |||
497 | /* update status field */ | 509 | /* update status field */ |
498 | *status |= pls; | 510 | *status |= pls; |
499 | } | 511 | } |
500 | 512 | ||
513 | /* | ||
514 | * Function for Compliance Mode Quirk. | ||
515 | * | ||
516 | * This Function verifies if all xhc USB3 ports have entered U0, if so, | ||
517 | * the compliance mode timer is deleted. A port won't enter | ||
518 | * compliance mode if it has previously entered U0. | ||
519 | */ | ||
520 | void xhci_del_comp_mod_timer(struct xhci_hcd *xhci, u32 status, u16 wIndex) | ||
521 | { | ||
522 | u32 all_ports_seen_u0 = ((1 << xhci->num_usb3_ports)-1); | ||
523 | bool port_in_u0 = ((status & PORT_PLS_MASK) == XDEV_U0); | ||
524 | |||
525 | if (!(xhci->quirks & XHCI_COMP_MODE_QUIRK)) | ||
526 | return; | ||
527 | |||
528 | if ((xhci->port_status_u0 != all_ports_seen_u0) && port_in_u0) { | ||
529 | xhci->port_status_u0 |= 1 << wIndex; | ||
530 | if (xhci->port_status_u0 == all_ports_seen_u0) { | ||
531 | del_timer_sync(&xhci->comp_mode_recovery_timer); | ||
532 | xhci_dbg(xhci, "All USB3 ports have entered U0 already!\n"); | ||
533 | xhci_dbg(xhci, "Compliance Mode Recovery Timer Deleted.\n"); | ||
534 | } | ||
535 | } | ||
536 | } | ||
537 | |||
501 | int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, | 538 | int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, |
502 | u16 wIndex, char *buf, u16 wLength) | 539 | u16 wIndex, char *buf, u16 wLength) |
503 | { | 540 | { |
@@ -651,6 +688,11 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, | |||
651 | /* Update Port Link State for super speed ports*/ | 688 | /* Update Port Link State for super speed ports*/ |
652 | if (hcd->speed == HCD_USB3) { | 689 | if (hcd->speed == HCD_USB3) { |
653 | xhci_hub_report_link_state(&status, temp); | 690 | xhci_hub_report_link_state(&status, temp); |
691 | /* | ||
692 | * Verify if all USB3 Ports Have entered U0 already. | ||
693 | * Delete Compliance Mode Timer if so. | ||
694 | */ | ||
695 | xhci_del_comp_mod_timer(xhci, temp, wIndex); | ||
654 | } | 696 | } |
655 | if (bus_state->port_c_suspend & (1 << wIndex)) | 697 | if (bus_state->port_c_suspend & (1 << wIndex)) |
656 | status |= 1 << USB_PORT_FEAT_C_SUSPEND; | 698 | status |= 1 << USB_PORT_FEAT_C_SUSPEND; |
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 617b0a77b2b4..6ece0ed288d4 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c | |||
@@ -26,6 +26,7 @@ | |||
26 | #include <linux/module.h> | 26 | #include <linux/module.h> |
27 | #include <linux/moduleparam.h> | 27 | #include <linux/moduleparam.h> |
28 | #include <linux/slab.h> | 28 | #include <linux/slab.h> |
29 | #include <linux/dmi.h> | ||
29 | 30 | ||
30 | #include "xhci.h" | 31 | #include "xhci.h" |
31 | 32 | ||
@@ -398,6 +399,95 @@ static void xhci_msix_sync_irqs(struct xhci_hcd *xhci) | |||
398 | 399 | ||
399 | #endif | 400 | #endif |
400 | 401 | ||
402 | static void compliance_mode_recovery(unsigned long arg) | ||
403 | { | ||
404 | struct xhci_hcd *xhci; | ||
405 | struct usb_hcd *hcd; | ||
406 | u32 temp; | ||
407 | int i; | ||
408 | |||
409 | xhci = (struct xhci_hcd *)arg; | ||
410 | |||
411 | for (i = 0; i < xhci->num_usb3_ports; i++) { | ||
412 | temp = xhci_readl(xhci, xhci->usb3_ports[i]); | ||
413 | if ((temp & PORT_PLS_MASK) == USB_SS_PORT_LS_COMP_MOD) { | ||
414 | /* | ||
415 | * Compliance Mode Detected. Letting USB Core | ||
416 | * handle the Warm Reset | ||
417 | */ | ||
418 | xhci_dbg(xhci, "Compliance Mode Detected->Port %d!\n", | ||
419 | i + 1); | ||
420 | xhci_dbg(xhci, "Attempting Recovery routine!\n"); | ||
421 | hcd = xhci->shared_hcd; | ||
422 | |||
423 | if (hcd->state == HC_STATE_SUSPENDED) | ||
424 | usb_hcd_resume_root_hub(hcd); | ||
425 | |||
426 | usb_hcd_poll_rh_status(hcd); | ||
427 | } | ||
428 | } | ||
429 | |||
430 | if (xhci->port_status_u0 != ((1 << xhci->num_usb3_ports)-1)) | ||
431 | mod_timer(&xhci->comp_mode_recovery_timer, | ||
432 | jiffies + msecs_to_jiffies(COMP_MODE_RCVRY_MSECS)); | ||
433 | } | ||
434 | |||
435 | /* | ||
436 | * Quirk to work around issue generated by the SN65LVPE502CP USB3.0 re-driver | ||
437 | * that causes ports behind that hardware to enter compliance mode sometimes. | ||
438 | * The quirk creates a timer that polls every 2 seconds the link state of | ||
439 | * each host controller's port and recovers it by issuing a Warm reset | ||
440 | * if Compliance mode is detected, otherwise the port will become "dead" (no | ||
441 | * device connections or disconnections will be detected anymore). Becasue no | ||
442 | * status event is generated when entering compliance mode (per xhci spec), | ||
443 | * this quirk is needed on systems that have the failing hardware installed. | ||
444 | */ | ||
445 | static void compliance_mode_recovery_timer_init(struct xhci_hcd *xhci) | ||
446 | { | ||
447 | xhci->port_status_u0 = 0; | ||
448 | init_timer(&xhci->comp_mode_recovery_timer); | ||
449 | |||
450 | xhci->comp_mode_recovery_timer.data = (unsigned long) xhci; | ||
451 | xhci->comp_mode_recovery_timer.function = compliance_mode_recovery; | ||
452 | xhci->comp_mode_recovery_timer.expires = jiffies + | ||
453 | msecs_to_jiffies(COMP_MODE_RCVRY_MSECS); | ||
454 | |||
455 | set_timer_slack(&xhci->comp_mode_recovery_timer, | ||
456 | msecs_to_jiffies(COMP_MODE_RCVRY_MSECS)); | ||
457 | add_timer(&xhci->comp_mode_recovery_timer); | ||
458 | xhci_dbg(xhci, "Compliance Mode Recovery Timer Initialized.\n"); | ||
459 | } | ||
460 | |||
461 | /* | ||
462 | * This function identifies the systems that have installed the SN65LVPE502CP | ||
463 | * USB3.0 re-driver and that need the Compliance Mode Quirk. | ||
464 | * Systems: | ||
465 | * Vendor: Hewlett-Packard -> System Models: Z420, Z620 and Z820 | ||
466 | */ | ||
467 | static bool compliance_mode_recovery_timer_quirk_check(void) | ||
468 | { | ||
469 | const char *dmi_product_name, *dmi_sys_vendor; | ||
470 | |||
471 | dmi_product_name = dmi_get_system_info(DMI_PRODUCT_NAME); | ||
472 | dmi_sys_vendor = dmi_get_system_info(DMI_SYS_VENDOR); | ||
473 | |||
474 | if (!(strstr(dmi_sys_vendor, "Hewlett-Packard"))) | ||
475 | return false; | ||
476 | |||
477 | if (strstr(dmi_product_name, "Z420") || | ||
478 | strstr(dmi_product_name, "Z620") || | ||
479 | strstr(dmi_product_name, "Z820")) | ||
480 | return true; | ||
481 | |||
482 | return false; | ||
483 | } | ||
484 | |||
485 | static int xhci_all_ports_seen_u0(struct xhci_hcd *xhci) | ||
486 | { | ||
487 | return (xhci->port_status_u0 == ((1 << xhci->num_usb3_ports)-1)); | ||
488 | } | ||
489 | |||
490 | |||
401 | /* | 491 | /* |
402 | * Initialize memory for HCD and xHC (one-time init). | 492 | * Initialize memory for HCD and xHC (one-time init). |
403 | * | 493 | * |
@@ -421,6 +511,12 @@ int xhci_init(struct usb_hcd *hcd) | |||
421 | retval = xhci_mem_init(xhci, GFP_KERNEL); | 511 | retval = xhci_mem_init(xhci, GFP_KERNEL); |
422 | xhci_dbg(xhci, "Finished xhci_init\n"); | 512 | xhci_dbg(xhci, "Finished xhci_init\n"); |
423 | 513 | ||
514 | /* Initializing Compliance Mode Recovery Data If Needed */ | ||
515 | if (compliance_mode_recovery_timer_quirk_check()) { | ||
516 | xhci->quirks |= XHCI_COMP_MODE_QUIRK; | ||
517 | compliance_mode_recovery_timer_init(xhci); | ||
518 | } | ||
519 | |||
424 | return retval; | 520 | return retval; |
425 | } | 521 | } |
426 | 522 | ||
@@ -629,6 +725,11 @@ void xhci_stop(struct usb_hcd *hcd) | |||
629 | del_timer_sync(&xhci->event_ring_timer); | 725 | del_timer_sync(&xhci->event_ring_timer); |
630 | #endif | 726 | #endif |
631 | 727 | ||
728 | /* Deleting Compliance Mode Recovery Timer */ | ||
729 | if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) && | ||
730 | (!(xhci_all_ports_seen_u0(xhci)))) | ||
731 | del_timer_sync(&xhci->comp_mode_recovery_timer); | ||
732 | |||
632 | if (xhci->quirks & XHCI_AMD_PLL_FIX) | 733 | if (xhci->quirks & XHCI_AMD_PLL_FIX) |
633 | usb_amd_dev_put(); | 734 | usb_amd_dev_put(); |
634 | 735 | ||
@@ -806,6 +907,16 @@ int xhci_suspend(struct xhci_hcd *xhci) | |||
806 | } | 907 | } |
807 | spin_unlock_irq(&xhci->lock); | 908 | spin_unlock_irq(&xhci->lock); |
808 | 909 | ||
910 | /* | ||
911 | * Deleting Compliance Mode Recovery Timer because the xHCI Host | ||
912 | * is about to be suspended. | ||
913 | */ | ||
914 | if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) && | ||
915 | (!(xhci_all_ports_seen_u0(xhci)))) { | ||
916 | del_timer_sync(&xhci->comp_mode_recovery_timer); | ||
917 | xhci_dbg(xhci, "Compliance Mode Recovery Timer Deleted!\n"); | ||
918 | } | ||
919 | |||
809 | /* step 5: remove core well power */ | 920 | /* step 5: remove core well power */ |
810 | /* synchronize irq when using MSI-X */ | 921 | /* synchronize irq when using MSI-X */ |
811 | xhci_msix_sync_irqs(xhci); | 922 | xhci_msix_sync_irqs(xhci); |
@@ -938,6 +1049,16 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) | |||
938 | usb_hcd_resume_root_hub(hcd); | 1049 | usb_hcd_resume_root_hub(hcd); |
939 | usb_hcd_resume_root_hub(xhci->shared_hcd); | 1050 | usb_hcd_resume_root_hub(xhci->shared_hcd); |
940 | } | 1051 | } |
1052 | |||
1053 | /* | ||
1054 | * If system is subject to the Quirk, Compliance Mode Timer needs to | ||
1055 | * be re-initialized Always after a system resume. Ports are subject | ||
1056 | * to suffer the Compliance Mode issue again. It doesn't matter if | ||
1057 | * ports have entered previously to U0 before system's suspension. | ||
1058 | */ | ||
1059 | if (xhci->quirks & XHCI_COMP_MODE_QUIRK) | ||
1060 | compliance_mode_recovery_timer_init(xhci); | ||
1061 | |||
941 | return retval; | 1062 | return retval; |
942 | } | 1063 | } |
943 | #endif /* CONFIG_PM */ | 1064 | #endif /* CONFIG_PM */ |
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index c713256297ac..1a05908c6673 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h | |||
@@ -1495,6 +1495,7 @@ struct xhci_hcd { | |||
1495 | #define XHCI_LPM_SUPPORT (1 << 11) | 1495 | #define XHCI_LPM_SUPPORT (1 << 11) |
1496 | #define XHCI_INTEL_HOST (1 << 12) | 1496 | #define XHCI_INTEL_HOST (1 << 12) |
1497 | #define XHCI_SPURIOUS_REBOOT (1 << 13) | 1497 | #define XHCI_SPURIOUS_REBOOT (1 << 13) |
1498 | #define XHCI_COMP_MODE_QUIRK (1 << 14) | ||
1498 | unsigned int num_active_eps; | 1499 | unsigned int num_active_eps; |
1499 | unsigned int limit_active_eps; | 1500 | unsigned int limit_active_eps; |
1500 | /* There are two roothubs to keep track of bus suspend info for */ | 1501 | /* There are two roothubs to keep track of bus suspend info for */ |
@@ -1511,6 +1512,11 @@ struct xhci_hcd { | |||
1511 | unsigned sw_lpm_support:1; | 1512 | unsigned sw_lpm_support:1; |
1512 | /* support xHCI 1.0 spec USB2 hardware LPM */ | 1513 | /* support xHCI 1.0 spec USB2 hardware LPM */ |
1513 | unsigned hw_lpm_support:1; | 1514 | unsigned hw_lpm_support:1; |
1515 | /* Compliance Mode Recovery Data */ | ||
1516 | struct timer_list comp_mode_recovery_timer; | ||
1517 | u32 port_status_u0; | ||
1518 | /* Compliance Mode Timer Triggered every 2 seconds */ | ||
1519 | #define COMP_MODE_RCVRY_MSECS 2000 | ||
1514 | }; | 1520 | }; |
1515 | 1521 | ||
1516 | /* convert between an HCD pointer and the corresponding EHCI_HCD */ | 1522 | /* convert between an HCD pointer and the corresponding EHCI_HCD */ |