Skip to content

Commit 7538e3d

Browse files
committed
PM: Add support for device power domains
The platform bus type is often used to handle Systems-on-a-Chip (SoC) where all devices are represented by objects of type struct platform_device. In those cases the same "platform" device driver may be used with multiple different system configurations, but the actions needed to put the devices it handles into a low-power state and back into the full-power state may depend on the design of the given SoC. The driver, however, cannot possibly include all the information necessary for the power management of its device on all the systems it is used with. Moreover, the device hierarchy in its current form also is not suitable for representing this kind of information. The patch below attempts to address this problem by introducing objects of type struct dev_power_domain that can be used for representing power domains within a SoC. Every struct dev_power_domain object provides a sets of device power management callbacks that can be used to perform what's needed for device power management in addition to the operations carried out by the device's driver and subsystem. Namely, if a struct dev_power_domain object is pointed to by the pwr_domain field in a struct device, the callbacks provided by its ops member will be executed in addition to the corresponding callbacks provided by the device's subsystem and driver during all power transitions. Signed-off-by: Rafael J. Wysocki <[email protected]> Tested-and-acked-by: Kevin Hilman <[email protected]>
1 parent 6831c6e commit 7538e3d

File tree

5 files changed

+107
-3
lines changed

5 files changed

+107
-3
lines changed

Documentation/power/devices.txt

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Device Power Management
22

3-
Copyright (c) 2010 Rafael J. Wysocki <[email protected]>, Novell Inc.
3+
Copyright (c) 2010-2011 Rafael J. Wysocki <[email protected]>, Novell Inc.
44
Copyright (c) 2010 Alan Stern <[email protected]>
55

66

@@ -507,6 +507,49 @@ routines. Nevertheless, different callback pointers are used in case there is a
507507
situation where it actually matters.
508508

509509

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+
510553
System Devices
511554
--------------
512555
System devices (sysdevs) follow a slightly different API, which can be found in

drivers/base/power/main.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,11 @@ static int device_resume_noirq(struct device *dev, pm_message_t state)
423423
TRACE_DEVICE(dev);
424424
TRACE_RESUME(0);
425425

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+
426431
if (dev->bus && dev->bus->pm) {
427432
pm_dev_dbg(dev, state, "EARLY ");
428433
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)
518523

519524
dev->power.in_suspend = false;
520525

526+
if (dev->pwr_domain) {
527+
pm_dev_dbg(dev, state, "power domain ");
528+
pm_op(dev, &dev->pwr_domain->ops, state);
529+
}
530+
521531
if (dev->bus) {
522532
if (dev->bus->pm) {
523533
pm_dev_dbg(dev, state, "");
@@ -629,6 +639,11 @@ static void device_complete(struct device *dev, pm_message_t state)
629639
{
630640
device_lock(dev);
631641

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+
632647
if (dev->class && dev->class->pm && dev->class->pm->complete) {
633648
pm_dev_dbg(dev, state, "completing class ");
634649
dev->class->pm->complete(dev);
@@ -745,6 +760,13 @@ static int device_suspend_noirq(struct device *dev, pm_message_t state)
745760
if (dev->bus && dev->bus->pm) {
746761
pm_dev_dbg(dev, state, "LATE ");
747762
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);
748770
}
749771

750772
End:
@@ -864,6 +886,13 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
864886
pm_dev_dbg(dev, state, "legacy ");
865887
error = legacy_suspend(dev, state, dev->bus->suspend);
866888
}
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);
867896
}
868897

869898
End:
@@ -976,7 +1005,15 @@ static int device_prepare(struct device *dev, pm_message_t state)
9761005
pm_dev_dbg(dev, state, "preparing class ");
9771006
error = dev->class->pm->prepare(dev);
9781007
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);
9791015
}
1016+
9801017
End:
9811018
device_unlock(dev);
9821019

drivers/base/power/runtime.c

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ static int rpm_check_suspend_allowed(struct device *dev)
168168
static int rpm_idle(struct device *dev, int rpmflags)
169169
{
170170
int (*callback)(struct device *);
171+
int (*domain_callback)(struct device *);
171172
int retval;
172173

173174
retval = rpm_check_suspend_allowed(dev);
@@ -222,10 +223,19 @@ static int rpm_idle(struct device *dev, int rpmflags)
222223
else
223224
callback = NULL;
224225

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) {
226232
spin_unlock_irq(&dev->power.lock);
227233

228-
callback(dev);
234+
if (domain_callback)
235+
retval = domain_callback(dev);
236+
237+
if (!retval && callback)
238+
callback(dev);
229239

230240
spin_lock_irq(&dev->power.lock);
231241
}
@@ -390,6 +400,8 @@ static int rpm_suspend(struct device *dev, int rpmflags)
390400
else
391401
pm_runtime_cancel_pending(dev);
392402
} else {
403+
if (dev->pwr_domain)
404+
rpm_callback(dev->pwr_domain->ops.runtime_suspend, dev);
393405
no_callback:
394406
__update_runtime_status(dev, RPM_SUSPENDED);
395407
pm_runtime_deactivate_timer(dev);
@@ -569,6 +581,9 @@ static int rpm_resume(struct device *dev, int rpmflags)
569581

570582
__update_runtime_status(dev, RPM_RESUMING);
571583

584+
if (dev->pwr_domain)
585+
rpm_callback(dev->pwr_domain->ops.runtime_resume, dev);
586+
572587
if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_resume)
573588
callback = dev->bus->pm->runtime_resume;
574589
else if (dev->type && dev->type->pm && dev->type->pm->runtime_resume)

include/linux/device.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@ struct device {
422422
void *platform_data; /* Platform specific data, device
423423
core doesn't touch it */
424424
struct dev_pm_info power;
425+
struct dev_power_domain *pwr_domain;
425426

426427
#ifdef CONFIG_NUMA
427428
int numa_node; /* NUMA node this device is close to */

include/linux/pm.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,14 @@ struct dev_pm_info {
465465

466466
extern void update_pm_runtime_accounting(struct device *dev);
467467

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+
};
468476

469477
/*
470478
* The PM_EVENT_ messages are also used by drivers implementing the legacy

0 commit comments

Comments
 (0)