diff options
author | Deepthi Dharwar <deepthi@linux.vnet.ibm.com> | 2014-01-14 05:56:02 -0500 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2014-01-29 01:02:22 -0500 |
commit | 962e7bd4976516c34fc9ef51d536aab801980767 (patch) | |
tree | b1ddb6a14ffd3d83567d16b2265a02ba16331658 /drivers/cpuidle | |
parent | d765ff23e3181413fb1bed090c8d702165448a84 (diff) |
powerpc/pseries/cpuidle: Move processor_idle.c to drivers/cpuidle.
Move the file from arch specific pseries/processor_idle.c
to drivers/cpuidle/cpuidle-pseries.c
Make the relevant Makefile and Kconfig changes.
Also, introduce Kconfig.powerpc in drivers/cpuidle
for all powerpc cpuidle drivers.
Signed-off-by: Deepthi Dharwar <deepthi@linux.vnet.ibm.com>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'drivers/cpuidle')
-rw-r--r-- | drivers/cpuidle/Kconfig | 5 | ||||
-rw-r--r-- | drivers/cpuidle/Kconfig.powerpc | 11 | ||||
-rw-r--r-- | drivers/cpuidle/Makefile | 4 | ||||
-rw-r--r-- | drivers/cpuidle/cpuidle-pseries.c | 361 |
4 files changed, 381 insertions, 0 deletions
diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index b3fb81d7cf04..f04e25f6c98d 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig | |||
@@ -35,6 +35,11 @@ depends on ARM | |||
35 | source "drivers/cpuidle/Kconfig.arm" | 35 | source "drivers/cpuidle/Kconfig.arm" |
36 | endmenu | 36 | endmenu |
37 | 37 | ||
38 | menu "POWERPC CPU Idle Drivers" | ||
39 | depends on PPC | ||
40 | source "drivers/cpuidle/Kconfig.powerpc" | ||
41 | endmenu | ||
42 | |||
38 | endif | 43 | endif |
39 | 44 | ||
40 | config ARCH_NEEDS_CPU_IDLE_COUPLED | 45 | config ARCH_NEEDS_CPU_IDLE_COUPLED |
diff --git a/drivers/cpuidle/Kconfig.powerpc b/drivers/cpuidle/Kconfig.powerpc new file mode 100644 index 000000000000..8147de522a96 --- /dev/null +++ b/drivers/cpuidle/Kconfig.powerpc | |||
@@ -0,0 +1,11 @@ | |||
1 | # | ||
2 | # POWERPC CPU Idle Drivers | ||
3 | # | ||
4 | config PSERIES_CPUIDLE | ||
5 | bool "Cpuidle driver for pSeries platforms" | ||
6 | depends on CPU_IDLE | ||
7 | depends on PPC_PSERIES | ||
8 | default y | ||
9 | help | ||
10 | Select this option to enable processor idle state management | ||
11 | through cpuidle subsystem. | ||
diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index 527be28e5c1e..a6331ad32738 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile | |||
@@ -13,3 +13,7 @@ obj-$(CONFIG_ARM_KIRKWOOD_CPUIDLE) += cpuidle-kirkwood.o | |||
13 | obj-$(CONFIG_ARM_ZYNQ_CPUIDLE) += cpuidle-zynq.o | 13 | obj-$(CONFIG_ARM_ZYNQ_CPUIDLE) += cpuidle-zynq.o |
14 | obj-$(CONFIG_ARM_U8500_CPUIDLE) += cpuidle-ux500.o | 14 | obj-$(CONFIG_ARM_U8500_CPUIDLE) += cpuidle-ux500.o |
15 | obj-$(CONFIG_ARM_AT91_CPUIDLE) += cpuidle-at91.o | 15 | obj-$(CONFIG_ARM_AT91_CPUIDLE) += cpuidle-at91.o |
16 | |||
17 | ############################################################################### | ||
18 | # POWERPC drivers | ||
19 | obj-$(CONFIG_PSERIES_CPUIDLE) += cpuidle-pseries.o | ||
diff --git a/drivers/cpuidle/cpuidle-pseries.c b/drivers/cpuidle/cpuidle-pseries.c new file mode 100644 index 000000000000..21154782402a --- /dev/null +++ b/drivers/cpuidle/cpuidle-pseries.c | |||
@@ -0,0 +1,361 @@ | |||
1 | /* | ||
2 | * cpuidle-pseries - idle state cpuidle driver. | ||
3 | * Adapted from drivers/idle/intel_idle.c and | ||
4 | * drivers/acpi/processor_idle.c | ||
5 | * | ||
6 | */ | ||
7 | |||
8 | #include <linux/kernel.h> | ||
9 | #include <linux/module.h> | ||
10 | #include <linux/init.h> | ||
11 | #include <linux/moduleparam.h> | ||
12 | #include <linux/cpuidle.h> | ||
13 | #include <linux/cpu.h> | ||
14 | #include <linux/notifier.h> | ||
15 | |||
16 | #include <asm/paca.h> | ||
17 | #include <asm/reg.h> | ||
18 | #include <asm/machdep.h> | ||
19 | #include <asm/firmware.h> | ||
20 | #include <asm/plpar_wrappers.h> | ||
21 | |||
22 | struct cpuidle_driver pseries_idle_driver = { | ||
23 | .name = "pseries_idle", | ||
24 | .owner = THIS_MODULE, | ||
25 | }; | ||
26 | |||
27 | #define MAX_IDLE_STATE_COUNT 2 | ||
28 | |||
29 | static int max_idle_state = MAX_IDLE_STATE_COUNT - 1; | ||
30 | static struct cpuidle_device __percpu *pseries_cpuidle_devices; | ||
31 | static struct cpuidle_state *cpuidle_state_table; | ||
32 | |||
33 | static inline void idle_loop_prolog(unsigned long *in_purr) | ||
34 | { | ||
35 | *in_purr = mfspr(SPRN_PURR); | ||
36 | /* | ||
37 | * Indicate to the HV that we are idle. Now would be | ||
38 | * a good time to find other work to dispatch. | ||
39 | */ | ||
40 | get_lppaca()->idle = 1; | ||
41 | } | ||
42 | |||
43 | static inline void idle_loop_epilog(unsigned long in_purr) | ||
44 | { | ||
45 | u64 wait_cycles; | ||
46 | |||
47 | wait_cycles = be64_to_cpu(get_lppaca()->wait_state_cycles); | ||
48 | wait_cycles += mfspr(SPRN_PURR) - in_purr; | ||
49 | get_lppaca()->wait_state_cycles = cpu_to_be64(wait_cycles); | ||
50 | get_lppaca()->idle = 0; | ||
51 | } | ||
52 | |||
53 | static int snooze_loop(struct cpuidle_device *dev, | ||
54 | struct cpuidle_driver *drv, | ||
55 | int index) | ||
56 | { | ||
57 | unsigned long in_purr; | ||
58 | int cpu = dev->cpu; | ||
59 | |||
60 | idle_loop_prolog(&in_purr); | ||
61 | local_irq_enable(); | ||
62 | set_thread_flag(TIF_POLLING_NRFLAG); | ||
63 | |||
64 | while ((!need_resched()) && cpu_online(cpu)) { | ||
65 | HMT_low(); | ||
66 | HMT_very_low(); | ||
67 | } | ||
68 | |||
69 | HMT_medium(); | ||
70 | clear_thread_flag(TIF_POLLING_NRFLAG); | ||
71 | smp_mb(); | ||
72 | |||
73 | idle_loop_epilog(in_purr); | ||
74 | |||
75 | return index; | ||
76 | } | ||
77 | |||
78 | static void check_and_cede_processor(void) | ||
79 | { | ||
80 | /* | ||
81 | * Ensure our interrupt state is properly tracked, | ||
82 | * also checks if no interrupt has occurred while we | ||
83 | * were soft-disabled | ||
84 | */ | ||
85 | if (prep_irq_for_idle()) { | ||
86 | cede_processor(); | ||
87 | #ifdef CONFIG_TRACE_IRQFLAGS | ||
88 | /* Ensure that H_CEDE returns with IRQs on */ | ||
89 | if (WARN_ON(!(mfmsr() & MSR_EE))) | ||
90 | __hard_irq_enable(); | ||
91 | #endif | ||
92 | } | ||
93 | } | ||
94 | |||
95 | static int dedicated_cede_loop(struct cpuidle_device *dev, | ||
96 | struct cpuidle_driver *drv, | ||
97 | int index) | ||
98 | { | ||
99 | unsigned long in_purr; | ||
100 | |||
101 | idle_loop_prolog(&in_purr); | ||
102 | get_lppaca()->donate_dedicated_cpu = 1; | ||
103 | |||
104 | HMT_medium(); | ||
105 | check_and_cede_processor(); | ||
106 | |||
107 | get_lppaca()->donate_dedicated_cpu = 0; | ||
108 | |||
109 | idle_loop_epilog(in_purr); | ||
110 | |||
111 | return index; | ||
112 | } | ||
113 | |||
114 | static int shared_cede_loop(struct cpuidle_device *dev, | ||
115 | struct cpuidle_driver *drv, | ||
116 | int index) | ||
117 | { | ||
118 | unsigned long in_purr; | ||
119 | |||
120 | idle_loop_prolog(&in_purr); | ||
121 | |||
122 | /* | ||
123 | * Yield the processor to the hypervisor. We return if | ||
124 | * an external interrupt occurs (which are driven prior | ||
125 | * to returning here) or if a prod occurs from another | ||
126 | * processor. When returning here, external interrupts | ||
127 | * are enabled. | ||
128 | */ | ||
129 | check_and_cede_processor(); | ||
130 | |||
131 | idle_loop_epilog(in_purr); | ||
132 | |||
133 | return index; | ||
134 | } | ||
135 | |||
136 | /* | ||
137 | * States for dedicated partition case. | ||
138 | */ | ||
139 | static struct cpuidle_state dedicated_states[MAX_IDLE_STATE_COUNT] = { | ||
140 | { /* Snooze */ | ||
141 | .name = "snooze", | ||
142 | .desc = "snooze", | ||
143 | .flags = CPUIDLE_FLAG_TIME_VALID, | ||
144 | .exit_latency = 0, | ||
145 | .target_residency = 0, | ||
146 | .enter = &snooze_loop }, | ||
147 | { /* CEDE */ | ||
148 | .name = "CEDE", | ||
149 | .desc = "CEDE", | ||
150 | .flags = CPUIDLE_FLAG_TIME_VALID, | ||
151 | .exit_latency = 10, | ||
152 | .target_residency = 100, | ||
153 | .enter = &dedicated_cede_loop }, | ||
154 | }; | ||
155 | |||
156 | /* | ||
157 | * States for shared partition case. | ||
158 | */ | ||
159 | static struct cpuidle_state shared_states[MAX_IDLE_STATE_COUNT] = { | ||
160 | { /* Shared Cede */ | ||
161 | .name = "Shared Cede", | ||
162 | .desc = "Shared Cede", | ||
163 | .flags = CPUIDLE_FLAG_TIME_VALID, | ||
164 | .exit_latency = 0, | ||
165 | .target_residency = 0, | ||
166 | .enter = &shared_cede_loop }, | ||
167 | }; | ||
168 | |||
169 | void update_smt_snooze_delay(int cpu, int residency) | ||
170 | { | ||
171 | struct cpuidle_driver *drv = cpuidle_get_driver(); | ||
172 | struct cpuidle_device *dev = per_cpu(cpuidle_devices, cpu); | ||
173 | |||
174 | if (cpuidle_state_table != dedicated_states) | ||
175 | return; | ||
176 | |||
177 | if (residency < 0) { | ||
178 | /* Disable the Nap state on that cpu */ | ||
179 | if (dev) | ||
180 | dev->states_usage[1].disable = 1; | ||
181 | } else | ||
182 | if (drv) | ||
183 | drv->states[1].target_residency = residency; | ||
184 | } | ||
185 | |||
186 | static int pseries_cpuidle_add_cpu_notifier(struct notifier_block *n, | ||
187 | unsigned long action, void *hcpu) | ||
188 | { | ||
189 | int hotcpu = (unsigned long)hcpu; | ||
190 | struct cpuidle_device *dev = | ||
191 | per_cpu_ptr(pseries_cpuidle_devices, hotcpu); | ||
192 | |||
193 | if (dev && cpuidle_get_driver()) { | ||
194 | switch (action) { | ||
195 | case CPU_ONLINE: | ||
196 | case CPU_ONLINE_FROZEN: | ||
197 | cpuidle_pause_and_lock(); | ||
198 | cpuidle_enable_device(dev); | ||
199 | cpuidle_resume_and_unlock(); | ||
200 | break; | ||
201 | |||
202 | case CPU_DEAD: | ||
203 | case CPU_DEAD_FROZEN: | ||
204 | cpuidle_pause_and_lock(); | ||
205 | cpuidle_disable_device(dev); | ||
206 | cpuidle_resume_and_unlock(); | ||
207 | break; | ||
208 | |||
209 | default: | ||
210 | return NOTIFY_DONE; | ||
211 | } | ||
212 | } | ||
213 | return NOTIFY_OK; | ||
214 | } | ||
215 | |||
216 | static struct notifier_block setup_hotplug_notifier = { | ||
217 | .notifier_call = pseries_cpuidle_add_cpu_notifier, | ||
218 | }; | ||
219 | |||
220 | /* | ||
221 | * pseries_cpuidle_driver_init() | ||
222 | */ | ||
223 | static int pseries_cpuidle_driver_init(void) | ||
224 | { | ||
225 | int idle_state; | ||
226 | struct cpuidle_driver *drv = &pseries_idle_driver; | ||
227 | |||
228 | drv->state_count = 0; | ||
229 | |||
230 | for (idle_state = 0; idle_state < MAX_IDLE_STATE_COUNT; ++idle_state) { | ||
231 | |||
232 | if (idle_state > max_idle_state) | ||
233 | break; | ||
234 | |||
235 | /* is the state not enabled? */ | ||
236 | if (cpuidle_state_table[idle_state].enter == NULL) | ||
237 | continue; | ||
238 | |||
239 | drv->states[drv->state_count] = /* structure copy */ | ||
240 | cpuidle_state_table[idle_state]; | ||
241 | |||
242 | drv->state_count += 1; | ||
243 | } | ||
244 | |||
245 | return 0; | ||
246 | } | ||
247 | |||
248 | /* pseries_idle_devices_uninit(void) | ||
249 | * unregister cpuidle devices and de-allocate memory | ||
250 | */ | ||
251 | static void pseries_idle_devices_uninit(void) | ||
252 | { | ||
253 | int i; | ||
254 | struct cpuidle_device *dev; | ||
255 | |||
256 | for_each_possible_cpu(i) { | ||
257 | dev = per_cpu_ptr(pseries_cpuidle_devices, i); | ||
258 | cpuidle_unregister_device(dev); | ||
259 | } | ||
260 | |||
261 | free_percpu(pseries_cpuidle_devices); | ||
262 | return; | ||
263 | } | ||
264 | |||
265 | /* pseries_idle_devices_init() | ||
266 | * allocate, initialize and register cpuidle device | ||
267 | */ | ||
268 | static int pseries_idle_devices_init(void) | ||
269 | { | ||
270 | int i; | ||
271 | struct cpuidle_driver *drv = &pseries_idle_driver; | ||
272 | struct cpuidle_device *dev; | ||
273 | |||
274 | pseries_cpuidle_devices = alloc_percpu(struct cpuidle_device); | ||
275 | if (pseries_cpuidle_devices == NULL) | ||
276 | return -ENOMEM; | ||
277 | |||
278 | for_each_possible_cpu(i) { | ||
279 | dev = per_cpu_ptr(pseries_cpuidle_devices, i); | ||
280 | dev->state_count = drv->state_count; | ||
281 | dev->cpu = i; | ||
282 | if (cpuidle_register_device(dev)) { | ||
283 | printk(KERN_DEBUG \ | ||
284 | "cpuidle_register_device %d failed!\n", i); | ||
285 | return -EIO; | ||
286 | } | ||
287 | } | ||
288 | |||
289 | return 0; | ||
290 | } | ||
291 | |||
292 | /* | ||
293 | * pseries_idle_probe() | ||
294 | * Choose state table for shared versus dedicated partition | ||
295 | */ | ||
296 | static int pseries_idle_probe(void) | ||
297 | { | ||
298 | |||
299 | if (!firmware_has_feature(FW_FEATURE_SPLPAR)) | ||
300 | return -ENODEV; | ||
301 | |||
302 | if (cpuidle_disable != IDLE_NO_OVERRIDE) | ||
303 | return -ENODEV; | ||
304 | |||
305 | if (max_idle_state == 0) { | ||
306 | printk(KERN_DEBUG "pseries processor idle disabled.\n"); | ||
307 | return -EPERM; | ||
308 | } | ||
309 | |||
310 | if (lppaca_shared_proc(get_lppaca())) | ||
311 | cpuidle_state_table = shared_states; | ||
312 | else | ||
313 | cpuidle_state_table = dedicated_states; | ||
314 | |||
315 | return 0; | ||
316 | } | ||
317 | |||
318 | static int __init pseries_processor_idle_init(void) | ||
319 | { | ||
320 | int retval; | ||
321 | |||
322 | retval = pseries_idle_probe(); | ||
323 | if (retval) | ||
324 | return retval; | ||
325 | |||
326 | pseries_cpuidle_driver_init(); | ||
327 | retval = cpuidle_register_driver(&pseries_idle_driver); | ||
328 | if (retval) { | ||
329 | printk(KERN_DEBUG "Registration of pseries driver failed.\n"); | ||
330 | return retval; | ||
331 | } | ||
332 | |||
333 | retval = pseries_idle_devices_init(); | ||
334 | if (retval) { | ||
335 | pseries_idle_devices_uninit(); | ||
336 | cpuidle_unregister_driver(&pseries_idle_driver); | ||
337 | return retval; | ||
338 | } | ||
339 | |||
340 | register_cpu_notifier(&setup_hotplug_notifier); | ||
341 | printk(KERN_DEBUG "pseries_idle_driver registered\n"); | ||
342 | |||
343 | return 0; | ||
344 | } | ||
345 | |||
346 | static void __exit pseries_processor_idle_exit(void) | ||
347 | { | ||
348 | |||
349 | unregister_cpu_notifier(&setup_hotplug_notifier); | ||
350 | pseries_idle_devices_uninit(); | ||
351 | cpuidle_unregister_driver(&pseries_idle_driver); | ||
352 | |||
353 | return; | ||
354 | } | ||
355 | |||
356 | module_init(pseries_processor_idle_init); | ||
357 | module_exit(pseries_processor_idle_exit); | ||
358 | |||
359 | MODULE_AUTHOR("Deepthi Dharwar <deepthi@linux.vnet.ibm.com>"); | ||
360 | MODULE_DESCRIPTION("Cpuidle driver for POWER"); | ||
361 | MODULE_LICENSE("GPL"); | ||