212 lines
		
	
	
	
		
			4.8 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			212 lines
		
	
	
	
		
			4.8 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*
 | ||
|  |  * 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 <will.deacon@arm.com> | ||
|  |  */ | ||
|  | 
 | ||
|  | #define pr_fmt(fmt) "psci: " fmt
 | ||
|  | 
 | ||
|  | #include <linux/init.h>
 | ||
|  | #include <linux/of.h>
 | ||
|  | 
 | ||
|  | #include <asm/compiler.h>
 | ||
|  | #include <asm/errno.h>
 | ||
|  | #include <asm/opcodes-sec.h>
 | ||
|  | #include <asm/opcodes-virt.h>
 | ||
|  | #include <asm/psci.h>
 | ||
|  | 
 | ||
|  | 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
 | ||
|  | 
 | ||
|  | 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; | ||
|  | 	}; | ||
|  | 
 | ||
|  | 	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[] __initconst = { | ||
|  | 	{ .compatible = "arm,psci",	}, | ||
|  | 	{}, | ||
|  | }; | ||
|  | 
 | ||
|  | static int __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 0; | ||
|  | 
 | ||
|  | 	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 0; | ||
|  | } | ||
|  | early_initcall(psci_init); |