diff options
Diffstat (limited to 'drivers/usb/host')
| -rw-r--r-- | drivers/usb/host/ehci-q.c | 12 | ||||
| -rw-r--r-- | drivers/usb/host/ohci-at91.c | 10 | ||||
| -rw-r--r-- | drivers/usb/host/pci-quirks.c | 42 | ||||
| -rw-r--r-- | drivers/usb/host/pci-quirks.h | 1 | ||||
| -rw-r--r-- | drivers/usb/host/xhci-hub.c | 42 | ||||
| -rw-r--r-- | drivers/usb/host/xhci-plat.c | 2 | ||||
| -rw-r--r-- | drivers/usb/host/xhci.c | 123 | ||||
| -rw-r--r-- | drivers/usb/host/xhci.h | 6 | 
8 files changed, 227 insertions, 11 deletions
diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 9bc39ca460c..4b66374bdc8 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -128,9 +128,17 @@ qh_refresh (struct ehci_hcd *ehci, struct ehci_qh *qh)  	else {  		qtd = list_entry (qh->qtd_list.next,  				struct ehci_qtd, qtd_list); -		/* first qtd may already be partially processed */ -		if (cpu_to_hc32(ehci, qtd->qtd_dma) == qh->hw->hw_current) +		/* +		 * first qtd may already be partially processed. +		 * If we come here during unlink, the QH overlay region +		 * might have reference to the just unlinked qtd. The +		 * qtd is updated in qh_completions(). Update the QH +		 * overlay here. +		 */ +		if (cpu_to_hc32(ehci, qtd->qtd_dma) == qh->hw->hw_current) { +			qh->hw->hw_qtd_next = qtd->hw_next;  			qtd = NULL; +		}  	}  	if (qtd) diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index a665b3eaa74..aaa8d2bce21 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -570,6 +570,16 @@ static int __devinit ohci_hcd_at91_drv_probe(struct platform_device *pdev)  	if (pdata) {  		at91_for_each_port(i) { +			/* +			 * do not configure PIO if not in relation with +			 * real USB port on board +			 */ +			if (i >= pdata->ports) { +				pdata->vbus_pin[i] = -EINVAL; +				pdata->overcurrent_pin[i] = -EINVAL; +				break; +			} +  			if (!gpio_is_valid(pdata->vbus_pin[i]))  				continue;  			gpio = pdata->vbus_pin[i]; diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index c5e9e4a76f1..966d1484ee7 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -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; @@ -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)); @@ -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. @@ -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; @@ -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; @@ -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 diff --git a/drivers/usb/host/pci-quirks.h b/drivers/usb/host/pci-quirks.h index ef004a5de20..7f69a39163c 100644 --- a/drivers/usb/host/pci-quirks.h +++ b/drivers/usb/host/pci-quirks.h @@ -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  */ diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 5896f0588d4..aa90ad4d4fd 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)  		 * 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)  { @@ -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; diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 689bc18b051..df90fe51b4a 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -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; diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 66bc5c3985f..a4b0ce13fa0 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -26,6 +26,7 @@  #include <linux/module.h>  #include <linux/moduleparam.h>  #include <linux/slab.h> +#include <linux/dmi.h>  #include "xhci.h" @@ -399,6 +400,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).   * @@ -422,6 +512,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;  } @@ -631,6 +727,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(); @@ -661,7 +762,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); @@ -808,6 +909,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); @@ -940,6 +1051,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 */ diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index e81ccfa3552..e44e2d3c83b 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1510,6 +1510,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 */ @@ -1526,6 +1527,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 */  |