2012-04-30 00:53:22 +04:00
/*
* kernel / power / autosleep . c
*
* Opportunistic sleep support .
*
* Copyright ( C ) 2012 Rafael J . Wysocki < rjw @ sisk . pl >
*/
# include <linux/device.h>
# include <linux/mutex.h>
# include <linux/pm_wakeup.h>
# include "power.h"
static suspend_state_t autosleep_state ;
static struct workqueue_struct * autosleep_wq ;
/*
* Note : it is only safe to mutex_lock ( & autosleep_lock ) if a wakeup_source
* is active , otherwise a deadlock with try_to_suspend ( ) is possible .
* Alternatively mutex_lock_interruptible ( ) can be used . This will then fail
* if an auto_sleep cycle tries to freeze processes .
*/
static DEFINE_MUTEX ( autosleep_lock ) ;
static struct wakeup_source * autosleep_ws ;
static void try_to_suspend ( struct work_struct * work )
{
unsigned int initial_count , final_count ;
if ( ! pm_get_wakeup_count ( & initial_count , true ) )
goto out ;
mutex_lock ( & autosleep_lock ) ;
2013-07-11 12:03:45 +04:00
if ( ! pm_save_wakeup_count ( initial_count ) | |
system_state ! = SYSTEM_RUNNING ) {
2012-04-30 00:53:22 +04:00
mutex_unlock ( & autosleep_lock ) ;
goto out ;
}
if ( autosleep_state = = PM_SUSPEND_ON ) {
mutex_unlock ( & autosleep_lock ) ;
return ;
}
if ( autosleep_state > = PM_SUSPEND_MAX )
hibernate ( ) ;
else
pm_suspend ( autosleep_state ) ;
mutex_unlock ( & autosleep_lock ) ;
if ( ! pm_get_wakeup_count ( & final_count , false ) )
goto out ;
/*
* If the wakeup occured for an unknown reason , wait to prevent the
* system from trying to suspend and waking up in a tight loop .
*/
if ( final_count = = initial_count )
schedule_timeout_uninterruptible ( HZ / 2 ) ;
out :
queue_up_suspend_work ( ) ;
}
static DECLARE_WORK ( suspend_work , try_to_suspend ) ;
void queue_up_suspend_work ( void )
{
2013-01-11 16:37:33 +04:00
if ( autosleep_state > PM_SUSPEND_ON )
2012-04-30 00:53:22 +04:00
queue_work ( autosleep_wq , & suspend_work ) ;
}
suspend_state_t pm_autosleep_state ( void )
{
return autosleep_state ;
}
int pm_autosleep_lock ( void )
{
return mutex_lock_interruptible ( & autosleep_lock ) ;
}
void pm_autosleep_unlock ( void )
{
mutex_unlock ( & autosleep_lock ) ;
}
int pm_autosleep_set_state ( suspend_state_t state )
{
# ifndef CONFIG_HIBERNATION
if ( state > = PM_SUSPEND_MAX )
return - EINVAL ;
# endif
__pm_stay_awake ( autosleep_ws ) ;
mutex_lock ( & autosleep_lock ) ;
autosleep_state = state ;
__pm_relax ( autosleep_ws ) ;
2012-04-30 00:53:32 +04:00
if ( state > PM_SUSPEND_ON ) {
pm_wakep_autosleep_enabled ( true ) ;
2012-04-30 00:53:22 +04:00
queue_up_suspend_work ( ) ;
2012-04-30 00:53:32 +04:00
} else {
pm_wakep_autosleep_enabled ( false ) ;
}
2012-04-30 00:53:22 +04:00
mutex_unlock ( & autosleep_lock ) ;
return 0 ;
}
int __init pm_autosleep_init ( void )
{
autosleep_ws = wakeup_source_register ( " autosleep " ) ;
if ( ! autosleep_ws )
return - ENOMEM ;
autosleep_wq = alloc_ordered_workqueue ( " autosleep " , 0 ) ;
if ( autosleep_wq )
return 0 ;
wakeup_source_unregister ( autosleep_ws ) ;
return - ENOMEM ;
}