Introduce Rust 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-08-01 14:32:08 +02:00
parent fea2ea5b61
commit 4637fec060
30 changed files with 2267 additions and 117 deletions

View File

@@ -8,7 +8,7 @@ members = [
"examples/simple",
"examples/embassy",
"examples/zedboard",
"zynq-mmu",
"zynq-mmu", "fsbl",
]
exclude = ["experiments"]

View File

@@ -137,7 +137,7 @@ async fn main(_spawner: Spawner) -> ! {
info!("Flex Pin 0 state (should be low): {}", flex_pin_0.is_high());
}
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let mut ticker = Ticker::every(Duration::from_millis(1000));

View File

@@ -75,7 +75,7 @@ async fn main(spawner: Spawner) -> ! {
zynq7000_hal::log::rb::init(log::LevelFilter::Trace);
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let led = Output::new_for_mio(mio_pins.mio7, PinState::Low);

View File

@@ -91,7 +91,7 @@ async fn main(_spawner: Spawner) -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let mut ticker = Ticker::every(Duration::from_millis(1000));

View File

@@ -77,7 +77,7 @@ async fn main(_spawner: Spawner) -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let mut ticker = Ticker::every(Duration::from_millis(1000));

View File

@@ -79,7 +79,7 @@ pub fn main() -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {boot_mode:?}");
let mut led = Output::new_for_mio(mio_pins.mio7, PinState::Low);

View File

@@ -64,22 +64,22 @@ pub fn main() -> ! {
#[zynq7000_rt::irq]
pub fn irq_handler() {}
#[unsafe(no_mangle)]
pub extern "C" fn _abort_handler() {
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _undefined_handler() {
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _prefetch_handler() {
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}

View File

@@ -249,7 +249,7 @@ async fn main(spawner: Spawner) -> ! {
// Safety: We are not multi-threaded yet.
unsafe { zynq7000_hal::log::uart_blocking::init_unsafe_single_core(uart, LOG_LEVEL, false) };
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
static ETH_RX_BUFS: static_cell::ConstStaticCell<[AlignedBuffer; NUM_RX_SLOTS]> =

View File

@@ -93,7 +93,7 @@ async fn main(_spawner: Spawner) -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let pin_sel = match I2C_ADDR_SEL {

View File

@@ -96,7 +96,7 @@ async fn main(spawner: Spawner) -> ! {
.unwrap();
zynq7000_hal::log::rb::init(log::LevelFilter::Trace);
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
if DEBUG_SPI_CLK_CONFIG {

View File

@@ -160,7 +160,7 @@ async fn main(_spawner: Spawner) -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let mut ticker = Ticker::every(Duration::from_millis(1000));

View File

@@ -254,7 +254,7 @@ async fn main(spawner: Spawner) -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let mio_led = Output::new_for_mio(gpio_pins.mio.mio7, PinState::Low);

View File

@@ -79,7 +79,7 @@ async fn main(_spawner: Spawner) -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let mut ticker = Ticker::every(Duration::from_millis(200));

25
fsbl/Cargo.toml Normal file
View File

@@ -0,0 +1,25 @@
[package]
name = "fsbl"
version = "0.1.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2024"
description = "Rust First Stage Bootloader for the Zynq7000 SoC"
homepage = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
license = "MIT OR Apache-2.0"
[dependencies]
cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2", features = ["critical-section-single-core"] }
zynq7000-rt = { path = "../zynq7000-rt" }
zynq7000 = { path = "../zynq7000" }
zynq7000-hal = { path = "../zynq7000-hal" }
embedded-io = "0.6"
embedded-hal = "1"
fugit = "0.3"
log = "0.4"
arbitrary-int = "1.3"
[profile.release]
codegen-units = 1
debug = true
lto = true

24
fsbl/memory.x Normal file
View File

@@ -0,0 +1,24 @@
MEMORY
{
/* The Zynq7000 has 192 kB of OCM memory which can be used for the FSBL */
CODE(rx) : ORIGIN = 0x00000000, LENGTH = 192K
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
/* Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This can
be used for something like DMA descriptors, but the DDR needs to be set up first in addition
to configuring the page at address 0x400_0000 accordingly */
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
}
REGION_ALIAS("DATA", CODE);
SECTIONS
{
/* Uncached memory */
.uncached (NOLOAD) : ALIGN(4) {
. = ALIGN(4);
_sbss_uncached = .;
*(.uncached .uncached.*);
. = ALIGN(4);
_ebss_uncached = .;
} > UNCACHED
}

56
fsbl/src/ddr_cfg.rs Normal file
View File

@@ -0,0 +1,56 @@
use zynq7000::slcr::ddriob::{DciType, DdriobConfig, InputType, OutputEnable};
use zynq7000_hal::ddr::DdriobConfigSet;
const DDRIOB_ADDR_CFG: DdriobConfig = 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::Disabled)
.with_termination_enable(false)
.with_dci_update_enable(false)
.with_inp_type(InputType::Off)
.build();
const DDRIOB_DATA_CFG: DdriobConfig = 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();
const DDRIOB_DIFF_CFG: DdriobConfig = 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();
const DDRIOB_CLOCK_CFG: DdriobConfig = 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::Disabled)
.with_termination_enable(false)
.with_dci_update_enable(false)
.with_inp_type(InputType::Off)
.build();
pub const DDRIOB_CFG_SET_ZEDBOARD: DdriobConfigSet = DdriobConfigSet {
addr0: DDRIOB_ADDR_CFG,
addr1: DDRIOB_ADDR_CFG,
data0: DDRIOB_DATA_CFG,
data1: DDRIOB_DATA_CFG,
diff0: DDRIOB_DIFF_CFG,
diff1: DDRIOB_DIFF_CFG,
clock: DDRIOB_CLOCK_CFG,
};

101
fsbl/src/main.rs Normal file
View File

@@ -0,0 +1,101 @@
//! Simple FSBL.
#![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, configure_arm_pll, configure_ddr_pll, configure_io_pll},
ddr::{calculate_dci_divisors, calibrate_iob_impedance_for_ddr3, configure_iob},
time::Hertz,
};
use zynq7000_rt as _;
pub mod ddr_cfg;
// PS clock input frequency.
const PS_CLK: Hertz = Hertz::from_raw(33_333_333);
/// 1600 MHz.
const ARM_CLK: Hertz = Hertz::from_raw(1_600_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) -> ! {
if cpu_id != 0 {
panic!("unexpected CPU ID {}", cpu_id);
}
main();
}
#[unsafe(export_name = "main")]
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(),
);
// 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_clks = unsafe {
zynq7000_hal::clocks::DdrClocks::new_with_2x_3x_init(DDR_CLK, u6::new(2), u6::new(3))
};
let dci_clk_cfg = calculate_dci_divisors(&ddr_clks);
calibrate_iob_impedance_for_ddr3(dci_clk_cfg, false);
configure_iob(&ddr_cfg::DDRIOB_CFG_SET_ZEDBOARD);
loop {
cortex_ar::asm::nop();
}
}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
error!("Panic: {info:?}");
loop {}
}

View File

@@ -1,8 +1,7 @@
MEMORY
{
/* Zedboard: 512 MB DDR3. Only use 63 MB for now, should be plenty for a bare-metal app.
Leave 1 MB of memory which will be configured as uncached device memory by the MPU. This is
Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This is
recommended for something like DMA descriptors. */
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M

View File

@@ -675,6 +675,18 @@ proc create_root_design { parentCell } {
connect_bd_net -net xlslice_0_Dout1 [get_bd_pins UART_MUX/Dout] [get_bd_pins uart_mux_0/sel]
connect_bd_net -net xlslice_1_Dout [get_bd_pins EMIO_O_0/Dout] [get_bd_pins LEDS/Din] [get_bd_pins EMIO_I/In0] [get_bd_pins UART_MUX/Din]
# Set DDR properties specified in the datasheet.
set_property -dict [list \
CONFIG.PCW_UIPARAM_DDR_BOARD_DELAY0 {0.410} \
CONFIG.PCW_UIPARAM_DDR_BOARD_DELAY1 {0.411} \
CONFIG.PCW_UIPARAM_DDR_BOARD_DELAY2 {0.341} \
CONFIG.PCW_UIPARAM_DDR_BOARD_DELAY3 {0.358} \
CONFIG.PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_0 {0.025} \
CONFIG.PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_1 {0.028} \
CONFIG.PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_2 {-0.009} \
CONFIG.PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_3 {-0.061} \
] [get_bd_cells processing_system7_0]
# Create address segments
assign_bd_address -offset 0x43C00000 -range 0x00010000 -target_address_space [get_bd_addr_spaces processing_system7_0/Data] [get_bd_addr_segs axi_uart16550_0/S_AXI/Reg] -force
assign_bd_address -offset 0x42C00000 -range 0x00010000 -target_address_space [get_bd_addr_spaces processing_system7_0/Data] [get_bd_addr_segs axi_uartlite_0/S_AXI/Reg] -force

View File

@@ -1,5 +1,7 @@
//! Clock module.
use arbitrary_int::Number;
use arbitrary_int::{Number, u6};
pub mod pll;
use zynq7000::slcr::{
ClockControl,
@@ -45,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
}
@@ -201,9 +249,9 @@ impl Clocks {
pub fn new_from_regs(ps_clk_freq: Hertz) -> Result<Self, ClockReadError> {
let mut clk_regs = unsafe { ClockControl::new_mmio_fixed() };
let arm_pll_cfg = clk_regs.read_arm_pll();
let io_pll_cfg = clk_regs.read_io_pll();
let ddr_pll_cfg = clk_regs.read_ddr_pll();
let arm_pll_cfg = clk_regs.read_arm_pll_ctrl();
let io_pll_cfg = clk_regs.read_io_pll_ctrl();
let ddr_pll_cfg = clk_regs.read_ddr_pll_ctrl();
if arm_pll_cfg.fdiv().as_u32() == 0
|| io_pll_cfg.fdiv().as_u32() == 0

View File

@@ -0,0 +1,356 @@
use core::sync::atomic::AtomicBool;
use arbitrary_int::{u4, u7, u10};
use crate::{BootMode, time::Hertz};
/// Minimal value based on Zynq-7000 TRM Table 25-6, p.744
pub const PLL_MUL_MIN: u32 = 13;
/// Maximum value based on Zynq-7000 TRM Table 25-6, p.744
pub const PLL_MUL_MAX: u32 = 66;
static ARM_PLL_INIT: AtomicBool = AtomicBool::new(false);
static IO_PLL_INIT: AtomicBool = AtomicBool::new(false);
static DDR_PLL_INIT: AtomicBool = AtomicBool::new(false);
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
#[error("pll muliplier value {0} is out of range ({PLL_MUL_MIN}..={PLL_MUL_MAX})")]
pub struct MulOutOfRangeError(pub u32);
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum PllConfigCtorError {
#[error("invalid input")]
InvalidInput,
#[error("pll multiplier out of range: {0}")]
MulOutOfRange(#[from] MulOutOfRangeError),
}
pub struct PllConfig {
fdiv: u7,
charge_pump: u4,
loop_resistor: u4,
lock_count: u10,
}
impl PllConfig {
pub fn new_from_target_clock(
ps_clk: Hertz,
target_clk: Hertz,
) -> Result<Self, PllConfigCtorError> {
if ps_clk.raw() == 0 {
return Err(PllConfigCtorError::InvalidInput);
}
let mul = target_clk / ps_clk;
Self::new(mul).map_err(PllConfigCtorError::MulOutOfRange)
}
/// Create a new PLL configuration based on the multiplier value.
///
/// These configuration values are based on the Zynq-7000 TRM Table 25-6, p.744.
pub fn new(pll_mul: u32) -> Result<Self, MulOutOfRangeError> {
if !(PLL_MUL_MIN..=PLL_MUL_MAX).contains(&pll_mul) {
return Err(MulOutOfRangeError(pll_mul));
}
Ok(match pll_mul {
13 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(6),
u10::new(750),
),
14 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(6),
u10::new(700),
),
15 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(6),
u10::new(650),
),
16 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(10),
u10::new(625),
),
17 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(10),
u10::new(575),
),
18 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(10),
u10::new(550),
),
19 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(10),
u10::new(525),
),
20 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(500),
),
21 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(475),
),
22 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(450),
),
23 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(425),
),
24..=25 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(400),
),
26 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(375),
),
27..=28 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(350),
),
29..=30 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(325),
),
31..=33 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(2),
u10::new(300),
),
34..=36 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(2),
u10::new(275),
),
37..=40 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(2),
u10::new(250),
),
41..=47 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(3),
u4::new(12),
u10::new(250),
),
48..=66 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(4),
u10::new(250),
),
_ => {
unreachable!()
}
})
}
/// Create a new PLL configuration with raw values.
///
/// It is recommended to use [Self::new] instead, which creates a configuration
/// based on a look-up table provided in the Zynq-7000 TRM.
pub fn new_raw(fdiv: u7, charge_pump: u4, loop_resistor: u4, lock_count: u10) -> Self {
Self {
fdiv,
charge_pump,
loop_resistor,
lock_count,
}
}
}
/// This function configures the ARM PLL based on the provided [PllConfig].
pub fn configure_arm_pll(boot_mode: BootMode, pll_config: PllConfig) {
if ARM_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
return;
}
// Safety: This will only run at most once because of the atomic boolean check.
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
}
/// This function configures the IO PLL based on the provided [PllConfig].
pub fn configure_io_pll(boot_mode: BootMode, pll_config: PllConfig) {
if IO_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
return;
}
// Safety: This will only run at most once because of the atomic boolean check.
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
}
/// This function configures the DDR PLL based on the provided [PllConfig].
pub fn configure_ddr_pll(boot_mode: BootMode, pll_config: PllConfig) {
if DDR_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
return;
}
// Safety: This will only run at most once because of the atomic boolean check.
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
}
/// This function configures the ARM PLL basejjon the provided [PllConfig].
///
/// # Safety
///
/// This function should only be called once during system initialization, for example in the
/// first-stage bootloader (FSBL).
pub unsafe fn configure_arm_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) {
unsafe {
crate::slcr::Slcr::with(|slcr| {
let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_arm_pll_ctrl();
let pll_cfg_reg = slcr.clk_ctrl().pointer_to_arm_pll_cfg();
configure_pll_unchecked(
boot_mode,
pll_config,
PllType::Arm,
slcr,
pll_ctrl_reg,
pll_cfg_reg,
);
});
}
}
/// This function configures the IO PLL based on the provided [PllConfig].
///
/// # Safety
///
/// This function should only be called once during system initialization, for example in the
/// first-stage bootloader (FSBL).
pub unsafe fn configure_io_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) {
unsafe {
crate::slcr::Slcr::with(|slcr| {
let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_io_pll_ctrl();
let pll_cfg_reg = slcr.clk_ctrl().pointer_to_io_pll_cfg();
configure_pll_unchecked(
boot_mode,
pll_config,
PllType::Io,
slcr,
pll_ctrl_reg,
pll_cfg_reg,
);
});
}
}
/// This function configures the DDR PLL based on the provided [PllConfig].
///
/// # Safety
///
/// This function should only be called once during system initialization, for example in the
/// first-stage bootloader (FSBL).
pub unsafe fn configure_ddr_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) {
unsafe {
crate::slcr::Slcr::with(|slcr| {
let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_ddr_pll_ctrl();
let pll_cfg_reg = slcr.clk_ctrl().pointer_to_ddr_pll_cfg();
configure_pll_unchecked(
boot_mode,
pll_config,
PllType::Ddr,
slcr,
pll_ctrl_reg,
pll_cfg_reg,
);
});
}
}
enum PllType {
Io,
Ddr,
Arm,
}
impl PllType {
pub const fn bit_offset_pll_locked(&self) -> usize {
match self {
PllType::Io => 2,
PllType::Ddr => 1,
PllType::Arm => 0,
}
}
}
unsafe fn configure_pll_unchecked(
boot_mode: BootMode,
cfg: PllConfig,
pll_type: PllType,
slcr: &mut zynq7000::slcr::MmioSlcr<'static>,
pll_ctrl_reg: *mut zynq7000::slcr::clocks::PllControl,
pll_cfg_reg: *mut zynq7000::slcr::clocks::PllConfig,
) {
// Step 1: Program the multiplier and other PLL configuration parameters.
// New values will only be consumed once the PLL is reset.
let mut pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
pll_ctrl.set_fdiv(cfg.fdiv);
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
let mut pll_cfg = unsafe { core::ptr::read_volatile(pll_cfg_reg) };
pll_cfg.set_charge_pump(cfg.charge_pump);
pll_cfg.set_loop_resistor(cfg.loop_resistor);
pll_cfg.set_lock_count(cfg.lock_count);
unsafe { core::ptr::write_volatile(pll_cfg_reg, pll_cfg) };
// Step 2: Force the PLL into bypass mode. If the PLL bypass mode pin is tied high,
// the PLLs need to be enabled. According to the TRM, this is done by setting the
// PLL_BYPASS_QUAL bit to 0, which de-asserts the reset to the Arm PLL.
pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
if boot_mode.pll_config() == zynq7000::slcr::BootPllConfig::Bypassed {
pll_ctrl.set_bypass_qual(false);
}
pll_ctrl.set_bypass_force(true);
pll_ctrl.set_pwrdwn(false);
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
// Step 3: Reset the PLL. This also loads the new configuration.
pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
pll_ctrl.set_reset(true);
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
pll_ctrl.set_reset(false);
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
while ((slcr.clk_ctrl().read_pll_status().raw_value() >> pll_type.bit_offset_pll_locked())
& 0b1)
!= 1
{
cortex_ar::asm::nop();
}
pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
pll_ctrl.set_bypass_force(false);
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
}

