Skip to content

Commit

Permalink
Merge tag 'for-usb-linus-2012-09-05' of git://git.kernel.org/pub/scm/…
Browse files Browse the repository at this point in the history
…linux/kernel/git/sarah/xhci into usb-linus

xHCI 3.6 bug fixes.

Hi Greg,

Here's seven bugfixes for 3.6.  All of them are marked for stable, and
most are vendor-specific fixes.

Details:
--------

 - Commits 052c7f9 and 2963657 fix a couple stupid mistakes I made in a
   Intel xHCI bug fix patch I pushed just before I left for vacation.

 - Commits 29d2145 and a96874a fix issues with the Intel Panther Point
   EHCI to xHCI port switchover.

 - Commit 71c731a adds the work-around for the TI redriver "dead port"
   issue.

 - Commit 319acdf adds a fix for non-PCI xHCI platform drivers.

 - Commit e955a1c works around the UEFI issue with the xHCI host
   sometimes returning 0xff's in the MMIO on boot.

Sarah Sharp
  • Loading branch information
Greg Kroah-Hartman committed Sep 6, 2012
2 parents 92fc7a8 + 2963657 commit 7f7cd3c
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 9 deletions.
42 changes: 35 additions & 7 deletions drivers/usb/host/pci-quirks.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@
#define NB_PIF0_PWRDOWN_1 0x01100013

#define USB_INTEL_XUSB2PR 0xD0
#define USB_INTEL_USB2PRM 0xD4
#define USB_INTEL_USB3_PSSEN 0xD8
#define USB_INTEL_USB3PRM 0xDC

static struct amd_chipset_info {
struct pci_dev *nb_dev;
Expand Down Expand Up @@ -772,10 +774,18 @@ void usb_enable_xhci_ports(struct pci_dev *xhci_pdev)
return;
}

ports_available = 0xffffffff;
/* Read USB3PRM, the USB 3.0 Port Routing Mask Register
* Indicate the ports that can be changed from OS.
*/
pci_read_config_dword(xhci_pdev, USB_INTEL_USB3PRM,
&ports_available);

dev_dbg(&xhci_pdev->dev, "Configurable ports to enable SuperSpeed: 0x%x\n",
ports_available);

/* Write USB3_PSSEN, the USB 3.0 Port SuperSpeed Enable
* Register, to turn on SuperSpeed terminations for all
* available ports.
* Register, to turn on SuperSpeed terminations for the
* switchable ports.
*/
pci_write_config_dword(xhci_pdev, USB_INTEL_USB3_PSSEN,
cpu_to_le32(ports_available));
Expand All @@ -785,7 +795,16 @@ void usb_enable_xhci_ports(struct pci_dev *xhci_pdev)
dev_dbg(&xhci_pdev->dev, "USB 3.0 ports that are now enabled "
"under xHCI: 0x%x\n", ports_available);

ports_available = 0xffffffff;
/* Read XUSB2PRM, xHCI USB 2.0 Port Routing Mask Register
* Indicate the USB 2.0 ports to be controlled by the xHCI host.
*/

pci_read_config_dword(xhci_pdev, USB_INTEL_USB2PRM,
&ports_available);

dev_dbg(&xhci_pdev->dev, "Configurable USB 2.0 ports to hand over to xCHI: 0x%x\n",
ports_available);

/* Write XUSB2PR, the xHC USB 2.0 Port Routing Register, to
* switch the USB 2.0 power and data lines over to the xHCI
* host.
Expand Down Expand Up @@ -822,12 +841,12 @@ static void __devinit quirk_usb_handoff_xhci(struct pci_dev *pdev)
void __iomem *op_reg_base;
u32 val;
int timeout;
int len = pci_resource_len(pdev, 0);

if (!mmio_resource_enabled(pdev, 0))
return;

base = ioremap_nocache(pci_resource_start(pdev, 0),
pci_resource_len(pdev, 0));
base = ioremap_nocache(pci_resource_start(pdev, 0), len);
if (base == NULL)
return;

Expand All @@ -837,9 +856,17 @@ static void __devinit quirk_usb_handoff_xhci(struct pci_dev *pdev)
*/
ext_cap_offset = xhci_find_next_cap_offset(base, XHCI_HCC_PARAMS_OFFSET);
do {
if ((ext_cap_offset + sizeof(val)) > len) {
/* We're reading garbage from the controller */
dev_warn(&pdev->dev,
"xHCI controller failing to respond");
return;
}

if (!ext_cap_offset)
/* We've reached the end of the extended capabilities */
goto hc_init;

val = readl(base + ext_cap_offset);
if (XHCI_EXT_CAPS_ID(val) == XHCI_EXT_CAPS_LEGACY)
break;
Expand Down Expand Up @@ -870,9 +897,10 @@ static void __devinit quirk_usb_handoff_xhci(struct pci_dev *pdev)
/* Disable any BIOS SMIs and clear all SMI events*/
writel(val, base + ext_cap_offset + XHCI_LEGACY_CONTROL_OFFSET);

hc_init:
if (usb_is_intel_switchable_xhci(pdev))
usb_enable_xhci_ports(pdev);
hc_init:

op_reg_base = base + XHCI_HC_LENGTH(readl(base));

/* Wait for the host controller to be ready before writing any
Expand Down
1 change: 1 addition & 0 deletions drivers/usb/host/pci-quirks.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ void usb_disable_xhci_ports(struct pci_dev *xhci_pdev);
static inline void usb_amd_quirk_pll_disable(void) {}
static inline void usb_amd_quirk_pll_enable(void) {}
static inline void usb_amd_dev_put(void) {}
static inline void usb_disable_xhci_ports(struct pci_dev *xhci_pdev) {}
#endif /* CONFIG_PCI */

#endif /* __LINUX_USB_PCI_QUIRKS_H */
42 changes: 42 additions & 0 deletions drivers/usb/host/xhci-hub.c
Original file line number Diff line number Diff line change
Expand Up @@ -493,11 +493,48 @@ static void xhci_hub_report_link_state(u32 *status, u32 status_reg)
* when this bit is set.
*/
pls |= USB_PORT_STAT_CONNECTION;
} else {
/*
* If CAS bit isn't set but the Port is already at
* Compliance Mode, fake a connection so the USB core
* notices the Compliance state and resets the port.
* This resolves an issue generated by the SN65LVPE502CP
* in which sometimes the port enters compliance mode
* caused by a delay on the host-device negotiation.
*/
if (pls == USB_SS_PORT_LS_COMP_MOD)
pls |= USB_PORT_STAT_CONNECTION;
}

