From 8463296c3f749c8de31d4e083ae6381187e09334 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 1 Aug 2025 14:32:08 +0200 Subject: [PATCH 1/4] Introduce Rust FSBL --- .../{def-config.toml => config.toml.template} | 0 Cargo.toml | 12 +- README.md | 2 +- examples/embassy/Cargo.toml | 2 +- examples/embassy/build.rs | 31 + memory.x => examples/embassy/memory.x | 3 +- .../embassy/src/bin/dht22-open-drain-pins.rs | 12 +- .../embassy/src/bin/logger-non-blocking.rs | 12 +- examples/embassy/src/bin/pwm.rs | 12 +- examples/embassy/src/main.rs | 77 +- examples/simple/build.rs | 31 + examples/simple/memory.x | 22 + examples/simple/src/bin/gtc-ticks.rs | 10 +- examples/simple/src/bin/logger.rs | 10 +- examples/simple/src/main.rs | 37 +- examples/zedboard/Cargo.toml | 7 +- examples/zedboard/build.rs | 31 + examples/zedboard/memory.x | 22 + examples/zedboard/src/bin/ethernet.rs | 30 +- examples/zedboard/src/bin/l3gd20h-i2c-mio.rs | 10 +- examples/zedboard/src/bin/l3gd20h-spi-mio.rs | 10 +- examples/zedboard/src/bin/qspi.rs | 211 +++++ examples/zedboard/src/bin/uart-blocking.rs | 14 +- .../zedboard/src/bin/uart-non-blocking.rs | 14 +- examples/zedboard/src/lib.rs | 1 - examples/zedboard/src/main.rs | 99 +- justfile | 5 + scripts/memory_ddr.x | 22 + scripts/memory_ocm.x | 24 + scripts/xsct-flasher.tcl | 144 +++ scripts/xsct-init.tcl | 86 -- scripts/zynq7000-init.py | 13 +- zedboard-bsp/Cargo.toml | 11 + zedboard-bsp/src/lib.rs | 4 + .../src/phy_marvell.rs | 0 zedboard-bsp/src/qspi_spansion.rs | 613 ++++++++++++ zedboard-fpga-design/src/zedboard-bd.tcl | 12 + zedboard-fsbl/Cargo.toml | 22 + zedboard-fsbl/build.rs | 31 + zedboard-fsbl/memory.x | 24 + zedboard-fsbl/src/ddr_cfg.rs | 503 ++++++++++ zedboard-fsbl/src/main.rs | 274 ++++++ zedboard-qspi-flasher/Cargo.toml | 14 + zedboard-qspi-flasher/src/main.rs | 139 +++ zynq-boot-image/Cargo.toml | 9 + zynq-boot-image/src/lib.rs | 437 +++++++++ zynq-boot-image/staging/.gitignore | 4 + zynq-boot-image/staging/README.md | 28 + zynq-boot-image/staging/boot.bif | 6 + zynq-boot-image/tester/.gitignore | 1 + zynq-boot-image/tester/Cargo.lock | 305 ++++++ zynq-boot-image/tester/Cargo.toml | 10 + zynq-boot-image/tester/src/main.rs | 82 ++ zynq-mmu/Cargo.toml | 2 +- zynq7000-hal/Cargo.toml | 9 +- zynq7000-hal/src/{clocks.rs => clocks/mod.rs} | 63 +- zynq7000-hal/src/clocks/pll.rs | 356 +++++++ zynq7000-hal/src/ddr/ll.rs | 334 +++++++ zynq7000-hal/src/ddr/mod.rs | 227 +++++ zynq7000-hal/src/devcfg.rs | 64 ++ zynq7000-hal/src/eth/ll.rs | 6 +- zynq7000-hal/src/eth/mod.rs | 122 +-- zynq7000-hal/src/eth/rx_descr.rs | 2 +- zynq7000-hal/src/gic.rs | 2 +- zynq7000-hal/src/gpio/mio.rs | 4 +- zynq7000-hal/src/gpio/mod.rs | 4 +- zynq7000-hal/src/gtc.rs | 2 +- zynq7000-hal/src/i2c.rs | 4 +- zynq7000-hal/src/lib.rs | 123 ++- zynq7000-hal/src/priv_tim.rs | 108 +++ zynq7000-hal/src/qspi/lqspi_configs.rs | 234 +++++ zynq7000-hal/src/qspi/mod.rs | 681 ++++++++++++++ zynq7000-hal/src/slcr.rs | 4 +- zynq7000-hal/src/spi/mod.rs | 30 +- zynq7000-hal/src/ttc.rs | 21 +- zynq7000-hal/src/uart/mod.rs | 66 +- zynq7000-hal/src/uart/rx.rs | 2 +- zynq7000-rt/Cargo.toml | 4 +- zynq7000-rt/src/mmu.rs | 5 + zynq7000/Cargo.toml | 4 +- zynq7000/src/ddrc.rs | 880 ++++++++++++++++++ zynq7000/src/devcfg.rs | 303 ++++++ zynq7000/src/lib.rs | 31 +- zynq7000/src/priv_tim.rs | 48 + zynq7000/src/qspi.rs | 278 ++++++ zynq7000/src/slcr/clocks.rs | 115 ++- zynq7000/src/slcr/ddriob.rs | 139 +++ zynq7000/src/slcr/mod.rs | 48 +- zynq7000/src/slcr/reset.rs | 11 +- zynq7000/src/spi.rs | 8 +- zynq7000/src/uart.rs | 4 +- zynq7000/src/xadc.rs | 29 + 92 files changed, 7361 insertions(+), 561 deletions(-) rename .cargo/{def-config.toml => config.toml.template} (100%) create mode 100644 examples/embassy/build.rs rename memory.x => examples/embassy/memory.x (94%) create mode 100644 examples/simple/build.rs create mode 100644 examples/simple/memory.x create mode 100644 examples/zedboard/build.rs create mode 100644 examples/zedboard/memory.x create mode 100644 examples/zedboard/src/bin/qspi.rs create mode 100644 justfile create mode 100644 scripts/memory_ddr.x create mode 100644 scripts/memory_ocm.x create mode 100644 scripts/xsct-flasher.tcl delete mode 100644 scripts/xsct-init.tcl create mode 100644 zedboard-bsp/Cargo.toml create mode 100644 zedboard-bsp/src/lib.rs rename {examples/zedboard => zedboard-bsp}/src/phy_marvell.rs (100%) create mode 100644 zedboard-bsp/src/qspi_spansion.rs create mode 100644 zedboard-fsbl/Cargo.toml create mode 100644 zedboard-fsbl/build.rs create mode 100644 zedboard-fsbl/memory.x create mode 100644 zedboard-fsbl/src/ddr_cfg.rs create mode 100644 zedboard-fsbl/src/main.rs create mode 100644 zedboard-qspi-flasher/Cargo.toml create mode 100644 zedboard-qspi-flasher/src/main.rs create mode 100644 zynq-boot-image/Cargo.toml create mode 100644 zynq-boot-image/src/lib.rs create mode 100644 zynq-boot-image/staging/.gitignore create mode 100644 zynq-boot-image/staging/README.md create mode 100644 zynq-boot-image/staging/boot.bif create mode 100644 zynq-boot-image/tester/.gitignore create mode 100644 zynq-boot-image/tester/Cargo.lock create mode 100644 zynq-boot-image/tester/Cargo.toml create mode 100644 zynq-boot-image/tester/src/main.rs rename zynq7000-hal/src/{clocks.rs => clocks/mod.rs} (86%) create mode 100644 zynq7000-hal/src/clocks/pll.rs create mode 100644 zynq7000-hal/src/ddr/ll.rs create mode 100644 zynq7000-hal/src/ddr/mod.rs create mode 100644 zynq7000-hal/src/devcfg.rs create mode 100644 zynq7000-hal/src/priv_tim.rs create mode 100644 zynq7000-hal/src/qspi/lqspi_configs.rs create mode 100644 zynq7000-hal/src/qspi/mod.rs create mode 100644 zynq7000/src/ddrc.rs create mode 100644 zynq7000/src/devcfg.rs create mode 100644 zynq7000/src/priv_tim.rs create mode 100644 zynq7000/src/qspi.rs create mode 100644 zynq7000/src/slcr/ddriob.rs create mode 100644 zynq7000/src/xadc.rs diff --git a/.cargo/def-config.toml b/.cargo/config.toml.template similarity index 100% rename from .cargo/def-config.toml rename to .cargo/config.toml.template diff --git a/Cargo.toml b/Cargo.toml index a8de1af..5c9db71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,14 @@ members = [ "examples/embassy", "examples/zedboard", "zynq-mmu", + "zedboard-fsbl", + "zedboard-bsp", + "zynq-boot-image", "zedboard-qspi-flasher", +] + +exclude = [ + "zynq-boot-image/tester", ] -exclude = ["experiments"] # cargo build/run --release [profile.release] @@ -21,3 +27,7 @@ incremental = false lto = true opt-level = 3 # <- overflow-checks = false # <- + +[profile.small] +inherits = "release" +opt-level = "z" diff --git a/README.md b/README.md index 7a1e81e..373294b 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ The folder contains a README with all the steps required to load this project fr Use the following command to have a starting `config.toml` file ```sh -cp .cargo/def-config.toml .cargo/config.toml +cp .cargo/config.toml.template .cargo/config.toml ``` You then can adapt the `config.toml` to your needs. For example, you can configure runners diff --git a/examples/embassy/Cargo.toml b/examples/embassy/Cargo.toml index e58dd47..c17151b 100644 --- a/examples/embassy/Cargo.toml +++ b/examples/embassy/Cargo.toml @@ -19,7 +19,7 @@ zynq7000-embassy = { path = "../../zynq7000-embassy" } dht-sensor = { git = "https://github.com/michaelbeaumont/dht-sensor.git", rev = "10319bdeae9ace3bb0fc79a15da2869c5bf50f52", features = ["async"] } static_cell = "2" critical-section = "1" -heapless = "0.8" +heapless = "0.9" embedded-io = "0.6" embedded-hal = "1" fugit = "0.3" 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..0c66df0 100644 --- a/examples/embassy/src/bin/dht22-open-drain-pins.rs +++ b/examples/embassy/src/bin/dht22-open-drain-pins.rs @@ -17,10 +17,10 @@ use zynq7000_hal::{ gtc::GlobalTimerCounter, l2_cache, time::Hertz, - uart::{ClockConfigRaw, Uart, UartConfig}, + uart::{ClockConfig, Config, Uart}, }; -use zynq7000::PsPeripherals; +use zynq7000::Peripherals; use zynq7000_rt as _; // Define the clock frequency as a constant @@ -44,7 +44,7 @@ const OPEN_DRAIN_PINS_MIO9_TO_MIO14: bool = false; #[embassy_executor::main] #[unsafe(export_name = "main")] async fn main(_spawner: Spawner) -> ! { - let mut dp = PsPeripherals::take().unwrap(); + let mut dp = Peripherals::take().unwrap(); l2_cache::init_with_defaults(&mut dp.l2c); // Clock was already initialized by PS7 Init TCL script or FSBL, we just read it. @@ -64,12 +64,12 @@ async fn main(_spawner: Spawner) -> ! { 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) + let uart_clk_config = ClockConfig::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), + Config::new_with_clk_config(uart_clk_config), (mio_pins.mio48, mio_pins.mio49), ) .unwrap(); @@ -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..3c09354 100644 --- a/examples/embassy/src/bin/logger-non-blocking.rs +++ b/examples/embassy/src/bin/logger-non-blocking.rs @@ -9,7 +9,7 @@ use embassy_time::{Duration, Ticker}; use embedded_hal::digital::StatefulOutputPin; use embedded_io::Write; use log::{error, info}; -use zynq7000::PsPeripherals; +use zynq7000::Peripherals; use zynq7000_hal::{ BootMode, clocks::Clocks, @@ -18,7 +18,7 @@ use zynq7000_hal::{ gtc::GlobalTimerCounter, l2_cache, time::Hertz, - uart::{ClockConfigRaw, TxAsync, Uart, UartConfig, on_interrupt_tx}, + uart::{ClockConfig, Config, TxAsync, Uart, on_interrupt_tx}, }; use zynq7000_rt as _; @@ -38,7 +38,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! { #[unsafe(export_name = "main")] #[embassy_executor::main] async fn main(spawner: Spawner) -> ! { - let mut dp = PsPeripherals::take().unwrap(); + let mut dp = Peripherals::take().unwrap(); l2_cache::init_with_defaults(&mut dp.l2c); // Clock was already initialized by PS7 Init TCL script or FSBL, we just read it. @@ -58,12 +58,12 @@ async fn main(spawner: Spawner) -> ! { let mio_pins = mio::Pins::new(dp.gpio); // Set up the UART, we are logging with it. - let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200) + let uart_clk_config = ClockConfig::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), + Config::new_with_clk_config(uart_clk_config), (mio_pins.mio48, mio_pins.mio49), ) .unwrap(); @@ -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..31f9034 100644 --- a/examples/embassy/src/bin/pwm.rs +++ b/examples/embassy/src/bin/pwm.rs @@ -23,10 +23,10 @@ use zynq7000_hal::{ gtc::GlobalTimerCounter, l2_cache, time::Hertz, - uart::{ClockConfigRaw, Uart, UartConfig}, + uart::{ClockConfig, Config, Uart}, }; -use zynq7000::PsPeripherals; +use zynq7000::Peripherals; use zynq7000_rt as _; // Define the clock frequency as a constant @@ -44,7 +44,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! { #[embassy_executor::main] #[unsafe(export_name = "main")] async fn main(_spawner: Spawner) -> ! { - let mut dp = PsPeripherals::take().unwrap(); + let mut dp = Peripherals::take().unwrap(); l2_cache::init_with_defaults(&mut dp.l2c); // Clock was already initialized by PS7 Init TCL script or FSBL, we just read it. @@ -71,12 +71,12 @@ async fn main(_spawner: Spawner) -> ! { pwm.set_duty_cycle_percent(50).unwrap(); // Set up the UART, we are logging with it. - let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200) + let uart_clk_config = ClockConfig::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), + Config::new_with_clk_config(uart_clk_config), (mio_pins.mio48, mio_pins.mio49), ) .unwrap(); @@ -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..afbdfe4 100644 --- a/examples/embassy/src/main.rs +++ b/examples/embassy/src/main.rs @@ -8,18 +8,8 @@ use embassy_time::{Duration, Ticker}; use embedded_hal::digital::StatefulOutputPin; use embedded_io::Write; use log::{error, info}; -use zynq7000_hal::{ - BootMode, - clocks::Clocks, - gic::{GicConfigurator, GicInterruptHelper, Interrupt}, - gpio::{Output, PinState, mio}, - gtc::GlobalTimerCounter, - l2_cache, - time::Hertz, - uart::{ClockConfigRaw, Uart, UartConfig}, -}; +use zynq7000_hal::{clocks, gic, gpio, gtc, time::Hertz, uart, BootMode, InteruptConfig}; -use zynq7000::PsPeripherals; use zynq7000_rt as _; // Define the clock frequency as a constant @@ -37,32 +27,27 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! { #[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); - - // 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 mio_pins = mio::Pins::new(dp.gpio); + let periphs = zynq7000_hal::init(zynq7000_hal::Config { + init_l2_cache: true, + level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll), + interrupt_config: Some(InteruptConfig::AllInterruptsToCpu0), + }) + .unwrap(); + let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap(); // Set up global timer counter and embassy time driver. - let gtc = GlobalTimerCounter::new(dp.gtc, clocks.arm_clocks()); + let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks()); zynq7000_embassy::init(clocks.arm_clocks(), gtc); + let mio_pins = gpio::mio::Pins::new(periphs.gpio); + // Set up the UART, we are logging with it. - let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200) + let uart_clk_config = uart::ClockConfig::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), + let mut uart = uart::Uart::new_with_mio( + periphs.uart_1, + uart::Config::new_with_clk_config(uart_clk_config), (mio_pins.mio48, mio_pins.mio49), ) .unwrap(); @@ -77,11 +62,11 @@ 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)); - let mut led = Output::new_for_mio(mio_pins.mio7, PinState::Low); + let mut led = gpio::Output::new_for_mio(mio_pins.mio7, gpio::PinState::Low); loop { info!("Hello, world!"); led.toggle().unwrap(); @@ -89,42 +74,42 @@ async fn main(_spawner: Spawner) -> ! { } } -#[unsafe(no_mangle)] -pub extern "C" fn _irq_handler() { - let mut gic_helper = GicInterruptHelper::new(); +#[zynq7000_rt::irq] +pub fn irq_handler() { + let mut gic_helper = gic::GicInterruptHelper::new(); let irq_info = gic_helper.acknowledge_interrupt(); match irq_info.interrupt() { - Interrupt::Sgi(_) => (), - Interrupt::Ppi(ppi_interrupt) => { + gic::Interrupt::Sgi(_) => (), + gic::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::Interrupt::Spi(_spi_interrupt) => (), + gic::Interrupt::Invalid(_) => (), + gic::Interrupt::Spurious => (), } gic_helper.end_of_interrupt(irq_info); } -#[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/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..895c839 100644 --- a/examples/simple/src/bin/gtc-ticks.rs +++ b/examples/simple/src/bin/gtc-ticks.rs @@ -15,13 +15,13 @@ use zynq7000_hal::{ l2_cache, prelude::*, time::Hertz, - uart::{ClockConfigRaw, Uart, UartConfig}, + uart::{ClockConfig, Config, Uart}, }; 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); @@ -36,7 +36,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! { #[unsafe(export_name = "main")] pub fn main() -> ! { - let mut dp = zynq7000::PsPeripherals::take().unwrap(); + let mut dp = zynq7000::Peripherals::take().unwrap(); l2_cache::init_with_defaults(&mut dp.l2c); // Clock was already initialized by PS7 Init TCL script or FSBL, we just read it. @@ -49,7 +49,7 @@ pub fn main() -> ! { // Enable interrupt exception. unsafe { gic.enable_interrupts() }; // Set up the UART, we are logging with it. - let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200) + let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200) .unwrap() .0; let mut gtc = GlobalTimerCounter::new(dp.gtc, clocks.arm_clocks()); @@ -64,7 +64,7 @@ pub fn main() -> ! { let mio_pins = mio::Pins::new(dp.gpio); let mut uart = Uart::new_with_mio( dp.uart_1, - UartConfig::new_with_clk_config(uart_clk_config), + Config::new_with_clk_config(uart_clk_config), (mio_pins.mio48, mio_pins.mio49), ) .unwrap(); diff --git a/examples/simple/src/bin/logger.rs b/examples/simple/src/bin/logger.rs index 2a359dc..3e6cd38 100644 --- a/examples/simple/src/bin/logger.rs +++ b/examples/simple/src/bin/logger.rs @@ -16,7 +16,7 @@ use zynq7000_hal::{ l2_cache, prelude::*, time::Hertz, - uart::{ClockConfigRaw, Uart, UartConfig}, + uart::{ClockConfig, Config, Uart}, }; use zynq7000_rt as _; @@ -37,7 +37,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! { #[unsafe(export_name = "main")] pub fn main() -> ! { - let mut dp = zynq7000::PsPeripherals::take().unwrap(); + let mut dp = zynq7000::Peripherals::take().unwrap(); l2_cache::init_with_defaults(&mut dp.l2c); // Clock was already initialized by PS7 Init TCL script or FSBL, we just read it. @@ -50,7 +50,7 @@ pub fn main() -> ! { // Enable interrupt exception. unsafe { gic.enable_interrupts() }; // Set up the UART, we are logging with it. - let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200) + let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200) .unwrap() .0; let mut gtc = GlobalTimerCounter::new(dp.gtc, clocks.arm_clocks()); @@ -64,7 +64,7 @@ pub fn main() -> ! { let mut uart = Uart::new_with_mio( dp.uart_1, - UartConfig::new_with_clk_config(uart_clk_config), + Config::new_with_clk_config(uart_clk_config), (mio_pins.mio48, mio_pins.mio49), ) .unwrap(); @@ -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..9c822f0 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 zynq7000::PsPeripherals; +use embedded_hal::{delay::DelayNs, digital::StatefulOutputPin}; +use zynq7000::Peripherals; 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) -> ! { @@ -48,14 +56,15 @@ pub fn main() -> ! { } } Lib::Hal => { - let dp = PsPeripherals::take().unwrap(); + let dp = Peripherals::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 5e8a56b..279562c 100644 --- a/examples/zedboard/Cargo.toml +++ b/examples/zedboard/Cargo.toml @@ -16,10 +16,12 @@ 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" -arbitrary-int = "1.3" +bitbybit = "1.4" +arbitrary-int = "2" embedded-io-async = "0.6" critical-section = "1" static_cell = "2" @@ -38,6 +40,7 @@ embassy-executor = { git = "https://github.com/us-irs/embassy.git", branch = "co embassy-time = { version = "0.5", features = ["tick-hz-1_000_000", "generic-queue-16"] } embassy-net = { version = "0.7", features = ["dhcpv4", "packet-trace", "medium-ethernet", "icmp", "tcp", "udp"] } embassy-sync = { version = "0.7" } +# TODO: Bump as soon as new compatible smoltcp/embassy-net version is released. heapless = "0.8" axi-uartlite = { git = "https://egit.irs.uni-stuttgart.de/rust/axi-uartlite.git" } axi-uart16550 = { git = "https://egit.irs.uni-stuttgart.de/rust/axi-uart16550.git" } 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..6f5eabb 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, @@ -48,10 +46,10 @@ use zynq7000_hal::{ gpio::{GpioPins, Output, PinState}, gtc::GlobalTimerCounter, l2_cache, - uart::{ClockConfigRaw, Uart, UartConfig}, + uart::{ClockConfig, Config, Uart}, }; -use zynq7000::{PsPeripherals, slcr::LevelShifterConfig}; +use zynq7000::{Peripherals, slcr::LevelShifterConfig}; use zynq7000_rt::{self as _, mmu::section_attrs::SHAREABLE_DEVICE, mmu_l1_table_mut}; const USE_DHCP: bool = true; @@ -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, @@ -208,7 +206,7 @@ async fn tcp_task(mut tcp: TcpSocket<'static>) -> ! { #[embassy_executor::main] #[unsafe(export_name = "main")] async fn main(spawner: Spawner) -> ! { - let mut dp = PsPeripherals::take().unwrap(); + let mut dp = Peripherals::take().unwrap(); l2_cache::init_with_defaults(&mut dp.l2c); // Enable PS-PL level shifters. @@ -236,12 +234,12 @@ async fn main(spawner: Spawner) -> ! { 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) + let uart_clk_config = ClockConfig::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), + Config::new_with_clk_config(uart_clk_config), (gpio_pins.mio.mio48, gpio_pins.mio.mio49), ) .unwrap(); @@ -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..cfb3e6b 100644 --- a/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs +++ b/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs @@ -29,7 +29,7 @@ use zynq7000_hal::{ uart, }; -use zynq7000::{PsPeripherals, slcr::LevelShifterConfig}; +use zynq7000::{Peripherals, slcr::LevelShifterConfig}; use zynq7000_rt as _; // Define the clock frequency as a constant @@ -48,7 +48,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! { #[embassy_executor::main] #[unsafe(export_name = "main")] async fn main(_spawner: Spawner) -> ! { - let mut dp = PsPeripherals::take().unwrap(); + let mut dp = Peripherals::take().unwrap(); l2_cache::init_with_defaults(&mut dp.l2c); // Enable PS-PL level shifters. @@ -73,12 +73,12 @@ async fn main(_spawner: Spawner) -> ! { zynq7000_embassy::init(clocks.arm_clocks(), gtc); // Set up the UART, we are logging with it. - let uart_clk_config = uart::ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200) + let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200) .unwrap() .0; let mut uart = uart::Uart::new_with_mio( dp.uart_1, - uart::UartConfig::new_with_clk_config(uart_clk_config), + uart::Config::new_with_clk_config(uart_clk_config), (gpio_pins.mio.mio48, gpio_pins.mio.mio49), ) .unwrap(); @@ -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..14f6bd6 100644 --- a/examples/zedboard/src/bin/l3gd20h-spi-mio.rs +++ b/examples/zedboard/src/bin/l3gd20h-spi-mio.rs @@ -30,7 +30,7 @@ use zynq7000_hal::{ uart::{self, TxAsync, on_interrupt_tx}, }; -use zynq7000::{PsPeripherals, slcr::LevelShifterConfig, spi::DelayControl}; +use zynq7000::{Peripherals, slcr::LevelShifterConfig, spi::DelayControl}; use zynq7000_rt as _; // Define the clock frequency as a constant @@ -51,7 +51,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! { #[embassy_executor::main] #[unsafe(export_name = "main")] async fn main(spawner: Spawner) -> ! { - let mut dp = PsPeripherals::take().unwrap(); + let mut dp = Peripherals::take().unwrap(); l2_cache::init_with_defaults(&mut dp.l2c); // Enable PS-PL level shifters. @@ -83,12 +83,12 @@ async fn main(spawner: Spawner) -> ! { zynq7000_embassy::init(clocks.arm_clocks(), gtc); // Set up the UART, we are logging with it. - let uart_clk_config = uart::ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200) + let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200) .unwrap() .0; let mut uart = uart::Uart::new_with_mio( dp.uart_1, - uart::UartConfig::new_with_clk_config(uart_clk_config), + uart::Config::new_with_clk_config(uart_clk_config), (gpio_pins.mio.mio48, gpio_pins.mio.mio49), ) .unwrap(); @@ -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..7c1e30f --- /dev/null +++ b/examples/zedboard/src/bin/qspi.rs @@ -0,0 +1,211 @@ +#![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::{clocks, gic, gpio, gtc, prelude::*, qspi, uart, BootMode}; + +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 periphs = zynq7000_hal::init(zynq7000_hal::Config { + init_l2_cache: true, + level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll), + interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0), + }) + .unwrap(); + // Clock was already initialized by PS7 Init TCL script or FSBL, we just read it. + let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap(); + + let gpio_pins = gpio::GpioPins::new(periphs.gpio); + + // Set up global timer counter and embassy time driver. + let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks()); + zynq7000_embassy::init(clocks.arm_clocks(), gtc); + + // Set up the UART, we are logging with it. + let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200) + .unwrap() + .0; + let mut uart = uart::Uart::new_with_mio( + periphs.uart_1, + uart::Config::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(qspi::SrcSelIo::IoPll, &clocks, 100.MHz()) + .expect("QSPI clock calculation failed"); + let qspi = qspi::Qspi::new_single_qspi_with_feedback( + periphs.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 = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low); + loop { + mio_led.toggle().unwrap(); + + ticker.next().await; // Wait for the next cycle of the ticker + } +} + +#[zynq7000_rt::irq] +fn irq_handler() { + let mut gic_helper = gic::GicInterruptHelper::new(); + let irq_info = gic_helper.acknowledge_interrupt(); + match irq_info.interrupt() { + gic::Interrupt::Sgi(_) => (), + gic::Interrupt::Ppi(ppi_interrupt) => { + if ppi_interrupt == gic::PpiInterrupt::GlobalTimer { + unsafe { + zynq7000_embassy::on_interrupt(); + } + } + } + gic::Interrupt::Spi(_spi_interrupt) => (), + gic::Interrupt::Invalid(_) => (), + gic::Interrupt::Spurious => (), + } + gic_helper.end_of_interrupt(irq_info); +} + +#[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/examples/zedboard/src/bin/uart-blocking.rs b/examples/zedboard/src/bin/uart-blocking.rs index 5d98926..afdb631 100644 --- a/examples/zedboard/src/bin/uart-blocking.rs +++ b/examples/zedboard/src/bin/uart-blocking.rs @@ -20,10 +20,10 @@ use zynq7000_hal::{ gpio::{GpioPins, Output, PinState}, gtc::GlobalTimerCounter, l2_cache, - uart::{ClockConfigRaw, Uart, UartConfig}, + uart::{ClockConfig, Config, Uart}, }; -use zynq7000::{PsPeripherals, slcr::LevelShifterConfig}; +use zynq7000::{Peripherals, slcr::LevelShifterConfig}; use zynq7000_rt as _; const INIT_STRING: &str = "-- Zynq 7000 Zedboard blocking UART example --\n\r"; @@ -101,7 +101,7 @@ impl UartMultiplexer { #[embassy_executor::main] #[unsafe(export_name = "main")] async fn main(_spawner: Spawner) -> ! { - let mut dp = PsPeripherals::take().unwrap(); + let mut dp = Peripherals::take().unwrap(); l2_cache::init_with_defaults(&mut dp.l2c); // Enable PS-PL level shifters. @@ -123,12 +123,12 @@ async fn main(_spawner: Spawner) -> ! { 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) + let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200) .unwrap() .0; let mut log_uart = Uart::new_with_mio( dp.uart_1, - UartConfig::new_with_clk_config(uart_clk_config), + Config::new_with_clk_config(uart_clk_config), (gpio_pins.mio.mio48, gpio_pins.mio.mio49), ) .unwrap(); @@ -145,7 +145,7 @@ async fn main(_spawner: Spawner) -> ! { // UART0 routed through EMIO to PL pins. let mut uart_0 = - Uart::new_with_emio(dp.uart_0, UartConfig::new_with_clk_config(uart_clk_config)).unwrap(); + Uart::new_with_emio(dp.uart_0, Config::new_with_clk_config(uart_clk_config)).unwrap(); // Safety: Valid address of AXI UARTLITE. let mut uartlite = unsafe { AxiUartlite::new(AXI_UARTLITE_BASE_ADDR) }; @@ -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..1b3064d 100644 --- a/examples/zedboard/src/bin/uart-non-blocking.rs +++ b/examples/zedboard/src/bin/uart-non-blocking.rs @@ -47,7 +47,7 @@ use zynq7000_hal::{ gtc::GlobalTimerCounter, l2_cache, time::Hertz, - uart::{ClockConfigRaw, Uart, UartConfig}, + uart::{ClockConfig, Config, Uart}, }; pub enum UartMode { @@ -62,7 +62,7 @@ const INIT_STRING: &str = "-- Zynq 7000 Zedboard non-blocking UART example --\n\ #[global_allocator] static HEAP: Heap = Heap::empty(); -use zynq7000::{PsPeripherals, slcr::LevelShifterConfig}; +use zynq7000::{Peripherals, slcr::LevelShifterConfig}; use zynq7000_rt as _; // Define the clock frequency as a constant @@ -166,7 +166,7 @@ impl UartMultiplexer { #[embassy_executor::main] #[unsafe(export_name = "main")] async fn main(spawner: Spawner) -> ! { - let mut dp = PsPeripherals::take().unwrap(); + let mut dp = Peripherals::take().unwrap(); l2_cache::init_with_defaults(&mut dp.l2c); // Enable PS-PL level shifters. @@ -194,12 +194,12 @@ async fn main(spawner: Spawner) -> ! { 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) + let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200) .unwrap() .0; let mut log_uart = Uart::new_with_mio( dp.uart_1, - UartConfig::new_with_clk_config(uart_clk_config), + Config::new_with_clk_config(uart_clk_config), (gpio_pins.mio.mio48, gpio_pins.mio.mio49), ) .unwrap(); @@ -237,7 +237,7 @@ async fn main(spawner: Spawner) -> ! { // UART0 routed through EMIO to PL pins. let uart_0 = - Uart::new_with_emio(dp.uart_0, UartConfig::new_with_clk_config(uart_clk_config)).unwrap(); + Uart::new_with_emio(dp.uart_0, Config::new_with_clk_config(uart_clk_config)).unwrap(); // Safety: Valid address of AXI UARTLITE. let mut uartlite = unsafe { AxiUartlite::new(AXI_UARTLITE_BASE_ADDR) }; // We need to call this before splitting the structure, because the interrupt signal is @@ -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..b08c435 100644 --- a/examples/zedboard/src/main.rs +++ b/examples/zedboard/src/main.rs @@ -9,18 +9,8 @@ use embedded_hal::digital::StatefulOutputPin; use embedded_io::Write; use log::{error, info}; use zedboard::PS_CLOCK_FREQUENCY; -use zynq7000_hal::{ - BootMode, - clocks::Clocks, - configure_level_shifter, - gic::{GicConfigurator, GicInterruptHelper, Interrupt}, - gpio::{GpioPins, Output, PinState}, - gtc::GlobalTimerCounter, - l2_cache, - uart::{ClockConfigRaw, Uart, UartConfig}, -}; +use zynq7000_hal::{clocks, gic, gpio, gtc, uart, BootMode}; -use zynq7000::{PsPeripherals, slcr::LevelShifterConfig}; use zynq7000_rt as _; const INIT_STRING: &str = "-- Zynq 7000 Zedboard GPIO blinky example --\n\r"; @@ -37,35 +27,28 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! { #[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); - + let periphs = zynq7000_hal::init(zynq7000_hal::Config { + init_l2_cache: true, + level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll), + interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0), + }) + .unwrap(); // 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 mut gpio_pins = GpioPins::new(dp.gpio); + let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap(); + + let mut gpio_pins = gpio::GpioPins::new(periphs.gpio); // Set up global timer counter and embassy time driver. - let gtc = GlobalTimerCounter::new(dp.gtc, clocks.arm_clocks()); + let gtc = gtc::GlobalTimerCounter::new(periphs.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) + let uart_clk_config = uart::ClockConfig::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), + let mut uart = uart::Uart::new_with_mio( + periphs.uart_1, + uart::Config::new_with_clk_config(uart_clk_config), (gpio_pins.mio.mio48, gpio_pins.mio.mio49), ) .unwrap(); @@ -79,21 +62,21 @@ 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)); - let mut mio_led = Output::new_for_mio(gpio_pins.mio.mio7, PinState::Low); - let mut emio_leds: [Output; 8] = [ - Output::new_for_emio(gpio_pins.emio.take(0).unwrap(), PinState::Low), - Output::new_for_emio(gpio_pins.emio.take(1).unwrap(), PinState::Low), - Output::new_for_emio(gpio_pins.emio.take(2).unwrap(), PinState::Low), - Output::new_for_emio(gpio_pins.emio.take(3).unwrap(), PinState::Low), - Output::new_for_emio(gpio_pins.emio.take(4).unwrap(), PinState::Low), - Output::new_for_emio(gpio_pins.emio.take(5).unwrap(), PinState::Low), - Output::new_for_emio(gpio_pins.emio.take(6).unwrap(), PinState::Low), - Output::new_for_emio(gpio_pins.emio.take(7).unwrap(), PinState::Low), + let mut mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low); + let mut emio_leds: [gpio::Output; 8] = [ + gpio::Output::new_for_emio(gpio_pins.emio.take(0).unwrap(), gpio::PinState::Low), + gpio::Output::new_for_emio(gpio_pins.emio.take(1).unwrap(), gpio::PinState::Low), + gpio::Output::new_for_emio(gpio_pins.emio.take(2).unwrap(), gpio::PinState::Low), + gpio::Output::new_for_emio(gpio_pins.emio.take(3).unwrap(), gpio::PinState::Low), + gpio::Output::new_for_emio(gpio_pins.emio.take(4).unwrap(), gpio::PinState::Low), + gpio::Output::new_for_emio(gpio_pins.emio.take(5).unwrap(), gpio::PinState::Low), + gpio::Output::new_for_emio(gpio_pins.emio.take(6).unwrap(), gpio::PinState::Low), + gpio::Output::new_for_emio(gpio_pins.emio.take(7).unwrap(), gpio::PinState::Low), ]; loop { mio_led.toggle().unwrap(); @@ -108,42 +91,42 @@ async fn main(_spawner: Spawner) -> ! { } } -#[unsafe(no_mangle)] -pub extern "C" fn _irq_handler() { - let mut gic_helper = GicInterruptHelper::new(); +#[zynq7000_rt::irq] +fn irq_handler() { + let mut gic_helper = gic::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 { + gic::Interrupt::Sgi(_) => (), + gic::Interrupt::Ppi(ppi_interrupt) => { + if ppi_interrupt == gic::PpiInterrupt::GlobalTimer { unsafe { zynq7000_embassy::on_interrupt(); } } } - Interrupt::Spi(_spi_interrupt) => (), - Interrupt::Invalid(_) => (), - Interrupt::Spurious => (), + gic::Interrupt::Spi(_spi_interrupt) => (), + gic::Interrupt::Invalid(_) => (), + gic::Interrupt::Spurious => (), } gic_helper.end_of_interrupt(irq_info); } -#[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/justfile b/justfile new file mode 100644 index 0000000..a761458 --- /dev/null +++ b/justfile @@ -0,0 +1,5 @@ + +[working-directory: 'zynq-boot-image/staging'] +bootgen: + bootgen -arch zynq -image boot.bif -o boot.bin -w on + echo "Generated boot.bin at zynq-boot-image/staging" 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..f8fa030 --- /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.4" +arbitrary-int = "2" +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..31258d6 --- /dev/null +++ b/zedboard-bsp/src/qspi_spansion.rs @@ -0,0 +1,613 @@ +use core::cell::RefCell; + +use arbitrary_int::{prelude::*, u24}; +use zynq7000_hal::qspi::{ + FIFO_DEPTH, LinearQspiConfig, QspiIoMode, QspiIoTransferGuard, QspiLinearAddressing, + QspiLinearReadGuard, +}; + +pub const QSPI_DEV_COMBINATION_REV_F: zynq7000_hal::qspi::QspiDeviceCombination = + zynq7000_hal::qspi::QspiDeviceCombination { + vendor: zynq7000_hal::qspi::QspiVendor::WinbondAndSpansion, + operating_mode: zynq7000_hal::qspi::OperatingMode::FastReadQuadOutput, + two_devices: false, + }; + +#[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 const BASE_ADDR: usize = QspiLinearAddressing::BASE_ADDRESS; + + 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..00a44e2 --- /dev/null +++ b/zedboard-fsbl/Cargo.toml @@ -0,0 +1,22 @@ +[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" } +zedboard-bsp = { path = "../zedboard-bsp" } +zynq-boot-image = { path = "../zynq-boot-image" } +embedded-io = "0.6" +embedded-hal = "1" +fugit = "0.3" +log = "0.4" +arbitrary-int = "2" 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..a7e1d68 --- /dev/null +++ b/zedboard-fsbl/src/main.rs @@ -0,0 +1,274 @@ +//! 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 zedboard_bsp::qspi_spansion::{self, QspiSpansionS25Fl256SLinearMode}; +use zynq_boot_image::DestinationDevice; +use zynq7000_hal::{ + BootMode, + clocks::{ + Clocks, + pll::{PllConfig, configure_arm_pll, configure_io_pll}, + }, + ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest}, + gic, gpio, l2_cache, + prelude::*, + qspi, + time::Hertz, + uart::{ClockConfig, Config, Uart}, +}; +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; + +#[derive(Debug, PartialEq, Eq)] +pub enum BootMethod { + Qspi, + SdCard, +} + +pub const BOOT_METHOD: BootMethod = BootMethod::Qspi; + +pub const ELF_BASE_ADDR: usize = 0x100000; + +/// 8 MB reserved for application ELF. +pub const BOOT_BIN_STAGING_OFFSET: usize = 8 * 1024 * 1024; + +/// 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::Peripherals::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 = gpio::GpioPins::new(dp.gpio); + let mio_pins = gpio_pins.mio; + // Set up the UART, we are logging with it. + let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200) + .unwrap() + .0; + let mut uart = Uart::new_with_mio( + dp.uart_1, + Config::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 = 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."); + } + + if BOOT_METHOD == BootMethod::Qspi { + let qspi_clock_config = qspi::ClockConfig::calculate_with_loopback( + zynq7000::slcr::clocks::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, + mio_pins.mio1, + (mio_pins.mio2, mio_pins.mio3, mio_pins.mio4, mio_pins.mio5), + mio_pins.mio6, + mio_pins.mio8, + ); + + let qspi_io_mode = qspi.into_io_mode(false); + let spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true); + let spansion_lqspi = + spansion_qspi.into_linear_addressed(qspi_spansion::QSPI_DEV_COMBINATION_REV_F.into()); + qspi_boot(spansion_lqspi); + } + loop { + cortex_ar::asm::nop(); + } +} + +fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode) -> ! { + let boot_bin_base_addr = ELF_BASE_ADDR + BOOT_BIN_STAGING_OFFSET; + let mut boot_header_slice = unsafe { + core::slice::from_raw_parts_mut( + boot_bin_base_addr as *mut u8, + zynq_boot_image::FIXED_BOOT_HEADER_SIZE, + ) + }; + let read_guard = qspi.read_guard(); + // Currently, only boot.bin at address 0x0 of the QSPI is supported. + unsafe { + core::ptr::copy_nonoverlapping( + QspiSpansionS25Fl256SLinearMode::BASE_ADDR as *mut u8, + boot_header_slice.as_mut_ptr(), + zynq_boot_image::FIXED_BOOT_HEADER_SIZE, + ); + } + drop(read_guard); + + let boot_header = zynq_boot_image::BootHeader::new(boot_header_slice).unwrap(); + let fsbl_offset = boot_header.source_offset(); + boot_header_slice = + unsafe { core::slice::from_raw_parts_mut(boot_bin_base_addr as *mut u8, fsbl_offset) }; + + // Read the rest of the boot header metadata. + let read_guard = qspi.read_guard(); + unsafe { + core::ptr::copy_nonoverlapping( + (QspiSpansionS25Fl256SLinearMode::BASE_ADDR + zynq_boot_image::FIXED_BOOT_HEADER_SIZE) + as *mut u8, + boot_header_slice[zynq_boot_image::FIXED_BOOT_HEADER_SIZE..].as_mut_ptr(), + fsbl_offset - zynq_boot_image::FIXED_BOOT_HEADER_SIZE, + ); + } + drop(read_guard); + + let boot_header = zynq_boot_image::BootHeader::new_unchecked(boot_header_slice); + + let mut name_buf: [u8; 256] = [0; 256]; + for image_header in boot_header.image_header_iterator().unwrap() { + let name = image_header.image_name(&mut name_buf).unwrap(); + for partition in image_header + .partition_header_iterator(boot_header_slice) + .unwrap() + { + let section_attrs = partition.section_attributes(); + if let Ok(dest_dev) = section_attrs.destination_device() { + match dest_dev { + DestinationDevice::Pl => { + info!("Loading image '{name}' to PL (FPGA).."); + // TODO: Load the bitstream. + } + DestinationDevice::Ps => { + // TODO: Load the binary into DDR. Jump at lowest load address after all + // partitions were parsed. + } + _ => { + error!("Unsupported destination device {dest_dev:?}"); + continue; + } + } + } + } + } + + 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/zedboard-qspi-flasher/Cargo.toml b/zedboard-qspi-flasher/Cargo.toml new file mode 100644 index 0000000..77d7669 --- /dev/null +++ b/zedboard-qspi-flasher/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "zedboard-qspi-flasher" +version = "0.1.0" +edition = "2024" + +[dependencies] +cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", branch = "main" } +zynq7000-rt = { path = "../zynq7000-rt" } +zynq7000 = { path = "../zynq7000" } +zynq7000-hal = { path = "../zynq7000-hal" } +zedboard-bsp = { path = "../zedboard-bsp" } +embedded-io = "0.6" +embedded-hal = "1" +log = "0.4" diff --git a/zedboard-qspi-flasher/src/main.rs b/zedboard-qspi-flasher/src/main.rs new file mode 100644 index 0000000..e43c3f7 --- /dev/null +++ b/zedboard-qspi-flasher/src/main.rs @@ -0,0 +1,139 @@ +//! QSPI flasher for the Zedboard. Assumes that external scripting took care of transferring +//! a boot binary to RAM. +#![no_std] +#![no_main] + +use core::panic::PanicInfo; +use cortex_ar::asm::nop; +use embedded_hal::{delay::DelayNs as _, digital::StatefulOutputPin as _}; +use embedded_io::Write as _; +use log::info; +use zedboard_bsp::qspi_spansion; +use zynq7000_hal::{ + clocks, gpio, prelude::*, priv_tim, qspi, time::Hertz, uart, BootMode, LevelShifterConfig, +}; +use zynq7000_rt as _; + +// 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); + +#[allow(dead_code)] +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 INIT_STRING: &str = "-- Zynq 7000 Zedboard QSPI flasher --\n\r"; + +#[unsafe(export_name = "main")] +pub fn main() -> ! { + let periphs = zynq7000_hal::init(zynq7000_hal::Config { + init_l2_cache: true, + level_shifter_config: Some(LevelShifterConfig::EnableAll), + interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0), + }) + .unwrap(); + let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap(); + + // Unwrap okay, we only call this once on core 0 here. + let mut timer = priv_tim::CpuPrivateTimer::take(clocks.arm_clocks()).unwrap(); + + let gpio_pins = gpio::GpioPins::new(periphs.gpio); + + // Set up the UART, we are logging with it. + let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200) + .unwrap() + .0; + let mut uart = uart::Uart::new_with_mio( + periphs.uart_1, + uart::Config::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(qspi::SrcSelIo::IoPll, &clocks, 100.MHz()) + .expect("QSPI clock calculation failed"); + let qspi = qspi::Qspi::new_single_qspi_with_feedback( + periphs.qspi, + qspi_clock_config, + qspi::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 _spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true); + + let mut mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low); + loop { + mio_led.toggle().unwrap(); + + timer.delay_ms(500); + } +} + +#[zynq7000_rt::irq] +pub fn irq_handler() {} + +#[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) -> ! { + loop { + nop(); + } +} diff --git a/zynq-boot-image/Cargo.toml b/zynq-boot-image/Cargo.toml new file mode 100644 index 0000000..922b69b --- /dev/null +++ b/zynq-boot-image/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "zynq-boot-image" +version = "0.1.0" +edition = "2024" + +[dependencies] +thiserror = { version = "2", default-features = false } +arbitrary-int = "2" +bitbybit = "1.4" diff --git a/zynq-boot-image/src/lib.rs b/zynq-boot-image/src/lib.rs new file mode 100644 index 0000000..1f27c97 --- /dev/null +++ b/zynq-boot-image/src/lib.rs @@ -0,0 +1,437 @@ +#![no_std] + +use core::str::Utf8Error; + +/// 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; + +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +pub enum InvalidBootHeader { + #[error("image ID invalid")] + ImageIdInvalid, + #[error("checksum is invalid")] + ChecksumInvalid, + #[error("provided data slice too small")] + DataTooSmall, +} + +pub struct BootHeader<'a> { + //base_addr: usize, + // Snapshot of the boot header and following data (at least fixed header size) + data: &'a [u8], +} + +impl<'a> BootHeader<'a> { + pub const FIXED_SIZED_PART: usize = FIXED_BOOT_HEADER_SIZE; + + /// Create a new boot header parser structure without performing any additional checks. + pub const fn new_unchecked(data: &'a [u8]) -> Self { + BootHeader { data } + } + + /// Create a new boot header parser structure while also performing any additonal checks. + /// + /// The passed buffer must have a minimal size of [Self::FIXED_SIZED_PART]. + /// This constructor calls [Self::check_image_id_validity] and [Self::verify_header_checksum] + /// to verify whether the boot header structure is actually valid. + pub fn new(data: &'a [u8]) -> Result { + if data.len() < Self::FIXED_SIZED_PART { + return Err(InvalidBootHeader::DataTooSmall); + } + let header = BootHeader { data }; + if !header.check_image_id_validity() { + return Err(InvalidBootHeader::ImageIdInvalid); + } + if !header.verify_header_checksum() { + return Err(InvalidBootHeader::ChecksumInvalid); + } + Ok(header) + } + + #[inline] + fn read_u32_le(&self, offset: usize) -> u32 { + let bytes = &self.data[offset..offset + 4]; + u32::from_le_bytes(bytes.try_into().unwrap()) + } + + #[inline] + pub fn image_id(&self) -> u32 { + self.read_u32_le(0x24) + } + + /// Check whether the image ID has the mandatory [IMAGE_ID_U32] value. + #[inline] + pub fn check_image_id_validity(&self) -> bool { + self.image_id() == IMAGE_ID_U32 + } + + /// Offset to the FSBL image in bytes. This information can be used to only extract the boot + /// binary metadata (everything except actual partition data). + #[inline] + pub fn source_offset(&self) -> usize { + self.read_u32_le(0x30) as usize + } + + #[inline] + pub fn header_checksum(&self) -> u32 { + self.read_u32_le(0x48) + } + + #[inline] + pub fn image_header_table_offset(&self) -> usize { + self.read_u32_le(0x98) as usize + } + + pub fn image_header_table(&self) -> Option> { + let offset = self.image_header_table_offset(); + if offset + ImageHeaderTable::SIZE > self.data.len() { + return None; + } + Some( + ImageHeaderTable::new( + &self.data[self.image_header_table_offset() + ..self.image_header_table_offset() + ImageHeaderTable::SIZE], + ) + .unwrap(), + ) + } + + pub fn image_header_iterator(&self) -> Option> { + let first_header_offset = self.image_header_table()?.first_image_header_offset()?; + ImageHeaderIterator::new(self.data, first_header_offset) + } + + #[inline] + pub fn partition_header_table_offset(&self) -> usize { + self.read_u32_le(0x9C) as usize + } + + #[inline] + pub fn verify_header_checksum(&self) -> bool { + let checksum = self.header_checksum(); + let mut sum = 0u32; + let mut ofs = 0x20; + while ofs < 0x48 { + sum = sum.wrapping_add(self.read_u32_le(ofs)); + ofs += 4; + } + !sum == checksum + } +} + +pub struct ImageHeaderTable<'a> { + data: &'a [u8], +} + +impl<'a> ImageHeaderTable<'a> { + pub const SIZE: usize = 0x18; + + pub const fn new(data: &'a [u8]) -> Option { + if data.len() != Self::SIZE { + return None; + } + Some(ImageHeaderTable { data }) + } + + #[inline] + fn read_u32_le(&self, offset: usize) -> u32 { + let bytes = &self.data[offset..offset + 4]; + u32::from_le_bytes(bytes.try_into().unwrap()) + } + + #[inline] + pub fn count_of_headers(&self) -> usize { + self.read_u32_le(0x04) as usize + } + + /// Returns [None] if the number of words times 4 exeeds [u32::MAX]. + #[inline] + pub fn first_image_header_offset(&self) -> Option { + self.read_u32_le(0x0C).checked_mul(4).map(|v| v as usize) + } + + /// Returns [None] if the number of words times 4 exeeds [u32::MAX]. + #[inline] + pub fn first_partition_header_offset(&self) -> Option { + self.read_u32_le(0x08).checked_mul(4).map(|v| v as usize) + } +} + +pub struct ImageHeaderIterator<'a> { + data: &'a [u8], + current_header_offset: usize, +} + +impl<'a> ImageHeaderIterator<'a> { + #[inline] + pub const fn new(data: &'a [u8], first_header_offset: usize) -> Option { + if first_header_offset + ImageHeader::MIN_SIZE > data.len() { + return None; + } + Some(ImageHeaderIterator { + data, + current_header_offset: first_header_offset, + }) + } +} +impl<'a> Iterator for ImageHeaderIterator<'a> { + type Item = ImageHeader<'a>; + + fn next(&mut self) -> Option { + if self.current_header_offset == 0 { + return None; + } + let next_image_header = ImageHeader::new(&self.data[self.current_header_offset..])?; + self.current_header_offset = next_image_header.next_image_header_offset()?; + Some(next_image_header) + } +} + +pub struct ImageHeader<'a> { + header_data: &'a [u8], +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +#[error("buffer too small")] +pub struct BufferTooSmallError; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +pub enum NameParsingError { + #[error("ut8 error")] + Utf8(#[from] Utf8Error), + #[error("buffer too small")] + BufferTooSmall(#[from] BufferTooSmallError), +} + +impl<'a> ImageHeader<'a> { + pub const MIN_SIZE: usize = 0x1C; + + #[inline] + pub fn new(data: &'a [u8]) -> Option { + if data.len() < Self::MIN_SIZE { + return None; + } + let mut current_offset = 0x14; + let mut prev_word = + u32::from_le_bytes(data[current_offset..current_offset + 4].try_into().unwrap()); + current_offset += 4; + // TODO: Upper bound. + loop { + if current_offset + 4 > data.len() { + return None; + } + let current_word = + u32::from_le_bytes(data[current_offset..current_offset + 4].try_into().unwrap()); + current_offset += 4; + if current_word == 0xffff_ffff || prev_word == 0x0000_0000 { + break; + } + prev_word = current_word; + } + Some(ImageHeader { + header_data: &data[0..current_offset], + }) + } + + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.header_data.len() + } + + #[inline] + fn read_u32_le(&self, offset: usize) -> u32 { + let bytes = &self.header_data[offset..offset + 4]; + u32::from_le_bytes(bytes.try_into().unwrap()) + } + + pub fn partition_header_iterator(&self, data: &'a [u8]) -> Option> { + let first_partition_header = self.first_partition_header_offset()?; + Some(PartitionHeaderIterator { + data, + current_partition_header_addr: first_partition_header, + current_partition_index: 0, + num_of_partitions: self.partition_count(), + }) + } + + #[inline] + pub fn next_image_header_offset(&self) -> Option { + self.read_u32_le(0x00).checked_mul(4).map(|v| v as usize) + } + + #[inline] + pub fn partition_count(&self) -> usize { + self.read_u32_le(0x0C) as usize + } + + pub fn first_partition_header_offset(&self) -> Option { + self.read_u32_le(0x04).checked_mul(4).map(|v| v as usize) + } + + pub fn image_name_copied(&self, buf: &mut [u8]) -> Result { + let mut current_offset = 0x10; + let mut current_buf_idx = 0; + let mut null_byte_found = false; + loop { + let next_bytes = &self.header_data[current_offset..current_offset + 4]; + for &byte in next_bytes.iter().rev() { + if byte == 0 { + null_byte_found = true; + break; + } + if current_buf_idx >= buf.len() { + return Err(BufferTooSmallError); + } + buf[current_buf_idx] = byte; + current_buf_idx += 1; + } + if null_byte_found { + break; + } + current_offset += 4; + } + Ok(current_buf_idx) + } + + pub fn image_name<'b>(&self, buf: &'b mut [u8]) -> Result<&'b str, NameParsingError> { + let name_len = self.image_name_copied(buf)?; + core::str::from_utf8(&buf[0..name_len]).map_err(NameParsingError::from) + } +} + +pub struct PartitionHeaderIterator<'a> { + data: &'a [u8], + current_partition_header_addr: usize, + current_partition_index: usize, + num_of_partitions: usize, +} + +impl<'a> Iterator for PartitionHeaderIterator<'a> { + type Item = PartitionHeader<'a>; + + fn next(&mut self) -> Option { + if self.current_partition_index >= self.num_of_partitions { + return None; + } + if self.current_partition_header_addr + PartitionHeader::SIZE > self.data.len() { + return None; + } + let header = PartitionHeader::new( + &self.data[self.current_partition_header_addr + ..self.current_partition_header_addr + PartitionHeader::SIZE], + ) + .ok()?; + self.current_partition_index += 1; + self.current_partition_header_addr += 0x40; + Some(header) + } +} + +pub struct PartitionHeader<'a> { + header_data: &'a [u8], +} + +#[bitbybit::bitenum(u2, exhaustive = false)] +#[derive(Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum PartitionOwner { + Fsbl = 0, + Uboot = 1, +} + +#[bitbybit::bitenum(u3, exhaustive = false)] +#[derive(Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum ChecksumType { + None = 0, + Md5 = 1, +} + +#[bitbybit::bitenum(u4, exhaustive = false)] +#[derive(Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum DestinationDevice { + None = 0, + Ps = 1, + Pl = 2, + Int = 3, +} + +#[bitbybit::bitfield(u32, debug)] +pub struct SectionAttributes { + #[bits(16..=17, rw)] + partition_owner: Option, + #[bit(15, rw)] + rsa_signature_present: bool, + #[bits(12..=14, rw)] + checksum_type: Option, + #[bits(4..=7, rw)] + destination_device: Option, +} + +impl<'a> PartitionHeader<'a> { + pub const SIZE: usize = 0x40; + + // TODO: Checksum check. + #[inline] + pub const fn new(header_data: &'a [u8]) -> Result { + if header_data.len() < Self::SIZE { + return Err(BufferTooSmallError); + } + Ok(PartitionHeader { header_data }) + } + + #[inline] + fn read_u32_le(&self, offset: usize) -> u32 { + let bytes = &self.header_data[offset..offset + 4]; + u32::from_le_bytes(bytes.try_into().unwrap()) + } + + #[inline] + pub fn encrypted_partition_length(&self) -> u32 { + self.read_u32_le(0x00) + } + + #[inline] + pub fn unencrypted_partition_length(&self) -> u32 { + self.read_u32_le(0x04) + } + + #[inline] + pub fn total_partition_length(&self) -> Option { + self.read_u32_le(0x08).checked_mul(4).map(|v| v as usize) + } + + #[inline] + pub fn destination_load_address(&self) -> u32 { + self.read_u32_le(0x0C) + } + + #[inline] + pub fn destination_exec_address(&self) -> u32 { + self.read_u32_le(0x10) + } + + #[inline] + pub fn section_attributes(&self) -> SectionAttributes { + SectionAttributes::new_with_raw_value(self.section_attributes_raw()) + } + + #[inline] + pub fn section_attributes_raw(&self) -> u32 { + self.read_u32_le(0x18) + } + + #[inline] + pub fn section_count(&self) -> usize { + self.read_u32_le(0x1C) as usize + } + + #[inline] + pub fn data_offset(&self) -> Option { + self.read_u32_le(0x14).checked_mul(4).map(|v| v as usize) + } +} diff --git a/zynq-boot-image/staging/.gitignore b/zynq-boot-image/staging/.gitignore new file mode 100644 index 0000000..8ef9722 --- /dev/null +++ b/zynq-boot-image/staging/.gitignore @@ -0,0 +1,4 @@ +/fsbl.elf +/fpga.bit +/application.elf +/boot.bin diff --git a/zynq-boot-image/staging/README.md b/zynq-boot-image/staging/README.md new file mode 100644 index 0000000..976ca93 --- /dev/null +++ b/zynq-boot-image/staging/README.md @@ -0,0 +1,28 @@ +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: + +```sh +bootgen -arch zynq -image boot.bif -o boot.bin -w on +``` diff --git a/zynq-boot-image/staging/boot.bif b/zynq-boot-image/staging/boot.bif new file mode 100644 index 0000000..7f3f789 --- /dev/null +++ b/zynq-boot-image/staging/boot.bif @@ -0,0 +1,6 @@ +all: +{ + [bootloader] fsbl.elf + fpga.bit + application.elf +} diff --git a/zynq-boot-image/tester/.gitignore b/zynq-boot-image/tester/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/zynq-boot-image/tester/.gitignore @@ -0,0 +1 @@ +/target diff --git a/zynq-boot-image/tester/Cargo.lock b/zynq-boot-image/tester/Cargo.lock new file mode 100644 index 0000000..e8d5fdc --- /dev/null +++ b/zynq-boot-image/tester/Cargo.lock @@ -0,0 +1,305 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "arbitrary-int" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825297538d77367557b912770ca3083f778a196054b3ee63b22673c4a3cae0a5" + +[[package]] +name = "arbitrary-int" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c858caffa49edfc4ecc45a4bec37abd3e88041a2903816f10f990b7b41abc281" + +[[package]] +name = "bitbybit" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec187a89ab07e209270175faf9e07ceb2755d984954e58a2296e325ddece2762" +dependencies = [ + "arbitrary-int 1.3.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap" +version = "4.5.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tester" +version = "0.1.0" +dependencies = [ + "clap", + "zynq-boot-image", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "zynq-boot-image" +version = "0.1.0" +dependencies = [ + "arbitrary-int 2.0.0", + "bitbybit", + "thiserror", +] diff --git a/zynq-boot-image/tester/Cargo.toml b/zynq-boot-image/tester/Cargo.toml new file mode 100644 index 0000000..4cc3de7 --- /dev/null +++ b/zynq-boot-image/tester/Cargo.toml @@ -0,0 +1,10 @@ +[workspace] + +[package] +name = "tester" +version = "0.1.0" +edition = "2024" + +[dependencies] +zynq-boot-image= { path = ".." } +clap = { version = "4", features = ["derive"] } diff --git a/zynq-boot-image/tester/src/main.rs b/zynq-boot-image/tester/src/main.rs new file mode 100644 index 0000000..bff0c55 --- /dev/null +++ b/zynq-boot-image/tester/src/main.rs @@ -0,0 +1,82 @@ +//! Small tester app to verify some of the API offers by the zynq-boot-image crate. +use std::{io::Read, path::Path}; + +use clap::Parser as _; +use zynq_boot_image::{BootHeader, FIXED_BOOT_HEADER_SIZE}; + +#[derive(clap::Parser, Debug)] +#[command(version, about)] +pub struct Cli { + /// Path to boot.bin file to test. + #[arg(short, long)] + path: String, +} + +fn main() { + let cli = Cli::parse(); + let boot_bin = Path::new(&cli.path); + if !boot_bin.exists() { + eprintln!("File not found: {}", boot_bin.display()); + std::process::exit(1); + } + let mut boot_bin_file = std::fs::File::open(boot_bin).expect("failed to open boot.bin file"); + let mut header_buf = Box::new([0u8; 8192]); + boot_bin_file + .read_exact(&mut header_buf[0..FIXED_BOOT_HEADER_SIZE]) + .expect("failed to read boot header"); + let mut boot_header = BootHeader::new(&header_buf[0..FIXED_BOOT_HEADER_SIZE]) + .expect("failed to parse boot header"); + let source_offset = boot_header.source_offset(); + boot_bin_file + .read_exact(&mut header_buf[FIXED_BOOT_HEADER_SIZE..source_offset - FIXED_BOOT_HEADER_SIZE]) + .expect("failed to read full boot binary metadata"); + // Re-assign with newly read data. + boot_header = BootHeader::new_unchecked(&header_buf[0..source_offset]); + let image_header_table = boot_header + .image_header_table() + .expect("failed extracting image header table"); + let image_headers = image_header_table.count_of_headers(); + println!( + "Image headers: {}, first image header offset {}, first partition header offset {}", + image_headers, + image_header_table + .first_image_header_offset() + .expect("failed reading first image header offset"), + image_header_table + .first_partition_header_offset() + .expect("failed reading first partition header offset") + ); + + let image_header_iter = boot_header + .image_header_iterator() + .expect("failed extracting boot header iterator"); + for (idx, image_header) in image_header_iter.enumerate() { + println!("--------------------------------------"); + println!( + "Image header {} with partition count {}", + idx, + image_header.partition_count() + ); + let mut test: [u8; 64] = [0; 64]; + let image_name = image_header + .image_name(&mut test) + .expect("image name error"); + println!("image name: {}", image_name); + let partition_iter = image_header + .partition_header_iterator(header_buf.as_slice()) + .unwrap(); + if image_header.partition_count() > 0 { + println!("--------------------------------------"); + } + for partition in partition_iter { + println!( + "partition with size {} and load address {:#08x}, section count {}", + partition.total_partition_length().unwrap(), + partition.destination_load_address(), + partition.section_count() + ); + println!("section attributes: {:?}", partition.section_attributes()); + } + println!("--------------------------------------\n\r"); + } +} diff --git a/zynq-mmu/Cargo.toml b/zynq-mmu/Cargo.toml index d37cdd1..7bf07b0 100644 --- a/zynq-mmu/Cargo.toml +++ b/zynq-mmu/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] thiserror = { version = "2", default-features = false } -cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2" } +cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", branch = "bump-arbitrary-int" } [features] tools = [] diff --git a/zynq7000-hal/Cargo.toml b/zynq7000-hal/Cargo.toml index d12bd8d..8e8e74d 100644 --- a/zynq7000-hal/Cargo.toml +++ b/zynq7000-hal/Cargo.toml @@ -11,12 +11,13 @@ keywords = ["no-std", "hal", "amd", "zynq7000", "xilinx", "bare-metal"] categories = ["embedded", "no-std", "hardware-support"] [dependencies] -cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2" } +cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", branch = "bump-arbitrary-int" } zynq7000 = { path = "../zynq7000" } zynq-mmu = { path = "../zynq-mmu", version = "0.1.0" } -bitbybit = "1.3" -arbitrary-int = "1.3" +static_assertions = "1.1" +bitbybit = "1.4" +arbitrary-int = "2" thiserror = { version = "2", default-features = false } num_enum = { version = "0.7", default-features = false } ringbuf = { version = "0.4.8", default-features = false } @@ -24,7 +25,7 @@ embedded-hal-nb = "1" embedded-io = "0.6" embedded-hal = "1" embedded-hal-async = "1" -heapless = "0.8" +heapless = "0.9" static_cell = "2" delegate = "0.13" paste = "1" diff --git a/zynq7000-hal/src/clocks.rs b/zynq7000-hal/src/clocks/mod.rs similarity index 86% rename from zynq7000-hal/src/clocks.rs rename to zynq7000-hal/src/clocks/mod.rs index f9370da..74aeeff 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::{prelude::*, 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 } @@ -178,13 +226,16 @@ pub enum ClockModuleId { #[derive(Debug)] pub struct DivisorZero(pub ClockModuleId); -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum ClockReadError { /// The feedback value for the PLL clock output calculation is zero. + #[error("PLL feedback divisor is zero")] PllFeedbackZero, /// Detected a divisor of zero. + #[error("divisor is zero")] DivisorZero(DivisorZero), - /// Detected a divisor that is not even. + /// Detected a divisor that is not even and should be. + #[error("divisor is not even")] DivisorNotEven, } @@ -201,9 +252,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..b7a3c62 --- /dev/null +++ b/zynq7000-hal/src/ddr/ll.rs @@ -0,0 +1,334 @@ +//! Low-level DDR configuration module. +use arbitrary_int::{prelude::*, 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/devcfg.rs b/zynq7000-hal/src/devcfg.rs new file mode 100644 index 0000000..bbee0e6 --- /dev/null +++ b/zynq7000-hal/src/devcfg.rs @@ -0,0 +1,64 @@ +#[derive(Debug, thiserror::Error)] +#[error("unaligned address: {0}")] +pub struct UnalignedAddrError(usize); + +/// Configures the bitstream using the PCAP interface in non-secure mode. +/// +/// Blocking function which only returns when the bitstream configuration is complete. +pub fn configure_bitstream_non_secure( + init_pl: bool, + bitstream: &[u8], +) -> Result<(), UnalignedAddrError> { + if !(bitstream.as_ptr() as usize).is_multiple_of(64) { + return Err(UnalignedAddrError(bitstream.as_ptr() as usize)); + } + if bitstream.is_empty() { + return Ok(()); + } + let mut devcfg = unsafe { zynq7000::devcfg::DevCfg::new_mmio_fixed() }; + devcfg.modify_control(|mut val| { + val.set_config_access_select(zynq7000::devcfg::PlConfigAccess::ConfigAccessPort); + val.set_access_port_select(zynq7000::devcfg::ConfigAccessPortSelect::Pcap); + val + }); + devcfg.write_interrupt_status(zynq7000::devcfg::Interrupt::new_with_raw_value(0xFFFF_FFFF)); + if init_pl { + devcfg.modify_control(|mut val| { + val.set_prog_b_bit(true); + val + }); + devcfg.modify_control(|mut val| { + val.set_prog_b_bit(false); + val + }); + while devcfg.read_status().pcfg_init() {} + devcfg.modify_control(|mut val| { + val.set_prog_b_bit(true); + val + }); + devcfg.write_interrupt_status( + zynq7000::devcfg::Interrupt::ZERO.with_pl_programming_done(true), + ); + } + while !devcfg.read_status().pcfg_init() {} + if !init_pl { + while devcfg.read_status().dma_command_queue_full() {} + } + devcfg.modify_misc_control(|mut val| { + val.set_loopback(false); + val + }); + devcfg.modify_control(|mut val| { + val.set_pcap_rate_enable(false); + val + }); + devcfg.write_dma_source_addr(bitstream.as_ptr() as u32); + devcfg.write_dma_dest_addr(0xFFFF_FFFF); + devcfg.write_dma_source_len(bitstream.len() as u32); + devcfg.write_dma_dest_len(bitstream.len() as u32); + + while !devcfg.read_interrupt_status().dma_done() {} + // TODO: Check for errors. + while !devcfg.read_interrupt_status().pl_programming_done() {} + Ok(()) +} diff --git a/zynq7000-hal/src/eth/ll.rs b/zynq7000-hal/src/eth/ll.rs index 1208a32..b275b06 100644 --- a/zynq7000-hal/src/eth/ll.rs +++ b/zynq7000-hal/src/eth/ll.rs @@ -1,10 +1,10 @@ -use arbitrary_int::{Number, u6}; +use arbitrary_int::{prelude::*, u6}; use zynq7000::{ eth::{InterruptControl, NetworkControl, RxStatus, TxStatus}, 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 8ee46ca..2565814 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: MioPin { +pub trait TxClockPin: MioPin { const ETH_ID: EthernetId; } -pub trait TxCtrl: MioPin { +pub trait TxControlPin: MioPin { const ETH_ID: EthernetId; } -pub trait TxData0: MioPin { +pub trait TxData0Pin: MioPin { const ETH_ID: EthernetId; } -pub trait TxData1: MioPin { +pub trait TxData1Pin: MioPin { const ETH_ID: EthernetId; } -pub trait TxData2: MioPin { +pub trait TxData2Pin: MioPin { const ETH_ID: EthernetId; } -pub trait TxData3: MioPin { +pub trait TxData3Pin: MioPin { const ETH_ID: EthernetId; } -pub trait RxClk: MioPin { +pub trait RxClockPin: MioPin { const ETH_ID: EthernetId; } -pub trait RxCtrl: MioPin { +pub trait RxControlPin: MioPin { const ETH_ID: EthernetId; } -pub trait RxData0: MioPin { +pub trait RxData0Pin: MioPin { const ETH_ID: EthernetId; } -pub trait RxData1: MioPin { +pub trait RxData1Pin: MioPin { const ETH_ID: EthernetId; } -pub trait RxData2: MioPin { +pub trait RxData2Pin: MioPin { const ETH_ID: EthernetId; } -pub trait RxData3: MioPin { +pub trait RxData3Pin: MioPin { const ETH_ID: EthernetId; } -pub trait MdClk: MioPin {} -pub trait MdIo: MioPin {} +pub trait MdClockPin: MioPin {} +pub trait MdIoPin: MioPin {} -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/eth/rx_descr.rs b/zynq7000-hal/src/eth/rx_descr.rs index cdf14b2..4b5a68a 100644 --- a/zynq7000-hal/src/eth/rx_descr.rs +++ b/zynq7000-hal/src/eth/rx_descr.rs @@ -4,7 +4,7 @@ use core::{cell::UnsafeCell, mem::MaybeUninit, sync::atomic::AtomicBool}; use crate::{cache::clean_and_invalidate_data_cache_range, eth::AlignedBuffer}; pub use super::shared::Ownership; -use arbitrary_int::{Number, u2, u3, u13, u30}; +use arbitrary_int::{prelude::*, u2, u3, u13, u30}; use vcell::VolatileCell; static RX_DESCR_TAKEN: AtomicBool = AtomicBool::new(false); diff --git a/zynq7000-hal/src/gic.rs b/zynq7000-hal/src/gic.rs index 35ff624..9b75816 100644 --- a/zynq7000-hal/src/gic.rs +++ b/zynq7000-hal/src/gic.rs @@ -6,7 +6,7 @@ //! # Examples //! //! - [GTC ticks](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/examples/simple/src/bin/gtc-ticks.rs) -use arbitrary_int::Number; +use arbitrary_int::prelude::*; use cortex_ar::interrupt; use zynq7000::gic::{ diff --git a/zynq7000-hal/src/gpio/mio.rs b/zynq7000-hal/src/gpio/mio.rs index 0972b5e..d0fb18c 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 MioPin { +pub trait MioPin: crate::sealed::Sealed { fn offset(&self) -> usize; } @@ -379,3 +379,5 @@ impl MioPin 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 19b1171..5b7702c 100644 --- a/zynq7000-hal/src/gpio/mod.rs +++ b/zynq7000-hal/src/gpio/mod.rs @@ -17,7 +17,7 @@ use ll::PinOffset; use mio::{MioPin, 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 b20d745..fa3185e 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..c04ea6e 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::{ + slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister}, + SpiClockPhase, SpiClockPolarity, +}; pub mod cache; pub mod clocks; +pub mod ddr; +pub mod devcfg; pub mod eth; pub mod gic; pub mod gpio; @@ -25,12 +30,65 @@ 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; pub mod ttc; pub mod uart; +pub use zynq7000 as pac; +pub use zynq7000::slcr::LevelShifterConfig; + +#[derive(Debug, thiserror::Error)] +pub enum InitError { + #[error("peripheral singleton was already taken")] + PeripheralsAlreadyTaken, +} + +#[derive(Debug)] +pub enum InteruptConfig { + /// GIC is configured to route all interrupts to CPU0. Suitable if the software handles all + /// the interrupts and only runs on CPU0. + AllInterruptsToCpu0, +} + +#[derive(Debug)] +pub struct Config { + pub init_l2_cache: bool, + /// If this has some value, it will configure the level shifter between PS and PL. + pub level_shifter_config: Option, + /// If this has some value, it configures the GIC to pre-defined settings. + pub interrupt_config: Option, +} + +/// Utility function to perform common initialization steps. +pub fn init(config: Config) -> Result { + let mut periphs = zynq7000::Peripherals::take().ok_or(InitError::PeripheralsAlreadyTaken)?; + if config.init_l2_cache { + l2_cache::init_with_defaults(&mut periphs.l2c); + } + if let Some(config) = config.level_shifter_config { + configure_level_shifter(config); + } + if let Some(interrupt_config) = config.interrupt_config { + let mut gic = gic::GicConfigurator::new_with_init(periphs.gicc, periphs.gicd); + match interrupt_config { + InteruptConfig::AllInterruptsToCpu0 => { + gic.enable_all_interrupts(); + gic.set_all_spi_interrupt_targets_cpu0(); + } + } + gic.enable(); + unsafe { + gic.enable_interrupts(); + } + } + + Ok(unsafe { zynq7000::Peripherals::steal() }) +} + /// This enumeration encodes the various boot sources. #[derive(Debug, Copy, Clone)] pub enum BootDevice { @@ -43,36 +101,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 +132,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 } } @@ -107,7 +151,7 @@ impl BootMode { /// system (PS). /// /// The Zynq-7000 TRM p.32 specifies more information about this register and how to use it. -pub fn configure_level_shifter(config: zynq7000::slcr::LevelShifterConfig) { +pub fn configure_level_shifter(config: LevelShifterConfig) { // Safety: We only manipulate the level shift registers. unsafe { Slcr::with(|slcr_unlocked| { @@ -142,7 +186,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,7 +249,30 @@ pub fn disable_amba_periph_clk(select: PeriphSelect) { } } -#[allow(dead_code)] +#[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, + ), + } +} + 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..c7c3528 --- /dev/null +++ b/zynq7000-hal/src/qspi/mod.rs @@ -0,0 +1,681 @@ +use core::ops::{Deref, DerefMut}; + +use arbitrary_int::{prelude::*, u2, u3, u6}; +pub use zynq7000::qspi::LinearQspiConfig; +use zynq7000::{ + qspi::{ + BaudRateDivisor, Config, InstructionCode, InterruptStatus, LoopbackMasterClockDelay, + SpiEnable, + }, + slcr::{clocks::SingleCommonPeriphIoClockControl, mio::Speed, reset::QspiResetControl}, +}; + +pub use embedded_hal::spi::{MODE_0, MODE_1, MODE_2, MODE_3, Mode}; +pub use zynq7000::slcr::clocks::SrcSelIo; +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, + MioPin, 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: MioPin {} +pub trait Qspi0Io0Pin: MioPin {} +pub trait Qspi0Io1Pin: MioPin {} +pub trait Qspi0Io2Pin: MioPin {} +pub trait Qspi0Io3Pin: MioPin {} +pub trait Qspi0ClockPin: MioPin {} + +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: MioPin {} +pub trait Qspi1Io0Pin: MioPin {} +pub trait Qspi1Io1Pin: MioPin {} +pub trait Qspi1Io2Pin: MioPin {} +pub trait Qspi1Io3Pin: MioPin {} +pub trait Qspi1ClockPin: MioPin {} + +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: MioPin {} + +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 { + /// Memory-mapped QSPI base address. + pub const BASE_ADDRESS: usize = QSPI_START_ADDRESS; + + 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<'_> { + /// Memory-mapped QSPI base address. + pub const BASE_ADDRESS: usize = QSPI_START_ADDRESS; + + 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 804286a..faba759 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 arbitrary_int::{prelude::*, 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 (Option, u16) { // TODO: Can this be optimized? - let mut prescaler_reg: Option = None; + let mut prescaler: Option = None; let mut tick_val = ref_clk / freq; while tick_val > u16::MAX as u32 { ref_clk /= 2; - if let Some(prescaler_reg) = prescaler_reg { - // TODO: Better error handling for this case? Can this even happen? - if prescaler_reg.value() == u4::MAX.value() { - break; - } else { - prescaler_reg.checked_add(u4::new(1)); + match prescaler { + Some(val) => { + if val == u4::MAX.as_u32() { + break; + } + prescaler = Some(val + 1); } - } else { - prescaler_reg = Some(u4::new(0)); + None => prescaler = Some(0), } tick_val = ref_clk / freq; } - (prescaler_reg, tick_val as u16) + (prescaler.map(|v| u4::new(v as u8)), tick_val as u16) } pub struct Pwm { diff --git a/zynq7000-hal/src/uart/mod.rs b/zynq7000-hal/src/uart/mod.rs index d753a69..2d3c6fd 100644 --- a/zynq7000-hal/src/uart/mod.rs +++ b/zynq7000-hal/src/uart/mod.rs @@ -8,13 +8,13 @@ use libm::round; use zynq7000::{ slcr::reset::DualRefAndClockReset, uart::{ - BaudRateDiv, Baudgen, ChMode, ClockSelect, FifoTrigger, InterruptControl, MmioUart, Mode, - UART_0_BASE, UART_1_BASE, + BaudRateDivisor, Baudgen, ChMode, ClockSelect, FifoTrigger, InterruptControl, MmioUart, + Mode, UART_0_BASE, UART_1_BASE, }, }; use crate::{ - enable_amba_periph_clk, + enable_amba_peripheral_clock, gpio::{ IoPeriphPin, mio::{ @@ -187,7 +187,7 @@ pub enum CharLen { } #[derive(Debug, Clone, Copy)] -pub struct ClockConfigRaw { +pub struct ClockConfig { cd: u16, bdiv: u8, } @@ -197,12 +197,12 @@ pub fn calculate_viable_configs( mut uart_clk: Hertz, clk_sel: ClockSelect, target_baud: u32, -) -> alloc::vec::Vec<(ClockConfigRaw, f64)> { +) -> alloc::vec::Vec<(ClockConfig, f64)> { let mut viable_cfgs = alloc::vec::Vec::new(); if clk_sel == ClockSelect::UartRefClkDiv8 { uart_clk /= 8; } - let mut current_clk_config = ClockConfigRaw::default(); + let mut current_clk_config = ClockConfig::default(); for bdiv in 4..u8::MAX { let cd = round(uart_clk.raw() as f64 / ((bdiv as u32 + 1) as f64 * target_baud as f64)) as u64; @@ -229,15 +229,15 @@ pub fn calculate_raw_baud_cfg_smallest_error( mut uart_clk: Hertz, clk_sel: ClockSelect, target_baud: u32, -) -> Result<(ClockConfigRaw, f64), DivisorZero> { +) -> Result<(ClockConfig, f64), DivisorZero> { if target_baud == 0 { return Err(DivisorZero); } if clk_sel == ClockSelect::UartRefClkDiv8 { uart_clk /= 8; } - let mut current_clk_config = ClockConfigRaw::default(); - let mut best_clk_config = ClockConfigRaw::default(); + let mut current_clk_config = ClockConfig::default(); + let mut best_clk_config = ClockConfig::default(); let mut smallest_error: f64 = 100.0; for bdiv in 4..u8::MAX { let cd = @@ -258,13 +258,13 @@ pub fn calculate_raw_baud_cfg_smallest_error( Ok((best_clk_config, smallest_error)) } -impl ClockConfigRaw { +impl ClockConfig { #[inline] pub const fn new(cd: u16, bdiv: u8) -> Result { if cd == 0 { return Err(DivisorZero); } - Ok(ClockConfigRaw { cd, bdiv }) + Ok(ClockConfig { cd, bdiv }) } /// Auto-calculates the best clock configuration settings for the target baudrate. @@ -316,16 +316,16 @@ impl ClockConfigRaw { } } -impl Default for ClockConfigRaw { +impl Default for ClockConfig { #[inline] fn default() -> Self { - ClockConfigRaw::new(1, 0).unwrap() + ClockConfig::new(1, 0).unwrap() } } #[derive(Debug)] -pub struct UartConfig { - clk_config: ClockConfigRaw, +pub struct Config { + clk_config: ClockConfig, chmode: ChMode, parity: Parity, stopbits: Stopbits, @@ -333,8 +333,8 @@ pub struct UartConfig { clk_sel: ClockSelect, } -impl UartConfig { - pub fn new_with_clk_config(clk_config: ClockConfigRaw) -> Self { +impl Config { + pub fn new_with_clk_config(clk_config: ClockConfig) -> Self { Self::new( clk_config, ChMode::default(), @@ -347,14 +347,14 @@ impl UartConfig { #[inline] pub const fn new( - clk_config: ClockConfigRaw, + clk_config: ClockConfig, chmode: ChMode, parity: Parity, stopbits: Stopbits, chrl: CharLen, clk_sel: ClockSelect, ) -> Self { - UartConfig { + Config { clk_config, chmode, parity, @@ -365,7 +365,7 @@ impl UartConfig { } #[inline] - pub const fn raw_clk_config(&self) -> ClockConfigRaw { + pub const fn raw_clk_config(&self) -> ClockConfig { self.clk_config } @@ -399,7 +399,7 @@ impl UartConfig { pub struct Uart { rx: Rx, tx: Tx, - cfg: UartConfig, + cfg: Config, } #[derive(Debug, thiserror::Error)] @@ -422,7 +422,7 @@ impl Uart { /// /// A valid PL design which routes the UART pins through into the PL must be used for this to /// work. - pub fn new_with_emio(uart: impl PsUart, cfg: UartConfig) -> Result { + pub fn new_with_emio(uart: impl PsUart, cfg: Config) -> Result { if uart.uart_id().is_none() { return Err(InvalidPsUart); } @@ -436,7 +436,7 @@ impl Uart { /// This is the constructor to use the PS UART with MIO pins. pub fn new_with_mio( uart: impl PsUart, - cfg: UartConfig, + cfg: Config, pins: (TxPinI, RxPinI), ) -> Result where @@ -465,13 +465,13 @@ impl Uart { pub fn new_generic_unchecked( mut reg_block: MmioUart<'static>, uart_id: UartId, - cfg: UartConfig, + cfg: Config, ) -> Uart { let periph_sel = match uart_id { UartId::Uart0 => 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(), ); @@ -557,7 +557,7 @@ impl Uart { } #[inline] - pub const fn cfg(&self) -> &UartConfig { + pub const fn cfg(&self) -> &Config { &self.cfg } @@ -657,7 +657,7 @@ mod tests { #[test] fn test_error_calc_0() { // Baud 600 - let cfg_0 = ClockConfigRaw::new(10417, 7).unwrap(); + let cfg_0 = ClockConfig::new(10417, 7).unwrap(); let actual_baud_0 = cfg_0.actual_baud(REF_UART_CLK); assert!(abs_diff_eq!(actual_baud_0, 599.980, epsilon = 0.01)); } @@ -665,7 +665,7 @@ mod tests { #[test] fn test_error_calc_1() { // Baud 9600 - let cfg = ClockConfigRaw::new(81, 7).unwrap(); + let cfg = ClockConfig::new(81, 7).unwrap(); let actual_baud = cfg.actual_baud(REF_UART_CLK_DIV_8); assert!(abs_diff_eq!(actual_baud, 9645.061, epsilon = 0.01)); } @@ -673,7 +673,7 @@ mod tests { #[test] fn test_error_calc_2() { // Baud 9600 - let cfg = ClockConfigRaw::new(651, 7).unwrap(); + let cfg = ClockConfig::new(651, 7).unwrap(); let actual_baud = cfg.actual_baud(REF_UART_CLK); assert!(abs_diff_eq!(actual_baud, 9600.614, epsilon = 0.01)); } @@ -681,7 +681,7 @@ mod tests { #[test] fn test_error_calc_3() { // Baud 28800 - let cfg = ClockConfigRaw::new(347, 4).unwrap(); + let cfg = ClockConfig::new(347, 4).unwrap(); let actual_baud = cfg.actual_baud(REF_UART_CLK); assert!(abs_diff_eq!(actual_baud, 28818.44, epsilon = 0.01)); } @@ -689,7 +689,7 @@ mod tests { #[test] fn test_error_calc_4() { // Baud 921600 - let cfg = ClockConfigRaw::new(9, 5).unwrap(); + let cfg = ClockConfig::new(9, 5).unwrap(); let actual_baud = cfg.actual_baud(REF_UART_CLK); assert!(abs_diff_eq!(actual_baud, 925925.92, epsilon = 0.01)); } @@ -697,7 +697,7 @@ mod tests { #[test] fn test_best_calc_0() { let result = - ClockConfigRaw::new_autocalc_with_raw_clk(REF_UART_CLK, ClockSelect::UartRefClk, 600); + ClockConfig::new_autocalc_with_raw_clk(REF_UART_CLK, ClockSelect::UartRefClk, 600); assert!(result.is_ok()); let (cfg, _error) = result.unwrap(); assert_eq!(cfg.cd(), 499); diff --git a/zynq7000-hal/src/uart/rx.rs b/zynq7000-hal/src/uart/rx.rs index 3702676..d232852 100644 --- a/zynq7000-hal/src/uart/rx.rs +++ b/zynq7000-hal/src/uart/rx.rs @@ -1,6 +1,6 @@ use core::convert::Infallible; -use arbitrary_int::Number; +use arbitrary_int::prelude::*; use zynq7000::uart::{InterruptControl, InterruptStatus, MmioUart}; use super::FIFO_DEPTH; diff --git a/zynq7000-rt/Cargo.toml b/zynq7000-rt/Cargo.toml index 034bd29..fd2df97 100644 --- a/zynq7000-rt/Cargo.toml +++ b/zynq7000-rt/Cargo.toml @@ -12,8 +12,8 @@ categories = ["embedded", "no-std", "hardware-support"] [dependencies] cortex-a-rt = { version = "0.1", optional = true, features = ["vfp-dp"] } -cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2" } -arbitrary-int = "1.3" +cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", branch = "bump-arbitrary-int" } +arbitrary-int = "2" zynq-mmu = { path = "../zynq-mmu", version = "0.1.0" } [features] 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/Cargo.toml b/zynq7000/Cargo.toml index 78f1465..4de4921 100644 --- a/zynq7000/Cargo.toml +++ b/zynq7000/Cargo.toml @@ -13,8 +13,8 @@ categories = ["embedded", "no-std", "hardware-support"] [dependencies] static_assertions = "1.1" derive-mmio = { version = "0.6", default-features = false } -bitbybit = "1.3" -arbitrary-int = "1.3" +bitbybit = "1.4" +arbitrary-int = "2" rustversion = "1" thiserror = { version = "2", default-features = false } once_cell = { version = "1", default-features = false, features = ["critical-section"] } 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/devcfg.rs b/zynq7000/src/devcfg.rs new file mode 100644 index 0000000..d7d67c1 --- /dev/null +++ b/zynq7000/src/devcfg.rs @@ -0,0 +1,303 @@ +use arbitrary_int::{u4, u5, u7}; + +pub const DEVCFG_BASE_ADDR: usize = 0xF8007000; + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum PlConfigAccess { + /// Used for JTAG access + TapController = 0, + /// Used for PCAP or ICAP access. + ConfigAccessPort = 1, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum ConfigAccessPortSelect { + /// Internal Configuration Access Port (ICAP), using PL or PS-based software. + Icap = 0, + /// Processor Configuration Access Port (PCAP), using PS-based software. + Pcap = 1, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum TimerSelect { + _64kTimer = 0, + _4kTimer = 1, +} + +#[bitbybit::bitenum(u3, exhaustive = false)] +#[derive(Debug, PartialEq, Eq)] +pub enum AesEnable { + Disable = 0b000, + Enable = 0b111, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum PsBootMode { + NonSecure = 0, + Secure = 1, +} + +#[bitbybit::bitenum(u3, exhaustive = false)] +#[derive(Debug, PartialEq, Eq)] +pub enum ArmDapEnable { + Enabled = 0b111, +} + +#[bitbybit::bitfield(u32, debug)] +pub struct Control { + #[bit(31, rw)] + force_reset: bool, + /// Program singal used to reset the PL. It acts at the PROG_B signal in the PL. + #[bit(30, rw)] + prog_b_bit: bool, + /// Called PCFG_POR_CNT_4K by Xilinx. + #[bit(29, rw)] + timer_select: TimerSelect, + /// Called XDCFG_CTRL_PCAP_PR_MASK by Xilinx. + #[bit(27, rw)] + access_port_select: ConfigAccessPortSelect, + #[bit(26, rw)] + config_access_select: PlConfigAccess, + #[bit(25, rw)] + pcap_rate_enable: bool, + #[bit(24, rw)] + multiboot_enable: bool, + #[bit(23, rw)] + jtag_chain_disable: bool, + #[bit(12, rw)] + pcfg_aes_fuse: bool, + #[bits(9..=11, rw)] + pcfg_aes_enable: Option, + #[bit(8, rw)] + seu_enable: bool, + /// Read-only because this is set and locked by BootROM. + #[bit(7, r)] + ps_boot_mode: PsBootMode, + /// SPNIDEN + #[bit(6, rw)] + secure_non_invasive_debug_enable: bool, + /// SPIDEN + #[bit(5, rw)] + secure_invasive_debug_enable: bool, + /// NIDEN + #[bit(4, rw)] + non_invasive_debug_enable: bool, + /// DBGEN + #[bit(3, rw)] + invasive_debug_enable: bool, + #[bits(0..=2, rw)] + dap_enable: Option, +} + +/// The bits in this register and read/write, set-only, which means that only a PS_POR_B reset +/// can clear the bits. +#[bitbybit::bitfield(u32, debug)] +pub struct Lock { + #[bit(4, rw)] + aes_fuse: bool, + #[bit(3, rw)] + aes: bool, + #[bit(2, rw)] + seu: bool, + /// Locks the SEC_EN bit. BootROM will set this bit. + #[bit(1, rw)] + sec: bool, + /// Locks SPNIDEN, SPIDEN, NIDEN, DBGEN and DAP_EN + #[bit(0, rw)] + debug: bool, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum EdgeConfig { + Falling = 0, + Rising = 1, +} + +/// Related to the full level for reads, and the empty level for writes. +#[bitbybit::bitenum(u2, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum FifoThresholdConfig { + OneFourth = 0b00, + HalfEmpty = 0b01, + ThreeFourth = 0b10, + EmptyOrFull = 0b11, +} + +#[bitbybit::bitfield(u32, debug)] +pub struct Config { + #[bits(10..=11, rw)] + read_fifo_threshhold: FifoThresholdConfig, + #[bits(8..=9, rw)] + write_fifo_threshold: FifoThresholdConfig, + #[bit(7, rw)] + read_data_active_clock_edge: EdgeConfig, + #[bit(6, rw)] + write_data_active_clock_edge: EdgeConfig, + #[bit(5, rw)] + disable_src_increment: bool, + #[bit(4, rw)] + disable_dst_incremenet: bool, +} + +#[bitbybit::bitfield(u32, debug)] +pub struct Interrupt { + /// Tri-state PL IO during HIZ. + #[bit(31, rw)] + gts_usr_b: bool, + #[bit(30, rw)] + first_config_done: bool, + #[bit(29, rw)] + global_powerdown: bool, + /// Tri-state PL IO during configuration. + #[bit(28, rw)] + gts_cfg_b: bool, + /// PSS_CFG_RESET_B_INT + #[bit(27, rw)] + pl_config_reset: bool, + #[bit(23, rw)] + axi_write_timeout: bool, + #[bit(22, rw)] + axi_write_response_error: bool, + #[bit(21, rw)] + axi_read_timeout: bool, + #[bit(20, rw)] + axi_read_response_error: bool, + #[bit(18, rw)] + rx_overflow: bool, + #[bit(17, rw)] + tx_fifo_below_threshold: bool, + #[bit(16, rw)] + rx_fifo_above_threshold: bool, + #[bit(15, rw)] + dma_illegal_command: bool, + #[bit(14, rw)] + dma_queue_overflow: bool, + #[bit(13, rw)] + dma_done: bool, + #[bit(12, rw)] + dma_pcap_done: bool, + #[bit(11, rw)] + inconsistent_pcap_to_dma_transfer_len: bool, + #[bit(6, rw)] + hamc_error: bool, + #[bit(5, rw)] + seu_error: bool, + #[bit(4, rw)] + pl_power_loss_por_b_low: bool, + #[bit(3, rw)] + pl_config_controller_under_reset: bool, + #[bit(2, rw)] + pl_programming_done: bool, + #[bit(1, rw)] + positive_edge_pl_init: bool, + #[bit(0, rw)] + negative_edge_pl_init: bool, +} + +#[bitbybit::bitfield(u32, debug)] +pub struct MiscControl { + #[bits(28..=31, r)] + ps_version: u4, + + #[bit(8, r)] + por_b_signal: bool, + + #[bit(4, rw)] + loopback: bool, +} + +#[bitbybit::bitenum(u2, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum UnacknowledgedDmaTransfers { + None = 0b00, + One = 0b01, + Two = 0b10, + ThreeOrMore = 0b11, +} + +#[bitbybit::bitfield(u32, debug)] +pub struct Status { + #[bit(31, rw)] + dma_command_queue_full: bool, + #[bit(30, rw)] + dma_command_queue_empty: bool, + #[bits(28..=29, rw)] + unacknowledged_dma_transfers: UnacknowledgedDmaTransfers, + #[bits(20..=24, rw)] + rx_fifo_level: u5, + #[bits(12..=18, rw)] + tx_fifo_level: u7, + #[bit(11, rw)] + gts_usr_b: bool, + #[bit(10, rw)] + first_config_done: bool, + #[bit(9, rw)] + global_powerdown: bool, + #[bit(8, rw)] + gts_cfg_b: bool, + #[bit(7, rw)] + secure_lockdown: bool, + #[bit(6, rw)] + illegal_apb_access: bool, + /// Active low reset bit. + #[bit(5, rw)] + pl_reset_n: bool, + #[bit(4, rw)] + pcfg_init: bool, + #[bit(3, rw)] + efuse_bbram_aes_key_disabled: bool, + #[bit(2, rw)] + efuse_sec_enable: bool, + #[bit(1, rw)] + efuse_jtag_disabled: bool, +} + +#[derive(derive_mmio::Mmio)] +#[repr(C)] +pub struct DevCfg { + control: Control, + lock: Lock, + config: Config, + /// Interrupt is cleared by writing to this register. + interrupt_status: Interrupt, + /// Bits can be set to one to mask the interrupts. + interrupt_mask: Interrupt, + status: Status, + dma_source_addr: u32, + dma_dest_addr: u32, + dma_source_len: u32, + dma_dest_len: u32, + _reserved0: u32, + multiboot_addr: u32, + _reserved1: u32, + unlock_control: u32, + _reserved2: [u32; 0x12], + misc_control: MiscControl, + + _reserved3: [u32; 0x1F], + + // Included here but not exposed to avoid providing multiple references to the same peripheral. + // Exposed in [crate::xadc]. + _xadc: crate::xadc::XAdc, +} + +static_assertions::const_assert_eq!(core::mem::size_of::(), 0x11C); + +impl DevCfg { + /// Create a new DevCfg MMIO instance for for device configuration peripheral at address + /// [DEVCFG_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 unsafe fn new_mmio_fixed() -> MmioDevCfg<'static> { + unsafe { Self::new_mmio_at(DEVCFG_BASE_ADDR) } + } +} diff --git a/zynq7000/src/lib.rs b/zynq7000/src/lib.rs index 9dec569..d544bb0 100644 --- a/zynq7000/src/lib.rs +++ b/zynq7000/src/lib.rs @@ -17,6 +17,8 @@ extern crate std; pub const MPCORE_BASE_ADDR: usize = 0xF8F0_0000; +pub mod ddrc; +pub mod devcfg; pub mod eth; pub mod gic; pub mod gpio; @@ -24,10 +26,13 @@ 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; pub mod uart; +pub mod xadc; static PERIPHERALS_TAKEN: AtomicBool = AtomicBool::new(false); @@ -36,10 +41,11 @@ static PERIPHERALS_TAKEN: AtomicBool = AtomicBool::new(false); /// It is a singleton which exposes all peripherals supported by this crate. /// The [`svd2rust` documentation](https://docs.rs/svd2rust/latest/svd2rust/#peripheral-api) /// provides some more information about this. -pub struct PsPeripherals { +pub struct Peripherals { 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,9 +59,12 @@ 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>, + pub devcfg: devcfg::MmioDevCfg<'static>, + pub xadc: xadc::MmioXAdc<'static>, } -impl PsPeripherals { +impl Peripherals { /// Returns all supported processing system peripherals *once*. pub fn take() -> Option { let taken = PERIPHERALS_TAKEN.swap(true, Ordering::Relaxed); @@ -76,6 +85,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 +99,24 @@ 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(), + devcfg: devcfg::DevCfg::new_mmio_fixed(), + xadc: xadc::XAdc::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..e52c3be 100644 --- a/zynq7000/src/slcr/clocks.rs +++ b/zynq7000/src/slcr/clocks.rs @@ -4,22 +4,15 @@ 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, +pub enum Bypass { + NotBypassed = 0b00, + /// This is the default reset value. + PinStrapSettings = 0b01, + Bypassed = 0b10, + BypassedRegardlessOfPinStrapping = 0b11, } -#[bitbybit::bitenum(u1, exhaustive = true)] -#[derive(Debug)] -pub enum BypassQual { - BypassForceBit = 0b0, - BootModeFourthBit = 0b1, -} - -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct PllControl { /// Feedback divisor for the PLL. /// @@ -29,10 +22,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,38 +34,69 @@ pub struct PllControl { reset: bool, } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +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, debug)] pub struct PllConfig { #[bits(12..=21, rw)] 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)] +#[bitbybit::bitfield(u32, 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, } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct FpgaClockControl { // Reset value 0x1 #[bits(20..=25, rw)] @@ -104,7 +128,7 @@ pub enum SrcSelArm { IoPll = 0b11, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct ArmClockControl { #[bit(28, rw)] cpu_peri_clk_act: bool, @@ -125,7 +149,7 @@ pub struct ArmClockControl { srcsel: SrcSelArm, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct DdrClockControl { /// Divisor for DDR 2x clock. Reset value: 0x6 #[bits(26..=31, rw)] @@ -141,7 +165,7 @@ pub struct DdrClockControl { ddr_3x_clk_act: bool, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct DciClockControl { /// Second cascade divider. Reset value: 0x1E #[bits(20..=25, rw)] @@ -154,8 +178,7 @@ pub struct DciClockControl { clk_act: bool, } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct ClockRatioSelectReg { /// Reset value: 0x1 (6:2:1 clock) #[bit(0, rw)] @@ -199,8 +222,7 @@ impl PartialEq for SrcSelIo { } } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct GigEthClockControl { #[bits(20..=25, rw)] divisor_1: u6, @@ -221,8 +243,7 @@ pub enum SrcSelGigEthRclk { Emio = 1, } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct GigEthRclkControl { #[bit(4, rw)] srcsel: SrcSelGigEthRclk, @@ -246,7 +267,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, @@ -256,8 +277,7 @@ pub struct SingleCommonPeriphIoClockControl { clk_act: bool, } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct DualCommonPeriphIoClockControl { #[bits(8..=13, rw)] divisor: u6, @@ -282,7 +302,7 @@ pub enum SrcSelTpiu { EmioTraceClkAlt2 = 0b111, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct TracePortClockControl { #[bits(8..=13, rw)] divisor: u6, @@ -297,8 +317,7 @@ pub struct TracePortClockControl { /// AMBA peripheral clock control. /// /// These clocks must be enabled if you want to read from the peripheral register space. -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct AperClockControl { #[bit(24, rw)] smc_1x_clk_act: bool, @@ -341,9 +360,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..13bc3f3 100644 --- a/zynq7000/src/spi.rs +++ b/zynq7000/src/spi.rs @@ -1,5 +1,7 @@ //! SPI register module. -use arbitrary_int::{Number, u4}; +use arbitrary_int::{prelude::*, 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, diff --git a/zynq7000/src/xadc.rs b/zynq7000/src/xadc.rs new file mode 100644 index 0000000..e80bf22 --- /dev/null +++ b/zynq7000/src/xadc.rs @@ -0,0 +1,29 @@ +pub const XADC_BASE_ADDR: usize = 0xF8007100; + +#[derive(derive_mmio::Mmio)] +#[repr(C)] +pub struct XAdc { + config: u32, + interrupt_status: u32, + interrupt_mask: u32, + misc_status: u32, + command_fifo: u32, + data_fifo: u32, + misc_control: u32, +} + +impl XAdc { + /// Create a new XADC MMIO instance for for device configuration peripheral at address + /// [XADC_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 unsafe fn new_mmio_fixed() -> MmioXAdc<'static> { + unsafe { XAdc::new_mmio_at(XADC_BASE_ADDR) } + } +} + +static_assertions::const_assert_eq!(core::mem::size_of::(), 0x1C); -- 2.43.0 From 077a44017d92b5862ae653854d980f71f3eb94d6 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 27 Sep 2025 15:53:20 +0200 Subject: [PATCH 2/4] finished QSPI flasher --- examples/embassy/src/main.rs | 2 +- examples/zedboard/src/bin/qspi.rs | 2 +- examples/zedboard/src/main.rs | 2 +- zedboard-bsp/src/qspi_spansion.rs | 67 +++++++++++++++--------- zedboard-fsbl/src/main.rs | 18 +++++-- zedboard-qspi-flasher/Cargo.toml | 1 + zedboard-qspi-flasher/src/main.rs | 86 +++++++++++++++++++++++++++++-- zynq7000-hal/src/lib.rs | 2 +- 8 files changed, 145 insertions(+), 35 deletions(-) diff --git a/examples/embassy/src/main.rs b/examples/embassy/src/main.rs index afbdfe4..1047373 100644 --- a/examples/embassy/src/main.rs +++ b/examples/embassy/src/main.rs @@ -8,7 +8,7 @@ use embassy_time::{Duration, Ticker}; use embedded_hal::digital::StatefulOutputPin; use embedded_io::Write; use log::{error, info}; -use zynq7000_hal::{clocks, gic, gpio, gtc, time::Hertz, uart, BootMode, InteruptConfig}; +use zynq7000_hal::{BootMode, InteruptConfig, clocks, gic, gpio, gtc, time::Hertz, uart}; use zynq7000_rt as _; diff --git a/examples/zedboard/src/bin/qspi.rs b/examples/zedboard/src/bin/qspi.rs index 7c1e30f..89fd2e8 100644 --- a/examples/zedboard/src/bin/qspi.rs +++ b/examples/zedboard/src/bin/qspi.rs @@ -10,7 +10,7 @@ use embedded_io::Write; use log::{error, info}; use zedboard::PS_CLOCK_FREQUENCY; use zedboard_bsp::qspi_spansion; -use zynq7000_hal::{clocks, gic, gpio, gtc, prelude::*, qspi, uart, BootMode}; +use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, prelude::*, qspi, uart}; use zynq7000_rt as _; diff --git a/examples/zedboard/src/main.rs b/examples/zedboard/src/main.rs index b08c435..59c4962 100644 --- a/examples/zedboard/src/main.rs +++ b/examples/zedboard/src/main.rs @@ -9,7 +9,7 @@ use embedded_hal::digital::StatefulOutputPin; use embedded_io::Write; use log::{error, info}; use zedboard::PS_CLOCK_FREQUENCY; -use zynq7000_hal::{clocks, gic, gpio, gtc, uart, BootMode}; +use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, uart}; use zynq7000_rt as _; diff --git a/zedboard-bsp/src/qspi_spansion.rs b/zedboard-bsp/src/qspi_spansion.rs index 31258d6..9eafd3b 100644 --- a/zedboard-bsp/src/qspi_spansion.rs +++ b/zedboard-bsp/src/qspi_spansion.rs @@ -64,6 +64,8 @@ pub enum SectorArchictecture { Hybrid = 0x01, } +pub const PAGE_SIZE: usize = 256; + #[derive(Debug, Clone, Copy)] pub struct BaseDeviceId { manufacturer_id: u8, @@ -221,6 +223,8 @@ pub enum ProgramPageError { ProgrammingErrorBitSet, #[error("address error: {0}")] Addr(#[from] AddrError), + #[error("data is larger than page size {PAGE_SIZE}")] + DataLargerThanPage, } pub struct QspiSpansionS25Fl256SIoMode(RefCell); @@ -428,14 +432,17 @@ impl QspiSpansionS25Fl256SIoMode { /// 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> { + /// The data length max not exceed the page size [PAGE_SIZE]. + pub fn program_page(&mut self, addr: u32, data: &[u8]) -> 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()); } + if data.len() > PAGE_SIZE { + return Err(ProgramPageError::DataLargerThanPage); + } self.write_enable(); let qspi = self.0.get_mut(); let mut transfer = qspi.transfer_guard(); @@ -448,8 +455,9 @@ impl QspiSpansionS25Fl256SIoMode { transfer.write_word_txd_00(u32::from_ne_bytes(raw_word)); let mut read_index: u32 = 0; let mut current_byte_index = 0; + let fifo_writes = data.len().div_ceil(4); // Fill the FIFO until it is full. - for _ in 0..FIFO_DEPTH - 1 { + for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) { transfer.write_word_txd_00(u32::from_ne_bytes( data[current_byte_index..current_byte_index + 4] .try_into() @@ -470,25 +478,38 @@ impl QspiSpansionS25Fl256SIoMode { } }; - // Immediately fill the FIFO again with the remaining 8 bytes. - wait_for_tx_slot(&mut transfer); + while current_byte_index < data.len() { + // 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; + let word = match core::cmp::min(4, data.len() - current_byte_index) { + 1 => { + let mut bytes = [0; 4]; + bytes[0] = data[current_byte_index]; + u32::from_ne_bytes(bytes) + } + 2 => { + let mut bytes = [0; 4]; + bytes[0..2].copy_from_slice(&data[current_byte_index..current_byte_index + 2]); + u32::from_ne_bytes(bytes) + } + 3 => { + let mut bytes = [0; 4]; + bytes[0..3].copy_from_slice(&data[current_byte_index..current_byte_index + 3]); + u32::from_ne_bytes(bytes) + } + 4 => u32::from_ne_bytes( + data[current_byte_index..current_byte_index + 4] + .try_into() + .unwrap(), + ), + _ => unreachable!(), + }; + transfer.write_word_txd_00(word); + 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 { + while read_index < data.len() as u32 { if transfer.read_status().rx_above_threshold() { transfer.read_rx_data(); read_index = read_index.wrapping_add(4); @@ -528,11 +549,7 @@ impl QspiSpansionS25Fl256SIoMode { 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 - }; + let fifo_writes = bytes_to_write.div_ceil(4); // 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); diff --git a/zedboard-fsbl/src/main.rs b/zedboard-fsbl/src/main.rs index a7e1d68..ce9fdad 100644 --- a/zedboard-fsbl/src/main.rs +++ b/zedboard-fsbl/src/main.rs @@ -16,9 +16,9 @@ use zynq7000_hal::{ pll::{PllConfig, configure_arm_pll, configure_io_pll}, }, ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest}, - gic, gpio, l2_cache, + devcfg, gic, gpio, l2_cache, prelude::*, - qspi, + qspi::{self, QSPI_START_ADDRESS}, time::Hertz, uart::{ClockConfig, Config, Uart}, }; @@ -225,7 +225,19 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode) -> ! { match dest_dev { DestinationDevice::Pl => { info!("Loading image '{name}' to PL (FPGA).."); - // TODO: Load the bitstream. + // Load the bitstream directly from linear mapped QSPI memory. + let boot_bin_slice = unsafe { + core::slice::from_raw_parts( + (QSPI_START_ADDRESS + + partition + .data_offset() + .expect("invalid PL partition data offset")) + as *const _, + partition.total_partition_length().unwrap(), + ) + }; + devcfg::configure_bitstream_non_secure(true, boot_bin_slice) + .expect("unexpected unaligned address"); } DestinationDevice::Ps => { // TODO: Load the binary into DDR. Jump at lowest load address after all diff --git a/zedboard-qspi-flasher/Cargo.toml b/zedboard-qspi-flasher/Cargo.toml index 77d7669..03ad697 100644 --- a/zedboard-qspi-flasher/Cargo.toml +++ b/zedboard-qspi-flasher/Cargo.toml @@ -8,6 +8,7 @@ cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar zynq7000-rt = { path = "../zynq7000-rt" } zynq7000 = { path = "../zynq7000" } zynq7000-hal = { path = "../zynq7000-hal" } +zynq-boot-image = { path = "../zynq-boot-image" } zedboard-bsp = { path = "../zedboard-bsp" } embedded-io = "0.6" embedded-hal = "1" diff --git a/zedboard-qspi-flasher/src/main.rs b/zedboard-qspi-flasher/src/main.rs index e43c3f7..a805531 100644 --- a/zedboard-qspi-flasher/src/main.rs +++ b/zedboard-qspi-flasher/src/main.rs @@ -7,10 +7,11 @@ use core::panic::PanicInfo; use cortex_ar::asm::nop; use embedded_hal::{delay::DelayNs as _, digital::StatefulOutputPin as _}; use embedded_io::Write as _; -use log::info; +use log::{error, info}; use zedboard_bsp::qspi_spansion; +use zynq_boot_image::BootHeader; use zynq7000_hal::{ - clocks, gpio, prelude::*, priv_tim, qspi, time::Hertz, uart, BootMode, LevelShifterConfig, + BootMode, LevelShifterConfig, clocks, gpio, prelude::*, priv_tim, qspi, time::Hertz, uart, }; use zynq7000_rt as _; @@ -19,6 +20,15 @@ use zynq7000_rt as _; // 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); +// TODO: Make this configurable somehow? +const BOOT_BIN_BASE_ADDR: usize = 0x1000_0000; +const BOOT_BIN_SIZE_ADDR: usize = 0x900_000; + +// Maximum of 16 MB is allowed for now. +const MAX_BOOT_BIN_SIZE: usize = 16 * 1024 * 1024; + +const VERIFY_PROGRAMMING: bool = true; + #[allow(dead_code)] const QSPI_DEV_COMBINATION: qspi::QspiDeviceCombination = qspi::QspiDeviceCombination { vendor: qspi::QspiVendor::WinbondAndSpansion, @@ -96,7 +106,77 @@ pub fn main() -> ! { let qspi_io_mode = qspi.into_io_mode(false); - let _spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true); + let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true); + + let mut boot_bin_slice = unsafe { + core::slice::from_raw_parts(BOOT_BIN_BASE_ADDR as *const _, BootHeader::FIXED_SIZED_PART) + }; + // This perform some basic validity checks. + let _boot_header = BootHeader::new(&boot_bin_slice[0..BootHeader::FIXED_SIZED_PART]) + .expect("failed to parse boot header"); + let boot_bin_size = + unsafe { core::ptr::read_volatile(BOOT_BIN_SIZE_ADDR as *const u32) as usize }; + if boot_bin_size == 0 || boot_bin_size > MAX_BOOT_BIN_SIZE { + panic!( + "boot binary size read at address {:#x} is invalid: found {}, must be in range [0, {}]", + BOOT_BIN_SIZE_ADDR, boot_bin_size, MAX_BOOT_BIN_SIZE + ); + } + boot_bin_slice = + unsafe { core::slice::from_raw_parts(BOOT_BIN_BASE_ADDR as *const _, boot_bin_size) }; + info!( + "flashing boot binary with {} bytes to QSPI address 0x0", + boot_bin_size + ); + + let mut current_addr = 0; + let mut read_buf = [0u8; 256]; + while current_addr < boot_bin_size { + if current_addr % 0x10000 == 0 { + info!("Erasing sector at address {:#x}", current_addr); + match spansion_qspi.erase_sector(current_addr as u32) { + Ok(()) => {} + Err(e) => { + error!( + "failed to erase sector at address {:#x}: {:?}", + current_addr, e + ); + panic!("QSPI erase failed"); + } + } + } + let write_size = core::cmp::min(256, boot_bin_size - current_addr); + let write_slice = &boot_bin_slice[current_addr..current_addr + write_size]; + info!("Programming address {:#x}", current_addr); + match spansion_qspi.program_page(current_addr as u32, write_slice) { + Ok(()) => {} + Err(e) => { + error!( + "failed to write data to QSPI at address {:#x}: {:?}", + current_addr, e + ); + panic!("QSPI write failed"); + } + } + if VERIFY_PROGRAMMING { + spansion_qspi.read_page_fast_read( + current_addr as u32, + &mut read_buf[0..write_size], + true, + ); + if &read_buf[0..write_size] != write_slice { + error!( + "data verification failed at address {:#x}: wrote {:x?}, read {:x?}", + current_addr, + &write_slice[0..core::cmp::min(16, write_size)], + &read_buf[0..core::cmp::min(16, write_size)] + ); + panic!("QSPI data verification failed"); + } + } + current_addr += write_size; + } + info!("flashing done"); let mut mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low); loop { diff --git a/zynq7000-hal/src/lib.rs b/zynq7000-hal/src/lib.rs index c04ea6e..56390bf 100644 --- a/zynq7000-hal/src/lib.rs +++ b/zynq7000-hal/src/lib.rs @@ -14,8 +14,8 @@ extern crate alloc; use slcr::Slcr; use zynq7000::{ - slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister}, SpiClockPhase, SpiClockPolarity, + slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister}, }; pub mod cache; -- 2.43.0 From 39bb423a48b9b21295cb36dcb7eedaf8b0bfdeda Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 1 Oct 2025 18:41:47 +0200 Subject: [PATCH 3/4] add qspi flasher tcl script --- zedboard-qspi-flasher/qspi-flasher.tcl | 150 +++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 zedboard-qspi-flasher/qspi-flasher.tcl diff --git a/zedboard-qspi-flasher/qspi-flasher.tcl b/zedboard-qspi-flasher/qspi-flasher.tcl new file mode 100644 index 0000000..69e488c --- /dev/null +++ b/zedboard-qspi-flasher/qspi-flasher.tcl @@ -0,0 +1,150 @@ +if {[info exists env(ip_address_hw_server)]} { + set ip $env(ip_address_hw_server) +} else { + set ip "localhost" +} + +# Defaults +set init_tcl "" +set bin "" +set bitstream "" + +# Usage helper +proc usage {} { + puts "Usage: xsct xsct-helper.tcl \[-a|--app app.elf\] \[-b|--bit design.bit]" + puts "Options:" + puts " -b, --bin Path to boot binary to flash" + 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 "-b" || $arg eq "--bin"} { set expecting app; 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 {$bin ne ""} { + if {![file exists $bin]} { + puts "error: the boot binary file '$bin' does not exist" + exit 1 + } +} elseif {[info exists env(BOOTBIN)]} { + if {[file exists $env(BOOTBIN)]} { + set bin $env(BOOTBIN) + } else { + puts "warning: BOOTBIN environment variable is set but file does not exist: $env(BOOTBIN)" + } +} + +if {$bin eq ""} { + puts "error: boot.bin binary required as BOOTBIN environment" + usage + exit 1 +} + +# 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 + +puts "Download boot.bin $bin to target DDR" +dow $bin + +# TODO: Flash QSPI flasher. + + +puts "Starting app" +con + +puts "Success" -- 2.43.0 From 685337453740a3c30b09bbbd8869e5899ad6ee41 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 2 Oct 2025 11:06:07 +0200 Subject: [PATCH 4/4] bump deps again --- examples/embassy/Cargo.toml | 4 ++-- examples/simple/Cargo.toml | 4 ++-- examples/zedboard/Cargo.toml | 4 ++-- zedboard-fsbl/Cargo.toml | 4 ++-- zedboard-qspi-flasher/Cargo.toml | 4 ++-- zynq7000-hal/Cargo.toml | 6 +++--- zynq7000-hal/src/uart/tx_async.rs | 5 +++++ 7 files changed, 18 insertions(+), 13 deletions(-) diff --git a/examples/embassy/Cargo.toml b/examples/embassy/Cargo.toml index c17151b..b2aaa27 100644 --- a/examples/embassy/Cargo.toml +++ b/examples/embassy/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"] categories = ["embedded", "no-std", "hardware-support"] [dependencies] -cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2", features = ["critical-section-single-core"] } +cortex-ar = "0.3" zynq7000-rt = { path = "../../zynq7000-rt" } zynq7000 = { path = "../../zynq7000" } zynq7000-hal = { path = "../../zynq7000-hal" } @@ -20,7 +20,7 @@ dht-sensor = { git = "https://github.com/michaelbeaumont/dht-sensor.git", rev = static_cell = "2" critical-section = "1" heapless = "0.9" -embedded-io = "0.6" +embedded-io = "0.7" embedded-hal = "1" fugit = "0.3" log = "0.4" diff --git a/examples/simple/Cargo.toml b/examples/simple/Cargo.toml index 64354b6..fdec76d 100644 --- a/examples/simple/Cargo.toml +++ b/examples/simple/Cargo.toml @@ -9,11 +9,11 @@ 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"] } +cortex-ar = "0.3" zynq7000-rt = { path = "../../zynq7000-rt" } zynq7000 = { path = "../../zynq7000" } zynq7000-hal = { path = "../../zynq7000-hal" } -embedded-io = "0.6" +embedded-io = "0.7" embedded-hal = "1" fugit = "0.3" log = "0.4" diff --git a/examples/zedboard/Cargo.toml b/examples/zedboard/Cargo.toml index 279562c..34cd460 100644 --- a/examples/zedboard/Cargo.toml +++ b/examples/zedboard/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"] categories = ["embedded", "no-std", "hardware-support"] [dependencies] -cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2", features = ["critical-section-single-core"] } +cortex-ar = "0.3" zynq7000-rt = { path = "../../zynq7000-rt" } zynq7000 = { path = "../../zynq7000" } zynq7000-hal = { path = "../../zynq7000-hal" } @@ -19,7 +19,7 @@ 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" +embedded-io = "0.7" bitbybit = "1.4" arbitrary-int = "2" embedded-io-async = "0.6" diff --git a/zedboard-fsbl/Cargo.toml b/zedboard-fsbl/Cargo.toml index 00a44e2..1f635d6 100644 --- a/zedboard-fsbl/Cargo.toml +++ b/zedboard-fsbl/Cargo.toml @@ -9,13 +9,13 @@ 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"] } +cortex-ar = "0.3" zynq7000-rt = { path = "../zynq7000-rt" } zynq7000 = { path = "../zynq7000" } zynq7000-hal = { path = "../zynq7000-hal" } zedboard-bsp = { path = "../zedboard-bsp" } zynq-boot-image = { path = "../zynq-boot-image" } -embedded-io = "0.6" +embedded-io = "0.7" embedded-hal = "1" fugit = "0.3" log = "0.4" diff --git a/zedboard-qspi-flasher/Cargo.toml b/zedboard-qspi-flasher/Cargo.toml index 03ad697..aa9a498 100644 --- a/zedboard-qspi-flasher/Cargo.toml +++ b/zedboard-qspi-flasher/Cargo.toml @@ -4,12 +4,12 @@ version = "0.1.0" edition = "2024" [dependencies] -cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", branch = "main" } +cortex-ar = { version = "0.3" } zynq7000-rt = { path = "../zynq7000-rt" } zynq7000 = { path = "../zynq7000" } zynq7000-hal = { path = "../zynq7000-hal" } zynq-boot-image = { path = "../zynq-boot-image" } zedboard-bsp = { path = "../zedboard-bsp" } -embedded-io = "0.6" +embedded-io = "0.7" embedded-hal = "1" log = "0.4" diff --git a/zynq7000-hal/Cargo.toml b/zynq7000-hal/Cargo.toml index 8e8e74d..6ffc9a4 100644 --- a/zynq7000-hal/Cargo.toml +++ b/zynq7000-hal/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["no-std", "hal", "amd", "zynq7000", "xilinx", "bare-metal"] categories = ["embedded", "no-std", "hardware-support"] [dependencies] -cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", branch = "bump-arbitrary-int" } +cortex-ar = { version = "0.3" } zynq7000 = { path = "../zynq7000" } zynq-mmu = { path = "../zynq-mmu", version = "0.1.0" } @@ -22,7 +22,7 @@ thiserror = { version = "2", default-features = false } num_enum = { version = "0.7", default-features = false } ringbuf = { version = "0.4.8", default-features = false } embedded-hal-nb = "1" -embedded-io = "0.6" +embedded-io = "0.7" embedded-hal = "1" embedded-hal-async = "1" heapless = "0.9" @@ -39,7 +39,7 @@ embassy-net-driver = "0.2" smoltcp = { version = "0.12", default-features = false, features = ["proto-ipv4", "medium-ethernet", "socket-raw"] } vcell = "0.1" raw-slicee = "0.1" -embedded-io-async = "0.6" +embedded-io-async = "0.7" [features] std = ["thiserror/std", "alloc"] diff --git a/zynq7000-hal/src/uart/tx_async.rs b/zynq7000-hal/src/uart/tx_async.rs index f349e71..3033123 100644 --- a/zynq7000-hal/src/uart/tx_async.rs +++ b/zynq7000-hal/src/uart/tx_async.rs @@ -202,4 +202,9 @@ impl embedded_io_async::Write for TxAsync { async fn write(&mut self, buf: &[u8]) -> Result { Ok(self.write(buf).await) } + + /// This implementation does not do anything. + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } } -- 2.43.0