564
zynq7000-hal/src/ddr.rs Normal file
View File

@@ -0,0 +1,564 @@
use arbitrary_int::{Number, u2, u3, u4, u5, u6, u7, u9, u10, u11, u12, u20};
use zynq7000::{
ddrc::{MmioDdrController, regs::*},
slcr::{clocks::DciClockControl, ddriob::DdriobConfig},
};
use crate::{clocks::DdrClocks, time::Hertz};
const DCI_MAX_FREQ: Hertz = Hertz::from_raw(10_000_000);
// 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()),
div1: u6::new(u6::MAX.value()),
};
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 error < best_error {
config.div0 = u6::new(divisor0 as u8);
config.div1 = u6::new(divisor1 as u8);
best_error = error;
}
}
}
config
}
pub fn configure_dci(ddr_clk: &DdrClocks) {
let cfg = calculate_dci_divisors(ddr_clk);
// Safety: Only writes to DCI clock related registers.
unsafe {
crate::Slcr::with(|slcr| {
slcr.clk_ctrl().write_dci_clk_ctrl(
DciClockControl::builder()
.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,
) {
// Safety: Only writes to DDR IOB related registers.
let mut slcr = unsafe { crate::slcr::Slcr::steal() };
slcr.modify(|slcr| {
slcr.clk_ctrl().write_dci_clk_ctrl(
DciClockControl::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,
}
pub fn configure_iob(cfg_set: &DdriobConfigSet) {
let mut slcr = unsafe { crate::slcr::Slcr::steal() };
slcr.modify(|slcr| {
let mut ddriob = slcr.ddriob();
ddriob.write_ddriob_addr0(cfg_set.addr0);
ddriob.write_ddriob_addr1(cfg_set.addr1);
ddriob.write_ddriob_data0(cfg_set.data0);
ddriob.write_ddriob_data1(cfg_set.data1);
ddriob.write_ddriob_diff0(cfg_set.diff0);
ddriob.write_ddriob_diff1(cfg_set.diff1);
ddriob.write_ddriob_clock(cfg_set.clock);
// These values were extracted from the ps7_init files and are not documented in the TRM.
// 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);
});
}
pub struct DdrcConfigSet {
pub ctrl: DdrcControl,
pub two_rank: TwoRankConfig,
pub hpr: LprHprQueueControl,
pub lpr: LprHprQueueControl,
pub wr: WriteQueueControl,
pub dram_param_0: DramParamReg0,
pub dram_param_1: DramParamReg1,
pub dram_param_2: DramParamReg2,
pub dram_param_3: DramParamReg3,
pub dram_param_4: DramParamReg4,
pub dram_init_param: DramInitParam,
pub dram_emr: DramEmr,
pub dram_emr_mr: DramEmrMr,
pub dram_burst8_rdwr: DramBurst8ReadWrite,
pub disable_dq: DisableDq,
pub dram_addr_map_bank: DramAddrMapBank,
pub dram_addr_map_col: DramAddrMapColumn,
pub dram_addr_map_row: DramAddrMapRow,
pub dram_odt: DramOdt,
pub phy_cmd_timeout_rddata_cpt: PhyCmdTimeoutRdDataCpt,
pub dll_calib: DllCalib,
pub odt_delay_hold: OdtDelayHold,
pub ctrl_reg1: CtrlReg1,
pub ctrl_reg2: CtrlReg2,
pub ctrl_reg3: CtrlReg3,
pub ctrl_reg4: CtrlReg4,
pub ctrl_reg5: CtrlReg5,
pub ctrl_reg6: CtrlReg6,
pub che_t_zq: CheTZq,
pub che_t_zq_short_interval_reg: CheTZqShortInterval,
pub deep_powerdown: DeepPowerdown,
pub reg_2c: Reg2c,
pub reg_2d: Reg2d,
pub dfi_timing: DfiTiming,
pub che_ecc_ctrl: CheEccControl,
pub ecc_scrub: EccScrub,
pub phy_receiver_enable: PhyReceiverEnable,
pub phy_config: [PhyConfig; 4],
pub phy_init_ratio: [PhyInitRatio; 4],
pub phy_rd_dqs_config: [PhyDqsConfig; 4],
pub phy_wr_dqs_config: [PhyDqsConfig; 4],
}
const DDRC_CONFIG_ZEDBOARD: DdrcConfigSet = DdrcConfigSet {
ctrl: DdrcControl::builder()
.with_disable_auto_refresh(false)
.with_disable_active_bypass(false)
.with_disable_read_bypass(false)
.with_read_write_idle_gap(u7::new(1))
.with_burst8_refresh(u3::new(0))
.with_data_bus_width(DataBusWidth::_32Bit)
.with_power_down_enable(false)
.with_soft_reset(SoftReset::Reset)
.build(),
two_rank: TwoRankConfig::builder()
.with_addrmap_cs_bit0(u5::new(0))
.with_ddrc_active_ranks(u2::new(1))
.with_rfc_nom_x32(u12::new(0x82))
.build(),
hpr: LprHprQueueControl::builder()
.with_xact_run_length(u4::new(0xf))
.with_max_starve_x32(u11::new(0xf))
.with_min_non_critical_x32(u11::new(0xf))
.build(),
lpr: LprHprQueueControl::builder()
.with_xact_run_length(u4::new(0x8))
.with_max_starve_x32(u11::new(0x2))
.with_min_non_critical_x32(u11::new(0x1))
.build(),
wr: WriteQueueControl::builder()
.with_max_starve_x32(u11::new(0x2))
.with_xact_run_length(u4::new(0x8))
.with_min_non_critical_x32(u11::new(0x1))
.build(),
dram_param_0: DramParamReg0::builder()
.with_post_selfref_gap_x32(u7::new(0x10))
.with_t_rfc_min(0x56)
.with_t_rc(u6::new(0x1b))
.build(),
dram_param_1: DramParamReg1::builder()
.with_t_cke(u4::new(0x4))
.with_t_ras_min(u5::new(0x13))
.with_t_ras_max(u6::new(0x24))
.with_t_faw(u6::new(0x16))
.with_powerdown_to_x32(u5::new(0x6))
.with_wr2pre(u5::new(0x13))
.build(),
dram_param_2: DramParamReg2::builder()
.with_t_rcd(u4::new(0x7))
.with_rd2pre(u5::new(0x5))
.with_pad_pd(u3::new(0x0))
.with_t_xp(u5::new(0x5))
.with_wr2rd(u5::new(0xf))
.with_rd2wr(u5::new(0x7))
.with_write_latency(u5::new(0x5))
.build(),
dram_param_3: DramParamReg3::builder()
.with_disable_pad_pd_feature(false)
.with_read_latency(u5::new(0x7))
.with_enable_dfi_dram_clk_disable(false)
.with_mobile(MobileSetting::Ddr2Ddr3)
.with_sdram(false)
.with_refresh_to_x32(u5::new(0x8))
.with_t_rp(u4::new(0x7))
.with_refresh_margin(u4::new(0x2))
.with_t_rrd(u3::new(0x6))
.with_t_ccd(u3::new(0x4))
.build(),
dram_param_4: DramParamReg4::builder()
.with_mr_rdata_valid(false)
.with_mr_type(ModeRegisterType::Write)
.with_mr_wr_busy(false)
.with_mr_data(0x0)
.with_mr_addr(u2::new(0x0))
.with_mr_wr(false)
.with_prefer_write(false)
.with_enable_2t_timing_mode(false)
.build(),
dram_init_param: DramInitParam::builder()
.with_t_mrd(u3::new(0x4))
.with_pre_ocd_x32(u4::new(0x0))
.with_final_wait_x32(u7::new(0x7))
.build(),
dram_emr: DramEmr::builder().with_emr3(0x0).with_emr2(0x8).build(),
dram_emr_mr: DramEmrMr::builder().with_emr(0x4).with_mr(0xb30).build(),
dram_burst8_rdwr: DramBurst8ReadWrite::builder()
.with_burst_rdwr(u4::new(0x4))
.with_pre_cke_x1024(u10::new(0x16d))
.with_post_cke_x1024(u10::new(0x1))
.with_burstchop(false)
.build(),
disable_dq: DisableDq::builder()
.with_dis_dq(false)
.with_force_low_pri_n(false)
.build(),
dram_addr_map_bank: DramAddrMapBank::builder()
.with_addrmap_bank_b6(u4::new(0))
.with_addrmap_bank_b5(u4::new(0))
.with_addrmap_bank_b2(u4::new(0x7))
.with_addrmap_bank_b1(u4::new(0x7))
.with_addrmap_bank_b0(u4::new(0x7))
.build(),
dram_addr_map_col: DramAddrMapColumn::builder()
.with_addrmap_col_b11(u4::new(0xf))
.with_addrmap_col_b10(u4::new(0xf))
.with_addrmap_col_b9(u4::new(0xf))
.with_addrmap_col_b8(u4::new(0x0))
.with_addrmap_col_b7(u4::new(0x0))
.with_addrmap_col_b4(u4::new(0x0))
.with_addrmap_col_b3(u4::new(0x0))
.with_addrmap_col_b2(u4::new(0x0))
.build(),
dram_addr_map_row: DramAddrMapRow::builder()
.with_addrmap_row_b15(u4::new(0xf))
.with_addrmap_row_b14(u4::new(0xf))
.with_addrmap_row_b13(u4::new(0x6))
.with_addrmap_row_b12(u4::new(0x6))
.with_addrmap_row_b2_11(u4::new(0x6))
.with_addrmap_row_b1(u4::new(0x6))
.with_addrmap_row_b0(u4::new(0x4))
.build(),
dram_odt: DramOdt::builder()
.with_phy_idle_local_odt(u2::new(0x0))
.with_phy_write_local_odt(u2::new(0x3))
.with_phy_read_local_odt(u2::new(0x0))
.with_rank0_wr_odt(u3::new(0x1))
.with_rank0_rd_odt(u3::new(0x0))
.build(),
phy_cmd_timeout_rddata_cpt: PhyCmdTimeoutRdDataCpt::builder()
.with_wrlvl_num_of_dq0(u4::new(0x7))
.with_gatelvl_num_of_dq0(u4::new(0x7))
.with_clk_stall_level(false)
.with_dis_phy_ctrl_rstn(false)
.with_rdc_fifo_rst_err_cnt_clr(false)
.with_use_fixed_re(true)
.with_rdc_we_to_re_delay(u4::new(0x8))
.with_wr_cmd_to_data(u4::new(0x0))
.with_rd_cmd_to_data(u4::new(0x0))
.build(),
dll_calib: DllCalib::builder().with_sel(DllCalibSel::Periodic).build(),
odt_delay_hold: OdtDelayHold::builder()
.with_wr_odt_hold(u4::new(0x5))
.with_rd_odt_hold(u4::new(0x0))
.with_wr_odt_delay(u4::new(0x0))
.with_rd_odt_delay(u4::new(0x3))
.build(),
ctrl_reg1: CtrlReg1::builder()
.with_selfref_enable(false)
.with_dis_collision_page_opt(false)
.with_dis_wc(false)
.with_refresh_update_level(false)
.with_auto_pre_en(false)
.with_lpr_num_entries(u6::new(0x1f))
.with_pageclose(false)
.build(),
ctrl_reg2: CtrlReg2::builder()
.with_go_2_critcal_enable(true)
.with_go_2_critical_hysteresis(0x0)
.build(),
ctrl_reg3: CtrlReg3::builder()
.with_dfi_t_wlmrd(u10::new(0x28))
.with_rdlvl_rr(0x41)
.with_wrlvl_ww(0x41)
.build(),
ctrl_reg4: CtrlReg4::builder()
.with_dfi_t_ctrlupd_interval_max_x1024(0x16)
.with_dfi_t_ctrlupd_interval_min_x1024(0x10)
.build(),
ctrl_reg5: CtrlReg5::builder()
.with_t_ckesr(u6::new(0x4))
.with_t_cksrx(u4::new(0x6))
.with_t_ckrse(u4::new(0x6))
.with_dfi_t_dram_clk_enable(u4::new(0x1))
.with_dfi_t_dram_clk_disable(u4::new(0x1))
.with_dfi_t_ctrl_delay(u4::new(0x1))
.build(),
ctrl_reg6: CtrlReg6::builder()
.with_t_cksx(u4::new(0x3))
.with_t_ckdpdx(u4::new(0x2))
.with_t_ckdpde(u4::new(0x2))
.with_t_ckpdx(u4::new(0x2))
.with_t_ckpde(u4::new(0x2))
.build(),
che_t_zq: CheTZq::builder()
.with_t_zq_short_nop(u10::new(0x40))
.with_t_zq_long_nop(u10::new(0x200))
.with_t_mode(u10::new(0x200))
.with_ddr3(true)
.with_dis_auto_zq(false)
.build(),
che_t_zq_short_interval_reg: CheTZqShortInterval::builder()
.with_dram_rstn_x1024(0x69)
.with_t_zq_short_interval(u20::new(0xcb73))
.build(),
deep_powerdown: DeepPowerdown::builder()
.with_deep_powerdown_to_x1024(0xff)
.with_enable(false)
.build(),
reg_2c: Reg2c::builder()
.with_dfi_rd_data_eye_train(true)
.with_dfi_rd_dqs_gate_level(true)
.with_dfi_wr_level_enable(true)
.with_trdlvl_max_error(false)
.with_twrlvl_max_error(false)
.with_dfi_rdlvl_max_x1024(u12::new(0xfff))
.with_dfi_wrlvl_max_x1024(u12::new(0xfff))
.build(),
reg_2d: Reg2d::builder().with_skip_ocd(true).build(),
dfi_timing: DfiTiming::builder()
.with_dfi_t_ctrlup_max(u10::new(0x40))
.with_dfi_t_ctrlup_min(u10::new(0x3))
.with_dfi_t_rddata_enable(u5::new(0x6))
.build(),
che_ecc_ctrl: CheEccControl::builder()
.with_clear_correctable_errors(false)
.with_clear_uncorrectable_errors(false)
.build(),
ecc_scrub: EccScrub::builder()
.with_disable_scrub(true)
.with_ecc_mode(EccMode::NoEcc)
.build(),
phy_receiver_enable: PhyReceiverEnable::builder()
.with_phy_dif_off(u4::new(0))
.with_phy_dif_on(u4::new(0))
.build(),
phy_config: [PhyConfig::builder()
.with_dq_offset(u7::new(0x40))
.with_wrlvl_inc_mode(false)
.with_gatelvl_inc_mode(false)
.with_rdlvl_inc_mode(false)
.with_data_slice_in_use(true)
.build(); 4],
phy_init_ratio: [
PhyInitRatio::builder()
.with_gatelvl_init_ratio(u10::new(0xcf))
.with_wrlvl_init_ratio(u10::new(0x3))
.build(),
PhyInitRatio::builder()
.with_gatelvl_init_ratio(u10::new(0xd0))
.with_wrlvl_init_ratio(u10::new(0x3))
.build(),
PhyInitRatio::builder()
.with_gatelvl_init_ratio(u10::new(0xbd))
.with_wrlvl_init_ratio(u10::new(0x0))
.build(),
PhyInitRatio::builder()
.with_gatelvl_init_ratio(u10::new(0xc1))
.with_wrlvl_init_ratio(u10::new(0x0))
.build(),
],
phy_rd_dqs_config: [
PhyDqsConfig::builder()
.with_dqs_slave_delay(u9::new(0x0))
.with_dqs_slave_force(false)
.with_dqs_slave_ratio(u10::new(0x35))
.build(),
PhyDqsConfig::builder()
.with_dqs_slave_delay(u9::new(0x0))
.with_dqs_slave_force(false)
.with_dqs_slave_ratio(u10::new(0x35))
.build(),
PhyDqsConfig::builder()
.with_dqs_slave_delay(u9::new(0x0))
.with_dqs_slave_force(false)
.with_dqs_slave_ratio(u10::new(0x35))
.build(),
PhyDqsConfig::builder()
.with_dqs_slave_delay(u9::new(0x0))
.with_dqs_slave_force(false)
.with_dqs_slave_ratio(u10::new(0x35))
.build(),
],
phy_wr_dqs_config: [
PhyDqsConfig::builder()
.with_dqs_slave_delay(u9::new(0x0))
.with_dqs_slave_force(false)
.with_dqs_slave_ratio(u10::new(0x83))
.build(),
PhyDqsConfig::builder()
.with_dqs_slave_delay(u9::new(0x0))
.with_dqs_slave_force(false)
.with_dqs_slave_ratio(u10::new(0x83))
.build(),
PhyDqsConfig::builder()
.with_dqs_slave_delay(u9::new(0x0))
.with_dqs_slave_force(false)
.with_dqs_slave_ratio(u10::new(0x7f))
.build(),
PhyDqsConfig::builder()
.with_dqs_slave_delay(u9::new(0x0))
.with_dqs_slave_force(false)
.with_dqs_slave_ratio(u10::new(0x78))
.build(),
],
};
pub fn configure_ddr(mut ddrc: MmioDdrController<'static>, cfg_set: &DdrcConfigSet) {
// Lock the DDR.
ddrc.write_ddrc_ctrl(cfg_set.ctrl);
// Write all configuration registers.
ddrc.write_two_rank_cfg(cfg_set.two_rank);
ddrc.write_hpr_queue_ctrl(cfg_set.hpr);
ddrc.write_lpr_queue_ctrl(cfg_set.lpr);
ddrc.write_wr_reg(cfg_set.wr);
ddrc.write_dram_param_reg0(cfg_set.dram_param_0);
ddrc.write_dram_param_reg1(cfg_set.dram_param_1);
ddrc.write_dram_param_reg2(cfg_set.dram_param_2);
ddrc.write_dram_param_reg3(cfg_set.dram_param_3);
ddrc.write_dram_param_reg4(cfg_set.dram_param_4);
ddrc.write_dram_init_param(cfg_set.dram_init_param);
ddrc.write_dram_emr(cfg_set.dram_emr);
ddrc.write_dram_emr_mr(cfg_set.dram_emr_mr);
ddrc.write_dram_burst8_rdwr(cfg_set.dram_burst8_rdwr);
ddrc.write_dram_disable_dq(cfg_set.disable_dq);
ddrc.write_phy_cmd_timeout_rddata_cpt(cfg_set.phy_cmd_timeout_rddata_cpt);
ddrc.write_dll_calib(cfg_set.dll_calib);
ddrc.write_odt_delay_hold(cfg_set.odt_delay_hold);
ddrc.write_ctrl_reg1(cfg_set.ctrl_reg1);
ddrc.write_ctrl_reg2(cfg_set.ctrl_reg2);
ddrc.write_ctrl_reg3(cfg_set.ctrl_reg3);
ddrc.write_ctrl_reg4(cfg_set.ctrl_reg4);
ddrc.write_ctrl_reg5(cfg_set.ctrl_reg5);
ddrc.write_ctrl_reg6(cfg_set.ctrl_reg6);
ddrc.write_che_t_zq(cfg_set.che_t_zq);
ddrc.write_che_t_zq_short_interval_reg(cfg_set.che_t_zq_short_interval_reg);
ddrc.write_deep_powerdown_reg(cfg_set.deep_powerdown);
ddrc.write_reg_2c(cfg_set.reg_2c);
ddrc.write_reg_2d(cfg_set.reg_2d);
ddrc.write_dfi_timing(cfg_set.dfi_timing);
ddrc.write_che_ecc_control(cfg_set.che_ecc_ctrl);
ddrc.write_ecc_scrub(cfg_set.ecc_scrub);
ddrc.write_phy_receiver_enable(cfg_set.phy_receiver_enable);
for i in 0..4 {
// Safety: Indexes are valid.
unsafe {
ddrc.write_phy_config_unchecked(i, cfg_set.phy_config[i]);
ddrc.write_phy_init_ratio_unchecked(i, cfg_set.phy_init_ratio[i]);
ddrc.write_phy_rd_dqs_cfg_unchecked(i, cfg_set.phy_rd_dqs_config[i]);
ddrc.write_phy_wr_dqs_cfg_unchecked(i, cfg_set.phy_wr_dqs_config[i]);
}
}
}

View File

@@ -13,10 +13,11 @@
extern crate alloc;
use slcr::Slcr;
use zynq7000::slcr::LevelShifterRegister;
use zynq7000::slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister};
pub mod cache;
pub mod clocks;
pub mod ddr;
pub mod eth;
pub mod gic;
pub mod gpio;
@@ -43,36 +44,26 @@ pub enum BootDevice {
}
#[derive(Debug, Copy, Clone)]
pub enum BootPllConfig {
Enabled,
Bypassed,
}
#[derive(Debug)]
pub struct BootMode {
boot_mode: Option<BootDevice>,
pll_config: BootPllConfig,
}
impl BootMode {
#[allow(clippy::new_without_default)]
/// Create a new boot mode information structure by reading the boot mode register from the
/// fixed SLCR block.
pub fn new() -> Self {
pub fn new_from_regs() -> Self {
// Safety: Only read a read-only register here.
Self::new_with_raw_reg(
unsafe { zynq7000::slcr::Slcr::new_mmio_fixed() }
.read_boot_mode()
.raw_value(),
)
Self::new_with_reg(unsafe { zynq7000::slcr::Slcr::new_mmio_fixed() }.read_boot_mode())
}
fn new_with_raw_reg(raw_register: u32) -> Self {
let msb_three_bits = (raw_register >> 1) & 0b111;
fn new_with_reg(boot_mode_reg: BootModeRegister) -> Self {
let boot_dev = boot_mode_reg.boot_mode();
let msb_three_bits = (boot_dev.value() >> 1) & 0b111;
let boot_mode = match msb_three_bits {
0b000 => {
if raw_register & 0b1 == 0 {
if boot_dev.value() & 0b1 == 0 {
Some(BootDevice::JtagCascaded)
} else {
Some(BootDevice::JtagIndependent)
@@ -84,21 +75,17 @@ impl BootMode {
0b110 => Some(BootDevice::SdCard),
_ => None,
};
let pll_config = if (raw_register >> 4) & 0b1 == 0 {
BootPllConfig::Enabled
} else {
BootPllConfig::Bypassed
};
Self {
boot_mode,
pll_config,
pll_config: boot_mode_reg.pll_config(),
}
}
pub fn boot_device(&self) -> Option<BootDevice> {
pub const fn boot_device(&self) -> Option<BootDevice> {
self.boot_mode
}
pub const fn pll_enable(&self) -> BootPllConfig {
pub const fn pll_config(&self) -> BootPllConfig {
self.pll_config
}
}

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

834
zynq7000/src/ddrc.rs Normal file
View File

@@ -0,0 +1,834 @@
pub const DDRC_BASE_ADDR: usize = 0xF800_6000;
pub mod regs {
use arbitrary_int::{u2, u3, u4, u5, u6, u7, u9, u10, u11, u12, u20};
#[bitbybit::bitenum(u2)]
#[derive(Debug, PartialEq, Eq)]
pub enum DataBusWidth {
_32Bit = 0b00,
_16Bit = 0b01,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum SoftReset {
Reset = 0,
Active = 1,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DdrcControl {
#[bit(16, rw)]
disable_auto_refresh: bool,
#[bit(15, rw)]
disable_active_bypass: bool,
#[bit(14, rw)]
disable_read_bypass: bool,
#[bits(7..=13, rw)]
read_write_idle_gap: u7,
#[bits(4..=6, rw)]
burst8_refresh: u3,
#[bits(2..=3, rw)]
data_bus_width: Option<DataBusWidth>,
#[bit(1, rw)]
power_down_enable: bool,
#[bit(0, rw)]
soft_reset: SoftReset,
}
#[bitbybit::bitfield(u32, default = 0x0)]
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,
}
/// Queue control for the low priority and high priority read queues.
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct LprHprQueueControl {
#[bits(22..=25, rw)]
xact_run_length: u4,
#[bits(11..=21, rw)]
max_starve_x32: u11,
#[bits(0..=10, rw)]
min_non_critical_x32: u11,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct WriteQueueControl {
#[bits(15..=25, rw)]
max_starve_x32: u11,
#[bits(11..=14, rw)]
xact_run_length: u4,
#[bits(0..=10, rw)]
min_non_critical_x32: u11,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DramParamReg0 {
/// Minimum time to wait after coming out of self refresh before doing anything. This must be
/// bigger than all the constraints that exist.
#[bits(14..=20, rw)]
post_selfref_gap_x32: u7,
/// tRFC(min) - Minimum time from refresh to refresh or activate in clock
/// cycles.
#[bits(6..=13, rw)]
t_rfc_min: u8,
/// tRC - Min time between activates to the same bank.
#[bits(0..=5, rw)]
t_rc: u6,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DramParamReg1 {
#[bits(28..=31, rw)]
t_cke: u4,
#[bits(22..=26, rw)]
t_ras_min: u5,
#[bits(16..=21, rw)]
t_ras_max: u6,
#[bits(10..=15, rw)]
t_faw: u6,
#[bits(5..=9, rw)]
powerdown_to_x32: u5,
#[bits(0..=4, rw)]
wr2pre: u5,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DramParamReg2 {
#[bits(28..=31, rw)]
t_rcd: u4,
#[bits(23..=27, rw)]
rd2pre: u5,
#[bits(20..=22, rw)]
pad_pd: u3,
#[bits(15..=19, rw)]
t_xp: u5,
#[bits(10..=14, rw)]
wr2rd: u5,
#[bits(5..=9, rw)]
rd2wr: u5,
#[bits(0..=4, rw)]
write_latency: u5,
}
/// Weird naming.
#[bitbybit::bitenum(u1, exhaustive = true)]
pub enum MobileSetting {
Ddr2Ddr3 = 0,
Lpddr2 = 1,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DramParamReg3 {
#[bit(30, rw)]
disable_pad_pd_feature: bool,
#[bits(24..=28, rw)]
read_latency: u5,
#[bit(23, rw)]
enable_dfi_dram_clk_disable: bool,
/// 0: DDR2 or DDR3. 1: LPDDR2.
#[bit(22, rw)]
mobile: MobileSetting,
/// Must be set to 0.
#[bit(21, rw)]
sdram: bool,
#[bits(16..=20, rw)]
refresh_to_x32: u5,
#[bits(12..=15, rw)]
t_rp: u4,
#[bits(8..=11, rw)]
refresh_margin: u4,
#[bits(5..=7, rw)]
t_rrd: u3,
#[bits(2..=4, rw)]
t_ccd: u3,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
pub enum ModeRegisterType {
Write = 0,
Read = 1,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DramParamReg4 {
#[bit(27, rw)]
mr_rdata_valid: bool,
#[bit(26, rw)]
mr_type: ModeRegisterType,
#[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,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DramEmr {
#[bits(16..=31, rw)]
emr3: u16,
#[bits(0..=15, rw)]
emr2: u16,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DramEmrMr {
#[bits(16..=31, rw)]
emr: u16,
#[bits(0..=15, rw)]
mr: u16,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DramBurst8ReadWrite {
#[bits(0..=3, rw)]
burst_rdwr: u4,
#[bits(4..=13, rw)]
pre_cke_x1024: u10,
#[bits(16..=25, rw)]
post_cke_x1024: u10,
#[bit(26, rw)]
burstchop: bool,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DisableDq {
#[bit(1, rw)]
dis_dq: bool,
#[bit(0, rw)]
force_low_pri_n: bool,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DramAddrMapBank {
#[bits(16..=19, rw)]
addrmap_bank_b6: u4,
#[bits(12..=15, rw)]
addrmap_bank_b5: u4,
#[bits(8..=11, rw)]
addrmap_bank_b2: u4,
#[bits(4..=7, rw)]
addrmap_bank_b1: u4,
#[bits(0..=3, rw)]
addrmap_bank_b0: u4,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DramAddrMapColumn {
#[bits(28..=31, rw)]
addrmap_col_b11: u4,
#[bits(24..=27, rw)]
addrmap_col_b10: u4,
#[bits(20..=23, rw)]
addrmap_col_b9: u4,
#[bits(16..=19, rw)]
addrmap_col_b8: u4,
#[bits(12..=15, rw)]
addrmap_col_b7: u4,
#[bits(8..=11, rw)]
addrmap_col_b4: u4,
#[bits(4..=7, rw)]
addrmap_col_b3: u4,
#[bits(0..=3, rw)]
addrmap_col_b2: u4,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DramAddrMapRow {
#[bits(24..=27, rw)]
addrmap_row_b15: u4,
#[bits(20..=23, rw)]
addrmap_row_b14: u4,
#[bits(16..=19, rw)]
addrmap_row_b13: u4,
#[bits(12..=15, rw)]
addrmap_row_b12: u4,
#[bits(8..=11, rw)]
addrmap_row_b2_11: u4,
#[bits(4..=7, rw)]
addrmap_row_b1: u4,
#[bits(0..=3, rw)]
addrmap_row_b0: u4,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DramOdt {
#[bits(16..=17, rw)]
phy_idle_local_odt: u2,
#[bits(14..=15, rw)]
phy_write_local_odt: u2,
#[bits(12..=13, rw)]
phy_read_local_odt: u2,
#[bits(3..=5, rw)]
rank0_wr_odt: u3,
#[bits(0..=2, rw)]
rank0_rd_odt: u3,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct PhyCmdTimeoutRdDataCpt {
#[bits(28..=31, rw)]
wrlvl_num_of_dq0: u4,
#[bits(24..=27, rw)]
gatelvl_num_of_dq0: u4,
#[bit(19, rw)]
clk_stall_level: bool,
#[bit(18, rw)]
dis_phy_ctrl_rstn: bool,
#[bit(17, rw)]
rdc_fifo_rst_err_cnt_clr: bool,
#[bit(16, rw)]
use_fixed_re: bool,
#[bits(8..=11, rw)]
rdc_we_to_re_delay: u4,
#[bits(4..=7, rw)]
wr_cmd_to_data: u4,
#[bits(0..=3, rw)]
rd_cmd_to_data: u4,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
pub enum DllCalibSel {
Periodic = 0,
Manual = 1,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DllCalib {
#[bit(16, rw)]
sel: DllCalibSel,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct OdtDelayHold {
#[bits(12..=15, rw)]
wr_odt_hold: u4,
#[bits(8..=11, rw)]
rd_odt_hold: u4,
#[bits(4..=7, rw)]
wr_odt_delay: u4,
#[bits(0..=3, rw)]
rd_odt_delay: u4,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct CtrlReg1 {
#[bit(12, rw)]
selfref_enable: bool,
#[bit(10, rw)]
dis_collision_page_opt: bool,
#[bit(9, rw)]
dis_wc: bool,
#[bit(8, rw)]
refresh_update_level: bool,
#[bit(7, rw)]
auto_pre_en: bool,
#[bits(1..=6, rw)]
lpr_num_entries: u6,
#[bit(0, rw)]
pageclose: bool,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct CtrlReg2 {
#[bit(17, rw)]
go_2_critcal_enable: bool,
#[bits(5..=12, rw)]
go_2_critical_hysteresis: u8,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct CtrlReg3 {
#[bits(16..=25, rw)]
dfi_t_wlmrd: u10,
#[bits(8..=15, rw)]
rdlvl_rr: u8,
#[bits(0..=7, rw)]
wrlvl_ww: u8,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct CtrlReg4 {
#[bits(8..=15, rw)]
dfi_t_ctrlupd_interval_max_x1024: u8,
#[bits(0..=7, rw)]
dfi_t_ctrlupd_interval_min_x1024: u8,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct CtrlReg5 {
#[bits(20..=25, rw)]
t_ckesr: u6,
#[bits(16..=19, rw)]
t_cksrx: u4,
#[bits(12..=15, rw)]
t_ckrse: u4,
#[bits(8..=11, rw)]
dfi_t_dram_clk_enable: u4,
#[bits(4..=7, rw)]
dfi_t_dram_clk_disable: u4,
#[bits(0..=3, rw)]
dfi_t_ctrl_delay: u4,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct CtrlReg6 {
#[bits(16..=19, rw)]
t_cksx: u4,
#[bits(12..=15, rw)]
t_ckdpdx: u4,
#[bits(8..=11, rw)]
t_ckdpde: u4,
#[bits(4..=7, rw)]
t_ckpdx: u4,
#[bits(0..=3, rw)]
t_ckpde: u4,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct CheTZq {
#[bits(22..=31, rw)]
t_zq_short_nop: u10,
#[bits(12..=21, rw)]
t_zq_long_nop: u10,
#[bits(2..=11, rw)]
t_mode: u10,
#[bit(1, rw)]
ddr3: bool,
#[bit(0, rw)]
dis_auto_zq: bool,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct CheTZqShortInterval {
#[bits(20..=27, rw)]
dram_rstn_x1024: u8,
#[bits(0..=19, rw)]
t_zq_short_interval: u20,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DeepPowerdown {
#[bits(1..=8, rw)]
deep_powerdown_to_x1024: u8,
#[bit(0, rw)]
enable: bool,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct Reg2c {
#[bit(28, rw)]
dfi_rd_data_eye_train: bool,
#[bit(27, rw)]
dfi_rd_dqs_gate_level: bool,
#[bit(26, rw)]
dfi_wr_level_enable: bool,
#[bit(25, rw)]
trdlvl_max_error: bool,
#[bit(24, rw)]
twrlvl_max_error: bool,
#[bits(12..=23, rw)]
dfi_rdlvl_max_x1024: u12,
#[bits(0..=11, rw)]
dfi_wrlvl_max_x1024: u12,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct Reg2d {
#[bit(9, rw)]
skip_ocd: bool,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DfiTiming {
#[bits(15..=24, rw)]
dfi_t_ctrlup_max: u10,
#[bits(5..=14, rw)]
dfi_t_ctrlup_min: u10,
#[bits(0..=4, rw)]
dfi_t_rddata_enable: u5,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct CheEccControl {
#[bit(1, rw)]
clear_correctable_errors: bool,
#[bit(0, rw)]
clear_uncorrectable_errors: bool,
}
#[bitbybit::bitenum(u3, exhaustive = false)]
pub enum EccMode {
NoEcc = 0b000,
SecDecOverOneBeat = 0b100,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct EccScrub {
#[bit(3, rw)]
disable_scrub: bool,
#[bits(0..=2, rw)]
ecc_mode: Option<EccMode>,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct PhyReceiverEnable {
#[bits(4..=7, rw)]
phy_dif_off: u4,
#[bits(0..=3, rw)]
phy_dif_on: u4,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct PhyConfig {
#[bits(24..=30, rw)]
dq_offset: u7,
#[bit(3, rw)]
wrlvl_inc_mode: bool,
#[bit(2, rw)]
gatelvl_inc_mode: bool,
#[bit(1, rw)]
rdlvl_inc_mode: bool,
#[bit(0, rw)]
data_slice_in_use: bool,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct PhyInitRatio {
#[bits(10..=19, rw)]
gatelvl_init_ratio: u10,
#[bits(0..=9, rw)]
wrlvl_init_ratio: u10,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct PhyDqsConfig {
#[bits(11..=19, rw)]
dqs_slave_delay: u9,
#[bit(10, rw)]
dqs_slave_force: bool,
#[bits(0..=9, rw)]
dqs_slave_ratio: u10,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct PhyWriteEnableConfig {
#[bits(12..=20, rw)]
fifo_we_in_delay: u9,
#[bit(11, rw)]
fifo_we_in_force: bool,
#[bits(0..=10, rw)]
fifo_we_slave_ratio: u11,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct PhyWriteDataSlaveConfig {
#[bits(11..=19, rw)]
wr_data_slave_delay: u9,
#[bit(10, rw)]
wr_data_slave_force: bool,
#[bits(0..=9, rw)]
wr_data_slave_ratio: u10,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct Reg64 {
#[bit(30, rw)]
cmd_latency: bool,
#[bit(29, rw)]
lpddr: bool,
#[bits(21..=27, rw)]
ctrl_slave_delay: u7,
#[bit(20, rw)]
ctrl_slave_force: bool,
#[bits(10..=19, rw)]
ctrl_slave_ratio: u10,
#[bit(9, rw)]
sel_logic: bool,
#[bit(7, rw)]
invert_clkout: bool,
#[bit(1, rw)]
bl2: bool,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct Reg65 {
#[bits(18..=19, rw)]
ctrl_slave_delay: u2,
#[bit(17, rw)]
dis_calib_rst: bool,
#[bit(16, rw)]
use_rd_data_eye_level: bool,
#[bit(15, rw)]
use_rd_dqs_gate_level: bool,
#[bit(14, rw)]
use_wr_level: bool,
#[bits(10..=13, rw)]
dll_lock_diff: u4,
#[bits(5..=9, rw)]
rd_rl_delay: u5,
#[bits(0..=4, rw)]
wr_rl_delay: u5,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct AxiPriorityWritePort {
#[bit(18, rw)]
disable_page_match: bool,
#[bit(17, rw)]
disable_urgent: bool,
#[bit(16, rw)]
disable_aging: bool,
#[bits(0..=9, rw)]
pri_wr_port: u10,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct AxiPriorityReadPort {
#[bit(19, rw)]
enable_hpr: bool,
#[bit(18, rw)]
disable_page_match: bool,
#[bit(17, rw)]
disable_urgent: bool,
#[bit(16, rw)]
disable_aging: bool,
#[bits(0..=9, rw)]
pri_wr_port_n: u10,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct ExclusiveAccessConfig {
#[bits(9..=17, rw)]
access_id1_port: u9,
#[bits(0..=8, rw)]
access_id0_port: u9,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
pub enum LpddrBit {
Ddr2Ddr3 = 0,
Lpddr2 = 1,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct LpddrControl0 {
#[bits(4..=11, rw)]
mr4_margin: u8,
#[bit(2, rw)]
derate_enable: bool,
#[bit(1, rw)]
per_bank_refresh: bool,
#[bit(0, rw)]
lpddr2: LpddrBit,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct LpddrControl1 {
#[bits(0..=31, rw)]
mr4_read_interval: u32,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct LpddrControl2 {
#[bits(12..=21, rw)]
t_mrw: u10,
#[bits(4..=11, rw)]
idle_after_reset_x32: u8,
#[bits(0..=3, rw)]
min_stable_clock_x1: u4,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct LpddrControl3 {
#[bits(8..=17, rw)]
dev_zqinit_x32: u10,
#[bits(0..=7, rw)]
max_auto_init_x1024: u8,
}
}
use regs::*;
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct DdrController {
ddrc_ctrl: DdrcControl,
two_rank_cfg: TwoRankConfig,
hpr_queue_ctrl: LprHprQueueControl,
lpr_queue_ctrl: LprHprQueueControl,
wr_reg: WriteQueueControl,
dram_param_reg0: DramParamReg0,
dram_param_reg1: DramParamReg1,
dram_param_reg2: DramParamReg2,
dram_param_reg3: DramParamReg3,
dram_param_reg4: DramParamReg4,
dram_init_param: DramInitParam,
dram_emr: DramEmr,
dram_emr_mr: DramEmrMr,
dram_burst8_rdwr: DramBurst8ReadWrite,
dram_disable_dq: DisableDq,
dram_addr_map_bank: DramAddrMapBank,
dram_addr_map_col: DramAddrMapColumn,
dram_addr_map_row: DramAddrMapRow,
dram_odt_reg: DramOdt,
#[mmio(PureRead)]
phy_debug_reg: u32,
phy_cmd_timeout_rddata_cpt: PhyCmdTimeoutRdDataCpt,
#[mmio(PureRead)]
mode_status: u32,
dll_calib: DllCalib,
odt_delay_hold: OdtDelayHold,
ctrl_reg1: CtrlReg1,
ctrl_reg2: CtrlReg2,
ctrl_reg3: CtrlReg3,
ctrl_reg4: CtrlReg4,
_reserved0: [u32; 0x2],
ctrl_reg5: CtrlReg5,
ctrl_reg6: CtrlReg6,
_reserved1: [u32; 0x8],
che_refresh_timer_01: u32,
che_t_zq: CheTZq,
che_t_zq_short_interval_reg: CheTZqShortInterval,
deep_powerdown_reg: DeepPowerdown,
reg_2c: Reg2c,
reg_2d: Reg2d,
dfi_timing: DfiTiming,
_reserved2: [u32; 0x2],
che_ecc_control: CheEccControl,
#[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: EccScrub,
#[mmio(PureRead)]
che_ecc_corr_bit_mask_31_0: u32,
#[mmio(PureRead)]
che_ecc_corr_bit_mask_63_32: u32,
_reserved3: [u32; 0x5],
phy_receiver_enable: PhyReceiverEnable,
phy_config: [PhyConfig; 0x4],
_reserved4: u32,
phy_init_ratio: [PhyInitRatio; 4],
_reserved5: u32,
phy_rd_dqs_cfg: [PhyDqsConfig; 4],
_reserved6: u32,
phy_wr_dqs_cfg: [PhyDqsConfig; 4],
_reserved7: u32,
phy_we_cfg: [PhyWriteEnableConfig; 4],
_reserved8: u32,
wr_data_slave: [PhyWriteDataSlaveConfig; 4],
_reserved9: u32,
reg_64: Reg64,
reg_65: Reg65,
_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,
#[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: [AxiPriorityWritePort; 0x4],
axi_priority_rd_port: [AxiPriorityReadPort; 0x4],
_reserved15: [u32; 0x1B],
excl_access_cfg: [ExclusiveAccessConfig; 0x4],
#[mmio(PureRead)]
mode_reg_read: u32,
lpddr_ctrl_0: LpddrControl0,
lpddr_ctrl_1: LpddrControl1,
lpddr_ctrl_2: LpddrControl2,
lpddr_ctrl_3: LpddrControl3,
}
static_assertions::const_assert_eq!(core::mem::size_of::<DdrController>(), 0x2B8);
impl DdrController {
/// Create a new DDR MMIO instance for the DDR controller at address [DDRC_BASE_ADDR].
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
pub const unsafe fn new_mmio_fixed() -> MmioDdrController<'static> {
unsafe { Self::new_mmio_at(DDRC_BASE_ADDR) }
}
}

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;
@@ -40,6 +41,7 @@ pub struct PsPeripherals {
pub gicc: gic::MmioGicCpuInterface<'static>,
pub gicd: gic::MmioGicDistributor<'static>,
pub l2c: l2_cache::MmioL2Cache<'static>,
pub ddrc: ddrc::MmioDdrController<'static>,
pub uart_0: uart::MmioUart<'static>,
pub uart_1: uart::MmioUart<'static>,
pub spi_0: spi::MmioSpi<'static>,
@@ -76,6 +78,7 @@ impl PsPeripherals {
gicc: gic::GicCpuInterface::new_mmio_fixed(),
gicd: gic::GicDistributor::new_mmio_fixed(),
l2c: l2_cache::L2Cache::new_mmio_fixed(),
ddrc: ddrc::DdrController::new_mmio_fixed(),
uart_0: uart::Uart::new_mmio_fixed_0(),
uart_1: uart::Uart::new_mmio_fixed_1(),
gtc: gtc::GlobalTimerCounter::new_mmio_fixed(),

View File

@@ -4,18 +4,12 @@
use super::{CLOCK_CONTROL_OFFSET, SLCR_BASE_ADDR};
use arbitrary_int::{u4, u6, u7, u10};
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug)]
pub enum BypassForce {
EnabledOrSetByBootMode = 0b0,
Bypassed = 0b1,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug)]
pub enum BypassQual {
BypassForceBit = 0b0,
BootModeFourthBit = 0b1,
pub enum Bypass {
NotBypassed = 0b00,
/// This is the default reset value.
PinStrapSettings = 0b01,
Bypassed = 0b10,
BypassedRegardlessOfPinStrapping = 0b11,
}
#[bitbybit::bitfield(u32)]
@@ -29,10 +23,10 @@ pub struct PllControl {
fdiv: u7,
/// Select source for the ARM PLL bypass control
#[bit(4, rw)]
bypass_force: BypassForce,
bypass_force: bool,
/// Select source for the ARM PLL bypass control
#[bit(3, rw)]
bypass_qual: BypassQual,
bypass_qual: bool,
// Power-down control
#[bit(1, rw)]
pwrdwn: bool,
@@ -41,6 +35,40 @@ pub struct PllControl {
reset: bool,
}
impl PllControl {
#[inline]
pub fn set_bypass(&mut self, bypass: Bypass) {
match bypass {
Bypass::NotBypassed => {
self.set_bypass_force(false);
self.set_bypass_qual(false);
}
Bypass::PinStrapSettings => {
self.set_bypass_force(false);
self.set_bypass_qual(true);
}
Bypass::Bypassed => {
self.set_bypass_force(true);
self.set_bypass_qual(false);
}
Bypass::BypassedRegardlessOfPinStrapping => {
self.set_bypass_force(true);
self.set_bypass_qual(true);
}
}
}
#[inline]
pub fn bypass(&self) -> Bypass {
match (self.bypass_force(), self.bypass_qual()) {
(false, false) => Bypass::NotBypassed,
(false, true) => Bypass::PinStrapSettings,
(true, false) => Bypass::Bypassed,
(true, true) => Bypass::BypassedRegardlessOfPinStrapping,
}
}
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct PllConfig {
@@ -48,26 +76,26 @@ pub struct PllConfig {
lock_count: u10,
/// Charge Pump control
#[bits(8..=11, rw)]
pll_cp: u4,
charge_pump: u4,
/// Loop resistor control
#[bits(4..=7, rw)]
pll_res: u4,
loop_resistor: u4,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct PllStatus {
#[bit(5)]
#[bit(5, r)]
io_pll_stable: bool,
#[bit(4)]
#[bit(4, r)]
ddr_pll_stable: bool,
#[bit(3)]
#[bit(3, r)]
arm_pll_stable: bool,
#[bit(2)]
#[bit(2, r)]
io_pll_lock: bool,
#[bit(1)]
#[bit(1, r)]
drr_pll_lock: bool,
#[bit(0)]
#[bit(0, r)]
arm_pll_lock: bool,
}
@@ -141,7 +169,7 @@ pub struct DdrClockControl {
ddr_3x_clk_act: bool,
}
#[bitbybit::bitfield(u32)]
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DciClockControl {
/// Second cascade divider. Reset value: 0x1E
#[bits(20..=25, rw)]
@@ -341,9 +369,9 @@ pub struct AperClockControl {
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct ClockControl {
arm_pll: PllControl,
ddr_pll: PllControl,
io_pll: PllControl,
arm_pll_ctrl: PllControl,
ddr_pll_ctrl: PllControl,
io_pll_ctrl: PllControl,
pll_status: PllStatus,
arm_pll_cfg: PllConfig,
ddr_pll_cfg: PllConfig,

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

@@ -0,0 +1,139 @@
use arbitrary_int::{u2, u3};
#[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,
@@ -93,11 +59,19 @@ impl GpiobRegisters {
}
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum BootPllConfig {
Enabled = 0,
/// Disabled and bypassed.
Bypassed = 1,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct BootModeRegister {
#[bit(4, r)]
pll_bypass: bool,
pll_config: BootPllConfig,
#[bits(0..=3, r)]
boot_mode: u4,
}
@@ -205,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);