/* update status field */
*status |= pls;
}

/*
* Function for Compliance Mode Quirk.
*
* This Function verifies if all xhc USB3 ports have entered U0, if so,
* the compliance mode timer is deleted. A port won't enter
* compliance mode if it has previously entered U0.
*/
void xhci_del_comp_mod_timer(struct xhci_hcd *xhci, u32 status, u16 wIndex)
{
u32 all_ports_seen_u0 = ((1 << xhci->num_usb3_ports)-1);
bool port_in_u0 = ((status & PORT_PLS_MASK) == XDEV_U0);

if (!(xhci->quirks & XHCI_COMP_MODE_QUIRK))
return;

if ((xhci->port_status_u0 != all_ports_seen_u0) && port_in_u0) {
xhci->port_status_u0 |= 1 << wIndex;
if (xhci->port_status_u0 == all_ports_seen_u0) {
del_timer_sync(&xhci->comp_mode_recovery_timer);
xhci_dbg(xhci, "All USB3 ports have entered U0 already!\n");
xhci_dbg(xhci, "Compliance Mode Recovery Timer Deleted.\n");
}
}
}

int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
u16 wIndex, char *buf, u16 wLength)
{
Expand Down Expand Up @@ -651,6 +688,11 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
/* Update Port Link State for super speed ports*/
if (hcd->speed == HCD_USB3) {
xhci_hub_report_link_state(&status, temp);
/*
* Verify if all USB3 Ports Have entered U0 already.
* Delete Compliance Mode Timer if so.
*/
xhci_del_comp_mod_timer(xhci, temp, wIndex);
}
if (bus_state->port_c_suspend & (1 << wIndex))
status |= 1 << USB_PORT_FEAT_C_SUSPEND;
Expand Down
2 changes: 1 addition & 1 deletion drivers/usb/host/xhci-plat.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ static int xhci_plat_probe(struct platform_device *pdev)
goto put_hcd;
}

hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len);
if (!hcd->regs) {
dev_dbg(&pdev->dev, "error mapping memory\n");
ret = -EFAULT;
Expand Down
123 changes: 122 additions & 1 deletion drivers/usb/host/xhci.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/dmi.h>

#include "xhci.h"

Expand Down Expand Up @@ -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).
*
Expand All @@ -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;
}

Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -659,7 +760,7 @@ void xhci_shutdown(struct usb_hcd *hcd)
{
struct xhci_hcd *xhci = hcd_to_xhci(hcd);

if (xhci->quirks && XHCI_SPURIOUS_REBOOT)
if (xhci->quirks & XHCI_SPURIOUS_REBOOT)
usb_disable_xhci_ports(to_pci_dev(hcd->self.controller));

spin_lock_irq(&xhci->lock);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 */
Expand Down
6 changes: 6 additions & 0 deletions drivers/usb/host/xhci.h
Original file line number Diff line number Diff line change
Expand Up @@ -1495,6 +1495,7 @@ struct xhci_hcd {
#define XHCI_LPM_SUPPORT (1 << 11)
#define XHCI_INTEL_HOST (1 << 12)
#define XHCI_SPURIOUS_REBOOT (1 << 13)
#define XHCI_COMP_MODE_QUIRK (1 << 14)
unsigned int num_active_eps;
unsigned int limit_active_eps;
/* There are two roothubs to keep track of bus suspend info for */
Expand All @@ -1511,6 +1512,11 @@ struct xhci_hcd {
unsigned sw_lpm_support:1;
/* support xHCI 1.0 spec USB2 hardware LPM */
unsigned hw_lpm_support:1;
/* Compliance Mode Recovery Data */
struct timer_list comp_mode_recovery_timer;
u32 port_status_u0;
/* Compliance Mode Timer Triggered every 2 seconds */
#define COMP_MODE_RCVRY_MSECS 2000
};

/* convert between an HCD pointer and the corresponding EHCI_HCD */
Expand Down

0 comments on commit 7f7cd3c

Please sign in to comment.