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> | |
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 | ||
82428b62 DB |
42 | if (dev->power.power_state) { |
43 | dev_dbg(dev, "PM: suspend %d-->%d\n", | |
44 | dev->power.power_state, state); | |
45 | } | |
46 | if (dev->power.pm_parent | |
47 | && dev->power.pm_parent->power.power_state) { | |
48 | dev_err(dev, | |
49 | "PM: suspend %d->%d, parent %s already %d\n", | |
50 | dev->power.power_state, state, | |
51 | dev->power.pm_parent->bus_id, | |
52 | dev->power.pm_parent->power.power_state); | |
53 | } | |
1da177e4 LT |
54 | |
55 | dev->power.prev_state = dev->power.power_state; | |
56 | ||
82428b62 DB |
57 | if (dev->bus && dev->bus->suspend && !dev->power.power_state) { |
58 | dev_dbg(dev, "suspending\n"); | |
1da177e4 | 59 | error = dev->bus->suspend(dev, state); |
82428b62 | 60 | } |
1da177e4 LT |
61 | |
62 | return error; | |
63 | } | |
64 | ||
65 | ||
66 | /** | |
67 | * device_suspend - Save state and stop all devices in system. | |
68 | * @state: Power state to put each device in. | |
69 | * | |
70 | * Walk the dpm_active list, call ->suspend() for each device, and move | |
71 | * it to dpm_off. | |
72 | * Check the return value for each. If it returns 0, then we move the | |
73 | * the device to the dpm_off list. If it returns -EAGAIN, we move it to | |
74 | * the dpm_off_irq list. If we get a different error, try and back out. | |
75 | * | |
76 | * If we hit a failure with any of the devices, call device_resume() | |
77 | * above to bring the suspended devices back to life. | |
78 | * | |
79 | */ | |
80 | ||
81 | int device_suspend(pm_message_t state) | |
82 | { | |
83 | int error = 0; | |
84 | ||
85 | down(&dpm_sem); | |
86 | down(&dpm_list_sem); | |
87 | while (!list_empty(&dpm_active) && error == 0) { | |
88 | struct list_head * entry = dpm_active.prev; | |
89 | struct device * dev = to_device(entry); | |
90 | ||
91 | get_device(dev); | |
92 | up(&dpm_list_sem); | |
93 | ||
94 | error = suspend_device(dev, state); | |
95 | ||
96 | down(&dpm_list_sem); | |
97 | ||
98 | /* Check if the device got removed */ | |
99 | if (!list_empty(&dev->power.entry)) { | |
100 | /* Move it to the dpm_off or dpm_off_irq list */ | |
101 | if (!error) { | |
102 | list_del(&dev->power.entry); | |
103 | list_add(&dev->power.entry, &dpm_off); | |
104 | } else if (error == -EAGAIN) { | |
105 | list_del(&dev->power.entry); | |
106 | list_add(&dev->power.entry, &dpm_off_irq); | |
107 | error = 0; | |
108 | } | |
109 | } | |
110 | if (error) | |
111 | printk(KERN_ERR "Could not suspend device %s: " | |
112 | "error %d\n", kobject_name(&dev->kobj), error); | |
113 | put_device(dev); | |
114 | } | |
115 | up(&dpm_list_sem); | |
116 | if (error) | |
117 | dpm_resume(); | |
118 | up(&dpm_sem); | |
119 | return error; | |
120 | } | |
121 | ||
122 | EXPORT_SYMBOL_GPL(device_suspend); | |
123 | ||
124 | ||
125 | /** | |
126 | * device_power_down - Shut down special devices. | |
127 | * @state: Power state to enter. | |
128 | * | |
129 | * Walk the dpm_off_irq list, calling ->power_down() for each device that | |
130 | * couldn't power down the device with interrupts enabled. When we're | |
131 | * done, power down system devices. | |
132 | */ | |
133 | ||
134 | int device_power_down(pm_message_t state) | |
135 | { | |
136 | int error = 0; | |
137 | struct device * dev; | |
138 | ||
139 | list_for_each_entry_reverse(dev, &dpm_off_irq, power.entry) { | |
140 | if ((error = suspend_device(dev, state))) | |
141 | break; | |
142 | } | |
143 | if (error) | |
144 | goto Error; | |
145 | if ((error = sysdev_suspend(state))) | |
146 | goto Error; | |
147 | Done: | |
148 | return error; | |
149 | Error: | |
150 | printk(KERN_ERR "Could not power down device %s: " | |
151 | "error %d\n", kobject_name(&dev->kobj), error); | |
152 | dpm_power_up(); | |
153 | goto Done; | |
154 | } | |
155 | ||
156 | EXPORT_SYMBOL_GPL(device_power_down); | |
157 |