| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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 |  | 
| mochel@digitalimplant.org | af70316 | 2005-03-21 10:41:04 -0800 | [diff] [blame] | 42 | down(&dev->sem); | 
| Pavel Machek | ca078ba | 2005-09-03 15:56:57 -0700 | [diff] [blame] | 43 | if (dev->power.power_state.event) { | 
| David Brownell | 82428b6 | 2005-05-09 08:07:00 -0700 | [diff] [blame] | 44 | dev_dbg(dev, "PM: suspend %d-->%d\n", | 
| Pavel Machek | ca078ba | 2005-09-03 15:56:57 -0700 | [diff] [blame] | 45 | dev->power.power_state.event, state.event); | 
| David Brownell | 82428b6 | 2005-05-09 08:07:00 -0700 | [diff] [blame] | 46 | } | 
|  | 47 | if (dev->power.pm_parent | 
| Pavel Machek | ca078ba | 2005-09-03 15:56:57 -0700 | [diff] [blame] | 48 | && dev->power.pm_parent->power.power_state.event) { | 
| David Brownell | 82428b6 | 2005-05-09 08:07:00 -0700 | [diff] [blame] | 49 | dev_err(dev, | 
|  | 50 | "PM: suspend %d->%d, parent %s already %d\n", | 
| Pavel Machek | ca078ba | 2005-09-03 15:56:57 -0700 | [diff] [blame] | 51 | dev->power.power_state.event, state.event, | 
| David Brownell | 82428b6 | 2005-05-09 08:07:00 -0700 | [diff] [blame] | 52 | dev->power.pm_parent->bus_id, | 
| Pavel Machek | ca078ba | 2005-09-03 15:56:57 -0700 | [diff] [blame] | 53 | dev->power.pm_parent->power.power_state.event); | 
| David Brownell | 82428b6 | 2005-05-09 08:07:00 -0700 | [diff] [blame] | 54 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 55 |  | 
|  | 56 | dev->power.prev_state = dev->power.power_state; | 
|  | 57 |  | 
| Pavel Machek | ca078ba | 2005-09-03 15:56:57 -0700 | [diff] [blame] | 58 | if (dev->bus && dev->bus->suspend && !dev->power.power_state.event) { | 
| David Brownell | 82428b6 | 2005-05-09 08:07:00 -0700 | [diff] [blame] | 59 | dev_dbg(dev, "suspending\n"); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 60 | error = dev->bus->suspend(dev, state); | 
| David Brownell | 82428b6 | 2005-05-09 08:07:00 -0700 | [diff] [blame] | 61 | } | 
| mochel@digitalimplant.org | af70316 | 2005-03-21 10:41:04 -0800 | [diff] [blame] | 62 | up(&dev->sem); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 63 | return error; | 
|  | 64 | } | 
|  | 65 |  | 
|  | 66 |  | 
|  | 67 | /** | 
|  | 68 | *	device_suspend - Save state and stop all devices in system. | 
|  | 69 | *	@state:		Power state to put each device in. | 
|  | 70 | * | 
|  | 71 | *	Walk the dpm_active list, call ->suspend() for each device, and move | 
|  | 72 | *	it to dpm_off. | 
|  | 73 | *	Check the return value for each. If it returns 0, then we move the | 
|  | 74 | *	the device to the dpm_off list. If it returns -EAGAIN, we move it to | 
|  | 75 | *	the dpm_off_irq list. If we get a different error, try and back out. | 
|  | 76 | * | 
|  | 77 | *	If we hit a failure with any of the devices, call device_resume() | 
|  | 78 | *	above to bring the suspended devices back to life. | 
|  | 79 | * | 
|  | 80 | */ | 
|  | 81 |  | 
|  | 82 | int device_suspend(pm_message_t state) | 
|  | 83 | { | 
|  | 84 | int error = 0; | 
|  | 85 |  | 
|  | 86 | down(&dpm_sem); | 
|  | 87 | down(&dpm_list_sem); | 
|  | 88 | while (!list_empty(&dpm_active) && error == 0) { | 
|  | 89 | struct list_head * entry = dpm_active.prev; | 
|  | 90 | struct device * dev = to_device(entry); | 
|  | 91 |  | 
|  | 92 | get_device(dev); | 
|  | 93 | up(&dpm_list_sem); | 
|  | 94 |  | 
|  | 95 | error = suspend_device(dev, state); | 
|  | 96 |  | 
|  | 97 | down(&dpm_list_sem); | 
|  | 98 |  | 
|  | 99 | /* Check if the device got removed */ | 
|  | 100 | if (!list_empty(&dev->power.entry)) { | 
|  | 101 | /* Move it to the dpm_off or dpm_off_irq list */ | 
|  | 102 | if (!error) { | 
|  | 103 | list_del(&dev->power.entry); | 
|  | 104 | list_add(&dev->power.entry, &dpm_off); | 
|  | 105 | } else if (error == -EAGAIN) { | 
|  | 106 | list_del(&dev->power.entry); | 
|  | 107 | list_add(&dev->power.entry, &dpm_off_irq); | 
|  | 108 | error = 0; | 
|  | 109 | } | 
|  | 110 | } | 
|  | 111 | if (error) | 
|  | 112 | printk(KERN_ERR "Could not suspend device %s: " | 
|  | 113 | "error %d\n", kobject_name(&dev->kobj), error); | 
|  | 114 | put_device(dev); | 
|  | 115 | } | 
|  | 116 | up(&dpm_list_sem); | 
| Benjamin Herrenschmidt | 42b16c0 | 2005-05-31 17:08:49 +1000 | [diff] [blame] | 117 | if (error) { | 
|  | 118 | /* we failed... before resuming, bring back devices from | 
|  | 119 | * dpm_off_irq list back to main dpm_off list, we do want | 
|  | 120 | * to call resume() on them, in case they partially suspended | 
|  | 121 | * despite returning -EAGAIN | 
|  | 122 | */ | 
|  | 123 | while (!list_empty(&dpm_off_irq)) { | 
|  | 124 | struct list_head * entry = dpm_off_irq.next; | 
|  | 125 | list_del(entry); | 
|  | 126 | list_add(entry, &dpm_off); | 
|  | 127 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 128 | dpm_resume(); | 
| Benjamin Herrenschmidt | 42b16c0 | 2005-05-31 17:08:49 +1000 | [diff] [blame] | 129 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 130 | up(&dpm_sem); | 
|  | 131 | return error; | 
|  | 132 | } | 
|  | 133 |  | 
|  | 134 | EXPORT_SYMBOL_GPL(device_suspend); | 
|  | 135 |  | 
|  | 136 |  | 
|  | 137 | /** | 
|  | 138 | *	device_power_down - Shut down special devices. | 
|  | 139 | *	@state:		Power state to enter. | 
|  | 140 | * | 
|  | 141 | *	Walk the dpm_off_irq list, calling ->power_down() for each device that | 
|  | 142 | *	couldn't power down the device with interrupts enabled. When we're | 
|  | 143 | *	done, power down system devices. | 
|  | 144 | */ | 
|  | 145 |  | 
|  | 146 | int device_power_down(pm_message_t state) | 
|  | 147 | { | 
|  | 148 | int error = 0; | 
|  | 149 | struct device * dev; | 
|  | 150 |  | 
|  | 151 | list_for_each_entry_reverse(dev, &dpm_off_irq, power.entry) { | 
|  | 152 | if ((error = suspend_device(dev, state))) | 
|  | 153 | break; | 
|  | 154 | } | 
|  | 155 | if (error) | 
|  | 156 | goto Error; | 
|  | 157 | if ((error = sysdev_suspend(state))) | 
|  | 158 | goto Error; | 
|  | 159 | Done: | 
|  | 160 | return error; | 
|  | 161 | Error: | 
|  | 162 | printk(KERN_ERR "Could not power down device %s: " | 
|  | 163 | "error %d\n", kobject_name(&dev->kobj), error); | 
|  | 164 | dpm_power_up(); | 
|  | 165 | goto Done; | 
|  | 166 | } | 
|  | 167 |  | 
|  | 168 | EXPORT_SYMBOL_GPL(device_power_down); | 
|  | 169 |  |