xref: /openbmc/linux/kernel/power/autosleep.c (revision e4b2897a)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
27483b4a4SRafael J. Wysocki /*
37483b4a4SRafael J. Wysocki  * kernel/power/autosleep.c
47483b4a4SRafael J. Wysocki  *
57483b4a4SRafael J. Wysocki  * Opportunistic sleep support.
67483b4a4SRafael J. Wysocki  *
77483b4a4SRafael J. Wysocki  * Copyright (C) 2012 Rafael J. Wysocki <rjw@sisk.pl>
87483b4a4SRafael J. Wysocki  */
97483b4a4SRafael J. Wysocki 
107483b4a4SRafael J. Wysocki #include <linux/device.h>
117483b4a4SRafael J. Wysocki #include <linux/mutex.h>
127483b4a4SRafael J. Wysocki #include <linux/pm_wakeup.h>
137483b4a4SRafael J. Wysocki 
147483b4a4SRafael J. Wysocki #include "power.h"
157483b4a4SRafael J. Wysocki 
167483b4a4SRafael J. Wysocki static suspend_state_t autosleep_state;
177483b4a4SRafael J. Wysocki static struct workqueue_struct *autosleep_wq;
187483b4a4SRafael J. Wysocki /*
197483b4a4SRafael J. Wysocki  * Note: it is only safe to mutex_lock(&autosleep_lock) if a wakeup_source
207483b4a4SRafael J. Wysocki  * is active, otherwise a deadlock with try_to_suspend() is possible.
217483b4a4SRafael J. Wysocki  * Alternatively mutex_lock_interruptible() can be used.  This will then fail
227483b4a4SRafael J. Wysocki  * if an auto_sleep cycle tries to freeze processes.
237483b4a4SRafael J. Wysocki  */
247483b4a4SRafael J. Wysocki static DEFINE_MUTEX(autosleep_lock);
257483b4a4SRafael J. Wysocki static struct wakeup_source *autosleep_ws;
267483b4a4SRafael J. Wysocki 
try_to_suspend(struct work_struct * work)277483b4a4SRafael J. Wysocki static void try_to_suspend(struct work_struct *work)
287483b4a4SRafael J. Wysocki {
297483b4a4SRafael J. Wysocki 	unsigned int initial_count, final_count;
307483b4a4SRafael J. Wysocki 
317483b4a4SRafael J. Wysocki 	if (!pm_get_wakeup_count(&initial_count, true))
327483b4a4SRafael J. Wysocki 		goto out;
337483b4a4SRafael J. Wysocki 
347483b4a4SRafael J. Wysocki 	mutex_lock(&autosleep_lock);
357483b4a4SRafael J. Wysocki 
36e5248a11SLiu ShuoX 	if (!pm_save_wakeup_count(initial_count) ||
37e5248a11SLiu ShuoX 		system_state != SYSTEM_RUNNING) {
387483b4a4SRafael J. Wysocki 		mutex_unlock(&autosleep_lock);
397483b4a4SRafael J. Wysocki 		goto out;
407483b4a4SRafael J. Wysocki 	}
417483b4a4SRafael J. Wysocki 
427483b4a4SRafael J. Wysocki 	if (autosleep_state == PM_SUSPEND_ON) {
437483b4a4SRafael J. Wysocki 		mutex_unlock(&autosleep_lock);
447483b4a4SRafael J. Wysocki 		return;
457483b4a4SRafael J. Wysocki 	}
467483b4a4SRafael J. Wysocki 	if (autosleep_state >= PM_SUSPEND_MAX)
477483b4a4SRafael J. Wysocki 		hibernate();
487483b4a4SRafael J. Wysocki 	else
497483b4a4SRafael J. Wysocki 		pm_suspend(autosleep_state);
507483b4a4SRafael J. Wysocki 
517483b4a4SRafael J. Wysocki 	mutex_unlock(&autosleep_lock);
527483b4a4SRafael J. Wysocki 
537483b4a4SRafael J. Wysocki 	if (!pm_get_wakeup_count(&final_count, false))
547483b4a4SRafael J. Wysocki 		goto out;
557483b4a4SRafael J. Wysocki 
567483b4a4SRafael J. Wysocki 	/*
57*e4b2897aSLu Jialin 	 * If the wakeup occurred for an unknown reason, wait to prevent the
587483b4a4SRafael J. Wysocki 	 * system from trying to suspend and waking up in a tight loop.
597483b4a4SRafael J. Wysocki 	 */
607483b4a4SRafael J. Wysocki 	if (final_count == initial_count)
617483b4a4SRafael J. Wysocki 		schedule_timeout_uninterruptible(HZ / 2);
627483b4a4SRafael J. Wysocki 
637483b4a4SRafael J. Wysocki  out:
647483b4a4SRafael J. Wysocki 	queue_up_suspend_work();
657483b4a4SRafael J. Wysocki }
667483b4a4SRafael J. Wysocki 
677483b4a4SRafael J. Wysocki static DECLARE_WORK(suspend_work, try_to_suspend);
687483b4a4SRafael J. Wysocki 
queue_up_suspend_work(void)697483b4a4SRafael J. Wysocki void queue_up_suspend_work(void)
707483b4a4SRafael J. Wysocki {
71ed1ac6e9STejun Heo 	if (autosleep_state > PM_SUSPEND_ON)
727483b4a4SRafael J. Wysocki 		queue_work(autosleep_wq, &suspend_work);
737483b4a4SRafael J. Wysocki }
747483b4a4SRafael J. Wysocki 
pm_autosleep_state(void)757483b4a4SRafael J. Wysocki suspend_state_t pm_autosleep_state(void)
767483b4a4SRafael J. Wysocki {
777483b4a4SRafael J. Wysocki 	return autosleep_state;
787483b4a4SRafael J. Wysocki }
797483b4a4SRafael J. Wysocki 
pm_autosleep_lock(void)807483b4a4SRafael J. Wysocki int pm_autosleep_lock(void)
817483b4a4SRafael J. Wysocki {
827483b4a4SRafael J. Wysocki 	return mutex_lock_interruptible(&autosleep_lock);
837483b4a4SRafael J. Wysocki }
847483b4a4SRafael J. Wysocki 
pm_autosleep_unlock(void)857483b4a4SRafael J. Wysocki void pm_autosleep_unlock(void)
867483b4a4SRafael J. Wysocki {
877483b4a4SRafael J. Wysocki 	mutex_unlock(&autosleep_lock);
887483b4a4SRafael J. Wysocki }
897483b4a4SRafael J. Wysocki 
pm_autosleep_set_state(suspend_state_t state)907483b4a4SRafael J. Wysocki int pm_autosleep_set_state(suspend_state_t state)
917483b4a4SRafael J. Wysocki {
927483b4a4SRafael J. Wysocki 
937483b4a4SRafael J. Wysocki #ifndef CONFIG_HIBERNATION
947483b4a4SRafael J. Wysocki 	if (state >= PM_SUSPEND_MAX)
957483b4a4SRafael J. Wysocki 		return -EINVAL;
967483b4a4SRafael J. Wysocki #endif
977483b4a4SRafael J. Wysocki 
987483b4a4SRafael J. Wysocki 	__pm_stay_awake(autosleep_ws);
997483b4a4SRafael J. Wysocki 
1007483b4a4SRafael J. Wysocki 	mutex_lock(&autosleep_lock);
1017483b4a4SRafael J. Wysocki 
1027483b4a4SRafael J. Wysocki 	autosleep_state = state;
1037483b4a4SRafael J. Wysocki 
1047483b4a4SRafael J. Wysocki 	__pm_relax(autosleep_ws);
1057483b4a4SRafael J. Wysocki 
10655850945SRafael J. Wysocki 	if (state > PM_SUSPEND_ON) {
10755850945SRafael J. Wysocki 		pm_wakep_autosleep_enabled(true);
1087483b4a4SRafael J. Wysocki 		queue_up_suspend_work();
10955850945SRafael J. Wysocki 	} else {
11055850945SRafael J. Wysocki 		pm_wakep_autosleep_enabled(false);
11155850945SRafael J. Wysocki 	}
1127483b4a4SRafael J. Wysocki 
1137483b4a4SRafael J. Wysocki 	mutex_unlock(&autosleep_lock);
1147483b4a4SRafael J. Wysocki 	return 0;
1157483b4a4SRafael J. Wysocki }
1167483b4a4SRafael J. Wysocki 
pm_autosleep_init(void)1177483b4a4SRafael J. Wysocki int __init pm_autosleep_init(void)
1187483b4a4SRafael J. Wysocki {
119c8377adfSTri Vo 	autosleep_ws = wakeup_source_register(NULL, "autosleep");
1207483b4a4SRafael J. Wysocki 	if (!autosleep_ws)
1217483b4a4SRafael J. Wysocki 		return -ENOMEM;
1227483b4a4SRafael J. Wysocki 
1237483b4a4SRafael J. Wysocki 	autosleep_wq = alloc_ordered_workqueue("autosleep", 0);
1247483b4a4SRafael J. Wysocki 	if (autosleep_wq)
1257483b4a4SRafael J. Wysocki 		return 0;
1267483b4a4SRafael J. Wysocki 
1277483b4a4SRafael J. Wysocki 	wakeup_source_unregister(autosleep_ws);
1287483b4a4SRafael J. Wysocki 	return -ENOMEM;
1297483b4a4SRafael J. Wysocki }
130