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