FSBL-rs #9

Open
muellerr wants to merge 4 commits from fsbl-rs into main
95 changed files with 7634 additions and 569 deletions

View File

@@ -9,8 +9,14 @@ members = [
"examples/embassy",
"examples/zedboard",
"zynq-mmu",
"zedboard-fsbl",
"zedboard-bsp",
"zynq-boot-image", "zedboard-qspi-flasher",
]
exclude = [
"zynq-boot-image/tester",
]
exclude = ["experiments"]
# cargo build/run --release
[profile.release]
@@ -21,3 +27,7 @@ incremental = false
lto = true
opt-level = 3 # <-
overflow-checks = false # <-
[profile.small]
inherits = "release"
opt-level = "z"

View File

@@ -35,7 +35,7 @@ The folder contains a README with all the steps required to load this project fr
Use the following command to have a starting `config.toml` file
```sh
cp .cargo/def-config.toml .cargo/config.toml
cp .cargo/config.toml.template .cargo/config.toml
```
You then can adapt the `config.toml` to your needs. For example, you can configure runners

View File

@@ -11,7 +11,7 @@ keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2", features = ["critical-section-single-core"] }
cortex-ar = "0.3"
zynq7000-rt = { path = "../../zynq7000-rt" }
zynq7000 = { path = "../../zynq7000" }
zynq7000-hal = { path = "../../zynq7000-hal" }
@@ -19,8 +19,8 @@ zynq7000-embassy = { path = "../../zynq7000-embassy" }
dht-sensor = { git = "https://github.com/michaelbeaumont/dht-sensor.git", rev = "10319bdeae9ace3bb0fc79a15da2869c5bf50f52", features = ["async"] }
static_cell = "2"
critical-section = "1"
heapless = "0.8"
embedded-io = "0.6"
heapless = "0.9"
embedded-io = "0.7"
embedded-hal = "1"
fugit = "0.3"
log = "0.4"

31
examples/embassy/build.rs Normal file
View 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");
}

View File

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

View File

@@ -17,10 +17,10 @@ use zynq7000_hal::{
gtc::GlobalTimerCounter,
l2_cache,
time::Hertz,
uart::{ClockConfigRaw, Uart, UartConfig},
uart::{ClockConfig, Config, Uart},
};
use zynq7000::PsPeripherals;
use zynq7000::Peripherals;
use zynq7000_rt as _;
// Define the clock frequency as a constant
@@ -44,7 +44,7 @@ const OPEN_DRAIN_PINS_MIO9_TO_MIO14: bool = false;
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
let mut dp = Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
@@ -64,12 +64,12 @@ async fn main(_spawner: Spawner) -> ! {
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)
let uart_clk_config = ClockConfig::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),
Config::new_with_clk_config(uart_clk_config),
(mio_pins.mio48, mio_pins.mio49),
)
.unwrap();
@@ -137,7 +137,7 @@ async fn main(_spawner: Spawner) -> ! {
info!("Flex Pin 0 state (should be low): {}", flex_pin_0.is_high());
}
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let mut ticker = Ticker::every(Duration::from_millis(1000));

View File

