diff options
Diffstat (limited to 'drivers/base/power/suspend.c')
-rw-r--r-- | drivers/base/power/suspend.c | 122 |
1 files changed, 92 insertions, 30 deletions
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: |