diff options
-rw-r--r-- | Documentation/power/devices.txt | 45 | ||||
-rw-r--r-- | drivers/base/power/main.c | 37 | ||||
-rw-r--r-- | drivers/base/power/runtime.c | 19 | ||||
-rw-r--r-- | include/linux/device.h | 1 | ||||
-rw-r--r-- | include/linux/pm.h | 8 |
5 files changed, 107 insertions, 3 deletions
diff --git a/Documentation/power/devices.txt b/Documentation/power/devices.txt index dd9b49251db3..df1a5cb10c42 100644 --- a/Documentation/power/devices.txt +++ b/Documentation/power/devices.txt | |||
@@ -1,6 +1,6 @@ | |||
1 | Device Power Management | 1 | Device Power Management |
2 | 2 | ||
3 | Copyright (c) 2010 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. | 3 | Copyright (c) 2010-2011 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. |
4 | Copyright (c) 2010 Alan Stern <stern@rowland.harvard.edu> | 4 | Copyright (c) 2010 Alan Stern <stern@rowland.harvard.edu> |
5 | 5 | ||
6 | 6 | ||
@@ -507,6 +507,49 @@ routines. Nevertheless, different callback pointers are used in case there is a | |||
507 | situation where it actually matters. | 507 | situation where it actually matters. |
508 | 508 | ||
509 | 509 | ||
510 | Device Power Domains | ||
511 | -------------------- | ||
512 | Sometimes devices share reference clocks or other power resources. In those | ||
513 | cases it generally is not possible to put devices into low-power states | ||
514 | individually. Instead, a set of devices sharing a power resource can be put | ||
515 | into a low-power state together at the same time by turning off the shared | ||
516 | power resource. Of course, they also need to be put into the full-power state | ||
517 | together, by turning the shared power resource on. A set of devices with this | ||
518 | property is often referred to as a power domain. | ||
519 | |||
520 | Support for power domains is provided through the pwr_domain field of struct | ||
521 | device. This field is a pointer to an object of type struct dev_power_domain, | ||
522 | defined in include/linux/pm.h, providing a set of power management callbacks | ||
523 | analogous to the subsystem-level and device driver callbacks that are executed | ||
524 | for the given device during all power transitions, in addition to the respective | ||
525 | subsystem-level callbacks. Specifically, the power domain "suspend" callbacks | ||
526 | (i.e. ->runtime_suspend(), ->suspend(), ->freeze(), ->poweroff(), etc.) are | ||
527 | executed after the analogous subsystem-level callbacks, while the power domain | ||
528 | "resume" callbacks (i.e. ->runtime_resume(), ->resume(), ->thaw(), ->restore, | ||
529 | etc.) are executed before the analogous subsystem-level callbacks. Error codes | ||
530 | returned by the "suspend" and "resume" power domain callbacks are ignored. | ||
531 | |||
532 | Power domain ->runtime_idle() callback is executed before the subsystem-level | ||
533 | ->runtime_idle() callback and the result returned by it is not ignored. Namely, | ||
534 | if it returns error code, the subsystem-level ->runtime_idle() callback will not | ||
535 | be called and the helper function rpm_idle() executing it will return error | ||
536 | code. This mechanism is intended to help platforms where saving device state | ||
537 | is a time consuming operation and should only be carried out if all devices | ||
538 | in the power domain are idle, before turning off the shared power resource(s). | ||
539 | Namely, the power domain ->runtime_idle() callback may return error code until | ||
540 | the pm_runtime_idle() helper (or its asychronous version) has been called for | ||
541 | all devices in the power domain (it is recommended that the returned error code | ||
542 | be -EBUSY in those cases), preventing the subsystem-level ->runtime_idle() | ||
543 | callback from being run prematurely. | ||
544 | |||
545 | The support for device power domains is only relevant to platforms needing to | ||
546 | use the same subsystem-level (e.g. platform bus type) and device driver power | ||
547 | management callbacks in many different power domain configurations and wanting | ||
548 | to avoid incorporating the support for power domains into the subsystem-level | ||
549 | callbacks. The other platforms need not implement it or take it into account | ||
550 | in any way. | ||
551 | |||
552 | |||
510 | System Devices | 553 | System Devices |
511 | -------------- | 554 | -------------- |
512 | System devices (sysdevs) follow a slightly different API, which can be found in | 555 | System devices (sysdevs) follow a slightly different API, which can be found in |
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index f7a755923751..05b989139b54 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c | |||
@@ -423,6 +423,11 @@ static int device_resume_noirq(struct device *dev, pm_message_t state) | |||
423 | TRACE_DEVICE(dev); | 423 | TRACE_DEVICE(dev); |
424 | TRACE_RESUME(0); | 424 | TRACE_RESUME(0); |
425 | 425 | ||
426 | if (dev->pwr_domain) { | ||
427 | pm_dev_dbg(dev, state, "EARLY power domain "); | ||
428 | pm_noirq_op(dev, &dev->pwr_domain->ops, state); | ||
429 | } | ||
430 | |||
426 | if (dev->bus && dev->bus->pm) { | 431 | if (dev->bus && dev->bus->pm) { |
427 | pm_dev_dbg(dev, state, "EARLY "); | 432 | pm_dev_dbg(dev, state, "EARLY "); |
428 | error = pm_noirq_op(dev, dev->bus->pm, state); | 433 | error = pm_noirq_op(dev, dev->bus->pm, state); |
@@ -518,6 +523,11 @@ static int device_resume(struct device *dev, pm_message_t state, bool async) | |||
518 | 523 | ||
519 | dev->power.in_suspend = false; | 524 | dev->power.in_suspend = false; |
520 | 525 | ||
526 | if (dev->pwr_domain) { | ||
527 | pm_dev_dbg(dev, state, "power domain "); | ||
528 | pm_op(dev, &dev->pwr_domain->ops, state); | ||
529 | } | ||
530 | |||
521 | if (dev->bus) { | 531 | if (dev->bus) { |
522 | if (dev->bus->pm) { | 532 | if (dev->bus->pm) { |
523 | pm_dev_dbg(dev, state, ""); | 533 | pm_dev_dbg(dev, state, ""); |
@@ -629,6 +639,11 @@ static void device_complete(struct device *dev, pm_message_t state) | |||
629 | { | 639 | { |
630 | device_lock(dev); | 640 | device_lock(dev); |
631 | 641 | ||
642 | if (dev->pwr_domain && dev->pwr_domain->ops.complete) { | ||
643 | pm_dev_dbg(dev, state, "completing power domain "); | ||
644 | dev->pwr_domain->ops.complete(dev); | ||
645 | } | ||
646 | |||
632 | if (dev->class && dev->class->pm && dev->class->pm->complete) { | 647 | if (dev->class && dev->class->pm && dev->class->pm->complete) { |
633 | pm_dev_dbg(dev, state, "completing class "); | 648 | pm_dev_dbg(dev, state, "completing class "); |
634 | dev->class->pm->complete(dev); | 649 | dev->class->pm->complete(dev); |
@@ -745,6 +760,13 @@ static int device_suspend_noirq(struct device *dev, pm_message_t state) | |||
745 | if (dev->bus && dev->bus->pm) { | 760 | if (dev->bus && dev->bus->pm) { |
746 | pm_dev_dbg(dev, state, "LATE "); | 761 | pm_dev_dbg(dev, state, "LATE "); |
747 | error = pm_noirq_op(dev, dev->bus->pm, state); | 762 | error = pm_noirq_op(dev, dev->bus->pm, state); |
763 | if (error) | ||
764 | goto End; | ||
765 | } | ||
766 | |||
767 | if (dev->pwr_domain) { | ||
768 | pm_dev_dbg(dev, state, "LATE power domain "); | ||
769 | pm_noirq_op(dev, &dev->pwr_domain->ops, state); | ||
748 | } | 770 | } |
749 | 771 | ||
750 | End: | 772 | End: |
@@ -864,6 +886,13 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) | |||
864 | pm_dev_dbg(dev, state, "legacy "); | 886 | pm_dev_dbg(dev, state, "legacy "); |
865 | error = legacy_suspend(dev, state, dev->bus->suspend); | 887 | error = legacy_suspend(dev, state, dev->bus->suspend); |
866 | } | 888 | } |
889 | if (error) | ||
890 | goto End; | ||
891 | } | ||
892 | |||
893 | if (dev->pwr_domain) { | ||
894 | pm_dev_dbg(dev, state, "power domain "); | ||
895 | pm_op(dev, &dev->pwr_domain->ops, state); | ||
867 | } | 896 | } |
868 | 897 | ||
869 | End: | 898 | End: |
@@ -976,7 +1005,15 @@ static int device_prepare(struct device *dev, pm_message_t state) | |||
976 | pm_dev_dbg(dev, state, "preparing class "); | 1005 | pm_dev_dbg(dev, state, "preparing class "); |
977 | error = dev->class->pm->prepare(dev); | 1006 | error = dev->class->pm->prepare(dev); |
978 | suspend_report_result(dev->class->pm->prepare, error); | 1007 | suspend_report_result(dev->class->pm->prepare, error); |
1008 | if (error) | ||
1009 | goto End; | ||
1010 | } | ||
1011 | |||
1012 | if (dev->pwr_domain && dev->pwr_domain->ops.prepare) { | ||
1013 | pm_dev_dbg(dev, state, "preparing power domain "); | ||
1014 | dev->pwr_domain->ops.prepare(dev); | ||
979 | } | 1015 | } |
1016 | |||
980 | End: | 1017 | End: |
981 | device_unlock(dev); | 1018 | device_unlock(dev); |
982 | 1019 | ||
diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 42615b419dfb..25edc9a3a489 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c | |||
@@ -168,6 +168,7 @@ static int rpm_check_suspend_allowed(struct device *dev) | |||
168 | static int rpm_idle(struct device *dev, int rpmflags) | 168 | static int rpm_idle(struct device *dev, int rpmflags) |
169 | { | 169 | { |
170 | int (*callback)(struct device *); | 170 | int (*callback)(struct device *); |
171 | int (*domain_callback)(struct device *); | ||
171 | int retval; | 172 | int retval; |
172 | 173 | ||
173 | retval = rpm_check_suspend_allowed(dev); | 174 | retval = rpm_check_suspend_allowed(dev); |
@@ -222,10 +223,19 @@ static int rpm_idle(struct device *dev, int rpmflags) | |||
222 | else | 223 | else |
223 | callback = NULL; | 224 | callback = NULL; |
224 | 225 | ||
225 | if (callback) { | 226 | if (dev->pwr_domain) |
227 | domain_callback = dev->pwr_domain->ops.runtime_idle; | ||
228 | else | ||
229 | domain_callback = NULL; | ||
230 | |||
231 | if (callback || domain_callback) { | ||
226 | spin_unlock_irq(&dev->power.lock); | 232 | spin_unlock_irq(&dev->power.lock); |
227 | 233 | ||
228 | callback(dev); | 234 | if (domain_callback) |
235 | retval = domain_callback(dev); | ||
236 | |||
237 | if (!retval && callback) | ||
238 | callback(dev); | ||
229 | 239 | ||
230 | spin_lock_irq(&dev->power.lock); | 240 | spin_lock_irq(&dev->power.lock); |
231 | } | 241 | } |
@@ -390,6 +400,8 @@ static int rpm_suspend(struct device *dev, int rpmflags) | |||
390 | else | 400 | else |
391 | pm_runtime_cancel_pending(dev); | 401 | pm_runtime_cancel_pending(dev); |
392 | } else { | 402 | } else { |
403 | if (dev->pwr_domain) | ||
404 | rpm_callback(dev->pwr_domain->ops.runtime_suspend, dev); | ||
393 | no_callback: | 405 | no_callback: |
394 | __update_runtime_status(dev, RPM_SUSPENDED); | 406 | __update_runtime_status(dev, RPM_SUSPENDED); |
395 | pm_runtime_deactivate_timer(dev); | 407 | pm_runtime_deactivate_timer(dev); |
@@ -569,6 +581,9 @@ static int rpm_resume(struct device *dev, int rpmflags) | |||
569 | 581 | ||
570 | __update_runtime_status(dev, RPM_RESUMING); | 582 | __update_runtime_status(dev, RPM_RESUMING); |
571 | 583 | ||
584 | if (dev->pwr_domain) | ||
585 | rpm_callback(dev->pwr_domain->ops.runtime_resume, dev); | ||
586 | |||
572 | if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_resume) | 587 | if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_resume) |
573 | callback = dev->bus->pm->runtime_resume; | 588 | callback = dev->bus->pm->runtime_resume; |
574 | else if (dev->type && dev->type->pm && dev->type->pm->runtime_resume) | 589 | else if (dev->type && dev->type->pm && dev->type->pm->runtime_resume) |
diff --git a/include/linux/device.h b/include/linux/device.h index 1bf5cf0b4513..22e9a8a7e1bc 100644 --- a/include/linux/device.h +++ b/include/linux/device.h | |||
@@ -422,6 +422,7 @@ struct device { | |||
422 | void *platform_data; /* Platform specific data, device | 422 | void *platform_data; /* Platform specific data, device |
423 | core doesn't touch it */ | 423 | core doesn't touch it */ |
424 | struct dev_pm_info power; | 424 | struct dev_pm_info power; |
425 | struct dev_power_domain *pwr_domain; | ||
425 | 426 | ||
426 | #ifdef CONFIG_NUMA | 427 | #ifdef CONFIG_NUMA |
427 | int numa_node; /* NUMA node this device is close to */ | 428 | int numa_node; /* NUMA node this device is close to */ |
diff --git a/include/linux/pm.h b/include/linux/pm.h index 1f79c98f1e56..6618216bb973 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h | |||
@@ -465,6 +465,14 @@ struct dev_pm_info { | |||
465 | 465 | ||
466 | extern void update_pm_runtime_accounting(struct device *dev); | 466 | extern void update_pm_runtime_accounting(struct device *dev); |
467 | 467 | ||
468 | /* | ||
469 | * Power domains provide callbacks that are executed during system suspend, | ||
470 | * hibernation, system resume and during runtime PM transitions along with | ||
471 | * subsystem-level and driver-level callbacks. | ||
472 | */ | ||
473 | struct dev_power_domain { | ||
474 | struct dev_pm_ops ops; | ||
475 | }; | ||
468 | 476 | ||
469 | /* | 477 | /* |
470 | * The PM_EVENT_ messages are also used by drivers implementing the legacy | 478 | * The PM_EVENT_ messages are also used by drivers implementing the legacy |