162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * kernel/power/autosleep.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Opportunistic sleep support. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Copyright (C) 2012 Rafael J. Wysocki <rjw@sisk.pl> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/device.h> 1162306a36Sopenharmony_ci#include <linux/mutex.h> 1262306a36Sopenharmony_ci#include <linux/pm_wakeup.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include "power.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_cistatic suspend_state_t autosleep_state; 1762306a36Sopenharmony_cistatic struct workqueue_struct *autosleep_wq; 1862306a36Sopenharmony_ci/* 1962306a36Sopenharmony_ci * Note: it is only safe to mutex_lock(&autosleep_lock) if a wakeup_source 2062306a36Sopenharmony_ci * is active, otherwise a deadlock with try_to_suspend() is possible. 2162306a36Sopenharmony_ci * Alternatively mutex_lock_interruptible() can be used. This will then fail 2262306a36Sopenharmony_ci * if an auto_sleep cycle tries to freeze processes. 2362306a36Sopenharmony_ci */ 2462306a36Sopenharmony_cistatic DEFINE_MUTEX(autosleep_lock); 2562306a36Sopenharmony_cistatic struct wakeup_source *autosleep_ws; 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistatic void try_to_suspend(struct work_struct *work) 2862306a36Sopenharmony_ci{ 2962306a36Sopenharmony_ci unsigned int initial_count, final_count; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci if (!pm_get_wakeup_count(&initial_count, true)) 3262306a36Sopenharmony_ci goto out; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci mutex_lock(&autosleep_lock); 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci if (!pm_save_wakeup_count(initial_count) || 3762306a36Sopenharmony_ci system_state != SYSTEM_RUNNING) { 3862306a36Sopenharmony_ci mutex_unlock(&autosleep_lock); 3962306a36Sopenharmony_ci goto out; 4062306a36Sopenharmony_ci } 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci if (autosleep_state == PM_SUSPEND_ON) { 4362306a36Sopenharmony_ci mutex_unlock(&autosleep_lock); 4462306a36Sopenharmony_ci return; 4562306a36Sopenharmony_ci } 4662306a36Sopenharmony_ci if (autosleep_state >= PM_SUSPEND_MAX) 4762306a36Sopenharmony_ci hibernate(); 4862306a36Sopenharmony_ci else 4962306a36Sopenharmony_ci pm_suspend(autosleep_state); 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci mutex_unlock(&autosleep_lock); 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci if (!pm_get_wakeup_count(&final_count, false)) 5462306a36Sopenharmony_ci goto out; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci /* 5762306a36Sopenharmony_ci * If the wakeup occurred for an unknown reason, wait to prevent the 5862306a36Sopenharmony_ci * system from trying to suspend and waking up in a tight loop. 5962306a36Sopenharmony_ci */ 6062306a36Sopenharmony_ci if (final_count == initial_count) 6162306a36Sopenharmony_ci schedule_timeout_uninterruptible(HZ / 2); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci out: 6462306a36Sopenharmony_ci queue_up_suspend_work(); 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic DECLARE_WORK(suspend_work, try_to_suspend); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_civoid queue_up_suspend_work(void) 7062306a36Sopenharmony_ci{ 7162306a36Sopenharmony_ci if (autosleep_state > PM_SUSPEND_ON) 7262306a36Sopenharmony_ci queue_work(autosleep_wq, &suspend_work); 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cisuspend_state_t pm_autosleep_state(void) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci return autosleep_state; 7862306a36Sopenharmony_ci} 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ciint pm_autosleep_lock(void) 8162306a36Sopenharmony_ci{ 8262306a36Sopenharmony_ci return mutex_lock_interruptible(&autosleep_lock); 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_civoid pm_autosleep_unlock(void) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci mutex_unlock(&autosleep_lock); 8862306a36Sopenharmony_ci} 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ciint pm_autosleep_set_state(suspend_state_t state) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci#ifndef CONFIG_HIBERNATION 9462306a36Sopenharmony_ci if (state >= PM_SUSPEND_MAX) 9562306a36Sopenharmony_ci return -EINVAL; 9662306a36Sopenharmony_ci#endif 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci __pm_stay_awake(autosleep_ws); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci mutex_lock(&autosleep_lock); 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci autosleep_state = state; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci __pm_relax(autosleep_ws); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci if (state > PM_SUSPEND_ON) { 10762306a36Sopenharmony_ci pm_wakep_autosleep_enabled(true); 10862306a36Sopenharmony_ci queue_up_suspend_work(); 10962306a36Sopenharmony_ci } else { 11062306a36Sopenharmony_ci pm_wakep_autosleep_enabled(false); 11162306a36Sopenharmony_ci } 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci mutex_unlock(&autosleep_lock); 11462306a36Sopenharmony_ci return 0; 11562306a36Sopenharmony_ci} 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ciint __init pm_autosleep_init(void) 11862306a36Sopenharmony_ci{ 11962306a36Sopenharmony_ci autosleep_ws = wakeup_source_register(NULL, "autosleep"); 12062306a36Sopenharmony_ci if (!autosleep_ws) 12162306a36Sopenharmony_ci return -ENOMEM; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci autosleep_wq = alloc_ordered_workqueue("autosleep", 0); 12462306a36Sopenharmony_ci if (autosleep_wq) 12562306a36Sopenharmony_ci return 0; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci wakeup_source_unregister(autosleep_ws); 12862306a36Sopenharmony_ci return -ENOMEM; 12962306a36Sopenharmony_ci} 130