[PATCH 3/3] usb: dwc3: add a quirk for device disconnection issue in Synopsis dwc3 core

Sneeker Yeh
2014-12-16T02:11:39Z

Synopsis DesignWare USB3 IP Core integrated with a config-free
phy needs special handling during device disconnection to avoid
the host controller dying.

This quirk makes sure PORT_CSC is cleared after the disable slot
command when usb device is disconnected from internal root hub,
otherwise, Synopsis core would fall into a state that cannot use
any endpoint command. Consequently, device disconnection procedure
might not be finished because sometimes endpoint need to be stop
by endpoint stop command issuing.

Symptom usually happens when disconnected device is keyboard,
mouse, and hub.

Signed-off-by: Sneeker Yeh <Sneeker.Yeh@tw.fujitsu.com>
---
drivers/usb/host/xhci-hub.c | 4 ++++
drivers/usb/host/xhci-plat.c | 5 +++++
drivers/usb/host/xhci.c | 29 +++++++++++++++++++++++++++++
drivers/usb/host/xhci.h | 7 +++++++
4 files changed, 45 insertions(+)

diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index a7865c4..3b8f7fc 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -368,6 +368,10 @@ static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue,
port_change_bit = "warm(BH) reset";
break;
case USB_PORT_FEAT_C_CONNECTION:
+ if ((xhci->quirks & XHCI_DISCONNECT_QUIRK) &&
+ !(readl(addr) & PORT_CONNECT))
+ return;
+
status = PORT_CSC;
port_change_bit = "connect";
break;
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index 08d402b..3ede6b4 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -33,6 +33,11 @@ static void xhci_plat_quirks(struct device *dev, struct xhci_hcd *xhci)
* dev struct in order to setup MSI
*/
xhci->quirks |= XHCI_PLAT;
+
+ if (dev->parent && dev->parent->parent &&
+ of_device_is_compatible(dev->parent->parent->of_node,
+ "fujitsu,mb86s70-dwc3"))
+ xhci->quirks |= XHCI_DISCONNECT_QUIRK;
}

/* called during probe() after chip reset completes */
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 01fcbb5..50d757b 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -3584,6 +3584,33 @@ command_cleanup:
return ret;
}

+static void xhci_try_to_clear_csc(struct usb_hcd *hcd, int dev_port_num)
+{
+ int max_ports;
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ __le32 __iomem **port_array;
+ u32 status;
+
+ /* print debug info */
+ if (hcd->speed == HCD_USB3) {
+ max_ports = xhci->num_usb3_ports;
+ port_array = xhci->usb3_ports;
+ } else {
+ max_ports = xhci->num_usb2_ports;
+ port_array = xhci->usb2_ports;
+ }
+
+ if (dev_port_num > max_ports) {
+ xhci_err(xhci, "%s() port number invalid", __func__);
+ return;
+ }
+ status = readl(port_array[dev_port_num - 1]);
+
+ /* write 1 to clear */
+ if (!(status & PORT_CONNECT) && (status & PORT_CSC))
+ writel(status & PORT_CSC, port_array[0]);
+}
+
/*
* At this point, the struct usb_device is about to go away, the device has
* disconnected, and all traffic has been stopped and the endpoints have been
@@ -3649,6 +3676,8 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev)
xhci_ring_cmd_db(xhci);
spin_unlock_irqrestore(&xhci->lock, flags);

+ if (xhci->quirks & XHCI_DISCONNECT_QUIRK)
+ xhci_try_to_clear_csc(hcd, udev->portnum);
/*
* Event command completion handler will free any data structures
* associated with the slot. XXX Can free sleep?
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index cc7c5bb..e6c820c 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1560,6 +1560,13 @@ struct xhci_hcd {
#define XHCI_SPURIOUS_WAKEUP (1 << 18)
/* For controllers with a broken beyond repair streams implementation */
#define XHCI_BROKEN_STREAMS (1 << 19)
+/*
+ * Synopsis dwc3 core integrated with a config-free phy needs special
+ * handling during device disconnection to avoid the host controller
+ * dying. This quirk makes sure PORT_CSC is cleared after the disable
+ * slot command.
+ */
+#define XHCI_DISCONNECT_QUIRK (1 << 20)
unsigned int num_active_eps;
unsigned int limit_active_eps;
/* There are two roothubs to keep track of bus suspend info for */
--
1.7.9.5