diff options
author | Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> | 2013-09-27 05:25:02 -0400 |
---|---|---|
committer | Catalin Marinas <catalin.marinas@arm.com> | 2014-09-12 05:48:56 -0400 |
commit | 18910ab0d916b1a87016d69efd027714a80521dd (patch) | |
tree | 3cb97f8ed90a87078e669d0ac6ca7ffd553b596d /arch/arm64/kernel | |
parent | d64f84f696463c58e1908510e45b0f5d450f737a (diff) |
arm64: add PSCI CPU_SUSPEND based cpu_suspend support
This patch implements the cpu_suspend cpu operations method through
the PSCI CPU SUSPEND API. The PSCI implementation translates the idle state
index passed by the cpu_suspend core call into a valid PSCI state according to
the PSCI states initialized at boot through the cpu_init_idle() CPU
operations hook.
The PSCI CPU suspend operation hook checks if the PSCI state is a
standby state. If it is, it calls the PSCI suspend implementation
straight away, without saving any context. If the state is a power
down state the kernel calls the __cpu_suspend API (that saves the CPU
context) and passed the PSCI suspend finisher as a parameter so that PSCI
can be called by the __cpu_suspend implementation after saving and flushing
the context as last function before power down.
For power down states, entry point is set to cpu_resume physical address,
that represents the default kernel execution address following a CPU reset.
Reviewed-by: Ashwin Chaugule <ashwin.chaugule@linaro.org>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
Diffstat (limited to 'arch/arm64/kernel')
-rw-r--r-- | arch/arm64/kernel/psci.c | 104 |
1 files changed, 104 insertions, 0 deletions
diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c index 553954771a67..866c1c821860 100644 --- a/arch/arm64/kernel/psci.c +++ b/arch/arm64/kernel/psci.c | |||
@@ -21,6 +21,7 @@ | |||
21 | #include <linux/reboot.h> | 21 | #include <linux/reboot.h> |
22 | #include <linux/pm.h> | 22 | #include <linux/pm.h> |
23 | #include <linux/delay.h> | 23 | #include <linux/delay.h> |
24 | #include <linux/slab.h> | ||
24 | #include <uapi/linux/psci.h> | 25 | #include <uapi/linux/psci.h> |
25 | 26 | ||
26 | #include <asm/compiler.h> | 27 | #include <asm/compiler.h> |
@@ -28,6 +29,7 @@ | |||
28 | #include <asm/errno.h> | 29 | #include <asm/errno.h> |
29 | #include <asm/psci.h> | 30 | #include <asm/psci.h> |
30 | #include <asm/smp_plat.h> | 31 | #include <asm/smp_plat.h> |
32 | #include <asm/suspend.h> | ||
31 | #include <asm/system_misc.h> | 33 | #include <asm/system_misc.h> |
32 | 34 | ||
33 | #define PSCI_POWER_STATE_TYPE_STANDBY 0 | 35 | #define PSCI_POWER_STATE_TYPE_STANDBY 0 |
@@ -65,6 +67,8 @@ enum psci_function { | |||
65 | PSCI_FN_MAX, | 67 | PSCI_FN_MAX, |
66 | }; | 68 | }; |
67 | 69 | ||
70 | static DEFINE_PER_CPU_READ_MOSTLY(struct psci_power_state *, psci_power_state); | ||
71 | |||
68 | static u32 psci_function_id[PSCI_FN_MAX]; | 72 | static u32 psci_function_id[PSCI_FN_MAX]; |
69 | 73 | ||
70 | static int psci_to_linux_errno(int errno) | 74 | static int psci_to_linux_errno(int errno) |
@@ -93,6 +97,18 @@ static u32 psci_power_state_pack(struct psci_power_state state) | |||
93 | & PSCI_0_2_POWER_STATE_AFFL_MASK); | 97 | & PSCI_0_2_POWER_STATE_AFFL_MASK); |
94 | } | 98 | } |
95 | 99 | ||
100 | static void psci_power_state_unpack(u32 power_state, | ||
101 | struct psci_power_state *state) | ||
102 | { | ||
103 | state->id = (power_state & PSCI_0_2_POWER_STATE_ID_MASK) >> | ||
104 | PSCI_0_2_POWER_STATE_ID_SHIFT; | ||
105 | state->type = (power_state & PSCI_0_2_POWER_STATE_TYPE_MASK) >> | ||
106 | PSCI_0_2_POWER_STATE_TYPE_SHIFT; | ||
107 | state->affinity_level = | ||
108 | (power_state & PSCI_0_2_POWER_STATE_AFFL_MASK) >> | ||
109 | PSCI_0_2_POWER_STATE_AFFL_SHIFT; | ||
110 | } | ||
111 | |||
96 | /* | 112 | /* |
97 | * The following two functions are invoked via the invoke_psci_fn pointer | 113 | * The following two functions are invoked via the invoke_psci_fn pointer |
98 | * and will not be inlined, allowing us to piggyback on the AAPCS. | 114 | * and will not be inlined, allowing us to piggyback on the AAPCS. |
@@ -199,6 +215,63 @@ static int psci_migrate_info_type(void) | |||
199 | return err; | 215 | return err; |
200 | } | 216 | } |
201 | 217 | ||
218 | static int __maybe_unused cpu_psci_cpu_init_idle(struct device_node *cpu_node, | ||
219 | unsigned int cpu) | ||
220 | { | ||
221 | int i, ret, count = 0; | ||
222 | struct psci_power_state *psci_states; | ||
223 | struct device_node *state_node; | ||
224 | |||
225 | /* | ||
226 | * If the PSCI cpu_suspend function hook has not been initialized | ||
227 | * idle states must not be enabled, so bail out | ||
228 | */ | ||
229 | if (!psci_ops.cpu_suspend) | ||
230 | return -EOPNOTSUPP; | ||
231 | |||
232 | /* Count idle states */ | ||
233 | while ((state_node = of_parse_phandle(cpu_node, "cpu-idle-states", | ||
234 | count))) { | ||
235 | count++; | ||
236 | of_node_put(state_node); | ||
237 | } | ||
238 | |||
239 | if (!count) | ||
240 | return -ENODEV; | ||
241 | |||
242 | psci_states = kcalloc(count, sizeof(*psci_states), GFP_KERNEL); | ||
243 | if (!psci_states) | ||
244 | return -ENOMEM; | ||
245 | |||
246 | for (i = 0; i < count; i++) { | ||
247 | u32 psci_power_state; | ||
248 | |||
249 | state_node = of_parse_phandle(cpu_node, "cpu-idle-states", i); | ||
250 | |||
251 | ret = of_property_read_u32(state_node, | ||
252 | "arm,psci-suspend-param", | ||
253 | &psci_power_state); | ||
254 | if (ret) { | ||
255 | pr_warn(" * %s missing arm,psci-suspend-param property\n", | ||
256 | state_node->full_name); | ||
257 | of_node_put(state_node); | ||
258 | goto free_mem; | ||
259 | } | ||
260 | |||
261 | of_node_put(state_node); | ||
262 | pr_debug("psci-power-state %#x index %d\n", psci_power_state, | ||
263 | i); | ||
264 | psci_power_state_unpack(psci_power_state, &psci_states[i]); | ||
265 | } | ||
266 | /* Idle states parsed correctly, initialize per-cpu pointer */ | ||
267 | per_cpu(psci_power_state, cpu) = psci_states; | ||
268 | return 0; | ||
269 | |||
270 | free_mem: | ||
271 | kfree(psci_states); | ||
272 | return ret; | ||
273 | } | ||
274 | |||
202 | static int get_set_conduit_method(struct device_node *np) | 275 | static int get_set_conduit_method(struct device_node *np) |
203 | { | 276 | { |
204 | const char *method; | 277 | const char *method; |
@@ -436,8 +509,39 @@ static int cpu_psci_cpu_kill(unsigned int cpu) | |||
436 | #endif | 509 | #endif |
437 | #endif | 510 | #endif |
438 | 511 | ||
512 | static int psci_suspend_finisher(unsigned long index) | ||
513 | { | ||
514 | struct psci_power_state *state = __get_cpu_var(psci_power_state); | ||
515 | |||
516 | return psci_ops.cpu_suspend(state[index - 1], | ||
517 | virt_to_phys(cpu_resume)); | ||
518 | } | ||
519 | |||
520 | static int __maybe_unused cpu_psci_cpu_suspend(unsigned long index) | ||
521 | { | ||
522 | int ret; | ||
523 | struct psci_power_state *state = __get_cpu_var(psci_power_state); | ||
524 | /* | ||
525 | * idle state index 0 corresponds to wfi, should never be called | ||
526 | * from the cpu_suspend operations | ||
527 | */ | ||
528 | if (WARN_ON_ONCE(!index)) | ||
529 | return -EINVAL; | ||
530 | |||
531 | if (state->type == PSCI_POWER_STATE_TYPE_STANDBY) | ||
532 | ret = psci_ops.cpu_suspend(state[index - 1], 0); | ||
533 | else | ||
534 | ret = __cpu_suspend(index, psci_suspend_finisher); | ||
535 | |||
536 | return ret; | ||
537 | } | ||
538 | |||
439 | const struct cpu_operations cpu_psci_ops = { | 539 | const struct cpu_operations cpu_psci_ops = { |
440 | .name = "psci", | 540 | .name = "psci", |
541 | #ifdef CONFIG_CPU_IDLE | ||
542 | .cpu_init_idle = cpu_psci_cpu_init_idle, | ||
543 | .cpu_suspend = cpu_psci_cpu_suspend, | ||
544 | #endif | ||
441 | #ifdef CONFIG_SMP | 545 | #ifdef CONFIG_SMP |
442 | .cpu_init = cpu_psci_cpu_init, | 546 | .cpu_init = cpu_psci_cpu_init, |
443 | .cpu_prepare = cpu_psci_cpu_prepare, | 547 | .cpu_prepare = cpu_psci_cpu_prepare, |