/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Copyright (C) 2012 ARM Limited * * Author: Will Deacon */ #define pr_fmt(fmt) "psci: " fmt #include #include #include #include #include #include #include struct psci_operations psci_ops; static int (*invoke_psci_fn)(u32, u32, u32, u32); enum psci_function { PSCI_FN_CPU_SUSPEND, PSCI_FN_CPU_ON, PSCI_FN_CPU_OFF, PSCI_FN_MIGRATE, PSCI_FN_MAX, }; static u32 psci_function_id[PSCI_FN_MAX]; #define PSCI_RET_SUCCESS 0 #define PSCI_RET_EOPNOTSUPP -1 #define PSCI_RET_EINVAL -2 #define PSCI_RET_EPERM -3 #define PSCI_RET_EALREADYON -4 static int psci_to_linux_errno(int errno) { switch (errno) { case PSCI_RET_SUCCESS: return 0; case PSCI_RET_EOPNOTSUPP: return -EOPNOTSUPP; case PSCI_RET_EINVAL: return -EINVAL; case PSCI_RET_EPERM: return -EPERM; case PSCI_RET_EALREADYON: return -EAGAIN; }; return -EINVAL; } #define PSCI_POWER_STATE_ID_MASK 0xffff #define PSCI_POWER_STATE_ID_SHIFT 0 #define PSCI_POWER_STATE_TYPE_MASK 0x1 #define PSCI_POWER_STATE_TYPE_SHIFT 16 #define PSCI_POWER_STATE_AFFL_MASK 0x3 #define PSCI_POWER_STATE_AFFL_SHIFT 24 static u32 psci_power_state_pack(struct psci_power_state state) { return ((state.id & PSCI_POWER_STATE_ID_MASK) << PSCI_POWER_STATE_ID_SHIFT) | ((state.type & PSCI_POWER_STATE_TYPE_MASK) << PSCI_POWER_STATE_TYPE_SHIFT) | ((state.affinity_level & PSCI_POWER_STATE_AFFL_MASK) << PSCI_POWER_STATE_AFFL_SHIFT); } /* * The following two functions are invoked via the invoke_psci_fn pointer * and will not be inlined, allowing us to piggyback on the AAPCS. */ static noinline int __invoke_psci_fn_hvc(u32 function_id, u32 arg0, u32 arg1, u32 arg2) { asm volatile( __asmeq("%0", "r0") __asmeq("%1", "r1") __asmeq("%2", "r2") __asmeq("%3", "r3") __HVC(0) : "+r" (function_id) : "r" (arg0), "r" (arg1), "r" (arg2)); return function_id; } static noinline int __invoke_psci_fn_smc(u32 function_id, u32 arg0, u32 arg1, u32 arg2) { asm volatile( __asmeq("%0", "r0") __asmeq("%1", "r1") __asmeq("%2", "r2") __asmeq("%3", "r3") __SMC(0) : "+r" (function_id) : "r" (arg0), "r" (arg1), "r" (arg2)); return function_id; } static int psci_cpu_suspend(struct psci_power_state state, unsigned long entry_point) { int err; u32 fn, power_state; fn = psci_function_id[PSCI_FN_CPU_SUSPEND]; power_state = psci_power_state_pack(state); err = invoke_psci_fn(fn, power_state, entry_point, 0); return psci_to_linux_errno(err); } static int psci_cpu_off(struct psci_power_state state) { int err; u32 fn, power_state; fn = psci_function_id[PSCI_FN_CPU_OFF]; power_state = psci_power_state_pack(state); err = invoke_psci_fn(fn, power_state, 0, 0); return psci_to_linux_errno(err); } static int psci_cpu_on(unsigned long cpuid, unsigned long entry_point) { int err; u32 fn; fn = psci_function_id[PSCI_FN_CPU_ON]; err = invoke_psci_fn(fn, cpuid, entry_point, 0); return psci_to_linux_errno(err); } static int psci_migrate(unsigned long cpuid) { int err; u32 fn; fn = psci_function_id[PSCI_FN_MIGRATE]; err = invoke_psci_fn(fn, cpuid, 0, 0); return psci_to_linux_errno(err); } static const struct of_device_id psci_of_match[] = { { .compatible = "arm,psci", }, {}, }; void __init psci_init(void) { struct device_node *np; const char *method; u32 id; np = of_find_matching_node(NULL, psci_of_match); if (!np) return; pr_info("probing function IDs from device-tree\n"); if (of_property_read_string(np, "method", &method)) { pr_warning("missing \"method\" property\n"); goto out_put_node; } if (!strcmp("hvc", method)) { invoke_psci_fn = __invoke_psci_fn_hvc; } else if (!strcmp("smc", method)) { invoke_psci_fn = __invoke_psci_fn_smc; } else { pr_warning("invalid \"method\" property: %s\n", method); goto out_put_node; } if (!of_property_read_u32(np, "cpu_suspend", &id)) { psci_function_id[PSCI_FN_CPU_SUSPEND] = id; psci_ops.cpu_suspend = psci_cpu_suspend; } if (!of_property_read_u32(np, "cpu_off", &id)) { psci_function_id[PSCI_FN_CPU_OFF] = id; psci_ops.cpu_off = psci_cpu_off; } if (!of_property_read_u32(np, "cpu_on", &id)) { psci_function_id[PSCI_FN_CPU_ON] = id; psci_ops.cpu_on = psci_cpu_on; } if (!of_property_read_u32(np, "migrate", &id)) { psci_function_id[PSCI_FN_MIGRATE] = id; psci_ops.migrate = psci_migrate; } out_put_node: of_node_put(np); return; } int psci_probe(void) { struct device_node *np; int ret = -ENODEV; np = of_find_matching_node(NULL, psci_of_match); if (np) ret = 0; of_node_put(np); return ret; }