@@ -9,7 +9,7 @@ use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use log::{error, info};
use zynq7000::PsPeripherals;
use zynq7000::Peripherals;
use zynq7000_hal::{
BootMode,
clocks::Clocks,
@@ -18,7 +18,7 @@ use zynq7000_hal::{
gtc::GlobalTimerCounter,
l2_cache,
time::Hertz,
uart::{ClockConfigRaw, TxAsync, Uart, UartConfig, on_interrupt_tx},
uart::{ClockConfig, Config, TxAsync, Uart, on_interrupt_tx},
};
use zynq7000_rt as _;
@@ -38,7 +38,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! {
#[unsafe(export_name = "main")]
#[embassy_executor::main]
async fn main(spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
let mut dp = Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
@@ -58,12 +58,12 @@ async fn main(spawner: Spawner) -> ! {
let mio_pins = mio::Pins::new(dp.gpio);
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = ClockConfig::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),
Config::new_with_clk_config(uart_clk_config),
(mio_pins.mio48, mio_pins.mio49),
)
.unwrap();
@@ -75,7 +75,7 @@ async fn main(spawner: Spawner) -> ! {
zynq7000_hal::log::rb::init(log::LevelFilter::Trace);
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let led = Output::new_for_mio(mio_pins.mio7, PinState::Low);

View File

@@ -23,10 +23,10 @@ use zynq7000_hal::{
gtc::GlobalTimerCounter,
l2_cache,
time::Hertz,
uart::{ClockConfigRaw, Uart, UartConfig},
uart::{ClockConfig, Config, Uart},
};
use zynq7000::PsPeripherals;
use zynq7000::Peripherals;
use zynq7000_rt as _;
// Define the clock frequency as a constant
@@ -44,7 +44,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! {
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
let mut dp = Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
@@ -71,12 +71,12 @@ async fn main(_spawner: Spawner) -> ! {
pwm.set_duty_cycle_percent(50).unwrap();
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = ClockConfig::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),
Config::new_with_clk_config(uart_clk_config),
(mio_pins.mio48, mio_pins.mio49),
)
.unwrap();
@@ -91,7 +91,7 @@ async fn main(_spawner: Spawner) -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let mut ticker = Ticker::every(Duration::from_millis(1000));

View File

@@ -8,18 +8,8 @@ use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use log::{error, info};
use zynq7000_hal::{
BootMode,
clocks::Clocks,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gpio::{Output, PinState, mio},
gtc::GlobalTimerCounter,
l2_cache,
time::Hertz,
uart::{ClockConfigRaw, Uart, UartConfig},
};
use zynq7000_hal::{BootMode, InteruptConfig, clocks, gic, gpio, gtc, time::Hertz, uart};
use zynq7000::PsPeripherals;
use zynq7000_rt as _;
// Define the clock frequency as a constant
@@ -37,32 +27,27 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! {
#[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);
// 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 mio_pins = mio::Pins::new(dp.gpio);
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
init_l2_cache: true,
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
interrupt_config: Some(InteruptConfig::AllInterruptsToCpu0),
})
.unwrap();
let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Set up global timer counter and embassy time driver.
let gtc = GlobalTimerCounter::new(dp.gtc, clocks.arm_clocks());
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
let mio_pins = gpio::mio::Pins::new(periphs.gpio);
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = uart::ClockConfig::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),
let mut uart = uart::Uart::new_with_mio(
periphs.uart_1,
uart::Config::new_with_clk_config(uart_clk_config),
(mio_pins.mio48, mio_pins.mio49),
)
.unwrap();
@@ -77,11 +62,11 @@ 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));
let mut led = Output::new_for_mio(mio_pins.mio7, PinState::Low);
let mut led = gpio::Output::new_for_mio(mio_pins.mio7, gpio::PinState::Low);
loop {
info!("Hello, world!");
led.toggle().unwrap();
@@ -89,42 +74,42 @@ async fn main(_spawner: Spawner) -> ! {
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
#[zynq7000_rt::irq]
pub fn irq_handler() {
let mut gic_helper = gic::GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
Interrupt::Sgi(_) => (),
Interrupt::Ppi(ppi_interrupt) => {
gic::Interrupt::Sgi(_) => (),
gic::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::Interrupt::Spi(_spi_interrupt) => (),
gic::Interrupt::Invalid(_) => (),
gic::Interrupt::Spurious => (),
}
gic_helper.end_of_interrupt(irq_info);
}
#[unsafe(no_mangle)]
pub extern "C" fn _abort_handler() {
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _undefined_handler() {
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _prefetch_handler() {
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}

View File

@@ -9,11 +9,11 @@ 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"] }
cortex-ar = "0.3"
zynq7000-rt = { path = "../../zynq7000-rt" }
zynq7000 = { path = "../../zynq7000" }
zynq7000-hal = { path = "../../zynq7000-hal" }
embedded-io = "0.6"
embedded-io = "0.7"
embedded-hal = "1"
fugit = "0.3"
log = "0.4"

31
examples/simple/build.rs Normal file
View 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
View 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
}

View File

@@ -15,13 +15,13 @@ use zynq7000_hal::{
l2_cache,
prelude::*,
time::Hertz,
uart::{ClockConfigRaw, Uart, UartConfig},
uart::{ClockConfig, Config, Uart},
};
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);
@@ -36,7 +36,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! {
#[unsafe(export_name = "main")]
pub fn main() -> ! {
let mut dp = zynq7000::PsPeripherals::take().unwrap();
let mut dp = zynq7000::Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
@@ -49,7 +49,7 @@ pub fn main() -> ! {
// Enable interrupt exception.
unsafe { gic.enable_interrupts() };
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut gtc = GlobalTimerCounter::new(dp.gtc, clocks.arm_clocks());
@@ -64,7 +64,7 @@ pub fn main() -> ! {
let mio_pins = mio::Pins::new(dp.gpio);
let mut uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
Config::new_with_clk_config(uart_clk_config),
(mio_pins.mio48, mio_pins.mio49),
)
.unwrap();

View File

@@ -16,7 +16,7 @@ use zynq7000_hal::{
l2_cache,
prelude::*,
time::Hertz,
uart::{ClockConfigRaw, Uart, UartConfig},
uart::{ClockConfig, Config, Uart},
};
use zynq7000_rt as _;
@@ -37,7 +37,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! {
#[unsafe(export_name = "main")]
pub fn main() -> ! {
let mut dp = zynq7000::PsPeripherals::take().unwrap();
let mut dp = zynq7000::Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
@@ -50,7 +50,7 @@ pub fn main() -> ! {
// Enable interrupt exception.
unsafe { gic.enable_interrupts() };
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut gtc = GlobalTimerCounter::new(dp.gtc, clocks.arm_clocks());
@@ -64,7 +64,7 @@ pub fn main() -> ! {
let mut uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
Config::new_with_clk_config(uart_clk_config),
(mio_pins.mio48, mio_pins.mio49),
)
.unwrap();
@@ -79,7 +79,7 @@ pub fn main() -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {boot_mode:?}");
let mut led = Output::new_for_mio(mio_pins.mio7, PinState::Low);

View File

@@ -4,25 +4,33 @@
use core::panic::PanicInfo;
use cortex_ar::asm::nop;
use embedded_hal::digital::StatefulOutputPin;
use zynq7000::PsPeripherals;
use embedded_hal::{delay::DelayNs, digital::StatefulOutputPin};
use zynq7000::Peripherals;
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) -> ! {
@@ -48,14 +56,15 @@ pub fn main() -> ! {
}
}
Lib::Hal => {
let dp = PsPeripherals::take().unwrap();
let dp = Peripherals::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();
}

View File

@@ -11,15 +11,17 @@ keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2", features = ["critical-section-single-core"] }
cortex-ar = "0.3"
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"
arbitrary-int = "1.3"
embedded-io = "0.7"
bitbybit = "1.4"
arbitrary-int = "2"
embedded-io-async = "0.6"
critical-section = "1"
static_cell = "2"
@@ -38,6 +40,7 @@ embassy-executor = { git = "https://github.com/us-irs/embassy.git", branch = "co
embassy-time = { version = "0.5", features = ["tick-hz-1_000_000", "generic-queue-16"] }
embassy-net = { version = "0.7", features = ["dhcpv4", "packet-trace", "medium-ethernet", "icmp", "tcp", "udp"] }
embassy-sync = { version = "0.7" }
# TODO: Bump as soon as new compatible smoltcp/embassy-net version is released.
heapless = "0.8"
axi-uartlite = { git = "https://egit.irs.uni-stuttgart.de/rust/axi-uartlite.git" }
axi-uart16550 = { git = "https://egit.irs.uni-stuttgart.de/rust/axi-uart16550.git" }

View 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");
}

View 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
}

View File

@@ -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,
@@ -48,10 +46,10 @@ use zynq7000_hal::{
gpio::{GpioPins, Output, PinState},
gtc::GlobalTimerCounter,
l2_cache,
uart::{ClockConfigRaw, Uart, UartConfig},
uart::{ClockConfig, Config, Uart},
};
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig};
use zynq7000::{Peripherals, slcr::LevelShifterConfig};
use zynq7000_rt::{self as _, mmu::section_attrs::SHAREABLE_DEVICE, mmu_l1_table_mut};
const USE_DHCP: bool = true;
@@ -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,
@@ -208,7 +206,7 @@ async fn tcp_task(mut tcp: TcpSocket<'static>) -> ! {
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
let mut dp = Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Enable PS-PL level shifters.
@@ -236,12 +234,12 @@ async fn main(spawner: Spawner) -> ! {
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)
let uart_clk_config = ClockConfig::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),
Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
@@ -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,9 +315,8 @@ 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())
.expect("could not auto-detect phy");
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: {:?}",
phy_rev
@@ -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;

View File

@@ -29,7 +29,7 @@ use zynq7000_hal::{
uart,
};
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig};
use zynq7000::{Peripherals, slcr::LevelShifterConfig};
use zynq7000_rt as _;
// Define the clock frequency as a constant
@@ -48,7 +48,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! {
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
let mut dp = Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Enable PS-PL level shifters.
@@ -73,12 +73,12 @@ async fn main(_spawner: Spawner) -> ! {
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Set up the UART, we are logging with it.
let uart_clk_config = uart::ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = uart::Uart::new_with_mio(
dp.uart_1,
uart::UartConfig::new_with_clk_config(uart_clk_config),
uart::Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
@@ -93,7 +93,7 @@ async fn main(_spawner: Spawner) -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let pin_sel = match I2C_ADDR_SEL {

View File

@@ -30,7 +30,7 @@ use zynq7000_hal::{
uart::{self, TxAsync, on_interrupt_tx},
};
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig, spi::DelayControl};
use zynq7000::{Peripherals, slcr::LevelShifterConfig, spi::DelayControl};
use zynq7000_rt as _;
// Define the clock frequency as a constant
@@ -51,7 +51,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! {
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
let mut dp = Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Enable PS-PL level shifters.
@@ -83,12 +83,12 @@ async fn main(spawner: Spawner) -> ! {
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Set up the UART, we are logging with it.
let uart_clk_config = uart::ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = uart::Uart::new_with_mio(
dp.uart_1,
uart::UartConfig::new_with_clk_config(uart_clk_config),
uart::Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
@@ -96,7 +96,7 @@ async fn main(spawner: Spawner) -> ! {
.unwrap();
zynq7000_hal::log::rb::init(log::LevelFilter::Trace);
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
if DEBUG_SPI_CLK_CONFIG {

View File

@@ -0,0 +1,211 @@
#![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, gic, gpio, gtc, prelude::*, qspi, uart};
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 periphs = zynq7000_hal::init(zynq7000_hal::Config {
init_l2_cache: true,
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0),
})
.unwrap();
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
let gpio_pins = gpio::GpioPins::new(periphs.gpio);
// Set up global timer counter and embassy time driver.
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Set up the UART, we are logging with it.
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = uart::Uart::new_with_mio(
periphs.uart_1,
uart::Config::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(qspi::SrcSelIo::IoPll, &clocks, 100.MHz())
.expect("QSPI clock calculation failed");
let qspi = qspi::Qspi::new_single_qspi_with_feedback(
periphs.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 = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
loop {
mio_led.toggle().unwrap();
ticker.next().await; // Wait for the next cycle of the ticker
}
}
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = gic::GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
gic::Interrupt::Sgi(_) => (),
gic::Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
gic::Interrupt::Spi(_spi_interrupt) => (),
gic::Interrupt::Invalid(_) => (),
gic::Interrupt::Spurious => (),
}
gic_helper.end_of_interrupt(irq_info);
}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
error!("Panic: {info:?}");
loop {}
}

View File

@@ -20,10 +20,10 @@ use zynq7000_hal::{
gpio::{GpioPins, Output, PinState},
gtc::GlobalTimerCounter,
l2_cache,
uart::{ClockConfigRaw, Uart, UartConfig},
uart::{ClockConfig, Config, Uart},
};
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig};
use zynq7000::{Peripherals, slcr::LevelShifterConfig};
use zynq7000_rt as _;
const INIT_STRING: &str = "-- Zynq 7000 Zedboard blocking UART example --\n\r";
@@ -101,7 +101,7 @@ impl UartMultiplexer {
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
let mut dp = Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Enable PS-PL level shifters.
@@ -123,12 +123,12 @@ async fn main(_spawner: Spawner) -> ! {
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)
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut log_uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
@@ -145,7 +145,7 @@ async fn main(_spawner: Spawner) -> ! {
// UART0 routed through EMIO to PL pins.
let mut uart_0 =
Uart::new_with_emio(dp.uart_0, UartConfig::new_with_clk_config(uart_clk_config)).unwrap();
Uart::new_with_emio(dp.uart_0, Config::new_with_clk_config(uart_clk_config)).unwrap();
// Safety: Valid address of AXI UARTLITE.
let mut uartlite = unsafe { AxiUartlite::new(AXI_UARTLITE_BASE_ADDR) };
@@ -160,7 +160,7 @@ async fn main(_spawner: Spawner) -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let mut ticker = Ticker::every(Duration::from_millis(1000));

View File

@@ -47,7 +47,7 @@ use zynq7000_hal::{
gtc::GlobalTimerCounter,
l2_cache,
time::Hertz,
uart::{ClockConfigRaw, Uart, UartConfig},
uart::{ClockConfig, Config, Uart},
};
pub enum UartMode {
@@ -62,7 +62,7 @@ const INIT_STRING: &str = "-- Zynq 7000 Zedboard non-blocking UART example --\n\
#[global_allocator]
static HEAP: Heap = Heap::empty();
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig};
use zynq7000::{Peripherals, slcr::LevelShifterConfig};
use zynq7000_rt as _;
// Define the clock frequency as a constant
@@ -166,7 +166,7 @@ impl UartMultiplexer {
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
let mut dp = Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Enable PS-PL level shifters.
@@ -194,12 +194,12 @@ async fn main(spawner: Spawner) -> ! {
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)
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut log_uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
@@ -237,7 +237,7 @@ async fn main(spawner: Spawner) -> ! {
// UART0 routed through EMIO to PL pins.
let uart_0 =
Uart::new_with_emio(dp.uart_0, UartConfig::new_with_clk_config(uart_clk_config)).unwrap();
Uart::new_with_emio(dp.uart_0, Config::new_with_clk_config(uart_clk_config)).unwrap();
// Safety: Valid address of AXI UARTLITE.
let mut uartlite = unsafe { AxiUartlite::new(AXI_UARTLITE_BASE_ADDR) };
// We need to call this before splitting the structure, because the interrupt signal is
@@ -254,7 +254,7 @@ async fn main(spawner: Spawner) -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let mio_led = Output::new_for_mio(gpio_pins.mio.mio7, PinState::Low);

View File

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

View File

@@ -9,18 +9,8 @@ use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use log::{error, info};
use zedboard::PS_CLOCK_FREQUENCY;
use zynq7000_hal::{
BootMode,
clocks::Clocks,
configure_level_shifter,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gpio::{GpioPins, Output, PinState},
gtc::GlobalTimerCounter,
l2_cache,
uart::{ClockConfigRaw, Uart, UartConfig},
};
use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, uart};
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig};
use zynq7000_rt as _;
const INIT_STRING: &str = "-- Zynq 7000 Zedboard GPIO blinky example --\n\r";
@@ -37,35 +27,28 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! {
#[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);
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
init_l2_cache: true,
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0),
})
.unwrap();
// 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 mut gpio_pins = GpioPins::new(dp.gpio);
let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
let mut gpio_pins = gpio::GpioPins::new(periphs.gpio);
// Set up global timer counter and embassy time driver.
let gtc = GlobalTimerCounter::new(dp.gtc, clocks.arm_clocks());
let gtc = gtc::GlobalTimerCounter::new(periphs.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)
let uart_clk_config = uart::ClockConfig::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),
let mut uart = uart::Uart::new_with_mio(
periphs.uart_1,
uart::Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
@@ -79,21 +62,21 @@ 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));
let mut mio_led = Output::new_for_mio(gpio_pins.mio.mio7, PinState::Low);
let mut emio_leds: [Output; 8] = [
Output::new_for_emio(gpio_pins.emio.take(0).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(1).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(2).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(3).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(4).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(5).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(6).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(7).unwrap(), PinState::Low),
let mut mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
let mut emio_leds: [gpio::Output; 8] = [
gpio::Output::new_for_emio(gpio_pins.emio.take(0).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(1).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(2).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(3).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(4).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(5).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(6).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(7).unwrap(), gpio::PinState::Low),
];
loop {
mio_led.toggle().unwrap();
@@ -108,42 +91,42 @@ async fn main(_spawner: Spawner) -> ! {
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = gic::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 {
gic::Interrupt::Sgi(_) => (),
gic::Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
Interrupt::Spi(_spi_interrupt) => (),
Interrupt::Invalid(_) => (),
Interrupt::Spurious => (),
gic::Interrupt::Spi(_spi_interrupt) => (),
gic::Interrupt::Invalid(_) => (),
gic::Interrupt::Spurious => (),
}
gic_helper.end_of_interrupt(irq_info);
}
#[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();
}

5
justfile Normal file
View File

@@ -0,0 +1,5 @@
[working-directory: 'zynq-boot-image/staging']
bootgen:
bootgen -arch zynq -image boot.bif -o boot.bin -w on
echo "Generated boot.bin at zynq-boot-image/staging"

22
scripts/memory_ddr.x Normal file
View 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
View File

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

144
scripts/xsct-flasher.tcl Normal file
View 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"

View File

@@ -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"

View File

@@ -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
View File

@@ -0,0 +1,11 @@
[package]
name = "zedboard-bsp"
version = "0.1.0"
edition = "2024"
[dependencies]
zynq7000-hal = { path = "../zynq7000-hal" }
bitbybit = "1.4"
arbitrary-int = "2"
num_enum = { version = "0.7", default-features = false }
thiserror = { version = "2", default-features = false }

4
zedboard-bsp/src/lib.rs Normal file
View File

@@ -0,0 +1,4 @@
#![no_std]
pub mod phy_marvell;
pub mod qspi_spansion;

View File

@@ -0,0 +1,630 @@
use core::cell::RefCell;
use arbitrary_int::{prelude::*, u24};
use zynq7000_hal::qspi::{
FIFO_DEPTH, LinearQspiConfig, QspiIoMode, QspiIoTransferGuard, QspiLinearAddressing,
QspiLinearReadGuard,
};
pub const QSPI_DEV_COMBINATION_REV_F: zynq7000_hal::qspi::QspiDeviceCombination =
zynq7000_hal::qspi::QspiDeviceCombination {
vendor: zynq7000_hal::qspi::QspiVendor::WinbondAndSpansion,
operating_mode: zynq7000_hal::qspi::OperatingMode::FastReadQuadOutput,
two_devices: false,
};
#[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,
}
pub const PAGE_SIZE: usize = 256;
#[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),
#[error("data is larger than page size {PAGE_SIZE}")]
DataLargerThanPage,
}
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.
///
/// The data length max not exceed the page size [PAGE_SIZE].
pub fn program_page(&mut self, addr: u32, data: &[u8]) -> 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());
}
if data.len() > PAGE_SIZE {
return Err(ProgramPageError::DataLargerThanPage);
}
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;
let fifo_writes = data.len().div_ceil(4);
// Fill the FIFO until it is full.
for _ in 0..core::cmp::min(fifo_writes, 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;
}
};
while current_byte_index < data.len() {
// Immediately fill the FIFO again with the remaining 8 bytes.
wait_for_tx_slot(&mut transfer);
let word = match core::cmp::min(4, data.len() - current_byte_index) {
1 => {
let mut bytes = [0; 4];
bytes[0] = data[current_byte_index];
u32::from_ne_bytes(bytes)
}
2 => {
let mut bytes = [0; 4];
bytes[0..2].copy_from_slice(&data[current_byte_index..current_byte_index + 2]);
u32::from_ne_bytes(bytes)
}
3 => {
let mut bytes = [0; 4];
bytes[0..3].copy_from_slice(&data[current_byte_index..current_byte_index + 3]);
u32::from_ne_bytes(bytes)
}
4 => u32::from_ne_bytes(
data[current_byte_index..current_byte_index + 4]
.try_into()
.unwrap(),
),
_ => unreachable!(),
};
transfer.write_word_txd_00(word);
current_byte_index += 4;
}
while read_index < data.len() as u32 {
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 = bytes_to_write.div_ceil(4);
// 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 const BASE_ADDR: usize = QspiLinearAddressing::BASE_ADDRESS;
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()
}
}

View File

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

22
zedboard-fsbl/Cargo.toml Normal file
View File

@@ -0,0 +1,22 @@
[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 = "0.3"
zynq7000-rt = { path = "../zynq7000-rt" }
zynq7000 = { path = "../zynq7000" }
zynq7000-hal = { path = "../zynq7000-hal" }
zedboard-bsp = { path = "../zedboard-bsp" }
zynq-boot-image = { path = "../zynq-boot-image" }
embedded-io = "0.7"
embedded-hal = "1"
fugit = "0.3"
log = "0.4"
arbitrary-int = "2"

31
zedboard-fsbl/build.rs Normal file
View 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
View File

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

View 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(),
};

286
zedboard-fsbl/src/main.rs Normal file
View File

@@ -0,0 +1,286 @@
//! 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 zedboard_bsp::qspi_spansion::{self, QspiSpansionS25Fl256SLinearMode};
use zynq_boot_image::DestinationDevice;
use zynq7000_hal::{
BootMode,
clocks::{
Clocks,
pll::{PllConfig, configure_arm_pll, configure_io_pll},
},
ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest},
devcfg, gic, gpio, l2_cache,
prelude::*,
qspi::{self, QSPI_START_ADDRESS},
time::Hertz,
uart::{ClockConfig, Config, Uart},
};
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;
#[derive(Debug, PartialEq, Eq)]
pub enum BootMethod {
Qspi,
SdCard,
}
pub const BOOT_METHOD: BootMethod = BootMethod::Qspi;
pub const ELF_BASE_ADDR: usize = 0x100000;
/// 8 MB reserved for application ELF.
pub const BOOT_BIN_STAGING_OFFSET: usize = 8 * 1024 * 1024;
/// 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::Peripherals::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 = gpio::GpioPins::new(dp.gpio);
let mio_pins = gpio_pins.mio;
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = Uart::new_with_mio(
dp.uart_1,
Config::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 = 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.");
}
if BOOT_METHOD == BootMethod::Qspi {
let qspi_clock_config = qspi::ClockConfig::calculate_with_loopback(
zynq7000::slcr::clocks::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,
mio_pins.mio1,
(mio_pins.mio2, mio_pins.mio3, mio_pins.mio4, mio_pins.mio5),
mio_pins.mio6,
mio_pins.mio8,
);
let qspi_io_mode = qspi.into_io_mode(false);
let spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
let spansion_lqspi =
spansion_qspi.into_linear_addressed(qspi_spansion::QSPI_DEV_COMBINATION_REV_F.into());
qspi_boot(spansion_lqspi);
}
loop {
cortex_ar::asm::nop();
}
}
fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode) -> ! {
let boot_bin_base_addr = ELF_BASE_ADDR + BOOT_BIN_STAGING_OFFSET;
let mut boot_header_slice = unsafe {
core::slice::from_raw_parts_mut(
boot_bin_base_addr as *mut u8,
zynq_boot_image::FIXED_BOOT_HEADER_SIZE,
)
};
let read_guard = qspi.read_guard();
// Currently, only boot.bin at address 0x0 of the QSPI is supported.
unsafe {
core::ptr::copy_nonoverlapping(
QspiSpansionS25Fl256SLinearMode::BASE_ADDR as *mut u8,
boot_header_slice.as_mut_ptr(),
zynq_boot_image::FIXED_BOOT_HEADER_SIZE,
);
}
drop(read_guard);
let boot_header = zynq_boot_image::BootHeader::new(boot_header_slice).unwrap();
let fsbl_offset = boot_header.source_offset();
boot_header_slice =
unsafe { core::slice::from_raw_parts_mut(boot_bin_base_addr as *mut u8, fsbl_offset) };
// Read the rest of the boot header metadata.
let read_guard = qspi.read_guard();
unsafe {
core::ptr::copy_nonoverlapping(
(QspiSpansionS25Fl256SLinearMode::BASE_ADDR + zynq_boot_image::FIXED_BOOT_HEADER_SIZE)
as *mut u8,
boot_header_slice[zynq_boot_image::FIXED_BOOT_HEADER_SIZE..].as_mut_ptr(),
fsbl_offset - zynq_boot_image::FIXED_BOOT_HEADER_SIZE,
);
}
drop(read_guard);
let boot_header = zynq_boot_image::BootHeader::new_unchecked(boot_header_slice);
let mut name_buf: [u8; 256] = [0; 256];
for image_header in boot_header.image_header_iterator().unwrap() {
let name = image_header.image_name(&mut name_buf).unwrap();
for partition in image_header
.partition_header_iterator(boot_header_slice)
.unwrap()
{
let section_attrs = partition.section_attributes();
if let Ok(dest_dev) = section_attrs.destination_device() {
match dest_dev {
DestinationDevice::Pl => {
info!("Loading image '{name}' to PL (FPGA)..");
// Load the bitstream directly from linear mapped QSPI memory.
let boot_bin_slice = unsafe {
core::slice::from_raw_parts(
(QSPI_START_ADDRESS
+ partition
.data_offset()
.expect("invalid PL partition data offset"))
as *const _,
partition.total_partition_length().unwrap(),
)
};
devcfg::configure_bitstream_non_secure(true, boot_bin_slice)
.expect("unexpected unaligned address");
}
DestinationDevice::Ps => {
// TODO: Load the binary into DDR. Jump at lowest load address after all
// partitions were parsed.
}
_ => {
error!("Unsupported destination device {dest_dev:?}");
continue;
}
}
}
}
}
loop {
cortex_ar::asm::nop();
}
}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
error!("Panic: {info:?}");
loop {}
}

View File

@@ -0,0 +1,15 @@
[package]
name = "zedboard-qspi-flasher"
version = "0.1.0"
edition = "2024"
[dependencies]
cortex-ar = { version = "0.3" }
zynq7000-rt = { path = "../zynq7000-rt" }
zynq7000 = { path = "../zynq7000" }
zynq7000-hal = { path = "../zynq7000-hal" }
zynq-boot-image = { path = "../zynq-boot-image" }
zedboard-bsp = { path = "../zedboard-bsp" }
embedded-io = "0.7"
embedded-hal = "1"
log = "0.4"

View File

@@ -0,0 +1,150 @@
if {[info exists env(ip_address_hw_server)]} {
set ip $env(ip_address_hw_server)
} else {
set ip "localhost"
}
# Defaults
set init_tcl ""
set bin ""
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 " -b, --bin Path to boot binary to flash"
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 "-b" || $arg eq "--bin"} { set expecting app; 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 {$bin ne ""} {
if {![file exists $bin]} {
puts "error: the boot binary file '$bin' does not exist"
exit 1
}
} elseif {[info exists env(BOOTBIN)]} {
if {[file exists $env(BOOTBIN)]} {
set bin $env(BOOTBIN)
} else {
puts "warning: BOOTBIN environment variable is set but file does not exist: $env(BOOTBIN)"
}
}
if {$bin eq ""} {
puts "error: boot.bin binary required as BOOTBIN environment"
usage
exit 1
}
# 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
puts "Download boot.bin $bin to target DDR"
dow $bin
# TODO: Flash QSPI flasher.
puts "Starting app"
con
puts "Success"

View File

@@ -0,0 +1,219 @@
//! QSPI flasher for the Zedboard. Assumes that external scripting took care of transferring
//! a boot binary to RAM.
#![no_std]
#![no_main]
use core::panic::PanicInfo;
use cortex_ar::asm::nop;
use embedded_hal::{delay::DelayNs as _, digital::StatefulOutputPin as _};
use embedded_io::Write as _;
use log::{error, info};
use zedboard_bsp::qspi_spansion;
use zynq_boot_image::BootHeader;
use zynq7000_hal::{
BootMode, LevelShifterConfig, clocks, gpio, prelude::*, priv_tim, qspi, time::Hertz, uart,
};
use zynq7000_rt as _;
// 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);
// TODO: Make this configurable somehow?
const BOOT_BIN_BASE_ADDR: usize = 0x1000_0000;
const BOOT_BIN_SIZE_ADDR: usize = 0x900_000;
// Maximum of 16 MB is allowed for now.
const MAX_BOOT_BIN_SIZE: usize = 16 * 1024 * 1024;
const VERIFY_PROGRAMMING: bool = true;
#[allow(dead_code)]
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 INIT_STRING: &str = "-- Zynq 7000 Zedboard QSPI flasher --\n\r";
#[unsafe(export_name = "main")]
pub fn main() -> ! {
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
init_l2_cache: true,
level_shifter_config: Some(LevelShifterConfig::EnableAll),
interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0),
})
.unwrap();
let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Unwrap okay, we only call this once on core 0 here.
let mut timer = priv_tim::CpuPrivateTimer::take(clocks.arm_clocks()).unwrap();
let gpio_pins = gpio::GpioPins::new(periphs.gpio);
// Set up the UART, we are logging with it.
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = uart::Uart::new_with_mio(
periphs.uart_1,
uart::Config::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(qspi::SrcSelIo::IoPll, &clocks, 100.MHz())
.expect("QSPI clock calculation failed");
let qspi = qspi::Qspi::new_single_qspi_with_feedback(
periphs.qspi,
qspi_clock_config,
qspi::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 mut boot_bin_slice = unsafe {
core::slice::from_raw_parts(BOOT_BIN_BASE_ADDR as *const _, BootHeader::FIXED_SIZED_PART)
};
// This perform some basic validity checks.
let _boot_header = BootHeader::new(&boot_bin_slice[0..BootHeader::FIXED_SIZED_PART])
.expect("failed to parse boot header");
let boot_bin_size =
unsafe { core::ptr::read_volatile(BOOT_BIN_SIZE_ADDR as *const u32) as usize };
if boot_bin_size == 0 || boot_bin_size > MAX_BOOT_BIN_SIZE {
panic!(
"boot binary size read at address {:#x} is invalid: found {}, must be in range [0, {}]",
BOOT_BIN_SIZE_ADDR, boot_bin_size, MAX_BOOT_BIN_SIZE
);
}
boot_bin_slice =
unsafe { core::slice::from_raw_parts(BOOT_BIN_BASE_ADDR as *const _, boot_bin_size) };
info!(
"flashing boot binary with {} bytes to QSPI address 0x0",
boot_bin_size
);
let mut current_addr = 0;
let mut read_buf = [0u8; 256];
while current_addr < boot_bin_size {
if current_addr % 0x10000 == 0 {
info!("Erasing sector at address {:#x}", current_addr);
match spansion_qspi.erase_sector(current_addr as u32) {
Ok(()) => {}
Err(e) => {
error!(
"failed to erase sector at address {:#x}: {:?}",
current_addr, e
);
panic!("QSPI erase failed");
}
}
}
let write_size = core::cmp::min(256, boot_bin_size - current_addr);
let write_slice = &boot_bin_slice[current_addr..current_addr + write_size];
info!("Programming address {:#x}", current_addr);
match spansion_qspi.program_page(current_addr as u32, write_slice) {
Ok(()) => {}
Err(e) => {
error!(
"failed to write data to QSPI at address {:#x}: {:?}",
current_addr, e
);
panic!("QSPI write failed");
}
}
if VERIFY_PROGRAMMING {
spansion_qspi.read_page_fast_read(
current_addr as u32,
&mut read_buf[0..write_size],
true,
);
if &read_buf[0..write_size] != write_slice {
error!(
"data verification failed at address {:#x}: wrote {:x?}, read {:x?}",
current_addr,
&write_slice[0..core::cmp::min(16, write_size)],
&read_buf[0..core::cmp::min(16, write_size)]
);
panic!("QSPI data verification failed");
}
}
current_addr += write_size;
}
info!("flashing done");
let mut mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
loop {
mio_led.toggle().unwrap();
timer.delay_ms(500);
}
}
#[zynq7000_rt::irq]
pub fn irq_handler() {}
#[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) -> ! {
loop {
nop();
}
}

View File

@@ -0,0 +1,9 @@
[package]
name = "zynq-boot-image"
version = "0.1.0"
edition = "2024"
[dependencies]
thiserror = { version = "2", default-features = false }
arbitrary-int = "2"
bitbybit = "1.4"

437
zynq-boot-image/src/lib.rs Normal file
View File

