diff --git a/Cargo.toml b/Cargo.toml index a8de1af..305fe5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "examples/embassy", "examples/zedboard", "zynq-mmu", + "zedboard-fsbl", "zedboard-bsp", ] exclude = ["experiments"] @@ -21,3 +22,7 @@ incremental = false lto = true opt-level = 3 # <- overflow-checks = false # <- + +[profile.small] +inherits = "release" +opt-level = "z" diff --git a/boot-image-staging/.gitignore b/boot-image-staging/.gitignore new file mode 100644 index 0000000..8ef9722 --- /dev/null +++ b/boot-image-staging/.gitignore @@ -0,0 +1,4 @@ +/fsbl.elf +/fpga.bit +/application.elf +/boot.bin diff --git a/boot-image-staging/README.md b/boot-image-staging/README.md new file mode 100644 index 0000000..9f6e028 --- /dev/null +++ b/boot-image-staging/README.md @@ -0,0 +1,24 @@ +Boot Image Creation Staging +======== + +This folder provides the basic files required to create bare metal boot images. + +This includes a simple `boot.bif` file which can be used by the AMD +[bootgen](https://docs.amd.com/r/en-US/ug1283-bootgen-user-guide) utility +to create a boot binary consisting of + +- A first-stage bootloader (FSBL) +- A FPGA bitstream which can be loaded to the Zynq PL by the FSBL. +- A primary application which runs in DDR memory and is also copied to DDR by the FSBL. + +## Example for the Zedboard + +An example use-case for the Zedboard would be a `boot.bin` containing the `zedboard-fsbl` Rust +FSBL or the Xilinx FSBL, a bitstream `zedboard-rust.bit` generated from the `zedboard-fpga-design` +project or your own Vivado project and finally any primary application of your choice which +should run in DDR. + +You can copy the files into the staging area, using the file names `fsbl.elf`, `fpga.bit` and +`application.elf`, which are also ignored by the VCS. + +Then, you can simply run `bootgen` to generate the boot binary. diff --git a/boot-image-staging/boot.bif b/boot-image-staging/boot.bif new file mode 100644 index 0000000..6d03ebf --- /dev/null +++ b/boot-image-staging/boot.bif @@ -0,0 +1,6 @@ +all: +{ + [bootloader] fsbl.elf + fpga.bit + [load=0x100000] application.elf +} diff --git a/examples/embassy/build.rs b/examples/embassy/build.rs new file mode 100644 index 0000000..d534cc3 --- /dev/null +++ b/examples/embassy/build.rs @@ -0,0 +1,31 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/memory.x b/examples/embassy/memory.x similarity index 94% rename from memory.x rename to examples/embassy/memory.x index 134a689..11faa59 100644 --- a/memory.x +++ b/examples/embassy/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/examples/embassy/src/bin/dht22-open-drain-pins.rs b/examples/embassy/src/bin/dht22-open-drain-pins.rs index 8f4b023..34ed594 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 bbdff1b..5f5169d 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 62a80f1..6f1a9a1 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 c943ca5..19bdfda 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/build.rs b/examples/simple/build.rs new file mode 100644 index 0000000..d534cc3 --- /dev/null +++ b/examples/simple/build.rs @@ -0,0 +1,31 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/examples/simple/memory.x b/examples/simple/memory.x new file mode 100644 index 0000000..11faa59 --- /dev/null +++ b/examples/simple/memory.x @@ -0,0 +1,22 @@ +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 MMU. This is + recommended for something like DMA descriptors. */ + CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M + 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/examples/simple/src/bin/gtc-ticks.rs b/examples/simple/src/bin/gtc-ticks.rs index 20fb40f..f591805 100644 --- a/examples/simple/src/bin/gtc-ticks.rs +++ b/examples/simple/src/bin/gtc-ticks.rs @@ -21,7 +21,7 @@ use zynq7000_hal::{ use zynq7000_rt as _; // Define the clock frequency as a constant -const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300); +const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_333); static MS_TICKS: AtomicU64 = AtomicU64::new(0); diff --git a/examples/simple/src/bin/logger.rs b/examples/simple/src/bin/logger.rs index 2a359dc..6ec71a3 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..4512126 100644 --- a/examples/simple/src/main.rs +++ b/examples/simple/src/main.rs @@ -4,25 +4,33 @@ use core::panic::PanicInfo; use cortex_ar::asm::nop; -use embedded_hal::digital::StatefulOutputPin; +use embedded_hal::{delay::DelayNs, digital::StatefulOutputPin}; use zynq7000::PsPeripherals; use zynq7000_hal::{ + clocks::Clocks, gpio::{Output, PinState, mio}, l2_cache, + priv_tim::CpuPrivateTimer, + time::Hertz, }; use zynq7000_rt as _; +pub const LIB: Lib = Lib::Hal; + /// One user LED is MIO7 const ZEDBOARD_LED_MASK: u32 = 1 << 7; +// Define the clock frequency as a constant. +// +// Not required for the PAC mode, is required for clean delays in HAL mode. +const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_333); + #[derive(Debug)] pub enum Lib { Pac, Hal, } -const LIB: Lib = Lib::Hal; - /// Entry point (not called like a normal main function) #[unsafe(no_mangle)] pub extern "C" fn boot_core(cpu_id: u32) -> ! { @@ -49,13 +57,14 @@ pub fn main() -> ! { } Lib::Hal => { let dp = PsPeripherals::take().unwrap(); + let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap(); + // Unwrap okay, we only call this once on core 0 here. + let mut cpu_tim = CpuPrivateTimer::take(clocks.arm_clocks()).unwrap(); let mio_pins = mio::Pins::new(dp.gpio); let mut led = Output::new_for_mio(mio_pins.mio7, PinState::High); loop { led.toggle().unwrap(); - for _ in 0..5_000_000 { - nop(); - } + cpu_tim.delay_ms(1000); } } } @@ -64,22 +73,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/Cargo.toml b/examples/zedboard/Cargo.toml index 796b26b..94d878f 100644 --- a/examples/zedboard/Cargo.toml +++ b/examples/zedboard/Cargo.toml @@ -16,6 +16,8 @@ zynq7000-rt = { path = "../../zynq7000-rt" } zynq7000 = { path = "../../zynq7000" } zynq7000-hal = { path = "../../zynq7000-hal" } zynq7000-embassy = { path = "../../zynq7000-embassy" } +zedboard-bsp = { path = "../../zedboard-bsp" } +num_enum = { version = "0.7", default-features = false } l3gd20 = { git = "https://github.com/us-irs/l3gd20.git", branch = "add-async-if" } embedded-io = "0.6" bitbybit = "1.3" diff --git a/examples/zedboard/build.rs b/examples/zedboard/build.rs new file mode 100644 index 0000000..d534cc3 --- /dev/null +++ b/examples/zedboard/build.rs @@ -0,0 +1,31 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/examples/zedboard/memory.x b/examples/zedboard/memory.x new file mode 100644 index 0000000..11faa59 --- /dev/null +++ b/examples/zedboard/memory.x @@ -0,0 +1,22 @@ +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 MMU. This is + recommended for something like DMA descriptors. */ + CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M + 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/examples/zedboard/src/bin/ethernet.rs b/examples/zedboard/src/bin/ethernet.rs index 84da268..4e2a27e 100644 --- a/examples/zedboard/src/bin/ethernet.rs +++ b/examples/zedboard/src/bin/ethernet.rs @@ -33,10 +33,8 @@ use embedded_io::Write; use embedded_io_async::Write as _; use log::{LevelFilter, debug, error, info, warn}; use rand::{RngCore, SeedableRng}; -use zedboard::{ - PS_CLOCK_FREQUENCY, - phy_marvell::{LatchingLinkStatus, MARVELL_88E1518_OUI}, -}; +use zedboard::PS_CLOCK_FREQUENCY; +use zedboard_bsp::phy_marvell; use zynq7000_hal::{ BootMode, clocks::Clocks, @@ -72,8 +70,8 @@ const INIT_STRING: &str = "-- Zynq 7000 Zedboard Ethernet Example --\n\r"; // Unicast address with OUI of the Marvell 88E1518 PHY. const MAC_ADDRESS: [u8; 6] = [ 0x00, - ((MARVELL_88E1518_OUI >> 8) & 0xff) as u8, - (MARVELL_88E1518_OUI & 0xff) as u8, + ((phy_marvell::MARVELL_88E1518_OUI >> 8) & 0xff) as u8, + (phy_marvell::MARVELL_88E1518_OUI & 0xff) as u8, 0x00, 0x00, 0x01, @@ -249,7 +247,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]> = @@ -317,9 +315,8 @@ async fn main(spawner: Spawner) -> ! { eth.set_rx_buf_descriptor_base_address(rx_descr_ref.base_addr()); eth.set_tx_buf_descriptor_base_address(tx_descr_ref.base_addr()); eth.start(); - let (mut phy, phy_rev) = - zedboard::phy_marvell::Marvell88E1518Phy::new_autoprobe_addr(eth.mdio_mut()) - .expect("could not auto-detect phy"); + let (mut phy, phy_rev) = phy_marvell::Marvell88E1518Phy::new_autoprobe_addr(eth.mdio_mut()) + .expect("could not auto-detect phy"); info!( "Detected Marvell 88E1518 PHY with revision number: {:?}", phy_rev @@ -456,7 +453,8 @@ async fn main(spawner: Spawner) -> ! { IpMode::StackReady => { let status = phy.read_copper_status(); // Periodically check for link changes. - if status.copper_link_status() == LatchingLinkStatus::DownSinceLastRead { + if status.copper_link_status() == phy_marvell::LatchingLinkStatus::DownSinceLastRead + { warn!("ethernet link is down."); ip_mode = IpMode::LinkDown; continue; diff --git a/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs b/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs index 1b665d1..20afae3 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 a0e4ceb..ceee881 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/qspi.rs b/examples/zedboard/src/bin/qspi.rs new file mode 100644 index 0000000..9dff48c --- /dev/null +++ b/examples/zedboard/src/bin/qspi.rs @@ -0,0 +1,233 @@ +#![no_std] +#![no_main] + +use core::panic::PanicInfo; +use cortex_ar::asm::nop; +use embassy_executor::Spawner; +use embassy_time::{Duration, Ticker}; +use embedded_hal::digital::StatefulOutputPin; +use embedded_io::Write; +use log::{error, info}; +use zedboard::PS_CLOCK_FREQUENCY; +use zedboard_bsp::qspi_spansion; +use zynq7000_hal::{ + BootMode, + clocks::Clocks, + configure_level_shifter, + gic::{GicConfigurator, GicInterruptHelper, Interrupt}, + gpio::{GpioPins, Output, PinState}, + gtc::GlobalTimerCounter, + l2_cache, + prelude::*, + qspi, + uart::{ClockConfigRaw, Uart, UartConfig}, +}; + +use zynq7000::{ + PsPeripherals, + slcr::{LevelShifterConfig, clocks::SrcSelIo}, +}; +use zynq7000_rt as _; + +const INIT_STRING: &str = "-- Zynq 7000 Zedboard QSPI example --\n\r"; +const QSPI_DEV_COMBINATION: qspi::QspiDeviceCombination = qspi::QspiDeviceCombination { + vendor: qspi::QspiVendor::WinbondAndSpansion, + operating_mode: qspi::OperatingMode::FastReadQuadOutput, + two_devices: false, +}; + +/// 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(); +} + +const ERASE_PROGRAM_READ_TEST: bool = false; + +#[embassy_executor::main] +#[unsafe(export_name = "main")] +async fn main(_spawner: Spawner) -> ! { + let mut dp = PsPeripherals::take().unwrap(); + l2_cache::init_with_defaults(&mut dp.l2c); + + // Enable PS-PL level shifters. + configure_level_shifter(LevelShifterConfig::EnableAll); + + // Clock was already initialized by PS7 Init TCL script or FSBL, we just read it. + let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap(); + // Set up the global interrupt controller. + let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd); + gic.enable_all_interrupts(); + gic.set_all_spi_interrupt_targets_cpu0(); + gic.enable(); + unsafe { + gic.enable_interrupts(); + } + let gpio_pins = GpioPins::new(dp.gpio); + + // Set up global timer counter and embassy time driver. + let gtc = GlobalTimerCounter::new(dp.gtc, clocks.arm_clocks()); + zynq7000_embassy::init(clocks.arm_clocks(), gtc); + + // Set up the UART, we are logging with it. + let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200) + .unwrap() + .0; + let mut uart = Uart::new_with_mio( + dp.uart_1, + UartConfig::new_with_clk_config(uart_clk_config), + (gpio_pins.mio.mio48, gpio_pins.mio.mio49), + ) + .unwrap(); + uart.write_all(INIT_STRING.as_bytes()).unwrap(); + // Safety: We are not multi-threaded yet. + unsafe { + zynq7000_hal::log::uart_blocking::init_unsafe_single_core( + uart, + log::LevelFilter::Trace, + false, + ) + }; + + let boot_mode = BootMode::new_from_regs(); + info!("Boot mode: {:?}", boot_mode); + + let qspi_clock_config = + qspi::ClockConfig::calculate_with_loopback(SrcSelIo::IoPll, &clocks, 100.MHz()) + .expect("QSPI clock calculation failed"); + let qspi = qspi::Qspi::new_single_qspi_with_feedback( + dp.qspi, + qspi_clock_config, + embedded_hal::spi::MODE_0, + qspi::IoType::LvCmos33, + gpio_pins.mio.mio1, + ( + gpio_pins.mio.mio2, + gpio_pins.mio.mio3, + gpio_pins.mio.mio4, + gpio_pins.mio.mio5, + ), + gpio_pins.mio.mio6, + gpio_pins.mio.mio8, + ); + + let qspi_io_mode = qspi.into_io_mode(false); + + let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true); + + let rdid = spansion_qspi.read_rdid_extended(); + info!( + "QSPI Info: manufacturer ID: {:?}, interface type: {:?}, density: {:?}, sector arch: {:?}, model number: {:?}", + rdid.base_id().manufacturer_id(), + rdid.base_id().memory_interface_type(), + rdid.base_id().density(), + rdid.sector_arch(), + rdid.model_number() + ); + let cr1 = spansion_qspi.read_configuration_register(); + info!("QSPI Configuration Register 1: {:?}", cr1); + + let mut write_buf: [u8; u8::MAX as usize + 1] = [0x0; u8::MAX as usize + 1]; + for (idx, byte) in write_buf.iter_mut().enumerate() { + *byte = idx as u8; + } + let mut read_buf = [0u8; 256]; + + if ERASE_PROGRAM_READ_TEST { + info!("performing erase, program, read test"); + spansion_qspi + .erase_sector(0x10000) + .expect("erasing sector failed"); + spansion_qspi.read_page_fast_read(0x10000, &mut read_buf, true); + for read in read_buf.iter() { + assert_eq!(*read, 0xFF); + } + read_buf.fill(0); + spansion_qspi.program_page(0x10000, &write_buf).unwrap(); + spansion_qspi.read_page_fast_read(0x10000, &mut read_buf, true); + for (read, written) in read_buf.iter().zip(write_buf.iter()) { + assert_eq!(read, written); + } + info!("test successful"); + } + + let mut spansion_lqspi = spansion_qspi.into_linear_addressed(QSPI_DEV_COMBINATION.into()); + + let guard = spansion_lqspi.read_guard(); + unsafe { + core::ptr::copy_nonoverlapping( + (qspi::QSPI_START_ADDRESS + 0x10000) as *const u8, + read_buf.as_mut_ptr(), + 256, + ); + } + drop(guard); + if ERASE_PROGRAM_READ_TEST { + for (read, written) in read_buf.iter().zip(write_buf.iter()) { + assert_eq!( + read, written, + "linear read failure, read and write missmatch" + ); + } + } + + let mut ticker = Ticker::every(Duration::from_millis(200)); + + let mut mio_led = Output::new_for_mio(gpio_pins.mio.mio7, PinState::Low); + loop { + mio_led.toggle().unwrap(); + + ticker.next().await; // Wait for the next cycle of the ticker + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn _irq_handler() { + let mut gic_helper = GicInterruptHelper::new(); + let irq_info = gic_helper.acknowledge_interrupt(); + match irq_info.interrupt() { + Interrupt::Sgi(_) => (), + Interrupt::Ppi(ppi_interrupt) => { + if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer { + unsafe { + zynq7000_embassy::on_interrupt(); + } + } + } + Interrupt::Spi(_spi_interrupt) => (), + Interrupt::Invalid(_) => (), + Interrupt::Spurious => (), + } + gic_helper.end_of_interrupt(irq_info); +} + +#[unsafe(no_mangle)] +pub extern "C" fn _abort_handler() { + loop { + nop(); + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn _undefined_handler() { + loop { + nop(); + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn _prefetch_handler() { + loop { + nop(); + } +} + +/// Panic handler +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + error!("Panic: {info:?}"); + loop {} +} diff --git a/examples/zedboard/src/bin/uart-blocking.rs b/examples/zedboard/src/bin/uart-blocking.rs index 5d98926..568d320 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 5dc3e6c..107be3d 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/lib.rs b/examples/zedboard/src/lib.rs index c8c587e..22324d4 100644 --- a/examples/zedboard/src/lib.rs +++ b/examples/zedboard/src/lib.rs @@ -1,6 +1,5 @@ #![no_std] use zynq7000_hal::time::Hertz; -pub mod phy_marvell; // Define the clock frequency as a constant pub const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_333); diff --git a/examples/zedboard/src/main.rs b/examples/zedboard/src/main.rs index d2c5c6d..8ee3160 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/scripts/memory_ddr.x b/scripts/memory_ddr.x new file mode 100644 index 0000000..11faa59 --- /dev/null +++ b/scripts/memory_ddr.x @@ -0,0 +1,22 @@ +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 MMU. This is + recommended for something like DMA descriptors. */ + CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M + 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/scripts/memory_ocm.x b/scripts/memory_ocm.x new file mode 100644 index 0000000..226f8a1 --- /dev/null +++ b/scripts/memory_ocm.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/scripts/xsct-flasher.tcl b/scripts/xsct-flasher.tcl new file mode 100644 index 0000000..390eaff --- /dev/null +++ b/scripts/xsct-flasher.tcl @@ -0,0 +1,144 @@ +if {[info exists env(ip_address_hw_server)]} { + set ip $env(ip_address_hw_server) +} else { + set ip "localhost" +} + +# Defaults +set init_tcl "" +set app "" +set bitstream "" + +# Usage helper +proc usage {} { + puts "Usage: xsct xsct-helper.tcl \[-a|--app app.elf\] \[-b|--bit design.bit]" + puts "Options:" + puts " -a, --app Path to application ELF to download" + puts " -b, --bit Path to FPGA bitstream (.bit) to program" + puts " -h, --help Show this help" +} + +# Compact, robust parser +set expecting "" +set endopts 0 +foreach arg $argv { + # If previous option expects a value, take this arg + if {$expecting ne ""} { + set $expecting $arg + set expecting "" + continue + } + + # Option handling (until we see --) + if {!$endopts && [string match "-*" $arg]} { + if {$arg eq "--"} { set endopts 1; continue } + if {$arg eq "-h" || $arg eq "--help"} { usage; exit 0 } + if {$arg eq "-a" || $arg eq "--app"} { set expecting app; continue } + if {$arg eq "-b" || $arg eq "--bit"} { set expecting bitstream; continue } + puts "error: unknown option: $arg"; usage; exit 1 + } + + # Positional: expect only + if {$init_tcl eq ""} { + set init_tcl $arg + } else { + puts "error: unexpected positional argument: $arg" + usage + exit 1 + } +} + +# Validate required init script +if {$init_tcl eq ""} { + puts "error: missing required first argument pointing to initialization TCL script" + usage + exit 1 +} +if {![file exists $init_tcl]} { + puts "error: the PS init tcl script '$init_tcl' does not exist" + exit 1 +} + +# Resolve app: CLI takes precedence over env(APP) +if {$app ne ""} { + if {![file exists $app]} { + puts "error: the app file '$app' does not exist" + exit 1 + } +} elseif {[info exists env(APP)]} { + if {[file exists $env(APP)]} { + set app $env(APP) + } else { + puts "warning: APP environment variable is set but file does not exist: $env(APP)" + } +} + +# Validate bitstream if provided +if {$bitstream ne "" && ![file exists $bitstream]} { + puts "error: the bitstream file '$bitstream' does not exist" + exit 1 +} + +puts "Hardware server IP address: $ip" +connect -url tcp:$ip:3121 + +set devices [targets] + +set apu_line [string trim [targets -nocase -filter {name =~ "APU"}]] +set arm_core_0_line [string trim [targets -nocase -filter {name =~ "ARM Cortex-A9 MPCore #0"}]] +set fpga_line [string trim [targets -nocase -filter {name =~ "xc7z020"}]] + +set apu_device_num [string index $apu_line 0] +set arm_core_0_num [string index $arm_core_0_line 0] +set fpga_device_num [string index $fpga_line 0] + +puts "Select ps target with number: $apu_device_num" + +# Select the target +target $apu_device_num + +# Resetting the target involved problems when an image is stored on the flash. +# It has turned out that it is not essential to reset the system before loading +# the software components into the device. +puts "Reset target" +# TODO: Make the reset optional/configurable via input argument. +# Reset the target +rst + +# Check if bitstream is set and the file exists before programming FPGA +if {$bitstream eq ""} { + puts "Skipping bitstream programming (bitstream argument not set)" +} elseif {![file exists $bitstream]} { + puts "Error: The bitstream file '$bitstream' does not exist" +} else { + puts "Set FPGA target with number: $fpga_device_num" + target $fpga_device_num + + # Without this delay, the FPGA programming may fail + after 1500 + + puts "Programming FPGA with bitstream: $bitstream" + fpga -f $bitstream +} + +puts "Set ps target with device number: $arm_core_0_num" +targets $arm_core_0_num + +puts "Initialize processing system" +# Init processing system +source $init_tcl + +ps7_init +ps7_post_config + +puts "Set arm core 0 target with number: $arm_core_0_num" +target $arm_core_0_num + +if {$app ne ""} { + puts "Download app $app to target" + dow $app + puts "Starting app" + con +} + +puts "Success" diff --git a/scripts/xsct-init.tcl b/scripts/xsct-init.tcl deleted file mode 100644 index 946332f..0000000 --- a/scripts/xsct-init.tcl +++ /dev/null @@ -1,86 +0,0 @@ -if {[info exists env(ip_address_hw_server)]} { - set ip $env(ip_address_hw_server) -} else { - set ip "localhost" -} - -set init_tcl "" -if {[llength $argv] >= 1} { - set init_tcl [lindex $argv 0] -} else { - puts "error: missing required first argument pointing to initialization TCL script" - exit 1 -} - -if {![file exists $init_tcl]} { - puts "the ps init tcl script '$init_tcl' does not exist" - exit 0 -} - -# parse command-line arguments -set bitstream "" -if {[llength $argv] >= 2} { - set bitstream [lindex $argv 1] -} - -puts "Hardware server IP address: $ip" -connect -url tcp:$ip:3121 - -set devices [targets] - -set apu_line [string trim [targets -nocase -filter {name =~ "APU"}]] -set arm_core_0_line [string trim [targets -nocase -filter {name =~ "ARM Cortex-A9 MPCore #0"}]] -set fpga_line [string trim [targets -nocase -filter {name =~ "xc7z020"}]] - -set apu_device_num [string index $apu_line 0] -set arm_core_0_num [string index $arm_core_0_line 0] -set fpga_device_num [string index $fpga_line 0] - -puts "Select ps target with number: $apu_device_num" - -# Select the target -target $apu_device_num - -# Resetting the target involved problems when an image is stored on the flash. -# It has turned out that it is not essential to reset the system before loading -# the software components into the device. -puts "Reset target" -# TODO: Make the reset optional/configurable via input argument. -# Reset the target -rst - -# Check if bitstream is set and the file exists before programming FPGA -if {$bitstream eq ""} { - puts "Skipping bitstream programming (bitstream argument not set)" -} elseif {![file exists $bitstream]} { - puts "Error: The bitstream file '$bitstream' does not exist" -} else { - puts "Set FPGA target with number: $fpga_device_num" - target $fpga_device_num - - # Without this delay, the FPGA programming may fail - after 1500 - - puts "Programming FPGA with bitstream: $bitstream" - fpga -f $bitstream -} - -puts "Set ps target with device number: $arm_core_0_num" -targets $arm_core_0_num - -puts "Initialize processing system" -# Init processing system -source $init_tcl - -ps7_init -ps7_post_config - -puts "Set arm core 0 target with number: $arm_core_0_num" -target $arm_core_0_num - -if {[info exists env(APP)] && [file exists $env(APP)]} { - puts "Download app $env(APP) to target" - dow $env(APP) -} - -puts "Successful" diff --git a/scripts/zynq7000-init.py b/scripts/zynq7000-init.py index b293ada..1de220d 100755 --- a/scripts/zynq7000-init.py +++ b/scripts/zynq7000-init.py @@ -7,7 +7,7 @@ import sys from pathlib import Path # Define the default values -TCL_SCRIPT_NAME = "xsct-init.tcl" +TCL_SCRIPT_NAME = "xsct-flasher.tcl" SCRIPTS_DIR = "scripts" DEFAULT_IP_ADDRESS_HW_SERVER = "localhost" @@ -55,7 +55,7 @@ def main(): default=os.getenv("TCL_INIT_SCRIPT"), help="Path to the ps7 initialization TCL file to prepare the processing system.\n" "You can also set the TCL_INIT_SCRIPT env variable to set this.\n" - "It is also set implicitely when specifying the SDT folder with --sdt" + "It is also set implicitely when specifying the SDT folder with --sdt", ) parser.add_argument( "-b", @@ -63,7 +63,7 @@ def main(): dest="bit", default=os.getenv("ZYNQ_BITSTREAM"), help="Optional path to the bitstream which will also be programed to the device. It is" - " also searched automatically if the --sdt option is used.\n" + " also searched automatically if the --sdt option is used.\n", ) args = parser.parse_args() @@ -91,9 +91,6 @@ def main(): print(f"The app '{args.app}' does not exist") sys.exit(1) - # Export environment variables - if args.app: - os.environ["APP"] = args.app os.environ["IP_ADDRESS_HW_SERVER"] = args.ip init_tcl = None bitstream = None @@ -136,7 +133,11 @@ def main(): # Prepare tcl_args as a list to avoid manual string concatenation issues cmd_list = ["xsct", str(xsct_script), init_tcl] if bitstream: + cmd_list.append("--bit") cmd_list.append(bitstream) + if args.app: + cmd_list.append("--app") + cmd_list.append(args.app) # Join safely for shell execution xsct_cmd = shlex.join(cmd_list) diff --git a/zedboard-bsp/Cargo.toml b/zedboard-bsp/Cargo.toml new file mode 100644 index 0000000..8e0f751 --- /dev/null +++ b/zedboard-bsp/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "zedboard-bsp" +version = "0.1.0" +edition = "2024" + +[dependencies] +zynq7000-hal = { path = "../zynq7000-hal" } +bitbybit = "1.3" +arbitrary-int = "1.3" +num_enum = { version = "0.7", default-features = false } +thiserror = { version = "2", default-features = false } diff --git a/zedboard-bsp/src/lib.rs b/zedboard-bsp/src/lib.rs new file mode 100644 index 0000000..719c662 --- /dev/null +++ b/zedboard-bsp/src/lib.rs @@ -0,0 +1,4 @@ +#![no_std] + +pub mod phy_marvell; +pub mod qspi_spansion; diff --git a/examples/zedboard/src/phy_marvell.rs b/zedboard-bsp/src/phy_marvell.rs similarity index 100% rename from examples/zedboard/src/phy_marvell.rs rename to zedboard-bsp/src/phy_marvell.rs diff --git a/zedboard-bsp/src/qspi_spansion.rs b/zedboard-bsp/src/qspi_spansion.rs new file mode 100644 index 0000000..2449fd2 --- /dev/null +++ b/zedboard-bsp/src/qspi_spansion.rs @@ -0,0 +1,604 @@ +use core::cell::RefCell; + +use arbitrary_int::{Number, u24}; +use zynq7000_hal::qspi::{ + FIFO_DEPTH, LinearQspiConfig, QspiIoMode, QspiIoTransferGuard, QspiLinearAddressing, + QspiLinearReadGuard, +}; + +#[derive(Debug, Clone, Copy)] +pub enum RegisterId { + /// WRR + WriteRegisters = 0x01, + /// PP + PageProgram = 0x02, + /// READ + Read = 0x03, + /// WRDI + WriteDisable = 0x04, + /// RDSR1 + ReadStatus1 = 0x05, + /// RDSR2 + ReadStatus2 = 0x07, + /// WREN + WriteEnable = 0x06, + /// FAST_READ + FastRead = 0x0B, + /// SE + SectorErase = 0xD8, + /// CSLR + ClearStatus = 0x30, + /// RDCR + ReadConfig = 0x35, + /// RDID + ReadId = 0x9F, +} + +#[derive(Debug, Clone, Copy, num_enum::TryFromPrimitive)] +#[repr(u8)] +pub enum MemoryInterfaceType { + _128Mb = 0x20, + _256Mb = 0x02, +} + +#[derive(Debug, Clone, Copy, num_enum::TryFromPrimitive)] +#[repr(u8)] +pub enum Density { + _128Mb = 0x18, + _256Mb = 0x19, +} + +#[derive(Debug, Clone, Copy, num_enum::TryFromPrimitive)] +#[repr(u8)] +pub enum SectorArchictecture { + /// Uniform 256 kB sectors. + Uniform = 0x00, + /// 32 4kB sectors and 64 kB sectors. + Hybrid = 0x01, +} + +#[derive(Debug, Clone, Copy)] +pub struct BaseDeviceId { + manufacturer_id: u8, + device_id: u16, +} + +impl BaseDeviceId { + #[inline] + pub const fn new(manufacturer_id: u8, device_id: u16) -> Self { + BaseDeviceId { + manufacturer_id, + device_id, + } + } + + #[inline] + pub const fn from_raw(raw: &[u8; 3]) -> Self { + BaseDeviceId::new(raw[0], ((raw[1] as u16) << 8) | raw[2] as u16) + } + + #[inline] + pub const fn manufacturer_id(&self) -> u8 { + self.manufacturer_id + } + + #[inline] + pub const fn device_id_raw(&self) -> u16 { + self.device_id + } + + #[inline] + pub fn memory_interface_type(&self) -> Result { + MemoryInterfaceType::try_from(((self.device_id >> 8) & 0xff) as u8).map_err(|e| e.number) + } + + #[inline] + pub fn density(&self) -> Result { + Density::try_from((self.device_id & 0xff) as u8).map_err(|e| e.number) + } +} + +#[derive(Debug)] +pub struct ExtendedDeviceId { + base: BaseDeviceId, + id_cfi_len: u8, + sector_arch: u8, + family_id: u8, + model_number: [u8; 2], +} + +impl ExtendedDeviceId { + pub const fn from_raw(raw: &[u8; 8]) -> Self { + ExtendedDeviceId { + base: BaseDeviceId::from_raw(&[raw[0], raw[1], raw[2]]), + id_cfi_len: raw[3], + sector_arch: raw[4], + family_id: raw[5], + model_number: [raw[6], raw[7]], + } + } + + pub const fn id_cfi_len(&self) -> u8 { + self.id_cfi_len + } + + pub const fn sector_arch_raw(&self) -> u8 { + self.sector_arch + } + + pub fn sector_arch(&self) -> Result { + SectorArchictecture::try_from(self.sector_arch_raw()).map_err(|e| e.number) + } + + pub const fn family_id(&self) -> u8 { + self.family_id + } + + pub const fn model_number_raw(&self) -> &[u8; 2] { + &self.model_number + } + + pub const fn model_number(&self) -> [char; 2] { + [self.model_number[0] as char, self.model_number[1] as char] + } +} + +impl ExtendedDeviceId { + #[inline] + pub fn base_id(&self) -> BaseDeviceId { + self.base + } +} + +#[bitbybit::bitfield(u8)] +#[derive(Debug)] +pub struct StatusRegister1 { + #[bit(7, rw)] + status_register_write_disable: bool, + #[bit(6, r)] + programming_error: bool, + #[bit(5, r)] + erase_error: bool, + #[bit(4, r)] + bp_2: bool, + #[bit(3, r)] + bp_1: bool, + #[bit(2, r)] + bp_0: bool, + #[bit(1, r)] + write_enable_latch: bool, + #[bit(0, r)] + write_in_progress: bool, +} + +#[bitbybit::bitfield(u8)] +#[derive(Debug)] +pub struct ConfigRegister1 { + #[bit(7, rw)] + latency_code_1: bool, + #[bit(6, rw)] + latency_code_0: bool, + /// This is an OTP bit. It can not be set back to 0 once it has been set to 1! + #[bit(5, rw)] + tbprot: bool, + #[bit(3, rw)] + bpnv: bool, + /// This is an OTP bit. It can not be set back to 0 once it has been set to 1! + #[bit(2, rw)] + tbparm: bool, + #[bit(1, rw)] + quad: bool, + #[bit(0, rw)] + freeze: bool, +} + +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +pub enum AddrError { + #[error("address out of range")] + OutOfRange, + #[error("address not aligned")] + Alignment, +} + +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +pub enum EraseError { + #[error("erase error bit set in status register")] + EraseErrorBitSet, + #[error("address error: {0}")] + Addr(#[from] AddrError), +} + +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +pub enum ProgramPageError { + #[error("programming error bit set in status register")] + ProgrammingErrorBitSet, + #[error("address error: {0}")] + Addr(#[from] AddrError), +} + +pub struct QspiSpansionS25Fl256SIoMode(RefCell); + +impl QspiSpansionS25Fl256SIoMode { + pub fn new(qspi: QspiIoMode, set_quad_bit_if_necessary: bool) -> Self { + let mut spansion_qspi = QspiSpansionS25Fl256SIoMode(RefCell::new(qspi)); + if set_quad_bit_if_necessary { + let mut cr1 = spansion_qspi.read_configuration_register(); + if cr1.quad() { + // Quad bit is already set. + return spansion_qspi; + } + cr1.set_quad(true); + // Preserve the status register by reading it first. + let sr1 = spansion_qspi.read_status_register_1(); + // Safety: Only the QUAD bit was set while all other bits are preserved. + unsafe { + spansion_qspi.write_status_and_config_register(sr1, cr1); + } + } + spansion_qspi + } + + pub fn into_linear_addressed( + self, + config: LinearQspiConfig, + ) -> QspiSpansionS25Fl256SLinearMode { + let qspi = self.0.into_inner().into_linear_addressed(config); + QspiSpansionS25Fl256SLinearMode(qspi) + } + + pub fn write_enable(&mut self) { + let qspi = self.0.get_mut(); + let mut transfer = qspi.transfer_guard(); + transfer.write_word_txd_01(RegisterId::WriteEnable as u32); + transfer.start(); + while !transfer.read_status().rx_above_threshold() {} + transfer.read_rx_data(); + } + + pub fn write_disable(&mut self) { + let qspi = self.0.get_mut(); + let mut transfer = qspi.transfer_guard(); + transfer.write_word_txd_01(RegisterId::WriteDisable as u32); + transfer.start(); + while !transfer.read_status().rx_above_threshold() {} + transfer.read_rx_data(); + } + + /// Write a new value for the status register. + /// + /// This API may not be used if the QUAD bit (CR1\[1\]) is set. + pub fn write_status(&mut self, sr: StatusRegister1) { + self.write_enable(); + let mut qspi = self.0.borrow_mut(); + let mut transfer = qspi.transfer_guard(); + transfer.write_word_txd_10(u32::from_ne_bytes([ + RegisterId::WriteRegisters as u8, + sr.raw_value(), + 0x00, + 0x00, + ])); + transfer.start(); + while !transfer.read_status().rx_above_threshold() {} + transfer.read_rx_data(); + } + + /// Write a new value for the status register. It is strongly recommended to read both + /// the status and config register first and preserve all unchanged bits. + /// + /// This API must be used if the QUAD bit (CR1\[1\]) is set. + /// + /// # Safety + /// + /// Misuse of this API does not lead to undefined behavior. However, it writes the + /// configuration register, which as OTP bits. Changing these bits from 0 to 1 is an + /// irreversible operation. + pub unsafe fn write_status_and_config_register( + &mut self, + sr: StatusRegister1, + cr: ConfigRegister1, + ) { + self.write_enable(); + let mut qspi = self.0.borrow_mut(); + let mut transfer = qspi.transfer_guard(); + transfer.write_word_txd_11(u32::from_ne_bytes([ + RegisterId::WriteRegisters as u8, + sr.raw_value(), + cr.raw_value(), + 0x00, + ])); + transfer.start(); + while !transfer.read_status().rx_above_threshold() {} + transfer.read_rx_data(); + } + + pub fn read_status_register_1(&self) -> StatusRegister1 { + let mut qspi = self.0.borrow_mut(); + let mut transfer = qspi.transfer_guard(); + transfer.write_word_txd_10(RegisterId::ReadStatus1 as u32); + transfer.start(); + while !transfer.read_status().rx_above_threshold() {} + let reply = transfer.read_rx_data(); + drop(transfer); + // little-endian architecture, so the second byte received is the MSB. + StatusRegister1::new_with_raw_value(((reply >> 24) & 0xff) as u8) + } + + pub fn read_configuration_register(&self) -> ConfigRegister1 { + let mut qspi = self.0.borrow_mut(); + let mut transfer = qspi.transfer_guard(); + transfer.write_word_txd_10(RegisterId::ReadConfig as u32); + transfer.start(); + while !transfer.read_status().rx_above_threshold() {} + let reply = transfer.read_rx_data(); + drop(transfer); + // little-endian architecture, so the second byte received is the MSB. + ConfigRegister1::new_with_raw_value(((reply >> 24) & 0xff) as u8) + } + + pub fn clear_status(&mut self) { + let qspi = self.0.get_mut(); + let mut transfer = qspi.transfer_guard(); + transfer.write_word_txd_01(RegisterId::ClearStatus as u32); + transfer.start(); + while !transfer.read_status().rx_above_threshold() {} + transfer.read_rx_data(); + } + + pub fn read_rdid_base(&self) -> BaseDeviceId { + let mut qspi = self.0.borrow_mut(); + let mut transfer = qspi.transfer_guard(); + transfer.write_word_txd_00(RegisterId::ReadId as u32); + transfer.start(); + while !transfer.read_status().rx_above_threshold() {} + let reply = transfer.read_rx_data(); + drop(transfer); + BaseDeviceId::from_raw(reply.to_ne_bytes()[1..].try_into().unwrap()) + } + + pub fn read_rdid_extended(&self) -> ExtendedDeviceId { + let mut reply: [u8; 12] = [0; 12]; + let mut qspi = self.0.borrow_mut(); + let mut transfer = qspi.transfer_guard(); + transfer.write_word_txd_00(RegisterId::ReadId as u32); + transfer.write_word_txd_00(0x00); + transfer.write_word_txd_00(0x00); + transfer.start(); + let mut read_index = 0; + + while read_index < 3 { + if transfer.read_status().rx_above_threshold() { + reply[read_index * 4..(read_index + 1) * 4] + .copy_from_slice(&transfer.read_rx_data().to_ne_bytes()); + read_index += 1; + } + } + ExtendedDeviceId::from_raw(reply[1..9].try_into().unwrap()) + } + + /// This function will block until the operation has completed. + pub fn erase_sector(&mut self, addr: u32) -> Result<(), EraseError> { + if addr + 0x10000 > u24::MAX.as_u32() { + return Err(AddrError::OutOfRange.into()); + } + if !addr.is_multiple_of(0x10000) { + return Err(AddrError::Alignment.into()); + } + self.write_enable(); + let qspi = self.0.get_mut(); + let mut transfer = qspi.transfer_guard(); + let raw_word: [u8; 4] = [ + RegisterId::SectorErase as u8, + ((addr >> 16) & 0xff) as u8, + ((addr >> 8) & 0xff) as u8, + (addr & 0xff) as u8, + ]; + transfer.write_word_txd_00(u32::from_ne_bytes(raw_word)); + transfer.start(); + + // Finish transfer + while !transfer.read_status().rx_above_threshold() {} + transfer.read_rx_data(); + + // Drive CS high to initiate the sector erase operation. + drop(transfer); + + // Now poll for completion. + loop { + let rdsr1 = self.read_status_register_1(); + if rdsr1.erase_error() { + // The datasheet mentions that the status should be cleared and writes + // should be disabled explicitely. + self.clear_status(); + self.write_disable(); + return Err(EraseError::EraseErrorBitSet); + } + if !rdsr1.write_in_progress() { + return Ok(()); + } + } + } + + /// This function also takes care of enabling writes before programming the page. + /// This function will block until the operation has completed. + /// + /// TODO: Allow smaller write size + pub fn program_page(&mut self, addr: u32, data: &[u8; 256]) -> Result<(), ProgramPageError> { + if addr + data.len() as u32 > u24::MAX.as_u32() { + return Err(AddrError::OutOfRange.into()); + } + if !addr.is_multiple_of(0x100) { + return Err(AddrError::Alignment.into()); + } + self.write_enable(); + let qspi = self.0.get_mut(); + let mut transfer = qspi.transfer_guard(); + let raw_word: [u8; 4] = [ + RegisterId::PageProgram as u8, + ((addr >> 16) & 0xff) as u8, + ((addr >> 8) & 0xff) as u8, + (addr & 0xff) as u8, + ]; + transfer.write_word_txd_00(u32::from_ne_bytes(raw_word)); + let mut read_index: u32 = 0; + let mut current_byte_index = 0; + // Fill the FIFO until it is full. + for _ in 0..FIFO_DEPTH - 1 { + transfer.write_word_txd_00(u32::from_ne_bytes( + data[current_byte_index..current_byte_index + 4] + .try_into() + .unwrap(), + )); + current_byte_index += 4; + } + transfer.start(); + + let mut wait_for_tx_slot = |transfer: &mut QspiIoTransferGuard| loop { + let status = transfer.read_status(); + if status.rx_above_threshold() { + transfer.read_rx_data(); + read_index = read_index.wrapping_add(4); + } + if !status.tx_full() { + break; + } + }; + + // Immediately fill the FIFO again with the remaining 8 bytes. + wait_for_tx_slot(&mut transfer); + + transfer.write_word_txd_00(u32::from_ne_bytes( + data[current_byte_index..current_byte_index + 4] + .try_into() + .unwrap(), + )); + current_byte_index += 4; + + wait_for_tx_slot(&mut transfer); + + transfer.write_word_txd_00(u32::from_ne_bytes( + data[current_byte_index..current_byte_index + 4] + .try_into() + .unwrap(), + )); + + while read_index < 256 { + if transfer.read_status().rx_above_threshold() { + transfer.read_rx_data(); + read_index = read_index.wrapping_add(4); + } + } + drop(transfer); + + // Now poll for completion. + loop { + let rdsr1 = self.read_status_register_1(); + if rdsr1.programming_error() { + // The datasheet mentions that the status should be cleared and writes + // should be disabled explicitely. + self.clear_status(); + self.write_disable(); + return Err(ProgramPageError::ProgrammingErrorBitSet); + } + if !rdsr1.write_in_progress() { + return Ok(()); + } + } + } + + pub fn read_page_fast_read(&self, addr: u32, buf: &mut [u8], dummy_byte: bool) { + let mut qspi = self.0.borrow_mut(); + let mut transfer = qspi.transfer_guard(); + let raw_word: [u8; 4] = [ + RegisterId::FastRead as u8, + ((addr >> 16) & 0xff) as u8, + ((addr >> 8) & 0xff) as u8, + (addr & 0xff) as u8, + ]; + transfer.write_word_txd_00(u32::from_ne_bytes(raw_word)); + let mut read_index = 0; + let mut written_words = 0; + let mut bytes_to_write = buf.len(); + if dummy_byte { + bytes_to_write += 1; + } + let fifo_writes = if bytes_to_write.is_multiple_of(4) { + bytes_to_write / 4 + } else { + (bytes_to_write / 4) + 1 + }; + // Fill the FIFO until it is full or all 0 bytes have been written. + for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) { + transfer.write_word_txd_00(0); + written_words += 1; + } + + transfer.start(); + let mut reply_word_index = 0; + + while read_index < buf.len() { + if transfer.read_status().rx_above_threshold() { + let reply = transfer.read_rx_data(); + if reply_word_index == 0 { + reply_word_index += 1; + continue; + } + let reply_as_bytes = reply.to_ne_bytes(); + let reply_size = core::cmp::min(buf.len() - read_index, 4); + read_index += match (reply_size, reply_word_index == 1 && dummy_byte) { + (1, false) => { + buf[read_index] = reply_as_bytes[0]; + 1 + } + (1, true) => { + buf[read_index] = reply_as_bytes[1]; + 1 + } + (2, false) => { + buf[read_index..read_index + 2].copy_from_slice(&reply_as_bytes[0..2]); + 2 + } + (2, true) => { + buf[read_index..read_index + 2].copy_from_slice(&reply_as_bytes[1..3]); + 2 + } + (3, false) => { + buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[0..3]); + 3 + } + (3, true) => { + buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[1..4]); + 3 + } + (4, false) => { + buf[read_index..read_index + 4].copy_from_slice(&reply_as_bytes[0..4]); + 4 + } + (4, true) => { + buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[1..4]); + 3 + } + _ => unreachable!(), + }; + reply_word_index += 1; + } + if written_words < fifo_writes && !transfer.read_status().tx_full() { + transfer.write_word_txd_00(0); + written_words += 1; + } + } + } +} + +/// If the Spansion QSPI is used in linear addressed mode, no IO operations are allowed. +pub struct QspiSpansionS25Fl256SLinearMode(QspiLinearAddressing); + +impl QspiSpansionS25Fl256SLinearMode { + pub fn into_io_mode(self, dual_flash: bool) -> QspiSpansionS25Fl256SIoMode { + let qspi = self.0.into_io_mode(dual_flash); + QspiSpansionS25Fl256SIoMode::new(qspi, false) + } + + pub fn read_guard(&mut self) -> QspiLinearReadGuard<'_> { + self.0.read_guard() + } +} diff --git a/zedboard-fpga-design/src/zedboard-bd.tcl b/zedboard-fpga-design/src/zedboard-bd.tcl index d5a3fdd..1199485 100644 --- a/zedboard-fpga-design/src/zedboard-bd.tcl +++ b/zedboard-fpga-design/src/zedboard-bd.tcl @@ -675,6 +675,18 @@ proc create_root_design { parentCell } { connect_bd_net -net xlslice_0_Dout1 [get_bd_pins UART_MUX/Dout] [get_bd_pins uart_mux_0/sel] connect_bd_net -net xlslice_1_Dout [get_bd_pins EMIO_O_0/Dout] [get_bd_pins LEDS/Din] [get_bd_pins EMIO_I/In0] [get_bd_pins UART_MUX/Din] + # Set DDR properties specified in the datasheet. + set_property -dict [list \ + CONFIG.PCW_UIPARAM_DDR_BOARD_DELAY0 {0.410} \ + CONFIG.PCW_UIPARAM_DDR_BOARD_DELAY1 {0.411} \ + CONFIG.PCW_UIPARAM_DDR_BOARD_DELAY2 {0.341} \ + CONFIG.PCW_UIPARAM_DDR_BOARD_DELAY3 {0.358} \ + CONFIG.PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_0 {0.025} \ + CONFIG.PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_1 {0.028} \ + CONFIG.PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_2 {-0.009} \ + CONFIG.PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_3 {-0.061} \ + ] [get_bd_cells processing_system7_0] + # Create address segments assign_bd_address -offset 0x43C00000 -range 0x00010000 -target_address_space [get_bd_addr_spaces processing_system7_0/Data] [get_bd_addr_segs axi_uart16550_0/S_AXI/Reg] -force assign_bd_address -offset 0x42C00000 -range 0x00010000 -target_address_space [get_bd_addr_spaces processing_system7_0/Data] [get_bd_addr_segs axi_uartlite_0/S_AXI/Reg] -force diff --git a/zedboard-fsbl/Cargo.toml b/zedboard-fsbl/Cargo.toml new file mode 100644 index 0000000..e85d54d --- /dev/null +++ b/zedboard-fsbl/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "zedboard-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" +arbitrary-int = "1.3" diff --git a/zedboard-fsbl/build.rs b/zedboard-fsbl/build.rs new file mode 100644 index 0000000..d534cc3 --- /dev/null +++ b/zedboard-fsbl/build.rs @@ -0,0 +1,31 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/zedboard-fsbl/memory.x b/zedboard-fsbl/memory.x new file mode 100644 index 0000000..226f8a1 --- /dev/null +++ b/zedboard-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/zedboard-fsbl/src/ddr_cfg.rs b/zedboard-fsbl/src/ddr_cfg.rs new file mode 100644 index 0000000..52d2dd0 --- /dev/null +++ b/zedboard-fsbl/src/ddr_cfg.rs @@ -0,0 +1,503 @@ +use arbitrary_int::{u2, u3, u4, u5, u6, u7, u9, u10, u11, u12, u20}; +use zynq7000::{ + ddrc::regs::{ + AxiPriorityReadPort, AxiPriorityWritePort, CheEccControl, CheTZq, CheTZqShortInterval, + CtrlReg1, CtrlReg2, CtrlReg3, CtrlReg4, CtrlReg5, CtrlReg6, DataBusWidth, DdrcControl, + DeepPowerdown, DfiTiming, DisableDq, DllCalib, DllCalibSel, DramAddrMapBank, + DramAddrMapColumn, DramAddrMapRow, DramBurst8ReadWrite, DramEmr, DramEmrMr, DramInitParam, + DramOdt, DramParamReg0, DramParamReg1, DramParamReg2, DramParamReg3, DramParamReg4, + EccMode, EccScrub, LpddrBit, LpddrControl0, LpddrControl1, LpddrControl2, LpddrControl3, + LprHprQueueControl, MobileSetting, ModeRegisterType, OdtDelayHold, PhyCmdTimeoutRdDataCpt, + PhyConfig, PhyDqsConfig, PhyInitRatio, PhyReceiverEnable, PhyWriteDataSlaveConfig, + PhyWriteEnableConfig, Reg2c, Reg2d, Reg64, Reg65, SoftReset, TwoRankConfig, + WriteQueueControl, + }, + slcr::ddriob::{DciType, DdriobConfig, InputType, OutputEnable}, +}; +use zynq7000_hal::ddr::{DdrcConfigSet, DdriobConfigSet}; + +const DDRIOB_ADDR_CFG: DdriobConfig = DdriobConfig::builder() + .with_pullup_enable(false) + .with_output_enable(OutputEnable::OBuf) + .with_term_disable_mode(false) + .with_ibuf_disable_mode(false) + .with_dci_type(DciType::Disabled) + .with_termination_enable(false) + .with_dci_update_enable(false) + .with_inp_type(InputType::Off) + .build(); + +const DDRIOB_DATA_CFG: DdriobConfig = DdriobConfig::builder() + .with_pullup_enable(false) + .with_output_enable(OutputEnable::OBuf) + .with_term_disable_mode(false) + .with_ibuf_disable_mode(false) + .with_dci_type(DciType::DciTermination) + .with_termination_enable(true) + .with_dci_update_enable(false) + .with_inp_type(InputType::VRefBasedDifferentialReceiverForSstlHstl) + .build(); + +const DDRIOB_DIFF_CFG: DdriobConfig = DdriobConfig::builder() + .with_pullup_enable(false) + .with_output_enable(OutputEnable::OBuf) + .with_term_disable_mode(false) + .with_ibuf_disable_mode(false) + .with_dci_type(DciType::DciTermination) + .with_termination_enable(true) + .with_dci_update_enable(false) + .with_inp_type(InputType::DifferentialInputReceiver) + .build(); + +const DDRIOB_CLOCK_CFG: DdriobConfig = DdriobConfig::builder() + .with_pullup_enable(false) + .with_output_enable(OutputEnable::OBuf) + .with_term_disable_mode(false) + .with_ibuf_disable_mode(false) + .with_dci_type(DciType::Disabled) + .with_termination_enable(false) + .with_dci_update_enable(false) + .with_inp_type(InputType::Off) + .build(); + +// TODO: Auto-generate from ps7init.tcl +pub const DDRIOB_CONFIG_SET_ZEDBOARD: DdriobConfigSet = DdriobConfigSet { + addr0: DDRIOB_ADDR_CFG, + addr1: DDRIOB_ADDR_CFG, + data0: DDRIOB_DATA_CFG, + data1: DDRIOB_DATA_CFG, + diff0: DDRIOB_DIFF_CFG, + diff1: DDRIOB_DIFF_CFG, + clock: DDRIOB_CLOCK_CFG, +}; + +// TODO: Auto-generate from ps7init.tcl +pub const DDRC_CONFIG_ZEDBOARD_FULL_BUILDERS: DdrcConfigSet = DdrcConfigSet { + ctrl: DdrcControl::builder() + .with_disable_auto_refresh(false) + .with_disable_active_bypass(false) + .with_disable_read_bypass(false) + .with_read_write_idle_gap(u7::new(1)) + .with_burst8_refresh(u3::new(0)) + .with_data_bus_width(DataBusWidth::_32Bit) + .with_power_down_enable(false) + .with_soft_reset(SoftReset::Reset) + .build(), + two_rank: TwoRankConfig::builder() + .with_addrmap_cs_bit0(u5::new(0)) + .with_ddrc_active_ranks(u2::new(1)) + .with_rfc_nom_x32(u12::new(0x82)) + .build(), + hpr: LprHprQueueControl::builder() + .with_xact_run_length(u4::new(0xf)) + .with_max_starve_x32(u11::new(0xf)) + .with_min_non_critical_x32(u11::new(0xf)) + .build(), + lpr: LprHprQueueControl::builder() + .with_xact_run_length(u4::new(0x8)) + .with_max_starve_x32(u11::new(0x2)) + .with_min_non_critical_x32(u11::new(0x1)) + .build(), + wr: WriteQueueControl::builder() + .with_max_starve_x32(u11::new(0x2)) + .with_xact_run_length(u4::new(0x8)) + .with_min_non_critical_x32(u11::new(0x1)) + .build(), + dram_param_0: DramParamReg0::builder() + .with_post_selfref_gap_x32(u7::new(0x10)) + .with_t_rfc_min(0x56) + .with_t_rc(u6::new(0x1b)) + .build(), + dram_param_1: DramParamReg1::builder() + .with_t_cke(u4::new(0x4)) + .with_t_ras_min(u5::new(0x13)) + .with_t_ras_max(u6::new(0x24)) + .with_t_faw(u6::new(0x16)) + .with_powerdown_to_x32(u5::new(0x6)) + .with_wr2pre(u5::new(0x13)) + .build(), + dram_param_2: DramParamReg2::builder() + .with_t_rcd(u4::new(0x7)) + .with_rd2pre(u5::new(0x5)) + .with_pad_pd(u3::new(0x0)) + .with_t_xp(u5::new(0x5)) + .with_wr2rd(u5::new(0xf)) + .with_rd2wr(u5::new(0x7)) + .with_write_latency(u5::new(0x5)) + .build(), + dram_param_3: DramParamReg3::builder() + .with_disable_pad_pd_feature(false) + .with_read_latency(u5::new(0x7)) + .with_enable_dfi_dram_clk_disable(false) + .with_mobile(MobileSetting::Ddr2Ddr3) + .with_sdram(false) + .with_refresh_to_x32(u5::new(0x8)) + .with_t_rp(u4::new(0x7)) + .with_refresh_margin(u4::new(0x2)) + .with_t_rrd(u3::new(0x6)) + .with_t_ccd(u3::new(0x4)) + .build(), + dram_param_4: DramParamReg4::builder() + .with_mr_rdata_valid(false) + .with_mr_type(ModeRegisterType::Write) + .with_mr_wr_busy(false) + .with_mr_data(0x0) + .with_mr_addr(u2::new(0x0)) + .with_mr_wr(false) + .with_prefer_write(false) + .with_enable_2t_timing_mode(false) + .build(), + dram_init_param: DramInitParam::builder() + .with_t_mrd(u3::new(0x4)) + .with_pre_ocd_x32(u4::new(0x0)) + .with_final_wait_x32(u7::new(0x7)) + .build(), + dram_emr: DramEmr::builder().with_emr3(0x0).with_emr2(0x8).build(), + dram_emr_mr: DramEmrMr::builder().with_emr(0x4).with_mr(0xb30).build(), + dram_burst8_rdwr: DramBurst8ReadWrite::builder() + .with_burst_rdwr(u4::new(0x4)) + .with_pre_cke_x1024(u10::new(0x16d)) + .with_post_cke_x1024(u10::new(0x1)) + .with_burstchop(false) + .build(), + disable_dq: DisableDq::builder() + .with_dis_dq(false) + .with_force_low_pri_n(false) + .build(), + dram_addr_map_bank: DramAddrMapBank::builder() + .with_addrmap_bank_b6(u4::new(0)) + .with_addrmap_bank_b5(u4::new(0)) + .with_addrmap_bank_b2(u4::new(0x7)) + .with_addrmap_bank_b1(u4::new(0x7)) + .with_addrmap_bank_b0(u4::new(0x7)) + .build(), + dram_addr_map_col: DramAddrMapColumn::builder() + .with_addrmap_col_b11(u4::new(0xf)) + .with_addrmap_col_b10(u4::new(0xf)) + .with_addrmap_col_b9(u4::new(0xf)) + .with_addrmap_col_b8(u4::new(0x0)) + .with_addrmap_col_b7(u4::new(0x0)) + .with_addrmap_col_b4(u4::new(0x0)) + .with_addrmap_col_b3(u4::new(0x0)) + .with_addrmap_col_b2(u4::new(0x0)) + .build(), + dram_addr_map_row: DramAddrMapRow::builder() + .with_addrmap_row_b15(u4::new(0xf)) + .with_addrmap_row_b14(u4::new(0xf)) + .with_addrmap_row_b13(u4::new(0x6)) + .with_addrmap_row_b12(u4::new(0x6)) + .with_addrmap_row_b2_11(u4::new(0x6)) + .with_addrmap_row_b1(u4::new(0x6)) + .with_addrmap_row_b0(u4::new(0x4)) + .build(), + dram_odt: DramOdt::builder() + .with_phy_idle_local_odt(u2::new(0x0)) + .with_phy_write_local_odt(u2::new(0x3)) + .with_phy_read_local_odt(u2::new(0x0)) + .with_rank0_wr_odt(u3::new(0x1)) + .with_rank0_rd_odt(u3::new(0x0)) + .build(), + phy_cmd_timeout_rddata_cpt: PhyCmdTimeoutRdDataCpt::builder() + .with_wrlvl_num_of_dq0(u4::new(0x7)) + .with_gatelvl_num_of_dq0(u4::new(0x7)) + .with_clk_stall_level(false) + .with_dis_phy_ctrl_rstn(false) + .with_rdc_fifo_rst_err_cnt_clr(false) + .with_use_fixed_re(true) + .with_rdc_we_to_re_delay(u4::new(0x8)) + .with_wr_cmd_to_data(u4::new(0x0)) + .with_rd_cmd_to_data(u4::new(0x0)) + .build(), + dll_calib: DllCalib::builder().with_sel(DllCalibSel::Periodic).build(), + odt_delay_hold: OdtDelayHold::builder() + .with_wr_odt_hold(u4::new(0x5)) + .with_rd_odt_hold(u4::new(0x0)) + .with_wr_odt_delay(u4::new(0x0)) + .with_rd_odt_delay(u4::new(0x3)) + .build(), + ctrl_reg1: CtrlReg1::builder() + .with_selfref_enable(false) + .with_dis_collision_page_opt(false) + .with_dis_wc(false) + .with_refresh_update_level(false) + .with_auto_pre_en(false) + .with_lpr_num_entries(u6::new(0x1f)) + .with_pageclose(false) + .build(), + ctrl_reg2: CtrlReg2::builder() + .with_go_2_critcal_enable(true) + .with_go_2_critical_hysteresis(0x0) + .build(), + ctrl_reg3: CtrlReg3::builder() + .with_dfi_t_wlmrd(u10::new(0x28)) + .with_rdlvl_rr(0x41) + .with_wrlvl_ww(0x41) + .build(), + ctrl_reg4: CtrlReg4::builder() + .with_dfi_t_ctrlupd_interval_max_x1024(0x16) + .with_dfi_t_ctrlupd_interval_min_x1024(0x10) + .build(), + ctrl_reg5: CtrlReg5::builder() + .with_t_ckesr(u6::new(0x4)) + .with_t_cksrx(u4::new(0x6)) + .with_t_ckrse(u4::new(0x6)) + .with_dfi_t_dram_clk_enable(u4::new(0x1)) + .with_dfi_t_dram_clk_disable(u4::new(0x1)) + .with_dfi_t_ctrl_delay(u4::new(0x1)) + .build(), + ctrl_reg6: CtrlReg6::builder() + .with_t_cksx(u4::new(0x3)) + .with_t_ckdpdx(u4::new(0x2)) + .with_t_ckdpde(u4::new(0x2)) + .with_t_ckpdx(u4::new(0x2)) + .with_t_ckpde(u4::new(0x2)) + .build(), + che_t_zq: CheTZq::builder() + .with_t_zq_short_nop(u10::new(0x40)) + .with_t_zq_long_nop(u10::new(0x200)) + .with_t_mode(u10::new(0x200)) + .with_ddr3(true) + .with_dis_auto_zq(false) + .build(), + che_t_zq_short_interval_reg: CheTZqShortInterval::builder() + .with_dram_rstn_x1024(0x69) + .with_t_zq_short_interval(u20::new(0xcb73)) + .build(), + deep_powerdown: DeepPowerdown::builder() + .with_deep_powerdown_to_x1024(0xff) + .with_enable(false) + .build(), + reg_2c: Reg2c::builder() + .with_dfi_rd_data_eye_train(true) + .with_dfi_rd_dqs_gate_level(true) + .with_dfi_wr_level_enable(true) + .with_trdlvl_max_error(false) + .with_twrlvl_max_error(false) + .with_dfi_rdlvl_max_x1024(u12::new(0xfff)) + .with_dfi_wrlvl_max_x1024(u12::new(0xfff)) + .build(), + reg_2d: Reg2d::builder().with_skip_ocd(true).build(), + dfi_timing: DfiTiming::builder() + .with_dfi_t_ctrlup_max(u10::new(0x40)) + .with_dfi_t_ctrlup_min(u10::new(0x3)) + .with_dfi_t_rddata_enable(u5::new(0x6)) + .build(), + che_ecc_ctrl: CheEccControl::builder() + .with_clear_correctable_errors(false) + .with_clear_uncorrectable_errors(false) + .build(), + ecc_scrub: EccScrub::builder() + .with_disable_scrub(true) + .with_ecc_mode(EccMode::NoEcc) + .build(), + phy_receiver_enable: PhyReceiverEnable::builder() + .with_phy_dif_off(u4::new(0)) + .with_phy_dif_on(u4::new(0)) + .build(), + phy_config: [PhyConfig::builder() + .with_dq_offset(u7::new(0x40)) + .with_wrlvl_inc_mode(false) + .with_gatelvl_inc_mode(false) + .with_rdlvl_inc_mode(false) + .with_data_slice_in_use(true) + .build(); 4], + phy_init_ratio: [ + PhyInitRatio::builder() + .with_gatelvl_init_ratio(u10::new(0xcf)) + .with_wrlvl_init_ratio(u10::new(0x3)) + .build(), + PhyInitRatio::builder() + .with_gatelvl_init_ratio(u10::new(0xd0)) + .with_wrlvl_init_ratio(u10::new(0x3)) + .build(), + PhyInitRatio::builder() + .with_gatelvl_init_ratio(u10::new(0xbd)) + .with_wrlvl_init_ratio(u10::new(0x0)) + .build(), + PhyInitRatio::builder() + .with_gatelvl_init_ratio(u10::new(0xc1)) + .with_wrlvl_init_ratio(u10::new(0x0)) + .build(), + ], + phy_rd_dqs_config: [ + PhyDqsConfig::builder() + .with_dqs_slave_delay(u9::new(0x0)) + .with_dqs_slave_force(false) + .with_dqs_slave_ratio(u10::new(0x35)) + .build(), + PhyDqsConfig::builder() + .with_dqs_slave_delay(u9::new(0x0)) + .with_dqs_slave_force(false) + .with_dqs_slave_ratio(u10::new(0x35)) + .build(), + PhyDqsConfig::builder() + .with_dqs_slave_delay(u9::new(0x0)) + .with_dqs_slave_force(false) + .with_dqs_slave_ratio(u10::new(0x35)) + .build(), + PhyDqsConfig::builder() + .with_dqs_slave_delay(u9::new(0x0)) + .with_dqs_slave_force(false) + .with_dqs_slave_ratio(u10::new(0x35)) + .build(), + ], + phy_wr_dqs_config: [ + PhyDqsConfig::builder() + .with_dqs_slave_delay(u9::new(0x0)) + .with_dqs_slave_force(false) + .with_dqs_slave_ratio(u10::new(0x83)) + .build(), + PhyDqsConfig::builder() + .with_dqs_slave_delay(u9::new(0x0)) + .with_dqs_slave_force(false) + .with_dqs_slave_ratio(u10::new(0x83)) + .build(), + PhyDqsConfig::builder() + .with_dqs_slave_delay(u9::new(0x0)) + .with_dqs_slave_force(false) + .with_dqs_slave_ratio(u10::new(0x7f)) + .build(), + PhyDqsConfig::builder() + .with_dqs_slave_delay(u9::new(0x0)) + .with_dqs_slave_force(false) + .with_dqs_slave_ratio(u10::new(0x78)) + .build(), + ], + phy_we_cfg: [ + PhyWriteEnableConfig::builder() + .with_fifo_we_in_delay(u9::new(0x0)) + .with_fifo_we_in_force(false) + .with_fifo_we_slave_ratio(u11::new(0x124)) + .build(), + PhyWriteEnableConfig::builder() + .with_fifo_we_in_delay(u9::new(0x0)) + .with_fifo_we_in_force(false) + .with_fifo_we_slave_ratio(u11::new(0x125)) + .build(), + PhyWriteEnableConfig::builder() + .with_fifo_we_in_delay(u9::new(0x0)) + .with_fifo_we_in_force(false) + .with_fifo_we_slave_ratio(u11::new(0x112)) + .build(), + PhyWriteEnableConfig::builder() + .with_fifo_we_in_delay(u9::new(0x0)) + .with_fifo_we_in_force(false) + .with_fifo_we_slave_ratio(u11::new(0x116)) + .build(), + ], + phy_wr_data_slv: [ + PhyWriteDataSlaveConfig::builder() + .with_wr_data_slave_delay(u9::new(0x0)) + .with_wr_data_slave_force(false) + .with_wr_data_slave_ratio(u10::new(0xc3)) + .build(), + PhyWriteDataSlaveConfig::builder() + .with_wr_data_slave_delay(u9::new(0x0)) + .with_wr_data_slave_force(false) + .with_wr_data_slave_ratio(u10::new(0xc3)) + .build(), + PhyWriteDataSlaveConfig::builder() + .with_wr_data_slave_delay(u9::new(0x0)) + .with_wr_data_slave_force(false) + .with_wr_data_slave_ratio(u10::new(0xbf)) + .build(), + PhyWriteDataSlaveConfig::builder() + .with_wr_data_slave_delay(u9::new(0x0)) + .with_wr_data_slave_force(false) + .with_wr_data_slave_ratio(u10::new(0xb8)) + .build(), + ], + reg64: Reg64::builder() + .with_cmd_latency(false) + .with_lpddr(false) + .with_ctrl_slave_delay(u7::new(0x0)) + .with_ctrl_slave_force(false) + .with_ctrl_slave_ratio(u10::new(0x100)) + .with_sel_logic(false) + .with_invert_clkout(true) + .with_bl2(false) + .build(), + reg65: Reg65::builder() + .with_ctrl_slave_delay(u2::new(0x0)) + .with_dis_calib_rst(false) + .with_use_rd_data_eye_level(true) + .with_use_rd_dqs_gate_level(true) + .with_use_wr_level(true) + .with_dll_lock_diff(u4::new(0xf)) + .with_rd_rl_delay(u5::new(0x4)) + .with_wr_rl_delay(u5::new(0x2)) + .build(), + page_mask: 0x0, + axi_priority_wr_port: [ + AxiPriorityWritePort::builder() + .with_disable_page_match(false) + .with_disable_urgent(false) + .with_disable_aging(false) + .with_pri_wr_port(u10::new(0x3ff)) + .build(), + AxiPriorityWritePort::builder() + .with_disable_page_match(false) + .with_disable_urgent(false) + .with_disable_aging(false) + .with_pri_wr_port(u10::new(0x3ff)) + .build(), + AxiPriorityWritePort::builder() + .with_disable_page_match(false) + .with_disable_urgent(false) + .with_disable_aging(false) + .with_pri_wr_port(u10::new(0x3ff)) + .build(), + AxiPriorityWritePort::builder() + .with_disable_page_match(false) + .with_disable_urgent(false) + .with_disable_aging(false) + .with_pri_wr_port(u10::new(0x3ff)) + .build(), + ], + axi_priority_rd_port: [ + AxiPriorityReadPort::builder() + .with_enable_hpr(false) + .with_disable_page_match(false) + .with_disable_urgent(false) + .with_disable_aging(false) + .with_pri_rd_port_n(u10::new(0x3ff)) + .build(), + AxiPriorityReadPort::builder() + .with_enable_hpr(false) + .with_disable_page_match(false) + .with_disable_urgent(false) + .with_disable_aging(false) + .with_pri_rd_port_n(u10::new(0x3ff)) + .build(), + AxiPriorityReadPort::builder() + .with_enable_hpr(false) + .with_disable_page_match(false) + .with_disable_urgent(false) + .with_disable_aging(false) + .with_pri_rd_port_n(u10::new(0x3ff)) + .build(), + AxiPriorityReadPort::builder() + .with_enable_hpr(false) + .with_disable_page_match(false) + .with_disable_urgent(false) + .with_disable_aging(false) + .with_pri_rd_port_n(u10::new(0x3ff)) + .build(), + ], + lpddr_ctrl_0: LpddrControl0::builder() + .with_mr4_margin(0x0) + .with_derate_enable(false) + .with_per_bank_refresh(false) + .with_lpddr2(LpddrBit::Ddr2Ddr3) + .build(), + lpddr_ctrl_1: LpddrControl1::builder().with_mr4_read_interval(0x0).build(), + lpddr_ctrl_2: LpddrControl2::builder() + .with_t_mrw(u10::new(0x5)) + .with_idle_after_reset_x32(0x12) + .with_min_stable_clock_x1(u4::new(0x5)) + .build(), + lpddr_ctrl_3: LpddrControl3::builder() + .with_dev_zqinit_x32(u10::new(0x12)) + .with_max_auto_init_x1024(0xa8) + .build(), +}; diff --git a/zedboard-fsbl/src/main.rs b/zedboard-fsbl/src/main.rs new file mode 100644 index 0000000..9f0512a --- /dev/null +++ b/zedboard-fsbl/src/main.rs @@ -0,0 +1,164 @@ +//! Simple FSBL. +#![no_std] +#![no_main] + +use arbitrary_int::u6; +use core::panic::PanicInfo; +use cortex_ar::asm::nop; +use embedded_io::Write as _; +use log::{error, info}; +use zynq7000_hal::{ + BootMode, + clocks::{ + Clocks, + pll::{PllConfig, configure_arm_pll, configure_io_pll}, + }, + ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest}, + gic::GicConfigurator, + gpio::GpioPins, + l2_cache, + time::Hertz, + uart::{ClockConfigRaw, Uart, UartConfig}, +}; +use zynq7000_rt as _; + +use crate::ddr_cfg::{DDRC_CONFIG_ZEDBOARD_FULL_BUILDERS, DDRIOB_CONFIG_SET_ZEDBOARD}; + +pub mod ddr_cfg; + +// 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); +/// 1000 MHz. +const IO_CLK: Hertz = Hertz::from_raw(1_000_000_000); + +/// DDR frequency for the MT41K128M16JT-125 device. +const DDR_FREQUENCY: Hertz = Hertz::from_raw(533_333_333); + +/// 1067 MHz. +const DDR_CLK: Hertz = Hertz::from_raw(2 * DDR_FREQUENCY.raw()); + +const PERFORM_DDR_MEMTEST: bool = false; + +/// 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(), + ); + + let mut dp = zynq7000::PsPeripherals::take().unwrap(); + + // Clock was already initialized by PS7 Init TCL script or FSBL, we just read it. + let clocks = Clocks::new_from_regs(PS_CLK).unwrap(); + + let gpio_pins = GpioPins::new(dp.gpio); + let mio_pins = gpio_pins.mio; + // Set up the UART, we are logging with it. + let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200) + .unwrap() + .0; + let mut uart = Uart::new_with_mio( + dp.uart_1, + UartConfig::new_with_clk_config(uart_clk_config), + (mio_pins.mio48, mio_pins.mio49), + ) + .unwrap(); + uart.write_all(b"-- Zedboard Rust FSBL --\n\r").unwrap(); + // Safety: We are not multi-threaded yet. + unsafe { + zynq7000_hal::log::uart_blocking::init_unsafe_single_core( + uart, + log::LevelFilter::Trace, + false, + ) + }; + + // Set up the global interrupt controller. + let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd); + gic.enable_all_interrupts(); + gic.set_all_spi_interrupt_targets_cpu0(); + gic.enable(); + // Enable interrupt exception. + unsafe { gic.enable_interrupts() }; + + info!("Configuring DDR.."); + configure_ddr_for_ddr3( + dp.ddrc, + boot_mode, + DdrClockSetupConfig { + ps_clk: PS_CLK, + ddr_clk: DDR_CLK, + ddr_3x_div: u6::new(2), + ddr_2x_div: u6::new(3), + }, + &DDRIOB_CONFIG_SET_ZEDBOARD, + &DDRC_CONFIG_ZEDBOARD_FULL_BUILDERS, + ); + info!("DDR init done."); + + info!("L2 cache init.."); + // Set up the L2 cache now that the DDR is in normal operation mode. + l2_cache::init_with_defaults(&mut dp.l2c); + info!("L2 cache init done."); + + if PERFORM_DDR_MEMTEST { + let ddr_base_addr = 0x100000; + info!("performing DDR memory test.."); + unsafe { + memtest::walking_zero_test(ddr_base_addr, 64).expect("walking one test failed"); + memtest::walking_one_test(ddr_base_addr, 64).expect("walking zero test failed"); + memtest::checkerboard_test(ddr_base_addr, 64).expect("checkerboard test failed"); + } + info!("DDR memory test success."); + } + 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/zynq7000-hal/Cargo.toml b/zynq7000-hal/Cargo.toml index d12bd8d..7f7835c 100644 --- a/zynq7000-hal/Cargo.toml +++ b/zynq7000-hal/Cargo.toml @@ -15,6 +15,7 @@ cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar zynq7000 = { path = "../zynq7000" } zynq-mmu = { path = "../zynq-mmu", version = "0.1.0" } +static_assertions = "1.1" bitbybit = "1.3" arbitrary-int = "1.3" thiserror = { version = "2", default-features = false } diff --git a/zynq7000-hal/src/boot_image.rs b/zynq7000-hal/src/boot_image.rs new file mode 100644 index 0000000..06a263a --- /dev/null +++ b/zynq7000-hal/src/boot_image.rs @@ -0,0 +1,296 @@ +/// ASCII 'XLNX' +pub const IMAGE_ID_U32: u32 = 0x584C4E58; + +/// This is the fixed size of the boot header. +pub const FIXED_BOOT_HEADER_SIZE: usize = 0xA0; + +pub struct BootHeader { + base_addr: u32, +} + +impl BootHeader { + /// # Safety + /// + /// This function will perform volatile reads for image header table fields on the given base + /// address. You must ensure that the address contains a valid image header table. + /// At the very least, [FIXED_BOOT_HEADER_SIZE] bytes of the boot header must be readable at + /// the given address. + pub const unsafe fn new(base_addr: u32) -> Self { + BootHeader { base_addr } + } + + #[inline] + pub fn image_id(&self) -> u32 { + unsafe { core::ptr::read_volatile((self.base_addr + 0x24) as *const _) } + } + + /// Check whether the image ID has the mandatory [IMAGE_ID_U32] value. + #[inline] + pub fn check_image_id_validity(&self) -> bool { + self.image_id() == 0x854C4E58 + } + + #[inline] + pub fn source_offset(&self) -> u32 { + unsafe { core::ptr::read_volatile((self.base_addr + 0x30) as *const _) } + } + + /// The boot image metadata is all the data up to the first partition of the FSBL. + /// + /// This information can be used to copy all relevant information for image and partition + /// parsing into RAM. This includes the image header table, the image headers and the + /// partition headers. + #[inline] + pub fn boot_image_metadata_len(&self) -> u32 { + self.source_offset() - self.base_addr + } + + #[inline] + pub fn header_checksum(&self) -> u32 { + unsafe { core::ptr::read_volatile((self.base_addr + 0x48) as *const _) } + } + + #[inline] + pub fn image_header_table_offset(&self) -> u32 { + unsafe { core::ptr::read_volatile((self.base_addr + 0x98) as *const _) } + } + + /// # Safety + /// + /// This function is only safe to use when the image header table structure + /// was copied to the correct offset of the base address used to initialize + /// this boot header structure. + pub unsafe fn image_header_table(&self) -> ImageHeaderTable { + let offset = self.image_header_table_offset(); + unsafe { ImageHeaderTable::new(self.base_addr + offset) } + } + + #[inline] + pub fn partition_header_table_offset(&self) -> u32 { + unsafe { core::ptr::read_volatile((self.base_addr + 0x9C) as *const _) } + } + + #[inline] + pub fn verify_header_checksum(&self) -> bool { + let checksum = unsafe { core::ptr::read_volatile((self.base_addr + 0x48) as *const _) }; + let end_addr = 0x48; + let mut current_addr = 0x20; + let mut sum = 0u32; + while current_addr < end_addr { + sum = sum.wrapping_add(unsafe { core::ptr::read_volatile(current_addr as *const u32) }); + current_addr += 4; + } + !sum == checksum + } +} + +pub struct ImageHeaderTable { + base_addr: u32, +} + +impl ImageHeaderTable { + /// # Safety + /// + /// This function will perform volatile reads for image header table fields on the given base + /// address. You must ensure that the address contains a valid image header table. + /// + /// This is a low-level function. It is recommended to use [BootHeader::image_header_table] + /// to retrieve the header table after checking boot header validity. + pub const unsafe fn new(base_addr: u32) -> Self { + ImageHeaderTable { base_addr } + } + + pub fn image_header_iterator(&self) -> ImageHeaderIterator { + let first_image_header = self.first_image_header_offset(); + ImageHeaderIterator { + current_image_header: first_image_header, + } + } + + #[inline] + pub fn count_of_headers(&self) -> u32 { + unsafe { core::ptr::read_volatile((self.base_addr + 0x04) as *const _) } + } + + #[inline] + pub fn first_partition_header(&self) -> u32 { + unsafe { core::ptr::read_volatile((self.base_addr + 0x08) as *const _) } + } + + #[inline] + pub fn first_image_header_offset(&self) -> u32 { + let words = unsafe { core::ptr::read_volatile((self.base_addr + 0x0C) as *const u32) }; + words * 4 + } + + /// You can also use [Self::image_header_iterator] to retrieve an iterator over all image + /// headers. + #[inline] + pub fn first_image_header(&self) -> ImageHeader { + unsafe { ImageHeader::new(self.base_addr + self.first_image_header_offset()) } + } +} + +pub struct ImageHeaderIterator { + current_image_header: u32, +} + +impl Iterator for ImageHeaderIterator { + type Item = ImageHeader; + + fn next(&mut self) -> Option { + let image_header = unsafe { ImageHeader::new(self.current_image_header) }; + image_header.next_image_header() + } +} + +pub struct ImageHeader { + base_addr: u32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +#[error("buffer too small")] +pub struct BufTooSmallError; + +impl ImageHeader { + /// # Safety + /// + /// This function will perform volatile reads for image header table fields on the given base + /// address. You must ensure that the address contains a valid image header table. + /// + /// This is a low-level function. It is recommended to use + /// [ImageHeaderTable::image_header_iterator] to retrieve the image headers after checking + /// boot header validity. + #[inline] + pub const unsafe fn new(base_addr: u32) -> Self { + ImageHeader { base_addr } + } + + #[inline] + pub fn next_image_header_offset(&self) -> u32 { + let next_image_header_words = + unsafe { core::ptr::read_volatile(self.base_addr as *const u32) }; + next_image_header_words * 4 + } + + #[inline] + pub fn partition_count(&self) -> u32 { + unsafe { core::ptr::read_volatile((self.base_addr + 0x0C) as *const u32) } + } + + pub fn first_partition_header_offset(&self) -> u32 { + let partition_header_word_offset = + unsafe { core::ptr::read_volatile((self.base_addr + 0x04) as *const u32) }; + partition_header_word_offset * 4 + } + + pub fn partition_header_iterator(&self) -> PartitionHeaderIterator { + let first_partition_header = self.first_partition_header_offset(); + PartitionHeaderIterator { + current_partition_header_addr: first_partition_header, + current_partition_index: 0, + num_of_partitions: self.partition_count(), + } + } + + pub fn image_name_copied(&self, buf: &mut [u8]) -> Result { + let mut current_addr = self.base_addr + 0x10; + let mut current_buf_idx = 0; + let mut null_byte_found = false; + loop { + let next_bytes = unsafe { core::slice::from_raw_parts(current_addr as *const u8, 4) }; + for &byte in next_bytes.iter() { + if byte == 0 { + null_byte_found = true; + break; + } + if current_buf_idx >= buf.len() { + return Err(BufTooSmallError); + } + buf[current_buf_idx] = byte; + current_buf_idx += 1; + } + if null_byte_found { + break; + } + current_addr += 4; + } + Ok(current_buf_idx) + } + + pub fn next_image_header(&self) -> Option { + if self.next_image_header_offset() == 0 { + None + } else { + Some(unsafe { ImageHeader::new(self.base_addr + self.next_image_header_offset()) }) + } + } +} + +pub struct PartitionHeaderIterator { + current_partition_header_addr: u32, + current_partition_index: u32, + num_of_partitions: u32, +} + +impl Iterator for PartitionHeaderIterator { + type Item = PartitionHeader; + + fn next(&mut self) -> Option { + if self.current_partition_index >= self.num_of_partitions { + return None; + } + let header = unsafe { PartitionHeader::new(self.current_partition_index) }; + self.current_partition_index += 1; + self.current_partition_header_addr += 0x40; + Some(header) + } +} + +pub struct PartitionHeader { + base_addr: u32, +} + +impl PartitionHeader { + /// # Safety + /// + /// This function will perform volatile reads for partition header fields on the given base + /// address. You must ensure that the address contains a valid partition header. + /// + /// This is a low-level function. It is recommended to use + /// [ImageHeader::partition_header_iterator] to retrieve the all partition headers for a given + /// image header. + #[inline] + pub const unsafe fn new(base_addr: u32) -> Self { + PartitionHeader { base_addr } + } + + #[inline] + pub fn encrypted_partition_length(&self) -> u32 { + unsafe { core::ptr::read_volatile(self.base_addr as *const u32) } + } + + #[inline] + pub fn unencrypted_partition_length(&self) -> u32 { + unsafe { core::ptr::read_volatile((self.base_addr + 0x04) as *const u32) } + } + + #[inline] + pub fn total_partition_length(&self) -> u32 { + let total_words = + unsafe { core::ptr::read_volatile((self.base_addr + 0x08) as *const u32) }; + total_words * 4 + } + + #[inline] + pub fn destination_load_address(&self) -> u32 { + unsafe { core::ptr::read_volatile((self.base_addr + 0x0C) as *const _) } + } + + #[inline] + pub fn data_offset(&self) -> u32 { + let offset_words = + unsafe { core::ptr::read_volatile((self.base_addr + 0x0C) as *const u32) }; + offset_words * 4 + } +} diff --git a/zynq7000-hal/src/clocks.rs b/zynq7000-hal/src/clocks/mod.rs similarity index 87% rename from zynq7000-hal/src/clocks.rs rename to zynq7000-hal/src/clocks/mod.rs index f9370da..5b008f0 100644 --- a/zynq7000-hal/src/clocks.rs +++ b/zynq7000-hal/src/clocks/mod.rs @@ -1,5 +1,7 @@ //! Clock module. -use arbitrary_int::Number; +use arbitrary_int::{Number, u6}; + +pub mod pll; use zynq7000::slcr::{ ClockControl, @@ -45,21 +47,67 @@ impl ArmClocks { #[derive(Debug)] pub struct DdrClocks { + /// DDR reference clock generated by the DDR PLL. ref_clk: Hertz, ddr_3x_clk: Hertz, ddr_2x_clk: Hertz, } impl DdrClocks { + /// Update the DDR 3x and 2x clocks in the SLCR. + /// + /// Usually, the DDR PLL output clock will be set to an even multiple of the DDR operating + /// frequency. In that case, the multiplicator should be used as the DDR 3x clock divisor. + /// The DDR 2x clock divisor should be set so that the resulting clock is 2/3 of the DDR + /// operating frequency. + /// + /// # Safety + /// + /// This should only be called once during start-up. It accesses the SLCR register. + pub unsafe fn configure_2x_3x_clk(ddr_3x_div: u6, ddr_2x_div: u6) { + // Safety: The DDR clock structure is a singleton. + unsafe { + crate::slcr::Slcr::with(|slcr| { + slcr.clk_ctrl().modify_ddr_clk_ctrl(|mut val| { + val.set_div_3x_clk(ddr_3x_div); + val.set_div_2x_clk(ddr_2x_div); + val + }); + }); + } + } + + /// Update the DDR 3x and 2x clocks in the SLCR and creates a DDR clock information structure. + /// + /// Usually, the DDR PLL output clock will be set to an even multiple of the DDR operating + /// frequency. In that case, the multiplicator should be used as the DDR 3x clock divisor. + /// The DDR 2x clock divisor should be set so that the resulting clock is 2/3 of the DDR + /// operating frequency. + /// + /// # Safety + /// + /// This should only be called once during start-up. It accesses the SLCR register. + pub unsafe fn new_with_2x_3x_init(ref_clk: Hertz, ddr_3x_div: u6, ddr_2x_div: u6) -> Self { + unsafe { Self::configure_2x_3x_clk(ddr_3x_div, ddr_2x_div) }; + Self { + ref_clk, + ddr_3x_clk: ref_clk / ddr_3x_div.as_u32(), + ddr_2x_clk: ref_clk / ddr_2x_div.as_u32(), + } + } + /// Reference clock provided by DDR PLL which is used to calculate all other clock frequencies. pub const fn ref_clk(&self) -> Hertz { self.ref_clk } + /// DDR 3x clock which is used by the DRAM and must be set to the operating frequency. pub fn ddr_3x_clk(&self) -> Hertz { self.ddr_3x_clk } + /// DDR 2x clock is used by the interconnect and is typically set to 2/3 of the operating + /// frequency. pub fn ddr_2x_clk(&self) -> Hertz { self.ddr_2x_clk } @@ -201,9 +249,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..126eaa0 --- /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 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_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::PllControl, + pll_cfg_reg: *mut zynq7000::slcr::clocks::PllConfig, +) { + // 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/ddr/ll.rs b/zynq7000-hal/src/ddr/ll.rs new file mode 100644 index 0000000..a2fbc88 --- /dev/null +++ b/zynq7000-hal/src/ddr/ll.rs @@ -0,0 +1,334 @@ +//! Low-level DDR configuration module. +use arbitrary_int::{Number, u2, u3, u6}; +use zynq7000::ddrc::{MmioDdrController, regs::*}; +use zynq7000::slcr::{clocks::DciClockControl, ddriob::DdriobConfig}; + +use crate::{clocks::DdrClocks, time::Hertz}; + +const DCI_MAX_FREQ: Hertz = Hertz::from_raw(10_000_000); + +// These values were extracted from the ps7_init files and are not documented in the TMR. +// zynq-rs uses the same values.. I assume they are constant. + +pub const DRIVE_SLEW_ADDR_CFG: u32 = 0x0018_c61c; +pub const DRIVE_SLEW_DATA_CFG: u32 = 0x00f9_861c; +pub const DRIVE_SLEW_DIFF_CFG: u32 = 0x00f9_861c; +pub const DRIVE_SLEW_CLOCK_CFG: u32 = 0x00f9_861c; + +#[derive(Debug, Clone, Copy)] +pub struct DciClkConfig { + div0: u6, + div1: u6, +} + +/// Calculate the required DCI divisors for the given DDR clock. +pub fn calculate_dci_divisors(ddr_clks: &DdrClocks) -> DciClkConfig { + calculate_dci_divisors_with_ddr_clk(ddr_clks.ref_clk()) +} + +/// Calculate the required DCI divisors for the given DDR clock frequency. +pub fn calculate_dci_divisors_with_ddr_clk(ddr_clk: Hertz) -> DciClkConfig { + let target_div = ddr_clk.raw().div_ceil(DCI_MAX_FREQ.raw()); + let mut config = DciClkConfig { + div0: u6::new(u6::MAX.value()), + div1: u6::new(u6::MAX.value()), + }; + + let mut best_error = 0; + for divisor0 in 1..63 { + for divisor1 in 1..63 { + let current_div = (divisor0 as u32) * (divisor1 as u32); + let error = current_div.abs_diff(target_div); + if error < best_error { + config.div0 = u6::new(divisor0 as u8); + config.div1 = u6::new(divisor1 as u8); + best_error = error; + } + } + } + config +} + +/// Configure the DCI module by configure its clock divisors and enabling it. +/// +/// # Safety +/// +/// This function writes to DCI related registers. It should only be called once during +/// DDR initialization. +pub unsafe fn configure_dci(ddr_clk: &DdrClocks) { + let cfg = calculate_dci_divisors(ddr_clk); + // Safety: Only writes to DCI clock related registers. + unsafe { + crate::Slcr::with(|slcr| { + slcr.clk_ctrl().write_dci_clk_ctrl( + DciClockControl::builder() + .with_divisor_1(cfg.div1) + .with_divisor_0(cfg.div0) + .with_clk_act(true) + .build(), + ); + }); + } +} + +/// Calibrates the IOB impedance for DDR3 memory according to to TRM p.325, DDR IOB Impedance +/// calibration. +/// +/// This function will also enable the DCI clock with the provided clock configuration. +/// You can use [calculate_dci_divisors] to calculate the divisor values for the given DDR clock, +/// or you can hardcode the values if they are fixed. +/// +/// Polling for completion can be disabled. The calibration takes 1-2 ms according to the TRM, so +/// the user can set other configuration values which are not reliant on DDR operation before +/// polling for completion. +/// +/// # Safety +/// +/// This function writes to the DDR IOB related registers. It should only be called once during +/// DDR initialization. +pub unsafe fn calibrate_iob_impedance_for_ddr3(dci_clk_cfg: DciClkConfig, poll_for_done: bool) { + unsafe { + calibrate_iob_impedance( + dci_clk_cfg, + u3::new(0), + u2::new(0), + u3::new(0b001), + u3::new(0), + u2::new(0), + poll_for_done, + ); + } +} + +/// Calibrates the IOB impedance according to to TRM p.325, DDR IOB Impedance calibration. +/// +/// This function will also enable the DCI clock with the provided clock configuration. +/// You can use [calculate_dci_divisors] to calculate the divisor values for the given DDR clock, +/// or you can hardcode the values if they are fixed. +/// +/// Polling for completion can be disabled. The calibration takes 1-2 ms according to the TRM, so +/// the user can set other configuration values which are not reliant on DDR operation before +/// polling for completion. +/// +/// # Safety +/// +/// This function writes to the DDR IOB related registers. It should only be called once during +/// DDR initialization. +pub unsafe fn calibrate_iob_impedance( + dci_clk_cfg: DciClkConfig, + pref_opt2: u3, + pref_opt1: u2, + nref_opt4: u3, + nref_opt2: u3, + nref_opt1: u2, + poll_for_done: bool, +) { + // Safety: Only writes to DDR IOB related registers. + let mut slcr = unsafe { crate::slcr::Slcr::steal() }; + slcr.modify(|slcr| { + slcr.clk_ctrl().write_dci_clk_ctrl( + DciClockControl::builder() + .with_divisor_1(dci_clk_cfg.div1) + .with_divisor_0(dci_clk_cfg.div0) + .with_clk_act(true) + .build(), + ); + let mut ddriob = slcr.ddriob(); + ddriob.modify_dci_ctrl(|mut val| { + val.set_reset(true); + val + }); + ddriob.modify_dci_ctrl(|mut val| { + val.set_reset(false); + val + }); + ddriob.modify_dci_ctrl(|mut val| { + val.set_reset(true); + val + }); + ddriob.modify_dci_ctrl(|mut val| { + val.set_pref_opt2(pref_opt2); + val.set_pref_opt1(pref_opt1); + val.set_nref_opt4(nref_opt4); + val.set_nref_opt2(nref_opt2); + val.set_nref_opt1(nref_opt1); + val + }); + ddriob.modify_dci_ctrl(|mut val| { + val.set_update_control(false); + val + }); + ddriob.modify_dci_ctrl(|mut val| { + val.set_enable(true); + val + }); + if poll_for_done { + while !slcr.ddriob().read_dci_status().done() { + // Wait for the DDR IOB impedance calibration to complete. + cortex_ar::asm::nop(); + } + } + }); +} + +/// Static configuration for DDR IOBs. +pub struct DdriobConfigSet { + pub addr0: DdriobConfig, + pub addr1: DdriobConfig, + pub data0: DdriobConfig, + pub data1: DdriobConfig, + pub diff0: DdriobConfig, + pub diff1: DdriobConfig, + pub clock: DdriobConfig, +} + +/// # Safety +/// +/// This function writes to the IOB related registers. It should only be called once during +/// DDR initialization. +pub unsafe fn configure_iob(cfg_set: &DdriobConfigSet) { + // Safety: Only configures IOB related registers. + let mut slcr = unsafe { crate::slcr::Slcr::steal() }; + slcr.modify(|slcr| { + let mut ddriob = slcr.ddriob(); + ddriob.write_ddriob_addr0(cfg_set.addr0); + ddriob.write_ddriob_addr1(cfg_set.addr1); + + ddriob.write_ddriob_data0(cfg_set.data0); + ddriob.write_ddriob_data1(cfg_set.data1); + + ddriob.write_ddriob_diff0(cfg_set.diff0); + ddriob.write_ddriob_diff1(cfg_set.diff1); + + ddriob.write_ddriob_clock(cfg_set.clock); + + // These values were extracted from the ps7_init files and are not documented in the TRM. + // zynq-rs uses the same values.. I assume they are constant. + ddriob.write_ddriob_drive_slew_addr(DRIVE_SLEW_ADDR_CFG); + ddriob.write_ddriob_drive_slew_data(DRIVE_SLEW_DATA_CFG); + ddriob.write_ddriob_drive_slew_diff(DRIVE_SLEW_DIFF_CFG); + ddriob.write_ddriob_drive_slew_clock(DRIVE_SLEW_CLOCK_CFG); + }); +} + +/// Full static DDRC configuration set. +pub struct DdrcConfigSet { + pub ctrl: DdrcControl, + pub two_rank: TwoRankConfig, + pub hpr: LprHprQueueControl, + pub lpr: LprHprQueueControl, + pub wr: WriteQueueControl, + pub dram_param_0: DramParamReg0, + pub dram_param_1: DramParamReg1, + pub dram_param_2: DramParamReg2, + pub dram_param_3: DramParamReg3, + pub dram_param_4: DramParamReg4, + pub dram_init_param: DramInitParam, + pub dram_emr: DramEmr, + pub dram_emr_mr: DramEmrMr, + pub dram_burst8_rdwr: DramBurst8ReadWrite, + pub disable_dq: DisableDq, + pub dram_addr_map_bank: DramAddrMapBank, + pub dram_addr_map_col: DramAddrMapColumn, + pub dram_addr_map_row: DramAddrMapRow, + pub dram_odt: DramOdt, + pub phy_cmd_timeout_rddata_cpt: PhyCmdTimeoutRdDataCpt, + pub dll_calib: DllCalib, + pub odt_delay_hold: OdtDelayHold, + pub ctrl_reg1: CtrlReg1, + pub ctrl_reg2: CtrlReg2, + pub ctrl_reg3: CtrlReg3, + pub ctrl_reg4: CtrlReg4, + pub ctrl_reg5: CtrlReg5, + pub ctrl_reg6: CtrlReg6, + pub che_t_zq: CheTZq, + pub che_t_zq_short_interval_reg: CheTZqShortInterval, + pub deep_powerdown: DeepPowerdown, + pub reg_2c: Reg2c, + pub reg_2d: Reg2d, + pub dfi_timing: DfiTiming, + pub che_ecc_ctrl: CheEccControl, + pub ecc_scrub: EccScrub, + pub phy_receiver_enable: PhyReceiverEnable, + pub phy_config: [PhyConfig; 4], + pub phy_init_ratio: [PhyInitRatio; 4], + pub phy_rd_dqs_config: [PhyDqsConfig; 4], + pub phy_wr_dqs_config: [PhyDqsConfig; 4], + pub phy_we_cfg: [PhyWriteEnableConfig; 4], + pub phy_wr_data_slv: [PhyWriteDataSlaveConfig; 4], + pub reg64: Reg64, + pub reg65: Reg65, + pub page_mask: u32, + pub axi_priority_wr_port: [AxiPriorityWritePort; 4], + pub axi_priority_rd_port: [AxiPriorityReadPort; 4], + pub lpddr_ctrl_0: LpddrControl0, + pub lpddr_ctrl_1: LpddrControl1, + pub lpddr_ctrl_2: LpddrControl2, + pub lpddr_ctrl_3: LpddrControl3, +} + +/// This low-level function sets all the configuration registers. +/// +/// It does NOT take care of taking the DDR controller out of reset and polling for DDR +/// configuration completion. +pub fn configure_ddr_config(ddrc: &mut MmioDdrController<'static>, cfg_set: &DdrcConfigSet) { + ddrc.write_ddrc_ctrl(cfg_set.ctrl); + // Write all configuration registers. + ddrc.write_two_rank_cfg(cfg_set.two_rank); + ddrc.write_hpr_queue_ctrl(cfg_set.hpr); + ddrc.write_lpr_queue_ctrl(cfg_set.lpr); + ddrc.write_wr_reg(cfg_set.wr); + ddrc.write_dram_param_reg0(cfg_set.dram_param_0); + ddrc.write_dram_param_reg1(cfg_set.dram_param_1); + ddrc.write_dram_param_reg2(cfg_set.dram_param_2); + ddrc.write_dram_param_reg3(cfg_set.dram_param_3); + ddrc.write_dram_param_reg4(cfg_set.dram_param_4); + ddrc.write_dram_init_param(cfg_set.dram_init_param); + ddrc.write_dram_emr(cfg_set.dram_emr); + ddrc.write_dram_emr_mr(cfg_set.dram_emr_mr); + ddrc.write_dram_burst8_rdwr(cfg_set.dram_burst8_rdwr); + ddrc.write_dram_disable_dq(cfg_set.disable_dq); + ddrc.write_phy_cmd_timeout_rddata_cpt(cfg_set.phy_cmd_timeout_rddata_cpt); + ddrc.write_dll_calib(cfg_set.dll_calib); + ddrc.write_odt_delay_hold(cfg_set.odt_delay_hold); + ddrc.write_ctrl_reg1(cfg_set.ctrl_reg1); + ddrc.write_ctrl_reg2(cfg_set.ctrl_reg2); + ddrc.write_ctrl_reg3(cfg_set.ctrl_reg3); + ddrc.write_ctrl_reg4(cfg_set.ctrl_reg4); + ddrc.write_ctrl_reg5(cfg_set.ctrl_reg5); + ddrc.write_ctrl_reg6(cfg_set.ctrl_reg6); + ddrc.write_che_t_zq(cfg_set.che_t_zq); + ddrc.write_che_t_zq_short_interval_reg(cfg_set.che_t_zq_short_interval_reg); + ddrc.write_deep_powerdown_reg(cfg_set.deep_powerdown); + ddrc.write_reg_2c(cfg_set.reg_2c); + ddrc.write_reg_2d(cfg_set.reg_2d); + ddrc.write_dfi_timing(cfg_set.dfi_timing); + ddrc.write_che_ecc_control(cfg_set.che_ecc_ctrl); + ddrc.write_ecc_scrub(cfg_set.ecc_scrub); + ddrc.write_phy_receiver_enable(cfg_set.phy_receiver_enable); + for i in 0..4 { + // Safety: Indexes are valid. + unsafe { + ddrc.write_phy_config_unchecked(i, cfg_set.phy_config[i]); + ddrc.write_phy_init_ratio_unchecked(i, cfg_set.phy_init_ratio[i]); + ddrc.write_phy_rd_dqs_cfg_unchecked(i, cfg_set.phy_rd_dqs_config[i]); + ddrc.write_phy_wr_dqs_cfg_unchecked(i, cfg_set.phy_wr_dqs_config[i]); + ddrc.write_phy_we_cfg_unchecked(i, cfg_set.phy_we_cfg[i]); + ddrc.write_phy_wr_data_slave_unchecked(i, cfg_set.phy_wr_data_slv[i]); + } + } + ddrc.write_reg_64(cfg_set.reg64); + ddrc.write_reg_65(cfg_set.reg65); + ddrc.write_page_mask(cfg_set.page_mask); + for i in 0..4 { + // Safety: Indexes are valid. + unsafe { + ddrc.write_axi_priority_wr_port_unchecked(i, cfg_set.axi_priority_wr_port[i]); + ddrc.write_axi_priority_rd_port_unchecked(i, cfg_set.axi_priority_rd_port[i]); + } + } + ddrc.write_lpddr_ctrl_0(cfg_set.lpddr_ctrl_0); + ddrc.write_lpddr_ctrl_1(cfg_set.lpddr_ctrl_1); + ddrc.write_lpddr_ctrl_2(cfg_set.lpddr_ctrl_2); + ddrc.write_lpddr_ctrl_3(cfg_set.lpddr_ctrl_3); +} diff --git a/zynq7000-hal/src/ddr/mod.rs b/zynq7000-hal/src/ddr/mod.rs new file mode 100644 index 0000000..825e648 --- /dev/null +++ b/zynq7000-hal/src/ddr/mod.rs @@ -0,0 +1,227 @@ +use arbitrary_int::u6; +use zynq7000::ddrc::MmioDdrController; + +use crate::{ + BootMode, + clocks::pll::{PllConfig, configure_ddr_pll}, + time::Hertz, +}; + +pub mod ll; + +pub use ll::{DdrcConfigSet, DdriobConfigSet}; + +#[derive(Debug, Clone, Copy)] +pub struct DdrClockSetupConfig { + pub ps_clk: Hertz, + pub ddr_clk: Hertz, + pub ddr_3x_div: u6, + pub ddr_2x_div: u6, +} + +impl DdrClockSetupConfig { + pub const fn new(ps_clk: Hertz, ddr_clk: Hertz, ddr_3x_div: u6, ddr_2x_div: u6) -> Self { + Self { + ps_clk, + ddr_clk, + ddr_3x_div, + ddr_2x_div, + } + } +} + +/// This completely sets up the DDR module for DDR3 operation. +/// +/// It performs the following steps accoridng to the functional programming model of the +/// DDR memory controller, TRM p.323. +/// +/// 1. Configures the DDR PLL to the target clock frequency. +/// 2. Configures the DDR clocks based on the user-provided configuration. +/// 3. Configures and sets up the DDR I/O buffers (DDR IOB) module. +/// 4. Configures and sets up the DDR controller (DDRC) module. +/// +/// This function consumes the DDRC register block once and thus provides a safe interface for DDR +/// initialization. +pub fn configure_ddr_for_ddr3( + mut ddrc_regs: MmioDdrController<'static>, + boot_mode: BootMode, + clk_setup_cfg: DdrClockSetupConfig, + ddriob_cfg: &DdriobConfigSet, + ddr_cfg: &DdrcConfigSet, +) { + // Set the DDR PLL output frequency to an even multiple of the operating frequency, + // as recommended by the DDR documentation. + configure_ddr_pll( + boot_mode, + PllConfig::new_from_target_clock(clk_setup_cfg.ps_clk, clk_setup_cfg.ddr_clk).unwrap(), + ); + // Safety: Only done once here during start-up. + let ddr_clks = unsafe { + crate::clocks::DdrClocks::new_with_2x_3x_init( + clk_setup_cfg.ddr_clk, + clk_setup_cfg.ddr_3x_div, + clk_setup_cfg.ddr_2x_div, + ) + }; + let dci_clk_cfg = ll::calculate_dci_divisors(&ddr_clks); + + ddrc_regs.modify_ddrc_ctrl(|mut val| { + val.set_soft_reset(zynq7000::ddrc::regs::SoftReset::Reset); + val + }); + + // Safety: This is only called once during DDR initialization. + unsafe { + ll::configure_iob(ddriob_cfg); + // Do not wait for completion, it takes a bit of time. We can set all the DDR config registers + // before polling for completion. + ll::calibrate_iob_impedance_for_ddr3(dci_clk_cfg, false); + } + ll::configure_ddr_config(&mut ddrc_regs, ddr_cfg); + // Safety: This is only called once during DDR initialization, and we only modify DDR related + // registers. + let slcr = unsafe { crate::slcr::Slcr::steal() }; + let ddriob_shared = slcr.regs().ddriob_shared(); + // Wait for DDR IOB impedance calibration to complete first. + while !ddriob_shared.read_dci_status().done() { + cortex_ar::asm::nop(); + } + log::debug!("DDR IOB impedance calib done"); + + // Now take the DDR out of reset. + ddrc_regs.modify_ddrc_ctrl(|mut val| { + val.set_soft_reset(zynq7000::ddrc::regs::SoftReset::Active); + val + }); + // Wait until the DDR setup has completed. + while ddrc_regs.read_mode_status().operating_mode() + != zynq7000::ddrc::regs::OperatingMode::NormalOperation + { + // Wait for the soft reset to complete. + cortex_ar::asm::nop(); + } +} + +pub mod memtest { + #[derive(Debug, thiserror::Error)] + pub enum MemTestError { + #[error("memory address is not aligned to 4 bytes")] + AddrNotAligned, + #[error("memory test error")] + Memory { + addr: usize, + expected: u32, + found: u32, + }, + } + + /// # Safety + /// + /// This tests writes and reads on a memory block starting at the base address + /// with the size `words` times 4. + pub unsafe fn walking_zero_test(base_addr: usize, words: usize) -> Result<(), MemTestError> { + unsafe { walking_value_test(true, base_addr, words) } + } + + /// # Safety + /// + /// This tests writes and reads on a memory block starting at the base address + /// with the size `words` times 4. + pub unsafe fn walking_one_test(base_addr: usize, words: usize) -> Result<(), MemTestError> { + unsafe { walking_value_test(true, base_addr, words) } + } + + /// # Safety + /// + /// This tests writes and reads on a memory block starting at the base address + /// with the size `words` times 4. + pub unsafe fn walking_value_test( + walking_zero: bool, + base_addr: usize, + words: usize, + ) -> Result<(), MemTestError> { + if words == 0 { + return Ok(()); + } + if !base_addr.is_multiple_of(4) { + return Err(MemTestError::AddrNotAligned); + } + let base_ptr = base_addr as *mut u32; + + // For each bit position 0..31 generate pattern = 1 << bit + for bit in 0..32 { + let pattern = if walking_zero { + !(1u32 << bit) + } else { + 1u32 << bit + }; + + // write pass + for i in 0..words { + unsafe { + let p = base_ptr.add(i); + core::ptr::write_volatile(p, pattern); + } + } + + // read/verify pass + for i in 0..words { + let val; + unsafe { + let p = base_ptr.add(i) as *const u32; + val = core::ptr::read_volatile(p); + } + if val != pattern { + return Err(MemTestError::Memory { + addr: base_addr + i * 4, + expected: pattern, + found: val, + }); + } + } + } + Ok(()) + } + + /// # Safety + /// + /// This tests writes and reads on a memory block starting at the base address + /// with the size `words` times 4. + pub unsafe fn checkerboard_test(base_addr: usize, words: usize) -> Result<(), MemTestError> { + if words == 0 { + return Ok(()); + } + if !base_addr.is_multiple_of(4) { + return Err(MemTestError::AddrNotAligned); + } + + let base_ptr = base_addr as *mut u32; + let patterns = [0xAAAAAAAAu32, 0x55555555u32]; + + for &pattern in &patterns { + // Write pass + for i in 0..words { + let value = if i % 2 == 0 { pattern } else { !pattern }; + unsafe { + core::ptr::write_volatile(base_ptr.add(i), value); + } + } + + // Read/verify pass + for i in 0..words { + let expected = if i % 2 == 0 { pattern } else { !pattern }; + let val = unsafe { core::ptr::read_volatile(base_ptr.add(i)) }; + + if val != expected { + return Err(MemTestError::Memory { + addr: base_addr + i * 4, + expected, + found: val, + }); + } + } + } + + Ok(()) + } +} diff --git a/zynq7000-hal/src/eth/ll.rs b/zynq7000-hal/src/eth/ll.rs index 1208a32..1f50d65 100644 --- a/zynq7000-hal/src/eth/ll.rs +++ b/zynq7000-hal/src/eth/ll.rs @@ -4,7 +4,7 @@ use zynq7000::{ slcr::reset::EthernetReset, }; -use crate::{clocks::IoClocks, enable_amba_periph_clk, slcr::Slcr, time::Hertz}; +use crate::{clocks::IoClocks, enable_amba_peripheral_clock, slcr::Slcr, time::Hertz}; use super::{EthernetId, PsEthernet as _}; @@ -239,7 +239,7 @@ impl EthernetLowLevel { EthernetId::Eth0 => crate::PeriphSelect::Gem0, EthernetId::Eth1 => crate::PeriphSelect::Gem1, }; - enable_amba_periph_clk(periph_sel); + enable_amba_peripheral_clock(periph_sel); } /// Completely configures the clock based on the provided [ClockConfig]. diff --git a/zynq7000-hal/src/eth/mod.rs b/zynq7000-hal/src/eth/mod.rs index 0c95157..8c16733 100644 --- a/zynq7000-hal/src/eth/mod.rs +++ b/zynq7000-hal/src/eth/mod.rs @@ -105,132 +105,132 @@ impl PsEthernet for MmioEthernet<'static> { } } -pub trait TxClk: MioPinMarker { +pub trait TxClockPin: MioPinMarker { const ETH_ID: EthernetId; } -pub trait TxCtrl: MioPinMarker { +pub trait TxControlPin: MioPinMarker { const ETH_ID: EthernetId; } -pub trait TxData0: MioPinMarker { +pub trait TxData0Pin: MioPinMarker { const ETH_ID: EthernetId; } -pub trait TxData1: MioPinMarker { +pub trait TxData1Pin: MioPinMarker { const ETH_ID: EthernetId; } -pub trait TxData2: MioPinMarker { +pub trait TxData2Pin: MioPinMarker { const ETH_ID: EthernetId; } -pub trait TxData3: MioPinMarker { +pub trait TxData3Pin: MioPinMarker { const ETH_ID: EthernetId; } -pub trait RxClk: MioPinMarker { +pub trait RxClockPin: MioPinMarker { const ETH_ID: EthernetId; } -pub trait RxCtrl: MioPinMarker { +pub trait RxControlPin: MioPinMarker { const ETH_ID: EthernetId; } -pub trait RxData0: MioPinMarker { +pub trait RxData0Pin: MioPinMarker { const ETH_ID: EthernetId; } -pub trait RxData1: MioPinMarker { +pub trait RxData1Pin: MioPinMarker { const ETH_ID: EthernetId; } -pub trait RxData2: MioPinMarker { +pub trait RxData2Pin: MioPinMarker { const ETH_ID: EthernetId; } -pub trait RxData3: MioPinMarker { +pub trait RxData3Pin: MioPinMarker { const ETH_ID: EthernetId; } -pub trait MdClk: MioPinMarker {} -pub trait MdIo: MioPinMarker {} +pub trait MdClockPin: MioPinMarker {} +pub trait MdIoPin: MioPinMarker {} -impl MdClk for Pin {} -impl MdIo for Pin {} +impl MdClockPin for Pin {} +impl MdIoPin for Pin {} #[cfg(not(feature = "7z010-7z007s-clg225"))] -impl TxClk for Pin { +impl TxClockPin for Pin { const ETH_ID: EthernetId = EthernetId::Eth0; } #[cfg(not(feature = "7z010-7z007s-clg225"))] -impl TxCtrl for Pin { +impl TxControlPin for Pin { const ETH_ID: EthernetId = EthernetId::Eth0; } #[cfg(not(feature = "7z010-7z007s-clg225"))] -impl TxData0 for Pin { +impl TxData0Pin for Pin { const ETH_ID: EthernetId = EthernetId::Eth0; } #[cfg(not(feature = "7z010-7z007s-clg225"))] -impl TxData1 for Pin { +impl TxData1Pin for Pin { const ETH_ID: EthernetId = EthernetId::Eth0; } #[cfg(not(feature = "7z010-7z007s-clg225"))] -impl TxData2 for Pin { +impl TxData2Pin for Pin { const ETH_ID: EthernetId = EthernetId::Eth0; } #[cfg(not(feature = "7z010-7z007s-clg225"))] -impl TxData3 for Pin { +impl TxData3Pin for Pin { const ETH_ID: EthernetId = EthernetId::Eth0; } #[cfg(not(feature = "7z010-7z007s-clg225"))] -impl RxClk for Pin { +impl RxClockPin for Pin { const ETH_ID: EthernetId = EthernetId::Eth0; } #[cfg(not(feature = "7z010-7z007s-clg225"))] -impl RxCtrl for Pin { +impl RxControlPin for Pin { const ETH_ID: EthernetId = EthernetId::Eth0; } #[cfg(not(feature = "7z010-7z007s-clg225"))] -impl RxData0 for Pin { +impl RxData0Pin for Pin { const ETH_ID: EthernetId = EthernetId::Eth0; } #[cfg(not(feature = "7z010-7z007s-clg225"))] -impl RxData1 for Pin { +impl RxData1Pin for Pin { const ETH_ID: EthernetId = EthernetId::Eth0; } #[cfg(not(feature = "7z010-7z007s-clg225"))] -impl RxData2 for Pin { +impl RxData2Pin for Pin { const ETH_ID: EthernetId = EthernetId::Eth0; } #[cfg(not(feature = "7z010-7z007s-clg225"))] -impl RxData3 for Pin { +impl RxData3Pin for Pin { const ETH_ID: EthernetId = EthernetId::Eth0; } -impl TxClk for Pin { +impl TxClockPin for Pin { const ETH_ID: EthernetId = EthernetId::Eth1; } -impl TxCtrl for Pin { +impl TxControlPin for Pin { const ETH_ID: EthernetId = EthernetId::Eth1; } -impl TxData0 for Pin { +impl TxData0Pin for Pin { const ETH_ID: EthernetId = EthernetId::Eth1; } -impl TxData1 for Pin { +impl TxData1Pin for Pin { const ETH_ID: EthernetId = EthernetId::Eth1; } -impl TxData2 for Pin { +impl TxData2Pin for Pin { const ETH_ID: EthernetId = EthernetId::Eth1; } -impl TxData3 for Pin { +impl TxData3Pin for Pin { const ETH_ID: EthernetId = EthernetId::Eth1; } -impl RxClk for Pin { +impl RxClockPin for Pin { const ETH_ID: EthernetId = EthernetId::Eth1; } -impl RxCtrl for Pin { +impl RxControlPin for Pin { const ETH_ID: EthernetId = EthernetId::Eth1; } -impl RxData0 for Pin { +impl RxData0Pin for Pin { const ETH_ID: EthernetId = EthernetId::Eth1; } -impl RxData1 for Pin { +impl RxData1Pin for Pin { const ETH_ID: EthernetId = EthernetId::Eth1; } -impl RxData2 for Pin { +impl RxData2Pin for Pin { const ETH_ID: EthernetId = EthernetId::Eth1; } -impl RxData3 for Pin { +impl RxData3Pin for Pin { const ETH_ID: EthernetId = EthernetId::Eth1; } @@ -328,30 +328,30 @@ impl Ethernet { /// configuring all the necessary MIO pins. #[allow(clippy::too_many_arguments)] pub fn new_with_mio< - TxClkPin: TxClk, - TxCtrlPin: TxCtrl, - TxData0Pin: TxData0, - TxData1Pin: TxData1, - TxData2Pin: TxData2, - TxData3Pin: TxData3, - RxClkPin: RxClk, - RxCtrlPin: RxCtrl, - RxData0Pin: RxData0, - RxData1Pin: RxData1, - RxData2Pin: RxData2, - RxData3Pin: RxData3, - MdClkPin: MdClk, - MdIoPin: MdIo, + TxClock: TxClockPin, + TxControl: TxControlPin, + TxData0: TxData0Pin, + TxData1: TxData1Pin, + TxData2: TxData2Pin, + TxData3: TxData3Pin, + RxClock: RxClockPin, + RxControl: RxControlPin, + RxData0: RxData0Pin, + RxData1: RxData1Pin, + RxData2: RxData2Pin, + RxData3: RxData3Pin, + MdClock: MdClockPin, + MdIo: MdIoPin, >( mut ll: ll::EthernetLowLevel, config: EthernetConfig, - tx_clk: TxClkPin, - tx_ctrl: TxCtrlPin, - tx_data: (TxData0Pin, TxData1Pin, TxData2Pin, TxData3Pin), - rx_clk: RxClkPin, - rx_ctrl: RxCtrlPin, - rx_data: (RxData0Pin, RxData1Pin, RxData2Pin, RxData3Pin), - md_pins: Option<(MdClkPin, MdIoPin)>, + tx_clk: TxClock, + tx_ctrl: TxControl, + tx_data: (TxData0, TxData1, TxData2, TxData3), + rx_clk: RxClock, + rx_ctrl: RxControl, + rx_data: (RxData0, RxData1, RxData2, RxData3), + md_pins: Option<(MdClock, MdIo)>, ) -> Self { Self::common_init(&mut ll, config.mac_address); let tx_mio_config = zynq7000::slcr::mio::Config::builder() diff --git a/zynq7000-hal/src/gpio/mio.rs b/zynq7000-hal/src/gpio/mio.rs index 7602f5c..9fd8f05 100644 --- a/zynq7000-hal/src/gpio/mio.rs +++ b/zynq7000-hal/src/gpio/mio.rs @@ -174,7 +174,7 @@ pin_id!(Mio51, 51); pin_id!(Mio52, 52); pin_id!(Mio53, 53); -pub trait MioPinMarker { +pub trait MioPinMarker: crate::sealed::Sealed { fn offset(&self) -> usize; } @@ -379,3 +379,5 @@ impl MioPinMarker for Pin { I::OFFSET } } + +impl crate::sealed::Sealed for Pin {} diff --git a/zynq7000-hal/src/gpio/mod.rs b/zynq7000-hal/src/gpio/mod.rs index 9e11e3d..27ce628 100644 --- a/zynq7000-hal/src/gpio/mod.rs +++ b/zynq7000-hal/src/gpio/mod.rs @@ -17,7 +17,7 @@ use ll::PinOffset; use mio::{MioPinMarker, MuxConfig}; use crate::gpio::ll::LowLevelGpio; -use crate::{enable_amba_periph_clk, slcr::Slcr}; +use crate::{enable_amba_peripheral_clock, slcr::Slcr}; pub use embedded_hal::digital::PinState; use zynq7000::{gpio::MmioGpio, slcr::reset::GpioClockReset}; @@ -33,7 +33,7 @@ pub struct GpioPins { impl GpioPins { pub fn new(gpio: MmioGpio) -> Self { - enable_amba_periph_clk(crate::PeriphSelect::Gpio); + enable_amba_peripheral_clock(crate::PeriphSelect::Gpio); Self { mio: mio::Pins::new(unsafe { gpio.clone() }), emio: emio::Pins::new(gpio), diff --git a/zynq7000-hal/src/gtc.rs b/zynq7000-hal/src/gtc.rs index eea056b..a59989a 100644 --- a/zynq7000-hal/src/gtc.rs +++ b/zynq7000-hal/src/gtc.rs @@ -156,7 +156,7 @@ impl GlobalTimerCounter { } } -/// GTC can be used for blocking delays. +/// GTC can also be used for blocking delays. impl embedded_hal::delay::DelayNs for GlobalTimerCounter { fn delay_ns(&mut self, ns: u32) { if self.cpu_3x2x_clock.is_none() { diff --git a/zynq7000-hal/src/i2c.rs b/zynq7000-hal/src/i2c.rs index 29bf5f5..d6ed448 100644 --- a/zynq7000-hal/src/i2c.rs +++ b/zynq7000-hal/src/i2c.rs @@ -11,7 +11,7 @@ use crate::gpio::mio::{ Mio41, Mio42, Mio43, Mio44, Mio45, Mio46, Mio47, Mio50, Mio51, }; use crate::{ - enable_amba_periph_clk, + enable_amba_peripheral_clock, gpio::{ IoPeriphPin, mio::{ @@ -348,7 +348,7 @@ impl I2c { I2cId::I2c0 => crate::PeriphSelect::I2c0, I2cId::I2c1 => crate::PeriphSelect::I2c1, }; - enable_amba_periph_clk(periph_sel); + enable_amba_peripheral_clock(periph_sel); //reset(id); regs.write_cr( Control::builder() diff --git a/zynq7000-hal/src/lib.rs b/zynq7000-hal/src/lib.rs index 5913068..f92a800 100644 --- a/zynq7000-hal/src/lib.rs +++ b/zynq7000-hal/src/lib.rs @@ -13,10 +13,15 @@ extern crate alloc; use slcr::Slcr; -use zynq7000::slcr::LevelShifterRegister; +use zynq7000::{ + SpiClockPhase, SpiClockPolarity, + slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister}, +}; +pub mod boot_image; pub mod cache; pub mod clocks; +pub mod ddr; pub mod eth; pub mod gic; pub mod gpio; @@ -25,6 +30,8 @@ pub mod i2c; pub mod l2_cache; pub mod log; pub mod prelude; +pub mod priv_tim; +pub mod qspi; pub mod slcr; pub mod spi; pub mod time; @@ -43,36 +50,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) @@ -84,21 +81,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 } } @@ -142,7 +135,7 @@ pub enum PeriphSelect { /// Enable the AMBA peripheral clock, which is required to read the registers of a peripheral /// block. #[inline] -pub fn enable_amba_periph_clk(select: PeriphSelect) { +pub fn enable_amba_peripheral_clock(select: PeriphSelect) { unsafe { Slcr::with(|regs| { regs.clk_ctrl().modify_aper_clk_ctrl(|mut val| { @@ -205,6 +198,30 @@ pub fn disable_amba_periph_clk(select: PeriphSelect) { } } +#[inline] +const fn spi_mode_const_to_cpol_cpha( + mode: embedded_hal::spi::Mode, +) -> (SpiClockPolarity, SpiClockPhase) { + match mode { + embedded_hal::spi::MODE_0 => ( + SpiClockPolarity::QuiescentLow, + SpiClockPhase::ActiveOutsideOfWord, + ), + embedded_hal::spi::MODE_1 => ( + SpiClockPolarity::QuiescentLow, + SpiClockPhase::InactiveOutsideOfWord, + ), + embedded_hal::spi::MODE_2 => ( + SpiClockPolarity::QuiescentHigh, + SpiClockPhase::ActiveOutsideOfWord, + ), + embedded_hal::spi::MODE_3 => ( + SpiClockPolarity::QuiescentHigh, + SpiClockPhase::InactiveOutsideOfWord, + ), + } +} + #[allow(dead_code)] pub(crate) mod sealed { pub trait Sealed {} diff --git a/zynq7000-hal/src/priv_tim.rs b/zynq7000-hal/src/priv_tim.rs new file mode 100644 index 0000000..d5f8e08 --- /dev/null +++ b/zynq7000-hal/src/priv_tim.rs @@ -0,0 +1,108 @@ +use core::{marker::PhantomData, sync::atomic::AtomicBool}; + +use zynq7000::priv_tim::InterruptStatus; + +use crate::{clocks::ArmClocks, time::Hertz}; + +static CORE_0_TIM_TAKEN: AtomicBool = AtomicBool::new(false); +static CORE_1_TIM_TAKEN: AtomicBool = AtomicBool::new(false); + +/// High-level CPU private timer driver. +pub struct CpuPrivateTimer { + regs: zynq7000::priv_tim::MmioCpuPrivateTimer<'static>, + cpu_3x2x_clock: Hertz, + // Add this marker to explicitely opt-out of Send and Sync. + // + // This is a CPU private timer and thus should not be sent to other threads. + _not_send: PhantomData<*const ()>, +} + +impl CpuPrivateTimer { + /// Take the CPU private timer for a given core. + /// + /// This function can only be called once for each given core. + pub fn take(clocks: &ArmClocks) -> Option { + let mpidr = cortex_ar::register::mpidr::Mpidr::read(); + let core = mpidr.0 & 0xff; + if core != 0 && core != 1 { + return None; + } + if (core == 0 && CORE_0_TIM_TAKEN.swap(true, core::sync::atomic::Ordering::Relaxed)) + || (core == 1 && CORE_1_TIM_TAKEN.swap(true, core::sync::atomic::Ordering::Relaxed)) + { + return None; + } + + Some(Self::steal(clocks)) + } + + /// Create a new CPU private timer driver. + /// + /// # Safety + /// + /// This function allows to potentially create an arbitrary amount of timers for both cores. + /// It also does not check the current core ID. + pub fn steal(clocks: &ArmClocks) -> Self { + Self { + regs: unsafe { zynq7000::priv_tim::CpuPrivateTimer::new_mmio_fixed() }, + cpu_3x2x_clock: clocks.cpu_3x2x_clk(), + _not_send: PhantomData, + } + } + + /// Write reload value which is set by the hardware when the timer reaches zero and + /// auto reload is enabled. + #[inline] + pub fn write_reload(&mut self, value: u32) { + self.regs.write_reload(value); + } + + #[inline] + pub fn write_counter(&mut self, value: u32) { + self.regs.write_counter(value); + } + + #[inline] + pub fn counter(&self) -> u32 { + self.regs.read_counter() + } +} + +impl embedded_hal::delay::DelayNs for CpuPrivateTimer { + fn delay_ns(&mut self, ns: u32) { + // Even for a value of 1000 MHz for CPU 3x2x and u32::MAX for nanoseconds, this will + // never overflow. + let ticks = (ns as u64 * self.cpu_3x2x_clock.raw() as u64) / 1_000_000_000; + + // Split the total delay into manageable chunks (u32::MAX ticks max). + let mut remaining = ticks; + + self.regs.modify_control(|mut val| { + val.set_enable(false); + // The event flag is still set, which is all we care about. + val.set_interrupt_enable(false); + val.set_auto_reload(false); + val + }); + while remaining > 0 { + let chunk = (remaining as u32).min(u32::MAX - 1); + + // Clear the timer flag by writing 1 to it. + self.regs + .write_interrupt_status(InterruptStatus::builder().with_event_flag(true).build()); + + // Load the timer with the chunk value and start it. + self.write_reload(chunk); + self.write_counter(chunk); + self.regs.modify_control(|mut val| { + val.set_enable(true); + val + }); + + // Wait for the timer to count down to zero. + while !self.regs.read_interrupt_status().event_flag() {} + + remaining -= chunk as u64; + } + } +} diff --git a/zynq7000-hal/src/qspi/lqspi_configs.rs b/zynq7000-hal/src/qspi/lqspi_configs.rs new file mode 100644 index 0000000..16ee4d6 --- /dev/null +++ b/zynq7000-hal/src/qspi/lqspi_configs.rs @@ -0,0 +1,234 @@ +// The constants here are also checked/tested at compile time against table 12-3 in the TRM +// p.368. +use arbitrary_int::u3; +use zynq7000::qspi::{InstructionCode, LinearQspiConfig}; + +pub const RD_ONE: LinearQspiConfig = LinearQspiConfig::builder() + .with_enable_linear_mode(true) + .with_both_memories(false) + .with_separate_memory_bus(false) + .with_upper_memory_page(false) + .with_mode_enable(false) + .with_mode_on(false) + .with_mode_bits(0x0) + .with_num_dummy_bytes(u3::new(0x0)) + .with_instruction_code(InstructionCode::Read) + .build(); +const RD_ONE_DEV_RAW: u32 = RD_ONE.raw_value(); +static_assertions::const_assert_eq!(RD_ONE_DEV_RAW, 0x8000_0003); + +pub const RD_TWO: LinearQspiConfig = LinearQspiConfig::builder() + .with_enable_linear_mode(true) + .with_both_memories(true) + .with_separate_memory_bus(true) + .with_upper_memory_page(false) + .with_mode_enable(false) + .with_mode_on(false) + .with_mode_bits(0x0) + .with_num_dummy_bytes(u3::new(0x0)) + .with_instruction_code(InstructionCode::Read) + .build(); +const RD_TWO_RAW: u32 = RD_TWO.raw_value(); +static_assertions::const_assert_eq!(RD_TWO_RAW, 0xE000_0003); + +pub const FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder() + .with_enable_linear_mode(true) + .with_both_memories(false) + .with_separate_memory_bus(false) + .with_upper_memory_page(false) + .with_mode_enable(false) + .with_mode_on(false) + .with_mode_bits(0x0) + .with_num_dummy_bytes(u3::new(0x1)) + .with_instruction_code(InstructionCode::FastRead) + .build(); +const FAST_RD_ONE_RAW: u32 = FAST_RD_ONE.raw_value(); +static_assertions::const_assert_eq!(FAST_RD_ONE_RAW, 0x8000_010B); + +pub const FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder() + .with_enable_linear_mode(true) + .with_both_memories(true) + .with_separate_memory_bus(true) + .with_upper_memory_page(false) + .with_mode_enable(false) + .with_mode_on(false) + .with_mode_bits(0x0) + .with_num_dummy_bytes(u3::new(0x1)) + .with_instruction_code(InstructionCode::FastRead) + .build(); +const FAST_RD_TWO_RAW: u32 = FAST_RD_TWO.raw_value(); +static_assertions::const_assert_eq!(FAST_RD_TWO_RAW, 0xE000_010B); + +pub const DUAL_OUT_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder() + .with_enable_linear_mode(true) + .with_both_memories(false) + .with_separate_memory_bus(false) + .with_upper_memory_page(false) + .with_mode_enable(false) + .with_mode_on(false) + .with_mode_bits(0x0) + .with_num_dummy_bytes(u3::new(0x1)) + .with_instruction_code(InstructionCode::FastReadDualOutput) + .build(); +const DUAL_OUT_FAST_RD_ONE_RAW: u32 = DUAL_OUT_FAST_RD_ONE.raw_value(); +static_assertions::const_assert_eq!(DUAL_OUT_FAST_RD_ONE_RAW, 0x8000_013B); + +pub const DUAL_OUT_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder() + .with_enable_linear_mode(true) + .with_both_memories(true) + .with_separate_memory_bus(true) + .with_upper_memory_page(false) + .with_mode_enable(false) + .with_mode_on(false) + .with_mode_bits(0x0) + .with_num_dummy_bytes(u3::new(0x1)) + .with_instruction_code(InstructionCode::FastReadDualOutput) + .build(); +const DUAL_OUT_FAST_RD_TWO_RAW: u32 = DUAL_OUT_FAST_RD_TWO.raw_value(); +static_assertions::const_assert_eq!(DUAL_OUT_FAST_RD_TWO_RAW, 0xE000_013B); + +pub const QUAD_OUT_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder() + .with_enable_linear_mode(true) + .with_both_memories(false) + .with_separate_memory_bus(false) + .with_upper_memory_page(false) + .with_mode_enable(false) + .with_mode_on(false) + .with_mode_bits(0x0) + .with_num_dummy_bytes(u3::new(0x1)) + .with_instruction_code(InstructionCode::FastReadQuadOutput) + .build(); +const QUAD_OUT_FAST_RD_ONE_RAW: u32 = QUAD_OUT_FAST_RD_ONE.raw_value(); +static_assertions::const_assert_eq!(QUAD_OUT_FAST_RD_ONE_RAW, 0x8000_016B); + +pub const QUAD_OUT_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder() + .with_enable_linear_mode(true) + .with_both_memories(true) + .with_separate_memory_bus(true) + .with_upper_memory_page(false) + .with_mode_enable(false) + .with_mode_on(false) + .with_mode_bits(0x0) + .with_num_dummy_bytes(u3::new(0x1)) + .with_instruction_code(InstructionCode::FastReadQuadOutput) + .build(); +const QUAD_OUT_FAST_RD_TWO_RAW: u32 = QUAD_OUT_FAST_RD_TWO.raw_value(); +static_assertions::const_assert_eq!(QUAD_OUT_FAST_RD_TWO_RAW, 0xE000_016B); + +pub(crate) mod winbond_spansion { + use super::*; + pub const DUAL_IO_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder() + .with_enable_linear_mode(true) + .with_both_memories(false) + .with_separate_memory_bus(false) + .with_upper_memory_page(false) + .with_mode_enable(true) + .with_mode_on(false) + .with_mode_bits(0xff) + .with_num_dummy_bytes(u3::new(0x0)) + .with_instruction_code(InstructionCode::FastReadDualIo) + .build(); + const DUAL_IO_FAST_RD_ONE_RAW: u32 = DUAL_IO_FAST_RD_ONE.raw_value(); + static_assertions::const_assert_eq!(DUAL_IO_FAST_RD_ONE_RAW, 0x82FF_00BB); + + pub const DUAL_IO_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder() + .with_enable_linear_mode(true) + .with_both_memories(true) + .with_separate_memory_bus(true) + .with_upper_memory_page(false) + .with_mode_enable(true) + .with_mode_on(false) + .with_mode_bits(0xff) + .with_num_dummy_bytes(u3::new(0x0)) + .with_instruction_code(InstructionCode::FastReadDualIo) + .build(); + const DUAL_IO_FAST_RD_TWO_RAW: u32 = DUAL_IO_FAST_RD_TWO.raw_value(); + static_assertions::const_assert_eq!(DUAL_IO_FAST_RD_TWO_RAW, 0xE2FF_00BB); + + pub const QUAD_IO_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder() + .with_enable_linear_mode(true) + .with_both_memories(false) + .with_separate_memory_bus(false) + .with_upper_memory_page(false) + .with_mode_enable(true) + .with_mode_on(false) + .with_mode_bits(0xff) + .with_num_dummy_bytes(u3::new(0x2)) + .with_instruction_code(InstructionCode::FastReadQuadIo) + .build(); + const QUAD_IO_FAST_RD_ONE_RAW: u32 = QUAD_IO_FAST_RD_ONE.raw_value(); + static_assertions::const_assert_eq!(QUAD_IO_FAST_RD_ONE_RAW, 0x82FF_02EB); + + pub const QUAD_IO_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder() + .with_enable_linear_mode(true) + .with_both_memories(true) + .with_separate_memory_bus(true) + .with_upper_memory_page(false) + .with_mode_enable(true) + .with_mode_on(false) + .with_mode_bits(0xff) + .with_num_dummy_bytes(u3::new(0x2)) + .with_instruction_code(InstructionCode::FastReadQuadIo) + .build(); + const QUAD_IO_FAST_RD_TWO_RAW: u32 = QUAD_IO_FAST_RD_TWO.raw_value(); + static_assertions::const_assert_eq!(QUAD_IO_FAST_RD_TWO_RAW, 0xE2FF_02EB); +} + +pub(crate) mod micron { + use super::*; + pub const DUAL_IO_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder() + .with_enable_linear_mode(true) + .with_both_memories(false) + .with_separate_memory_bus(false) + .with_upper_memory_page(false) + .with_mode_enable(true) + .with_mode_on(false) + .with_mode_bits(0xff) + .with_num_dummy_bytes(u3::new(0x1)) + .with_instruction_code(InstructionCode::FastReadDualIo) + .build(); + const DUAL_IO_FAST_RD_ONE_RAW: u32 = DUAL_IO_FAST_RD_ONE.raw_value(); + static_assertions::const_assert_eq!(DUAL_IO_FAST_RD_ONE_RAW, 0x82FF_01BB); + + pub const DUAL_IO_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder() + .with_enable_linear_mode(true) + .with_both_memories(true) + .with_separate_memory_bus(true) + .with_upper_memory_page(false) + .with_mode_enable(true) + .with_mode_on(false) + .with_mode_bits(0xff) + .with_num_dummy_bytes(u3::new(0x1)) + .with_instruction_code(InstructionCode::FastReadDualIo) + .build(); + const DUAL_IO_FAST_RD_TWO_RAW: u32 = DUAL_IO_FAST_RD_TWO.raw_value(); + static_assertions::const_assert_eq!(DUAL_IO_FAST_RD_TWO_RAW, 0xE2FF_01BB); + + pub const QUAD_IO_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder() + .with_enable_linear_mode(true) + .with_both_memories(false) + .with_separate_memory_bus(false) + .with_upper_memory_page(false) + .with_mode_enable(true) + .with_mode_on(false) + .with_mode_bits(0xff) + .with_num_dummy_bytes(u3::new(0x4)) + .with_instruction_code(InstructionCode::FastReadQuadIo) + .build(); + const QUAD_IO_FAST_RD_ONE_RAW: u32 = QUAD_IO_FAST_RD_ONE.raw_value(); + static_assertions::const_assert_eq!(QUAD_IO_FAST_RD_ONE_RAW, 0x82FF_04EB); + + pub const QUAD_IO_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder() + .with_enable_linear_mode(true) + .with_both_memories(true) + .with_separate_memory_bus(true) + .with_upper_memory_page(false) + .with_mode_enable(true) + .with_mode_on(false) + .with_mode_bits(0xff) + .with_num_dummy_bytes(u3::new(0x4)) + .with_instruction_code(InstructionCode::FastReadQuadIo) + .build(); + const QUAD_IO_FAST_RD_TWO_RAW: u32 = QUAD_IO_FAST_RD_TWO.raw_value(); + static_assertions::const_assert_eq!(QUAD_IO_FAST_RD_TWO_RAW, 0xE2FF_04EB); +} diff --git a/zynq7000-hal/src/qspi/mod.rs b/zynq7000-hal/src/qspi/mod.rs new file mode 100644 index 0000000..d695d7f --- /dev/null +++ b/zynq7000-hal/src/qspi/mod.rs @@ -0,0 +1,677 @@ +use core::ops::{Deref, DerefMut}; + +use arbitrary_int::{Number, u2, u3, u6}; +pub use zynq7000::qspi::LinearQspiConfig; +use zynq7000::{ + qspi::{ + BaudRateDivisor, Config, InstructionCode, InterruptStatus, LoopbackMasterClockDelay, + SpiEnable, + }, + slcr::{ + clocks::{SingleCommonPeriphIoClockControl, SrcSelIo}, + mio::Speed, + reset::QspiResetControl, + }, +}; + +pub use zynq7000::slcr::mio::IoType; + +use crate::{ + PeriphSelect, + clocks::Clocks, + enable_amba_peripheral_clock, + gpio::{ + IoPeriphPin, + mio::{ + Mio0, Mio1, Mio2, Mio3, Mio4, Mio5, Mio6, Mio8, Mio9, Mio10, Mio11, Mio12, Mio13, + MioPinMarker, MuxConfig, Pin, + }, + }, + slcr::Slcr, + spi_mode_const_to_cpol_cpha, + time::Hertz, +}; + +pub(crate) mod lqspi_configs; + +pub const QSPI_MUX_CONFIG: MuxConfig = MuxConfig::new_with_l0(); +pub const FIFO_DEPTH: usize = 63; +/// In linear-addressed mode, the QSPI is memory-mapped, with the address starting here. +pub const QSPI_START_ADDRESS: usize = 0xFC00_0000; + +#[derive(Debug, thiserror::Error)] +pub enum ClockCalculationError { + #[error("violated clock ratio restriction")] + RefClockSmallerThanCpu1xClock, + #[error("reference divisor out of range")] + RefDivOutOfRange, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BaudRateConfig { + WithLoopback, + WithoutLoopback(BaudRateDivisor), +} + +impl BaudRateConfig { + #[inline] + pub const fn baud_rate_divisor(&self) -> BaudRateDivisor { + match self { + BaudRateConfig::WithLoopback => BaudRateDivisor::_2, + BaudRateConfig::WithoutLoopback(divisor) => *divisor, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ClockConfig { + pub src_sel: SrcSelIo, + pub ref_clk_div: u6, + pub baud_rate_config: BaudRateConfig, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum QspiVendor { + WinbondAndSpansion, + Micron, +} + +pub type OperatingMode = InstructionCode; + +pub trait Qspi0ChipSelectPin: MioPinMarker {} +pub trait Qspi0Io0Pin: MioPinMarker {} +pub trait Qspi0Io1Pin: MioPinMarker {} +pub trait Qspi0Io2Pin: MioPinMarker {} +pub trait Qspi0Io3Pin: MioPinMarker {} +pub trait Qspi0ClockPin: MioPinMarker {} + +impl Qspi0ChipSelectPin for Pin {} +impl Qspi0Io0Pin for Pin {} +impl Qspi0Io1Pin for Pin {} +impl Qspi0Io2Pin for Pin {} +impl Qspi0Io3Pin for Pin {} +impl Qspi0ClockPin for Pin {} + +pub trait Qspi1ChipSelectPin: MioPinMarker {} +pub trait Qspi1Io0Pin: MioPinMarker {} +pub trait Qspi1Io1Pin: MioPinMarker {} +pub trait Qspi1Io2Pin: MioPinMarker {} +pub trait Qspi1Io3Pin: MioPinMarker {} +pub trait Qspi1ClockPin: MioPinMarker {} + +impl Qspi1ChipSelectPin for Pin {} +impl Qspi1Io0Pin for Pin {} +impl Qspi1Io1Pin for Pin {} +impl Qspi1Io2Pin for Pin {} +impl Qspi1Io3Pin for Pin {} +impl Qspi1ClockPin for Pin {} + +pub trait FeedbackClockPin: MioPinMarker {} + +impl FeedbackClockPin for Pin {} + +pub struct QspiDeviceCombination { + pub vendor: QspiVendor, + pub operating_mode: OperatingMode, + pub two_devices: bool, +} + +impl From for LinearQspiConfig { + fn from(value: QspiDeviceCombination) -> Self { + linear_mode_config_for_common_devices(value) + } +} + +pub const fn linear_mode_config_for_common_devices( + dev_combination: QspiDeviceCombination, +) -> LinearQspiConfig { + match dev_combination.operating_mode { + InstructionCode::Read => { + if dev_combination.two_devices { + lqspi_configs::RD_TWO + } else { + lqspi_configs::RD_ONE + } + } + InstructionCode::FastRead => { + if dev_combination.two_devices { + lqspi_configs::FAST_RD_TWO + } else { + lqspi_configs::FAST_RD_ONE + } + } + InstructionCode::FastReadDualOutput => { + if dev_combination.two_devices { + lqspi_configs::DUAL_OUT_FAST_RD_TWO + } else { + lqspi_configs::DUAL_OUT_FAST_RD_ONE + } + } + InstructionCode::FastReadQuadOutput => { + if dev_combination.two_devices { + lqspi_configs::QUAD_OUT_FAST_RD_TWO + } else { + lqspi_configs::QUAD_OUT_FAST_RD_ONE + } + } + InstructionCode::FastReadDualIo => { + match (dev_combination.vendor, dev_combination.two_devices) { + (QspiVendor::WinbondAndSpansion, false) => { + lqspi_configs::winbond_spansion::DUAL_IO_FAST_RD_ONE + } + (QspiVendor::WinbondAndSpansion, true) => { + lqspi_configs::winbond_spansion::DUAL_IO_FAST_RD_TWO + } + (QspiVendor::Micron, false) => lqspi_configs::micron::DUAL_IO_FAST_RD_ONE, + (QspiVendor::Micron, true) => lqspi_configs::micron::DUAL_IO_FAST_RD_TWO, + } + } + InstructionCode::FastReadQuadIo => { + match (dev_combination.vendor, dev_combination.two_devices) { + (QspiVendor::WinbondAndSpansion, false) => { + lqspi_configs::winbond_spansion::QUAD_IO_FAST_RD_ONE + } + (QspiVendor::WinbondAndSpansion, true) => { + lqspi_configs::winbond_spansion::QUAD_IO_FAST_RD_TWO + } + (QspiVendor::Micron, false) => lqspi_configs::micron::QUAD_IO_FAST_RD_ONE, + (QspiVendor::Micron, true) => lqspi_configs::micron::QUAD_IO_FAST_RD_TWO, + } + } + } +} + +impl ClockConfig { + pub fn new(src_sel: SrcSelIo, ref_clk_div: u6, baud_rate_config: BaudRateConfig) -> Self { + Self { + src_sel, + ref_clk_div, + baud_rate_config, + } + } + + /// This constructor calculates the necessary clock divisor for a target QSPI reference clock, + /// assuming that a loopback clock is used and thus constraining the baud rate divisor to 2. + /// + /// It also checks that the clock ratio restriction is not violated: The QSPI reference clock must + /// be greater than the CPU 1x clock. + pub fn calculate_with_loopback( + src_sel: SrcSelIo, + clocks: &Clocks, + target_qspi_interface_clock: Hertz, + ) -> Result { + // For loopback mode, the baud rate divisor MUST be 2. + let target_ref_clock = target_qspi_interface_clock * 2; + let ref_clk = match src_sel { + SrcSelIo::IoPll | SrcSelIo::IoPllAlt => clocks.io_clocks().ref_clk(), + SrcSelIo::ArmPll => clocks.arm_clocks().ref_clk(), + SrcSelIo::DdrPll => clocks.ddr_clocks().ref_clk(), + }; + let ref_clk_div = ref_clk.raw().div_ceil(target_ref_clock.raw()); + if ref_clk_div > u6::MAX.as_u32() { + return Err(ClockCalculationError::RefDivOutOfRange); + } + Ok(Self { + src_sel, + ref_clk_div: u6::new(ref_clk_div as u8), + baud_rate_config: BaudRateConfig::WithLoopback, + }) + } + + /// This constructor calculates the necessary clock configuration for both a target QSPI + /// reference clock as well as a target QSPI interface clock. + /// + /// It also checks that the clock ratio restriction is not violated: The QSPI reference clock must + /// be greater than the CPU 1x clock. + pub fn calculate( + src_sel: SrcSelIo, + clocks: &Clocks, + target_qspi_ref_clock: Hertz, + target_qspi_interface_clock: Hertz, + ) -> Result { + let (ref_clk_div, ref_clk) = match src_sel { + SrcSelIo::IoPll | SrcSelIo::IoPllAlt => ( + clocks + .io_clocks() + .ref_clk() + .raw() + .div_ceil(target_qspi_ref_clock.raw()), + clocks.io_clocks().ref_clk(), + ), + SrcSelIo::ArmPll => ( + clocks + .arm_clocks() + .ref_clk() + .raw() + .div_ceil(target_qspi_ref_clock.raw()), + clocks.arm_clocks().ref_clk(), + ), + SrcSelIo::DdrPll => ( + clocks + .ddr_clocks() + .ref_clk() + .raw() + .div_ceil(target_qspi_ref_clock.raw()), + clocks.ddr_clocks().ref_clk(), + ), + }; + if ref_clk_div > u6::MAX.as_u32() { + return Err(ClockCalculationError::RefDivOutOfRange); + } + let qspi_ref_clk = ref_clk / ref_clk_div; + if qspi_ref_clk < clocks.arm_clocks().cpu_1x_clk() { + return Err(ClockCalculationError::RefClockSmallerThanCpu1xClock); + } + let qspi_baud_rate_div = qspi_ref_clk / target_qspi_interface_clock; + let baud_rate_div = match qspi_baud_rate_div { + 0..=2 => BaudRateDivisor::_2, + 3..=4 => BaudRateDivisor::_4, + 5..=8 => BaudRateDivisor::_8, + 9..=16 => BaudRateDivisor::_16, + 17..=32 => BaudRateDivisor::_32, + 65..=128 => BaudRateDivisor::_64, + 129..=256 => BaudRateDivisor::_128, + _ => BaudRateDivisor::_256, + }; + Ok(Self { + src_sel, + ref_clk_div: u6::new(ref_clk_div as u8), + baud_rate_config: BaudRateConfig::WithoutLoopback(baud_rate_div), + }) + } +} + +pub struct QspiLowLevel(zynq7000::qspi::MmioQspi<'static>); + +impl QspiLowLevel { + #[inline] + pub fn new(regs: zynq7000::qspi::MmioQspi<'static>) -> Self { + Self(regs) + } + + pub fn regs(&mut self) -> &mut zynq7000::qspi::MmioQspi<'static> { + &mut self.0 + } + + pub fn initialize(&mut self, clock_config: ClockConfig, mode: embedded_hal::spi::Mode) { + enable_amba_peripheral_clock(PeriphSelect::Lqspi); + reset(); + let (cpol, cpha) = spi_mode_const_to_cpol_cpha(mode); + unsafe { + Slcr::with(|slcr| { + slcr.clk_ctrl().write_lqspi_clk_ctrl( + SingleCommonPeriphIoClockControl::builder() + .with_divisor(clock_config.ref_clk_div) + .with_srcsel(clock_config.src_sel) + .with_clk_act(true) + .build(), + ); + }) + } + let baudrate_config = clock_config.baud_rate_config; + self.0.write_config( + Config::builder() + .with_interface_mode(zynq7000::qspi::InterfaceMode::FlashMemoryInterface) + .with_edianness(zynq7000::qspi::Endianness::Little) + .with_holdb_dr(true) + .with_manual_start_command(false) + .with_manual_start_enable(false) + .with_manual_cs(false) + .with_peripheral_chip_select(false) + .with_fifo_width(u2::new(0b11)) + .with_baud_rate_div(baudrate_config.baud_rate_divisor()) + .with_clock_phase(cpha) + .with_clock_polarity(cpol) + .with_mode_select(true) + .build(), + ); + if baudrate_config == BaudRateConfig::WithLoopback { + self.0.write_loopback_master_clock_delay( + LoopbackMasterClockDelay::builder() + .with_use_loopback(true) + .with_delay_1(u2::new(0x0)) + .with_delay_0(u3::new(0x0)) + .build(), + ); + } + } + + pub fn enable_linear_addressing(&mut self, config: LinearQspiConfig) { + self.0 + .write_spi_enable(SpiEnable::builder().with_enable(false).build()); + self.0.modify_config(|mut val| { + // Those two bits should be set to 0 according to the TRM. + val.set_manual_start_enable(false); + val.set_manual_cs(false); + val.set_peripheral_chip_select(false); + val + }); + self.0.write_linear_qspi_config(config); + } + + pub fn enable_io_mode(&mut self, dual_flash: bool) { + self.0.modify_config(|mut val| { + val.set_manual_start_enable(true); + val.set_manual_cs(true); + val + }); + self.0.write_rx_fifo_threshold(0x1); + self.0.write_tx_fifo_threshold(0x1); + self.0.write_linear_qspi_config( + LinearQspiConfig::builder() + .with_enable_linear_mode(false) + .with_both_memories(dual_flash) + .with_separate_memory_bus(dual_flash) + .with_upper_memory_page(false) + .with_mode_enable(false) + .with_mode_on(true) + // Reset values from the TRM are set here, but they do not matter anyway. + .with_mode_bits(0xA0) + .with_num_dummy_bytes(u3::new(0x2)) + .with_instruction_code(InstructionCode::FastReadQuadIo) + .build(), + ); + } + + pub fn disable(&mut self) { + self.0 + .write_spi_enable(SpiEnable::builder().with_enable(false).build()); + self.0.modify_config(|mut val| { + val.set_peripheral_chip_select(true); + val + }); + } +} + +pub struct Qspi { + ll: QspiLowLevel, +} + +impl Qspi { + pub fn new_single_qspi< + ChipSelect: Qspi0ChipSelectPin, + Io0: Qspi0Io0Pin, + Io1: Qspi0Io1Pin, + Io2: Qspi0Io2Pin, + Io3: Qspi0Io3Pin, + Clock: Qspi0ClockPin, + >( + regs: zynq7000::qspi::MmioQspi<'static>, + clock_config: ClockConfig, + mode: embedded_hal::spi::Mode, + voltage: IoType, + cs: ChipSelect, + ios: (Io0, Io1, Io2, Io3), + clock: Clock, + ) -> Self { + IoPeriphPin::new_with_full_config( + cs, + zynq7000::slcr::mio::Config::builder() + .with_disable_hstl_rcvr(false) + .with_pullup(true) + .with_io_type(voltage) + .with_speed(Speed::SlowCmosEdge) + .with_l3_sel(QSPI_MUX_CONFIG.l3_sel()) + .with_l2_sel(QSPI_MUX_CONFIG.l2_sel()) + .with_l1_sel(QSPI_MUX_CONFIG.l1_sel()) + .with_l0_sel(QSPI_MUX_CONFIG.l0_sel()) + .with_tri_enable(false) + .build(), + ); + let io_and_clock_config = zynq7000::slcr::mio::Config::builder() + .with_disable_hstl_rcvr(false) + .with_pullup(false) + .with_io_type(voltage) + .with_speed(Speed::SlowCmosEdge) + .with_l3_sel(QSPI_MUX_CONFIG.l3_sel()) + .with_l2_sel(QSPI_MUX_CONFIG.l2_sel()) + .with_l1_sel(QSPI_MUX_CONFIG.l1_sel()) + .with_l0_sel(QSPI_MUX_CONFIG.l0_sel()) + .with_tri_enable(false) + .build(); + IoPeriphPin::new_with_full_config(ios.0, io_and_clock_config); + IoPeriphPin::new_with_full_config(ios.1, io_and_clock_config); + IoPeriphPin::new_with_full_config(ios.2, io_and_clock_config); + IoPeriphPin::new_with_full_config(ios.3, io_and_clock_config); + IoPeriphPin::new_with_full_config(clock, io_and_clock_config); + let mut ll = QspiLowLevel::new(regs); + ll.initialize(clock_config, mode); + Self { ll } + } + + #[allow(clippy::too_many_arguments)] + pub fn new_single_qspi_with_feedback< + ChipSelect: Qspi0ChipSelectPin, + Io0: Qspi0Io0Pin, + Io1: Qspi0Io1Pin, + Io2: Qspi0Io2Pin, + Io3: Qspi0Io3Pin, + Clock: Qspi0ClockPin, + Feedback: FeedbackClockPin, + >( + regs: zynq7000::qspi::MmioQspi<'static>, + clock_config: ClockConfig, + mode: embedded_hal::spi::Mode, + voltage: IoType, + cs: ChipSelect, + io: (Io0, Io1, Io2, Io3), + clock: Clock, + feedback: Feedback, + ) -> Self { + IoPeriphPin::new_with_full_config( + feedback, + zynq7000::slcr::mio::Config::builder() + .with_disable_hstl_rcvr(false) + .with_pullup(false) + .with_io_type(voltage) + .with_speed(Speed::SlowCmosEdge) + .with_l3_sel(QSPI_MUX_CONFIG.l3_sel()) + .with_l2_sel(QSPI_MUX_CONFIG.l2_sel()) + .with_l1_sel(QSPI_MUX_CONFIG.l1_sel()) + .with_l0_sel(QSPI_MUX_CONFIG.l0_sel()) + .with_tri_enable(false) + .build(), + ); + Self::new_single_qspi(regs, clock_config, mode, voltage, cs, io, clock) + } + + #[inline] + pub fn regs(&mut self) -> &mut zynq7000::qspi::MmioQspi<'static> { + &mut self.ll.0 + } + + pub fn into_linear_addressed(mut self, config: LinearQspiConfig) -> QspiLinearAddressing { + self.ll.enable_linear_addressing(config); + QspiLinearAddressing { ll: self.ll } + } + + pub fn into_io_mode(mut self, dual_flash: bool) -> QspiIoMode { + self.ll.enable_io_mode(dual_flash); + QspiIoMode { ll: self.ll } + } +} + +pub struct QspiIoMode { + ll: QspiLowLevel, +} + +impl QspiIoMode { + #[inline] + pub fn regs(&mut self) -> &mut zynq7000::qspi::MmioQspi<'static> { + &mut self.ll.0 + } + + pub fn transfer_guard(&mut self) -> QspiIoTransferGuard<'_> { + QspiIoTransferGuard::new(self) + } + + pub fn into_qspi(mut self, ll: QspiLowLevel) -> Qspi { + self.ll.disable(); + Qspi { ll } + } + + /// Transmits 1-byte command and 3-byte data OR 4-byte data. + #[inline] + pub fn write_word_txd_00(&mut self, word: u32) { + self.regs().write_tx_data_00(word); + } + + /// Transmits 1-byte command. + #[inline] + pub fn write_word_txd_01(&mut self, word: u32) { + self.regs().write_tx_data_01(word); + } + + /// Transmits 1-byte command and 1-byte data. + #[inline] + pub fn write_word_txd_10(&mut self, word: u32) { + self.regs().write_tx_data_10(word); + } + + /// Transmits 1-byte command and 2-byte data. + #[inline] + pub fn write_word_txd_11(&mut self, word: u32) { + self.regs().write_tx_data_11(word); + } + + #[inline] + pub fn read_rx_data(&mut self) -> u32 { + self.regs().read_rx_data() + } + + pub fn transfer_init(&mut self) { + self.regs().modify_config(|mut val| { + val.set_peripheral_chip_select(false); + val + }); + self.regs() + .write_spi_enable(SpiEnable::builder().with_enable(true).build()); + } + + pub fn transfer_start(&mut self) { + self.regs().modify_config(|mut val| { + val.set_manual_start_command(true); + val + }); + } + + pub fn transfer_done(&mut self) { + self.regs().modify_config(|mut val| { + val.set_peripheral_chip_select(true); + val + }); + self.regs() + .write_spi_enable(SpiEnable::builder().with_enable(false).build()); + } + + pub fn read_status(&mut self) -> InterruptStatus { + self.regs().read_interrupt_status() + } + + pub fn clear_rx_fifo(&mut self) { + while self.read_status().rx_above_threshold() { + self.read_rx_data(); + } + } + + pub fn into_linear_addressed(mut self, config: LinearQspiConfig) -> QspiLinearAddressing { + self.ll.enable_linear_addressing(config); + QspiLinearAddressing { ll: self.ll } + } +} + +/// This guard structure takes care of commonly required operations before starting a transfer +/// and after finishing it. +pub struct QspiIoTransferGuard<'a>(&'a mut QspiIoMode); + +impl<'a> QspiIoTransferGuard<'a> { + pub fn new(qspi: &'a mut QspiIoMode) -> Self { + qspi.clear_rx_fifo(); + qspi.transfer_init(); + Self(qspi) + } +} + +impl QspiIoTransferGuard<'_> { + pub fn start(&mut self) { + self.0.transfer_start(); + } +} + +impl Deref for QspiIoTransferGuard<'_> { + type Target = QspiIoMode; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl DerefMut for QspiIoTransferGuard<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0 + } +} + +impl<'a> Drop for QspiIoTransferGuard<'a> { + fn drop(&mut self) { + self.0.transfer_done(); + } +} + +pub struct QspiLinearAddressing { + ll: QspiLowLevel, +} + +impl QspiLinearAddressing { + pub fn into_io_mode(mut self, dual_flash: bool) -> QspiIoMode { + self.ll.enable_io_mode(dual_flash); + QspiIoMode { ll: self.ll } + } + + pub fn read_guard(&mut self) -> QspiLinearReadGuard<'_> { + QspiLinearReadGuard::new(self) + } +} + +pub struct QspiLinearReadGuard<'a>(&'a mut QspiLinearAddressing); + +impl QspiLinearReadGuard<'_> { + pub fn new(qspi: &mut QspiLinearAddressing) -> QspiLinearReadGuard<'_> { + qspi.ll + .0 + .write_spi_enable(SpiEnable::builder().with_enable(true).build()); + QspiLinearReadGuard(qspi) + } +} + +impl Drop for QspiLinearReadGuard<'_> { + fn drop(&mut self) { + self.0 + .ll + .0 + .write_spi_enable(SpiEnable::builder().with_enable(false).build()); + } +} + +/// Reset the QSPI peripheral using the SLCR reset register for QSPI. +/// +/// Please note that this function will interfere with an already configured +/// QSPI instance. +#[inline] +pub fn reset() { + unsafe { + Slcr::with(|regs| { + regs.reset_ctrl().write_lqspi( + QspiResetControl::builder() + .with_qspi_ref_reset(true) + .with_cpu_1x_reset(true) + .build(), + ); + // Keep it in reset for some cycles. + for _ in 0..3 { + cortex_ar::asm::nop(); + } + regs.reset_ctrl().write_lqspi(QspiResetControl::DEFAULT); + }); + } +} diff --git a/zynq7000-hal/src/slcr.rs b/zynq7000-hal/src/slcr.rs index 1c710ea..20cd90d 100644 --- a/zynq7000-hal/src/slcr.rs +++ b/zynq7000-hal/src/slcr.rs @@ -40,8 +40,8 @@ impl Slcr { /// Returns a mutable reference to the SLCR MMIO block. /// /// The MMIO block will not be unlocked. However, the registers can still be read. - pub fn regs(&mut self) -> &mut MmioSlcr<'static> { - &mut self.0 + pub fn regs(&self) -> &MmioSlcr<'static> { + &self.0 } /// Modify the SLCR register. diff --git a/zynq7000-hal/src/spi/mod.rs b/zynq7000-hal/src/spi/mod.rs index 98f4f75..b19c68a 100644 --- a/zynq7000-hal/src/spi/mod.rs +++ b/zynq7000-hal/src/spi/mod.rs @@ -2,7 +2,6 @@ use core::convert::Infallible; use crate::clocks::Clocks; -use crate::enable_amba_periph_clk; use crate::gpio::IoPeriphPin; use crate::gpio::mio::{ Mio10, Mio11, Mio12, Mio13, Mio14, Mio15, Mio28, Mio29, Mio30, Mio31, Mio32, Mio33, Mio34, @@ -13,12 +12,13 @@ use crate::gpio::mio::{ Mio16, Mio17, Mio18, Mio19, Mio20, Mio21, Mio22, Mio23, Mio24, Mio25, Mio26, Mio27, Mio40, Mio41, Mio42, Mio43, Mio44, Mio45, Mio46, Mio47, Mio48, Mio49, Mio50, Mio51, }; +use crate::{enable_amba_peripheral_clock, spi_mode_const_to_cpol_cpha}; use crate::{clocks::IoClocks, slcr::Slcr, time::Hertz}; use arbitrary_int::{Number, u3, u4, u6}; use embedded_hal::delay::DelayNs; pub use embedded_hal::spi::Mode; -use embedded_hal::spi::{MODE_0, MODE_1, MODE_2, MODE_3, SpiBus as _}; +use embedded_hal::spi::SpiBus as _; use zynq7000::slcr::reset::DualRefAndClockReset; use zynq7000::spi::{ BaudDivSel, DelayControl, FifoWrite, InterruptControl, InterruptMask, InterruptStatus, MmioSpi, @@ -457,15 +457,10 @@ impl SpiLowLevel { /// Re-configures the mode register. #[inline] pub fn configure_mode(&mut self, mode: Mode) { - let (cpol, cpha) = match mode { - MODE_0 => (false, false), - MODE_1 => (false, true), - MODE_2 => (true, false), - MODE_3 => (true, true), - }; + let (cpol, cpha) = spi_mode_const_to_cpol_cpha(mode); self.regs.modify_cr(|mut val| { val.set_cpha(cpha); - val.set_cpha(cpol); + val.set_cpol(cpol); val }); } @@ -485,12 +480,7 @@ impl SpiLowLevel { SlaveSelectConfig::AutoWithManualStart => (false, true), SlaveSelectConfig::AutoWithAutoStart => (false, false), }; - let (cpol, cpha) = match config.init_mode { - MODE_0 => (false, false), - MODE_1 => (false, true), - MODE_2 => (true, false), - MODE_3 => (true, true), - }; + let (cpol, cpha) = spi_mode_const_to_cpol_cpha(config.init_mode); self.regs.write_cr( zynq7000::spi::Config::builder() @@ -822,7 +812,7 @@ impl Spi { SpiId::Spi0 => crate::PeriphSelect::Spi0, SpiId::Spi1 => crate::PeriphSelect::Spi1, }; - enable_amba_periph_clk(periph_sel); + enable_amba_peripheral_clock(periph_sel); let sclk = clocks.spi_clk() / config.baud_div.div_value() as u32; let mut spi = Self { inner: SpiLowLevel { regs, id }, @@ -1141,8 +1131,8 @@ pub fn reset(id: SpiId) { /// [configure_spi_ref_clk] can be used to configure the SPI reference clock with the calculated /// value. pub fn calculate_largest_allowed_spi_ref_clk_divisor(clks: &Clocks) -> Option { - let mut slcr = unsafe { Slcr::steal() }; - let spi_clk_ctrl = slcr.regs().clk_ctrl().read_spi_clk_ctrl(); + let slcr = unsafe { Slcr::steal() }; + let spi_clk_ctrl = slcr.regs().clk_ctrl_shared().read_spi_clk_ctrl(); let div = match spi_clk_ctrl.srcsel() { zynq7000::slcr::clocks::SrcSelIo::IoPll | zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => { clks.io_clocks().ref_clk() / clks.arm_clocks().cpu_1x_clk() @@ -1163,7 +1153,7 @@ pub fn calculate_largest_allowed_spi_ref_clk_divisor(clks: &Clocks) -> Option crate::PeriphSelect::Uart0, UartId::Uart1 => crate::PeriphSelect::Uart1, }; - enable_amba_periph_clk(periph_sel); + enable_amba_peripheral_clock(periph_sel); reset(uart_id); reg_block.modify_cr(|mut v| { v.set_tx_dis(true); @@ -506,7 +506,7 @@ impl Uart { .build(), ); reg_block.write_baud_rate_div( - BaudRateDiv::builder() + BaudRateDivisor::builder() .with_bdiv(cfg.raw_clk_config().bdiv()) .build(), ); diff --git a/zynq7000-rt/src/mmu.rs b/zynq7000-rt/src/mmu.rs index ff8f379..3c103c6 100644 --- a/zynq7000-rt/src/mmu.rs +++ b/zynq7000-rt/src/mmu.rs @@ -135,6 +135,11 @@ pub mod section_attrs { execute_never: false, memory_attrs: MemoryRegionAttributes::OuterAndInnerWriteBackNoWriteAlloc.as_raw(), }; + /// For the QSPI XIP, we profit from caching reads to both inner and outer cache. + /// Writes are not relevant, because the QSPI controller does not support writes in linear + /// addressing mode. The TRM mentions that the AXI bus will immediately acknowledge the command + /// but will not perform any actual operations. I think using write through without allocation + /// prevents cache pollution for writes, but those should never happen anyway.. pub const QSPI_XIP: SectionAttributes = SectionAttributes { non_global: false, p_bit: false, diff --git a/zynq7000/src/ddrc.rs b/zynq7000/src/ddrc.rs new file mode 100644 index 0000000..567e0af --- /dev/null +++ b/zynq7000/src/ddrc.rs @@ -0,0 +1,880 @@ +pub const DDRC_BASE_ADDR: usize = 0xF800_6000; + +pub mod regs { + use arbitrary_int::{u2, u3, u4, u5, u6, u7, u9, u10, u11, u12, u20}; + + #[bitbybit::bitenum(u2)] + #[derive(Debug, PartialEq, Eq)] + pub enum DataBusWidth { + _32Bit = 0b00, + _16Bit = 0b01, + } + + #[bitbybit::bitenum(u1, exhaustive = true)] + #[derive(Debug, PartialEq, Eq)] + pub enum SoftReset { + Reset = 0, + Active = 1, + } + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DdrcControl { + #[bit(16, rw)] + disable_auto_refresh: bool, + #[bit(15, rw)] + disable_active_bypass: bool, + #[bit(14, rw)] + disable_read_bypass: bool, + #[bits(7..=13, rw)] + read_write_idle_gap: u7, + #[bits(4..=6, rw)] + burst8_refresh: u3, + #[bits(2..=3, rw)] + data_bus_width: Option, + #[bit(1, rw)] + power_down_enable: bool, + #[bit(0, rw)] + soft_reset: SoftReset, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct TwoRankConfig { + #[bits(14..=18, rw)] + addrmap_cs_bit0: u5, + /// Reserved register, but for some reason, Xilinx tooling writes a 1 here? + #[bits(12..=13, rw)] + ddrc_active_ranks: u2, + /// tREFI - Average time between refreshes, in multiples of 32 clocks. + #[bits(0..=11, rw)] + rfc_nom_x32: u12, + } + + /// Queue control for the low priority and high priority read queues. + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct LprHprQueueControl { + #[bits(22..=25, rw)] + xact_run_length: u4, + #[bits(11..=21, rw)] + max_starve_x32: u11, + #[bits(0..=10, rw)] + min_non_critical_x32: u11, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct WriteQueueControl { + #[bits(15..=25, rw)] + max_starve_x32: u11, + #[bits(11..=14, rw)] + xact_run_length: u4, + #[bits(0..=10, rw)] + min_non_critical_x32: u11, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramParamReg0 { + /// Minimum time to wait after coming out of self refresh before doing anything. This must be + /// bigger than all the constraints that exist. + #[bits(14..=20, rw)] + post_selfref_gap_x32: u7, + /// tRFC(min) - Minimum time from refresh to refresh or activate in clock + /// cycles. + #[bits(6..=13, rw)] + t_rfc_min: u8, + /// tRC - Min time between activates to the same bank. + #[bits(0..=5, rw)] + t_rc: u6, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramParamReg1 { + #[bits(28..=31, rw)] + t_cke: u4, + #[bits(22..=26, rw)] + t_ras_min: u5, + #[bits(16..=21, rw)] + t_ras_max: u6, + #[bits(10..=15, rw)] + t_faw: u6, + #[bits(5..=9, rw)] + powerdown_to_x32: u5, + #[bits(0..=4, rw)] + wr2pre: u5, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramParamReg2 { + #[bits(28..=31, rw)] + t_rcd: u4, + #[bits(23..=27, rw)] + rd2pre: u5, + #[bits(20..=22, rw)] + pad_pd: u3, + #[bits(15..=19, rw)] + t_xp: u5, + #[bits(10..=14, rw)] + wr2rd: u5, + #[bits(5..=9, rw)] + rd2wr: u5, + #[bits(0..=4, rw)] + write_latency: u5, + } + + /// Weird naming. + #[bitbybit::bitenum(u1, exhaustive = true)] + pub enum MobileSetting { + Ddr2Ddr3 = 0, + Lpddr2 = 1, + } + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramParamReg3 { + #[bit(30, rw)] + disable_pad_pd_feature: bool, + #[bits(24..=28, rw)] + read_latency: u5, + #[bit(23, rw)] + enable_dfi_dram_clk_disable: bool, + /// 0: DDR2 or DDR3. 1: LPDDR2. + #[bit(22, rw)] + mobile: MobileSetting, + /// Must be set to 0. + #[bit(21, rw)] + sdram: bool, + #[bits(16..=20, rw)] + refresh_to_x32: u5, + #[bits(12..=15, rw)] + t_rp: u4, + #[bits(8..=11, rw)] + refresh_margin: u4, + #[bits(5..=7, rw)] + t_rrd: u3, + #[bits(2..=4, rw)] + t_ccd: u3, + } + + #[bitbybit::bitenum(u1, exhaustive = true)] + pub enum ModeRegisterType { + Write = 0, + Read = 1, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramParamReg4 { + #[bit(27, rw)] + mr_rdata_valid: bool, + #[bit(26, rw)] + mr_type: ModeRegisterType, + #[bit(25, rw)] + mr_wr_busy: bool, + #[bits(9..=24, rw)] + mr_data: u16, + #[bits(7..=8, rw)] + mr_addr: u2, + #[bit(6, rw)] + mr_wr: bool, + #[bit(1, rw)] + prefer_write: bool, + #[bit(0, rw)] + enable_2t_timing_mode: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramInitParam { + #[bits(11..=13, rw)] + t_mrd: u3, + #[bits(7..=10, rw)] + pre_ocd_x32: u4, + #[bits(0..=6, rw)] + final_wait_x32: u7, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramEmr { + #[bits(16..=31, rw)] + emr3: u16, + #[bits(0..=15, rw)] + emr2: u16, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramEmrMr { + #[bits(16..=31, rw)] + emr: u16, + #[bits(0..=15, rw)] + mr: u16, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramBurst8ReadWrite { + #[bits(0..=3, rw)] + burst_rdwr: u4, + #[bits(4..=13, rw)] + pre_cke_x1024: u10, + #[bits(16..=25, rw)] + post_cke_x1024: u10, + #[bit(26, rw)] + burstchop: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DisableDq { + #[bit(1, rw)] + dis_dq: bool, + #[bit(0, rw)] + force_low_pri_n: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramAddrMapBank { + #[bits(16..=19, rw)] + addrmap_bank_b6: u4, + #[bits(12..=15, rw)] + addrmap_bank_b5: u4, + #[bits(8..=11, rw)] + addrmap_bank_b2: u4, + #[bits(4..=7, rw)] + addrmap_bank_b1: u4, + #[bits(0..=3, rw)] + addrmap_bank_b0: u4, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramAddrMapColumn { + #[bits(28..=31, rw)] + addrmap_col_b11: u4, + #[bits(24..=27, rw)] + addrmap_col_b10: u4, + #[bits(20..=23, rw)] + addrmap_col_b9: u4, + #[bits(16..=19, rw)] + addrmap_col_b8: u4, + #[bits(12..=15, rw)] + addrmap_col_b7: u4, + #[bits(8..=11, rw)] + addrmap_col_b4: u4, + #[bits(4..=7, rw)] + addrmap_col_b3: u4, + #[bits(0..=3, rw)] + addrmap_col_b2: u4, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramAddrMapRow { + #[bits(24..=27, rw)] + addrmap_row_b15: u4, + #[bits(20..=23, rw)] + addrmap_row_b14: u4, + #[bits(16..=19, rw)] + addrmap_row_b13: u4, + #[bits(12..=15, rw)] + addrmap_row_b12: u4, + #[bits(8..=11, rw)] + addrmap_row_b2_11: u4, + #[bits(4..=7, rw)] + addrmap_row_b1: u4, + #[bits(0..=3, rw)] + addrmap_row_b0: u4, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramOdt { + #[bits(16..=17, rw)] + phy_idle_local_odt: u2, + #[bits(14..=15, rw)] + phy_write_local_odt: u2, + #[bits(12..=13, rw)] + phy_read_local_odt: u2, + #[bits(3..=5, rw)] + rank0_wr_odt: u3, + #[bits(0..=2, rw)] + rank0_rd_odt: u3, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct PhyCmdTimeoutRdDataCpt { + #[bits(28..=31, rw)] + wrlvl_num_of_dq0: u4, + #[bits(24..=27, rw)] + gatelvl_num_of_dq0: u4, + #[bit(19, rw)] + clk_stall_level: bool, + #[bit(18, rw)] + dis_phy_ctrl_rstn: bool, + #[bit(17, rw)] + rdc_fifo_rst_err_cnt_clr: bool, + #[bit(16, rw)] + use_fixed_re: bool, + #[bits(8..=11, rw)] + rdc_we_to_re_delay: u4, + #[bits(4..=7, rw)] + wr_cmd_to_data: u4, + #[bits(0..=3, rw)] + rd_cmd_to_data: u4, + } + + #[bitbybit::bitenum(u1, exhaustive = true)] + pub enum DllCalibSel { + Periodic = 0, + Manual = 1, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DllCalib { + #[bit(16, rw)] + sel: DllCalibSel, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct OdtDelayHold { + #[bits(12..=15, rw)] + wr_odt_hold: u4, + #[bits(8..=11, rw)] + rd_odt_hold: u4, + #[bits(4..=7, rw)] + wr_odt_delay: u4, + #[bits(0..=3, rw)] + rd_odt_delay: u4, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CtrlReg1 { + #[bit(12, rw)] + selfref_enable: bool, + #[bit(10, rw)] + dis_collision_page_opt: bool, + #[bit(9, rw)] + dis_wc: bool, + #[bit(8, rw)] + refresh_update_level: bool, + #[bit(7, rw)] + auto_pre_en: bool, + #[bits(1..=6, rw)] + lpr_num_entries: u6, + #[bit(0, rw)] + pageclose: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CtrlReg2 { + #[bit(17, rw)] + go_2_critcal_enable: bool, + #[bits(5..=12, rw)] + go_2_critical_hysteresis: u8, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CtrlReg3 { + #[bits(16..=25, rw)] + dfi_t_wlmrd: u10, + #[bits(8..=15, rw)] + rdlvl_rr: u8, + #[bits(0..=7, rw)] + wrlvl_ww: u8, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CtrlReg4 { + #[bits(8..=15, rw)] + dfi_t_ctrlupd_interval_max_x1024: u8, + #[bits(0..=7, rw)] + dfi_t_ctrlupd_interval_min_x1024: u8, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CtrlReg5 { + #[bits(20..=25, rw)] + t_ckesr: u6, + #[bits(16..=19, rw)] + t_cksrx: u4, + #[bits(12..=15, rw)] + t_ckrse: u4, + #[bits(8..=11, rw)] + dfi_t_dram_clk_enable: u4, + #[bits(4..=7, rw)] + dfi_t_dram_clk_disable: u4, + #[bits(0..=3, rw)] + dfi_t_ctrl_delay: u4, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CtrlReg6 { + #[bits(16..=19, rw)] + t_cksx: u4, + #[bits(12..=15, rw)] + t_ckdpdx: u4, + #[bits(8..=11, rw)] + t_ckdpde: u4, + #[bits(4..=7, rw)] + t_ckpdx: u4, + #[bits(0..=3, rw)] + t_ckpde: u4, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CheTZq { + #[bits(22..=31, rw)] + t_zq_short_nop: u10, + #[bits(12..=21, rw)] + t_zq_long_nop: u10, + #[bits(2..=11, rw)] + t_mode: u10, + #[bit(1, rw)] + ddr3: bool, + #[bit(0, rw)] + dis_auto_zq: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CheTZqShortInterval { + #[bits(20..=27, rw)] + dram_rstn_x1024: u8, + #[bits(0..=19, rw)] + t_zq_short_interval: u20, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DeepPowerdown { + #[bits(1..=8, rw)] + deep_powerdown_to_x1024: u8, + #[bit(0, rw)] + enable: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct Reg2c { + #[bit(28, rw)] + dfi_rd_data_eye_train: bool, + #[bit(27, rw)] + dfi_rd_dqs_gate_level: bool, + #[bit(26, rw)] + dfi_wr_level_enable: bool, + #[bit(25, rw)] + trdlvl_max_error: bool, + #[bit(24, rw)] + twrlvl_max_error: bool, + #[bits(12..=23, rw)] + dfi_rdlvl_max_x1024: u12, + #[bits(0..=11, rw)] + dfi_wrlvl_max_x1024: u12, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct Reg2d { + #[bit(9, rw)] + skip_ocd: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DfiTiming { + #[bits(15..=24, rw)] + dfi_t_ctrlup_max: u10, + #[bits(5..=14, rw)] + dfi_t_ctrlup_min: u10, + #[bits(0..=4, rw)] + dfi_t_rddata_enable: u5, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CheEccControl { + #[bit(1, rw)] + clear_correctable_errors: bool, + #[bit(0, rw)] + clear_uncorrectable_errors: bool, + } + + #[bitbybit::bitenum(u3, exhaustive = false)] + pub enum EccMode { + NoEcc = 0b000, + SecDecOverOneBeat = 0b100, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct EccScrub { + #[bit(3, rw)] + disable_scrub: bool, + #[bits(0..=2, rw)] + ecc_mode: Option, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct PhyReceiverEnable { + #[bits(4..=7, rw)] + phy_dif_off: u4, + #[bits(0..=3, rw)] + phy_dif_on: u4, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct PhyConfig { + #[bits(24..=30, rw)] + dq_offset: u7, + #[bit(3, rw)] + wrlvl_inc_mode: bool, + #[bit(2, rw)] + gatelvl_inc_mode: bool, + #[bit(1, rw)] + rdlvl_inc_mode: bool, + #[bit(0, rw)] + data_slice_in_use: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct PhyInitRatio { + #[bits(10..=19, rw)] + gatelvl_init_ratio: u10, + #[bits(0..=9, rw)] + wrlvl_init_ratio: u10, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct PhyDqsConfig { + #[bits(11..=19, rw)] + dqs_slave_delay: u9, + #[bit(10, rw)] + dqs_slave_force: bool, + #[bits(0..=9, rw)] + dqs_slave_ratio: u10, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct PhyWriteEnableConfig { + #[bits(12..=20, rw)] + fifo_we_in_delay: u9, + #[bit(11, rw)] + fifo_we_in_force: bool, + #[bits(0..=10, rw)] + fifo_we_slave_ratio: u11, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct PhyWriteDataSlaveConfig { + #[bits(11..=19, rw)] + wr_data_slave_delay: u9, + #[bit(10, rw)] + wr_data_slave_force: bool, + #[bits(0..=9, rw)] + wr_data_slave_ratio: u10, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct Reg64 { + #[bit(30, rw)] + cmd_latency: bool, + #[bit(29, rw)] + lpddr: bool, + #[bits(21..=27, rw)] + ctrl_slave_delay: u7, + #[bit(20, rw)] + ctrl_slave_force: bool, + #[bits(10..=19, rw)] + ctrl_slave_ratio: u10, + #[bit(9, rw)] + sel_logic: bool, + #[bit(7, rw)] + invert_clkout: bool, + #[bit(1, rw)] + bl2: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct Reg65 { + #[bits(18..=19, rw)] + ctrl_slave_delay: u2, + #[bit(17, rw)] + dis_calib_rst: bool, + #[bit(16, rw)] + use_rd_data_eye_level: bool, + #[bit(15, rw)] + use_rd_dqs_gate_level: bool, + #[bit(14, rw)] + use_wr_level: bool, + #[bits(10..=13, rw)] + dll_lock_diff: u4, + #[bits(5..=9, rw)] + rd_rl_delay: u5, + #[bits(0..=4, rw)] + wr_rl_delay: u5, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct AxiPriorityWritePort { + #[bit(18, rw)] + disable_page_match: bool, + #[bit(17, rw)] + disable_urgent: bool, + #[bit(16, rw)] + disable_aging: bool, + #[bits(0..=9, rw)] + pri_wr_port: u10, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct AxiPriorityReadPort { + #[bit(19, rw)] + enable_hpr: bool, + #[bit(18, rw)] + disable_page_match: bool, + #[bit(17, rw)] + disable_urgent: bool, + #[bit(16, rw)] + disable_aging: bool, + #[bits(0..=9, rw)] + pri_rd_port_n: u10, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct ExclusiveAccessConfig { + #[bits(9..=17, rw)] + access_id1_port: u9, + #[bits(0..=8, rw)] + access_id0_port: u9, + } + + #[bitbybit::bitenum(u1, exhaustive = true)] + pub enum LpddrBit { + Ddr2Ddr3 = 0, + Lpddr2 = 1, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct LpddrControl0 { + #[bits(4..=11, rw)] + mr4_margin: u8, + #[bit(2, rw)] + derate_enable: bool, + #[bit(1, rw)] + per_bank_refresh: bool, + #[bit(0, rw)] + lpddr2: LpddrBit, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct LpddrControl1 { + #[bits(0..=31, rw)] + mr4_read_interval: u32, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct LpddrControl2 { + #[bits(12..=21, rw)] + t_mrw: u10, + #[bits(4..=11, rw)] + idle_after_reset_x32: u8, + #[bits(0..=3, rw)] + min_stable_clock_x1: u4, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct LpddrControl3 { + #[bits(8..=17, rw)] + dev_zqinit_x32: u10, + #[bits(0..=7, rw)] + max_auto_init_x1024: u8, + } + + #[bitbybit::bitenum(u3, exhaustive = true)] + #[derive(Debug, PartialEq, Eq)] + pub enum OperatingMode { + DdrcInit = 0, + NormalOperation = 1, + Powerdown = 2, + SelfRefresh = 3, + DeepPowerdown = 4, + DeepPowerdownAlt1 = 5, + DeepPowerdownAlt2 = 6, + DeepPowerdownAlt3 = 7, + } + + impl OperatingMode { + pub fn is_deep_powerdown(&self) -> bool { + matches!( + self, + OperatingMode::DeepPowerdown + | OperatingMode::DeepPowerdownAlt1 + | OperatingMode::DeepPowerdownAlt2 + | OperatingMode::DeepPowerdownAlt3 + ) + } + } + + #[bitbybit::bitenum(u1, exhaustive = true)] + #[derive(Debug, PartialEq, Eq)] + pub enum DebugStallBit { + CommandsAccepted = 0, + CommandsNotAccepted = 1, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct ModeStatus { + #[bits(16..=20, r)] + dbg_hpr_queue_depth: u5, + #[bits(10..=15, r)] + dbg_lpr_queue_depth: u6, + #[bits(4..=9, r)] + dbg_wr_queue_depth: u6, + #[bit(3, r)] + dbg_stall: DebugStallBit, + #[bits(0..=2, r)] + operating_mode: OperatingMode, + } +} + +use regs::*; + +#[derive(derive_mmio::Mmio)] +#[repr(C)] +pub struct DdrController { + ddrc_ctrl: DdrcControl, + two_rank_cfg: TwoRankConfig, + hpr_queue_ctrl: LprHprQueueControl, + lpr_queue_ctrl: LprHprQueueControl, + wr_reg: WriteQueueControl, + dram_param_reg0: DramParamReg0, + dram_param_reg1: DramParamReg1, + dram_param_reg2: DramParamReg2, + dram_param_reg3: DramParamReg3, + dram_param_reg4: DramParamReg4, + dram_init_param: DramInitParam, + dram_emr: DramEmr, + dram_emr_mr: DramEmrMr, + dram_burst8_rdwr: DramBurst8ReadWrite, + dram_disable_dq: DisableDq, + dram_addr_map_bank: DramAddrMapBank, + dram_addr_map_col: DramAddrMapColumn, + dram_addr_map_row: DramAddrMapRow, + dram_odt_reg: DramOdt, + #[mmio(PureRead)] + phy_debug_reg: u32, + phy_cmd_timeout_rddata_cpt: PhyCmdTimeoutRdDataCpt, + #[mmio(PureRead)] + mode_status: ModeStatus, + dll_calib: DllCalib, + odt_delay_hold: OdtDelayHold, + ctrl_reg1: CtrlReg1, + ctrl_reg2: CtrlReg2, + ctrl_reg3: CtrlReg3, + ctrl_reg4: CtrlReg4, + _reserved0: [u32; 0x2], + ctrl_reg5: CtrlReg5, + ctrl_reg6: CtrlReg6, + + _reserved1: [u32; 0x8], + + che_refresh_timer_01: u32, + che_t_zq: CheTZq, + che_t_zq_short_interval_reg: CheTZqShortInterval, + deep_powerdown_reg: DeepPowerdown, + reg_2c: Reg2c, + reg_2d: Reg2d, + dfi_timing: DfiTiming, + _reserved2: [u32; 0x2], + che_ecc_control: CheEccControl, + #[mmio(PureRead)] + che_corr_ecc_log: u32, + #[mmio(PureRead)] + che_corr_ecc_addr: u32, + #[mmio(PureRead)] + che_corr_ecc_data_31_0: u32, + #[mmio(PureRead)] + che_corr_ecc_data_63_32: u32, + #[mmio(PureRead)] + che_corr_ecc_data_71_64: u32, + /// Clear on write, but the write is performed on another register. + #[mmio(PureRead)] + che_uncorr_ecc_log: u32, + #[mmio(PureRead)] + che_uncorr_ecc_addr: u32, + #[mmio(PureRead)] + che_uncorr_ecc_data_31_0: u32, + #[mmio(PureRead)] + che_uncorr_ecc_data_63_32: u32, + #[mmio(PureRead)] + che_uncorr_ecc_data_71_64: u32, + #[mmio(PureRead)] + che_ecc_stats: u32, + ecc_scrub: EccScrub, + #[mmio(PureRead)] + che_ecc_corr_bit_mask_31_0: u32, + #[mmio(PureRead)] + che_ecc_corr_bit_mask_63_32: u32, + + _reserved3: [u32; 0x5], + + phy_receiver_enable: PhyReceiverEnable, + phy_config: [PhyConfig; 0x4], + _reserved4: u32, + phy_init_ratio: [PhyInitRatio; 4], + _reserved5: u32, + phy_rd_dqs_cfg: [PhyDqsConfig; 4], + _reserved6: u32, + phy_wr_dqs_cfg: [PhyDqsConfig; 4], + _reserved7: u32, + phy_we_cfg: [PhyWriteEnableConfig; 4], + _reserved8: u32, + phy_wr_data_slave: [PhyWriteDataSlaveConfig; 4], + + _reserved9: u32, + + reg_64: Reg64, + reg_65: Reg65, + _reserved10: [u32; 3], + #[mmio(PureRead)] + reg69_6a0: u32, + #[mmio(PureRead)] + reg69_6a1: u32, + _reserved11: u32, + #[mmio(PureRead)] + reg69_6d2: u32, + #[mmio(PureRead)] + reg69_6d3: u32, + #[mmio(PureRead)] + reg69_710: u32, + #[mmio(PureRead)] + reg6e_711: u32, + #[mmio(PureRead)] + reg6e_712: u32, + #[mmio(PureRead)] + reg6e_713: u32, + _reserved12: u32, + #[mmio(PureRead)] + phy_dll_status: [u32; 4], + _reserved13: u32, + #[mmio(PureRead)] + dll_lock_status: u32, + #[mmio(PureRead)] + phy_control_status: u32, + #[mmio(PureRead)] + phy_control_status_2: u32, + + _reserved14: [u32; 0x5], + + // DDRI registers. + #[mmio(PureRead)] + axi_id: u32, + page_mask: u32, + axi_priority_wr_port: [AxiPriorityWritePort; 0x4], + axi_priority_rd_port: [AxiPriorityReadPort; 0x4], + + _reserved15: [u32; 0x1B], + + excl_access_cfg: [ExclusiveAccessConfig; 0x4], + #[mmio(PureRead)] + mode_reg_read: u32, + lpddr_ctrl_0: LpddrControl0, + lpddr_ctrl_1: LpddrControl1, + lpddr_ctrl_2: LpddrControl2, + lpddr_ctrl_3: LpddrControl3, +} + +static_assertions::const_assert_eq!(core::mem::size_of::(), 0x2B8); + +impl DdrController { + /// Create a new DDR MMIO instance for the DDR controller at address [DDRC_BASE_ADDR]. + /// + /// # Safety + /// + /// This API can be used to potentially create a driver to the same peripheral structure + /// from multiple threads. The user must ensure that concurrent accesses are safe and do not + /// interfere with each other. + pub const unsafe fn new_mmio_fixed() -> MmioDdrController<'static> { + unsafe { Self::new_mmio_at(DDRC_BASE_ADDR) } + } +} diff --git a/zynq7000/src/lib.rs b/zynq7000/src/lib.rs index 9dec569..ee52340 100644 --- a/zynq7000/src/lib.rs +++ b/zynq7000/src/lib.rs @@ -17,6 +17,7 @@ extern crate std; pub const MPCORE_BASE_ADDR: usize = 0xF8F0_0000; +pub mod ddrc; pub mod eth; pub mod gic; pub mod gpio; @@ -24,6 +25,8 @@ pub mod gtc; pub mod i2c; pub mod l2_cache; pub mod mpcore; +pub mod priv_tim; +pub mod qspi; pub mod slcr; pub mod spi; pub mod ttc; @@ -40,6 +43,7 @@ pub struct PsPeripherals { pub gicc: gic::MmioGicCpuInterface<'static>, pub gicd: gic::MmioGicDistributor<'static>, pub l2c: l2_cache::MmioL2Cache<'static>, + pub ddrc: ddrc::MmioDdrController<'static>, pub uart_0: uart::MmioUart<'static>, pub uart_1: uart::MmioUart<'static>, pub spi_0: spi::MmioSpi<'static>, @@ -53,6 +57,7 @@ pub struct PsPeripherals { pub ttc_1: ttc::MmioTtc<'static>, pub eth_0: eth::MmioEthernet<'static>, pub eth_1: eth::MmioEthernet<'static>, + pub qspi: qspi::MmioQspi<'static>, } impl PsPeripherals { @@ -76,6 +81,7 @@ impl PsPeripherals { gicc: gic::GicCpuInterface::new_mmio_fixed(), gicd: gic::GicDistributor::new_mmio_fixed(), l2c: l2_cache::L2Cache::new_mmio_fixed(), + ddrc: ddrc::DdrController::new_mmio_fixed(), uart_0: uart::Uart::new_mmio_fixed_0(), uart_1: uart::Uart::new_mmio_fixed_1(), gtc: gtc::GlobalTimerCounter::new_mmio_fixed(), @@ -89,7 +95,22 @@ impl PsPeripherals { ttc_1: ttc::Ttc::new_mmio_fixed_1(), eth_0: eth::Ethernet::new_mmio_fixed_0(), eth_1: eth::Ethernet::new_mmio_fixed_1(), + qspi: qspi::Qspi::new_mmio_fixed(), } } } } + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum SpiClockPhase { + ActiveOutsideOfWord = 0, + InactiveOutsideOfWord = 1, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum SpiClockPolarity { + QuiescentLow = 0, + QuiescentHigh = 1, +} diff --git a/zynq7000/src/priv_tim.rs b/zynq7000/src/priv_tim.rs new file mode 100644 index 0000000..dc6b1a0 --- /dev/null +++ b/zynq7000/src/priv_tim.rs @@ -0,0 +1,48 @@ +//! # CPU private timer module. + +pub const CPU_PRIV_TIM_BASE_ADDR: usize = super::mpcore::MPCORE_BASE_ADDR + 0x0000_0600; + +#[bitbybit::bitfield(u32, default = 0x0)] +pub struct Control { + #[bits(8..=15, rw)] + prescaler: u8, + #[bit(2, rw)] + interrupt_enable: bool, + #[bit(1, rw)] + auto_reload: bool, + #[bit(0, rw)] + enable: bool, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +pub struct InterruptStatus { + /// Cleared by writing a one. + #[bit(0, rw)] + event_flag: bool, +} + +#[derive(derive_mmio::Mmio)] +#[repr(C)] +pub struct CpuPrivateTimer { + reload: u32, + counter: u32, + control: Control, + interrupt_status: InterruptStatus, +} + +impl CpuPrivateTimer { + /// Create a new CPU Private Timer MMIO instance at the fixed base address. + /// + /// # Safety + /// + /// This API can be used to potentially create a driver to the same peripheral structure + /// from multiple threads. The user must ensure that concurrent accesses are safe and do not + /// interfere with each other. + /// + /// It should also be noted that the calls to this MMIO structure are private for each CPU + /// core, which might lead to unexpected results when using this in a SMP system. + #[inline] + pub const unsafe fn new_mmio_fixed() -> MmioCpuPrivateTimer<'static> { + unsafe { CpuPrivateTimer::new_mmio_at(CPU_PRIV_TIM_BASE_ADDR) } + } +} diff --git a/zynq7000/src/qspi.rs b/zynq7000/src/qspi.rs new file mode 100644 index 0000000..46ba719 --- /dev/null +++ b/zynq7000/src/qspi.rs @@ -0,0 +1,278 @@ +use arbitrary_int::{u2, u3}; + +pub use crate::{SpiClockPhase, SpiClockPolarity}; + +const QSPI_BASE_ADDR: usize = 0xE000D000; + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum InterfaceMode { + LegacySpi = 0, + FlashMemoryInterface = 1, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum Endianness { + Little = 0, + Big = 1, +} + +/// Baud rate divisor register values. +#[bitbybit::bitenum(u3, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum BaudRateDivisor { + _2 = 0b000, + _4 = 0b001, + _8 = 0b010, + _16 = 0b011, + _32 = 0b100, + _64 = 0b101, + _128 = 0b110, + _256 = 0b111, +} + +impl BaudRateDivisor { + /// Actual divisor value. + pub fn divisor(&self) -> usize { + match self { + BaudRateDivisor::_2 => 2, + BaudRateDivisor::_4 => 4, + BaudRateDivisor::_8 => 8, + BaudRateDivisor::_16 => 16, + BaudRateDivisor::_32 => 32, + BaudRateDivisor::_64 => 64, + BaudRateDivisor::_128 => 128, + BaudRateDivisor::_256 => 256, + } + } +} + +#[bitbybit::bitfield(u32, default = 0x0)] +pub struct Config { + #[bit(31, rw)] + interface_mode: InterfaceMode, + #[bit(26, rw)] + edianness: Endianness, + #[bit(19, rw)] + holdb_dr: bool, + #[bit(16, w)] + manual_start_command: bool, + #[bit(15, rw)] + manual_start_enable: bool, + #[bit(14, rw)] + manual_cs: bool, + /// Directly drives the chip select line when CS is driven manually (bit 14 is set) + #[bit(10, rw)] + peripheral_chip_select: bool, + /// The only valid value is 0b11 (32 bits) + #[bits(6..=7, rw)] + fifo_width: u2, + #[bits(3..=5, rw)] + baud_rate_div: BaudRateDivisor, + #[bit(2, rw)] + clock_phase: SpiClockPhase, + #[bit(1, rw)] + clock_polarity: SpiClockPolarity, + /// Must be set to 1 before using QSPI, 0 is a reserved value. + #[bit(0, rw)] + mode_select: bool, +} + +#[bitbybit::bitfield(u32)] +pub struct InterruptStatus { + /// Write-to-clear bit. + #[bit(6, rw)] + tx_underflow: bool, + #[bit(5, r)] + rx_full: bool, + #[bit(4, r)] + rx_above_threshold: bool, + #[bit(3, r)] + tx_full: bool, + #[bit(2, r)] + tx_below_threshold: bool, + /// Write-to-clear bit. + #[bit(0, rw)] + rx_overrun: bool, +} + +#[bitbybit::bitfield(u32)] +pub struct InterruptControl { + #[bit(6, w)] + tx_underflow: bool, + #[bit(5, w)] + rx_full: bool, + #[bit(4, w)] + rx_not_empty: bool, + #[bit(3, w)] + tx_full: bool, + #[bit(2, w)] + tx_not_full: bool, + #[bit(0, w)] + rx_overrun: bool, +} + +#[bitbybit::bitfield(u32)] +pub struct InterruptMask { + #[bit(6, r)] + tx_underflow: bool, + #[bit(5, r)] + rx_full: bool, + #[bit(4, r)] + rx_not_empty: bool, + #[bit(3, r)] + tx_full: bool, + #[bit(2, r)] + tx_not_full: bool, + #[bit(0, r)] + rx_overrun: bool, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +pub struct SpiEnable { + #[bit(0, rw)] + enable: bool, +} + +/// All the delays are in SPI reference block or external clock cycles. +#[bitbybit::bitfield(u32)] +pub struct Delay { + /// Length of the master mode chip select output de-asserts between words when CPHA = 0. + #[bits(24..=31, rw)] + deassert: u8, + /// Delay between one chip select being de-activated and another being activated. + #[bits(16..=23, rw)] + between: u8, + /// Length between last bit of current word and first bit of next word. + #[bits(8..=15, rw)] + after: u8, + /// Delay between setting chip select low and first bit transfer. + #[bits(0..=7, rw)] + init: u8, +} + +#[bitbybit::bitfield(u32)] +pub struct Gpio { + /// Active low write-protect bit. + #[bit(0, rw)] + write_protect_n: bool, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +pub struct LoopbackMasterClockDelay { + /// Use internal loopback master clock for read data capturing when the baud rate divisor + /// is 2. + #[bit(5, rw)] + use_loopback: bool, + #[bits(3..=4,rw)] + delay_1: u2, + #[bits(0..=2 ,rw)] + delay_0: u3, +} + +#[bitbybit::bitenum(u8, exhaustive = false)] +#[derive(Debug, PartialEq, Eq)] +pub enum InstructionCode { + Read = 0x03, + FastRead = 0x0B, + FastReadDualOutput = 0x3B, + FastReadQuadOutput = 0x6B, + FastReadDualIo = 0xBB, + FastReadQuadIo = 0xEB, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +pub struct LinearQspiConfig { + #[bit(31, rw)] + enable_linear_mode: bool, + #[bit(30, rw)] + both_memories: bool, + /// Only has a meaning is bit 30 is set (both memories). + #[bit(29, rw)] + separate_memory_bus: bool, + /// Upper memory page, if set. Only has a meaning if bit 30 is set and bit 29 / bit 31 are + /// cleared. + /// + /// In LQSPI mode, address bit 25 will indicate the lower (0) or upper (1) page. + /// In IO mode, this bit selects the lower or upper memory. + #[bit(28, rw)] + upper_memory_page: bool, + #[bit(25, rw)] + mode_enable: bool, + #[bit(24, rw)] + mode_on: bool, + #[bits(16..=23, rw)] + mode_bits: u8, + #[bits(8..=10, rw)] + num_dummy_bytes: u3, + #[bits(0..=7, rw)] + instruction_code: Option, +} + +#[bitbybit::bitfield(u32)] +pub struct LinearQspiStatus { + #[bit(2, rw)] + data_fsm_error: bool, + #[bit(1, rw)] + axi_write_command_received: bool, +} + +#[derive(derive_mmio::Mmio)] +#[repr(C)] +pub struct Qspi { + config: Config, + interrupt_status: InterruptStatus, + #[mmio(Write)] + interrupt_enable: InterruptControl, + #[mmio(Write)] + interrupt_disable: InterruptControl, + #[mmio(PureRead)] + interupt_mask: InterruptMask, + spi_enable: SpiEnable, + delay: Delay, + /// Transmits 1-byte command and 3-byte data OR 4-byte data. + #[mmio(Write)] + tx_data_00: u32, + #[mmio(PureRead)] + rx_data: u32, + slave_idle_count: u32, + /// Defines the level at which the TX FIFO not full interrupt is generated. + tx_fifo_threshold: u32, + /// Defines the level at which the RX FIFO not empty interrupt is generated. + rx_fifo_threshold: u32, + gpio: Gpio, + _reserved0: u32, + loopback_master_clock_delay: LoopbackMasterClockDelay, + _reserved1: [u32; 0x11], + /// Transmits 1-byte command. + #[mmio(Write)] + tx_data_01: u32, + /// Transmits 1-byte command and 1-byte data. + #[mmio(Write)] + tx_data_10: u32, + /// Transmits 1-byte command and 2-byte data. + #[mmio(Write)] + tx_data_11: u32, + _reserved2: [u32; 0x5], + linear_qspi_config: LinearQspiConfig, + linear_qspi_status: LinearQspiStatus, + _reserved3: [u32; 0x15], + /// Module ID value with reset value 0x1090101. + module_id: u32, +} + +static_assertions::const_assert_eq!(core::mem::size_of::(), 0x100); + +impl Qspi { + /// Create a new QSPI MMIO instance for for QSPI controller at address [QSPI_BASE_ADDR]. + /// + /// # Safety + /// + /// This API can be used to potentially create a driver to the same peripheral structure + /// from multiple threads. The user must ensure that concurrent accesses are safe and do not + /// interfere with each other. + pub const unsafe fn new_mmio_fixed() -> MmioQspi<'static> { + unsafe { Self::new_mmio_at(QSPI_BASE_ADDR) } + } +} diff --git a/zynq7000/src/slcr/clocks.rs b/zynq7000/src/slcr/clocks.rs index cd7838b..36d6197 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 PllControl { 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 PllControl { reset: bool, } +impl PllControl { + #[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 PllConfig { @@ -48,26 +76,26 @@ pub struct PllConfig { 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, } @@ -141,7 +169,7 @@ pub struct DdrClockControl { ddr_3x_clk_act: bool, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, default = 0x0)] pub struct DciClockControl { /// Second cascade divider. Reset value: 0x1E #[bits(20..=25, rw)] @@ -246,7 +274,7 @@ pub struct CanClockControl { clk_0_act: bool, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, default = 0x0)] pub struct SingleCommonPeriphIoClockControl { #[bits(8..=13, rw)] divisor: u6, @@ -341,9 +369,9 @@ pub struct AperClockControl { #[derive(derive_mmio::Mmio)] #[repr(C)] pub struct ClockControl { - arm_pll: PllControl, - ddr_pll: PllControl, - io_pll: PllControl, + arm_pll_ctrl: PllControl, + ddr_pll_ctrl: PllControl, + io_pll_ctrl: PllControl, pll_status: PllStatus, arm_pll_cfg: PllConfig, ddr_pll_cfg: PllConfig, diff --git a/zynq7000/src/slcr/ddriob.rs b/zynq7000/src/slcr/ddriob.rs new file mode 100644 index 0000000..b88e395 --- /dev/null +++ b/zynq7000/src/slcr/ddriob.rs @@ -0,0 +1,139 @@ +use arbitrary_int::{u2, u3}; + +#[bitbybit::bitenum(u4, exhaustive = false)] +pub enum VRefSel { + /// VREF = 0.6 V + Lpddr2 = 0b0001, + /// VREF = 0.675 V + Ddr3l = 0b0010, + /// VREF = 0.75 V + Ddr3 = 0b0100, + /// VREF = 0.9 V + Ddr2 = 0b1000, +} + +#[bitbybit::bitfield(u32)] +pub struct DdrControl { + /// Enables VRP/VRN. + #[bit(9, rw)] + refio_enable: bool, + #[bit(6, rw)] + vref_ext_en_upper_bits: bool, + #[bit(5, rw)] + vref_ext_en_lower_bits: bool, + #[bits(1..=4, rw)] + vref_sel: Option, + #[bit(0, rw)] + vref_int_en: bool, +} + +#[bitbybit::bitfield(u32, default = 0x00)] +pub struct DciControl { + #[bit(20, rw)] + update_control: bool, + #[bits(17..=19, rw)] + pref_opt2: u3, + #[bits(14..=15, rw)] + pref_opt1: u2, + #[bits(11..=13, rw)] + nref_opt4: u3, + #[bits(8..=10, rw)] + nref_opt2: u3, + #[bits(6..=7, rw)] + nref_opt1: u2, + #[bit(1, rw)] + enable: bool, + /// Reset value 0. Should be toggled once to initialize flops in DCI system. + #[bit(0, rw)] + reset: bool, +} + +#[bitbybit::bitfield(u32)] +pub struct DciStatus { + #[bit(13, rw)] + done: bool, + #[bit(0, rw)] + lock: bool, +} + +#[bitbybit::bitenum(u2, exhaustive = true)] +#[derive(Debug)] +pub enum OutputEnable { + IBuf = 0b00, + __Reserved0 = 0b01, + __Reserved1 = 0b10, + OBuf = 0b11, +} + +#[bitbybit::bitenum(u2, exhaustive = true)] +#[derive(Debug)] +pub enum InputType { + Off = 0b00, + VRefBasedDifferentialReceiverForSstlHstl = 0b01, + DifferentialInputReceiver = 0b10, + LvcmosReceiver = 0b11, +} + +#[bitbybit::bitenum(u2, exhaustive = true)] +#[derive(Debug)] +pub enum DciType { + Disabled = 0b00, + DciDrive = 0b01, + __Reserved = 0b10, + DciTermination = 0b11, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +pub struct DdriobConfig { + #[bit(11, rw)] + pullup_enable: bool, + #[bits(9..=10, rw)] + output_enable: OutputEnable, + #[bit(8, rw)] + term_disable_mode: bool, + #[bit(7, rw)] + ibuf_disable_mode: bool, + #[bits(5..=6, rw)] + dci_type: DciType, + #[bit(4, rw)] + termination_enable: bool, + #[bit(3, rw)] + dci_update_enable: bool, + #[bits(1..=2, rw)] + inp_type: InputType, +} + +#[derive(derive_mmio::Mmio)] +#[repr(C)] +pub struct DdrIoB { + ddriob_addr0: DdriobConfig, + ddriob_addr1: DdriobConfig, + ddriob_data0: DdriobConfig, + ddriob_data1: DdriobConfig, + ddriob_diff0: DdriobConfig, + ddriob_diff1: DdriobConfig, + ddriob_clock: DdriobConfig, + ddriob_drive_slew_addr: u32, + ddriob_drive_slew_data: u32, + ddriob_drive_slew_diff: u32, + ddriob_drive_slew_clock: u32, + ddr_ctrl: DdrControl, + dci_ctrl: DciControl, + dci_status: DciStatus, +} + +impl DdrIoB { + /// Create a new handle to this peripheral. + /// + /// Writing to this register requires unlocking the SLCR registers first. + /// + /// # Safety + /// + /// If you create multiple instances of this handle at the same time, you are responsible for + /// ensuring that there are no read-modify-write races on any of the registers. + pub unsafe fn new_mmio_fixed() -> MmioDdrIoB<'static> { + unsafe { Self::new_mmio_at(super::SLCR_BASE_ADDR + super::DDRIOB_OFFSET) } + } +} + +static_assertions::const_assert_eq!(core::mem::size_of::(), 0x38); diff --git a/zynq7000/src/slcr/mod.rs b/zynq7000/src/slcr/mod.rs index 072c136..ffd4cea 100644 --- a/zynq7000/src/slcr/mod.rs +++ b/zynq7000/src/slcr/mod.rs @@ -12,44 +12,10 @@ const GPIOB_OFFSET: usize = 0xB00; const DDRIOB_OFFSET: usize = 0xB40; pub mod clocks; +pub mod ddriob; pub mod mio; pub mod reset; -#[derive(derive_mmio::Mmio)] -#[repr(C)] -pub struct DdrIoB { - ddriob_addr0: u32, - ddriob_addr1: u32, - ddriob_data0: u32, - ddriob_data1: u32, - ddriob_diff0: u32, - ddriob_diff1: u32, - ddriob_clock: u32, - ddriob_drive_slew_addr: u32, - ddriob_drive_slew_data: u32, - ddriob_drive_slew_diff: u32, - ddriob_drive_slew_clock: u32, - ddriob_ddr_ctrl: u32, - ddriob_dci_ctrl: u32, - ddriob_dci_status: u32, -} - -impl DdrIoB { - /// Create a new handle to this peripheral. - /// - /// Writing to this register requires unlocking the SLCR registers first. - /// - /// # Safety - /// - /// If you create multiple instances of this handle at the same time, you are responsible for - /// ensuring that there are no read-modify-write races on any of the registers. - pub unsafe fn new_mmio_fixed() -> MmioDdrIoB<'static> { - unsafe { Self::new_mmio_at(SLCR_BASE_ADDR + DDRIOB_OFFSET) } - } -} - -static_assertions::const_assert_eq!(core::mem::size_of::(), 0x38); - #[bitbybit::bitenum(u3, exhaustive = false)] pub enum VrefSel { Disabled = 0b000, @@ -93,11 +59,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, } @@ -205,7 +179,7 @@ pub struct Slcr { gpiob: GpiobRegisters, #[mmio(Inner)] - ddriob: DdrIoB, + ddriob: ddriob::DdrIoB, } static_assertions::const_assert_eq!(core::mem::size_of::(), 0xB78); diff --git a/zynq7000/src/slcr/reset.rs b/zynq7000/src/slcr/reset.rs index 286ed23..4e409ce 100644 --- a/zynq7000/src/slcr/reset.rs +++ b/zynq7000/src/slcr/reset.rs @@ -52,6 +52,15 @@ pub struct EthernetReset { gem0_cpu1x_rst: bool, } +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +pub struct QspiResetControl { + #[bit(2, rw)] + qspi_ref_reset: bool, + #[bit(1, rw)] + cpu_1x_reset: bool, +} + #[derive(derive_mmio::Mmio)] #[repr(C)] pub struct ResetControl { @@ -69,7 +78,7 @@ pub struct ResetControl { i2c: DualClockReset, uart: DualRefAndClockReset, gpio: GpioClockReset, - lqspi: u32, + lqspi: QspiResetControl, smc: u32, ocm: u32, _gap0: u32, diff --git a/zynq7000/src/spi.rs b/zynq7000/src/spi.rs index eab4bc0..33efb1e 100644 --- a/zynq7000/src/spi.rs +++ b/zynq7000/src/spi.rs @@ -1,6 +1,8 @@ //! SPI register module. use arbitrary_int::{Number, u4}; +pub use crate::{SpiClockPhase, SpiClockPolarity}; + pub const SPI_0_BASE_ADDR: usize = 0xE000_6000; pub const SPI_1_BASE_ADDR: usize = 0xE000_7000; @@ -56,10 +58,10 @@ pub struct Config { baud_rate_div: Option, /// Clock phase. 1: The SPI clock is inactive outside the word. #[bit(2, rw)] - cpha: bool, + cpha: SpiClockPhase, /// Clock phase. 1: The SPI clock is quiescent high. #[bit(1, rw)] - cpol: bool, + cpol: SpiClockPolarity, /// Master mode enable. 1 is master mode. #[bit(0, rw)] master_ern: bool, diff --git a/zynq7000/src/uart.rs b/zynq7000/src/uart.rs index d2df746..bedb2f2 100644 --- a/zynq7000/src/uart.rs +++ b/zynq7000/src/uart.rs @@ -112,7 +112,7 @@ pub struct Baudgen { #[bitbybit::bitfield(u32, default = 0x0)] #[derive(Debug)] -pub struct BaudRateDiv { +pub struct BaudRateDivisor { #[bits(0..=7, rw)] bdiv: u8, } @@ -319,7 +319,7 @@ pub struct Uart { #[mmio(Read, Write)] fifo: Fifo, /// Baud rate divider register - baud_rate_div: BaudRateDiv, + baud_rate_div: BaudRateDivisor, /// Flow control delay register flow_delay: u32,