Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
73fc553b18
|
@@ -8,7 +8,7 @@ members = [
|
||||
"examples/simple",
|
||||
"examples/embassy",
|
||||
"examples/zedboard",
|
||||
"zynq-mmu",
|
||||
"zynq-mmu", "fsbl",
|
||||
]
|
||||
exclude = ["experiments"]
|
||||
|
||||
|
@@ -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));
|
||||
|
@@ -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);
|
||||
|
@@ -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));
|
||||
|
@@ -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));
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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]> =
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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));
|
||||
|
@@ -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);
|
||||
|
@@ -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
25
fsbl/Cargo.toml
Normal 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
24
fsbl/memory.x
Normal 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
56
fsbl/src/ddr_cfg.rs
Normal 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
98
fsbl/src/main.rs
Normal 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 {}
|
||||
}
|
3
memory.x
3
memory.x
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
356
zynq7000-hal/src/clocks/pll.rs
Normal file
356
zynq7000-hal/src/clocks/pll.rs
Normal 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
311
zynq7000-hal/src/ddr.rs
Normal 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(),
|
||||
);
|
||||
}
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -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.
|
||||
|
@@ -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);
|
||||
|
@@ -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
579
zynq7000/src/ddrc.rs
Normal 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) }
|
||||
}
|
||||
}
|
@@ -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(),
|
||||
|
@@ -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
139
zynq7000/src/slcr/ddriob.rs
Normal 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);
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user