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