aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/base/power
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 18:20:36 -0400
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 18:20:36 -0400
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/base/power
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
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 000000000000..c0219ad94aca
--- /dev/null
+++ b/drivers/base/power/Makefile
@@ -0,0 +1,6 @@
1obj-y := shutdown.o
2obj-$(CONFIG_PM) += main.o suspend.o resume.o runtime.o sysfs.o
3
4ifeq ($(CONFIG_DEBUG_DRIVER),y)
5EXTRA_CFLAGS += -DDEBUG
6endif
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
new file mode 100644
index 000000000000..15e6a8f951f1
--- /dev/null
+++ b/drivers/base/power/main.c
@@ -0,0 +1,99 @@
1/*
2 * drivers/base/power/main.c - Where the driver meets power management.
3 *
4 * Copyright (c) 2003 Patrick Mochel
5 * Copyright (c) 2003 Open Source Development Lab
6 *
7 * This file is released under the GPLv2
8 *
9 *
10 * The driver model core calls device_pm_add() when a device is registered.
11 * This will intialize the embedded device_pm_info object in the device
12 * and add it to the list of power-controlled devices. sysfs entries for
13 * controlling device power management will also be added.
14 *
15 * A different set of lists than the global subsystem list are used to
16 * keep track of power info because we use different lists to hold
17 * devices based on what stage of the power management process they
18 * are in. The power domain dependencies may also differ from the
19 * ancestral dependencies that the subsystem list maintains.
20 */
21
22#include <linux/config.h>
23#include <linux/device.h>
24#include "power.h"
25
26LIST_HEAD(dpm_active);
27LIST_HEAD(dpm_off);
28LIST_HEAD(dpm_off_irq);
29
30DECLARE_MUTEX(dpm_sem);
31DECLARE_MUTEX(dpm_list_sem);
32
33/*
34 * PM Reference Counting.
35 */
36
37static inline void device_pm_hold(struct device * dev)
38{
39 if (dev)
40 atomic_inc(&dev->power.pm_users);
41}
42
43static inline void device_pm_release(struct device * dev)
44{
45 if (dev)
46 atomic_dec(&dev->power.pm_users);
47}
48
49
50/**
51 * device_pm_set_parent - Specify power dependency.
52 * @dev: Device who needs power.
53 * @parent: Device that supplies power.
54 *
55 * This function is used to manually describe a power-dependency
56 * relationship. It may be used to specify a transversal relationship
57 * (where the power supplier is not the physical (or electrical)
58 * ancestor of a specific device.
59 * The effect of this is that the supplier will not be powered down
60 * before the power dependent.
61 */
62
63void device_pm_set_parent(struct device * dev, struct device * parent)
64{
65 struct device * old_parent = dev->power.pm_parent;
66 device_pm_release(old_parent);
67 dev->power.pm_parent = parent;
68 device_pm_hold(parent);
69}
70EXPORT_SYMBOL_GPL(device_pm_set_parent);
71
72int device_pm_add(struct device * dev)
73{
74 int error;
75
76 pr_debug("PM: Adding info for %s:%s\n",
77 dev->bus ? dev->bus->name : "No Bus", dev->kobj.name);
78 atomic_set(&dev->power.pm_users, 0);
79 down(&dpm_list_sem);
80 list_add_tail(&dev->power.entry, &dpm_active);
81 device_pm_set_parent(dev, dev->parent);
82 if ((error = dpm_sysfs_add(dev)))
83 list_del(&dev->power.entry);
84 up(&dpm_list_sem);
85 return error;
86}
87
88void device_pm_remove(struct device * dev)
89{
90 pr_debug("PM: Removing info for %s:%s\n",
91 dev->bus ? dev->bus->name : "No Bus", dev->kobj.name);
92 down(&dpm_list_sem);
93 dpm_sysfs_remove(dev);
94 device_pm_release(dev->power.pm_parent);
95 list_del_init(&dev->power.entry);
96 up(&dpm_list_sem);
97}
98
99
diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h
new file mode 100644
index 000000000000..e5eda746f2a6
--- /dev/null
+++ b/drivers/base/power/power.h
@@ -0,0 +1,106 @@
1
2
3enum {
4 DEVICE_PM_ON,
5 DEVICE_PM1,
6 DEVICE_PM2,
7 DEVICE_PM3,
8 DEVICE_PM_OFF,
9};
10
11/*
12 * shutdown.c
13 */
14
15extern int device_detach_shutdown(struct device *);
16extern void device_shutdown(void);
17
18
19#ifdef CONFIG_PM
20
21/*
22 * main.c
23 */
24
25/*
26 * Used to synchronize global power management operations.
27 */
28extern struct semaphore dpm_sem;
29
30/*
31 * Used to serialize changes to the dpm_* lists.
32 */
33extern struct semaphore dpm_list_sem;
34
35/*
36 * The PM lists.
37 */
38extern struct list_head dpm_active;
39extern struct list_head dpm_off;
40extern struct list_head dpm_off_irq;
41
42
43static inline struct dev_pm_info * to_pm_info(struct list_head * entry)
44{
45 return container_of(entry, struct dev_pm_info, entry);
46}
47
48static inline struct device * to_device(struct list_head * entry)
49{
50 return container_of(to_pm_info(entry), struct device, power);
51}
52
53extern int device_pm_add(struct device *);
54extern void device_pm_remove(struct device *);
55
56/*
57 * sysfs.c
58 */
59
60extern int dpm_sysfs_add(struct device *);
61extern void dpm_sysfs_remove(struct device *);
62
63/*
64 * resume.c
65 */
66
67extern void dpm_resume(void);
68extern void dpm_power_up(void);
69extern int resume_device(struct device *);
70
71/*
72 * suspend.c
73 */
74extern int suspend_device(struct device *, pm_message_t);
75
76
77/*
78 * runtime.c
79 */
80
81extern int dpm_runtime_suspend(struct device *, pm_message_t);
82extern void dpm_runtime_resume(struct device *);
83
84#else /* CONFIG_PM */
85
86
87static inline int device_pm_add(struct device * dev)
88{
89 return 0;
90}
91static inline void device_pm_remove(struct device * dev)
92{
93
94}
95
96static inline int dpm_runtime_suspend(struct device * dev, pm_message_t state)
97{
98 return 0;
99}
100
101static inline void dpm_runtime_resume(struct device * dev)
102{
103
104}
105
106#endif
diff --git a/drivers/base/power/resume.c b/drivers/base/power/resume.c
new file mode 100644
index 000000000000..f8f5055754d6
--- /dev/null
+++ b/drivers/base/power/resume.c
@@ -0,0 +1,112 @@
1/*
2 * resume.c - Functions for waking devices up.
3 *
4 * Copyright (c) 2003 Patrick Mochel
5 * Copyright (c) 2003 Open Source Development Labs
6 *
7 * This file is released under the GPLv2
8 *
9 */
10
11#include <linux/device.h>
12#include "power.h"
13
14extern int sysdev_resume(void);
15
16
17/**
18 * resume_device - Restore state for one device.
19 * @dev: Device.
20 *
21 */
22
23int resume_device(struct device * dev)
24{
25 if (dev->bus && dev->bus->resume)
26 return dev->bus->resume(dev);
27 return 0;
28}
29
30
31
32void dpm_resume(void)
33{
34 down(&dpm_list_sem);
35 while(!list_empty(&dpm_off)) {
36 struct list_head * entry = dpm_off.next;
37 struct device * dev = to_device(entry);
38
39 get_device(dev);
40 list_del_init(entry);
41 list_add_tail(entry, &dpm_active);
42
43 up(&dpm_list_sem);
44 if (!dev->power.prev_state)
45 resume_device(dev);
46 down(&dpm_list_sem);
47 put_device(dev);
48 }
49 up(&dpm_list_sem);
50}
51
52
53/**
54 * device_resume - Restore state of each device in system.
55 *
56 * Walk the dpm_off list, remove each entry, resume the device,
57 * then add it to the dpm_active list.
58 */
59
60void device_resume(void)
61{
62 down(&dpm_sem);
63 dpm_resume();
64 up(&dpm_sem);
65}
66
67EXPORT_SYMBOL_GPL(device_resume);
68
69
70/**
71 * device_power_up_irq - Power on some devices.
72 *
73 * Walk the dpm_off_irq list and power each device up. This
74 * is used for devices that required they be powered down with
75 * interrupts disabled. As devices are powered on, they are moved to
76 * the dpm_suspended list.
77 *
78 * Interrupts must be disabled when calling this.
79 */
80
81void dpm_power_up(void)
82{
83 while(!list_empty(&dpm_off_irq)) {
84 struct list_head * entry = dpm_off_irq.next;
85 struct device * dev = to_device(entry);
86
87 get_device(dev);
88 list_del_init(entry);
89 list_add_tail(entry, &dpm_active);
90 resume_device(dev);
91 put_device(dev);
92 }
93}
94
95
96/**
97 * device_pm_power_up - Turn on all devices that need special attention.
98 *
99 * Power on system devices then devices that required we shut them down
100 * with interrupts disabled.
101 * Called with interrupts disabled.
102 */
103
104void device_power_up(void)
105{
106 sysdev_resume();
107 dpm_power_up();
108}
109
110EXPORT_SYMBOL_GPL(device_power_up);
111
112
diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
new file mode 100644
index 000000000000..325962d80191
--- /dev/null
+++ b/drivers/base/power/runtime.c
@@ -0,0 +1,81 @@
1/*
2 * drivers/base/power/runtime.c - Handling dynamic device power management.
3 *
4 * Copyright (c) 2003 Patrick Mochel
5 * Copyright (c) 2003 Open Source Development Lab
6 *
7 */
8
9#include <linux/device.h>
10#include "power.h"
11
12
13static void runtime_resume(struct device * dev)
14{
15 dev_dbg(dev, "resuming\n");
16 if (!dev->power.power_state)
17 return;
18 if (!resume_device(dev))
19 dev->power.power_state = 0;
20}
21
22
23/**
24 * dpm_runtime_resume - Power one device back on.
25 * @dev: Device.
26 *
27 * Bring one device back to the on state by first powering it
28 * on, then restoring state. We only operate on devices that aren't
29 * already on.
30 * FIXME: We need to handle devices that are in an unknown state.
31 */
32
33void dpm_runtime_resume(struct device * dev)
34{
35 down(&dpm_sem);
36 runtime_resume(dev);
37 up(&dpm_sem);
38}
39
40
41/**
42 * dpm_runtime_suspend - Put one device in low-power state.
43 * @dev: Device.
44 * @state: State to enter.
45 */
46
47int dpm_runtime_suspend(struct device * dev, pm_message_t state)
48{
49 int error = 0;
50
51 down(&dpm_sem);
52 if (dev->power.power_state == state)
53 goto Done;
54
55 if (dev->power.power_state)
56 runtime_resume(dev);
57
58 if (!(error = suspend_device(dev, state)))
59 dev->power.power_state = state;
60 Done:
61 up(&dpm_sem);
62 return error;
63}
64
65
66/**
67 * dpm_set_power_state - Update power_state field.
68 * @dev: Device.
69 * @state: Power state device is in.
70 *
71 * This is an update mechanism for drivers to notify the core
72 * what power state a device is in. Device probing code may not
73 * always be able to tell, but we need accurate information to
74 * work reliably.
75 */
76void dpm_set_power_state(struct device * dev, pm_message_t state)
77{
78 down(&dpm_sem);
79 dev->power.power_state = state;
80 up(&dpm_sem);
81}
diff --git a/drivers/base/power/shutdown.c b/drivers/base/power/shutdown.c
new file mode 100644
index 000000000000..d1e023fbe169
--- /dev/null
+++ b/drivers/base/power/shutdown.c
@@ -0,0 +1,67 @@
1/*
2 * shutdown.c - power management functions for the device tree.
3 *
4 * Copyright (c) 2002-3 Patrick Mochel
5 * 2002-3 Open Source Development Lab
6 *
7 * This file is released under the GPLv2
8 *
9 */
10
11#include <linux/config.h>
12#include <linux/device.h>
13#include <asm/semaphore.h>
14
15#include "power.h"
16
17#define to_dev(node) container_of(node, struct device, kobj.entry)
18
19extern struct subsystem devices_subsys;
20
21
22int device_detach_shutdown(struct device * dev)
23{
24 if (!dev->detach_state)
25 return 0;
26
27 if (dev->detach_state == DEVICE_PM_OFF) {
28 if (dev->driver && dev->driver->shutdown)
29 dev->driver->shutdown(dev);
30 return 0;
31 }
32 return dpm_runtime_suspend(dev, dev->detach_state);
33}
34
35
36/**
37 * We handle system devices differently - we suspend and shut them
38 * down last and resume them first. That way, we don't do anything stupid like
39 * shutting down the interrupt controller before any devices..
40 *
41 * Note that there are not different stages for power management calls -
42 * they only get one called once when interrupts are disabled.
43 */
44
45extern int sysdev_shutdown(void);
46
47/**
48 * device_shutdown - call ->shutdown() on each device to shutdown.
49 */
50void device_shutdown(void)
51{
52 struct device * dev;
53
54 down_write(&devices_subsys.rwsem);
55 list_for_each_entry_reverse(dev, &devices_subsys.kset.list, kobj.entry) {
56 pr_debug("shutting down %s: ", dev->bus_id);
57 if (dev->driver && dev->driver->shutdown) {
58 pr_debug("Ok\n");
59 dev->driver->shutdown(dev);
60 } else
61 pr_debug("Ignored.\n");
62 }
63 up_write(&devices_subsys.rwsem);
64
65 sysdev_shutdown();
66}
67
diff --git a/drivers/base/power/suspend.c b/drivers/base/power/suspend.c
new file mode 100644
index 000000000000..a0b5cf689e63
--- /dev/null
+++ b/drivers/base/power/suspend.c
@@ -0,0 +1,144 @@
1/*
2 * suspend.c - Functions for putting devices to sleep.
3 *
4 * Copyright (c) 2003 Patrick Mochel
5 * Copyright (c) 2003 Open Source Development Labs
6 *
7 * This file is released under the GPLv2
8 *
9 */
10
11#include <linux/device.h>
12#include "power.h"
13
14extern int sysdev_suspend(pm_message_t state);
15
16/*
17 * The entries in the dpm_active list are in a depth first order, simply
18 * because children are guaranteed to be discovered after parents, and
19 * are inserted at the back of the list on discovery.
20 *
21 * All list on the suspend path are done in reverse order, so we operate
22 * on the leaves of the device tree (or forests, depending on how you want
23 * to look at it ;) first. As nodes are removed from the back of the list,
24 * they are inserted into the front of their destintation lists.
25 *
26 * Things are the reverse on the resume path - iterations are done in
27 * forward order, and nodes are inserted at the back of their destination
28 * lists. This way, the ancestors will be accessed before their descendents.
29 */
30
31
32/**
33 * suspend_device - Save state of one device.
34 * @dev: Device.
35 * @state: Power state device is entering.
36 */
37
38int suspend_device(struct device * dev, pm_message_t state)
39{
40 int error = 0;
41
42 dev_dbg(dev, "suspending\n");
43
44 dev->power.prev_state = dev->power.power_state;
45
46 if (dev->bus && dev->bus->suspend && !dev->power.power_state)
47 error = dev->bus->suspend(dev, state);
48
49 return error;
50}
51
52
53/**
54 * device_suspend - Save state and stop all devices in system.
55 * @state: Power state to put each device in.
56 *
57 * Walk the dpm_active list, call ->suspend() for each device, and move
58 * it to dpm_off.
59 * Check the return value for each. If it returns 0, then we move the
60 * the device to the dpm_off list. If it returns -EAGAIN, we move it to
61 * the dpm_off_irq list. If we get a different error, try and back out.
62 *
63 * If we hit a failure with any of the devices, call device_resume()
64 * above to bring the suspended devices back to life.
65 *
66 */
67
68int device_suspend(pm_message_t state)
69{
70 int error = 0;
71
72 down(&dpm_sem);
73 down(&dpm_list_sem);
74 while (!list_empty(&dpm_active) && error == 0) {
75 struct list_head * entry = dpm_active.prev;
76 struct device * dev = to_device(entry);
77
78 get_device(dev);
79 up(&dpm_list_sem);
80
81 error = suspend_device(dev, state);
82
83 down(&dpm_list_sem);
84
85 /* Check if the device got removed */
86 if (!list_empty(&dev->power.entry)) {
87 /* Move it to the dpm_off or dpm_off_irq list */
88 if (!error) {
89 list_del(&dev->power.entry);
90 list_add(&dev->power.entry, &dpm_off);
91 } else if (error == -EAGAIN) {
92 list_del(&dev->power.entry);
93 list_add(&dev->power.entry, &dpm_off_irq);
94 error = 0;
95 }
96 }
97 if (error)
98 printk(KERN_ERR "Could not suspend device %s: "
99 "error %d\n", kobject_name(&dev->kobj), error);
100 put_device(dev);
101 }
102 up(&dpm_list_sem);
103 if (error)
104 dpm_resume();
105 up(&dpm_sem);
106 return error;
107}
108
109EXPORT_SYMBOL_GPL(device_suspend);
110
111
112/**
113 * device_power_down - Shut down special devices.
114 * @state: Power state to enter.
115 *
116 * Walk the dpm_off_irq list, calling ->power_down() for each device that
117 * couldn't power down the device with interrupts enabled. When we're
118 * done, power down system devices.
119 */
120
121int device_power_down(pm_message_t state)
122{
123 int error = 0;
124 struct device * dev;
125
126 list_for_each_entry_reverse(dev, &dpm_off_irq, power.entry) {
127 if ((error = suspend_device(dev, state)))
128 break;
129 }
130 if (error)
131 goto Error;
132 if ((error = sysdev_suspend(state)))
133 goto Error;
134 Done:
135 return error;
136 Error:
137 printk(KERN_ERR "Could not power down device %s: "
138 "error %d\n", kobject_name(&dev->kobj), error);
139 dpm_power_up();
140 goto Done;
141}
142
143EXPORT_SYMBOL_GPL(device_power_down);
144
diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c
new file mode 100644
index 000000000000..6ac96349a8e8
--- /dev/null
+++ b/drivers/base/power/sysfs.c
@@ -0,0 +1,68 @@
1/*
2 * drivers/base/power/sysfs.c - sysfs entries for device PM
3 */
4
5#include <linux/device.h>
6#include "power.h"
7
8
9/**
10 * state - Control current power state of device
11 *
12 * show() returns the current power state of the device. '0' indicates
13 * the device is on. Other values (1-3) indicate the device is in a low
14 * power state.
15 *
16 * store() sets the current power state, which is an integer value
17 * between 0-3. If the device is on ('0'), and the value written is
18 * greater than 0, then the device is placed directly into the low-power
19 * state (via its driver's ->suspend() method).
20 * If the device is currently in a low-power state, and the value is 0,
21 * the device is powered back on (via the ->resume() method).
22 * If the device is in a low-power state, and a different low-power state
23 * is requested, the device is first resumed, then suspended into the new
24 * low-power state.
25 */
26
27static ssize_t state_show(struct device * dev, char * buf)
28{
29 return sprintf(buf, "%u\n", dev->power.power_state);
30}
31
32static ssize_t state_store(struct device * dev, const char * buf, size_t n)
33{
34 u32 state;
35 char * rest;
36 int error = 0;
37
38 state = simple_strtoul(buf, &rest, 10);
39 if (*rest)
40 return -EINVAL;
41 if (state)
42 error = dpm_runtime_suspend(dev, state);
43 else
44 dpm_runtime_resume(dev);
45 return error ? error : n;
46}
47
48static DEVICE_ATTR(state, 0644, state_show, state_store);
49
50
51static struct attribute * power_attrs[] = {
52 &dev_attr_state.attr,
53 NULL,
54};
55static struct attribute_group pm_attr_group = {
56 .name = "power",
57 .attrs = power_attrs,
58};
59
60int dpm_sysfs_add(struct device * dev)
61{
62 return sysfs_create_group(&dev->kobj, &pm_attr_group);
63}
64
65void dpm_sysfs_remove(struct device * dev)
66{
67 sysfs_remove_group(&dev->kobj, &pm_attr_group);
68}