Commit 346e9973 authored by Mathias Nyman's avatar Mathias Nyman Committed by Greg Kroah-Hartman

xhci: workaround for hosts missing CAS bit

If a device is unplugged and replugged during Sx system suspend
some  Intel xHC hosts will overwrite the CAS (Cold attach status) flag
and no device connection is noticed in resume.

A device in this state can be identified in resume if its link state
is in polling or compliance mode, and the current connect status is 0.
A device in this state needs to be warm reset.

Intel 100/c230 series PCH specification update Doc #332692-006 Errata #8

Observed on Cherryview and Apollolake as they go into compliance mode
if LFPS times out during polling, and re-plugged devices are not
discovered at resume.
Signed-off-by: default avatarMathias Nyman <mathias.nyman@linux.intel.com>
CC: <stable@vger.kernel.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 4c39135a
...@@ -1355,6 +1355,35 @@ int xhci_bus_suspend(struct usb_hcd *hcd) ...@@ -1355,6 +1355,35 @@ int xhci_bus_suspend(struct usb_hcd *hcd)
return 0; return 0;
} }
/*
* Workaround for missing Cold Attach Status (CAS) if device re-plugged in S3.
* warm reset a USB3 device stuck in polling or compliance mode after resume.
* See Intel 100/c230 series PCH specification update Doc #332692-006 Errata #8
*/
static bool xhci_port_missing_cas_quirk(int port_index,
__le32 __iomem **port_array)
{
u32 portsc;
portsc = readl(port_array[port_index]);
/* if any of these are set we are not stuck */
if (portsc & (PORT_CONNECT | PORT_CAS))
return false;
if (((portsc & PORT_PLS_MASK) != XDEV_POLLING) &&
((portsc & PORT_PLS_MASK) != XDEV_COMP_MODE))
return false;
/* clear wakeup/change bits, and do a warm port reset */
portsc &= ~(PORT_RWC_BITS | PORT_CEC | PORT_WAKE_BITS);
portsc |= PORT_WR;
writel(portsc, port_array[port_index]);
/* flush write */
readl(port_array[port_index]);
return true;
}
int xhci_bus_resume(struct usb_hcd *hcd) int xhci_bus_resume(struct usb_hcd *hcd)
{ {
struct xhci_hcd *xhci = hcd_to_xhci(hcd); struct xhci_hcd *xhci = hcd_to_xhci(hcd);
...@@ -1392,6 +1421,14 @@ int xhci_bus_resume(struct usb_hcd *hcd) ...@@ -1392,6 +1421,14 @@ int xhci_bus_resume(struct usb_hcd *hcd)
u32 temp; u32 temp;
temp = readl(port_array[port_index]); temp = readl(port_array[port_index]);
/* warm reset CAS limited ports stuck in polling/compliance */
if ((xhci->quirks & XHCI_MISSING_CAS) &&
(hcd->speed >= HCD_USB3) &&
xhci_port_missing_cas_quirk(port_index, port_array)) {
xhci_dbg(xhci, "reset stuck port %d\n", port_index);
continue;
}
if (DEV_SUPERSPEED_ANY(temp)) if (DEV_SUPERSPEED_ANY(temp))
temp &= ~(PORT_RWC_BITS | PORT_CEC | PORT_WAKE_BITS); temp &= ~(PORT_RWC_BITS | PORT_CEC | PORT_WAKE_BITS);
else else
......
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
#define PCI_DEVICE_ID_INTEL_SUNRISEPOINT_LP_XHCI 0x9d2f #define PCI_DEVICE_ID_INTEL_SUNRISEPOINT_LP_XHCI 0x9d2f
#define PCI_DEVICE_ID_INTEL_BROXTON_M_XHCI 0x0aa8 #define PCI_DEVICE_ID_INTEL_BROXTON_M_XHCI 0x0aa8
#define PCI_DEVICE_ID_INTEL_BROXTON_B_XHCI 0x1aa8 #define PCI_DEVICE_ID_INTEL_BROXTON_B_XHCI 0x1aa8
#define PCI_DEVICE_ID_INTEL_APL_XHCI 0x5aa8
static const char hcd_name[] = "xhci_hcd"; static const char hcd_name[] = "xhci_hcd";
...@@ -171,6 +172,11 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) ...@@ -171,6 +172,11 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci)
pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI) { pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI) {
xhci->quirks |= XHCI_SSIC_PORT_UNUSED; xhci->quirks |= XHCI_SSIC_PORT_UNUSED;
} }
if (pdev->vendor == PCI_VENDOR_ID_INTEL &&
(pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI ||
pdev->device == PCI_DEVICE_ID_INTEL_APL_XHCI))
xhci->quirks |= XHCI_MISSING_CAS;
if (pdev->vendor == PCI_VENDOR_ID_ETRON && if (pdev->vendor == PCI_VENDOR_ID_ETRON &&
pdev->device == PCI_DEVICE_ID_EJ168) { pdev->device == PCI_DEVICE_ID_EJ168) {
xhci->quirks |= XHCI_RESET_ON_RESUME; xhci->quirks |= XHCI_RESET_ON_RESUME;
......
...@@ -314,6 +314,8 @@ struct xhci_op_regs { ...@@ -314,6 +314,8 @@ struct xhci_op_regs {
#define XDEV_U2 (0x2 << 5) #define XDEV_U2 (0x2 << 5)
#define XDEV_U3 (0x3 << 5) #define XDEV_U3 (0x3 << 5)
#define XDEV_INACTIVE (0x6 << 5) #define XDEV_INACTIVE (0x6 << 5)
#define XDEV_POLLING (0x7 << 5)
#define XDEV_COMP_MODE (0xa << 5)
#define XDEV_RESUME (0xf << 5) #define XDEV_RESUME (0xf << 5)
/* true: port has power (see HCC_PPC) */ /* true: port has power (see HCC_PPC) */
#define PORT_POWER (1 << 9) #define PORT_POWER (1 << 9)
...@@ -1653,6 +1655,7 @@ struct xhci_hcd { ...@@ -1653,6 +1655,7 @@ struct xhci_hcd {
#define XHCI_MTK_HOST (1 << 21) #define XHCI_MTK_HOST (1 << 21)
#define XHCI_SSIC_PORT_UNUSED (1 << 22) #define XHCI_SSIC_PORT_UNUSED (1 << 22)
#define XHCI_NO_64BIT_SUPPORT (1 << 23) #define XHCI_NO_64BIT_SUPPORT (1 << 23)
#define XHCI_MISSING_CAS (1 << 24)
unsigned int num_active_eps; unsigned int num_active_eps;
unsigned int limit_active_eps; unsigned int limit_active_eps;
/* There are two roothubs to keep track of bus suspend info for */ /* There are two roothubs to keep track of bus suspend info for */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment