From 4637fec06007369b18ee28d5aa3fe92e924562cc Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 1 Aug 2025 14:32:08 +0200 Subject: [PATCH] Introduce Rust FSBL --- Cargo.toml | 2 +- .../embassy/src/bin/dht22-open-drain-pins.rs | 2 +- .../embassy/src/bin/logger-non-blocking.rs | 2 +- examples/embassy/src/bin/pwm.rs | 2 +- examples/embassy/src/main.rs | 2 +- examples/simple/src/bin/logger.rs | 2 +- examples/simple/src/main.rs | 12 +- examples/zedboard/src/bin/ethernet.rs | 2 +- examples/zedboard/src/bin/l3gd20h-i2c-mio.rs | 2 +- examples/zedboard/src/bin/l3gd20h-spi-mio.rs | 2 +- examples/zedboard/src/bin/uart-blocking.rs | 2 +- .../zedboard/src/bin/uart-non-blocking.rs | 2 +- examples/zedboard/src/main.rs | 2 +- fsbl/Cargo.toml | 25 + fsbl/memory.x | 24 + fsbl/src/ddr_cfg.rs | 56 ++ fsbl/src/main.rs | 101 +++ memory.x | 3 +- zedboard-fpga-design/src/zedboard-bd.tcl | 12 + zynq7000-hal/src/{clocks.rs => clocks/mod.rs} | 56 +- zynq7000-hal/src/clocks/pll.rs | 356 ++++++++ zynq7000-hal/src/ddr.rs | 564 ++++++++++++ zynq7000-hal/src/lib.rs | 37 +- zynq7000-hal/src/slcr.rs | 4 +- zynq7000-hal/src/spi/mod.rs | 6 +- zynq7000/src/ddrc.rs | 834 ++++++++++++++++++ zynq7000/src/lib.rs | 3 + zynq7000/src/slcr/clocks.rs | 80 +- zynq7000/src/slcr/ddriob.rs | 139 +++ zynq7000/src/slcr/mod.rs | 48 +- 30 files changed, 2267 insertions(+), 117 deletions(-) create mode 100644 fsbl/Cargo.toml create mode 100644 fsbl/memory.x create mode 100644 fsbl/src/ddr_cfg.rs create mode 100644 fsbl/src/main.rs rename zynq7000-hal/src/{clocks.rs => clocks/mod.rs} (87%) create mode 100644 zynq7000-hal/src/clocks/pll.rs create mode 100644 zynq7000-hal/src/ddr.rs create mode 100644 zynq7000/src/ddrc.rs create mode 100644 zynq7000/src/slcr/ddriob.rs diff --git a/Cargo.toml b/Cargo.toml index a8de1af..da7a669 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ "examples/simple", "examples/embassy", "examples/zedboard", - "zynq-mmu", + "zynq-mmu", "fsbl", ] exclude = ["experiments"] diff --git a/examples/embassy/src/bin/dht22-open-drain-pins.rs b/examples/embassy/src/bin/dht22-open-drain-pins.rs index 8f4b023..34ed594 100644 --- a/examples/embassy/src/bin/dht22-open-drain-pins.rs +++ b/examples/embassy/src/bin/dht22-open-drain-pins.rs @@ -137,7 +137,7 @@ async fn main(_spawner: Spawner) -> ! { info!("Flex Pin 0 state (should be low): {}", flex_pin_0.is_high()); } - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); let mut ticker = Ticker::every(Duration::from_millis(1000)); diff --git a/examples/embassy/src/bin/logger-non-blocking.rs b/examples/embassy/src/bin/logger-non-blocking.rs index bbdff1b..5f5169d 100644 --- a/examples/embassy/src/bin/logger-non-blocking.rs +++ b/examples/embassy/src/bin/logger-non-blocking.rs @@ -75,7 +75,7 @@ async fn main(spawner: Spawner) -> ! { zynq7000_hal::log::rb::init(log::LevelFilter::Trace); - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); let led = Output::new_for_mio(mio_pins.mio7, PinState::Low); diff --git a/examples/embassy/src/bin/pwm.rs b/examples/embassy/src/bin/pwm.rs index 62a80f1..6f1a9a1 100644 --- a/examples/embassy/src/bin/pwm.rs +++ b/examples/embassy/src/bin/pwm.rs @@ -91,7 +91,7 @@ async fn main(_spawner: Spawner) -> ! { ) }; - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); let mut ticker = Ticker::every(Duration::from_millis(1000)); diff --git a/examples/embassy/src/main.rs b/examples/embassy/src/main.rs index c943ca5..19bdfda 100644 --- a/examples/embassy/src/main.rs +++ b/examples/embassy/src/main.rs @@ -77,7 +77,7 @@ async fn main(_spawner: Spawner) -> ! { ) }; - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); let mut ticker = Ticker::every(Duration::from_millis(1000)); diff --git a/examples/simple/src/bin/logger.rs b/examples/simple/src/bin/logger.rs index 2a359dc..6ec71a3 100644 --- a/examples/simple/src/bin/logger.rs +++ b/examples/simple/src/bin/logger.rs @@ -79,7 +79,7 @@ pub fn main() -> ! { ) }; - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {boot_mode:?}"); let mut led = Output::new_for_mio(mio_pins.mio7, PinState::Low); diff --git a/examples/simple/src/main.rs b/examples/simple/src/main.rs index dc86303..8bfa308 100644 --- a/examples/simple/src/main.rs +++ b/examples/simple/src/main.rs @@ -64,22 +64,22 @@ pub fn main() -> ! { #[zynq7000_rt::irq] pub fn irq_handler() {} -#[unsafe(no_mangle)] -pub extern "C" fn _abort_handler() { +#[zynq7000_rt::exception(DataAbort)] +fn data_abort_handler(_faulting_addr: usize) -> ! { loop { nop(); } } -#[unsafe(no_mangle)] -pub extern "C" fn _undefined_handler() { +#[zynq7000_rt::exception(Undefined)] +fn undefined_handler(_faulting_addr: usize) -> ! { loop { nop(); } } -#[unsafe(no_mangle)] -pub extern "C" fn _prefetch_handler() { +#[zynq7000_rt::exception(PrefetchAbort)] +fn prefetch_handler(_faulting_addr: usize) -> ! { loop { nop(); } diff --git a/examples/zedboard/src/bin/ethernet.rs b/examples/zedboard/src/bin/ethernet.rs index 84da268..fc66550 100644 --- a/examples/zedboard/src/bin/ethernet.rs +++ b/examples/zedboard/src/bin/ethernet.rs @@ -249,7 +249,7 @@ async fn main(spawner: Spawner) -> ! { // Safety: We are not multi-threaded yet. unsafe { zynq7000_hal::log::uart_blocking::init_unsafe_single_core(uart, LOG_LEVEL, false) }; - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); static ETH_RX_BUFS: static_cell::ConstStaticCell<[AlignedBuffer; NUM_RX_SLOTS]> = diff --git a/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs b/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs index 1b665d1..20afae3 100644 --- a/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs +++ b/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs @@ -93,7 +93,7 @@ async fn main(_spawner: Spawner) -> ! { ) }; - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); let pin_sel = match I2C_ADDR_SEL { diff --git a/examples/zedboard/src/bin/l3gd20h-spi-mio.rs b/examples/zedboard/src/bin/l3gd20h-spi-mio.rs index a0e4ceb..ceee881 100644 --- a/examples/zedboard/src/bin/l3gd20h-spi-mio.rs +++ b/examples/zedboard/src/bin/l3gd20h-spi-mio.rs @@ -96,7 +96,7 @@ async fn main(spawner: Spawner) -> ! { .unwrap(); zynq7000_hal::log::rb::init(log::LevelFilter::Trace); - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); if DEBUG_SPI_CLK_CONFIG { diff --git a/examples/zedboard/src/bin/uart-blocking.rs b/examples/zedboard/src/bin/uart-blocking.rs index 5d98926..568d320 100644 --- a/examples/zedboard/src/bin/uart-blocking.rs +++ b/examples/zedboard/src/bin/uart-blocking.rs @@ -160,7 +160,7 @@ async fn main(_spawner: Spawner) -> ! { ) }; - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); let mut ticker = Ticker::every(Duration::from_millis(1000)); diff --git a/examples/zedboard/src/bin/uart-non-blocking.rs b/examples/zedboard/src/bin/uart-non-blocking.rs index 5dc3e6c..107be3d 100644 --- a/examples/zedboard/src/bin/uart-non-blocking.rs +++ b/examples/zedboard/src/bin/uart-non-blocking.rs @@ -254,7 +254,7 @@ async fn main(spawner: Spawner) -> ! { ) }; - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); let mio_led = Output::new_for_mio(gpio_pins.mio.mio7, PinState::Low); diff --git a/examples/zedboard/src/main.rs b/examples/zedboard/src/main.rs index d2c5c6d..8ee3160 100644 --- a/examples/zedboard/src/main.rs +++ b/examples/zedboard/src/main.rs @@ -79,7 +79,7 @@ async fn main(_spawner: Spawner) -> ! { ) }; - let boot_mode = BootMode::new(); + let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); let mut ticker = Ticker::every(Duration::from_millis(200)); diff --git a/fsbl/Cargo.toml b/fsbl/Cargo.toml new file mode 100644 index 0000000..e8f8693 --- /dev/null +++ b/fsbl/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "fsbl" +version = "0.1.0" +authors = ["Robin Mueller "] +edition = "2024" +description = "Rust First Stage Bootloader for the Zynq7000 SoC" +homepage = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs" +repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs" +license = "MIT OR Apache-2.0" + +[dependencies] +cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2", features = ["critical-section-single-core"] } +zynq7000-rt = { path = "../zynq7000-rt" } +zynq7000 = { path = "../zynq7000" } +zynq7000-hal = { path = "../zynq7000-hal" } +embedded-io = "0.6" +embedded-hal = "1" +fugit = "0.3" +log = "0.4" +arbitrary-int = "1.3" + +[profile.release] +codegen-units = 1 +debug = true +lto = true diff --git a/fsbl/memory.x b/fsbl/memory.x new file mode 100644 index 0000000..226f8a1 --- /dev/null +++ b/fsbl/memory.x @@ -0,0 +1,24 @@ +MEMORY +{ + /* The Zynq7000 has 192 kB of OCM memory which can be used for the FSBL */ + CODE(rx) : ORIGIN = 0x00000000, LENGTH = 192K + OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K + /* Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This can + be used for something like DMA descriptors, but the DDR needs to be set up first in addition + to configuring the page at address 0x400_0000 accordingly */ + UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M +} + +REGION_ALIAS("DATA", CODE); + +SECTIONS +{ + /* Uncached memory */ + .uncached (NOLOAD) : ALIGN(4) { + . = ALIGN(4); + _sbss_uncached = .; + *(.uncached .uncached.*); + . = ALIGN(4); + _ebss_uncached = .; + } > UNCACHED +} diff --git a/fsbl/src/ddr_cfg.rs b/fsbl/src/ddr_cfg.rs new file mode 100644 index 0000000..e7147de --- /dev/null +++ b/fsbl/src/ddr_cfg.rs @@ -0,0 +1,56 @@ +use zynq7000::slcr::ddriob::{DciType, DdriobConfig, InputType, OutputEnable}; +use zynq7000_hal::ddr::DdriobConfigSet; + +const DDRIOB_ADDR_CFG: DdriobConfig = DdriobConfig::builder() + .with_pullup_enable(false) + .with_output_enable(OutputEnable::OBuf) + .with_term_disable_mode(false) + .with_ibuf_disable_mode(false) + .with_dci_type(DciType::Disabled) + .with_termination_enable(false) + .with_dci_update_enable(false) + .with_inp_type(InputType::Off) + .build(); + +const DDRIOB_DATA_CFG: DdriobConfig = DdriobConfig::builder() + .with_pullup_enable(false) + .with_output_enable(OutputEnable::OBuf) + .with_term_disable_mode(false) + .with_ibuf_disable_mode(false) + .with_dci_type(DciType::DciTermination) + .with_termination_enable(true) + .with_dci_update_enable(false) + .with_inp_type(InputType::VRefBasedDifferentialReceiverForSstlHstl) + .build(); + +const DDRIOB_DIFF_CFG: DdriobConfig = DdriobConfig::builder() + .with_pullup_enable(false) + .with_output_enable(OutputEnable::OBuf) + .with_term_disable_mode(false) + .with_ibuf_disable_mode(false) + .with_dci_type(DciType::DciTermination) + .with_termination_enable(true) + .with_dci_update_enable(false) + .with_inp_type(InputType::DifferentialInputReceiver) + .build(); + +const DDRIOB_CLOCK_CFG: DdriobConfig = DdriobConfig::builder() + .with_pullup_enable(false) + .with_output_enable(OutputEnable::OBuf) + .with_term_disable_mode(false) + .with_ibuf_disable_mode(false) + .with_dci_type(DciType::Disabled) + .with_termination_enable(false) + .with_dci_update_enable(false) + .with_inp_type(InputType::Off) + .build(); + +pub const DDRIOB_CFG_SET_ZEDBOARD: DdriobConfigSet = DdriobConfigSet { + addr0: DDRIOB_ADDR_CFG, + addr1: DDRIOB_ADDR_CFG, + data0: DDRIOB_DATA_CFG, + data1: DDRIOB_DATA_CFG, + diff0: DDRIOB_DIFF_CFG, + diff1: DDRIOB_DIFF_CFG, + clock: DDRIOB_CLOCK_CFG, +}; diff --git a/fsbl/src/main.rs b/fsbl/src/main.rs new file mode 100644 index 0000000..04cabcc --- /dev/null +++ b/fsbl/src/main.rs @@ -0,0 +1,101 @@ +//! Simple FSBL. +#![no_std] +#![no_main] + +use arbitrary_int::u6; +use core::panic::PanicInfo; +use cortex_ar::asm::nop; +use log::error; +use zynq7000_hal::{ + BootMode, + clocks::pll::{PllConfig, configure_arm_pll, configure_ddr_pll, configure_io_pll}, + ddr::{calculate_dci_divisors, calibrate_iob_impedance_for_ddr3, configure_iob}, + time::Hertz, +}; +use zynq7000_rt as _; + +pub mod ddr_cfg; + +// PS clock input frequency. +const PS_CLK: Hertz = Hertz::from_raw(33_333_333); + +/// 1600 MHz. +const ARM_CLK: Hertz = Hertz::from_raw(1_600_000_000); +/// 1000 MHz. +const IO_CLK: Hertz = Hertz::from_raw(1_000_000_000); + +/// DDR frequency for the MT41K128M16JT-125 device. +const DDR_FREQUENCY: Hertz = Hertz::from_raw(533_333_333); + +/// 1067 MHz. +const DDR_CLK: Hertz = Hertz::from_raw(2 * DDR_FREQUENCY.raw()); + +/// 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(), + ); + + // 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(PS_CLK, DDR_CLK).unwrap(), + ); + // Safety: Only done once here during start-up. + let ddr_clks = unsafe { + zynq7000_hal::clocks::DdrClocks::new_with_2x_3x_init(DDR_CLK, u6::new(2), u6::new(3)) + }; + let dci_clk_cfg = calculate_dci_divisors(&ddr_clks); + calibrate_iob_impedance_for_ddr3(dci_clk_cfg, false); + configure_iob(&ddr_cfg::DDRIOB_CFG_SET_ZEDBOARD); + + loop { + cortex_ar::asm::nop(); + } +} + +#[zynq7000_rt::exception(DataAbort)] +fn data_abort_handler(_faulting_addr: usize) -> ! { + loop { + nop(); + } +} + +#[zynq7000_rt::exception(Undefined)] +fn undefined_handler(_faulting_addr: usize) -> ! { + loop { + nop(); + } +} + +#[zynq7000_rt::exception(PrefetchAbort)] +fn prefetch_handler(_faulting_addr: usize) -> ! { + loop { + nop(); + } +} + +/// Panic handler +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + error!("Panic: {info:?}"); + loop {} +} diff --git a/memory.x b/memory.x index 134a689..11faa59 100644 --- a/memory.x +++ b/memory.x @@ -1,8 +1,7 @@ - MEMORY { /* Zedboard: 512 MB DDR3. Only use 63 MB for now, should be plenty for a bare-metal app. - Leave 1 MB of memory which will be configured as uncached device memory by the MPU. This is + Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This is recommended for something like DMA descriptors. */ CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M diff --git a/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/zynq7000-hal/src/clocks.rs b/zynq7000-hal/src/clocks/mod.rs similarity index 87% rename from zynq7000-hal/src/clocks.rs rename to zynq7000-hal/src/clocks/mod.rs index f9370da..5b008f0 100644 --- a/zynq7000-hal/src/clocks.rs +++ b/zynq7000-hal/src/clocks/mod.rs @@ -1,5 +1,7 @@ //! Clock module. -use arbitrary_int::Number; +use arbitrary_int::{Number, u6}; + +pub mod pll; use zynq7000::slcr::{ ClockControl, @@ -45,21 +47,67 @@ impl ArmClocks { #[derive(Debug)] pub struct DdrClocks { + /// DDR reference clock generated by the DDR PLL. ref_clk: Hertz, ddr_3x_clk: Hertz, ddr_2x_clk: Hertz, } impl DdrClocks { + /// Update the DDR 3x and 2x clocks in the SLCR. + /// + /// Usually, the DDR PLL output clock will be set to an even multiple of the DDR operating + /// frequency. In that case, the multiplicator should be used as the DDR 3x clock divisor. + /// The DDR 2x clock divisor should be set so that the resulting clock is 2/3 of the DDR + /// operating frequency. + /// + /// # Safety + /// + /// This should only be called once during start-up. It accesses the SLCR register. + pub unsafe fn configure_2x_3x_clk(ddr_3x_div: u6, ddr_2x_div: u6) { + // Safety: The DDR clock structure is a singleton. + unsafe { + crate::slcr::Slcr::with(|slcr| { + slcr.clk_ctrl().modify_ddr_clk_ctrl(|mut val| { + val.set_div_3x_clk(ddr_3x_div); + val.set_div_2x_clk(ddr_2x_div); + val + }); + }); + } + } + + /// Update the DDR 3x and 2x clocks in the SLCR and creates a DDR clock information structure. + /// + /// Usually, the DDR PLL output clock will be set to an even multiple of the DDR operating + /// frequency. In that case, the multiplicator should be used as the DDR 3x clock divisor. + /// The DDR 2x clock divisor should be set so that the resulting clock is 2/3 of the DDR + /// operating frequency. + /// + /// # Safety + /// + /// This should only be called once during start-up. It accesses the SLCR register. + pub unsafe fn new_with_2x_3x_init(ref_clk: Hertz, ddr_3x_div: u6, ddr_2x_div: u6) -> Self { + unsafe { Self::configure_2x_3x_clk(ddr_3x_div, ddr_2x_div) }; + Self { + ref_clk, + ddr_3x_clk: ref_clk / ddr_3x_div.as_u32(), + ddr_2x_clk: ref_clk / ddr_2x_div.as_u32(), + } + } + /// Reference clock provided by DDR PLL which is used to calculate all other clock frequencies. pub const fn ref_clk(&self) -> Hertz { self.ref_clk } + /// DDR 3x clock which is used by the DRAM and must be set to the operating frequency. pub fn ddr_3x_clk(&self) -> Hertz { self.ddr_3x_clk } + /// DDR 2x clock is used by the interconnect and is typically set to 2/3 of the operating + /// frequency. pub fn ddr_2x_clk(&self) -> Hertz { self.ddr_2x_clk } @@ -201,9 +249,9 @@ impl Clocks { pub fn new_from_regs(ps_clk_freq: Hertz) -> Result { let mut clk_regs = unsafe { ClockControl::new_mmio_fixed() }; - let arm_pll_cfg = clk_regs.read_arm_pll(); - let io_pll_cfg = clk_regs.read_io_pll(); - let ddr_pll_cfg = clk_regs.read_ddr_pll(); + let arm_pll_cfg = clk_regs.read_arm_pll_ctrl(); + let io_pll_cfg = clk_regs.read_io_pll_ctrl(); + let ddr_pll_cfg = clk_regs.read_ddr_pll_ctrl(); if arm_pll_cfg.fdiv().as_u32() == 0 || io_pll_cfg.fdiv().as_u32() == 0 diff --git a/zynq7000-hal/src/clocks/pll.rs b/zynq7000-hal/src/clocks/pll.rs new file mode 100644 index 0000000..ed04c10 --- /dev/null +++ b/zynq7000-hal/src/clocks/pll.rs @@ -0,0 +1,356 @@ +use core::sync::atomic::AtomicBool; + +use arbitrary_int::{u4, u7, u10}; + +use crate::{BootMode, time::Hertz}; + +/// Minimal value based on Zynq-7000 TRM Table 25-6, p.744 +pub const PLL_MUL_MIN: u32 = 13; +/// Maximum value based on Zynq-7000 TRM Table 25-6, p.744 +pub const PLL_MUL_MAX: u32 = 66; + +static ARM_PLL_INIT: AtomicBool = AtomicBool::new(false); +static IO_PLL_INIT: AtomicBool = AtomicBool::new(false); +static DDR_PLL_INIT: AtomicBool = AtomicBool::new(false); + +#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] +#[error("pll muliplier value {0} is out of range ({PLL_MUL_MIN}..={PLL_MUL_MAX})")] +pub struct MulOutOfRangeError(pub u32); + +#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] +pub enum PllConfigCtorError { + #[error("invalid input")] + InvalidInput, + #[error("pll multiplier out of range: {0}")] + MulOutOfRange(#[from] MulOutOfRangeError), +} + +pub struct PllConfig { + fdiv: u7, + charge_pump: u4, + loop_resistor: u4, + lock_count: u10, +} + +impl PllConfig { + pub fn new_from_target_clock( + ps_clk: Hertz, + target_clk: Hertz, + ) -> Result { + if ps_clk.raw() == 0 { + return Err(PllConfigCtorError::InvalidInput); + } + let mul = target_clk / ps_clk; + Self::new(mul).map_err(PllConfigCtorError::MulOutOfRange) + } + /// Create a new PLL configuration based on the multiplier value. + /// + /// These configuration values are based on the Zynq-7000 TRM Table 25-6, p.744. + pub fn new(pll_mul: u32) -> Result { + if !(PLL_MUL_MIN..=PLL_MUL_MAX).contains(&pll_mul) { + return Err(MulOutOfRangeError(pll_mul)); + } + + Ok(match pll_mul { + 13 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(6), + u10::new(750), + ), + 14 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(6), + u10::new(700), + ), + 15 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(6), + u10::new(650), + ), + 16 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(10), + u10::new(625), + ), + 17 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(10), + u10::new(575), + ), + 18 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(10), + u10::new(550), + ), + 19 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(10), + u10::new(525), + ), + 20 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(12), + u10::new(500), + ), + 21 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(12), + u10::new(475), + ), + 22 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(12), + u10::new(450), + ), + 23 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(12), + u10::new(425), + ), + 24..=25 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(12), + u10::new(400), + ), + 26 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(12), + u10::new(375), + ), + 27..=28 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(12), + u10::new(350), + ), + + 29..=30 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(12), + u10::new(325), + ), + 31..=33 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(2), + u10::new(300), + ), + 34..=36 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(2), + u10::new(275), + ), + 37..=40 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(2), + u10::new(250), + ), + 41..=47 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(3), + u4::new(12), + u10::new(250), + ), + 48..=66 => Self::new_raw( + u7::new(pll_mul as u8), + u4::new(2), + u4::new(4), + u10::new(250), + ), + _ => { + unreachable!() + } + }) + } + + /// Create a new PLL configuration with raw values. + /// + /// It is recommended to use [Self::new] instead, which creates a configuration + /// based on a look-up table provided in the Zynq-7000 TRM. + pub fn new_raw(fdiv: u7, charge_pump: u4, loop_resistor: u4, lock_count: u10) -> Self { + Self { + fdiv, + charge_pump, + loop_resistor, + lock_count, + } + } +} + +/// This function configures the ARM PLL based on the provided [PllConfig]. +pub fn configure_arm_pll(boot_mode: BootMode, pll_config: PllConfig) { + if ARM_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) { + return; + } + // Safety: This will only run at most once because of the atomic boolean check. + unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) }; +} + +/// This function configures the IO PLL based on the provided [PllConfig]. +pub fn configure_io_pll(boot_mode: BootMode, pll_config: PllConfig) { + if IO_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) { + return; + } + // Safety: This will only run at most once because of the atomic boolean check. + unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) }; +} + +/// This function configures the DDR PLL based on the provided [PllConfig]. +pub fn configure_ddr_pll(boot_mode: BootMode, pll_config: PllConfig) { + if DDR_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) { + return; + } + // Safety: This will only run at most once because of the atomic boolean check. + unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) }; +} + +/// This function configures the ARM PLL basejjon the provided [PllConfig]. +/// +/// # Safety +/// +/// This function should only be called once during system initialization, for example in the +/// first-stage bootloader (FSBL). +pub unsafe fn configure_arm_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) { + unsafe { + crate::slcr::Slcr::with(|slcr| { + let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_arm_pll_ctrl(); + let pll_cfg_reg = slcr.clk_ctrl().pointer_to_arm_pll_cfg(); + configure_pll_unchecked( + boot_mode, + pll_config, + PllType::Arm, + slcr, + pll_ctrl_reg, + pll_cfg_reg, + ); + }); + } +} + +/// This function configures the IO PLL based on the provided [PllConfig]. +/// +/// # Safety +/// +/// This function should only be called once during system initialization, for example in the +/// first-stage bootloader (FSBL). +pub unsafe fn configure_io_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) { + unsafe { + crate::slcr::Slcr::with(|slcr| { + let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_io_pll_ctrl(); + let pll_cfg_reg = slcr.clk_ctrl().pointer_to_io_pll_cfg(); + configure_pll_unchecked( + boot_mode, + pll_config, + PllType::Io, + slcr, + pll_ctrl_reg, + pll_cfg_reg, + ); + }); + } +} + +/// This function configures the DDR PLL based on the provided [PllConfig]. +/// +/// # Safety +/// +/// This function should only be called once during system initialization, for example in the +/// first-stage bootloader (FSBL). +pub unsafe fn configure_ddr_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) { + unsafe { + crate::slcr::Slcr::with(|slcr| { + let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_ddr_pll_ctrl(); + let pll_cfg_reg = slcr.clk_ctrl().pointer_to_ddr_pll_cfg(); + configure_pll_unchecked( + boot_mode, + pll_config, + PllType::Ddr, + slcr, + pll_ctrl_reg, + pll_cfg_reg, + ); + }); + } +} + +enum PllType { + Io, + Ddr, + Arm, +} + +impl PllType { + pub const fn bit_offset_pll_locked(&self) -> usize { + match self { + PllType::Io => 2, + PllType::Ddr => 1, + PllType::Arm => 0, + } + } +} + +unsafe fn configure_pll_unchecked( + boot_mode: BootMode, + cfg: PllConfig, + pll_type: PllType, + slcr: &mut zynq7000::slcr::MmioSlcr<'static>, + pll_ctrl_reg: *mut zynq7000::slcr::clocks::PllControl, + pll_cfg_reg: *mut zynq7000::slcr::clocks::PllConfig, +) { + // Step 1: Program the multiplier and other PLL configuration parameters. + // New values will only be consumed once the PLL is reset. + let mut pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) }; + pll_ctrl.set_fdiv(cfg.fdiv); + unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) }; + + let mut pll_cfg = unsafe { core::ptr::read_volatile(pll_cfg_reg) }; + pll_cfg.set_charge_pump(cfg.charge_pump); + pll_cfg.set_loop_resistor(cfg.loop_resistor); + pll_cfg.set_lock_count(cfg.lock_count); + unsafe { core::ptr::write_volatile(pll_cfg_reg, pll_cfg) }; + + // Step 2: Force the PLL into bypass mode. If the PLL bypass mode pin is tied high, + // the PLLs need to be enabled. According to the TRM, this is done by setting the + // PLL_BYPASS_QUAL bit to 0, which de-asserts the reset to the Arm PLL. + pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) }; + if boot_mode.pll_config() == zynq7000::slcr::BootPllConfig::Bypassed { + pll_ctrl.set_bypass_qual(false); + } + pll_ctrl.set_bypass_force(true); + pll_ctrl.set_pwrdwn(false); + unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) }; + + // Step 3: Reset the PLL. This also loads the new configuration. + pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) }; + pll_ctrl.set_reset(true); + unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) }; + pll_ctrl.set_reset(false); + unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) }; + + while ((slcr.clk_ctrl().read_pll_status().raw_value() >> pll_type.bit_offset_pll_locked()) + & 0b1) + != 1 + { + cortex_ar::asm::nop(); + } + + pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) }; + pll_ctrl.set_bypass_force(false); + unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) }; +} diff --git a/zynq7000-hal/src/ddr.rs b/zynq7000-hal/src/ddr.rs new file mode 100644 index 0000000..b8a9350 --- /dev/null +++ b/zynq7000-hal/src/ddr.rs @@ -0,0 +1,564 @@ +use arbitrary_int::{Number, u2, u3, u4, u5, u6, u7, u9, u10, u11, u12, u20}; +use zynq7000::{ + ddrc::{MmioDdrController, regs::*}, + 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, +} + +pub fn calculate_dci_divisors(ddr_clks: &DdrClocks) -> DciClkConfig { + calculate_dci_divisors_with_ddr_clk(ddr_clks.ref_clk()) +} + +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 +} + +pub 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. +pub fn calibrate_iob_impedance_for_ddr3(dci_clk_cfg: DciClkConfig, poll_for_done: bool) { + 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. +pub 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(); + } + } + }); +} + +pub struct DdriobConfigSet { + pub addr0: DdriobConfig, + pub addr1: DdriobConfig, + pub data0: DdriobConfig, + pub data1: DdriobConfig, + pub diff0: DdriobConfig, + pub diff1: DdriobConfig, + pub clock: DdriobConfig, +} + +pub fn configure_iob(cfg_set: &DdriobConfigSet) { + 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); + }); +} + +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], +} + +const DDRC_CONFIG_ZEDBOARD: DdrcConfigSet = DdrcConfigSet { + ctrl: DdrcControl::builder() + .with_disable_auto_refresh(false) + .with_disable_active_bypass(false) + .with_disable_read_bypass(false) + .with_read_write_idle_gap(u7::new(1)) + .with_burst8_refresh(u3::new(0)) + .with_data_bus_width(DataBusWidth::_32Bit) + .with_power_down_enable(false) + .with_soft_reset(SoftReset::Reset) + .build(), + two_rank: TwoRankConfig::builder() + .with_addrmap_cs_bit0(u5::new(0)) + .with_ddrc_active_ranks(u2::new(1)) + .with_rfc_nom_x32(u12::new(0x82)) + .build(), + hpr: LprHprQueueControl::builder() + .with_xact_run_length(u4::new(0xf)) + .with_max_starve_x32(u11::new(0xf)) + .with_min_non_critical_x32(u11::new(0xf)) + .build(), + lpr: LprHprQueueControl::builder() + .with_xact_run_length(u4::new(0x8)) + .with_max_starve_x32(u11::new(0x2)) + .with_min_non_critical_x32(u11::new(0x1)) + .build(), + wr: WriteQueueControl::builder() + .with_max_starve_x32(u11::new(0x2)) + .with_xact_run_length(u4::new(0x8)) + .with_min_non_critical_x32(u11::new(0x1)) + .build(), + dram_param_0: DramParamReg0::builder() + .with_post_selfref_gap_x32(u7::new(0x10)) + .with_t_rfc_min(0x56) + .with_t_rc(u6::new(0x1b)) + .build(), + dram_param_1: DramParamReg1::builder() + .with_t_cke(u4::new(0x4)) + .with_t_ras_min(u5::new(0x13)) + .with_t_ras_max(u6::new(0x24)) + .with_t_faw(u6::new(0x16)) + .with_powerdown_to_x32(u5::new(0x6)) + .with_wr2pre(u5::new(0x13)) + .build(), + dram_param_2: DramParamReg2::builder() + .with_t_rcd(u4::new(0x7)) + .with_rd2pre(u5::new(0x5)) + .with_pad_pd(u3::new(0x0)) + .with_t_xp(u5::new(0x5)) + .with_wr2rd(u5::new(0xf)) + .with_rd2wr(u5::new(0x7)) + .with_write_latency(u5::new(0x5)) + .build(), + dram_param_3: DramParamReg3::builder() + .with_disable_pad_pd_feature(false) + .with_read_latency(u5::new(0x7)) + .with_enable_dfi_dram_clk_disable(false) + .with_mobile(MobileSetting::Ddr2Ddr3) + .with_sdram(false) + .with_refresh_to_x32(u5::new(0x8)) + .with_t_rp(u4::new(0x7)) + .with_refresh_margin(u4::new(0x2)) + .with_t_rrd(u3::new(0x6)) + .with_t_ccd(u3::new(0x4)) + .build(), + dram_param_4: DramParamReg4::builder() + .with_mr_rdata_valid(false) + .with_mr_type(ModeRegisterType::Write) + .with_mr_wr_busy(false) + .with_mr_data(0x0) + .with_mr_addr(u2::new(0x0)) + .with_mr_wr(false) + .with_prefer_write(false) + .with_enable_2t_timing_mode(false) + .build(), + dram_init_param: DramInitParam::builder() + .with_t_mrd(u3::new(0x4)) + .with_pre_ocd_x32(u4::new(0x0)) + .with_final_wait_x32(u7::new(0x7)) + .build(), + dram_emr: DramEmr::builder().with_emr3(0x0).with_emr2(0x8).build(), + dram_emr_mr: DramEmrMr::builder().with_emr(0x4).with_mr(0xb30).build(), + dram_burst8_rdwr: DramBurst8ReadWrite::builder() + .with_burst_rdwr(u4::new(0x4)) + .with_pre_cke_x1024(u10::new(0x16d)) + .with_post_cke_x1024(u10::new(0x1)) + .with_burstchop(false) + .build(), + disable_dq: DisableDq::builder() + .with_dis_dq(false) + .with_force_low_pri_n(false) + .build(), + dram_addr_map_bank: DramAddrMapBank::builder() + .with_addrmap_bank_b6(u4::new(0)) + .with_addrmap_bank_b5(u4::new(0)) + .with_addrmap_bank_b2(u4::new(0x7)) + .with_addrmap_bank_b1(u4::new(0x7)) + .with_addrmap_bank_b0(u4::new(0x7)) + .build(), + dram_addr_map_col: DramAddrMapColumn::builder() + .with_addrmap_col_b11(u4::new(0xf)) + .with_addrmap_col_b10(u4::new(0xf)) + .with_addrmap_col_b9(u4::new(0xf)) + .with_addrmap_col_b8(u4::new(0x0)) + .with_addrmap_col_b7(u4::new(0x0)) + .with_addrmap_col_b4(u4::new(0x0)) + .with_addrmap_col_b3(u4::new(0x0)) + .with_addrmap_col_b2(u4::new(0x0)) + .build(), + dram_addr_map_row: DramAddrMapRow::builder() + .with_addrmap_row_b15(u4::new(0xf)) + .with_addrmap_row_b14(u4::new(0xf)) + .with_addrmap_row_b13(u4::new(0x6)) + .with_addrmap_row_b12(u4::new(0x6)) + .with_addrmap_row_b2_11(u4::new(0x6)) + .with_addrmap_row_b1(u4::new(0x6)) + .with_addrmap_row_b0(u4::new(0x4)) + .build(), + dram_odt: DramOdt::builder() + .with_phy_idle_local_odt(u2::new(0x0)) + .with_phy_write_local_odt(u2::new(0x3)) + .with_phy_read_local_odt(u2::new(0x0)) + .with_rank0_wr_odt(u3::new(0x1)) + .with_rank0_rd_odt(u3::new(0x0)) + .build(), + phy_cmd_timeout_rddata_cpt: PhyCmdTimeoutRdDataCpt::builder() + .with_wrlvl_num_of_dq0(u4::new(0x7)) + .with_gatelvl_num_of_dq0(u4::new(0x7)) + .with_clk_stall_level(false) + .with_dis_phy_ctrl_rstn(false) + .with_rdc_fifo_rst_err_cnt_clr(false) + .with_use_fixed_re(true) + .with_rdc_we_to_re_delay(u4::new(0x8)) + .with_wr_cmd_to_data(u4::new(0x0)) + .with_rd_cmd_to_data(u4::new(0x0)) + .build(), + dll_calib: DllCalib::builder().with_sel(DllCalibSel::Periodic).build(), + odt_delay_hold: OdtDelayHold::builder() + .with_wr_odt_hold(u4::new(0x5)) + .with_rd_odt_hold(u4::new(0x0)) + .with_wr_odt_delay(u4::new(0x0)) + .with_rd_odt_delay(u4::new(0x3)) + .build(), + ctrl_reg1: CtrlReg1::builder() + .with_selfref_enable(false) + .with_dis_collision_page_opt(false) + .with_dis_wc(false) + .with_refresh_update_level(false) + .with_auto_pre_en(false) + .with_lpr_num_entries(u6::new(0x1f)) + .with_pageclose(false) + .build(), + ctrl_reg2: CtrlReg2::builder() + .with_go_2_critcal_enable(true) + .with_go_2_critical_hysteresis(0x0) + .build(), + ctrl_reg3: CtrlReg3::builder() + .with_dfi_t_wlmrd(u10::new(0x28)) + .with_rdlvl_rr(0x41) + .with_wrlvl_ww(0x41) + .build(), + ctrl_reg4: CtrlReg4::builder() + .with_dfi_t_ctrlupd_interval_max_x1024(0x16) + .with_dfi_t_ctrlupd_interval_min_x1024(0x10) + .build(), + ctrl_reg5: CtrlReg5::builder() + .with_t_ckesr(u6::new(0x4)) + .with_t_cksrx(u4::new(0x6)) + .with_t_ckrse(u4::new(0x6)) + .with_dfi_t_dram_clk_enable(u4::new(0x1)) + .with_dfi_t_dram_clk_disable(u4::new(0x1)) + .with_dfi_t_ctrl_delay(u4::new(0x1)) + .build(), + ctrl_reg6: CtrlReg6::builder() + .with_t_cksx(u4::new(0x3)) + .with_t_ckdpdx(u4::new(0x2)) + .with_t_ckdpde(u4::new(0x2)) + .with_t_ckpdx(u4::new(0x2)) + .with_t_ckpde(u4::new(0x2)) + .build(), + che_t_zq: CheTZq::builder() + .with_t_zq_short_nop(u10::new(0x40)) + .with_t_zq_long_nop(u10::new(0x200)) + .with_t_mode(u10::new(0x200)) + .with_ddr3(true) + .with_dis_auto_zq(false) + .build(), + che_t_zq_short_interval_reg: CheTZqShortInterval::builder() + .with_dram_rstn_x1024(0x69) + .with_t_zq_short_interval(u20::new(0xcb73)) + .build(), + deep_powerdown: DeepPowerdown::builder() + .with_deep_powerdown_to_x1024(0xff) + .with_enable(false) + .build(), + reg_2c: Reg2c::builder() + .with_dfi_rd_data_eye_train(true) + .with_dfi_rd_dqs_gate_level(true) + .with_dfi_wr_level_enable(true) + .with_trdlvl_max_error(false) + .with_twrlvl_max_error(false) + .with_dfi_rdlvl_max_x1024(u12::new(0xfff)) + .with_dfi_wrlvl_max_x1024(u12::new(0xfff)) + .build(), + reg_2d: Reg2d::builder().with_skip_ocd(true).build(), + dfi_timing: DfiTiming::builder() + .with_dfi_t_ctrlup_max(u10::new(0x40)) + .with_dfi_t_ctrlup_min(u10::new(0x3)) + .with_dfi_t_rddata_enable(u5::new(0x6)) + .build(), + che_ecc_ctrl: CheEccControl::builder() + .with_clear_correctable_errors(false) + .with_clear_uncorrectable_errors(false) + .build(), + ecc_scrub: EccScrub::builder() + .with_disable_scrub(true) + .with_ecc_mode(EccMode::NoEcc) + .build(), + phy_receiver_enable: PhyReceiverEnable::builder() + .with_phy_dif_off(u4::new(0)) + .with_phy_dif_on(u4::new(0)) + .build(), + phy_config: [PhyConfig::builder() + .with_dq_offset(u7::new(0x40)) + .with_wrlvl_inc_mode(false) + .with_gatelvl_inc_mode(false) + .with_rdlvl_inc_mode(false) + .with_data_slice_in_use(true) + .build(); 4], + phy_init_ratio: [ + PhyInitRatio::builder() + .with_gatelvl_init_ratio(u10::new(0xcf)) + .with_wrlvl_init_ratio(u10::new(0x3)) + .build(), + PhyInitRatio::builder() + .with_gatelvl_init_ratio(u10::new(0xd0)) + .with_wrlvl_init_ratio(u10::new(0x3)) + .build(), + PhyInitRatio::builder() + .with_gatelvl_init_ratio(u10::new(0xbd)) + .with_wrlvl_init_ratio(u10::new(0x0)) + .build(), + PhyInitRatio::builder() + .with_gatelvl_init_ratio(u10::new(0xc1)) + .with_wrlvl_init_ratio(u10::new(0x0)) + .build(), + ], + phy_rd_dqs_config: [ + PhyDqsConfig::builder() + .with_dqs_slave_delay(u9::new(0x0)) + .with_dqs_slave_force(false) + .with_dqs_slave_ratio(u10::new(0x35)) + .build(), + PhyDqsConfig::builder() + .with_dqs_slave_delay(u9::new(0x0)) + .with_dqs_slave_force(false) + .with_dqs_slave_ratio(u10::new(0x35)) + .build(), + PhyDqsConfig::builder() + .with_dqs_slave_delay(u9::new(0x0)) + .with_dqs_slave_force(false) + .with_dqs_slave_ratio(u10::new(0x35)) + .build(), + PhyDqsConfig::builder() + .with_dqs_slave_delay(u9::new(0x0)) + .with_dqs_slave_force(false) + .with_dqs_slave_ratio(u10::new(0x35)) + .build(), + ], + phy_wr_dqs_config: [ + PhyDqsConfig::builder() + .with_dqs_slave_delay(u9::new(0x0)) + .with_dqs_slave_force(false) + .with_dqs_slave_ratio(u10::new(0x83)) + .build(), + PhyDqsConfig::builder() + .with_dqs_slave_delay(u9::new(0x0)) + .with_dqs_slave_force(false) + .with_dqs_slave_ratio(u10::new(0x83)) + .build(), + PhyDqsConfig::builder() + .with_dqs_slave_delay(u9::new(0x0)) + .with_dqs_slave_force(false) + .with_dqs_slave_ratio(u10::new(0x7f)) + .build(), + PhyDqsConfig::builder() + .with_dqs_slave_delay(u9::new(0x0)) + .with_dqs_slave_force(false) + .with_dqs_slave_ratio(u10::new(0x78)) + .build(), + ], +}; + +pub fn configure_ddr(mut ddrc: MmioDdrController<'static>, cfg_set: &DdrcConfigSet) { + // Lock the DDR. + 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]); + } + } +} diff --git a/zynq7000-hal/src/lib.rs b/zynq7000-hal/src/lib.rs index 5913068..123c50d 100644 --- a/zynq7000-hal/src/lib.rs +++ b/zynq7000-hal/src/lib.rs @@ -13,10 +13,11 @@ extern crate alloc; use slcr::Slcr; -use zynq7000::slcr::LevelShifterRegister; +use zynq7000::slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister}; pub mod cache; pub mod clocks; +pub mod ddr; pub mod eth; pub mod gic; pub mod gpio; @@ -43,36 +44,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 +75,17 @@ impl BootMode { 0b110 => Some(BootDevice::SdCard), _ => None, }; - let pll_config = if (raw_register >> 4) & 0b1 == 0 { - BootPllConfig::Enabled - } else { - BootPllConfig::Bypassed - }; Self { boot_mode, - pll_config, + pll_config: boot_mode_reg.pll_config(), } } - pub fn boot_device(&self) -> Option { + + pub const fn boot_device(&self) -> Option { self.boot_mode } - pub const fn pll_enable(&self) -> BootPllConfig { + pub const fn pll_config(&self) -> BootPllConfig { self.pll_config } } diff --git a/zynq7000-hal/src/slcr.rs b/zynq7000-hal/src/slcr.rs index 1c710ea..20cd90d 100644 --- a/zynq7000-hal/src/slcr.rs +++ b/zynq7000-hal/src/slcr.rs @@ -40,8 +40,8 @@ impl Slcr { /// Returns a mutable reference to the SLCR MMIO block. /// /// The MMIO block will not be unlocked. However, the registers can still be read. - pub fn regs(&mut self) -> &mut MmioSlcr<'static> { - &mut self.0 + pub fn regs(&self) -> &MmioSlcr<'static> { + &self.0 } /// Modify the SLCR register. diff --git a/zynq7000-hal/src/spi/mod.rs b/zynq7000-hal/src/spi/mod.rs index 98f4f75..951979d 100644 --- a/zynq7000-hal/src/spi/mod.rs +++ b/zynq7000-hal/src/spi/mod.rs @@ -1141,8 +1141,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 +1163,7 @@ pub fn calculate_largest_allowed_spi_ref_clk_divisor(clks: &Clocks) -> Option, + #[bit(1, rw)] + power_down_enable: bool, + #[bit(0, rw)] + soft_reset: SoftReset, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct TwoRankConfig { + #[bits(14..=18, rw)] + addrmap_cs_bit0: u5, + /// Reserved register, but for some reason, Xilinx tooling writes a 1 here? + #[bits(12..=13, rw)] + ddrc_active_ranks: u2, + /// tREFI - Average time between refreshes, in multiples of 32 clocks. + #[bits(0..=11, rw)] + rfc_nom_x32: u12, + } + + /// Queue control for the low priority and high priority read queues. + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct LprHprQueueControl { + #[bits(22..=25, rw)] + xact_run_length: u4, + #[bits(11..=21, rw)] + max_starve_x32: u11, + #[bits(0..=10, rw)] + min_non_critical_x32: u11, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct WriteQueueControl { + #[bits(15..=25, rw)] + max_starve_x32: u11, + #[bits(11..=14, rw)] + xact_run_length: u4, + #[bits(0..=10, rw)] + min_non_critical_x32: u11, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramParamReg0 { + /// Minimum time to wait after coming out of self refresh before doing anything. This must be + /// bigger than all the constraints that exist. + #[bits(14..=20, rw)] + post_selfref_gap_x32: u7, + /// tRFC(min) - Minimum time from refresh to refresh or activate in clock + /// cycles. + #[bits(6..=13, rw)] + t_rfc_min: u8, + /// tRC - Min time between activates to the same bank. + #[bits(0..=5, rw)] + t_rc: u6, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramParamReg1 { + #[bits(28..=31, rw)] + t_cke: u4, + #[bits(22..=26, rw)] + t_ras_min: u5, + #[bits(16..=21, rw)] + t_ras_max: u6, + #[bits(10..=15, rw)] + t_faw: u6, + #[bits(5..=9, rw)] + powerdown_to_x32: u5, + #[bits(0..=4, rw)] + wr2pre: u5, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramParamReg2 { + #[bits(28..=31, rw)] + t_rcd: u4, + #[bits(23..=27, rw)] + rd2pre: u5, + #[bits(20..=22, rw)] + pad_pd: u3, + #[bits(15..=19, rw)] + t_xp: u5, + #[bits(10..=14, rw)] + wr2rd: u5, + #[bits(5..=9, rw)] + rd2wr: u5, + #[bits(0..=4, rw)] + write_latency: u5, + } + + /// Weird naming. + #[bitbybit::bitenum(u1, exhaustive = true)] + pub enum MobileSetting { + Ddr2Ddr3 = 0, + Lpddr2 = 1, + } + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramParamReg3 { + #[bit(30, rw)] + disable_pad_pd_feature: bool, + #[bits(24..=28, rw)] + read_latency: u5, + #[bit(23, rw)] + enable_dfi_dram_clk_disable: bool, + /// 0: DDR2 or DDR3. 1: LPDDR2. + #[bit(22, rw)] + mobile: MobileSetting, + /// Must be set to 0. + #[bit(21, rw)] + sdram: bool, + #[bits(16..=20, rw)] + refresh_to_x32: u5, + #[bits(12..=15, rw)] + t_rp: u4, + #[bits(8..=11, rw)] + refresh_margin: u4, + #[bits(5..=7, rw)] + t_rrd: u3, + #[bits(2..=4, rw)] + t_ccd: u3, + } + + #[bitbybit::bitenum(u1, exhaustive = true)] + pub enum ModeRegisterType { + Write = 0, + Read = 1, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramParamReg4 { + #[bit(27, rw)] + mr_rdata_valid: bool, + #[bit(26, rw)] + mr_type: ModeRegisterType, + #[bit(25, rw)] + mr_wr_busy: bool, + #[bits(9..=24, rw)] + mr_data: u16, + #[bits(7..=8, rw)] + mr_addr: u2, + #[bit(6, rw)] + mr_wr: bool, + #[bit(1, rw)] + prefer_write: bool, + #[bit(0, rw)] + enable_2t_timing_mode: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramInitParam { + #[bits(11..=13, rw)] + t_mrd: u3, + #[bits(7..=10, rw)] + pre_ocd_x32: u4, + #[bits(0..=6, rw)] + final_wait_x32: u7, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramEmr { + #[bits(16..=31, rw)] + emr3: u16, + #[bits(0..=15, rw)] + emr2: u16, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramEmrMr { + #[bits(16..=31, rw)] + emr: u16, + #[bits(0..=15, rw)] + mr: u16, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramBurst8ReadWrite { + #[bits(0..=3, rw)] + burst_rdwr: u4, + #[bits(4..=13, rw)] + pre_cke_x1024: u10, + #[bits(16..=25, rw)] + post_cke_x1024: u10, + #[bit(26, rw)] + burstchop: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DisableDq { + #[bit(1, rw)] + dis_dq: bool, + #[bit(0, rw)] + force_low_pri_n: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramAddrMapBank { + #[bits(16..=19, rw)] + addrmap_bank_b6: u4, + #[bits(12..=15, rw)] + addrmap_bank_b5: u4, + #[bits(8..=11, rw)] + addrmap_bank_b2: u4, + #[bits(4..=7, rw)] + addrmap_bank_b1: u4, + #[bits(0..=3, rw)] + addrmap_bank_b0: u4, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramAddrMapColumn { + #[bits(28..=31, rw)] + addrmap_col_b11: u4, + #[bits(24..=27, rw)] + addrmap_col_b10: u4, + #[bits(20..=23, rw)] + addrmap_col_b9: u4, + #[bits(16..=19, rw)] + addrmap_col_b8: u4, + #[bits(12..=15, rw)] + addrmap_col_b7: u4, + #[bits(8..=11, rw)] + addrmap_col_b4: u4, + #[bits(4..=7, rw)] + addrmap_col_b3: u4, + #[bits(0..=3, rw)] + addrmap_col_b2: u4, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramAddrMapRow { + #[bits(24..=27, rw)] + addrmap_row_b15: u4, + #[bits(20..=23, rw)] + addrmap_row_b14: u4, + #[bits(16..=19, rw)] + addrmap_row_b13: u4, + #[bits(12..=15, rw)] + addrmap_row_b12: u4, + #[bits(8..=11, rw)] + addrmap_row_b2_11: u4, + #[bits(4..=7, rw)] + addrmap_row_b1: u4, + #[bits(0..=3, rw)] + addrmap_row_b0: u4, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DramOdt { + #[bits(16..=17, rw)] + phy_idle_local_odt: u2, + #[bits(14..=15, rw)] + phy_write_local_odt: u2, + #[bits(12..=13, rw)] + phy_read_local_odt: u2, + #[bits(3..=5, rw)] + rank0_wr_odt: u3, + #[bits(0..=2, rw)] + rank0_rd_odt: u3, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct PhyCmdTimeoutRdDataCpt { + #[bits(28..=31, rw)] + wrlvl_num_of_dq0: u4, + #[bits(24..=27, rw)] + gatelvl_num_of_dq0: u4, + #[bit(19, rw)] + clk_stall_level: bool, + #[bit(18, rw)] + dis_phy_ctrl_rstn: bool, + #[bit(17, rw)] + rdc_fifo_rst_err_cnt_clr: bool, + #[bit(16, rw)] + use_fixed_re: bool, + #[bits(8..=11, rw)] + rdc_we_to_re_delay: u4, + #[bits(4..=7, rw)] + wr_cmd_to_data: u4, + #[bits(0..=3, rw)] + rd_cmd_to_data: u4, + } + + #[bitbybit::bitenum(u1, exhaustive = true)] + pub enum DllCalibSel { + Periodic = 0, + Manual = 1, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DllCalib { + #[bit(16, rw)] + sel: DllCalibSel, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct OdtDelayHold { + #[bits(12..=15, rw)] + wr_odt_hold: u4, + #[bits(8..=11, rw)] + rd_odt_hold: u4, + #[bits(4..=7, rw)] + wr_odt_delay: u4, + #[bits(0..=3, rw)] + rd_odt_delay: u4, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CtrlReg1 { + #[bit(12, rw)] + selfref_enable: bool, + #[bit(10, rw)] + dis_collision_page_opt: bool, + #[bit(9, rw)] + dis_wc: bool, + #[bit(8, rw)] + refresh_update_level: bool, + #[bit(7, rw)] + auto_pre_en: bool, + #[bits(1..=6, rw)] + lpr_num_entries: u6, + #[bit(0, rw)] + pageclose: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CtrlReg2 { + #[bit(17, rw)] + go_2_critcal_enable: bool, + #[bits(5..=12, rw)] + go_2_critical_hysteresis: u8, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CtrlReg3 { + #[bits(16..=25, rw)] + dfi_t_wlmrd: u10, + #[bits(8..=15, rw)] + rdlvl_rr: u8, + #[bits(0..=7, rw)] + wrlvl_ww: u8, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CtrlReg4 { + #[bits(8..=15, rw)] + dfi_t_ctrlupd_interval_max_x1024: u8, + #[bits(0..=7, rw)] + dfi_t_ctrlupd_interval_min_x1024: u8, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CtrlReg5 { + #[bits(20..=25, rw)] + t_ckesr: u6, + #[bits(16..=19, rw)] + t_cksrx: u4, + #[bits(12..=15, rw)] + t_ckrse: u4, + #[bits(8..=11, rw)] + dfi_t_dram_clk_enable: u4, + #[bits(4..=7, rw)] + dfi_t_dram_clk_disable: u4, + #[bits(0..=3, rw)] + dfi_t_ctrl_delay: u4, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CtrlReg6 { + #[bits(16..=19, rw)] + t_cksx: u4, + #[bits(12..=15, rw)] + t_ckdpdx: u4, + #[bits(8..=11, rw)] + t_ckdpde: u4, + #[bits(4..=7, rw)] + t_ckpdx: u4, + #[bits(0..=3, rw)] + t_ckpde: u4, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CheTZq { + #[bits(22..=31, rw)] + t_zq_short_nop: u10, + #[bits(12..=21, rw)] + t_zq_long_nop: u10, + #[bits(2..=11, rw)] + t_mode: u10, + #[bit(1, rw)] + ddr3: bool, + #[bit(0, rw)] + dis_auto_zq: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CheTZqShortInterval { + #[bits(20..=27, rw)] + dram_rstn_x1024: u8, + #[bits(0..=19, rw)] + t_zq_short_interval: u20, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DeepPowerdown { + #[bits(1..=8, rw)] + deep_powerdown_to_x1024: u8, + #[bit(0, rw)] + enable: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct Reg2c { + #[bit(28, rw)] + dfi_rd_data_eye_train: bool, + #[bit(27, rw)] + dfi_rd_dqs_gate_level: bool, + #[bit(26, rw)] + dfi_wr_level_enable: bool, + #[bit(25, rw)] + trdlvl_max_error: bool, + #[bit(24, rw)] + twrlvl_max_error: bool, + #[bits(12..=23, rw)] + dfi_rdlvl_max_x1024: u12, + #[bits(0..=11, rw)] + dfi_wrlvl_max_x1024: u12, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct Reg2d { + #[bit(9, rw)] + skip_ocd: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct DfiTiming { + #[bits(15..=24, rw)] + dfi_t_ctrlup_max: u10, + #[bits(5..=14, rw)] + dfi_t_ctrlup_min: u10, + #[bits(0..=4, rw)] + dfi_t_rddata_enable: u5, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct CheEccControl { + #[bit(1, rw)] + clear_correctable_errors: bool, + #[bit(0, rw)] + clear_uncorrectable_errors: bool, + } + + #[bitbybit::bitenum(u3, exhaustive = false)] + pub enum EccMode { + NoEcc = 0b000, + SecDecOverOneBeat = 0b100, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct EccScrub { + #[bit(3, rw)] + disable_scrub: bool, + #[bits(0..=2, rw)] + ecc_mode: Option, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct PhyReceiverEnable { + #[bits(4..=7, rw)] + phy_dif_off: u4, + #[bits(0..=3, rw)] + phy_dif_on: u4, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct PhyConfig { + #[bits(24..=30, rw)] + dq_offset: u7, + #[bit(3, rw)] + wrlvl_inc_mode: bool, + #[bit(2, rw)] + gatelvl_inc_mode: bool, + #[bit(1, rw)] + rdlvl_inc_mode: bool, + #[bit(0, rw)] + data_slice_in_use: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct PhyInitRatio { + #[bits(10..=19, rw)] + gatelvl_init_ratio: u10, + #[bits(0..=9, rw)] + wrlvl_init_ratio: u10, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct PhyDqsConfig { + #[bits(11..=19, rw)] + dqs_slave_delay: u9, + #[bit(10, rw)] + dqs_slave_force: bool, + #[bits(0..=9, rw)] + dqs_slave_ratio: u10, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct PhyWriteEnableConfig { + #[bits(12..=20, rw)] + fifo_we_in_delay: u9, + #[bit(11, rw)] + fifo_we_in_force: bool, + #[bits(0..=10, rw)] + fifo_we_slave_ratio: u11, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct PhyWriteDataSlaveConfig { + #[bits(11..=19, rw)] + wr_data_slave_delay: u9, + #[bit(10, rw)] + wr_data_slave_force: bool, + #[bits(0..=9, rw)] + wr_data_slave_ratio: u10, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct Reg64 { + #[bit(30, rw)] + cmd_latency: bool, + #[bit(29, rw)] + lpddr: bool, + #[bits(21..=27, rw)] + ctrl_slave_delay: u7, + #[bit(20, rw)] + ctrl_slave_force: bool, + #[bits(10..=19, rw)] + ctrl_slave_ratio: u10, + #[bit(9, rw)] + sel_logic: bool, + #[bit(7, rw)] + invert_clkout: bool, + #[bit(1, rw)] + bl2: bool, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct Reg65 { + #[bits(18..=19, rw)] + ctrl_slave_delay: u2, + #[bit(17, rw)] + dis_calib_rst: bool, + #[bit(16, rw)] + use_rd_data_eye_level: bool, + #[bit(15, rw)] + use_rd_dqs_gate_level: bool, + #[bit(14, rw)] + use_wr_level: bool, + #[bits(10..=13, rw)] + dll_lock_diff: u4, + #[bits(5..=9, rw)] + rd_rl_delay: u5, + #[bits(0..=4, rw)] + wr_rl_delay: u5, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct AxiPriorityWritePort { + #[bit(18, rw)] + disable_page_match: bool, + #[bit(17, rw)] + disable_urgent: bool, + #[bit(16, rw)] + disable_aging: bool, + #[bits(0..=9, rw)] + pri_wr_port: u10, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct AxiPriorityReadPort { + #[bit(19, rw)] + enable_hpr: bool, + #[bit(18, rw)] + disable_page_match: bool, + #[bit(17, rw)] + disable_urgent: bool, + #[bit(16, rw)] + disable_aging: bool, + #[bits(0..=9, rw)] + pri_wr_port_n: u10, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct ExclusiveAccessConfig { + #[bits(9..=17, rw)] + access_id1_port: u9, + #[bits(0..=8, rw)] + access_id0_port: u9, + } + + #[bitbybit::bitenum(u1, exhaustive = true)] + pub enum LpddrBit { + Ddr2Ddr3 = 0, + Lpddr2 = 1, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct LpddrControl0 { + #[bits(4..=11, rw)] + mr4_margin: u8, + #[bit(2, rw)] + derate_enable: bool, + #[bit(1, rw)] + per_bank_refresh: bool, + #[bit(0, rw)] + lpddr2: LpddrBit, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct LpddrControl1 { + #[bits(0..=31, rw)] + mr4_read_interval: u32, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct LpddrControl2 { + #[bits(12..=21, rw)] + t_mrw: u10, + #[bits(4..=11, rw)] + idle_after_reset_x32: u8, + #[bits(0..=3, rw)] + min_stable_clock_x1: u4, + } + + #[bitbybit::bitfield(u32, default = 0x0)] + pub struct LpddrControl3 { + #[bits(8..=17, rw)] + dev_zqinit_x32: u10, + #[bits(0..=7, rw)] + max_auto_init_x1024: u8, + } +} + +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: u32, + 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, + wr_data_slave: [PhyWriteDataSlaveConfig; 4], + + _reserved9: u32, + + reg_64: Reg64, + reg_65: Reg65, + _reserved10: [u32; 3], + #[mmio(PureRead)] + reg69_6a0: u32, + #[mmio(PureRead)] + reg69_6a1: u32, + _reserved11: u32, + #[mmio(PureRead)] + reg69_6d2: u32, + #[mmio(PureRead)] + reg69_6d3: u32, + #[mmio(PureRead)] + reg69_710: u32, + #[mmio(PureRead)] + reg6e_711: u32, + #[mmio(PureRead)] + reg6e_712: u32, + #[mmio(PureRead)] + reg6e_713: u32, + _reserved12: u32, + #[mmio(PureRead)] + phy_dll_status: [u32; 4], + _reserved13: u32, + #[mmio(PureRead)] + dll_lock_status: u32, + #[mmio(PureRead)] + phy_control_status: u32, + #[mmio(PureRead)] + phy_control_status_2: u32, + + _reserved14: [u32; 0x5], + + // DDRI registers. + #[mmio(PureRead)] + axi_id: u32, + page_mask: u32, + axi_priority_wr_port: [AxiPriorityWritePort; 0x4], + axi_priority_rd_port: [AxiPriorityReadPort; 0x4], + + _reserved15: [u32; 0x1B], + + excl_access_cfg: [ExclusiveAccessConfig; 0x4], + #[mmio(PureRead)] + mode_reg_read: u32, + lpddr_ctrl_0: LpddrControl0, + lpddr_ctrl_1: LpddrControl1, + lpddr_ctrl_2: LpddrControl2, + lpddr_ctrl_3: LpddrControl3, +} + +static_assertions::const_assert_eq!(core::mem::size_of::(), 0x2B8); + +impl DdrController { + /// Create a new DDR MMIO instance for the DDR controller at address [DDRC_BASE_ADDR]. + /// + /// # Safety + /// + /// This API can be used to potentially create a driver to the same peripheral structure + /// from multiple threads. The user must ensure that concurrent accesses are safe and do not + /// interfere with each other. + pub const unsafe fn new_mmio_fixed() -> MmioDdrController<'static> { + unsafe { Self::new_mmio_at(DDRC_BASE_ADDR) } + } +} diff --git a/zynq7000/src/lib.rs b/zynq7000/src/lib.rs index 9dec569..7dafa0b 100644 --- a/zynq7000/src/lib.rs +++ b/zynq7000/src/lib.rs @@ -17,6 +17,7 @@ extern crate std; pub const MPCORE_BASE_ADDR: usize = 0xF8F0_0000; +pub mod ddrc; pub mod eth; pub mod gic; pub mod gpio; @@ -40,6 +41,7 @@ pub struct PsPeripherals { pub gicc: gic::MmioGicCpuInterface<'static>, pub gicd: gic::MmioGicDistributor<'static>, pub l2c: l2_cache::MmioL2Cache<'static>, + pub ddrc: ddrc::MmioDdrController<'static>, pub uart_0: uart::MmioUart<'static>, pub uart_1: uart::MmioUart<'static>, pub spi_0: spi::MmioSpi<'static>, @@ -76,6 +78,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(), diff --git a/zynq7000/src/slcr/clocks.rs b/zynq7000/src/slcr/clocks.rs index cd7838b..bc861bb 100644 --- a/zynq7000/src/slcr/clocks.rs +++ b/zynq7000/src/slcr/clocks.rs @@ -4,18 +4,12 @@ use super::{CLOCK_CONTROL_OFFSET, SLCR_BASE_ADDR}; use arbitrary_int::{u4, u6, u7, u10}; -#[bitbybit::bitenum(u1, exhaustive = true)] -#[derive(Debug)] -pub enum BypassForce { - EnabledOrSetByBootMode = 0b0, - Bypassed = 0b1, -} - -#[bitbybit::bitenum(u1, exhaustive = true)] -#[derive(Debug)] -pub enum BypassQual { - BypassForceBit = 0b0, - BootModeFourthBit = 0b1, +pub enum Bypass { + NotBypassed = 0b00, + /// This is the default reset value. + PinStrapSettings = 0b01, + Bypassed = 0b10, + BypassedRegardlessOfPinStrapping = 0b11, } #[bitbybit::bitfield(u32)] @@ -29,10 +23,10 @@ pub struct PllControl { fdiv: u7, /// Select source for the ARM PLL bypass control #[bit(4, rw)] - bypass_force: BypassForce, + bypass_force: bool, /// Select source for the ARM PLL bypass control #[bit(3, rw)] - bypass_qual: BypassQual, + bypass_qual: bool, // Power-down control #[bit(1, rw)] pwrdwn: bool, @@ -41,6 +35,40 @@ pub struct PllControl { reset: bool, } +impl PllControl { + #[inline] + pub fn set_bypass(&mut self, bypass: Bypass) { + match bypass { + Bypass::NotBypassed => { + self.set_bypass_force(false); + self.set_bypass_qual(false); + } + Bypass::PinStrapSettings => { + self.set_bypass_force(false); + self.set_bypass_qual(true); + } + Bypass::Bypassed => { + self.set_bypass_force(true); + self.set_bypass_qual(false); + } + Bypass::BypassedRegardlessOfPinStrapping => { + self.set_bypass_force(true); + self.set_bypass_qual(true); + } + } + } + + #[inline] + pub fn bypass(&self) -> Bypass { + match (self.bypass_force(), self.bypass_qual()) { + (false, false) => Bypass::NotBypassed, + (false, true) => Bypass::PinStrapSettings, + (true, false) => Bypass::Bypassed, + (true, true) => Bypass::BypassedRegardlessOfPinStrapping, + } + } +} + #[bitbybit::bitfield(u32)] #[derive(Debug)] pub struct PllConfig { @@ -48,26 +76,26 @@ pub struct PllConfig { lock_count: u10, /// Charge Pump control #[bits(8..=11, rw)] - pll_cp: u4, + charge_pump: u4, /// Loop resistor control #[bits(4..=7, rw)] - pll_res: u4, + loop_resistor: u4, } #[bitbybit::bitfield(u32)] #[derive(Debug)] pub struct PllStatus { - #[bit(5)] + #[bit(5, r)] io_pll_stable: bool, - #[bit(4)] + #[bit(4, r)] ddr_pll_stable: bool, - #[bit(3)] + #[bit(3, r)] arm_pll_stable: bool, - #[bit(2)] + #[bit(2, r)] io_pll_lock: bool, - #[bit(1)] + #[bit(1, r)] drr_pll_lock: bool, - #[bit(0)] + #[bit(0, r)] arm_pll_lock: bool, } @@ -141,7 +169,7 @@ pub struct DdrClockControl { ddr_3x_clk_act: bool, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, default = 0x0)] pub struct DciClockControl { /// Second cascade divider. Reset value: 0x1E #[bits(20..=25, rw)] @@ -341,9 +369,9 @@ pub struct AperClockControl { #[derive(derive_mmio::Mmio)] #[repr(C)] pub struct ClockControl { - arm_pll: PllControl, - ddr_pll: PllControl, - io_pll: PllControl, + arm_pll_ctrl: PllControl, + ddr_pll_ctrl: PllControl, + io_pll_ctrl: PllControl, pll_status: PllStatus, arm_pll_cfg: PllConfig, ddr_pll_cfg: PllConfig, diff --git a/zynq7000/src/slcr/ddriob.rs b/zynq7000/src/slcr/ddriob.rs new file mode 100644 index 0000000..b88e395 --- /dev/null +++ b/zynq7000/src/slcr/ddriob.rs @@ -0,0 +1,139 @@ +use arbitrary_int::{u2, u3}; + +#[bitbybit::bitenum(u4, exhaustive = false)] +pub enum VRefSel { + /// VREF = 0.6 V + Lpddr2 = 0b0001, + /// VREF = 0.675 V + Ddr3l = 0b0010, + /// VREF = 0.75 V + Ddr3 = 0b0100, + /// VREF = 0.9 V + Ddr2 = 0b1000, +} + +#[bitbybit::bitfield(u32)] +pub struct DdrControl { + /// Enables VRP/VRN. + #[bit(9, rw)] + refio_enable: bool, + #[bit(6, rw)] + vref_ext_en_upper_bits: bool, + #[bit(5, rw)] + vref_ext_en_lower_bits: bool, + #[bits(1..=4, rw)] + vref_sel: Option, + #[bit(0, rw)] + vref_int_en: bool, +} + +#[bitbybit::bitfield(u32, default = 0x00)] +pub struct DciControl { + #[bit(20, rw)] + update_control: bool, + #[bits(17..=19, rw)] + pref_opt2: u3, + #[bits(14..=15, rw)] + pref_opt1: u2, + #[bits(11..=13, rw)] + nref_opt4: u3, + #[bits(8..=10, rw)] + nref_opt2: u3, + #[bits(6..=7, rw)] + nref_opt1: u2, + #[bit(1, rw)] + enable: bool, + /// Reset value 0. Should be toggled once to initialize flops in DCI system. + #[bit(0, rw)] + reset: bool, +} + +#[bitbybit::bitfield(u32)] +pub struct DciStatus { + #[bit(13, rw)] + done: bool, + #[bit(0, rw)] + lock: bool, +} + +#[bitbybit::bitenum(u2, exhaustive = true)] +#[derive(Debug)] +pub enum OutputEnable { + IBuf = 0b00, + __Reserved0 = 0b01, + __Reserved1 = 0b10, + OBuf = 0b11, +} + +#[bitbybit::bitenum(u2, exhaustive = true)] +#[derive(Debug)] +pub enum InputType { + Off = 0b00, + VRefBasedDifferentialReceiverForSstlHstl = 0b01, + DifferentialInputReceiver = 0b10, + LvcmosReceiver = 0b11, +} + +#[bitbybit::bitenum(u2, exhaustive = true)] +#[derive(Debug)] +pub enum DciType { + Disabled = 0b00, + DciDrive = 0b01, + __Reserved = 0b10, + DciTermination = 0b11, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +pub struct DdriobConfig { + #[bit(11, rw)] + pullup_enable: bool, + #[bits(9..=10, rw)] + output_enable: OutputEnable, + #[bit(8, rw)] + term_disable_mode: bool, + #[bit(7, rw)] + ibuf_disable_mode: bool, + #[bits(5..=6, rw)] + dci_type: DciType, + #[bit(4, rw)] + termination_enable: bool, + #[bit(3, rw)] + dci_update_enable: bool, + #[bits(1..=2, rw)] + inp_type: InputType, +} + +#[derive(derive_mmio::Mmio)] +#[repr(C)] +pub struct DdrIoB { + ddriob_addr0: DdriobConfig, + ddriob_addr1: DdriobConfig, + ddriob_data0: DdriobConfig, + ddriob_data1: DdriobConfig, + ddriob_diff0: DdriobConfig, + ddriob_diff1: DdriobConfig, + ddriob_clock: DdriobConfig, + ddriob_drive_slew_addr: u32, + ddriob_drive_slew_data: u32, + ddriob_drive_slew_diff: u32, + ddriob_drive_slew_clock: u32, + ddr_ctrl: DdrControl, + dci_ctrl: DciControl, + dci_status: DciStatus, +} + +impl DdrIoB { + /// Create a new handle to this peripheral. + /// + /// Writing to this register requires unlocking the SLCR registers first. + /// + /// # Safety + /// + /// If you create multiple instances of this handle at the same time, you are responsible for + /// ensuring that there are no read-modify-write races on any of the registers. + pub unsafe fn new_mmio_fixed() -> MmioDdrIoB<'static> { + unsafe { Self::new_mmio_at(super::SLCR_BASE_ADDR + super::DDRIOB_OFFSET) } + } +} + +static_assertions::const_assert_eq!(core::mem::size_of::(), 0x38); diff --git a/zynq7000/src/slcr/mod.rs b/zynq7000/src/slcr/mod.rs index 072c136..ffd4cea 100644 --- a/zynq7000/src/slcr/mod.rs +++ b/zynq7000/src/slcr/mod.rs @@ -12,44 +12,10 @@ const GPIOB_OFFSET: usize = 0xB00; const DDRIOB_OFFSET: usize = 0xB40; pub mod clocks; +pub mod ddriob; pub mod mio; pub mod reset; -#[derive(derive_mmio::Mmio)] -#[repr(C)] -pub struct DdrIoB { - ddriob_addr0: u32, - ddriob_addr1: u32, - ddriob_data0: u32, - ddriob_data1: u32, - ddriob_diff0: u32, - ddriob_diff1: u32, - ddriob_clock: u32, - ddriob_drive_slew_addr: u32, - ddriob_drive_slew_data: u32, - ddriob_drive_slew_diff: u32, - ddriob_drive_slew_clock: u32, - ddriob_ddr_ctrl: u32, - ddriob_dci_ctrl: u32, - ddriob_dci_status: u32, -} - -impl DdrIoB { - /// Create a new handle to this peripheral. - /// - /// Writing to this register requires unlocking the SLCR registers first. - /// - /// # Safety - /// - /// If you create multiple instances of this handle at the same time, you are responsible for - /// ensuring that there are no read-modify-write races on any of the registers. - pub unsafe fn new_mmio_fixed() -> MmioDdrIoB<'static> { - unsafe { Self::new_mmio_at(SLCR_BASE_ADDR + DDRIOB_OFFSET) } - } -} - -static_assertions::const_assert_eq!(core::mem::size_of::(), 0x38); - #[bitbybit::bitenum(u3, exhaustive = false)] pub enum VrefSel { Disabled = 0b000, @@ -93,11 +59,19 @@ impl GpiobRegisters { } } +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum BootPllConfig { + Enabled = 0, + /// Disabled and bypassed. + Bypassed = 1, +} + #[bitbybit::bitfield(u32)] #[derive(Debug)] pub struct BootModeRegister { #[bit(4, r)] - pll_bypass: bool, + pll_config: BootPllConfig, #[bits(0..=3, r)] boot_mode: u4, } @@ -205,7 +179,7 @@ pub struct Slcr { gpiob: GpiobRegisters, #[mmio(Inner)] - ddriob: DdrIoB, + ddriob: ddriob::DdrIoB, } static_assertions::const_assert_eq!(core::mem::size_of::(), 0xB78);