diff options
Diffstat (limited to 'drivers/usb/core/hub.c')
| -rw-r--r-- | drivers/usb/core/hub.c | 39 | 
1 files changed, 37 insertions, 2 deletions
| diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 770d1e91183c..256fe8c86828 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -68,6 +68,12 @@   */  #define USB_SHORT_SET_ADDRESS_REQ_TIMEOUT	500  /* ms */ +/* + * Give SS hubs 200ms time after wake to train downstream links before + * assuming no port activity and allowing hub to runtime suspend back. + */ +#define USB_SS_PORT_U0_WAKE_TIME	200  /* ms */ +  /* Protect struct usb_device->state and ->children members   * Note: Both are also protected by ->dev.sem, except that ->state can   * change to USB_STATE_NOTATTACHED even when the semaphore isn't held. */ @@ -1095,6 +1101,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)  			goto init2;  		goto init3;  	} +  	hub_get(hub);  	/* The superspeed hub except for root hub has to use Hub Depth @@ -1343,6 +1350,17 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)  		device_unlock(&hdev->dev);  	} +	if (type == HUB_RESUME && hub_is_superspeed(hub->hdev)) { +		/* give usb3 downstream links training time after hub resume */ +		usb_autopm_get_interface_no_resume( +			to_usb_interface(hub->intfdev)); + +		queue_delayed_work(system_power_efficient_wq, +				   &hub->post_resume_work, +				   msecs_to_jiffies(USB_SS_PORT_U0_WAKE_TIME)); +		return; +	} +  	hub_put(hub);  } @@ -1361,6 +1379,14 @@ static void hub_init_func3(struct work_struct *ws)  	hub_activate(hub, HUB_INIT3);  } +static void hub_post_resume(struct work_struct *ws) +{ +	struct usb_hub *hub = container_of(ws, struct usb_hub, post_resume_work.work); + +	usb_autopm_put_interface_async(to_usb_interface(hub->intfdev)); +	hub_put(hub); +} +  enum hub_quiescing_type {  	HUB_DISCONNECT, HUB_PRE_RESET, HUB_SUSPEND  }; @@ -1386,6 +1412,7 @@ static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type)  	/* Stop hub_wq and related activity */  	timer_delete_sync(&hub->irq_urb_retry); +	flush_delayed_work(&hub->post_resume_work);  	usb_kill_urb(hub->urb);  	if (hub->has_indicators)  		cancel_delayed_work_sync(&hub->leds); @@ -1944,6 +1971,7 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)  	hub->hdev = hdev;  	INIT_DELAYED_WORK(&hub->leds, led_work);  	INIT_DELAYED_WORK(&hub->init_work, NULL); +	INIT_DELAYED_WORK(&hub->post_resume_work, hub_post_resume);  	INIT_WORK(&hub->events, hub_event);  	INIT_LIST_HEAD(&hub->onboard_devs);  	spin_lock_init(&hub->irq_urb_lock); @@ -2337,6 +2365,9 @@ void usb_disconnect(struct usb_device **pdev)  	usb_remove_ep_devs(&udev->ep0);  	usb_unlock_device(udev); +	if (udev->usb4_link) +		device_link_del(udev->usb4_link); +  	/* Unregister the device.  The device driver is responsible  	 * for de-configuring the device and invoking the remove-device  	 * notifier chain (used by usbfs and possibly others). @@ -5720,6 +5751,7 @@ static void port_event(struct usb_hub *hub, int port1)  	struct usb_device *hdev = hub->hdev;  	u16 portstatus, portchange;  	int i = 0; +	int err;  	connect_change = test_bit(port1, hub->change_bits);  	clear_bit(port1, hub->event_bits); @@ -5816,8 +5848,11 @@ static void port_event(struct usb_hub *hub, int port1)  		} else if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION)  				|| udev->state == USB_STATE_NOTATTACHED) {  			dev_dbg(&port_dev->dev, "do warm reset, port only\n"); -			if (hub_port_reset(hub, port1, NULL, -					HUB_BH_RESET_TIME, true) < 0) +			err = hub_port_reset(hub, port1, NULL, +					     HUB_BH_RESET_TIME, true); +			if (!udev && err == -ENOTCONN) +				connect_change = 0; +			else if (err < 0)  				hub_port_disable(hub, port1, 1);  		} else {  			dev_dbg(&port_dev->dev, "do warm reset, full device\n"); | 