@@ -0,0 +1,437 @@
#![no_std]
use core::str::Utf8Error;
/// 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;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum InvalidBootHeader {
#[error("image ID invalid")]
ImageIdInvalid,
#[error("checksum is invalid")]
ChecksumInvalid,
#[error("provided data slice too small")]
DataTooSmall,
}
pub struct BootHeader<'a> {
//base_addr: usize,
// Snapshot of the boot header and following data (at least fixed header size)
data: &'a [u8],
}
impl<'a> BootHeader<'a> {
pub const FIXED_SIZED_PART: usize = FIXED_BOOT_HEADER_SIZE;
/// Create a new boot header parser structure without performing any additional checks.
pub const fn new_unchecked(data: &'a [u8]) -> Self {
BootHeader { data }
}
/// Create a new boot header parser structure while also performing any additonal checks.
///
/// The passed buffer must have a minimal size of [Self::FIXED_SIZED_PART].
/// This constructor calls [Self::check_image_id_validity] and [Self::verify_header_checksum]
/// to verify whether the boot header structure is actually valid.
pub fn new(data: &'a [u8]) -> Result<Self, InvalidBootHeader> {
if data.len() < Self::FIXED_SIZED_PART {
return Err(InvalidBootHeader::DataTooSmall);
}
let header = BootHeader { data };
if !header.check_image_id_validity() {
return Err(InvalidBootHeader::ImageIdInvalid);
}
if !header.verify_header_checksum() {
return Err(InvalidBootHeader::ChecksumInvalid);
}
Ok(header)
}
#[inline]
fn read_u32_le(&self, offset: usize) -> u32 {
let bytes = &self.data[offset..offset + 4];
u32::from_le_bytes(bytes.try_into().unwrap())
}
#[inline]
pub fn image_id(&self) -> u32 {
self.read_u32_le(0x24)
}
/// Check whether the image ID has the mandatory [IMAGE_ID_U32] value.
#[inline]
pub fn check_image_id_validity(&self) -> bool {
self.image_id() == IMAGE_ID_U32
}
/// Offset to the FSBL image in bytes. This information can be used to only extract the boot
/// binary metadata (everything except actual partition data).
#[inline]
pub fn source_offset(&self) -> usize {
self.read_u32_le(0x30) as usize
}
#[inline]
pub fn header_checksum(&self) -> u32 {
self.read_u32_le(0x48)
}
#[inline]
pub fn image_header_table_offset(&self) -> usize {
self.read_u32_le(0x98) as usize
}
pub fn image_header_table(&self) -> Option<ImageHeaderTable<'a>> {
let offset = self.image_header_table_offset();
if offset + ImageHeaderTable::SIZE > self.data.len() {
return None;
}
Some(
ImageHeaderTable::new(
&self.data[self.image_header_table_offset()
..self.image_header_table_offset() + ImageHeaderTable::SIZE],
)
.unwrap(),
)
}
pub fn image_header_iterator(&self) -> Option<ImageHeaderIterator<'a>> {
let first_header_offset = self.image_header_table()?.first_image_header_offset()?;
ImageHeaderIterator::new(self.data, first_header_offset)
}
#[inline]
pub fn partition_header_table_offset(&self) -> usize {
self.read_u32_le(0x9C) as usize
}
#[inline]
pub fn verify_header_checksum(&self) -> bool {
let checksum = self.header_checksum();
let mut sum = 0u32;
let mut ofs = 0x20;
while ofs < 0x48 {
sum = sum.wrapping_add(self.read_u32_le(ofs));
ofs += 4;
}
!sum == checksum
}
}
pub struct ImageHeaderTable<'a> {
data: &'a [u8],
}
impl<'a> ImageHeaderTable<'a> {
pub const SIZE: usize = 0x18;
pub const fn new(data: &'a [u8]) -> Option<Self> {
if data.len() != Self::SIZE {
return None;
}
Some(ImageHeaderTable { data })
}
#[inline]
fn read_u32_le(&self, offset: usize) -> u32 {
let bytes = &self.data[offset..offset + 4];
u32::from_le_bytes(bytes.try_into().unwrap())
}
#[inline]
pub fn count_of_headers(&self) -> usize {
self.read_u32_le(0x04) as usize
}
/// Returns [None] if the number of words times 4 exeeds [u32::MAX].
#[inline]
pub fn first_image_header_offset(&self) -> Option<usize> {
self.read_u32_le(0x0C).checked_mul(4).map(|v| v as usize)
}
/// Returns [None] if the number of words times 4 exeeds [u32::MAX].
#[inline]
pub fn first_partition_header_offset(&self) -> Option<usize> {
self.read_u32_le(0x08).checked_mul(4).map(|v| v as usize)
}
}
pub struct ImageHeaderIterator<'a> {
data: &'a [u8],
current_header_offset: usize,
}
impl<'a> ImageHeaderIterator<'a> {
#[inline]
pub const fn new(data: &'a [u8], first_header_offset: usize) -> Option<Self> {
if first_header_offset + ImageHeader::MIN_SIZE > data.len() {
return None;
}
Some(ImageHeaderIterator {
data,
current_header_offset: first_header_offset,
})
}
}
impl<'a> Iterator for ImageHeaderIterator<'a> {
type Item = ImageHeader<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.current_header_offset == 0 {
return None;
}
let next_image_header = ImageHeader::new(&self.data[self.current_header_offset..])?;
self.current_header_offset = next_image_header.next_image_header_offset()?;
Some(next_image_header)
}
}
pub struct ImageHeader<'a> {
header_data: &'a [u8],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("buffer too small")]
pub struct BufferTooSmallError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum NameParsingError {
#[error("ut8 error")]
Utf8(#[from] Utf8Error),
#[error("buffer too small")]
BufferTooSmall(#[from] BufferTooSmallError),
}
impl<'a> ImageHeader<'a> {
pub const MIN_SIZE: usize = 0x1C;
#[inline]
pub fn new(data: &'a [u8]) -> Option<Self> {
if data.len() < Self::MIN_SIZE {
return None;
}
let mut current_offset = 0x14;
let mut prev_word =
u32::from_le_bytes(data[current_offset..current_offset + 4].try_into().unwrap());
current_offset += 4;
// TODO: Upper bound.
loop {
if current_offset + 4 > data.len() {
return None;
}
let current_word =
u32::from_le_bytes(data[current_offset..current_offset + 4].try_into().unwrap());
current_offset += 4;
if current_word == 0xffff_ffff || prev_word == 0x0000_0000 {
break;
}
prev_word = current_word;
}
Some(ImageHeader {
header_data: &data[0..current_offset],
})
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.header_data.len()
}
#[inline]
fn read_u32_le(&self, offset: usize) -> u32 {
let bytes = &self.header_data[offset..offset + 4];
u32::from_le_bytes(bytes.try_into().unwrap())
}
pub fn partition_header_iterator(&self, data: &'a [u8]) -> Option<PartitionHeaderIterator<'a>> {
let first_partition_header = self.first_partition_header_offset()?;
Some(PartitionHeaderIterator {
data,
current_partition_header_addr: first_partition_header,
current_partition_index: 0,
num_of_partitions: self.partition_count(),
})
}
#[inline]
pub fn next_image_header_offset(&self) -> Option<usize> {
self.read_u32_le(0x00).checked_mul(4).map(|v| v as usize)
}
#[inline]
pub fn partition_count(&self) -> usize {
self.read_u32_le(0x0C) as usize
}
pub fn first_partition_header_offset(&self) -> Option<usize> {
self.read_u32_le(0x04).checked_mul(4).map(|v| v as usize)
}
pub fn image_name_copied(&self, buf: &mut [u8]) -> Result<usize, BufferTooSmallError> {
let mut current_offset = 0x10;
let mut current_buf_idx = 0;
let mut null_byte_found = false;
loop {
let next_bytes = &self.header_data[current_offset..current_offset + 4];
for &byte in next_bytes.iter().rev() {
if byte == 0 {
null_byte_found = true;
break;
}
if current_buf_idx >= buf.len() {
return Err(BufferTooSmallError);
}
buf[current_buf_idx] = byte;
current_buf_idx += 1;
}
if null_byte_found {
break;
}
current_offset += 4;
}
Ok(current_buf_idx)
}
pub fn image_name<'b>(&self, buf: &'b mut [u8]) -> Result<&'b str, NameParsingError> {
let name_len = self.image_name_copied(buf)?;
core::str::from_utf8(&buf[0..name_len]).map_err(NameParsingError::from)
}
}
pub struct PartitionHeaderIterator<'a> {
data: &'a [u8],
current_partition_header_addr: usize,
current_partition_index: usize,
num_of_partitions: usize,
}
impl<'a> Iterator for PartitionHeaderIterator<'a> {
type Item = PartitionHeader<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.current_partition_index >= self.num_of_partitions {
return None;
}
if self.current_partition_header_addr + PartitionHeader::SIZE > self.data.len() {
return None;
}
let header = PartitionHeader::new(
&self.data[self.current_partition_header_addr
..self.current_partition_header_addr + PartitionHeader::SIZE],
)
.ok()?;
self.current_partition_index += 1;
self.current_partition_header_addr += 0x40;
Some(header)
}
}
pub struct PartitionHeader<'a> {
header_data: &'a [u8],
}
#[bitbybit::bitenum(u2, exhaustive = false)]
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum PartitionOwner {
Fsbl = 0,
Uboot = 1,
}
#[bitbybit::bitenum(u3, exhaustive = false)]
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ChecksumType {
None = 0,
Md5 = 1,
}
#[bitbybit::bitenum(u4, exhaustive = false)]
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DestinationDevice {
None = 0,
Ps = 1,
Pl = 2,
Int = 3,
}
#[bitbybit::bitfield(u32, debug)]
pub struct SectionAttributes {
#[bits(16..=17, rw)]
partition_owner: Option<PartitionOwner>,
#[bit(15, rw)]
rsa_signature_present: bool,
#[bits(12..=14, rw)]
checksum_type: Option<ChecksumType>,
#[bits(4..=7, rw)]
destination_device: Option<DestinationDevice>,
}
impl<'a> PartitionHeader<'a> {
pub const SIZE: usize = 0x40;
// TODO: Checksum check.
#[inline]
pub const fn new(header_data: &'a [u8]) -> Result<Self, BufferTooSmallError> {
if header_data.len() < Self::SIZE {
return Err(BufferTooSmallError);
}
Ok(PartitionHeader { header_data })
}
#[inline]
fn read_u32_le(&self, offset: usize) -> u32 {
let bytes = &self.header_data[offset..offset + 4];
u32::from_le_bytes(bytes.try_into().unwrap())
}
#[inline]
pub fn encrypted_partition_length(&self) -> u32 {
self.read_u32_le(0x00)
}
#[inline]
pub fn unencrypted_partition_length(&self) -> u32 {
self.read_u32_le(0x04)
}
#[inline]
pub fn total_partition_length(&self) -> Option<usize> {
self.read_u32_le(0x08).checked_mul(4).map(|v| v as usize)
}
#[inline]
pub fn destination_load_address(&self) -> u32 {
self.read_u32_le(0x0C)
}
#[inline]
pub fn destination_exec_address(&self) -> u32 {
self.read_u32_le(0x10)
}
#[inline]
pub fn section_attributes(&self) -> SectionAttributes {
SectionAttributes::new_with_raw_value(self.section_attributes_raw())
}
#[inline]
pub fn section_attributes_raw(&self) -> u32 {
self.read_u32_le(0x18)
}
#[inline]
pub fn section_count(&self) -> usize {
self.read_u32_le(0x1C) as usize
}
#[inline]
pub fn data_offset(&self) -> Option<usize> {
self.read_u32_le(0x14).checked_mul(4).map(|v| v as usize)
}
}

4
zynq-boot-image/staging/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/fsbl.elf
/fpga.bit
/application.elf
/boot.bin

View File

@@ -0,0 +1,28 @@
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:
```sh
bootgen -arch zynq -image boot.bif -o boot.bin -w on
```

View File

@@ -0,0 +1,6 @@
all:
{
[bootloader] fsbl.elf
fpga.bit
application.elf
}

1
zynq-boot-image/tester/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

305
zynq-boot-image/tester/Cargo.lock generated Normal file
View File

