diff options
Diffstat (limited to 'drivers/usb/core/hub.c')
| -rw-r--r-- | drivers/usb/core/hub.c | 97 | 
1 files changed, 84 insertions, 13 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 1af04bdeaf0..a815fd2cc5e 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -39,6 +39,9 @@  #endif  #endif +#define USB_VENDOR_GENESYS_LOGIC		0x05e3 +#define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND	0x01 +  struct usb_port {  	struct usb_device *child;  	struct device dev; @@ -86,6 +89,8 @@ struct usb_hub {  	unsigned		quiescing:1;  	unsigned		disconnected:1; +	unsigned		quirk_check_port_auto_suspend:1; +  	unsigned		has_indicators:1;  	u8			indicator[USB_MAXCHILDREN];  	struct delayed_work	leds; @@ -736,7 +741,6 @@ static void hub_tt_work(struct work_struct *work)  	struct usb_hub		*hub =  		container_of(work, struct usb_hub, tt.clear_work);  	unsigned long		flags; -	int			limit = 100;  	spin_lock_irqsave (&hub->tt.lock, flags);  	while (!list_empty(&hub->tt.clear_list)) { @@ -746,9 +750,6 @@ static void hub_tt_work(struct work_struct *work)  		const struct hc_driver	*drv;  		int			status; -		if (!hub->quiescing && --limit < 0) -			break; -  		next = hub->tt.clear_list.next;  		clear = list_entry (next, struct usb_tt_clear, clear_list);  		list_del (&clear->clear_list); @@ -1612,6 +1613,41 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)  	desc = intf->cur_altsetting;  	hdev = interface_to_usbdev(intf); +	/* +	 * Set default autosuspend delay as 0 to speedup bus suspend, +	 * based on the below considerations: +	 * +	 * - Unlike other drivers, the hub driver does not rely on the +	 *   autosuspend delay to provide enough time to handle a wakeup +	 *   event, and the submitted status URB is just to check future +	 *   change on hub downstream ports, so it is safe to do it. +	 * +	 * - The patch might cause one or more auto supend/resume for +	 *   below very rare devices when they are plugged into hub +	 *   first time: +	 * +	 *   	devices having trouble initializing, and disconnect +	 *   	themselves from the bus and then reconnect a second +	 *   	or so later +	 * +	 *   	devices just for downloading firmware, and disconnects +	 *   	themselves after completing it +	 * +	 *   For these quite rare devices, their drivers may change the +	 *   autosuspend delay of their parent hub in the probe() to one +	 *   appropriate value to avoid the subtle problem if someone +	 *   does care it. +	 * +	 * - The patch may cause one or more auto suspend/resume on +	 *   hub during running 'lsusb', but it is probably too +	 *   infrequent to worry about. +	 * +	 * - Change autosuspend delay of hub can avoid unnecessary auto +	 *   suspend timer for hub, also may decrease power consumption +	 *   of USB bus. +	 */ +	pm_runtime_set_autosuspend_delay(&hdev->dev, 0); +  	/* Hubs have proper suspend/resume support. */  	usb_enable_autosuspend(hdev); @@ -1670,6 +1706,9 @@ descriptor_error:  	if (hdev->speed == USB_SPEED_HIGH)  		highspeed_hubs++; +	if (id->driver_info & HUB_QUIRK_CHECK_PORT_AUTOSUSPEND) +		hub->quirk_check_port_auto_suspend = 1; +  	if (hub_configure(hub, endpoint) >= 0)  		return 0; @@ -2012,7 +2051,7 @@ static void show_string(struct usb_device *udev, char *id, char *string)  {  	if (!string)  		return; -	dev_printk(KERN_INFO, &udev->dev, "%s: %s\n", id, string); +	dev_info(&udev->dev, "%s: %s\n", id, string);  }  static void announce_device(struct usb_device *udev) @@ -2879,6 +2918,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)  				(PMSG_IS_AUTO(msg) ? "auto-" : ""),  				udev->do_remote_wakeup);  		usb_set_device_state(udev, USB_STATE_SUSPENDED); +		udev->port_is_suspended = 1;  		msleep(10);  	}  	usb_mark_last_busy(hub->hdev); @@ -3043,6 +3083,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)   SuspendCleared:  	if (status == 0) { +		udev->port_is_suspended = 0;  		if (hub_is_superspeed(hub->hdev)) {  			if (portchange & USB_PORT_STAT_C_LINK_STATE)  				clear_port_feature(hub->hdev, port1, @@ -3126,6 +3167,21 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)  #endif +static int check_ports_changed(struct usb_hub *hub) +{ +	int port1; + +	for (port1 = 1; port1 <= hub->hdev->maxchild; ++port1) { +		u16 portstatus, portchange; +		int status; + +		status = hub_port_status(hub, port1, &portstatus, &portchange); +		if (!status && portchange) +			return 1; +	} +	return 0; +} +  static int hub_suspend(struct usb_interface *intf, pm_message_t msg)  {  	struct usb_hub		*hub = usb_get_intfdata (intf); @@ -3144,6 +3200,16 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)  				return -EBUSY;  		}  	} + +	if (hdev->do_remote_wakeup && hub->quirk_check_port_auto_suspend) { +		/* check if there are changes pending on hub ports */ +		if (check_ports_changed(hub)) { +			if (PMSG_IS_AUTO(msg)) +				return -EBUSY; +			pm_wakeup_event(&hdev->dev, 2000); +		} +	} +  	if (hub_is_superspeed(hdev) && hdev->do_remote_wakeup) {  		/* Enable hub to send remote wakeup for all ports. */  		for (port1 = 1; port1 <= hdev->maxchild; port1++) { @@ -3972,6 +4038,9 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,  	if (retval)  		goto fail; +	if (hcd->phy && !hdev->parent) +		usb_phy_notify_connect(hcd->phy, udev->speed); +  	/*  	 * Some superspeed devices have finished the link training process  	 * and attached to a superspeed hub port, but the device descriptor @@ -4166,8 +4235,12 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,  	}  	/* Disconnect any existing devices under this port */ -	if (udev) +	if (udev) { +		if (hcd->phy && !hdev->parent && +				!(portstatus & USB_PORT_STAT_CONNECTION)) +			usb_phy_notify_disconnect(hcd->phy, udev->speed);  		usb_disconnect(&hub->ports[port1 - 1]->child); +	}  	clear_bit(port1, hub->change_bits);  	/* We can forget about a "removed" device when there's a physical @@ -4190,13 +4263,6 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,  		}  	} -	if (hcd->phy && !hdev->parent) { -		if (portstatus & USB_PORT_STAT_CONNECTION) -			usb_phy_notify_connect(hcd->phy, port1); -		else -			usb_phy_notify_disconnect(hcd->phy, port1); -	} -  	/* Return now if debouncing failed or nothing is connected or  	 * the device was "removed".  	 */ @@ -4648,6 +4714,11 @@ static int hub_thread(void *__unused)  }  static const struct usb_device_id hub_id_table[] = { +    { .match_flags = USB_DEVICE_ID_MATCH_VENDOR +	           | USB_DEVICE_ID_MATCH_INT_CLASS, +      .idVendor = USB_VENDOR_GENESYS_LOGIC, +      .bInterfaceClass = USB_CLASS_HUB, +      .driver_info = HUB_QUIRK_CHECK_PORT_AUTOSUSPEND},      { .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS,        .bDeviceClass = USB_CLASS_HUB},      { .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,  |