diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/base/power/suspend.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/base/power/suspend.c')
-rw-r--r-- | drivers/base/power/suspend.c | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/drivers/base/power/suspend.c b/drivers/base/power/suspend.c new file mode 100644 index 00000000000..a0b5cf689e6 --- /dev/null +++ b/drivers/base/power/suspend.c @@ -0,0 +1,144 @@ +/* + * suspend.c - Functions for putting devices to sleep. + * + * Copyright (c) 2003 Patrick Mochel + * Copyright (c) 2003 Open Source Development Labs + * + * This file is released under the GPLv2 + * + */ + +#include <linux/device.h> +#include "power.h" + +extern int sysdev_suspend(pm_message_t state); + +/* + * The entries in the dpm_active list are in a depth first order, simply + * because children are guaranteed to be discovered after parents, and + * are inserted at the back of the list on discovery. + * + * All list on the suspend path are done in reverse order, so we operate + * on the leaves of the device tree (or forests, depending on how you want + * to look at it ;) first. As nodes are removed from the back of the list, + * they are inserted into the front of their destintation lists. + * + * Things are the reverse on the resume path - iterations are done in + * forward order, and nodes are inserted at the back of their destination + * lists. This way, the ancestors will be accessed before their descendents. + */ + + +/** + * suspend_device - Save state of one device. + * @dev: Device. + * @state: Power state device is entering. + */ + +int suspend_device(struct device * dev, pm_message_t state) +{ + int error = 0; + + dev_dbg(dev, "suspending\n"); + + dev->power.prev_state = dev->power.power_state; + + if (dev->bus && dev->bus->suspend && !dev->power.power_state) + error = dev->bus->suspend(dev, state); + + return error; +} + + +/** + * device_suspend - Save state and stop all devices in system. + * @state: Power state to put each device in. + * + * Walk the dpm_active list, call ->suspend() for each device, and move + * it to dpm_off. + * Check the return value for each. If it returns 0, then we move the + * the device to the dpm_off list. If it returns -EAGAIN, we move it to + * the dpm_off_irq list. If we get a different error, try and back out. + * + * If we hit a failure with any of the devices, call device_resume() + * above to bring the suspended devices back to life. + * + */ + +int device_suspend(pm_message_t state) +{ + int error = 0; + + down(&dpm_sem); + down(&dpm_list_sem); + while (!list_empty(&dpm_active) && error == 0) { + struct list_head * entry = dpm_active.prev; + struct device * dev = to_device(entry); + + get_device(dev); + up(&dpm_list_sem); + + error = suspend_device(dev, state); + + down(&dpm_list_sem); + + /* Check if the device got removed */ + if (!list_empty(&dev->power.entry)) { + /* Move it to the dpm_off or dpm_off_irq list */ + if (!error) { + list_del(&dev->power.entry); + list_add(&dev->power.entry, &dpm_off); + } else if (error == -EAGAIN) { + list_del(&dev->power.entry); + list_add(&dev->power.entry, &dpm_off_irq); + error = 0; + } + } + if (error) + printk(KERN_ERR "Could not suspend device %s: " + "error %d\n", kobject_name(&dev->kobj), error); + put_device(dev); + } + up(&dpm_list_sem); + if (error) + dpm_resume(); + up(&dpm_sem); + return error; +} + +EXPORT_SYMBOL_GPL(device_suspend); + + +/** + * device_power_down - Shut down special devices. + * @state: Power state to enter. + * + * Walk the dpm_off_irq list, calling ->power_down() for each device that + * couldn't power down the device with interrupts enabled. When we're + * done, power down system devices. + */ + +int device_power_down(pm_message_t state) +{ + int error = 0; + struct device * dev; + + list_for_each_entry_reverse(dev, &dpm_off_irq, power.entry) { + if ((error = suspend_device(dev, state))) + break; + } + if (error) + goto Error; + if ((error = sysdev_suspend(state))) + goto Error; + Done: + return error; + Error: + printk(KERN_ERR "Could not power down device %s: " + "error %d\n", kobject_name(&dev->kobj), error); + dpm_power_up(); + goto Done; +} + +EXPORT_SYMBOL_GPL(device_power_down); + |