1/* 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. 5 * 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. 10 * 11 * Copyright (C) 2012 ARM Limited 12 * 13 * Author: Will Deacon <will.deacon@arm.com> 14 */ 15 16#define pr_fmt(fmt) "psci: " fmt 17 18#include <linux/init.h> 19#include <linux/of.h> 20#include <linux/reboot.h> 21#include <linux/pm.h> 22#include <uapi/linux/psci.h> 23 24#include <asm/compiler.h> 25#include <asm/errno.h> 26#include <asm/psci.h> 27#include <asm/system_misc.h> 28 29struct psci_operations psci_ops; 30 31static int (*invoke_psci_fn)(u32, u32, u32, u32); 32typedef int (*psci_initcall_t)(const struct device_node *); 33 34asmlinkage int __invoke_psci_fn_hvc(u32, u32, u32, u32); 35asmlinkage int __invoke_psci_fn_smc(u32, u32, u32, u32); 36 37enum psci_function { 38 PSCI_FN_CPU_SUSPEND, 39 PSCI_FN_CPU_ON, 40 PSCI_FN_CPU_OFF, 41 PSCI_FN_MIGRATE, 42 PSCI_FN_AFFINITY_INFO, 43 PSCI_FN_MIGRATE_INFO_TYPE, 44 PSCI_FN_MAX, 45}; 46 47static u32 psci_function_id[PSCI_FN_MAX]; 48 49static int psci_to_linux_errno(int errno) 50{ 51 switch (errno) { 52 case PSCI_RET_SUCCESS: 53 return 0; 54 case PSCI_RET_NOT_SUPPORTED: 55 return -EOPNOTSUPP; 56 case PSCI_RET_INVALID_PARAMS: 57 return -EINVAL; 58 case PSCI_RET_DENIED: 59 return -EPERM; 60 }; 61 62 return -EINVAL; 63} 64 65static u32 psci_power_state_pack(struct psci_power_state state) 66{ 67 return ((state.id << PSCI_0_2_POWER_STATE_ID_SHIFT) 68 & PSCI_0_2_POWER_STATE_ID_MASK) | 69 ((state.type << PSCI_0_2_POWER_STATE_TYPE_SHIFT) 70 & PSCI_0_2_POWER_STATE_TYPE_MASK) | 71 ((state.affinity_level << PSCI_0_2_POWER_STATE_AFFL_SHIFT) 72 & PSCI_0_2_POWER_STATE_AFFL_MASK); 73} 74 75static int psci_get_version(void) 76{ 77 int err; 78 79 err = invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0); 80 return err; 81} 82 83static int psci_cpu_suspend(struct psci_power_state state, 84 unsigned long entry_point) 85{ 86 int err; 87 u32 fn, power_state; 88 89 fn = psci_function_id[PSCI_FN_CPU_SUSPEND]; 90 power_state = psci_power_state_pack(state); 91 err = invoke_psci_fn(fn, power_state, entry_point, 0); 92 return psci_to_linux_errno(err); 93} 94 95static int psci_cpu_off(struct psci_power_state state) 96{ 97 int err; 98 u32 fn, power_state; 99 100 fn = psci_function_id[PSCI_FN_CPU_OFF]; 101 power_state = psci_power_state_pack(state); 102 err = invoke_psci_fn(fn, power_state, 0, 0); 103 return psci_to_linux_errno(err); 104} 105 106static int psci_cpu_on(unsigned long cpuid, unsigned long entry_point) 107{ 108 int err; 109 u32 fn; 110 111 fn = psci_function_id[PSCI_FN_CPU_ON]; 112 err = invoke_psci_fn(fn, cpuid, entry_point, 0); 113 return psci_to_linux_errno(err); 114} 115 116static int psci_migrate(unsigned long cpuid) 117{ 118 int err; 119 u32 fn; 120 121 fn = psci_function_id[PSCI_FN_MIGRATE]; 122 err = invoke_psci_fn(fn, cpuid, 0, 0); 123 return psci_to_linux_errno(err); 124} 125 126static int psci_affinity_info(unsigned long target_affinity, 127 unsigned long lowest_affinity_level) 128{ 129 int err; 130 u32 fn; 131 132 fn = psci_function_id[PSCI_FN_AFFINITY_INFO]; 133 err = invoke_psci_fn(fn, target_affinity, lowest_affinity_level, 0); 134 return err; 135} 136 137static int psci_migrate_info_type(void) 138{ 139 int err; 140 u32 fn; 141 142 fn = psci_function_id[PSCI_FN_MIGRATE_INFO_TYPE]; 143 err = invoke_psci_fn(fn, 0, 0, 0); 144 return err; 145} 146 147static int get_set_conduit_method(struct device_node *np) 148{ 149 const char *method; 150 151 pr_info("probing for conduit method from DT.\n"); 152 153 if (of_property_read_string(np, "method", &method)) { 154 pr_warn("missing \"method\" property\n"); 155 return -ENXIO; 156 } 157 158 if (!strcmp("hvc", method)) { 159 invoke_psci_fn = __invoke_psci_fn_hvc; 160 } else if (!strcmp("smc", method)) { 161 invoke_psci_fn = __invoke_psci_fn_smc; 162 } else { 163 pr_warn("invalid \"method\" property: %s\n", method); 164 return -EINVAL; 165 } 166 return 0; 167} 168 169static void psci_sys_reset(enum reboot_mode reboot_mode, const char *cmd) 170{ 171 invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0); 172} 173 174static void psci_sys_poweroff(void) 175{ 176 invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0); 177} 178 179/* 180 * PSCI Function IDs for v0.2+ are well defined so use 181 * standard values. 182 */ 183static int psci_0_2_init(struct device_node *np) 184{ 185 int err, ver; 186 187 err = get_set_conduit_method(np); 188 189 if (err) 190 goto out_put_node; 191 192 ver = psci_get_version(); 193 194 if (ver == PSCI_RET_NOT_SUPPORTED) { 195 /* PSCI v0.2 mandates implementation of PSCI_ID_VERSION. */ 196 pr_err("PSCI firmware does not comply with the v0.2 spec.\n"); 197 err = -EOPNOTSUPP; 198 goto out_put_node; 199 } else { 200 pr_info("PSCIv%d.%d detected in firmware.\n", 201 PSCI_VERSION_MAJOR(ver), 202 PSCI_VERSION_MINOR(ver)); 203 204 if (PSCI_VERSION_MAJOR(ver) == 0 && 205 PSCI_VERSION_MINOR(ver) < 2) { 206 err = -EINVAL; 207 pr_err("Conflicting PSCI version detected.\n"); 208 goto out_put_node; 209 } 210 } 211 212 pr_info("Using standard PSCI v0.2 function IDs\n"); 213 psci_function_id[PSCI_FN_CPU_SUSPEND] = PSCI_0_2_FN_CPU_SUSPEND; 214 psci_ops.cpu_suspend = psci_cpu_suspend; 215 216 psci_function_id[PSCI_FN_CPU_OFF] = PSCI_0_2_FN_CPU_OFF; 217 psci_ops.cpu_off = psci_cpu_off; 218 219 psci_function_id[PSCI_FN_CPU_ON] = PSCI_0_2_FN_CPU_ON; 220 psci_ops.cpu_on = psci_cpu_on; 221 222 psci_function_id[PSCI_FN_MIGRATE] = PSCI_0_2_FN_MIGRATE; 223 psci_ops.migrate = psci_migrate; 224 225 psci_function_id[PSCI_FN_AFFINITY_INFO] = PSCI_0_2_FN_AFFINITY_INFO; 226 psci_ops.affinity_info = psci_affinity_info; 227 228 psci_function_id[PSCI_FN_MIGRATE_INFO_TYPE] = 229 PSCI_0_2_FN_MIGRATE_INFO_TYPE; 230 psci_ops.migrate_info_type = psci_migrate_info_type; 231 232 arm_pm_restart = psci_sys_reset; 233 234 pm_power_off = psci_sys_poweroff; 235 236out_put_node: 237 of_node_put(np); 238 return err; 239} 240 241/* 242 * PSCI < v0.2 get PSCI Function IDs via DT. 243 */ 244static int psci_0_1_init(struct device_node *np) 245{ 246 u32 id; 247 int err; 248 249 err = get_set_conduit_method(np); 250 251 if (err) 252 goto out_put_node; 253 254 pr_info("Using PSCI v0.1 Function IDs from DT\n"); 255 256 if (!of_property_read_u32(np, "cpu_suspend", &id)) { 257 psci_function_id[PSCI_FN_CPU_SUSPEND] = id; 258 psci_ops.cpu_suspend = psci_cpu_suspend; 259 } 260 261 if (!of_property_read_u32(np, "cpu_off", &id)) { 262 psci_function_id[PSCI_FN_CPU_OFF] = id; 263 psci_ops.cpu_off = psci_cpu_off; 264 } 265 266 if (!of_property_read_u32(np, "cpu_on", &id)) { 267 psci_function_id[PSCI_FN_CPU_ON] = id; 268 psci_ops.cpu_on = psci_cpu_on; 269 } 270 271 if (!of_property_read_u32(np, "migrate", &id)) { 272 psci_function_id[PSCI_FN_MIGRATE] = id; 273 psci_ops.migrate = psci_migrate; 274 } 275 276out_put_node: 277 of_node_put(np); 278 return err; 279} 280 281static const struct of_device_id psci_of_match[] __initconst = { 282 { .compatible = "arm,psci", .data = psci_0_1_init}, 283 { .compatible = "arm,psci-0.2", .data = psci_0_2_init}, 284 {}, 285}; 286 287int __init psci_init(void) 288{ 289 struct device_node *np; 290 const struct of_device_id *matched_np; 291 psci_initcall_t init_fn; 292 293 np = of_find_matching_node_and_match(NULL, psci_of_match, &matched_np); 294 if (!np) 295 return -ENODEV; 296 297 init_fn = (psci_initcall_t)matched_np->data; 298 return init_fn(np); 299} 300