summaryrefslogtreecommitdiff
path: root/drivers/usb/host
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host')
-rw-r--r--drivers/usb/host/ehci-omap.c167
-rw-r--r--drivers/usb/host/ehci-q.c12
-rw-r--r--drivers/usb/host/ehci-sead3.c2
-rw-r--r--drivers/usb/host/ehci-tegra.c3
-rw-r--r--drivers/usb/host/isp1362-hcd.c8
-rw-r--r--drivers/usb/host/ohci-at91.c10
-rw-r--r--drivers/usb/host/pci-quirks.c49
-rw-r--r--drivers/usb/host/pci-quirks.h2
-rw-r--r--drivers/usb/host/xhci-hub.c42
-rw-r--r--drivers/usb/host/xhci-pci.c10
-rw-r--r--drivers/usb/host/xhci-plat.c2
-rw-r--r--drivers/usb/host/xhci-ring.c40
-rw-r--r--drivers/usb/host/xhci.c129
-rw-r--r--drivers/usb/host/xhci.h9
14 files changed, 285 insertions, 200 deletions
diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c
index bb55eb4a7d48..d7fe287d0678 100644
--- a/drivers/usb/host/ehci-omap.c
+++ b/drivers/usb/host/ehci-omap.c
@@ -56,15 +56,6 @@
#define EHCI_INSNREG05_ULPI_EXTREGADD_SHIFT 8
#define EHCI_INSNREG05_ULPI_WRDATA_SHIFT 0
-/* Errata i693 */
-static struct clk *utmi_p1_fck;
-static struct clk *utmi_p2_fck;
-static struct clk *xclk60mhsp1_ck;
-static struct clk *xclk60mhsp2_ck;
-static struct clk *usbhost_p1_fck;
-static struct clk *usbhost_p2_fck;
-static struct clk *init_60m_fclk;
-
/*-------------------------------------------------------------------------*/
static const struct hc_driver ehci_omap_hc_driver;
@@ -80,40 +71,6 @@ static inline u32 ehci_read(void __iomem *base, u32 reg)
return __raw_readl(base + reg);
}
-/* Erratum i693 workaround sequence */
-static void omap_ehci_erratum_i693(struct ehci_hcd *ehci)
-{
- int ret = 0;
-
- /* Switch to the internal 60 MHz clock */
- ret = clk_set_parent(utmi_p1_fck, init_60m_fclk);
- if (ret != 0)
- ehci_err(ehci, "init_60m_fclk set parent"
- "failed error:%d\n", ret);
-
- ret = clk_set_parent(utmi_p2_fck, init_60m_fclk);
- if (ret != 0)
- ehci_err(ehci, "init_60m_fclk set parent"
- "failed error:%d\n", ret);
-
- clk_enable(usbhost_p1_fck);
- clk_enable(usbhost_p2_fck);
-
- /* Wait 1ms and switch back to the external clock */
- mdelay(1);
- ret = clk_set_parent(utmi_p1_fck, xclk60mhsp1_ck);
- if (ret != 0)
- ehci_err(ehci, "xclk60mhsp1_ck set parent"
- "failed error:%d\n", ret);
-
- ret = clk_set_parent(utmi_p2_fck, xclk60mhsp2_ck);
- if (ret != 0)
- ehci_err(ehci, "xclk60mhsp2_ck set parent"
- "failed error:%d\n", ret);
-
- clk_disable(usbhost_p1_fck);
- clk_disable(usbhost_p2_fck);
-}
static void omap_ehci_soft_phy_reset(struct usb_hcd *hcd, u8 port)
{
@@ -195,50 +152,6 @@ static int omap_ehci_init(struct usb_hcd *hcd)
return rc;
}
-static int omap_ehci_hub_control(
- struct usb_hcd *hcd,
- u16 typeReq,
- u16 wValue,
- u16 wIndex,
- char *buf,
- u16 wLength
-)
-{
- struct ehci_hcd *ehci = hcd_to_ehci(hcd);
- u32 __iomem *status_reg = &ehci->regs->port_status[
- (wIndex & 0xff) - 1];
- u32 temp;
- unsigned long flags;
- int retval = 0;
-
- spin_lock_irqsave(&ehci->lock, flags);
-
- if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) {
- temp = ehci_readl(ehci, status_reg);
- if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) {
- retval = -EPIPE;
- goto done;
- }
-
- temp &= ~PORT_WKCONN_E;
- temp |= PORT_WKDISC_E | PORT_WKOC_E;
- ehci_writel(ehci, temp | PORT_SUSPEND, status_reg);
-
- omap_ehci_erratum_i693(ehci);
-
- set_bit((wIndex & 0xff) - 1, &ehci->suspended_ports);
- goto done;
- }
-
- spin_unlock_irqrestore(&ehci->lock, flags);
-
- /* Handle the hub control events here */
- return ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
-done:
- spin_unlock_irqrestore(&ehci->lock, flags);
- return retval;
-}
-
static void disable_put_regulator(
struct ehci_hcd_omap_platform_data *pdata)
{
@@ -351,79 +264,9 @@ static int ehci_hcd_omap_probe(struct platform_device *pdev)
goto err_pm_runtime;
}
- /* get clocks */
- utmi_p1_fck = clk_get(dev, "utmi_p1_gfclk");
- if (IS_ERR(utmi_p1_fck)) {
- ret = PTR_ERR(utmi_p1_fck);
- dev_err(dev, "utmi_p1_gfclk failed error:%d\n", ret);
- goto err_add_hcd;
- }
-
- xclk60mhsp1_ck = clk_get(dev, "xclk60mhsp1_ck");
- if (IS_ERR(xclk60mhsp1_ck)) {
- ret = PTR_ERR(xclk60mhsp1_ck);
- dev_err(dev, "xclk60mhsp1_ck failed error:%d\n", ret);
- goto err_utmi_p1_fck;
- }
-
- utmi_p2_fck = clk_get(dev, "utmi_p2_gfclk");
- if (IS_ERR(utmi_p2_fck)) {
- ret = PTR_ERR(utmi_p2_fck);
- dev_err(dev, "utmi_p2_gfclk failed error:%d\n", ret);
- goto err_xclk60mhsp1_ck;
- }
-
- xclk60mhsp2_ck = clk_get(dev, "xclk60mhsp2_ck");
- if (IS_ERR(xclk60mhsp2_ck)) {
- ret = PTR_ERR(xclk60mhsp2_ck);
- dev_err(dev, "xclk60mhsp2_ck failed error:%d\n", ret);
- goto err_utmi_p2_fck;
- }
-
- usbhost_p1_fck = clk_get(dev, "usb_host_hs_utmi_p1_clk");
- if (IS_ERR(usbhost_p1_fck)) {
- ret = PTR_ERR(usbhost_p1_fck);
- dev_err(dev, "usbhost_p1_fck failed error:%d\n", ret);
- goto err_xclk60mhsp2_ck;
- }
-
- usbhost_p2_fck = clk_get(dev, "usb_host_hs_utmi_p2_clk");
- if (IS_ERR(usbhost_p2_fck)) {
- ret = PTR_ERR(usbhost_p2_fck);
- dev_err(dev, "usbhost_p2_fck failed error:%d\n", ret);
- goto err_usbhost_p1_fck;
- }
-
- init_60m_fclk = clk_get(dev, "init_60m_fclk");
- if (IS_ERR(init_60m_fclk)) {
- ret = PTR_ERR(init_60m_fclk);
- dev_err(dev, "init_60m_fclk failed error:%d\n", ret);
- goto err_usbhost_p2_fck;
- }
return 0;
-err_usbhost_p2_fck:
- clk_put(usbhost_p2_fck);
-
-err_usbhost_p1_fck:
- clk_put(usbhost_p1_fck);
-
-err_xclk60mhsp2_ck:
- clk_put(xclk60mhsp2_ck);
-
-err_utmi_p2_fck:
- clk_put(utmi_p2_fck);
-
-err_xclk60mhsp1_ck:
- clk_put(xclk60mhsp1_ck);
-
-err_utmi_p1_fck:
- clk_put(utmi_p1_fck);
-
-err_add_hcd:
- usb_remove_hcd(hcd);
-
err_pm_runtime:
disable_put_regulator(pdata);
pm_runtime_put_sync(dev);
@@ -454,14 +297,6 @@ static int ehci_hcd_omap_remove(struct platform_device *pdev)
iounmap(hcd->regs);
usb_put_hcd(hcd);
- clk_put(utmi_p1_fck);
- clk_put(utmi_p2_fck);
- clk_put(xclk60mhsp1_ck);
- clk_put(xclk60mhsp2_ck);
- clk_put(usbhost_p1_fck);
- clk_put(usbhost_p2_fck);
- clk_put(init_60m_fclk);
-
pm_runtime_put_sync(dev);
pm_runtime_disable(dev);
@@ -532,7 +367,7 @@ static const struct hc_driver ehci_omap_hc_driver = {
* root hub support
*/
.hub_status_data = ehci_hub_status_data,
- .hub_control = omap_ehci_hub_control,
+ .hub_control = ehci_hub_control,
.bus_suspend = ehci_bus_suspend,
.bus_resume = ehci_bus_resume,
diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
index 9bc39ca460c8..4b66374bdc8e 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/ehci-sead3.c b/drivers/usb/host/ehci-sead3.c
index 58c96bd50d22..0c9e43cfaff5 100644
--- a/drivers/usb/host/ehci-sead3.c
+++ b/drivers/usb/host/ehci-sead3.c
@@ -40,7 +40,7 @@ static int ehci_sead3_setup(struct usb_hcd *hcd)
ehci->need_io_watchdog = 0;
/* Set burst length to 16 words. */
- ehci_writel(ehci, 0x1010, &ehci->regs->reserved[1]);
+ ehci_writel(ehci, 0x1010, &ehci->regs->reserved1[1]);
return ret;
}
diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c
index 950e95efa381..26dedb30ad0b 100644
--- a/drivers/usb/host/ehci-tegra.c
+++ b/drivers/usb/host/ehci-tegra.c
@@ -799,11 +799,12 @@ static int tegra_ehci_remove(struct platform_device *pdev)
#endif
usb_remove_hcd(hcd);
- usb_put_hcd(hcd);
tegra_usb_phy_close(tegra->phy);
iounmap(hcd->regs);
+ usb_put_hcd(hcd);
+
clk_disable_unprepare(tegra->clk);
clk_put(tegra->clk);
diff --git a/drivers/usb/host/isp1362-hcd.c b/drivers/usb/host/isp1362-hcd.c
index 2ed112d3e159..256326322cfd 100644
--- a/drivers/usb/host/isp1362-hcd.c
+++ b/drivers/usb/host/isp1362-hcd.c
@@ -543,12 +543,12 @@ static void postproc_ep(struct isp1362_hcd *isp1362_hcd, struct isp1362_ep *ep)
usb_pipein(urb->pipe) ? "IN" : "OUT", ep->nextpid,
short_ok ? "" : "not_",
PTD_GET_COUNT(ptd), ep->maxpacket, len);
+ /* save the data underrun error code for later and
+ * proceed with the status stage
+ */
+ urb->actual_length += PTD_GET_COUNT(ptd);
if (usb_pipecontrol(urb->pipe)) {
ep->nextpid = USB_PID_ACK;
- /* save the data underrun error code for later and
- * proceed with the status stage
- */
- urb->actual_length += PTD_GET_COUNT(ptd);
BUG_ON(urb->actual_length > urb->transfer_buffer_length);
if (urb->status == -EINPROGRESS)
diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c
index a665b3eaa746..aaa8d2bce217 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 df0828cb2aa3..966d1484ee79 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.
@@ -800,6 +819,13 @@ void usb_enable_xhci_ports(struct pci_dev *xhci_pdev)
}
EXPORT_SYMBOL_GPL(usb_enable_xhci_ports);
+void usb_disable_xhci_ports(struct pci_dev *xhci_pdev)
+{
+ pci_write_config_dword(xhci_pdev, USB_INTEL_USB3_PSSEN, 0x0);
+ pci_write_config_dword(xhci_pdev, USB_INTEL_XUSB2PR, 0x0);
+}
+EXPORT_SYMBOL_GPL(usb_disable_xhci_ports);
+
/**
* PCI Quirks for xHCI.
*
@@ -815,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;
@@ -830,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;
@@ -863,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 b1002a8ef96f..7f69a39163ce 100644
--- a/drivers/usb/host/pci-quirks.h
+++ b/drivers/usb/host/pci-quirks.h
@@ -10,10 +10,12 @@ void usb_amd_quirk_pll_disable(void);
void usb_amd_quirk_pll_enable(void);
bool usb_is_intel_switchable_xhci(struct pci_dev *pdev);
void usb_enable_xhci_ports(struct pci_dev *xhci_pdev);
+void usb_disable_xhci_ports(struct pci_dev *xhci_pdev);
#else
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 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)
* 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-pci.c b/drivers/usb/host/xhci-pci.c
index 18b231b0c5d3..9bfd4ca1153c 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -94,11 +94,21 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci)
xhci->quirks |= XHCI_EP_LIMIT_QUIRK;
xhci->limit_active_eps = 64;
xhci->quirks |= XHCI_SW_BW_CHECKING;
+ /*
+ * PPT desktop boards DH77EB and DH77DF will power back on after
+ * a few seconds of being shutdown. The fix for this is to
+ * switch the ports from xHCI to EHCI on shutdown. We can't use
+ * DMI information to find those particular boards (since each
+ * vendor will change the board name), so we have to key off all
+ * PPT chipsets.
+ */
+ xhci->quirks |= XHCI_SPURIOUS_REBOOT;
}
if (pdev->vendor == PCI_VENDOR_ID_ETRON &&
pdev->device == PCI_DEVICE_ID_ASROCK_P67) {
xhci->quirks |= XHCI_RESET_ON_RESUME;
xhci_dbg(xhci, "QUIRK: Resetting on resume\n");
+ xhci->quirks |= XHCI_TRUST_TX_LENGTH;
}
if (pdev->vendor == PCI_VENDOR_ID_VIA)
xhci->quirks |= XHCI_RESET_ON_RESUME;
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index 689bc18b051d..df90fe51b4aa 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-ring.c b/drivers/usb/host/xhci-ring.c
index 8275645889da..643c2f3f3e73 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -145,29 +145,37 @@ static void next_trb(struct xhci_hcd *xhci,
*/
static void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring)
{
- union xhci_trb *next;
unsigned long long addr;
ring->deq_updates++;
- /* If this is not event ring, there is one more usable TRB */
+ /*
+ * If this is not event ring, and the dequeue pointer
+ * is not on a link TRB, there is one more usable TRB
+ */
if (ring->type != TYPE_EVENT &&
!last_trb(xhci, ring, ring->deq_seg, ring->dequeue))
ring->num_trbs_free++;
- next = ++(ring->dequeue);
- /* Update the dequeue pointer further if that was a link TRB or we're at
- * the end of an event ring segment (which doesn't have link TRBS)
- */
- while (last_trb(xhci, ring, ring->deq_seg, next)) {
- if (ring->type == TYPE_EVENT && last_trb_on_last_seg(xhci,
- ring, ring->deq_seg, next)) {
- ring->cycle_state = (ring->cycle_state ? 0 : 1);
+ do {
+ /*
+ * Update the dequeue pointer further if that was a link TRB or
+ * we're at the end of an event ring segment (which doesn't have
+ * link TRBS)
+ */
+ if (last_trb(xhci, ring, ring->deq_seg, ring->dequeue)) {
+ if (ring->type == TYPE_EVENT &&
+ last_trb_on_last_seg(xhci, ring,
+ ring->deq_seg, ring->dequeue)) {
+ ring->cycle_state = (ring->cycle_state ? 0 : 1);
+ }
+ ring->deq_seg = ring->deq_seg->next;
+ ring->dequeue = ring->deq_seg->trbs;
+ } else {
+ ring->dequeue++;
}
- ring->deq_seg = ring->deq_seg->next;
- ring->dequeue = ring->deq_seg->trbs;
- next = ring->dequeue;
- }
+ } while (last_trb(xhci, ring, ring->deq_seg, ring->dequeue));
+
addr = (unsigned long long) xhci_trb_virt_to_dma(ring->deq_seg, ring->dequeue);
}
@@ -2073,8 +2081,8 @@ static int handle_tx_event(struct xhci_hcd *xhci,
if (xhci->quirks & XHCI_TRUST_TX_LENGTH)
trb_comp_code = COMP_SHORT_TX;
else
- xhci_warn(xhci, "WARN Successful completion on short TX: "
- "needs XHCI_TRUST_TX_LENGTH quirk?\n");
+ xhci_warn_ratelimited(xhci,
+ "WARN Successful completion on short TX: needs XHCI_TRUST_TX_LENGTH quirk?\n");
case COMP_SHORT_TX:
break;
case COMP_STOP:
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 7648b2d4b268..6ece0ed288d4 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"
@@ -166,7 +167,7 @@ int xhci_reset(struct xhci_hcd *xhci)
xhci_writel(xhci, command, &xhci->op_regs->command);
ret = handshake(xhci, &xhci->op_regs->command,
- CMD_RESET, 0, 250 * 1000);
+ CMD_RESET, 0, 10 * 1000 * 1000);
if (ret)
return ret;
@@ -175,7 +176,8 @@ int xhci_reset(struct xhci_hcd *xhci)
* xHCI cannot write to any doorbells or operational registers other
* than status until the "Controller Not Ready" flag is cleared.
*/
- ret = handshake(xhci, &xhci->op_regs->status, STS_CNR, 0, 250 * 1000);
+ ret = handshake(xhci, &xhci->op_regs->status,
+ STS_CNR, 0, 10 * 1000 * 1000);
for (i = 0; i < 2; ++i) {
xhci->bus_state[i].port_c_suspend = 0;
@@ -397,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).
*
@@ -420,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;
}
@@ -628,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();
@@ -658,6 +760,9 @@ void xhci_shutdown(struct usb_hcd *hcd)
{
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ if (xhci->quirks & XHCI_SPURIOUS_REBOOT)
+ usb_disable_xhci_ports(to_pci_dev(hcd->self.controller));
+
spin_lock_irq(&xhci->lock);
xhci_halt(xhci);
spin_unlock_irq(&xhci->lock);
@@ -802,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);
@@ -934,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 */
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 55c0785810c9..1a05908c6673 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1494,6 +1494,8 @@ struct xhci_hcd {
#define XHCI_TRUST_TX_LENGTH (1 << 10)
#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 */
@@ -1510,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 */
@@ -1537,6 +1544,8 @@ static inline struct usb_hcd *xhci_to_hcd(struct xhci_hcd *xhci)
dev_err(xhci_to_hcd(xhci)->self.controller , fmt , ## args)
#define xhci_warn(xhci, fmt, args...) \
dev_warn(xhci_to_hcd(xhci)->self.controller , fmt , ## args)
+#define xhci_warn_ratelimited(xhci, fmt, args...) \
+ dev_warn_ratelimited(xhci_to_hcd(xhci)->self.controller , fmt , ## args)
/* TODO: copied from ehci.h - can be refactored? */
/* xHCI spec says all registers are little endian */