USB: xhci: Add PORTSC register write delay quirk for DWC3 controller
In Synopsis DWC3 controller, PORTSC register access involves multiple clock
domains. When the software does a PORTSC write, handshakes are needed
across these clock domains. This results in long access times, especially
for USB 2.0 ports. In order to solve this issue, when the PORTSC write
operations happen on the system bus, the command is latched and system bus
is released immediately. However, the real PORTSC write access will take
some time internally to complete. If the software quickly does a read to
the PORTSC, some fields (port status change related fields like OCC, etc.)
may not have correct value due to the current way of handling these bits.
The workaround is to give some delay (5 mac2_clk -> UTMI clock = 60 MHz ->
(16.66 ns x 5 = 84ns) ~100ns after writing to the PORTSC register.
Add controller vendor id and revision fields to the XHCI platform data.
Update quirks field based on the vendor id and revision in the XHCI
platform driver.
CRs-fixed: 371299
Change-Id: Ibe4a88119c483afb522e9a96667f17dccbf74122
Signed-off-by: Pavankumar Kondeti <pkondeti@codeaurora.org>
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index 56a6234..099708b 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -38,11 +38,13 @@
#include <linux/platform_device.h>
#include "core.h"
+#include "xhci.h"
int dwc3_host_init(struct dwc3 *dwc)
{
struct platform_device *xhci;
int ret;
+ struct xhci_plat_data pdata;
xhci = platform_device_alloc("xhci-hcd", -1);
if (!xhci) {
@@ -58,6 +60,16 @@
xhci->dev.dma_parms = dwc->dev->dma_parms;
dwc->xhci = xhci;
+ pdata.vendor = ((dwc->revision & DWC3_GSNPSID_MASK) >>
+ __ffs(DWC3_GSNPSID_MASK) & DWC3_GSNPSREV_MASK);
+ pdata.revision = dwc->revision & DWC3_GSNPSREV_MASK;
+
+ ret = platform_device_add_data(xhci, (const void *) &pdata,
+ sizeof(struct xhci_plat_data));
+ if (ret) {
+ dev_err(dwc->dev, "couldn't add pdata to xHCI device\n");
+ goto err1;
+ }
ret = platform_device_add_resources(xhci, dwc->xhci_resources,
DWC3_XHCI_RESOURCES_NUM);
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index 673ad12..78ece8d 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -343,6 +343,8 @@
/* Write 1 to disable the port */
xhci_writel(xhci, port_status | PORT_PE, addr);
+ if (xhci->quirks & XHCI_PORTSC_DELAY)
+ ndelay(100);
port_status = xhci_readl(xhci, addr);
xhci_dbg(xhci, "disable port, actual port %d status = 0x%x\n",
wIndex, port_status);
@@ -389,6 +391,8 @@
}
/* Change bits are all write 1 to clear */
xhci_writel(xhci, port_status | status, addr);
+ if (xhci->quirks & XHCI_PORTSC_DELAY)
+ ndelay(100);
port_status = xhci_readl(xhci, addr);
xhci_dbg(xhci, "clear port %s change, actual port %d status = 0x%x\n",
port_change_bit, wIndex, port_status);
@@ -420,6 +424,8 @@
temp &= ~PORT_PLS_MASK;
temp |= PORT_LINK_STROBE | link_state;
xhci_writel(xhci, temp, port_array[port_id]);
+ if (xhci->quirks & XHCI_PORTSC_DELAY)
+ ndelay(100);
}
void xhci_set_remote_wake_mask(struct xhci_hcd *xhci,
@@ -446,6 +452,8 @@
temp &= ~PORT_WKOC_E;
xhci_writel(xhci, temp, port_array[port_id]);
+ if (xhci->quirks & XHCI_PORTSC_DELAY)
+ ndelay(100);
}
/* Test and clear port RWC bit */
@@ -459,6 +467,8 @@
temp = xhci_port_state_to_neutral(temp);
temp |= port_bit;
xhci_writel(xhci, temp, port_array[port_id]);
+ if (xhci->quirks & XHCI_PORTSC_DELAY)
+ ndelay(100);
}
}
@@ -721,6 +731,8 @@
*/
xhci_writel(xhci, temp | PORT_POWER,
port_array[wIndex]);
+ if (xhci->quirks & XHCI_PORTSC_DELAY)
+ ndelay(100);
temp = xhci_readl(xhci, port_array[wIndex]);
xhci_dbg(xhci, "set port power, actual port %d status = 0x%x\n", wIndex, temp);
@@ -728,6 +740,8 @@
case USB_PORT_FEAT_RESET:
temp = (temp | PORT_RESET);
xhci_writel(xhci, temp, port_array[wIndex]);
+ if (xhci->quirks & XHCI_PORTSC_DELAY)
+ ndelay(100);
temp = xhci_readl(xhci, port_array[wIndex]);
xhci_dbg(xhci, "set port reset, actual port %d status = 0x%x\n", wIndex, temp);
@@ -743,6 +757,8 @@
case USB_PORT_FEAT_BH_PORT_RESET:
temp |= PORT_WR;
xhci_writel(xhci, temp, port_array[wIndex]);
+ if (xhci->quirks & XHCI_PORTSC_DELAY)
+ ndelay(100);
temp = xhci_readl(xhci, port_array[wIndex]);
break;
@@ -936,8 +952,11 @@
t2 &= ~PORT_WAKE_BITS;
t1 = xhci_port_state_to_neutral(t1);
- if (t1 != t2)
+ if (t1 != t2) {
xhci_writel(xhci, t2, port_array[port_index]);
+ if (xhci->quirks & XHCI_PORTSC_DELAY)
+ ndelay(100);
+ }
if (hcd->speed != HCD_USB3) {
/* enable remote wake up for USB 2.0 */
@@ -951,6 +970,8 @@
tmp = xhci_readl(xhci, addr);
tmp |= PORT_RWE;
xhci_writel(xhci, tmp, addr);
+ if (xhci->quirks & XHCI_PORTSC_DELAY)
+ ndelay(100);
}
}
hcd->state = HC_STATE_SUSPENDED;
@@ -1028,8 +1049,11 @@
xhci, port_index + 1);
if (slot_id)
xhci_ring_device(xhci, slot_id);
- } else
+ } else {
xhci_writel(xhci, temp, port_array[port_index]);
+ if (xhci->quirks & XHCI_PORTSC_DELAY)
+ ndelay(100);
+ }
if (hcd->speed != HCD_USB3) {
/* disable remote wake up for USB 2.0 */
@@ -1043,6 +1067,8 @@
tmp = xhci_readl(xhci, addr);
tmp &= ~PORT_RWE;
xhci_writel(xhci, tmp, addr);
+ if (xhci->quirks & XHCI_PORTSC_DELAY)
+ ndelay(100);
}
}
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index e03b0bb..8467dc0 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -18,16 +18,26 @@
#include "xhci.h"
+#define SYNOPSIS_DWC3_VENDOR 0x5533
+
static struct usb_phy *phy;
static void xhci_plat_quirks(struct device *dev, struct xhci_hcd *xhci)
{
+ struct xhci_plat_data *pdata = dev->platform_data;
+
/*
* As of now platform drivers don't provide MSI support so we ensure
* here that the generic code does not try to make a pci_dev from our
* dev struct in order to setup MSI
*/
xhci->quirks |= XHCI_BROKEN_MSI;
+
+ if (!pdata)
+ return;
+ else if (pdata->vendor == SYNOPSIS_DWC3_VENDOR &&
+ pdata->revision < 0x230A)
+ xhci->quirks |= XHCI_PORTSC_DELAY;
}
/* called during probe() after chip reset completes */
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index a38fa26..2c26998 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -3722,6 +3722,8 @@
hird = xhci_calculate_hird_besl(xhci, udev);
temp = PORT_L1DS(udev->slot_id) | PORT_HIRD(hird);
xhci_writel(xhci, temp, pm_addr);
+ if (xhci->quirks & XHCI_PORTSC_DELAY)
+ ndelay(100);
/* Set port link state to U2(L1) */
addr = port_array[port_num];
@@ -3799,6 +3801,7 @@
unsigned int port_num;
unsigned long flags;
int hird;
+ bool delay;
if (hcd->speed == HCD_USB3 || !xhci->hw_lpm_support ||
!udev->lpm_capable)
@@ -3811,6 +3814,9 @@
if (udev->usb2_hw_lpm_capable != 1)
return -EPERM;
+ if (xhci->quirks & XHCI_PORTSC_DELAY)
+ delay = true;
+
spin_lock_irqsave(&xhci->lock, flags);
port_array = xhci->usb2_ports;
@@ -3827,12 +3833,18 @@
temp &= ~PORT_HIRD_MASK;
temp |= PORT_HIRD(hird) | PORT_RWE;
xhci_writel(xhci, temp, pm_addr);
+ if (delay)
+ ndelay(100);
temp = xhci_readl(xhci, pm_addr);
temp |= PORT_HLE;
xhci_writel(xhci, temp, pm_addr);
+ if (delay)
+ ndelay(100);
} else {
temp &= ~(PORT_HLE | PORT_RWE | PORT_HIRD_MASK);
xhci_writel(xhci, temp, pm_addr);
+ if (delay)
+ ndelay(100);
}
spin_unlock_irqrestore(&xhci->lock, flags);
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 3d69c4b..127b0e9 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1479,6 +1479,21 @@
#define XHCI_RESET_ON_RESUME (1 << 7)
#define XHCI_SW_BW_CHECKING (1 << 8)
#define XHCI_AMD_0x96_HOST (1 << 9)
+/*
+ * In Synopsis DWC3 controller, PORTSC register access involves multiple clock
+ * domains. When the software does a PORTSC write, handshakes are needed
+ * across these clock domains. This results in long access times, especially
+ * for USB 2.0 ports. In order to solve this issue, when the PORTSC write
+ * operations happen on the system bus, the command is latched and system bus
+ * is released immediately. However, the real PORTSC write access will take
+ * some time internally to complete. If the software quickly does a read to the
+ * PORTSC, some fields (port status change related fields like OCC, etc.) may
+ * not have correct value due to the current way of handling these bits.
+ *
+ * The workaround is to give some delay (5 mac2_clk -> UTMI clock = 60 MHz ->
+ * (16.66 ns x 5 = 84ns) ~100ns after writing to the PORTSC register.
+ */
+#define XHCI_PORTSC_DELAY (1 << 10)
unsigned int num_active_eps;
unsigned int limit_active_eps;
/* There are two roothubs to keep track of bus suspend info for */
@@ -1667,6 +1682,11 @@
static inline void xhci_unregister_pci(void) {}
#endif
+struct xhci_plat_data {
+ unsigned vendor;
+ unsigned revision;
+};
+
#if defined(CONFIG_USB_XHCI_PLATFORM) \
|| defined(CONFIG_USB_XHCI_PLATFORM_MODULE)
int xhci_register_plat(void);