continue with FSBL
Some checks failed
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled

This commit is contained in:
2025-07-30 12:29:55 +02:00
parent d1c7aba6f7
commit 11b5f77bbe
8 changed files with 191 additions and 38 deletions

View File

@@ -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

View File

@@ -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();

View File

@@ -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
}

View File

@@ -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(),
);
});
}
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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);

View File

@@ -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)]