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 | ||
35 | if (!pm_save_wakeup_count(initial_count)) { | |
36 | mutex_unlock(&autosleep_lock); | |
37 | goto out; | |
38 | } | |
39 | ||
40 | if (autosleep_state == PM_SUSPEND_ON) { | |
41 | mutex_unlock(&autosleep_lock); | |
42 | return; | |
43 | } | |
44 | if (autosleep_state >= PM_SUSPEND_MAX) | |
45 | hibernate(); | |
46 | else | |
47 | pm_suspend(autosleep_state); | |
48 | ||
49 | mutex_unlock(&autosleep_lock); | |
50 | ||
51 | if (!pm_get_wakeup_count(&final_count, false)) | |
52 | goto out; | |
53 | ||
54 | /* | |
55 | * If the wakeup occured for an unknown reason, wait to prevent the | |
56 | * system from trying to suspend and waking up in a tight loop. | |
57 | */ | |
58 | if (final_count == initial_count) | |
59 | schedule_timeout_uninterruptible(HZ / 2); | |
60 | ||
61 | out: | |
62 | queue_up_suspend_work(); | |
63 | } | |
64 | ||
65 | static DECLARE_WORK(suspend_work, try_to_suspend); | |
66 | ||
67 | void queue_up_suspend_work(void) | |
68 | { | |
69 | if (!work_pending(&suspend_work) && autosleep_state > PM_SUSPEND_ON) | |
70 | queue_work(autosleep_wq, &suspend_work); | |
71 | } | |
72 | ||
73 | suspend_state_t pm_autosleep_state(void) | |
74 | { | |
75 | return autosleep_state; | |
76 | } | |
77 | ||
78 | int pm_autosleep_lock(void) | |
79 | { | |
80 | return mutex_lock_interruptible(&autosleep_lock); | |
81 | } | |
82 | ||
83 | void pm_autosleep_unlock(void) | |
84 | { | |
85 | mutex_unlock(&autosleep_lock); | |
86 | } | |
87 | ||
88 | int pm_autosleep_set_state(suspend_state_t state) | |
89 | { | |
90 | ||
91 | #ifndef CONFIG_HIBERNATION | |
92 | if (state >= PM_SUSPEND_MAX) | |
93 | return -EINVAL; | |
94 | #endif | |
95 | ||
96 | __pm_stay_awake(autosleep_ws); | |
97 | ||
98 | mutex_lock(&autosleep_lock); | |
99 | ||
100 | autosleep_state = state; | |
101 | ||
102 | __pm_relax(autosleep_ws); | |
103 | ||
55850945 RW |
104 | if (state > PM_SUSPEND_ON) { |
105 | pm_wakep_autosleep_enabled(true); | |
7483b4a4 | 106 | queue_up_suspend_work(); |
55850945 RW |
107 | } else { |
108 | pm_wakep_autosleep_enabled(false); | |
109 | } | |
7483b4a4 RW |
110 | |
111 | mutex_unlock(&autosleep_lock); | |
112 | return 0; | |
113 | } | |
114 | ||
115 | int __init pm_autosleep_init(void) | |
116 | { | |
117 | autosleep_ws = wakeup_source_register("autosleep"); | |
118 | if (!autosleep_ws) | |
119 | return -ENOMEM; | |
120 | ||
121 | autosleep_wq = alloc_ordered_workqueue("autosleep", 0); | |
122 | if (autosleep_wq) | |
123 | return 0; | |
124 | ||
125 | wakeup_source_unregister(autosleep_ws); | |
126 | return -ENOMEM; | |
127 | } |