text: /*
   * 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>
  
  #ifdef CONFIG_POWERSUSPEND
  #include <linux/powersuspend.h>
  #endif
  
  #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);
  
  	if (!pm_save_wakeup_count(initial_count) ||
  		system_state != SYSTEM_RUNNING) {
  		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)
  {
  	if (autosleep_state > PM_SUSPEND_ON)
  		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);
  
  	if (state > PM_SUSPEND_ON) {
  		pm_wakep_autosleep_enabled(true);
  		queue_up_suspend_work();
  #ifdef CONFIG_POWERSUSPEND
  		// Yank555.lu : add hook to handle powersuspend tasks (sleep)
  		set_power_suspend_state_autosleep_hook(POWER_SUSPEND_ACTIVE);
  #endif
  	} else {
  		pm_wakep_autosleep_enabled(false);
  #ifdef CONFIG_POWERSUSPEND
  		// Yank555.lu : add hook to handle powersuspend tasks (wakeup)
  		set_power_suspend_state_autosleep_hook(POWER_SUSPEND_INACTIVE);
  #endif
  	}
  
  	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;
  }
  