Compare commits

1 Commits

Author SHA1 Message Date
73fc553b18 Introduce Rust FSBL
Some checks failed
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
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
2025-07-31 19:56:40 +02:00
32 changed files with 1766 additions and 127 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

@@ -250,7 +250,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,
};

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

@@ -0,0 +1,98 @@
//! 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::{
clocks::pll::{configure_arm_pll, configure_ddr_pll, configure_io_pll, PllConfig}, ddr::{calculate_dci_divisors, calibrate_iob_impedance_for_ddr3, configure_iob}, time::Hertz, BootMode
};
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::PllCtrl,
pll_cfg_reg: *mut zynq7000::slcr::clocks::PllCfg,
) {
// 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::BootPllCfg::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) };
}

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

@@ -0,0 +1,311 @@
use arbitrary_int::{Number, u2, u3, u4, u5, u6, u7, u10, u11, u12};
use zynq7000::{
ddrc::{
DataBusWidth, DdrcControl, DisableDq, DramBurst8ReadWrite, DramEmr, DramEmrMr,
DramInitParam, DramParamReg0, DramParamReg1, DramParamReg2, DramParamReg3, DramParamReg4,
LprHprQueueControl, MmioDdrController, MobileSetting, ModeRegisterType, SoftReset,
TwoRankConfig, WriteQueueControl,
},
slcr::{clocks::DciClkCtrl, 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() as u8),
div1: u6::new(u6::MAX.value() as u8),
};
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 done once here during start-up.
unsafe {
crate::Slcr::with(|slcr| {
slcr.clk_ctrl().write_dci_clk_ctrl(
DciClkCtrl::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,
) {
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,
}
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 fn configure_ddr(mut ddrc: MmioDdrController<'static>) {
// Lock the DDR.
ddrc.write_ddrc_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(),
);
ddrc.write_two_rank_cfg(
TwoRankConfig::builder()
.with_addrmap_cs_bit0(u5::new(0))
.with_ddrc_active_ranks(u2::new(1))
.with_rfc_nom_x32(u12::new(0x82))
.build(),
);
ddrc.write_hpr_queue_ctrl(
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(),
);
ddrc.write_lpr_queue_ctrl(
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(),
);
ddrc.write_wr_reg(
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(),
);
ddrc.write_dram_param_reg0(
DramParamReg0::builder()
.with_post_selfref_gap_x32(u7::new(0x10))
.with_t_rfc_min(0x56)
.with_t_rc(u6::new(0x1b))
.build(),
);
ddrc.write_dram_param_reg0(
DramParamReg0::builder()
.with_post_selfref_gap_x32(u7::new(0x10))
.with_t_rfc_min(0x56)
.with_t_rc(u6::new(0x1b))
.build(),
);
ddrc.write_dram_param_reg1(
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(),
);
ddrc.write_dram_param_reg2(
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(),
);
ddrc.write_dram_param_reg3(
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(),
);
ddrc.write_dram_param_reg4(
DramParamReg4::builder()
.with_mr_rdata_valid(false)
.with_mr_type(ModeRegisterType::Write)
.with_mr_wr_busy(false)
.with_mr_data(u16::new(0x0))
.with_mr_addr(u2::new(0x0))
.with_mr_wr(false)
.with_prefer_write(false)
.with_enable_2t_timing_mode(false)
.build(),
);
ddrc.write_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(),
);
ddrc.write_dram_emr(DramEmr::builder().with_emr3(0x0).with_emr2(0x8).build());
ddrc.write_dram_emr_mr(DramEmrMr::builder().with_emr(0x4).with_mr(0xb30).build());
ddrc.write_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(),
);
ddrc.write_dram_disable_dq(
DisableDq::builder()
.with_dis_dq(false)
.with_force_low_pri_n(false)
.build(),
);
}

View File

