xref: /openbmc/linux/arch/arm64/kernel/psci.c (revision e23feb16)
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) 2013 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 
21 #include <asm/compiler.h>
22 #include <asm/errno.h>
23 #include <asm/psci.h>
24 
25 struct psci_operations psci_ops;
26 
27 static int (*invoke_psci_fn)(u64, u64, u64, u64);
28 
29 enum psci_function {
30 	PSCI_FN_CPU_SUSPEND,
31 	PSCI_FN_CPU_ON,
32 	PSCI_FN_CPU_OFF,
33 	PSCI_FN_MIGRATE,
34 	PSCI_FN_MAX,
35 };
36 
37 static u32 psci_function_id[PSCI_FN_MAX];
38 
39 #define PSCI_RET_SUCCESS		0
40 #define PSCI_RET_EOPNOTSUPP		-1
41 #define PSCI_RET_EINVAL			-2
42 #define PSCI_RET_EPERM			-3
43 
44 static int psci_to_linux_errno(int errno)
45 {
46 	switch (errno) {
47 	case PSCI_RET_SUCCESS:
48 		return 0;
49 	case PSCI_RET_EOPNOTSUPP:
50 		return -EOPNOTSUPP;
51 	case PSCI_RET_EINVAL:
52 		return -EINVAL;
53 	case PSCI_RET_EPERM:
54 		return -EPERM;
55 	};
56 
57 	return -EINVAL;
58 }
59 
60 #define PSCI_POWER_STATE_ID_MASK	0xffff
61 #define PSCI_POWER_STATE_ID_SHIFT	0
62 #define PSCI_POWER_STATE_TYPE_MASK	0x1
63 #define PSCI_POWER_STATE_TYPE_SHIFT	16
64 #define PSCI_POWER_STATE_AFFL_MASK	0x3
65 #define PSCI_POWER_STATE_AFFL_SHIFT	24
66 
67 static u32 psci_power_state_pack(struct psci_power_state state)
68 {
69 	return	((state.id & PSCI_POWER_STATE_ID_MASK)
70 			<< PSCI_POWER_STATE_ID_SHIFT)	|
71 		((state.type & PSCI_POWER_STATE_TYPE_MASK)
72 			<< PSCI_POWER_STATE_TYPE_SHIFT)	|
73 		((state.affinity_level & PSCI_POWER_STATE_AFFL_MASK)
74 			<< PSCI_POWER_STATE_AFFL_SHIFT);
75 }
76 
77 /*
78  * The following two functions are invoked via the invoke_psci_fn pointer
79  * and will not be inlined, allowing us to piggyback on the AAPCS.
80  */
81 static noinline int __invoke_psci_fn_hvc(u64 function_id, u64 arg0, u64 arg1,
82 					 u64 arg2)
83 {
84 	asm volatile(
85 			__asmeq("%0", "x0")
86 			__asmeq("%1", "x1")
87 			__asmeq("%2", "x2")
88 			__asmeq("%3", "x3")
89 			"hvc	#0\n"
90 		: "+r" (function_id)
91 		: "r" (arg0), "r" (arg1), "r" (arg2));
92 
93 	return function_id;
94 }
95 
96 static noinline int __invoke_psci_fn_smc(u64 function_id, u64 arg0, u64 arg1,
97 					 u64 arg2)
98 {
99 	asm volatile(
100 			__asmeq("%0", "x0")
101 			__asmeq("%1", "x1")
102 			__asmeq("%2", "x2")
103 			__asmeq("%3", "x3")
104 			"smc	#0\n"
105 		: "+r" (function_id)
106 		: "r" (arg0), "r" (arg1), "r" (arg2));
107 
108 	return function_id;
109 }
110 
111 static int psci_cpu_suspend(struct psci_power_state state,
112 			    unsigned long entry_point)
113 {
114 	int err;
115 	u32 fn, power_state;
116 
117 	fn = psci_function_id[PSCI_FN_CPU_SUSPEND];
118 	power_state = psci_power_state_pack(state);
119 	err = invoke_psci_fn(fn, power_state, entry_point, 0);
120 	return psci_to_linux_errno(err);
121 }
122 
123 static int psci_cpu_off(struct psci_power_state state)
124 {
125 	int err;
126 	u32 fn, power_state;
127 
128 	fn = psci_function_id[PSCI_FN_CPU_OFF];
129 	power_state = psci_power_state_pack(state);
130 	err = invoke_psci_fn(fn, power_state, 0, 0);
131 	return psci_to_linux_errno(err);
132 }
133 
134 static int psci_cpu_on(unsigned long cpuid, unsigned long entry_point)
135 {
136 	int err;
137 	u32 fn;
138 
139 	fn = psci_function_id[PSCI_FN_CPU_ON];
140 	err = invoke_psci_fn(fn, cpuid, entry_point, 0);
141 	return psci_to_linux_errno(err);
142 }
143 
144 static int psci_migrate(unsigned long cpuid)
145 {
146 	int err;
147 	u32 fn;
148 
149 	fn = psci_function_id[PSCI_FN_MIGRATE];
150 	err = invoke_psci_fn(fn, cpuid, 0, 0);
151 	return psci_to_linux_errno(err);
152 }
153 
154 static const struct of_device_id psci_of_match[] __initconst = {
155 	{ .compatible = "arm,psci",	},
156 	{},
157 };
158 
159 int __init psci_init(void)
160 {
161 	struct device_node *np;
162 	const char *method;
163 	u32 id;
164 	int err = 0;
165 
166 	np = of_find_matching_node(NULL, psci_of_match);
167 	if (!np)
168 		return -ENODEV;
169 
170 	pr_info("probing function IDs from device-tree\n");
171 
172 	if (of_property_read_string(np, "method", &method)) {
173 		pr_warning("missing \"method\" property\n");
174 		err = -ENXIO;
175 		goto out_put_node;
176 	}
177 
178 	if (!strcmp("hvc", method)) {
179 		invoke_psci_fn = __invoke_psci_fn_hvc;
180 	} else if (!strcmp("smc", method)) {
181 		invoke_psci_fn = __invoke_psci_fn_smc;
182 	} else {
183 		pr_warning("invalid \"method\" property: %s\n", method);
184 		err = -EINVAL;
185 		goto out_put_node;
186 	}
187 
188 	if (!of_property_read_u32(np, "cpu_suspend", &id)) {
189 		psci_function_id[PSCI_FN_CPU_SUSPEND] = id;
190 		psci_ops.cpu_suspend = psci_cpu_suspend;
191 	}
192 
193 	if (!of_property_read_u32(np, "cpu_off", &id)) {
194 		psci_function_id[PSCI_FN_CPU_OFF] = id;
195 		psci_ops.cpu_off = psci_cpu_off;
196 	}
197 
198 	if (!of_property_read_u32(np, "cpu_on", &id)) {
199 		psci_function_id[PSCI_FN_CPU_ON] = id;
200 		psci_ops.cpu_on = psci_cpu_on;
201 	}
202 
203 	if (!of_property_read_u32(np, "migrate", &id)) {
204 		psci_function_id[PSCI_FN_MIGRATE] = id;
205 		psci_ops.migrate = psci_migrate;
206 	}
207 
208 out_put_node:
209 	of_node_put(np);
210 	return err;
211 }
212