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

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

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

139
zynq7000/src/slcr/ddriob.rs Normal file
View File

@@ -0,0 +1,139 @@
use arbitrary_int::{u2, u3, u4};
#[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<VRefSel>,
#[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::<DdrIoB>(), 0x38);

View File

@@ -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::<DdrIoB>(), 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::<Slcr>(), 0xB78);