1 /*
2  * Copyright 2020 Carlo Caione <ccaione@baylibre.com>
3  *
4  * Copyright (c) 2023, Intel Corporation.
5  *
6  * SPDX-License-Identifier: Apache-2.0
7  */
8 
9 #define DT_DRV_COMPAT arm_psci_0_2
10 
11 #define LOG_LEVEL CONFIG_PM_CPU_OPS_LOG_LEVEL
12 #include <zephyr/logging/log.h>
13 LOG_MODULE_REGISTER(psci);
14 
15 #include <zephyr/kernel.h>
16 #include <zephyr/arch/cpu.h>
17 
18 #include <zephyr/device.h>
19 #include <zephyr/init.h>
20 
21 #include <zephyr/drivers/pm_cpu_ops.h>
22 #include "pm_cpu_ops_psci.h"
23 
24 #ifdef CONFIG_POWEROFF
25 #include <zephyr/sys/__assert.h>
26 #include <zephyr/sys/poweroff.h>
27 #endif /* CONFIG_POWEROFF */
28 
29 /* PSCI data object. */
30 static struct psci_data_t psci_data;
31 
psci_to_dev_err(int ret)32 static int psci_to_dev_err(int ret)
33 {
34 	switch (ret) {
35 	case PSCI_RET_SUCCESS:
36 		return 0;
37 	case PSCI_RET_NOT_SUPPORTED:
38 		return -ENOTSUP;
39 	case PSCI_RET_INVALID_PARAMS:
40 	case PSCI_RET_INVALID_ADDRESS:
41 		return -EINVAL;
42 	case PSCI_RET_DENIED:
43 		return -EPERM;
44 	}
45 
46 	return -EINVAL;
47 }
48 
pm_cpu_off(void)49 int pm_cpu_off(void)
50 {
51 	int ret;
52 
53 	if (psci_data.conduit == SMCCC_CONDUIT_NONE) {
54 		return -EINVAL;
55 	}
56 
57 	ret = psci_data.invoke_psci_fn(PSCI_0_2_FN_CPU_OFF, 0, 0, 0);
58 
59 	return psci_to_dev_err(ret);
60 }
61 
pm_cpu_on(unsigned long cpuid,uintptr_t entry_point)62 int pm_cpu_on(unsigned long cpuid,
63 	      uintptr_t entry_point)
64 {
65 	int ret;
66 
67 	if (psci_data.conduit == SMCCC_CONDUIT_NONE) {
68 		return -EINVAL;
69 	}
70 
71 	ret = psci_data.invoke_psci_fn(PSCI_FN_NATIVE(0_2, CPU_ON), cpuid,
72 				       (unsigned long) entry_point, 0);
73 
74 	return psci_to_dev_err(ret);
75 }
76 
77 #ifdef CONFIG_POWEROFF
z_sys_poweroff(void)78 void z_sys_poweroff(void)
79 {
80 	int ret;
81 
82 	__ASSERT_NO_MSG(psci_data.conduit != SMCCC_CONDUIT_NONE);
83 
84 	ret = psci_data.invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0);
85 	if (ret < 0) {
86 		printk("System power off failed (%d) - halting\n", ret);
87 	}
88 
89 	for (;;) {
90 		/* wait for power off */
91 	}
92 }
93 #endif /* CONFIG_POWEROFF */
94 
95 /**
96  * This function checks whether the given ID is supported or not, using
97  * PSCI_FEATURES command.PSCI_FEATURES is supported from version 1.0 onwards.
98  */
psci_features_check(unsigned long function_id)99 static int psci_features_check(unsigned long function_id)
100 {
101 	/* PSCI_FEATURES function ID is supported from PSCI 1.0 onwards. */
102 	if (!(PSCI_VERSION_MAJOR(psci_data.ver) >= 1)) {
103 		LOG_ERR("Function ID %lu not supported", function_id);
104 		return -ENOTSUP;
105 	}
106 
107 	return psci_data.invoke_psci_fn(PSCI_FN_NATIVE(1_0, PSCI_FEATURES), function_id, 0, 0);
108 }
109 
pm_system_reset(unsigned char reset_type)110 int pm_system_reset(unsigned char reset_type)
111 {
112 	int ret;
113 
114 	if (psci_data.conduit == SMCCC_CONDUIT_NONE) {
115 		return -EINVAL;
116 	}
117 
118 	if ((reset_type == SYS_WARM_RESET) &&
119 	    (!psci_features_check(PSCI_FN_NATIVE(1_1, SYSTEM_RESET2)))) {
120 		ret = psci_data.invoke_psci_fn(PSCI_FN_NATIVE(1_1, SYSTEM_RESET2), 0, 0, 0);
121 	} else if (reset_type == SYS_COLD_RESET) {
122 		ret = psci_data.invoke_psci_fn(PSCI_FN_NATIVE(0_2, SYSTEM_RESET), 0, 0, 0);
123 	} else {
124 		LOG_ERR("Invalid system reset type issued");
125 		return -EINVAL;
126 	}
127 
128 	return psci_to_dev_err(ret);
129 
130 }
131 
__invoke_psci_fn_hvc(unsigned long function_id,unsigned long arg0,unsigned long arg1,unsigned long arg2)132 static unsigned long __invoke_psci_fn_hvc(unsigned long function_id,
133 					  unsigned long arg0,
134 					  unsigned long arg1,
135 					  unsigned long arg2)
136 {
137 	struct arm_smccc_res res;
138 
139 	arm_smccc_hvc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
140 	return res.a0;
141 }
142 
__invoke_psci_fn_smc(unsigned long function_id,unsigned long arg0,unsigned long arg1,unsigned long arg2)143 static unsigned long __invoke_psci_fn_smc(unsigned long function_id,
144 					  unsigned long arg0,
145 					  unsigned long arg1,
146 					  unsigned long arg2)
147 {
148 	struct arm_smccc_res res;
149 
150 	arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
151 	return res.a0;
152 }
153 
psci_get_version(void)154 static uint32_t psci_get_version(void)
155 {
156 	return psci_data.invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0);
157 }
158 
set_conduit_method(const struct device * dev)159 static int set_conduit_method(const struct device *dev)
160 {
161 	const struct psci_config_t *dev_config = (const struct psci_config_t *)dev->config;
162 
163 	if (!strcmp("hvc", dev_config->method)) {
164 		psci_data.conduit = SMCCC_CONDUIT_HVC;
165 		psci_data.invoke_psci_fn = __invoke_psci_fn_hvc;
166 	} else if (!strcmp("smc", dev_config->method)) {
167 		psci_data.conduit = SMCCC_CONDUIT_SMC;
168 		psci_data.invoke_psci_fn = __invoke_psci_fn_smc;
169 	} else {
170 		LOG_ERR("Invalid conduit method");
171 		return -EINVAL;
172 	}
173 
174 	return 0;
175 }
176 
psci_detect(void)177 static int psci_detect(void)
178 {
179 	uint32_t ver = psci_get_version();
180 
181 	LOG_DBG("Detected PSCIv%d.%d",
182 		PSCI_VERSION_MAJOR(ver),
183 		PSCI_VERSION_MINOR(ver));
184 
185 	if (PSCI_VERSION_MAJOR(ver) == 0 && PSCI_VERSION_MINOR(ver) < 2) {
186 		LOG_ERR("PSCI unsupported version");
187 		return -ENOTSUP;
188 	}
189 
190 	psci_data.ver = ver;
191 
192 	return 0;
193 }
194 
psci_version(void)195 uint32_t psci_version(void)
196 {
197 	return psci_data.ver;
198 }
199 
psci_init(const struct device * dev)200 static int psci_init(const struct device *dev)
201 {
202 	psci_data.conduit = SMCCC_CONDUIT_NONE;
203 
204 	if (set_conduit_method(dev)) {
205 		return -ENOTSUP;
206 	}
207 
208 	return psci_detect();
209 }
210 
211 /**
212  * Each PSCI interface versions have different DT compatible strings like arm,psci-0.2,
213  * arm,psci-1.1 and so on. However, the same driver can be used for all the versions with
214  * the below mentioned DT method where we need to #undef the default version arm,psci-0.2
215  * and #define the required version like arm,psci-1.0 or arm,psci-1.1.
216  */
217 #define PSCI_DEFINE(inst, ver)						\
218 	static const struct psci_config_t psci_config_##inst##ver = {	\
219 		.method = DT_PROP(DT_DRV_INST(inst), method)		\
220 	};								\
221 	DEVICE_DT_INST_DEFINE(inst,					\
222 			&psci_init,					\
223 			NULL,						\
224 			&psci_data,					\
225 			&psci_config_##inst##ver,				\
226 			PRE_KERNEL_1,					\
227 			CONFIG_KERNEL_INIT_PRIORITY_DEVICE,		\
228 			NULL);
229 
230 #define PSCI_0_2_INIT(n) PSCI_DEFINE(n, PSCI_0_2)
231 #undef DT_DRV_COMPAT
232 #define DT_DRV_COMPAT arm_psci_0_2
233 DT_INST_FOREACH_STATUS_OKAY(PSCI_0_2_INIT)
234 
235 #define PSCI_1_1_INIT(n) PSCI_DEFINE(n, PSCI_1_1)
236 #undef DT_DRV_COMPAT
237 #define DT_DRV_COMPAT arm_psci_1_1
238 DT_INST_FOREACH_STATUS_OKAY(PSCI_1_1_INIT)
239