| /* |
| * 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); |
| |
| if (!pm_save_wakeup_count(initial_count)) { |
| 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 (!work_pending(&suspend_work) && 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(); |
| } else { |
| pm_wakep_autosleep_enabled(false); |
| } |
| |
| 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; |
| } |