summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/power/runtime_pm.txt37
-rw-r--r--drivers/base/power/power.h1
-rw-r--r--drivers/base/power/runtime.c54
-rw-r--r--drivers/base/power/sysfs.c56
-rw-r--r--include/linux/pm.h7
-rw-r--r--include/linux/pm_runtime.h2
6 files changed, 149 insertions, 8 deletions
diff --git a/Documentation/power/runtime_pm.txt b/Documentation/power/runtime_pm.txt
index 55b859b3bc72..9ba49b21ac86 100644
--- a/Documentation/power/runtime_pm.txt
+++ b/Documentation/power/runtime_pm.txt
@@ -1,6 +1,7 @@
Run-time Power Management Framework for I/O Devices
(C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
+(C) 2010 Alan Stern <stern@rowland.harvard.edu>
1. Introduction
@@ -230,6 +231,11 @@ defined in include/linux/pm.h:
interface; it may only be modified with the help of the pm_runtime_allow()
and pm_runtime_forbid() helper functions
+ unsigned int no_callbacks;
+ - indicates that the device does not use the run-time PM callbacks (see
+ Section 8); it may be modified only by the pm_runtime_no_callbacks()
+ helper function
+
All of the above fields are members of the 'power' member of 'struct device'.
4. Run-time PM Device Helper Functions
@@ -349,6 +355,11 @@ drivers/base/power/runtime.c and include/linux/pm_runtime.h:
counter (used by the /sys/devices/.../power/control interface to
effectively prevent the device from being power managed at run time)
+ void pm_runtime_no_callbacks(struct device *dev);
+ - set the power.no_callbacks flag for the device and remove the run-time
+ PM attributes from /sys/devices/.../power (or prevent them from being
+ added when the device is registered)
+
It is safe to execute the following helper functions from interrupt context:
pm_request_idle()
@@ -524,3 +535,29 @@ poweroff and run-time suspend callback, and similarly for system resume, thaw,
restore, and run-time resume, can achieve this with the help of the
UNIVERSAL_DEV_PM_OPS macro defined in include/linux/pm.h (possibly setting its
last argument to NULL).
+
+8. "No-Callback" Devices
+
+Some "devices" are only logical sub-devices of their parent and cannot be
+power-managed on their own. (The prototype example is a USB interface. Entire
+USB devices can go into low-power mode or send wake-up requests, but neither is
+possible for individual interfaces.) The drivers for these devices have no
+need of run-time PM callbacks; if the callbacks did exist, ->runtime_suspend()
+and ->runtime_resume() would always return 0 without doing anything else and
+->runtime_idle() would always call pm_runtime_suspend().
+
+Subsystems can tell the PM core about these devices by calling
+pm_runtime_no_callbacks(). This should be done after the device structure is
+initialized and before it is registered (although after device registration is
+also okay). The routine will set the device's power.no_callbacks flag and
+prevent the non-debugging run-time PM sysfs attributes from being created.
+
+When power.no_callbacks is set, the PM core will not invoke the
+->runtime_idle(), ->runtime_suspend(), or ->runtime_resume() callbacks.
+Instead it will assume that suspends and resumes always succeed and that idle
+devices should be suspended.
+
+As a consequence, the PM core will never directly inform the device's subsystem
+or driver about run-time power changes. Instead, the driver for the device's
+parent must take responsibility for telling the device's driver when the
+parent's power state changes.
diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h
index 8b2745c69e2a..698dde742587 100644
--- a/drivers/base/power/power.h
+++ b/drivers/base/power/power.h
@@ -60,6 +60,7 @@ static inline void device_pm_move_last(struct device *dev) {}
extern int dpm_sysfs_add(struct device *);
extern void dpm_sysfs_remove(struct device *);
+extern void rpm_sysfs_remove(struct device *);
#else /* CONFIG_PM */
diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
index ed227b7c1bb5..5bd4daa93ef1 100644
--- a/drivers/base/power/runtime.c
+++ b/drivers/base/power/runtime.c
@@ -10,8 +10,10 @@
#include <linux/sched.h>
#include <linux/pm_runtime.h>
#include <linux/jiffies.h>
+#include "power.h"
static int rpm_resume(struct device *dev, int rpmflags);
+static int rpm_suspend(struct device *dev, int rpmflags);
/**
* update_pm_runtime_accounting - Update the time accounting of power states
@@ -148,6 +150,12 @@ static int rpm_idle(struct device *dev, int rpmflags)
/* Pending requests need to be canceled. */
dev->power.request = RPM_REQ_NONE;
+ if (dev->power.no_callbacks) {
+ /* Assume ->runtime_idle() callback would have suspended. */
+ retval = rpm_suspend(dev, rpmflags);
+ goto out;
+ }
+
/* Carry out an asynchronous or a synchronous idle notification. */
if (rpmflags & RPM_ASYNC) {
dev->power.request = RPM_REQ_IDLE;
@@ -254,6 +262,10 @@ static int rpm_suspend(struct device *dev, int rpmflags)
goto repeat;
}
+ dev->power.deferred_resume = false;
+ if (dev->power.no_callbacks)
+ goto no_callback; /* Assume success. */
+
/* Carry out an asynchronous or a synchronous suspend. */
if (rpmflags & RPM_ASYNC) {
dev->power.request = RPM_REQ_SUSPEND;
@@ -265,7 +277,6 @@ static int rpm_suspend(struct device *dev, int rpmflags)
}
__update_runtime_status(dev, RPM_SUSPENDING);
- dev->power.deferred_resume = false;
if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_suspend) {
spin_unlock_irq(&dev->power.lock);
@@ -305,6 +316,7 @@ static int rpm_suspend(struct device *dev, int rpmflags)
pm_runtime_cancel_pending(dev);
}
} else {
+ no_callback:
__update_runtime_status(dev, RPM_SUSPENDED);
pm_runtime_deactivate_timer(dev);
@@ -409,6 +421,23 @@ static int rpm_resume(struct device *dev, int rpmflags)
goto repeat;
}
+ /*
+ * See if we can skip waking up the parent. This is safe only if
+ * power.no_callbacks is set, because otherwise we don't know whether
+ * the resume will actually succeed.
+ */
+ if (dev->power.no_callbacks && !parent && dev->parent) {
+ spin_lock(&dev->parent->power.lock);
+ if (dev->parent->power.disable_depth > 0
+ || dev->parent->power.ignore_children
+ || dev->parent->power.runtime_status == RPM_ACTIVE) {
+ atomic_inc(&dev->parent->power.child_count);
+ spin_unlock(&dev->parent->power.lock);
+ goto no_callback; /* Assume success. */
+ }
+ spin_unlock(&dev->parent->power.lock);
+ }
+
/* Carry out an asynchronous or a synchronous resume. */
if (rpmflags & RPM_ASYNC) {
dev->power.request = RPM_REQ_RESUME;
@@ -449,6 +478,9 @@ static int rpm_resume(struct device *dev, int rpmflags)
goto repeat;
}
+ if (dev->power.no_callbacks)
+ goto no_callback; /* Assume success. */
+
__update_runtime_status(dev, RPM_RESUMING);
if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_resume) {
@@ -482,6 +514,7 @@ static int rpm_resume(struct device *dev, int rpmflags)
__update_runtime_status(dev, RPM_SUSPENDED);
pm_runtime_cancel_pending(dev);
} else {
+ no_callback:
__update_runtime_status(dev, RPM_ACTIVE);
if (parent)
atomic_inc(&parent->power.child_count);
@@ -955,6 +988,25 @@ void pm_runtime_allow(struct device *dev)
EXPORT_SYMBOL_GPL(pm_runtime_allow);
/**
+ * pm_runtime_no_callbacks - Ignore run-time PM callbacks for a device.
+ * @dev: Device to handle.
+ *
+ * Set the power.no_callbacks flag, which tells the PM core that this
+ * device is power-managed through its parent and has no run-time PM
+ * callbacks of its own. The run-time sysfs attributes will be removed.
+ *
+ */
+void pm_runtime_no_callbacks(struct device *dev)
+{
+ spin_lock_irq(&dev->power.lock);
+ dev->power.no_callbacks = 1;
+ spin_unlock_irq(&dev->power.lock);
+ if (device_is_registered(dev))
+ rpm_sysfs_remove(dev);
+}
+EXPORT_SYMBOL_GPL(pm_runtime_no_callbacks);
+
+/**
* pm_runtime_init - Initialize run-time PM fields in given device object.
* @dev: Device object to initialize.
*/
diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c
index 8859780817e1..b5708c47ce2d 100644
--- a/drivers/base/power/sysfs.c
+++ b/drivers/base/power/sysfs.c
@@ -81,6 +81,9 @@
static const char enabled[] = "enabled";
static const char disabled[] = "disabled";
+const char power_group_name[] = "power";
+EXPORT_SYMBOL_GPL(power_group_name);
+
#ifdef CONFIG_PM_RUNTIME
static const char ctrl_auto[] = "auto";
static const char ctrl_on[] = "on";
@@ -390,12 +393,6 @@ static DEVICE_ATTR(async, 0644, async_show, async_store);
#endif /* CONFIG_PM_ADVANCED_DEBUG */
static struct attribute * power_attrs[] = {
-#ifdef CONFIG_PM_RUNTIME
- &dev_attr_control.attr,
- &dev_attr_runtime_status.attr,
- &dev_attr_runtime_suspended_time.attr,
- &dev_attr_runtime_active_time.attr,
-#endif
&dev_attr_wakeup.attr,
#ifdef CONFIG_PM_SLEEP
&dev_attr_wakeup_count.attr,
@@ -409,6 +406,7 @@ static struct attribute * power_attrs[] = {
#ifdef CONFIG_PM_ADVANCED_DEBUG
&dev_attr_async.attr,
#ifdef CONFIG_PM_RUNTIME
+ &dev_attr_runtime_status.attr,
&dev_attr_runtime_usage.attr,
&dev_attr_runtime_active_kids.attr,
&dev_attr_runtime_enabled.attr,
@@ -417,10 +415,52 @@ static struct attribute * power_attrs[] = {
NULL,
};
static struct attribute_group pm_attr_group = {
- .name = "power",
+ .name = power_group_name,
.attrs = power_attrs,
};
+#ifdef CONFIG_PM_RUNTIME
+
+static struct attribute *runtime_attrs[] = {
+#ifndef CONFIG_PM_ADVANCED_DEBUG
+ &dev_attr_runtime_status.attr,
+#endif
+ &dev_attr_control.attr,
+ &dev_attr_runtime_suspended_time.attr,
+ &dev_attr_runtime_active_time.attr,
+ NULL,
+};
+static struct attribute_group pm_runtime_attr_group = {
+ .name = power_group_name,
+ .attrs = runtime_attrs,
+};
+
+int dpm_sysfs_add(struct device *dev)
+{
+ int rc;
+
+ rc = sysfs_create_group(&dev->kobj, &pm_attr_group);
+ if (rc == 0 && !dev->power.no_callbacks) {
+ rc = sysfs_merge_group(&dev->kobj, &pm_runtime_attr_group);
+ if (rc)
+ sysfs_remove_group(&dev->kobj, &pm_attr_group);
+ }
+ return rc;
+}
+
+void rpm_sysfs_remove(struct device *dev)
+{
+ sysfs_unmerge_group(&dev->kobj, &pm_runtime_attr_group);
+}
+
+void dpm_sysfs_remove(struct device *dev)
+{
+ rpm_sysfs_remove(dev);
+ sysfs_remove_group(&dev->kobj, &pm_attr_group);
+}
+
+#else /* CONFIG_PM_RUNTIME */
+
int dpm_sysfs_add(struct device * dev)
{
return sysfs_create_group(&dev->kobj, &pm_attr_group);
@@ -430,3 +470,5 @@ void dpm_sysfs_remove(struct device * dev)
{
sysfs_remove_group(&dev->kobj, &pm_attr_group);
}
+
+#endif
diff --git a/include/linux/pm.h b/include/linux/pm.h
index 1abfe84f447d..abd81ffaba3c 100644
--- a/include/linux/pm.h
+++ b/include/linux/pm.h
@@ -41,6 +41,12 @@ extern void (*pm_power_off_prepare)(void);
struct device;
+#ifdef CONFIG_PM
+extern const char power_group_name[]; /* = "power" */
+#else
+#define power_group_name NULL
+#endif
+
typedef struct pm_message {
int event;
} pm_message_t;
@@ -475,6 +481,7 @@ struct dev_pm_info {
unsigned int deferred_resume:1;
unsigned int run_wake:1;
unsigned int runtime_auto:1;
+ unsigned int no_callbacks:1;
enum rpm_request request;
enum rpm_status runtime_status;
int runtime_error;
diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h
index 5869d87fffac..8ca52f7c357e 100644
--- a/include/linux/pm_runtime.h
+++ b/include/linux/pm_runtime.h
@@ -36,6 +36,7 @@ extern void pm_runtime_forbid(struct device *dev);
extern int pm_generic_runtime_idle(struct device *dev);
extern int pm_generic_runtime_suspend(struct device *dev);
extern int pm_generic_runtime_resume(struct device *dev);
+extern void pm_runtime_no_callbacks(struct device *dev);
static inline bool pm_children_suspended(struct device *dev)
{
@@ -110,6 +111,7 @@ static inline bool pm_runtime_suspended(struct device *dev) { return false; }
static inline int pm_generic_runtime_idle(struct device *dev) { return 0; }
static inline int pm_generic_runtime_suspend(struct device *dev) { return 0; }
static inline int pm_generic_runtime_resume(struct device *dev) { return 0; }
+static inline void pm_runtime_no_callbacks(struct device *dev) {}
#endif /* !CONFIG_PM_RUNTIME */