1 /*
2 * Copyright (c) 2020, ARM Limited and Contributors. All rights reserved.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7 #include <assert.h>
8
9 #include <lib/psci/psci.h>
10 #include <plat/arm/common/plat_arm.h>
11 #include <plat/common/platform.h>
12
13 #include "fpga_private.h"
14 #include <platform_def.h>
15
16 /*
17 * This is a basic PSCI implementation that allows secondary CPUs to be
18 * released from their initial state and continue to the warm boot entrypoint.
19 *
20 * The secondary CPUs are placed in a holding pen and released by calls
21 * to fpga_pwr_domain_on(mpidr), which updates the hold entry for the CPU
22 * specified by the mpidr argument - the (polling) target CPU will then branch
23 * to the BL31 warm boot sequence at the entrypoint address.
24 *
25 * Additionally, the secondary CPUs are kept in a low-power wfe() state
26 * (placed there at the end of each poll) and woken when necessary through
27 * calls to sev() in fpga_pwr_domain_on(mpidr), once the hold state for the
28 * relevant CPU has been updated.
29 *
30 * Hotplug is currently implemented using a wfi-loop, which removes the
31 * dependencies on any power controllers or other mechanism that is specific
32 * to the running system as specified by the FPGA image.
33 */
34
35 uint64_t hold_base[PLATFORM_CORE_COUNT];
36 uintptr_t fpga_sec_entrypoint;
37
38 /*
39 * Calls to the CPU specified by the mpidr will set its hold entry to a value
40 * indicating that it should stop polling and branch off to the warm entrypoint.
41 */
fpga_pwr_domain_on(u_register_t mpidr)42 static int fpga_pwr_domain_on(u_register_t mpidr)
43 {
44 int pos = plat_core_pos_by_mpidr(mpidr);
45 unsigned long current_mpidr = read_mpidr_el1();
46
47 if (pos < 0) {
48 panic();
49 }
50
51 if (mpidr == current_mpidr) {
52 return PSCI_E_ALREADY_ON;
53 }
54 hold_base[pos] = PLAT_FPGA_HOLD_STATE_GO;
55 flush_dcache_range((uintptr_t)&hold_base[pos], sizeof(uint64_t));
56 sev(); /* Wake any CPUs from wfe */
57
58 return PSCI_E_SUCCESS;
59 }
60
fpga_pwr_domain_on_finish(const psci_power_state_t * target_state)61 void fpga_pwr_domain_on_finish(const psci_power_state_t *target_state)
62 {
63 fpga_pwr_gic_on_finish();
64 }
65
fpga_pwr_domain_off(const psci_power_state_t * target_state)66 static void fpga_pwr_domain_off(const psci_power_state_t *target_state)
67 {
68 fpga_pwr_gic_off();
69
70 while (1) {
71 wfi();
72 }
73 }
74
fpga_cpu_standby(plat_local_state_t cpu_state)75 static void fpga_cpu_standby(plat_local_state_t cpu_state)
76 {
77 /*
78 * Enter standby state
79 * dsb is good practice before using wfi to enter low power states
80 */
81 u_register_t scr = read_scr_el3();
82 write_scr_el3(scr|SCR_IRQ_BIT);
83 dsb();
84 wfi();
85 write_scr_el3(scr);
86 }
87
88 plat_psci_ops_t plat_fpga_psci_pm_ops = {
89 .pwr_domain_on = fpga_pwr_domain_on,
90 .pwr_domain_on_finish = fpga_pwr_domain_on_finish,
91 .pwr_domain_off = fpga_pwr_domain_off,
92 .cpu_standby = fpga_cpu_standby
93 };
94
plat_setup_psci_ops(uintptr_t sec_entrypoint,const plat_psci_ops_t ** psci_ops)95 int plat_setup_psci_ops(uintptr_t sec_entrypoint,
96 const plat_psci_ops_t **psci_ops)
97 {
98 fpga_sec_entrypoint = sec_entrypoint;
99 flush_dcache_range((uint64_t)&fpga_sec_entrypoint,
100 sizeof(fpga_sec_entrypoint));
101 *psci_ops = &plat_fpga_psci_pm_ops;
102 return 0;
103 }
104