@@ -0,0 +1,305 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "anstream"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "arbitrary-int"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "825297538d77367557b912770ca3083f778a196054b3ee63b22673c4a3cae0a5"
[[package]]
name = "arbitrary-int"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c858caffa49edfc4ecc45a4bec37abd3e88041a2903816f10f990b7b41abc281"
[[package]]
name = "bitbybit"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec187a89ab07e209270175faf9e07ceb2755d984954e58a2296e325ddece2762"
dependencies = [
"arbitrary-int 1.3.0",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap"
version = "4.5.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "proc-macro2"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tester"
version = "0.1.0"
dependencies = [
"clap",
"zynq-boot-image",
]
[[package]]
name = "thiserror"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "zynq-boot-image"
version = "0.1.0"
dependencies = [
"arbitrary-int 2.0.0",
"bitbybit",
"thiserror",
]

View File

@@ -0,0 +1,10 @@
[workspace]
[package]
name = "tester"
version = "0.1.0"
edition = "2024"
[dependencies]
zynq-boot-image= { path = ".." }
clap = { version = "4", features = ["derive"] }

View File

@@ -0,0 +1,82 @@
//! Small tester app to verify some of the API offers by the zynq-boot-image crate.
use std::{io::Read, path::Path};
use clap::Parser as _;
use zynq_boot_image::{BootHeader, FIXED_BOOT_HEADER_SIZE};
#[derive(clap::Parser, Debug)]
#[command(version, about)]
pub struct Cli {
/// Path to boot.bin file to test.
#[arg(short, long)]
path: String,
}
fn main() {
let cli = Cli::parse();
let boot_bin = Path::new(&cli.path);
if !boot_bin.exists() {
eprintln!("File not found: {}", boot_bin.display());
std::process::exit(1);
}
let mut boot_bin_file = std::fs::File::open(boot_bin).expect("failed to open boot.bin file");
let mut header_buf = Box::new([0u8; 8192]);
boot_bin_file
.read_exact(&mut header_buf[0..FIXED_BOOT_HEADER_SIZE])
.expect("failed to read boot header");
let mut boot_header = BootHeader::new(&header_buf[0..FIXED_BOOT_HEADER_SIZE])
.expect("failed to parse boot header");
let source_offset = boot_header.source_offset();
boot_bin_file
.read_exact(&mut header_buf[FIXED_BOOT_HEADER_SIZE..source_offset - FIXED_BOOT_HEADER_SIZE])
.expect("failed to read full boot binary metadata");
// Re-assign with newly read data.
boot_header = BootHeader::new_unchecked(&header_buf[0..source_offset]);
let image_header_table = boot_header
.image_header_table()
.expect("failed extracting image header table");
let image_headers = image_header_table.count_of_headers();
println!(
"Image headers: {}, first image header offset {}, first partition header offset {}",
image_headers,
image_header_table
.first_image_header_offset()
.expect("failed reading first image header offset"),
image_header_table
.first_partition_header_offset()
.expect("failed reading first partition header offset")
);
let image_header_iter = boot_header
.image_header_iterator()
.expect("failed extracting boot header iterator");
for (idx, image_header) in image_header_iter.enumerate() {
println!("--------------------------------------");
println!(
"Image header {} with partition count {}",
idx,
image_header.partition_count()
);
let mut test: [u8; 64] = [0; 64];
let image_name = image_header
.image_name(&mut test)
.expect("image name error");
println!("image name: {}", image_name);
let partition_iter = image_header
.partition_header_iterator(header_buf.as_slice())
.unwrap();
if image_header.partition_count() > 0 {
println!("--------------------------------------");
}
for partition in partition_iter {
println!(
"partition with size {} and load address {:#08x}, section count {}",
partition.total_partition_length().unwrap(),
partition.destination_load_address(),
partition.section_count()
);
println!("section attributes: {:?}", partition.section_attributes());
}
println!("--------------------------------------\n\r");
}
}

View File

@@ -6,7 +6,7 @@ edition = "2024"
[dependencies]
thiserror = { version = "2", default-features = false }
cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2" }
cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", branch = "bump-arbitrary-int" }
[features]
tools = []

View File

@@ -11,20 +11,21 @@ keywords = ["no-std", "hal", "amd", "zynq7000", "xilinx", "bare-metal"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2" }
cortex-ar = { version = "0.3" }
zynq7000 = { path = "../zynq7000" }
zynq-mmu = { path = "../zynq-mmu", version = "0.1.0" }
bitbybit = "1.3"
arbitrary-int = "1.3"
static_assertions = "1.1"
bitbybit = "1.4"
arbitrary-int = "2"
thiserror = { version = "2", default-features = false }
num_enum = { version = "0.7", default-features = false }
ringbuf = { version = "0.4.8", default-features = false }
embedded-hal-nb = "1"
embedded-io = "0.6"
embedded-io = "0.7"
embedded-hal = "1"
embedded-hal-async = "1"
heapless = "0.8"
heapless = "0.9"
static_cell = "2"
delegate = "0.13"
paste = "1"
@@ -38,7 +39,7 @@ embassy-net-driver = "0.2"
smoltcp = { version = "0.12", default-features = false, features = ["proto-ipv4", "medium-ethernet", "socket-raw"] }
vcell = "0.1"
raw-slicee = "0.1"
embedded-io-async = "0.6"
embedded-io-async = "0.7"
[features]
std = ["thiserror/std", "alloc"]

View File

@@ -1,5 +1,7 @@
//! Clock module.
use arbitrary_int::Number;
use arbitrary_int::{prelude::*, 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
}
@@ -178,13 +226,16 @@ pub enum ClockModuleId {
#[derive(Debug)]
pub struct DivisorZero(pub ClockModuleId);
#[derive(Debug)]
#[derive(Debug, thiserror::Error)]
pub enum ClockReadError {
/// The feedback value for the PLL clock output calculation is zero.
#[error("PLL feedback divisor is zero")]
PllFeedbackZero,
/// Detected a divisor of zero.
#[error("divisor is zero")]
DivisorZero(DivisorZero),
/// Detected a divisor that is not even.
/// Detected a divisor that is not even and should be.
#[error("divisor is not even")]
DivisorNotEven,
}
@@ -201,9 +252,9 @@ impl Clocks {
pub fn new_from_regs(ps_clk_freq: Hertz) -> Result<Self, ClockReadError> {
let mut clk_regs = unsafe { ClockControl::new_mmio_fixed() };
let arm_pll_cfg = clk_regs.read_arm_pll();
let io_pll_cfg = clk_regs.read_io_pll();
let ddr_pll_cfg = clk_regs.read_ddr_pll();
let arm_pll_cfg = clk_regs.read_arm_pll_ctrl();
let io_pll_cfg = clk_regs.read_io_pll_ctrl();
let ddr_pll_cfg = clk_regs.read_ddr_pll_ctrl();
if arm_pll_cfg.fdiv().as_u32() == 0
|| io_pll_cfg.fdiv().as_u32() == 0

View File

@@ -0,0 +1,356 @@
use core::sync::atomic::AtomicBool;
use arbitrary_int::{u4, u7, u10};
use crate::{BootMode, time::Hertz};
/// Minimal value based on Zynq-7000 TRM Table 25-6, p.744
pub const PLL_MUL_MIN: u32 = 13;
/// Maximum value based on Zynq-7000 TRM Table 25-6, p.744
pub const PLL_MUL_MAX: u32 = 66;
static ARM_PLL_INIT: AtomicBool = AtomicBool::new(false);
static IO_PLL_INIT: AtomicBool = AtomicBool::new(false);
static DDR_PLL_INIT: AtomicBool = AtomicBool::new(false);
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
#[error("pll muliplier value {0} is out of range ({PLL_MUL_MIN}..={PLL_MUL_MAX})")]
pub struct MulOutOfRangeError(pub u32);
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum PllConfigCtorError {
#[error("invalid input")]
InvalidInput,
#[error("pll multiplier out of range: {0}")]
MulOutOfRange(#[from] MulOutOfRangeError),
}
pub struct PllConfig {
fdiv: u7,
charge_pump: u4,
loop_resistor: u4,
lock_count: u10,
}
impl PllConfig {
pub fn new_from_target_clock(
ps_clk: Hertz,
target_clk: Hertz,
) -> Result<Self, PllConfigCtorError> {
if ps_clk.raw() == 0 {
return Err(PllConfigCtorError::InvalidInput);
}
let mul = target_clk / ps_clk;
Self::new(mul).map_err(PllConfigCtorError::MulOutOfRange)
}
/// Create a new PLL configuration based on the multiplier value.
///
/// These configuration values are based on the Zynq-7000 TRM Table 25-6, p.744.
pub fn new(pll_mul: u32) -> Result<Self, MulOutOfRangeError> {
if !(PLL_MUL_MIN..=PLL_MUL_MAX).contains(&pll_mul) {
return Err(MulOutOfRangeError(pll_mul));
}
Ok(match pll_mul {
13 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(6),
u10::new(750),
),
14 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(6),
u10::new(700),
),
15 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(6),
u10::new(650),
),
16 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(10),
u10::new(625),
),
17 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(10),
u10::new(575),
),
18 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(10),
u10::new(550),
),
19 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(10),
u10::new(525),
),
20 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(500),
),
21 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(475),
),
22 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(450),
),
23 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(425),
),
24..=25 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(400),
),
26 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(375),
),
27..=28 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(350),
),
29..=30 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(325),
),
31..=33 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(2),
u10::new(300),
),
34..=36 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(2),
u10::new(275),
),
37..=40 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(2),
u10::new(250),
),
41..=47 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(3),
u4::new(12),
u10::new(250),
),
48..=66 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(4),
u10::new(250),
),
_ => {
unreachable!()
}
})
}
/// Create a new PLL configuration with raw values.
///
/// It is recommended to use [Self::new] instead, which creates a configuration
/// based on a look-up table provided in the Zynq-7000 TRM.
pub fn new_raw(fdiv: u7, charge_pump: u4, loop_resistor: u4, lock_count: u10) -> Self {
Self {
fdiv,
charge_pump,
loop_resistor,
lock_count,
}
}
}
/// This function configures the ARM PLL based on the provided [PllConfig].
pub fn configure_arm_pll(boot_mode: BootMode, pll_config: PllConfig) {
if ARM_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
return;
}
// Safety: This will only run at most once because of the atomic boolean check.
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
}
/// This function configures the IO PLL based on the provided [PllConfig].
pub fn configure_io_pll(boot_mode: BootMode, pll_config: PllConfig) {
if IO_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
return;
}
// Safety: This will only run at most once because of the atomic boolean check.
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
}
/// This function configures the DDR PLL based on the provided [PllConfig].
pub fn configure_ddr_pll(boot_mode: BootMode, pll_config: PllConfig) {
if DDR_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
return;
}
// Safety: This will only run at most once because of the atomic boolean check.
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
}
/// This function configures the ARM PLL 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
View File

@@ -0,0 +1,334 @@
//! Low-level DDR configuration module.
use arbitrary_int::{prelude::*, 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
View 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(())
}
}

View File

@@ -0,0 +1,64 @@
#[derive(Debug, thiserror::Error)]
#[error("unaligned address: {0}")]
pub struct UnalignedAddrError(usize);
/// Configures the bitstream using the PCAP interface in non-secure mode.
///
/// Blocking function which only returns when the bitstream configuration is complete.
pub fn configure_bitstream_non_secure(
init_pl: bool,
bitstream: &[u8],
) -> Result<(), UnalignedAddrError> {
if !(bitstream.as_ptr() as usize).is_multiple_of(64) {
return Err(UnalignedAddrError(bitstream.as_ptr() as usize));
}
if bitstream.is_empty() {
return Ok(());
}
let mut devcfg = unsafe { zynq7000::devcfg::DevCfg::new_mmio_fixed() };
devcfg.modify_control(|mut val| {
val.set_config_access_select(zynq7000::devcfg::PlConfigAccess::ConfigAccessPort);
val.set_access_port_select(zynq7000::devcfg::ConfigAccessPortSelect::Pcap);
val
});
devcfg.write_interrupt_status(zynq7000::devcfg::Interrupt::new_with_raw_value(0xFFFF_FFFF));
if init_pl {
devcfg.modify_control(|mut val| {
val.set_prog_b_bit(true);
val
});
devcfg.modify_control(|mut val| {
val.set_prog_b_bit(false);
val
});
while devcfg.read_status().pcfg_init() {}
devcfg.modify_control(|mut val| {
val.set_prog_b_bit(true);
val
});
devcfg.write_interrupt_status(
zynq7000::devcfg::Interrupt::ZERO.with_pl_programming_done(true),
);
}
while !devcfg.read_status().pcfg_init() {}
if !init_pl {
while devcfg.read_status().dma_command_queue_full() {}
}
devcfg.modify_misc_control(|mut val| {
val.set_loopback(false);
val
});
devcfg.modify_control(|mut val| {
val.set_pcap_rate_enable(false);
val
});
devcfg.write_dma_source_addr(bitstream.as_ptr() as u32);
devcfg.write_dma_dest_addr(0xFFFF_FFFF);
devcfg.write_dma_source_len(bitstream.len() as u32);
devcfg.write_dma_dest_len(bitstream.len() as u32);
while !devcfg.read_interrupt_status().dma_done() {}
// TODO: Check for errors.
while !devcfg.read_interrupt_status().pl_programming_done() {}
Ok(())
}

View File

@@ -1,10 +1,10 @@
use arbitrary_int::{Number, u6};
use arbitrary_int::{prelude::*, u6};
use zynq7000::{
eth::{InterruptControl, NetworkControl, RxStatus, TxStatus},
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].

View File

