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. Wysockistatic 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. Wysockivoid 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. Wysockisuspend_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. Wysockiint 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. Wysockivoid 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. Wysockiint 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. Wysockiint __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