diff options
-rw-r--r-- | drivers/base/power/resume.c | 28 | ||||
-rw-r--r-- | drivers/base/power/suspend.c | 122 | ||||
-rw-r--r-- | include/linux/device.h | 11 | ||||
-rw-r--r-- | include/linux/pm.h | 1 | ||||
-rw-r--r-- | kernel/power/main.c | 4 |
5 files changed, 130 insertions, 36 deletions
diff --git a/drivers/base/power/resume.c b/drivers/base/power/resume.c index 826093ef4c7e..48e3d49d7b65 100644 --- a/drivers/base/power/resume.c +++ b/drivers/base/power/resume.c | |||
@@ -38,13 +38,35 @@ int resume_device(struct device * dev) | |||
38 | dev_dbg(dev,"resuming\n"); | 38 | dev_dbg(dev,"resuming\n"); |
39 | error = dev->bus->resume(dev); | 39 | error = dev->bus->resume(dev); |
40 | } | 40 | } |
41 | if (dev->class && dev->class->resume) { | ||
42 | dev_dbg(dev,"class resume\n"); | ||
43 | error = dev->class->resume(dev); | ||
44 | } | ||
41 | up(&dev->sem); | 45 | up(&dev->sem); |
42 | TRACE_RESUME(error); | 46 | TRACE_RESUME(error); |
43 | return error; | 47 | return error; |
44 | } | 48 | } |
45 | 49 | ||
46 | 50 | ||
51 | static int resume_device_early(struct device * dev) | ||
52 | { | ||
53 | int error = 0; | ||
47 | 54 | ||
55 | TRACE_DEVICE(dev); | ||
56 | TRACE_RESUME(0); | ||
57 | if (dev->bus && dev->bus->resume_early) { | ||
58 | dev_dbg(dev,"EARLY resume\n"); | ||
59 | error = dev->bus->resume_early(dev); | ||
60 | } | ||
61 | TRACE_RESUME(error); | ||
62 | return error; | ||
63 | } | ||
64 | |||
65 | /* | ||
66 | * Resume the devices that have either not gone through | ||
67 | * the late suspend, or that did go through it but also | ||
68 | * went through the early resume | ||
69 | */ | ||
48 | void dpm_resume(void) | 70 | void dpm_resume(void) |
49 | { | 71 | { |
50 | down(&dpm_list_sem); | 72 | down(&dpm_list_sem); |
@@ -99,10 +121,8 @@ void dpm_power_up(void) | |||
99 | struct list_head * entry = dpm_off_irq.next; | 121 | struct list_head * entry = dpm_off_irq.next; |
100 | struct device * dev = to_device(entry); | 122 | struct device * dev = to_device(entry); |
101 | 123 | ||
102 | get_device(dev); | 124 | list_move_tail(entry, &dpm_off); |
103 | list_move_tail(entry, &dpm_active); | 125 | resume_device_early(dev); |
104 | resume_device(dev); | ||
105 | put_device(dev); | ||
106 | } | 126 | } |
107 | } | 127 | } |
108 | 128 | ||
diff --git a/drivers/base/power/suspend.c b/drivers/base/power/suspend.c index 69509e02f703..10e8032aee1a 100644 --- a/drivers/base/power/suspend.c +++ b/drivers/base/power/suspend.c | |||
@@ -65,7 +65,19 @@ int suspend_device(struct device * dev, pm_message_t state) | |||
65 | 65 | ||
66 | dev->power.prev_state = dev->power.power_state; | 66 | dev->power.prev_state = dev->power.power_state; |
67 | 67 | ||
68 | if (dev->bus && dev->bus->suspend && !dev->power.power_state.event) { | 68 | if (dev->class && dev->class->suspend && !dev->power.power_state.event) { |
69 | dev_dbg(dev, "class %s%s\n", | ||
70 | suspend_verb(state.event), | ||
71 | ((state.event == PM_EVENT_SUSPEND) | ||
72 | && device_may_wakeup(dev)) | ||
73 | ? ", may wakeup" | ||
74 | : "" | ||
75 | ); | ||
76 | error = dev->class->suspend(dev, state); | ||
77 | suspend_report_result(dev->class->suspend, error); | ||
78 | } | ||
79 | |||
80 | if (!error && dev->bus && dev->bus->suspend && !dev->power.power_state.event) { | ||
69 | dev_dbg(dev, "%s%s\n", | 81 | dev_dbg(dev, "%s%s\n", |
70 | suspend_verb(state.event), | 82 | suspend_verb(state.event), |
71 | ((state.event == PM_EVENT_SUSPEND) | 83 | ((state.event == PM_EVENT_SUSPEND) |
@@ -81,15 +93,74 @@ int suspend_device(struct device * dev, pm_message_t state) | |||
81 | } | 93 | } |
82 | 94 | ||
83 | 95 | ||
96 | /* | ||
97 | * This is called with interrupts off, only a single CPU | ||
98 | * running. We can't do down() on a semaphore (and we don't | ||
99 | * need the protection) | ||
100 | */ | ||
101 | static int suspend_device_late(struct device *dev, pm_message_t state) | ||
102 | { | ||
103 | int error = 0; | ||
104 | |||
105 | if (dev->power.power_state.event) { | ||
106 | dev_dbg(dev, "PM: suspend_late %d-->%d\n", | ||
107 | dev->power.power_state.event, state.event); | ||
108 | } | ||
109 | |||
110 | if (dev->bus && dev->bus->suspend_late && !dev->power.power_state.event) { | ||
111 | dev_dbg(dev, "LATE %s%s\n", | ||
112 | suspend_verb(state.event), | ||
113 | ((state.event == PM_EVENT_SUSPEND) | ||
114 | && device_may_wakeup(dev)) | ||
115 | ? ", may wakeup" | ||
116 | : "" | ||
117 | ); | ||
118 | error = dev->bus->suspend_late(dev, state); | ||
119 | suspend_report_result(dev->bus->suspend_late, error); | ||
120 | } | ||
121 | return error; | ||
122 | } | ||
123 | |||
124 | /** | ||
125 | * device_prepare_suspend - save state and prepare to suspend | ||
126 | * | ||
127 | * NOTE! Devices cannot detach at this point - not only do we | ||
128 | * hold the device list semaphores over the whole prepare, but | ||
129 | * the whole point is to do non-invasive preparatory work, not | ||
130 | * the actual suspend. | ||
131 | */ | ||
132 | int device_prepare_suspend(pm_message_t state) | ||
133 | { | ||
134 | int error = 0; | ||
135 | struct device * dev; | ||
136 | |||
137 | down(&dpm_sem); | ||
138 | down(&dpm_list_sem); | ||
139 | list_for_each_entry_reverse(dev, &dpm_active, power.entry) { | ||
140 | if (!dev->bus || !dev->bus->suspend_prepare) | ||
141 | continue; | ||
142 | error = dev->bus->suspend_prepare(dev, state); | ||
143 | if (error) | ||
144 | break; | ||
145 | } | ||
146 | up(&dpm_list_sem); | ||
147 | up(&dpm_sem); | ||
148 | return error; | ||
149 | } | ||
150 | |||
84 | /** | 151 | /** |
85 | * device_suspend - Save state and stop all devices in system. | 152 | * device_suspend - Save state and stop all devices in system. |
86 | * @state: Power state to put each device in. | 153 | * @state: Power state to put each device in. |
87 | * | 154 | * |
88 | * Walk the dpm_active list, call ->suspend() for each device, and move | 155 | * Walk the dpm_active list, call ->suspend() for each device, and move |
89 | * it to dpm_off. | 156 | * it to the dpm_off list. |
90 | * Check the return value for each. If it returns 0, then we move the | 157 | * |
91 | * the device to the dpm_off list. If it returns -EAGAIN, we move it to | 158 | * (For historical reasons, if it returns -EAGAIN, that used to mean |
92 | * the dpm_off_irq list. If we get a different error, try and back out. | 159 | * that the device would be called again with interrupts disabled. |
160 | * These days, we use the "suspend_late()" callback for that, so we | ||
161 | * print a warning and consider it an error). | ||
162 | * | ||
163 | * If we get a different error, try and back out. | ||
93 | * | 164 | * |
94 | * If we hit a failure with any of the devices, call device_resume() | 165 | * If we hit a failure with any of the devices, call device_resume() |
95 | * above to bring the suspended devices back to life. | 166 | * above to bring the suspended devices back to life. |
@@ -115,39 +186,27 @@ int device_suspend(pm_message_t state) | |||
115 | 186 | ||
116 | /* Check if the device got removed */ | 187 | /* Check if the device got removed */ |
117 | if (!list_empty(&dev->power.entry)) { | 188 | if (!list_empty(&dev->power.entry)) { |
118 | /* Move it to the dpm_off or dpm_off_irq list */ | 189 | /* Move it to the dpm_off list */ |
119 | if (!error) | 190 | if (!error) |
120 | list_move(&dev->power.entry, &dpm_off); | 191 | list_move(&dev->power.entry, &dpm_off); |
121 | else if (error == -EAGAIN) { | ||
122 | list_move(&dev->power.entry, &dpm_off_irq); | ||
123 | error = 0; | ||
124 | } | ||
125 | } | 192 | } |
126 | if (error) | 193 | if (error) |
127 | printk(KERN_ERR "Could not suspend device %s: " | 194 | printk(KERN_ERR "Could not suspend device %s: " |
128 | "error %d\n", kobject_name(&dev->kobj), error); | 195 | "error %d%s\n", |
196 | kobject_name(&dev->kobj), error, | ||
197 | error == -EAGAIN ? " (please convert to suspend_late)" : ""); | ||
129 | put_device(dev); | 198 | put_device(dev); |
130 | } | 199 | } |
131 | up(&dpm_list_sem); | 200 | up(&dpm_list_sem); |
132 | if (error) { | 201 | if (error) |
133 | /* we failed... before resuming, bring back devices from | ||
134 | * dpm_off_irq list back to main dpm_off list, we do want | ||
135 | * to call resume() on them, in case they partially suspended | ||
136 | * despite returning -EAGAIN | ||
137 | */ | ||
138 | while (!list_empty(&dpm_off_irq)) { | ||
139 | struct list_head * entry = dpm_off_irq.next; | ||
140 | list_move(entry, &dpm_off); | ||
141 | } | ||
142 | dpm_resume(); | 202 | dpm_resume(); |
143 | } | 203 | |
144 | up(&dpm_sem); | 204 | up(&dpm_sem); |
145 | return error; | 205 | return error; |
146 | } | 206 | } |
147 | 207 | ||
148 | EXPORT_SYMBOL_GPL(device_suspend); | 208 | EXPORT_SYMBOL_GPL(device_suspend); |
149 | 209 | ||
150 | |||
151 | /** | 210 | /** |
152 | * device_power_down - Shut down special devices. | 211 | * device_power_down - Shut down special devices. |
153 | * @state: Power state to enter. | 212 | * @state: Power state to enter. |
@@ -162,14 +221,17 @@ int device_power_down(pm_message_t state) | |||
162 | int error = 0; | 221 | int error = 0; |
163 | struct device * dev; | 222 | struct device * dev; |
164 | 223 | ||
165 | list_for_each_entry_reverse(dev, &dpm_off_irq, power.entry) { | 224 | while (!list_empty(&dpm_off)) { |
166 | if ((error = suspend_device(dev, state))) | 225 | struct list_head * entry = dpm_off.prev; |
167 | break; | 226 | |
227 | dev = to_device(entry); | ||
228 | error = suspend_device_late(dev, state); | ||
229 | if (error) | ||
230 | goto Error; | ||
231 | list_move(&dev->power.entry, &dpm_off_irq); | ||
168 | } | 232 | } |
169 | if (error) | 233 | |
170 | goto Error; | 234 | error = sysdev_suspend(state); |
171 | if ((error = sysdev_suspend(state))) | ||
172 | goto Error; | ||
173 | Done: | 235 | Done: |
174 | return error; | 236 | return error; |
175 | Error: | 237 | Error: |
diff --git a/include/linux/device.h b/include/linux/device.h index 8a648cd94fa9..b40be6fca6fa 100644 --- a/include/linux/device.h +++ b/include/linux/device.h | |||
@@ -51,8 +51,12 @@ struct bus_type { | |||
51 | int (*probe)(struct device * dev); | 51 | int (*probe)(struct device * dev); |
52 | int (*remove)(struct device * dev); | 52 | int (*remove)(struct device * dev); |
53 | void (*shutdown)(struct device * dev); | 53 | void (*shutdown)(struct device * dev); |
54 | int (*suspend)(struct device * dev, pm_message_t state); | 54 | |
55 | int (*resume)(struct device * dev); | 55 | int (*suspend_prepare)(struct device * dev, pm_message_t state); |
56 | int (*suspend)(struct device * dev, pm_message_t state); | ||
57 | int (*suspend_late)(struct device * dev, pm_message_t state); | ||
58 | int (*resume_early)(struct device * dev); | ||
59 | int (*resume)(struct device * dev); | ||
56 | }; | 60 | }; |
57 | 61 | ||
58 | extern int bus_register(struct bus_type * bus); | 62 | extern int bus_register(struct bus_type * bus); |
@@ -154,6 +158,9 @@ struct class { | |||
154 | 158 | ||
155 | void (*release)(struct class_device *dev); | 159 | void (*release)(struct class_device *dev); |
156 | void (*class_release)(struct class *class); | 160 | void (*class_release)(struct class *class); |
161 | |||
162 | int (*suspend)(struct device *, pm_message_t state); | ||
163 | int (*resume)(struct device *); | ||
157 | }; | 164 | }; |
158 | 165 | ||
159 | extern int class_register(struct class *); | 166 | extern int class_register(struct class *); |
diff --git a/include/linux/pm.h b/include/linux/pm.h index 658c1b93d5bb..096fb6f754cf 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h | |||
@@ -190,6 +190,7 @@ extern void device_resume(void); | |||
190 | extern suspend_disk_method_t pm_disk_mode; | 190 | extern suspend_disk_method_t pm_disk_mode; |
191 | 191 | ||
192 | extern int device_suspend(pm_message_t state); | 192 | extern int device_suspend(pm_message_t state); |
193 | extern int device_prepare_suspend(pm_message_t state); | ||
193 | 194 | ||
194 | #define device_set_wakeup_enable(dev,val) \ | 195 | #define device_set_wakeup_enable(dev,val) \ |
195 | ((dev)->power.should_wakeup = !!(val)) | 196 | ((dev)->power.should_wakeup = !!(val)) |
diff --git a/kernel/power/main.c b/kernel/power/main.c index 6d295c776794..0c3ed6ac938e 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c | |||
@@ -57,6 +57,10 @@ static int suspend_prepare(suspend_state_t state) | |||
57 | if (!pm_ops || !pm_ops->enter) | 57 | if (!pm_ops || !pm_ops->enter) |
58 | return -EPERM; | 58 | return -EPERM; |
59 | 59 | ||
60 | error = device_prepare_suspend(PMSG_SUSPEND); | ||
61 | if (error) | ||
62 | return error; | ||
63 | |||
60 | pm_prepare_console(); | 64 | pm_prepare_console(); |
61 | 65 | ||
62 | disable_nonboot_cpus(); | 66 | disable_nonboot_cpus(); |