]>
Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
7483b4a4 RW |
2 | /* |
3 | * kernel/power/autosleep.c | |
4 | * | |
5 | * Opportunistic sleep support. | |
6 | * | |
7 | * Copyright (C) 2012 Rafael J. Wysocki <[email protected]> | |
8 | */ | |
9 | ||
10 | #include <linux/device.h> | |
11 | #include <linux/mutex.h> | |
12 | #include <linux/pm_wakeup.h> | |
13 | ||
14 | #include "power.h" | |
15 | ||
16 | static suspend_state_t autosleep_state; | |
17 | static struct workqueue_struct *autosleep_wq; | |
18 | /* | |
19 | * Note: it is only safe to mutex_lock(&autosleep_lock) if a wakeup_source | |
20 | * is active, otherwise a deadlock with try_to_suspend() is possible. | |
21 | * Alternatively mutex_lock_interruptible() can be used. This will then fail | |
22 | * if an auto_sleep cycle tries to freeze processes. | |
23 | */ | |
24 | static DEFINE_MUTEX(autosleep_lock); | |
25 | static struct wakeup_source *autosleep_ws; | |
26 | ||
27 | static void try_to_suspend(struct work_struct *work) | |
28 | { | |
29 | unsigned int initial_count, final_count; | |
30 | ||
31 | if (!pm_get_wakeup_count(&initial_count, true)) | |
32 | goto out; | |
33 | ||
34 | mutex_lock(&autosleep_lock); | |
35 | ||
e5248a11 LS |
36 | if (!pm_save_wakeup_count(initial_count) || |
37 | system_state != SYSTEM_RUNNING) { | |
7483b4a4 RW |
38 | mutex_unlock(&autosleep_lock); |
39 | goto out; | |
40 | } | |
41 | ||
42 | if (autosleep_state == PM_SUSPEND_ON) { | |
43 | mutex_unlock(&autosleep_lock); | |
44 | return; | |
45 | } | |
46 | if (autosleep_state >= PM_SUSPEND_MAX) | |
47 | hibernate(); | |
48 | else | |
49 | pm_suspend(autosleep_state); | |
50 | ||
51 | mutex_unlock(&autosleep_lock); | |
52 | ||
53 | if (!pm_get_wakeup_count(&final_count, false)) | |
54 | goto out; | |
55 | ||
56 | /* | |
e4b2897a | 57 | * If the wakeup occurred for an unknown reason, wait to prevent the |
7483b4a4 RW |
58 | * system from trying to suspend and waking up in a tight loop. |
59 | */ | |
60 | if (final_count == initial_count) | |
61 | schedule_timeout_uninterruptible(HZ / 2); | |
62 | ||
63 | out: | |
64 | queue_up_suspend_work(); | |
65 | } | |
66 | ||
67 | static DECLARE_WORK(suspend_work, try_to_suspend); | |
68 | ||
69 | void queue_up_suspend_work(void) | |
70 | { | |
ed1ac6e9 | 71 | if (autosleep_state > PM_SUSPEND_ON) |
7483b4a4 RW |
72 | queue_work(autosleep_wq, &suspend_work); |
73 | } | |
74 | ||
75 | suspend_state_t pm_autosleep_state(void) | |
76 | { | |
77 | return autosleep_state; | |
78 | } | |
79 | ||
80 | int pm_autosleep_lock(void) | |
81 | { | |
82 | return mutex_lock_interruptible(&autosleep_lock); | |
83 | } | |
84 | ||
85 | void pm_autosleep_unlock(void) | |
86 | { | |
87 | mutex_unlock(&autosleep_lock); | |
88 | } | |
89 | ||
90 | int pm_autosleep_set_state(suspend_state_t state) | |
91 | { | |
92 | ||
93 | #ifndef CONFIG_HIBERNATION | |
94 | if (state >= PM_SUSPEND_MAX) | |
95 | return -EINVAL; | |
96 | #endif | |
97 | ||
98 | __pm_stay_awake(autosleep_ws); | |
99 | ||
100 | mutex_lock(&autosleep_lock); | |
101 | ||
102 | autosleep_state = state; | |
103 | ||
104 | __pm_relax(autosleep_ws); | |
105 | ||
55850945 RW |
106 | if (state > PM_SUSPEND_ON) { |
107 | pm_wakep_autosleep_enabled(true); | |
7483b4a4 | 108 | queue_up_suspend_work(); |
55850945 RW |
109 | } else { |
110 | pm_wakep_autosleep_enabled(false); | |
111 | } | |
7483b4a4 RW |
112 | |
113 | mutex_unlock(&autosleep_lock); | |
114 | return 0; | |
115 | } | |
116 | ||
117 | int __init pm_autosleep_init(void) | |
118 | { | |
c8377adf | 119 | autosleep_ws = wakeup_source_register(NULL, "autosleep"); |
7483b4a4 RW |
120 | if (!autosleep_ws) |
121 | return -ENOMEM; | |
122 | ||
123 | autosleep_wq = alloc_ordered_workqueue("autosleep", 0); | |
124 | if (autosleep_wq) | |
125 | return 0; | |
126 | ||
127 | wakeup_source_unregister(autosleep_ws); | |
128 | return -ENOMEM; | |
129 | } |