diff --git a/.cargo/def-config.toml b/.cargo/config.toml.template similarity index 54% rename from .cargo/def-config.toml rename to .cargo/config.toml.template index a2dd61b..5092f1a 100644 --- a/.cargo/def-config.toml +++ b/.cargo/config.toml.template @@ -1,24 +1,3 @@ -[target.armv7a-none-eabihf] -runner = "./scripts/runner.sh" - -rustflags = [ - "-Ctarget-cpu=cortex-a9", - "-Ctarget-feature=+vfp3", - "-Ctarget-feature=+neon", - "-Clink-arg=-Tlink.x", - # If this is not enabled, debugging / stepping can become problematic. - "-Cforce-frame-pointers=yes", - # Can be useful for debugging. - # "-Clink-args=-Map=app.map" -] - -# Tier 3 target, so no pre-compiled artifacts included. -[unstable] -build-std = ["core", "alloc"] - -[build] -target = "armv7a-none-eabihf" - [env] # The following two env variables need to be set for the supplied runner.sh script to work. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46698e7..900e867 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,41 +7,69 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: extractions/setup-just@v3 - uses: dtolnay/rust-toolchain@nightly with: components: rust-src - # Copy config file for rustflags and to build core/alloc. - - run: cp .cargo/def-config.toml .cargo/config.toml - - run: cargo check --target armv7a-none-eabihf -p zynq7000 - - run: cargo check --target armv7a-none-eabihf -p zynq7000-rt + - run: just check zynq + + - uses: dtolnay/rust-toolchain@stable + - run: just check tools + - run: just check zynq7000-boot-image + + build: + name: Check build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: extractions/setup-just@v3 + + - uses: dtolnay/rust-toolchain@nightly + with: + components: rust-src + - run: just build zynq + + - uses: dtolnay/rust-toolchain@stable + - run: just build tools + - run: just build zynq7000-boot-image fmt: name: Check formatting runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly + - uses: extractions/setup-just@v3 + - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - - run: cargo fmt --all -- --check + - run: just fmt zynq + - run: just fmt tools + - run: just fmt zynq7000-boot-image docs: name: Check Documentation Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: extractions/setup-just@v3 - uses: dtolnay/rust-toolchain@nightly - - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p zynq7000-rt --all-features - - run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p zynq7000 --all-features + with: + components: rust-src + - run: just docs-zynq clippy: name: Clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: extractions/setup-just@v3 - uses: dtolnay/rust-toolchain@nightly with: components: clippy, rust-src - # Copy config file for rustflags and to build core/alloc. - - run: cp .cargo/def-config.toml .cargo/config.toml - - run: cargo clippy --target armv7a-none-eabihf -- -D warnings + - run: just clippy zynq + + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - run: just clippy tools + - run: just clippy zynq7000-boot-image diff --git a/.gitignore b/.gitignore index cad995a..a2e6b44 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -/target +target + /app.map /xsct-output.log /.vscode diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index a8de1af..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[workspace] -resolver = "3" -members = [ - "zynq7000-rt", - "zynq7000", - "zynq7000-hal", - "zynq7000-embassy", - "examples/simple", - "examples/embassy", - "examples/zedboard", - "zynq-mmu", -] -exclude = ["experiments"] - -# cargo build/run --release -[profile.release] -codegen-units = 1 -debug = 2 -debug-assertions = false # <- -incremental = false -lto = true -opt-level = 3 # <- -overflow-checks = false # <- diff --git a/README.md b/README.md index 7a1e81e..3fd640a 100644 --- a/README.md +++ b/README.md @@ -6,36 +6,60 @@ family of SoCs. # List of crates -This workspace contains the following crates: +This project contains the following crates: -- The [`zynq7000-rt`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq7000-rt) +## [Zynq Workspace](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq) + +This workspace contains libraries and application which can only be run on the target system. + +- The [`zynq7000-rt`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000-rt) run-time crate containing basic low-level startup code necessary to boot a Rust app on the Zynq7000. -- The [`zynq7000`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq7000) PAC - crate containing basic low-level register definition. -- The [`zynq7000-hal`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq7000-hal) +- The [`zynq7000`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000) PAC + crate containing basic low-level register definitions. +- The [`zynq7000-mmu`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000-hal) + crate containing common MMU abstractions used by both the HAL and the run-time crate. +- The [`zynq7000-hal`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000-hal) HAL crate containing higher-level abstractions on top of the PAC register crate. -- The [`zynq7000-embassy`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq7000-embassy) - crate containing support for running the embassy-rs RTOS. +- The [`zynq7000-embassy`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000-embassy) + crate containing support for running the embassy-rs asynchronous run-time. + +This project was developed using a Zedboard, so there are several crates available targeted towards +this board: + +- The [`zedboard-bsp`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zedboard-bsp) + crate containing board specific components for the Zedboard. +- The [`zedboard-fsbl`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zedboard-fsbl) + contains a simple first-stage bootloader application for the Zedboard. +- The [`zedboard-qspi-flasher`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zedboard-qspi-flasher) + contains an application which is able to flash a boot binary from DDR to the QSPI. It also contains the following helper crates: -- The [`examples`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/examples) +- The [`examples`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/examples) folder contains various example applications crates using the HAL and the PAC. This folder also contains dedicated example applications using the [`embassy`](https://github.com/embassy-rs/embassy) native Rust RTOS. -The [`zedboard-fpga-design`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zedboard-fpga-design) -folder contains a sample FPGA design and block design which was used in -some of the provided software examples. The project was created with Vivado version 2024.1. -The folder contains a README with all the steps required to load this project from a TCL script. +## Other libraries and tools + +- The [`zedboard-fpga-design`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zedboard-fpga-design) + folder contains a sample FPGA design and block design which was used in some of the provided software examples. The project was created with Vivado version 2024.1. + The folder contains a README with all the steps required to load this project from a TCL script. +- The [`zynq7000-boot-image`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq7000-boot-image) + library contains generic helpers to interface with the AMD + [boot binary](https://docs.amd.com/r/en-US/ug1283-bootgen-user-guide). +- The [`tools/zynq7000-ps7init-extract`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/tools/zynq7000-ps7init-extract) + tool allows extracting configuration from the AMD generated `ps7init.tcl` file which contains + static configuration parameters for DDR initialization. # Using the `.cargo/config.toml` file +This is mostly relevant for development directly inside this repostiory. 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/zedboard/src/main.rs b/examples/zedboard/src/main.rs deleted file mode 100644 index d2c5c6d..0000000 --- a/examples/zedboard/src/main.rs +++ /dev/null @@ -1,157 +0,0 @@ -#![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 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::{PsPeripherals, slcr::LevelShifterConfig}; -use zynq7000_rt as _; - -const INIT_STRING: &str = "-- Zynq 7000 Zedboard GPIO blinky example --\n\r"; - -/// 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(); -} - -#[embassy_executor::main] -#[unsafe(export_name = "main")] -async fn main(_spawner: Spawner) -> ! { - let mut dp = PsPeripherals::take().unwrap(); - l2_cache::init_with_defaults(&mut dp.l2c); - - // Enable PS-PL level shifters. - configure_level_shifter(LevelShifterConfig::EnableAll); - - // Clock was already initialized by PS7 Init TCL script or FSBL, we just read it. - let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap(); - // Set up the global interrupt controller. - let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd); - gic.enable_all_interrupts(); - gic.set_all_spi_interrupt_targets_cpu0(); - gic.enable(); - unsafe { - gic.enable_interrupts(); - } - let mut gpio_pins = GpioPins::new(dp.gpio); - - // Set up global timer counter and embassy time driver. - let gtc = GlobalTimerCounter::new(dp.gtc, clocks.arm_clocks()); - zynq7000_embassy::init(clocks.arm_clocks(), gtc); - - // Set up the UART, we are logging with it. - let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200) - .unwrap() - .0; - let mut uart = Uart::new_with_mio( - dp.uart_1, - UartConfig::new_with_clk_config(uart_clk_config), - (gpio_pins.mio.mio48, gpio_pins.mio.mio49), - ) - .unwrap(); - uart.write_all(INIT_STRING.as_bytes()).unwrap(); - // Safety: We are not multi-threaded yet. - unsafe { - zynq7000_hal::log::uart_blocking::init_unsafe_single_core( - uart, - log::LevelFilter::Trace, - false, - ) - }; - - let boot_mode = BootMode::new(); - 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), - ]; - loop { - mio_led.toggle().unwrap(); - - // Create a wave pattern for emio_leds - for led in emio_leds.iter_mut() { - led.toggle().unwrap(); - ticker.next().await; // Wait for the next ticker for each toggle - } - - ticker.next().await; // Wait for the next cycle of the ticker - } -} - -#[unsafe(no_mangle)] -pub extern "C" fn _irq_handler() { - let mut gic_helper = GicInterruptHelper::new(); - let irq_info = gic_helper.acknowledge_interrupt(); - match irq_info.interrupt() { - Interrupt::Sgi(_) => (), - Interrupt::Ppi(ppi_interrupt) => { - if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer { - unsafe { - zynq7000_embassy::on_interrupt(); - } - } - } - Interrupt::Spi(_spi_interrupt) => (), - Interrupt::Invalid(_) => (), - Interrupt::Spurious => (), - } - gic_helper.end_of_interrupt(irq_info); -} - -#[unsafe(no_mangle)] -pub extern "C" fn _abort_handler() { - loop { - nop(); - } -} - -#[unsafe(no_mangle)] -pub extern "C" fn _undefined_handler() { - loop { - nop(); - } -} - -#[unsafe(no_mangle)] -pub extern "C" fn _prefetch_handler() { - loop { - nop(); - } -} - -/// Panic handler -#[panic_handler] -fn panic(info: &PanicInfo) -> ! { - error!("Panic: {info:?}"); - loop {} -} diff --git a/justfile b/justfile new file mode 100644 index 0000000..fe226d9 --- /dev/null +++ b/justfile @@ -0,0 +1,45 @@ +all: check-all build-all clean-all fmt-all clippy-all docs-zynq + +check-all: (check "zynq") (check "tools") (check "zynq7000-boot-image") +clean-all: (clean "zynq") (clean "tools") (clean "zynq7000-boot-image") +build-all: build-zynq (build "tools") (build "zynq7000-boot-image") +fmt-all: (fmt "zynq") (fmt "tools") (fmt "zynq7000-boot-image") +clippy-all: (clippy "zynq") (clippy "tools") (clippy "zynq7000-boot-image") + +check target: + cd {{target}} && cargo check + +build target: + cd {{target}} && cargo build + +build-zynq: (build "zynq") + cd "zynq/zedboard-fsbl" && cargo build --release + +clean target: + cd {{target}} && cargo clean + +fmt target: + cd {{target}} && cargo +stable fmt --all -- --check + +clippy target: + cd {{target}} && cargo clippy -- -D warnings + +[working-directory: 'zynq'] +docs-zynq: + RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p zynq7000 + RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p zynq7000-hal --features alloc + RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p zynq7000-mmu + RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p zynq7000-rt + +[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" + +[no-cd] +run binary: + # Run the initialization script. It needs to be run inside the justfile directory. + python3 {{justfile_directory()}}/scripts/zynq7000-init.py + + # Run the GDB debugger in GUI mode. + gdb-multiarch -q -x {{justfile_directory()}}/zynq/gdb.gdb {{binary}} -tui 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/runner.sh b/scripts/runner.sh deleted file mode 100755 index 49e7049..0000000 --- a/scripts/runner.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -# Exit if no arguments are provided -if [ "$#" -eq 0 ]; then - echo "Error: No arguments provided." - echo "Usage: run.sh " - exit 1 -fi - -# Get the absolute path to the `scripts/` directory -SCRIPT_DIR="$(cd -- "$(dirname -- "$0")" && pwd)" - -# Get the absolute path to the project root -ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" - -# Run the initialization script -"$SCRIPT_DIR/zynq7000-init.py" - -# Run the GDB debugger in GUI mode. -gdb-multiarch -q -x gdb.gdb "$@" -tui \ No newline at end of file diff --git a/scripts/unittest.sh b/scripts/unittest.sh deleted file mode 100755 index f3dab7d..0000000 --- a/scripts/unittest.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -cargo +stable test --target $(rustc -vV | grep host | cut -d ' ' -f2) -p zynq7000-hal -cargo +stable test --target $(rustc -vV | grep host | cut -d ' ' -f2) -p zynq7000 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..fdcae84 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 @@ -124,7 +121,9 @@ def main(): bitstream = args.bit init_tcl = args.init_tcl - xsct_script = Path(TCL_SCRIPT_NAME) + # Get the script's directory + script_dir = Path(__file__).resolve().parent + xsct_script = script_dir / TCL_SCRIPT_NAME if not xsct_script.exists(): xsct_script = Path(os.path.join(SCRIPTS_DIR, TCL_SCRIPT_NAME)) @@ -136,7 +135,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/tools/Cargo.lock b/tools/Cargo.lock new file mode 100644 index 0000000..ecb2c06 --- /dev/null +++ b/tools/Cargo.lock @@ -0,0 +1,796 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[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 0.60.2", +] + +[[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 0.60.2", +] + +[[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 = "arm-targets" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3371884971a96d71d8bd4e781188a7d327d7e5e455d07ef4c352922c66695e9e" + +[[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 = "boot-image-test" +version = "0.1.0" +dependencies = [ + "clap", + "zynq7000-boot-image", +] + +[[package]] +name = "clap" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +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 = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.59.0", +] + +[[package]] +name = "cortex-ar" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ea2a354642e242870bc43b57a517359b0be6e96d302b2811cd0644c979c54e" +dependencies = [ + "arbitrary-int 2.0.0", + "arm-targets", + "bitbybit", + "num_enum", + "thiserror", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "deranged" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive-mmio" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005a6dabf68a87a460d3cb9b8e2fd5de3f474fc34e8d9451f5a1b6db518da143" +dependencies = [ + "derive-mmio-macro", + "rustversion", +] + +[[package]] +name = "derive-mmio-macro" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "584dc8e12e4aeb88000c2be8ef7db15657c817fba3e77999a24807d1efcdeefa" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[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 = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mmu-table-gen" +version = "0.1.0" +dependencies = [ + "zynq7000-mmu", + "zynq7000-rt", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num_enum" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[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.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "simple_logger" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c5dfa5e08767553704aa0ffd9d9794d527103c736aba9854773851fd7497eb" +dependencies = [ + "colored", + "log", + "time", + "windows-sys 0.48.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[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 = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[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.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "zynq7000" +version = "0.1.0" +dependencies = [ + "arbitrary-int 2.0.0", + "bitbybit", + "derive-mmio", + "once_cell", + "rustversion", + "static_assertions", + "thiserror", +] + +[[package]] +name = "zynq7000-boot-image" +version = "0.1.0" +dependencies = [ + "arbitrary-int 2.0.0", + "bitbybit", + "thiserror", +] + +[[package]] +name = "zynq7000-mmu" +version = "0.1.0" +dependencies = [ + "cortex-ar", + "thiserror", +] + +[[package]] +name = "zynq7000-ps7init-extract" +version = "0.1.0" +dependencies = [ + "clap", + "log", + "proc-macro2", + "quote", + "regex", + "simple_logger", + "syn", + "zynq7000", +] + +[[package]] +name = "zynq7000-rt" +version = "0.1.0" +dependencies = [ + "arbitrary-int 2.0.0", + "cortex-ar", + "zynq7000-mmu", +] diff --git a/tools/Cargo.toml b/tools/Cargo.toml new file mode 100644 index 0000000..c3e5666 --- /dev/null +++ b/tools/Cargo.toml @@ -0,0 +1,7 @@ +[workspace] +resolver = "3" +members = [ + "boot-image-test", + "mmu-table-gen", + "zynq7000-ps7init-extract", +] diff --git a/tools/boot-image-test/.gitignore b/tools/boot-image-test/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/tools/boot-image-test/.gitignore @@ -0,0 +1 @@ +/target diff --git a/tools/boot-image-test/Cargo.lock b/tools/boot-image-test/Cargo.lock new file mode 100644 index 0000000..e8d5fdc --- /dev/null +++ b/tools/boot-image-test/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/tools/boot-image-test/Cargo.toml b/tools/boot-image-test/Cargo.toml new file mode 100644 index 0000000..cdca892 --- /dev/null +++ b/tools/boot-image-test/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "boot-image-test" +version = "0.1.0" +edition = "2024" + +[dependencies] +zynq7000-boot-image= { path = "../../zynq7000-boot-image" } +clap = { version = "4", features = ["derive"] } diff --git a/tools/boot-image-test/src/main.rs b/tools/boot-image-test/src/main.rs new file mode 100644 index 0000000..c793ef5 --- /dev/null +++ b/tools/boot-image-test/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 zynq7000_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/tools/mmu-table-gen/Cargo.toml b/tools/mmu-table-gen/Cargo.toml new file mode 100644 index 0000000..9736709 --- /dev/null +++ b/tools/mmu-table-gen/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "mmu-table-gen" +version = "0.1.0" +edition = "2024" + +[dependencies] +zynq7000-rt = { path = "../../zynq/zynq7000-rt", default-features = false } +zynq7000-mmu = { path = "../../zynq/zynq7000-mmu", features = ["tools"] } diff --git a/zynq7000-rt/src/bin/table-gen.rs b/tools/mmu-table-gen/src/main.rs similarity index 98% rename from zynq7000-rt/src/bin/table-gen.rs rename to tools/mmu-table-gen/src/main.rs index dccfb0e..faa3504 100644 --- a/zynq7000-rt/src/bin/table-gen.rs +++ b/tools/mmu-table-gen/src/main.rs @@ -16,7 +16,7 @@ macro_rules! write_l1_section { } fn main() { - let file_path = "src/mmu_table.rs"; + let file_path = "mmu_table.rs"; let file = File::create(file_path).expect("Failed to create file"); let mut offset = 0; @@ -56,7 +56,7 @@ fn main() { writeln!(buf_writer, "use crate::mmu::section_attrs;").unwrap(); writeln!(buf_writer, "use cortex_ar::mmu::L1Section;").unwrap(); - writeln!(buf_writer, "use zynq_mmu::L1Table;").unwrap(); + writeln!(buf_writer, "use zynq7000_mmu::L1Table;").unwrap(); writeln!(buf_writer, "").unwrap(); writeln!(buf_writer, "/// MMU Level 1 Page table.").unwrap(); diff --git a/tools/zynq7000-ps7init-extract/.gitignore b/tools/zynq7000-ps7init-extract/.gitignore new file mode 100644 index 0000000..f632ad1 --- /dev/null +++ b/tools/zynq7000-ps7init-extract/.gitignore @@ -0,0 +1,2 @@ +/ddrc_config_autogen.rs +/ddriob_config_autogen.rs diff --git a/tools/zynq7000-ps7init-extract/Cargo.lock b/tools/zynq7000-ps7init-extract/Cargo.lock new file mode 100644 index 0000000..074b1ff --- /dev/null +++ b/tools/zynq7000-ps7init-extract/Cargo.lock @@ -0,0 +1,251 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[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 = "clap" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +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.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +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 = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[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.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "zynq7000-ps7init-extract" +version = "0.1.0" +dependencies = [ + "clap", +] diff --git a/tools/zynq7000-ps7init-extract/Cargo.toml b/tools/zynq7000-ps7init-extract/Cargo.toml new file mode 100644 index 0000000..27b5b51 --- /dev/null +++ b/tools/zynq7000-ps7init-extract/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "zynq7000-ps7init-extract" +version = "0.1.0" +edition = "2024" + +[dependencies] +clap = { version = "4", features = ["derive"] } +zynq7000 = { path = "../../zynq/zynq7000" } +log = "0.4" +simple_logger = "5" +regex = "1" +quote = "1" +syn = "2" +proc-macro2 = "1" diff --git a/tools/zynq7000-ps7init-extract/README.md b/tools/zynq7000-ps7init-extract/README.md new file mode 100644 index 0000000..37179e6 --- /dev/null +++ b/tools/zynq7000-ps7init-extract/README.md @@ -0,0 +1,26 @@ +Zynq7000 PS7 Init Extractor +========= + +AMD provides tooling to auto-generate some of the hardware initialization for the external DDR +as native Rust code. + +The AMD tooling generates these files as `ps7init.tcl`, `ps7init.c`, `ps7init.h` files but not as +Rust files. The specific parameters required for different DDR chips are proprietary, so that +portion is required for Rust programs as well. Do avoid the need of compiling the PS7 initialization +scripts with a C compiler, this tool extracts all required configuration parameters for DDR and +DDRIOB initialization and configuration and exports them as native Rust constants. + +The generates files can be placed in individual projects or board support packages to initialize +the DDR in conjunction with the [Zynq7000 HAL library](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/fsbl-rs/zynq/zynq7000-hal). + +Right now, the script expects the `ps7init.tcl` file to be passed as a command line argument +for `-p` or `--path`. It then generates the configuration as a `ddrc_config_autogen.rs` and +`ddrc_config_autogen.rs` file. + +For example, assuming that there is a `ps7init.tcl` script in the current directory, you can use + +```sh +cargo run -- --path ./ps7init.tcl +``` + +to generate the configuration files. diff --git a/tools/zynq7000-ps7init-extract/src/main.rs b/tools/zynq7000-ps7init-extract/src/main.rs new file mode 100644 index 0000000..db195c2 --- /dev/null +++ b/tools/zynq7000-ps7init-extract/src/main.rs @@ -0,0 +1,341 @@ +use std::{collections::HashMap, ops::RangeInclusive, path::Path}; + +use clap::Parser as _; +use simple_logger::SimpleLogger; + +const DDRC_ADDR_RANGE: RangeInclusive = 0xf800_6000..=0xf800_62b4; +const DDRIOB_ADDR_RANGE: RangeInclusive = 0xf800_0b40..=0xf800_0b68; + +const DDRC_FILE_NAME: &str = "ddrc_config_autogen.rs"; +const DDRIOB_FILE_NAME: &str = "ddriob_config_autogen.rs"; + +#[derive(clap::Parser, Debug)] +#[command(version, about)] +pub struct Cli { + /// Path to ps7init.tcl file. + #[arg(short, long)] + path: String, +} + +fn extract_hex_values(line: &str) -> Option<(u32, u32, u32)> { + let re = regex::Regex::new(r"0[xX]([0-9A-Fa-f]+)").unwrap(); + + let captures: Vec = re + .captures_iter(line) + .filter_map(|cap| u32::from_str_radix(&cap[1], 16).ok()) + .collect(); + + if captures.len() == 3 { + Some((captures[0], captures[1], captures[2])) + } else { + None + } +} + +#[derive(Default)] +pub struct RegisterToValueMap(pub HashMap); + +impl RegisterToValueMap { + fn val_as_token(&self, reg_name: &str, addr: u32) -> proc_macro2::TokenStream { + let val = self.0.get(&addr).unwrap_or_else(|| { + panic!( + "failed to retrieve register value for register {}", + reg_name + ) + }); + format!("{:#010x}", val) + .parse::() + .unwrap() + } +} + +enum ParsingMode { + DdrRev3, + MioRev3, +} + +fn main() -> std::io::Result<()> { + SimpleLogger::new().init().unwrap(); + let cli = Cli::parse(); + let ps7init_tcl = Path::new(&cli.path); + if !ps7init_tcl.exists() { + log::error!("File not found: {}", ps7init_tcl.display()); + std::process::exit(1); + } + let mut parsing_mode = None; + + let mut reg_to_values = RegisterToValueMap::default(); + + for line in std::fs::read_to_string(ps7init_tcl)?.lines() { + match parsing_mode { + None => { + if line.contains("ps7_ddr_init_data_3_0") { + parsing_mode = Some(ParsingMode::DdrRev3); + } else if line.contains("ps7_mio_init_data_3_0") { + parsing_mode = Some(ParsingMode::MioRev3); + } + continue; + } + Some(ParsingMode::MioRev3) => { + if line.contains("}") { + parsing_mode = None; + continue; + } + } + Some(ParsingMode::DdrRev3) => { + if line.contains("}") { + parsing_mode = None; + continue; + } + } + } + + if let Some((addr, _mask, value)) = extract_hex_values(line) + && (DDRC_ADDR_RANGE.contains(&addr) || DDRIOB_ADDR_RANGE.contains(&addr)) + && addr % 4 == 0 + { + // Only use first value. + if reg_to_values.0.contains_key(&addr) { + if addr != 0xF800_6000 { + log::warn!("detected duplicate register value for address {}", addr); + } + continue; + } + reg_to_values.0.insert(addr, value); + } + } + + log::info!("generating DDRC config files: {}", DDRC_FILE_NAME); + generate_ddrc_config(®_to_values, DDRC_FILE_NAME)?; + + log::info!("generating DDRIOB config files: {}", DDRIOB_FILE_NAME); + generate_ddriob_config(®_to_values, DDRIOB_FILE_NAME)?; + + Ok(()) +} + +fn generate_ddrc_config( + reg_to_values: &RegisterToValueMap, + file_name: &str, +) -> std::io::Result<()> { + // Format as hex strings + let ddrc = reg_to_values.val_as_token("DDRC Control", 0xF800_6000); + let two_rank = reg_to_values.val_as_token("Two Rank", 0xF800_6004); + let hpr = reg_to_values.val_as_token("HPR", 0xF800_6008); + let lpr = reg_to_values.val_as_token("LPR", 0xF800_600C); + let wr = reg_to_values.val_as_token("WR", 0xF800_6010); + let dram_param_0 = reg_to_values.val_as_token("DRAM Reg0", 0xF800_6014); + let dram_param_1 = reg_to_values.val_as_token("DRAM Reg1", 0xF800_6018); + let dram_param_2 = reg_to_values.val_as_token("DRAM Reg2", 0xF800_601C); + let dram_param_3 = reg_to_values.val_as_token("DRAM Reg3", 0xF800_6020); + let dram_param_4 = reg_to_values.val_as_token("DRAM Reg4", 0xF800_6024); + let dram_init_param = reg_to_values.val_as_token("DRAM Init Param", 0xF800_6028); + let dram_emr = reg_to_values.val_as_token("DRAM EMR", 0xF800_602C); + let dram_emr_mr = reg_to_values.val_as_token("DRAM EMR MR", 0xF800_6030); + let dram_burst8_rdwr = reg_to_values.val_as_token("DRAM Burst8 RDWR", 0xF800_6034); + let dram_disable_dq = reg_to_values.val_as_token("DRAM Disable DQ", 0xF800_6038); + let dram_addr_map_bank = reg_to_values.val_as_token("DRAM Addr Map Bank", 0xF800_603C); + let dram_addr_map_col = reg_to_values.val_as_token("DRAM Addr Map Col", 0xF800_6040); + let dram_addr_map_row = reg_to_values.val_as_token("DRAM Addr Map Row", 0xF800_6044); + let dram_odt = reg_to_values.val_as_token("DRAM ODT", 0xF800_6048); + let phy_cmd_timeout_rddata_cpt = reg_to_values.val_as_token("PHY CMD Timeout", 0xF800_6050); + let dll_calib = reg_to_values.val_as_token("DLL Calib", 0xF800_6058); + let odt_delay_hold = reg_to_values.val_as_token("ODT Delay Hold", 0xF800_605C); + let ctrl_reg1 = reg_to_values.val_as_token("CTRL Reg 1", 0xF800_6060); + let ctrl_reg2 = reg_to_values.val_as_token("CTRL Reg 2", 0xF800_6064); + let ctrl_reg3 = reg_to_values.val_as_token("CTRL Reg 3", 0xF800_6068); + let ctrl_reg4 = reg_to_values.val_as_token("CTRL Reg 4", 0xF800_606C); + let ctrl_reg5 = reg_to_values.val_as_token("CTRL Reg 5", 0xF800_6078); + let ctrl_reg6 = reg_to_values.val_as_token("CTRL Reg 6", 0xF800_607C); + let che_t_zq = reg_to_values.val_as_token("CHE T ZQ", 0xF800_60A4); + let che_t_zq_short_interval_reg = + reg_to_values.val_as_token("CHE T ZQ Short Interval", 0xF800_60A8); + let deep_powerdown = reg_to_values.val_as_token("Deep Powerdown", 0xF800_60AC); + let reg_2c = reg_to_values.val_as_token("Reg 2C", 0xF800_60B0); + let reg_2d = reg_to_values.val_as_token("Reg 2D", 0xF800_60B4); + let dfi_timing = reg_to_values.val_as_token("DFI Timing", 0xF800_60B8); + let che_ecc_ctrl = reg_to_values.val_as_token("CHE ECC CTRL", 0xF800_60C4); + let ecc_scrub = reg_to_values.val_as_token("ECC Scrub", 0xF800_60F4); + let phy_receiver_enable = reg_to_values.val_as_token("PHY Receiver Enable", 0xF800_6114); + let phy_config_0 = reg_to_values.val_as_token("PHY Config 0", 0xF800_6118); + let phy_config_1 = reg_to_values.val_as_token("PHY Config 1", 0xF800_611C); + let phy_config_2 = reg_to_values.val_as_token("PHY Config 2", 0xF800_6120); + let phy_config_3 = reg_to_values.val_as_token("PHY Config 3", 0xF800_6124); + let phy_init_ratio_0 = reg_to_values.val_as_token("PHY Init Ratio 0", 0xF800_612C); + let phy_init_ratio_1 = reg_to_values.val_as_token("PHY Init Ratio 1", 0xF800_6130); + let phy_init_ratio_2 = reg_to_values.val_as_token("PHY Init Ratio 2", 0xF800_6134); + let phy_init_ratio_3 = reg_to_values.val_as_token("PHY Init Ratio 3", 0xF800_6138); + let phy_rd_dqs_config_0 = reg_to_values.val_as_token("PHY RD DQS Config 0", 0xF800_6140); + let phy_rd_dqs_config_1 = reg_to_values.val_as_token("PHY RD DQS Config 1", 0xF800_6144); + let phy_rd_dqs_config_2 = reg_to_values.val_as_token("PHY RD DQS Config 2", 0xF800_6148); + let phy_rd_dqs_config_3 = reg_to_values.val_as_token("PHY RD DQS Config 3", 0xF800_614C); + let phy_wr_dqs_config_0 = reg_to_values.val_as_token("PHY WR DQS Config 0", 0xF800_6154); + let phy_wr_dqs_config_1 = reg_to_values.val_as_token("PHY WR DQS Config 1", 0xF800_6158); + let phy_wr_dqs_config_2 = reg_to_values.val_as_token("PHY WR DQS Config 2", 0xF800_615C); + let phy_wr_dqs_config_3 = reg_to_values.val_as_token("PHY WR DQS Config 3", 0xF800_6160); + let phy_we_cfg_0 = reg_to_values.val_as_token("PHY WE Config 0", 0xF800_6168); + let phy_we_cfg_1 = reg_to_values.val_as_token("PHY WE Config 1", 0xF800_616C); + let phy_we_cfg_2 = reg_to_values.val_as_token("PHY WE Config 2", 0xF800_6170); + let phy_we_cfg_3 = reg_to_values.val_as_token("PHY WE Config 3", 0xF800_6174); + let phy_wr_data_slv_0 = reg_to_values.val_as_token("PHY WR Data Slv 0", 0xF800_617C); + let phy_wr_data_slv_1 = reg_to_values.val_as_token("PHY WR Data Slv 1", 0xF800_6180); + let phy_wr_data_slv_2 = reg_to_values.val_as_token("PHY WR Data Slv 2", 0xF800_6184); + let phy_wr_data_slv_3 = reg_to_values.val_as_token("PHY WR Data Slv 3", 0xF800_6188); + let reg64 = reg_to_values.val_as_token("Reg64", 0xF800_6190); + let reg65 = reg_to_values.val_as_token("Reg65", 0xF800_6194); + let page_mask = reg_to_values.val_as_token("Page Mask", 0xF800_6204); + let axi_priority_wr_port_0 = reg_to_values.val_as_token("AXI Priority WR Port 0", 0xF800_6208); + let axi_priority_wr_port_1 = reg_to_values.val_as_token("AXI Priority WR Port 1", 0xF800_620C); + let axi_priority_wr_port_2 = reg_to_values.val_as_token("AXI Priority WR Port 2", 0xF800_6210); + let axi_priority_wr_port_3 = reg_to_values.val_as_token("AXI Priority WR Port 3", 0xF800_6214); + let axi_priority_rd_port_0 = reg_to_values.val_as_token("AXI Priority RD Port 0", 0xF800_6218); + let axi_priority_rd_port_1 = reg_to_values.val_as_token("AXI Priority RD Port 1", 0xF800_621C); + let axi_priority_rd_port_2 = reg_to_values.val_as_token("AXI Priority RD Port 2", 0xF800_6220); + let axi_priority_rd_port_3 = reg_to_values.val_as_token("AXI Priority RD Port 3", 0xF800_6224); + let lpddr_ctrl_0 = reg_to_values.val_as_token("LPDDR CTRL 0", 0xF800_62A8); + let lpddr_ctrl_1 = reg_to_values.val_as_token("LPDDR CTRL 1", 0xF800_62AC); + let lpddr_ctrl_2 = reg_to_values.val_as_token("LPDDR CTRL 2", 0xF800_62B0); + let lpddr_ctrl_3 = reg_to_values.val_as_token("LPDDR CTRL 3", 0xF800_62B4); + + let generated = quote::quote! { + //!This file was auto-generated by the [zynq7000-ps7init-extract](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/tools/zynq7000-ps7init-extract) program. + //! + //!This configuration file contains static DDR configuration parameters extracted from the + //!AMD ps7init.tcl file + use zynq7000::ddrc::regs; + use zynq7000_hal::ddr::DdrcConfigSet; + + pub const DDRC_CONFIG_ZEDBOARD: DdrcConfigSet = DdrcConfigSet { + ctrl: regs::DdrcControl::new_with_raw_value(#ddrc), + two_rank: regs::TwoRankConfig::new_with_raw_value(#two_rank), + hpr: regs::LprHprQueueControl::new_with_raw_value(#hpr), + lpr: regs::LprHprQueueControl::new_with_raw_value(#lpr), + wr: regs::WriteQueueControl::new_with_raw_value(#wr), + dram_param_0: regs::DramParamReg0::new_with_raw_value(#dram_param_0), + dram_param_1: regs::DramParamReg1::new_with_raw_value(#dram_param_1), + dram_param_2: regs::DramParamReg2::new_with_raw_value(#dram_param_2), + dram_param_3: regs::DramParamReg3::new_with_raw_value(#dram_param_3), + dram_param_4: regs::DramParamReg4::new_with_raw_value(#dram_param_4), + dram_init_param: regs::DramInitParam::new_with_raw_value(#dram_init_param), + dram_emr: regs::DramEmr::new_with_raw_value(#dram_emr), + dram_emr_mr: regs::DramEmrMr::new_with_raw_value(#dram_emr_mr), + dram_burst8_rdwr: regs::DramBurst8ReadWrite::new_with_raw_value(#dram_burst8_rdwr), + disable_dq: regs::DisableDq::new_with_raw_value(#dram_disable_dq), + dram_addr_map_bank: regs::DramAddrMapBank::new_with_raw_value(#dram_addr_map_bank), + dram_addr_map_col: regs::DramAddrMapColumn::new_with_raw_value(#dram_addr_map_col), + dram_addr_map_row: regs::DramAddrMapRow::new_with_raw_value(#dram_addr_map_row), + dram_odt: regs::DramOdt::new_with_raw_value(#dram_odt), + phy_cmd_timeout_rddata_cpt: regs::PhyCmdTimeoutRdDataCpt::new_with_raw_value(#phy_cmd_timeout_rddata_cpt), + dll_calib: regs::DllCalib::new_with_raw_value(#dll_calib), + odt_delay_hold: regs::OdtDelayHold::new_with_raw_value(#odt_delay_hold), + ctrl_reg1: regs::CtrlReg1::new_with_raw_value(#ctrl_reg1), + ctrl_reg2: regs::CtrlReg2::new_with_raw_value(#ctrl_reg2), + ctrl_reg3: regs::CtrlReg3::new_with_raw_value(#ctrl_reg3), + ctrl_reg4: regs::CtrlReg4::new_with_raw_value(#ctrl_reg4), + ctrl_reg5: regs::CtrlReg5::new_with_raw_value(#ctrl_reg5), + ctrl_reg6: regs::CtrlReg6::new_with_raw_value(#ctrl_reg6), + che_t_zq: regs::CheTZq::new_with_raw_value(#che_t_zq), + che_t_zq_short_interval_reg: regs::CheTZqShortInterval::new_with_raw_value(#che_t_zq_short_interval_reg), + deep_powerdown: regs::DeepPowerdown::new_with_raw_value(#deep_powerdown), + reg_2c: regs::Reg2c::new_with_raw_value(#reg_2c), + reg_2d: regs::Reg2d::new_with_raw_value(#reg_2d), + dfi_timing: regs::DfiTiming::new_with_raw_value(#dfi_timing), + che_ecc_ctrl: regs::CheEccControl::new_with_raw_value(#che_ecc_ctrl), + ecc_scrub: regs::EccScrub::new_with_raw_value(#ecc_scrub), + phy_receiver_enable: regs::PhyReceiverEnable::new_with_raw_value(#phy_receiver_enable), + phy_config: [ + regs::PhyConfig::new_with_raw_value(#phy_config_0), + regs::PhyConfig::new_with_raw_value(#phy_config_1), + regs::PhyConfig::new_with_raw_value(#phy_config_2), + regs::PhyConfig::new_with_raw_value(#phy_config_3), + ], + phy_init_ratio: [ + regs::PhyInitRatio::new_with_raw_value(#phy_init_ratio_0), + regs::PhyInitRatio::new_with_raw_value(#phy_init_ratio_1), + regs::PhyInitRatio::new_with_raw_value(#phy_init_ratio_2), + regs::PhyInitRatio::new_with_raw_value(#phy_init_ratio_3), + ], + phy_rd_dqs_config: [ + regs::PhyDqsConfig::new_with_raw_value(#phy_rd_dqs_config_0), + regs::PhyDqsConfig::new_with_raw_value(#phy_rd_dqs_config_1), + regs::PhyDqsConfig::new_with_raw_value(#phy_rd_dqs_config_2), + regs::PhyDqsConfig::new_with_raw_value(#phy_rd_dqs_config_3), + ], + phy_wr_dqs_config: [ + regs::PhyDqsConfig::new_with_raw_value(#phy_wr_dqs_config_0), + regs::PhyDqsConfig::new_with_raw_value(#phy_wr_dqs_config_1), + regs::PhyDqsConfig::new_with_raw_value(#phy_wr_dqs_config_2), + regs::PhyDqsConfig::new_with_raw_value(#phy_wr_dqs_config_3), + ], + phy_we_cfg: [ + regs::PhyWriteEnableConfig::new_with_raw_value(#phy_we_cfg_0), + regs::PhyWriteEnableConfig::new_with_raw_value(#phy_we_cfg_1), + regs::PhyWriteEnableConfig::new_with_raw_value(#phy_we_cfg_2), + regs::PhyWriteEnableConfig::new_with_raw_value(#phy_we_cfg_3), + ], + phy_wr_data_slv: [ + regs::PhyWriteDataSlaveConfig::new_with_raw_value(#phy_wr_data_slv_0), + regs::PhyWriteDataSlaveConfig::new_with_raw_value(#phy_wr_data_slv_1), + regs::PhyWriteDataSlaveConfig::new_with_raw_value(#phy_wr_data_slv_2), + regs::PhyWriteDataSlaveConfig::new_with_raw_value(#phy_wr_data_slv_3), + ], + reg64: regs::Reg64::new_with_raw_value(#reg64), + reg65: regs::Reg65::new_with_raw_value(#reg65), + page_mask: #page_mask, + axi_priority_wr_port: [ + regs::AxiPriorityWritePort::new_with_raw_value(#axi_priority_wr_port_0), + regs::AxiPriorityWritePort::new_with_raw_value(#axi_priority_wr_port_1), + regs::AxiPriorityWritePort::new_with_raw_value(#axi_priority_wr_port_2), + regs::AxiPriorityWritePort::new_with_raw_value(#axi_priority_wr_port_3), + ], + axi_priority_rd_port: [ + regs::AxiPriorityReadPort::new_with_raw_value(#axi_priority_rd_port_0), + regs::AxiPriorityReadPort::new_with_raw_value(#axi_priority_rd_port_1), + regs::AxiPriorityReadPort::new_with_raw_value(#axi_priority_rd_port_2), + regs::AxiPriorityReadPort::new_with_raw_value(#axi_priority_rd_port_3), + ], + lpddr_ctrl_0: regs::LpddrControl0::new_with_raw_value(#lpddr_ctrl_0), + lpddr_ctrl_1: regs::LpddrControl1::new_with_raw_value(#lpddr_ctrl_1), + lpddr_ctrl_2: regs::LpddrControl2::new_with_raw_value(#lpddr_ctrl_2), + lpddr_ctrl_3: regs::LpddrControl3::new_with_raw_value(#lpddr_ctrl_3), + }; + }; + + std::fs::write(file_name, generated.to_string())?; + Ok(()) +} + +fn generate_ddriob_config( + reg_to_values: &RegisterToValueMap, + file_name: &str, +) -> std::io::Result<()> { + // Format as hex strings + let addr0 = reg_to_values.val_as_token("DDRIOB Addr 0", 0xF800_0B40); + let addr1 = reg_to_values.val_as_token("DDRIOB Addr 1", 0xF800_0B44); + let data0 = reg_to_values.val_as_token("DDRIOB Data 0", 0xF800_0B48); + let data1 = reg_to_values.val_as_token("DDRIOB Data 1", 0xF800_0B4C); + let diff0 = reg_to_values.val_as_token("DDRIOB Diff 0", 0xF800_0B50); + let diff1 = reg_to_values.val_as_token("DDRIOB Diff 1", 0xF800_0B54); + let clock = reg_to_values.val_as_token("DDRIOB Clock", 0xF800_0B58); + let generated = quote::quote! { + //!This file was auto-generated by the [zynq7000-ps7init-extract](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/tools/zynq7000-ps7init-extract) program. + //! + //!This configuration file contains static DDRIOB configuration parameters extracted from the + //!AMD ps7init.tcl file + use zynq7000::ddrc::regs; + use zynq7000_hal::ddr::DdriobConfigSet; + + pub const DDRIOB_CONFIG_SET_ZEDBOARD: DdriobConfigSet = DdriobConfigSet { + addr0: regs::DdriobConfig::new_with_raw_value(#addr0), + addr1: regs::DdriobConfig::new_with_raw_value(#addr1), + data0: regs::DdriobConfig::new_with_raw_value(#data0), + data1: regs::DdriobConfig::new_with_raw_value(#data1), + diff0: regs::DdriobConfig::new_with_raw_value(#diff0), + diff1: regs::DdriobConfig::new_with_raw_value(#diff1), + clock: regs::DdriobConfig::new_with_raw_value(#clock), + }; + }; + + std::fs::write(file_name, generated.to_string())?; + Ok(()) +} diff --git a/zedboard-fpga-design/README.md b/zedboard-fpga-design/README.md index 2e4d5f6..2533876 100644 --- a/zedboard-fpga-design/README.md +++ b/zedboard-fpga-design/README.md @@ -38,3 +38,25 @@ vivado zedboard-rust.xpr You can perform all the steps specified in the Vivado GUI as well using `Execute TCL script` and `Load Project`. + +# Generating the SDT folder from a hardware description + +You can generate a hardware description by building the block design by using `Generate Bitstream` +inside the Vivado GUI and then exporting the hardware description via +`File -> Export -> Export Hardware`. This allows to generate a `*.xsa` file which describes the +hardware. + +After that, you can generate the SDT output folder which contains various useful files like +the `ps7_init.tcl` script. The provided ` sdtgen.tcl` and `stdgen.py` script simplify this process. + +For example, the following command generates the SDT output folder inside a folder +named `sdt_out` for a hardware description files `zedboard-rust/zedboard-rust.xsa`, +assuming that the Vitis tool suite is installed at `/tools/Xilinx/Vitis/2024.1`: + +```sh +export AMD_TOOLS="/tools/Xilinx/Vitis/2024.1" +./sdtgen.py -x ./zedboard-rust/zedboard-rust.xsa +``` + +Run `stdgen.py -h` for more information and configuration options. The `stdgen.py` is a helper +script which will invoke `sdtgen.tcl` to generate the SDT. 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/zynq-mmu/Cargo.toml b/zynq-mmu/Cargo.toml deleted file mode 100644 index d37cdd1..0000000 --- a/zynq-mmu/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "zynq-mmu" -description = "Zynq MMU structures" -version = "0.1.0" -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" } - -[features] -tools = [] diff --git a/zynq/.cargo/config.toml b/zynq/.cargo/config.toml new file mode 100644 index 0000000..2a4d3aa --- /dev/null +++ b/zynq/.cargo/config.toml @@ -0,0 +1,20 @@ +[target.armv7a-none-eabihf] +runner = "just run" + +rustflags = [ + "-Ctarget-cpu=cortex-a9", + "-Ctarget-feature=+vfp3", + "-Ctarget-feature=+neon", + "-Clink-arg=-Tlink.x", + # If this is not enabled, debugging / stepping can become problematic. + "-Cforce-frame-pointers=yes", + # Can be useful for debugging. + # "-Clink-args=-Map=app.map" +] + +# Tier 3 target, so no pre-compiled artifacts included. +[unstable] +build-std = ["core", "alloc"] + +[build] +target = "armv7a-none-eabihf" diff --git a/zynq/.gitignore b/zynq/.gitignore new file mode 100644 index 0000000..03314f7 --- /dev/null +++ b/zynq/.gitignore @@ -0,0 +1 @@ +Cargo.lock diff --git a/zynq/Cargo.toml b/zynq/Cargo.toml new file mode 100644 index 0000000..9101e86 --- /dev/null +++ b/zynq/Cargo.toml @@ -0,0 +1,21 @@ +[workspace] +resolver = "3" +members = [ + "zynq7000-rt", + "zynq7000-mmu", + "zynq7000", + "zynq7000-hal", + "zynq7000-embassy", + + "examples/simple", + "examples/embassy", + "examples/zedboard", + + "zedboard-bsp", + "zedboard-qspi-flasher", +] + +exclude = [ + # Exclude, can not be built with debug optimization level, too large. + "zedboard-fsbl", +] diff --git a/examples/embassy/Cargo.toml b/zynq/examples/embassy/Cargo.toml similarity index 61% rename from examples/embassy/Cargo.toml rename to zynq/examples/embassy/Cargo.toml index e58dd47..f32f638 100644 --- a/examples/embassy/Cargo.toml +++ b/zynq/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 = { version = "0.3", features = ["critical-section-single-core"] } zynq7000-rt = { path = "../../zynq7000-rt" } zynq7000 = { path = "../../zynq7000" } zynq7000-hal = { path = "../../zynq7000-hal" } @@ -19,20 +19,36 @@ 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" -embedded-io = "0.6" +heapless = "0.9" +embedded-io = "0.7" embedded-hal = "1" fugit = "0.3" log = "0.4" -embassy-executor = { git = "https://github.com/us-irs/embassy.git", branch = "cortex-ar-update", features = [ +embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", features = [ "arch-cortex-ar", "executor-thread", ]} # TODO: Remove generic-queue-16 feature as soon as upstream executor is used again. embassy-time = { version = "0.5", features = ["tick-hz-1_000_000", "generic-queue-16"] } -[profile.release] +# cargo build/run +[profile.dev] +# default is opt-level = '0', but that makes very +# verbose machine code +opt-level = 's' +# trade compile speed for slightly better optimisations codegen-units = 1 -debug = true -lto = true + +# cargo build/run --release +[profile.release] +# default is opt-level = '3', but that makes quite +# verbose machine code +opt-level = 's' +# trade compile speed for slightly better optimisations +codegen-units = 1 +# Use Link Time Optimisations to further inline things across +# crates +lto = 'fat' +# Leave the debug symbols in (default is no debug info) +debug = 2 diff --git a/zynq/examples/embassy/build.rs b/zynq/examples/embassy/build.rs new file mode 100644 index 0000000..d534cc3 --- /dev/null +++ b/zynq/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/zynq/examples/embassy/memory.x similarity index 94% rename from memory.x rename to zynq/examples/embassy/memory.x index 134a689..11faa59 100644 --- a/memory.x +++ b/zynq/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/zynq/examples/embassy/src/bin/dht22-open-drain-pins.rs similarity index 90% rename from examples/embassy/src/bin/dht22-open-drain-pins.rs rename to zynq/examples/embassy/src/bin/dht22-open-drain-pins.rs index 8f4b023..b8f4ed5 100644 --- a/examples/embassy/src/bin/dht22-open-drain-pins.rs +++ b/zynq/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)); @@ -161,8 +161,8 @@ async fn main(_spawner: Spawner) -> ! { } } -#[unsafe(no_mangle)] -pub extern "C" fn _irq_handler() { +#[zynq7000_rt::irq] +fn irq_handler() { let mut gic_helper = GicInterruptHelper::new(); let irq_info = gic_helper.acknowledge_interrupt(); match irq_info.interrupt() { @@ -181,22 +181,22 @@ pub extern "C" fn _irq_handler() { 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/embassy/src/main.rs b/zynq/examples/embassy/src/bin/embassy-hello.rs similarity index 54% rename from examples/embassy/src/main.rs rename to zynq/examples/embassy/src/bin/embassy-hello.rs index c943ca5..ae35eb1 100644 --- a/examples/embassy/src/main.rs +++ b/zynq/examples/embassy/src/bin/embassy-hello.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::{BootMode, InteruptConfig, clocks, gic, gpio, gtc, time::Hertz, uart}; -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] +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/embassy/src/bin/logger-non-blocking.rs b/zynq/examples/embassy/src/bin/logger-non-blocking.rs similarity index 85% rename from examples/embassy/src/bin/logger-non-blocking.rs rename to zynq/examples/embassy/src/bin/logger-non-blocking.rs index bbdff1b..a06cd50 100644 --- a/examples/embassy/src/bin/logger-non-blocking.rs +++ b/zynq/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,11 +75,11 @@ 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); - spawner.spawn(led_task(led)).unwrap(); + spawner.spawn(led_task(led).unwrap()); let mut log_buf: [u8; 2048] = [0; 2048]; let frame_queue = zynq7000_hal::log::rb::get_frame_queue(); loop { @@ -123,22 +123,22 @@ pub extern "C" fn _irq_handler() { 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/embassy/src/bin/pwm.rs b/zynq/examples/embassy/src/bin/pwm.rs similarity index 86% rename from examples/embassy/src/bin/pwm.rs rename to zynq/examples/embassy/src/bin/pwm.rs index 62a80f1..5332c00 100644 --- a/examples/embassy/src/bin/pwm.rs +++ b/zynq/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)); @@ -111,8 +111,8 @@ async fn main(_spawner: Spawner) -> ! { } } -#[unsafe(no_mangle)] -pub extern "C" fn _irq_handler() { +#[zynq7000_rt::irq] +fn irq_handler() { let mut gic_helper = GicInterruptHelper::new(); let irq_info = gic_helper.acknowledge_interrupt(); match irq_info.interrupt() { @@ -131,22 +131,22 @@ pub extern "C" fn _irq_handler() { 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/zynq/examples/embassy/src/main.rs b/zynq/examples/embassy/src/main.rs new file mode 100644 index 0000000..8840657 --- /dev/null +++ b/zynq/examples/embassy/src/main.rs @@ -0,0 +1,95 @@ +#![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 log::error; +use zynq7000_hal::{InteruptConfig, clocks, gic, gpio, gtc, time::Hertz}; + +use zynq7000_rt as _; + +// Define the clock frequency as a constant +const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300); + +/// 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(); +} + +#[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(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 = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks()); + zynq7000_embassy::init(clocks.arm_clocks(), gtc); + let mio_pins = gpio::mio::Pins::new(periphs.gpio); + let mut ticker = Ticker::every(Duration::from_millis(1000)); + let mut led = gpio::Output::new_for_mio(mio_pins.mio7, gpio::PinState::Low); + loop { + led.toggle().unwrap(); + ticker.next().await; + } +} + +#[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() { + gic::Interrupt::Sgi(_) => (), + gic::Interrupt::Ppi(ppi_interrupt) => { + if ppi_interrupt == zynq7000_hal::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/simple/Cargo.toml b/zynq/examples/simple/Cargo.toml similarity index 72% rename from examples/simple/Cargo.toml rename to zynq/examples/simple/Cargo.toml index 64354b6..8edfe4c 100644 --- a/examples/simple/Cargo.toml +++ b/zynq/examples/simple/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "blinky" +name = "simple" version = "0.1.0" authors = ["Robin Mueller "] edition = "2024" @@ -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/zynq/examples/simple/build.rs b/zynq/examples/simple/build.rs new file mode 100644 index 0000000..d534cc3 --- /dev/null +++ b/zynq/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/zynq/examples/simple/memory.x b/zynq/examples/simple/memory.x new file mode 100644 index 0000000..11faa59 --- /dev/null +++ b/zynq/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/main.rs b/zynq/examples/simple/src/bin/blinky.rs similarity index 62% rename from examples/simple/src/main.rs rename to zynq/examples/simple/src/bin/blinky.rs index dc86303..27b89ed 100644 --- a/examples/simple/src/main.rs +++ b/zynq/examples/simple/src/bin/blinky.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,38 +56,39 @@ 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); } } } } #[zynq7000_rt::irq] -pub fn irq_handler() {} +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/simple/src/bin/gtc-ticks.rs b/zynq/examples/simple/src/bin/gtc-ticks.rs similarity index 84% rename from examples/simple/src/bin/gtc-ticks.rs rename to zynq/examples/simple/src/bin/gtc-ticks.rs index 20fb40f..20b8a45 100644 --- a/examples/simple/src/bin/gtc-ticks.rs +++ b/zynq/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(); @@ -92,8 +92,8 @@ pub fn main() -> ! { } } -#[unsafe(no_mangle)] -pub extern "C" fn _irq_handler() { +#[zynq7000_rt::irq] +fn irq_handler() { let mut gic_helper = GicInterruptHelper::new(); let irq_info = gic_helper.acknowledge_interrupt(); match irq_info.interrupt() { @@ -110,22 +110,22 @@ pub extern "C" fn _irq_handler() { 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/src/bin/logger.rs b/zynq/examples/simple/src/bin/logger.rs similarity index 85% rename from examples/simple/src/bin/logger.rs rename to zynq/examples/simple/src/bin/logger.rs index 2a359dc..13c54f1 100644 --- a/examples/simple/src/bin/logger.rs +++ b/zynq/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); @@ -94,8 +94,8 @@ pub fn main() -> ! { } } -#[unsafe(no_mangle)] -pub extern "C" fn _irq_handler() { +#[zynq7000_rt::irq] +fn irq_handler() { let mut gic_helper = GicInterruptHelper::new(); let irq_info = gic_helper.acknowledge_interrupt(); match irq_info.interrupt() { @@ -113,22 +113,22 @@ pub extern "C" fn _irq_handler() { 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/zynq/examples/simple/src/main.rs b/zynq/examples/simple/src/main.rs new file mode 100644 index 0000000..3988dc0 --- /dev/null +++ b/zynq/examples/simple/src/main.rs @@ -0,0 +1,55 @@ +//! Simple blinky app, showing a PAC variant and a HAL variant. +#![no_std] +#![no_main] + +use core::panic::PanicInfo; +use cortex_ar::asm::nop; +use zynq7000_rt as _; + +/// 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() -> ! { + loop { + cortex_ar::asm::nop(); + } +} + +#[zynq7000_rt::irq] +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/examples/zedboard/Cargo.toml b/zynq/examples/zedboard/Cargo.toml similarity index 86% rename from examples/zedboard/Cargo.toml rename to zynq/examples/zedboard/Cargo.toml index 5e8a56b..34cd460 100644 --- a/examples/zedboard/Cargo.toml +++ b/zynq/examples/zedboard/Cargo.toml @@ -11,15 +11,17 @@ 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" } 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" +embedded-io = "0.7" +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/zynq/examples/zedboard/build.rs b/zynq/examples/zedboard/build.rs new file mode 100644 index 0000000..d534cc3 --- /dev/null +++ b/zynq/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/zynq/examples/zedboard/memory.x b/zynq/examples/zedboard/memory.x new file mode 100644 index 0000000..11faa59 --- /dev/null +++ b/zynq/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/zynq/examples/zedboard/src/bin/ethernet.rs similarity index 94% rename from examples/zedboard/src/bin/ethernet.rs rename to zynq/examples/zedboard/src/bin/ethernet.rs index 84da268..1865e07 100644 --- a/examples/zedboard/src/bin/ethernet.rs +++ b/zynq/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; @@ -467,8 +465,8 @@ async fn main(spawner: Spawner) -> ! { } } -#[unsafe(no_mangle)] -pub extern "C" fn _irq_handler() { +#[zynq7000_rt::irq] +fn irq_handler() { let mut gic_helper = GicInterruptHelper::new(); let irq_info = gic_helper.acknowledge_interrupt(); match irq_info.interrupt() { @@ -499,22 +497,22 @@ pub extern "C" fn _irq_handler() { 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/zedboard/src/bin/l3gd20h-i2c-mio.rs b/zynq/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs similarity index 95% rename from examples/zedboard/src/bin/l3gd20h-i2c-mio.rs rename to zynq/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs index 1b665d1..cfb3e6b 100644 --- a/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs +++ b/zynq/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/zynq/examples/zedboard/src/bin/l3gd20h-spi-mio.rs similarity index 96% rename from examples/zedboard/src/bin/l3gd20h-spi-mio.rs rename to zynq/examples/zedboard/src/bin/l3gd20h-spi-mio.rs index a0e4ceb..14f6bd6 100644 --- a/examples/zedboard/src/bin/l3gd20h-spi-mio.rs +++ b/zynq/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/zynq/examples/zedboard/src/bin/qspi.rs b/zynq/examples/zedboard/src/bin/qspi.rs new file mode 100644 index 0000000..89fd2e8 --- /dev/null +++ b/zynq/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::{BootMode, clocks, gic, gpio, gtc, prelude::*, qspi, uart}; + +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/zynq/examples/zedboard/src/bin/uart-blocking.rs similarity index 91% rename from examples/zedboard/src/bin/uart-blocking.rs rename to zynq/examples/zedboard/src/bin/uart-blocking.rs index 5d98926..4e84027 100644 --- a/examples/zedboard/src/bin/uart-blocking.rs +++ b/zynq/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)); @@ -218,8 +218,8 @@ async fn main(_spawner: Spawner) -> ! { } } -#[unsafe(no_mangle)] -pub extern "C" fn _irq_handler() { +#[zynq7000_rt::irq] +fn irq_handler() { let mut gic_helper = GicInterruptHelper::new(); let irq_info = gic_helper.acknowledge_interrupt(); match irq_info.interrupt() { @@ -238,22 +238,22 @@ pub extern "C" fn _irq_handler() { 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/zedboard/src/bin/uart-non-blocking.rs b/zynq/examples/zedboard/src/bin/uart-non-blocking.rs similarity index 96% rename from examples/zedboard/src/bin/uart-non-blocking.rs rename to zynq/examples/zedboard/src/bin/uart-non-blocking.rs index 5dc3e6c..9c5dd9d 100644 --- a/examples/zedboard/src/bin/uart-non-blocking.rs +++ b/zynq/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); @@ -432,8 +432,8 @@ async fn uart_16550_task(uart_tx: axi_uart16550::Tx) { } } -#[unsafe(no_mangle)] -pub extern "C" fn _irq_handler() { +#[zynq7000_rt::irq] +fn irq_handler() { let mut gic_helper = GicInterruptHelper::new(); let irq_info = gic_helper.acknowledge_interrupt(); @@ -465,7 +465,7 @@ pub extern "C" fn _irq_handler() { gic_helper.end_of_interrupt(irq_info); } -pub fn on_interrupt_axi_uartlite() { +fn on_interrupt_axi_uartlite() { let mut buf = [0; axi_uartlite::FIFO_DEPTH]; let mut rx = unsafe { axi_uartlite::Rx::steal(AXI_UARTLITE_BASE_ADDR as usize) }; // Handle RX first: Empty the FIFO content into local buffer. @@ -485,7 +485,7 @@ pub fn on_interrupt_axi_uartlite() { } } -pub fn on_interrupt_axi_16550() { +fn on_interrupt_axi_16550() { let mut buf = [0; axi_uart16550::FIFO_DEPTH]; let mut read_bytes = 0; let mut rx = unsafe { axi_uart16550::Rx::steal(AXI_UAR16550_BASE_ADDR as usize) }; @@ -545,22 +545,22 @@ fn on_interrupt_uart_0() { } } -#[unsafe(no_mangle)] -pub extern "C" fn _abort_handler() { +#[zynq7000_rt::exception(DataAbort)] +fn data_abort_handler(_faulting_addr: usize) -> ! { loop { nop(); } } -#[unsafe(no_mangle)] -pub extern "C" fn _undefined_handler() { +#[zynq7000_rt::exception(Undefined)] +fn undefined_handler(_faulting_addr: usize) -> ! { loop { nop(); } } -#[unsafe(no_mangle)] -pub extern "C" fn _prefetch_handler() { +#[zynq7000_rt::exception(PrefetchAbort)] +fn prefetch_handler(_faulting_addr: usize) -> ! { loop { nop(); } diff --git a/examples/zedboard/src/lib.rs b/zynq/examples/zedboard/src/lib.rs similarity index 88% rename from examples/zedboard/src/lib.rs rename to zynq/examples/zedboard/src/lib.rs index c8c587e..22324d4 100644 --- a/examples/zedboard/src/lib.rs +++ b/zynq/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/zynq/examples/zedboard/src/main.rs b/zynq/examples/zedboard/src/main.rs new file mode 100644 index 0000000..59c4962 --- /dev/null +++ b/zynq/examples/zedboard/src/main.rs @@ -0,0 +1,140 @@ +#![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 zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, uart}; + +use zynq7000_rt as _; + +const INIT_STRING: &str = "-- Zynq 7000 Zedboard GPIO blinky example --\n\r"; + +/// 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(); +} + +#[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 mut 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 mut ticker = Ticker::every(Duration::from_millis(200)); + + 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(); + + // Create a wave pattern for emio_leds + for led in emio_leds.iter_mut() { + led.toggle().unwrap(); + ticker.next().await; // Wait for the next ticker for each toggle + } + + 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/gdb.gdb b/zynq/gdb.gdb similarity index 100% rename from gdb.gdb rename to zynq/gdb.gdb diff --git a/rust-toolchain.toml b/zynq/rust-toolchain.toml similarity index 60% rename from rust-toolchain.toml rename to zynq/rust-toolchain.toml index 0e3c7a9..5d56faf 100644 --- a/rust-toolchain.toml +++ b/zynq/rust-toolchain.toml @@ -1,3 +1,2 @@ [toolchain] -# channel = "stable" channel = "nightly" diff --git a/zynq/zedboard-bsp/Cargo.toml b/zynq/zedboard-bsp/Cargo.toml new file mode 100644 index 0000000..249959f --- /dev/null +++ b/zynq/zedboard-bsp/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "zedboard-bsp" +version = "0.1.0" +edition = "2024" + +[dependencies] +zynq7000 = { path = "../zynq7000" } +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/zynq/zedboard-bsp/src/ddrc_config_autogen.rs b/zynq/zedboard-bsp/src/ddrc_config_autogen.rs new file mode 100644 index 0000000..22670cb --- /dev/null +++ b/zynq/zedboard-bsp/src/ddrc_config_autogen.rs @@ -0,0 +1,100 @@ +#![doc = r"This file was auto-generated by the [zynq7000-ps7init-extract](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/tools/zynq7000-ps7init-extract) program."] +#![doc = r""] +#![doc = r"This configuration file contains static DDR configuration parameters extracted from the"] +#![doc = r"AMD ps7init.tcl file"] +use zynq7000::ddrc::regs; +use zynq7000_hal::ddr::DdrcConfigSet; +pub const DDRC_CONFIG_ZEDBOARD: DdrcConfigSet = DdrcConfigSet { + ctrl: regs::DdrcControl::new_with_raw_value(0x00000080), + two_rank: regs::TwoRankConfig::new_with_raw_value(0x00001082), + hpr: regs::LprHprQueueControl::new_with_raw_value(0x03c0780f), + lpr: regs::LprHprQueueControl::new_with_raw_value(0x02001001), + wr: regs::WriteQueueControl::new_with_raw_value(0x00014001), + dram_param_0: regs::DramParamReg0::new_with_raw_value(0x0004159b), + dram_param_1: regs::DramParamReg1::new_with_raw_value(0x44e458d3), + dram_param_2: regs::DramParamReg2::new_with_raw_value(0x7282bce5), + dram_param_3: regs::DramParamReg3::new_with_raw_value(0x270872d0), + dram_param_4: regs::DramParamReg4::new_with_raw_value(0x00000000), + dram_init_param: regs::DramInitParam::new_with_raw_value(0x00002007), + dram_emr: regs::DramEmr::new_with_raw_value(0x00000008), + dram_emr_mr: regs::DramEmrMr::new_with_raw_value(0x00040b30), + dram_burst8_rdwr: regs::DramBurst8ReadWrite::new_with_raw_value(0x000116d4), + disable_dq: regs::DisableDq::new_with_raw_value(0x00000000), + dram_addr_map_bank: regs::DramAddrMapBank::new_with_raw_value(0x00000777), + dram_addr_map_col: regs::DramAddrMapColumn::new_with_raw_value(0xfff00000), + dram_addr_map_row: regs::DramAddrMapRow::new_with_raw_value(0x0ff66666), + dram_odt: regs::DramOdt::new_with_raw_value(0x0003c008), + phy_cmd_timeout_rddata_cpt: regs::PhyCmdTimeoutRdDataCpt::new_with_raw_value(0x77010800), + dll_calib: regs::DllCalib::new_with_raw_value(0x00000000), + odt_delay_hold: regs::OdtDelayHold::new_with_raw_value(0x00005003), + ctrl_reg1: regs::CtrlReg1::new_with_raw_value(0x0000003e), + ctrl_reg2: regs::CtrlReg2::new_with_raw_value(0x00020000), + ctrl_reg3: regs::CtrlReg3::new_with_raw_value(0x00284141), + ctrl_reg4: regs::CtrlReg4::new_with_raw_value(0x00001610), + ctrl_reg5: regs::CtrlReg5::new_with_raw_value(0x00466111), + ctrl_reg6: regs::CtrlReg6::new_with_raw_value(0x00032222), + che_t_zq: regs::CheTZq::new_with_raw_value(0x10200802), + che_t_zq_short_interval_reg: regs::CheTZqShortInterval::new_with_raw_value(0x10200802), + deep_powerdown: regs::DeepPowerdown::new_with_raw_value(0x000001fe), + reg_2c: regs::Reg2c::new_with_raw_value(0x1cffffff), + reg_2d: regs::Reg2d::new_with_raw_value(0x00000200), + dfi_timing: regs::DfiTiming::new_with_raw_value(0x00200066), + che_ecc_ctrl: regs::CheEccControl::new_with_raw_value(0x00000000), + ecc_scrub: regs::EccScrub::new_with_raw_value(0x00000008), + phy_receiver_enable: regs::PhyReceiverEnable::new_with_raw_value(0x00000000), + phy_config: [ + regs::PhyConfig::new_with_raw_value(0x40000001), + regs::PhyConfig::new_with_raw_value(0x40000001), + regs::PhyConfig::new_with_raw_value(0x40000001), + regs::PhyConfig::new_with_raw_value(0x40000001), + ], + phy_init_ratio: [ + regs::PhyInitRatio::new_with_raw_value(0x00033c03), + regs::PhyInitRatio::new_with_raw_value(0x00034003), + regs::PhyInitRatio::new_with_raw_value(0x0002f400), + regs::PhyInitRatio::new_with_raw_value(0x00030400), + ], + phy_rd_dqs_config: [ + regs::PhyDqsConfig::new_with_raw_value(0x00000035), + regs::PhyDqsConfig::new_with_raw_value(0x00000035), + regs::PhyDqsConfig::new_with_raw_value(0x00000035), + regs::PhyDqsConfig::new_with_raw_value(0x00000035), + ], + phy_wr_dqs_config: [ + regs::PhyDqsConfig::new_with_raw_value(0x00000083), + regs::PhyDqsConfig::new_with_raw_value(0x00000083), + regs::PhyDqsConfig::new_with_raw_value(0x0000007f), + regs::PhyDqsConfig::new_with_raw_value(0x00000078), + ], + phy_we_cfg: [ + regs::PhyWriteEnableConfig::new_with_raw_value(0x00000124), + regs::PhyWriteEnableConfig::new_with_raw_value(0x00000125), + regs::PhyWriteEnableConfig::new_with_raw_value(0x00000112), + regs::PhyWriteEnableConfig::new_with_raw_value(0x00000116), + ], + phy_wr_data_slv: [ + regs::PhyWriteDataSlaveConfig::new_with_raw_value(0x000000c3), + regs::PhyWriteDataSlaveConfig::new_with_raw_value(0x000000c3), + regs::PhyWriteDataSlaveConfig::new_with_raw_value(0x000000bf), + regs::PhyWriteDataSlaveConfig::new_with_raw_value(0x000000b8), + ], + reg64: regs::Reg64::new_with_raw_value(0x00040080), + reg65: regs::Reg65::new_with_raw_value(0x0001fc82), + page_mask: 0x00000000, + axi_priority_wr_port: [ + regs::AxiPriorityWritePort::new_with_raw_value(0x000003ff), + regs::AxiPriorityWritePort::new_with_raw_value(0x000003ff), + regs::AxiPriorityWritePort::new_with_raw_value(0x000003ff), + regs::AxiPriorityWritePort::new_with_raw_value(0x000003ff), + ], + axi_priority_rd_port: [ + regs::AxiPriorityReadPort::new_with_raw_value(0x000003ff), + regs::AxiPriorityReadPort::new_with_raw_value(0x000003ff), + regs::AxiPriorityReadPort::new_with_raw_value(0x000003ff), + regs::AxiPriorityReadPort::new_with_raw_value(0x000003ff), + ], + lpddr_ctrl_0: regs::LpddrControl0::new_with_raw_value(0x00000000), + lpddr_ctrl_1: regs::LpddrControl1::new_with_raw_value(0x00000000), + lpddr_ctrl_2: regs::LpddrControl2::new_with_raw_value(0x00005125), + lpddr_ctrl_3: regs::LpddrControl3::new_with_raw_value(0x000012a8), +}; diff --git a/zynq/zedboard-bsp/src/ddriob_config_autogen.rs b/zynq/zedboard-bsp/src/ddriob_config_autogen.rs new file mode 100644 index 0000000..b1aebdd --- /dev/null +++ b/zynq/zedboard-bsp/src/ddriob_config_autogen.rs @@ -0,0 +1,15 @@ +#![doc = r"This file was auto-generated by the [zynq7000-ps7init-extract](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/tools/zynq7000-ps7init-extract) program."] +#![doc = r""] +#![doc = r"This configuration file contains static DDRIOB configuration parameters extracted from the"] +#![doc = r"AMD ps7init.tcl file"] +use zynq7000::ddrc::regs; +use zynq7000_hal::ddr::DdriobConfigSet; +pub const DDRIOB_CONFIG_SET_ZEDBOARD: DdriobConfigSet = DdriobConfigSet { + addr0: regs::DdriobConfig::new_with_raw_value(0x00000600), + addr1: regs::DdriobConfig::new_with_raw_value(0x00000600), + data0: regs::DdriobConfig::new_with_raw_value(0x00000672), + data1: regs::DdriobConfig::new_with_raw_value(0x00000672), + diff0: regs::DdriobConfig::new_with_raw_value(0x00000674), + diff1: regs::DdriobConfig::new_with_raw_value(0x00000674), + clock: regs::DdriobConfig::new_with_raw_value(0x00000600), +}; diff --git a/zynq/zedboard-bsp/src/lib.rs b/zynq/zedboard-bsp/src/lib.rs new file mode 100644 index 0000000..1b4cd7d --- /dev/null +++ b/zynq/zedboard-bsp/src/lib.rs @@ -0,0 +1,6 @@ +#![no_std] + +pub mod ddrc_config_autogen; +pub mod ddriob_config_autogen; +pub mod phy_marvell; +pub mod qspi_spansion; diff --git a/examples/zedboard/src/phy_marvell.rs b/zynq/zedboard-bsp/src/phy_marvell.rs similarity index 100% rename from examples/zedboard/src/phy_marvell.rs rename to zynq/zedboard-bsp/src/phy_marvell.rs diff --git a/zynq/zedboard-bsp/src/qspi_spansion.rs b/zynq/zedboard-bsp/src/qspi_spansion.rs new file mode 100644 index 0000000..9eafd3b --- /dev/null +++ b/zynq/zedboard-bsp/src/qspi_spansion.rs @@ -0,0 +1,630 @@ +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, +} + +pub const PAGE_SIZE: usize = 256; + +#[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), + #[error("data is larger than page size {PAGE_SIZE}")] + DataLargerThanPage, +} + +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. + /// + /// 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(); + 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; + let fifo_writes = data.len().div_ceil(4); + // Fill the FIFO until it is full. + 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() + .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; + } + }; + + while current_byte_index < data.len() { + // Immediately fill the FIFO again with the remaining 8 bytes. + wait_for_tx_slot(&mut transfer); + + 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; + } + + 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); + } + } + 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 = 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); + 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/zynq/zedboard-fsbl/Cargo.toml b/zynq/zedboard-fsbl/Cargo.toml new file mode 100644 index 0000000..ef65825 --- /dev/null +++ b/zynq/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 Zedboard" +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.3", features = ["critical-section-single-core"] } +zynq7000-rt = { path = "../zynq7000-rt" } +zynq7000 = { path = "../zynq7000" } +zynq7000-hal = { path = "../zynq7000-hal" } +zynq7000-boot-image = { path = "../../zynq7000-boot-image" } +zedboard-bsp = { path = "../zedboard-bsp" } +embedded-io = "0.7" +embedded-hal = "1" +fugit = "0.3" +log = "0.4" +arbitrary-int = "2" diff --git a/zynq/zedboard-fsbl/build.rs b/zynq/zedboard-fsbl/build.rs new file mode 100644 index 0000000..d534cc3 --- /dev/null +++ b/zynq/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/zynq/zedboard-fsbl/memory.x b/zynq/zedboard-fsbl/memory.x new file mode 100644 index 0000000..226f8a1 --- /dev/null +++ b/zynq/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/zynq/zedboard-fsbl/src/main.rs b/zynq/zedboard-fsbl/src/main.rs new file mode 100644 index 0000000..725a469 --- /dev/null +++ b/zynq/zedboard-fsbl/src/main.rs @@ -0,0 +1,362 @@ +//! Simple FSBL for the Zedboard. +//! +//!This first variant is simplistic. It is currently only capable of QSPI boot. It searches for a +//! bitstream and ELF file inside the boot binary, flashes them and jumps to them. +//! +//! It can be easily adapted to other boards by changing the static DDR/DDRIOB configuration +//! and the used QSPI memory driver. +#![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 zynq7000_boot_image::DestinationDevice; +use zynq7000_hal::priv_tim; +use zynq7000_hal::{ + BootMode, + clocks::{ + Clocks, + pll::{PllConfig, configure_arm_pll, configure_io_pll}, + }, + ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest}, + devcfg, gic, gpio, l2_cache, + prelude::*, + qspi::{self, QSPI_START_ADDRESS}, + time::Hertz, + uart::{ClockConfig, Config, Uart}, +}; +use zynq7000_rt as _; + +// PS clock input frequency. +const PS_CLK: Hertz = Hertz::from_raw(33_333_333); + +/// 1600 MHz. +const ARM_CLK: Hertz = Hertz::from_raw(1_600_000_000); +/// 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 periphs = 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(periphs.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 logger_uart = Uart::new_with_mio( + periphs.uart_1, + Config::new_with_clk_config(uart_clk_config), + (mio_pins.mio48, mio_pins.mio49), + ) + .unwrap(); + logger_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( + logger_uart, + log::LevelFilter::Trace, + false, + ) + }; + + // Set up the global interrupt controller. + let mut gic = gic::GicConfigurator::new_with_init(periphs.gicc, periphs.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( + periphs.ddrc, + boot_mode, + DdrClockSetupConfig { + ps_clk: PS_CLK, + ddr_clk: DDR_CLK, + ddr_3x_div: u6::new(2), + ddr_2x_div: u6::new(3), + }, + &zedboard_bsp::ddriob_config_autogen::DDRIOB_CONFIG_SET_ZEDBOARD, + &zedboard_bsp::ddrc_config_autogen::DDRC_CONFIG_ZEDBOARD, + ); + 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 periphs.l2c); + info!("L2 cache init done."); + + let priv_tim = priv_tim::CpuPrivateTimer::take(clocks.arm_clocks()).unwrap(); + + 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( + periphs.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, priv_tim); + } + loop { + cortex_ar::asm::nop(); + } +} + +fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode, _priv_tim: priv_tim::CpuPrivateTimer) -> ! { + 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, + zynq7000_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(), + zynq7000_boot_image::FIXED_BOOT_HEADER_SIZE, + ); + } + drop(read_guard); + + let boot_header = zynq7000_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 + + zynq7000_boot_image::FIXED_BOOT_HEADER_SIZE) as *mut u8, + boot_header_slice[zynq7000_boot_image::FIXED_BOOT_HEADER_SIZE..].as_mut_ptr(), + fsbl_offset - zynq7000_boot_image::FIXED_BOOT_HEADER_SIZE, + ); + } + drop(read_guard); + + let boot_header = zynq7000_boot_image::BootHeader::new_unchecked(boot_header_slice); + + let mut name_buf: [u8; 256] = [0; 256]; + let mut opt_jump_addr = None; + for (index, image_header) in boot_header.image_header_iterator().unwrap().enumerate() { + let name = image_header.image_name(&mut name_buf).unwrap(); + if index == 0 { + if !name.contains("fsbl") { + log::warn!("first image name did not contain FSBL string"); + } + // Skip the FSBL. It is probably currently running, and we do not want to re-flash it, + // which would also lead to a self-overwrite. + log::info!("skipping FSBL image"); + continue; + } + + let _read_guard = qspi.read_guard(); + + for (partition_index, partition) in image_header + .partition_header_iterator(boot_header_slice) + .unwrap() + .enumerate() + { + let section_attrs = partition.section_attributes(); + if section_attrs.destination_device().is_err() { + log::error!( + "invalid destination device ID {}", + section_attrs.destination_device().unwrap_err() + ); + continue; + } + let dest_dev = section_attrs.destination_device().unwrap(); + match dest_dev { + DestinationDevice::Pl => { + info!("Loading image '{name}' to PL (FPGA).."); + // 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(), + ) + }; + // The DMA will read from the linear mapped QSPI directly, so it + // has to be configured for reads using the guard! + devcfg::configure_bitstream_non_secure(true, boot_bin_slice) + .expect("unexpected unaligned address"); + log::info!("loaded bitstream successfully"); + } + DestinationDevice::Ps => { + // Load the bitstream directly from linear mapped QSPI memory. + let load_addr = partition.destination_load_address(); + if load_addr < 0x10_0000 { + panic!("invalid load address which is not located in DDR memory"); + } + log::info!( + "Loading partition {partition_index} for '{name}' to PS with load address {load_addr}.." + ); + + let source_slice = unsafe { + core::slice::from_raw_parts( + (QSPI_START_ADDRESS + + partition + .data_offset() + .expect("invalid PS partition data offset")) + as *const _, + partition.total_partition_length().unwrap(), + ) + }; + let target_slice = unsafe { + core::slice::from_raw_parts_mut( + load_addr as *mut u8, + partition.total_partition_length().unwrap(), + ) + }; + + // Copy from the linear mapped QSPI to DDR, + target_slice.copy_from_slice(source_slice); + + match &mut opt_jump_addr { + Some(current) => *current = core::cmp::min(*current, load_addr), + None => opt_jump_addr = Some(load_addr), + } + log::info!("load success"); + } + _ => { + error!("Unsupported destination device {dest_dev:?}"); + continue; + } + } + } + } + + match opt_jump_addr { + Some(jump_addr) => { + log::info!("jumping to address {}", jump_addr); + zynq7000_hal::log::uart_blocking::flush(); + + // Some clean up and preparation for jumping to the user application. + zynq7000_hal::cache::clean_and_invalidate_data_cache(); + cortex_ar::register::TlbIAll::write(); + cortex_ar::register::BpIAll::write(); + cortex_ar::asm::dsb(); + cortex_ar::asm::isb(); + + let jump_func: extern "C" fn() -> ! = unsafe { core::mem::transmute(jump_addr) }; + jump_func(); + } + None => panic!("did not find application elf to boot inside boot binary!"), + } +} + +#[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/zynq/zedboard-qspi-flasher/Cargo.toml b/zynq/zedboard-qspi-flasher/Cargo.toml new file mode 100644 index 0000000..11eb7ab --- /dev/null +++ b/zynq/zedboard-qspi-flasher/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "zedboard-qspi-flasher" +version = "0.1.0" +edition = "2024" + +[dependencies] +cortex-ar = { version = "0.3" } +zynq7000-rt = { path = "../zynq7000-rt" } +zynq7000 = { path = "../zynq7000" } +zynq7000-hal = { path = "../zynq7000-hal" } +zynq7000-boot-image = { path = "../../zynq7000-boot-image" } +zedboard-bsp = { path = "../zedboard-bsp" } +embedded-io = "0.7" +embedded-hal = "1" +log = "0.4" +libm = "0.2" diff --git a/zynq/zedboard-qspi-flasher/build.rs b/zynq/zedboard-qspi-flasher/build.rs new file mode 100644 index 0000000..d534cc3 --- /dev/null +++ b/zynq/zedboard-qspi-flasher/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/zynq/zedboard-qspi-flasher/memory.x b/zynq/zedboard-qspi-flasher/memory.x new file mode 100644 index 0000000..11faa59 --- /dev/null +++ b/zynq/zedboard-qspi-flasher/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/zynq/zedboard-qspi-flasher/qspi-flasher.tcl b/zynq/zedboard-qspi-flasher/qspi-flasher.tcl new file mode 100644 index 0000000..5c49727 --- /dev/null +++ b/zynq/zedboard-qspi-flasher/qspi-flasher.tcl @@ -0,0 +1,151 @@ +if {[info exists env(ip_address_hw_server)]} { + set ip $env(ip_address_hw_server) +} else { + set ip "localhost" +} + +# absolute directory that contains *this* script +set script_dir [file dirname [info script]] + +# Defaults +set boot_bin_addr 0x10000000 +set boot_bin_size_addr 0x900000 +set init_tcl "" +set bin "" +set bitstream "" + +# Usage helper +proc usage {} { + puts "Usage: xsct qspi-flasher.tcl \[-b|--bin \]" + 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 bin; 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 + } +} + +# Check that QSPI flasher app exists. +set flasher_app [file join $script_dir .. target armv7a-none-eabihf release zedboard-qspi-flasher] +if {![file exists $flasher_app]} { + error "QSPI flasher application not found at path: $flasher_app" +} +set flasher_app [file normalize $flasher_app] + +# 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" + 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 + +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 at address $boot_bin_addr" +dow -data $bin $boot_bin_addr + +# Write boot.bin binary size to specific address. +set boot_bin_size [file size $bin] +puts "Writing boot.bin size $boot_bin_size to target DDR at address $boot_bin_size_addr" +mwr ${boot_bin_size_addr} ${boot_bin_size} + +puts "Flashing QSPI flasher app" +dow $flasher_app + +puts "Starting QSPI flasher app" +con + +puts "Success" diff --git a/zynq/zedboard-qspi-flasher/src/main.rs b/zynq/zedboard-qspi-flasher/src/main.rs new file mode 100644 index 0000000..eff2638 --- /dev/null +++ b/zynq/zedboard-qspi-flasher/src/main.rs @@ -0,0 +1,225 @@ +//! 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::{error, info}; +use zedboard_bsp::qspi_spansion; +use zynq7000_boot_image::BootHeader; +use zynq7000_hal::{ + BootMode, LevelShifterConfig, clocks, gpio, prelude::*, priv_tim, qspi, time::Hertz, uart, +}; +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); + +// 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, + 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::Info, + 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 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]; + let mut next_checkpoint = 0.05; + while current_addr < boot_bin_size { + if current_addr % 0x10000 == 0 { + log::debug!("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]; + log::debug!("Programming address {:#x}", current_addr); + match spansion_qspi.program_page(current_addr as u32, write_slice) { + Ok(()) => {} + Err(e) => { + log::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; + if current_addr as f32 / boot_bin_size as f32 >= next_checkpoint { + log::info!("Write progress {} %", libm::roundf(next_checkpoint * 100.0)); + next_checkpoint += 0.05; + } + } + info!("flashing done"); + + 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/zynq7000-embassy/Cargo.toml b/zynq/zynq7000-embassy/Cargo.toml similarity index 100% rename from zynq7000-embassy/Cargo.toml rename to zynq/zynq7000-embassy/Cargo.toml diff --git a/zynq7000-embassy/LICENSE-APACHE b/zynq/zynq7000-embassy/LICENSE-APACHE similarity index 100% rename from zynq7000-embassy/LICENSE-APACHE rename to zynq/zynq7000-embassy/LICENSE-APACHE diff --git a/zynq7000-embassy/LICENSE-MIT b/zynq/zynq7000-embassy/LICENSE-MIT similarity index 100% rename from zynq7000-embassy/LICENSE-MIT rename to zynq/zynq7000-embassy/LICENSE-MIT diff --git a/zynq7000-embassy/README.md b/zynq/zynq7000-embassy/README.md similarity index 100% rename from zynq7000-embassy/README.md rename to zynq/zynq7000-embassy/README.md diff --git a/zynq7000-embassy/src/lib.rs b/zynq/zynq7000-embassy/src/lib.rs similarity index 100% rename from zynq7000-embassy/src/lib.rs rename to zynq/zynq7000-embassy/src/lib.rs diff --git a/zynq7000-hal/Cargo.toml b/zynq/zynq7000-hal/Cargo.toml similarity index 80% rename from zynq7000-hal/Cargo.toml rename to zynq/zynq7000-hal/Cargo.toml index d12bd8d..f5ffc47 100644 --- a/zynq7000-hal/Cargo.toml +++ b/zynq/zynq7000-hal/Cargo.toml @@ -11,20 +11,21 @@ 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.3" } zynq7000 = { path = "../zynq7000" } -zynq-mmu = { path = "../zynq-mmu", version = "0.1.0" } +zynq7000-mmu = { path = "../zynq7000-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 } embedded-hal-nb = "1" -embedded-io = "0.6" +embedded-io = "0.7" embedded-hal = "1" embedded-hal-async = "1" -heapless = "0.8" +heapless = "0.9" static_cell = "2" delegate = "0.13" paste = "1" @@ -38,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/LICENSE-APACHE b/zynq/zynq7000-hal/LICENSE-APACHE similarity index 100% rename from zynq7000-hal/LICENSE-APACHE rename to zynq/zynq7000-hal/LICENSE-APACHE diff --git a/zynq7000-hal/LICENSE-MIT b/zynq/zynq7000-hal/LICENSE-MIT similarity index 100% rename from zynq7000-hal/LICENSE-MIT rename to zynq/zynq7000-hal/LICENSE-MIT diff --git a/zynq7000-hal/README.md b/zynq/zynq7000-hal/README.md similarity index 100% rename from zynq7000-hal/README.md rename to zynq/zynq7000-hal/README.md diff --git a/zynq7000-hal/docs.sh b/zynq/zynq7000-hal/docs.sh similarity index 100% rename from zynq7000-hal/docs.sh rename to zynq/zynq7000-hal/docs.sh diff --git a/zynq7000-hal/src/cache.rs b/zynq/zynq7000-hal/src/cache.rs similarity index 100% rename from zynq7000-hal/src/cache.rs rename to zynq/zynq7000-hal/src/cache.rs diff --git a/zynq7000-hal/src/clocks.rs b/zynq/zynq7000-hal/src/clocks/mod.rs similarity index 86% rename from zynq7000-hal/src/clocks.rs rename to zynq/zynq7000-hal/src/clocks/mod.rs index f9370da..74aeeff 100644 --- a/zynq7000-hal/src/clocks.rs +++ b/zynq/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/zynq/zynq7000-hal/src/clocks/pll.rs b/zynq/zynq7000-hal/src/clocks/pll.rs new file mode 100644 index 0000000..126eaa0 --- /dev/null +++ b/zynq/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/zynq/zynq7000-hal/src/ddr/ll.rs b/zynq/zynq7000-hal/src/ddr/ll.rs new file mode 100644 index 0000000..f5618a0 --- /dev/null +++ b/zynq/zynq7000-hal/src/ddr/ll.rs @@ -0,0 +1,335 @@ +//! 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. +#[derive(Debug)] +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/zynq/zynq7000-hal/src/ddr/mod.rs b/zynq/zynq7000-hal/src/ddr/mod.rs new file mode 100644 index 0000000..825e648 --- /dev/null +++ b/zynq/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/zynq/zynq7000-hal/src/devcfg.rs b/zynq/zynq7000-hal/src/devcfg.rs new file mode 100644 index 0000000..bbee0e6 --- /dev/null +++ b/zynq/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/embassy_net.rs b/zynq/zynq7000-hal/src/eth/embassy_net.rs similarity index 100% rename from zynq7000-hal/src/eth/embassy_net.rs rename to zynq/zynq7000-hal/src/eth/embassy_net.rs diff --git a/zynq7000-hal/src/eth/ll.rs b/zynq/zynq7000-hal/src/eth/ll.rs similarity index 98% rename from zynq7000-hal/src/eth/ll.rs rename to zynq/zynq7000-hal/src/eth/ll.rs index 1208a32..b275b06 100644 --- a/zynq7000-hal/src/eth/ll.rs +++ b/zynq/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/mdio.rs b/zynq/zynq7000-hal/src/eth/mdio.rs similarity index 100% rename from zynq7000-hal/src/eth/mdio.rs rename to zynq/zynq7000-hal/src/eth/mdio.rs diff --git a/zynq7000-hal/src/eth/mod.rs b/zynq/zynq7000-hal/src/eth/mod.rs similarity index 92% rename from zynq7000-hal/src/eth/mod.rs rename to zynq/zynq7000-hal/src/eth/mod.rs index 8ee46ca..2565814 100644 --- a/zynq7000-hal/src/eth/mod.rs +++ b/zynq/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/zynq/zynq7000-hal/src/eth/rx_descr.rs similarity index 99% rename from zynq7000-hal/src/eth/rx_descr.rs rename to zynq/zynq7000-hal/src/eth/rx_descr.rs index cdf14b2..4b5a68a 100644 --- a/zynq7000-hal/src/eth/rx_descr.rs +++ b/zynq/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/eth/smoltcp.rs b/zynq/zynq7000-hal/src/eth/smoltcp.rs similarity index 100% rename from zynq7000-hal/src/eth/smoltcp.rs rename to zynq/zynq7000-hal/src/eth/smoltcp.rs diff --git a/zynq7000-hal/src/eth/tx_descr.rs b/zynq/zynq7000-hal/src/eth/tx_descr.rs similarity index 100% rename from zynq7000-hal/src/eth/tx_descr.rs rename to zynq/zynq7000-hal/src/eth/tx_descr.rs diff --git a/zynq7000-hal/src/gic.rs b/zynq/zynq7000-hal/src/gic.rs similarity index 99% rename from zynq7000-hal/src/gic.rs rename to zynq/zynq7000-hal/src/gic.rs index 35ff624..9b75816 100644 --- a/zynq7000-hal/src/gic.rs +++ b/zynq/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/emio.rs b/zynq/zynq7000-hal/src/gpio/emio.rs similarity index 100% rename from zynq7000-hal/src/gpio/emio.rs rename to zynq/zynq7000-hal/src/gpio/emio.rs diff --git a/zynq7000-hal/src/gpio/ll.rs b/zynq/zynq7000-hal/src/gpio/ll.rs similarity index 100% rename from zynq7000-hal/src/gpio/ll.rs rename to zynq/zynq7000-hal/src/gpio/ll.rs diff --git a/zynq7000-hal/src/gpio/mio.rs b/zynq/zynq7000-hal/src/gpio/mio.rs similarity index 99% rename from zynq7000-hal/src/gpio/mio.rs rename to zynq/zynq7000-hal/src/gpio/mio.rs index 0972b5e..d0fb18c 100644 --- a/zynq7000-hal/src/gpio/mio.rs +++ b/zynq/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/zynq/zynq7000-hal/src/gpio/mod.rs similarity index 99% rename from zynq7000-hal/src/gpio/mod.rs rename to zynq/zynq7000-hal/src/gpio/mod.rs index 19b1171..5b7702c 100644 --- a/zynq7000-hal/src/gpio/mod.rs +++ b/zynq/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/zynq/zynq7000-hal/src/gtc.rs similarity index 99% rename from zynq7000-hal/src/gtc.rs rename to zynq/zynq7000-hal/src/gtc.rs index eea056b..a59989a 100644 --- a/zynq7000-hal/src/gtc.rs +++ b/zynq/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/zynq/zynq7000-hal/src/i2c.rs similarity index 99% rename from zynq7000-hal/src/i2c.rs rename to zynq/zynq7000-hal/src/i2c.rs index b20d745..fa3185e 100644 --- a/zynq7000-hal/src/i2c.rs +++ b/zynq/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/l2_cache.rs b/zynq/zynq7000-hal/src/l2_cache.rs similarity index 100% rename from zynq7000-hal/src/l2_cache.rs rename to zynq/zynq7000-hal/src/l2_cache.rs diff --git a/zynq7000-hal/src/lib.rs b/zynq/zynq7000-hal/src/lib.rs similarity index 65% rename from zynq7000-hal/src/lib.rs rename to zynq/zynq7000-hal/src/lib.rs index 5913068..56390bf 100644 --- a/zynq7000-hal/src/lib.rs +++ b/zynq/zynq7000-hal/src/lib.rs @@ -13,10 +13,15 @@ extern crate alloc; use slcr::Slcr; -use zynq7000::slcr::LevelShifterRegister; +use zynq7000::{ + SpiClockPhase, SpiClockPolarity, + slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister}, +}; pub mod 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/log.rs b/zynq/zynq7000-hal/src/log.rs similarity index 80% rename from zynq7000-hal/src/log.rs rename to zynq/zynq7000-hal/src/log.rs index 06e698a..6593365 100644 --- a/zynq7000-hal/src/log.rs +++ b/zynq/zynq7000-hal/src/log.rs @@ -1,13 +1,23 @@ //! # Simple logging providers. +use core::sync::atomic::{AtomicBool, AtomicU8}; + +static LOGGER_INIT_DONE: AtomicBool = AtomicBool::new(false); + +const LOG_SEL_LOCKED: u8 = 1; +const LOG_SEL_UNSAFE_SINGLE_CORE: u8 = 2; + +static LOG_SEL: AtomicU8 = AtomicU8::new(0); + /// Blocking UART loggers. pub mod uart_blocking { + use super::*; use core::cell::{Cell, RefCell, UnsafeCell}; use embedded_io::Write as _; use cortex_ar::register::Cpsr; use critical_section::Mutex; - use log::{LevelFilter, set_logger, set_max_level}; + use log::{LevelFilter, Log, set_logger, set_max_level}; use crate::uart::Uart; @@ -27,7 +37,10 @@ pub mod uart_blocking { /// For async applications, it is strongly recommended to use the asynchronous ring buffer /// logger instead. pub fn init_with_locks(uart: Uart, level: LevelFilter) { - // TODO: Impl debug for Uart + if LOGGER_INIT_DONE.swap(true, core::sync::atomic::Ordering::Relaxed) { + return; + } + LOG_SEL.swap(LOG_SEL_LOCKED, core::sync::atomic::Ordering::Relaxed); critical_section::with(|cs| { let inner = UART_LOGGER_BLOCKING.0.borrow(cs); inner.replace(Some(uart)); @@ -53,7 +66,16 @@ pub mod uart_blocking { }) } - fn flush(&self) {} + fn flush(&self) { + critical_section::with(|cs| { + let mut opt_logger = self.0.borrow(cs).borrow_mut(); + if opt_logger.is_none() { + return; + } + let logger = opt_logger.as_mut().unwrap(); + logger.flush().unwrap(); + }); + } } pub struct UartLoggerUnsafeSingleThread { @@ -70,23 +92,6 @@ pub mod uart_blocking { uart: UnsafeCell::new(None), }; - /// Initialize the logger with a blocking UART instance. - /// - /// For async applications, it is strongly recommended to use the asynchronous ring buffer - /// logger instead. - /// - /// # Safety - /// - /// This is a blocking logger which performs a write WITHOUT a critical section. This logger is - /// NOT thread-safe. Users must ensure that this logger is not used inside a pre-emptive - /// multi-threading context and interrupt handlers. - pub unsafe fn create_unsafe_single_thread_logger(uart: Uart) -> UartLoggerUnsafeSingleThread { - UartLoggerUnsafeSingleThread { - skip_in_isr: Cell::new(false), - uart: UnsafeCell::new(Some(uart)), - } - } - /// Initialize the logger with a blocking UART instance which does not use locks. /// /// # Safety @@ -95,6 +100,13 @@ pub mod uart_blocking { /// NOT thread-safe, which might lead to garbled output. Log output in ISRs can optionally be /// surpressed. pub unsafe fn init_unsafe_single_core(uart: Uart, level: LevelFilter, skip_in_isr: bool) { + if LOGGER_INIT_DONE.swap(true, core::sync::atomic::Ordering::Relaxed) { + return; + } + LOG_SEL.swap( + LOG_SEL_UNSAFE_SINGLE_CORE, + core::sync::atomic::Ordering::Relaxed, + ); let opt_uart = unsafe { &mut *UART_LOGGER_UNSAFE_SINGLE_THREAD.uart.get() }; opt_uart.replace(uart); UART_LOGGER_UNSAFE_SINGLE_THREAD @@ -134,7 +146,22 @@ pub mod uart_blocking { .unwrap(); } - fn flush(&self) {} + fn flush(&self) { + let uart_mut = unsafe { &mut *self.uart.get() }.as_mut(); + if uart_mut.is_none() { + return; + } + uart_mut.unwrap().flush().unwrap(); + } + } + + // Flush the selected logger instance. + pub fn flush() { + match LOG_SEL.load(core::sync::atomic::Ordering::Relaxed) { + val if val == LOG_SEL_LOCKED => UART_LOGGER_BLOCKING.flush(), + val if val == LOG_SEL_UNSAFE_SINGLE_CORE => UART_LOGGER_UNSAFE_SINGLE_THREAD.flush(), + _ => (), + } } } @@ -205,6 +232,9 @@ pub mod rb { } pub fn init(level: LevelFilter) { + if super::LOGGER_INIT_DONE.swap(true, core::sync::atomic::Ordering::Relaxed) { + return; + } critical_section::with(|cs| { let rb = StaticRb::::default(); let rb_ref = LOGGER_RB.ring_buf.borrow(cs); diff --git a/zynq7000-hal/src/prelude.rs b/zynq/zynq7000-hal/src/prelude.rs similarity index 100% rename from zynq7000-hal/src/prelude.rs rename to zynq/zynq7000-hal/src/prelude.rs diff --git a/zynq/zynq7000-hal/src/priv_tim.rs b/zynq/zynq7000-hal/src/priv_tim.rs new file mode 100644 index 0000000..d5f8e08 --- /dev/null +++ b/zynq/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/zynq/zynq7000-hal/src/qspi/lqspi_configs.rs b/zynq/zynq7000-hal/src/qspi/lqspi_configs.rs new file mode 100644 index 0000000..16ee4d6 --- /dev/null +++ b/zynq/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/zynq/zynq7000-hal/src/qspi/mod.rs b/zynq/zynq7000-hal/src/qspi/mod.rs new file mode 100644 index 0000000..c7c3528 --- /dev/null +++ b/zynq/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/zynq/zynq7000-hal/src/slcr.rs similarity index 96% rename from zynq7000-hal/src/slcr.rs rename to zynq/zynq7000-hal/src/slcr.rs index 1c710ea..20cd90d 100644 --- a/zynq7000-hal/src/slcr.rs +++ b/zynq/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/asynch.rs b/zynq/zynq7000-hal/src/spi/asynch.rs similarity index 100% rename from zynq7000-hal/src/spi/asynch.rs rename to zynq/zynq7000-hal/src/spi/asynch.rs diff --git a/zynq7000-hal/src/spi/mod.rs b/zynq/zynq7000-hal/src/spi/mod.rs similarity index 97% rename from zynq7000-hal/src/spi/mod.rs rename to zynq/zynq7000-hal/src/spi/mod.rs index 804286a..faba759 100644 --- a/zynq7000-hal/src/spi/mod.rs +++ b/zynq/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/zynq/zynq7000-hal/src/uart/mod.rs similarity index 92% rename from zynq7000-hal/src/uart/mod.rs rename to zynq/zynq7000-hal/src/uart/mod.rs index d753a69..ba9e99d 100644 --- a/zynq7000-hal/src/uart/mod.rs +++ b/zynq/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 } @@ -578,7 +578,7 @@ impl embedded_hal_nb::serial::Write for Uart { } fn flush(&mut self) -> nb::Result<(), Self::Error> { - self.tx.flush() + embedded_hal_nb::serial::Write::flush(&mut self.tx) } } @@ -604,7 +604,8 @@ impl embedded_io::Write for Uart { } fn flush(&mut self) -> Result<(), Self::Error> { - self.tx.flush() + self.tx.flush(); + Ok(()) } } @@ -657,7 +658,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 +666,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 +674,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 +682,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 +690,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 +698,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/zynq/zynq7000-hal/src/uart/rx.rs similarity index 99% rename from zynq7000-hal/src/uart/rx.rs rename to zynq/zynq7000-hal/src/uart/rx.rs index 3702676..d232852 100644 --- a/zynq7000-hal/src/uart/rx.rs +++ b/zynq/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-hal/src/uart/tx.rs b/zynq/zynq7000-hal/src/uart/tx.rs similarity index 96% rename from zynq7000-hal/src/uart/tx.rs rename to zynq/zynq7000-hal/src/uart/tx.rs index 9433174..984e0f8 100644 --- a/zynq7000-hal/src/uart/tx.rs +++ b/zynq/zynq7000-hal/src/uart/tx.rs @@ -78,6 +78,10 @@ impl Tx { } } + pub fn flush(&mut self) { + while !self.regs.read_sr().tx_empty() {} + } + #[inline] pub fn write_fifo_unchecked(&mut self, word: u8) { self.regs.write_fifo(Fifo::new_with_raw_value(word as u32)); @@ -161,11 +165,10 @@ impl embedded_hal_nb::serial::Write for Tx { } fn flush(&mut self) -> nb::Result<(), Self::Error> { - loop { - if self.regs.read_sr().tx_empty() { - return Ok(()); - } + if self.regs.read_sr().tx_empty() { + return Ok(()); } + Err(nb::Error::WouldBlock) } } @@ -195,7 +198,7 @@ impl embedded_io::Write for Tx { } fn flush(&mut self) -> Result<(), Self::Error> { - >::flush(self).ok(); + self.flush(); Ok(()) } } diff --git a/zynq7000-hal/src/uart/tx_async.rs b/zynq/zynq7000-hal/src/uart/tx_async.rs similarity index 97% rename from zynq7000-hal/src/uart/tx_async.rs rename to zynq/zynq7000-hal/src/uart/tx_async.rs index f349e71..3033123 100644 --- a/zynq7000-hal/src/uart/tx_async.rs +++ b/zynq/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(()) + } } diff --git a/zynq/zynq7000-mmu/Cargo.toml b/zynq/zynq7000-mmu/Cargo.toml new file mode 100644 index 0000000..06946cf --- /dev/null +++ b/zynq/zynq7000-mmu/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "zynq7000-mmu" +description = "Zynq7000 MMU structures" +version = "0.1.0" +edition = "2024" + +[dependencies] +thiserror = { version = "2", default-features = false } +cortex-ar = { version = "0.3" } + +[features] +tools = [] diff --git a/zynq-mmu/src/lib.rs b/zynq/zynq7000-mmu/src/lib.rs similarity index 100% rename from zynq-mmu/src/lib.rs rename to zynq/zynq7000-mmu/src/lib.rs diff --git a/zynq7000-rt/Cargo.toml b/zynq/zynq7000-rt/Cargo.toml similarity index 64% rename from zynq7000-rt/Cargo.toml rename to zynq/zynq7000-rt/Cargo.toml index 034bd29..944fa4c 100644 --- a/zynq7000-rt/Cargo.toml +++ b/zynq/zynq7000-rt/Cargo.toml @@ -12,17 +12,10 @@ 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" -zynq-mmu = { path = "../zynq-mmu", version = "0.1.0" } +cortex-ar = { version = "0.3" } +arbitrary-int = "2" +zynq7000-mmu = { path = "../zynq7000-mmu", version = "0.1.0" } [features] default = ["rt"] -tools = ["zynq-mmu/tools"] rt = ["dep:cortex-a-rt"] - -[[bin]] -name = "table-gen" -path = "src/bin/table-gen.rs" -# Prevents default build -required-features = ["tools"] diff --git a/zynq7000-rt/README.md b/zynq/zynq7000-rt/README.md similarity index 100% rename from zynq7000-rt/README.md rename to zynq/zynq7000-rt/README.md diff --git a/zynq7000-rt/docs.sh b/zynq/zynq7000-rt/docs.sh similarity index 100% rename from zynq7000-rt/docs.sh rename to zynq/zynq7000-rt/docs.sh diff --git a/zynq7000-rt/regen-table.sh b/zynq/zynq7000-rt/regen-table.sh similarity index 100% rename from zynq7000-rt/regen-table.sh rename to zynq/zynq7000-rt/regen-table.sh diff --git a/zynq7000-rt/src/lib.rs b/zynq/zynq7000-rt/src/lib.rs similarity index 96% rename from zynq7000-rt/src/lib.rs rename to zynq/zynq7000-rt/src/lib.rs index baec7fd..6ae917e 100644 --- a/zynq7000-rt/src/lib.rs +++ b/zynq/zynq7000-rt/src/lib.rs @@ -12,7 +12,7 @@ pub use cortex_a_rt::*; #[cfg(feature = "rt")] -use zynq_mmu::L1TableWrapper; +use zynq7000_mmu::L1TableWrapper; pub mod mmu; #[cfg(feature = "rt")] diff --git a/zynq7000-rt/src/mmu.rs b/zynq/zynq7000-rt/src/mmu.rs similarity index 95% rename from zynq7000-rt/src/mmu.rs rename to zynq/zynq7000-rt/src/mmu.rs index ff8f379..3c103c6 100644 --- a/zynq7000-rt/src/mmu.rs +++ b/zynq/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-rt/src/mmu_table.rs b/zynq/zynq7000-rt/src/mmu_table.rs similarity index 99% rename from zynq7000-rt/src/mmu_table.rs rename to zynq/zynq7000-rt/src/mmu_table.rs index 4ecd507..c4f82ff 100644 --- a/zynq7000-rt/src/mmu_table.rs +++ b/zynq/zynq7000-rt/src/mmu_table.rs @@ -1,7 +1,7 @@ //! This file was auto-generated by table-gen.rs use crate::mmu::section_attrs; use cortex_ar::mmu::L1Section; -use zynq_mmu::L1Table; +use zynq7000_mmu::L1Table; /// MMU Level 1 Page table. /// diff --git a/zynq7000-rt/src/rt.rs b/zynq/zynq7000-rt/src/rt.rs similarity index 100% rename from zynq7000-rt/src/rt.rs rename to zynq/zynq7000-rt/src/rt.rs diff --git a/zynq7000/Cargo.toml b/zynq/zynq7000/Cargo.toml similarity index 95% rename from zynq7000/Cargo.toml rename to zynq/zynq7000/Cargo.toml index 78f1465..4de4921 100644 --- a/zynq7000/Cargo.toml +++ b/zynq/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/LICENSE-APACHE b/zynq/zynq7000/LICENSE-APACHE similarity index 100% rename from zynq7000/LICENSE-APACHE rename to zynq/zynq7000/LICENSE-APACHE diff --git a/zynq7000/LICENSE-MIT b/zynq/zynq7000/LICENSE-MIT similarity index 100% rename from zynq7000/LICENSE-MIT rename to zynq/zynq7000/LICENSE-MIT diff --git a/zynq7000/README.md b/zynq/zynq7000/README.md similarity index 100% rename from zynq7000/README.md rename to zynq/zynq7000/README.md diff --git a/zynq7000/build.rs b/zynq/zynq7000/build.rs similarity index 100% rename from zynq7000/build.rs rename to zynq/zynq7000/build.rs diff --git a/zynq7000/docs.sh b/zynq/zynq7000/docs.sh similarity index 100% rename from zynq7000/docs.sh rename to zynq/zynq7000/docs.sh diff --git a/zynq/zynq7000/src/ddrc.rs b/zynq/zynq7000/src/ddrc.rs new file mode 100644 index 0000000..d1fe31c --- /dev/null +++ b/zynq/zynq7000/src/ddrc.rs @@ -0,0 +1,886 @@ +pub const DDRC_BASE_ADDR: usize = 0xF800_6000; + +pub mod regs { + pub use crate::slcr::ddriob::DdriobConfig; + 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, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + 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)] + #[derive(Debug)] + pub enum MobileSetting { + Ddr2Ddr3 = 0, + Lpddr2 = 1, + } + #[bitbybit::bitfield(u32, default = 0x0, debug)] + 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)] + #[derive(Debug)] + pub enum ModeRegisterType { + Write = 0, + Read = 1, + } + + #[bitbybit::bitfield(u32, default = 0x0, debug)] + 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, debug)] + 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, debug)] + pub struct DramEmr { + #[bits(16..=31, rw)] + emr3: u16, + #[bits(0..=15, rw)] + emr2: u16, + } + + #[bitbybit::bitfield(u32, default = 0x0, debug)] + pub struct DramEmrMr { + #[bits(16..=31, rw)] + emr: u16, + #[bits(0..=15, rw)] + mr: u16, + } + + #[bitbybit::bitfield(u32, default = 0x0, debug)] + 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, debug)] + pub struct DisableDq { + #[bit(1, rw)] + dis_dq: bool, + #[bit(0, rw)] + force_low_pri_n: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + 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)] + #[derive(Debug)] + pub enum DllCalibSel { + Periodic = 0, + Manual = 1, + } + + #[bitbybit::bitfield(u32, default = 0x0, debug)] + pub struct DllCalib { + #[bit(16, rw)] + sel: DllCalibSel, + } + + #[bitbybit::bitfield(u32, default = 0x0, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + pub struct DeepPowerdown { + #[bits(1..=8, rw)] + deep_powerdown_to_x1024: u8, + #[bit(0, rw)] + enable: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0, debug)] + 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, debug)] + pub struct Reg2d { + #[bit(9, rw)] + skip_ocd: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0, debug)] + 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, debug)] + pub struct CheEccControl { + #[bit(1, rw)] + clear_correctable_errors: bool, + #[bit(0, rw)] + clear_uncorrectable_errors: bool, + } + + #[bitbybit::bitenum(u3, exhaustive = false)] + #[derive(Debug)] + pub enum EccMode { + NoEcc = 0b000, + SecDecOverOneBeat = 0b100, + } + + #[bitbybit::bitfield(u32, default = 0x0, debug)] + pub struct EccScrub { + #[bit(3, rw)] + disable_scrub: bool, + #[bits(0..=2, rw)] + ecc_mode: Option, + } + + #[bitbybit::bitfield(u32, default = 0x0, debug)] + pub struct PhyReceiverEnable { + #[bits(4..=7, rw)] + phy_dif_off: u4, + #[bits(0..=3, rw)] + phy_dif_on: u4, + } + + #[bitbybit::bitfield(u32, default = 0x0, debug)] + 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, debug)] + pub struct PhyInitRatio { + #[bits(10..=19, rw)] + gatelvl_init_ratio: u10, + #[bits(0..=9, rw)] + wrlvl_init_ratio: u10, + } + + #[bitbybit::bitfield(u32, default = 0x0, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + 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, debug)] + pub struct ExclusiveAccessConfig { + #[bits(9..=17, rw)] + access_id1_port: u9, + #[bits(0..=8, rw)] + access_id0_port: u9, + } + + #[bitbybit::bitenum(u1, exhaustive = true)] + #[derive(Debug)] + pub enum LpddrBit { + Ddr2Ddr3 = 0, + Lpddr2 = 1, + } + + #[bitbybit::bitfield(u32, default = 0x0, debug)] + 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, debug)] + pub struct LpddrControl1 { + #[bits(0..=31, rw)] + mr4_read_interval: u32, + } + + #[bitbybit::bitfield(u32, default = 0x0, debug)] + 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, debug)] + 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, debug)] + 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/zynq/zynq7000/src/devcfg.rs b/zynq/zynq7000/src/devcfg.rs new file mode 100644 index 0000000..d7d67c1 --- /dev/null +++ b/zynq/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/eth.rs b/zynq/zynq7000/src/eth.rs similarity index 97% rename from zynq7000/src/eth.rs rename to zynq/zynq7000/src/eth.rs index 62cb959..38ab358 100644 --- a/zynq7000/src/eth.rs +++ b/zynq/zynq7000/src/eth.rs @@ -82,8 +82,7 @@ impl MdcClockDivisor { } } -#[bitbybit::bitfield(u32, default = 0x0)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct NetworkConfig { #[bit(30, rw)] ignore_ipg_rx_error: bool, @@ -141,8 +140,7 @@ pub struct NetworkConfig { } /// PHY management status information. -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct NetworkStatus { #[bit(6, r)] pfc_pri_pause_neg: bool, @@ -215,8 +213,7 @@ impl DmaRxBufSize { } } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct DmaConfig { #[bit(24, rw)] discard_when_ahb_full: bool, @@ -244,8 +241,7 @@ pub struct DmaConfig { burst_length: u5, } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct TxStatus { #[bit(8, rw)] hresp_not_ok: bool, @@ -276,8 +272,7 @@ impl TxStatus { } } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct RxStatus { #[bit(3, rw)] hresp_not_ok: bool, @@ -295,8 +290,7 @@ impl RxStatus { } } -#[bitbybit::bitfield(u32, default = 0x0)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct InterruptStatus { #[bit(26, rw)] tsu_sec_incr: bool, @@ -387,13 +381,13 @@ impl InterruptControl { } #[bitbybit::bitenum(u2, exhaustive = false)] +#[derive(Debug)] pub enum PhyOperation { Read = 0b10, Write = 0b01, } -#[bitbybit::bitfield(u32, default = 0x0)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct PhyMaintenance { /// Must be 1 for Clause 22 operations. #[bit(30, rw)] @@ -410,15 +404,13 @@ pub struct PhyMaintenance { data: u16, } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct PauseQuantum { #[bits(0..=15, rw)] value: u16, } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct MatchRegister { #[bit(31, rw)] copy_enable: bool, diff --git a/zynq7000/src/gic.rs b/zynq/zynq7000/src/gic.rs similarity index 96% rename from zynq7000/src/gic.rs rename to zynq/zynq7000/src/gic.rs index b1b8514..28ddb07 100644 --- a/zynq7000/src/gic.rs +++ b/zynq/zynq7000/src/gic.rs @@ -4,7 +4,7 @@ use arbitrary_int::{u3, u5, u10}; use static_assertions::const_assert_eq; /// Distributor Control Register -#[bitbybit::bitfield(u32, default = 0x0)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct DistributorControlRegister { #[bit(1, rw)] enable_non_secure: bool, @@ -13,7 +13,7 @@ pub struct DistributorControlRegister { } /// Read only bit. This register only returns fixed constants. -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct TypeRegister { #[bits(11..=15, r)] lspi: u5, @@ -127,7 +127,7 @@ impl GicDistributor { } /// CPU interface control register. -#[bitbybit::bitfield(u32, default = 0x0)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct InterfaceControl { #[bit(4, rw)] sbpr: bool, @@ -142,15 +142,14 @@ pub struct InterfaceControl { } /// Priority Mask Register -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct PriorityRegister { #[bits(0..=7, rw)] priority: u8, } /// Interrupt acknowledge register. -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct InterruptSignalRegister { #[bits(10..=12, rw)] cpu_id: u3, diff --git a/zynq7000/src/gpio.rs b/zynq/zynq7000/src/gpio.rs similarity index 98% rename from zynq7000/src/gpio.rs rename to zynq/zynq7000/src/gpio.rs index 34773de..5decc98 100644 --- a/zynq7000/src/gpio.rs +++ b/zynq/zynq7000/src/gpio.rs @@ -3,9 +3,9 @@ #[derive(Debug)] pub struct MaskedOutput { #[bits(16..=31, w)] - pub mask: u16, + mask: u16, #[bits(0..=15, rw)] - pub output: u16, + output: u16, } #[derive(derive_mmio::Mmio)] diff --git a/zynq7000/src/gtc.rs b/zynq/zynq7000/src/gtc.rs similarity index 95% rename from zynq7000/src/gtc.rs rename to zynq/zynq7000/src/gtc.rs index cc089c0..3e29b12 100644 --- a/zynq7000/src/gtc.rs +++ b/zynq/zynq7000/src/gtc.rs @@ -2,7 +2,7 @@ pub const GTC_BASE_ADDR: usize = super::mpcore::MPCORE_BASE_ADDR + 0x0000_0200; -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct GtcControl { #[bits(8..=15, rw)] prescaler: u8, @@ -16,7 +16,7 @@ pub struct GtcControl { enable: bool, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct InterruptStatus { #[bit(0, rw)] event_flag: bool, diff --git a/zynq7000/src/i2c.rs b/zynq/zynq7000/src/i2c.rs similarity index 93% rename from zynq7000/src/i2c.rs rename to zynq/zynq7000/src/i2c.rs index 3d3ca78..8ea7663 100644 --- a/zynq7000/src/i2c.rs +++ b/zynq/zynq7000/src/i2c.rs @@ -18,7 +18,7 @@ pub enum Mode { Master = 0b1, } -#[bitbybit::bitfield(u32, default = 0x0)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct Control { /// Divides the input PCLK frequency by this value + 1 #[bits(14..=15, rw)] @@ -47,7 +47,7 @@ pub struct Control { dir: Direction, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct Status { #[bit(8, r)] bus_active: bool, @@ -65,19 +65,19 @@ pub struct Status { rx_rw: bool, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct Address { #[bits(0..=9, rw)] addr: u10, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct Fifo { #[bits(0..=7, rw)] data: u8, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct InterruptStatus { #[bit(9, rw)] arbitration_lost: bool, @@ -99,7 +99,7 @@ pub struct InterruptStatus { complete: bool, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct InterruptMask { #[bit(9, r)] arbitration_lost: bool, @@ -143,14 +143,14 @@ pub struct InterruptControl { complete: bool, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct Timeout { /// Reset value: 0x1F. #[bits(0..=7, rw)] timeout: u8, } -#[bitbybit::bitfield(u32, default = 0x0)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct TransferSize { #[bits(0..=7, rw)] size: u8, diff --git a/zynq7000/src/l2_cache.rs b/zynq/zynq7000/src/l2_cache.rs similarity index 95% rename from zynq7000/src/l2_cache.rs rename to zynq/zynq7000/src/l2_cache.rs index 98238ee..46d090a 100644 --- a/zynq7000/src/l2_cache.rs +++ b/zynq/zynq7000/src/l2_cache.rs @@ -9,15 +9,13 @@ pub struct LockdownRegisters { instruction: u32, } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct CacheSync { #[bit(0, r)] busy: bool, } -#[bitbybit::bitfield(u32, default = 0x0)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct DebugControl { #[bit(2, rw)] spniden: bool, @@ -27,8 +25,7 @@ pub struct DebugControl { disable_cache_linefill: bool, } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct CacheId { #[bits(24..=31, r)] implementer: u8, @@ -59,6 +56,7 @@ impl Control { } #[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug)] pub enum ReplacementPolicy { PseudoRandomWithLfsr = 0, RoundRobin = 1, @@ -86,7 +84,7 @@ pub enum Associativity { _16Way = 1, } -#[bitbybit::bitfield(u32, default = 0x0)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct AuxControl { #[bit(30, rw)] early_bresp_enable: bool, @@ -125,8 +123,8 @@ pub struct AuxControl { full_line_zero_enable: bool, } -#[bitbybit::bitfield(u32, default = 0x0)] -#[derive(Debug, PartialEq, Eq)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] +#[derive(PartialEq, Eq)] pub struct LatencyConfig { /// Latency is the numerical value + 1 cycles. #[bits(8..=10, rw)] @@ -139,8 +137,7 @@ pub struct LatencyConfig { setup_latency: u3, } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct InterruptStatus { #[bit(8, r)] dec_error_l3: bool, diff --git a/zynq7000/src/lib.rs b/zynq/zynq7000/src/lib.rs similarity index 80% rename from zynq7000/src/lib.rs rename to zynq/zynq7000/src/lib.rs index 9dec569..d544bb0 100644 --- a/zynq7000/src/lib.rs +++ b/zynq/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/mpcore.rs b/zynq/zynq7000/src/mpcore.rs similarity index 100% rename from zynq7000/src/mpcore.rs rename to zynq/zynq7000/src/mpcore.rs diff --git a/zynq/zynq7000/src/priv_tim.rs b/zynq/zynq7000/src/priv_tim.rs new file mode 100644 index 0000000..cc86ede --- /dev/null +++ b/zynq/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, debug)] +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, debug)] +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/zynq/zynq7000/src/qspi.rs b/zynq/zynq7000/src/qspi.rs new file mode 100644 index 0000000..d963991 --- /dev/null +++ b/zynq/zynq7000/src/qspi.rs @@ -0,0 +1,280 @@ +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, + } + } +} + +// TODO: Use bitbybit debug support as soon as support for write fields has been implemented. +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +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, debug)] +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, debug)] +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, debug)] +pub struct SpiEnable { + #[bit(0, rw)] + enable: bool, +} + +/// All the delays are in SPI reference block or external clock cycles. +#[bitbybit::bitfield(u32, debug)] +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, debug)] +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, debug)] +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, debug)] +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/zynq/zynq7000/src/slcr/clocks.rs similarity index 81% rename from zynq7000/src/slcr/clocks.rs rename to zynq/zynq7000/src/slcr/clocks.rs index cd7838b..e52c3be 100644 --- a/zynq7000/src/slcr/clocks.rs +++ b/zynq/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/zynq/zynq7000/src/slcr/ddriob.rs b/zynq/zynq7000/src/slcr/ddriob.rs new file mode 100644 index 0000000..28ae5bb --- /dev/null +++ b/zynq/zynq7000/src/slcr/ddriob.rs @@ -0,0 +1,140 @@ +use arbitrary_int::{u2, u3}; + +#[bitbybit::bitenum(u4, exhaustive = false)] +#[derive(Debug)] +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, debug)] +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, debug)] +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, debug)] +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, debug)] +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/mio.rs b/zynq/zynq7000/src/slcr/mio.rs similarity index 89% rename from zynq7000/src/slcr/mio.rs rename to zynq/zynq7000/src/slcr/mio.rs index f37ec10..4116a39 100644 --- a/zynq7000/src/slcr/mio.rs +++ b/zynq/zynq7000/src/slcr/mio.rs @@ -4,12 +4,14 @@ use arbitrary_int::{u2, u3}; #[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug)] pub enum Speed { SlowCmosEdge = 0b0, FastCmosEdge = 0b1, } #[bitbybit::bitenum(u3)] +#[derive(Debug)] pub enum IoType { LvCmos18 = 0b001, LvCmos25 = 0b010, @@ -17,8 +19,8 @@ pub enum IoType { Hstl = 0b100, } -#[bitbybit::bitfield(u32, default = 0x0)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] +#[derive(PartialEq, Eq)] pub struct Config { #[bit(13, rw)] disable_hstl_rcvr: bool, diff --git a/zynq7000/src/slcr/mod.rs b/zynq/zynq7000/src/slcr/mod.rs similarity index 79% rename from zynq7000/src/slcr/mod.rs rename to zynq/zynq7000/src/slcr/mod.rs index 072c136..5bbdc60 100644 --- a/zynq7000/src/slcr/mod.rs +++ b/zynq/zynq7000/src/slcr/mod.rs @@ -12,45 +12,12 @@ 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)] +#[derive(Debug)] pub enum VrefSel { Disabled = 0b000, Vref0_9V = 0b001, @@ -93,11 +60,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, } @@ -110,7 +85,7 @@ pub enum LevelShifterConfig { EnableAll = 0xF, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct LevelShifterRegister { #[bits(0..=3, rw)] user_lvl_shftr_en: Option, @@ -205,7 +180,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/zynq/zynq7000/src/slcr/reset.rs similarity index 84% rename from zynq7000/src/slcr/reset.rs rename to zynq/zynq7000/src/slcr/reset.rs index 286ed23..8275ad5 100644 --- a/zynq7000/src/slcr/reset.rs +++ b/zynq/zynq7000/src/slcr/reset.rs @@ -1,7 +1,6 @@ use super::{RESET_BLOCK_OFFSET, SLCR_BASE_ADDR}; -#[bitbybit::bitfield(u32, default = 0x0)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct DualClockReset { /// Peripheral 1 AMBA software reset. #[bit(1, rw)] @@ -11,8 +10,7 @@ pub struct DualClockReset { periph0_cpu1x_rst: bool, } -#[bitbybit::bitfield(u32, default = 0x0)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct DualRefAndClockReset { /// Periperal 1 Reference software reset. #[bit(3, rw)] @@ -28,15 +26,13 @@ pub struct DualRefAndClockReset { periph0_cpu1x_rst: bool, } -#[bitbybit::bitfield(u32, default = 0x0)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct GpioClockReset { #[bit(0, rw)] gpio_cpu1x_rst: bool, } -#[bitbybit::bitfield(u32, default = 0x0)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct EthernetReset { #[bit(5, rw)] gem1_ref_rst: bool, @@ -52,6 +48,14 @@ pub struct EthernetReset { gem0_cpu1x_rst: bool, } +#[bitbybit::bitfield(u32, default = 0x0, 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 +73,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/zynq/zynq7000/src/spi.rs similarity index 94% rename from zynq7000/src/spi.rs rename to zynq/zynq7000/src/spi.rs index eab4bc0..dbb5ae4 100644 --- a/zynq7000/src/spi.rs +++ b/zynq/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; @@ -31,6 +33,7 @@ impl BaudDivSel { } } +// TODO: Use bitbybit debug support as soon as it was added. #[bitbybit::bitfield(u32, default = 0x0)] #[derive(Debug)] pub struct Config { @@ -56,17 +59,16 @@ 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, } -#[bitbybit::bitfield(u32, default = 0x0)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct InterruptStatus { #[bit(6, rw)] tx_underflow: bool, @@ -105,8 +107,7 @@ pub struct InterruptControl { rx_ovr: bool, } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct InterruptMask { #[bit(6, r)] tx_underflow: bool, @@ -161,8 +162,7 @@ impl FifoRead { } /// The numbers specified in the register fields are always specified in number of -#[bitbybit::bitfield(u32, default = 0x0)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct DelayControl { /// Number of cycles the chip select is de-asserted between words when CPHA = 0 #[bits(24..=31, rw)] diff --git a/zynq7000/src/ttc.rs b/zynq/zynq7000/src/ttc.rs similarity index 94% rename from zynq7000/src/ttc.rs rename to zynq/zynq7000/src/ttc.rs index 76fe4d8..58a1cd5 100644 --- a/zynq7000/src/ttc.rs +++ b/zynq/zynq7000/src/ttc.rs @@ -13,7 +13,7 @@ pub enum ClockSource { External = 0b1, } -#[bitbybit::bitfield(u32, default = 0x0)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct ClockControl { /// When this bit is set and the external clock is selected, the counter clocks on the /// negative edge of the external clock input. @@ -27,15 +27,15 @@ pub struct ClockControl { prescale_enable: bool, } -#[derive(Debug)] #[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug)] pub enum Mode { Overflow = 0b0, Interval = 0b1, } -#[derive(Debug, Default)] #[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, Default)] pub enum WavePolarity { /// The waveform output goes from high to low on a match 0 interrupt and returns high on /// overflow or interval interrupt. @@ -46,14 +46,14 @@ pub enum WavePolarity { LowToHighOnMatch1 = 0b1, } -#[derive(Debug)] #[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug)] pub enum WaveEnable { Enable = 0b0, Disable = 0b1, } -#[bitbybit::bitfield(u32, default = 0x0)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct CounterControl { #[bit(6, rw)] wave_polarity: WavePolarity, @@ -76,19 +76,19 @@ pub struct CounterControl { disable: bool, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct Counter { #[bits(0..=15, r)] count: u16, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct RwValue { #[bits(0..=15, rw)] value: u16, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct InterruptStatus { /// Even timer overflow interrupt. #[bit(5, r)] @@ -105,7 +105,7 @@ pub struct InterruptStatus { interval: bool, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct InterruptControl { /// Even timer overflow interrupt. #[bit(5, rw)] @@ -122,7 +122,7 @@ pub struct InterruptControl { interval: bool, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct EventControl { /// E_Ov bit. When set to 0, the event timer is disabled and set to 0 when an event timer /// register overflow occurs. Otherwise, continue counting on overflow. @@ -136,7 +136,7 @@ pub struct EventControl { enable: bool, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, debug)] pub struct EventCount { #[bits(0..=15, r)] count: u16, diff --git a/zynq7000/src/uart.rs b/zynq/zynq7000/src/uart.rs similarity index 95% rename from zynq7000/src/uart.rs rename to zynq/zynq7000/src/uart.rs index d2df746..de94ff7 100644 --- a/zynq7000/src/uart.rs +++ b/zynq/zynq7000/src/uart.rs @@ -5,6 +5,7 @@ pub const UART_0_BASE: usize = 0xE000_0000; pub const UART_1_BASE: usize = 0xE000_1000; #[bitbybit::bitenum(u3, exhaustive = true)] +#[derive(Debug)] pub enum Parity { Even = 0b000, Odd = 0b001, @@ -55,8 +56,7 @@ pub enum ChMode { RemoteLoopback = 0b11, } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct Control { /// Stop transmitter break. #[bit(8, rw)] @@ -87,8 +87,7 @@ pub struct Control { rx_rst: bool, } -#[bitbybit::bitfield(u32, default = 0x0)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct Mode { #[bits(8..=9, rw)] chmode: ChMode, @@ -103,35 +102,32 @@ pub struct Mode { clksel: ClockSelect, } -#[bitbybit::bitfield(u32, default = 0x0)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, default = 0, debug)] pub struct Baudgen { #[bits(0..=15, rw)] cd: u16, } -#[bitbybit::bitfield(u32, default = 0x0)] -#[derive(Debug)] -pub struct BaudRateDiv { +#[bitbybit::bitfield(u32, default = 0, debug)] +pub struct BaudRateDivisor { #[bits(0..=7, rw)] bdiv: u8, } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct Fifo { #[bits(0..=7, rw)] fifo: u8, } #[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug)] pub enum Ttrig { LessThanTTrig = 0b0, GreaterEqualTTrig = 0b1, } -#[bitbybit::bitfield(u32)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, debug)] pub struct Status { #[bit(14, r)] tx_near_full: bool, @@ -230,8 +226,7 @@ pub struct InterruptMask { rx_trg: bool, } -#[bitbybit::bitfield(u32, default = 0x0)] -#[derive(Debug)] +#[bitbybit::bitfield(u32, default = 0x0, debug)] pub struct InterruptStatus { #[bit(12, rw)] tx_over: bool, @@ -319,7 +314,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/zynq/zynq7000/src/xadc.rs b/zynq/zynq7000/src/xadc.rs new file mode 100644 index 0000000..e80bf22 --- /dev/null +++ b/zynq/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); diff --git a/zynq7000-boot-image/.gitignore b/zynq7000-boot-image/.gitignore new file mode 100644 index 0000000..5a44eef --- /dev/null +++ b/zynq7000-boot-image/.gitignore @@ -0,0 +1 @@ +/Cargo.lock diff --git a/zynq7000-boot-image/Cargo.toml b/zynq7000-boot-image/Cargo.toml new file mode 100644 index 0000000..f72493a --- /dev/null +++ b/zynq7000-boot-image/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "zynq7000-boot-image" +version = "0.1.0" +edition = "2024" + +[dependencies] +thiserror = { version = "2", default-features = false } +arbitrary-int = "2" +bitbybit = "1.4" diff --git a/zynq7000-boot-image/src/lib.rs b/zynq7000-boot-image/src/lib.rs new file mode 100644 index 0000000..1f27c97 --- /dev/null +++ b/zynq7000-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/zynq7000-boot-image/staging/.gitignore b/zynq7000-boot-image/staging/.gitignore new file mode 100644 index 0000000..8ef9722 --- /dev/null +++ b/zynq7000-boot-image/staging/.gitignore @@ -0,0 +1,4 @@ +/fsbl.elf +/fpga.bit +/application.elf +/boot.bin diff --git a/zynq7000-boot-image/staging/README.md b/zynq7000-boot-image/staging/README.md new file mode 100644 index 0000000..976ca93 --- /dev/null +++ b/zynq7000-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/zynq7000-boot-image/staging/boot.bif b/zynq7000-boot-image/staging/boot.bif new file mode 100644 index 0000000..7f3f789 --- /dev/null +++ b/zynq7000-boot-image/staging/boot.bif @@ -0,0 +1,6 @@ +all: +{ + [bootloader] fsbl.elf + fpga.bit + application.elf +} diff --git a/zynq7000-rt/Cargo.lock b/zynq7000-rt/Cargo.lock deleted file mode 100644 index d536135..0000000 --- a/zynq7000-rt/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "zynq-rt" -version = "0.1.0"