summaryrefslogtreecommitdiffstats
path: root/drivers/base/power
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/base/power')
-rw-r--r--drivers/base/power/Makefile6
-rw-r--r--drivers/base/power/main.c99
-rw-r--r--drivers/base/power/power.h106
-rw-r--r--drivers/base/power/resume.c112
-rw-r--r--drivers/base/power/runtime.c81
-rw-r--r--drivers/base/power/shutdown.c67
-rw-r--r--drivers/base/power/suspend.c144
-rw-r--r--drivers/base/power/sysfs.c68
8 files changed, 683 insertions, 0 deletions
diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile
new file mode 100644
index 00000000000..c0219ad94ac
--- /dev/null
+++ b/drivers/base/power/Makefile
@@ -0,0 +1,6 @@
+obj-y := shutdown.o
+obj-$(CONFIG_PM) += main.o suspend.o resume.o runtime.o sysfs.o
+
+ifeq ($(CONFIG_DEBUG_DRIVER),y)
+EXTRA_CFLAGS += -DDEBUG
+endif
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
new file mode 100644
index 00000000000..15e6a8f951f
--- /dev/null
+++ b/drivers/base/power/main.c
@@ -0,0 +1,99 @@
+/*
+ * drivers/base/power/main.c - Where the driver meets power management.
+ *
+ * Copyright (c) 2003 Patrick Mochel
+ * Copyright (c) 2003 Open Source Development Lab
+ *
+ * This file is released under the GPLv2
+ *
+ *
+ * The driver model core calls device_pm_add() when a device is registered.
+ * This will intialize the embedded device_pm_info object in the device
+ * and add it to the list of power-controlled devices. sysfs entries for
+ * controlling device power management will also be added.
+ *
+ * A different set of lists than the global subsystem list are used to
+ * keep track of power info because we use different lists to hold
+ * devices based on what stage of the power management process they
+ * are in. The power domain dependencies may also differ from the
+ * ancestral dependencies that the subsystem list maintains.
+ */
+
+#include <linux/config.h>
+#include <linux/device.h>
+#include "power.h"
+
+LIST_HEAD(dpm_active);
+LIST_HEAD(dpm_off);
+LIST_HEAD(dpm_off_irq);
+
+DECLARE_MUTEX(dpm_sem);
+DECLARE_MUTEX(dpm_list_sem);
+
+/*
+ * PM Reference Counting.
+ */
+
+static inline void device_pm_hold(struct device * dev)
+{
+ if (dev)
+ atomic_inc(&dev->power.pm_users);
+}
+
+static inline void device_pm_release(struct device * dev)
+{
+ if (dev)
+ atomic_dec(&dev->power.pm_users);
+}
+
+
+/**
+ * device_pm_set_parent - Specify power dependency.
+ * @dev: Device who needs power.
+ * @parent: Device that supplies power.
+ *
+ * This function is used to manually describe a power-dependency
+ * relationship. It may be used to specify a transversal relationship
+ * (where the power supplier is not the physical (or electrical)
+ * ancestor of a specific device.
+ * The effect of this is that the supplier will not be powered down
+ * before the power dependent.
+ */
+
+void device_pm_set_parent(struct device * dev, struct device * parent)
+{
+ struct device * old_parent = dev->power.pm_parent;
+ device_pm_release(old_parent);
+ dev->power.pm_parent = parent;
+ device_pm_hold(parent);
+}
+EXPORT_SYMBOL_GPL(device_pm_set_parent);
+
+int device_pm_add(struct device * dev)
+{
+ int error;
+
+ pr_debug("PM: Adding info for %s:%s\n",
+ dev->bus ? dev->bus->name : "No Bus", dev->kobj.name);
+ atomic_set(&dev->power.pm_users, 0);
+ down(&dpm_list_sem);
+ list_add_tail(&dev->power.entry, &dpm_active);
+ device_pm_set_parent(dev, dev->parent);
+ if ((error = dpm_sysfs_add(dev)))
+ list_del(&dev->power.entry);
+ up(&dpm_list_sem);
+ return error;
+}
+
+void device_pm_remove(struct device * dev)
+{
+ pr_debug("PM: Removing info for %s:%s\n",
+ dev->bus ? dev->bus->name : "No Bus", dev->kobj.name);
+ down(&dpm_list_sem);
+ dpm_sysfs_remove(dev);
+ device_pm_release(dev->power.pm_parent);
+ list_del_init(&dev->power.entry);
+ up(&dpm_list_sem);
+}
+
+
diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h
new file mode 100644
index 00000000000..e5eda746f2a
--- /dev/null
+++ b/drivers/base/power/power.h
@@ -0,0 +1,106 @@
+
+
+enum {
+ DEVICE_PM_ON,
+ DEVICE_PM1,
+ DEVICE_PM2,
+ DEVICE_PM3,
+ DEVICE_PM_OFF,
+};
+
+/*
+ * shutdown.c
+ */
+
+extern int device_detach_shutdown(struct device *);
+extern void device_shutdown(void);
+
+
+#ifdef CONFIG_PM
+
+/*
+ * main.c
+ */
+
+/*
+ * Used to synchronize global power management operations.
+ */
+extern struct semaphore dpm_sem;
+
+/*
+ * Used to serialize changes to the dpm_* lists.
+ */
+extern struct semaphore dpm_list_sem;
+
+/*
+ * The PM lists.
+ */
+extern struct list_head dpm_active;
+extern struct list_head dpm_off;
+extern struct list_head dpm_off_irq;
+
+
+static inline struct dev_pm_info * to_pm_info(struct list_head * entry)
+{
+ return container_of(entry, struct dev_pm_info, entry);
+}
+
+static inline struct device * to_device(struct list_head * entry)
+{
+ return container_of(to_pm_info(entry), struct device, power);
+}
+
+extern int device_pm_add(struct device *);
+extern void device_pm_remove(struct device *);
+
+/*
+ * sysfs.c
+ */
+
+extern int dpm_sysfs_add(struct device *);
+extern void dpm_sysfs_remove(struct device *);
+
+/*
+ * resume.c
+ */
+
+extern void dpm_resume(void);
+extern void dpm_power_up(void);
+extern int resume_device(struct device *);
+
+/*
+ * suspend.c
+ */
+extern int suspend_device(struct device *, pm_message_t);
+
+
+/*
+ * runtime.c
+ */
+
+extern int dpm_runtime_suspend(struct device *, pm_message_t);
+extern void dpm_runtime_resume(struct device *);
+
+#else /* CONFIG_PM */
+
+
+static inline int device_pm_add(struct device * dev)
+{
+ return 0;
+}
+static inline void device_pm_remove(struct device * dev)
+{
+
+}
+
+static inline int dpm_runtime_suspend(struct device * dev, pm_message_t state)
+{
+ return 0;
+}
+
+static inline void dpm_runtime_resume(struct device * dev)
+{
+
+}
+
+#endif
diff --git a/drivers/base/power/resume.c b/drivers/base/power/resume.c
new file mode 100644
index 00000000000..f8f5055754d
--- /dev/null
+++ b/drivers/base/power/resume.c
@@ -0,0 +1,112 @@
+/*
+ * resume.c - Functions for waking devices up.
+ *
+ * Copyright (c) 2003 Patrick Mochel
+ * Copyright (c) 2003 Open Source Development Labs
+ *
+ * This file is released under the GPLv2
+ *
+ */
+
+#include <linux/device.h>
+#include "power.h"
+
+extern int sysdev_resume(void);
+
+
+/**
+ * resume_device - Restore state for one device.
+ * @dev: Device.
+ *
+ */
+
+int resume_device(struct device * dev)
+{
+ if (dev->bus && dev->bus->resume)
+ return dev->bus->resume(dev);
+ return 0;
+}
+
+
+
+void dpm_resume(void)
+{
+ down(&dpm_list_sem);
+ while(!list_empty(&dpm_off)) {
+ struct list_head * entry = dpm_off.next;
+ struct device * dev = to_device(entry);
+
+ get_device(dev);
+ list_del_init(entry);
+ list_add_tail(entry, &dpm_active);
+
+ up(&dpm_list_sem);
+ if (!dev->power.prev_state)
+ resume_device(dev);
+ down(&dpm_list_sem);
+ put_device(dev);
+ }
+ up(&dpm_list_sem);
+}
+
+
+/**
+ * device_resume - Restore state of each device in system.
+ *
+ * Walk the dpm_off list, remove each entry, resume the device,
+ * then add it to the dpm_active list.
+ */
+
+void device_resume(void)
+{
+ down(&dpm_sem);
+ dpm_resume();
+ up(&dpm_sem);
+}
+
+EXPORT_SYMBOL_GPL(device_resume);
+
+
+/**
+ * device_power_up_irq - Power on some devices.
+ *
+ * Walk the dpm_off_irq list and power each device up. This
+ * is used for devices that required they be powered down with
+ * interrupts disabled. As devices are powered on, they are moved to
+ * the dpm_suspended list.
+ *
+ * Interrupts must be disabled when calling this.
+ */
+
+void dpm_power_up(void)
+{
+ while(!list_empty(&dpm_off_irq)) {
+ struct list_head * entry = dpm_off_irq.next;
+ struct device * dev = to_device(entry);
+
+ get_device(dev);
+ list_del_init(entry);
+ list_add_tail(entry, &dpm_active);
+ resume_device(dev);
+ put_device(dev);
+ }
+}
+
+
+/**
+ * device_pm_power_up - Turn on all devices that need special attention.
+ *
+ * Power on system devices then devices that required we shut them down
+ * with interrupts disabled.
+ * Called with interrupts disabled.
+ */
+
+void device_power_up(void)
+{
+ sysdev_resume();
+ dpm_power_up();
+}
+
+EXPORT_SYMBOL_GPL(device_power_up);
+
+
diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
new file mode 100644
index 00000000000..325962d8019
--- /dev/null
+++ b/drivers/base/power/runtime.c
@@ -0,0 +1,81 @@
+/*
+ * drivers/base/power/runtime.c - Handling dynamic device power management.
+ *
+ * Copyright (c) 2003 Patrick Mochel
+ * Copyright (c) 2003 Open Source Development Lab
+ *
+ */
+
+#include <linux/device.h>
+#include "power.h"
+
+
+static void runtime_resume(struct device * dev)
+{
+ dev_dbg(dev, "resuming\n");
+ if (!dev->power.power_state)
+ return;
+ if (!resume_device(dev))
+ dev->power.power_state = 0;
+}
+
+
+/**
+ * dpm_runtime_resume - Power one device back on.
+ * @dev: Device.
+ *
+ * Bring one device back to the on state by first powering it
+ * on, then restoring state. We only operate on devices that aren't
+ * already on.
+ * FIXME: We need to handle devices that are in an unknown state.
+ */
+
+void dpm_runtime_resume(struct device * dev)
+{
+ down(&dpm_sem);
+ runtime_resume(dev);
+ up(&dpm_sem);
+}
+
+
+/**
+ * dpm_runtime_suspend - Put one device in low-power state.
+ * @dev: Device.
+ * @state: State to enter.
+ */
+
+int dpm_runtime_suspend(struct device * dev, pm_message_t state)
+{
+ int error = 0;
+
+ down(&dpm_sem);
+ if (dev->power.power_state == state)
+ goto Done;
+
+ if (dev->power.power_state)
+ runtime_resume(dev);
+
+ if (!(error = suspend_device(dev, state)))
+ dev->power.power_state = state;
+ Done:
+ up(&dpm_sem);
+ return error;
+}
+
+
+/**
+ * dpm_set_power_state - Update power_state field.
+ * @dev: Device.
+ * @state: Power state device is in.
+ *
+ * This is an update mechanism for drivers to notify the core
+ * what power state a device is in. Device probing code may not
+ * always be able to tell, but we need accurate information to
+ * work reliably.
+ */
+void dpm_set_power_state(struct device * dev, pm_message_t state)
+{
+ down(&dpm_sem);
+ dev->power.power_state = state;
+ up(&dpm_sem);
+}
diff --git a/drivers/base/power/shutdown.c b/drivers/base/power/shutdown.c
new file mode 100644
index 00000000000..d1e023fbe16
--- /dev/null
+++ b/drivers/base/power/shutdown.c
@@ -0,0 +1,67 @@
+/*
+ * shutdown.c - power management functions for the device tree.
+ *
+ * Copyright (c) 2002-3 Patrick Mochel
+ * 2002-3 Open Source Development Lab
+ *
+ * This file is released under the GPLv2
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/device.h>
+#include <asm/semaphore.h>
+
+#include "power.h"
+
+#define to_dev(node) container_of(node, struct device, kobj.entry)
+
+extern struct subsystem devices_subsys;
+
+
+int device_detach_shutdown(struct device * dev)
+{
+ if (!dev->detach_state)
+ return 0;
+
+ if (dev->detach_state == DEVICE_PM_OFF) {
+ if (dev->driver && dev->driver->shutdown)
+ dev->driver->shutdown(dev);
+ return 0;
+ }
+ return dpm_runtime_suspend(dev, dev->detach_state);
+}
+
+
+/**
+ * We handle system devices differently - we suspend and shut them
+ * down last and resume them first. That way, we don't do anything stupid like
+ * shutting down the interrupt controller before any devices..
+ *
+ * Note that there are not different stages for power management calls -
+ * they only get one called once when interrupts are disabled.
+ */
+
+extern int sysdev_shutdown(void);
+
+/**
+ * device_shutdown - call ->shutdown() on each device to shutdown.
+ */
+void device_shutdown(void)
+{
+ struct device * dev;
+
+ down_write(&devices_subsys.rwsem);
+ list_for_each_entry_reverse(dev, &devices_subsys.kset.list, kobj.entry) {
+ pr_debug("shutting down %s: ", dev->bus_id);
+ if (dev->driver && dev->driver->shutdown) {
+ pr_debug("Ok\n");
+ dev->driver->shutdown(dev);
+ } else
+ pr_debug("Ignored.\n");
+ }
+ up_write(&devices_subsys.rwsem);
+
+ sysdev_shutdown();
+}
+
diff --git a/drivers/base/power/suspend.c b/drivers/base/power/suspend.c
new file mode 100644
index 00000000000..a0b5cf689e6
--- /dev/null
+++ b/drivers/base/power/suspend.c
@@ -0,0 +1,144 @@
+/*
+ * suspend.c - Functions for putting devices to sleep.
+ *
+ * Copyright (c) 2003 Patrick Mochel
+ * Copyright (c) 2003 Open Source Development Labs
+ *
+ * This file is released under the GPLv2
+ *
+ */
+
+#include <linux/device.h>
+#include "power.h"
+
+extern int sysdev_suspend(pm_message_t state);
+
+/*
+ * The entries in the dpm_active list are in a depth first order, simply
+ * because children are guaranteed to be discovered after parents, and
+ * are inserted at the back of the list on discovery.
+ *
+ * All list on the suspend path are done in reverse order, so we operate
+ * on the leaves of the device tree (or forests, depending on how you want
+ * to look at it ;) first. As nodes are removed from the back of the list,
+ * they are inserted into the front of their destintation lists.
+ *
+ * Things are the reverse on the resume path - iterations are done in
+ * forward order, and nodes are inserted at the back of their destination
+ * lists. This way, the ancestors will be accessed before their descendents.
+ */
+
+
+/**
+ * suspend_device - Save state of one device.
+ * @dev: Device.
+ * @state: Power state device is entering.
+ */
+
+int suspend_device(struct device * dev, pm_message_t state)
+{
+ int error = 0;
+
+ dev_dbg(dev, "suspending\n");
+
+ dev->power.prev_state = dev->power.power_state;
+
+ if (dev->bus && dev->bus->suspend && !dev->power.power_state)
+ error = dev->bus->suspend(dev, state);
+
+ return error;
+}
+
+
+/**
+ * device_suspend - Save state and stop all devices in system.
+ * @state: Power state to put each device in.
+ *
+ * Walk the dpm_active list, call ->suspend() for each device, and move
+ * it to dpm_off.
+ * Check the return value for each. If it returns 0, then we move the
+ * the device to the dpm_off list. If it returns -EAGAIN, we move it to
+ * the dpm_off_irq list. If we get a different error, try and back out.
+ *
+ * If we hit a failure with any of the devices, call device_resume()
+ * above to bring the suspended devices back to life.
+ *
+ */
+
+int device_suspend(pm_message_t state)
+{
+ int error = 0;
+
+ down(&dpm_sem);
+ down(&dpm_list_sem);
+ while (!list_empty(&dpm_active) && error == 0) {
+ struct list_head * entry = dpm_active.prev;
+ struct device * dev = to_device(entry);
+
+ get_device(dev);
+ up(&dpm_list_sem);
+
+ error = suspend_device(dev, state);
+
+ down(&dpm_list_sem);
+
+ /* Check if the device got removed */
+ if (!list_empty(&dev->power.entry)) {
+ /* Move it to the dpm_off or dpm_off_irq list */
+ if (!error) {
+ list_del(&dev->power.entry);
+ list_add(&dev->power.entry, &dpm_off);
+ } else if (error == -EAGAIN) {
+ list_del(&dev->power.entry);
+ list_add(&dev->power.entry, &dpm_off_irq);
+ error = 0;
+ }
+ }
+ if (error)
+ printk(KERN_ERR "Could not suspend device %s: "
+ "error %d\n", kobject_name(&dev->kobj), error);
+ put_device(dev);
+ }
+ up(&dpm_list_sem);
+ if (error)
+ dpm_resume();
+ up(&dpm_sem);
+ return error;
+}
+
+EXPORT_SYMBOL_GPL(device_suspend);
+
+
+/**
+ * device_power_down - Shut down special devices.
+ * @state: Power state to enter.
+ *
+ * Walk the dpm_off_irq list, calling ->power_down() for each device that
+ * couldn't power down the device with interrupts enabled. When we're
+ * done, power down system devices.
+ */
+
+int device_power_down(pm_message_t state)
+{
+ int error = 0;
+ struct device * dev;
+
+ list_for_each_entry_reverse(dev, &dpm_off_irq, power.entry) {
+ if ((error = suspend_device(dev, state)))
+ break;
+ }
+ if (error)
+ goto Error;
+ if ((error = sysdev_suspend(state)))
+ goto Error;
+ Done:
+ return error;
+ Error:
+ printk(KERN_ERR "Could not power down device %s: "
+ "error %d\n", kobject_name(&dev->kobj), error);
+ dpm_power_up();
+ goto Done;
+}
+
+EXPORT_SYMBOL_GPL(device_power_down);
+
diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c
new file mode 100644
index 00000000000..6ac96349a8e
--- /dev/null
+++ b/drivers/base/power/sysfs.c
@@ -0,0 +1,68 @@
+/*
+ * drivers/base/power/sysfs.c - sysfs entries for device PM
+ */
+
+#include <linux/device.h>
+#include "power.h"
+
+
+/**
+ * state - Control current power state of device
+ *
+ * show() returns the current power state of the device. '0' indicates
+ * the device is on. Other values (1-3) indicate the device is in a low
+ * power state.
+ *
+ * store() sets the current power state, which is an integer value
+ * between 0-3. If the device is on ('0'), and the value written is
+ * greater than 0, then the device is placed directly into the low-power
+ * state (via its driver's ->suspend() method).
+ * If the device is currently in a low-power state, and the value is 0,
+ * the device is powered back on (via the ->resume() method).
+ * If the device is in a low-power state, and a different low-power state
+ * is requested, the device is first resumed, then suspended into the new
+ * low-power state.
+ */
+
+static ssize_t state_show(struct device * dev, char * buf)
+{
+ return sprintf(buf, "%u\n", dev->power.power_state);
+}
+
+static ssize_t state_store(struct device * dev, const char * buf, size_t n)
+{
+ u32 state;
+ char * rest;
+ int error = 0;
+
+ state = simple_strtoul(buf, &rest, 10);
+ if (*rest)
+ return -EINVAL;
+ if (state)
+ error = dpm_runtime_suspend(dev, state);
+ else
+ dpm_runtime_resume(dev);
+ return error ? error : n;
+}
+
+static DEVICE_ATTR(state, 0644, state_show, state_store);
+
+
+static struct attribute * power_attrs[] = {
+ &dev_attr_state.attr,
+ NULL,
+};
+static struct attribute_group pm_attr_group = {
+ .name = "power",
+ .attrs = power_attrs,
+};
+
+int dpm_sysfs_add(struct device * dev)
+{
+ return sysfs_create_group(&dev->kobj, &pm_attr_group);
+}
+
+void dpm_sysfs_remove(struct device * dev)
+{
+ sysfs_remove_group(&dev->kobj, &pm_attr_group);
+}