diff options
author | Ashwin Chaugule <ashwin.chaugule@linaro.org> | 2014-04-17 14:38:41 -0400 |
---|---|---|
committer | Ashwin Chaugule <ashwin.chaugule@linaro.org> | 2014-05-15 10:16:00 -0400 |
commit | e71246a23acbc89e9cb4ebf1558d60e65733479f (patch) | |
tree | 733dd0f1f714b4b6473c7dade260861f5e51f551 /arch/arm64/kernel/psci.c | |
parent | 4447a208f7fc2e2dff8c6a8df2a1fd6dd72fb3e2 (diff) |
PSCI: Add initial support for PSCIv0.2 functions
The PSCIv0.2 spec defines standard values of function IDs
and introduces a few new functions. Detect version of PSCI
and appropriately select the right PSCI functions.
Signed-off-by: Ashwin Chaugule <ashwin.chaugule@linaro.org>
Reviewed-by: Rob Herring <robh@kernel.org>
Acked-by: Catalin Marinas <catalin.marinas@arm.com>
Diffstat (limited to 'arch/arm64/kernel/psci.c')
-rw-r--r-- | arch/arm64/kernel/psci.c | 200 |
1 files changed, 163 insertions, 37 deletions
diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c index ea4828a4aa96..90df6e641227 100644 --- a/arch/arm64/kernel/psci.c +++ b/arch/arm64/kernel/psci.c | |||
@@ -18,12 +18,16 @@ | |||
18 | #include <linux/init.h> | 18 | #include <linux/init.h> |
19 | #include <linux/of.h> | 19 | #include <linux/of.h> |
20 | #include <linux/smp.h> | 20 | #include <linux/smp.h> |
21 | #include <linux/reboot.h> | ||
22 | #include <linux/pm.h> | ||
23 | #include <uapi/linux/psci.h> | ||
21 | 24 | ||
22 | #include <asm/compiler.h> | 25 | #include <asm/compiler.h> |
23 | #include <asm/cpu_ops.h> | 26 | #include <asm/cpu_ops.h> |
24 | #include <asm/errno.h> | 27 | #include <asm/errno.h> |
25 | #include <asm/psci.h> | 28 | #include <asm/psci.h> |
26 | #include <asm/smp_plat.h> | 29 | #include <asm/smp_plat.h> |
30 | #include <asm/system_misc.h> | ||
27 | 31 | ||
28 | #define PSCI_POWER_STATE_TYPE_STANDBY 0 | 32 | #define PSCI_POWER_STATE_TYPE_STANDBY 0 |
29 | #define PSCI_POWER_STATE_TYPE_POWER_DOWN 1 | 33 | #define PSCI_POWER_STATE_TYPE_POWER_DOWN 1 |
@@ -40,58 +44,52 @@ struct psci_operations { | |||
40 | int (*cpu_off)(struct psci_power_state state); | 44 | int (*cpu_off)(struct psci_power_state state); |
41 | int (*cpu_on)(unsigned long cpuid, unsigned long entry_point); | 45 | int (*cpu_on)(unsigned long cpuid, unsigned long entry_point); |
42 | int (*migrate)(unsigned long cpuid); | 46 | int (*migrate)(unsigned long cpuid); |
47 | int (*affinity_info)(unsigned long target_affinity, | ||
48 | unsigned long lowest_affinity_level); | ||
49 | int (*migrate_info_type)(void); | ||
43 | }; | 50 | }; |
44 | 51 | ||
45 | static struct psci_operations psci_ops; | 52 | static struct psci_operations psci_ops; |
46 | 53 | ||
47 | static int (*invoke_psci_fn)(u64, u64, u64, u64); | 54 | static int (*invoke_psci_fn)(u64, u64, u64, u64); |
55 | typedef int (*psci_initcall_t)(const struct device_node *); | ||
48 | 56 | ||
49 | enum psci_function { | 57 | enum psci_function { |
50 | PSCI_FN_CPU_SUSPEND, | 58 | PSCI_FN_CPU_SUSPEND, |
51 | PSCI_FN_CPU_ON, | 59 | PSCI_FN_CPU_ON, |
52 | PSCI_FN_CPU_OFF, | 60 | PSCI_FN_CPU_OFF, |
53 | PSCI_FN_MIGRATE, | 61 | PSCI_FN_MIGRATE, |
62 | PSCI_FN_AFFINITY_INFO, | ||
63 | PSCI_FN_MIGRATE_INFO_TYPE, | ||
54 | PSCI_FN_MAX, | 64 | PSCI_FN_MAX, |
55 | }; | 65 | }; |
56 | 66 | ||
57 | static u32 psci_function_id[PSCI_FN_MAX]; | 67 | static u32 psci_function_id[PSCI_FN_MAX]; |
58 | 68 | ||
59 | #define PSCI_RET_SUCCESS 0 | ||
60 | #define PSCI_RET_EOPNOTSUPP -1 | ||
61 | #define PSCI_RET_EINVAL -2 | ||
62 | #define PSCI_RET_EPERM -3 | ||
63 | |||
64 | static int psci_to_linux_errno(int errno) | 69 | static int psci_to_linux_errno(int errno) |
65 | { | 70 | { |
66 | switch (errno) { | 71 | switch (errno) { |
67 | case PSCI_RET_SUCCESS: | 72 | case PSCI_RET_SUCCESS: |
68 | return 0; | 73 | return 0; |
69 | case PSCI_RET_EOPNOTSUPP: | 74 | case PSCI_RET_NOT_SUPPORTED: |
70 | return -EOPNOTSUPP; | 75 | return -EOPNOTSUPP; |
71 | case PSCI_RET_EINVAL: | 76 | case PSCI_RET_INVALID_PARAMS: |
72 | return -EINVAL; | 77 | return -EINVAL; |
73 | case PSCI_RET_EPERM: | 78 | case PSCI_RET_DENIED: |
74 | return -EPERM; | 79 | return -EPERM; |
75 | }; | 80 | }; |
76 | 81 | ||
77 | return -EINVAL; | 82 | return -EINVAL; |
78 | } | 83 | } |
79 | 84 | ||
80 | #define PSCI_POWER_STATE_ID_MASK 0xffff | ||
81 | #define PSCI_POWER_STATE_ID_SHIFT 0 | ||
82 | #define PSCI_POWER_STATE_TYPE_MASK 0x1 | ||
83 | #define PSCI_POWER_STATE_TYPE_SHIFT 16 | ||
84 | #define PSCI_POWER_STATE_AFFL_MASK 0x3 | ||
85 | #define PSCI_POWER_STATE_AFFL_SHIFT 24 | ||
86 | |||
87 | static u32 psci_power_state_pack(struct psci_power_state state) | 85 | static u32 psci_power_state_pack(struct psci_power_state state) |
88 | { | 86 | { |
89 | return ((state.id & PSCI_POWER_STATE_ID_MASK) | 87 | return ((state.id << PSCI_0_2_POWER_STATE_ID_SHIFT) |
90 | << PSCI_POWER_STATE_ID_SHIFT) | | 88 | & PSCI_0_2_POWER_STATE_ID_MASK) | |
91 | ((state.type & PSCI_POWER_STATE_TYPE_MASK) | 89 | ((state.type << PSCI_0_2_POWER_STATE_TYPE_SHIFT) |
92 | << PSCI_POWER_STATE_TYPE_SHIFT) | | 90 | & PSCI_0_2_POWER_STATE_TYPE_MASK) | |
93 | ((state.affinity_level & PSCI_POWER_STATE_AFFL_MASK) | 91 | ((state.affinity_level << PSCI_0_2_POWER_STATE_AFFL_SHIFT) |
94 | << PSCI_POWER_STATE_AFFL_SHIFT); | 92 | & PSCI_0_2_POWER_STATE_AFFL_MASK); |
95 | } | 93 | } |
96 | 94 | ||
97 | /* | 95 | /* |
@@ -128,6 +126,14 @@ static noinline int __invoke_psci_fn_smc(u64 function_id, u64 arg0, u64 arg1, | |||
128 | return function_id; | 126 | return function_id; |
129 | } | 127 | } |
130 | 128 | ||
129 | static int psci_get_version(void) | ||
130 | { | ||
131 | int err; | ||
132 | |||
133 | err = invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0); | ||
134 | return err; | ||
135 | } | ||
136 | |||
131 | static int psci_cpu_suspend(struct psci_power_state state, | 137 | static int psci_cpu_suspend(struct psci_power_state state, |
132 | unsigned long entry_point) | 138 | unsigned long entry_point) |
133 | { | 139 | { |
@@ -171,26 +177,36 @@ static int psci_migrate(unsigned long cpuid) | |||
171 | return psci_to_linux_errno(err); | 177 | return psci_to_linux_errno(err); |
172 | } | 178 | } |
173 | 179 | ||
174 | static const struct of_device_id psci_of_match[] __initconst = { | 180 | static int psci_affinity_info(unsigned long target_affinity, |
175 | { .compatible = "arm,psci", }, | 181 | unsigned long lowest_affinity_level) |
176 | {}, | 182 | { |
177 | }; | 183 | int err; |
184 | u32 fn; | ||
185 | |||
186 | fn = psci_function_id[PSCI_FN_AFFINITY_INFO]; | ||
187 | err = invoke_psci_fn(fn, target_affinity, lowest_affinity_level, 0); | ||
188 | return err; | ||
189 | } | ||
178 | 190 | ||
179 | void __init psci_init(void) | 191 | static int psci_migrate_info_type(void) |
180 | { | 192 | { |
181 | struct device_node *np; | 193 | int err; |
182 | const char *method; | 194 | u32 fn; |
183 | u32 id; | ||
184 | 195 | ||
185 | np = of_find_matching_node(NULL, psci_of_match); | 196 | fn = psci_function_id[PSCI_FN_MIGRATE_INFO_TYPE]; |
186 | if (!np) | 197 | err = invoke_psci_fn(fn, 0, 0, 0); |
187 | return; | 198 | return err; |
199 | } | ||
200 | |||
201 | static int get_set_conduit_method(struct device_node *np) | ||
202 | { | ||
203 | const char *method; | ||
188 | 204 | ||
189 | pr_info("probing function IDs from device-tree\n"); | 205 | pr_info("probing for conduit method from DT.\n"); |
190 | 206 | ||
191 | if (of_property_read_string(np, "method", &method)) { | 207 | if (of_property_read_string(np, "method", &method)) { |
192 | pr_warning("missing \"method\" property\n"); | 208 | pr_warn("missing \"method\" property\n"); |
193 | goto out_put_node; | 209 | return -ENXIO; |
194 | } | 210 | } |
195 | 211 | ||
196 | if (!strcmp("hvc", method)) { | 212 | if (!strcmp("hvc", method)) { |
@@ -198,10 +214,99 @@ void __init psci_init(void) | |||
198 | } else if (!strcmp("smc", method)) { | 214 | } else if (!strcmp("smc", method)) { |
199 | invoke_psci_fn = __invoke_psci_fn_smc; | 215 | invoke_psci_fn = __invoke_psci_fn_smc; |
200 | } else { | 216 | } else { |
201 | pr_warning("invalid \"method\" property: %s\n", method); | 217 | pr_warn("invalid \"method\" property: %s\n", method); |
218 | return -EINVAL; | ||
219 | } | ||
220 | return 0; | ||
221 | } | ||
222 | |||
223 | static void psci_sys_reset(enum reboot_mode reboot_mode, const char *cmd) | ||
224 | { | ||
225 | invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0); | ||
226 | } | ||
227 | |||
228 | static void psci_sys_poweroff(void) | ||
229 | { | ||
230 | invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0); | ||
231 | } | ||
232 | |||
233 | /* | ||
234 | * PSCI Function IDs for v0.2+ are well defined so use | ||
235 | * standard values. | ||
236 | */ | ||
237 | static int psci_0_2_init(struct device_node *np) | ||
238 | { | ||
239 | int err, ver; | ||
240 | |||
241 | err = get_set_conduit_method(np); | ||
242 | |||
243 | if (err) | ||
244 | goto out_put_node; | ||
245 | |||
246 | ver = psci_get_version(); | ||
247 | |||
248 | if (ver == PSCI_RET_NOT_SUPPORTED) { | ||
249 | /* PSCI v0.2 mandates implementation of PSCI_ID_VERSION. */ | ||
250 | pr_err("PSCI firmware does not comply with the v0.2 spec.\n"); | ||
251 | err = -EOPNOTSUPP; | ||
202 | goto out_put_node; | 252 | goto out_put_node; |
253 | } else { | ||
254 | pr_info("PSCIv%d.%d detected in firmware.\n", | ||
255 | PSCI_VERSION_MAJOR(ver), | ||
256 | PSCI_VERSION_MINOR(ver)); | ||
257 | |||
258 | if (PSCI_VERSION_MAJOR(ver) == 0 && | ||
259 | PSCI_VERSION_MINOR(ver) < 2) { | ||
260 | err = -EINVAL; | ||
261 | pr_err("Conflicting PSCI version detected.\n"); | ||
262 | goto out_put_node; | ||
263 | } | ||
203 | } | 264 | } |
204 | 265 | ||
266 | pr_info("Using standard PSCI v0.2 function IDs\n"); | ||
267 | psci_function_id[PSCI_FN_CPU_SUSPEND] = PSCI_0_2_FN64_CPU_SUSPEND; | ||
268 | psci_ops.cpu_suspend = psci_cpu_suspend; | ||
269 | |||
270 | psci_function_id[PSCI_FN_CPU_OFF] = PSCI_0_2_FN_CPU_OFF; | ||
271 | psci_ops.cpu_off = psci_cpu_off; | ||
272 | |||
273 | psci_function_id[PSCI_FN_CPU_ON] = PSCI_0_2_FN64_CPU_ON; | ||
274 | psci_ops.cpu_on = psci_cpu_on; | ||
275 | |||
276 | psci_function_id[PSCI_FN_MIGRATE] = PSCI_0_2_FN64_MIGRATE; | ||
277 | psci_ops.migrate = psci_migrate; | ||
278 | |||
279 | psci_function_id[PSCI_FN_AFFINITY_INFO] = PSCI_0_2_FN64_AFFINITY_INFO; | ||
280 | psci_ops.affinity_info = psci_affinity_info; | ||
281 | |||
282 | psci_function_id[PSCI_FN_MIGRATE_INFO_TYPE] = | ||
283 | PSCI_0_2_FN_MIGRATE_INFO_TYPE; | ||
284 | psci_ops.migrate_info_type = psci_migrate_info_type; | ||
285 | |||
286 | arm_pm_restart = psci_sys_reset; | ||
287 | |||
288 | pm_power_off = psci_sys_poweroff; | ||
289 | |||
290 | out_put_node: | ||
291 | of_node_put(np); | ||
292 | return err; | ||
293 | } | ||
294 | |||
295 | /* | ||
296 | * PSCI < v0.2 get PSCI Function IDs via DT. | ||
297 | */ | ||
298 | static int psci_0_1_init(struct device_node *np) | ||
299 | { | ||
300 | u32 id; | ||
301 | int err; | ||
302 | |||
303 | err = get_set_conduit_method(np); | ||
304 | |||
305 | if (err) | ||
306 | goto out_put_node; | ||
307 | |||
308 | pr_info("Using PSCI v0.1 Function IDs from DT\n"); | ||
309 | |||
205 | if (!of_property_read_u32(np, "cpu_suspend", &id)) { | 310 | if (!of_property_read_u32(np, "cpu_suspend", &id)) { |
206 | psci_function_id[PSCI_FN_CPU_SUSPEND] = id; | 311 | psci_function_id[PSCI_FN_CPU_SUSPEND] = id; |
207 | psci_ops.cpu_suspend = psci_cpu_suspend; | 312 | psci_ops.cpu_suspend = psci_cpu_suspend; |
@@ -224,7 +329,28 @@ void __init psci_init(void) | |||
224 | 329 | ||
225 | out_put_node: | 330 | out_put_node: |
226 | of_node_put(np); | 331 | of_node_put(np); |
227 | return; | 332 | return err; |
333 | } | ||
334 | |||
335 | static const struct of_device_id psci_of_match[] __initconst = { | ||
336 | { .compatible = "arm,psci", .data = psci_0_1_init}, | ||
337 | { .compatible = "arm,psci-0.2", .data = psci_0_2_init}, | ||
338 | {}, | ||
339 | }; | ||
340 | |||
341 | int __init psci_init(void) | ||
342 | { | ||
343 | struct device_node *np; | ||
344 | const struct of_device_id *matched_np; | ||
345 | psci_initcall_t init_fn; | ||
346 | |||
347 | np = of_find_matching_node_and_match(NULL, psci_of_match, &matched_np); | ||
348 | |||
349 | if (!np) | ||
350 | return -ENODEV; | ||
351 | |||
352 | init_fn = (psci_initcall_t)matched_np->data; | ||
353 | return init_fn(np); | ||
228 | } | 354 | } |
229 | 355 | ||
230 | #ifdef CONFIG_SMP | 356 | #ifdef CONFIG_SMP |