diff options
Diffstat (limited to 'drivers/usb/host/xhci-hub.c')
| -rw-r--r-- | drivers/usb/host/xhci-hub.c | 66 | 
1 files changed, 49 insertions, 17 deletions
| diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 12eea73d9f20..94aca1b5ac8a 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -876,7 +876,7 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd,  			status |= USB_PORT_STAT_SUSPEND;  	}  	if ((raw_port_status & PORT_PLS_MASK) == XDEV_RESUME && -		!DEV_SUPERSPEED_ANY(raw_port_status)) { +		!DEV_SUPERSPEED_ANY(raw_port_status) && hcd->speed < HCD_USB3) {  		if ((raw_port_status & PORT_RESET) ||  				!(raw_port_status & PORT_PE))  			return 0xffffffff; @@ -921,7 +921,7 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd,  			time_left = wait_for_completion_timeout(  					&bus_state->rexit_done[wIndex],  					msecs_to_jiffies( -						XHCI_MAX_REXIT_TIMEOUT)); +						XHCI_MAX_REXIT_TIMEOUT_MS));  			spin_lock_irqsave(&xhci->lock, flags);  			if (time_left) { @@ -935,7 +935,7 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd,  			} else {  				int port_status = readl(port->addr);  				xhci_warn(xhci, "Port resume took longer than %i msec, port status = 0x%x\n", -						XHCI_MAX_REXIT_TIMEOUT, +						XHCI_MAX_REXIT_TIMEOUT_MS,  						port_status);  				status |= USB_PORT_STAT_SUSPEND;  				clear_bit(wIndex, &bus_state->rexit_ports); @@ -1474,15 +1474,18 @@ int xhci_bus_suspend(struct usb_hcd *hcd)  	unsigned long flags;  	struct xhci_hub *rhub;  	struct xhci_port **ports; +	u32 portsc_buf[USB_MAXCHILDREN]; +	bool wake_enabled;  	rhub = xhci_get_rhub(hcd);  	ports = rhub->ports;  	max_ports = rhub->num_ports;  	bus_state = &xhci->bus_state[hcd_index(hcd)]; +	wake_enabled = hcd->self.root_hub->do_remote_wakeup;  	spin_lock_irqsave(&xhci->lock, flags); -	if (hcd->self.root_hub->do_remote_wakeup) { +	if (wake_enabled) {  		if (bus_state->resuming_ports ||	/* USB2 */  		    bus_state->port_remote_wakeup) {	/* USB3 */  			spin_unlock_irqrestore(&xhci->lock, flags); @@ -1490,26 +1493,36 @@ int xhci_bus_suspend(struct usb_hcd *hcd)  			return -EBUSY;  		}  	} - -	port_index = max_ports; +	/* +	 * Prepare ports for suspend, but don't write anything before all ports +	 * are checked and we know bus suspend can proceed +	 */  	bus_state->bus_suspended = 0; +	port_index = max_ports;  	while (port_index--) { -		/* suspend the port if the port is not suspended */  		u32 t1, t2; -		int slot_id;  		t1 = readl(ports[port_index]->addr);  		t2 = xhci_port_state_to_neutral(t1); +		portsc_buf[port_index] = 0; -		if ((t1 & PORT_PE) && !(t1 & PORT_PLS_MASK)) { -			xhci_dbg(xhci, "port %d not suspended\n", port_index); -			slot_id = xhci_find_slot_id_by_port(hcd, xhci, -					port_index + 1); -			if (slot_id) { +		/* Bail out if a USB3 port has a new device in link training */ +		if ((t1 & PORT_PLS_MASK) == XDEV_POLLING) { +			bus_state->bus_suspended = 0; +			spin_unlock_irqrestore(&xhci->lock, flags); +			xhci_dbg(xhci, "Bus suspend bailout, port in polling\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) { +				bus_state->bus_suspended = 0;  				spin_unlock_irqrestore(&xhci->lock, flags); -				xhci_stop_device(xhci, slot_id, 1); -				spin_lock_irqsave(&xhci->lock, flags); +				xhci_dbg(xhci, "Bus suspend bailout, port connect change\n"); +				return -EBUSY;  			} +			xhci_dbg(xhci, "port %d not suspended\n", port_index);  			t2 &= ~PORT_PLS_MASK;  			t2 |= PORT_LINK_STROBE | XDEV_U3;  			set_bit(port_index, &bus_state->bus_suspended); @@ -1518,7 +1531,7 @@ int xhci_bus_suspend(struct usb_hcd *hcd)  		 * including the USB 3.0 roothub, but only if CONFIG_PM  		 * is enabled, so also enable remote wake here.  		 */ -		if (hcd->self.root_hub->do_remote_wakeup) { +		if (wake_enabled) {  			if (t1 & PORT_CONNECT) {  				t2 |= PORT_WKOC_E | PORT_WKDISC_E;  				t2 &= ~PORT_WKCONN_E; @@ -1538,7 +1551,26 @@ int xhci_bus_suspend(struct usb_hcd *hcd)  		t1 = xhci_port_state_to_neutral(t1);  		if (t1 != t2) -			writel(t2, ports[port_index]->addr); +			portsc_buf[port_index] = t2; +	} + +	/* write port settings, stopping and suspending ports if needed */ +	port_index = max_ports; +	while (port_index--) { +		if (!portsc_buf[port_index]) +			continue; +		if (test_bit(port_index, &bus_state->bus_suspended)) { +			int slot_id; + +			slot_id = xhci_find_slot_id_by_port(hcd, xhci, +							    port_index + 1); +			if (slot_id) { +				spin_unlock_irqrestore(&xhci->lock, flags); +				xhci_stop_device(xhci, slot_id, 1); +				spin_lock_irqsave(&xhci->lock, flags); +			} +		} +		writel(portsc_buf[port_index], ports[port_index]->addr);  	}  	hcd->state = HC_STATE_SUSPENDED;  	bus_state->next_statechange = jiffies + msecs_to_jiffies(10); | 
