Skip to content

Commit a32c9e2

Browse files
author
Nicolas Pitre
committed
drivers: pm_cpu_ops: Add ARM FVP CPU power management driver
Add a new PM CPU ops driver for ARM Fixed Virtual Platform (FVP) that enables bare metal SMP support without ARM Trusted Firmware (ATF). The driver provides CPU power-on and system reset operations by directly interfacing with FVP's power controller (PWRC) and V2M system registers. The implementation includes RVBAR_EL3 configuration to redirect secondary CPU reset vectors to Zephyr's image header, enabling proper SMP initialization without firmware assistance. Signed-off-by: Nicolas Pitre <[email protected]>
1 parent 05b77ec commit a32c9e2

File tree

4 files changed

+188
-0
lines changed

4 files changed

+188
-0
lines changed

boards/arm/fvp_base_revc_2xaemv8a/board.cmake

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,12 @@ if(CONFIG_BUILD_WITH_TFA)
4343
set(FVP_FLASH_FILE ${TFA_BINARY_DIR}/fvp/release/fip.bin)
4444
endif()
4545

46+
elseif(CONFIG_PM_CPU_OPS_FVP)
47+
# Configure RVBAR_EL3 for bare metal SMP when using FVP PM CPU ops driver
48+
set(ARMFVP_FLAGS ${ARMFVP_FLAGS}
49+
# Set RVBAR_EL3 for secondary CPUs to Zephyr image base address
50+
-C cluster0.cpu1.RVBAR=${CONFIG_KERNEL_VM_BASE}
51+
-C cluster0.cpu2.RVBAR=${CONFIG_KERNEL_VM_BASE}
52+
-C cluster0.cpu3.RVBAR=${CONFIG_KERNEL_VM_BASE}
53+
)
4654
endif()

drivers/pm_cpu_ops/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ zephyr_library_sources_ifdef(CONFIG_PM_CPU_OPS pm_cpu_ops_weak_impl.c)
66

77
zephyr_library_sources_ifdef(CONFIG_PM_CPU_OPS_PSCI pm_cpu_ops_psci.c)
88

9+
zephyr_library_sources_ifdef(CONFIG_PM_CPU_OPS_FVP pm_cpu_ops_fvp.c)
10+
911
zephyr_library_sources_ifdef(CONFIG_PSCI_SHELL psci_shell.c)

