diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2010-01-08 12:56:54 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2010-03-02 17:54:10 -0500 |
commit | 088f7fec8a0e683db72fd8826c5d3ab914e197b1 (patch) | |
tree | 971ef100d0db9727e4403865b67c72de62e8247a | |
parent | 0c4db6df915bc470f0cd32fe48287fa6eb6adfb4 (diff) |
USB: implement usb_enable_autosuspend
This patch (as1326) adds usb_enable_autosuspend() and
usb_disable_autosuspend() routines for use by drivers. If a driver
knows that its device can handle suspends and resumes correctly, it
can enable autosuspend all by itself. This is equivalent to the user
writing "auto" to the device's power/level attribute.
The implementation differs slightly from what it used to be. Now
autosuspend is disabled simply by doing usb_autoresume_device() (to
increment the usage counter) and enabled by doing
usb_autosuspend_device() (to decrement the usage counter).
The set_level() attribute method is updated to use the new routines,
and the USB Power-Management documentation is updated.
The patch adds a usb_enable_autosuspend() call to the hub driver's
probe routine, allowing the special-case code for hubs in quirks.c to
be removed.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r-- | Documentation/usb/power-management.txt | 18 | ||||
-rw-r--r-- | drivers/usb/core/driver.c | 42 | ||||
-rw-r--r-- | drivers/usb/core/hub.c | 3 | ||||
-rw-r--r-- | drivers/usb/core/quirks.c | 9 | ||||
-rw-r--r-- | drivers/usb/core/sysfs.c | 23 | ||||
-rw-r--r-- | include/linux/usb.h | 8 |
6 files changed, 82 insertions, 21 deletions
diff --git a/Documentation/usb/power-management.txt b/Documentation/usb/power-management.txt index 3bf6818c8cf5..e3fa189c257a 100644 --- a/Documentation/usb/power-management.txt +++ b/Documentation/usb/power-management.txt | |||
@@ -229,6 +229,11 @@ necessary operations by hand or add them to a udev script. You can | |||
229 | also change the idle-delay time; 2 seconds is not the best choice for | 229 | also change the idle-delay time; 2 seconds is not the best choice for |
230 | every device. | 230 | every device. |
231 | 231 | ||
232 | If a driver knows that its device has proper suspend/resume support, | ||
233 | it can enable autosuspend all by itself. For example, the video | ||
234 | driver for a laptop's webcam might do this, since these devices are | ||
235 | rarely used and so should normally be autosuspended. | ||
236 | |||
232 | Sometimes it turns out that even when a device does work okay with | 237 | Sometimes it turns out that even when a device does work okay with |
233 | autosuspend there are still problems. For example, there are | 238 | autosuspend there are still problems. For example, there are |
234 | experimental patches adding autosuspend support to the usbhid driver, | 239 | experimental patches adding autosuspend support to the usbhid driver, |
@@ -384,6 +389,19 @@ autosuspend, there's no delay for an autoresume. | |||
384 | Other parts of the driver interface | 389 | Other parts of the driver interface |
385 | ----------------------------------- | 390 | ----------------------------------- |
386 | 391 | ||
392 | Drivers can enable autosuspend for their devices by calling | ||
393 | |||
394 | usb_enable_autosuspend(struct usb_device *udev); | ||
395 | |||
396 | in their probe() routine, if they know that the device is capable of | ||
397 | suspending and resuming correctly. This is exactly equivalent to | ||
398 | writing "auto" to the device's power/level attribute. Likewise, | ||
399 | drivers can disable autosuspend by calling | ||
400 | |||
401 | usb_disable_autosuspend(struct usb_device *udev); | ||
402 | |||
403 | This is exactly the same as writing "on" to the power/level attribute. | ||
404 | |||
387 | Sometimes a driver needs to make sure that remote wakeup is enabled | 405 | Sometimes a driver needs to make sure that remote wakeup is enabled |
388 | during autosuspend. For example, there's not much point | 406 | during autosuspend. For example, there's not much point |
389 | autosuspending a keyboard if the user can't cause the keyboard to do a | 407 | autosuspending a keyboard if the user can't cause the keyboard to do a |
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 2b39583040d0..057eeab06004 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c | |||
@@ -1415,6 +1415,48 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg) | |||
1415 | 1415 | ||
1416 | #ifdef CONFIG_USB_SUSPEND | 1416 | #ifdef CONFIG_USB_SUSPEND |
1417 | 1417 | ||
1418 | /** | ||
1419 | * usb_enable_autosuspend - allow a USB device to be autosuspended | ||
1420 | * @udev: the USB device which may be autosuspended | ||
1421 | * | ||
1422 | * This routine allows @udev to be autosuspended. An autosuspend won't | ||
1423 | * take place until the autosuspend_delay has elapsed and all the other | ||
1424 | * necessary conditions are satisfied. | ||
1425 | * | ||
1426 | * The caller must hold @udev's device lock. | ||
1427 | */ | ||
1428 | int usb_enable_autosuspend(struct usb_device *udev) | ||
1429 | { | ||
1430 | if (udev->autosuspend_disabled) { | ||
1431 | udev->autosuspend_disabled = 0; | ||
1432 | usb_autosuspend_device(udev); | ||
1433 | } | ||
1434 | return 0; | ||
1435 | } | ||
1436 | EXPORT_SYMBOL_GPL(usb_enable_autosuspend); | ||
1437 | |||
1438 | /** | ||
1439 | * usb_disable_autosuspend - prevent a USB device from being autosuspended | ||
1440 | * @udev: the USB device which may not be autosuspended | ||
1441 | * | ||
1442 | * This routine prevents @udev from being autosuspended and wakes it up | ||
1443 | * if it is already autosuspended. | ||
1444 | * | ||
1445 | * The caller must hold @udev's device lock. | ||
1446 | */ | ||
1447 | int usb_disable_autosuspend(struct usb_device *udev) | ||
1448 | { | ||
1449 | int rc = 0; | ||
1450 | |||
1451 | if (!udev->autosuspend_disabled) { | ||
1452 | rc = usb_autoresume_device(udev); | ||
1453 | if (rc == 0) | ||
1454 | udev->autosuspend_disabled = 1; | ||
1455 | } | ||
1456 | return rc; | ||
1457 | } | ||
1458 | EXPORT_SYMBOL_GPL(usb_disable_autosuspend); | ||
1459 | |||
1418 | /* Internal routine to adjust a device's usage counter and change | 1460 | /* Internal routine to adjust a device's usage counter and change |
1419 | * its autosuspend state. | 1461 | * its autosuspend state. |
1420 | */ | 1462 | */ |
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index bfa6123bbdb5..746f26f222ab 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c | |||
@@ -1224,6 +1224,9 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) | |||
1224 | desc = intf->cur_altsetting; | 1224 | desc = intf->cur_altsetting; |
1225 | hdev = interface_to_usbdev(intf); | 1225 | hdev = interface_to_usbdev(intf); |
1226 | 1226 | ||
1227 | /* Hubs have proper suspend/resume support */ | ||
1228 | usb_enable_autosuspend(hdev); | ||
1229 | |||
1227 | if (hdev->level == MAX_TOPO_LEVEL) { | 1230 | if (hdev->level == MAX_TOPO_LEVEL) { |
1228 | dev_err(&intf->dev, | 1231 | dev_err(&intf->dev, |
1229 | "Unsupported bus topology: hub nested too deep\n"); | 1232 | "Unsupported bus topology: hub nested too deep\n"); |
diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 0b689224394b..4314f259524b 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c | |||
@@ -103,11 +103,10 @@ void usb_detect_quirks(struct usb_device *udev) | |||
103 | dev_dbg(&udev->dev, "USB quirks for this device: %x\n", | 103 | dev_dbg(&udev->dev, "USB quirks for this device: %x\n", |
104 | udev->quirks); | 104 | udev->quirks); |
105 | 105 | ||
106 | /* By default, disable autosuspend for all non-hubs */ | 106 | /* By default, disable autosuspend for all devices. The hub driver |
107 | #ifdef CONFIG_USB_SUSPEND | 107 | * will enable it for hubs. |
108 | if (udev->descriptor.bDeviceClass != USB_CLASS_HUB) | 108 | */ |
109 | udev->autosuspend_disabled = 1; | 109 | usb_disable_autosuspend(udev); |
110 | #endif | ||
111 | 110 | ||
112 | /* For the present, all devices default to USB-PERSIST enabled */ | 111 | /* For the present, all devices default to USB-PERSIST enabled */ |
113 | #if 0 /* was: #ifdef CONFIG_PM */ | 112 | #if 0 /* was: #ifdef CONFIG_PM */ |
diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 5a1a0e2b6474..313e241f5ccc 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c | |||
@@ -389,34 +389,25 @@ set_level(struct device *dev, struct device_attribute *attr, | |||
389 | struct usb_device *udev = to_usb_device(dev); | 389 | struct usb_device *udev = to_usb_device(dev); |
390 | int len = count; | 390 | int len = count; |
391 | char *cp; | 391 | char *cp; |
392 | int rc = 0; | 392 | int rc; |
393 | int old_autosuspend_disabled; | ||
394 | 393 | ||
395 | cp = memchr(buf, '\n', count); | 394 | cp = memchr(buf, '\n', count); |
396 | if (cp) | 395 | if (cp) |
397 | len = cp - buf; | 396 | len = cp - buf; |
398 | 397 | ||
399 | usb_lock_device(udev); | 398 | usb_lock_device(udev); |
400 | old_autosuspend_disabled = udev->autosuspend_disabled; | ||
401 | 399 | ||
402 | /* Setting the flags without calling usb_pm_lock is a subject to | ||
403 | * races, but who cares... | ||
404 | */ | ||
405 | if (len == sizeof on_string - 1 && | 400 | if (len == sizeof on_string - 1 && |
406 | strncmp(buf, on_string, len) == 0) { | 401 | strncmp(buf, on_string, len) == 0) |
407 | udev->autosuspend_disabled = 1; | 402 | rc = usb_disable_autosuspend(udev); |
408 | rc = usb_external_resume_device(udev, PMSG_USER_RESUME); | ||
409 | 403 | ||
410 | } else if (len == sizeof auto_string - 1 && | 404 | else if (len == sizeof auto_string - 1 && |
411 | strncmp(buf, auto_string, len) == 0) { | 405 | strncmp(buf, auto_string, len) == 0) |
412 | udev->autosuspend_disabled = 0; | 406 | rc = usb_enable_autosuspend(udev); |
413 | rc = usb_external_resume_device(udev, PMSG_USER_RESUME); | ||
414 | 407 | ||
415 | } else | 408 | else |
416 | rc = -EINVAL; | 409 | rc = -EINVAL; |
417 | 410 | ||
418 | if (rc) | ||
419 | udev->autosuspend_disabled = old_autosuspend_disabled; | ||
420 | usb_unlock_device(udev); | 411 | usb_unlock_device(udev); |
421 | return (rc < 0 ? rc : count); | 412 | return (rc < 0 ? rc : count); |
422 | } | 413 | } |
diff --git a/include/linux/usb.h b/include/linux/usb.h index 332eaea61021..e6419ac89ea2 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h | |||
@@ -542,6 +542,9 @@ extern struct usb_device *usb_find_device(u16 vendor_id, u16 product_id); | |||
542 | 542 | ||
543 | /* USB autosuspend and autoresume */ | 543 | /* USB autosuspend and autoresume */ |
544 | #ifdef CONFIG_USB_SUSPEND | 544 | #ifdef CONFIG_USB_SUSPEND |
545 | extern int usb_enable_autosuspend(struct usb_device *udev); | ||
546 | extern int usb_disable_autosuspend(struct usb_device *udev); | ||
547 | |||
545 | extern int usb_autopm_get_interface(struct usb_interface *intf); | 548 | extern int usb_autopm_get_interface(struct usb_interface *intf); |
546 | extern void usb_autopm_put_interface(struct usb_interface *intf); | 549 | extern void usb_autopm_put_interface(struct usb_interface *intf); |
547 | extern int usb_autopm_get_interface_async(struct usb_interface *intf); | 550 | extern int usb_autopm_get_interface_async(struct usb_interface *intf); |
@@ -565,6 +568,11 @@ static inline void usb_mark_last_busy(struct usb_device *udev) | |||
565 | 568 | ||
566 | #else | 569 | #else |
567 | 570 | ||
571 | static inline int usb_enable_autosuspend(struct usb_device *udev) | ||
572 | { return 0; } | ||
573 | static inline int usb_disable_autosuspend(struct usb_device *udev) | ||
574 | { return 0; } | ||
575 | |||
568 | static inline int usb_autopm_get_interface(struct usb_interface *intf) | 576 | static inline int usb_autopm_get_interface(struct usb_interface *intf) |
569 | { return 0; } | 577 | { return 0; } |
570 | static inline int usb_autopm_get_interface_async(struct usb_interface *intf) | 578 | static inline int usb_autopm_get_interface_async(struct usb_interface *intf) |