2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License version 2 as
4 * published by the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
11 * Copyright (C) 2012 ARM Limited
13 * Author: Will Deacon <will.deacon@arm.com>
16 #define pr_fmt(fmt) "psci: " fmt
18 #include <linux/init.h>
20 #include <linux/reboot.h>
22 #include <uapi/linux/psci.h>
24 #include <asm/compiler.h>
25 #include <asm/errno.h>
26 #include <asm/opcodes-sec.h>
27 #include <asm/opcodes-virt.h>
29 #include <asm/system_misc.h>
31 struct psci_operations psci_ops
;
33 static int (*invoke_psci_fn
)(u32
, u32
, u32
, u32
);
34 typedef int (*psci_initcall_t
)(const struct device_node
*);
41 PSCI_FN_AFFINITY_INFO
,
42 PSCI_FN_MIGRATE_INFO_TYPE
,
46 static u32 psci_function_id
[PSCI_FN_MAX
];
48 static int psci_to_linux_errno(int errno
)
51 case PSCI_RET_SUCCESS
:
53 case PSCI_RET_NOT_SUPPORTED
:
55 case PSCI_RET_INVALID_PARAMS
:
64 static u32
psci_power_state_pack(struct psci_power_state state
)
66 return ((state
.id
<< PSCI_0_2_POWER_STATE_ID_SHIFT
)
67 & PSCI_0_2_POWER_STATE_ID_MASK
) |
68 ((state
.type
<< PSCI_0_2_POWER_STATE_TYPE_SHIFT
)
69 & PSCI_0_2_POWER_STATE_TYPE_MASK
) |
70 ((state
.affinity_level
<< PSCI_0_2_POWER_STATE_AFFL_SHIFT
)
71 & PSCI_0_2_POWER_STATE_AFFL_MASK
);
75 * The following two functions are invoked via the invoke_psci_fn pointer
76 * and will not be inlined, allowing us to piggyback on the AAPCS.
78 static noinline
int __invoke_psci_fn_hvc(u32 function_id
, u32 arg0
, u32 arg1
,
88 : "r" (arg0
), "r" (arg1
), "r" (arg2
));
93 static noinline
int __invoke_psci_fn_smc(u32 function_id
, u32 arg0
, u32 arg1
,
103 : "r" (arg0
), "r" (arg1
), "r" (arg2
));
108 #define PSCI_VER_MAJOR_MASK 0xffff0000
109 #define PSCI_VER_MINOR_MASK 0x0000ffff
110 #define PSCI_VER_MAJOR_SHIFT 16
111 #define PSCI_VER_MAJOR(ver) \
112 ((ver & PSCI_VER_MAJOR_MASK) >> PSCI_VER_MAJOR_SHIFT)
113 #define PSCI_VER_MINOR(ver) (ver & PSCI_VER_MINOR_MASK)
115 static int psci_get_version(void)
119 err
= invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION
, 0, 0, 0);
123 static int psci_cpu_suspend(struct psci_power_state state
,
124 unsigned long entry_point
)
129 fn
= psci_function_id
[PSCI_FN_CPU_SUSPEND
];
130 power_state
= psci_power_state_pack(state
);
131 err
= invoke_psci_fn(fn
, power_state
, entry_point
, 0);
132 return psci_to_linux_errno(err
);
135 static int psci_cpu_off(struct psci_power_state state
)
140 fn
= psci_function_id
[PSCI_FN_CPU_OFF
];
141 power_state
= psci_power_state_pack(state
);
142 err
= invoke_psci_fn(fn
, power_state
, 0, 0);
143 return psci_to_linux_errno(err
);
146 static int psci_cpu_on(unsigned long cpuid
, unsigned long entry_point
)
151 fn
= psci_function_id
[PSCI_FN_CPU_ON
];
152 err
= invoke_psci_fn(fn
, cpuid
, entry_point
, 0);
153 return psci_to_linux_errno(err
);
156 static int psci_migrate(unsigned long cpuid
)
161 fn
= psci_function_id
[PSCI_FN_MIGRATE
];
162 err
= invoke_psci_fn(fn
, cpuid
, 0, 0);
163 return psci_to_linux_errno(err
);
166 static int psci_affinity_info(unsigned long target_affinity
,
167 unsigned long lowest_affinity_level
)
172 fn
= psci_function_id
[PSCI_FN_AFFINITY_INFO
];
173 err
= invoke_psci_fn(fn
, target_affinity
, lowest_affinity_level
, 0);
177 static int psci_migrate_info_type(void)
182 fn
= psci_function_id
[PSCI_FN_MIGRATE_INFO_TYPE
];
183 err
= invoke_psci_fn(fn
, 0, 0, 0);
187 static int get_set_conduit_method(struct device_node
*np
)
191 pr_info("probing for conduit method from DT.\n");
193 if (of_property_read_string(np
, "method", &method
)) {
194 pr_warn("missing \"method\" property\n");
198 if (!strcmp("hvc", method
)) {
199 invoke_psci_fn
= __invoke_psci_fn_hvc
;
200 } else if (!strcmp("smc", method
)) {
201 invoke_psci_fn
= __invoke_psci_fn_smc
;
203 pr_warn("invalid \"method\" property: %s\n", method
);
209 static void psci_sys_reset(enum reboot_mode reboot_mode
, const char *cmd
)
211 invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET
, 0, 0, 0);
214 static void psci_sys_poweroff(void)
216 invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF
, 0, 0, 0);
220 * PSCI Function IDs for v0.2+ are well defined so use
223 static int psci_0_2_init(struct device_node
*np
)
227 err
= get_set_conduit_method(np
);
232 ver
= psci_get_version();
234 if (ver
== PSCI_RET_NOT_SUPPORTED
) {
235 /* PSCI v0.2 mandates implementation of PSCI_ID_VERSION. */
236 pr_err("PSCI firmware does not comply with the v0.2 spec.\n");
240 pr_info("PSCIv%d.%d detected in firmware.\n",
241 PSCI_VERSION_MAJOR(ver
),
242 PSCI_VERSION_MINOR(ver
));
244 if (PSCI_VERSION_MAJOR(ver
) == 0 &&
245 PSCI_VERSION_MINOR(ver
) < 2) {
247 pr_err("Conflicting PSCI version detected.\n");
252 pr_info("Using standard PSCI v0.2 function IDs\n");
253 psci_function_id
[PSCI_FN_CPU_SUSPEND
] = PSCI_0_2_FN_CPU_SUSPEND
;
254 psci_ops
.cpu_suspend
= psci_cpu_suspend
;
256 psci_function_id
[PSCI_FN_CPU_OFF
] = PSCI_0_2_FN_CPU_OFF
;
257 psci_ops
.cpu_off
= psci_cpu_off
;
259 psci_function_id
[PSCI_FN_CPU_ON
] = PSCI_0_2_FN_CPU_ON
;
260 psci_ops
.cpu_on
= psci_cpu_on
;
262 psci_function_id
[PSCI_FN_MIGRATE
] = PSCI_0_2_FN_MIGRATE
;
263 psci_ops
.migrate
= psci_migrate
;
265 psci_function_id
[PSCI_FN_AFFINITY_INFO
] = PSCI_0_2_FN_AFFINITY_INFO
;
266 psci_ops
.affinity_info
= psci_affinity_info
;
268 psci_function_id
[PSCI_FN_MIGRATE_INFO_TYPE
] =
269 PSCI_0_2_FN_MIGRATE_INFO_TYPE
;
270 psci_ops
.migrate_info_type
= psci_migrate_info_type
;
272 //TODO: verify how arm_pm_restart and pm_power_off works with atf
273 //arm_pm_restart = psci_sys_reset;
275 //pm_power_off = psci_sys_poweroff;
283 * PSCI < v0.2 get PSCI Function IDs via DT.
285 static int psci_0_1_init(struct device_node
*np
)
290 err
= get_set_conduit_method(np
);
295 pr_info("Using PSCI v0.1 Function IDs from DT\n");
297 if (!of_property_read_u32(np
, "cpu_suspend", &id
)) {
298 psci_function_id
[PSCI_FN_CPU_SUSPEND
] = id
;
299 psci_ops
.cpu_suspend
= psci_cpu_suspend
;
302 if (!of_property_read_u32(np
, "cpu_off", &id
)) {
303 psci_function_id
[PSCI_FN_CPU_OFF
] = id
;
304 psci_ops
.cpu_off
= psci_cpu_off
;
307 if (!of_property_read_u32(np
, "cpu_on", &id
)) {
308 psci_function_id
[PSCI_FN_CPU_ON
] = id
;
309 psci_ops
.cpu_on
= psci_cpu_on
;
312 if (!of_property_read_u32(np
, "migrate", &id
)) {
313 psci_function_id
[PSCI_FN_MIGRATE
] = id
;
314 psci_ops
.migrate
= psci_migrate
;
322 static const struct of_device_id psci_of_match
[] __initconst
= {
323 { .compatible
= "arm,psci", .data
= psci_0_1_init
},
324 { .compatible
= "arm,psci-0.2", .data
= psci_0_2_init
},
328 int __init
psci_init(void)
330 struct device_node
*np
;
331 const struct of_device_id
*matched_np
;
332 psci_initcall_t init_fn
;
334 np
= of_find_matching_node_and_match(NULL
, psci_of_match
, &matched_np
);
338 init_fn
= (psci_initcall_t
)matched_np
->data
;