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/arm/kernel | |
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/arm/kernel')
-rw-r--r-- | arch/arm/kernel/psci.c | 196 |
1 files changed, 159 insertions, 37 deletions
diff --git a/arch/arm/kernel/psci.c b/arch/arm/kernel/psci.c index 46931880093d..f73891b6b730 100644 --- a/arch/arm/kernel/psci.c +++ b/arch/arm/kernel/psci.c | |||
@@ -17,63 +17,58 @@ | |||
17 | 17 | ||
18 | #include <linux/init.h> | 18 | #include <linux/init.h> |
19 | #include <linux/of.h> | 19 | #include <linux/of.h> |
20 | #include <linux/reboot.h> | ||
21 | #include <linux/pm.h> | ||
22 | #include <uapi/linux/psci.h> | ||
20 | 23 | ||
21 | #include <asm/compiler.h> | 24 | #include <asm/compiler.h> |
22 | #include <asm/errno.h> | 25 | #include <asm/errno.h> |
23 | #include <asm/opcodes-sec.h> | 26 | #include <asm/opcodes-sec.h> |
24 | #include <asm/opcodes-virt.h> | 27 | #include <asm/opcodes-virt.h> |
25 | #include <asm/psci.h> | 28 | #include <asm/psci.h> |
29 | #include <asm/system_misc.h> | ||
26 | 30 | ||
27 | struct psci_operations psci_ops; | 31 | struct psci_operations psci_ops; |
28 | 32 | ||
29 | static int (*invoke_psci_fn)(u32, u32, u32, u32); | 33 | static int (*invoke_psci_fn)(u32, u32, u32, u32); |
34 | typedef int (*psci_initcall_t)(const struct device_node *); | ||
30 | 35 | ||
31 | enum psci_function { | 36 | enum psci_function { |
32 | PSCI_FN_CPU_SUSPEND, | 37 | PSCI_FN_CPU_SUSPEND, |
33 | PSCI_FN_CPU_ON, | 38 | PSCI_FN_CPU_ON, |
34 | PSCI_FN_CPU_OFF, | 39 | PSCI_FN_CPU_OFF, |
35 | PSCI_FN_MIGRATE, | 40 | PSCI_FN_MIGRATE, |
41 | PSCI_FN_AFFINITY_INFO, | ||
42 | PSCI_FN_MIGRATE_INFO_TYPE, | ||
36 | PSCI_FN_MAX, | 43 | PSCI_FN_MAX, |
37 | }; | 44 | }; |
38 | 45 | ||
39 | static u32 psci_function_id[PSCI_FN_MAX]; | 46 | static u32 psci_function_id[PSCI_FN_MAX]; |
40 | 47 | ||
41 | #define PSCI_RET_SUCCESS 0 | ||
42 | #define PSCI_RET_EOPNOTSUPP -1 | ||
43 | #define PSCI_RET_EINVAL -2 | ||
44 | #define PSCI_RET_EPERM -3 | ||
45 | |||
46 | static int psci_to_linux_errno(int errno) | 48 | static int psci_to_linux_errno(int errno) |
47 | { | 49 | { |
48 | switch (errno) { | 50 | switch (errno) { |
49 | case PSCI_RET_SUCCESS: | 51 | case PSCI_RET_SUCCESS: |
50 | return 0; | 52 | return 0; |
51 | case PSCI_RET_EOPNOTSUPP: | 53 | case PSCI_RET_NOT_SUPPORTED: |
52 | return -EOPNOTSUPP; | 54 | return -EOPNOTSUPP; |
53 | case PSCI_RET_EINVAL: | 55 | case PSCI_RET_INVALID_PARAMS: |
54 | return -EINVAL; | 56 | return -EINVAL; |
55 | case PSCI_RET_EPERM: | 57 | case PSCI_RET_DENIED: |
56 | return -EPERM; | 58 | return -EPERM; |
57 | }; | 59 | }; |
58 | 60 | ||
59 | return -EINVAL; | 61 | return -EINVAL; |
60 | } | 62 | } |
61 | 63 | ||
62 | #define PSCI_POWER_STATE_ID_MASK 0xffff | ||
63 | #define PSCI_POWER_STATE_ID_SHIFT 0 | ||
64 | #define PSCI_POWER_STATE_TYPE_MASK 0x1 | ||
65 | #define PSCI_POWER_STATE_TYPE_SHIFT 16 | ||
66 | #define PSCI_POWER_STATE_AFFL_MASK 0x3 | ||
67 | #define PSCI_POWER_STATE_AFFL_SHIFT 24 | ||
68 | |||
69 | static u32 psci_power_state_pack(struct psci_power_state state) | 64 | static u32 psci_power_state_pack(struct psci_power_state state) |
70 | { | 65 | { |
71 | return ((state.id & PSCI_POWER_STATE_ID_MASK) | 66 | return ((state.id << PSCI_0_2_POWER_STATE_ID_SHIFT) |
72 | << PSCI_POWER_STATE_ID_SHIFT) | | 67 | & PSCI_0_2_POWER_STATE_ID_MASK) | |
73 | ((state.type & PSCI_POWER_STATE_TYPE_MASK) | 68 | ((state.type << PSCI_0_2_POWER_STATE_TYPE_SHIFT) |
74 | << PSCI_POWER_STATE_TYPE_SHIFT) | | 69 | & PSCI_0_2_POWER_STATE_TYPE_MASK) | |
75 | ((state.affinity_level & PSCI_POWER_STATE_AFFL_MASK) | 70 | ((state.affinity_level << PSCI_0_2_POWER_STATE_AFFL_SHIFT) |
76 | << PSCI_POWER_STATE_AFFL_SHIFT); | 71 | & PSCI_0_2_POWER_STATE_AFFL_MASK); |
77 | } | 72 | } |
78 | 73 | ||
79 | /* | 74 | /* |
@@ -110,6 +105,14 @@ static noinline int __invoke_psci_fn_smc(u32 function_id, u32 arg0, u32 arg1, | |||
110 | return function_id; | 105 | return function_id; |
111 | } | 106 | } |
112 | 107 | ||
108 | static int psci_get_version(void) | ||
109 | { | ||
110 | int err; | ||
111 | |||
112 | err = invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0); | ||
113 | return err; | ||
114 | } | ||
115 | |||
113 | static int psci_cpu_suspend(struct psci_power_state state, | 116 | static int psci_cpu_suspend(struct psci_power_state state, |
114 | unsigned long entry_point) | 117 | unsigned long entry_point) |
115 | { | 118 | { |
@@ -153,26 +156,36 @@ static int psci_migrate(unsigned long cpuid) | |||
153 | return psci_to_linux_errno(err); | 156 | return psci_to_linux_errno(err); |
154 | } | 157 | } |
155 | 158 | ||
156 | static const struct of_device_id psci_of_match[] __initconst = { | 159 | static int psci_affinity_info(unsigned long target_affinity, |
157 | { .compatible = "arm,psci", }, | 160 | unsigned long lowest_affinity_level) |
158 | {}, | 161 | { |
159 | }; | 162 | int err; |
163 | u32 fn; | ||
164 | |||
165 | fn = psci_function_id[PSCI_FN_AFFINITY_INFO]; | ||
166 | err = invoke_psci_fn(fn, target_affinity, lowest_affinity_level, 0); | ||
167 | return err; | ||
168 | } | ||
160 | 169 | ||
161 | void __init psci_init(void) | 170 | static int psci_migrate_info_type(void) |
162 | { | 171 | { |
163 | struct device_node *np; | 172 | int err; |
164 | const char *method; | 173 | u32 fn; |
165 | u32 id; | ||
166 | 174 | ||
167 | np = of_find_matching_node(NULL, psci_of_match); | 175 | fn = psci_function_id[PSCI_FN_MIGRATE_INFO_TYPE]; |
168 | if (!np) | 176 | err = invoke_psci_fn(fn, 0, 0, 0); |
169 | return; | 177 | return err; |
178 | } | ||
179 | |||
180 | static int get_set_conduit_method(struct device_node *np) | ||
181 | { | ||
182 | const char *method; | ||
170 | 183 | ||
171 | pr_info("probing function IDs from device-tree\n"); | 184 | pr_info("probing for conduit method from DT.\n"); |
172 | 185 | ||
173 | if (of_property_read_string(np, "method", &method)) { | 186 | if (of_property_read_string(np, "method", &method)) { |
174 | pr_warning("missing \"method\" property\n"); | 187 | pr_warn("missing \"method\" property\n"); |
175 | goto out_put_node; | 188 | return -ENXIO; |
176 | } | 189 | } |
177 | 190 | ||
178 | if (!strcmp("hvc", method)) { | 191 | if (!strcmp("hvc", method)) { |
@@ -180,10 +193,99 @@ void __init psci_init(void) | |||
180 | } else if (!strcmp("smc", method)) { | 193 | } else if (!strcmp("smc", method)) { |
181 | invoke_psci_fn = __invoke_psci_fn_smc; | 194 | invoke_psci_fn = __invoke_psci_fn_smc; |
182 | } else { | 195 | } else { |
183 | pr_warning("invalid \"method\" property: %s\n", method); | 196 | pr_warn("invalid \"method\" property: %s\n", method); |
197 | return -EINVAL; | ||
198 | } | ||
199 | return 0; | ||
200 | } | ||
201 | |||
202 | static void psci_sys_reset(enum reboot_mode reboot_mode, const char *cmd) | ||
203 | { | ||
204 | invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0); | ||
205 | } | ||
206 | |||
207 | static void psci_sys_poweroff(void) | ||
208 | { | ||
209 | invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0); | ||
210 | } | ||
211 | |||
212 | /* | ||
213 | * PSCI Function IDs for v0.2+ are well defined so use | ||
214 | * standard values. | ||
215 | */ | ||
216 | static int psci_0_2_init(struct device_node *np) | ||
217 | { | ||
218 | int err, ver; | ||
219 | |||
220 | err = get_set_conduit_method(np); | ||
221 | |||
222 | if (err) | ||
223 | goto out_put_node; | ||
224 | |||
225 | ver = psci_get_version(); | ||
226 | |||
227 | if (ver == PSCI_RET_NOT_SUPPORTED) { | ||
228 | /* PSCI v0.2 mandates implementation of PSCI_ID_VERSION. */ | ||
229 | pr_err("PSCI firmware does not comply with the v0.2 spec.\n"); | ||
230 | err = -EOPNOTSUPP; | ||
184 | goto out_put_node; | 231 | goto out_put_node; |
232 | } else { | ||
233 | pr_info("PSCIv%d.%d detected in firmware.\n", | ||
234 | PSCI_VERSION_MAJOR(ver), | ||
235 | PSCI_VERSION_MINOR(ver)); | ||
236 | |||
237 | if (PSCI_VERSION_MAJOR(ver) == 0 && | ||
238 | PSCI_VERSION_MINOR(ver) < 2) { | ||
239 | err = -EINVAL; | ||
240 | pr_err("Conflicting PSCI version detected.\n"); | ||
241 | goto out_put_node; | ||
242 | } | ||
185 | } | 243 | } |
186 | 244 | ||
245 | pr_info("Using standard PSCI v0.2 function IDs\n"); | ||
246 | psci_function_id[PSCI_FN_CPU_SUSPEND] = PSCI_0_2_FN_CPU_SUSPEND; | ||
247 | psci_ops.cpu_suspend = psci_cpu_suspend; | ||
248 | |||
249 | psci_function_id[PSCI_FN_CPU_OFF] = PSCI_0_2_FN_CPU_OFF; | ||
250 | psci_ops.cpu_off = psci_cpu_off; | ||
251 | |||
252 | psci_function_id[PSCI_FN_CPU_ON] = PSCI_0_2_FN_CPU_ON; | ||
253 | psci_ops.cpu_on = psci_cpu_on; | ||
254 | |||
255 | psci_function_id[PSCI_FN_MIGRATE] = PSCI_0_2_FN_MIGRATE; | ||
256 | psci_ops.migrate = psci_migrate; | ||
257 | |||
258 | psci_function_id[PSCI_FN_AFFINITY_INFO] = PSCI_0_2_FN_AFFINITY_INFO; | ||
259 | psci_ops.affinity_info = psci_affinity_info; | ||
260 | |||
261 | psci_function_id[PSCI_FN_MIGRATE_INFO_TYPE] = | ||
262 | PSCI_0_2_FN_MIGRATE_INFO_TYPE; | ||
263 | psci_ops.migrate_info_type = psci_migrate_info_type; | ||
264 | |||
265 | arm_pm_restart = psci_sys_reset; | ||
266 | |||
267 | pm_power_off = psci_sys_poweroff; | ||
268 | |||
269 | out_put_node: | ||
270 | of_node_put(np); | ||
271 | return err; | ||
272 | } | ||
273 | |||
274 | /* | ||
275 | * PSCI < v0.2 get PSCI Function IDs via DT. | ||
276 | */ | ||
277 | static int psci_0_1_init(struct device_node *np) | ||
278 | { | ||
279 | u32 id; | ||
280 | int err; | ||
281 | |||
282 | err = get_set_conduit_method(np); | ||
283 | |||
284 | if (err) | ||
285 | goto out_put_node; | ||
286 | |||
287 | pr_info("Using PSCI v0.1 Function IDs from DT\n"); | ||
288 | |||
187 | if (!of_property_read_u32(np, "cpu_suspend", &id)) { | 289 | if (!of_property_read_u32(np, "cpu_suspend", &id)) { |
188 | psci_function_id[PSCI_FN_CPU_SUSPEND] = id; | 290 | psci_function_id[PSCI_FN_CPU_SUSPEND] = id; |
189 | psci_ops.cpu_suspend = psci_cpu_suspend; | 291 | psci_ops.cpu_suspend = psci_cpu_suspend; |
@@ -206,5 +308,25 @@ void __init psci_init(void) | |||
206 | 308 | ||
207 | out_put_node: | 309 | out_put_node: |
208 | of_node_put(np); | 310 | of_node_put(np); |
209 | return; | 311 | return err; |
312 | } | ||
313 | |||
314 | static const struct of_device_id psci_of_match[] __initconst = { | ||
315 | { .compatible = "arm,psci", .data = psci_0_1_init}, | ||
316 | { .compatible = "arm,psci-0.2", .data = psci_0_2_init}, | ||
317 | {}, | ||
318 | }; | ||
319 | |||
320 | int __init psci_init(void) | ||
321 | { | ||
322 | struct device_node *np; | ||
323 | const struct of_device_id *matched_np; | ||
324 | psci_initcall_t init_fn; | ||
325 | |||
326 | np = of_find_matching_node_and_match(NULL, psci_of_match, &matched_np); | ||
327 | if (!np) | ||
328 | return -ENODEV; | ||
329 | |||
330 | init_fn = (psci_initcall_t)matched_np->data; | ||
331 | return init_fn(np); | ||
210 | } | 332 | } |