diff --git a/Cargo.toml b/Cargo.toml index a8de1af..da7a669 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ "examples/simple", "examples/embassy", "examples/zedboard", - "zynq-mmu", + "zynq-mmu", "fsbl", ] exclude = ["experiments"] diff --git a/examples/embassy/src/bin/dht22-open-drain-pins.rs b/examples/embassy/src/bin/dht22-open-drain-pins.rs index 0e7711d..02fde27 100644 --- a/examples/embassy/src/bin/dht22-open-drain-pins.rs +++ b/examples/embassy/src/bin/dht22-open-drain-pins.rs @@ -137,7 +137,7 @@ async fn main(_spawner: Spawner) -> ! { info!("Flex Pin 0 state (should be low): {}", flex_pin_0.is_high()); } - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); let mut ticker = Ticker::every(Duration::from_millis(1000)); diff --git a/examples/embassy/src/bin/logger-non-blocking.rs b/examples/embassy/src/bin/logger-non-blocking.rs index 145e66d..fc1afaa 100644 --- a/examples/embassy/src/bin/logger-non-blocking.rs +++ b/examples/embassy/src/bin/logger-non-blocking.rs @@ -75,7 +75,7 @@ async fn main(spawner: Spawner) -> ! { zynq7000_hal::log::rb::init(log::LevelFilter::Trace); - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); let led = Output::new_for_mio(mio_pins.mio7, PinState::Low); diff --git a/examples/embassy/src/bin/pwm.rs b/examples/embassy/src/bin/pwm.rs index 0d2c4bd..d697763 100644 --- a/examples/embassy/src/bin/pwm.rs +++ b/examples/embassy/src/bin/pwm.rs @@ -91,7 +91,7 @@ async fn main(_spawner: Spawner) -> ! { ) }; - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); let mut ticker = Ticker::every(Duration::from_millis(1000)); diff --git a/examples/embassy/src/main.rs b/examples/embassy/src/main.rs index 31fc883..0b244b4 100644 --- a/examples/embassy/src/main.rs +++ b/examples/embassy/src/main.rs @@ -77,7 +77,7 @@ async fn main(_spawner: Spawner) -> ! { ) }; - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); let mut ticker = Ticker::every(Duration::from_millis(1000)); diff --git a/examples/simple/src/bin/logger.rs b/examples/simple/src/bin/logger.rs index 579bcb7..21769fe 100644 --- a/examples/simple/src/bin/logger.rs +++ b/examples/simple/src/bin/logger.rs @@ -79,7 +79,7 @@ pub fn main() -> ! { ) }; - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {boot_mode:?}"); let mut led = Output::new_for_mio(mio_pins.mio7, PinState::Low); diff --git a/examples/simple/src/main.rs b/examples/simple/src/main.rs index dc86303..8bfa308 100644 --- a/examples/simple/src/main.rs +++ b/examples/simple/src/main.rs @@ -64,22 +64,22 @@ pub fn main() -> ! { #[zynq7000_rt::irq] pub fn irq_handler() {} -#[unsafe(no_mangle)] -pub extern "C" fn _abort_handler() { +#[zynq7000_rt::exception(DataAbort)] +fn data_abort_handler(_faulting_addr: usize) -> ! { loop { nop(); } } -#[unsafe(no_mangle)] -pub extern "C" fn _undefined_handler() { +#[zynq7000_rt::exception(Undefined)] +fn undefined_handler(_faulting_addr: usize) -> ! { loop { nop(); } } -#[unsafe(no_mangle)] -pub extern "C" fn _prefetch_handler() { +#[zynq7000_rt::exception(PrefetchAbort)] +fn prefetch_handler(_faulting_addr: usize) -> ! { loop { nop(); } diff --git a/examples/zedboard/src/bin/ethernet.rs b/examples/zedboard/src/bin/ethernet.rs index 6a468c8..ca5e7af 100644 --- a/examples/zedboard/src/bin/ethernet.rs +++ b/examples/zedboard/src/bin/ethernet.rs @@ -250,7 +250,7 @@ async fn main(spawner: Spawner) -> ! { // Safety: We are not multi-threaded yet. unsafe { zynq7000_hal::log::uart_blocking::init_unsafe_single_core(uart, LOG_LEVEL, false) }; - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); static ETH_RX_BUFS: static_cell::ConstStaticCell<[AlignedBuffer; NUM_RX_SLOTS]> = diff --git a/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs b/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs index 1a6ec09..ec2aaba 100644 --- a/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs +++ b/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs @@ -93,7 +93,7 @@ async fn main(_spawner: Spawner) -> ! { ) }; - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); let pin_sel = match I2C_ADDR_SEL { diff --git a/examples/zedboard/src/bin/l3gd20h-spi-mio.rs b/examples/zedboard/src/bin/l3gd20h-spi-mio.rs index d7b38b9..f122929 100644 --- a/examples/zedboard/src/bin/l3gd20h-spi-mio.rs +++ b/examples/zedboard/src/bin/l3gd20h-spi-mio.rs @@ -96,7 +96,7 @@ async fn main(spawner: Spawner) -> ! { .unwrap(); zynq7000_hal::log::rb::init(log::LevelFilter::Trace); - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); if DEBUG_SPI_CLK_CONFIG { diff --git a/examples/zedboard/src/bin/uart-blocking.rs b/examples/zedboard/src/bin/uart-blocking.rs index 617cfae..6e36f90 100644 --- a/examples/zedboard/src/bin/uart-blocking.rs +++ b/examples/zedboard/src/bin/uart-blocking.rs @@ -160,7 +160,7 @@ async fn main(_spawner: Spawner) -> ! { ) }; - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); let mut ticker = Ticker::every(Duration::from_millis(1000)); diff --git a/examples/zedboard/src/bin/uart-non-blocking.rs b/examples/zedboard/src/bin/uart-non-blocking.rs index a2b712e..8ae0e83 100644 --- a/examples/zedboard/src/bin/uart-non-blocking.rs +++ b/examples/zedboard/src/bin/uart-non-blocking.rs @@ -254,7 +254,7 @@ async fn main(spawner: Spawner) -> ! { ) }; - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); let mio_led = Output::new_for_mio(gpio_pins.mio.mio7, PinState::Low); diff --git a/examples/zedboard/src/main.rs b/examples/zedboard/src/main.rs index b6aaa33..e5df38f 100644 --- a/examples/zedboard/src/main.rs +++ b/examples/zedboard/src/main.rs @@ -79,7 +79,7 @@ async fn main(_spawner: Spawner) -> ! { ) }; - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); let mut ticker = Ticker::every(Duration::from_millis(200)); diff --git a/fsbl/Cargo.toml b/fsbl/Cargo.toml new file mode 100644 index 0000000..966ef5b --- /dev/null +++ b/fsbl/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "fsbl" +version = "0.1.0" +authors = ["Robin Mueller "] +edition = "2024" +description = "Rust First Stage Bootloader for the Zynq7000 SoC" +homepage = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs" +repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs" +license = "MIT OR Apache-2.0" + +[dependencies] +cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2", features = ["critical-section-single-core"] } +zynq7000-rt = { path = "../zynq7000-rt" } +zynq7000 = { path = "../zynq7000" } +zynq7000-hal = { path = "../zynq7000-hal" } +embedded-io = "0.6" +embedded-hal = "1" +fugit = "0.3" +log = "0.4" + +[profile.release] +codegen-units = 1 +debug = true +lto = true diff --git a/fsbl/memory.x b/fsbl/memory.x new file mode 100644 index 0000000..226f8a1 --- /dev/null +++ b/fsbl/memory.x @@ -0,0 +1,24 @@ +MEMORY +{ + /* The Zynq7000 has 192 kB of OCM memory which can be used for the FSBL */ + CODE(rx) : ORIGIN = 0x00000000, LENGTH = 192K + OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K + /* Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This can + be used for something like DMA descriptors, but the DDR needs to be set up first in addition + to configuring the page at address 0x400_0000 accordingly */ + UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M +} + +REGION_ALIAS("DATA", CODE); + +SECTIONS +{ + /* Uncached memory */ + .uncached (NOLOAD) : ALIGN(4) { + . = ALIGN(4); + _sbss_uncached = .; + *(.uncached .uncached.*); + . = ALIGN(4); + _ebss_uncached = .; + } > UNCACHED +} diff --git a/fsbl/src/main.rs b/fsbl/src/main.rs new file mode 100644 index 0000000..2c75d89 --- /dev/null +++ b/fsbl/src/main.rs @@ -0,0 +1,77 @@ +//! Simple FSBL. +#![no_std] +#![no_main] + +use core::panic::PanicInfo; +use cortex_ar::asm::nop; +use log::error; +use zynq7000_hal::{ + BootMode, + clocks::pll::{ + PllConfig, PllConfigCtorError, configure_arm_pll, configure_ddr_pll, configure_io_pll, + }, + gpio::{Output, PinState, mio}, + time::Hertz, +}; +use zynq7000_rt as _; + +// PS clock input frequency. +const PS_CLK: Hertz = Hertz::from_raw(33_333_333); + +/// 1600 MHz. +const ARM_CLK: Hertz = Hertz::from_raw(1_600_000_000); +/// 1067 MHz. +const DDR_CLK: Hertz = Hertz::from_raw(1_067_000_000); +/// 1000 MHz. +const IO_CLK: Hertz = Hertz::from_raw(1_000_000_000); + +/// Entry point (not called like a normal main function) +#[unsafe(no_mangle)] +pub extern "C" fn boot_core(cpu_id: u32) -> ! { + if cpu_id != 0 { + panic!("unexpected CPU ID {}", cpu_id); + } + main(); +} + +#[unsafe(export_name = "main")] +pub fn main() -> ! { + let boot_mode = BootMode::new_from_regs(); + // The unwraps are okay here, the provided clock frequencies are standard values also used + // by other Xilinx tools. + configure_arm_pll(boot_mode, PllConfig::new_from_target_clock(PS_CLK, ARM_CLK).unwrap()); + configure_io_pll(boot_mode, PllConfig::new_from_target_clock(PS_CLK, IO_CLK).unwrap()); + configure_ddr_pll(boot_mode, PllConfig::new_from_target_clock(PS_CLK, DDR_CLK).unwrap()); + + loop { + cortex_ar::asm::nop(); + } +} + +#[zynq7000_rt::exception(DataAbort)] +fn data_abort_handler(_faulting_addr: usize) -> ! { + loop { + nop(); + } +} + +#[zynq7000_rt::exception(Undefined)] +fn undefined_handler(_faulting_addr: usize) -> ! { + loop { + nop(); + } +} + +#[zynq7000_rt::exception(PrefetchAbort)] +fn prefetch_handler(_faulting_addr: usize) -> ! { + loop { + nop(); + } +} + +/// Panic handler +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + error!("Panic: {info:?}"); + loop {} +} diff --git a/memory.x b/memory.x index 134a689..11faa59 100644 --- a/memory.x +++ b/memory.x @@ -1,8 +1,7 @@ - MEMORY { /* Zedboard: 512 MB DDR3. Only use 63 MB for now, should be plenty for a bare-metal app. - Leave 1 MB of memory which will be configured as uncached device memory by the MPU. This is + Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This is recommended for something like DMA descriptors. */ CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M diff --git a/zynq7000-hal/src/clocks.rs b/zynq7000-hal/src/clocks/mod.rs similarity index 98% rename from zynq7000-hal/src/clocks.rs rename to zynq7000-hal/src/clocks/mod.rs index 3a6feb2..2691344 100644 --- a/zynq7000-hal/src/clocks.rs +++ b/zynq7000-hal/src/clocks/mod.rs @@ -1,6 +1,8 @@ //! Clock module. use arbitrary_int::Number; +pub mod pll; + use zynq7000::slcr::{ ClockControl, clocks::{ @@ -201,9 +203,9 @@ impl Clocks { pub fn new_from_regs(ps_clk_freq: Hertz) -> Result { let mut clk_regs = unsafe { ClockControl::new_mmio_fixed() }; - let arm_pll_cfg = clk_regs.read_arm_pll(); - let io_pll_cfg = clk_regs.read_io_pll(); - let ddr_pll_cfg = clk_regs.read_ddr_pll(); + let arm_pll_cfg = clk_regs.read_arm_pll_ctrl(); + let io_pll_cfg = clk_regs.read_io_pll_ctrl(); + let ddr_pll_cfg = clk_regs.read_ddr_pll_ctrl(); if arm_pll_cfg.fdiv().as_u32() == 0 || io_pll_cfg.fdiv().as_u32() == 0 diff --git a/zynq7000-hal/src/clocks/pll.rs b/zynq7000-hal/src/clocks/pll.rs new file mode 100644 index 0000000..8949a37 --- /dev/null +++ b/zynq7000-hal/src/clocks/pll.rs @@ -0,0 +1,356 @@ +use core::sync::atomic::AtomicBool; + +use arbitrary_int::{u4, u7, u10}; + +use crate::{BootMode, time::Hertz}; + +/// Minimal value based on Zynq-7000 TRM Table 25-6, p.744 +pub const PLL_MUL_MIN: u32 = 13; +/// Maximum value based on Zynq-7000 TRM Table 25-6, p.744 +pub const PLL_MUL_MAX: u32 = 66; + +static ARM_PLL_INIT: AtomicBool = AtomicBool::new(false); +static IO_PLL_INIT: AtomicBool = AtomicBool::new(false); +static DDR_PLL_INIT: AtomicBool = AtomicBool::new(false); + +#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] +#[error("pll muliplier value {0} is out of range ({PLL_MUL_MIN}..={PLL_MUL_MAX})")] +pub struct MulOutOfRangeError(pub u32); + +#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] +pub enum PllConfigCtorError { + #[error("invalid input")] + InvalidInput, + #[error("pll multiplier out of range: {0}")] + MulOutOfRange(#[from] MulOutOfRangeError), +} + +pub struct PllConfig { + fdiv: u7, + charge_pump: u4, + loop_resistor: u4, + lock_count: u10, +} + +impl PllConfig { + pub fn new_from_target_clock( + ps_clk: Hertz, + target_clk: Hertz, + ) -> Result { + if ps_clk.raw() == 0 { + return Err(PllConfigCtorError::InvalidInput); + } + let mul = target_clk / ps_clk; + Self::new(mul).map_err(PllConfigCtorError::MulOutOfRange) + } + /// Create a new PLL configuration based on the multiplier value. + /// + /// These configuration values are based on the Zynq-7000 TRM Table 25-6, p.744. + pub fn new(pll_mul: u32) -> Result { + if !(PLL_MUL_MIN..=PLL_MUL_MAX).contains(&pll_mul) { + return Err(MulOutOfRangeError(pll_mul)); + } + + Ok(match pll_mul { + 13 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(6), + u10::new(750), + ), + 14 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(6), + u10::new(700), + ), + 15 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(6), + u10::new(650), + ), + 16 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(10), + u10::new(625), + ), + 17 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(10), + u10::new(575), + ), + 18 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(10), + u10::new(550), + ), + 19 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(10), + u10::new(525), + ), + 20 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(12), + u10::new(500), + ), + 21 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(12), + u10::new(475), + ), + 22 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(12), + u10::new(450), + ), + 23 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(12), + u10::new(425), + ), + 24..=25 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(12), + u10::new(400), + ), + 26 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(12), + u10::new(375), + ), + 27..=28 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(12), + u10::new(350), + ), + + 29..=30 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(12), + u10::new(325), + ), + 31..=33 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(2), + u10::new(300), + ), + 34..=36 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(2), + u10::new(275), + ), + 37..=40 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(2), + u10::new(250), + ), + 41..=47 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(3), + u4::new(12), + u10::new(250), + ), + 48..=66 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(4), + u10::new(250), + ), + _ => { + unreachable!() + } + }) + } + + /// Create a new PLL configuration with raw values. + /// + /// It is recommended to use [Self::new] instead, which creates a configuration + /// based on a look-up table provided in the Zynq-7000 TRM. + pub fn new_raw(fdiv: u7, charge_pump: u4, loop_resistor: u4, lock_count: u10) -> Self { + Self { + fdiv, + charge_pump, + loop_resistor, + lock_count, + } + } +} + +/// This function configures the ARM PLL based on the provided [PllConfig]. +pub fn configure_arm_pll(boot_mode: BootMode, pll_config: PllConfig) { + if ARM_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) { + return; + } + // Safety: This will only run at most once because of the atomic boolean check. + unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) }; +} + +/// This function configures the IO PLL based on the provided [PllConfig]. +pub fn configure_io_pll(boot_mode: BootMode, pll_config: PllConfig) { + if IO_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) { + return; + } + // Safety: This will only run at most once because of the atomic boolean check. + unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) }; +} + +/// This function configures the DDR PLL based on the provided [PllConfig]. +pub fn configure_ddr_pll(boot_mode: BootMode, pll_config: PllConfig) { + if DDR_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) { + return; + } + // Safety: This will only run at most once because of the atomic boolean check. + unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) }; +} + +/// This function configures the ARM PLL basejjon the provided [PllConfig]. +/// +/// # Safety +/// +/// This function should only be called once during system initialization, for example in the +/// first-stage bootloader (FSBL). +pub unsafe fn configure_arm_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) { + unsafe { + crate::slcr::Slcr::with(|slcr| { + let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_arm_pll_ctrl(); + let pll_cfg_reg = slcr.clk_ctrl().pointer_to_arm_pll_cfg(); + configure_pll_unchecked( + boot_mode, + pll_config, + PllType::Arm, + slcr, + pll_ctrl_reg, + pll_cfg_reg, + ); + }); + } +} + +/// This function configures the IO PLL based on the provided [PllConfig]. +/// +/// # Safety +/// +/// This function should only be called once during system initialization, for example in the +/// first-stage bootloader (FSBL). +pub unsafe fn configure_io_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) { + unsafe { + crate::slcr::Slcr::with(|slcr| { + let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_io_pll_ctrl(); + let pll_cfg_reg = slcr.clk_ctrl().pointer_to_io_pll_cfg(); + configure_pll_unchecked( + boot_mode, + pll_config, + PllType::Io, + slcr, + pll_ctrl_reg, + pll_cfg_reg, + ); + }); + } +} + +/// This function configures the DDR PLL based on the provided [PllConfig]. +/// +/// # Safety +/// +/// This function should only be called once during system initialization, for example in the +/// first-stage bootloader (FSBL). +pub unsafe fn configure_ddr_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) { + unsafe { + crate::slcr::Slcr::with(|slcr| { + let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_ddr_pll_ctrl(); + let pll_cfg_reg = slcr.clk_ctrl().pointer_to_ddr_pll_cfg(); + configure_pll_unchecked( + boot_mode, + pll_config, + PllType::Ddr, + slcr, + pll_ctrl_reg, + pll_cfg_reg, + ); + }); + } +} + +enum PllType { + Io, + Ddr, + Arm, +} + +impl PllType { + pub const fn bit_offset_pll_locked(&self) -> usize { + match self { + PllType::Io => 2, + PllType::Ddr => 1, + PllType::Arm => 0, + } + } +} + +unsafe fn configure_pll_unchecked( + boot_mode: BootMode, + cfg: PllConfig, + pll_type: PllType, + slcr: &mut zynq7000::slcr::MmioSlcr<'static>, + pll_ctrl_reg: *mut zynq7000::slcr::clocks::PllCtrl, + pll_cfg_reg: *mut zynq7000::slcr::clocks::PllCfg, +) { + // Step 1: Program the multiplier and other PLL configuration parameters. + // New values will only be consumed once the PLL is reset. + let mut pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) }; + pll_ctrl.set_fdiv(cfg.fdiv); + unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) }; + + let mut pll_cfg = unsafe { core::ptr::read_volatile(pll_cfg_reg) }; + pll_cfg.set_charge_pump(cfg.charge_pump); + pll_cfg.set_loop_resistor(cfg.loop_resistor); + pll_cfg.set_lock_count(cfg.lock_count); + unsafe { core::ptr::write_volatile(pll_cfg_reg, pll_cfg) }; + + // Step 2: Force the PLL into bypass mode. If the PLL bypass mode pin is tied high, + // the PLLs need to be enabled. According to the TRM, this is done by setting the + // PLL_BYPASS_QUAL bit to 0, which de-asserts the reset to the Arm PLL. + pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) }; + if boot_mode.pll_config() == zynq7000::slcr::BootPllConfig::Bypassed { + pll_ctrl.set_bypass_qual(false); + } + pll_ctrl.set_bypass_force(true); + pll_ctrl.set_pwrdwn(false); + unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) }; + + // Step 3: Reset the PLL. This also loads the new configuration. + pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) }; + pll_ctrl.set_reset(true); + unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) }; + pll_ctrl.set_reset(false); + unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) }; + + while ((slcr.clk_ctrl().read_pll_status().raw_value() >> pll_type.bit_offset_pll_locked()) + & 0b1) + != 1 + { + cortex_ar::asm::nop(); + } + + pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) }; + pll_ctrl.set_bypass_force(false); + unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) }; +} diff --git a/zynq7000-hal/src/lib.rs b/zynq7000-hal/src/lib.rs index 7d0f45b..3c15a3c 100644 --- a/zynq7000-hal/src/lib.rs +++ b/zynq7000-hal/src/lib.rs @@ -10,7 +10,7 @@ #![no_std] use slcr::Slcr; -use zynq7000::slcr::LevelShifterReg; +use zynq7000::slcr::{BootModeRegister, BootPllConfig, LevelShifterReg}; pub mod cache; pub mod clocks; @@ -40,36 +40,26 @@ pub enum BootDevice { } #[derive(Debug, Copy, Clone)] -pub enum BootPllConfig { - Enabled, - Bypassed, -} - -#[derive(Debug)] pub struct BootMode { boot_mode: Option, pll_config: BootPllConfig, } impl BootMode { - #[allow(clippy::new_without_default)] /// Create a new boot mode information structure by reading the boot mode register from the /// fixed SLCR block. - pub fn new() -> Self { + pub fn new_from_regs() -> Self { // Safety: Only read a read-only register here. - Self::new_with_raw_reg( - unsafe { zynq7000::slcr::Slcr::new_mmio_fixed() } - .read_boot_mode() - .raw_value(), - ) + Self::new_with_reg(unsafe { zynq7000::slcr::Slcr::new_mmio_fixed() }.read_boot_mode()) } - fn new_with_raw_reg(raw_register: u32) -> Self { - let msb_three_bits = (raw_register >> 1) & 0b111; + fn new_with_reg(boot_mode_reg: BootModeRegister) -> Self { + let boot_dev = boot_mode_reg.boot_mode(); + let msb_three_bits = (boot_dev.value() >> 1) & 0b111; let boot_mode = match msb_three_bits { 0b000 => { - if raw_register & 0b1 == 0 { + if boot_dev.value() & 0b1 == 0 { Some(BootDevice::JtagCascaded) } else { Some(BootDevice::JtagIndependent) @@ -81,21 +71,17 @@ impl BootMode { 0b110 => Some(BootDevice::SdCard), _ => None, }; - let pll_config = if (raw_register >> 4) & 0b1 == 0 { - BootPllConfig::Enabled - } else { - BootPllConfig::Bypassed - }; Self { boot_mode, - pll_config, + pll_config: boot_mode_reg.pll_config(), } } - pub fn boot_device(&self) -> Option { + + pub const fn boot_device(&self) -> Option { self.boot_mode } - pub const fn pll_enable(&self) -> BootPllConfig { + pub const fn pll_config(&self) -> BootPllConfig { self.pll_config } } diff --git a/zynq7000/src/slcr/clocks.rs b/zynq7000/src/slcr/clocks.rs index bc1e8cb..0429eb1 100644 --- a/zynq7000/src/slcr/clocks.rs +++ b/zynq7000/src/slcr/clocks.rs @@ -4,18 +4,12 @@ use super::{CLOCK_CONTROL_OFFSET, SLCR_BASE_ADDR}; use arbitrary_int::{u4, u6, u7, u10}; -#[bitbybit::bitenum(u1, exhaustive = true)] -#[derive(Debug)] -pub enum BypassForce { - EnabledOrSetByBootMode = 0b0, - Bypassed = 0b1, -} - -#[bitbybit::bitenum(u1, exhaustive = true)] -#[derive(Debug)] -pub enum BypassQual { - BypassForceBit = 0b0, - BootModeFourthBit = 0b1, +pub enum Bypass { + NotBypassed = 0b00, + /// This is the default reset value. + PinStrapSettings = 0b01, + Bypassed = 0b10, + BypassedRegardlessOfPinStrapping = 0b11, } #[bitbybit::bitfield(u32)] @@ -29,10 +23,10 @@ pub struct PllCtrl { fdiv: u7, /// Select source for the ARM PLL bypass control #[bit(4, rw)] - bypass_force: BypassForce, + bypass_force: bool, /// Select source for the ARM PLL bypass control #[bit(3, rw)] - bypass_qual: BypassQual, + bypass_qual: bool, // Power-down control #[bit(1, rw)] pwrdwn: bool, @@ -41,6 +35,40 @@ pub struct PllCtrl { reset: bool, } +impl PllCtrl { + #[inline] + pub fn set_bypass(&mut self, bypass: Bypass) { + match bypass { + Bypass::NotBypassed => { + self.set_bypass_force(false); + self.set_bypass_qual(false); + } + Bypass::PinStrapSettings => { + self.set_bypass_force(false); + self.set_bypass_qual(true); + } + Bypass::Bypassed => { + self.set_bypass_force(true); + self.set_bypass_qual(false); + } + Bypass::BypassedRegardlessOfPinStrapping => { + self.set_bypass_force(true); + self.set_bypass_qual(true); + } + } + } + + #[inline] + pub fn bypass(&self) -> Bypass { + match (self.bypass_force(), self.bypass_qual()) { + (false, false) => Bypass::NotBypassed, + (false, true) => Bypass::PinStrapSettings, + (true, false) => Bypass::Bypassed, + (true, true) => Bypass::BypassedRegardlessOfPinStrapping, + } + } +} + #[bitbybit::bitfield(u32)] #[derive(Debug)] pub struct PllCfg { @@ -48,26 +76,26 @@ pub struct PllCfg { lock_count: u10, /// Charge Pump control #[bits(8..=11, rw)] - pll_cp: u4, + charge_pump: u4, /// Loop resistor control #[bits(4..=7, rw)] - pll_res: u4, + loop_resistor: u4, } #[bitbybit::bitfield(u32)] #[derive(Debug)] pub struct PllStatus { - #[bit(5)] + #[bit(5, r)] io_pll_stable: bool, - #[bit(4)] + #[bit(4, r)] ddr_pll_stable: bool, - #[bit(3)] + #[bit(3, r)] arm_pll_stable: bool, - #[bit(2)] + #[bit(2, r)] io_pll_lock: bool, - #[bit(1)] + #[bit(1, r)] drr_pll_lock: bool, - #[bit(0)] + #[bit(0, r)] arm_pll_lock: bool, } @@ -341,9 +369,9 @@ pub struct AperClkCtrl { #[derive(derive_mmio::Mmio)] #[repr(C)] pub struct ClockControl { - arm_pll: PllCtrl, - ddr_pll: PllCtrl, - io_pll: PllCtrl, + arm_pll_ctrl: PllCtrl, + ddr_pll_ctrl: PllCtrl, + io_pll_ctrl: PllCtrl, pll_status: PllStatus, arm_pll_cfg: PllCfg, ddr_pll_cfg: PllCfg, diff --git a/zynq7000/src/slcr/mod.rs b/zynq7000/src/slcr/mod.rs index 4c72b09..66e0338 100644 --- a/zynq7000/src/slcr/mod.rs +++ b/zynq7000/src/slcr/mod.rs @@ -93,11 +93,19 @@ impl GpiobRegisters { } } +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum BootPllConfig { + Enabled = 0, + /// Disabled and bypassed. + Bypassed = 1, +} + #[bitbybit::bitfield(u32)] #[derive(Debug)] pub struct BootModeRegister { #[bit(4, r)] - pll_bypass: bool, + pll_config: BootPllConfig, #[bits(0..=3, r)] boot_mode: u4, }