aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/host/xhci-hub.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/xhci-hub.c')
-rw-r--r--drivers/usb/host/xhci-hub.c72
1 files changed, 62 insertions, 10 deletions
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index af92b2576fe9..f37316d2c8fa 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -567,6 +567,7 @@ struct xhci_hub *xhci_get_rhub(struct usb_hcd *hcd)
*/
static void xhci_set_port_power(struct xhci_hcd *xhci, struct usb_hcd *hcd,
u16 index, bool on, unsigned long *flags)
+ __must_hold(&xhci->lock)
{
struct xhci_hub *rhub;
struct xhci_port *port;
@@ -617,6 +618,7 @@ static void xhci_port_set_test_mode(struct xhci_hcd *xhci,
static int xhci_enter_test_mode(struct xhci_hcd *xhci,
u16 test_mode, u16 wIndex, unsigned long *flags)
+ __must_hold(&xhci->lock)
{
int i, retval;
@@ -1306,7 +1308,47 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
wIndex, link_state);
goto error;
}
+
+ /*
+ * set link to U0, steps depend on current link state.
+ * U3: set link to U0 and wait for u3exit completion.
+ * U1/U2: no PLC complete event, only set link to U0.
+ * Resume/Recovery: device initiated U0, only wait for
+ * completion
+ */
+ if (link_state == USB_SS_PORT_LS_U0) {
+ u32 pls = temp & PORT_PLS_MASK;
+ bool wait_u0 = false;
+
+ /* already in U0 */
+ if (pls == XDEV_U0)
+ break;
+ if (pls == XDEV_U3 ||
+ pls == XDEV_RESUME ||
+ pls == XDEV_RECOVERY) {
+ wait_u0 = true;
+ reinit_completion(&bus_state->u3exit_done[wIndex]);
+ }
+ if (pls <= XDEV_U3) /* U1, U2, U3 */
+ xhci_set_link_state(xhci, ports[wIndex],
+ USB_SS_PORT_LS_U0);
+ if (!wait_u0) {
+ if (pls > XDEV_U3)
+ goto error;
+ break;
+ }
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ if (!wait_for_completion_timeout(&bus_state->u3exit_done[wIndex],
+ msecs_to_jiffies(100)))
+ xhci_dbg(xhci, "missing U0 port change event for port %d\n",
+ wIndex);
+ spin_lock_irqsave(&xhci->lock, flags);
+ temp = readl(ports[wIndex]->addr);
+ break;
+ }
+
if (link_state == USB_SS_PORT_LS_U3) {
+ int retries = 16;
slot_id = xhci_find_slot_id_by_port(hcd, xhci,
wIndex + 1);
if (slot_id) {
@@ -1317,17 +1359,18 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
xhci_stop_device(xhci, slot_id, 1);
spin_lock_irqsave(&xhci->lock, flags);
}
- }
-
- xhci_set_link_state(xhci, ports[wIndex], link_state);
-
- spin_unlock_irqrestore(&xhci->lock, flags);
- msleep(20); /* wait device to enter */
- spin_lock_irqsave(&xhci->lock, flags);
-
- temp = readl(ports[wIndex]->addr);
- if (link_state == USB_SS_PORT_LS_U3)
+ xhci_set_link_state(xhci, ports[wIndex], USB_SS_PORT_LS_U3);
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ while (retries--) {
+ usleep_range(4000, 8000);
+ temp = readl(ports[wIndex]->addr);
+ if ((temp & PORT_PLS_MASK) == XDEV_U3)
+ break;
+ }
+ spin_lock_irqsave(&xhci->lock, flags);
+ temp = readl(ports[wIndex]->addr);
bus_state->suspended_ports |= 1 << wIndex;
+ }
break;
case USB_PORT_FEAT_POWER:
/*
@@ -1528,6 +1571,8 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf)
}
if ((temp & PORT_RC))
reset_change = true;
+ if (temp & PORT_OC)
+ status = 1;
}
if (!status && !reset_change) {
xhci_dbg(xhci, "%s: stopping port polling.\n", __func__);
@@ -1593,6 +1638,13 @@ retry:
port_index);
goto retry;
}
+ /* bail out if port detected a over-current condition */
+ if (t1 & PORT_OC) {
+ bus_state->bus_suspended = 0;
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ xhci_dbg(xhci, "Bus suspend bailout, port over-current detected\n");
+ return -EBUSY;
+ }
/* suspend ports in U0, or bail out for new connect changes */
if ((t1 & PORT_PE) && (t1 & PORT_PLS_MASK) == XDEV_U0) {
if ((t1 & PORT_CSC) && wake_enabled) {