Add FVP Base Power Controller driver
Change-Id: Ie66850ae966366708321573802efc679f2b149c2
Signed-off-by: Tomás González <tomasagustin.gonzalezorlando@arm.com>
diff --git a/Cargo.lock b/Cargo.lock
index 8f49c08..b3983aa 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9,8 +9,10 @@
"arm-gic",
"arm-pl011-uart",
"arm-sp805",
+ "bitflags",
"safe-mmio",
"spin",
+ "zerocopy",
]
[[package]]
@@ -101,9 +103,9 @@
[[package]]
name = "safe-mmio"
-version = "0.2.0"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3bc59a975d8faa0b4475145266e9f9eac01f0b3f393d0e5eecc8b88646ca2d29"
+checksum = "f96d86dc25e8f742cf117e00b58db99e0c3505208babc33f124f0067f6c22024"
dependencies = [
"zerocopy",
]
@@ -153,18 +155,18 @@
[[package]]
name = "zerocopy"
-version = "0.8.23"
+version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
+checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.23"
+version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
+checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
dependencies = [
"proc-macro2",
"quote",
diff --git a/Cargo.toml b/Cargo.toml
index 20eb5c9..4132e4b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,3 +20,5 @@
arm-sp805 = { git = "https://git.trustedfirmware.org/rust-spmc/arm-sp805.git" }
safe-mmio = "0.2"
spin = { version = "0.9", default-features = false, features = ["spin_mutex"] }
+bitflags = "2.9"
+zerocopy = { version = "0.8", features = ["derive"] }
diff --git a/src/lib.rs b/src/lib.rs
index 185ab21..bb954de 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -12,11 +12,13 @@
pub use arm_pl011_uart;
pub use arm_sp805;
pub use safe_mmio::{PhysicalInstance, UniqueMmioPointer};
+pub mod power_controller;
use arm_gic::GICDRegisters;
use arm_pl011_uart::PL011Registers;
use arm_sp805::SP805Registers;
use core::fmt::Debug;
+use power_controller::FvpPowerControllerRegisters;
use spin::mutex::Mutex;
static PERIPHERALS_TAKEN: Mutex<bool> = Mutex::new(false);
@@ -30,6 +32,7 @@
pub uart3: PhysicalInstance<PL011Registers>,
pub watchdog: PhysicalInstance<SP805Registers>,
pub gicd: PhysicalInstance<GICDRegisters>,
+ pub power_controller: PhysicalInstance<FvpPowerControllerRegisters>,
}
impl Peripherals {
@@ -57,6 +60,7 @@
uart2: PhysicalInstance::new(0x1c0b_0000),
uart3: PhysicalInstance::new(0x1c0c_0000),
watchdog: PhysicalInstance::new(0x1c0f_0000),
+ power_controller: PhysicalInstance::new(0x1c10_0000),
gicd: PhysicalInstance::new(0x2f00_0000),
}
}
diff --git a/src/power_controller.rs b/src/power_controller.rs
new file mode 100644
index 0000000..4ca07dc
--- /dev/null
+++ b/src/power_controller.rs
@@ -0,0 +1,313 @@
+// SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates <open-source-office@arm.com>
+// SPDX-License-Identifier: MIT OR Apache-2.0
+
+use bitflags::bitflags;
+use safe_mmio::UniqueMmioPointer;
+use safe_mmio::{field, fields::ReadPureWrite};
+use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
+
+// Register descriptions
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum PowerOnReason {
+ /// Cold power-on.
+ ColdPowerOn,
+ /// System reset pin.
+ SystemResetPin,
+ /// Wake by PPONR.
+ WakeByProcessorOn,
+ /// Wake by GIC WakeRequest signal.
+ WakeByGicSignal,
+}
+
+impl PowerOnReason {
+ const SHIFT: u32 = 24;
+ const MASK: u32 = 0b11;
+ const COLD_PWR_ON: u32 = 0b00;
+ const SYS_RESET_PIN: u32 = 0b01;
+ const BY_PPONR: u32 = 0b10;
+ const BY_GIC_WAKE_REQ_SIGNAL: u32 = 0b11;
+}
+
+impl From<u32> for PowerOnReason {
+ fn from(value: u32) -> Self {
+ let masked_shifted_value = (value >> Self::SHIFT) & Self::MASK;
+
+ match masked_shifted_value {
+ Self::COLD_PWR_ON => PowerOnReason::ColdPowerOn,
+ Self::SYS_RESET_PIN => PowerOnReason::SystemResetPin,
+ Self::BY_PPONR => PowerOnReason::WakeByProcessorOn,
+ Self::BY_GIC_WAKE_REQ_SIGNAL => PowerOnReason::WakeByGicSignal,
+ _ => unreachable!(),
+ }
+ }
+}
+
+bitflags! {
+ /// Power Control Wakeup Register
+ struct WakeupRegister: u32 {
+ /// If set, enables wakeup interrupts (return from SUSPEND) for this cluster.
+ const WEN = 1 << 31;
+ }
+
+ /// Power Control SYS Status Register
+ pub struct SysStatusRegister: u32 {
+ /// A value of 1 indicates that affinity level 2 is active/on. If affinity level 2 is not
+ /// implemented this bit is RAZ.
+ const L2 = 1 << 31;
+ /// A value of 1 indicates that affinity level 1 is active/on. If affinity level 1 is not
+ /// implemented this bit is RAZ.
+ const L1 = 1 << 30;
+ /// A value of 1 indicates that affinity level 0 is active/on.
+ const L0 = 1 << 29;
+ /// A value of 1 indicates wakeup interrupts, return from SUSPEND, enabled for this
+ /// processor. This is an alias of PWKUPR.WEN for this core.
+ const WEN = 1 << 28;
+ /// A value of 1 indicates pending cluster off, the cluster enters low-power mode the next
+ /// time it raises signal STANDBYWFIL2.
+ const PC = 1 << 27;
+ /// A value of 1 indicates pending processor off, the processor enters low-power mode the
+ /// next time it raises signal STANDBYWFI.
+ const PP = 1 << 26;
+ }
+}
+
+/// FVP Power Controller register map
+#[derive(Clone, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
+#[repr(C, align(4))]
+pub struct FvpPowerControllerRegisters {
+ /// 0x00 - Power Control Processor Off Register
+ ppoffr: ReadPureWrite<u32>,
+ /// 0x04 - Power Control Processor On Register
+ pponr: ReadPureWrite<u32>,
+ /// 0x08 - Power Control Cluster Off Register
+ pcoffr: ReadPureWrite<u32>,
+ /// 0x0C - Power Control Wakeup Register
+ pwkupr: ReadPureWrite<u32>,
+ /// 0x10 - Power Control SYS Status Register
+ psysr: ReadPureWrite<u32>,
+}
+
+/// FVP Power Controller implementation
+pub struct FvpPowerController<'a> {
+ regs: UniqueMmioPointer<'a, FvpPowerControllerRegisters>,
+}
+
+impl<'a> FvpPowerController<'a> {
+ const MPIDR_MASK: u32 = 0xff_ffff;
+
+ /// Creates new FVP Power Controller instance.
+ pub fn new(regs: UniqueMmioPointer<'a, FvpPowerControllerRegisters>) -> Self {
+ Self { regs }
+ }
+
+ // Provides the value of the PSYS register as a u32 for internal use only.
+ fn system_status_reg(&mut self, mpidr: u32) -> u32 {
+ field!(self.regs, psysr).write(mpidr & Self::MPIDR_MASK);
+ field!(self.regs, psysr).read()
+ }
+
+ /// Provides information on the powered status of a given core.
+ ///
+ /// This is done by writing the ID for the required core to the PSYS register and then reading
+ /// the value along with the associated status.
+ /// Please see `power_on_reason` for other related information.
+ pub fn system_status(&mut self, mpidr: u32) -> SysStatusRegister {
+ // There are no usage constraints
+ SysStatusRegister::from_bits_truncate(self.system_status_reg(mpidr))
+ }
+
+ /// Brings up the given processor from low-power mode by writing to the PPONR register
+ ///
+ /// Processor must make power-on requests only for other powered-off processors in the system,
+ /// otherwise get a Programming error.
+ pub fn power_on_processor(&mut self, mpidr: u32) {
+ field!(self.regs, pponr).write(mpidr & Self::MPIDR_MASK);
+ }
+
+ /// Processor SUSPEND command (by writing to the PPOFFR register)
+ ///
+ /// when PWKUPR and the GIC are programmed appropriately to provide wakeup events from IRQ and
+ /// FIQ events to that processor.
+ /// Processor must make power-off requests only for itself, otherwise get a Programming error.
+ pub fn power_off_processor(&mut self, mpidr: u32) {
+ field!(self.regs, ppoffr).write(mpidr & Self::MPIDR_MASK);
+ }
+
+ /// Turns the cluster off
+ ///
+ /// Cluster must make power-off requests only for itself, otherwise get a Programming error.
+ pub fn power_off_cluster(&mut self, mpidr: u32) {
+ field!(self.regs, pcoffr).write(mpidr & Self::MPIDR_MASK);
+ }
+
+ /// Configures whether wakeup requests from the GIC are enabled for this cluster
+ pub fn disable_wakeup_requests(&mut self, mpidr: u32) {
+ // There are no usage constraints
+ let wkup_reg = WakeupRegister::empty();
+
+ field!(self.regs, pwkupr).write((mpidr & Self::MPIDR_MASK) | wkup_reg.bits());
+ }
+
+ /// Configures whether wakeup requests from the GIC are enabled for this cluster
+ pub fn enable_wakeup_requests(&mut self, mpidr: u32) {
+ // There are no usage constraints
+ let wkup_reg = WakeupRegister::empty().union(WakeupRegister::WEN);
+
+ field!(self.regs, pwkupr).write((mpidr & Self::MPIDR_MASK) | wkup_reg.bits());
+ }
+
+ /// Provides information on the reason for Power On of the given core.
+ ///
+ /// This is done by writing the ID for the required core to the PSYS register and reading the
+ /// value along with the associated status.
+ /// Please see `system_status` for other related information.
+ pub fn power_on_reason(&mut self, mpidr: u32) -> PowerOnReason {
+ PowerOnReason::from(self.system_status_reg(mpidr))
+ }
+}
+
+// SAFETY: An `&FvpPowerController` only allows operations which read registers, which can safely be
+// done from multiple threads simultaneously.
+unsafe impl Sync for FvpPowerController<'_> {}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use zerocopy::transmute_mut;
+
+ pub struct FakeFvpPowerControllerRegisters {
+ regs: [u32; 5],
+ }
+
+ impl FakeFvpPowerControllerRegisters {
+ pub fn new() -> Self {
+ Self { regs: [0u32; 5] }
+ }
+
+ #[allow(unused)]
+ pub fn reg_write(&mut self, offset: usize, value: u32) {
+ self.regs[offset / 4] = value;
+ }
+
+ pub fn reg_read(&self, offset: usize) -> u32 {
+ self.regs[offset / 4]
+ }
+
+ fn get(&mut self) -> UniqueMmioPointer<FvpPowerControllerRegisters> {
+ UniqueMmioPointer::from(transmute_mut!(&mut self.regs))
+ }
+
+ pub fn fvp_power_controller_for_test(&mut self) -> FvpPowerController {
+ FvpPowerController::new(self.get())
+ }
+ }
+
+ #[test]
+ fn regs_size() {
+ assert_eq!(core::mem::size_of::<FvpPowerControllerRegisters>(), 0x14);
+ }
+
+ #[test]
+ fn sys_status() {
+ let mut regs = FakeFvpPowerControllerRegisters::new();
+ let fake_mpidr = 111234;
+ let mut fvp_power_controller = regs.fvp_power_controller_for_test();
+
+ let sys_status = fvp_power_controller.system_status(fake_mpidr);
+
+ assert!(!sys_status.contains(SysStatusRegister::L2));
+ assert!(!sys_status.contains(SysStatusRegister::L1));
+ assert!(!sys_status.contains(SysStatusRegister::L0));
+ assert!(!sys_status.contains(SysStatusRegister::WEN));
+ assert!(!sys_status.contains(SysStatusRegister::PC));
+ assert!(!sys_status.contains(SysStatusRegister::PP));
+ }
+
+ #[test]
+ fn pwkupr() {
+ let mut regs = FakeFvpPowerControllerRegisters::new();
+ let fake_mpidr = 865032;
+ let wen_flag = 1 << 31;
+ {
+ let mut fvp_power_controller = regs.fvp_power_controller_for_test();
+ fvp_power_controller.enable_wakeup_requests(fake_mpidr);
+
+ assert_eq!(regs.reg_read(0x0C), fake_mpidr | wen_flag);
+ }
+
+ {
+ let mut fvp_power_controller = regs.fvp_power_controller_for_test();
+ fvp_power_controller.disable_wakeup_requests(fake_mpidr);
+
+ assert_eq!(regs.reg_read(0x0C), fake_mpidr);
+ }
+ }
+
+ #[test]
+ fn pponr() {
+ let mut regs = FakeFvpPowerControllerRegisters::new();
+ let fake_mpidr = 865032;
+ let mut fvp_power_controller = regs.fvp_power_controller_for_test();
+ fvp_power_controller.power_off_processor(fake_mpidr);
+ assert_eq!(regs.reg_read(0x00), fake_mpidr);
+ }
+
+ #[test]
+ fn ppoffr() {
+ let mut regs = FakeFvpPowerControllerRegisters::new();
+ let fake_mpidr = 865032;
+ let mut fvp_power_controller = regs.fvp_power_controller_for_test();
+ fvp_power_controller.power_on_processor(fake_mpidr);
+ assert_eq!(regs.reg_read(0x04), fake_mpidr);
+ }
+
+ #[test]
+ fn pcoffr() {
+ let mut regs = FakeFvpPowerControllerRegisters::new();
+ let fake_mpidr = 0b1010_0010_1010_0010_1010_0010;
+ let mut fvp_power_controller = regs.fvp_power_controller_for_test();
+ fvp_power_controller.power_off_cluster(fake_mpidr);
+ assert_eq!(regs.reg_read(0x08), fake_mpidr);
+ }
+
+ #[test]
+ fn power_on_reason() {
+ let mut regs = FakeFvpPowerControllerRegisters::new();
+ let fake_mpidr = 0b1010_0010_1010_0010_1010_0010;
+ let mut fvp_power_controller = regs.fvp_power_controller_for_test();
+ assert_eq!(
+ fvp_power_controller.power_on_reason(fake_mpidr),
+ PowerOnReason::ColdPowerOn
+ );
+ }
+
+ #[test]
+ fn power_on_reason_enum() {
+ // Power on Reason bits: 00 -> Cold power-on.
+ let sysr_cold_pwr_on = 0b1011_1100_1010_0010_1010_0010_1010_0010;
+ // Power on Reason bits: 11 -> Wake by GIC WakeRequest signal.
+ let sysr_gic_wake_req_sig = !sysr_cold_pwr_on;
+ // Power on Reason bits: 01 -> System reset pin.
+ let sysr_system_reset_pin = 0b0110_1001_1010_0110_1111_0000_1010_0010;
+ // Power on Reason bits: 10 -> Wake by PPONR.
+ let sysr_by_pponr = !sysr_system_reset_pin;
+
+ assert_eq!(
+ PowerOnReason::from(sysr_cold_pwr_on),
+ PowerOnReason::ColdPowerOn
+ );
+ assert_eq!(
+ PowerOnReason::from(sysr_gic_wake_req_sig),
+ PowerOnReason::WakeByGicSignal
+ );
+ assert_eq!(
+ PowerOnReason::from(sysr_system_reset_pin),
+ PowerOnReason::SystemResetPin
+ );
+ assert_eq!(
+ PowerOnReason::from(sysr_by_pponr),
+ PowerOnReason::WakeByProcessorOn
+ );
+ }
+}