From d94e97734f3f76c2456c5ad8ff0bf2da9a7b48fb Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 31 Jul 2025 11:45:53 +0200 Subject: [PATCH] continue fsbl --- fsbl/src/main.rs | 7 +- zynq7000-hal/src/ddr.rs | 184 +++++++++++++++++++++++++++++++++--- zynq7000-hal/src/slcr.rs | 4 +- zynq7000-hal/src/spi/mod.rs | 6 +- zynq7000/src/slcr/ddriob.rs | 139 +++++++++++++++++++++++++++ zynq7000/src/slcr/mod.rs | 38 +------- 6 files changed, 322 insertions(+), 56 deletions(-) create mode 100644 zynq7000/src/slcr/ddriob.rs diff --git a/fsbl/src/main.rs b/fsbl/src/main.rs index 78bfbc2..1d06462 100644 --- a/fsbl/src/main.rs +++ b/fsbl/src/main.rs @@ -9,7 +9,7 @@ use log::error; use zynq7000_hal::{ BootMode, clocks::pll::{PllConfig, configure_arm_pll, configure_ddr_pll, configure_io_pll}, - ddr::configure_dci, + ddr::{calculate_dci_divisors, calibrate_iob_impedance_for_ddr3}, time::Hertz, }; use zynq7000_rt as _; @@ -58,10 +58,11 @@ pub fn main() -> ! { PllConfig::new_from_target_clock(PS_CLK, DDR_CLK).unwrap(), ); // Safety: Only done once here during start-up. - let ddr_clk = unsafe { + let ddr_clks = unsafe { zynq7000_hal::clocks::DdrClocks::new_with_2x_3x_init(DDR_CLK, u6::new(2), u6::new(3)) }; - configure_dci(&ddr_clk); + let dci_clk_cfg = calculate_dci_divisors(&ddr_clks); + calibrate_iob_impedance_for_ddr3(dci_clk_cfg, false); loop { cortex_ar::asm::nop(); diff --git a/zynq7000-hal/src/ddr.rs b/zynq7000-hal/src/ddr.rs index 092e3f5..45e8ad3 100644 --- a/zynq7000-hal/src/ddr.rs +++ b/zynq7000-hal/src/ddr.rs @@ -1,41 +1,201 @@ -use arbitrary_int::u6; -use zynq7000::slcr::clocks::DciClkCtrl; +use arbitrary_int::{Number, u2, u3, u6}; +use zynq7000::slcr::{ + clocks::DciClkCtrl, + ddriob::{DciType, DdriobConfig, InputType, OutputEnable}, +}; use crate::{clocks::DdrClocks, time::Hertz}; const DCI_MAX_FREQ: Hertz = Hertz::from_raw(10_000_000); -pub fn calculate_dci_divisors(ddr_clk: Hertz) -> (u6, u6) { - let target_div = ddr_clk.raw().div_ceil(DCI_MAX_FREQ.raw()); +// 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() as u8), + div1: u6::new(u6::MAX.value() as u8), + }; - let mut best = None; 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 best.is_none() || best_error > error { - best = Some((divisor0, divisor1)); + if error < best_error { + config.div0 = u6::new(divisor0 as u8); + config.div1 = u6::new(divisor1 as u8); best_error = error; } } } - best.map(|(div0, div1)| (u6::new(div0), u6::new(div1))) - .unwrap() + config } pub fn configure_dci(ddr_clk: &DdrClocks) { - let (divisor0, divisor1) = calculate_dci_divisors(ddr_clk.ref_clk()); + let cfg = calculate_dci_divisors(ddr_clk); // Safety: Only done once here during start-up. unsafe { crate::Slcr::with(|slcr| { slcr.clk_ctrl().write_dci_clk_ctrl( DciClkCtrl::builder() - .with_divisor_1(divisor1) - .with_divisor_0(divisor0) + .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, +) { + let mut slcr = unsafe { crate::slcr::Slcr::steal() }; + slcr.modify(|slcr| { + slcr.clk_ctrl().write_dci_clk_ctrl( + DciClkCtrl::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, +} + +/// TODO: We could pull out the values from the ps7 init file, convert them into rust constants, +/// and take a configuration set passed here. +pub fn configure_iob_for_ddr3() { + let mut slcr = unsafe { crate::slcr::Slcr::steal() }; + slcr.modify(|slcr| { + let mut ddriob = slcr.ddriob(); + let mut addr_and_clk_cfg = DdriobConfig::new_with_raw_value(0); + addr_and_clk_cfg.set_output_enable(OutputEnable::OBuf); + ddriob.write_ddriob_addr0(addr_and_clk_cfg); + ddriob.write_ddriob_addr1(addr_and_clk_cfg); + + let data_config = 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(); + ddriob.write_ddriob_data0(data_config); + ddriob.write_ddriob_data1(data_config); + let diff_config = 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(); + ddriob.write_ddriob_diff0(diff_config); + ddriob.write_ddriob_diff1(diff_config); + + ddriob.write_ddriob_clock(addr_and_clk_cfg); + + // 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. + 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); + }); +} 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 62449d1..96acfe8 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(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 66e0338..5927d08 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, @@ -213,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);