diff --git a/fsbl/Cargo.toml b/fsbl/Cargo.toml index 966ef5b..e8f8693 100644 --- a/fsbl/Cargo.toml +++ b/fsbl/Cargo.toml @@ -17,6 +17,7 @@ embedded-io = "0.6" embedded-hal = "1" fugit = "0.3" log = "0.4" +arbitrary-int = "1.3" [profile.release] codegen-units = 1 diff --git a/fsbl/src/main.rs b/fsbl/src/main.rs index 39f5d64..78bfbc2 100644 --- a/fsbl/src/main.rs +++ b/fsbl/src/main.rs @@ -2,15 +2,14 @@ #![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, PllConfigCtorError, configure_arm_pll, configure_ddr_pll, configure_io_pll, - }, - gpio::{Output, PinState, mio}, + clocks::pll::{PllConfig, configure_arm_pll, configure_ddr_pll, configure_io_pll}, + ddr::configure_dci, time::Hertz, }; use zynq7000_rt as _; @@ -20,14 +19,15 @@ const PS_CLK: Hertz = Hertz::from_raw(33_333_333); /// 1600 MHz. const ARM_CLK: Hertz = Hertz::from_raw(1_600_000_000); -/// 1067 MHz. -//const DDR_CLK: Hertz = Hertz::from_raw(1_067_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) -> ! { @@ -42,10 +42,26 @@ 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()); + 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(), + ); - configure_ddr_pll(boot_mode, PllConfig::new_from_target_clock(PS_CLK, 2 * DDR_FREQUENCY).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_clk = unsafe { + zynq7000_hal::clocks::DdrClocks::new_with_2x_3x_init(DDR_CLK, u6::new(2), u6::new(3)) + }; + configure_dci(&ddr_clk); loop { cortex_ar::asm::nop(); diff --git a/zynq7000-hal/src/clocks/mod.rs b/zynq7000-hal/src/clocks/mod.rs index 2691344..dc45074 100644 --- a/zynq7000-hal/src/clocks/mod.rs +++ b/zynq7000-hal/src/clocks/mod.rs @@ -1,5 +1,5 @@ //! Clock module. -use arbitrary_int::Number; +use arbitrary_int::{Number, u6}; pub mod pll; @@ -47,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 } diff --git a/zynq7000-hal/src/ddr.rs b/zynq7000-hal/src/ddr.rs index e69de29..092e3f5 100644 --- a/zynq7000-hal/src/ddr.rs +++ b/zynq7000-hal/src/ddr.rs @@ -0,0 +1,41 @@ +use arbitrary_int::u6; +use zynq7000::slcr::clocks::DciClkCtrl; + +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()); + + 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)); + best_error = error; + } + } + } + best.map(|(div0, div1)| (u6::new(div0), u6::new(div1))) + .unwrap() +} + +pub fn configure_dci(ddr_clk: &DdrClocks) { + let (divisor0, divisor1) = calculate_dci_divisors(ddr_clk.ref_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_clk_act(true) + .build(), + ); + }); + } +} diff --git a/zynq7000-hal/src/lib.rs b/zynq7000-hal/src/lib.rs index 3c15a3c..9f3a9c3 100644 --- a/zynq7000-hal/src/lib.rs +++ b/zynq7000-hal/src/lib.rs @@ -14,6 +14,7 @@ use zynq7000::slcr::{BootModeRegister, BootPllConfig, LevelShifterReg}; pub mod cache; pub mod clocks; +pub mod ddr; pub mod eth; pub mod gic; pub mod gpio; diff --git a/zynq7000/src/ddrc.rs b/zynq7000/src/ddrc.rs index ca98b5d..dbffa5e 100644 --- a/zynq7000/src/ddrc.rs +++ b/zynq7000/src/ddrc.rs @@ -1,4 +1,4 @@ -use arbitrary_int::{u11, u12, u3, u4, u5, u6, u7}; +use arbitrary_int::{u2, u3, u4, u5, u6, u7, u11, u12}; pub const DDRC_BASE_ADDR: usize = 0xF800_6000; @@ -39,6 +39,9 @@ pub struct DdrcControl { 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, @@ -137,9 +140,38 @@ pub struct DramParamReg3 { #[bits(5..=7, rw)] t_rrd: u3, #[bits(2..=4, rw)] - t_ccd: u3 + t_ccd: u3, } +#[bitbybit::bitfield(u32, default = 0x0)] +pub struct DramParamReg4 { + #[bit(27, rw)] + mr_rdata_valid: bool, + #[bit(26, rw)] + mr_type: bool, + #[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, +} #[derive(derive_mmio::Mmio)] #[repr(C)] pub struct DdrController { @@ -149,13 +181,13 @@ pub struct DdrController { lpr_queue_ctrl: LprHprQueueControl, wr_reg: WriteQueueControl, dram_param_reg0: DramParamReg0, - dram_param_reg1: u32, - dram_param_reg2: u32, - dram_param_reg3: u32, - dram_param_reg4: u32, - dram_init_param: u32, - dram_emr_reg: u32, - dram_emr_mr_reg: u32, + dram_param_reg1: DramParamReg1, + dram_param_reg2: DramParamReg2, + dram_param_reg3: DramParamReg3, + dram_param_reg4: DramParamReg4, + dram_init_param: DramInitParam, + dram_emr: u32, + dram_emr_mr: u32, dram_burst8_rdwr: u32, dram_disable_dq: u32, dram_addr_map_bank: u32, @@ -164,7 +196,8 @@ pub struct DdrController { dram_odt_reg: u32, phy_debug_reg: u32, phy_cmd_timeout_rddata_cpt: u32, - mode_status_reg: u32, + #[mmio(PureRead)] + mode_status: u32, dll_calib: u32, odt_delay_hold: u32, ctrl_reg1: u32, @@ -186,16 +219,28 @@ pub struct DdrController { dfi_timing: u32, _reserved2: [u32; 0x2], che_corr_control: u32, + #[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: u32, che_ecc_corr_bit_mask_31_0: u32, @@ -221,44 +266,47 @@ pub struct DdrController { reg_64: u32, reg_65: u32, _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, - phy_dll_status_0: u32, - phy_dll_status_1: u32, - phy_dll_status_2: u32, - phy_dll_status_3: 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_0: u32, - axi_priority_wr_port_1: u32, - axi_priority_wr_port_2: u32, - axi_priority_wr_port_3: u32, - axi_priority_rd_port_0: u32, - axi_priority_rd_port_1: u32, - axi_priority_rd_port_2: u32, - axi_priority_rd_port_3: u32, + axi_priority_wr_port: [u32; 4], + axi_priority_rd_port: [u32; 4], _reserved15: [u32; 0x1B], - excl_access_cfg_0: u32, - excl_access_cfg_1: u32, - excl_access_cfg_2: u32, - excl_access_cfg_3: u32, + excl_access_cfg: [u32; 4], + #[mmio(PureRead)] mode_reg_read: u32, lpddr_ctrl_0: u32, lpddr_ctrl_1: u32, diff --git a/zynq7000/src/lib.rs b/zynq7000/src/lib.rs index fd58efd..a7fe8ba 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; @@ -28,7 +29,6 @@ pub mod slcr; pub mod spi; pub mod ttc; pub mod uart; -pub mod ddrc; static PERIPHERALS_TAKEN: AtomicBool = AtomicBool::new(false); diff --git a/zynq7000/src/slcr/clocks.rs b/zynq7000/src/slcr/clocks.rs index 0429eb1..cd7f796 100644 --- a/zynq7000/src/slcr/clocks.rs +++ b/zynq7000/src/slcr/clocks.rs @@ -169,7 +169,7 @@ pub struct DdrClkCtrl { ddr_3x_clk_act: bool, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, default = 0x0)] pub struct DciClkCtrl { /// Second cascade divider. Reset value: 0x1E #[bits(20..=25, rw)]