blob: e86b930e00322f525b6c38d2eb31f94781d74013 [file] [log] [blame]
// SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates <open-source-office@arm.com>
// SPDX-License-Identifier: MIT OR Apache-2.0
//! FVP System Peripheral driver implementation.
use bitflags::bitflags;
use safe_mmio::{
field, field_shared,
fields::{ReadPure, ReadPureWrite, WriteOnly},
UniqueMmioPointer,
};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
/// Status of data transfer to motherboard controller.
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
struct SysCfgStat(u32);
bitflags! {
impl SysCfgStat: u32 {
const ERROR = 1 << 1;
const COMPLETE = 1 << 0;
}
}
/// FVP System Peripheral registers based on 'Table 5-17 VE_SysRegs registers'
#[derive(Clone, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
#[repr(C, align(4))]
pub struct FvpSystemRegisters {
/// 0x00 System identity
sys_id: ReadPure<u32>,
/// 0x04 Bits\[7:0\] map to switch S6.
sys_sw: ReadPureWrite<u32>,
/// 0x08 Bits\[7:0\] map to user LEDs.
sys_led: ReadPureWrite<u32>,
/// 0x0c - 0x20
reserved_0c: [u32; 6],
/// 0x24 100Hz counter.
sys_100hz: ReadPure<u32>,
/// 0x28 - 0x2c
reserved_28: [u32; 2],
/// 0x30 General-purpose flags.
sys_flags: ReadPureWrite<u32>,
/// 0x34 Clear bits in general-purpose flags.
sys_flagsclr: WriteOnly<u32>,
/// 0x38 General-purpose non-volatile flags.
sys_nvflags: ReadPureWrite<u32>,
/// 0x3c Clear bits in general-purpose non-volatile flags.
sys_nvflagsclr: WriteOnly<u32>,
/// 0x40 - 0x44
reserved_40: [u32; 2],
/// 0x48 MCI.
sys_mci: ReadPure<u32>,
/// 0x4c Flash control.
sys_flash: ReadPureWrite<u32>,
/// 0x50 - 0x54
reserved_50: [u32; 2],
/// 0x58 Boot-select switch.
sys_cfgsw: ReadPureWrite<u32>,
/// 0x5c 24MHz counter.
sys_24mhz: ReadPure<u32>,
/// 0x60 Miscellaneous control flags.
sys_misc: ReadPureWrite<u32>,
/// 0x64 Read/write DMA peripheral map.
sys_dma: ReadPureWrite<u32>,
/// 0x68 - 0x80
reserved_68: [u32; 7],
/// 0x84 Processor ID.
sys_procid0: ReadPureWrite<u32>,
/// 0x88 Processor ID.
sys_procid1: ReadPureWrite<u32>,
/// 0x8c Processor ID.
sys_procid2: ReadPureWrite<u32>,
/// 0x90 Processor ID.
sys_procid3: ReadPureWrite<u32>,
/// 0x94 - 0x9c
reserved_8c: [u32; 3],
/// 0xa0 Data to read/write from & to motherboard controller.
sys_cfgdata: ReadPureWrite<u32>,
/// 0xa4 Control data transfer to motherboard controller.
sys_cfgctrl: ReadPureWrite<u32>,
/// 0xa8 Status of data transfer to motherboard controller.
sys_cfgstat: ReadPure<SysCfgStat>,
/// 0xac - 0xffc
reserved_ac: [u32; 981],
}
/// FVP System Peripheral error type.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
InvalidBoardRevision,
InvalidHbi,
InvalidVariant,
InvalidPlatformType,
}
/// Board revision.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BoardRevision {
RevA,
RevB,
RevC,
}
impl TryFrom<u32> for BoardRevision {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
0x0 => Self::RevA,
0x1 => Self::RevB,
0x2 => Self::RevC,
_ => return Err(Error::InvalidBoardRevision),
})
}
}
/// HBI platform description.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Hbi {
V8FoundationPlatform,
V8BasePlatform,
}
impl TryFrom<u32> for Hbi {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
0x10 => Self::V8FoundationPlatform,
0x20 => Self::V8BasePlatform,
_ => return Err(Error::InvalidHbi),
})
}
}
/// Platform variant.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Variant {
VariantA = 0x0,
VariantB = 0x1,
VariantC = 0x2,
}
impl TryFrom<u32> for Variant {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
0x0 => Self::VariantA,
0x1 => Self::VariantB,
0x2 => Self::VariantC,
_ => return Err(Error::InvalidVariant),
})
}
}
/// Platform type.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlatformType {
Board,
Model,
Emulator,
Simulator,
Fpga,
}
impl TryFrom<u32> for PlatformType {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
0x0 => Self::Board,
0x1 => Self::Model,
0x2 => Self::Emulator,
0x3 => Self::Simulator,
0x4 => Self::Fpga,
_ => return Err(Error::InvalidPlatformType),
})
}
}
/// System identification of the FVP platform.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SystemId {
pub revision: BoardRevision,
pub hbi: Hbi,
pub variant: Variant,
pub platform_type: PlatformType,
pub fpga_build: u8,
}
impl TryFrom<u32> for SystemId {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(Self {
revision: ((value >> 28) & 0x0f).try_into()?,
hbi: ((value >> 16) & 0x3ff).try_into()?,
variant: ((value >> 12) & 0x0f).try_into()?,
platform_type: ((value >> 8) & 0x0f).try_into()?,
fpga_build: value as u8,
})
}
}
/// System configuration functions
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum SystemConfigFunction {
/// Shutdown system.
Shutdown = 0x08,
/// Reboot system.
Reboot = 0x09,
}
/// FVP System Peripheral Driver
pub struct FvpSystemPeripheral<'a> {
regs: UniqueMmioPointer<'a, FvpSystemRegisters>,
}
impl<'a> FvpSystemPeripheral<'a> {
const SYSCFG_START: u32 = 1 << 31;
const SYSCFG_WRITE: u32 = 1 << 30;
const SYSCFG_FUNC_SHIFT: usize = 20;
/// Create new driver instance.
pub fn new(regs: UniqueMmioPointer<'a, FvpSystemRegisters>) -> Self {
Self { regs }
}
/// Reads system ID.
pub fn system_id(&self) -> Result<SystemId, Error> {
field_shared!(self.regs, sys_id).read().try_into()
}
/// Reads switch state.
pub fn switches(&self) -> u8 {
field_shared!(self.regs, sys_sw).read() as u8
}
/// Sets LED state.
pub fn set_leds(&mut self, leds: u8) {
field!(self.regs, sys_led).write(leds as u32);
}
/// Reads LED state.
pub fn leds(&self) -> u8 {
field_shared!(self.regs, sys_led).read() as u8
}
/// Reads 100Hz counter value.
pub fn counter_100hz(&self) -> u32 {
field_shared!(self.regs, sys_100hz).read()
}
/// Sets flag bits.
pub fn set_flags(&mut self, flags: u32) {
field!(self.regs, sys_flags).write(flags);
}
/// Clears flag bits.
pub fn clear_flags(&mut self, flags: u32) {
field!(self.regs, sys_flagsclr).write(flags);
}
/// Reads flag bits.
pub fn flags(&self) -> u32 {
field_shared!(self.regs, sys_flags).read()
}
/// Sets non-volatile flag bits.
pub fn set_non_volatile_flags(&mut self, flags: u32) {
field!(self.regs, sys_nvflags).write(flags);
}
/// Clears non-volatile flag bits.
pub fn clear_non_volatile_flags(&mut self, flags: u32) {
field!(self.regs, sys_nvflagsclr).write(flags);
}
/// Reads non-volatile flag bits.
pub fn non_volatile_flags(&self) -> u32 {
field_shared!(self.regs, sys_nvflags).read()
}
/// Checks that the MMC card is present.
pub fn mmc_card_present(&self) -> bool {
(field_shared!(self.regs, sys_mci).read() & 0x0000_0001) != 0
}
/// Sets flash control register value.
pub fn set_flash_control(&mut self, value: u32) {
field!(self.regs, sys_flash).write(value);
}
/// Reads flash control register value.
pub fn flash_control(&self) -> u32 {
field_shared!(self.regs, sys_flash).read()
}
/// Read boot select switch.
pub fn boot_select_switch(&self) -> u32 {
field_shared!(self.regs, sys_cfgsw).read()
}
/// Read 24MHz counter.
pub fn counter_24mhz(&self) -> u32 {
field_shared!(self.regs, sys_24mhz).read()
}
/// Miscellaneous control flags.
pub fn misc(&self) -> u32 {
field_shared!(self.regs, sys_misc).read()
}
/// Reads DMA peripheral map.
pub fn dma(&self) -> u32 {
field_shared!(self.regs, sys_dma).read()
}
/// Reads processor ID.
pub fn processor_id(&self) -> [u32; 4] {
[
field_shared!(self.regs, sys_procid0).read(),
field_shared!(self.regs, sys_procid1).read(),
field_shared!(self.regs, sys_procid2).read(),
field_shared!(self.regs, sys_procid3).read(),
]
}
/// Writes system configuration and its optional data.
pub fn write_system_configuration(&mut self, function: SystemConfigFunction) {
field!(self.regs, sys_cfgctrl).write(
Self::SYSCFG_START
| Self::SYSCFG_WRITE
| ((function as u32) << Self::SYSCFG_FUNC_SHIFT),
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use zerocopy::transmute_mut;
#[repr(align(4096))]
pub struct FakeSystemRegisters {
regs: [u32; 1024],
}
impl FakeSystemRegisters {
pub fn new() -> Self {
Self { regs: [0u32; 1024] }
}
pub fn clear(&mut self) {
self.regs.fill(0);
}
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<FvpSystemRegisters> {
UniqueMmioPointer::from(transmute_mut!(&mut self.regs))
}
pub fn system_for_test(&mut self) -> FvpSystemPeripheral {
FvpSystemPeripheral::new(self.get())
}
}
#[test]
fn regs_size() {
assert_eq!(core::mem::size_of::<FvpSystemRegisters>(), 0x1000);
}
#[test]
fn board_revision() {
assert_eq!(Ok(BoardRevision::RevA), BoardRevision::try_from(0x0));
assert_eq!(Ok(BoardRevision::RevB), BoardRevision::try_from(0x1));
assert_eq!(Ok(BoardRevision::RevC), BoardRevision::try_from(0x2));
assert_eq!(
Err(Error::InvalidBoardRevision),
BoardRevision::try_from(0x3)
);
}
#[test]
fn hbi() {
assert_eq!(Ok(Hbi::V8FoundationPlatform), Hbi::try_from(0x10));
assert_eq!(Ok(Hbi::V8BasePlatform), Hbi::try_from(0x20));
assert_eq!(Err(Error::InvalidHbi), Hbi::try_from(0xff));
}
#[test]
fn variant() {
assert_eq!(Ok(Variant::VariantA), Variant::try_from(0x0));
assert_eq!(Ok(Variant::VariantB), Variant::try_from(0x1));
assert_eq!(Ok(Variant::VariantC), Variant::try_from(0x2));
assert_eq!(Err(Error::InvalidVariant), Variant::try_from(0x3));
}
#[test]
fn platform_type() {
assert_eq!(Ok(PlatformType::Board), PlatformType::try_from(0x0));
assert_eq!(Ok(PlatformType::Model), PlatformType::try_from(0x1));
assert_eq!(Ok(PlatformType::Emulator), PlatformType::try_from(0x2));
assert_eq!(Ok(PlatformType::Simulator), PlatformType::try_from(0x3));
assert_eq!(Ok(PlatformType::Fpga), PlatformType::try_from(0x4));
assert_eq!(Err(Error::InvalidPlatformType), PlatformType::try_from(0x5));
}
#[test]
fn system_id() {
assert_eq!(
Ok(SystemId {
revision: BoardRevision::RevC,
hbi: Hbi::V8BasePlatform,
variant: Variant::VariantC,
platform_type: PlatformType::Fpga,
fpga_build: 0x5a
}),
SystemId::try_from(0x2020_245a)
);
assert_eq!(
Err(Error::InvalidBoardRevision),
SystemId::try_from(0xf020_245a)
);
assert_eq!(Err(Error::InvalidHbi), SystemId::try_from(0x20f0_245a));
assert_eq!(Err(Error::InvalidVariant), SystemId::try_from(0x2020_f45a));
assert_eq!(
Err(Error::InvalidPlatformType),
SystemId::try_from(0x2020_2f5a)
);
}
#[test]
fn system_peripheral() {
let mut regs = FakeSystemRegisters::new();
{
// System ID
regs.reg_write(0x00, 0x2020_245a);
let sys = regs.system_for_test();
assert_eq!(
Ok(SystemId {
revision: BoardRevision::RevC,
hbi: Hbi::V8BasePlatform,
variant: Variant::VariantC,
platform_type: PlatformType::Fpga,
fpga_build: 0x5a
}),
sys.system_id()
);
}
regs.clear();
{
// Switches
regs.reg_write(0x04, 0xabcd_ef5a);
let sys = regs.system_for_test();
assert_eq!(0x5a, sys.switches());
}
regs.clear();
{
// LEDs
regs.reg_write(0x08, 0xabcd_ef5a);
let mut sys = regs.system_for_test();
assert_eq!(0x5a, sys.leds());
sys.set_leds(0xa5);
}
assert_eq!(0xa5, regs.reg_read(0x08));
regs.clear();
{
// 100Hz counter
regs.reg_write(0x24, 100);
let sys = regs.system_for_test();
assert_eq!(100, sys.counter_100hz());
}
regs.clear();
{
// Flags
let mut sys = regs.system_for_test();
// The set and read registers are the same, the clear register is a separate one.
sys.set_flags(0x0123_4567);
assert_eq!(0x0123_4567, sys.flags());
sys.clear_flags(0x89ab_cdef);
}
assert_eq!(0x0123_4567, regs.reg_read(0x30));
assert_eq!(0x89ab_cdef, regs.reg_read(0x34));
regs.clear();
{
// Non-volatile flags
let mut sys = regs.system_for_test();
// The set and read registers are the same, the clear register is a separate one.
sys.set_non_volatile_flags(0x0123_4567);
assert_eq!(0x0123_4567, sys.non_volatile_flags());
sys.clear_non_volatile_flags(0x89ab_cdef);
}
assert_eq!(0x0123_4567, regs.reg_read(0x38));
assert_eq!(0x89ab_cdef, regs.reg_read(0x3c));
regs.clear();
{
// MMC card present
regs.reg_write(0x48, 0x0000_0001);
let sys = regs.system_for_test();
assert!(sys.mmc_card_present());
}
regs.clear();
{
// Flash control
let mut sys = regs.system_for_test();
sys.set_flash_control(0x89ab_cdef);
assert_eq!(0x89ab_cdef, sys.flash_control());
}
assert_eq!(0x89ab_cdef, regs.reg_read(0x4c));
regs.clear();
{
// Boot select switch
regs.reg_write(0x58, 0x89ab_cdef);
let sys = regs.system_for_test();
assert_eq!(0x89ab_cdef, sys.boot_select_switch());
}
regs.clear();
{
// 24MHz counter
regs.reg_write(0x5c, 0x89ab_cdef);
let sys = regs.system_for_test();
assert_eq!(0x89ab_cdef, sys.counter_24mhz());
}
regs.clear();
{
// Miscellaneous control flags.
regs.reg_write(0x60, 0x89ab_cdef);
let sys = regs.system_for_test();
assert_eq!(0x89ab_cdef, sys.misc());
}
regs.clear();
{
// DMA peripheral map
regs.reg_write(0x64, 0x89ab_cdef);
let sys = regs.system_for_test();
assert_eq!(0x89ab_cdef, sys.dma());
}
regs.clear();
{
// Processor ID
regs.reg_write(0x84, 0x0123_4567);
regs.reg_write(0x88, 0x89ab_cdef);
regs.reg_write(0x8c, 0x4567_89ab);
regs.reg_write(0x90, 0xcdef_0123);
let sys = regs.system_for_test();
assert_eq!(
[0x0123_4567, 0x89ab_cdef, 0x4567_89ab, 0xcdef_0123],
sys.processor_id()
);
}
regs.clear();
{
// System configuration
let mut sys = regs.system_for_test();
sys.write_system_configuration(SystemConfigFunction::Shutdown);
}
assert_eq!(0xc080_0000, regs.reg_read(0xa4));
}
}