xref: /linux/kernel/cpu_pm.c (revision e58e871becec2d3b04ed91c0c16fe8deac9c9dfa)
1 /*
2  * Copyright (C) 2011 Google, Inc.
3  *
4  * Author:
5  *	Colin Cross <ccross@android.com>
6  *
7  * This software is licensed under the terms of the GNU General Public
8  * License version 2, as published by the Free Software Foundation, and
9  * may be copied, distributed, and modified under those terms.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  */
17 
18 #include <linux/kernel.h>
19 #include <linux/cpu_pm.h>
20 #include <linux/module.h>
21 #include <linux/notifier.h>
22 #include <linux/spinlock.h>
23 #include <linux/syscore_ops.h>
24 
25 static DEFINE_RWLOCK(cpu_pm_notifier_lock);
26 static RAW_NOTIFIER_HEAD(cpu_pm_notifier_chain);
27 
28 static int cpu_pm_notify(enum cpu_pm_event event, int nr_to_call, int *nr_calls)
29 {
30 	int ret;
31 
32 	ret = __raw_notifier_call_chain(&cpu_pm_notifier_chain, event, NULL,
33 		nr_to_call, nr_calls);
34 
35 	return notifier_to_errno(ret);
36 }
37 
38 /**
39  * cpu_pm_register_notifier - register a driver with cpu_pm
40  * @nb: notifier block to register
41  *
42  * Add a driver to a list of drivers that are notified about
43  * CPU and CPU cluster low power entry and exit.
44  *
45  * This function may sleep, and has the same return conditions as
46  * raw_notifier_chain_register.
47  */
48 int cpu_pm_register_notifier(struct notifier_block *nb)
49 {
50 	unsigned long flags;
51 	int ret;
52 
53 	write_lock_irqsave(&cpu_pm_notifier_lock, flags);
54 	ret = raw_notifier_chain_register(&cpu_pm_notifier_chain, nb);
55 	write_unlock_irqrestore(&cpu_pm_notifier_lock, flags);
56 
57 	return ret;
58 }
59 EXPORT_SYMBOL_GPL(cpu_pm_register_notifier);
60 
61 /**
62  * cpu_pm_unregister_notifier - unregister a driver with cpu_pm
63  * @nb: notifier block to be unregistered
64  *
65  * Remove a driver from the CPU PM notifier list.
66  *
67  * This function may sleep, and has the same return conditions as
68  * raw_notifier_chain_unregister.
69  */
70 int cpu_pm_unregister_notifier(struct notifier_block *nb)
71 {
72 	unsigned long flags;
73 	int ret;
74 
75 	write_lock_irqsave(&cpu_pm_notifier_lock, flags);
76 	ret = raw_notifier_chain_unregister(&cpu_pm_notifier_chain, nb);
77 	write_unlock_irqrestore(&cpu_pm_notifier_lock, flags);
78 
79 	return ret;
80 }
81 EXPORT_SYMBOL_GPL(cpu_pm_unregister_notifier);
82 
83 /**
84  * cpu_pm_enter - CPU low power entry notifier
85  *
86  * Notifies listeners that a single CPU is entering a low power state that may
87  * cause some blocks in the same power domain as the cpu to reset.
88  *
89  * Must be called on the affected CPU with interrupts disabled.  Platform is
90  * responsible for ensuring that cpu_pm_enter is not called twice on the same
91  * CPU before cpu_pm_exit is called. Notified drivers can include VFP
92  * co-processor, interrupt controller and its PM extensions, local CPU
93  * timers context save/restore which shouldn't be interrupted. Hence it
94  * must be called with interrupts disabled.
95  *
96  * Return conditions are same as __raw_notifier_call_chain.
97  */
98 int cpu_pm_enter(void)
99 {
100 	int nr_calls;
101 	int ret = 0;
102 
103 	read_lock(&cpu_pm_notifier_lock);
104 	ret = cpu_pm_notify(CPU_PM_ENTER, -1, &nr_calls);
105 	if (ret)
106 		/*
107 		 * Inform listeners (nr_calls - 1) about failure of CPU PM
108 		 * PM entry who are notified earlier to prepare for it.
109 		 */
110 		cpu_pm_notify(CPU_PM_ENTER_FAILED, nr_calls - 1, NULL);
111 	read_unlock(&cpu_pm_notifier_lock);
112 
113 	return ret;
114 }
115 EXPORT_SYMBOL_GPL(cpu_pm_enter);
116 
117 /**
118  * cpu_pm_exit - CPU low power exit notifier
119  *
120  * Notifies listeners that a single CPU is exiting a low power state that may
121  * have caused some blocks in the same power domain as the cpu to reset.
122  *
123  * Notified drivers can include VFP co-processor, interrupt controller
124  * and its PM extensions, local CPU timers context save/restore which
125  * shouldn't be interrupted. Hence it must be called with interrupts disabled.
126  *
127  * Return conditions are same as __raw_notifier_call_chain.
128  */
129 int cpu_pm_exit(void)
130 {
131 	int ret;
132 
133 	read_lock(&cpu_pm_notifier_lock);
134 	ret = cpu_pm_notify(CPU_PM_EXIT, -1, NULL);
135 	read_unlock(&cpu_pm_notifier_lock);
136 
137 	return ret;
138 }
139 EXPORT_SYMBOL_GPL(cpu_pm_exit);
140 
141 /**
142  * cpu_cluster_pm_enter - CPU cluster low power entry notifier
143  *
144  * Notifies listeners that all cpus in a power domain are entering a low power
145  * state that may cause some blocks in the same power domain to reset.
146  *
147  * Must be called after cpu_pm_enter has been called on all cpus in the power
148  * domain, and before cpu_pm_exit has been called on any cpu in the power
149  * domain. Notified drivers can include VFP co-processor, interrupt controller
150  * and its PM extensions, local CPU timers context save/restore which
151  * shouldn't be interrupted. Hence it must be called with interrupts disabled.
152  *
153  * Must be called with interrupts disabled.
154  *
155  * Return conditions are same as __raw_notifier_call_chain.
156  */
157 int cpu_cluster_pm_enter(void)
158 {
159 	int nr_calls;
160 	int ret = 0;
161 
162 	read_lock(&cpu_pm_notifier_lock);
163 	ret = cpu_pm_notify(CPU_CLUSTER_PM_ENTER, -1, &nr_calls);
164 	if (ret)
165 		/*
166 		 * Inform listeners (nr_calls - 1) about failure of CPU cluster
167 		 * PM entry who are notified earlier to prepare for it.
168 		 */
169 		cpu_pm_notify(CPU_CLUSTER_PM_ENTER_FAILED, nr_calls - 1, NULL);
170 	read_unlock(&cpu_pm_notifier_lock);
171 
172 	return ret;
173 }
174 EXPORT_SYMBOL_GPL(cpu_cluster_pm_enter);
175 
176 /**
177  * cpu_cluster_pm_exit - CPU cluster low power exit notifier
178  *
179  * Notifies listeners that all cpus in a power domain are exiting form a
180  * low power state that may have caused some blocks in the same power domain
181  * to reset.
182  *
183  * Must be called after cpu_cluster_pm_enter has been called for the power
184  * domain, and before cpu_pm_exit has been called on any cpu in the power
185  * domain. Notified drivers can include VFP co-processor, interrupt controller
186  * and its PM extensions, local CPU timers context save/restore which
187  * shouldn't be interrupted. Hence it must be called with interrupts disabled.
188  *
189  * Return conditions are same as __raw_notifier_call_chain.
190  */
191 int cpu_cluster_pm_exit(void)
192 {
193 	int ret;
194 
195 	read_lock(&cpu_pm_notifier_lock);
196 	ret = cpu_pm_notify(CPU_CLUSTER_PM_EXIT, -1, NULL);
197 	read_unlock(&cpu_pm_notifier_lock);
198 
199 	return ret;
200 }
201 EXPORT_SYMBOL_GPL(cpu_cluster_pm_exit);
202 
203 #ifdef CONFIG_PM
204 static int cpu_pm_suspend(void)
205 {
206 	int ret;
207 
208 	ret = cpu_pm_enter();
209 	if (ret)
210 		return ret;
211 
212 	ret = cpu_cluster_pm_enter();
213 	return ret;
214 }
215 
216 static void cpu_pm_resume(void)
217 {
218 	cpu_cluster_pm_exit();
219 	cpu_pm_exit();
220 }
221 
222 static struct syscore_ops cpu_pm_syscore_ops = {
223 	.suspend = cpu_pm_suspend,
224 	.resume = cpu_pm_resume,
225 };
226 
227 static int cpu_pm_init(void)
228 {
229 	register_syscore_ops(&cpu_pm_syscore_ops);
230 	return 0;
231 }
232 core_initcall(cpu_pm_init);
233 #endif
234