drivers/pm_cpu_ops/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ config PM_CPU_OPS_PSCI
3030
0022A ("Power State Coordination Interface System Software on
3131
ARM processors").
3232

33+
config PM_CPU_OPS_FVP
34+
bool "Support for ARM FVP CPU power management"
35+
depends on BOARD_FVP_BASE_REVC_2XAEMV8A && !PM_CPU_OPS_PSCI
36+
default y if SMP
37+
select PM_CPU_OPS_HAS_DRIVER
38+
help
39+
Say Y here if you want Zephyr to support CPU power management
40+
operations on ARM Fixed Virtual Platform (FVP) without PSCI.
41+
This driver directly controls the FVP power controller and
42+
V2M system registers for CPU power-on and system reset operations.
43+
3344
config PSCI_SHELL
3445
bool "Support for PSCI interface shell commands"
3546
depends on SHELL && PM_CPU_OPS_PSCI
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* Copyright (c) 2025 BayLibre SAS
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#define LOG_LEVEL CONFIG_PM_CPU_OPS_LOG_LEVEL
8+
#include <zephyr/logging/log.h>
9+
LOG_MODULE_REGISTER(fvp_pm_cpu_ops);
10+
11+
#include <zephyr/kernel.h>
12+
#include <zephyr/drivers/pm_cpu_ops.h>
13+
#include <zephyr/sys/barrier.h>
14+
#include <zephyr/sys/util.h>
15+
16+
/* FVP Platform Constants */
17+
#define FVP_PWRC_BASE 0x1c100000UL /* Power controller base */
18+
#define FVP_V2M_SYSREGS_BASE 0x1c010000UL /* V2M system registers */
19+
20+
/* FVP Power Controller Register Offsets */
21+
#define PWRC_PPONR_OFF 0x4 /* Power-on Request */
22+
#define PWRC_PSYSR_OFF 0x10 /* System Status Register */
23+
24+
/* PSYSR Register Bits */
25+
#define PSYSR_AFF_L0 BIT(29) /* Affinity Level 0 */
26+
27+
/* V2M System Registers */
28+
#define V2M_SYS_CFGCTRL_OFF 0xa4 /* System Configuration Control Register */
29+
30+
/* V2M Configuration Control Register bits */
31+
#define V2M_CFGCTRL_START BIT(31) /* Start operation */
32+
#define V2M_CFGCTRL_RW BIT(30) /* Read/Write operation */
33+
#define V2M_CFGCTRL_FUNC_MASK GENMASK(27, 20) /* Function field */
34+
#define V2M_CFGCTRL_FUNC(fn) FIELD_PREP(V2M_CFGCTRL_FUNC_MASK, (fn))
35+
36+
/* V2M System Configuration Functions */
37+
#define V2M_FUNC_REBOOT 0x09 /* System reboot */
38+
39+
/*
40+
* Memory mapping strategy:
41+
*
42+
* To conserve memory (especially page tables) in Zephyr, we use temporary
43+
* mappings for hardware register access. Each operation maps the required
44+
* registers, performs the operation, then unmaps them immediately.
45+
*
46+
* CPU power operations are infrequent and not performance-critical. Memory
47+
* conservation is therefore more important than runtime optimizations.
48+
*/
49+
#define FVP_REGISTER_MAP_SIZE 0x1000 /* 4KB pages for register mapping */
50+
51+
static inline void fvp_pwrc_write_pponr(uintptr_t pwrc_vaddr, unsigned long mpidr)
52+
{
53+
sys_write32((uint32_t)mpidr, pwrc_vaddr + PWRC_PPONR_OFF);
54+
LOG_DBG("FVP: PPONR write: MPIDR=0x%lx", mpidr);
55+
}
56+
57+
static inline uint32_t fvp_pwrc_read_psysr(uintptr_t pwrc_vaddr, unsigned long mpidr)
58+
{
59+
/* Write MPIDR to PSYSR to select which CPU to query */
60+
sys_write32((uint32_t)mpidr, pwrc_vaddr + PWRC_PSYSR_OFF);
61+
62+
/* Read the status for the selected CPU */
63+
return sys_read32(pwrc_vaddr + PWRC_PSYSR_OFF);
64+
}
65+
66+
67+
static int fvp_cpu_power_on(unsigned long target_mpidr, uintptr_t entry_point)
68+
{
69+
uint8_t *pwrc_vaddr_ptr;
70+
uintptr_t pwrc_vaddr;
71+
uint32_t psysr;
72+
int timeout = 10000; /* 1 second timeout */
73+
74+
LOG_DBG("FVP: Powering on CPU MPIDR=0x%lx, entry=0x%lx",
75+
target_mpidr, entry_point);
76+
77+
/* Map power controller registers once for the entire operation */
78+
k_mem_map_phys_bare(&pwrc_vaddr_ptr, FVP_PWRC_BASE, FVP_REGISTER_MAP_SIZE,
79+
K_MEM_PERM_RW | K_MEM_CACHE_NONE);
80+
pwrc_vaddr = (uintptr_t)pwrc_vaddr_ptr;
81+
82+
/*
83+
* Wait for any pending power-off to complete.
84+
* The target CPU must be in OFF state before we can power it on.
85+
*/
86+
do {
87+
psysr = fvp_pwrc_read_psysr(pwrc_vaddr, target_mpidr);
88+
if (timeout-- <= 0) {
89+
LOG_ERR("FVP: Timeout waiting for CPU 0x%lx power-off "
90+
"to complete, PSYSR=0x%x", target_mpidr, psysr);
91+
k_mem_unmap_phys_bare(pwrc_vaddr_ptr, FVP_REGISTER_MAP_SIZE);
92+
return -ETIMEDOUT;
93+
}
94+
k_busy_wait(100);
95+
} while ((psysr & PSYSR_AFF_L0) != 0);
96+
97+
LOG_DBG("FVP: CPU 0x%lx is powered off (PSYSR=0x%x)", target_mpidr, psysr);
98+
99+
/* Power on the target CPU via FVP power controller */
100+
fvp_pwrc_write_pponr(pwrc_vaddr, target_mpidr);
101+
102+
/* Unmap power controller registers */
103+
k_mem_unmap_phys_bare(pwrc_vaddr_ptr, FVP_REGISTER_MAP_SIZE);
104+
105+
/* Ensure power-on request completes */
106+
barrier_dsync_fence_full();
107+
108+
/* Send event to wake up the target CPU from WFE loop */
109+
__asm__ volatile("sev" ::: "memory");
110+
111+
LOG_DBG("FVP: Power-on request completed for CPU 0x%lx", target_mpidr);
112+
return 0;
113+
}
114+
115+
int pm_cpu_on(unsigned long mpidr, uintptr_t entry_point)
116+
{
117+
return fvp_cpu_power_on(mpidr, entry_point);
118+
}
119+
120+
int pm_cpu_off(void)
121+
{
122+
/*
123+
* This is incompatible with our register mapping strategy.
124+
* A CPU that shuts itself down might not complete the register
125+
* unmapping before losing power.
126+
*/
127+
return -ENOTSUP;
128+
}
129+
130+
static inline void fvp_v2m_sys_cfgwrite(uint32_t function)
131+
{
132+
uint8_t *v2m_vaddr_ptr;
133+
uintptr_t v2m_vaddr;
134+
uint32_t val = V2M_CFGCTRL_START | V2M_CFGCTRL_RW | V2M_CFGCTRL_FUNC(function);
135+
136+
/* Temporarily map V2M system registers */
137+
k_mem_map_phys_bare(&v2m_vaddr_ptr, FVP_V2M_SYSREGS_BASE, FVP_REGISTER_MAP_SIZE,
138+
K_MEM_PERM_RW | K_MEM_CACHE_NONE);
139+
v2m_vaddr = (uintptr_t)v2m_vaddr_ptr;
140+
141+
sys_write32(val, v2m_vaddr + V2M_SYS_CFGCTRL_OFF);
142+
143+
LOG_DBG("FVP: V2M SYS_CFGCTRL write: 0x%x (func=0x%x)", val, function);
144+
145+
/* Unmap V2M registers */
146+
k_mem_unmap_phys_bare(v2m_vaddr_ptr, FVP_REGISTER_MAP_SIZE);
147+
}
148+
149+
int pm_system_reset(unsigned char reset_type)
150+
{
151+
LOG_DBG("FVP: System reset requested (type=%u)", reset_type);
152+
153+
/*
154+
* FVP supports system reset via the V2M System Configuration Controller.
155+
* Both warm and cold reset use the same mechanism - the V2M reboot function.
156+
*/
157+
fvp_v2m_sys_cfgwrite(V2M_FUNC_REBOOT);
158+
159+
/*
160+
* The reset should happen immediately, but in case it doesn't work,
161+
* we'll wait a bit and return an error.
162+
*/
163+
k_busy_wait(1000000); /* Wait 1 second */
164+
165+
LOG_ERR("FVP: System reset failed - system did not reset");
166+
return -ETIMEDOUT;
167+
}

0 commit comments

Comments
 (0)