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
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:
@@ -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();
|
||||
|
@@ -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);
|
||||
});
|
||||
}
|
||||
|
@@ -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.
|
||||
|
@@ -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
139
zynq7000/src/slcr/ddriob.rs
Normal 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);
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user