continue 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-31 11:45:53 +02:00
parent f118f292de
commit d94e97734f
6 changed files with 322 additions and 56 deletions

View File

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

View File

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

View File

@@ -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<u6> {
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<u6
pub fn configure_spi_ref_clk(clks: &mut Clocks, divisor: u6) {
let mut slcr = unsafe { Slcr::steal() };
let spi_clk_ctrl = slcr.regs().clk_ctrl().read_spi_clk_ctrl();
let spi_clk_ctrl = slcr.regs().clk_ctrl_shared().read_spi_clk_ctrl();
slcr.modify(|regs| {
regs.clk_ctrl().modify_spi_clk_ctrl(|mut val| {
val.set_divisor(divisor);