Commit | Line | Data |
---|---|---|
1da177e4 LT |
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> | |
02669492 AM |
12 | #include <linux/kallsyms.h> |
13 | #include <linux/pm.h> | |
f67d115f | 14 | #include "../base.h" |
1da177e4 LT |
15 | #include "power.h" |
16 | ||
1da177e4 LT |
17 | /* |
18 | * The entries in the dpm_active list are in a depth first order, simply | |
19 | * because children are guaranteed to be discovered after parents, and | |
20 | * are inserted at the back of the list on discovery. | |
21 | * | |
22 | * All list on the suspend path are done in reverse order, so we operate | |
23 | * on the leaves of the device tree (or forests, depending on how you want | |
24 | * to look at it ;) first. As nodes are removed from the back of the list, | |
25 | * they are inserted into the front of their destintation lists. | |
26 | * | |
27 | * Things are the reverse on the resume path - iterations are done in | |
28 | * forward order, and nodes are inserted at the back of their destination | |
29 | * lists. This way, the ancestors will be accessed before their descendents. | |
30 | */ | |
31 | ||
fd869db6 DB |
32 | static inline char *suspend_verb(u32 event) |
33 | { | |
34 | switch (event) { | |
35 | case PM_EVENT_SUSPEND: return "suspend"; | |
36 | case PM_EVENT_FREEZE: return "freeze"; | |
f1cc0a89 | 37 | case PM_EVENT_PRETHAW: return "prethaw"; |
fd869db6 DB |
38 | default: return "(unknown suspend event)"; |
39 | } | |
40 | } | |
41 | ||
1da177e4 LT |
42 | |
43 | /** | |
44 | * suspend_device - Save state of one device. | |
45 | * @dev: Device. | |
46 | * @state: Power state device is entering. | |
47 | */ | |
48 | ||
49 | int suspend_device(struct device * dev, pm_message_t state) | |
50 | { | |
51 | int error = 0; | |
52 | ||
af70316a | 53 | down(&dev->sem); |
ca078bae | 54 | if (dev->power.power_state.event) { |
82428b62 | 55 | dev_dbg(dev, "PM: suspend %d-->%d\n", |
ca078bae | 56 | dev->power.power_state.event, state.event); |
82428b62 DB |
57 | } |
58 | if (dev->power.pm_parent | |
ca078bae | 59 | && dev->power.pm_parent->power.power_state.event) { |
82428b62 DB |
60 | dev_err(dev, |
61 | "PM: suspend %d->%d, parent %s already %d\n", | |
ca078bae | 62 | dev->power.power_state.event, state.event, |
82428b62 | 63 | dev->power.pm_parent->bus_id, |
ca078bae | 64 | dev->power.pm_parent->power.power_state.event); |
82428b62 | 65 | } |
1da177e4 LT |
66 | |
67 | dev->power.prev_state = dev->power.power_state; | |
68 | ||
7c8265f5 LT |
69 | if (dev->class && dev->class->suspend && !dev->power.power_state.event) { |
70 | dev_dbg(dev, "class %s%s\n", | |
71 | suspend_verb(state.event), | |
72 | ((state.event == PM_EVENT_SUSPEND) | |
73 | && device_may_wakeup(dev)) | |
74 | ? ", may wakeup" | |
75 | : "" | |
76 | ); | |
77 | error = dev->class->suspend(dev, state); | |
78 | suspend_report_result(dev->class->suspend, error); | |
79 | } | |
80 | ||
81 | if (!error && dev->bus && dev->bus->suspend && !dev->power.power_state.event) { | |
fd869db6 DB |
82 | dev_dbg(dev, "%s%s\n", |
83 | suspend_verb(state.event), | |
84 | ((state.event == PM_EVENT_SUSPEND) | |
85 | && device_may_wakeup(dev)) | |
86 | ? ", may wakeup" | |
87 | : "" | |
88 | ); | |
1da177e4 | 89 | error = dev->bus->suspend(dev, state); |
02669492 | 90 | suspend_report_result(dev->bus->suspend, error); |
82428b62 | 91 | } |
af70316a | 92 | up(&dev->sem); |
1da177e4 LT |
93 | return error; |
94 | } | |
95 | ||
760f1fce | 96 | |
7c8265f5 LT |
97 | /* |
98 | * This is called with interrupts off, only a single CPU | |
99 | * running. We can't do down() on a semaphore (and we don't | |
100 | * need the protection) | |
101 | */ | |
102 | static int suspend_device_late(struct device *dev, pm_message_t state) | |
103 | { | |
104 | int error = 0; | |
105 | ||
7c8265f5 LT |
106 | if (dev->bus && dev->bus->suspend_late && !dev->power.power_state.event) { |
107 | dev_dbg(dev, "LATE %s%s\n", | |
108 | suspend_verb(state.event), | |
109 | ((state.event == PM_EVENT_SUSPEND) | |
110 | && device_may_wakeup(dev)) | |
111 | ? ", may wakeup" | |
112 | : "" | |
113 | ); | |
114 | error = dev->bus->suspend_late(dev, state); | |
115 | suspend_report_result(dev->bus->suspend_late, error); | |
116 | } | |
117 | return error; | |
118 | } | |
119 | ||
1da177e4 LT |
120 | /** |
121 | * device_suspend - Save state and stop all devices in system. | |
122 | * @state: Power state to put each device in. | |
123 | * | |
124 | * Walk the dpm_active list, call ->suspend() for each device, and move | |
7c8265f5 LT |
125 | * it to the dpm_off list. |
126 | * | |
127 | * (For historical reasons, if it returns -EAGAIN, that used to mean | |
128 | * that the device would be called again with interrupts disabled. | |
129 | * These days, we use the "suspend_late()" callback for that, so we | |
130 | * print a warning and consider it an error). | |
131 | * | |
132 | * If we get a different error, try and back out. | |
1da177e4 LT |
133 | * |
134 | * If we hit a failure with any of the devices, call device_resume() | |
135 | * above to bring the suspended devices back to life. | |
136 | * | |
137 | */ | |
138 | ||
139 | int device_suspend(pm_message_t state) | |
140 | { | |
141 | int error = 0; | |
142 | ||
bb84c89f | 143 | might_sleep(); |
1da177e4 LT |
144 | down(&dpm_sem); |
145 | down(&dpm_list_sem); | |
146 | while (!list_empty(&dpm_active) && error == 0) { | |
147 | struct list_head * entry = dpm_active.prev; | |
148 | struct device * dev = to_device(entry); | |
149 | ||
150 | get_device(dev); | |
151 | up(&dpm_list_sem); | |
152 | ||
153 | error = suspend_device(dev, state); | |
154 | ||
155 | down(&dpm_list_sem); | |
156 | ||
157 | /* Check if the device got removed */ | |
158 | if (!list_empty(&dev->power.entry)) { | |
7c8265f5 | 159 | /* Move it to the dpm_off list */ |
1bfba4e8 AM |
160 | if (!error) |
161 | list_move(&dev->power.entry, &dpm_off); | |
1da177e4 LT |
162 | } |
163 | if (error) | |
164 | printk(KERN_ERR "Could not suspend device %s: " | |
7c8265f5 LT |
165 | "error %d%s\n", |
166 | kobject_name(&dev->kobj), error, | |
167 | error == -EAGAIN ? " (please convert to suspend_late)" : ""); | |
1da177e4 LT |
168 | put_device(dev); |
169 | } | |
170 | up(&dpm_list_sem); | |
7c8265f5 | 171 | if (error) |
1da177e4 | 172 | dpm_resume(); |
7c8265f5 | 173 | |
1da177e4 LT |
174 | up(&dpm_sem); |
175 | return error; | |
176 | } | |
177 | ||
178 | EXPORT_SYMBOL_GPL(device_suspend); | |
179 | ||
1da177e4 LT |
180 | /** |
181 | * device_power_down - Shut down special devices. | |
182 | * @state: Power state to enter. | |
183 | * | |
184 | * Walk the dpm_off_irq list, calling ->power_down() for each device that | |
185 | * couldn't power down the device with interrupts enabled. When we're | |
186 | * done, power down system devices. | |
187 | */ | |
188 | ||
189 | int device_power_down(pm_message_t state) | |
190 | { | |
191 | int error = 0; | |
192 | struct device * dev; | |
193 | ||
7c8265f5 LT |
194 | while (!list_empty(&dpm_off)) { |
195 | struct list_head * entry = dpm_off.prev; | |
196 | ||
197 | dev = to_device(entry); | |
198 | error = suspend_device_late(dev, state); | |
199 | if (error) | |
200 | goto Error; | |
201 | list_move(&dev->power.entry, &dpm_off_irq); | |
1da177e4 | 202 | } |
7c8265f5 LT |
203 | |
204 | error = sysdev_suspend(state); | |
1da177e4 LT |
205 | Done: |
206 | return error; | |
207 | Error: | |
208 | printk(KERN_ERR "Could not power down device %s: " | |
209 | "error %d\n", kobject_name(&dev->kobj), error); | |
210 | dpm_power_up(); | |
211 | goto Done; | |
212 | } | |
213 | ||
214 | EXPORT_SYMBOL_GPL(device_power_down); | |
215 | ||
02669492 AM |
216 | void __suspend_report_result(const char *function, void *fn, int ret) |
217 | { | |
218 | if (ret) { | |
219 | printk(KERN_ERR "%s(): ", function); | |
220 | print_fn_descriptor_symbol("%s() returns ", (unsigned long)fn); | |
221 | printk("%d\n", ret); | |
222 | } | |
223 | } | |
224 | EXPORT_SYMBOL_GPL(__suspend_report_result); |