@@ -10,7 +10,8 @@ use arbitrary_int::Number;
use cortex_ar::interrupt;
use zynq7000::gic::{
Dcr, GicCpuInterface, GicDistributor, InterfaceCtrl, InterruptSignalRegister, MmioGicCpuInterface, MmioGicDistributor, PriorityReg,
Dcr, GicCpuInterface, GicDistributor, InterfaceCtrl, InterruptSignalRegister,
MmioGicCpuInterface, MmioGicDistributor, PriorityReg,
};
const SPURIOUS_INTERRUPT_ID: u32 = 1023;
@@ -231,7 +232,10 @@ impl GicConfigurator {
/// Create a new GIC controller instance and calls [Self::initialize] to perform
/// strongly recommended initialization routines for the GIC.
#[inline]
pub fn new_with_init(gicc: MmioGicCpuInterface<'static>, gicd: MmioGicDistributor<'static>) -> Self {
pub fn new_with_init(
gicc: MmioGicCpuInterface<'static>,
gicd: MmioGicDistributor<'static>,
) -> Self {
let mut gic = GicConfigurator { gicc, gicd };
gic.initialize();
gic

View File

@@ -10,10 +10,11 @@
#![no_std]
use slcr::Slcr;
use zynq7000::slcr::LevelShifterReg;
use zynq7000::slcr::{BootModeReg, BootPllCfg, LevelShifterReg};
pub mod cache;
pub mod clocks;
pub mod ddr;
pub mod eth;
pub mod gic;
pub mod gpio;
@@ -40,36 +41,26 @@ pub enum BootDevice {
}
#[derive(Debug, Copy, Clone)]
pub enum BootPllCfg {
Enabled,
Bypassed,
}
#[derive(Debug)]
pub struct BootMode {
boot_mode: Option<BootDevice>,
pll_config: BootPllCfg,
}
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: BootModeReg) -> 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)
@@ -81,21 +72,17 @@ impl BootMode {
0b110 => Some(BootDevice::SdCard),
_ => None,
};
let pll_config = if (raw_register >> 4) & 0b1 == 0 {
BootPllCfg::Enabled
} else {
BootPllCfg::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) -> BootPllCfg {
pub const fn pll_config(&self) -> BootPllCfg {
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

@@ -21,8 +21,8 @@ pub use embedded_hal::spi::Mode;
use embedded_hal::spi::{MODE_0, MODE_1, MODE_2, MODE_3, SpiBus as _};
use zynq7000::slcr::reset::DualRefAndClkRst;
use zynq7000::spi::{
BaudDivSel, DelayControl, FifoWrite, InterruptControl, InterruptMask, InterruptStatus,
MmioSpi, SPI_0_BASE_ADDR, SPI_1_BASE_ADDR,
BaudDivSel, DelayControl, FifoWrite, InterruptControl, InterruptMask, InterruptStatus, MmioSpi,
SPI_0_BASE_ADDR, SPI_1_BASE_ADDR,
};
pub const FIFO_DEPTH: usize = 128;
@@ -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);

View File

@@ -186,10 +186,7 @@ pub enum TtcConstructionError {
FrequencyIsZero(#[from] FrequencyIsZeroError),
}
pub fn calc_prescaler_reg_and_interval_ticks(
mut ref_clk: Hertz,
freq: Hertz,
) -> (Option<u4>, u16) {
pub fn calc_prescaler_reg_and_interval_ticks(mut ref_clk: Hertz, freq: Hertz) -> (Option<u4>, u16) {
// TODO: Can this be optimized?
let mut prescaler_reg: Option<u4> = None;
let mut tick_val = ref_clk / freq;
@@ -261,8 +258,7 @@ impl Pwm {
return Err(FrequencyIsZeroError);
}
let id = self.channel.id() as usize;
let (prescaler_reg, tick_val) =
calc_prescaler_reg_and_interval_ticks(self.ref_clk, freq);
let (prescaler_reg, tick_val) = calc_prescaler_reg_and_interval_ticks(self.ref_clk, freq);
self.set_up_and_configure_pwm(id, prescaler_reg, tick_val);
Ok(())
}

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

@@ -0,0 +1,579 @@
use arbitrary_int::{u2, u3, u4, u5, u6, u7, u10, u11, u12};
pub const DDRC_BASE_ADDR: usize = 0xF800_6000;
#[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,
}
#[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: u32,
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: u32,
deep_powerdown_reg: u32,
reg_2c: u32,
reg_2d: u32,
dfi_timing: u32,
_reserved2: [u32; 0x2],
che_corr_control: u32,
#[mmio(PureRead)]
che_corr_ecc_log: u32,
#[mmio(PureRead)]
che_corr_ecc_addr: u32,
#[mmio(PureRead)]
che_corr_ecc_data_31_0: u32,
#[mmio(PureRead)]
che_corr_ecc_data_63_32: u32,
#[mmio(PureRead)]
che_corr_ecc_data_71_64: u32,
/// Clear on write, but the write is performed on another register.
#[mmio(PureRead)]
che_uncorr_ecc_log: u32,
#[mmio(PureRead)]
che_uncorr_ecc_addr: u32,
#[mmio(PureRead)]
che_uncorr_ecc_data_31_0: u32,
#[mmio(PureRead)]
che_uncorr_ecc_data_63_32: u32,
#[mmio(PureRead)]
che_uncorr_ecc_data_71_64: u32,
#[mmio(PureRead)]
che_ecc_stats: u32,
ecc_scrub: u32,
che_ecc_corr_bit_mask_31_0: u32,
che_ecc_corr_bit_mask_63_32: u32,
_reserved3: [u32; 0x5],
phy_receiver_enable: u32,
phy_config: [u32; 4],
_reserved4: u32,
phy_init_ratio: [u32; 4],
_reserved5: u32,
phy_rd_dqs_cfg: [u32; 4],
_reserved6: u32,
phy_wr_dqs_cfg: [u32; 4],
_reserved7: u32,
phy_we_cfg_0: [u32; 4],
_reserved8: u32,
wr_data_slave: [u32; 4],
_reserved9: u32,
reg_64: u32,
reg_65: u32,
_reserved10: [u32; 3],
#[mmio(PureRead)]
reg69_6a0: u32,
#[mmio(PureRead)]
reg69_6a1: u32,
_reserved11: u32,
#[mmio(PureRead)]
reg69_6d2: u32,
#[mmio(PureRead)]
reg69_6d3: u32,
#[mmio(PureRead)]
reg69_710: u32,
#[mmio(PureRead)]
reg6e_711: u32,
#[mmio(PureRead)]
reg6e_712: u32,
#[mmio(PureRead)]
reg6e_713: u32,
_reserved12: u32,
#[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: [u32; 4],
axi_priority_rd_port: [u32; 4],
_reserved15: [u32; 0x1B],
excl_access_cfg: [u32; 4],
#[mmio(PureRead)]
mode_reg_read: u32,
lpddr_ctrl_0: u32,
lpddr_ctrl_1: u32,
lpddr_ctrl_2: u32,
lpddr_ctrl_3: u32,
}
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 PllCtrl {
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 PllCtrl {
reset: bool,
}
impl PllCtrl {
#[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 PllCfg {
@@ -48,26 +76,26 @@ pub struct PllCfg {
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 DdrClkCtrl {
ddr_3x_clk_act: bool,
}
#[bitbybit::bitfield(u32)]
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DciClkCtrl {
/// Second cascade divider. Reset value: 0x1E
#[bits(20..=25, rw)]
@@ -341,9 +369,9 @@ pub struct AperClkCtrl {
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct ClockControl {
arm_pll: PllCtrl,
ddr_pll: PllCtrl,
io_pll: PllCtrl,
arm_pll_ctrl: PllCtrl,
ddr_pll_ctrl: PllCtrl,
io_pll_ctrl: PllCtrl,
pll_status: PllStatus,
arm_pll_cfg: PllCfg,
ddr_pll_cfg: PllCfg,

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 BootPllCfg {
Enabled = 0,
/// Disabled and bypassed.
Bypassed = 1,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct BootModeReg {
#[bit(4, r)]
pll_bypass: bool,
pll_config: BootPllCfg,
#[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);