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