1 /*
2 * Copyright (c) 2013-2022, Arm Limited and Contributors. All rights reserved.
3 * Copyright (c) 2022-2025, Advanced Micro Devices, Inc. All rights reserved.
4 *
5 * SPDX-License-Identifier: BSD-3-Clause
6 */
7
8 #include <assert.h>
9 #include <errno.h>
10
11 #include <arch_helpers.h>
12 #include <common/debug.h>
13 #include <drivers/arm/gicv2.h>
14 #include <lib/mmio.h>
15 #include <lib/psci/psci.h>
16 #include <plat/arm/common/plat_arm.h>
17 #include <plat/common/platform.h>
18
19 #include <plat_private.h>
20 #include "pm_client.h"
21 #include "zynqmp_pm_api_sys.h"
22
23 static uintptr_t zynqmp_sec_entry;
24
zynqmp_cpu_standby(plat_local_state_t cpu_state)25 static void zynqmp_cpu_standby(plat_local_state_t cpu_state)
26 {
27 VERBOSE("%s: cpu_state: 0x%x\n", __func__, cpu_state);
28
29 dsb();
30 wfi();
31 }
32
zynqmp_pwr_domain_on(u_register_t mpidr)33 static int32_t zynqmp_pwr_domain_on(u_register_t mpidr)
34 {
35 int32_t cpu_id = plat_core_pos_by_mpidr(mpidr);
36 const struct pm_proc *proc;
37 uint32_t buff[3];
38 enum pm_ret_status ret;
39 int32_t result = PSCI_E_INTERN_FAIL;
40
41 VERBOSE("%s: mpidr: 0x%lx\n", __func__, mpidr);
42
43 if (cpu_id == -1) {
44 goto exit_label;
45 }
46
47 proc = pm_get_proc(cpu_id);
48 if (proc == NULL) {
49 goto exit_label;
50 }
51
52 /* Check the APU proc status before wakeup */
53 ret = pm_get_node_status(proc->node_id, buff, NON_SECURE);
54 if ((ret != PM_RET_SUCCESS) || (buff[0] == PM_PROC_STATE_SUSPENDING)) {
55 goto exit_label;
56 }
57
58 /* Clear power down request */
59 pm_client_wakeup(proc);
60
61 /* Send request to PMU to wake up selected APU CPU core */
62 (void)pm_req_wakeup(proc->node_id, 1, zynqmp_sec_entry, REQ_ACK_BLOCKING,
63 NON_SECURE);
64
65 result = PSCI_E_SUCCESS;
66
67 exit_label:
68 return result;
69 }
70
zynqmp_pwr_domain_off(const psci_power_state_t * target_state)71 static void zynqmp_pwr_domain_off(const psci_power_state_t *target_state)
72 {
73 uint32_t cpu_id = plat_my_core_pos();
74 const struct pm_proc *proc = pm_get_proc(cpu_id);
75
76 if (proc == NULL) {
77 return;
78 }
79
80 for (size_t i = 0; i <= PLAT_MAX_PWR_LVL; i++) {
81 VERBOSE("%s: target_state->pwr_domain_state[%lu]=%x\n",
82 __func__, i, target_state->pwr_domain_state[i]);
83 }
84
85 /* Prevent interrupts from spuriously waking up this cpu */
86 gicv2_cpuif_disable();
87
88 /*
89 * Send request to PMU to power down the appropriate APU CPU
90 * core.
91 * According to PSCI specification, CPU_off function does not
92 * have resume address and CPU core can only be woken up
93 * invoking CPU_on function, during which resume address will
94 * be set.
95 */
96 (void)pm_self_suspend(proc->node_id, MAX_LATENCY, PM_STATE_CPU_IDLE, 0,
97 NON_SECURE);
98 }
99
zynqmp_pwr_domain_suspend(const psci_power_state_t * target_state)100 static void zynqmp_pwr_domain_suspend(const psci_power_state_t *target_state)
101 {
102 uint32_t state;
103 uint32_t cpu_id = plat_my_core_pos();
104 const struct pm_proc *proc = pm_get_proc(cpu_id);
105
106 if (proc == NULL) {
107 return;
108 }
109
110 for (size_t i = 0; i <= PLAT_MAX_PWR_LVL; i++) {
111 VERBOSE("%s: target_state->pwr_domain_state[%lu]=%x\n",
112 __func__, i, target_state->pwr_domain_state[i]);
113 }
114
115 state = (target_state->pwr_domain_state[1] > PLAT_MAX_RET_STATE) ?
116 PM_STATE_SUSPEND_TO_RAM : PM_STATE_CPU_IDLE;
117
118 /* Send request to PMU to suspend this core */
119 (void)pm_self_suspend(proc->node_id, MAX_LATENCY, state, zynqmp_sec_entry,
120 NON_SECURE);
121
122 /* APU is to be turned off */
123 if (target_state->pwr_domain_state[1] > PLAT_MAX_RET_STATE) {
124 /* disable coherency */
125 plat_arm_interconnect_exit_coherency();
126 }
127 }
128
zynqmp_pwr_domain_on_finish(const psci_power_state_t * target_state)129 static void zynqmp_pwr_domain_on_finish(const psci_power_state_t *target_state)
130 {
131 for (size_t i = 0; i <= PLAT_MAX_PWR_LVL; i++) {
132 VERBOSE("%s: target_state->pwr_domain_state[%lu]=%x\n",
133 __func__, i, target_state->pwr_domain_state[i]);
134 }
135 plat_arm_gic_pcpu_init();
136 gicv2_cpuif_enable();
137 }
138
zynqmp_pwr_domain_suspend_finish(const psci_power_state_t * target_state)139 static void zynqmp_pwr_domain_suspend_finish(const psci_power_state_t *target_state)
140 {
141 uint32_t cpu_id = plat_my_core_pos();
142 const struct pm_proc *proc = pm_get_proc(cpu_id);
143
144 if (proc == NULL) {
145 return;
146 }
147
148 for (size_t i = 0; i <= PLAT_MAX_PWR_LVL; i++) {
149 VERBOSE("%s: target_state->pwr_domain_state[%lu]=%x\n",
150 __func__, i, target_state->pwr_domain_state[i]);
151 }
152
153 /* Clear the APU power control register for this cpu */
154 pm_client_wakeup(proc);
155
156 /* enable coherency */
157 plat_arm_interconnect_enter_coherency();
158 /* APU was turned off */
159 if (target_state->pwr_domain_state[1] > PLAT_MAX_RET_STATE) {
160 plat_arm_gic_init();
161 } else {
162 gicv2_cpuif_enable();
163 gicv2_pcpu_distif_init();
164 }
165 }
166
167 /*******************************************************************************
168 * ZynqMP handlers to shutdown/reboot the system
169 ******************************************************************************/
170
zynqmp_system_off(void)171 static void __dead2 zynqmp_system_off(void)
172 {
173 /* disable coherency */
174 plat_arm_interconnect_exit_coherency();
175
176 /* Send the power down request to the PMU */
177 (void)pm_system_shutdown((uint32_t)PMF_SHUTDOWN_TYPE_SHUTDOWN,
178 pm_get_shutdown_scope(),
179 NON_SECURE);
180
181 while (true) {
182 wfi();
183 }
184 }
185
zynqmp_system_reset(void)186 static void __dead2 zynqmp_system_reset(void)
187 {
188 /* disable coherency */
189 plat_arm_interconnect_exit_coherency();
190
191 /* Send the system reset request to the PMU */
192 (void)pm_system_shutdown((uint32_t)PMF_SHUTDOWN_TYPE_RESET,
193 pm_get_shutdown_scope(),
194 NON_SECURE);
195
196 while (true) {
197 wfi();
198 }
199 }
200
zynqmp_validate_ns_entrypoint(uint64_t ns_entrypoint)201 static int32_t zynqmp_validate_ns_entrypoint(uint64_t ns_entrypoint)
202 {
203 int32_t ret = PSCI_E_SUCCESS;
204
205 if (((ns_entrypoint >= PLAT_DDR_LOWMEM_MAX) && (ns_entrypoint <= PLAT_DDR_HIGHMEM_MAX)) ||
206 ((ns_entrypoint >= BL31_BASE) && (ns_entrypoint <= BL31_LIMIT))) {
207 ret = PSCI_E_INVALID_ADDRESS;
208 }
209
210 return ret;
211 }
212
zynqmp_validate_power_state(uint32_t power_state,psci_power_state_t * req_state)213 static int32_t zynqmp_validate_power_state(uint32_t power_state,
214 psci_power_state_t *req_state)
215 {
216 VERBOSE("%s: power_state: 0x%x\n", __func__, power_state);
217
218 uint32_t pstate = psci_get_pstate_type(power_state);
219 int32_t result = PSCI_E_INVALID_PARAMS;
220
221 assert(req_state);
222
223 /* Sanity check the requested state */
224 if (pstate == PSTATE_TYPE_STANDBY) {
225 req_state->pwr_domain_state[MPIDR_AFFLVL0] = PLAT_MAX_RET_STATE;
226 } else {
227 req_state->pwr_domain_state[MPIDR_AFFLVL0] = PLAT_MAX_OFF_STATE;
228 }
229 /* We expect the 'state id' to be zero */
230 if (psci_get_pstate_id(power_state) == 0U) {
231 result = PSCI_E_SUCCESS;
232 }
233
234 return result;
235 }
236
zynqmp_get_sys_suspend_power_state(psci_power_state_t * req_state)237 static void zynqmp_get_sys_suspend_power_state(psci_power_state_t *req_state)
238 {
239 req_state->pwr_domain_state[PSCI_CPU_PWR_LVL] = PLAT_MAX_OFF_STATE;
240 req_state->pwr_domain_state[1] = PLAT_MAX_OFF_STATE;
241 }
242
243 /*******************************************************************************
244 * Export the platform handlers to enable psci to invoke them
245 ******************************************************************************/
246 static const struct plat_psci_ops zynqmp_psci_ops = {
247 .cpu_standby = zynqmp_cpu_standby,
248 .pwr_domain_on = zynqmp_pwr_domain_on,
249 .pwr_domain_off = zynqmp_pwr_domain_off,
250 .pwr_domain_suspend = zynqmp_pwr_domain_suspend,
251 .pwr_domain_on_finish = zynqmp_pwr_domain_on_finish,
252 .pwr_domain_suspend_finish = zynqmp_pwr_domain_suspend_finish,
253 .system_off = zynqmp_system_off,
254 .system_reset = zynqmp_system_reset,
255 .validate_ns_entrypoint = zynqmp_validate_ns_entrypoint,
256 .validate_power_state = zynqmp_validate_power_state,
257 .get_sys_suspend_power_state = zynqmp_get_sys_suspend_power_state,
258 };
259
260 /*******************************************************************************
261 * Export the platform specific power ops.
262 ******************************************************************************/
plat_setup_psci_ops(uintptr_t sec_entrypoint,const struct plat_psci_ops ** psci_ops)263 int plat_setup_psci_ops(uintptr_t sec_entrypoint,
264 const struct plat_psci_ops **psci_ops)
265 {
266 zynqmp_sec_entry = sec_entrypoint;
267
268 *psci_ops = &zynqmp_psci_ops;
269
270 return 0;
271 }
272