Introduce Rust FSBL
Some checks failed
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
Some checks failed
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
This commit is contained in:
@@ -9,6 +9,7 @@ members = [
|
||||
"examples/embassy",
|
||||
"examples/zedboard",
|
||||
"zynq-mmu",
|
||||
"zedboard-fsbl", "zedboard-bsp",
|
||||
]
|
||||
exclude = ["experiments"]
|
||||
|
||||
@@ -21,3 +22,7 @@ incremental = false
|
||||
lto = true
|
||||
opt-level = 3 # <-
|
||||
overflow-checks = false # <-
|
||||
|
||||
[profile.small]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
|
||||
4
boot-image-staging/.gitignore
vendored
Normal file
4
boot-image-staging/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/fsbl.elf
|
||||
/fpga.bit
|
||||
/application.elf
|
||||
/boot.bin
|
||||
24
boot-image-staging/README.md
Normal file
24
boot-image-staging/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
Boot Image Creation Staging
|
||||
========
|
||||
|
||||
This folder provides the basic files required to create bare metal boot images.
|
||||
|
||||
This includes a simple `boot.bif` file which can be used by the AMD
|
||||
[bootgen](https://docs.amd.com/r/en-US/ug1283-bootgen-user-guide) utility
|
||||
to create a boot binary consisting of
|
||||
|
||||
- A first-stage bootloader (FSBL)
|
||||
- A FPGA bitstream which can be loaded to the Zynq PL by the FSBL.
|
||||
- A primary application which runs in DDR memory and is also copied to DDR by the FSBL.
|
||||
|
||||
## Example for the Zedboard
|
||||
|
||||
An example use-case for the Zedboard would be a `boot.bin` containing the `zedboard-fsbl` Rust
|
||||
FSBL or the Xilinx FSBL, a bitstream `zedboard-rust.bit` generated from the `zedboard-fpga-design`
|
||||
project or your own Vivado project and finally any primary application of your choice which
|
||||
should run in DDR.
|
||||
|
||||
You can copy the files into the staging area, using the file names `fsbl.elf`, `fpga.bit` and
|
||||
`application.elf`, which are also ignored by the VCS.
|
||||
|
||||
Then, you can simply run `bootgen` to generate the boot binary.
|
||||
6
boot-image-staging/boot.bif
Normal file
6
boot-image-staging/boot.bif
Normal file
@@ -0,0 +1,6 @@
|
||||
all:
|
||||
{
|
||||
[bootloader] fsbl.elf
|
||||
fpga.bit
|
||||
[load=0x100000] application.elf
|
||||
}
|
||||
31
examples/embassy/build.rs
Normal file
31
examples/embassy/build.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
// By default, Cargo will re-run a build script whenever
|
||||
// any file in the project changes. By specifying `memory.x`
|
||||
// here, we ensure the build script is only re-run when
|
||||
// `memory.x` is changed.
|
||||
println!("cargo:rerun-if-changed=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
|
||||
@@ -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));
|
||||
|
||||
31
examples/simple/build.rs
Normal file
31
examples/simple/build.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
// By default, Cargo will re-run a build script whenever
|
||||
// any file in the project changes. By specifying `memory.x`
|
||||
// here, we ensure the build script is only re-run when
|
||||
// `memory.x` is changed.
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
}
|
||||
22
examples/simple/memory.x
Normal file
22
examples/simple/memory.x
Normal file
@@ -0,0 +1,22 @@
|
||||
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 MMU. This is
|
||||
recommended for something like DMA descriptors. */
|
||||
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M
|
||||
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
|
||||
}
|
||||
@@ -21,7 +21,7 @@ use zynq7000_hal::{
|
||||
use zynq7000_rt as _;
|
||||
|
||||
// Define the clock frequency as a constant
|
||||
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
|
||||
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_333);
|
||||
|
||||
static MS_TICKS: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -4,25 +4,33 @@
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_hal::{delay::DelayNs, digital::StatefulOutputPin};
|
||||
use zynq7000::PsPeripherals;
|
||||
use zynq7000_hal::{
|
||||
clocks::Clocks,
|
||||
gpio::{Output, PinState, mio},
|
||||
l2_cache,
|
||||
priv_tim::CpuPrivateTimer,
|
||||
time::Hertz,
|
||||
};
|
||||
use zynq7000_rt as _;
|
||||
|
||||
pub const LIB: Lib = Lib::Hal;
|
||||
|
||||
/// One user LED is MIO7
|
||||
const ZEDBOARD_LED_MASK: u32 = 1 << 7;
|
||||
|
||||
// Define the clock frequency as a constant.
|
||||
//
|
||||
// Not required for the PAC mode, is required for clean delays in HAL mode.
|
||||
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_333);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Lib {
|
||||
Pac,
|
||||
Hal,
|
||||
}
|
||||
|
||||
const LIB: Lib = Lib::Hal;
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
@@ -49,13 +57,14 @@ pub fn main() -> ! {
|
||||
}
|
||||
Lib::Hal => {
|
||||
let dp = PsPeripherals::take().unwrap();
|
||||
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
// Unwrap okay, we only call this once on core 0 here.
|
||||
let mut cpu_tim = CpuPrivateTimer::take(clocks.arm_clocks()).unwrap();
|
||||
let mio_pins = mio::Pins::new(dp.gpio);
|
||||
let mut led = Output::new_for_mio(mio_pins.mio7, PinState::High);
|
||||
loop {
|
||||
led.toggle().unwrap();
|
||||
for _ in 0..5_000_000 {
|
||||
nop();
|
||||
}
|
||||
cpu_tim.delay_ms(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,22 +73,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();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ zynq7000-rt = { path = "../../zynq7000-rt" }
|
||||
zynq7000 = { path = "../../zynq7000" }
|
||||
zynq7000-hal = { path = "../../zynq7000-hal" }
|
||||
zynq7000-embassy = { path = "../../zynq7000-embassy" }
|
||||
zedboard-bsp = { path = "../../zedboard-bsp" }
|
||||
num_enum = { version = "0.7", default-features = false }
|
||||
l3gd20 = { git = "https://github.com/us-irs/l3gd20.git", branch = "add-async-if" }
|
||||
embedded-io = "0.6"
|
||||
bitbybit = "1.3"
|
||||
|
||||
31
examples/zedboard/build.rs
Normal file
31
examples/zedboard/build.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
// By default, Cargo will re-run a build script whenever
|
||||
// any file in the project changes. By specifying `memory.x`
|
||||
// here, we ensure the build script is only re-run when
|
||||
// `memory.x` is changed.
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
}
|
||||
22
examples/zedboard/memory.x
Normal file
22
examples/zedboard/memory.x
Normal file
@@ -0,0 +1,22 @@
|
||||
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 MMU. This is
|
||||
recommended for something like DMA descriptors. */
|
||||
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M
|
||||
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
|
||||
}
|
||||
@@ -33,10 +33,8 @@ use embedded_io::Write;
|
||||
use embedded_io_async::Write as _;
|
||||
use log::{LevelFilter, debug, error, info, warn};
|
||||
use rand::{RngCore, SeedableRng};
|
||||
use zedboard::{
|
||||
PS_CLOCK_FREQUENCY,
|
||||
phy_marvell::{LatchingLinkStatus, MARVELL_88E1518_OUI},
|
||||
};
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zedboard_bsp::phy_marvell;
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
@@ -72,8 +70,8 @@ const INIT_STRING: &str = "-- Zynq 7000 Zedboard Ethernet Example --\n\r";
|
||||
// Unicast address with OUI of the Marvell 88E1518 PHY.
|
||||
const MAC_ADDRESS: [u8; 6] = [
|
||||
0x00,
|
||||
((MARVELL_88E1518_OUI >> 8) & 0xff) as u8,
|
||||
(MARVELL_88E1518_OUI & 0xff) as u8,
|
||||
((phy_marvell::MARVELL_88E1518_OUI >> 8) & 0xff) as u8,
|
||||
(phy_marvell::MARVELL_88E1518_OUI & 0xff) as u8,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
@@ -249,7 +247,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]> =
|
||||
@@ -317,8 +315,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
eth.set_rx_buf_descriptor_base_address(rx_descr_ref.base_addr());
|
||||
eth.set_tx_buf_descriptor_base_address(tx_descr_ref.base_addr());
|
||||
eth.start();
|
||||
let (mut phy, phy_rev) =
|
||||
zedboard::phy_marvell::Marvell88E1518Phy::new_autoprobe_addr(eth.mdio_mut())
|
||||
let (mut phy, phy_rev) = phy_marvell::Marvell88E1518Phy::new_autoprobe_addr(eth.mdio_mut())
|
||||
.expect("could not auto-detect phy");
|
||||
info!(
|
||||
"Detected Marvell 88E1518 PHY with revision number: {:?}",
|
||||
@@ -456,7 +453,8 @@ async fn main(spawner: Spawner) -> ! {
|
||||
IpMode::StackReady => {
|
||||
let status = phy.read_copper_status();
|
||||
// Periodically check for link changes.
|
||||
if status.copper_link_status() == LatchingLinkStatus::DownSinceLastRead {
|
||||
if status.copper_link_status() == phy_marvell::LatchingLinkStatus::DownSinceLastRead
|
||||
{
|
||||
warn!("ethernet link is down.");
|
||||
ip_mode = IpMode::LinkDown;
|
||||
continue;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
233
examples/zedboard/src/bin/qspi.rs
Normal file
233
examples/zedboard/src/bin/qspi.rs
Normal file
@@ -0,0 +1,233 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write;
|
||||
use log::{error, info};
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zedboard_bsp::qspi_spansion;
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
configure_level_shifter,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
gpio::{GpioPins, Output, PinState},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
prelude::*,
|
||||
qspi,
|
||||
uart::{ClockConfigRaw, Uart, UartConfig},
|
||||
};
|
||||
|
||||
use zynq7000::{
|
||||
PsPeripherals,
|
||||
slcr::{LevelShifterConfig, clocks::SrcSelIo},
|
||||
};
|
||||
use zynq7000_rt as _;
|
||||
|
||||
const INIT_STRING: &str = "-- Zynq 7000 Zedboard QSPI example --\n\r";
|
||||
const QSPI_DEV_COMBINATION: qspi::QspiDeviceCombination = qspi::QspiDeviceCombination {
|
||||
vendor: qspi::QspiVendor::WinbondAndSpansion,
|
||||
operating_mode: qspi::OperatingMode::FastReadQuadOutput,
|
||||
two_devices: false,
|
||||
};
|
||||
|
||||
/// 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();
|
||||
}
|
||||
|
||||
const ERASE_PROGRAM_READ_TEST: bool = false;
|
||||
|
||||
#[embassy_executor::main]
|
||||
#[unsafe(export_name = "main")]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let mut dp = PsPeripherals::take().unwrap();
|
||||
l2_cache::init_with_defaults(&mut dp.l2c);
|
||||
|
||||
// Enable PS-PL level shifters.
|
||||
configure_level_shifter(LevelShifterConfig::EnableAll);
|
||||
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
// Set up the global interrupt controller.
|
||||
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
unsafe {
|
||||
gic.enable_interrupts();
|
||||
}
|
||||
let gpio_pins = GpioPins::new(dp.gpio);
|
||||
|
||||
// Set up global timer counter and embassy time driver.
|
||||
let gtc = GlobalTimerCounter::new(dp.gtc, clocks.arm_clocks());
|
||||
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
|
||||
|
||||
// Set up the UART, we are logging with it.
|
||||
let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = Uart::new_with_mio(
|
||||
dp.uart_1,
|
||||
UartConfig::new_with_clk_config(uart_clk_config),
|
||||
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
|
||||
)
|
||||
.unwrap();
|
||||
uart.write_all(INIT_STRING.as_bytes()).unwrap();
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
uart,
|
||||
log::LevelFilter::Trace,
|
||||
false,
|
||||
)
|
||||
};
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
|
||||
let qspi_clock_config =
|
||||
qspi::ClockConfig::calculate_with_loopback(SrcSelIo::IoPll, &clocks, 100.MHz())
|
||||
.expect("QSPI clock calculation failed");
|
||||
let qspi = qspi::Qspi::new_single_qspi_with_feedback(
|
||||
dp.qspi,
|
||||
qspi_clock_config,
|
||||
embedded_hal::spi::MODE_0,
|
||||
qspi::IoType::LvCmos33,
|
||||
gpio_pins.mio.mio1,
|
||||
(
|
||||
gpio_pins.mio.mio2,
|
||||
gpio_pins.mio.mio3,
|
||||
gpio_pins.mio.mio4,
|
||||
gpio_pins.mio.mio5,
|
||||
),
|
||||
gpio_pins.mio.mio6,
|
||||
gpio_pins.mio.mio8,
|
||||
);
|
||||
|
||||
let qspi_io_mode = qspi.into_io_mode(false);
|
||||
|
||||
let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
|
||||
|
||||
let rdid = spansion_qspi.read_rdid_extended();
|
||||
info!(
|
||||
"QSPI Info: manufacturer ID: {:?}, interface type: {:?}, density: {:?}, sector arch: {:?}, model number: {:?}",
|
||||
rdid.base_id().manufacturer_id(),
|
||||
rdid.base_id().memory_interface_type(),
|
||||
rdid.base_id().density(),
|
||||
rdid.sector_arch(),
|
||||
rdid.model_number()
|
||||
);
|
||||
let cr1 = spansion_qspi.read_configuration_register();
|
||||
info!("QSPI Configuration Register 1: {:?}", cr1);
|
||||
|
||||
let mut write_buf: [u8; u8::MAX as usize + 1] = [0x0; u8::MAX as usize + 1];
|
||||
for (idx, byte) in write_buf.iter_mut().enumerate() {
|
||||
*byte = idx as u8;
|
||||
}
|
||||
let mut read_buf = [0u8; 256];
|
||||
|
||||
if ERASE_PROGRAM_READ_TEST {
|
||||
info!("performing erase, program, read test");
|
||||
spansion_qspi
|
||||
.erase_sector(0x10000)
|
||||
.expect("erasing sector failed");
|
||||
spansion_qspi.read_page_fast_read(0x10000, &mut read_buf, true);
|
||||
for read in read_buf.iter() {
|
||||
assert_eq!(*read, 0xFF);
|
||||
}
|
||||
read_buf.fill(0);
|
||||
spansion_qspi.program_page(0x10000, &write_buf).unwrap();
|
||||
spansion_qspi.read_page_fast_read(0x10000, &mut read_buf, true);
|
||||
for (read, written) in read_buf.iter().zip(write_buf.iter()) {
|
||||
assert_eq!(read, written);
|
||||
}
|
||||
info!("test successful");
|
||||
}
|
||||
|
||||
let mut spansion_lqspi = spansion_qspi.into_linear_addressed(QSPI_DEV_COMBINATION.into());
|
||||
|
||||
let guard = spansion_lqspi.read_guard();
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(
|
||||
(qspi::QSPI_START_ADDRESS + 0x10000) as *const u8,
|
||||
read_buf.as_mut_ptr(),
|
||||
256,
|
||||
);
|
||||
}
|
||||
drop(guard);
|
||||
if ERASE_PROGRAM_READ_TEST {
|
||||
for (read, written) in read_buf.iter().zip(write_buf.iter()) {
|
||||
assert_eq!(
|
||||
read, written,
|
||||
"linear read failure, read and write missmatch"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut ticker = Ticker::every(Duration::from_millis(200));
|
||||
|
||||
let mut mio_led = Output::new_for_mio(gpio_pins.mio.mio7, PinState::Low);
|
||||
loop {
|
||||
mio_led.toggle().unwrap();
|
||||
|
||||
ticker.next().await; // Wait for the next cycle of the ticker
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Spi(_spi_interrupt) => (),
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _abort_handler() {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _undefined_handler() {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _prefetch_handler() {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Panic handler
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
error!("Panic: {info:?}");
|
||||
loop {}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#![no_std]
|
||||
use zynq7000_hal::time::Hertz;
|
||||
pub mod phy_marvell;
|
||||
|
||||
// Define the clock frequency as a constant
|
||||
pub const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_333);
|
||||
|
||||
@@ -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));
|
||||
|
||||
22
scripts/memory_ddr.x
Normal file
22
scripts/memory_ddr.x
Normal file
@@ -0,0 +1,22 @@
|
||||
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 MMU. This is
|
||||
recommended for something like DMA descriptors. */
|
||||
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M
|
||||
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
|
||||
}
|
||||
24
scripts/memory_ocm.x
Normal file
24
scripts/memory_ocm.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
|
||||
}
|
||||
144
scripts/xsct-flasher.tcl
Normal file
144
scripts/xsct-flasher.tcl
Normal file
@@ -0,0 +1,144 @@
|
||||
if {[info exists env(ip_address_hw_server)]} {
|
||||
set ip $env(ip_address_hw_server)
|
||||
} else {
|
||||
set ip "localhost"
|
||||
}
|
||||
|
||||
# Defaults
|
||||
set init_tcl ""
|
||||
set app ""
|
||||
set bitstream ""
|
||||
|
||||
# Usage helper
|
||||
proc usage {} {
|
||||
puts "Usage: xsct xsct-helper.tcl <init.tcl> \[-a|--app app.elf\] \[-b|--bit design.bit]"
|
||||
puts "Options:"
|
||||
puts " -a, --app Path to application ELF to download"
|
||||
puts " -b, --bit Path to FPGA bitstream (.bit) to program"
|
||||
puts " -h, --help Show this help"
|
||||
}
|
||||
|
||||
# Compact, robust parser
|
||||
set expecting ""
|
||||
set endopts 0
|
||||
foreach arg $argv {
|
||||
# If previous option expects a value, take this arg
|
||||
if {$expecting ne ""} {
|
||||
set $expecting $arg
|
||||
set expecting ""
|
||||
continue
|
||||
}
|
||||
|
||||
# Option handling (until we see --)
|
||||
if {!$endopts && [string match "-*" $arg]} {
|
||||
if {$arg eq "--"} { set endopts 1; continue }
|
||||
if {$arg eq "-h" || $arg eq "--help"} { usage; exit 0 }
|
||||
if {$arg eq "-a" || $arg eq "--app"} { set expecting app; continue }
|
||||
if {$arg eq "-b" || $arg eq "--bit"} { set expecting bitstream; continue }
|
||||
puts "error: unknown option: $arg"; usage; exit 1
|
||||
}
|
||||
|
||||
# Positional: expect only <init.tcl>
|
||||
if {$init_tcl eq ""} {
|
||||
set init_tcl $arg
|
||||
} else {
|
||||
puts "error: unexpected positional argument: $arg"
|
||||
usage
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Validate required init script
|
||||
if {$init_tcl eq ""} {
|
||||
puts "error: missing required first argument pointing to initialization TCL script"
|
||||
usage
|
||||
exit 1
|
||||
}
|
||||
if {![file exists $init_tcl]} {
|
||||
puts "error: the PS init tcl script '$init_tcl' does not exist"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Resolve app: CLI takes precedence over env(APP)
|
||||
if {$app ne ""} {
|
||||
if {![file exists $app]} {
|
||||
puts "error: the app file '$app' does not exist"
|
||||
exit 1
|
||||
}
|
||||
} elseif {[info exists env(APP)]} {
|
||||
if {[file exists $env(APP)]} {
|
||||
set app $env(APP)
|
||||
} else {
|
||||
puts "warning: APP environment variable is set but file does not exist: $env(APP)"
|
||||
}
|
||||
}
|
||||
|
||||
# Validate bitstream if provided
|
||||
if {$bitstream ne "" && ![file exists $bitstream]} {
|
||||
puts "error: the bitstream file '$bitstream' does not exist"
|
||||
exit 1
|
||||
}
|
||||
|
||||
puts "Hardware server IP address: $ip"
|
||||
connect -url tcp:$ip:3121
|
||||
|
||||
set devices [targets]
|
||||
|
||||
set apu_line [string trim [targets -nocase -filter {name =~ "APU"}]]
|
||||
set arm_core_0_line [string trim [targets -nocase -filter {name =~ "ARM Cortex-A9 MPCore #0"}]]
|
||||
set fpga_line [string trim [targets -nocase -filter {name =~ "xc7z020"}]]
|
||||
|
||||
set apu_device_num [string index $apu_line 0]
|
||||
set arm_core_0_num [string index $arm_core_0_line 0]
|
||||
set fpga_device_num [string index $fpga_line 0]
|
||||
|
||||
puts "Select ps target with number: $apu_device_num"
|
||||
|
||||
# Select the target
|
||||
target $apu_device_num
|
||||
|
||||
# Resetting the target involved problems when an image is stored on the flash.
|
||||
# It has turned out that it is not essential to reset the system before loading
|
||||
# the software components into the device.
|
||||
puts "Reset target"
|
||||
# TODO: Make the reset optional/configurable via input argument.
|
||||
# Reset the target
|
||||
rst
|
||||
|
||||
# Check if bitstream is set and the file exists before programming FPGA
|
||||
if {$bitstream eq ""} {
|
||||
puts "Skipping bitstream programming (bitstream argument not set)"
|
||||
} elseif {![file exists $bitstream]} {
|
||||
puts "Error: The bitstream file '$bitstream' does not exist"
|
||||
} else {
|
||||
puts "Set FPGA target with number: $fpga_device_num"
|
||||
target $fpga_device_num
|
||||
|
||||
# Without this delay, the FPGA programming may fail
|
||||
after 1500
|
||||
|
||||
puts "Programming FPGA with bitstream: $bitstream"
|
||||
fpga -f $bitstream
|
||||
}
|
||||
|
||||
puts "Set ps target with device number: $arm_core_0_num"
|
||||
targets $arm_core_0_num
|
||||
|
||||
puts "Initialize processing system"
|
||||
# Init processing system
|
||||
source $init_tcl
|
||||
|
||||
ps7_init
|
||||
ps7_post_config
|
||||
|
||||
puts "Set arm core 0 target with number: $arm_core_0_num"
|
||||
target $arm_core_0_num
|
||||
|
||||
if {$app ne ""} {
|
||||
puts "Download app $app to target"
|
||||
dow $app
|
||||
puts "Starting app"
|
||||
con
|
||||
}
|
||||
|
||||
puts "Success"
|
||||
@@ -1,86 +0,0 @@
|
||||
if {[info exists env(ip_address_hw_server)]} {
|
||||
set ip $env(ip_address_hw_server)
|
||||
} else {
|
||||
set ip "localhost"
|
||||
}
|
||||
|
||||
set init_tcl ""
|
||||
if {[llength $argv] >= 1} {
|
||||
set init_tcl [lindex $argv 0]
|
||||
} else {
|
||||
puts "error: missing required first argument pointing to initialization TCL script"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if {![file exists $init_tcl]} {
|
||||
puts "the ps init tcl script '$init_tcl' does not exist"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# parse command-line arguments
|
||||
set bitstream ""
|
||||
if {[llength $argv] >= 2} {
|
||||
set bitstream [lindex $argv 1]
|
||||
}
|
||||
|
||||
puts "Hardware server IP address: $ip"
|
||||
connect -url tcp:$ip:3121
|
||||
|
||||
set devices [targets]
|
||||
|
||||
set apu_line [string trim [targets -nocase -filter {name =~ "APU"}]]
|
||||
set arm_core_0_line [string trim [targets -nocase -filter {name =~ "ARM Cortex-A9 MPCore #0"}]]
|
||||
set fpga_line [string trim [targets -nocase -filter {name =~ "xc7z020"}]]
|
||||
|
||||
set apu_device_num [string index $apu_line 0]
|
||||
set arm_core_0_num [string index $arm_core_0_line 0]
|
||||
set fpga_device_num [string index $fpga_line 0]
|
||||
|
||||
puts "Select ps target with number: $apu_device_num"
|
||||
|
||||
# Select the target
|
||||
target $apu_device_num
|
||||
|
||||
# Resetting the target involved problems when an image is stored on the flash.
|
||||
# It has turned out that it is not essential to reset the system before loading
|
||||
# the software components into the device.
|
||||
puts "Reset target"
|
||||
# TODO: Make the reset optional/configurable via input argument.
|
||||
# Reset the target
|
||||
rst
|
||||
|
||||
# Check if bitstream is set and the file exists before programming FPGA
|
||||
if {$bitstream eq ""} {
|
||||
puts "Skipping bitstream programming (bitstream argument not set)"
|
||||
} elseif {![file exists $bitstream]} {
|
||||
puts "Error: The bitstream file '$bitstream' does not exist"
|
||||
} else {
|
||||
puts "Set FPGA target with number: $fpga_device_num"
|
||||
target $fpga_device_num
|
||||
|
||||
# Without this delay, the FPGA programming may fail
|
||||
after 1500
|
||||
|
||||
puts "Programming FPGA with bitstream: $bitstream"
|
||||
fpga -f $bitstream
|
||||
}
|
||||
|
||||
puts "Set ps target with device number: $arm_core_0_num"
|
||||
targets $arm_core_0_num
|
||||
|
||||
puts "Initialize processing system"
|
||||
# Init processing system
|
||||
source $init_tcl
|
||||
|
||||
ps7_init
|
||||
ps7_post_config
|
||||
|
||||
puts "Set arm core 0 target with number: $arm_core_0_num"
|
||||
target $arm_core_0_num
|
||||
|
||||
if {[info exists env(APP)] && [file exists $env(APP)]} {
|
||||
puts "Download app $env(APP) to target"
|
||||
dow $env(APP)
|
||||
}
|
||||
|
||||
puts "Successful"
|
||||
@@ -7,7 +7,7 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Define the default values
|
||||
TCL_SCRIPT_NAME = "xsct-init.tcl"
|
||||
TCL_SCRIPT_NAME = "xsct-flasher.tcl"
|
||||
SCRIPTS_DIR = "scripts"
|
||||
DEFAULT_IP_ADDRESS_HW_SERVER = "localhost"
|
||||
|
||||
@@ -55,7 +55,7 @@ def main():
|
||||
default=os.getenv("TCL_INIT_SCRIPT"),
|
||||
help="Path to the ps7 initialization TCL file to prepare the processing system.\n"
|
||||
"You can also set the TCL_INIT_SCRIPT env variable to set this.\n"
|
||||
"It is also set implicitely when specifying the SDT folder with --sdt"
|
||||
"It is also set implicitely when specifying the SDT folder with --sdt",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-b",
|
||||
@@ -63,7 +63,7 @@ def main():
|
||||
dest="bit",
|
||||
default=os.getenv("ZYNQ_BITSTREAM"),
|
||||
help="Optional path to the bitstream which will also be programed to the device. It is"
|
||||
" also searched automatically if the --sdt option is used.\n"
|
||||
" also searched automatically if the --sdt option is used.\n",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
@@ -91,9 +91,6 @@ def main():
|
||||
print(f"The app '{args.app}' does not exist")
|
||||
sys.exit(1)
|
||||
|
||||
# Export environment variables
|
||||
if args.app:
|
||||
os.environ["APP"] = args.app
|
||||
os.environ["IP_ADDRESS_HW_SERVER"] = args.ip
|
||||
init_tcl = None
|
||||
bitstream = None
|
||||
@@ -136,7 +133,11 @@ def main():
|
||||
# Prepare tcl_args as a list to avoid manual string concatenation issues
|
||||
cmd_list = ["xsct", str(xsct_script), init_tcl]
|
||||
if bitstream:
|
||||
cmd_list.append("--bit")
|
||||
cmd_list.append(bitstream)
|
||||
if args.app:
|
||||
cmd_list.append("--app")
|
||||
cmd_list.append(args.app)
|
||||
|
||||
# Join safely for shell execution
|
||||
xsct_cmd = shlex.join(cmd_list)
|
||||
|
||||
11
zedboard-bsp/Cargo.toml
Normal file
11
zedboard-bsp/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "zedboard-bsp"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
zynq7000-hal = { path = "../zynq7000-hal" }
|
||||
bitbybit = "1.3"
|
||||
arbitrary-int = "1.3"
|
||||
num_enum = { version = "0.7", default-features = false }
|
||||
thiserror = { version = "2", default-features = false }
|
||||
4
zedboard-bsp/src/lib.rs
Normal file
4
zedboard-bsp/src/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
#![no_std]
|
||||
|
||||
pub mod phy_marvell;
|
||||
pub mod qspi_spansion;
|
||||
604
zedboard-bsp/src/qspi_spansion.rs
Normal file
604
zedboard-bsp/src/qspi_spansion.rs
Normal file
@@ -0,0 +1,604 @@
|
||||
use core::cell::RefCell;
|
||||
|
||||
use arbitrary_int::{Number, u24};
|
||||
use zynq7000_hal::qspi::{
|
||||
FIFO_DEPTH, LinearQspiConfig, QspiIoMode, QspiIoTransferGuard, QspiLinearAddressing,
|
||||
QspiLinearReadGuard,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum RegisterId {
|
||||
/// WRR
|
||||
WriteRegisters = 0x01,
|
||||
/// PP
|
||||
PageProgram = 0x02,
|
||||
/// READ
|
||||
Read = 0x03,
|
||||
/// WRDI
|
||||
WriteDisable = 0x04,
|
||||
/// RDSR1
|
||||
ReadStatus1 = 0x05,
|
||||
/// RDSR2
|
||||
ReadStatus2 = 0x07,
|
||||
/// WREN
|
||||
WriteEnable = 0x06,
|
||||
/// FAST_READ
|
||||
FastRead = 0x0B,
|
||||
/// SE
|
||||
SectorErase = 0xD8,
|
||||
/// CSLR
|
||||
ClearStatus = 0x30,
|
||||
/// RDCR
|
||||
ReadConfig = 0x35,
|
||||
/// RDID
|
||||
ReadId = 0x9F,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, num_enum::TryFromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum MemoryInterfaceType {
|
||||
_128Mb = 0x20,
|
||||
_256Mb = 0x02,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, num_enum::TryFromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum Density {
|
||||
_128Mb = 0x18,
|
||||
_256Mb = 0x19,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, num_enum::TryFromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum SectorArchictecture {
|
||||
/// Uniform 256 kB sectors.
|
||||
Uniform = 0x00,
|
||||
/// 32 4kB sectors and 64 kB sectors.
|
||||
Hybrid = 0x01,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BaseDeviceId {
|
||||
manufacturer_id: u8,
|
||||
device_id: u16,
|
||||
}
|
||||
|
||||
impl BaseDeviceId {
|
||||
#[inline]
|
||||
pub const fn new(manufacturer_id: u8, device_id: u16) -> Self {
|
||||
BaseDeviceId {
|
||||
manufacturer_id,
|
||||
device_id,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn from_raw(raw: &[u8; 3]) -> Self {
|
||||
BaseDeviceId::new(raw[0], ((raw[1] as u16) << 8) | raw[2] as u16)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn manufacturer_id(&self) -> u8 {
|
||||
self.manufacturer_id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn device_id_raw(&self) -> u16 {
|
||||
self.device_id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn memory_interface_type(&self) -> Result<MemoryInterfaceType, u8> {
|
||||
MemoryInterfaceType::try_from(((self.device_id >> 8) & 0xff) as u8).map_err(|e| e.number)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn density(&self) -> Result<Density, u8> {
|
||||
Density::try_from((self.device_id & 0xff) as u8).map_err(|e| e.number)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExtendedDeviceId {
|
||||
base: BaseDeviceId,
|
||||
id_cfi_len: u8,
|
||||
sector_arch: u8,
|
||||
family_id: u8,
|
||||
model_number: [u8; 2],
|
||||
}
|
||||
|
||||
impl ExtendedDeviceId {
|
||||
pub const fn from_raw(raw: &[u8; 8]) -> Self {
|
||||
ExtendedDeviceId {
|
||||
base: BaseDeviceId::from_raw(&[raw[0], raw[1], raw[2]]),
|
||||
id_cfi_len: raw[3],
|
||||
sector_arch: raw[4],
|
||||
family_id: raw[5],
|
||||
model_number: [raw[6], raw[7]],
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn id_cfi_len(&self) -> u8 {
|
||||
self.id_cfi_len
|
||||
}
|
||||
|
||||
pub const fn sector_arch_raw(&self) -> u8 {
|
||||
self.sector_arch
|
||||
}
|
||||
|
||||
pub fn sector_arch(&self) -> Result<SectorArchictecture, u8> {
|
||||
SectorArchictecture::try_from(self.sector_arch_raw()).map_err(|e| e.number)
|
||||
}
|
||||
|
||||
pub const fn family_id(&self) -> u8 {
|
||||
self.family_id
|
||||
}
|
||||
|
||||
pub const fn model_number_raw(&self) -> &[u8; 2] {
|
||||
&self.model_number
|
||||
}
|
||||
|
||||
pub const fn model_number(&self) -> [char; 2] {
|
||||
[self.model_number[0] as char, self.model_number[1] as char]
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtendedDeviceId {
|
||||
#[inline]
|
||||
pub fn base_id(&self) -> BaseDeviceId {
|
||||
self.base
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u8)]
|
||||
#[derive(Debug)]
|
||||
pub struct StatusRegister1 {
|
||||
#[bit(7, rw)]
|
||||
status_register_write_disable: bool,
|
||||
#[bit(6, r)]
|
||||
programming_error: bool,
|
||||
#[bit(5, r)]
|
||||
erase_error: bool,
|
||||
#[bit(4, r)]
|
||||
bp_2: bool,
|
||||
#[bit(3, r)]
|
||||
bp_1: bool,
|
||||
#[bit(2, r)]
|
||||
bp_0: bool,
|
||||
#[bit(1, r)]
|
||||
write_enable_latch: bool,
|
||||
#[bit(0, r)]
|
||||
write_in_progress: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u8)]
|
||||
#[derive(Debug)]
|
||||
pub struct ConfigRegister1 {
|
||||
#[bit(7, rw)]
|
||||
latency_code_1: bool,
|
||||
#[bit(6, rw)]
|
||||
latency_code_0: bool,
|
||||
/// This is an OTP bit. It can not be set back to 0 once it has been set to 1!
|
||||
#[bit(5, rw)]
|
||||
tbprot: bool,
|
||||
#[bit(3, rw)]
|
||||
bpnv: bool,
|
||||
/// This is an OTP bit. It can not be set back to 0 once it has been set to 1!
|
||||
#[bit(2, rw)]
|
||||
tbparm: bool,
|
||||
#[bit(1, rw)]
|
||||
quad: bool,
|
||||
#[bit(0, rw)]
|
||||
freeze: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum AddrError {
|
||||
#[error("address out of range")]
|
||||
OutOfRange,
|
||||
#[error("address not aligned")]
|
||||
Alignment,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum EraseError {
|
||||
#[error("erase error bit set in status register")]
|
||||
EraseErrorBitSet,
|
||||
#[error("address error: {0}")]
|
||||
Addr(#[from] AddrError),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum ProgramPageError {
|
||||
#[error("programming error bit set in status register")]
|
||||
ProgrammingErrorBitSet,
|
||||
#[error("address error: {0}")]
|
||||
Addr(#[from] AddrError),
|
||||
}
|
||||
|
||||
pub struct QspiSpansionS25Fl256SIoMode(RefCell<QspiIoMode>);
|
||||
|
||||
impl QspiSpansionS25Fl256SIoMode {
|
||||
pub fn new(qspi: QspiIoMode, set_quad_bit_if_necessary: bool) -> Self {
|
||||
let mut spansion_qspi = QspiSpansionS25Fl256SIoMode(RefCell::new(qspi));
|
||||
if set_quad_bit_if_necessary {
|
||||
let mut cr1 = spansion_qspi.read_configuration_register();
|
||||
if cr1.quad() {
|
||||
// Quad bit is already set.
|
||||
return spansion_qspi;
|
||||
}
|
||||
cr1.set_quad(true);
|
||||
// Preserve the status register by reading it first.
|
||||
let sr1 = spansion_qspi.read_status_register_1();
|
||||
// Safety: Only the QUAD bit was set while all other bits are preserved.
|
||||
unsafe {
|
||||
spansion_qspi.write_status_and_config_register(sr1, cr1);
|
||||
}
|
||||
}
|
||||
spansion_qspi
|
||||
}
|
||||
|
||||
pub fn into_linear_addressed(
|
||||
self,
|
||||
config: LinearQspiConfig,
|
||||
) -> QspiSpansionS25Fl256SLinearMode {
|
||||
let qspi = self.0.into_inner().into_linear_addressed(config);
|
||||
QspiSpansionS25Fl256SLinearMode(qspi)
|
||||
}
|
||||
|
||||
pub fn write_enable(&mut self) {
|
||||
let qspi = self.0.get_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
transfer.write_word_txd_01(RegisterId::WriteEnable as u32);
|
||||
transfer.start();
|
||||
while !transfer.read_status().rx_above_threshold() {}
|
||||
transfer.read_rx_data();
|
||||
}
|
||||
|
||||
pub fn write_disable(&mut self) {
|
||||
let qspi = self.0.get_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
transfer.write_word_txd_01(RegisterId::WriteDisable as u32);
|
||||
transfer.start();
|
||||
while !transfer.read_status().rx_above_threshold() {}
|
||||
transfer.read_rx_data();
|
||||
}
|
||||
|
||||
/// Write a new value for the status register.
|
||||
///
|
||||
/// This API may not be used if the QUAD bit (CR1\[1\]) is set.
|
||||
pub fn write_status(&mut self, sr: StatusRegister1) {
|
||||
self.write_enable();
|
||||
let mut qspi = self.0.borrow_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
transfer.write_word_txd_10(u32::from_ne_bytes([
|
||||
RegisterId::WriteRegisters as u8,
|
||||
sr.raw_value(),
|
||||
0x00,
|
||||
0x00,
|
||||
]));
|
||||
transfer.start();
|
||||
while !transfer.read_status().rx_above_threshold() {}
|
||||
transfer.read_rx_data();
|
||||
}
|
||||
|
||||
/// Write a new value for the status register. It is strongly recommended to read both
|
||||
/// the status and config register first and preserve all unchanged bits.
|
||||
///
|
||||
/// This API must be used if the QUAD bit (CR1\[1\]) is set.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Misuse of this API does not lead to undefined behavior. However, it writes the
|
||||
/// configuration register, which as OTP bits. Changing these bits from 0 to 1 is an
|
||||
/// irreversible operation.
|
||||
pub unsafe fn write_status_and_config_register(
|
||||
&mut self,
|
||||
sr: StatusRegister1,
|
||||
cr: ConfigRegister1,
|
||||
) {
|
||||
self.write_enable();
|
||||
let mut qspi = self.0.borrow_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
transfer.write_word_txd_11(u32::from_ne_bytes([
|
||||
RegisterId::WriteRegisters as u8,
|
||||
sr.raw_value(),
|
||||
cr.raw_value(),
|
||||
0x00,
|
||||
]));
|
||||
transfer.start();
|
||||
while !transfer.read_status().rx_above_threshold() {}
|
||||
transfer.read_rx_data();
|
||||
}
|
||||
|
||||
pub fn read_status_register_1(&self) -> StatusRegister1 {
|
||||
let mut qspi = self.0.borrow_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
transfer.write_word_txd_10(RegisterId::ReadStatus1 as u32);
|
||||
transfer.start();
|
||||
while !transfer.read_status().rx_above_threshold() {}
|
||||
let reply = transfer.read_rx_data();
|
||||
drop(transfer);
|
||||
// little-endian architecture, so the second byte received is the MSB.
|
||||
StatusRegister1::new_with_raw_value(((reply >> 24) & 0xff) as u8)
|
||||
}
|
||||
|
||||
pub fn read_configuration_register(&self) -> ConfigRegister1 {
|
||||
let mut qspi = self.0.borrow_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
transfer.write_word_txd_10(RegisterId::ReadConfig as u32);
|
||||
transfer.start();
|
||||
while !transfer.read_status().rx_above_threshold() {}
|
||||
let reply = transfer.read_rx_data();
|
||||
drop(transfer);
|
||||
// little-endian architecture, so the second byte received is the MSB.
|
||||
ConfigRegister1::new_with_raw_value(((reply >> 24) & 0xff) as u8)
|
||||
}
|
||||
|
||||
pub fn clear_status(&mut self) {
|
||||
let qspi = self.0.get_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
transfer.write_word_txd_01(RegisterId::ClearStatus as u32);
|
||||
transfer.start();
|
||||
while !transfer.read_status().rx_above_threshold() {}
|
||||
transfer.read_rx_data();
|
||||
}
|
||||
|
||||
pub fn read_rdid_base(&self) -> BaseDeviceId {
|
||||
let mut qspi = self.0.borrow_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
transfer.write_word_txd_00(RegisterId::ReadId as u32);
|
||||
transfer.start();
|
||||
while !transfer.read_status().rx_above_threshold() {}
|
||||
let reply = transfer.read_rx_data();
|
||||
drop(transfer);
|
||||
BaseDeviceId::from_raw(reply.to_ne_bytes()[1..].try_into().unwrap())
|
||||
}
|
||||
|
||||
pub fn read_rdid_extended(&self) -> ExtendedDeviceId {
|
||||
let mut reply: [u8; 12] = [0; 12];
|
||||
let mut qspi = self.0.borrow_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
transfer.write_word_txd_00(RegisterId::ReadId as u32);
|
||||
transfer.write_word_txd_00(0x00);
|
||||
transfer.write_word_txd_00(0x00);
|
||||
transfer.start();
|
||||
let mut read_index = 0;
|
||||
|
||||
while read_index < 3 {
|
||||
if transfer.read_status().rx_above_threshold() {
|
||||
reply[read_index * 4..(read_index + 1) * 4]
|
||||
.copy_from_slice(&transfer.read_rx_data().to_ne_bytes());
|
||||
read_index += 1;
|
||||
}
|
||||
}
|
||||
ExtendedDeviceId::from_raw(reply[1..9].try_into().unwrap())
|
||||
}
|
||||
|
||||
/// This function will block until the operation has completed.
|
||||
pub fn erase_sector(&mut self, addr: u32) -> Result<(), EraseError> {
|
||||
if addr + 0x10000 > u24::MAX.as_u32() {
|
||||
return Err(AddrError::OutOfRange.into());
|
||||
}
|
||||
if !addr.is_multiple_of(0x10000) {
|
||||
return Err(AddrError::Alignment.into());
|
||||
}
|
||||
self.write_enable();
|
||||
let qspi = self.0.get_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
let raw_word: [u8; 4] = [
|
||||
RegisterId::SectorErase as u8,
|
||||
((addr >> 16) & 0xff) as u8,
|
||||
((addr >> 8) & 0xff) as u8,
|
||||
(addr & 0xff) as u8,
|
||||
];
|
||||
transfer.write_word_txd_00(u32::from_ne_bytes(raw_word));
|
||||
transfer.start();
|
||||
|
||||
// Finish transfer
|
||||
while !transfer.read_status().rx_above_threshold() {}
|
||||
transfer.read_rx_data();
|
||||
|
||||
// Drive CS high to initiate the sector erase operation.
|
||||
drop(transfer);
|
||||
|
||||
// Now poll for completion.
|
||||
loop {
|
||||
let rdsr1 = self.read_status_register_1();
|
||||
if rdsr1.erase_error() {
|
||||
// The datasheet mentions that the status should be cleared and writes
|
||||
// should be disabled explicitely.
|
||||
self.clear_status();
|
||||
self.write_disable();
|
||||
return Err(EraseError::EraseErrorBitSet);
|
||||
}
|
||||
if !rdsr1.write_in_progress() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This function also takes care of enabling writes before programming the page.
|
||||
/// This function will block until the operation has completed.
|
||||
///
|
||||
/// TODO: Allow smaller write size
|
||||
pub fn program_page(&mut self, addr: u32, data: &[u8; 256]) -> Result<(), ProgramPageError> {
|
||||
if addr + data.len() as u32 > u24::MAX.as_u32() {
|
||||
return Err(AddrError::OutOfRange.into());
|
||||
}
|
||||
if !addr.is_multiple_of(0x100) {
|
||||
return Err(AddrError::Alignment.into());
|
||||
}
|
||||
self.write_enable();
|
||||
let qspi = self.0.get_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
let raw_word: [u8; 4] = [
|
||||
RegisterId::PageProgram as u8,
|
||||
((addr >> 16) & 0xff) as u8,
|
||||
((addr >> 8) & 0xff) as u8,
|
||||
(addr & 0xff) as u8,
|
||||
];
|
||||
transfer.write_word_txd_00(u32::from_ne_bytes(raw_word));
|
||||
let mut read_index: u32 = 0;
|
||||
let mut current_byte_index = 0;
|
||||
// Fill the FIFO until it is full.
|
||||
for _ in 0..FIFO_DEPTH - 1 {
|
||||
transfer.write_word_txd_00(u32::from_ne_bytes(
|
||||
data[current_byte_index..current_byte_index + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
));
|
||||
current_byte_index += 4;
|
||||
}
|
||||
transfer.start();
|
||||
|
||||
let mut wait_for_tx_slot = |transfer: &mut QspiIoTransferGuard| loop {
|
||||
let status = transfer.read_status();
|
||||
if status.rx_above_threshold() {
|
||||
transfer.read_rx_data();
|
||||
read_index = read_index.wrapping_add(4);
|
||||
}
|
||||
if !status.tx_full() {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Immediately fill the FIFO again with the remaining 8 bytes.
|
||||
wait_for_tx_slot(&mut transfer);
|
||||
|
||||
transfer.write_word_txd_00(u32::from_ne_bytes(
|
||||
data[current_byte_index..current_byte_index + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
));
|
||||
current_byte_index += 4;
|
||||
|
||||
wait_for_tx_slot(&mut transfer);
|
||||
|
||||
transfer.write_word_txd_00(u32::from_ne_bytes(
|
||||
data[current_byte_index..current_byte_index + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
));
|
||||
|
||||
while read_index < 256 {
|
||||
if transfer.read_status().rx_above_threshold() {
|
||||
transfer.read_rx_data();
|
||||
read_index = read_index.wrapping_add(4);
|
||||
}
|
||||
}
|
||||
drop(transfer);
|
||||
|
||||
// Now poll for completion.
|
||||
loop {
|
||||
let rdsr1 = self.read_status_register_1();
|
||||
if rdsr1.programming_error() {
|
||||
// The datasheet mentions that the status should be cleared and writes
|
||||
// should be disabled explicitely.
|
||||
self.clear_status();
|
||||
self.write_disable();
|
||||
return Err(ProgramPageError::ProgrammingErrorBitSet);
|
||||
}
|
||||
if !rdsr1.write_in_progress() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_page_fast_read(&self, addr: u32, buf: &mut [u8], dummy_byte: bool) {
|
||||
let mut qspi = self.0.borrow_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
let raw_word: [u8; 4] = [
|
||||
RegisterId::FastRead as u8,
|
||||
((addr >> 16) & 0xff) as u8,
|
||||
((addr >> 8) & 0xff) as u8,
|
||||
(addr & 0xff) as u8,
|
||||
];
|
||||
transfer.write_word_txd_00(u32::from_ne_bytes(raw_word));
|
||||
let mut read_index = 0;
|
||||
let mut written_words = 0;
|
||||
let mut bytes_to_write = buf.len();
|
||||
if dummy_byte {
|
||||
bytes_to_write += 1;
|
||||
}
|
||||
let fifo_writes = if bytes_to_write.is_multiple_of(4) {
|
||||
bytes_to_write / 4
|
||||
} else {
|
||||
(bytes_to_write / 4) + 1
|
||||
};
|
||||
// Fill the FIFO until it is full or all 0 bytes have been written.
|
||||
for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) {
|
||||
transfer.write_word_txd_00(0);
|
||||
written_words += 1;
|
||||
}
|
||||
|
||||
transfer.start();
|
||||
let mut reply_word_index = 0;
|
||||
|
||||
while read_index < buf.len() {
|
||||
if transfer.read_status().rx_above_threshold() {
|
||||
let reply = transfer.read_rx_data();
|
||||
if reply_word_index == 0 {
|
||||
reply_word_index += 1;
|
||||
continue;
|
||||
}
|
||||
let reply_as_bytes = reply.to_ne_bytes();
|
||||
let reply_size = core::cmp::min(buf.len() - read_index, 4);
|
||||
read_index += match (reply_size, reply_word_index == 1 && dummy_byte) {
|
||||
(1, false) => {
|
||||
buf[read_index] = reply_as_bytes[0];
|
||||
1
|
||||
}
|
||||
(1, true) => {
|
||||
buf[read_index] = reply_as_bytes[1];
|
||||
1
|
||||
}
|
||||
(2, false) => {
|
||||
buf[read_index..read_index + 2].copy_from_slice(&reply_as_bytes[0..2]);
|
||||
2
|
||||
}
|
||||
(2, true) => {
|
||||
buf[read_index..read_index + 2].copy_from_slice(&reply_as_bytes[1..3]);
|
||||
2
|
||||
}
|
||||
(3, false) => {
|
||||
buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[0..3]);
|
||||
3
|
||||
}
|
||||
(3, true) => {
|
||||
buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[1..4]);
|
||||
3
|
||||
}
|
||||
(4, false) => {
|
||||
buf[read_index..read_index + 4].copy_from_slice(&reply_as_bytes[0..4]);
|
||||
4
|
||||
}
|
||||
(4, true) => {
|
||||
buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[1..4]);
|
||||
3
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
reply_word_index += 1;
|
||||
}
|
||||
if written_words < fifo_writes && !transfer.read_status().tx_full() {
|
||||
transfer.write_word_txd_00(0);
|
||||
written_words += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If the Spansion QSPI is used in linear addressed mode, no IO operations are allowed.
|
||||
pub struct QspiSpansionS25Fl256SLinearMode(QspiLinearAddressing);
|
||||
|
||||
impl QspiSpansionS25Fl256SLinearMode {
|
||||
pub fn into_io_mode(self, dual_flash: bool) -> QspiSpansionS25Fl256SIoMode {
|
||||
let qspi = self.0.into_io_mode(dual_flash);
|
||||
QspiSpansionS25Fl256SIoMode::new(qspi, false)
|
||||
}
|
||||
|
||||
pub fn read_guard(&mut self) -> QspiLinearReadGuard<'_> {
|
||||
self.0.read_guard()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
20
zedboard-fsbl/Cargo.toml
Normal file
20
zedboard-fsbl/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "zedboard-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"
|
||||
31
zedboard-fsbl/build.rs
Normal file
31
zedboard-fsbl/build.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
// By default, Cargo will re-run a build script whenever
|
||||
// any file in the project changes. By specifying `memory.x`
|
||||
// here, we ensure the build script is only re-run when
|
||||
// `memory.x` is changed.
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
}
|
||||
24
zedboard-fsbl/memory.x
Normal file
24
zedboard-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
|
||||
}
|
||||
503
zedboard-fsbl/src/ddr_cfg.rs
Normal file
503
zedboard-fsbl/src/ddr_cfg.rs
Normal file
@@ -0,0 +1,503 @@
|
||||
use arbitrary_int::{u2, u3, u4, u5, u6, u7, u9, u10, u11, u12, u20};
|
||||
use zynq7000::{
|
||||
ddrc::regs::{
|
||||
AxiPriorityReadPort, AxiPriorityWritePort, CheEccControl, CheTZq, CheTZqShortInterval,
|
||||
CtrlReg1, CtrlReg2, CtrlReg3, CtrlReg4, CtrlReg5, CtrlReg6, DataBusWidth, DdrcControl,
|
||||
DeepPowerdown, DfiTiming, DisableDq, DllCalib, DllCalibSel, DramAddrMapBank,
|
||||
DramAddrMapColumn, DramAddrMapRow, DramBurst8ReadWrite, DramEmr, DramEmrMr, DramInitParam,
|
||||
DramOdt, DramParamReg0, DramParamReg1, DramParamReg2, DramParamReg3, DramParamReg4,
|
||||
EccMode, EccScrub, LpddrBit, LpddrControl0, LpddrControl1, LpddrControl2, LpddrControl3,
|
||||
LprHprQueueControl, MobileSetting, ModeRegisterType, OdtDelayHold, PhyCmdTimeoutRdDataCpt,
|
||||
PhyConfig, PhyDqsConfig, PhyInitRatio, PhyReceiverEnable, PhyWriteDataSlaveConfig,
|
||||
PhyWriteEnableConfig, Reg2c, Reg2d, Reg64, Reg65, SoftReset, TwoRankConfig,
|
||||
WriteQueueControl,
|
||||
},
|
||||
slcr::ddriob::{DciType, DdriobConfig, InputType, OutputEnable},
|
||||
};
|
||||
use zynq7000_hal::ddr::{DdrcConfigSet, 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();
|
||||
|
||||
// TODO: Auto-generate from ps7init.tcl
|
||||
pub const DDRIOB_CONFIG_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,
|
||||
};
|
||||
|
||||
// TODO: Auto-generate from ps7init.tcl
|
||||
pub const DDRC_CONFIG_ZEDBOARD_FULL_BUILDERS: DdrcConfigSet = DdrcConfigSet {
|
||||
ctrl: DdrcControl::builder()
|
||||
.with_disable_auto_refresh(false)
|
||||
.with_disable_active_bypass(false)
|
||||
.with_disable_read_bypass(false)
|
||||
.with_read_write_idle_gap(u7::new(1))
|
||||
.with_burst8_refresh(u3::new(0))
|
||||
.with_data_bus_width(DataBusWidth::_32Bit)
|
||||
.with_power_down_enable(false)
|
||||
.with_soft_reset(SoftReset::Reset)
|
||||
.build(),
|
||||
two_rank: TwoRankConfig::builder()
|
||||
.with_addrmap_cs_bit0(u5::new(0))
|
||||
.with_ddrc_active_ranks(u2::new(1))
|
||||
.with_rfc_nom_x32(u12::new(0x82))
|
||||
.build(),
|
||||
hpr: LprHprQueueControl::builder()
|
||||
.with_xact_run_length(u4::new(0xf))
|
||||
.with_max_starve_x32(u11::new(0xf))
|
||||
.with_min_non_critical_x32(u11::new(0xf))
|
||||
.build(),
|
||||
lpr: LprHprQueueControl::builder()
|
||||
.with_xact_run_length(u4::new(0x8))
|
||||
.with_max_starve_x32(u11::new(0x2))
|
||||
.with_min_non_critical_x32(u11::new(0x1))
|
||||
.build(),
|
||||
wr: WriteQueueControl::builder()
|
||||
.with_max_starve_x32(u11::new(0x2))
|
||||
.with_xact_run_length(u4::new(0x8))
|
||||
.with_min_non_critical_x32(u11::new(0x1))
|
||||
.build(),
|
||||
dram_param_0: DramParamReg0::builder()
|
||||
.with_post_selfref_gap_x32(u7::new(0x10))
|
||||
.with_t_rfc_min(0x56)
|
||||
.with_t_rc(u6::new(0x1b))
|
||||
.build(),
|
||||
dram_param_1: DramParamReg1::builder()
|
||||
.with_t_cke(u4::new(0x4))
|
||||
.with_t_ras_min(u5::new(0x13))
|
||||
.with_t_ras_max(u6::new(0x24))
|
||||
.with_t_faw(u6::new(0x16))
|
||||
.with_powerdown_to_x32(u5::new(0x6))
|
||||
.with_wr2pre(u5::new(0x13))
|
||||
.build(),
|
||||
dram_param_2: DramParamReg2::builder()
|
||||
.with_t_rcd(u4::new(0x7))
|
||||
.with_rd2pre(u5::new(0x5))
|
||||
.with_pad_pd(u3::new(0x0))
|
||||
.with_t_xp(u5::new(0x5))
|
||||
.with_wr2rd(u5::new(0xf))
|
||||
.with_rd2wr(u5::new(0x7))
|
||||
.with_write_latency(u5::new(0x5))
|
||||
.build(),
|
||||
dram_param_3: DramParamReg3::builder()
|
||||
.with_disable_pad_pd_feature(false)
|
||||
.with_read_latency(u5::new(0x7))
|
||||
.with_enable_dfi_dram_clk_disable(false)
|
||||
.with_mobile(MobileSetting::Ddr2Ddr3)
|
||||
.with_sdram(false)
|
||||
.with_refresh_to_x32(u5::new(0x8))
|
||||
.with_t_rp(u4::new(0x7))
|
||||
.with_refresh_margin(u4::new(0x2))
|
||||
.with_t_rrd(u3::new(0x6))
|
||||
.with_t_ccd(u3::new(0x4))
|
||||
.build(),
|
||||
dram_param_4: DramParamReg4::builder()
|
||||
.with_mr_rdata_valid(false)
|
||||
.with_mr_type(ModeRegisterType::Write)
|
||||
.with_mr_wr_busy(false)
|
||||
.with_mr_data(0x0)
|
||||
.with_mr_addr(u2::new(0x0))
|
||||
.with_mr_wr(false)
|
||||
.with_prefer_write(false)
|
||||
.with_enable_2t_timing_mode(false)
|
||||
.build(),
|
||||
dram_init_param: DramInitParam::builder()
|
||||
.with_t_mrd(u3::new(0x4))
|
||||
.with_pre_ocd_x32(u4::new(0x0))
|
||||
.with_final_wait_x32(u7::new(0x7))
|
||||
.build(),
|
||||
dram_emr: DramEmr::builder().with_emr3(0x0).with_emr2(0x8).build(),
|
||||
dram_emr_mr: DramEmrMr::builder().with_emr(0x4).with_mr(0xb30).build(),
|
||||
dram_burst8_rdwr: DramBurst8ReadWrite::builder()
|
||||
.with_burst_rdwr(u4::new(0x4))
|
||||
.with_pre_cke_x1024(u10::new(0x16d))
|
||||
.with_post_cke_x1024(u10::new(0x1))
|
||||
.with_burstchop(false)
|
||||
.build(),
|
||||
disable_dq: DisableDq::builder()
|
||||
.with_dis_dq(false)
|
||||
.with_force_low_pri_n(false)
|
||||
.build(),
|
||||
dram_addr_map_bank: DramAddrMapBank::builder()
|
||||
.with_addrmap_bank_b6(u4::new(0))
|
||||
.with_addrmap_bank_b5(u4::new(0))
|
||||
.with_addrmap_bank_b2(u4::new(0x7))
|
||||
.with_addrmap_bank_b1(u4::new(0x7))
|
||||
.with_addrmap_bank_b0(u4::new(0x7))
|
||||
.build(),
|
||||
dram_addr_map_col: DramAddrMapColumn::builder()
|
||||
.with_addrmap_col_b11(u4::new(0xf))
|
||||
.with_addrmap_col_b10(u4::new(0xf))
|
||||
.with_addrmap_col_b9(u4::new(0xf))
|
||||
.with_addrmap_col_b8(u4::new(0x0))
|
||||
.with_addrmap_col_b7(u4::new(0x0))
|
||||
.with_addrmap_col_b4(u4::new(0x0))
|
||||
.with_addrmap_col_b3(u4::new(0x0))
|
||||
.with_addrmap_col_b2(u4::new(0x0))
|
||||
.build(),
|
||||
dram_addr_map_row: DramAddrMapRow::builder()
|
||||
.with_addrmap_row_b15(u4::new(0xf))
|
||||
.with_addrmap_row_b14(u4::new(0xf))
|
||||
.with_addrmap_row_b13(u4::new(0x6))
|
||||
.with_addrmap_row_b12(u4::new(0x6))
|
||||
.with_addrmap_row_b2_11(u4::new(0x6))
|
||||
.with_addrmap_row_b1(u4::new(0x6))
|
||||
.with_addrmap_row_b0(u4::new(0x4))
|
||||
.build(),
|
||||
dram_odt: DramOdt::builder()
|
||||
.with_phy_idle_local_odt(u2::new(0x0))
|
||||
.with_phy_write_local_odt(u2::new(0x3))
|
||||
.with_phy_read_local_odt(u2::new(0x0))
|
||||
.with_rank0_wr_odt(u3::new(0x1))
|
||||
.with_rank0_rd_odt(u3::new(0x0))
|
||||
.build(),
|
||||
phy_cmd_timeout_rddata_cpt: PhyCmdTimeoutRdDataCpt::builder()
|
||||
.with_wrlvl_num_of_dq0(u4::new(0x7))
|
||||
.with_gatelvl_num_of_dq0(u4::new(0x7))
|
||||
.with_clk_stall_level(false)
|
||||
.with_dis_phy_ctrl_rstn(false)
|
||||
.with_rdc_fifo_rst_err_cnt_clr(false)
|
||||
.with_use_fixed_re(true)
|
||||
.with_rdc_we_to_re_delay(u4::new(0x8))
|
||||
.with_wr_cmd_to_data(u4::new(0x0))
|
||||
.with_rd_cmd_to_data(u4::new(0x0))
|
||||
.build(),
|
||||
dll_calib: DllCalib::builder().with_sel(DllCalibSel::Periodic).build(),
|
||||
odt_delay_hold: OdtDelayHold::builder()
|
||||
.with_wr_odt_hold(u4::new(0x5))
|
||||
.with_rd_odt_hold(u4::new(0x0))
|
||||
.with_wr_odt_delay(u4::new(0x0))
|
||||
.with_rd_odt_delay(u4::new(0x3))
|
||||
.build(),
|
||||
ctrl_reg1: CtrlReg1::builder()
|
||||
.with_selfref_enable(false)
|
||||
.with_dis_collision_page_opt(false)
|
||||
.with_dis_wc(false)
|
||||
.with_refresh_update_level(false)
|
||||
.with_auto_pre_en(false)
|
||||
.with_lpr_num_entries(u6::new(0x1f))
|
||||
.with_pageclose(false)
|
||||
.build(),
|
||||
ctrl_reg2: CtrlReg2::builder()
|
||||
.with_go_2_critcal_enable(true)
|
||||
.with_go_2_critical_hysteresis(0x0)
|
||||
.build(),
|
||||
ctrl_reg3: CtrlReg3::builder()
|
||||
.with_dfi_t_wlmrd(u10::new(0x28))
|
||||
.with_rdlvl_rr(0x41)
|
||||
.with_wrlvl_ww(0x41)
|
||||
.build(),
|
||||
ctrl_reg4: CtrlReg4::builder()
|
||||
.with_dfi_t_ctrlupd_interval_max_x1024(0x16)
|
||||
.with_dfi_t_ctrlupd_interval_min_x1024(0x10)
|
||||
.build(),
|
||||
ctrl_reg5: CtrlReg5::builder()
|
||||
.with_t_ckesr(u6::new(0x4))
|
||||
.with_t_cksrx(u4::new(0x6))
|
||||
.with_t_ckrse(u4::new(0x6))
|
||||
.with_dfi_t_dram_clk_enable(u4::new(0x1))
|
||||
.with_dfi_t_dram_clk_disable(u4::new(0x1))
|
||||
.with_dfi_t_ctrl_delay(u4::new(0x1))
|
||||
.build(),
|
||||
ctrl_reg6: CtrlReg6::builder()
|
||||
.with_t_cksx(u4::new(0x3))
|
||||
.with_t_ckdpdx(u4::new(0x2))
|
||||
.with_t_ckdpde(u4::new(0x2))
|
||||
.with_t_ckpdx(u4::new(0x2))
|
||||
.with_t_ckpde(u4::new(0x2))
|
||||
.build(),
|
||||
che_t_zq: CheTZq::builder()
|
||||
.with_t_zq_short_nop(u10::new(0x40))
|
||||
.with_t_zq_long_nop(u10::new(0x200))
|
||||
.with_t_mode(u10::new(0x200))
|
||||
.with_ddr3(true)
|
||||
.with_dis_auto_zq(false)
|
||||
.build(),
|
||||
che_t_zq_short_interval_reg: CheTZqShortInterval::builder()
|
||||
.with_dram_rstn_x1024(0x69)
|
||||
.with_t_zq_short_interval(u20::new(0xcb73))
|
||||
.build(),
|
||||
deep_powerdown: DeepPowerdown::builder()
|
||||
.with_deep_powerdown_to_x1024(0xff)
|
||||
.with_enable(false)
|
||||
.build(),
|
||||
reg_2c: Reg2c::builder()
|
||||
.with_dfi_rd_data_eye_train(true)
|
||||
.with_dfi_rd_dqs_gate_level(true)
|
||||
.with_dfi_wr_level_enable(true)
|
||||
.with_trdlvl_max_error(false)
|
||||
.with_twrlvl_max_error(false)
|
||||
.with_dfi_rdlvl_max_x1024(u12::new(0xfff))
|
||||
.with_dfi_wrlvl_max_x1024(u12::new(0xfff))
|
||||
.build(),
|
||||
reg_2d: Reg2d::builder().with_skip_ocd(true).build(),
|
||||
dfi_timing: DfiTiming::builder()
|
||||
.with_dfi_t_ctrlup_max(u10::new(0x40))
|
||||
.with_dfi_t_ctrlup_min(u10::new(0x3))
|
||||
.with_dfi_t_rddata_enable(u5::new(0x6))
|
||||
.build(),
|
||||
che_ecc_ctrl: CheEccControl::builder()
|
||||
.with_clear_correctable_errors(false)
|
||||
.with_clear_uncorrectable_errors(false)
|
||||
.build(),
|
||||
ecc_scrub: EccScrub::builder()
|
||||
.with_disable_scrub(true)
|
||||
.with_ecc_mode(EccMode::NoEcc)
|
||||
.build(),
|
||||
phy_receiver_enable: PhyReceiverEnable::builder()
|
||||
.with_phy_dif_off(u4::new(0))
|
||||
.with_phy_dif_on(u4::new(0))
|
||||
.build(),
|
||||
phy_config: [PhyConfig::builder()
|
||||
.with_dq_offset(u7::new(0x40))
|
||||
.with_wrlvl_inc_mode(false)
|
||||
.with_gatelvl_inc_mode(false)
|
||||
.with_rdlvl_inc_mode(false)
|
||||
.with_data_slice_in_use(true)
|
||||
.build(); 4],
|
||||
phy_init_ratio: [
|
||||
PhyInitRatio::builder()
|
||||
.with_gatelvl_init_ratio(u10::new(0xcf))
|
||||
.with_wrlvl_init_ratio(u10::new(0x3))
|
||||
.build(),
|
||||
PhyInitRatio::builder()
|
||||
.with_gatelvl_init_ratio(u10::new(0xd0))
|
||||
.with_wrlvl_init_ratio(u10::new(0x3))
|
||||
.build(),
|
||||
PhyInitRatio::builder()
|
||||
.with_gatelvl_init_ratio(u10::new(0xbd))
|
||||
.with_wrlvl_init_ratio(u10::new(0x0))
|
||||
.build(),
|
||||
PhyInitRatio::builder()
|
||||
.with_gatelvl_init_ratio(u10::new(0xc1))
|
||||
.with_wrlvl_init_ratio(u10::new(0x0))
|
||||
.build(),
|
||||
],
|
||||
phy_rd_dqs_config: [
|
||||
PhyDqsConfig::builder()
|
||||
.with_dqs_slave_delay(u9::new(0x0))
|
||||
.with_dqs_slave_force(false)
|
||||
.with_dqs_slave_ratio(u10::new(0x35))
|
||||
.build(),
|
||||
PhyDqsConfig::builder()
|
||||
.with_dqs_slave_delay(u9::new(0x0))
|
||||
.with_dqs_slave_force(false)
|
||||
.with_dqs_slave_ratio(u10::new(0x35))
|
||||
.build(),
|
||||
PhyDqsConfig::builder()
|
||||
.with_dqs_slave_delay(u9::new(0x0))
|
||||
.with_dqs_slave_force(false)
|
||||
.with_dqs_slave_ratio(u10::new(0x35))
|
||||
.build(),
|
||||
PhyDqsConfig::builder()
|
||||
.with_dqs_slave_delay(u9::new(0x0))
|
||||
.with_dqs_slave_force(false)
|
||||
.with_dqs_slave_ratio(u10::new(0x35))
|
||||
.build(),
|
||||
],
|
||||
phy_wr_dqs_config: [
|
||||
PhyDqsConfig::builder()
|
||||
.with_dqs_slave_delay(u9::new(0x0))
|
||||
.with_dqs_slave_force(false)
|
||||
.with_dqs_slave_ratio(u10::new(0x83))
|
||||
.build(),
|
||||
PhyDqsConfig::builder()
|
||||
.with_dqs_slave_delay(u9::new(0x0))
|
||||
.with_dqs_slave_force(false)
|
||||
.with_dqs_slave_ratio(u10::new(0x83))
|
||||
.build(),
|
||||
PhyDqsConfig::builder()
|
||||
.with_dqs_slave_delay(u9::new(0x0))
|
||||
.with_dqs_slave_force(false)
|
||||
.with_dqs_slave_ratio(u10::new(0x7f))
|
||||
.build(),
|
||||
PhyDqsConfig::builder()
|
||||
.with_dqs_slave_delay(u9::new(0x0))
|
||||
.with_dqs_slave_force(false)
|
||||
.with_dqs_slave_ratio(u10::new(0x78))
|
||||
.build(),
|
||||
],
|
||||
phy_we_cfg: [
|
||||
PhyWriteEnableConfig::builder()
|
||||
.with_fifo_we_in_delay(u9::new(0x0))
|
||||
.with_fifo_we_in_force(false)
|
||||
.with_fifo_we_slave_ratio(u11::new(0x124))
|
||||
.build(),
|
||||
PhyWriteEnableConfig::builder()
|
||||
.with_fifo_we_in_delay(u9::new(0x0))
|
||||
.with_fifo_we_in_force(false)
|
||||
.with_fifo_we_slave_ratio(u11::new(0x125))
|
||||
.build(),
|
||||
PhyWriteEnableConfig::builder()
|
||||
.with_fifo_we_in_delay(u9::new(0x0))
|
||||
.with_fifo_we_in_force(false)
|
||||
.with_fifo_we_slave_ratio(u11::new(0x112))
|
||||
.build(),
|
||||
PhyWriteEnableConfig::builder()
|
||||
.with_fifo_we_in_delay(u9::new(0x0))
|
||||
.with_fifo_we_in_force(false)
|
||||
.with_fifo_we_slave_ratio(u11::new(0x116))
|
||||
.build(),
|
||||
],
|
||||
phy_wr_data_slv: [
|
||||
PhyWriteDataSlaveConfig::builder()
|
||||
.with_wr_data_slave_delay(u9::new(0x0))
|
||||
.with_wr_data_slave_force(false)
|
||||
.with_wr_data_slave_ratio(u10::new(0xc3))
|
||||
.build(),
|
||||
PhyWriteDataSlaveConfig::builder()
|
||||
.with_wr_data_slave_delay(u9::new(0x0))
|
||||
.with_wr_data_slave_force(false)
|
||||
.with_wr_data_slave_ratio(u10::new(0xc3))
|
||||
.build(),
|
||||
PhyWriteDataSlaveConfig::builder()
|
||||
.with_wr_data_slave_delay(u9::new(0x0))
|
||||
.with_wr_data_slave_force(false)
|
||||
.with_wr_data_slave_ratio(u10::new(0xbf))
|
||||
.build(),
|
||||
PhyWriteDataSlaveConfig::builder()
|
||||
.with_wr_data_slave_delay(u9::new(0x0))
|
||||
.with_wr_data_slave_force(false)
|
||||
.with_wr_data_slave_ratio(u10::new(0xb8))
|
||||
.build(),
|
||||
],
|
||||
reg64: Reg64::builder()
|
||||
.with_cmd_latency(false)
|
||||
.with_lpddr(false)
|
||||
.with_ctrl_slave_delay(u7::new(0x0))
|
||||
.with_ctrl_slave_force(false)
|
||||
.with_ctrl_slave_ratio(u10::new(0x100))
|
||||
.with_sel_logic(false)
|
||||
.with_invert_clkout(true)
|
||||
.with_bl2(false)
|
||||
.build(),
|
||||
reg65: Reg65::builder()
|
||||
.with_ctrl_slave_delay(u2::new(0x0))
|
||||
.with_dis_calib_rst(false)
|
||||
.with_use_rd_data_eye_level(true)
|
||||
.with_use_rd_dqs_gate_level(true)
|
||||
.with_use_wr_level(true)
|
||||
.with_dll_lock_diff(u4::new(0xf))
|
||||
.with_rd_rl_delay(u5::new(0x4))
|
||||
.with_wr_rl_delay(u5::new(0x2))
|
||||
.build(),
|
||||
page_mask: 0x0,
|
||||
axi_priority_wr_port: [
|
||||
AxiPriorityWritePort::builder()
|
||||
.with_disable_page_match(false)
|
||||
.with_disable_urgent(false)
|
||||
.with_disable_aging(false)
|
||||
.with_pri_wr_port(u10::new(0x3ff))
|
||||
.build(),
|
||||
AxiPriorityWritePort::builder()
|
||||
.with_disable_page_match(false)
|
||||
.with_disable_urgent(false)
|
||||
.with_disable_aging(false)
|
||||
.with_pri_wr_port(u10::new(0x3ff))
|
||||
.build(),
|
||||
AxiPriorityWritePort::builder()
|
||||
.with_disable_page_match(false)
|
||||
.with_disable_urgent(false)
|
||||
.with_disable_aging(false)
|
||||
.with_pri_wr_port(u10::new(0x3ff))
|
||||
.build(),
|
||||
AxiPriorityWritePort::builder()
|
||||
.with_disable_page_match(false)
|
||||
.with_disable_urgent(false)
|
||||
.with_disable_aging(false)
|
||||
.with_pri_wr_port(u10::new(0x3ff))
|
||||
.build(),
|
||||
],
|
||||
axi_priority_rd_port: [
|
||||
AxiPriorityReadPort::builder()
|
||||
.with_enable_hpr(false)
|
||||
.with_disable_page_match(false)
|
||||
.with_disable_urgent(false)
|
||||
.with_disable_aging(false)
|
||||
.with_pri_rd_port_n(u10::new(0x3ff))
|
||||
.build(),
|
||||
AxiPriorityReadPort::builder()
|
||||
.with_enable_hpr(false)
|
||||
.with_disable_page_match(false)
|
||||
.with_disable_urgent(false)
|
||||
.with_disable_aging(false)
|
||||
.with_pri_rd_port_n(u10::new(0x3ff))
|
||||
.build(),
|
||||
AxiPriorityReadPort::builder()
|
||||
.with_enable_hpr(false)
|
||||
.with_disable_page_match(false)
|
||||
.with_disable_urgent(false)
|
||||
.with_disable_aging(false)
|
||||
.with_pri_rd_port_n(u10::new(0x3ff))
|
||||
.build(),
|
||||
AxiPriorityReadPort::builder()
|
||||
.with_enable_hpr(false)
|
||||
.with_disable_page_match(false)
|
||||
.with_disable_urgent(false)
|
||||
.with_disable_aging(false)
|
||||
.with_pri_rd_port_n(u10::new(0x3ff))
|
||||
.build(),
|
||||
],
|
||||
lpddr_ctrl_0: LpddrControl0::builder()
|
||||
.with_mr4_margin(0x0)
|
||||
.with_derate_enable(false)
|
||||
.with_per_bank_refresh(false)
|
||||
.with_lpddr2(LpddrBit::Ddr2Ddr3)
|
||||
.build(),
|
||||
lpddr_ctrl_1: LpddrControl1::builder().with_mr4_read_interval(0x0).build(),
|
||||
lpddr_ctrl_2: LpddrControl2::builder()
|
||||
.with_t_mrw(u10::new(0x5))
|
||||
.with_idle_after_reset_x32(0x12)
|
||||
.with_min_stable_clock_x1(u4::new(0x5))
|
||||
.build(),
|
||||
lpddr_ctrl_3: LpddrControl3::builder()
|
||||
.with_dev_zqinit_x32(u10::new(0x12))
|
||||
.with_max_auto_init_x1024(0xa8)
|
||||
.build(),
|
||||
};
|
||||
164
zedboard-fsbl/src/main.rs
Normal file
164
zedboard-fsbl/src/main.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
//! Simple FSBL.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use arbitrary_int::u6;
|
||||
use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use embedded_io::Write as _;
|
||||
use log::{error, info};
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::{
|
||||
Clocks,
|
||||
pll::{PllConfig, configure_arm_pll, configure_io_pll},
|
||||
},
|
||||
ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest},
|
||||
gic::GicConfigurator,
|
||||
gpio::GpioPins,
|
||||
l2_cache,
|
||||
time::Hertz,
|
||||
uart::{ClockConfigRaw, Uart, UartConfig},
|
||||
};
|
||||
use zynq7000_rt as _;
|
||||
|
||||
use crate::ddr_cfg::{DDRC_CONFIG_ZEDBOARD_FULL_BUILDERS, DDRIOB_CONFIG_SET_ZEDBOARD};
|
||||
|
||||
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());
|
||||
|
||||
const PERFORM_DDR_MEMTEST: bool = false;
|
||||
|
||||
/// 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(),
|
||||
);
|
||||
|
||||
let mut dp = zynq7000::PsPeripherals::take().unwrap();
|
||||
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let clocks = Clocks::new_from_regs(PS_CLK).unwrap();
|
||||
|
||||
let gpio_pins = GpioPins::new(dp.gpio);
|
||||
let mio_pins = gpio_pins.mio;
|
||||
// Set up the UART, we are logging with it.
|
||||
let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = Uart::new_with_mio(
|
||||
dp.uart_1,
|
||||
UartConfig::new_with_clk_config(uart_clk_config),
|
||||
(mio_pins.mio48, mio_pins.mio49),
|
||||
)
|
||||
.unwrap();
|
||||
uart.write_all(b"-- Zedboard Rust FSBL --\n\r").unwrap();
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
uart,
|
||||
log::LevelFilter::Trace,
|
||||
false,
|
||||
)
|
||||
};
|
||||
|
||||
// Set up the global interrupt controller.
|
||||
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
// Enable interrupt exception.
|
||||
unsafe { gic.enable_interrupts() };
|
||||
|
||||
info!("Configuring DDR..");
|
||||
configure_ddr_for_ddr3(
|
||||
dp.ddrc,
|
||||
boot_mode,
|
||||
DdrClockSetupConfig {
|
||||
ps_clk: PS_CLK,
|
||||
ddr_clk: DDR_CLK,
|
||||
ddr_3x_div: u6::new(2),
|
||||
ddr_2x_div: u6::new(3),
|
||||
},
|
||||
&DDRIOB_CONFIG_SET_ZEDBOARD,
|
||||
&DDRC_CONFIG_ZEDBOARD_FULL_BUILDERS,
|
||||
);
|
||||
info!("DDR init done.");
|
||||
|
||||
info!("L2 cache init..");
|
||||
// Set up the L2 cache now that the DDR is in normal operation mode.
|
||||
l2_cache::init_with_defaults(&mut dp.l2c);
|
||||
info!("L2 cache init done.");
|
||||
|
||||
if PERFORM_DDR_MEMTEST {
|
||||
let ddr_base_addr = 0x100000;
|
||||
info!("performing DDR memory test..");
|
||||
unsafe {
|
||||
memtest::walking_zero_test(ddr_base_addr, 64).expect("walking one test failed");
|
||||
memtest::walking_one_test(ddr_base_addr, 64).expect("walking zero test failed");
|
||||
memtest::checkerboard_test(ddr_base_addr, 64).expect("checkerboard test failed");
|
||||
}
|
||||
info!("DDR memory test success.");
|
||||
}
|
||||
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 {}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar
|
||||
zynq7000 = { path = "../zynq7000" }
|
||||
zynq-mmu = { path = "../zynq-mmu", version = "0.1.0" }
|
||||
|
||||
static_assertions = "1.1"
|
||||
bitbybit = "1.3"
|
||||
arbitrary-int = "1.3"
|
||||
thiserror = { version = "2", default-features = false }
|
||||
|
||||
296
zynq7000-hal/src/boot_image.rs
Normal file
296
zynq7000-hal/src/boot_image.rs
Normal file
@@ -0,0 +1,296 @@
|
||||
/// ASCII 'XLNX'
|
||||
pub const IMAGE_ID_U32: u32 = 0x584C4E58;
|
||||
|
||||
/// This is the fixed size of the boot header.
|
||||
pub const FIXED_BOOT_HEADER_SIZE: usize = 0xA0;
|
||||
|
||||
pub struct BootHeader {
|
||||
base_addr: u32,
|
||||
}
|
||||
|
||||
impl BootHeader {
|
||||
/// # Safety
|
||||
///
|
||||
/// This function will perform volatile reads for image header table fields on the given base
|
||||
/// address. You must ensure that the address contains a valid image header table.
|
||||
/// At the very least, [FIXED_BOOT_HEADER_SIZE] bytes of the boot header must be readable at
|
||||
/// the given address.
|
||||
pub const unsafe fn new(base_addr: u32) -> Self {
|
||||
BootHeader { base_addr }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn image_id(&self) -> u32 {
|
||||
unsafe { core::ptr::read_volatile((self.base_addr + 0x24) as *const _) }
|
||||
}
|
||||
|
||||
/// Check whether the image ID has the mandatory [IMAGE_ID_U32] value.
|
||||
#[inline]
|
||||
pub fn check_image_id_validity(&self) -> bool {
|
||||
self.image_id() == 0x854C4E58
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn source_offset(&self) -> u32 {
|
||||
unsafe { core::ptr::read_volatile((self.base_addr + 0x30) as *const _) }
|
||||
}
|
||||
|
||||
/// The boot image metadata is all the data up to the first partition of the FSBL.
|
||||
///
|
||||
/// This information can be used to copy all relevant information for image and partition
|
||||
/// parsing into RAM. This includes the image header table, the image headers and the
|
||||
/// partition headers.
|
||||
#[inline]
|
||||
pub fn boot_image_metadata_len(&self) -> u32 {
|
||||
self.source_offset() - self.base_addr
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn header_checksum(&self) -> u32 {
|
||||
unsafe { core::ptr::read_volatile((self.base_addr + 0x48) as *const _) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn image_header_table_offset(&self) -> u32 {
|
||||
unsafe { core::ptr::read_volatile((self.base_addr + 0x98) as *const _) }
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is only safe to use when the image header table structure
|
||||
/// was copied to the correct offset of the base address used to initialize
|
||||
/// this boot header structure.
|
||||
pub unsafe fn image_header_table(&self) -> ImageHeaderTable {
|
||||
let offset = self.image_header_table_offset();
|
||||
unsafe { ImageHeaderTable::new(self.base_addr + offset) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn partition_header_table_offset(&self) -> u32 {
|
||||
unsafe { core::ptr::read_volatile((self.base_addr + 0x9C) as *const _) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn verify_header_checksum(&self) -> bool {
|
||||
let checksum = unsafe { core::ptr::read_volatile((self.base_addr + 0x48) as *const _) };
|
||||
let end_addr = 0x48;
|
||||
let mut current_addr = 0x20;
|
||||
let mut sum = 0u32;
|
||||
while current_addr < end_addr {
|
||||
sum = sum.wrapping_add(unsafe { core::ptr::read_volatile(current_addr as *const u32) });
|
||||
current_addr += 4;
|
||||
}
|
||||
!sum == checksum
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ImageHeaderTable {
|
||||
base_addr: u32,
|
||||
}
|
||||
|
||||
impl ImageHeaderTable {
|
||||
/// # Safety
|
||||
///
|
||||
/// This function will perform volatile reads for image header table fields on the given base
|
||||
/// address. You must ensure that the address contains a valid image header table.
|
||||
///
|
||||
/// This is a low-level function. It is recommended to use [BootHeader::image_header_table]
|
||||
/// to retrieve the header table after checking boot header validity.
|
||||
pub const unsafe fn new(base_addr: u32) -> Self {
|
||||
ImageHeaderTable { base_addr }
|
||||
}
|
||||
|
||||
pub fn image_header_iterator(&self) -> ImageHeaderIterator {
|
||||
let first_image_header = self.first_image_header_offset();
|
||||
ImageHeaderIterator {
|
||||
current_image_header: first_image_header,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn count_of_headers(&self) -> u32 {
|
||||
unsafe { core::ptr::read_volatile((self.base_addr + 0x04) as *const _) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn first_partition_header(&self) -> u32 {
|
||||
unsafe { core::ptr::read_volatile((self.base_addr + 0x08) as *const _) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn first_image_header_offset(&self) -> u32 {
|
||||
let words = unsafe { core::ptr::read_volatile((self.base_addr + 0x0C) as *const u32) };
|
||||
words * 4
|
||||
}
|
||||
|
||||
/// You can also use [Self::image_header_iterator] to retrieve an iterator over all image
|
||||
/// headers.
|
||||
#[inline]
|
||||
pub fn first_image_header(&self) -> ImageHeader {
|
||||
unsafe { ImageHeader::new(self.base_addr + self.first_image_header_offset()) }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ImageHeaderIterator {
|
||||
current_image_header: u32,
|
||||
}
|
||||
|
||||
impl Iterator for ImageHeaderIterator {
|
||||
type Item = ImageHeader;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let image_header = unsafe { ImageHeader::new(self.current_image_header) };
|
||||
image_header.next_image_header()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ImageHeader {
|
||||
base_addr: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||
#[error("buffer too small")]
|
||||
pub struct BufTooSmallError;
|
||||
|
||||
impl ImageHeader {
|
||||
/// # Safety
|
||||
///
|
||||
/// This function will perform volatile reads for image header table fields on the given base
|
||||
/// address. You must ensure that the address contains a valid image header table.
|
||||
///
|
||||
/// This is a low-level function. It is recommended to use
|
||||
/// [ImageHeaderTable::image_header_iterator] to retrieve the image headers after checking
|
||||
/// boot header validity.
|
||||
#[inline]
|
||||
pub const unsafe fn new(base_addr: u32) -> Self {
|
||||
ImageHeader { base_addr }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn next_image_header_offset(&self) -> u32 {
|
||||
let next_image_header_words =
|
||||
unsafe { core::ptr::read_volatile(self.base_addr as *const u32) };
|
||||
next_image_header_words * 4
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn partition_count(&self) -> u32 {
|
||||
unsafe { core::ptr::read_volatile((self.base_addr + 0x0C) as *const u32) }
|
||||
}
|
||||
|
||||
pub fn first_partition_header_offset(&self) -> u32 {
|
||||
let partition_header_word_offset =
|
||||
unsafe { core::ptr::read_volatile((self.base_addr + 0x04) as *const u32) };
|
||||
partition_header_word_offset * 4
|
||||
}
|
||||
|
||||
pub fn partition_header_iterator(&self) -> PartitionHeaderIterator {
|
||||
let first_partition_header = self.first_partition_header_offset();
|
||||
PartitionHeaderIterator {
|
||||
current_partition_header_addr: first_partition_header,
|
||||
current_partition_index: 0,
|
||||
num_of_partitions: self.partition_count(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image_name_copied(&self, buf: &mut [u8]) -> Result<usize, BufTooSmallError> {
|
||||
let mut current_addr = self.base_addr + 0x10;
|
||||
let mut current_buf_idx = 0;
|
||||
let mut null_byte_found = false;
|
||||
loop {
|
||||
let next_bytes = unsafe { core::slice::from_raw_parts(current_addr as *const u8, 4) };
|
||||
for &byte in next_bytes.iter() {
|
||||
if byte == 0 {
|
||||
null_byte_found = true;
|
||||
break;
|
||||
}
|
||||
if current_buf_idx >= buf.len() {
|
||||
return Err(BufTooSmallError);
|
||||
}
|
||||
buf[current_buf_idx] = byte;
|
||||
current_buf_idx += 1;
|
||||
}
|
||||
if null_byte_found {
|
||||
break;
|
||||
}
|
||||
current_addr += 4;
|
||||
}
|
||||
Ok(current_buf_idx)
|
||||
}
|
||||
|
||||
pub fn next_image_header(&self) -> Option<ImageHeader> {
|
||||
if self.next_image_header_offset() == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { ImageHeader::new(self.base_addr + self.next_image_header_offset()) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PartitionHeaderIterator {
|
||||
current_partition_header_addr: u32,
|
||||
current_partition_index: u32,
|
||||
num_of_partitions: u32,
|
||||
}
|
||||
|
||||
impl Iterator for PartitionHeaderIterator {
|
||||
type Item = PartitionHeader;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.current_partition_index >= self.num_of_partitions {
|
||||
return None;
|
||||
}
|
||||
let header = unsafe { PartitionHeader::new(self.current_partition_index) };
|
||||
self.current_partition_index += 1;
|
||||
self.current_partition_header_addr += 0x40;
|
||||
Some(header)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PartitionHeader {
|
||||
base_addr: u32,
|
||||
}
|
||||
|
||||
impl PartitionHeader {
|
||||
/// # Safety
|
||||
///
|
||||
/// This function will perform volatile reads for partition header fields on the given base
|
||||
/// address. You must ensure that the address contains a valid partition header.
|
||||
///
|
||||
/// This is a low-level function. It is recommended to use
|
||||
/// [ImageHeader::partition_header_iterator] to retrieve the all partition headers for a given
|
||||
/// image header.
|
||||
#[inline]
|
||||
pub const unsafe fn new(base_addr: u32) -> Self {
|
||||
PartitionHeader { base_addr }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn encrypted_partition_length(&self) -> u32 {
|
||||
unsafe { core::ptr::read_volatile(self.base_addr as *const u32) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn unencrypted_partition_length(&self) -> u32 {
|
||||
unsafe { core::ptr::read_volatile((self.base_addr + 0x04) as *const u32) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn total_partition_length(&self) -> u32 {
|
||||
let total_words =
|
||||
unsafe { core::ptr::read_volatile((self.base_addr + 0x08) as *const u32) };
|
||||
total_words * 4
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn destination_load_address(&self) -> u32 {
|
||||
unsafe { core::ptr::read_volatile((self.base_addr + 0x0C) as *const _) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn data_offset(&self) -> u32 {
|
||||
let offset_words =
|
||||
unsafe { core::ptr::read_volatile((self.base_addr + 0x0C) as *const u32) };
|
||||
offset_words * 4
|
||||
}
|
||||
}
|
||||
@@ -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 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_arm_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) {
|
||||
unsafe {
|
||||
crate::slcr::Slcr::with(|slcr| {
|
||||
let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_arm_pll_ctrl();
|
||||
let pll_cfg_reg = slcr.clk_ctrl().pointer_to_arm_pll_cfg();
|
||||
configure_pll_unchecked(
|
||||
boot_mode,
|
||||
pll_config,
|
||||
PllType::Arm,
|
||||
slcr,
|
||||
pll_ctrl_reg,
|
||||
pll_cfg_reg,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// This function configures the IO PLL based on the provided [PllConfig].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should only be called once during system initialization, for example in the
|
||||
/// first-stage bootloader (FSBL).
|
||||
pub unsafe fn configure_io_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) {
|
||||
unsafe {
|
||||
crate::slcr::Slcr::with(|slcr| {
|
||||
let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_io_pll_ctrl();
|
||||
let pll_cfg_reg = slcr.clk_ctrl().pointer_to_io_pll_cfg();
|
||||
configure_pll_unchecked(
|
||||
boot_mode,
|
||||
pll_config,
|
||||
PllType::Io,
|
||||
slcr,
|
||||
pll_ctrl_reg,
|
||||
pll_cfg_reg,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// This function configures the DDR PLL based on the provided [PllConfig].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should only be called once during system initialization, for example in the
|
||||
/// first-stage bootloader (FSBL).
|
||||
pub unsafe fn configure_ddr_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) {
|
||||
unsafe {
|
||||
crate::slcr::Slcr::with(|slcr| {
|
||||
let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_ddr_pll_ctrl();
|
||||
let pll_cfg_reg = slcr.clk_ctrl().pointer_to_ddr_pll_cfg();
|
||||
configure_pll_unchecked(
|
||||
boot_mode,
|
||||
pll_config,
|
||||
PllType::Ddr,
|
||||
slcr,
|
||||
pll_ctrl_reg,
|
||||
pll_cfg_reg,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enum PllType {
|
||||
Io,
|
||||
Ddr,
|
||||
Arm,
|
||||
}
|
||||
|
||||
impl PllType {
|
||||
pub const fn bit_offset_pll_locked(&self) -> usize {
|
||||
match self {
|
||||
PllType::Io => 2,
|
||||
PllType::Ddr => 1,
|
||||
PllType::Arm => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn configure_pll_unchecked(
|
||||
boot_mode: BootMode,
|
||||
cfg: PllConfig,
|
||||
pll_type: PllType,
|
||||
slcr: &mut zynq7000::slcr::MmioSlcr<'static>,
|
||||
pll_ctrl_reg: *mut zynq7000::slcr::clocks::PllControl,
|
||||
pll_cfg_reg: *mut zynq7000::slcr::clocks::PllConfig,
|
||||
) {
|
||||
// Step 1: Program the multiplier and other PLL configuration parameters.
|
||||
// New values will only be consumed once the PLL is reset.
|
||||
let mut pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
|
||||
pll_ctrl.set_fdiv(cfg.fdiv);
|
||||
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
|
||||
|
||||
let mut pll_cfg = unsafe { core::ptr::read_volatile(pll_cfg_reg) };
|
||||
pll_cfg.set_charge_pump(cfg.charge_pump);
|
||||
pll_cfg.set_loop_resistor(cfg.loop_resistor);
|
||||
pll_cfg.set_lock_count(cfg.lock_count);
|
||||
unsafe { core::ptr::write_volatile(pll_cfg_reg, pll_cfg) };
|
||||
|
||||
// Step 2: Force the PLL into bypass mode. If the PLL bypass mode pin is tied high,
|
||||
// the PLLs need to be enabled. According to the TRM, this is done by setting the
|
||||
// PLL_BYPASS_QUAL bit to 0, which de-asserts the reset to the Arm PLL.
|
||||
pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
|
||||
if boot_mode.pll_config() == zynq7000::slcr::BootPllConfig::Bypassed {
|
||||
pll_ctrl.set_bypass_qual(false);
|
||||
}
|
||||
pll_ctrl.set_bypass_force(true);
|
||||
pll_ctrl.set_pwrdwn(false);
|
||||
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
|
||||
|
||||
// Step 3: Reset the PLL. This also loads the new configuration.
|
||||
pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
|
||||
pll_ctrl.set_reset(true);
|
||||
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
|
||||
pll_ctrl.set_reset(false);
|
||||
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
|
||||
|
||||
while ((slcr.clk_ctrl().read_pll_status().raw_value() >> pll_type.bit_offset_pll_locked())
|
||||
& 0b1)
|
||||
!= 1
|
||||
{
|
||||
cortex_ar::asm::nop();
|
||||
}
|
||||
|
||||
pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
|
||||
pll_ctrl.set_bypass_force(false);
|
||||
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
|
||||
}
|
||||
334
zynq7000-hal/src/ddr/ll.rs
Normal file
334
zynq7000-hal/src/ddr/ll.rs
Normal file
@@ -0,0 +1,334 @@
|
||||
//! Low-level DDR configuration module.
|
||||
use arbitrary_int::{Number, u2, u3, u6};
|
||||
use zynq7000::ddrc::{MmioDdrController, regs::*};
|
||||
use zynq7000::slcr::{clocks::DciClockControl, ddriob::DdriobConfig};
|
||||
|
||||
use crate::{clocks::DdrClocks, time::Hertz};
|
||||
|
||||
const DCI_MAX_FREQ: Hertz = Hertz::from_raw(10_000_000);
|
||||
|
||||
// These values were extracted from the ps7_init files and are not documented in the TMR.
|
||||
// zynq-rs uses the same values.. I assume they are constant.
|
||||
|
||||
pub const DRIVE_SLEW_ADDR_CFG: u32 = 0x0018_c61c;
|
||||
pub const DRIVE_SLEW_DATA_CFG: u32 = 0x00f9_861c;
|
||||
pub const DRIVE_SLEW_DIFF_CFG: u32 = 0x00f9_861c;
|
||||
pub const DRIVE_SLEW_CLOCK_CFG: u32 = 0x00f9_861c;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DciClkConfig {
|
||||
div0: u6,
|
||||
div1: u6,
|
||||
}
|
||||
|
||||
/// Calculate the required DCI divisors for the given DDR clock.
|
||||
pub fn calculate_dci_divisors(ddr_clks: &DdrClocks) -> DciClkConfig {
|
||||
calculate_dci_divisors_with_ddr_clk(ddr_clks.ref_clk())
|
||||
}
|
||||
|
||||
/// Calculate the required DCI divisors for the given DDR clock frequency.
|
||||
pub fn calculate_dci_divisors_with_ddr_clk(ddr_clk: Hertz) -> DciClkConfig {
|
||||
let target_div = ddr_clk.raw().div_ceil(DCI_MAX_FREQ.raw());
|
||||
let mut config = DciClkConfig {
|
||||
div0: u6::new(u6::MAX.value()),
|
||||
div1: u6::new(u6::MAX.value()),
|
||||
};
|
||||
|
||||
let mut best_error = 0;
|
||||
for divisor0 in 1..63 {
|
||||
for divisor1 in 1..63 {
|
||||
let current_div = (divisor0 as u32) * (divisor1 as u32);
|
||||
let error = current_div.abs_diff(target_div);
|
||||
if error < best_error {
|
||||
config.div0 = u6::new(divisor0 as u8);
|
||||
config.div1 = u6::new(divisor1 as u8);
|
||||
best_error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
/// Configure the DCI module by configure its clock divisors and enabling it.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function writes to DCI related registers. It should only be called once during
|
||||
/// DDR initialization.
|
||||
pub unsafe fn configure_dci(ddr_clk: &DdrClocks) {
|
||||
let cfg = calculate_dci_divisors(ddr_clk);
|
||||
// Safety: Only writes to DCI clock related registers.
|
||||
unsafe {
|
||||
crate::Slcr::with(|slcr| {
|
||||
slcr.clk_ctrl().write_dci_clk_ctrl(
|
||||
DciClockControl::builder()
|
||||
.with_divisor_1(cfg.div1)
|
||||
.with_divisor_0(cfg.div0)
|
||||
.with_clk_act(true)
|
||||
.build(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Calibrates the IOB impedance for DDR3 memory according to to TRM p.325, DDR IOB Impedance
|
||||
/// calibration.
|
||||
///
|
||||
/// This function will also enable the DCI clock with the provided clock configuration.
|
||||
/// You can use [calculate_dci_divisors] to calculate the divisor values for the given DDR clock,
|
||||
/// or you can hardcode the values if they are fixed.
|
||||
///
|
||||
/// Polling for completion can be disabled. The calibration takes 1-2 ms according to the TRM, so
|
||||
/// the user can set other configuration values which are not reliant on DDR operation before
|
||||
/// polling for completion.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function writes to the DDR IOB related registers. It should only be called once during
|
||||
/// DDR initialization.
|
||||
pub unsafe fn calibrate_iob_impedance_for_ddr3(dci_clk_cfg: DciClkConfig, poll_for_done: bool) {
|
||||
unsafe {
|
||||
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.
|
||||
///
|
||||
/// Polling for completion can be disabled. The calibration takes 1-2 ms according to the TRM, so
|
||||
/// the user can set other configuration values which are not reliant on DDR operation before
|
||||
/// polling for completion.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function writes to the DDR IOB related registers. It should only be called once during
|
||||
/// DDR initialization.
|
||||
pub unsafe fn calibrate_iob_impedance(
|
||||
dci_clk_cfg: DciClkConfig,
|
||||
pref_opt2: u3,
|
||||
pref_opt1: u2,
|
||||
nref_opt4: u3,
|
||||
nref_opt2: u3,
|
||||
nref_opt1: u2,
|
||||
poll_for_done: bool,
|
||||
) {
|
||||
// Safety: Only writes to DDR IOB related registers.
|
||||
let mut slcr = unsafe { crate::slcr::Slcr::steal() };
|
||||
slcr.modify(|slcr| {
|
||||
slcr.clk_ctrl().write_dci_clk_ctrl(
|
||||
DciClockControl::builder()
|
||||
.with_divisor_1(dci_clk_cfg.div1)
|
||||
.with_divisor_0(dci_clk_cfg.div0)
|
||||
.with_clk_act(true)
|
||||
.build(),
|
||||
);
|
||||
let mut ddriob = slcr.ddriob();
|
||||
ddriob.modify_dci_ctrl(|mut val| {
|
||||
val.set_reset(true);
|
||||
val
|
||||
});
|
||||
ddriob.modify_dci_ctrl(|mut val| {
|
||||
val.set_reset(false);
|
||||
val
|
||||
});
|
||||
ddriob.modify_dci_ctrl(|mut val| {
|
||||
val.set_reset(true);
|
||||
val
|
||||
});
|
||||
ddriob.modify_dci_ctrl(|mut val| {
|
||||
val.set_pref_opt2(pref_opt2);
|
||||
val.set_pref_opt1(pref_opt1);
|
||||
val.set_nref_opt4(nref_opt4);
|
||||
val.set_nref_opt2(nref_opt2);
|
||||
val.set_nref_opt1(nref_opt1);
|
||||
val
|
||||
});
|
||||
ddriob.modify_dci_ctrl(|mut val| {
|
||||
val.set_update_control(false);
|
||||
val
|
||||
});
|
||||
ddriob.modify_dci_ctrl(|mut val| {
|
||||
val.set_enable(true);
|
||||
val
|
||||
});
|
||||
if poll_for_done {
|
||||
while !slcr.ddriob().read_dci_status().done() {
|
||||
// Wait for the DDR IOB impedance calibration to complete.
|
||||
cortex_ar::asm::nop();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Static configuration for DDR IOBs.
|
||||
pub struct DdriobConfigSet {
|
||||
pub addr0: DdriobConfig,
|
||||
pub addr1: DdriobConfig,
|
||||
pub data0: DdriobConfig,
|
||||
pub data1: DdriobConfig,
|
||||
pub diff0: DdriobConfig,
|
||||
pub diff1: DdriobConfig,
|
||||
pub clock: DdriobConfig,
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This function writes to the IOB related registers. It should only be called once during
|
||||
/// DDR initialization.
|
||||
pub unsafe fn configure_iob(cfg_set: &DdriobConfigSet) {
|
||||
// Safety: Only configures IOB related registers.
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
/// Full static DDRC configuration set.
|
||||
pub struct DdrcConfigSet {
|
||||
pub ctrl: DdrcControl,
|
||||
pub two_rank: TwoRankConfig,
|
||||
pub hpr: LprHprQueueControl,
|
||||
pub lpr: LprHprQueueControl,
|
||||
pub wr: WriteQueueControl,
|
||||
pub dram_param_0: DramParamReg0,
|
||||
pub dram_param_1: DramParamReg1,
|
||||
pub dram_param_2: DramParamReg2,
|
||||
pub dram_param_3: DramParamReg3,
|
||||
pub dram_param_4: DramParamReg4,
|
||||
pub dram_init_param: DramInitParam,
|
||||
pub dram_emr: DramEmr,
|
||||
pub dram_emr_mr: DramEmrMr,
|
||||
pub dram_burst8_rdwr: DramBurst8ReadWrite,
|
||||
pub disable_dq: DisableDq,
|
||||
pub dram_addr_map_bank: DramAddrMapBank,
|
||||
pub dram_addr_map_col: DramAddrMapColumn,
|
||||
pub dram_addr_map_row: DramAddrMapRow,
|
||||
pub dram_odt: DramOdt,
|
||||
pub phy_cmd_timeout_rddata_cpt: PhyCmdTimeoutRdDataCpt,
|
||||
pub dll_calib: DllCalib,
|
||||
pub odt_delay_hold: OdtDelayHold,
|
||||
pub ctrl_reg1: CtrlReg1,
|
||||
pub ctrl_reg2: CtrlReg2,
|
||||
pub ctrl_reg3: CtrlReg3,
|
||||
pub ctrl_reg4: CtrlReg4,
|
||||
pub ctrl_reg5: CtrlReg5,
|
||||
pub ctrl_reg6: CtrlReg6,
|
||||
pub che_t_zq: CheTZq,
|
||||
pub che_t_zq_short_interval_reg: CheTZqShortInterval,
|
||||
pub deep_powerdown: DeepPowerdown,
|
||||
pub reg_2c: Reg2c,
|
||||
pub reg_2d: Reg2d,
|
||||
pub dfi_timing: DfiTiming,
|
||||
pub che_ecc_ctrl: CheEccControl,
|
||||
pub ecc_scrub: EccScrub,
|
||||
pub phy_receiver_enable: PhyReceiverEnable,
|
||||
pub phy_config: [PhyConfig; 4],
|
||||
pub phy_init_ratio: [PhyInitRatio; 4],
|
||||
pub phy_rd_dqs_config: [PhyDqsConfig; 4],
|
||||
pub phy_wr_dqs_config: [PhyDqsConfig; 4],
|
||||
pub phy_we_cfg: [PhyWriteEnableConfig; 4],
|
||||
pub phy_wr_data_slv: [PhyWriteDataSlaveConfig; 4],
|
||||
pub reg64: Reg64,
|
||||
pub reg65: Reg65,
|
||||
pub page_mask: u32,
|
||||
pub axi_priority_wr_port: [AxiPriorityWritePort; 4],
|
||||
pub axi_priority_rd_port: [AxiPriorityReadPort; 4],
|
||||
pub lpddr_ctrl_0: LpddrControl0,
|
||||
pub lpddr_ctrl_1: LpddrControl1,
|
||||
pub lpddr_ctrl_2: LpddrControl2,
|
||||
pub lpddr_ctrl_3: LpddrControl3,
|
||||
}
|
||||
|
||||
/// This low-level function sets all the configuration registers.
|
||||
///
|
||||
/// It does NOT take care of taking the DDR controller out of reset and polling for DDR
|
||||
/// configuration completion.
|
||||
pub fn configure_ddr_config(ddrc: &mut MmioDdrController<'static>, cfg_set: &DdrcConfigSet) {
|
||||
ddrc.write_ddrc_ctrl(cfg_set.ctrl);
|
||||
// Write all configuration registers.
|
||||
ddrc.write_two_rank_cfg(cfg_set.two_rank);
|
||||
ddrc.write_hpr_queue_ctrl(cfg_set.hpr);
|
||||
ddrc.write_lpr_queue_ctrl(cfg_set.lpr);
|
||||
ddrc.write_wr_reg(cfg_set.wr);
|
||||
ddrc.write_dram_param_reg0(cfg_set.dram_param_0);
|
||||
ddrc.write_dram_param_reg1(cfg_set.dram_param_1);
|
||||
ddrc.write_dram_param_reg2(cfg_set.dram_param_2);
|
||||
ddrc.write_dram_param_reg3(cfg_set.dram_param_3);
|
||||
ddrc.write_dram_param_reg4(cfg_set.dram_param_4);
|
||||
ddrc.write_dram_init_param(cfg_set.dram_init_param);
|
||||
ddrc.write_dram_emr(cfg_set.dram_emr);
|
||||
ddrc.write_dram_emr_mr(cfg_set.dram_emr_mr);
|
||||
ddrc.write_dram_burst8_rdwr(cfg_set.dram_burst8_rdwr);
|
||||
ddrc.write_dram_disable_dq(cfg_set.disable_dq);
|
||||
ddrc.write_phy_cmd_timeout_rddata_cpt(cfg_set.phy_cmd_timeout_rddata_cpt);
|
||||
ddrc.write_dll_calib(cfg_set.dll_calib);
|
||||
ddrc.write_odt_delay_hold(cfg_set.odt_delay_hold);
|
||||
ddrc.write_ctrl_reg1(cfg_set.ctrl_reg1);
|
||||
ddrc.write_ctrl_reg2(cfg_set.ctrl_reg2);
|
||||
ddrc.write_ctrl_reg3(cfg_set.ctrl_reg3);
|
||||
ddrc.write_ctrl_reg4(cfg_set.ctrl_reg4);
|
||||
ddrc.write_ctrl_reg5(cfg_set.ctrl_reg5);
|
||||
ddrc.write_ctrl_reg6(cfg_set.ctrl_reg6);
|
||||
ddrc.write_che_t_zq(cfg_set.che_t_zq);
|
||||
ddrc.write_che_t_zq_short_interval_reg(cfg_set.che_t_zq_short_interval_reg);
|
||||
ddrc.write_deep_powerdown_reg(cfg_set.deep_powerdown);
|
||||
ddrc.write_reg_2c(cfg_set.reg_2c);
|
||||
ddrc.write_reg_2d(cfg_set.reg_2d);
|
||||
ddrc.write_dfi_timing(cfg_set.dfi_timing);
|
||||
ddrc.write_che_ecc_control(cfg_set.che_ecc_ctrl);
|
||||
ddrc.write_ecc_scrub(cfg_set.ecc_scrub);
|
||||
ddrc.write_phy_receiver_enable(cfg_set.phy_receiver_enable);
|
||||
for i in 0..4 {
|
||||
// Safety: Indexes are valid.
|
||||
unsafe {
|
||||
ddrc.write_phy_config_unchecked(i, cfg_set.phy_config[i]);
|
||||
ddrc.write_phy_init_ratio_unchecked(i, cfg_set.phy_init_ratio[i]);
|
||||
ddrc.write_phy_rd_dqs_cfg_unchecked(i, cfg_set.phy_rd_dqs_config[i]);
|
||||
ddrc.write_phy_wr_dqs_cfg_unchecked(i, cfg_set.phy_wr_dqs_config[i]);
|
||||
ddrc.write_phy_we_cfg_unchecked(i, cfg_set.phy_we_cfg[i]);
|
||||
ddrc.write_phy_wr_data_slave_unchecked(i, cfg_set.phy_wr_data_slv[i]);
|
||||
}
|
||||
}
|
||||
ddrc.write_reg_64(cfg_set.reg64);
|
||||
ddrc.write_reg_65(cfg_set.reg65);
|
||||
ddrc.write_page_mask(cfg_set.page_mask);
|
||||
for i in 0..4 {
|
||||
// Safety: Indexes are valid.
|
||||
unsafe {
|
||||
ddrc.write_axi_priority_wr_port_unchecked(i, cfg_set.axi_priority_wr_port[i]);
|
||||
ddrc.write_axi_priority_rd_port_unchecked(i, cfg_set.axi_priority_rd_port[i]);
|
||||
}
|
||||
}
|
||||
ddrc.write_lpddr_ctrl_0(cfg_set.lpddr_ctrl_0);
|
||||
ddrc.write_lpddr_ctrl_1(cfg_set.lpddr_ctrl_1);
|
||||
ddrc.write_lpddr_ctrl_2(cfg_set.lpddr_ctrl_2);
|
||||
ddrc.write_lpddr_ctrl_3(cfg_set.lpddr_ctrl_3);
|
||||
}
|
||||
227
zynq7000-hal/src/ddr/mod.rs
Normal file
227
zynq7000-hal/src/ddr/mod.rs
Normal file
@@ -0,0 +1,227 @@
|
||||
use arbitrary_int::u6;
|
||||
use zynq7000::ddrc::MmioDdrController;
|
||||
|
||||
use crate::{
|
||||
BootMode,
|
||||
clocks::pll::{PllConfig, configure_ddr_pll},
|
||||
time::Hertz,
|
||||
};
|
||||
|
||||
pub mod ll;
|
||||
|
||||
pub use ll::{DdrcConfigSet, DdriobConfigSet};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DdrClockSetupConfig {
|
||||
pub ps_clk: Hertz,
|
||||
pub ddr_clk: Hertz,
|
||||
pub ddr_3x_div: u6,
|
||||
pub ddr_2x_div: u6,
|
||||
}
|
||||
|
||||
impl DdrClockSetupConfig {
|
||||
pub const fn new(ps_clk: Hertz, ddr_clk: Hertz, ddr_3x_div: u6, ddr_2x_div: u6) -> Self {
|
||||
Self {
|
||||
ps_clk,
|
||||
ddr_clk,
|
||||
ddr_3x_div,
|
||||
ddr_2x_div,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This completely sets up the DDR module for DDR3 operation.
|
||||
///
|
||||
/// It performs the following steps accoridng to the functional programming model of the
|
||||
/// DDR memory controller, TRM p.323.
|
||||
///
|
||||
/// 1. Configures the DDR PLL to the target clock frequency.
|
||||
/// 2. Configures the DDR clocks based on the user-provided configuration.
|
||||
/// 3. Configures and sets up the DDR I/O buffers (DDR IOB) module.
|
||||
/// 4. Configures and sets up the DDR controller (DDRC) module.
|
||||
///
|
||||
/// This function consumes the DDRC register block once and thus provides a safe interface for DDR
|
||||
/// initialization.
|
||||
pub fn configure_ddr_for_ddr3(
|
||||
mut ddrc_regs: MmioDdrController<'static>,
|
||||
boot_mode: BootMode,
|
||||
clk_setup_cfg: DdrClockSetupConfig,
|
||||
ddriob_cfg: &DdriobConfigSet,
|
||||
ddr_cfg: &DdrcConfigSet,
|
||||
) {
|
||||
// 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(clk_setup_cfg.ps_clk, clk_setup_cfg.ddr_clk).unwrap(),
|
||||
);
|
||||
// Safety: Only done once here during start-up.
|
||||
let ddr_clks = unsafe {
|
||||
crate::clocks::DdrClocks::new_with_2x_3x_init(
|
||||
clk_setup_cfg.ddr_clk,
|
||||
clk_setup_cfg.ddr_3x_div,
|
||||
clk_setup_cfg.ddr_2x_div,
|
||||
)
|
||||
};
|
||||
let dci_clk_cfg = ll::calculate_dci_divisors(&ddr_clks);
|
||||
|
||||
ddrc_regs.modify_ddrc_ctrl(|mut val| {
|
||||
val.set_soft_reset(zynq7000::ddrc::regs::SoftReset::Reset);
|
||||
val
|
||||
});
|
||||
|
||||
// Safety: This is only called once during DDR initialization.
|
||||
unsafe {
|
||||
ll::configure_iob(ddriob_cfg);
|
||||
// Do not wait for completion, it takes a bit of time. We can set all the DDR config registers
|
||||
// before polling for completion.
|
||||
ll::calibrate_iob_impedance_for_ddr3(dci_clk_cfg, false);
|
||||
}
|
||||
ll::configure_ddr_config(&mut ddrc_regs, ddr_cfg);
|
||||
// Safety: This is only called once during DDR initialization, and we only modify DDR related
|
||||
// registers.
|
||||
let slcr = unsafe { crate::slcr::Slcr::steal() };
|
||||
let ddriob_shared = slcr.regs().ddriob_shared();
|
||||
// Wait for DDR IOB impedance calibration to complete first.
|
||||
while !ddriob_shared.read_dci_status().done() {
|
||||
cortex_ar::asm::nop();
|
||||
}
|
||||
log::debug!("DDR IOB impedance calib done");
|
||||
|
||||
// Now take the DDR out of reset.
|
||||
ddrc_regs.modify_ddrc_ctrl(|mut val| {
|
||||
val.set_soft_reset(zynq7000::ddrc::regs::SoftReset::Active);
|
||||
val
|
||||
});
|
||||
// Wait until the DDR setup has completed.
|
||||
while ddrc_regs.read_mode_status().operating_mode()
|
||||
!= zynq7000::ddrc::regs::OperatingMode::NormalOperation
|
||||
{
|
||||
// Wait for the soft reset to complete.
|
||||
cortex_ar::asm::nop();
|
||||
}
|
||||
}
|
||||
|
||||
pub mod memtest {
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum MemTestError {
|
||||
#[error("memory address is not aligned to 4 bytes")]
|
||||
AddrNotAligned,
|
||||
#[error("memory test error")]
|
||||
Memory {
|
||||
addr: usize,
|
||||
expected: u32,
|
||||
found: u32,
|
||||
},
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This tests writes and reads on a memory block starting at the base address
|
||||
/// with the size `words` times 4.
|
||||
pub unsafe fn walking_zero_test(base_addr: usize, words: usize) -> Result<(), MemTestError> {
|
||||
unsafe { walking_value_test(true, base_addr, words) }
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This tests writes and reads on a memory block starting at the base address
|
||||
/// with the size `words` times 4.
|
||||
pub unsafe fn walking_one_test(base_addr: usize, words: usize) -> Result<(), MemTestError> {
|
||||
unsafe { walking_value_test(true, base_addr, words) }
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This tests writes and reads on a memory block starting at the base address
|
||||
/// with the size `words` times 4.
|
||||
pub unsafe fn walking_value_test(
|
||||
walking_zero: bool,
|
||||
base_addr: usize,
|
||||
words: usize,
|
||||
) -> Result<(), MemTestError> {
|
||||
if words == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
if !base_addr.is_multiple_of(4) {
|
||||
return Err(MemTestError::AddrNotAligned);
|
||||
}
|
||||
let base_ptr = base_addr as *mut u32;
|
||||
|
||||
// For each bit position 0..31 generate pattern = 1 << bit
|
||||
for bit in 0..32 {
|
||||
let pattern = if walking_zero {
|
||||
!(1u32 << bit)
|
||||
} else {
|
||||
1u32 << bit
|
||||
};
|
||||
|
||||
// write pass
|
||||
for i in 0..words {
|
||||
unsafe {
|
||||
let p = base_ptr.add(i);
|
||||
core::ptr::write_volatile(p, pattern);
|
||||
}
|
||||
}
|
||||
|
||||
// read/verify pass
|
||||
for i in 0..words {
|
||||
let val;
|
||||
unsafe {
|
||||
let p = base_ptr.add(i) as *const u32;
|
||||
val = core::ptr::read_volatile(p);
|
||||
}
|
||||
if val != pattern {
|
||||
return Err(MemTestError::Memory {
|
||||
addr: base_addr + i * 4,
|
||||
expected: pattern,
|
||||
found: val,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This tests writes and reads on a memory block starting at the base address
|
||||
/// with the size `words` times 4.
|
||||
pub unsafe fn checkerboard_test(base_addr: usize, words: usize) -> Result<(), MemTestError> {
|
||||
if words == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
if !base_addr.is_multiple_of(4) {
|
||||
return Err(MemTestError::AddrNotAligned);
|
||||
}
|
||||
|
||||
let base_ptr = base_addr as *mut u32;
|
||||
let patterns = [0xAAAAAAAAu32, 0x55555555u32];
|
||||
|
||||
for &pattern in &patterns {
|
||||
// Write pass
|
||||
for i in 0..words {
|
||||
let value = if i % 2 == 0 { pattern } else { !pattern };
|
||||
unsafe {
|
||||
core::ptr::write_volatile(base_ptr.add(i), value);
|
||||
}
|
||||
}
|
||||
|
||||
// Read/verify pass
|
||||
for i in 0..words {
|
||||
let expected = if i % 2 == 0 { pattern } else { !pattern };
|
||||
let val = unsafe { core::ptr::read_volatile(base_ptr.add(i)) };
|
||||
|
||||
if val != expected {
|
||||
return Err(MemTestError::Memory {
|
||||
addr: base_addr + i * 4,
|
||||
expected,
|
||||
found: val,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use zynq7000::{
|
||||
slcr::reset::EthernetReset,
|
||||
};
|
||||
|
||||
use crate::{clocks::IoClocks, enable_amba_periph_clk, slcr::Slcr, time::Hertz};
|
||||
use crate::{clocks::IoClocks, enable_amba_peripheral_clock, slcr::Slcr, time::Hertz};
|
||||
|
||||
use super::{EthernetId, PsEthernet as _};
|
||||
|
||||
@@ -239,7 +239,7 @@ impl EthernetLowLevel {
|
||||
EthernetId::Eth0 => crate::PeriphSelect::Gem0,
|
||||
EthernetId::Eth1 => crate::PeriphSelect::Gem1,
|
||||
};
|
||||
enable_amba_periph_clk(periph_sel);
|
||||
enable_amba_peripheral_clock(periph_sel);
|
||||
}
|
||||
|
||||
/// Completely configures the clock based on the provided [ClockConfig].
|
||||
|
||||
@@ -105,132 +105,132 @@ impl PsEthernet for MmioEthernet<'static> {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TxClk: MioPinMarker {
|
||||
pub trait TxClockPin: MioPinMarker {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait TxCtrl: MioPinMarker {
|
||||
pub trait TxControlPin: MioPinMarker {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait TxData0: MioPinMarker {
|
||||
pub trait TxData0Pin: MioPinMarker {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait TxData1: MioPinMarker {
|
||||
pub trait TxData1Pin: MioPinMarker {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait TxData2: MioPinMarker {
|
||||
pub trait TxData2Pin: MioPinMarker {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait TxData3: MioPinMarker {
|
||||
pub trait TxData3Pin: MioPinMarker {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait RxClk: MioPinMarker {
|
||||
pub trait RxClockPin: MioPinMarker {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait RxCtrl: MioPinMarker {
|
||||
pub trait RxControlPin: MioPinMarker {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait RxData0: MioPinMarker {
|
||||
pub trait RxData0Pin: MioPinMarker {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait RxData1: MioPinMarker {
|
||||
pub trait RxData1Pin: MioPinMarker {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait RxData2: MioPinMarker {
|
||||
pub trait RxData2Pin: MioPinMarker {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait RxData3: MioPinMarker {
|
||||
pub trait RxData3Pin: MioPinMarker {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
|
||||
pub trait MdClk: MioPinMarker {}
|
||||
pub trait MdIo: MioPinMarker {}
|
||||
pub trait MdClockPin: MioPinMarker {}
|
||||
pub trait MdIoPin: MioPinMarker {}
|
||||
|
||||
impl MdClk for Pin<Mio52> {}
|
||||
impl MdIo for Pin<Mio53> {}
|
||||
impl MdClockPin for Pin<Mio52> {}
|
||||
impl MdIoPin for Pin<Mio53> {}
|
||||
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl TxClk for Pin<Mio16> {
|
||||
impl TxClockPin for Pin<Mio16> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl TxCtrl for Pin<Mio21> {
|
||||
impl TxControlPin for Pin<Mio21> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl TxData0 for Pin<Mio17> {
|
||||
impl TxData0Pin for Pin<Mio17> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl TxData1 for Pin<Mio18> {
|
||||
impl TxData1Pin for Pin<Mio18> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl TxData2 for Pin<Mio19> {
|
||||
impl TxData2Pin for Pin<Mio19> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl TxData3 for Pin<Mio20> {
|
||||
impl TxData3Pin for Pin<Mio20> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl RxClk for Pin<Mio22> {
|
||||
impl RxClockPin for Pin<Mio22> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl RxCtrl for Pin<Mio27> {
|
||||
impl RxControlPin for Pin<Mio27> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl RxData0 for Pin<Mio23> {
|
||||
impl RxData0Pin for Pin<Mio23> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl RxData1 for Pin<Mio24> {
|
||||
impl RxData1Pin for Pin<Mio24> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl RxData2 for Pin<Mio25> {
|
||||
impl RxData2Pin for Pin<Mio25> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl RxData3 for Pin<Mio26> {
|
||||
impl RxData3Pin for Pin<Mio26> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
|
||||
impl TxClk for Pin<Mio28> {
|
||||
impl TxClockPin for Pin<Mio28> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl TxCtrl for Pin<Mio33> {
|
||||
impl TxControlPin for Pin<Mio33> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl TxData0 for Pin<Mio29> {
|
||||
impl TxData0Pin for Pin<Mio29> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl TxData1 for Pin<Mio30> {
|
||||
impl TxData1Pin for Pin<Mio30> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl TxData2 for Pin<Mio31> {
|
||||
impl TxData2Pin for Pin<Mio31> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl TxData3 for Pin<Mio32> {
|
||||
impl TxData3Pin for Pin<Mio32> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl RxClk for Pin<Mio34> {
|
||||
impl RxClockPin for Pin<Mio34> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl RxCtrl for Pin<Mio39> {
|
||||
impl RxControlPin for Pin<Mio39> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl RxData0 for Pin<Mio35> {
|
||||
impl RxData0Pin for Pin<Mio35> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl RxData1 for Pin<Mio36> {
|
||||
impl RxData1Pin for Pin<Mio36> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl RxData2 for Pin<Mio37> {
|
||||
impl RxData2Pin for Pin<Mio37> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl RxData3 for Pin<Mio38> {
|
||||
impl RxData3Pin for Pin<Mio38> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
|
||||
@@ -328,30 +328,30 @@ impl Ethernet {
|
||||
/// configuring all the necessary MIO pins.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new_with_mio<
|
||||
TxClkPin: TxClk,
|
||||
TxCtrlPin: TxCtrl,
|
||||
TxData0Pin: TxData0,
|
||||
TxData1Pin: TxData1,
|
||||
TxData2Pin: TxData2,
|
||||
TxData3Pin: TxData3,
|
||||
RxClkPin: RxClk,
|
||||
RxCtrlPin: RxCtrl,
|
||||
RxData0Pin: RxData0,
|
||||
RxData1Pin: RxData1,
|
||||
RxData2Pin: RxData2,
|
||||
RxData3Pin: RxData3,
|
||||
MdClkPin: MdClk,
|
||||
MdIoPin: MdIo,
|
||||
TxClock: TxClockPin,
|
||||
TxControl: TxControlPin,
|
||||
TxData0: TxData0Pin,
|
||||
TxData1: TxData1Pin,
|
||||
TxData2: TxData2Pin,
|
||||
TxData3: TxData3Pin,
|
||||
RxClock: RxClockPin,
|
||||
RxControl: RxControlPin,
|
||||
RxData0: RxData0Pin,
|
||||
RxData1: RxData1Pin,
|
||||
RxData2: RxData2Pin,
|
||||
RxData3: RxData3Pin,
|
||||
MdClock: MdClockPin,
|
||||
MdIo: MdIoPin,
|
||||
>(
|
||||
mut ll: ll::EthernetLowLevel,
|
||||
config: EthernetConfig,
|
||||
tx_clk: TxClkPin,
|
||||
tx_ctrl: TxCtrlPin,
|
||||
tx_data: (TxData0Pin, TxData1Pin, TxData2Pin, TxData3Pin),
|
||||
rx_clk: RxClkPin,
|
||||
rx_ctrl: RxCtrlPin,
|
||||
rx_data: (RxData0Pin, RxData1Pin, RxData2Pin, RxData3Pin),
|
||||
md_pins: Option<(MdClkPin, MdIoPin)>,
|
||||
tx_clk: TxClock,
|
||||
tx_ctrl: TxControl,
|
||||
tx_data: (TxData0, TxData1, TxData2, TxData3),
|
||||
rx_clk: RxClock,
|
||||
rx_ctrl: RxControl,
|
||||
rx_data: (RxData0, RxData1, RxData2, RxData3),
|
||||
md_pins: Option<(MdClock, MdIo)>,
|
||||
) -> Self {
|
||||
Self::common_init(&mut ll, config.mac_address);
|
||||
let tx_mio_config = zynq7000::slcr::mio::Config::builder()
|
||||
|
||||
@@ -174,7 +174,7 @@ pin_id!(Mio51, 51);
|
||||
pin_id!(Mio52, 52);
|
||||
pin_id!(Mio53, 53);
|
||||
|
||||
pub trait MioPinMarker {
|
||||
pub trait MioPinMarker: crate::sealed::Sealed {
|
||||
fn offset(&self) -> usize;
|
||||
}
|
||||
|
||||
@@ -379,3 +379,5 @@ impl<I: PinId> MioPinMarker for Pin<I> {
|
||||
I::OFFSET
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: PinId> crate::sealed::Sealed for Pin<I> {}
|
||||
|
||||
@@ -17,7 +17,7 @@ use ll::PinOffset;
|
||||
use mio::{MioPinMarker, MuxConfig};
|
||||
|
||||
use crate::gpio::ll::LowLevelGpio;
|
||||
use crate::{enable_amba_periph_clk, slcr::Slcr};
|
||||
use crate::{enable_amba_peripheral_clock, slcr::Slcr};
|
||||
pub use embedded_hal::digital::PinState;
|
||||
use zynq7000::{gpio::MmioGpio, slcr::reset::GpioClockReset};
|
||||
|
||||
@@ -33,7 +33,7 @@ pub struct GpioPins {
|
||||
|
||||
impl GpioPins {
|
||||
pub fn new(gpio: MmioGpio) -> Self {
|
||||
enable_amba_periph_clk(crate::PeriphSelect::Gpio);
|
||||
enable_amba_peripheral_clock(crate::PeriphSelect::Gpio);
|
||||
Self {
|
||||
mio: mio::Pins::new(unsafe { gpio.clone() }),
|
||||
emio: emio::Pins::new(gpio),
|
||||
|
||||
@@ -156,7 +156,7 @@ impl GlobalTimerCounter {
|
||||
}
|
||||
}
|
||||
|
||||
/// GTC can be used for blocking delays.
|
||||
/// GTC can also be used for blocking delays.
|
||||
impl embedded_hal::delay::DelayNs for GlobalTimerCounter {
|
||||
fn delay_ns(&mut self, ns: u32) {
|
||||
if self.cpu_3x2x_clock.is_none() {
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::gpio::mio::{
|
||||
Mio41, Mio42, Mio43, Mio44, Mio45, Mio46, Mio47, Mio50, Mio51,
|
||||
};
|
||||
use crate::{
|
||||
enable_amba_periph_clk,
|
||||
enable_amba_peripheral_clock,
|
||||
gpio::{
|
||||
IoPeriphPin,
|
||||
mio::{
|
||||
@@ -348,7 +348,7 @@ impl I2c {
|
||||
I2cId::I2c0 => crate::PeriphSelect::I2c0,
|
||||
I2cId::I2c1 => crate::PeriphSelect::I2c1,
|
||||
};
|
||||
enable_amba_periph_clk(periph_sel);
|
||||
enable_amba_peripheral_clock(periph_sel);
|
||||
//reset(id);
|
||||
regs.write_cr(
|
||||
Control::builder()
|
||||
|
||||
@@ -13,10 +13,15 @@
|
||||
extern crate alloc;
|
||||
|
||||
use slcr::Slcr;
|
||||
use zynq7000::slcr::LevelShifterRegister;
|
||||
use zynq7000::{
|
||||
SpiClockPhase, SpiClockPolarity,
|
||||
slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister},
|
||||
};
|
||||
|
||||
pub mod boot_image;
|
||||
pub mod cache;
|
||||
pub mod clocks;
|
||||
pub mod ddr;
|
||||
pub mod eth;
|
||||
pub mod gic;
|
||||
pub mod gpio;
|
||||
@@ -25,6 +30,8 @@ pub mod i2c;
|
||||
pub mod l2_cache;
|
||||
pub mod log;
|
||||
pub mod prelude;
|
||||
pub mod priv_tim;
|
||||
pub mod qspi;
|
||||
pub mod slcr;
|
||||
pub mod spi;
|
||||
pub mod time;
|
||||
@@ -43,36 +50,26 @@ pub enum BootDevice {
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum BootPllConfig {
|
||||
Enabled,
|
||||
Bypassed,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BootMode {
|
||||
boot_mode: Option<BootDevice>,
|
||||
pll_config: BootPllConfig,
|
||||
}
|
||||
|
||||
impl BootMode {
|
||||
#[allow(clippy::new_without_default)]
|
||||
/// Create a new boot mode information structure by reading the boot mode register from the
|
||||
/// fixed SLCR block.
|
||||
pub fn new() -> Self {
|
||||
pub fn new_from_regs() -> Self {
|
||||
// Safety: Only read a read-only register here.
|
||||
Self::new_with_raw_reg(
|
||||
unsafe { zynq7000::slcr::Slcr::new_mmio_fixed() }
|
||||
.read_boot_mode()
|
||||
.raw_value(),
|
||||
)
|
||||
Self::new_with_reg(unsafe { zynq7000::slcr::Slcr::new_mmio_fixed() }.read_boot_mode())
|
||||
}
|
||||
|
||||
fn new_with_raw_reg(raw_register: u32) -> Self {
|
||||
let msb_three_bits = (raw_register >> 1) & 0b111;
|
||||
fn new_with_reg(boot_mode_reg: BootModeRegister) -> Self {
|
||||
let boot_dev = boot_mode_reg.boot_mode();
|
||||
let msb_three_bits = (boot_dev.value() >> 1) & 0b111;
|
||||
|
||||
let boot_mode = match msb_three_bits {
|
||||
0b000 => {
|
||||
if raw_register & 0b1 == 0 {
|
||||
if boot_dev.value() & 0b1 == 0 {
|
||||
Some(BootDevice::JtagCascaded)
|
||||
} else {
|
||||
Some(BootDevice::JtagIndependent)
|
||||
@@ -84,21 +81,17 @@ impl BootMode {
|
||||
0b110 => Some(BootDevice::SdCard),
|
||||
_ => None,
|
||||
};
|
||||
let pll_config = if (raw_register >> 4) & 0b1 == 0 {
|
||||
BootPllConfig::Enabled
|
||||
} else {
|
||||
BootPllConfig::Bypassed
|
||||
};
|
||||
Self {
|
||||
boot_mode,
|
||||
pll_config,
|
||||
pll_config: boot_mode_reg.pll_config(),
|
||||
}
|
||||
}
|
||||
pub fn boot_device(&self) -> Option<BootDevice> {
|
||||
|
||||
pub const fn boot_device(&self) -> Option<BootDevice> {
|
||||
self.boot_mode
|
||||
}
|
||||
|
||||
pub const fn pll_enable(&self) -> BootPllConfig {
|
||||
pub const fn pll_config(&self) -> BootPllConfig {
|
||||
self.pll_config
|
||||
}
|
||||
}
|
||||
@@ -142,7 +135,7 @@ pub enum PeriphSelect {
|
||||
/// Enable the AMBA peripheral clock, which is required to read the registers of a peripheral
|
||||
/// block.
|
||||
#[inline]
|
||||
pub fn enable_amba_periph_clk(select: PeriphSelect) {
|
||||
pub fn enable_amba_peripheral_clock(select: PeriphSelect) {
|
||||
unsafe {
|
||||
Slcr::with(|regs| {
|
||||
regs.clk_ctrl().modify_aper_clk_ctrl(|mut val| {
|
||||
@@ -205,6 +198,30 @@ pub fn disable_amba_periph_clk(select: PeriphSelect) {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const fn spi_mode_const_to_cpol_cpha(
|
||||
mode: embedded_hal::spi::Mode,
|
||||
) -> (SpiClockPolarity, SpiClockPhase) {
|
||||
match mode {
|
||||
embedded_hal::spi::MODE_0 => (
|
||||
SpiClockPolarity::QuiescentLow,
|
||||
SpiClockPhase::ActiveOutsideOfWord,
|
||||
),
|
||||
embedded_hal::spi::MODE_1 => (
|
||||
SpiClockPolarity::QuiescentLow,
|
||||
SpiClockPhase::InactiveOutsideOfWord,
|
||||
),
|
||||
embedded_hal::spi::MODE_2 => (
|
||||
SpiClockPolarity::QuiescentHigh,
|
||||
SpiClockPhase::ActiveOutsideOfWord,
|
||||
),
|
||||
embedded_hal::spi::MODE_3 => (
|
||||
SpiClockPolarity::QuiescentHigh,
|
||||
SpiClockPhase::InactiveOutsideOfWord,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) mod sealed {
|
||||
pub trait Sealed {}
|
||||
|
||||
108
zynq7000-hal/src/priv_tim.rs
Normal file
108
zynq7000-hal/src/priv_tim.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use core::{marker::PhantomData, sync::atomic::AtomicBool};
|
||||
|
||||
use zynq7000::priv_tim::InterruptStatus;
|
||||
|
||||
use crate::{clocks::ArmClocks, time::Hertz};
|
||||
|
||||
static CORE_0_TIM_TAKEN: AtomicBool = AtomicBool::new(false);
|
||||
static CORE_1_TIM_TAKEN: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// High-level CPU private timer driver.
|
||||
pub struct CpuPrivateTimer {
|
||||
regs: zynq7000::priv_tim::MmioCpuPrivateTimer<'static>,
|
||||
cpu_3x2x_clock: Hertz,
|
||||
// Add this marker to explicitely opt-out of Send and Sync.
|
||||
//
|
||||
// This is a CPU private timer and thus should not be sent to other threads.
|
||||
_not_send: PhantomData<*const ()>,
|
||||
}
|
||||
|
||||
impl CpuPrivateTimer {
|
||||
/// Take the CPU private timer for a given core.
|
||||
///
|
||||
/// This function can only be called once for each given core.
|
||||
pub fn take(clocks: &ArmClocks) -> Option<Self> {
|
||||
let mpidr = cortex_ar::register::mpidr::Mpidr::read();
|
||||
let core = mpidr.0 & 0xff;
|
||||
if core != 0 && core != 1 {
|
||||
return None;
|
||||
}
|
||||
if (core == 0 && CORE_0_TIM_TAKEN.swap(true, core::sync::atomic::Ordering::Relaxed))
|
||||
|| (core == 1 && CORE_1_TIM_TAKEN.swap(true, core::sync::atomic::Ordering::Relaxed))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self::steal(clocks))
|
||||
}
|
||||
|
||||
/// Create a new CPU private timer driver.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function allows to potentially create an arbitrary amount of timers for both cores.
|
||||
/// It also does not check the current core ID.
|
||||
pub fn steal(clocks: &ArmClocks) -> Self {
|
||||
Self {
|
||||
regs: unsafe { zynq7000::priv_tim::CpuPrivateTimer::new_mmio_fixed() },
|
||||
cpu_3x2x_clock: clocks.cpu_3x2x_clk(),
|
||||
_not_send: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Write reload value which is set by the hardware when the timer reaches zero and
|
||||
/// auto reload is enabled.
|
||||
#[inline]
|
||||
pub fn write_reload(&mut self, value: u32) {
|
||||
self.regs.write_reload(value);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_counter(&mut self, value: u32) {
|
||||
self.regs.write_counter(value);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn counter(&self) -> u32 {
|
||||
self.regs.read_counter()
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal::delay::DelayNs for CpuPrivateTimer {
|
||||
fn delay_ns(&mut self, ns: u32) {
|
||||
// Even for a value of 1000 MHz for CPU 3x2x and u32::MAX for nanoseconds, this will
|
||||
// never overflow.
|
||||
let ticks = (ns as u64 * self.cpu_3x2x_clock.raw() as u64) / 1_000_000_000;
|
||||
|
||||
// Split the total delay into manageable chunks (u32::MAX ticks max).
|
||||
let mut remaining = ticks;
|
||||
|
||||
self.regs.modify_control(|mut val| {
|
||||
val.set_enable(false);
|
||||
// The event flag is still set, which is all we care about.
|
||||
val.set_interrupt_enable(false);
|
||||
val.set_auto_reload(false);
|
||||
val
|
||||
});
|
||||
while remaining > 0 {
|
||||
let chunk = (remaining as u32).min(u32::MAX - 1);
|
||||
|
||||
// Clear the timer flag by writing 1 to it.
|
||||
self.regs
|
||||
.write_interrupt_status(InterruptStatus::builder().with_event_flag(true).build());
|
||||
|
||||
// Load the timer with the chunk value and start it.
|
||||
self.write_reload(chunk);
|
||||
self.write_counter(chunk);
|
||||
self.regs.modify_control(|mut val| {
|
||||
val.set_enable(true);
|
||||
val
|
||||
});
|
||||
|
||||
// Wait for the timer to count down to zero.
|
||||
while !self.regs.read_interrupt_status().event_flag() {}
|
||||
|
||||
remaining -= chunk as u64;
|
||||
}
|
||||
}
|
||||
}
|
||||
234
zynq7000-hal/src/qspi/lqspi_configs.rs
Normal file
234
zynq7000-hal/src/qspi/lqspi_configs.rs
Normal file
@@ -0,0 +1,234 @@
|
||||
// The constants here are also checked/tested at compile time against table 12-3 in the TRM
|
||||
// p.368.
|
||||
use arbitrary_int::u3;
|
||||
use zynq7000::qspi::{InstructionCode, LinearQspiConfig};
|
||||
|
||||
pub const RD_ONE: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(false)
|
||||
.with_separate_memory_bus(false)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(false)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0x0)
|
||||
.with_num_dummy_bytes(u3::new(0x0))
|
||||
.with_instruction_code(InstructionCode::Read)
|
||||
.build();
|
||||
const RD_ONE_DEV_RAW: u32 = RD_ONE.raw_value();
|
||||
static_assertions::const_assert_eq!(RD_ONE_DEV_RAW, 0x8000_0003);
|
||||
|
||||
pub const RD_TWO: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(true)
|
||||
.with_separate_memory_bus(true)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(false)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0x0)
|
||||
.with_num_dummy_bytes(u3::new(0x0))
|
||||
.with_instruction_code(InstructionCode::Read)
|
||||
.build();
|
||||
const RD_TWO_RAW: u32 = RD_TWO.raw_value();
|
||||
static_assertions::const_assert_eq!(RD_TWO_RAW, 0xE000_0003);
|
||||
|
||||
pub const FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(false)
|
||||
.with_separate_memory_bus(false)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(false)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0x0)
|
||||
.with_num_dummy_bytes(u3::new(0x1))
|
||||
.with_instruction_code(InstructionCode::FastRead)
|
||||
.build();
|
||||
const FAST_RD_ONE_RAW: u32 = FAST_RD_ONE.raw_value();
|
||||
static_assertions::const_assert_eq!(FAST_RD_ONE_RAW, 0x8000_010B);
|
||||
|
||||
pub const FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(true)
|
||||
.with_separate_memory_bus(true)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(false)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0x0)
|
||||
.with_num_dummy_bytes(u3::new(0x1))
|
||||
.with_instruction_code(InstructionCode::FastRead)
|
||||
.build();
|
||||
const FAST_RD_TWO_RAW: u32 = FAST_RD_TWO.raw_value();
|
||||
static_assertions::const_assert_eq!(FAST_RD_TWO_RAW, 0xE000_010B);
|
||||
|
||||
pub const DUAL_OUT_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(false)
|
||||
.with_separate_memory_bus(false)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(false)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0x0)
|
||||
.with_num_dummy_bytes(u3::new(0x1))
|
||||
.with_instruction_code(InstructionCode::FastReadDualOutput)
|
||||
.build();
|
||||
const DUAL_OUT_FAST_RD_ONE_RAW: u32 = DUAL_OUT_FAST_RD_ONE.raw_value();
|
||||
static_assertions::const_assert_eq!(DUAL_OUT_FAST_RD_ONE_RAW, 0x8000_013B);
|
||||
|
||||
pub const DUAL_OUT_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(true)
|
||||
.with_separate_memory_bus(true)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(false)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0x0)
|
||||
.with_num_dummy_bytes(u3::new(0x1))
|
||||
.with_instruction_code(InstructionCode::FastReadDualOutput)
|
||||
.build();
|
||||
const DUAL_OUT_FAST_RD_TWO_RAW: u32 = DUAL_OUT_FAST_RD_TWO.raw_value();
|
||||
static_assertions::const_assert_eq!(DUAL_OUT_FAST_RD_TWO_RAW, 0xE000_013B);
|
||||
|
||||
pub const QUAD_OUT_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(false)
|
||||
.with_separate_memory_bus(false)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(false)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0x0)
|
||||
.with_num_dummy_bytes(u3::new(0x1))
|
||||
.with_instruction_code(InstructionCode::FastReadQuadOutput)
|
||||
.build();
|
||||
const QUAD_OUT_FAST_RD_ONE_RAW: u32 = QUAD_OUT_FAST_RD_ONE.raw_value();
|
||||
static_assertions::const_assert_eq!(QUAD_OUT_FAST_RD_ONE_RAW, 0x8000_016B);
|
||||
|
||||
pub const QUAD_OUT_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(true)
|
||||
.with_separate_memory_bus(true)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(false)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0x0)
|
||||
.with_num_dummy_bytes(u3::new(0x1))
|
||||
.with_instruction_code(InstructionCode::FastReadQuadOutput)
|
||||
.build();
|
||||
const QUAD_OUT_FAST_RD_TWO_RAW: u32 = QUAD_OUT_FAST_RD_TWO.raw_value();
|
||||
static_assertions::const_assert_eq!(QUAD_OUT_FAST_RD_TWO_RAW, 0xE000_016B);
|
||||
|
||||
pub(crate) mod winbond_spansion {
|
||||
use super::*;
|
||||
pub const DUAL_IO_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(false)
|
||||
.with_separate_memory_bus(false)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(true)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0xff)
|
||||
.with_num_dummy_bytes(u3::new(0x0))
|
||||
.with_instruction_code(InstructionCode::FastReadDualIo)
|
||||
.build();
|
||||
const DUAL_IO_FAST_RD_ONE_RAW: u32 = DUAL_IO_FAST_RD_ONE.raw_value();
|
||||
static_assertions::const_assert_eq!(DUAL_IO_FAST_RD_ONE_RAW, 0x82FF_00BB);
|
||||
|
||||
pub const DUAL_IO_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(true)
|
||||
.with_separate_memory_bus(true)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(true)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0xff)
|
||||
.with_num_dummy_bytes(u3::new(0x0))
|
||||
.with_instruction_code(InstructionCode::FastReadDualIo)
|
||||
.build();
|
||||
const DUAL_IO_FAST_RD_TWO_RAW: u32 = DUAL_IO_FAST_RD_TWO.raw_value();
|
||||
static_assertions::const_assert_eq!(DUAL_IO_FAST_RD_TWO_RAW, 0xE2FF_00BB);
|
||||
|
||||
pub const QUAD_IO_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(false)
|
||||
.with_separate_memory_bus(false)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(true)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0xff)
|
||||
.with_num_dummy_bytes(u3::new(0x2))
|
||||
.with_instruction_code(InstructionCode::FastReadQuadIo)
|
||||
.build();
|
||||
const QUAD_IO_FAST_RD_ONE_RAW: u32 = QUAD_IO_FAST_RD_ONE.raw_value();
|
||||
static_assertions::const_assert_eq!(QUAD_IO_FAST_RD_ONE_RAW, 0x82FF_02EB);
|
||||
|
||||
pub const QUAD_IO_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(true)
|
||||
.with_separate_memory_bus(true)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(true)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0xff)
|
||||
.with_num_dummy_bytes(u3::new(0x2))
|
||||
.with_instruction_code(InstructionCode::FastReadQuadIo)
|
||||
.build();
|
||||
const QUAD_IO_FAST_RD_TWO_RAW: u32 = QUAD_IO_FAST_RD_TWO.raw_value();
|
||||
static_assertions::const_assert_eq!(QUAD_IO_FAST_RD_TWO_RAW, 0xE2FF_02EB);
|
||||
}
|
||||
|
||||
pub(crate) mod micron {
|
||||
use super::*;
|
||||
pub const DUAL_IO_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(false)
|
||||
.with_separate_memory_bus(false)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(true)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0xff)
|
||||
.with_num_dummy_bytes(u3::new(0x1))
|
||||
.with_instruction_code(InstructionCode::FastReadDualIo)
|
||||
.build();
|
||||
const DUAL_IO_FAST_RD_ONE_RAW: u32 = DUAL_IO_FAST_RD_ONE.raw_value();
|
||||
static_assertions::const_assert_eq!(DUAL_IO_FAST_RD_ONE_RAW, 0x82FF_01BB);
|
||||
|
||||
pub const DUAL_IO_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(true)
|
||||
.with_separate_memory_bus(true)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(true)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0xff)
|
||||
.with_num_dummy_bytes(u3::new(0x1))
|
||||
.with_instruction_code(InstructionCode::FastReadDualIo)
|
||||
.build();
|
||||
const DUAL_IO_FAST_RD_TWO_RAW: u32 = DUAL_IO_FAST_RD_TWO.raw_value();
|
||||
static_assertions::const_assert_eq!(DUAL_IO_FAST_RD_TWO_RAW, 0xE2FF_01BB);
|
||||
|
||||
pub const QUAD_IO_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(false)
|
||||
.with_separate_memory_bus(false)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(true)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0xff)
|
||||
.with_num_dummy_bytes(u3::new(0x4))
|
||||
.with_instruction_code(InstructionCode::FastReadQuadIo)
|
||||
.build();
|
||||
const QUAD_IO_FAST_RD_ONE_RAW: u32 = QUAD_IO_FAST_RD_ONE.raw_value();
|
||||
static_assertions::const_assert_eq!(QUAD_IO_FAST_RD_ONE_RAW, 0x82FF_04EB);
|
||||
|
||||
pub const QUAD_IO_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(true)
|
||||
.with_separate_memory_bus(true)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(true)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0xff)
|
||||
.with_num_dummy_bytes(u3::new(0x4))
|
||||
.with_instruction_code(InstructionCode::FastReadQuadIo)
|
||||
.build();
|
||||
const QUAD_IO_FAST_RD_TWO_RAW: u32 = QUAD_IO_FAST_RD_TWO.raw_value();
|
||||
static_assertions::const_assert_eq!(QUAD_IO_FAST_RD_TWO_RAW, 0xE2FF_04EB);
|
||||
}
|
||||
677
zynq7000-hal/src/qspi/mod.rs
Normal file
677
zynq7000-hal/src/qspi/mod.rs
Normal file
@@ -0,0 +1,677 @@
|
||||
use core::ops::{Deref, DerefMut};
|
||||
|
||||
use arbitrary_int::{Number, u2, u3, u6};
|
||||
pub use zynq7000::qspi::LinearQspiConfig;
|
||||
use zynq7000::{
|
||||
qspi::{
|
||||
BaudRateDivisor, Config, InstructionCode, InterruptStatus, LoopbackMasterClockDelay,
|
||||
SpiEnable,
|
||||
},
|
||||
slcr::{
|
||||
clocks::{SingleCommonPeriphIoClockControl, SrcSelIo},
|
||||
mio::Speed,
|
||||
reset::QspiResetControl,
|
||||
},
|
||||
};
|
||||
|
||||
pub use zynq7000::slcr::mio::IoType;
|
||||
|
||||
use crate::{
|
||||
PeriphSelect,
|
||||
clocks::Clocks,
|
||||
enable_amba_peripheral_clock,
|
||||
gpio::{
|
||||
IoPeriphPin,
|
||||
mio::{
|
||||
Mio0, Mio1, Mio2, Mio3, Mio4, Mio5, Mio6, Mio8, Mio9, Mio10, Mio11, Mio12, Mio13,
|
||||
MioPinMarker, MuxConfig, Pin,
|
||||
},
|
||||
},
|
||||
slcr::Slcr,
|
||||
spi_mode_const_to_cpol_cpha,
|
||||
time::Hertz,
|
||||
};
|
||||
|
||||
pub(crate) mod lqspi_configs;
|
||||
|
||||
pub const QSPI_MUX_CONFIG: MuxConfig = MuxConfig::new_with_l0();
|
||||
pub const FIFO_DEPTH: usize = 63;
|
||||
/// In linear-addressed mode, the QSPI is memory-mapped, with the address starting here.
|
||||
pub const QSPI_START_ADDRESS: usize = 0xFC00_0000;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ClockCalculationError {
|
||||
#[error("violated clock ratio restriction")]
|
||||
RefClockSmallerThanCpu1xClock,
|
||||
#[error("reference divisor out of range")]
|
||||
RefDivOutOfRange,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BaudRateConfig {
|
||||
WithLoopback,
|
||||
WithoutLoopback(BaudRateDivisor),
|
||||
}
|
||||
|
||||
impl BaudRateConfig {
|
||||
#[inline]
|
||||
pub const fn baud_rate_divisor(&self) -> BaudRateDivisor {
|
||||
match self {
|
||||
BaudRateConfig::WithLoopback => BaudRateDivisor::_2,
|
||||
BaudRateConfig::WithoutLoopback(divisor) => *divisor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ClockConfig {
|
||||
pub src_sel: SrcSelIo,
|
||||
pub ref_clk_div: u6,
|
||||
pub baud_rate_config: BaudRateConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum QspiVendor {
|
||||
WinbondAndSpansion,
|
||||
Micron,
|
||||
}
|
||||
|
||||
pub type OperatingMode = InstructionCode;
|
||||
|
||||
pub trait Qspi0ChipSelectPin: MioPinMarker {}
|
||||
pub trait Qspi0Io0Pin: MioPinMarker {}
|
||||
pub trait Qspi0Io1Pin: MioPinMarker {}
|
||||
pub trait Qspi0Io2Pin: MioPinMarker {}
|
||||
pub trait Qspi0Io3Pin: MioPinMarker {}
|
||||
pub trait Qspi0ClockPin: MioPinMarker {}
|
||||
|
||||
impl Qspi0ChipSelectPin for Pin<Mio1> {}
|
||||
impl Qspi0Io0Pin for Pin<Mio2> {}
|
||||
impl Qspi0Io1Pin for Pin<Mio3> {}
|
||||
impl Qspi0Io2Pin for Pin<Mio4> {}
|
||||
impl Qspi0Io3Pin for Pin<Mio5> {}
|
||||
impl Qspi0ClockPin for Pin<Mio6> {}
|
||||
|
||||
pub trait Qspi1ChipSelectPin: MioPinMarker {}
|
||||
pub trait Qspi1Io0Pin: MioPinMarker {}
|
||||
pub trait Qspi1Io1Pin: MioPinMarker {}
|
||||
pub trait Qspi1Io2Pin: MioPinMarker {}
|
||||
pub trait Qspi1Io3Pin: MioPinMarker {}
|
||||
pub trait Qspi1ClockPin: MioPinMarker {}
|
||||
|
||||
impl Qspi1ChipSelectPin for Pin<Mio0> {}
|
||||
impl Qspi1Io0Pin for Pin<Mio10> {}
|
||||
impl Qspi1Io1Pin for Pin<Mio11> {}
|
||||
impl Qspi1Io2Pin for Pin<Mio12> {}
|
||||
impl Qspi1Io3Pin for Pin<Mio13> {}
|
||||
impl Qspi1ClockPin for Pin<Mio9> {}
|
||||
|
||||
pub trait FeedbackClockPin: MioPinMarker {}
|
||||
|
||||
impl FeedbackClockPin for Pin<Mio8> {}
|
||||
|
||||
pub struct QspiDeviceCombination {
|
||||
pub vendor: QspiVendor,
|
||||
pub operating_mode: OperatingMode,
|
||||
pub two_devices: bool,
|
||||
}
|
||||
|
||||
impl From<QspiDeviceCombination> for LinearQspiConfig {
|
||||
fn from(value: QspiDeviceCombination) -> Self {
|
||||
linear_mode_config_for_common_devices(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn linear_mode_config_for_common_devices(
|
||||
dev_combination: QspiDeviceCombination,
|
||||
) -> LinearQspiConfig {
|
||||
match dev_combination.operating_mode {
|
||||
InstructionCode::Read => {
|
||||
if dev_combination.two_devices {
|
||||
lqspi_configs::RD_TWO
|
||||
} else {
|
||||
lqspi_configs::RD_ONE
|
||||
}
|
||||
}
|
||||
InstructionCode::FastRead => {
|
||||
if dev_combination.two_devices {
|
||||
lqspi_configs::FAST_RD_TWO
|
||||
} else {
|
||||
lqspi_configs::FAST_RD_ONE
|
||||
}
|
||||
}
|
||||
InstructionCode::FastReadDualOutput => {
|
||||
if dev_combination.two_devices {
|
||||
lqspi_configs::DUAL_OUT_FAST_RD_TWO
|
||||
} else {
|
||||
lqspi_configs::DUAL_OUT_FAST_RD_ONE
|
||||
}
|
||||
}
|
||||
InstructionCode::FastReadQuadOutput => {
|
||||
if dev_combination.two_devices {
|
||||
lqspi_configs::QUAD_OUT_FAST_RD_TWO
|
||||
} else {
|
||||
lqspi_configs::QUAD_OUT_FAST_RD_ONE
|
||||
}
|
||||
}
|
||||
InstructionCode::FastReadDualIo => {
|
||||
match (dev_combination.vendor, dev_combination.two_devices) {
|
||||
(QspiVendor::WinbondAndSpansion, false) => {
|
||||
lqspi_configs::winbond_spansion::DUAL_IO_FAST_RD_ONE
|
||||
}
|
||||
(QspiVendor::WinbondAndSpansion, true) => {
|
||||
lqspi_configs::winbond_spansion::DUAL_IO_FAST_RD_TWO
|
||||
}
|
||||
(QspiVendor::Micron, false) => lqspi_configs::micron::DUAL_IO_FAST_RD_ONE,
|
||||
(QspiVendor::Micron, true) => lqspi_configs::micron::DUAL_IO_FAST_RD_TWO,
|
||||
}
|
||||
}
|
||||
InstructionCode::FastReadQuadIo => {
|
||||
match (dev_combination.vendor, dev_combination.two_devices) {
|
||||
(QspiVendor::WinbondAndSpansion, false) => {
|
||||
lqspi_configs::winbond_spansion::QUAD_IO_FAST_RD_ONE
|
||||
}
|
||||
(QspiVendor::WinbondAndSpansion, true) => {
|
||||
lqspi_configs::winbond_spansion::QUAD_IO_FAST_RD_TWO
|
||||
}
|
||||
(QspiVendor::Micron, false) => lqspi_configs::micron::QUAD_IO_FAST_RD_ONE,
|
||||
(QspiVendor::Micron, true) => lqspi_configs::micron::QUAD_IO_FAST_RD_TWO,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClockConfig {
|
||||
pub fn new(src_sel: SrcSelIo, ref_clk_div: u6, baud_rate_config: BaudRateConfig) -> Self {
|
||||
Self {
|
||||
src_sel,
|
||||
ref_clk_div,
|
||||
baud_rate_config,
|
||||
}
|
||||
}
|
||||
|
||||
/// This constructor calculates the necessary clock divisor for a target QSPI reference clock,
|
||||
/// assuming that a loopback clock is used and thus constraining the baud rate divisor to 2.
|
||||
///
|
||||
/// It also checks that the clock ratio restriction is not violated: The QSPI reference clock must
|
||||
/// be greater than the CPU 1x clock.
|
||||
pub fn calculate_with_loopback(
|
||||
src_sel: SrcSelIo,
|
||||
clocks: &Clocks,
|
||||
target_qspi_interface_clock: Hertz,
|
||||
) -> Result<Self, ClockCalculationError> {
|
||||
// For loopback mode, the baud rate divisor MUST be 2.
|
||||
let target_ref_clock = target_qspi_interface_clock * 2;
|
||||
let ref_clk = match src_sel {
|
||||
SrcSelIo::IoPll | SrcSelIo::IoPllAlt => clocks.io_clocks().ref_clk(),
|
||||
SrcSelIo::ArmPll => clocks.arm_clocks().ref_clk(),
|
||||
SrcSelIo::DdrPll => clocks.ddr_clocks().ref_clk(),
|
||||
};
|
||||
let ref_clk_div = ref_clk.raw().div_ceil(target_ref_clock.raw());
|
||||
if ref_clk_div > u6::MAX.as_u32() {
|
||||
return Err(ClockCalculationError::RefDivOutOfRange);
|
||||
}
|
||||
Ok(Self {
|
||||
src_sel,
|
||||
ref_clk_div: u6::new(ref_clk_div as u8),
|
||||
baud_rate_config: BaudRateConfig::WithLoopback,
|
||||
})
|
||||
}
|
||||
|
||||
/// This constructor calculates the necessary clock configuration for both a target QSPI
|
||||
/// reference clock as well as a target QSPI interface clock.
|
||||
///
|
||||
/// It also checks that the clock ratio restriction is not violated: The QSPI reference clock must
|
||||
/// be greater than the CPU 1x clock.
|
||||
pub fn calculate(
|
||||
src_sel: SrcSelIo,
|
||||
clocks: &Clocks,
|
||||
target_qspi_ref_clock: Hertz,
|
||||
target_qspi_interface_clock: Hertz,
|
||||
) -> Result<Self, ClockCalculationError> {
|
||||
let (ref_clk_div, ref_clk) = match src_sel {
|
||||
SrcSelIo::IoPll | SrcSelIo::IoPllAlt => (
|
||||
clocks
|
||||
.io_clocks()
|
||||
.ref_clk()
|
||||
.raw()
|
||||
.div_ceil(target_qspi_ref_clock.raw()),
|
||||
clocks.io_clocks().ref_clk(),
|
||||
),
|
||||
SrcSelIo::ArmPll => (
|
||||
clocks
|
||||
.arm_clocks()
|
||||
.ref_clk()
|
||||
.raw()
|
||||
.div_ceil(target_qspi_ref_clock.raw()),
|
||||
clocks.arm_clocks().ref_clk(),
|
||||
),
|
||||
SrcSelIo::DdrPll => (
|
||||
clocks
|
||||
.ddr_clocks()
|
||||
.ref_clk()
|
||||
.raw()
|
||||
.div_ceil(target_qspi_ref_clock.raw()),
|
||||
clocks.ddr_clocks().ref_clk(),
|
||||
),
|
||||
};
|
||||
if ref_clk_div > u6::MAX.as_u32() {
|
||||
return Err(ClockCalculationError::RefDivOutOfRange);
|
||||
}
|
||||
let qspi_ref_clk = ref_clk / ref_clk_div;
|
||||
if qspi_ref_clk < clocks.arm_clocks().cpu_1x_clk() {
|
||||
return Err(ClockCalculationError::RefClockSmallerThanCpu1xClock);
|
||||
}
|
||||
let qspi_baud_rate_div = qspi_ref_clk / target_qspi_interface_clock;
|
||||
let baud_rate_div = match qspi_baud_rate_div {
|
||||
0..=2 => BaudRateDivisor::_2,
|
||||
3..=4 => BaudRateDivisor::_4,
|
||||
5..=8 => BaudRateDivisor::_8,
|
||||
9..=16 => BaudRateDivisor::_16,
|
||||
17..=32 => BaudRateDivisor::_32,
|
||||
65..=128 => BaudRateDivisor::_64,
|
||||
129..=256 => BaudRateDivisor::_128,
|
||||
_ => BaudRateDivisor::_256,
|
||||
};
|
||||
Ok(Self {
|
||||
src_sel,
|
||||
ref_clk_div: u6::new(ref_clk_div as u8),
|
||||
baud_rate_config: BaudRateConfig::WithoutLoopback(baud_rate_div),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QspiLowLevel(zynq7000::qspi::MmioQspi<'static>);
|
||||
|
||||
impl QspiLowLevel {
|
||||
#[inline]
|
||||
pub fn new(regs: zynq7000::qspi::MmioQspi<'static>) -> Self {
|
||||
Self(regs)
|
||||
}
|
||||
|
||||
pub fn regs(&mut self) -> &mut zynq7000::qspi::MmioQspi<'static> {
|
||||
&mut self.0
|
||||
}
|
||||
|
||||
pub fn initialize(&mut self, clock_config: ClockConfig, mode: embedded_hal::spi::Mode) {
|
||||
enable_amba_peripheral_clock(PeriphSelect::Lqspi);
|
||||
reset();
|
||||
let (cpol, cpha) = spi_mode_const_to_cpol_cpha(mode);
|
||||
unsafe {
|
||||
Slcr::with(|slcr| {
|
||||
slcr.clk_ctrl().write_lqspi_clk_ctrl(
|
||||
SingleCommonPeriphIoClockControl::builder()
|
||||
.with_divisor(clock_config.ref_clk_div)
|
||||
.with_srcsel(clock_config.src_sel)
|
||||
.with_clk_act(true)
|
||||
.build(),
|
||||
);
|
||||
})
|
||||
}
|
||||
let baudrate_config = clock_config.baud_rate_config;
|
||||
self.0.write_config(
|
||||
Config::builder()
|
||||
.with_interface_mode(zynq7000::qspi::InterfaceMode::FlashMemoryInterface)
|
||||
.with_edianness(zynq7000::qspi::Endianness::Little)
|
||||
.with_holdb_dr(true)
|
||||
.with_manual_start_command(false)
|
||||
.with_manual_start_enable(false)
|
||||
.with_manual_cs(false)
|
||||
.with_peripheral_chip_select(false)
|
||||
.with_fifo_width(u2::new(0b11))
|
||||
.with_baud_rate_div(baudrate_config.baud_rate_divisor())
|
||||
.with_clock_phase(cpha)
|
||||
.with_clock_polarity(cpol)
|
||||
.with_mode_select(true)
|
||||
.build(),
|
||||
);
|
||||
if baudrate_config == BaudRateConfig::WithLoopback {
|
||||
self.0.write_loopback_master_clock_delay(
|
||||
LoopbackMasterClockDelay::builder()
|
||||
.with_use_loopback(true)
|
||||
.with_delay_1(u2::new(0x0))
|
||||
.with_delay_0(u3::new(0x0))
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_linear_addressing(&mut self, config: LinearQspiConfig) {
|
||||
self.0
|
||||
.write_spi_enable(SpiEnable::builder().with_enable(false).build());
|
||||
self.0.modify_config(|mut val| {
|
||||
// Those two bits should be set to 0 according to the TRM.
|
||||
val.set_manual_start_enable(false);
|
||||
val.set_manual_cs(false);
|
||||
val.set_peripheral_chip_select(false);
|
||||
val
|
||||
});
|
||||
self.0.write_linear_qspi_config(config);
|
||||
}
|
||||
|
||||
pub fn enable_io_mode(&mut self, dual_flash: bool) {
|
||||
self.0.modify_config(|mut val| {
|
||||
val.set_manual_start_enable(true);
|
||||
val.set_manual_cs(true);
|
||||
val
|
||||
});
|
||||
self.0.write_rx_fifo_threshold(0x1);
|
||||
self.0.write_tx_fifo_threshold(0x1);
|
||||
self.0.write_linear_qspi_config(
|
||||
LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(false)
|
||||
.with_both_memories(dual_flash)
|
||||
.with_separate_memory_bus(dual_flash)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(false)
|
||||
.with_mode_on(true)
|
||||
// Reset values from the TRM are set here, but they do not matter anyway.
|
||||
.with_mode_bits(0xA0)
|
||||
.with_num_dummy_bytes(u3::new(0x2))
|
||||
.with_instruction_code(InstructionCode::FastReadQuadIo)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn disable(&mut self) {
|
||||
self.0
|
||||
.write_spi_enable(SpiEnable::builder().with_enable(false).build());
|
||||
self.0.modify_config(|mut val| {
|
||||
val.set_peripheral_chip_select(true);
|
||||
val
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Qspi {
|
||||
ll: QspiLowLevel,
|
||||
}
|
||||
|
||||
impl Qspi {
|
||||
pub fn new_single_qspi<
|
||||
ChipSelect: Qspi0ChipSelectPin,
|
||||
Io0: Qspi0Io0Pin,
|
||||
Io1: Qspi0Io1Pin,
|
||||
Io2: Qspi0Io2Pin,
|
||||
Io3: Qspi0Io3Pin,
|
||||
Clock: Qspi0ClockPin,
|
||||
>(
|
||||
regs: zynq7000::qspi::MmioQspi<'static>,
|
||||
clock_config: ClockConfig,
|
||||
mode: embedded_hal::spi::Mode,
|
||||
voltage: IoType,
|
||||
cs: ChipSelect,
|
||||
ios: (Io0, Io1, Io2, Io3),
|
||||
clock: Clock,
|
||||
) -> Self {
|
||||
IoPeriphPin::new_with_full_config(
|
||||
cs,
|
||||
zynq7000::slcr::mio::Config::builder()
|
||||
.with_disable_hstl_rcvr(false)
|
||||
.with_pullup(true)
|
||||
.with_io_type(voltage)
|
||||
.with_speed(Speed::SlowCmosEdge)
|
||||
.with_l3_sel(QSPI_MUX_CONFIG.l3_sel())
|
||||
.with_l2_sel(QSPI_MUX_CONFIG.l2_sel())
|
||||
.with_l1_sel(QSPI_MUX_CONFIG.l1_sel())
|
||||
.with_l0_sel(QSPI_MUX_CONFIG.l0_sel())
|
||||
.with_tri_enable(false)
|
||||
.build(),
|
||||
);
|
||||
let io_and_clock_config = zynq7000::slcr::mio::Config::builder()
|
||||
.with_disable_hstl_rcvr(false)
|
||||
.with_pullup(false)
|
||||
.with_io_type(voltage)
|
||||
.with_speed(Speed::SlowCmosEdge)
|
||||
.with_l3_sel(QSPI_MUX_CONFIG.l3_sel())
|
||||
.with_l2_sel(QSPI_MUX_CONFIG.l2_sel())
|
||||
.with_l1_sel(QSPI_MUX_CONFIG.l1_sel())
|
||||
.with_l0_sel(QSPI_MUX_CONFIG.l0_sel())
|
||||
.with_tri_enable(false)
|
||||
.build();
|
||||
IoPeriphPin::new_with_full_config(ios.0, io_and_clock_config);
|
||||
IoPeriphPin::new_with_full_config(ios.1, io_and_clock_config);
|
||||
IoPeriphPin::new_with_full_config(ios.2, io_and_clock_config);
|
||||
IoPeriphPin::new_with_full_config(ios.3, io_and_clock_config);
|
||||
IoPeriphPin::new_with_full_config(clock, io_and_clock_config);
|
||||
let mut ll = QspiLowLevel::new(regs);
|
||||
ll.initialize(clock_config, mode);
|
||||
Self { ll }
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new_single_qspi_with_feedback<
|
||||
ChipSelect: Qspi0ChipSelectPin,
|
||||
Io0: Qspi0Io0Pin,
|
||||
Io1: Qspi0Io1Pin,
|
||||
Io2: Qspi0Io2Pin,
|
||||
Io3: Qspi0Io3Pin,
|
||||
Clock: Qspi0ClockPin,
|
||||
Feedback: FeedbackClockPin,
|
||||
>(
|
||||
regs: zynq7000::qspi::MmioQspi<'static>,
|
||||
clock_config: ClockConfig,
|
||||
mode: embedded_hal::spi::Mode,
|
||||
voltage: IoType,
|
||||
cs: ChipSelect,
|
||||
io: (Io0, Io1, Io2, Io3),
|
||||
clock: Clock,
|
||||
feedback: Feedback,
|
||||
) -> Self {
|
||||
IoPeriphPin::new_with_full_config(
|
||||
feedback,
|
||||
zynq7000::slcr::mio::Config::builder()
|
||||
.with_disable_hstl_rcvr(false)
|
||||
.with_pullup(false)
|
||||
.with_io_type(voltage)
|
||||
.with_speed(Speed::SlowCmosEdge)
|
||||
.with_l3_sel(QSPI_MUX_CONFIG.l3_sel())
|
||||
.with_l2_sel(QSPI_MUX_CONFIG.l2_sel())
|
||||
.with_l1_sel(QSPI_MUX_CONFIG.l1_sel())
|
||||
.with_l0_sel(QSPI_MUX_CONFIG.l0_sel())
|
||||
.with_tri_enable(false)
|
||||
.build(),
|
||||
);
|
||||
Self::new_single_qspi(regs, clock_config, mode, voltage, cs, io, clock)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn regs(&mut self) -> &mut zynq7000::qspi::MmioQspi<'static> {
|
||||
&mut self.ll.0
|
||||
}
|
||||
|
||||
pub fn into_linear_addressed(mut self, config: LinearQspiConfig) -> QspiLinearAddressing {
|
||||
self.ll.enable_linear_addressing(config);
|
||||
QspiLinearAddressing { ll: self.ll }
|
||||
}
|
||||
|
||||
pub fn into_io_mode(mut self, dual_flash: bool) -> QspiIoMode {
|
||||
self.ll.enable_io_mode(dual_flash);
|
||||
QspiIoMode { ll: self.ll }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QspiIoMode {
|
||||
ll: QspiLowLevel,
|
||||
}
|
||||
|
||||
impl QspiIoMode {
|
||||
#[inline]
|
||||
pub fn regs(&mut self) -> &mut zynq7000::qspi::MmioQspi<'static> {
|
||||
&mut self.ll.0
|
||||
}
|
||||
|
||||
pub fn transfer_guard(&mut self) -> QspiIoTransferGuard<'_> {
|
||||
QspiIoTransferGuard::new(self)
|
||||
}
|
||||
|
||||
pub fn into_qspi(mut self, ll: QspiLowLevel) -> Qspi {
|
||||
self.ll.disable();
|
||||
Qspi { ll }
|
||||
}
|
||||
|
||||
/// Transmits 1-byte command and 3-byte data OR 4-byte data.
|
||||
#[inline]
|
||||
pub fn write_word_txd_00(&mut self, word: u32) {
|
||||
self.regs().write_tx_data_00(word);
|
||||
}
|
||||
|
||||
/// Transmits 1-byte command.
|
||||
#[inline]
|
||||
pub fn write_word_txd_01(&mut self, word: u32) {
|
||||
self.regs().write_tx_data_01(word);
|
||||
}
|
||||
|
||||
/// Transmits 1-byte command and 1-byte data.
|
||||
#[inline]
|
||||
pub fn write_word_txd_10(&mut self, word: u32) {
|
||||
self.regs().write_tx_data_10(word);
|
||||
}
|
||||
|
||||
/// Transmits 1-byte command and 2-byte data.
|
||||
#[inline]
|
||||
pub fn write_word_txd_11(&mut self, word: u32) {
|
||||
self.regs().write_tx_data_11(word);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_rx_data(&mut self) -> u32 {
|
||||
self.regs().read_rx_data()
|
||||
}
|
||||
|
||||
pub fn transfer_init(&mut self) {
|
||||
self.regs().modify_config(|mut val| {
|
||||
val.set_peripheral_chip_select(false);
|
||||
val
|
||||
});
|
||||
self.regs()
|
||||
.write_spi_enable(SpiEnable::builder().with_enable(true).build());
|
||||
}
|
||||
|
||||
pub fn transfer_start(&mut self) {
|
||||
self.regs().modify_config(|mut val| {
|
||||
val.set_manual_start_command(true);
|
||||
val
|
||||
});
|
||||
}
|
||||
|
||||
pub fn transfer_done(&mut self) {
|
||||
self.regs().modify_config(|mut val| {
|
||||
val.set_peripheral_chip_select(true);
|
||||
val
|
||||
});
|
||||
self.regs()
|
||||
.write_spi_enable(SpiEnable::builder().with_enable(false).build());
|
||||
}
|
||||
|
||||
pub fn read_status(&mut self) -> InterruptStatus {
|
||||
self.regs().read_interrupt_status()
|
||||
}
|
||||
|
||||
pub fn clear_rx_fifo(&mut self) {
|
||||
while self.read_status().rx_above_threshold() {
|
||||
self.read_rx_data();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_linear_addressed(mut self, config: LinearQspiConfig) -> QspiLinearAddressing {
|
||||
self.ll.enable_linear_addressing(config);
|
||||
QspiLinearAddressing { ll: self.ll }
|
||||
}
|
||||
}
|
||||
|
||||
/// This guard structure takes care of commonly required operations before starting a transfer
|
||||
/// and after finishing it.
|
||||
pub struct QspiIoTransferGuard<'a>(&'a mut QspiIoMode);
|
||||
|
||||
impl<'a> QspiIoTransferGuard<'a> {
|
||||
pub fn new(qspi: &'a mut QspiIoMode) -> Self {
|
||||
qspi.clear_rx_fifo();
|
||||
qspi.transfer_init();
|
||||
Self(qspi)
|
||||
}
|
||||
}
|
||||
|
||||
impl QspiIoTransferGuard<'_> {
|
||||
pub fn start(&mut self) {
|
||||
self.0.transfer_start();
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for QspiIoTransferGuard<'_> {
|
||||
type Target = QspiIoMode;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for QspiIoTransferGuard<'_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for QspiIoTransferGuard<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.0.transfer_done();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QspiLinearAddressing {
|
||||
ll: QspiLowLevel,
|
||||
}
|
||||
|
||||
impl QspiLinearAddressing {
|
||||
pub fn into_io_mode(mut self, dual_flash: bool) -> QspiIoMode {
|
||||
self.ll.enable_io_mode(dual_flash);
|
||||
QspiIoMode { ll: self.ll }
|
||||
}
|
||||
|
||||
pub fn read_guard(&mut self) -> QspiLinearReadGuard<'_> {
|
||||
QspiLinearReadGuard::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QspiLinearReadGuard<'a>(&'a mut QspiLinearAddressing);
|
||||
|
||||
impl QspiLinearReadGuard<'_> {
|
||||
pub fn new(qspi: &mut QspiLinearAddressing) -> QspiLinearReadGuard<'_> {
|
||||
qspi.ll
|
||||
.0
|
||||
.write_spi_enable(SpiEnable::builder().with_enable(true).build());
|
||||
QspiLinearReadGuard(qspi)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for QspiLinearReadGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.0
|
||||
.ll
|
||||
.0
|
||||
.write_spi_enable(SpiEnable::builder().with_enable(false).build());
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the QSPI peripheral using the SLCR reset register for QSPI.
|
||||
///
|
||||
/// Please note that this function will interfere with an already configured
|
||||
/// QSPI instance.
|
||||
#[inline]
|
||||
pub fn reset() {
|
||||
unsafe {
|
||||
Slcr::with(|regs| {
|
||||
regs.reset_ctrl().write_lqspi(
|
||||
QspiResetControl::builder()
|
||||
.with_qspi_ref_reset(true)
|
||||
.with_cpu_1x_reset(true)
|
||||
.build(),
|
||||
);
|
||||
// Keep it in reset for some cycles.
|
||||
for _ in 0..3 {
|
||||
cortex_ar::asm::nop();
|
||||
}
|
||||
regs.reset_ctrl().write_lqspi(QspiResetControl::DEFAULT);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
use core::convert::Infallible;
|
||||
|
||||
use crate::clocks::Clocks;
|
||||
use crate::enable_amba_periph_clk;
|
||||
use crate::gpio::IoPeriphPin;
|
||||
use crate::gpio::mio::{
|
||||
Mio10, Mio11, Mio12, Mio13, Mio14, Mio15, Mio28, Mio29, Mio30, Mio31, Mio32, Mio33, Mio34,
|
||||
@@ -13,12 +12,13 @@ use crate::gpio::mio::{
|
||||
Mio16, Mio17, Mio18, Mio19, Mio20, Mio21, Mio22, Mio23, Mio24, Mio25, Mio26, Mio27, Mio40,
|
||||
Mio41, Mio42, Mio43, Mio44, Mio45, Mio46, Mio47, Mio48, Mio49, Mio50, Mio51,
|
||||
};
|
||||
use crate::{enable_amba_peripheral_clock, spi_mode_const_to_cpol_cpha};
|
||||
|
||||
use crate::{clocks::IoClocks, slcr::Slcr, time::Hertz};
|
||||
use arbitrary_int::{Number, u3, u4, u6};
|
||||
use embedded_hal::delay::DelayNs;
|
||||
pub use embedded_hal::spi::Mode;
|
||||
use embedded_hal::spi::{MODE_0, MODE_1, MODE_2, MODE_3, SpiBus as _};
|
||||
use embedded_hal::spi::SpiBus as _;
|
||||
use zynq7000::slcr::reset::DualRefAndClockReset;
|
||||
use zynq7000::spi::{
|
||||
BaudDivSel, DelayControl, FifoWrite, InterruptControl, InterruptMask, InterruptStatus, MmioSpi,
|
||||
@@ -457,15 +457,10 @@ impl SpiLowLevel {
|
||||
/// Re-configures the mode register.
|
||||
#[inline]
|
||||
pub fn configure_mode(&mut self, mode: Mode) {
|
||||
let (cpol, cpha) = match mode {
|
||||
MODE_0 => (false, false),
|
||||
MODE_1 => (false, true),
|
||||
MODE_2 => (true, false),
|
||||
MODE_3 => (true, true),
|
||||
};
|
||||
let (cpol, cpha) = spi_mode_const_to_cpol_cpha(mode);
|
||||
self.regs.modify_cr(|mut val| {
|
||||
val.set_cpha(cpha);
|
||||
val.set_cpha(cpol);
|
||||
val.set_cpol(cpol);
|
||||
val
|
||||
});
|
||||
}
|
||||
@@ -485,12 +480,7 @@ impl SpiLowLevel {
|
||||
SlaveSelectConfig::AutoWithManualStart => (false, true),
|
||||
SlaveSelectConfig::AutoWithAutoStart => (false, false),
|
||||
};
|
||||
let (cpol, cpha) = match config.init_mode {
|
||||
MODE_0 => (false, false),
|
||||
MODE_1 => (false, true),
|
||||
MODE_2 => (true, false),
|
||||
MODE_3 => (true, true),
|
||||
};
|
||||
let (cpol, cpha) = spi_mode_const_to_cpol_cpha(config.init_mode);
|
||||
|
||||
self.regs.write_cr(
|
||||
zynq7000::spi::Config::builder()
|
||||
@@ -822,7 +812,7 @@ impl Spi {
|
||||
SpiId::Spi0 => crate::PeriphSelect::Spi0,
|
||||
SpiId::Spi1 => crate::PeriphSelect::Spi1,
|
||||
};
|
||||
enable_amba_periph_clk(periph_sel);
|
||||
enable_amba_peripheral_clock(periph_sel);
|
||||
let sclk = clocks.spi_clk() / config.baud_div.div_value() as u32;
|
||||
let mut spi = Self {
|
||||
inner: SpiLowLevel { regs, id },
|
||||
@@ -1141,8 +1131,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 +1153,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);
|
||||
|
||||
@@ -8,13 +8,13 @@ use libm::round;
|
||||
use zynq7000::{
|
||||
slcr::reset::DualRefAndClockReset,
|
||||
uart::{
|
||||
BaudRateDiv, Baudgen, ChMode, ClockSelect, FifoTrigger, InterruptControl, MmioUart, Mode,
|
||||
UART_0_BASE, UART_1_BASE,
|
||||
BaudRateDivisor, Baudgen, ChMode, ClockSelect, FifoTrigger, InterruptControl, MmioUart,
|
||||
Mode, UART_0_BASE, UART_1_BASE,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
enable_amba_periph_clk,
|
||||
enable_amba_peripheral_clock,
|
||||
gpio::{
|
||||
IoPeriphPin,
|
||||
mio::{
|
||||
@@ -471,7 +471,7 @@ impl Uart {
|
||||
UartId::Uart0 => crate::PeriphSelect::Uart0,
|
||||
UartId::Uart1 => crate::PeriphSelect::Uart1,
|
||||
};
|
||||
enable_amba_periph_clk(periph_sel);
|
||||
enable_amba_peripheral_clock(periph_sel);
|
||||
reset(uart_id);
|
||||
reg_block.modify_cr(|mut v| {
|
||||
v.set_tx_dis(true);
|
||||
@@ -506,7 +506,7 @@ impl Uart {
|
||||
.build(),
|
||||
);
|
||||
reg_block.write_baud_rate_div(
|
||||
BaudRateDiv::builder()
|
||||
BaudRateDivisor::builder()
|
||||
.with_bdiv(cfg.raw_clk_config().bdiv())
|
||||
.build(),
|
||||
);
|
||||
|
||||
@@ -135,6 +135,11 @@ pub mod section_attrs {
|
||||
execute_never: false,
|
||||
memory_attrs: MemoryRegionAttributes::OuterAndInnerWriteBackNoWriteAlloc.as_raw(),
|
||||
};
|
||||
/// For the QSPI XIP, we profit from caching reads to both inner and outer cache.
|
||||
/// Writes are not relevant, because the QSPI controller does not support writes in linear
|
||||
/// addressing mode. The TRM mentions that the AXI bus will immediately acknowledge the command
|
||||
/// but will not perform any actual operations. I think using write through without allocation
|
||||
/// prevents cache pollution for writes, but those should never happen anyway..
|
||||
pub const QSPI_XIP: SectionAttributes = SectionAttributes {
|
||||
non_global: false,
|
||||
p_bit: false,
|
||||
|
||||
880
zynq7000/src/ddrc.rs
Normal file
880
zynq7000/src/ddrc.rs
Normal file
@@ -0,0 +1,880 @@
|
||||
pub const DDRC_BASE_ADDR: usize = 0xF800_6000;
|
||||
|
||||
pub mod regs {
|
||||
use arbitrary_int::{u2, u3, u4, u5, u6, u7, u9, u10, u11, u12, u20};
|
||||
|
||||
#[bitbybit::bitenum(u2)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum DataBusWidth {
|
||||
_32Bit = 0b00,
|
||||
_16Bit = 0b01,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SoftReset {
|
||||
Reset = 0,
|
||||
Active = 1,
|
||||
}
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DdrcControl {
|
||||
#[bit(16, rw)]
|
||||
disable_auto_refresh: bool,
|
||||
#[bit(15, rw)]
|
||||
disable_active_bypass: bool,
|
||||
#[bit(14, rw)]
|
||||
disable_read_bypass: bool,
|
||||
#[bits(7..=13, rw)]
|
||||
read_write_idle_gap: u7,
|
||||
#[bits(4..=6, rw)]
|
||||
burst8_refresh: u3,
|
||||
#[bits(2..=3, rw)]
|
||||
data_bus_width: Option<DataBusWidth>,
|
||||
#[bit(1, rw)]
|
||||
power_down_enable: bool,
|
||||
#[bit(0, rw)]
|
||||
soft_reset: SoftReset,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct TwoRankConfig {
|
||||
#[bits(14..=18, rw)]
|
||||
addrmap_cs_bit0: u5,
|
||||
/// Reserved register, but for some reason, Xilinx tooling writes a 1 here?
|
||||
#[bits(12..=13, rw)]
|
||||
ddrc_active_ranks: u2,
|
||||
/// tREFI - Average time between refreshes, in multiples of 32 clocks.
|
||||
#[bits(0..=11, rw)]
|
||||
rfc_nom_x32: u12,
|
||||
}
|
||||
|
||||
/// Queue control for the low priority and high priority read queues.
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct LprHprQueueControl {
|
||||
#[bits(22..=25, rw)]
|
||||
xact_run_length: u4,
|
||||
#[bits(11..=21, rw)]
|
||||
max_starve_x32: u11,
|
||||
#[bits(0..=10, rw)]
|
||||
min_non_critical_x32: u11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct WriteQueueControl {
|
||||
#[bits(15..=25, rw)]
|
||||
max_starve_x32: u11,
|
||||
#[bits(11..=14, rw)]
|
||||
xact_run_length: u4,
|
||||
#[bits(0..=10, rw)]
|
||||
min_non_critical_x32: u11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramParamReg0 {
|
||||
/// Minimum time to wait after coming out of self refresh before doing anything. This must be
|
||||
/// bigger than all the constraints that exist.
|
||||
#[bits(14..=20, rw)]
|
||||
post_selfref_gap_x32: u7,
|
||||
/// tRFC(min) - Minimum time from refresh to refresh or activate in clock
|
||||
/// cycles.
|
||||
#[bits(6..=13, rw)]
|
||||
t_rfc_min: u8,
|
||||
/// tRC - Min time between activates to the same bank.
|
||||
#[bits(0..=5, rw)]
|
||||
t_rc: u6,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramParamReg1 {
|
||||
#[bits(28..=31, rw)]
|
||||
t_cke: u4,
|
||||
#[bits(22..=26, rw)]
|
||||
t_ras_min: u5,
|
||||
#[bits(16..=21, rw)]
|
||||
t_ras_max: u6,
|
||||
#[bits(10..=15, rw)]
|
||||
t_faw: u6,
|
||||
#[bits(5..=9, rw)]
|
||||
powerdown_to_x32: u5,
|
||||
#[bits(0..=4, rw)]
|
||||
wr2pre: u5,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramParamReg2 {
|
||||
#[bits(28..=31, rw)]
|
||||
t_rcd: u4,
|
||||
#[bits(23..=27, rw)]
|
||||
rd2pre: u5,
|
||||
#[bits(20..=22, rw)]
|
||||
pad_pd: u3,
|
||||
#[bits(15..=19, rw)]
|
||||
t_xp: u5,
|
||||
#[bits(10..=14, rw)]
|
||||
wr2rd: u5,
|
||||
#[bits(5..=9, rw)]
|
||||
rd2wr: u5,
|
||||
#[bits(0..=4, rw)]
|
||||
write_latency: u5,
|
||||
}
|
||||
|
||||
/// Weird naming.
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
pub enum MobileSetting {
|
||||
Ddr2Ddr3 = 0,
|
||||
Lpddr2 = 1,
|
||||
}
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramParamReg3 {
|
||||
#[bit(30, rw)]
|
||||
disable_pad_pd_feature: bool,
|
||||
#[bits(24..=28, rw)]
|
||||
read_latency: u5,
|
||||
#[bit(23, rw)]
|
||||
enable_dfi_dram_clk_disable: bool,
|
||||
/// 0: DDR2 or DDR3. 1: LPDDR2.
|
||||
#[bit(22, rw)]
|
||||
mobile: MobileSetting,
|
||||
/// Must be set to 0.
|
||||
#[bit(21, rw)]
|
||||
sdram: bool,
|
||||
#[bits(16..=20, rw)]
|
||||
refresh_to_x32: u5,
|
||||
#[bits(12..=15, rw)]
|
||||
t_rp: u4,
|
||||
#[bits(8..=11, rw)]
|
||||
refresh_margin: u4,
|
||||
#[bits(5..=7, rw)]
|
||||
t_rrd: u3,
|
||||
#[bits(2..=4, rw)]
|
||||
t_ccd: u3,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
pub enum ModeRegisterType {
|
||||
Write = 0,
|
||||
Read = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramParamReg4 {
|
||||
#[bit(27, rw)]
|
||||
mr_rdata_valid: bool,
|
||||
#[bit(26, rw)]
|
||||
mr_type: ModeRegisterType,
|
||||
#[bit(25, rw)]
|
||||
mr_wr_busy: bool,
|
||||
#[bits(9..=24, rw)]
|
||||
mr_data: u16,
|
||||
#[bits(7..=8, rw)]
|
||||
mr_addr: u2,
|
||||
#[bit(6, rw)]
|
||||
mr_wr: bool,
|
||||
#[bit(1, rw)]
|
||||
prefer_write: bool,
|
||||
#[bit(0, rw)]
|
||||
enable_2t_timing_mode: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramInitParam {
|
||||
#[bits(11..=13, rw)]
|
||||
t_mrd: u3,
|
||||
#[bits(7..=10, rw)]
|
||||
pre_ocd_x32: u4,
|
||||
#[bits(0..=6, rw)]
|
||||
final_wait_x32: u7,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramEmr {
|
||||
#[bits(16..=31, rw)]
|
||||
emr3: u16,
|
||||
#[bits(0..=15, rw)]
|
||||
emr2: u16,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramEmrMr {
|
||||
#[bits(16..=31, rw)]
|
||||
emr: u16,
|
||||
#[bits(0..=15, rw)]
|
||||
mr: u16,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramBurst8ReadWrite {
|
||||
#[bits(0..=3, rw)]
|
||||
burst_rdwr: u4,
|
||||
#[bits(4..=13, rw)]
|
||||
pre_cke_x1024: u10,
|
||||
#[bits(16..=25, rw)]
|
||||
post_cke_x1024: u10,
|
||||
#[bit(26, rw)]
|
||||
burstchop: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DisableDq {
|
||||
#[bit(1, rw)]
|
||||
dis_dq: bool,
|
||||
#[bit(0, rw)]
|
||||
force_low_pri_n: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramAddrMapBank {
|
||||
#[bits(16..=19, rw)]
|
||||
addrmap_bank_b6: u4,
|
||||
#[bits(12..=15, rw)]
|
||||
addrmap_bank_b5: u4,
|
||||
#[bits(8..=11, rw)]
|
||||
addrmap_bank_b2: u4,
|
||||
#[bits(4..=7, rw)]
|
||||
addrmap_bank_b1: u4,
|
||||
#[bits(0..=3, rw)]
|
||||
addrmap_bank_b0: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramAddrMapColumn {
|
||||
#[bits(28..=31, rw)]
|
||||
addrmap_col_b11: u4,
|
||||
#[bits(24..=27, rw)]
|
||||
addrmap_col_b10: u4,
|
||||
#[bits(20..=23, rw)]
|
||||
addrmap_col_b9: u4,
|
||||
#[bits(16..=19, rw)]
|
||||
addrmap_col_b8: u4,
|
||||
#[bits(12..=15, rw)]
|
||||
addrmap_col_b7: u4,
|
||||
#[bits(8..=11, rw)]
|
||||
addrmap_col_b4: u4,
|
||||
#[bits(4..=7, rw)]
|
||||
addrmap_col_b3: u4,
|
||||
#[bits(0..=3, rw)]
|
||||
addrmap_col_b2: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramAddrMapRow {
|
||||
#[bits(24..=27, rw)]
|
||||
addrmap_row_b15: u4,
|
||||
#[bits(20..=23, rw)]
|
||||
addrmap_row_b14: u4,
|
||||
#[bits(16..=19, rw)]
|
||||
addrmap_row_b13: u4,
|
||||
#[bits(12..=15, rw)]
|
||||
addrmap_row_b12: u4,
|
||||
#[bits(8..=11, rw)]
|
||||
addrmap_row_b2_11: u4,
|
||||
#[bits(4..=7, rw)]
|
||||
addrmap_row_b1: u4,
|
||||
#[bits(0..=3, rw)]
|
||||
addrmap_row_b0: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramOdt {
|
||||
#[bits(16..=17, rw)]
|
||||
phy_idle_local_odt: u2,
|
||||
#[bits(14..=15, rw)]
|
||||
phy_write_local_odt: u2,
|
||||
#[bits(12..=13, rw)]
|
||||
phy_read_local_odt: u2,
|
||||
#[bits(3..=5, rw)]
|
||||
rank0_wr_odt: u3,
|
||||
#[bits(0..=2, rw)]
|
||||
rank0_rd_odt: u3,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct PhyCmdTimeoutRdDataCpt {
|
||||
#[bits(28..=31, rw)]
|
||||
wrlvl_num_of_dq0: u4,
|
||||
#[bits(24..=27, rw)]
|
||||
gatelvl_num_of_dq0: u4,
|
||||
#[bit(19, rw)]
|
||||
clk_stall_level: bool,
|
||||
#[bit(18, rw)]
|
||||
dis_phy_ctrl_rstn: bool,
|
||||
#[bit(17, rw)]
|
||||
rdc_fifo_rst_err_cnt_clr: bool,
|
||||
#[bit(16, rw)]
|
||||
use_fixed_re: bool,
|
||||
#[bits(8..=11, rw)]
|
||||
rdc_we_to_re_delay: u4,
|
||||
#[bits(4..=7, rw)]
|
||||
wr_cmd_to_data: u4,
|
||||
#[bits(0..=3, rw)]
|
||||
rd_cmd_to_data: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
pub enum DllCalibSel {
|
||||
Periodic = 0,
|
||||
Manual = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DllCalib {
|
||||
#[bit(16, rw)]
|
||||
sel: DllCalibSel,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct OdtDelayHold {
|
||||
#[bits(12..=15, rw)]
|
||||
wr_odt_hold: u4,
|
||||
#[bits(8..=11, rw)]
|
||||
rd_odt_hold: u4,
|
||||
#[bits(4..=7, rw)]
|
||||
wr_odt_delay: u4,
|
||||
#[bits(0..=3, rw)]
|
||||
rd_odt_delay: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CtrlReg1 {
|
||||
#[bit(12, rw)]
|
||||
selfref_enable: bool,
|
||||
#[bit(10, rw)]
|
||||
dis_collision_page_opt: bool,
|
||||
#[bit(9, rw)]
|
||||
dis_wc: bool,
|
||||
#[bit(8, rw)]
|
||||
refresh_update_level: bool,
|
||||
#[bit(7, rw)]
|
||||
auto_pre_en: bool,
|
||||
#[bits(1..=6, rw)]
|
||||
lpr_num_entries: u6,
|
||||
#[bit(0, rw)]
|
||||
pageclose: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CtrlReg2 {
|
||||
#[bit(17, rw)]
|
||||
go_2_critcal_enable: bool,
|
||||
#[bits(5..=12, rw)]
|
||||
go_2_critical_hysteresis: u8,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CtrlReg3 {
|
||||
#[bits(16..=25, rw)]
|
||||
dfi_t_wlmrd: u10,
|
||||
#[bits(8..=15, rw)]
|
||||
rdlvl_rr: u8,
|
||||
#[bits(0..=7, rw)]
|
||||
wrlvl_ww: u8,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CtrlReg4 {
|
||||
#[bits(8..=15, rw)]
|
||||
dfi_t_ctrlupd_interval_max_x1024: u8,
|
||||
#[bits(0..=7, rw)]
|
||||
dfi_t_ctrlupd_interval_min_x1024: u8,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CtrlReg5 {
|
||||
#[bits(20..=25, rw)]
|
||||
t_ckesr: u6,
|
||||
#[bits(16..=19, rw)]
|
||||
t_cksrx: u4,
|
||||
#[bits(12..=15, rw)]
|
||||
t_ckrse: u4,
|
||||
#[bits(8..=11, rw)]
|
||||
dfi_t_dram_clk_enable: u4,
|
||||
#[bits(4..=7, rw)]
|
||||
dfi_t_dram_clk_disable: u4,
|
||||
#[bits(0..=3, rw)]
|
||||
dfi_t_ctrl_delay: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CtrlReg6 {
|
||||
#[bits(16..=19, rw)]
|
||||
t_cksx: u4,
|
||||
#[bits(12..=15, rw)]
|
||||
t_ckdpdx: u4,
|
||||
#[bits(8..=11, rw)]
|
||||
t_ckdpde: u4,
|
||||
#[bits(4..=7, rw)]
|
||||
t_ckpdx: u4,
|
||||
#[bits(0..=3, rw)]
|
||||
t_ckpde: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CheTZq {
|
||||
#[bits(22..=31, rw)]
|
||||
t_zq_short_nop: u10,
|
||||
#[bits(12..=21, rw)]
|
||||
t_zq_long_nop: u10,
|
||||
#[bits(2..=11, rw)]
|
||||
t_mode: u10,
|
||||
#[bit(1, rw)]
|
||||
ddr3: bool,
|
||||
#[bit(0, rw)]
|
||||
dis_auto_zq: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CheTZqShortInterval {
|
||||
#[bits(20..=27, rw)]
|
||||
dram_rstn_x1024: u8,
|
||||
#[bits(0..=19, rw)]
|
||||
t_zq_short_interval: u20,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DeepPowerdown {
|
||||
#[bits(1..=8, rw)]
|
||||
deep_powerdown_to_x1024: u8,
|
||||
#[bit(0, rw)]
|
||||
enable: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct Reg2c {
|
||||
#[bit(28, rw)]
|
||||
dfi_rd_data_eye_train: bool,
|
||||
#[bit(27, rw)]
|
||||
dfi_rd_dqs_gate_level: bool,
|
||||
#[bit(26, rw)]
|
||||
dfi_wr_level_enable: bool,
|
||||
#[bit(25, rw)]
|
||||
trdlvl_max_error: bool,
|
||||
#[bit(24, rw)]
|
||||
twrlvl_max_error: bool,
|
||||
#[bits(12..=23, rw)]
|
||||
dfi_rdlvl_max_x1024: u12,
|
||||
#[bits(0..=11, rw)]
|
||||
dfi_wrlvl_max_x1024: u12,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct Reg2d {
|
||||
#[bit(9, rw)]
|
||||
skip_ocd: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DfiTiming {
|
||||
#[bits(15..=24, rw)]
|
||||
dfi_t_ctrlup_max: u10,
|
||||
#[bits(5..=14, rw)]
|
||||
dfi_t_ctrlup_min: u10,
|
||||
#[bits(0..=4, rw)]
|
||||
dfi_t_rddata_enable: u5,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CheEccControl {
|
||||
#[bit(1, rw)]
|
||||
clear_correctable_errors: bool,
|
||||
#[bit(0, rw)]
|
||||
clear_uncorrectable_errors: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u3, exhaustive = false)]
|
||||
pub enum EccMode {
|
||||
NoEcc = 0b000,
|
||||
SecDecOverOneBeat = 0b100,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct EccScrub {
|
||||
#[bit(3, rw)]
|
||||
disable_scrub: bool,
|
||||
#[bits(0..=2, rw)]
|
||||
ecc_mode: Option<EccMode>,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct PhyReceiverEnable {
|
||||
#[bits(4..=7, rw)]
|
||||
phy_dif_off: u4,
|
||||
#[bits(0..=3, rw)]
|
||||
phy_dif_on: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct PhyConfig {
|
||||
#[bits(24..=30, rw)]
|
||||
dq_offset: u7,
|
||||
#[bit(3, rw)]
|
||||
wrlvl_inc_mode: bool,
|
||||
#[bit(2, rw)]
|
||||
gatelvl_inc_mode: bool,
|
||||
#[bit(1, rw)]
|
||||
rdlvl_inc_mode: bool,
|
||||
#[bit(0, rw)]
|
||||
data_slice_in_use: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct PhyInitRatio {
|
||||
#[bits(10..=19, rw)]
|
||||
gatelvl_init_ratio: u10,
|
||||
#[bits(0..=9, rw)]
|
||||
wrlvl_init_ratio: u10,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct PhyDqsConfig {
|
||||
#[bits(11..=19, rw)]
|
||||
dqs_slave_delay: u9,
|
||||
#[bit(10, rw)]
|
||||
dqs_slave_force: bool,
|
||||
#[bits(0..=9, rw)]
|
||||
dqs_slave_ratio: u10,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct PhyWriteEnableConfig {
|
||||
#[bits(12..=20, rw)]
|
||||
fifo_we_in_delay: u9,
|
||||
#[bit(11, rw)]
|
||||
fifo_we_in_force: bool,
|
||||
#[bits(0..=10, rw)]
|
||||
fifo_we_slave_ratio: u11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct PhyWriteDataSlaveConfig {
|
||||
#[bits(11..=19, rw)]
|
||||
wr_data_slave_delay: u9,
|
||||
#[bit(10, rw)]
|
||||
wr_data_slave_force: bool,
|
||||
#[bits(0..=9, rw)]
|
||||
wr_data_slave_ratio: u10,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct Reg64 {
|
||||
#[bit(30, rw)]
|
||||
cmd_latency: bool,
|
||||
#[bit(29, rw)]
|
||||
lpddr: bool,
|
||||
#[bits(21..=27, rw)]
|
||||
ctrl_slave_delay: u7,
|
||||
#[bit(20, rw)]
|
||||
ctrl_slave_force: bool,
|
||||
#[bits(10..=19, rw)]
|
||||
ctrl_slave_ratio: u10,
|
||||
#[bit(9, rw)]
|
||||
sel_logic: bool,
|
||||
#[bit(7, rw)]
|
||||
invert_clkout: bool,
|
||||
#[bit(1, rw)]
|
||||
bl2: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct Reg65 {
|
||||
#[bits(18..=19, rw)]
|
||||
ctrl_slave_delay: u2,
|
||||
#[bit(17, rw)]
|
||||
dis_calib_rst: bool,
|
||||
#[bit(16, rw)]
|
||||
use_rd_data_eye_level: bool,
|
||||
#[bit(15, rw)]
|
||||
use_rd_dqs_gate_level: bool,
|
||||
#[bit(14, rw)]
|
||||
use_wr_level: bool,
|
||||
#[bits(10..=13, rw)]
|
||||
dll_lock_diff: u4,
|
||||
#[bits(5..=9, rw)]
|
||||
rd_rl_delay: u5,
|
||||
#[bits(0..=4, rw)]
|
||||
wr_rl_delay: u5,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct AxiPriorityWritePort {
|
||||
#[bit(18, rw)]
|
||||
disable_page_match: bool,
|
||||
#[bit(17, rw)]
|
||||
disable_urgent: bool,
|
||||
#[bit(16, rw)]
|
||||
disable_aging: bool,
|
||||
#[bits(0..=9, rw)]
|
||||
pri_wr_port: u10,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct AxiPriorityReadPort {
|
||||
#[bit(19, rw)]
|
||||
enable_hpr: bool,
|
||||
#[bit(18, rw)]
|
||||
disable_page_match: bool,
|
||||
#[bit(17, rw)]
|
||||
disable_urgent: bool,
|
||||
#[bit(16, rw)]
|
||||
disable_aging: bool,
|
||||
#[bits(0..=9, rw)]
|
||||
pri_rd_port_n: u10,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct ExclusiveAccessConfig {
|
||||
#[bits(9..=17, rw)]
|
||||
access_id1_port: u9,
|
||||
#[bits(0..=8, rw)]
|
||||
access_id0_port: u9,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
pub enum LpddrBit {
|
||||
Ddr2Ddr3 = 0,
|
||||
Lpddr2 = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct LpddrControl0 {
|
||||
#[bits(4..=11, rw)]
|
||||
mr4_margin: u8,
|
||||
#[bit(2, rw)]
|
||||
derate_enable: bool,
|
||||
#[bit(1, rw)]
|
||||
per_bank_refresh: bool,
|
||||
#[bit(0, rw)]
|
||||
lpddr2: LpddrBit,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct LpddrControl1 {
|
||||
#[bits(0..=31, rw)]
|
||||
mr4_read_interval: u32,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct LpddrControl2 {
|
||||
#[bits(12..=21, rw)]
|
||||
t_mrw: u10,
|
||||
#[bits(4..=11, rw)]
|
||||
idle_after_reset_x32: u8,
|
||||
#[bits(0..=3, rw)]
|
||||
min_stable_clock_x1: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct LpddrControl3 {
|
||||
#[bits(8..=17, rw)]
|
||||
dev_zqinit_x32: u10,
|
||||
#[bits(0..=7, rw)]
|
||||
max_auto_init_x1024: u8,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u3, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum OperatingMode {
|
||||
DdrcInit = 0,
|
||||
NormalOperation = 1,
|
||||
Powerdown = 2,
|
||||
SelfRefresh = 3,
|
||||
DeepPowerdown = 4,
|
||||
DeepPowerdownAlt1 = 5,
|
||||
DeepPowerdownAlt2 = 6,
|
||||
DeepPowerdownAlt3 = 7,
|
||||
}
|
||||
|
||||
impl OperatingMode {
|
||||
pub fn is_deep_powerdown(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
OperatingMode::DeepPowerdown
|
||||
| OperatingMode::DeepPowerdownAlt1
|
||||
| OperatingMode::DeepPowerdownAlt2
|
||||
| OperatingMode::DeepPowerdownAlt3
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum DebugStallBit {
|
||||
CommandsAccepted = 0,
|
||||
CommandsNotAccepted = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct ModeStatus {
|
||||
#[bits(16..=20, r)]
|
||||
dbg_hpr_queue_depth: u5,
|
||||
#[bits(10..=15, r)]
|
||||
dbg_lpr_queue_depth: u6,
|
||||
#[bits(4..=9, r)]
|
||||
dbg_wr_queue_depth: u6,
|
||||
#[bit(3, r)]
|
||||
dbg_stall: DebugStallBit,
|
||||
#[bits(0..=2, r)]
|
||||
operating_mode: OperatingMode,
|
||||
}
|
||||
}
|
||||
|
||||
use regs::*;
|
||||
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[repr(C)]
|
||||
pub struct DdrController {
|
||||
ddrc_ctrl: DdrcControl,
|
||||
two_rank_cfg: TwoRankConfig,
|
||||
hpr_queue_ctrl: LprHprQueueControl,
|
||||
lpr_queue_ctrl: LprHprQueueControl,
|
||||
wr_reg: WriteQueueControl,
|
||||
dram_param_reg0: DramParamReg0,
|
||||
dram_param_reg1: DramParamReg1,
|
||||
dram_param_reg2: DramParamReg2,
|
||||
dram_param_reg3: DramParamReg3,
|
||||
dram_param_reg4: DramParamReg4,
|
||||
dram_init_param: DramInitParam,
|
||||
dram_emr: DramEmr,
|
||||
dram_emr_mr: DramEmrMr,
|
||||
dram_burst8_rdwr: DramBurst8ReadWrite,
|
||||
dram_disable_dq: DisableDq,
|
||||
dram_addr_map_bank: DramAddrMapBank,
|
||||
dram_addr_map_col: DramAddrMapColumn,
|
||||
dram_addr_map_row: DramAddrMapRow,
|
||||
dram_odt_reg: DramOdt,
|
||||
#[mmio(PureRead)]
|
||||
phy_debug_reg: u32,
|
||||
phy_cmd_timeout_rddata_cpt: PhyCmdTimeoutRdDataCpt,
|
||||
#[mmio(PureRead)]
|
||||
mode_status: ModeStatus,
|
||||
dll_calib: DllCalib,
|
||||
odt_delay_hold: OdtDelayHold,
|
||||
ctrl_reg1: CtrlReg1,
|
||||
ctrl_reg2: CtrlReg2,
|
||||
ctrl_reg3: CtrlReg3,
|
||||
ctrl_reg4: CtrlReg4,
|
||||
_reserved0: [u32; 0x2],
|
||||
ctrl_reg5: CtrlReg5,
|
||||
ctrl_reg6: CtrlReg6,
|
||||
|
||||
_reserved1: [u32; 0x8],
|
||||
|
||||
che_refresh_timer_01: u32,
|
||||
che_t_zq: CheTZq,
|
||||
che_t_zq_short_interval_reg: CheTZqShortInterval,
|
||||
deep_powerdown_reg: DeepPowerdown,
|
||||
reg_2c: Reg2c,
|
||||
reg_2d: Reg2d,
|
||||
dfi_timing: DfiTiming,
|
||||
_reserved2: [u32; 0x2],
|
||||
che_ecc_control: CheEccControl,
|
||||
#[mmio(PureRead)]
|
||||
che_corr_ecc_log: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_corr_ecc_addr: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_corr_ecc_data_31_0: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_corr_ecc_data_63_32: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_corr_ecc_data_71_64: u32,
|
||||
/// Clear on write, but the write is performed on another register.
|
||||
#[mmio(PureRead)]
|
||||
che_uncorr_ecc_log: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_uncorr_ecc_addr: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_uncorr_ecc_data_31_0: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_uncorr_ecc_data_63_32: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_uncorr_ecc_data_71_64: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_ecc_stats: u32,
|
||||
ecc_scrub: EccScrub,
|
||||
#[mmio(PureRead)]
|
||||
che_ecc_corr_bit_mask_31_0: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_ecc_corr_bit_mask_63_32: u32,
|
||||
|
||||
_reserved3: [u32; 0x5],
|
||||
|
||||
phy_receiver_enable: PhyReceiverEnable,
|
||||
phy_config: [PhyConfig; 0x4],
|
||||
_reserved4: u32,
|
||||
phy_init_ratio: [PhyInitRatio; 4],
|
||||
_reserved5: u32,
|
||||
phy_rd_dqs_cfg: [PhyDqsConfig; 4],
|
||||
_reserved6: u32,
|
||||
phy_wr_dqs_cfg: [PhyDqsConfig; 4],
|
||||
_reserved7: u32,
|
||||
phy_we_cfg: [PhyWriteEnableConfig; 4],
|
||||
_reserved8: u32,
|
||||
phy_wr_data_slave: [PhyWriteDataSlaveConfig; 4],
|
||||
|
||||
_reserved9: u32,
|
||||
|
||||
reg_64: Reg64,
|
||||
reg_65: Reg65,
|
||||
_reserved10: [u32; 3],
|
||||
#[mmio(PureRead)]
|
||||
reg69_6a0: u32,
|
||||
#[mmio(PureRead)]
|
||||
reg69_6a1: u32,
|
||||
_reserved11: u32,
|
||||
#[mmio(PureRead)]
|
||||
reg69_6d2: u32,
|
||||
#[mmio(PureRead)]
|
||||
reg69_6d3: u32,
|
||||
#[mmio(PureRead)]
|
||||
reg69_710: u32,
|
||||
#[mmio(PureRead)]
|
||||
reg6e_711: u32,
|
||||
#[mmio(PureRead)]
|
||||
reg6e_712: u32,
|
||||
#[mmio(PureRead)]
|
||||
reg6e_713: u32,
|
||||
_reserved12: u32,
|
||||
#[mmio(PureRead)]
|
||||
phy_dll_status: [u32; 4],
|
||||
_reserved13: u32,
|
||||
#[mmio(PureRead)]
|
||||
dll_lock_status: u32,
|
||||
#[mmio(PureRead)]
|
||||
phy_control_status: u32,
|
||||
#[mmio(PureRead)]
|
||||
phy_control_status_2: u32,
|
||||
|
||||
_reserved14: [u32; 0x5],
|
||||
|
||||
// DDRI registers.
|
||||
#[mmio(PureRead)]
|
||||
axi_id: u32,
|
||||
page_mask: u32,
|
||||
axi_priority_wr_port: [AxiPriorityWritePort; 0x4],
|
||||
axi_priority_rd_port: [AxiPriorityReadPort; 0x4],
|
||||
|
||||
_reserved15: [u32; 0x1B],
|
||||
|
||||
excl_access_cfg: [ExclusiveAccessConfig; 0x4],
|
||||
#[mmio(PureRead)]
|
||||
mode_reg_read: u32,
|
||||
lpddr_ctrl_0: LpddrControl0,
|
||||
lpddr_ctrl_1: LpddrControl1,
|
||||
lpddr_ctrl_2: LpddrControl2,
|
||||
lpddr_ctrl_3: LpddrControl3,
|
||||
}
|
||||
|
||||
static_assertions::const_assert_eq!(core::mem::size_of::<DdrController>(), 0x2B8);
|
||||
|
||||
impl DdrController {
|
||||
/// Create a new DDR MMIO instance for the DDR controller at address [DDRC_BASE_ADDR].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This API can be used to potentially create a driver to the same peripheral structure
|
||||
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
|
||||
/// interfere with each other.
|
||||
pub const unsafe fn new_mmio_fixed() -> MmioDdrController<'static> {
|
||||
unsafe { Self::new_mmio_at(DDRC_BASE_ADDR) }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -24,6 +25,8 @@ pub mod gtc;
|
||||
pub mod i2c;
|
||||
pub mod l2_cache;
|
||||
pub mod mpcore;
|
||||
pub mod priv_tim;
|
||||
pub mod qspi;
|
||||
pub mod slcr;
|
||||
pub mod spi;
|
||||
pub mod ttc;
|
||||
@@ -40,6 +43,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>,
|
||||
@@ -53,6 +57,7 @@ pub struct PsPeripherals {
|
||||
pub ttc_1: ttc::MmioTtc<'static>,
|
||||
pub eth_0: eth::MmioEthernet<'static>,
|
||||
pub eth_1: eth::MmioEthernet<'static>,
|
||||
pub qspi: qspi::MmioQspi<'static>,
|
||||
}
|
||||
|
||||
impl PsPeripherals {
|
||||
@@ -76,6 +81,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(),
|
||||
@@ -89,7 +95,22 @@ impl PsPeripherals {
|
||||
ttc_1: ttc::Ttc::new_mmio_fixed_1(),
|
||||
eth_0: eth::Ethernet::new_mmio_fixed_0(),
|
||||
eth_1: eth::Ethernet::new_mmio_fixed_1(),
|
||||
qspi: qspi::Qspi::new_mmio_fixed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SpiClockPhase {
|
||||
ActiveOutsideOfWord = 0,
|
||||
InactiveOutsideOfWord = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SpiClockPolarity {
|
||||
QuiescentLow = 0,
|
||||
QuiescentHigh = 1,
|
||||
}
|
||||
|
||||
48
zynq7000/src/priv_tim.rs
Normal file
48
zynq7000/src/priv_tim.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
//! # CPU private timer module.
|
||||
|
||||
pub const CPU_PRIV_TIM_BASE_ADDR: usize = super::mpcore::MPCORE_BASE_ADDR + 0x0000_0600;
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct Control {
|
||||
#[bits(8..=15, rw)]
|
||||
prescaler: u8,
|
||||
#[bit(2, rw)]
|
||||
interrupt_enable: bool,
|
||||
#[bit(1, rw)]
|
||||
auto_reload: bool,
|
||||
#[bit(0, rw)]
|
||||
enable: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct InterruptStatus {
|
||||
/// Cleared by writing a one.
|
||||
#[bit(0, rw)]
|
||||
event_flag: bool,
|
||||
}
|
||||
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[repr(C)]
|
||||
pub struct CpuPrivateTimer {
|
||||
reload: u32,
|
||||
counter: u32,
|
||||
control: Control,
|
||||
interrupt_status: InterruptStatus,
|
||||
}
|
||||
|
||||
impl CpuPrivateTimer {
|
||||
/// Create a new CPU Private Timer MMIO instance at the fixed base address.
|
||||
///
|
||||
/// # 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.
|
||||
///
|
||||
/// It should also be noted that the calls to this MMIO structure are private for each CPU
|
||||
/// core, which might lead to unexpected results when using this in a SMP system.
|
||||
#[inline]
|
||||
pub const unsafe fn new_mmio_fixed() -> MmioCpuPrivateTimer<'static> {
|
||||
unsafe { CpuPrivateTimer::new_mmio_at(CPU_PRIV_TIM_BASE_ADDR) }
|
||||
}
|
||||
}
|
||||
278
zynq7000/src/qspi.rs
Normal file
278
zynq7000/src/qspi.rs
Normal file
@@ -0,0 +1,278 @@
|
||||
use arbitrary_int::{u2, u3};
|
||||
|
||||
pub use crate::{SpiClockPhase, SpiClockPolarity};
|
||||
|
||||
const QSPI_BASE_ADDR: usize = 0xE000D000;
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum InterfaceMode {
|
||||
LegacySpi = 0,
|
||||
FlashMemoryInterface = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Endianness {
|
||||
Little = 0,
|
||||
Big = 1,
|
||||
}
|
||||
|
||||
/// Baud rate divisor register values.
|
||||
#[bitbybit::bitenum(u3, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BaudRateDivisor {
|
||||
_2 = 0b000,
|
||||
_4 = 0b001,
|
||||
_8 = 0b010,
|
||||
_16 = 0b011,
|
||||
_32 = 0b100,
|
||||
_64 = 0b101,
|
||||
_128 = 0b110,
|
||||
_256 = 0b111,
|
||||
}
|
||||
|
||||
impl BaudRateDivisor {
|
||||
/// Actual divisor value.
|
||||
pub fn divisor(&self) -> usize {
|
||||
match self {
|
||||
BaudRateDivisor::_2 => 2,
|
||||
BaudRateDivisor::_4 => 4,
|
||||
BaudRateDivisor::_8 => 8,
|
||||
BaudRateDivisor::_16 => 16,
|
||||
BaudRateDivisor::_32 => 32,
|
||||
BaudRateDivisor::_64 => 64,
|
||||
BaudRateDivisor::_128 => 128,
|
||||
BaudRateDivisor::_256 => 256,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct Config {
|
||||
#[bit(31, rw)]
|
||||
interface_mode: InterfaceMode,
|
||||
#[bit(26, rw)]
|
||||
edianness: Endianness,
|
||||
#[bit(19, rw)]
|
||||
holdb_dr: bool,
|
||||
#[bit(16, w)]
|
||||
manual_start_command: bool,
|
||||
#[bit(15, rw)]
|
||||
manual_start_enable: bool,
|
||||
#[bit(14, rw)]
|
||||
manual_cs: bool,
|
||||
/// Directly drives the chip select line when CS is driven manually (bit 14 is set)
|
||||
#[bit(10, rw)]
|
||||
peripheral_chip_select: bool,
|
||||
/// The only valid value is 0b11 (32 bits)
|
||||
#[bits(6..=7, rw)]
|
||||
fifo_width: u2,
|
||||
#[bits(3..=5, rw)]
|
||||
baud_rate_div: BaudRateDivisor,
|
||||
#[bit(2, rw)]
|
||||
clock_phase: SpiClockPhase,
|
||||
#[bit(1, rw)]
|
||||
clock_polarity: SpiClockPolarity,
|
||||
/// Must be set to 1 before using QSPI, 0 is a reserved value.
|
||||
#[bit(0, rw)]
|
||||
mode_select: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct InterruptStatus {
|
||||
/// Write-to-clear bit.
|
||||
#[bit(6, rw)]
|
||||
tx_underflow: bool,
|
||||
#[bit(5, r)]
|
||||
rx_full: bool,
|
||||
#[bit(4, r)]
|
||||
rx_above_threshold: bool,
|
||||
#[bit(3, r)]
|
||||
tx_full: bool,
|
||||
#[bit(2, r)]
|
||||
tx_below_threshold: bool,
|
||||
/// Write-to-clear bit.
|
||||
#[bit(0, rw)]
|
||||
rx_overrun: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct InterruptControl {
|
||||
#[bit(6, w)]
|
||||
tx_underflow: bool,
|
||||
#[bit(5, w)]
|
||||
rx_full: bool,
|
||||
#[bit(4, w)]
|
||||
rx_not_empty: bool,
|
||||
#[bit(3, w)]
|
||||
tx_full: bool,
|
||||
#[bit(2, w)]
|
||||
tx_not_full: bool,
|
||||
#[bit(0, w)]
|
||||
rx_overrun: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct InterruptMask {
|
||||
#[bit(6, r)]
|
||||
tx_underflow: bool,
|
||||
#[bit(5, r)]
|
||||
rx_full: bool,
|
||||
#[bit(4, r)]
|
||||
rx_not_empty: bool,
|
||||
#[bit(3, r)]
|
||||
tx_full: bool,
|
||||
#[bit(2, r)]
|
||||
tx_not_full: bool,
|
||||
#[bit(0, r)]
|
||||
rx_overrun: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct SpiEnable {
|
||||
#[bit(0, rw)]
|
||||
enable: bool,
|
||||
}
|
||||
|
||||
/// All the delays are in SPI reference block or external clock cycles.
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct Delay {
|
||||
/// Length of the master mode chip select output de-asserts between words when CPHA = 0.
|
||||
#[bits(24..=31, rw)]
|
||||
deassert: u8,
|
||||
/// Delay between one chip select being de-activated and another being activated.
|
||||
#[bits(16..=23, rw)]
|
||||
between: u8,
|
||||
/// Length between last bit of current word and first bit of next word.
|
||||
#[bits(8..=15, rw)]
|
||||
after: u8,
|
||||
/// Delay between setting chip select low and first bit transfer.
|
||||
#[bits(0..=7, rw)]
|
||||
init: u8,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct Gpio {
|
||||
/// Active low write-protect bit.
|
||||
#[bit(0, rw)]
|
||||
write_protect_n: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct LoopbackMasterClockDelay {
|
||||
/// Use internal loopback master clock for read data capturing when the baud rate divisor
|
||||
/// is 2.
|
||||
#[bit(5, rw)]
|
||||
use_loopback: bool,
|
||||
#[bits(3..=4,rw)]
|
||||
delay_1: u2,
|
||||
#[bits(0..=2 ,rw)]
|
||||
delay_0: u3,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u8, exhaustive = false)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum InstructionCode {
|
||||
Read = 0x03,
|
||||
FastRead = 0x0B,
|
||||
FastReadDualOutput = 0x3B,
|
||||
FastReadQuadOutput = 0x6B,
|
||||
FastReadDualIo = 0xBB,
|
||||
FastReadQuadIo = 0xEB,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct LinearQspiConfig {
|
||||
#[bit(31, rw)]
|
||||
enable_linear_mode: bool,
|
||||
#[bit(30, rw)]
|
||||
both_memories: bool,
|
||||
/// Only has a meaning is bit 30 is set (both memories).
|
||||
#[bit(29, rw)]
|
||||
separate_memory_bus: bool,
|
||||
/// Upper memory page, if set. Only has a meaning if bit 30 is set and bit 29 / bit 31 are
|
||||
/// cleared.
|
||||
///
|
||||
/// In LQSPI mode, address bit 25 will indicate the lower (0) or upper (1) page.
|
||||
/// In IO mode, this bit selects the lower or upper memory.
|
||||
#[bit(28, rw)]
|
||||
upper_memory_page: bool,
|
||||
#[bit(25, rw)]
|
||||
mode_enable: bool,
|
||||
#[bit(24, rw)]
|
||||
mode_on: bool,
|
||||
#[bits(16..=23, rw)]
|
||||
mode_bits: u8,
|
||||
#[bits(8..=10, rw)]
|
||||
num_dummy_bytes: u3,
|
||||
#[bits(0..=7, rw)]
|
||||
instruction_code: Option<InstructionCode>,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct LinearQspiStatus {
|
||||
#[bit(2, rw)]
|
||||
data_fsm_error: bool,
|
||||
#[bit(1, rw)]
|
||||
axi_write_command_received: bool,
|
||||
}
|
||||
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[repr(C)]
|
||||
pub struct Qspi {
|
||||
config: Config,
|
||||
interrupt_status: InterruptStatus,
|
||||
#[mmio(Write)]
|
||||
interrupt_enable: InterruptControl,
|
||||
#[mmio(Write)]
|
||||
interrupt_disable: InterruptControl,
|
||||
#[mmio(PureRead)]
|
||||
interupt_mask: InterruptMask,
|
||||
spi_enable: SpiEnable,
|
||||
delay: Delay,
|
||||
/// Transmits 1-byte command and 3-byte data OR 4-byte data.
|
||||
#[mmio(Write)]
|
||||
tx_data_00: u32,
|
||||
#[mmio(PureRead)]
|
||||
rx_data: u32,
|
||||
slave_idle_count: u32,
|
||||
/// Defines the level at which the TX FIFO not full interrupt is generated.
|
||||
tx_fifo_threshold: u32,
|
||||
/// Defines the level at which the RX FIFO not empty interrupt is generated.
|
||||
rx_fifo_threshold: u32,
|
||||
gpio: Gpio,
|
||||
_reserved0: u32,
|
||||
loopback_master_clock_delay: LoopbackMasterClockDelay,
|
||||
_reserved1: [u32; 0x11],
|
||||
/// Transmits 1-byte command.
|
||||
#[mmio(Write)]
|
||||
tx_data_01: u32,
|
||||
/// Transmits 1-byte command and 1-byte data.
|
||||
#[mmio(Write)]
|
||||
tx_data_10: u32,
|
||||
/// Transmits 1-byte command and 2-byte data.
|
||||
#[mmio(Write)]
|
||||
tx_data_11: u32,
|
||||
_reserved2: [u32; 0x5],
|
||||
linear_qspi_config: LinearQspiConfig,
|
||||
linear_qspi_status: LinearQspiStatus,
|
||||
_reserved3: [u32; 0x15],
|
||||
/// Module ID value with reset value 0x1090101.
|
||||
module_id: u32,
|
||||
}
|
||||
|
||||
static_assertions::const_assert_eq!(core::mem::size_of::<Qspi>(), 0x100);
|
||||
|
||||
impl Qspi {
|
||||
/// Create a new QSPI MMIO instance for for QSPI controller at address [QSPI_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() -> MmioQspi<'static> {
|
||||
unsafe { Self::new_mmio_at(QSPI_BASE_ADDR) }
|
||||
}
|
||||
}
|
||||
@@ -4,18 +4,12 @@
|
||||
use super::{CLOCK_CONTROL_OFFSET, SLCR_BASE_ADDR};
|
||||
use arbitrary_int::{u4, u6, u7, u10};
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug)]
|
||||
pub enum BypassForce {
|
||||
EnabledOrSetByBootMode = 0b0,
|
||||
Bypassed = 0b1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug)]
|
||||
pub enum BypassQual {
|
||||
BypassForceBit = 0b0,
|
||||
BootModeFourthBit = 0b1,
|
||||
pub enum Bypass {
|
||||
NotBypassed = 0b00,
|
||||
/// This is the default reset value.
|
||||
PinStrapSettings = 0b01,
|
||||
Bypassed = 0b10,
|
||||
BypassedRegardlessOfPinStrapping = 0b11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
@@ -29,10 +23,10 @@ pub struct PllControl {
|
||||
fdiv: u7,
|
||||
/// Select source for the ARM PLL bypass control
|
||||
#[bit(4, rw)]
|
||||
bypass_force: BypassForce,
|
||||
bypass_force: bool,
|
||||
/// Select source for the ARM PLL bypass control
|
||||
#[bit(3, rw)]
|
||||
bypass_qual: BypassQual,
|
||||
bypass_qual: bool,
|
||||
// Power-down control
|
||||
#[bit(1, rw)]
|
||||
pwrdwn: bool,
|
||||
@@ -41,6 +35,40 @@ pub struct PllControl {
|
||||
reset: bool,
|
||||
}
|
||||
|
||||
impl PllControl {
|
||||
#[inline]
|
||||
pub fn set_bypass(&mut self, bypass: Bypass) {
|
||||
match bypass {
|
||||
Bypass::NotBypassed => {
|
||||
self.set_bypass_force(false);
|
||||
self.set_bypass_qual(false);
|
||||
}
|
||||
Bypass::PinStrapSettings => {
|
||||
self.set_bypass_force(false);
|
||||
self.set_bypass_qual(true);
|
||||
}
|
||||
Bypass::Bypassed => {
|
||||
self.set_bypass_force(true);
|
||||
self.set_bypass_qual(false);
|
||||
}
|
||||
Bypass::BypassedRegardlessOfPinStrapping => {
|
||||
self.set_bypass_force(true);
|
||||
self.set_bypass_qual(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bypass(&self) -> Bypass {
|
||||
match (self.bypass_force(), self.bypass_qual()) {
|
||||
(false, false) => Bypass::NotBypassed,
|
||||
(false, true) => Bypass::PinStrapSettings,
|
||||
(true, false) => Bypass::Bypassed,
|
||||
(true, true) => Bypass::BypassedRegardlessOfPinStrapping,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct PllConfig {
|
||||
@@ -48,26 +76,26 @@ pub struct PllConfig {
|
||||
lock_count: u10,
|
||||
/// Charge Pump control
|
||||
#[bits(8..=11, rw)]
|
||||
pll_cp: u4,
|
||||
charge_pump: u4,
|
||||
/// Loop resistor control
|
||||
#[bits(4..=7, rw)]
|
||||
pll_res: u4,
|
||||
loop_resistor: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct PllStatus {
|
||||
#[bit(5)]
|
||||
#[bit(5, r)]
|
||||
io_pll_stable: bool,
|
||||
#[bit(4)]
|
||||
#[bit(4, r)]
|
||||
ddr_pll_stable: bool,
|
||||
#[bit(3)]
|
||||
#[bit(3, r)]
|
||||
arm_pll_stable: bool,
|
||||
#[bit(2)]
|
||||
#[bit(2, r)]
|
||||
io_pll_lock: bool,
|
||||
#[bit(1)]
|
||||
#[bit(1, r)]
|
||||
drr_pll_lock: bool,
|
||||
#[bit(0)]
|
||||
#[bit(0, r)]
|
||||
arm_pll_lock: bool,
|
||||
}
|
||||
|
||||
@@ -141,7 +169,7 @@ pub struct DdrClockControl {
|
||||
ddr_3x_clk_act: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DciClockControl {
|
||||
/// Second cascade divider. Reset value: 0x1E
|
||||
#[bits(20..=25, rw)]
|
||||
@@ -246,7 +274,7 @@ pub struct CanClockControl {
|
||||
clk_0_act: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct SingleCommonPeriphIoClockControl {
|
||||
#[bits(8..=13, rw)]
|
||||
divisor: u6,
|
||||
@@ -341,9 +369,9 @@ pub struct AperClockControl {
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[repr(C)]
|
||||
pub struct ClockControl {
|
||||
arm_pll: PllControl,
|
||||
ddr_pll: PllControl,
|
||||
io_pll: PllControl,
|
||||
arm_pll_ctrl: PllControl,
|
||||
ddr_pll_ctrl: PllControl,
|
||||
io_pll_ctrl: PllControl,
|
||||
pll_status: PllStatus,
|
||||
arm_pll_cfg: PllConfig,
|
||||
ddr_pll_cfg: PllConfig,
|
||||
|
||||
139
zynq7000/src/slcr/ddriob.rs
Normal file
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 BootPllConfig {
|
||||
Enabled = 0,
|
||||
/// Disabled and bypassed.
|
||||
Bypassed = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct BootModeRegister {
|
||||
#[bit(4, r)]
|
||||
pll_bypass: bool,
|
||||
pll_config: BootPllConfig,
|
||||
#[bits(0..=3, r)]
|
||||
boot_mode: u4,
|
||||
}
|
||||
@@ -205,7 +179,7 @@ pub struct Slcr {
|
||||
gpiob: GpiobRegisters,
|
||||
|
||||
#[mmio(Inner)]
|
||||
ddriob: DdrIoB,
|
||||
ddriob: ddriob::DdrIoB,
|
||||
}
|
||||
|
||||
static_assertions::const_assert_eq!(core::mem::size_of::<Slcr>(), 0xB78);
|
||||
|
||||
@@ -52,6 +52,15 @@ pub struct EthernetReset {
|
||||
gem0_cpu1x_rst: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
#[derive(Debug)]
|
||||
pub struct QspiResetControl {
|
||||
#[bit(2, rw)]
|
||||
qspi_ref_reset: bool,
|
||||
#[bit(1, rw)]
|
||||
cpu_1x_reset: bool,
|
||||
}
|
||||
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[repr(C)]
|
||||
pub struct ResetControl {
|
||||
@@ -69,7 +78,7 @@ pub struct ResetControl {
|
||||
i2c: DualClockReset,
|
||||
uart: DualRefAndClockReset,
|
||||
gpio: GpioClockReset,
|
||||
lqspi: u32,
|
||||
lqspi: QspiResetControl,
|
||||
smc: u32,
|
||||
ocm: u32,
|
||||
_gap0: u32,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! SPI register module.
|
||||
use arbitrary_int::{Number, u4};
|
||||
|
||||
pub use crate::{SpiClockPhase, SpiClockPolarity};
|
||||
|
||||
pub const SPI_0_BASE_ADDR: usize = 0xE000_6000;
|
||||
pub const SPI_1_BASE_ADDR: usize = 0xE000_7000;
|
||||
|
||||
@@ -56,10 +58,10 @@ pub struct Config {
|
||||
baud_rate_div: Option<BaudDivSel>,
|
||||
/// Clock phase. 1: The SPI clock is inactive outside the word.
|
||||
#[bit(2, rw)]
|
||||
cpha: bool,
|
||||
cpha: SpiClockPhase,
|
||||
/// Clock phase. 1: The SPI clock is quiescent high.
|
||||
#[bit(1, rw)]
|
||||
cpol: bool,
|
||||
cpol: SpiClockPolarity,
|
||||
/// Master mode enable. 1 is master mode.
|
||||
#[bit(0, rw)]
|
||||
master_ern: bool,
|
||||
|
||||
@@ -112,7 +112,7 @@ pub struct Baudgen {
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
#[derive(Debug)]
|
||||
pub struct BaudRateDiv {
|
||||
pub struct BaudRateDivisor {
|
||||
#[bits(0..=7, rw)]
|
||||
bdiv: u8,
|
||||
}
|
||||
@@ -319,7 +319,7 @@ pub struct Uart {
|
||||
#[mmio(Read, Write)]
|
||||
fifo: Fifo,
|
||||
/// Baud rate divider register
|
||||
baud_rate_div: BaudRateDiv,
|
||||
baud_rate_div: BaudRateDivisor,
|
||||
/// Flow control delay register
|
||||
flow_delay: u32,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user