xref: /openbmc/linux/kernel/cpu_pm.c (revision 5a244f48)
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 ATOMIC_NOTIFIER_HEAD(cpu_pm_notifier_chain);
26 
27 static int cpu_pm_notify(enum cpu_pm_event event, int nr_to_call, int *nr_calls)
28 {
29 	int ret;
30 
31 	/*
32 	 * __atomic_notifier_call_chain has a RCU read critical section, which
33 	 * could be disfunctional in cpu idle. Copy RCU_NONIDLE code to let
34 	 * RCU know this.
35 	 */
36 	rcu_irq_enter_irqson();
37 	ret = __atomic_notifier_call_chain(&cpu_pm_notifier_chain, event, NULL,
38 		nr_to_call, nr_calls);
39 	rcu_irq_exit_irqson();
40 
41 	return notifier_to_errno(ret);
42 }
43 
44 /**
45  * cpu_pm_register_notifier - register a driver with cpu_pm
46  * @nb: notifier block to register
47  *
48  * Add a driver to a list of drivers that are notified about
49  * CPU and CPU cluster low power entry and exit.
50  *
51  * This function may sleep, and has the same return conditions as
52  * raw_notifier_chain_register.
53  */
54 int cpu_pm_register_notifier(struct notifier_block *nb)
55 {
56 	return atomic_notifier_chain_register(&cpu_pm_notifier_chain, nb);
57 }
58 EXPORT_SYMBOL_GPL(cpu_pm_register_notifier);
59 
60 /**
61  * cpu_pm_unregister_notifier - unregister a driver with cpu_pm
62  * @nb: notifier block to be unregistered
63  *
64  * Remove a driver from the CPU PM notifier list.
65  *
66  * This function may sleep, and has the same return conditions as
67  * raw_notifier_chain_unregister.
68  */
69 int cpu_pm_unregister_notifier(struct notifier_block *nb)
70 {
71 	return atomic_notifier_chain_unregister(&cpu_pm_notifier_chain, nb);
72 }
73 EXPORT_SYMBOL_GPL(cpu_pm_unregister_notifier);
74 
75 /**
76  * cpu_pm_enter - CPU low power entry notifier
77  *
78  * Notifies listeners that a single CPU is entering a low power state that may
79  * cause some blocks in the same power domain as the cpu to reset.
80  *
81  * Must be called on the affected CPU with interrupts disabled.  Platform is
82  * responsible for ensuring that cpu_pm_enter is not called twice on the same
83  * CPU before cpu_pm_exit is called. Notified drivers can include VFP
84  * co-processor, interrupt controller and its PM extensions, local CPU
85  * timers context save/restore which shouldn't be interrupted. Hence it
86  * must be called with interrupts disabled.
87  *
88  * Return conditions are same as __raw_notifier_call_chain.
89  */
90 int cpu_pm_enter(void)
91 {
92 	int nr_calls;
93 	int ret = 0;
94 
95 	ret = cpu_pm_notify(CPU_PM_ENTER, -1, &nr_calls);
96 	if (ret)
97 		/*
98 		 * Inform listeners (nr_calls - 1) about failure of CPU PM
99 		 * PM entry who are notified earlier to prepare for it.
100 		 */
101 		cpu_pm_notify(CPU_PM_ENTER_FAILED, nr_calls - 1, NULL);
102 
103 	return ret;
104 }
105 EXPORT_SYMBOL_GPL(cpu_pm_enter);
106 
107 /**
108  * cpu_pm_exit - CPU low power exit notifier
109  *
110  * Notifies listeners that a single CPU is exiting a low power state that may
111  * have caused some blocks in the same power domain as the cpu to reset.
112  *
113  * Notified drivers can include VFP co-processor, interrupt controller
114  * and its PM extensions, local CPU timers context save/restore which
115  * shouldn't be interrupted. Hence it must be called with interrupts disabled.
116  *
117  * Return conditions are same as __raw_notifier_call_chain.
118  */
119 int cpu_pm_exit(void)
120 {
121 	return cpu_pm_notify(CPU_PM_EXIT, -1, NULL);
122 }
123 EXPORT_SYMBOL_GPL(cpu_pm_exit);
124 
125 /**
126  * cpu_cluster_pm_enter - CPU cluster low power entry notifier
127  *
128  * Notifies listeners that all cpus in a power domain are entering a low power
129  * state that may cause some blocks in the same power domain to reset.
130  *
131  * Must be called after cpu_pm_enter has been called on all cpus in the power
132  * domain, and before cpu_pm_exit has been called on any cpu in the power
133  * domain. Notified drivers can include VFP co-processor, interrupt controller
134  * and its PM extensions, local CPU timers context save/restore which
135  * shouldn't be interrupted. Hence it must be called with interrupts disabled.
136  *
137  * Must be called with interrupts disabled.
138  *
139  * Return conditions are same as __raw_notifier_call_chain.
140  */
141 int cpu_cluster_pm_enter(void)
142 {
143 	int nr_calls;
144 	int ret = 0;
145 
146 	ret = cpu_pm_notify(CPU_CLUSTER_PM_ENTER, -1, &nr_calls);
147 	if (ret)
148 		/*
149 		 * Inform listeners (nr_calls - 1) about failure of CPU cluster
150 		 * PM entry who are notified earlier to prepare for it.
151 		 */
152 		cpu_pm_notify(CPU_CLUSTER_PM_ENTER_FAILED, nr_calls - 1, NULL);
153 
154 	return ret;
155 }
156 EXPORT_SYMBOL_GPL(cpu_cluster_pm_enter);
157 
158 /**
159  * cpu_cluster_pm_exit - CPU cluster low power exit notifier
160  *
161  * Notifies listeners that all cpus in a power domain are exiting form a
162  * low power state that may have caused some blocks in the same power domain
163  * to reset.
164  *
165  * Must be called after cpu_cluster_pm_enter has been called for the power
166  * domain, and before cpu_pm_exit has been called on any cpu in the power
167  * domain. Notified drivers can include VFP co-processor, interrupt controller
168  * and its PM extensions, local CPU timers context save/restore which
169  * shouldn't be interrupted. Hence it must be called with interrupts disabled.
170  *
171  * Return conditions are same as __raw_notifier_call_chain.
172  */
173 int cpu_cluster_pm_exit(void)
174 {
175 	return cpu_pm_notify(CPU_CLUSTER_PM_EXIT, -1, NULL);
176 }
177 EXPORT_SYMBOL_GPL(cpu_cluster_pm_exit);
178 
179 #ifdef CONFIG_PM
180 static int cpu_pm_suspend(void)
181 {
182 	int ret;
183 
184 	ret = cpu_pm_enter();
185 	if (ret)
186 		return ret;
187 
188 	ret = cpu_cluster_pm_enter();
189 	return ret;
190 }
191 
192 static void cpu_pm_resume(void)
193 {
194 	cpu_cluster_pm_exit();
195 	cpu_pm_exit();
196 }
197 
198 static struct syscore_ops cpu_pm_syscore_ops = {
199 	.suspend = cpu_pm_suspend,
200 	.resume = cpu_pm_resume,
201 };
202 
203 static int cpu_pm_init(void)
204 {
205 	register_syscore_ops(&cpu_pm_syscore_ops);
206 	return 0;
207 }
208 core_initcall(cpu_pm_init);
209 #endif
210