| 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> | 
| Andrew Morton | 0266949 | 2006-03-23 01:38:34 -0800 | [diff] [blame] | 12 | #include <linux/kallsyms.h> | 
|  | 13 | #include <linux/pm.h> | 
| Adrian Bunk | f67d115 | 2006-01-19 17:30:17 +0100 | [diff] [blame] | 14 | #include "../base.h" | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 15 | #include "power.h" | 
|  | 16 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 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 |  | 
| David Brownell | fd869db | 2006-05-16 17:03:25 -0700 | [diff] [blame] | 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"; | 
|  | 37 | default:		return "(unknown suspend event)"; | 
|  | 38 | } | 
|  | 39 | } | 
|  | 40 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 41 |  | 
|  | 42 | /** | 
|  | 43 | *	suspend_device - Save state of one device. | 
|  | 44 | *	@dev:	Device. | 
|  | 45 | *	@state:	Power state device is entering. | 
|  | 46 | */ | 
|  | 47 |  | 
|  | 48 | int suspend_device(struct device * dev, pm_message_t state) | 
|  | 49 | { | 
|  | 50 | int error = 0; | 
|  | 51 |  | 
| mochel@digitalimplant.org | af70316 | 2005-03-21 10:41:04 -0800 | [diff] [blame] | 52 | down(&dev->sem); | 
| Pavel Machek | ca078ba | 2005-09-03 15:56:57 -0700 | [diff] [blame] | 53 | if (dev->power.power_state.event) { | 
| David Brownell | 82428b6 | 2005-05-09 08:07:00 -0700 | [diff] [blame] | 54 | dev_dbg(dev, "PM: suspend %d-->%d\n", | 
| Pavel Machek | ca078ba | 2005-09-03 15:56:57 -0700 | [diff] [blame] | 55 | dev->power.power_state.event, state.event); | 
| David Brownell | 82428b6 | 2005-05-09 08:07:00 -0700 | [diff] [blame] | 56 | } | 
|  | 57 | if (dev->power.pm_parent | 
| Pavel Machek | ca078ba | 2005-09-03 15:56:57 -0700 | [diff] [blame] | 58 | && dev->power.pm_parent->power.power_state.event) { | 
| David Brownell | 82428b6 | 2005-05-09 08:07:00 -0700 | [diff] [blame] | 59 | dev_err(dev, | 
|  | 60 | "PM: suspend %d->%d, parent %s already %d\n", | 
| Pavel Machek | ca078ba | 2005-09-03 15:56:57 -0700 | [diff] [blame] | 61 | dev->power.power_state.event, state.event, | 
| David Brownell | 82428b6 | 2005-05-09 08:07:00 -0700 | [diff] [blame] | 62 | dev->power.pm_parent->bus_id, | 
| Pavel Machek | ca078ba | 2005-09-03 15:56:57 -0700 | [diff] [blame] | 63 | dev->power.pm_parent->power.power_state.event); | 
| David Brownell | 82428b6 | 2005-05-09 08:07:00 -0700 | [diff] [blame] | 64 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 65 |  | 
|  | 66 | dev->power.prev_state = dev->power.power_state; | 
|  | 67 |  | 
| Pavel Machek | ca078ba | 2005-09-03 15:56:57 -0700 | [diff] [blame] | 68 | if (dev->bus && dev->bus->suspend && !dev->power.power_state.event) { | 
| David Brownell | fd869db | 2006-05-16 17:03:25 -0700 | [diff] [blame] | 69 | dev_dbg(dev, "%s%s\n", | 
|  | 70 | suspend_verb(state.event), | 
|  | 71 | ((state.event == PM_EVENT_SUSPEND) | 
|  | 72 | && device_may_wakeup(dev)) | 
|  | 73 | ? ", may wakeup" | 
|  | 74 | : "" | 
|  | 75 | ); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 76 | error = dev->bus->suspend(dev, state); | 
| Andrew Morton | 0266949 | 2006-03-23 01:38:34 -0800 | [diff] [blame] | 77 | suspend_report_result(dev->bus->suspend, error); | 
| David Brownell | 82428b6 | 2005-05-09 08:07:00 -0700 | [diff] [blame] | 78 | } | 
| mochel@digitalimplant.org | af70316 | 2005-03-21 10:41:04 -0800 | [diff] [blame] | 79 | up(&dev->sem); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 80 | return error; | 
|  | 81 | } | 
|  | 82 |  | 
| Andrew Morton | 760f1fc | 2006-05-30 21:26:03 -0700 | [diff] [blame] | 83 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 84 | /** | 
|  | 85 | *	device_suspend - Save state and stop all devices in system. | 
|  | 86 | *	@state:		Power state to put each device in. | 
|  | 87 | * | 
|  | 88 | *	Walk the dpm_active list, call ->suspend() for each device, and move | 
|  | 89 | *	it to dpm_off. | 
|  | 90 | *	Check the return value for each. If it returns 0, then we move the | 
|  | 91 | *	the device to the dpm_off list. If it returns -EAGAIN, we move it to | 
|  | 92 | *	the dpm_off_irq list. If we get a different error, try and back out. | 
|  | 93 | * | 
|  | 94 | *	If we hit a failure with any of the devices, call device_resume() | 
|  | 95 | *	above to bring the suspended devices back to life. | 
|  | 96 | * | 
|  | 97 | */ | 
|  | 98 |  | 
|  | 99 | int device_suspend(pm_message_t state) | 
|  | 100 | { | 
|  | 101 | int error = 0; | 
|  | 102 |  | 
|  | 103 | down(&dpm_sem); | 
|  | 104 | down(&dpm_list_sem); | 
|  | 105 | while (!list_empty(&dpm_active) && error == 0) { | 
|  | 106 | struct list_head * entry = dpm_active.prev; | 
|  | 107 | struct device * dev = to_device(entry); | 
|  | 108 |  | 
|  | 109 | get_device(dev); | 
|  | 110 | up(&dpm_list_sem); | 
|  | 111 |  | 
|  | 112 | error = suspend_device(dev, state); | 
|  | 113 |  | 
|  | 114 | down(&dpm_list_sem); | 
|  | 115 |  | 
|  | 116 | /* Check if the device got removed */ | 
|  | 117 | if (!list_empty(&dev->power.entry)) { | 
|  | 118 | /* Move it to the dpm_off or dpm_off_irq list */ | 
| Akinobu Mita | 1bfba4e | 2006-06-26 00:24:40 -0700 | [diff] [blame] | 119 | if (!error) | 
|  | 120 | list_move(&dev->power.entry, &dpm_off); | 
|  | 121 | else if (error == -EAGAIN) { | 
|  | 122 | list_move(&dev->power.entry, &dpm_off_irq); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 123 | error = 0; | 
|  | 124 | } | 
|  | 125 | } | 
|  | 126 | if (error) | 
|  | 127 | printk(KERN_ERR "Could not suspend device %s: " | 
|  | 128 | "error %d\n", kobject_name(&dev->kobj), error); | 
|  | 129 | put_device(dev); | 
|  | 130 | } | 
|  | 131 | up(&dpm_list_sem); | 
| Benjamin Herrenschmidt | 42b16c05 | 2005-05-31 17:08:49 +1000 | [diff] [blame] | 132 | 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; | 
| Akinobu Mita | 1bfba4e | 2006-06-26 00:24:40 -0700 | [diff] [blame] | 140 | list_move(entry, &dpm_off); | 
| Benjamin Herrenschmidt | 42b16c05 | 2005-05-31 17:08:49 +1000 | [diff] [blame] | 141 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 142 | dpm_resume(); | 
| Benjamin Herrenschmidt | 42b16c05 | 2005-05-31 17:08:49 +1000 | [diff] [blame] | 143 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 144 | up(&dpm_sem); | 
|  | 145 | return error; | 
|  | 146 | } | 
|  | 147 |  | 
|  | 148 | EXPORT_SYMBOL_GPL(device_suspend); | 
|  | 149 |  | 
|  | 150 |  | 
|  | 151 | /** | 
|  | 152 | *	device_power_down - Shut down special devices. | 
|  | 153 | *	@state:		Power state to enter. | 
|  | 154 | * | 
|  | 155 | *	Walk the dpm_off_irq list, calling ->power_down() for each device that | 
|  | 156 | *	couldn't power down the device with interrupts enabled. When we're | 
|  | 157 | *	done, power down system devices. | 
|  | 158 | */ | 
|  | 159 |  | 
|  | 160 | int device_power_down(pm_message_t state) | 
|  | 161 | { | 
|  | 162 | int error = 0; | 
|  | 163 | struct device * dev; | 
|  | 164 |  | 
|  | 165 | list_for_each_entry_reverse(dev, &dpm_off_irq, power.entry) { | 
|  | 166 | if ((error = suspend_device(dev, state))) | 
|  | 167 | break; | 
|  | 168 | } | 
|  | 169 | if (error) | 
|  | 170 | goto Error; | 
|  | 171 | if ((error = sysdev_suspend(state))) | 
|  | 172 | goto Error; | 
|  | 173 | Done: | 
|  | 174 | return error; | 
|  | 175 | Error: | 
|  | 176 | printk(KERN_ERR "Could not power down device %s: " | 
|  | 177 | "error %d\n", kobject_name(&dev->kobj), error); | 
|  | 178 | dpm_power_up(); | 
|  | 179 | goto Done; | 
|  | 180 | } | 
|  | 181 |  | 
|  | 182 | EXPORT_SYMBOL_GPL(device_power_down); | 
|  | 183 |  | 
| Andrew Morton | 0266949 | 2006-03-23 01:38:34 -0800 | [diff] [blame] | 184 | void __suspend_report_result(const char *function, void *fn, int ret) | 
|  | 185 | { | 
|  | 186 | if (ret) { | 
|  | 187 | printk(KERN_ERR "%s(): ", function); | 
|  | 188 | print_fn_descriptor_symbol("%s() returns ", (unsigned long)fn); | 
|  | 189 | printk("%d\n", ret); | 
|  | 190 | } | 
|  | 191 | } | 
|  | 192 | EXPORT_SYMBOL_GPL(__suspend_report_result); |