diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /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/Makefile | 6 | ||||
-rw-r--r-- | drivers/base/power/main.c | 99 | ||||
-rw-r--r-- | drivers/base/power/power.h | 106 | ||||
-rw-r--r-- | drivers/base/power/resume.c | 112 | ||||
-rw-r--r-- | drivers/base/power/runtime.c | 81 | ||||
-rw-r--r-- | drivers/base/power/shutdown.c | 67 | ||||
-rw-r--r-- | drivers/base/power/suspend.c | 144 | ||||
-rw-r--r-- | drivers/base/power/sysfs.c | 68 |
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 @@ | |||
1 | obj-y := shutdown.o | ||
2 | obj-$(CONFIG_PM) += main.o suspend.o resume.o runtime.o sysfs.o | ||
3 | |||
4 | ifeq ($(CONFIG_DEBUG_DRIVER),y) | ||
5 | EXTRA_CFLAGS += -DDEBUG | ||
6 | endif | ||
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 | |||
26 | LIST_HEAD(dpm_active); | ||
27 | LIST_HEAD(dpm_off); | ||
28 | LIST_HEAD(dpm_off_irq); | ||
29 | |||
30 | DECLARE_MUTEX(dpm_sem); | ||
31 | DECLARE_MUTEX(dpm_list_sem); | ||
32 | |||
33 | /* | ||
34 | * PM Reference Counting. | ||
35 | */ | ||
36 | |||
37 | static inline void device_pm_hold(struct device * dev) | ||
38 | { | ||
39 | if (dev) | ||
40 | atomic_inc(&dev->power.pm_users); | ||
41 | } | ||
42 | |||
43 | static 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 | |||
63 | void 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 | } | ||
70 | EXPORT_SYMBOL_GPL(device_pm_set_parent); | ||
71 | |||
72 | int 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 | |||
88 | void 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 | |||
3 | enum { | ||
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 | |||
15 | extern int device_detach_shutdown(struct device *); | ||
16 | extern 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 | */ | ||
28 | extern struct semaphore dpm_sem; | ||
29 | |||
30 | /* | ||
31 | * Used to serialize changes to the dpm_* lists. | ||
32 | */ | ||
33 | extern struct semaphore dpm_list_sem; | ||
34 | |||
35 | /* | ||
36 | * The PM lists. | ||
37 | */ | ||
38 | extern struct list_head dpm_active; | ||
39 | extern struct list_head dpm_off; | ||
40 | extern struct list_head dpm_off_irq; | ||
41 | |||
42 | |||
43 | static 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 | |||
48 | static inline struct device * to_device(struct list_head * entry) | ||
49 | { | ||
50 | return container_of(to_pm_info(entry), struct device, power); | ||
51 | } | ||
52 | |||
53 | extern int device_pm_add(struct device *); | ||
54 | extern void device_pm_remove(struct device *); | ||
55 | |||
56 | /* | ||
57 | * sysfs.c | ||
58 | */ | ||
59 | |||
60 | extern int dpm_sysfs_add(struct device *); | ||
61 | extern void dpm_sysfs_remove(struct device *); | ||
62 | |||
63 | /* | ||
64 | * resume.c | ||
65 | */ | ||
66 | |||
67 | extern void dpm_resume(void); | ||
68 | extern void dpm_power_up(void); | ||
69 | extern int resume_device(struct device *); | ||
70 | |||
71 | /* | ||
72 | * suspend.c | ||
73 | */ | ||
74 | extern int suspend_device(struct device *, pm_message_t); | ||
75 | |||
76 | |||
77 | /* | ||
78 | * runtime.c | ||
79 | */ | ||
80 | |||
81 | extern int dpm_runtime_suspend(struct device *, pm_message_t); | ||
82 | extern void dpm_runtime_resume(struct device *); | ||
83 | |||
84 | #else /* CONFIG_PM */ | ||
85 | |||
86 | |||
87 | static inline int device_pm_add(struct device * dev) | ||
88 | { | ||
89 | return 0; | ||
90 | } | ||
91 | static inline void device_pm_remove(struct device * dev) | ||
92 | { | ||
93 | |||
94 | } | ||
95 | |||
96 | static inline int dpm_runtime_suspend(struct device * dev, pm_message_t state) | ||
97 | { | ||
98 | return 0; | ||
99 | } | ||
100 | |||
101 | static 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 | |||
14 | extern int sysdev_resume(void); | ||
15 | |||
16 | |||
17 | /** | ||
18 | * resume_device - Restore state for one device. | ||
19 | * @dev: Device. | ||
20 | * | ||
21 | */ | ||
22 | |||
23 | int 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 | |||
32 | void 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 | |||
60 | void device_resume(void) | ||
61 | { | ||
62 | down(&dpm_sem); | ||
63 | dpm_resume(); | ||
64 | up(&dpm_sem); | ||
65 | } | ||
66 | |||
67 | EXPORT_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 | |||
81 | void 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 | |||
104 | void device_power_up(void) | ||
105 | { | ||
106 | sysdev_resume(); | ||
107 | dpm_power_up(); | ||
108 | } | ||
109 | |||
110 | EXPORT_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 | |||
13 | static 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 | |||
33 | void 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 | |||
47 | int 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 | */ | ||
76 | void 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 | |||
19 | extern struct subsystem devices_subsys; | ||
20 | |||
21 | |||
22 | int 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 | |||
45 | extern int sysdev_shutdown(void); | ||
46 | |||
47 | /** | ||
48 | * device_shutdown - call ->shutdown() on each device to shutdown. | ||
49 | */ | ||
50 | void 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 | |||
14 | extern 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 | |||
38 | int 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 | |||
68 | int 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 | |||
109 | EXPORT_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 | |||
121 | int 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 | |||
143 | EXPORT_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 | |||
27 | static ssize_t state_show(struct device * dev, char * buf) | ||
28 | { | ||
29 | return sprintf(buf, "%u\n", dev->power.power_state); | ||
30 | } | ||
31 | |||
32 | static 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 | |||
48 | static DEVICE_ATTR(state, 0644, state_show, state_store); | ||
49 | |||
50 | |||
51 | static struct attribute * power_attrs[] = { | ||
52 | &dev_attr_state.attr, | ||
53 | NULL, | ||
54 | }; | ||
55 | static struct attribute_group pm_attr_group = { | ||
56 | .name = "power", | ||
57 | .attrs = power_attrs, | ||
58 | }; | ||
59 | |||
60 | int dpm_sysfs_add(struct device * dev) | ||
61 | { | ||
62 | return sysfs_create_group(&dev->kobj, &pm_attr_group); | ||
63 | } | ||
64 | |||
65 | void dpm_sysfs_remove(struct device * dev) | ||
66 | { | ||
67 | sysfs_remove_group(&dev->kobj, &pm_attr_group); | ||
68 | } | ||