@@ -105,132 +105,132 @@ impl PsEthernet for MmioEthernet<'static> {
}
}
pub trait TxClk: MioPin {
pub trait TxClockPin: MioPin {
const ETH_ID: EthernetId;
}
pub trait TxCtrl: MioPin {
pub trait TxControlPin: MioPin {
const ETH_ID: EthernetId;
}
pub trait TxData0: MioPin {
pub trait TxData0Pin: MioPin {
const ETH_ID: EthernetId;
}
pub trait TxData1: MioPin {
pub trait TxData1Pin: MioPin {
const ETH_ID: EthernetId;
}
pub trait TxData2: MioPin {
pub trait TxData2Pin: MioPin {
const ETH_ID: EthernetId;
}
pub trait TxData3: MioPin {
pub trait TxData3Pin: MioPin {
const ETH_ID: EthernetId;
}
pub trait RxClk: MioPin {
pub trait RxClockPin: MioPin {
const ETH_ID: EthernetId;
}
pub trait RxCtrl: MioPin {
pub trait RxControlPin: MioPin {
const ETH_ID: EthernetId;
}
pub trait RxData0: MioPin {
pub trait RxData0Pin: MioPin {
const ETH_ID: EthernetId;
}
pub trait RxData1: MioPin {
pub trait RxData1Pin: MioPin {
const ETH_ID: EthernetId;
}
pub trait RxData2: MioPin {
pub trait RxData2Pin: MioPin {
const ETH_ID: EthernetId;
}
pub trait RxData3: MioPin {
pub trait RxData3Pin: MioPin {
const ETH_ID: EthernetId;
}
pub trait MdClk: MioPin {}
pub trait MdIo: MioPin {}
pub trait MdClockPin: MioPin {}
pub trait MdIoPin: MioPin {}
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()

View File

@@ -4,7 +4,7 @@ use core::{cell::UnsafeCell, mem::MaybeUninit, sync::atomic::AtomicBool};
use crate::{cache::clean_and_invalidate_data_cache_range, eth::AlignedBuffer};
pub use super::shared::Ownership;
use arbitrary_int::{Number, u2, u3, u13, u30};
use arbitrary_int::{prelude::*, u2, u3, u13, u30};
use vcell::VolatileCell;
static RX_DESCR_TAKEN: AtomicBool = AtomicBool::new(false);

View File

@@ -6,7 +6,7 @@
//! # Examples
//!
//! - [GTC ticks](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/examples/simple/src/bin/gtc-ticks.rs)
use arbitrary_int::Number;
use arbitrary_int::prelude::*;
use cortex_ar::interrupt;
use zynq7000::gic::{

View File

@@ -174,7 +174,7 @@ pin_id!(Mio51, 51);
pin_id!(Mio52, 52);
pin_id!(Mio53, 53);
pub trait MioPin {
pub trait MioPin: crate::sealed::Sealed {
fn offset(&self) -> usize;
}
@@ -379,3 +379,5 @@ impl<I: PinId> MioPin for Pin<I> {
I::OFFSET
}
}
impl<I: PinId> crate::sealed::Sealed for Pin<I> {}

View File

@@ -17,7 +17,7 @@ use ll::PinOffset;
use mio::{MioPin, 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),

View File

@@ -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() {

View File

@@ -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()

View File

@@ -13,10 +13,15 @@
extern crate alloc;
use slcr::Slcr;
use zynq7000::slcr::LevelShifterRegister;
use zynq7000::{
SpiClockPhase, SpiClockPolarity,
slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister},
};
pub mod cache;
pub mod clocks;
pub mod ddr;
pub mod devcfg;
pub mod eth;
pub mod gic;
pub mod gpio;
@@ -25,12 +30,65 @@ 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;
pub mod ttc;
pub mod uart;
pub use zynq7000 as pac;
pub use zynq7000::slcr::LevelShifterConfig;
#[derive(Debug, thiserror::Error)]
pub enum InitError {
#[error("peripheral singleton was already taken")]
PeripheralsAlreadyTaken,
}
#[derive(Debug)]
pub enum InteruptConfig {
/// GIC is configured to route all interrupts to CPU0. Suitable if the software handles all
/// the interrupts and only runs on CPU0.
AllInterruptsToCpu0,
}
#[derive(Debug)]
pub struct Config {
pub init_l2_cache: bool,
/// If this has some value, it will configure the level shifter between PS and PL.
pub level_shifter_config: Option<LevelShifterConfig>,
/// If this has some value, it configures the GIC to pre-defined settings.
pub interrupt_config: Option<InteruptConfig>,
}
/// Utility function to perform common initialization steps.
pub fn init(config: Config) -> Result<zynq7000::Peripherals, InitError> {
let mut periphs = zynq7000::Peripherals::take().ok_or(InitError::PeripheralsAlreadyTaken)?;
if config.init_l2_cache {
l2_cache::init_with_defaults(&mut periphs.l2c);
}
if let Some(config) = config.level_shifter_config {
configure_level_shifter(config);
}
if let Some(interrupt_config) = config.interrupt_config {
let mut gic = gic::GicConfigurator::new_with_init(periphs.gicc, periphs.gicd);
match interrupt_config {
InteruptConfig::AllInterruptsToCpu0 => {
gic.enable_all_interrupts();
gic.set_all_spi_interrupt_targets_cpu0();
}
}
gic.enable();
unsafe {
gic.enable_interrupts();
}
}
Ok(unsafe { zynq7000::Peripherals::steal() })
}
/// This enumeration encodes the various boot sources.
#[derive(Debug, Copy, Clone)]
pub enum BootDevice {
@@ -43,36 +101,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 +132,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
}
}
@@ -107,7 +151,7 @@ impl BootMode {
/// system (PS).
///
/// The Zynq-7000 TRM p.32 specifies more information about this register and how to use it.
pub fn configure_level_shifter(config: zynq7000::slcr::LevelShifterConfig) {
pub fn configure_level_shifter(config: LevelShifterConfig) {
// Safety: We only manipulate the level shift registers.
unsafe {
Slcr::with(|slcr_unlocked| {
@@ -142,7 +186,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,7 +249,30 @@ pub fn disable_amba_periph_clk(select: PeriphSelect) {
}
}
#[allow(dead_code)]
#[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,
),
}
}
pub(crate) mod sealed {
pub trait Sealed {}
}

View 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;
}
}
}

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

View File

@@ -0,0 +1,681 @@
use core::ops::{Deref, DerefMut};
use arbitrary_int::{prelude::*, u2, u3, u6};
pub use zynq7000::qspi::LinearQspiConfig;
use zynq7000::{
qspi::{
BaudRateDivisor, Config, InstructionCode, InterruptStatus, LoopbackMasterClockDelay,
SpiEnable,
},
slcr::{clocks::SingleCommonPeriphIoClockControl, mio::Speed, reset::QspiResetControl},
};
pub use embedded_hal::spi::{MODE_0, MODE_1, MODE_2, MODE_3, Mode};
pub use zynq7000::slcr::clocks::SrcSelIo;
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,
MioPin, 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: MioPin {}
pub trait Qspi0Io0Pin: MioPin {}
pub trait Qspi0Io1Pin: MioPin {}
pub trait Qspi0Io2Pin: MioPin {}
pub trait Qspi0Io3Pin: MioPin {}
pub trait Qspi0ClockPin: MioPin {}
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: MioPin {}
pub trait Qspi1Io0Pin: MioPin {}
pub trait Qspi1Io1Pin: MioPin {}
pub trait Qspi1Io2Pin: MioPin {}
pub trait Qspi1Io3Pin: MioPin {}
pub trait Qspi1ClockPin: MioPin {}
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: MioPin {}
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 {
/// Memory-mapped QSPI base address.
pub const BASE_ADDRESS: usize = QSPI_START_ADDRESS;
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<'_> {
/// Memory-mapped QSPI base address.
pub const BASE_ADDRESS: usize = QSPI_START_ADDRESS;
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);
});
}
}

View File

@@ -40,8 +40,8 @@ impl Slcr {
/// Returns a mutable reference to the SLCR MMIO block.
///
/// The MMIO block will not be unlocked. However, the registers can still be read.
pub fn regs(&mut self) -> &mut MmioSlcr<'static> {
&mut self.0
pub fn regs(&self) -> &MmioSlcr<'static> {
&self.0
}
/// Modify the SLCR register.

View File

@@ -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 arbitrary_int::{prelude::*, 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);

View File

@@ -4,7 +4,7 @@
use core::convert::Infallible;
use arbitrary_int::{Number, u3, u4};
use arbitrary_int::{prelude::*, u3, u4};
use zynq7000::ttc::{MmioTtc, TTC_0_BASE_ADDR, TTC_1_BASE_ADDR};
#[cfg(not(feature = "7z010-7z007s-clg225"))]
@@ -188,23 +188,22 @@ pub enum TtcConstructionError {
pub fn calc_prescaler_reg_and_interval_ticks(mut ref_clk: Hertz, freq: Hertz) -> (Option<u4>, u16) {
// TODO: Can this be optimized?
let mut prescaler_reg: Option<u4> = None;
let mut prescaler: Option<u32> = None;
let mut tick_val = ref_clk / freq;
while tick_val > u16::MAX as u32 {
ref_clk /= 2;
if let Some(prescaler_reg) = prescaler_reg {
// TODO: Better error handling for this case? Can this even happen?
if prescaler_reg.value() == u4::MAX.value() {
break;
} else {
prescaler_reg.checked_add(u4::new(1));
match prescaler {
Some(val) => {
if val == u4::MAX.as_u32() {
break;
}
prescaler = Some(val + 1);
}
} else {
prescaler_reg = Some(u4::new(0));
None => prescaler = Some(0),
}
tick_val = ref_clk / freq;
}
(prescaler_reg, tick_val as u16)
(prescaler.map(|v| u4::new(v as u8)), tick_val as u16)
}
pub struct Pwm {

View File

@@ -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::{
@@ -187,7 +187,7 @@ pub enum CharLen {
}
#[derive(Debug, Clone, Copy)]
pub struct ClockConfigRaw {
pub struct ClockConfig {
cd: u16,
bdiv: u8,
}
@@ -197,12 +197,12 @@ pub fn calculate_viable_configs(
mut uart_clk: Hertz,
clk_sel: ClockSelect,
target_baud: u32,
) -> alloc::vec::Vec<(ClockConfigRaw, f64)> {
) -> alloc::vec::Vec<(ClockConfig, f64)> {
let mut viable_cfgs = alloc::vec::Vec::new();
if clk_sel == ClockSelect::UartRefClkDiv8 {
uart_clk /= 8;
}
let mut current_clk_config = ClockConfigRaw::default();
let mut current_clk_config = ClockConfig::default();
for bdiv in 4..u8::MAX {
let cd =
round(uart_clk.raw() as f64 / ((bdiv as u32 + 1) as f64 * target_baud as f64)) as u64;
@@ -229,15 +229,15 @@ pub fn calculate_raw_baud_cfg_smallest_error(
mut uart_clk: Hertz,
clk_sel: ClockSelect,
target_baud: u32,
) -> Result<(ClockConfigRaw, f64), DivisorZero> {
) -> Result<(ClockConfig, f64), DivisorZero> {
if target_baud == 0 {
return Err(DivisorZero);
}
if clk_sel == ClockSelect::UartRefClkDiv8 {
uart_clk /= 8;
}
let mut current_clk_config = ClockConfigRaw::default();
let mut best_clk_config = ClockConfigRaw::default();
let mut current_clk_config = ClockConfig::default();
let mut best_clk_config = ClockConfig::default();
let mut smallest_error: f64 = 100.0;
for bdiv in 4..u8::MAX {
let cd =
@@ -258,13 +258,13 @@ pub fn calculate_raw_baud_cfg_smallest_error(
Ok((best_clk_config, smallest_error))
}
impl ClockConfigRaw {
impl ClockConfig {
#[inline]
pub const fn new(cd: u16, bdiv: u8) -> Result<Self, DivisorZero> {
if cd == 0 {
return Err(DivisorZero);
}
Ok(ClockConfigRaw { cd, bdiv })
Ok(ClockConfig { cd, bdiv })
}
/// Auto-calculates the best clock configuration settings for the target baudrate.
@@ -316,16 +316,16 @@ impl ClockConfigRaw {
}
}
impl Default for ClockConfigRaw {
impl Default for ClockConfig {
#[inline]
fn default() -> Self {
ClockConfigRaw::new(1, 0).unwrap()
ClockConfig::new(1, 0).unwrap()
}
}
#[derive(Debug)]
pub struct UartConfig {
clk_config: ClockConfigRaw,
pub struct Config {
clk_config: ClockConfig,
chmode: ChMode,
parity: Parity,
stopbits: Stopbits,
@@ -333,8 +333,8 @@ pub struct UartConfig {
clk_sel: ClockSelect,
}
impl UartConfig {
pub fn new_with_clk_config(clk_config: ClockConfigRaw) -> Self {
impl Config {
pub fn new_with_clk_config(clk_config: ClockConfig) -> Self {
Self::new(
clk_config,
ChMode::default(),
@@ -347,14 +347,14 @@ impl UartConfig {
#[inline]
pub const fn new(
clk_config: ClockConfigRaw,
clk_config: ClockConfig,
chmode: ChMode,
parity: Parity,
stopbits: Stopbits,
chrl: CharLen,
clk_sel: ClockSelect,
) -> Self {
UartConfig {
Config {
clk_config,
chmode,
parity,
@@ -365,7 +365,7 @@ impl UartConfig {
}
#[inline]
pub const fn raw_clk_config(&self) -> ClockConfigRaw {
pub const fn raw_clk_config(&self) -> ClockConfig {
self.clk_config
}
@@ -399,7 +399,7 @@ impl UartConfig {
pub struct Uart {
rx: Rx,
tx: Tx,
cfg: UartConfig,
cfg: Config,
}
#[derive(Debug, thiserror::Error)]
@@ -422,7 +422,7 @@ impl Uart {
///
/// A valid PL design which routes the UART pins through into the PL must be used for this to
/// work.
pub fn new_with_emio(uart: impl PsUart, cfg: UartConfig) -> Result<Uart, InvalidPsUart> {
pub fn new_with_emio(uart: impl PsUart, cfg: Config) -> Result<Uart, InvalidPsUart> {
if uart.uart_id().is_none() {
return Err(InvalidPsUart);
}
@@ -436,7 +436,7 @@ impl Uart {
/// This is the constructor to use the PS UART with MIO pins.
pub fn new_with_mio<TxPinI: TxPin, RxPinI: RxPin>(
uart: impl PsUart,
cfg: UartConfig,
cfg: Config,
pins: (TxPinI, RxPinI),
) -> Result<Self, UartConstructionError>
where
@@ -465,13 +465,13 @@ impl Uart {
pub fn new_generic_unchecked(
mut reg_block: MmioUart<'static>,
uart_id: UartId,
cfg: UartConfig,
cfg: Config,
) -> Uart {
let periph_sel = match uart_id {
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(),
);
@@ -557,7 +557,7 @@ impl Uart {
}
#[inline]
pub const fn cfg(&self) -> &UartConfig {
pub const fn cfg(&self) -> &Config {
&self.cfg
}
@@ -657,7 +657,7 @@ mod tests {
#[test]
fn test_error_calc_0() {
// Baud 600
let cfg_0 = ClockConfigRaw::new(10417, 7).unwrap();
let cfg_0 = ClockConfig::new(10417, 7).unwrap();
let actual_baud_0 = cfg_0.actual_baud(REF_UART_CLK);
assert!(abs_diff_eq!(actual_baud_0, 599.980, epsilon = 0.01));
}
@@ -665,7 +665,7 @@ mod tests {
#[test]
fn test_error_calc_1() {
// Baud 9600
let cfg = ClockConfigRaw::new(81, 7).unwrap();
let cfg = ClockConfig::new(81, 7).unwrap();
let actual_baud = cfg.actual_baud(REF_UART_CLK_DIV_8);
assert!(abs_diff_eq!(actual_baud, 9645.061, epsilon = 0.01));
}
@@ -673,7 +673,7 @@ mod tests {
#[test]
fn test_error_calc_2() {
// Baud 9600
let cfg = ClockConfigRaw::new(651, 7).unwrap();
let cfg = ClockConfig::new(651, 7).unwrap();
let actual_baud = cfg.actual_baud(REF_UART_CLK);
assert!(abs_diff_eq!(actual_baud, 9600.614, epsilon = 0.01));
}
@@ -681,7 +681,7 @@ mod tests {
#[test]
fn test_error_calc_3() {
// Baud 28800
let cfg = ClockConfigRaw::new(347, 4).unwrap();
let cfg = ClockConfig::new(347, 4).unwrap();
let actual_baud = cfg.actual_baud(REF_UART_CLK);
assert!(abs_diff_eq!(actual_baud, 28818.44, epsilon = 0.01));
}
@@ -689,7 +689,7 @@ mod tests {
#[test]
fn test_error_calc_4() {
// Baud 921600
let cfg = ClockConfigRaw::new(9, 5).unwrap();
let cfg = ClockConfig::new(9, 5).unwrap();
let actual_baud = cfg.actual_baud(REF_UART_CLK);
assert!(abs_diff_eq!(actual_baud, 925925.92, epsilon = 0.01));
}
@@ -697,7 +697,7 @@ mod tests {
#[test]
fn test_best_calc_0() {
let result =
ClockConfigRaw::new_autocalc_with_raw_clk(REF_UART_CLK, ClockSelect::UartRefClk, 600);
ClockConfig::new_autocalc_with_raw_clk(REF_UART_CLK, ClockSelect::UartRefClk, 600);
assert!(result.is_ok());
let (cfg, _error) = result.unwrap();
assert_eq!(cfg.cd(), 499);

View File

@@ -1,6 +1,6 @@
use core::convert::Infallible;
use arbitrary_int::Number;
use arbitrary_int::prelude::*;
use zynq7000::uart::{InterruptControl, InterruptStatus, MmioUart};
use super::FIFO_DEPTH;

View File

@@ -202,4 +202,9 @@ impl embedded_io_async::Write for TxAsync {
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
Ok(self.write(buf).await)
}
/// This implementation does not do anything.
async fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}

View File

@@ -12,8 +12,8 @@ categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
cortex-a-rt = { version = "0.1", optional = true, features = ["vfp-dp"] }
cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2" }
arbitrary-int = "1.3"
cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", branch = "bump-arbitrary-int" }
arbitrary-int = "2"
zynq-mmu = { path = "../zynq-mmu", version = "0.1.0" }
[features]

View File

@@ -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,

View File

@@ -13,8 +13,8 @@ categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
static_assertions = "1.1"
derive-mmio = { version = "0.6", default-features = false }
bitbybit = "1.3"
arbitrary-int = "1.3"
bitbybit = "1.4"
arbitrary-int = "2"
rustversion = "1"
thiserror = { version = "2", default-features = false }
once_cell = { version = "1", default-features = false, features = ["critical-section"] }

880
zynq7000/src/ddrc.rs Normal file
View 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) }
}
}

303
zynq7000/src/devcfg.rs Normal file
View File

@@ -0,0 +1,303 @@
use arbitrary_int::{u4, u5, u7};
pub const DEVCFG_BASE_ADDR: usize = 0xF8007000;
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum PlConfigAccess {
/// Used for JTAG access
TapController = 0,
/// Used for PCAP or ICAP access.
ConfigAccessPort = 1,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum ConfigAccessPortSelect {
/// Internal Configuration Access Port (ICAP), using PL or PS-based software.
Icap = 0,
/// Processor Configuration Access Port (PCAP), using PS-based software.
Pcap = 1,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum TimerSelect {
_64kTimer = 0,
_4kTimer = 1,
}
#[bitbybit::bitenum(u3, exhaustive = false)]
#[derive(Debug, PartialEq, Eq)]
pub enum AesEnable {
Disable = 0b000,
Enable = 0b111,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum PsBootMode {
NonSecure = 0,
Secure = 1,
}
#[bitbybit::bitenum(u3, exhaustive = false)]
#[derive(Debug, PartialEq, Eq)]
pub enum ArmDapEnable {
Enabled = 0b111,
}
#[bitbybit::bitfield(u32, debug)]
pub struct Control {
#[bit(31, rw)]
force_reset: bool,
/// Program singal used to reset the PL. It acts at the PROG_B signal in the PL.
#[bit(30, rw)]
prog_b_bit: bool,
/// Called PCFG_POR_CNT_4K by Xilinx.
#[bit(29, rw)]
timer_select: TimerSelect,
/// Called XDCFG_CTRL_PCAP_PR_MASK by Xilinx.
#[bit(27, rw)]
access_port_select: ConfigAccessPortSelect,
#[bit(26, rw)]
config_access_select: PlConfigAccess,
#[bit(25, rw)]
pcap_rate_enable: bool,
#[bit(24, rw)]
multiboot_enable: bool,
#[bit(23, rw)]
jtag_chain_disable: bool,
#[bit(12, rw)]
pcfg_aes_fuse: bool,
#[bits(9..=11, rw)]
pcfg_aes_enable: Option<AesEnable>,
#[bit(8, rw)]
seu_enable: bool,
/// Read-only because this is set and locked by BootROM.
#[bit(7, r)]
ps_boot_mode: PsBootMode,
/// SPNIDEN
#[bit(6, rw)]
secure_non_invasive_debug_enable: bool,
/// SPIDEN
#[bit(5, rw)]
secure_invasive_debug_enable: bool,
/// NIDEN
#[bit(4, rw)]
non_invasive_debug_enable: bool,
/// DBGEN
#[bit(3, rw)]
invasive_debug_enable: bool,
#[bits(0..=2, rw)]
dap_enable: Option<ArmDapEnable>,
}
/// The bits in this register and read/write, set-only, which means that only a PS_POR_B reset
/// can clear the bits.
#[bitbybit::bitfield(u32, debug)]
pub struct Lock {
#[bit(4, rw)]
aes_fuse: bool,
#[bit(3, rw)]
aes: bool,
#[bit(2, rw)]
seu: bool,
/// Locks the SEC_EN bit. BootROM will set this bit.
#[bit(1, rw)]
sec: bool,
/// Locks SPNIDEN, SPIDEN, NIDEN, DBGEN and DAP_EN
#[bit(0, rw)]
debug: bool,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum EdgeConfig {
Falling = 0,
Rising = 1,
}
/// Related to the full level for reads, and the empty level for writes.
#[bitbybit::bitenum(u2, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum FifoThresholdConfig {
OneFourth = 0b00,
HalfEmpty = 0b01,
ThreeFourth = 0b10,
EmptyOrFull = 0b11,
}
#[bitbybit::bitfield(u32, debug)]
pub struct Config {
#[bits(10..=11, rw)]
read_fifo_threshhold: FifoThresholdConfig,
#[bits(8..=9, rw)]
write_fifo_threshold: FifoThresholdConfig,
#[bit(7, rw)]
read_data_active_clock_edge: EdgeConfig,
#[bit(6, rw)]
write_data_active_clock_edge: EdgeConfig,
#[bit(5, rw)]
disable_src_increment: bool,
#[bit(4, rw)]
disable_dst_incremenet: bool,
}
#[bitbybit::bitfield(u32, debug)]
pub struct Interrupt {
/// Tri-state PL IO during HIZ.
#[bit(31, rw)]
gts_usr_b: bool,
#[bit(30, rw)]
first_config_done: bool,
#[bit(29, rw)]
global_powerdown: bool,
/// Tri-state PL IO during configuration.
#[bit(28, rw)]
gts_cfg_b: bool,
/// PSS_CFG_RESET_B_INT
#[bit(27, rw)]
pl_config_reset: bool,
#[bit(23, rw)]
axi_write_timeout: bool,
#[bit(22, rw)]
axi_write_response_error: bool,
#[bit(21, rw)]
axi_read_timeout: bool,
#[bit(20, rw)]
axi_read_response_error: bool,
#[bit(18, rw)]
rx_overflow: bool,
#[bit(17, rw)]
tx_fifo_below_threshold: bool,
#[bit(16, rw)]
rx_fifo_above_threshold: bool,
#[bit(15, rw)]
dma_illegal_command: bool,
#[bit(14, rw)]
dma_queue_overflow: bool,
#[bit(13, rw)]
dma_done: bool,
#[bit(12, rw)]
dma_pcap_done: bool,
#[bit(11, rw)]
inconsistent_pcap_to_dma_transfer_len: bool,
#[bit(6, rw)]
hamc_error: bool,
#[bit(5, rw)]
seu_error: bool,
#[bit(4, rw)]
pl_power_loss_por_b_low: bool,
#[bit(3, rw)]
pl_config_controller_under_reset: bool,
#[bit(2, rw)]
pl_programming_done: bool,
#[bit(1, rw)]
positive_edge_pl_init: bool,
#[bit(0, rw)]
negative_edge_pl_init: bool,
}
#[bitbybit::bitfield(u32, debug)]
pub struct MiscControl {
#[bits(28..=31, r)]
ps_version: u4,
#[bit(8, r)]
por_b_signal: bool,
#[bit(4, rw)]
loopback: bool,
}
#[bitbybit::bitenum(u2, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum UnacknowledgedDmaTransfers {
None = 0b00,
One = 0b01,
Two = 0b10,
ThreeOrMore = 0b11,
}
#[bitbybit::bitfield(u32, debug)]
pub struct Status {
#[bit(31, rw)]
dma_command_queue_full: bool,
#[bit(30, rw)]
dma_command_queue_empty: bool,
#[bits(28..=29, rw)]
unacknowledged_dma_transfers: UnacknowledgedDmaTransfers,
#[bits(20..=24, rw)]
rx_fifo_level: u5,
#[bits(12..=18, rw)]
tx_fifo_level: u7,
#[bit(11, rw)]
gts_usr_b: bool,
#[bit(10, rw)]
first_config_done: bool,
#[bit(9, rw)]
global_powerdown: bool,
#[bit(8, rw)]
gts_cfg_b: bool,
#[bit(7, rw)]
secure_lockdown: bool,
#[bit(6, rw)]
illegal_apb_access: bool,
/// Active low reset bit.
#[bit(5, rw)]
pl_reset_n: bool,
#[bit(4, rw)]
pcfg_init: bool,
#[bit(3, rw)]
efuse_bbram_aes_key_disabled: bool,
#[bit(2, rw)]
efuse_sec_enable: bool,
#[bit(1, rw)]
efuse_jtag_disabled: bool,
}
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct DevCfg {
control: Control,
lock: Lock,
config: Config,
/// Interrupt is cleared by writing to this register.
interrupt_status: Interrupt,
/// Bits can be set to one to mask the interrupts.
interrupt_mask: Interrupt,
status: Status,
dma_source_addr: u32,
dma_dest_addr: u32,
dma_source_len: u32,
dma_dest_len: u32,
_reserved0: u32,
multiboot_addr: u32,
_reserved1: u32,
unlock_control: u32,
_reserved2: [u32; 0x12],
misc_control: MiscControl,
_reserved3: [u32; 0x1F],
// Included here but not exposed to avoid providing multiple references to the same peripheral.
// Exposed in [crate::xadc].
_xadc: crate::xadc::XAdc,
}
static_assertions::const_assert_eq!(core::mem::size_of::<DevCfg>(), 0x11C);
impl DevCfg {
/// Create a new DevCfg MMIO instance for for device configuration peripheral at address
/// [DEVCFG_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 unsafe fn new_mmio_fixed() -> MmioDevCfg<'static> {
unsafe { Self::new_mmio_at(DEVCFG_BASE_ADDR) }
}
}

View File

@@ -17,6 +17,8 @@ extern crate std;
pub const MPCORE_BASE_ADDR: usize = 0xF8F0_0000;
pub mod ddrc;
pub mod devcfg;
pub mod eth;
pub mod gic;
pub mod gpio;
@@ -24,10 +26,13 @@ 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;
pub mod uart;
pub mod xadc;
static PERIPHERALS_TAKEN: AtomicBool = AtomicBool::new(false);
@@ -36,10 +41,11 @@ static PERIPHERALS_TAKEN: AtomicBool = AtomicBool::new(false);
/// It is a singleton which exposes all peripherals supported by this crate.
/// The [`svd2rust` documentation](https://docs.rs/svd2rust/latest/svd2rust/#peripheral-api)
/// provides some more information about this.
pub struct PsPeripherals {
pub struct Peripherals {
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,9 +59,12 @@ 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>,
pub devcfg: devcfg::MmioDevCfg<'static>,
pub xadc: xadc::MmioXAdc<'static>,
}
impl PsPeripherals {
impl Peripherals {
/// Returns all supported processing system peripherals *once*.
pub fn take() -> Option<Self> {
let taken = PERIPHERALS_TAKEN.swap(true, Ordering::Relaxed);
@@ -76,6 +85,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 +99,24 @@ 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(),
devcfg: devcfg::DevCfg::new_mmio_fixed(),
xadc: xadc::XAdc::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
View 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
View 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) }
}
}

View File

@@ -4,22 +4,15 @@
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,
pub enum Bypass {
NotBypassed = 0b00,
/// This is the default reset value.
PinStrapSettings = 0b01,
Bypassed = 0b10,
BypassedRegardlessOfPinStrapping = 0b11,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug)]
pub enum BypassQual {
BypassForceBit = 0b0,
BootModeFourthBit = 0b1,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
#[bitbybit::bitfield(u32, debug)]
pub struct PllControl {
/// Feedback divisor for the PLL.
///
@@ -29,10 +22,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,38 +34,69 @@ pub struct PllControl {
reset: bool,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
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, debug)]
pub struct PllConfig {
#[bits(12..=21, rw)]
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)]
#[bitbybit::bitfield(u32, 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,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
#[bitbybit::bitfield(u32, debug)]
pub struct FpgaClockControl {
// Reset value 0x1
#[bits(20..=25, rw)]
@@ -104,7 +128,7 @@ pub enum SrcSelArm {
IoPll = 0b11,
}
#[bitbybit::bitfield(u32)]
#[bitbybit::bitfield(u32, debug)]
pub struct ArmClockControl {
#[bit(28, rw)]
cpu_peri_clk_act: bool,
@@ -125,7 +149,7 @@ pub struct ArmClockControl {
srcsel: SrcSelArm,
}
#[bitbybit::bitfield(u32)]
#[bitbybit::bitfield(u32, debug)]
pub struct DdrClockControl {
/// Divisor for DDR 2x clock. Reset value: 0x6
#[bits(26..=31, rw)]
@@ -141,7 +165,7 @@ pub struct DdrClockControl {
ddr_3x_clk_act: bool,
}
#[bitbybit::bitfield(u32)]
#[bitbybit::bitfield(u32, default = 0x0, debug)]
pub struct DciClockControl {
/// Second cascade divider. Reset value: 0x1E
#[bits(20..=25, rw)]
@@ -154,8 +178,7 @@ pub struct DciClockControl {
clk_act: bool,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
#[bitbybit::bitfield(u32, debug)]
pub struct ClockRatioSelectReg {
/// Reset value: 0x1 (6:2:1 clock)
#[bit(0, rw)]
@@ -199,8 +222,7 @@ impl PartialEq for SrcSelIo {
}
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
#[bitbybit::bitfield(u32, debug)]
pub struct GigEthClockControl {
#[bits(20..=25, rw)]
divisor_1: u6,
@@ -221,8 +243,7 @@ pub enum SrcSelGigEthRclk {
Emio = 1,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
#[bitbybit::bitfield(u32, debug)]
pub struct GigEthRclkControl {
#[bit(4, rw)]
srcsel: SrcSelGigEthRclk,
@@ -246,7 +267,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,
@@ -256,8 +277,7 @@ pub struct SingleCommonPeriphIoClockControl {
clk_act: bool,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
#[bitbybit::bitfield(u32, debug)]
pub struct DualCommonPeriphIoClockControl {
#[bits(8..=13, rw)]
divisor: u6,
@@ -282,7 +302,7 @@ pub enum SrcSelTpiu {
EmioTraceClkAlt2 = 0b111,
}
#[bitbybit::bitfield(u32)]
#[bitbybit::bitfield(u32, debug)]
pub struct TracePortClockControl {
#[bits(8..=13, rw)]
divisor: u6,
@@ -297,8 +317,7 @@ pub struct TracePortClockControl {
/// AMBA peripheral clock control.
///
/// These clocks must be enabled if you want to read from the peripheral register space.
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
#[bitbybit::bitfield(u32, debug)]
pub struct AperClockControl {
#[bit(24, rw)]
smc_1x_clk_act: bool,
@@ -341,9 +360,9 @@ pub struct AperClockControl {
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct ClockControl {
arm_pll: PllControl,
ddr_pll: PllControl,
io_pll: PllControl,
arm_pll_ctrl: PllControl,
ddr_pll_ctrl: PllControl,
io_pll_ctrl: PllControl,
pll_status: PllStatus,
arm_pll_cfg: PllConfig,
ddr_pll_cfg: PllConfig,

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

@@ -0,0 +1,139 @@
use arbitrary_int::{u2, u3};
#[bitbybit::bitenum(u4, exhaustive = false)]
pub enum VRefSel {
/// VREF = 0.6 V
Lpddr2 = 0b0001,
/// VREF = 0.675 V
Ddr3l = 0b0010,
/// VREF = 0.75 V
Ddr3 = 0b0100,
/// VREF = 0.9 V
Ddr2 = 0b1000,
}
#[bitbybit::bitfield(u32)]
pub struct DdrControl {
/// Enables VRP/VRN.
#[bit(9, rw)]
refio_enable: bool,
#[bit(6, rw)]
vref_ext_en_upper_bits: bool,
#[bit(5, rw)]
vref_ext_en_lower_bits: bool,
#[bits(1..=4, rw)]
vref_sel: Option<VRefSel>,
#[bit(0, rw)]
vref_int_en: bool,
}
#[bitbybit::bitfield(u32, default = 0x00)]
pub struct DciControl {
#[bit(20, rw)]
update_control: bool,
#[bits(17..=19, rw)]
pref_opt2: u3,
#[bits(14..=15, rw)]
pref_opt1: u2,
#[bits(11..=13, rw)]
nref_opt4: u3,
#[bits(8..=10, rw)]
nref_opt2: u3,
#[bits(6..=7, rw)]
nref_opt1: u2,
#[bit(1, rw)]
enable: bool,
/// Reset value 0. Should be toggled once to initialize flops in DCI system.
#[bit(0, rw)]
reset: bool,
}
#[bitbybit::bitfield(u32)]
pub struct DciStatus {
#[bit(13, rw)]
done: bool,
#[bit(0, rw)]
lock: bool,
}
#[bitbybit::bitenum(u2, exhaustive = true)]
#[derive(Debug)]
pub enum OutputEnable {
IBuf = 0b00,
__Reserved0 = 0b01,
__Reserved1 = 0b10,
OBuf = 0b11,
}
#[bitbybit::bitenum(u2, exhaustive = true)]
#[derive(Debug)]
pub enum InputType {
Off = 0b00,
VRefBasedDifferentialReceiverForSstlHstl = 0b01,
DifferentialInputReceiver = 0b10,
LvcmosReceiver = 0b11,
}
#[bitbybit::bitenum(u2, exhaustive = true)]
#[derive(Debug)]
pub enum DciType {
Disabled = 0b00,
DciDrive = 0b01,
__Reserved = 0b10,
DciTermination = 0b11,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct DdriobConfig {
#[bit(11, rw)]
pullup_enable: bool,
#[bits(9..=10, rw)]
output_enable: OutputEnable,
#[bit(8, rw)]
term_disable_mode: bool,
#[bit(7, rw)]
ibuf_disable_mode: bool,
#[bits(5..=6, rw)]
dci_type: DciType,
#[bit(4, rw)]
termination_enable: bool,
#[bit(3, rw)]
dci_update_enable: bool,
#[bits(1..=2, rw)]
inp_type: InputType,
}
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct DdrIoB {
ddriob_addr0: DdriobConfig,
ddriob_addr1: DdriobConfig,
ddriob_data0: DdriobConfig,
ddriob_data1: DdriobConfig,
ddriob_diff0: DdriobConfig,
ddriob_diff1: DdriobConfig,
ddriob_clock: DdriobConfig,
ddriob_drive_slew_addr: u32,
ddriob_drive_slew_data: u32,
ddriob_drive_slew_diff: u32,
ddriob_drive_slew_clock: u32,
ddr_ctrl: DdrControl,
dci_ctrl: DciControl,
dci_status: DciStatus,
}
impl DdrIoB {
/// Create a new handle to this peripheral.
///
/// Writing to this register requires unlocking the SLCR registers first.
///
/// # Safety
///
/// If you create multiple instances of this handle at the same time, you are responsible for
/// ensuring that there are no read-modify-write races on any of the registers.
pub unsafe fn new_mmio_fixed() -> MmioDdrIoB<'static> {
unsafe { Self::new_mmio_at(super::SLCR_BASE_ADDR + super::DDRIOB_OFFSET) }
}
}
static_assertions::const_assert_eq!(core::mem::size_of::<DdrIoB>(), 0x38);

View File

@@ -12,44 +12,10 @@ const GPIOB_OFFSET: usize = 0xB00;
const DDRIOB_OFFSET: usize = 0xB40;
pub mod clocks;
pub mod ddriob;
pub mod mio;
pub mod reset;
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct DdrIoB {
ddriob_addr0: u32,
ddriob_addr1: u32,
ddriob_data0: u32,
ddriob_data1: u32,
ddriob_diff0: u32,
ddriob_diff1: u32,
ddriob_clock: u32,
ddriob_drive_slew_addr: u32,
ddriob_drive_slew_data: u32,
ddriob_drive_slew_diff: u32,
ddriob_drive_slew_clock: u32,
ddriob_ddr_ctrl: u32,
ddriob_dci_ctrl: u32,
ddriob_dci_status: u32,
}
impl DdrIoB {
/// Create a new handle to this peripheral.
///
/// Writing to this register requires unlocking the SLCR registers first.
///
/// # Safety
///
/// If you create multiple instances of this handle at the same time, you are responsible for
/// ensuring that there are no read-modify-write races on any of the registers.
pub unsafe fn new_mmio_fixed() -> MmioDdrIoB<'static> {
unsafe { Self::new_mmio_at(SLCR_BASE_ADDR + DDRIOB_OFFSET) }
}
}
static_assertions::const_assert_eq!(core::mem::size_of::<DdrIoB>(), 0x38);
#[bitbybit::bitenum(u3, exhaustive = false)]
pub enum VrefSel {
Disabled = 0b000,
@@ -93,11 +59,19 @@ impl GpiobRegisters {
}
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
pub enum BootPllConfig {
Enabled = 0,
/// Disabled and bypassed.
Bypassed = 1,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct BootModeRegister {
#[bit(4, r)]
pll_bypass: bool,
pll_config: BootPllConfig,
#[bits(0..=3, r)]
boot_mode: u4,
}
@@ -205,7 +179,7 @@ pub struct Slcr {
gpiob: GpiobRegisters,
#[mmio(Inner)]
ddriob: DdrIoB,
ddriob: ddriob::DdrIoB,
}
static_assertions::const_assert_eq!(core::mem::size_of::<Slcr>(), 0xB78);

View File

@@ -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,

View File

@@ -1,5 +1,7 @@
//! SPI register module.
use arbitrary_int::{Number, u4};
use arbitrary_int::{prelude::*, 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,

View File

@@ -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,

29
zynq7000/src/xadc.rs Normal file
View File

@@ -0,0 +1,29 @@
pub const XADC_BASE_ADDR: usize = 0xF8007100;
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct XAdc {
config: u32,
interrupt_status: u32,
interrupt_mask: u32,
misc_status: u32,
command_fifo: u32,
data_fifo: u32,
misc_control: u32,
}
impl XAdc {
/// Create a new XADC MMIO instance for for device configuration peripheral at address
/// [XADC_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 unsafe fn new_mmio_fixed() -> MmioXAdc<'static> {
unsafe { XAdc::new_mmio_at(XADC_BASE_ADDR) }
}
}
static_assertions::const_assert_eq!(core::mem::size_of::<XAdc>(), 0x1C);