diff options
Diffstat (limited to 'drivers/usb/core/driver.c')
| -rw-r--r-- | drivers/usb/core/driver.c | 50 | 
1 files changed, 43 insertions, 7 deletions
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index f3c233806fa..2f3dc4cdf79 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -23,6 +23,7 @@   */  #include <linux/device.h> +#include <linux/slab.h>  #include <linux/usb.h>  #include <linux/usb/quirks.h>  #include <linux/pm_runtime.h> @@ -300,7 +301,7 @@ static int usb_probe_interface(struct device *dev)  	intf->condition = USB_INTERFACE_BINDING; -	/* Bound interfaces are initially active.  They are +	/* Probed interfaces are initially active.  They are  	 * runtime-PM-enabled only if the driver has autosuspend support.  	 * They are sensitive to their children's power states.  	 */ @@ -436,11 +437,11 @@ int usb_driver_claim_interface(struct usb_driver *driver,  	iface->condition = USB_INTERFACE_BOUND; -	/* Bound interfaces are initially active.  They are +	/* Claimed interfaces are initially inactive (suspended).  They are  	 * runtime-PM-enabled only if the driver has autosuspend support.  	 * They are sensitive to their children's power states.  	 */ -	pm_runtime_set_active(dev); +	pm_runtime_set_suspended(dev);  	pm_suspend_ignore_children(dev, false);  	if (driver->supports_autosuspend)  		pm_runtime_enable(dev); @@ -1169,7 +1170,7 @@ done:  static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)  {  	int			status = 0; -	int			i = 0; +	int			i = 0, n = 0;  	struct usb_interface	*intf;  	if (udev->state == USB_STATE_NOTATTACHED || @@ -1178,7 +1179,8 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)  	/* Suspend all the interfaces and then udev itself */  	if (udev->actconfig) { -		for (; i < udev->actconfig->desc.bNumInterfaces; i++) { +		n = udev->actconfig->desc.bNumInterfaces; +		for (i = n - 1; i >= 0; --i) {  			intf = udev->actconfig->interface[i];  			status = usb_suspend_interface(udev, intf, msg);  			if (status != 0) @@ -1191,7 +1193,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)  	/* If the suspend failed, resume interfaces that did get suspended */  	if (status != 0) {  		msg.event ^= (PM_EVENT_SUSPEND | PM_EVENT_RESUME); -		while (--i >= 0) { +		while (++i < n) {  			intf = udev->actconfig->interface[i];  			usb_resume_interface(udev, intf, msg, 0);  		} @@ -1262,13 +1264,47 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg)  	return status;  } +static void choose_wakeup(struct usb_device *udev, pm_message_t msg) +{ +	int			w, i; +	struct usb_interface	*intf; + +	/* Remote wakeup is needed only when we actually go to sleep. +	 * For things like FREEZE and QUIESCE, if the device is already +	 * autosuspended then its current wakeup setting is okay. +	 */ +	if (msg.event == PM_EVENT_FREEZE || msg.event == PM_EVENT_QUIESCE) { +		if (udev->state != USB_STATE_SUSPENDED) +			udev->do_remote_wakeup = 0; +		return; +	} + +	/* If remote wakeup is permitted, see whether any interface drivers +	 * actually want it. +	 */ +	w = 0; +	if (device_may_wakeup(&udev->dev) && udev->actconfig) { +		for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { +			intf = udev->actconfig->interface[i]; +			w |= intf->needs_remote_wakeup; +		} +	} + +	/* If the device is autosuspended with the wrong wakeup setting, +	 * autoresume now so the setting can be changed. +	 */ +	if (udev->state == USB_STATE_SUSPENDED && w != udev->do_remote_wakeup) +		pm_runtime_resume(&udev->dev); +	udev->do_remote_wakeup = w; +} +  /* The device lock is held by the PM core */  int usb_suspend(struct device *dev, pm_message_t msg)  {  	struct usb_device	*udev = to_usb_device(dev);  	do_unbind_rebind(udev, DO_UNBIND); -	udev->do_remote_wakeup = device_may_wakeup(&udev->dev); +	choose_wakeup(udev, msg);  	return usb_suspend_both(udev, msg);  }  |