2005-04-17 02:20:36 +04:00
/*
* 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 ;
2005-03-21 21:41:04 +03:00
down ( & dev - > sem ) ;
2005-05-09 19:07:00 +04:00
if ( dev - > power . power_state ) {
dev_dbg ( dev , " PM: suspend %d-->%d \n " ,
dev - > power . power_state , state ) ;
}
if ( dev - > power . pm_parent
& & dev - > power . pm_parent - > power . power_state ) {
dev_err ( dev ,
" PM: suspend %d->%d, parent %s already %d \n " ,
dev - > power . power_state , state ,
dev - > power . pm_parent - > bus_id ,
dev - > power . pm_parent - > power . power_state ) ;
}
2005-04-17 02:20:36 +04:00
dev - > power . prev_state = dev - > power . power_state ;
2005-05-09 19:07:00 +04:00
if ( dev - > bus & & dev - > bus - > suspend & & ! dev - > power . power_state ) {
dev_dbg ( dev , " suspending \n " ) ;
2005-04-17 02:20:36 +04:00
error = dev - > bus - > suspend ( dev , state ) ;
2005-05-09 19:07:00 +04:00
}
2005-03-21 21:41:04 +03:00
up ( & dev - > sem ) ;
2005-04-17 02:20:36 +04:00
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 ) ;
2005-05-31 11:08:49 +04:00
if ( error ) {
/* we failed... before resuming, bring back devices from
* dpm_off_irq list back to main dpm_off list , we do want
* to call resume ( ) on them , in case they partially suspended
* despite returning - EAGAIN
*/
while ( ! list_empty ( & dpm_off_irq ) ) {
struct list_head * entry = dpm_off_irq . next ;
list_del ( entry ) ;
list_add ( entry , & dpm_off ) ;
}
2005-04-17 02:20:36 +04:00
dpm_resume ( ) ;
2005-05-31 11:08:49 +04:00
}
2005-04-17 02:20:36 +04:00
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 ) ;