FSBL-rs #9
12
Cargo.toml
12
Cargo.toml
@@ -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"
|
||||
|
@@ -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
|
||||
|
@@ -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
31
examples/embassy/build.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
// By default, Cargo will re-run a build script whenever
|
||||
// any file in the project changes. By specifying `memory.x`
|
||||
// here, we ensure the build script is only re-run when
|
||||
// `memory.x` is changed.
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
}
|
@@ -1,8 +1,7 @@
|
||||
|
||||
MEMORY
|
||||
{
|
||||
/* Zedboard: 512 MB DDR3. Only use 63 MB for now, should be plenty for a bare-metal app.
|
||||
Leave 1 MB of memory which will be configured as uncached device memory by the MPU. This is
|
||||
Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This is
|
||||
recommended for something like DMA descriptors. */
|
||||
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
@@ -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));
|
||||
|
@@ -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);
|
||||
|
@@ -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));
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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
31
examples/simple/build.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
// By default, Cargo will re-run a build script whenever
|
||||
// any file in the project changes. By specifying `memory.x`
|
||||
// here, we ensure the build script is only re-run when
|
||||
// `memory.x` is changed.
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
}
|
22
examples/simple/memory.x
Normal file
22
examples/simple/memory.x
Normal file
@@ -0,0 +1,22 @@
|
||||
MEMORY
|
||||
{
|
||||
/* Zedboard: 512 MB DDR3. Only use 63 MB for now, should be plenty for a bare-metal app.
|
||||
Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This is
|
||||
recommended for something like DMA descriptors. */
|
||||
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
||||
}
|
||||
|
||||
REGION_ALIAS("DATA", CODE);
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* Uncached memory */
|
||||
.uncached (NOLOAD) : ALIGN(4) {
|
||||
. = ALIGN(4);
|
||||
_sbss_uncached = .;
|
||||
*(.uncached .uncached.*);
|
||||
. = ALIGN(4);
|
||||
_ebss_uncached = .;
|
||||
} > UNCACHED
|
||||
}
|
@@ -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();
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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" }
|
||||
|
31
examples/zedboard/build.rs
Normal file
31
examples/zedboard/build.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
// By default, Cargo will re-run a build script whenever
|
||||
// any file in the project changes. By specifying `memory.x`
|
||||
// here, we ensure the build script is only re-run when
|
||||
// `memory.x` is changed.
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
}
|
22
examples/zedboard/memory.x
Normal file
22
examples/zedboard/memory.x
Normal file
@@ -0,0 +1,22 @@
|
||||
MEMORY
|
||||
{
|
||||
/* Zedboard: 512 MB DDR3. Only use 63 MB for now, should be plenty for a bare-metal app.
|
||||
Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This is
|
||||
recommended for something like DMA descriptors. */
|
||||
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
||||
}
|
||||
|
||||
REGION_ALIAS("DATA", CODE);
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* Uncached memory */
|
||||
.uncached (NOLOAD) : ALIGN(4) {
|
||||
. = ALIGN(4);
|
||||
_sbss_uncached = .;
|
||||
*(.uncached .uncached.*);
|
||||
. = ALIGN(4);
|
||||
_ebss_uncached = .;
|
||||
} > UNCACHED
|
||||
}
|
@@ -33,10 +33,8 @@ use embedded_io::Write;
|
||||
use embedded_io_async::Write as _;
|
||||
use log::{LevelFilter, debug, error, info, warn};
|
||||
use rand::{RngCore, SeedableRng};
|
||||
use zedboard::{
|
||||
PS_CLOCK_FREQUENCY,
|
||||
phy_marvell::{LatchingLinkStatus, MARVELL_88E1518_OUI},
|
||||
};
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zedboard_bsp::phy_marvell;
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
@@ -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;
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
211
examples/zedboard/src/bin/qspi.rs
Normal file
211
examples/zedboard/src/bin/qspi.rs
Normal 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 {}
|
||||
}
|
@@ -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));
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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
5
justfile
Normal 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
22
scripts/memory_ddr.x
Normal file
@@ -0,0 +1,22 @@
|
||||
MEMORY
|
||||
{
|
||||
/* Zedboard: 512 MB DDR3. Only use 63 MB for now, should be plenty for a bare-metal app.
|
||||
Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This is
|
||||
recommended for something like DMA descriptors. */
|
||||
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
||||
}
|
||||
|
||||
REGION_ALIAS("DATA", CODE);
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* Uncached memory */
|
||||
.uncached (NOLOAD) : ALIGN(4) {
|
||||
. = ALIGN(4);
|
||||
_sbss_uncached = .;
|
||||
*(.uncached .uncached.*);
|
||||
. = ALIGN(4);
|
||||
_ebss_uncached = .;
|
||||
} > UNCACHED
|
||||
}
|
24
scripts/memory_ocm.x
Normal file
24
scripts/memory_ocm.x
Normal file
@@ -0,0 +1,24 @@
|
||||
MEMORY
|
||||
{
|
||||
/* The Zynq7000 has 192 kB of OCM memory which can be used for the FSBL */
|
||||
CODE(rx) : ORIGIN = 0x00000000, LENGTH = 192K
|
||||
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
|
||||
/* Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This can
|
||||
be used for something like DMA descriptors, but the DDR needs to be set up first in addition
|
||||
to configuring the page at address 0x400_0000 accordingly */
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
||||
}
|
||||
|
||||
REGION_ALIAS("DATA", CODE);
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* Uncached memory */
|
||||
.uncached (NOLOAD) : ALIGN(4) {
|
||||
. = ALIGN(4);
|
||||
_sbss_uncached = .;
|
||||
*(.uncached .uncached.*);
|
||||
. = ALIGN(4);
|
||||
_ebss_uncached = .;
|
||||
} > UNCACHED
|
||||
}
|
144
scripts/xsct-flasher.tcl
Normal file
144
scripts/xsct-flasher.tcl
Normal file
@@ -0,0 +1,144 @@
|
||||
if {[info exists env(ip_address_hw_server)]} {
|
||||
set ip $env(ip_address_hw_server)
|
||||
} else {
|
||||
set ip "localhost"
|
||||
}
|
||||
|
||||
# Defaults
|
||||
set init_tcl ""
|
||||
set app ""
|
||||
set bitstream ""
|
||||
|
||||
# Usage helper
|
||||
proc usage {} {
|
||||
puts "Usage: xsct xsct-helper.tcl <init.tcl> \[-a|--app app.elf\] \[-b|--bit design.bit]"
|
||||
puts "Options:"
|
||||
puts " -a, --app Path to application ELF to download"
|
||||
puts " -b, --bit Path to FPGA bitstream (.bit) to program"
|
||||
puts " -h, --help Show this help"
|
||||
}
|
||||
|
||||
# Compact, robust parser
|
||||
set expecting ""
|
||||
set endopts 0
|
||||
foreach arg $argv {
|
||||
# If previous option expects a value, take this arg
|
||||
if {$expecting ne ""} {
|
||||
set $expecting $arg
|
||||
set expecting ""
|
||||
continue
|
||||
}
|
||||
|
||||
# Option handling (until we see --)
|
||||
if {!$endopts && [string match "-*" $arg]} {
|
||||
if {$arg eq "--"} { set endopts 1; continue }
|
||||
if {$arg eq "-h" || $arg eq "--help"} { usage; exit 0 }
|
||||
if {$arg eq "-a" || $arg eq "--app"} { set expecting app; continue }
|
||||
if {$arg eq "-b" || $arg eq "--bit"} { set expecting bitstream; continue }
|
||||
puts "error: unknown option: $arg"; usage; exit 1
|
||||
}
|
||||
|
||||
# Positional: expect only <init.tcl>
|
||||
if {$init_tcl eq ""} {
|
||||
set init_tcl $arg
|
||||
} else {
|
||||
puts "error: unexpected positional argument: $arg"
|
||||
usage
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Validate required init script
|
||||
if {$init_tcl eq ""} {
|
||||
puts "error: missing required first argument pointing to initialization TCL script"
|
||||
usage
|
||||
exit 1
|
||||
}
|
||||
if {![file exists $init_tcl]} {
|
||||
puts "error: the PS init tcl script '$init_tcl' does not exist"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Resolve app: CLI takes precedence over env(APP)
|
||||
if {$app ne ""} {
|
||||
if {![file exists $app]} {
|
||||
puts "error: the app file '$app' does not exist"
|
||||
exit 1
|
||||
}
|
||||
} elseif {[info exists env(APP)]} {
|
||||
if {[file exists $env(APP)]} {
|
||||
set app $env(APP)
|
||||
} else {
|
||||
puts "warning: APP environment variable is set but file does not exist: $env(APP)"
|
||||
}
|
||||
}
|
||||
|
||||
# Validate bitstream if provided
|
||||
if {$bitstream ne "" && ![file exists $bitstream]} {
|
||||
puts "error: the bitstream file '$bitstream' does not exist"
|
||||
exit 1
|
||||
}
|
||||
|
||||
puts "Hardware server IP address: $ip"
|
||||
connect -url tcp:$ip:3121
|
||||
|
||||
set devices [targets]
|
||||
|
||||
set apu_line [string trim [targets -nocase -filter {name =~ "APU"}]]
|
||||
set arm_core_0_line [string trim [targets -nocase -filter {name =~ "ARM Cortex-A9 MPCore #0"}]]
|
||||
set fpga_line [string trim [targets -nocase -filter {name =~ "xc7z020"}]]
|
||||
|
||||
set apu_device_num [string index $apu_line 0]
|
||||
set arm_core_0_num [string index $arm_core_0_line 0]
|
||||
set fpga_device_num [string index $fpga_line 0]
|
||||
|
||||
puts "Select ps target with number: $apu_device_num"
|
||||
|
||||
# Select the target
|
||||
target $apu_device_num
|
||||
|
||||
# Resetting the target involved problems when an image is stored on the flash.
|
||||
# It has turned out that it is not essential to reset the system before loading
|
||||
# the software components into the device.
|
||||
puts "Reset target"
|
||||
# TODO: Make the reset optional/configurable via input argument.
|
||||
# Reset the target
|
||||
rst
|
||||
|
||||
# Check if bitstream is set and the file exists before programming FPGA
|
||||
if {$bitstream eq ""} {
|
||||
puts "Skipping bitstream programming (bitstream argument not set)"
|
||||
} elseif {![file exists $bitstream]} {
|
||||
puts "Error: The bitstream file '$bitstream' does not exist"
|
||||
} else {
|
||||
puts "Set FPGA target with number: $fpga_device_num"
|
||||
target $fpga_device_num
|
||||
|
||||
# Without this delay, the FPGA programming may fail
|
||||
after 1500
|
||||
|
||||
puts "Programming FPGA with bitstream: $bitstream"
|
||||
fpga -f $bitstream
|
||||
}
|
||||
|
||||
puts "Set ps target with device number: $arm_core_0_num"
|
||||
targets $arm_core_0_num
|
||||
|
||||
puts "Initialize processing system"
|
||||
# Init processing system
|
||||
source $init_tcl
|
||||
|
||||
ps7_init
|
||||
ps7_post_config
|
||||
|
||||
puts "Set arm core 0 target with number: $arm_core_0_num"
|
||||
target $arm_core_0_num
|
||||
|
||||
if {$app ne ""} {
|
||||
puts "Download app $app to target"
|
||||
dow $app
|
||||
puts "Starting app"
|
||||
con
|
||||
}
|
||||
|
||||
puts "Success"
|
@@ -1,86 +0,0 @@
|
||||
if {[info exists env(ip_address_hw_server)]} {
|
||||
set ip $env(ip_address_hw_server)
|
||||
} else {
|
||||
set ip "localhost"
|
||||
}
|
||||
|
||||
set init_tcl ""
|
||||
if {[llength $argv] >= 1} {
|
||||
set init_tcl [lindex $argv 0]
|
||||
} else {
|
||||
puts "error: missing required first argument pointing to initialization TCL script"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if {![file exists $init_tcl]} {
|
||||
puts "the ps init tcl script '$init_tcl' does not exist"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# parse command-line arguments
|
||||
set bitstream ""
|
||||
if {[llength $argv] >= 2} {
|
||||
set bitstream [lindex $argv 1]
|
||||
}
|
||||
|
||||
puts "Hardware server IP address: $ip"
|
||||
connect -url tcp:$ip:3121
|
||||
|
||||
set devices [targets]
|
||||
|
||||
set apu_line [string trim [targets -nocase -filter {name =~ "APU"}]]
|
||||
set arm_core_0_line [string trim [targets -nocase -filter {name =~ "ARM Cortex-A9 MPCore #0"}]]
|
||||
set fpga_line [string trim [targets -nocase -filter {name =~ "xc7z020"}]]
|
||||
|
||||
set apu_device_num [string index $apu_line 0]
|
||||
set arm_core_0_num [string index $arm_core_0_line 0]
|
||||
set fpga_device_num [string index $fpga_line 0]
|
||||
|
||||
puts "Select ps target with number: $apu_device_num"
|
||||
|
||||
# Select the target
|
||||
target $apu_device_num
|
||||
|
||||
# Resetting the target involved problems when an image is stored on the flash.
|
||||
# It has turned out that it is not essential to reset the system before loading
|
||||
# the software components into the device.
|
||||
puts "Reset target"
|
||||
# TODO: Make the reset optional/configurable via input argument.
|
||||
# Reset the target
|
||||
rst
|
||||
|
||||
# Check if bitstream is set and the file exists before programming FPGA
|
||||
if {$bitstream eq ""} {
|
||||
puts "Skipping bitstream programming (bitstream argument not set)"
|
||||
} elseif {![file exists $bitstream]} {
|
||||
puts "Error: The bitstream file '$bitstream' does not exist"
|
||||
} else {
|
||||
puts "Set FPGA target with number: $fpga_device_num"
|
||||
target $fpga_device_num
|
||||
|
||||
# Without this delay, the FPGA programming may fail
|
||||
after 1500
|
||||
|
||||
puts "Programming FPGA with bitstream: $bitstream"
|
||||
fpga -f $bitstream
|
||||
}
|
||||
|
||||
puts "Set ps target with device number: $arm_core_0_num"
|
||||
targets $arm_core_0_num
|
||||
|
||||
puts "Initialize processing system"
|
||||
# Init processing system
|
||||
source $init_tcl
|
||||
|
||||
ps7_init
|
||||
ps7_post_config
|
||||
|
||||
puts "Set arm core 0 target with number: $arm_core_0_num"
|
||||
target $arm_core_0_num
|
||||
|
||||
if {[info exists env(APP)] && [file exists $env(APP)]} {
|
||||
puts "Download app $env(APP) to target"
|
||||
dow $env(APP)
|
||||
}
|
||||
|
||||
puts "Successful"
|
@@ -7,7 +7,7 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Define the default values
|
||||
TCL_SCRIPT_NAME = "xsct-init.tcl"
|
||||
TCL_SCRIPT_NAME = "xsct-flasher.tcl"
|
||||
SCRIPTS_DIR = "scripts"
|
||||
DEFAULT_IP_ADDRESS_HW_SERVER = "localhost"
|
||||
|
||||
@@ -55,7 +55,7 @@ def main():
|
||||
default=os.getenv("TCL_INIT_SCRIPT"),
|
||||
help="Path to the ps7 initialization TCL file to prepare the processing system.\n"
|
||||
"You can also set the TCL_INIT_SCRIPT env variable to set this.\n"
|
||||
"It is also set implicitely when specifying the SDT folder with --sdt"
|
||||
"It is also set implicitely when specifying the SDT folder with --sdt",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-b",
|
||||
@@ -63,7 +63,7 @@ def main():
|
||||
dest="bit",
|
||||
default=os.getenv("ZYNQ_BITSTREAM"),
|
||||
help="Optional path to the bitstream which will also be programed to the device. It is"
|
||||
" also searched automatically if the --sdt option is used.\n"
|
||||
" also searched automatically if the --sdt option is used.\n",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
@@ -91,9 +91,6 @@ def main():
|
||||
print(f"The app '{args.app}' does not exist")
|
||||
sys.exit(1)
|
||||
|
||||
# Export environment variables
|
||||
if args.app:
|
||||
os.environ["APP"] = args.app
|
||||
os.environ["IP_ADDRESS_HW_SERVER"] = args.ip
|
||||
init_tcl = None
|
||||
bitstream = None
|
||||
@@ -136,7 +133,11 @@ def main():
|
||||
# Prepare tcl_args as a list to avoid manual string concatenation issues
|
||||
cmd_list = ["xsct", str(xsct_script), init_tcl]
|
||||
if bitstream:
|
||||
cmd_list.append("--bit")
|
||||
cmd_list.append(bitstream)
|
||||
if args.app:
|
||||
cmd_list.append("--app")
|
||||
cmd_list.append(args.app)
|
||||
|
||||
# Join safely for shell execution
|
||||
xsct_cmd = shlex.join(cmd_list)
|
||||
|
11
zedboard-bsp/Cargo.toml
Normal file
11
zedboard-bsp/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "zedboard-bsp"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
zynq7000-hal = { path = "../zynq7000-hal" }
|
||||
bitbybit = "1.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
4
zedboard-bsp/src/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
#![no_std]
|
||||
|
||||
pub mod phy_marvell;
|
||||
pub mod qspi_spansion;
|
630
zedboard-bsp/src/qspi_spansion.rs
Normal file
630
zedboard-bsp/src/qspi_spansion.rs
Normal 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()
|
||||
}
|
||||
}
|
@@ -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
22
zedboard-fsbl/Cargo.toml
Normal 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
31
zedboard-fsbl/build.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
// By default, Cargo will re-run a build script whenever
|
||||
// any file in the project changes. By specifying `memory.x`
|
||||
// here, we ensure the build script is only re-run when
|
||||
// `memory.x` is changed.
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
}
|
24
zedboard-fsbl/memory.x
Normal file
24
zedboard-fsbl/memory.x
Normal file
@@ -0,0 +1,24 @@
|
||||
MEMORY
|
||||
{
|
||||
/* The Zynq7000 has 192 kB of OCM memory which can be used for the FSBL */
|
||||
CODE(rx) : ORIGIN = 0x00000000, LENGTH = 192K
|
||||
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
|
||||
/* Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This can
|
||||
be used for something like DMA descriptors, but the DDR needs to be set up first in addition
|
||||
to configuring the page at address 0x400_0000 accordingly */
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
||||
}
|
||||
|
||||
REGION_ALIAS("DATA", CODE);
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* Uncached memory */
|
||||
.uncached (NOLOAD) : ALIGN(4) {
|
||||
. = ALIGN(4);
|
||||
_sbss_uncached = .;
|
||||
*(.uncached .uncached.*);
|
||||
. = ALIGN(4);
|
||||
_ebss_uncached = .;
|
||||
} > UNCACHED
|
||||
}
|
503
zedboard-fsbl/src/ddr_cfg.rs
Normal file
503
zedboard-fsbl/src/ddr_cfg.rs
Normal file
@@ -0,0 +1,503 @@
|
||||
use arbitrary_int::{u2, u3, u4, u5, u6, u7, u9, u10, u11, u12, u20};
|
||||
use zynq7000::{
|
||||
ddrc::regs::{
|
||||
AxiPriorityReadPort, AxiPriorityWritePort, CheEccControl, CheTZq, CheTZqShortInterval,
|
||||
CtrlReg1, CtrlReg2, CtrlReg3, CtrlReg4, CtrlReg5, CtrlReg6, DataBusWidth, DdrcControl,
|
||||
DeepPowerdown, DfiTiming, DisableDq, DllCalib, DllCalibSel, DramAddrMapBank,
|
||||
DramAddrMapColumn, DramAddrMapRow, DramBurst8ReadWrite, DramEmr, DramEmrMr, DramInitParam,
|
||||
DramOdt, DramParamReg0, DramParamReg1, DramParamReg2, DramParamReg3, DramParamReg4,
|
||||
EccMode, EccScrub, LpddrBit, LpddrControl0, LpddrControl1, LpddrControl2, LpddrControl3,
|
||||
LprHprQueueControl, MobileSetting, ModeRegisterType, OdtDelayHold, PhyCmdTimeoutRdDataCpt,
|
||||
PhyConfig, PhyDqsConfig, PhyInitRatio, PhyReceiverEnable, PhyWriteDataSlaveConfig,
|
||||
PhyWriteEnableConfig, Reg2c, Reg2d, Reg64, Reg65, SoftReset, TwoRankConfig,
|
||||
WriteQueueControl,
|
||||
},
|
||||
slcr::ddriob::{DciType, DdriobConfig, InputType, OutputEnable},
|
||||
};
|
||||
use zynq7000_hal::ddr::{DdrcConfigSet, DdriobConfigSet};
|
||||
|
||||
const DDRIOB_ADDR_CFG: DdriobConfig = DdriobConfig::builder()
|
||||
.with_pullup_enable(false)
|
||||
.with_output_enable(OutputEnable::OBuf)
|
||||
.with_term_disable_mode(false)
|
||||
.with_ibuf_disable_mode(false)
|
||||
.with_dci_type(DciType::Disabled)
|
||||
.with_termination_enable(false)
|
||||
.with_dci_update_enable(false)
|
||||
.with_inp_type(InputType::Off)
|
||||
.build();
|
||||
|
||||
const DDRIOB_DATA_CFG: DdriobConfig = DdriobConfig::builder()
|
||||
.with_pullup_enable(false)
|
||||
.with_output_enable(OutputEnable::OBuf)
|
||||
.with_term_disable_mode(false)
|
||||
.with_ibuf_disable_mode(false)
|
||||
.with_dci_type(DciType::DciTermination)
|
||||
.with_termination_enable(true)
|
||||
.with_dci_update_enable(false)
|
||||
.with_inp_type(InputType::VRefBasedDifferentialReceiverForSstlHstl)
|
||||
.build();
|
||||
|
||||
const DDRIOB_DIFF_CFG: DdriobConfig = DdriobConfig::builder()
|
||||
.with_pullup_enable(false)
|
||||
.with_output_enable(OutputEnable::OBuf)
|
||||
.with_term_disable_mode(false)
|
||||
.with_ibuf_disable_mode(false)
|
||||
.with_dci_type(DciType::DciTermination)
|
||||
.with_termination_enable(true)
|
||||
.with_dci_update_enable(false)
|
||||
.with_inp_type(InputType::DifferentialInputReceiver)
|
||||
.build();
|
||||
|
||||
const DDRIOB_CLOCK_CFG: DdriobConfig = DdriobConfig::builder()
|
||||
.with_pullup_enable(false)
|
||||
.with_output_enable(OutputEnable::OBuf)
|
||||
.with_term_disable_mode(false)
|
||||
.with_ibuf_disable_mode(false)
|
||||
.with_dci_type(DciType::Disabled)
|
||||
.with_termination_enable(false)
|
||||
.with_dci_update_enable(false)
|
||||
.with_inp_type(InputType::Off)
|
||||
.build();
|
||||
|
||||
// TODO: Auto-generate from ps7init.tcl
|
||||
pub const DDRIOB_CONFIG_SET_ZEDBOARD: DdriobConfigSet = DdriobConfigSet {
|
||||
addr0: DDRIOB_ADDR_CFG,
|
||||
addr1: DDRIOB_ADDR_CFG,
|
||||
data0: DDRIOB_DATA_CFG,
|
||||
data1: DDRIOB_DATA_CFG,
|
||||
diff0: DDRIOB_DIFF_CFG,
|
||||
diff1: DDRIOB_DIFF_CFG,
|
||||
clock: DDRIOB_CLOCK_CFG,
|
||||
};
|
||||
|
||||
// TODO: Auto-generate from ps7init.tcl
|
||||
pub const DDRC_CONFIG_ZEDBOARD_FULL_BUILDERS: DdrcConfigSet = DdrcConfigSet {
|
||||
ctrl: DdrcControl::builder()
|
||||
.with_disable_auto_refresh(false)
|
||||
.with_disable_active_bypass(false)
|
||||
.with_disable_read_bypass(false)
|
||||
.with_read_write_idle_gap(u7::new(1))
|
||||
.with_burst8_refresh(u3::new(0))
|
||||
.with_data_bus_width(DataBusWidth::_32Bit)
|
||||
.with_power_down_enable(false)
|
||||
.with_soft_reset(SoftReset::Reset)
|
||||
.build(),
|
||||
two_rank: TwoRankConfig::builder()
|
||||
.with_addrmap_cs_bit0(u5::new(0))
|
||||
.with_ddrc_active_ranks(u2::new(1))
|
||||
.with_rfc_nom_x32(u12::new(0x82))
|
||||
.build(),
|
||||
hpr: LprHprQueueControl::builder()
|
||||
.with_xact_run_length(u4::new(0xf))
|
||||
.with_max_starve_x32(u11::new(0xf))
|
||||
.with_min_non_critical_x32(u11::new(0xf))
|
||||
.build(),
|
||||
lpr: LprHprQueueControl::builder()
|
||||
.with_xact_run_length(u4::new(0x8))
|
||||
.with_max_starve_x32(u11::new(0x2))
|
||||
.with_min_non_critical_x32(u11::new(0x1))
|
||||
.build(),
|
||||
wr: WriteQueueControl::builder()
|
||||
.with_max_starve_x32(u11::new(0x2))
|
||||
.with_xact_run_length(u4::new(0x8))
|
||||
.with_min_non_critical_x32(u11::new(0x1))
|
||||
.build(),
|
||||
dram_param_0: DramParamReg0::builder()
|
||||
.with_post_selfref_gap_x32(u7::new(0x10))
|
||||
.with_t_rfc_min(0x56)
|
||||
.with_t_rc(u6::new(0x1b))
|
||||
.build(),
|
||||
dram_param_1: DramParamReg1::builder()
|
||||
.with_t_cke(u4::new(0x4))
|
||||
.with_t_ras_min(u5::new(0x13))
|
||||
.with_t_ras_max(u6::new(0x24))
|
||||
.with_t_faw(u6::new(0x16))
|
||||
.with_powerdown_to_x32(u5::new(0x6))
|
||||
.with_wr2pre(u5::new(0x13))
|
||||
.build(),
|
||||
dram_param_2: DramParamReg2::builder()
|
||||
.with_t_rcd(u4::new(0x7))
|
||||
.with_rd2pre(u5::new(0x5))
|
||||
.with_pad_pd(u3::new(0x0))
|
||||
.with_t_xp(u5::new(0x5))
|
||||
.with_wr2rd(u5::new(0xf))
|
||||
.with_rd2wr(u5::new(0x7))
|
||||
.with_write_latency(u5::new(0x5))
|
||||
.build(),
|
||||
dram_param_3: DramParamReg3::builder()
|
||||
.with_disable_pad_pd_feature(false)
|
||||
.with_read_latency(u5::new(0x7))
|
||||
.with_enable_dfi_dram_clk_disable(false)
|
||||
.with_mobile(MobileSetting::Ddr2Ddr3)
|
||||
.with_sdram(false)
|
||||
.with_refresh_to_x32(u5::new(0x8))
|
||||
.with_t_rp(u4::new(0x7))
|
||||
.with_refresh_margin(u4::new(0x2))
|
||||
.with_t_rrd(u3::new(0x6))
|
||||
.with_t_ccd(u3::new(0x4))
|
||||
.build(),
|
||||
dram_param_4: DramParamReg4::builder()
|
||||
.with_mr_rdata_valid(false)
|
||||
.with_mr_type(ModeRegisterType::Write)
|
||||
.with_mr_wr_busy(false)
|
||||
.with_mr_data(0x0)
|
||||
.with_mr_addr(u2::new(0x0))
|
||||
.with_mr_wr(false)
|
||||
.with_prefer_write(false)
|
||||
.with_enable_2t_timing_mode(false)
|
||||
.build(),
|
||||
dram_init_param: DramInitParam::builder()
|
||||
.with_t_mrd(u3::new(0x4))
|
||||
.with_pre_ocd_x32(u4::new(0x0))
|
||||
.with_final_wait_x32(u7::new(0x7))
|
||||
.build(),
|
||||
dram_emr: DramEmr::builder().with_emr3(0x0).with_emr2(0x8).build(),
|
||||
dram_emr_mr: DramEmrMr::builder().with_emr(0x4).with_mr(0xb30).build(),
|
||||
dram_burst8_rdwr: DramBurst8ReadWrite::builder()
|
||||
.with_burst_rdwr(u4::new(0x4))
|
||||
.with_pre_cke_x1024(u10::new(0x16d))
|
||||
.with_post_cke_x1024(u10::new(0x1))
|
||||
.with_burstchop(false)
|
||||
.build(),
|
||||
disable_dq: DisableDq::builder()
|
||||
.with_dis_dq(false)
|
||||
.with_force_low_pri_n(false)
|
||||
.build(),
|
||||
dram_addr_map_bank: DramAddrMapBank::builder()
|
||||
.with_addrmap_bank_b6(u4::new(0))
|
||||
.with_addrmap_bank_b5(u4::new(0))
|
||||
.with_addrmap_bank_b2(u4::new(0x7))
|
||||
.with_addrmap_bank_b1(u4::new(0x7))
|
||||
.with_addrmap_bank_b0(u4::new(0x7))
|
||||
.build(),
|
||||
dram_addr_map_col: DramAddrMapColumn::builder()
|
||||
.with_addrmap_col_b11(u4::new(0xf))
|
||||
.with_addrmap_col_b10(u4::new(0xf))
|
||||
.with_addrmap_col_b9(u4::new(0xf))
|
||||
.with_addrmap_col_b8(u4::new(0x0))
|
||||
.with_addrmap_col_b7(u4::new(0x0))
|
||||
.with_addrmap_col_b4(u4::new(0x0))
|
||||
.with_addrmap_col_b3(u4::new(0x0))
|
||||
.with_addrmap_col_b2(u4::new(0x0))
|
||||
.build(),
|
||||
dram_addr_map_row: DramAddrMapRow::builder()
|
||||
.with_addrmap_row_b15(u4::new(0xf))
|
||||
.with_addrmap_row_b14(u4::new(0xf))
|
||||
.with_addrmap_row_b13(u4::new(0x6))
|
||||
.with_addrmap_row_b12(u4::new(0x6))
|
||||
.with_addrmap_row_b2_11(u4::new(0x6))
|
||||
.with_addrmap_row_b1(u4::new(0x6))
|
||||
.with_addrmap_row_b0(u4::new(0x4))
|
||||
.build(),
|
||||
dram_odt: DramOdt::builder()
|
||||
.with_phy_idle_local_odt(u2::new(0x0))
|
||||
.with_phy_write_local_odt(u2::new(0x3))
|
||||
.with_phy_read_local_odt(u2::new(0x0))
|
||||
.with_rank0_wr_odt(u3::new(0x1))
|
||||
.with_rank0_rd_odt(u3::new(0x0))
|
||||
.build(),
|
||||
phy_cmd_timeout_rddata_cpt: PhyCmdTimeoutRdDataCpt::builder()
|
||||
.with_wrlvl_num_of_dq0(u4::new(0x7))
|
||||
.with_gatelvl_num_of_dq0(u4::new(0x7))
|
||||
.with_clk_stall_level(false)
|
||||
.with_dis_phy_ctrl_rstn(false)
|
||||
.with_rdc_fifo_rst_err_cnt_clr(false)
|
||||
.with_use_fixed_re(true)
|
||||
.with_rdc_we_to_re_delay(u4::new(0x8))
|
||||
.with_wr_cmd_to_data(u4::new(0x0))
|
||||
.with_rd_cmd_to_data(u4::new(0x0))
|
||||
.build(),
|
||||
dll_calib: DllCalib::builder().with_sel(DllCalibSel::Periodic).build(),
|
||||
odt_delay_hold: OdtDelayHold::builder()
|
||||
.with_wr_odt_hold(u4::new(0x5))
|
||||
.with_rd_odt_hold(u4::new(0x0))
|
||||
.with_wr_odt_delay(u4::new(0x0))
|
||||
.with_rd_odt_delay(u4::new(0x3))
|
||||
.build(),
|
||||
ctrl_reg1: CtrlReg1::builder()
|
||||
.with_selfref_enable(false)
|
||||
.with_dis_collision_page_opt(false)
|
||||
.with_dis_wc(false)
|
||||
.with_refresh_update_level(false)
|
||||
.with_auto_pre_en(false)
|
||||
.with_lpr_num_entries(u6::new(0x1f))
|
||||
.with_pageclose(false)
|
||||
.build(),
|
||||
ctrl_reg2: CtrlReg2::builder()
|
||||
.with_go_2_critcal_enable(true)
|
||||
.with_go_2_critical_hysteresis(0x0)
|
||||
.build(),
|
||||
ctrl_reg3: CtrlReg3::builder()
|
||||
.with_dfi_t_wlmrd(u10::new(0x28))
|
||||
.with_rdlvl_rr(0x41)
|
||||
.with_wrlvl_ww(0x41)
|
||||
.build(),
|
||||
ctrl_reg4: CtrlReg4::builder()
|
||||
.with_dfi_t_ctrlupd_interval_max_x1024(0x16)
|
||||
.with_dfi_t_ctrlupd_interval_min_x1024(0x10)
|
||||
.build(),
|
||||
ctrl_reg5: CtrlReg5::builder()
|
||||
.with_t_ckesr(u6::new(0x4))
|
||||
.with_t_cksrx(u4::new(0x6))
|
||||
.with_t_ckrse(u4::new(0x6))
|
||||
.with_dfi_t_dram_clk_enable(u4::new(0x1))
|
||||
.with_dfi_t_dram_clk_disable(u4::new(0x1))
|
||||
.with_dfi_t_ctrl_delay(u4::new(0x1))
|
||||
.build(),
|
||||
ctrl_reg6: CtrlReg6::builder()
|
||||
.with_t_cksx(u4::new(0x3))
|
||||
.with_t_ckdpdx(u4::new(0x2))
|
||||
.with_t_ckdpde(u4::new(0x2))
|
||||
.with_t_ckpdx(u4::new(0x2))
|
||||
.with_t_ckpde(u4::new(0x2))
|
||||
.build(),
|
||||
che_t_zq: CheTZq::builder()
|
||||
.with_t_zq_short_nop(u10::new(0x40))
|
||||
.with_t_zq_long_nop(u10::new(0x200))
|
||||
.with_t_mode(u10::new(0x200))
|
||||
.with_ddr3(true)
|
||||
.with_dis_auto_zq(false)
|
||||
.build(),
|
||||
che_t_zq_short_interval_reg: CheTZqShortInterval::builder()
|
||||
.with_dram_rstn_x1024(0x69)
|
||||
.with_t_zq_short_interval(u20::new(0xcb73))
|
||||
.build(),
|
||||
deep_powerdown: DeepPowerdown::builder()
|
||||
.with_deep_powerdown_to_x1024(0xff)
|
||||
.with_enable(false)
|
||||
.build(),
|
||||
reg_2c: Reg2c::builder()
|
||||
.with_dfi_rd_data_eye_train(true)
|
||||
.with_dfi_rd_dqs_gate_level(true)
|
||||
.with_dfi_wr_level_enable(true)
|
||||
.with_trdlvl_max_error(false)
|
||||
.with_twrlvl_max_error(false)
|
||||
.with_dfi_rdlvl_max_x1024(u12::new(0xfff))
|
||||
.with_dfi_wrlvl_max_x1024(u12::new(0xfff))
|
||||
.build(),
|
||||
reg_2d: Reg2d::builder().with_skip_ocd(true).build(),
|
||||
dfi_timing: DfiTiming::builder()
|
||||
.with_dfi_t_ctrlup_max(u10::new(0x40))
|
||||
.with_dfi_t_ctrlup_min(u10::new(0x3))
|
||||
.with_dfi_t_rddata_enable(u5::new(0x6))
|
||||
.build(),
|
||||
che_ecc_ctrl: CheEccControl::builder()
|
||||
.with_clear_correctable_errors(false)
|
||||
.with_clear_uncorrectable_errors(false)
|
||||
.build(),
|
||||
ecc_scrub: EccScrub::builder()
|
||||
.with_disable_scrub(true)
|
||||
.with_ecc_mode(EccMode::NoEcc)
|
||||
.build(),
|
||||
phy_receiver_enable: PhyReceiverEnable::builder()
|
||||
.with_phy_dif_off(u4::new(0))
|
||||
.with_phy_dif_on(u4::new(0))
|
||||
.build(),
|
||||
phy_config: [PhyConfig::builder()
|
||||
.with_dq_offset(u7::new(0x40))
|
||||
.with_wrlvl_inc_mode(false)
|
||||
.with_gatelvl_inc_mode(false)
|
||||
.with_rdlvl_inc_mode(false)
|
||||
.with_data_slice_in_use(true)
|
||||
.build(); 4],
|
||||
phy_init_ratio: [
|
||||
PhyInitRatio::builder()
|
||||
.with_gatelvl_init_ratio(u10::new(0xcf))
|
||||
.with_wrlvl_init_ratio(u10::new(0x3))
|
||||
.build(),
|
||||
PhyInitRatio::builder()
|
||||
.with_gatelvl_init_ratio(u10::new(0xd0))
|
||||
.with_wrlvl_init_ratio(u10::new(0x3))
|
||||
.build(),
|
||||
PhyInitRatio::builder()
|
||||
.with_gatelvl_init_ratio(u10::new(0xbd))
|
||||
.with_wrlvl_init_ratio(u10::new(0x0))
|
||||
.build(),
|
||||
PhyInitRatio::builder()
|
||||
.with_gatelvl_init_ratio(u10::new(0xc1))
|
||||
.with_wrlvl_init_ratio(u10::new(0x0))
|
||||
.build(),
|
||||
],
|
||||
phy_rd_dqs_config: [
|
||||
PhyDqsConfig::builder()
|
||||
.with_dqs_slave_delay(u9::new(0x0))
|
||||
.with_dqs_slave_force(false)
|
||||
.with_dqs_slave_ratio(u10::new(0x35))
|
||||
.build(),
|
||||
PhyDqsConfig::builder()
|
||||
.with_dqs_slave_delay(u9::new(0x0))
|
||||
.with_dqs_slave_force(false)
|
||||
.with_dqs_slave_ratio(u10::new(0x35))
|
||||
.build(),
|
||||
PhyDqsConfig::builder()
|
||||
.with_dqs_slave_delay(u9::new(0x0))
|
||||
.with_dqs_slave_force(false)
|
||||
.with_dqs_slave_ratio(u10::new(0x35))
|
||||
.build(),
|
||||
PhyDqsConfig::builder()
|
||||
.with_dqs_slave_delay(u9::new(0x0))
|
||||
.with_dqs_slave_force(false)
|
||||
.with_dqs_slave_ratio(u10::new(0x35))
|
||||
.build(),
|
||||
],
|
||||
phy_wr_dqs_config: [
|
||||
PhyDqsConfig::builder()
|
||||
.with_dqs_slave_delay(u9::new(0x0))
|
||||
.with_dqs_slave_force(false)
|
||||
.with_dqs_slave_ratio(u10::new(0x83))
|
||||
.build(),
|
||||
PhyDqsConfig::builder()
|
||||
.with_dqs_slave_delay(u9::new(0x0))
|
||||
.with_dqs_slave_force(false)
|
||||
.with_dqs_slave_ratio(u10::new(0x83))
|
||||
.build(),
|
||||
PhyDqsConfig::builder()
|
||||
.with_dqs_slave_delay(u9::new(0x0))
|
||||
.with_dqs_slave_force(false)
|
||||
.with_dqs_slave_ratio(u10::new(0x7f))
|
||||
.build(),
|
||||
PhyDqsConfig::builder()
|
||||
.with_dqs_slave_delay(u9::new(0x0))
|
||||
.with_dqs_slave_force(false)
|
||||
.with_dqs_slave_ratio(u10::new(0x78))
|
||||
.build(),
|
||||
],
|
||||
phy_we_cfg: [
|
||||
PhyWriteEnableConfig::builder()
|
||||
.with_fifo_we_in_delay(u9::new(0x0))
|
||||
.with_fifo_we_in_force(false)
|
||||
.with_fifo_we_slave_ratio(u11::new(0x124))
|
||||
.build(),
|
||||
PhyWriteEnableConfig::builder()
|
||||
.with_fifo_we_in_delay(u9::new(0x0))
|
||||
.with_fifo_we_in_force(false)
|
||||
.with_fifo_we_slave_ratio(u11::new(0x125))
|
||||
.build(),
|
||||
PhyWriteEnableConfig::builder()
|
||||
.with_fifo_we_in_delay(u9::new(0x0))
|
||||
.with_fifo_we_in_force(false)
|
||||
.with_fifo_we_slave_ratio(u11::new(0x112))
|
||||
.build(),
|
||||
PhyWriteEnableConfig::builder()
|
||||
.with_fifo_we_in_delay(u9::new(0x0))
|
||||
.with_fifo_we_in_force(false)
|
||||
.with_fifo_we_slave_ratio(u11::new(0x116))
|
||||
.build(),
|
||||
],
|
||||
phy_wr_data_slv: [
|
||||
PhyWriteDataSlaveConfig::builder()
|
||||
.with_wr_data_slave_delay(u9::new(0x0))
|
||||
.with_wr_data_slave_force(false)
|
||||
.with_wr_data_slave_ratio(u10::new(0xc3))
|
||||
.build(),
|
||||
PhyWriteDataSlaveConfig::builder()
|
||||
.with_wr_data_slave_delay(u9::new(0x0))
|
||||
.with_wr_data_slave_force(false)
|
||||
.with_wr_data_slave_ratio(u10::new(0xc3))
|
||||
.build(),
|
||||
PhyWriteDataSlaveConfig::builder()
|
||||
.with_wr_data_slave_delay(u9::new(0x0))
|
||||
.with_wr_data_slave_force(false)
|
||||
.with_wr_data_slave_ratio(u10::new(0xbf))
|
||||
.build(),
|
||||
PhyWriteDataSlaveConfig::builder()
|
||||
.with_wr_data_slave_delay(u9::new(0x0))
|
||||
.with_wr_data_slave_force(false)
|
||||
.with_wr_data_slave_ratio(u10::new(0xb8))
|
||||
.build(),
|
||||
],
|
||||
reg64: Reg64::builder()
|
||||
.with_cmd_latency(false)
|
||||
.with_lpddr(false)
|
||||
.with_ctrl_slave_delay(u7::new(0x0))
|
||||
.with_ctrl_slave_force(false)
|
||||
.with_ctrl_slave_ratio(u10::new(0x100))
|
||||
.with_sel_logic(false)
|
||||
.with_invert_clkout(true)
|
||||
.with_bl2(false)
|
||||
.build(),
|
||||
reg65: Reg65::builder()
|
||||
.with_ctrl_slave_delay(u2::new(0x0))
|
||||
.with_dis_calib_rst(false)
|
||||
.with_use_rd_data_eye_level(true)
|
||||
.with_use_rd_dqs_gate_level(true)
|
||||
.with_use_wr_level(true)
|
||||
.with_dll_lock_diff(u4::new(0xf))
|
||||
.with_rd_rl_delay(u5::new(0x4))
|
||||
.with_wr_rl_delay(u5::new(0x2))
|
||||
.build(),
|
||||
page_mask: 0x0,
|
||||
axi_priority_wr_port: [
|
||||
AxiPriorityWritePort::builder()
|
||||
.with_disable_page_match(false)
|
||||
.with_disable_urgent(false)
|
||||
.with_disable_aging(false)
|
||||
.with_pri_wr_port(u10::new(0x3ff))
|
||||
.build(),
|
||||
AxiPriorityWritePort::builder()
|
||||
.with_disable_page_match(false)
|
||||
.with_disable_urgent(false)
|
||||
.with_disable_aging(false)
|
||||
.with_pri_wr_port(u10::new(0x3ff))
|
||||
.build(),
|
||||
AxiPriorityWritePort::builder()
|
||||
.with_disable_page_match(false)
|
||||
.with_disable_urgent(false)
|
||||
.with_disable_aging(false)
|
||||
.with_pri_wr_port(u10::new(0x3ff))
|
||||
.build(),
|
||||
AxiPriorityWritePort::builder()
|
||||
.with_disable_page_match(false)
|
||||
.with_disable_urgent(false)
|
||||
.with_disable_aging(false)
|
||||
.with_pri_wr_port(u10::new(0x3ff))
|
||||
.build(),
|
||||
],
|
||||
axi_priority_rd_port: [
|
||||
AxiPriorityReadPort::builder()
|
||||
.with_enable_hpr(false)
|
||||
.with_disable_page_match(false)
|
||||
.with_disable_urgent(false)
|
||||
.with_disable_aging(false)
|
||||
.with_pri_rd_port_n(u10::new(0x3ff))
|
||||
.build(),
|
||||
AxiPriorityReadPort::builder()
|
||||
.with_enable_hpr(false)
|
||||
.with_disable_page_match(false)
|
||||
.with_disable_urgent(false)
|
||||
.with_disable_aging(false)
|
||||
.with_pri_rd_port_n(u10::new(0x3ff))
|
||||
.build(),
|
||||
AxiPriorityReadPort::builder()
|
||||
.with_enable_hpr(false)
|
||||
.with_disable_page_match(false)
|
||||
.with_disable_urgent(false)
|
||||
.with_disable_aging(false)
|
||||
.with_pri_rd_port_n(u10::new(0x3ff))
|
||||
.build(),
|
||||
AxiPriorityReadPort::builder()
|
||||
.with_enable_hpr(false)
|
||||
.with_disable_page_match(false)
|
||||
.with_disable_urgent(false)
|
||||
.with_disable_aging(false)
|
||||
.with_pri_rd_port_n(u10::new(0x3ff))
|
||||
.build(),
|
||||
],
|
||||
lpddr_ctrl_0: LpddrControl0::builder()
|
||||
.with_mr4_margin(0x0)
|
||||
.with_derate_enable(false)
|
||||
.with_per_bank_refresh(false)
|
||||
.with_lpddr2(LpddrBit::Ddr2Ddr3)
|
||||
.build(),
|
||||
lpddr_ctrl_1: LpddrControl1::builder().with_mr4_read_interval(0x0).build(),
|
||||
lpddr_ctrl_2: LpddrControl2::builder()
|
||||
.with_t_mrw(u10::new(0x5))
|
||||
.with_idle_after_reset_x32(0x12)
|
||||
.with_min_stable_clock_x1(u4::new(0x5))
|
||||
.build(),
|
||||
lpddr_ctrl_3: LpddrControl3::builder()
|
||||
.with_dev_zqinit_x32(u10::new(0x12))
|
||||
.with_max_auto_init_x1024(0xa8)
|
||||
.build(),
|
||||
};
|
286
zedboard-fsbl/src/main.rs
Normal file
286
zedboard-fsbl/src/main.rs
Normal 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 {}
|
||||
}
|
15
zedboard-qspi-flasher/Cargo.toml
Normal file
15
zedboard-qspi-flasher/Cargo.toml
Normal 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"
|
150
zedboard-qspi-flasher/qspi-flasher.tcl
Normal file
150
zedboard-qspi-flasher/qspi-flasher.tcl
Normal 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"
|
219
zedboard-qspi-flasher/src/main.rs
Normal file
219
zedboard-qspi-flasher/src/main.rs
Normal 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();
|
||||
}
|
||||
}
|
9
zynq-boot-image/Cargo.toml
Normal file
9
zynq-boot-image/Cargo.toml
Normal 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
437
zynq-boot-image/src/lib.rs
Normal 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
4
zynq-boot-image/staging/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/fsbl.elf
|
||||
/fpga.bit
|
||||
/application.elf
|
||||
/boot.bin
|
28
zynq-boot-image/staging/README.md
Normal file
28
zynq-boot-image/staging/README.md
Normal 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
|
||||
```
|
6
zynq-boot-image/staging/boot.bif
Normal file
6
zynq-boot-image/staging/boot.bif
Normal file
@@ -0,0 +1,6 @@
|
||||
all:
|
||||
{
|
||||
[bootloader] fsbl.elf
|
||||
fpga.bit
|
||||
application.elf
|
||||
}
|
1
zynq-boot-image/tester/.gitignore
vendored
Normal file
1
zynq-boot-image/tester/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
305
zynq-boot-image/tester/Cargo.lock
generated
Normal file
305
zynq-boot-image/tester/Cargo.lock
generated
Normal 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",
|
||||
]
|
10
zynq-boot-image/tester/Cargo.toml
Normal file
10
zynq-boot-image/tester/Cargo.toml
Normal 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"] }
|
82
zynq-boot-image/tester/src/main.rs
Normal file
82
zynq-boot-image/tester/src/main.rs
Normal 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");
|
||||
}
|
||||
}
|
@@ -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 = []
|
||||
|
@@ -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"]
|
||||
|
@@ -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
|
356
zynq7000-hal/src/clocks/pll.rs
Normal file
356
zynq7000-hal/src/clocks/pll.rs
Normal file
@@ -0,0 +1,356 @@
|
||||
use core::sync::atomic::AtomicBool;
|
||||
|
||||
use arbitrary_int::{u4, u7, u10};
|
||||
|
||||
use crate::{BootMode, time::Hertz};
|
||||
|
||||
/// Minimal value based on Zynq-7000 TRM Table 25-6, p.744
|
||||
pub const PLL_MUL_MIN: u32 = 13;
|
||||
/// Maximum value based on Zynq-7000 TRM Table 25-6, p.744
|
||||
pub const PLL_MUL_MAX: u32 = 66;
|
||||
|
||||
static ARM_PLL_INIT: AtomicBool = AtomicBool::new(false);
|
||||
static IO_PLL_INIT: AtomicBool = AtomicBool::new(false);
|
||||
static DDR_PLL_INIT: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[error("pll muliplier value {0} is out of range ({PLL_MUL_MIN}..={PLL_MUL_MAX})")]
|
||||
pub struct MulOutOfRangeError(pub u32);
|
||||
|
||||
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PllConfigCtorError {
|
||||
#[error("invalid input")]
|
||||
InvalidInput,
|
||||
#[error("pll multiplier out of range: {0}")]
|
||||
MulOutOfRange(#[from] MulOutOfRangeError),
|
||||
}
|
||||
|
||||
pub struct PllConfig {
|
||||
fdiv: u7,
|
||||
charge_pump: u4,
|
||||
loop_resistor: u4,
|
||||
lock_count: u10,
|
||||
}
|
||||
|
||||
impl PllConfig {
|
||||
pub fn new_from_target_clock(
|
||||
ps_clk: Hertz,
|
||||
target_clk: Hertz,
|
||||
) -> Result<Self, PllConfigCtorError> {
|
||||
if ps_clk.raw() == 0 {
|
||||
return Err(PllConfigCtorError::InvalidInput);
|
||||
}
|
||||
let mul = target_clk / ps_clk;
|
||||
Self::new(mul).map_err(PllConfigCtorError::MulOutOfRange)
|
||||
}
|
||||
/// Create a new PLL configuration based on the multiplier value.
|
||||
///
|
||||
/// These configuration values are based on the Zynq-7000 TRM Table 25-6, p.744.
|
||||
pub fn new(pll_mul: u32) -> Result<Self, MulOutOfRangeError> {
|
||||
if !(PLL_MUL_MIN..=PLL_MUL_MAX).contains(&pll_mul) {
|
||||
return Err(MulOutOfRangeError(pll_mul));
|
||||
}
|
||||
|
||||
Ok(match pll_mul {
|
||||
13 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(6),
|
||||
u10::new(750),
|
||||
),
|
||||
14 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(6),
|
||||
u10::new(700),
|
||||
),
|
||||
15 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(6),
|
||||
u10::new(650),
|
||||
),
|
||||
16 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(10),
|
||||
u10::new(625),
|
||||
),
|
||||
17 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(10),
|
||||
u10::new(575),
|
||||
),
|
||||
18 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(10),
|
||||
u10::new(550),
|
||||
),
|
||||
19 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(10),
|
||||
u10::new(525),
|
||||
),
|
||||
20 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(12),
|
||||
u10::new(500),
|
||||
),
|
||||
21 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(12),
|
||||
u10::new(475),
|
||||
),
|
||||
22 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(12),
|
||||
u10::new(450),
|
||||
),
|
||||
23 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(12),
|
||||
u10::new(425),
|
||||
),
|
||||
24..=25 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(12),
|
||||
u10::new(400),
|
||||
),
|
||||
26 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(12),
|
||||
u10::new(375),
|
||||
),
|
||||
27..=28 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(12),
|
||||
u10::new(350),
|
||||
),
|
||||
|
||||
29..=30 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(12),
|
||||
u10::new(325),
|
||||
),
|
||||
31..=33 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(2),
|
||||
u10::new(300),
|
||||
),
|
||||
34..=36 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(2),
|
||||
u10::new(275),
|
||||
),
|
||||
37..=40 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(2),
|
||||
u10::new(250),
|
||||
),
|
||||
41..=47 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(3),
|
||||
u4::new(12),
|
||||
u10::new(250),
|
||||
),
|
||||
48..=66 => Self::new_raw(
|
||||
u7::new(pll_mul as u8),
|
||||
u4::new(2),
|
||||
u4::new(4),
|
||||
u10::new(250),
|
||||
),
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new PLL configuration with raw values.
|
||||
///
|
||||
/// It is recommended to use [Self::new] instead, which creates a configuration
|
||||
/// based on a look-up table provided in the Zynq-7000 TRM.
|
||||
pub fn new_raw(fdiv: u7, charge_pump: u4, loop_resistor: u4, lock_count: u10) -> Self {
|
||||
Self {
|
||||
fdiv,
|
||||
charge_pump,
|
||||
loop_resistor,
|
||||
lock_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This function configures the ARM PLL based on the provided [PllConfig].
|
||||
pub fn configure_arm_pll(boot_mode: BootMode, pll_config: PllConfig) {
|
||||
if ARM_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
// Safety: This will only run at most once because of the atomic boolean check.
|
||||
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
|
||||
}
|
||||
|
||||
/// This function configures the IO PLL based on the provided [PllConfig].
|
||||
pub fn configure_io_pll(boot_mode: BootMode, pll_config: PllConfig) {
|
||||
if IO_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
// Safety: This will only run at most once because of the atomic boolean check.
|
||||
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
|
||||
}
|
||||
|
||||
/// This function configures the DDR PLL based on the provided [PllConfig].
|
||||
pub fn configure_ddr_pll(boot_mode: BootMode, pll_config: PllConfig) {
|
||||
if DDR_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
// Safety: This will only run at most once because of the atomic boolean check.
|
||||
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
|
||||
}
|
||||
|
||||
/// This function configures the ARM PLL based on the provided [PllConfig].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should only be called once during system initialization, for example in the
|
||||
/// first-stage bootloader (FSBL).
|
||||
pub unsafe fn configure_arm_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) {
|
||||
unsafe {
|
||||
crate::slcr::Slcr::with(|slcr| {
|
||||
let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_arm_pll_ctrl();
|
||||
let pll_cfg_reg = slcr.clk_ctrl().pointer_to_arm_pll_cfg();
|
||||
configure_pll_unchecked(
|
||||
boot_mode,
|
||||
pll_config,
|
||||
PllType::Arm,
|
||||
slcr,
|
||||
pll_ctrl_reg,
|
||||
pll_cfg_reg,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// This function configures the IO PLL based on the provided [PllConfig].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should only be called once during system initialization, for example in the
|
||||
/// first-stage bootloader (FSBL).
|
||||
pub unsafe fn configure_io_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) {
|
||||
unsafe {
|
||||
crate::slcr::Slcr::with(|slcr| {
|
||||
let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_io_pll_ctrl();
|
||||
let pll_cfg_reg = slcr.clk_ctrl().pointer_to_io_pll_cfg();
|
||||
configure_pll_unchecked(
|
||||
boot_mode,
|
||||
pll_config,
|
||||
PllType::Io,
|
||||
slcr,
|
||||
pll_ctrl_reg,
|
||||
pll_cfg_reg,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// This function configures the DDR PLL based on the provided [PllConfig].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should only be called once during system initialization, for example in the
|
||||
/// first-stage bootloader (FSBL).
|
||||
pub unsafe fn configure_ddr_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) {
|
||||
unsafe {
|
||||
crate::slcr::Slcr::with(|slcr| {
|
||||
let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_ddr_pll_ctrl();
|
||||
let pll_cfg_reg = slcr.clk_ctrl().pointer_to_ddr_pll_cfg();
|
||||
configure_pll_unchecked(
|
||||
boot_mode,
|
||||
pll_config,
|
||||
PllType::Ddr,
|
||||
slcr,
|
||||
pll_ctrl_reg,
|
||||
pll_cfg_reg,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enum PllType {
|
||||
Io,
|
||||
Ddr,
|
||||
Arm,
|
||||
}
|
||||
|
||||
impl PllType {
|
||||
pub const fn bit_offset_pll_locked(&self) -> usize {
|
||||
match self {
|
||||
PllType::Io => 2,
|
||||
PllType::Ddr => 1,
|
||||
PllType::Arm => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn configure_pll_unchecked(
|
||||
boot_mode: BootMode,
|
||||
cfg: PllConfig,
|
||||
pll_type: PllType,
|
||||
slcr: &mut zynq7000::slcr::MmioSlcr<'static>,
|
||||
pll_ctrl_reg: *mut zynq7000::slcr::clocks::PllControl,
|
||||
pll_cfg_reg: *mut zynq7000::slcr::clocks::PllConfig,
|
||||
) {
|
||||
// Step 1: Program the multiplier and other PLL configuration parameters.
|
||||
// New values will only be consumed once the PLL is reset.
|
||||
let mut pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
|
||||
pll_ctrl.set_fdiv(cfg.fdiv);
|
||||
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
|
||||
|
||||
let mut pll_cfg = unsafe { core::ptr::read_volatile(pll_cfg_reg) };
|
||||
pll_cfg.set_charge_pump(cfg.charge_pump);
|
||||
pll_cfg.set_loop_resistor(cfg.loop_resistor);
|
||||
pll_cfg.set_lock_count(cfg.lock_count);
|
||||
unsafe { core::ptr::write_volatile(pll_cfg_reg, pll_cfg) };
|
||||
|
||||
// Step 2: Force the PLL into bypass mode. If the PLL bypass mode pin is tied high,
|
||||
// the PLLs need to be enabled. According to the TRM, this is done by setting the
|
||||
// PLL_BYPASS_QUAL bit to 0, which de-asserts the reset to the Arm PLL.
|
||||
pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
|
||||
if boot_mode.pll_config() == zynq7000::slcr::BootPllConfig::Bypassed {
|
||||
pll_ctrl.set_bypass_qual(false);
|
||||
}
|
||||
pll_ctrl.set_bypass_force(true);
|
||||
pll_ctrl.set_pwrdwn(false);
|
||||
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
|
||||
|
||||
// Step 3: Reset the PLL. This also loads the new configuration.
|
||||
pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
|
||||
pll_ctrl.set_reset(true);
|
||||
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
|
||||
pll_ctrl.set_reset(false);
|
||||
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
|
||||
|
||||
while ((slcr.clk_ctrl().read_pll_status().raw_value() >> pll_type.bit_offset_pll_locked())
|
||||
& 0b1)
|
||||
!= 1
|
||||
{
|
||||
cortex_ar::asm::nop();
|
||||
}
|
||||
|
||||
pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
|
||||
pll_ctrl.set_bypass_force(false);
|
||||
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
|
||||
}
|
334
zynq7000-hal/src/ddr/ll.rs
Normal file
334
zynq7000-hal/src/ddr/ll.rs
Normal file
@@ -0,0 +1,334 @@
|
||||
//! Low-level DDR configuration module.
|
||||
use arbitrary_int::{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
227
zynq7000-hal/src/ddr/mod.rs
Normal file
@@ -0,0 +1,227 @@
|
||||
use arbitrary_int::u6;
|
||||
use zynq7000::ddrc::MmioDdrController;
|
||||
|
||||
use crate::{
|
||||
BootMode,
|
||||
clocks::pll::{PllConfig, configure_ddr_pll},
|
||||
time::Hertz,
|
||||
};
|
||||
|
||||
pub mod ll;
|
||||
|
||||
pub use ll::{DdrcConfigSet, DdriobConfigSet};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DdrClockSetupConfig {
|
||||
pub ps_clk: Hertz,
|
||||
pub ddr_clk: Hertz,
|
||||
pub ddr_3x_div: u6,
|
||||
pub ddr_2x_div: u6,
|
||||
}
|
||||
|
||||
impl DdrClockSetupConfig {
|
||||
pub const fn new(ps_clk: Hertz, ddr_clk: Hertz, ddr_3x_div: u6, ddr_2x_div: u6) -> Self {
|
||||
Self {
|
||||
ps_clk,
|
||||
ddr_clk,
|
||||
ddr_3x_div,
|
||||
ddr_2x_div,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This completely sets up the DDR module for DDR3 operation.
|
||||
///
|
||||
/// It performs the following steps accoridng to the functional programming model of the
|
||||
/// DDR memory controller, TRM p.323.
|
||||
///
|
||||
/// 1. Configures the DDR PLL to the target clock frequency.
|
||||
/// 2. Configures the DDR clocks based on the user-provided configuration.
|
||||
/// 3. Configures and sets up the DDR I/O buffers (DDR IOB) module.
|
||||
/// 4. Configures and sets up the DDR controller (DDRC) module.
|
||||
///
|
||||
/// This function consumes the DDRC register block once and thus provides a safe interface for DDR
|
||||
/// initialization.
|
||||
pub fn configure_ddr_for_ddr3(
|
||||
mut ddrc_regs: MmioDdrController<'static>,
|
||||
boot_mode: BootMode,
|
||||
clk_setup_cfg: DdrClockSetupConfig,
|
||||
ddriob_cfg: &DdriobConfigSet,
|
||||
ddr_cfg: &DdrcConfigSet,
|
||||
) {
|
||||
// Set the DDR PLL output frequency to an even multiple of the operating frequency,
|
||||
// as recommended by the DDR documentation.
|
||||
configure_ddr_pll(
|
||||
boot_mode,
|
||||
PllConfig::new_from_target_clock(clk_setup_cfg.ps_clk, clk_setup_cfg.ddr_clk).unwrap(),
|
||||
);
|
||||
// Safety: Only done once here during start-up.
|
||||
let ddr_clks = unsafe {
|
||||
crate::clocks::DdrClocks::new_with_2x_3x_init(
|
||||
clk_setup_cfg.ddr_clk,
|
||||
clk_setup_cfg.ddr_3x_div,
|
||||
clk_setup_cfg.ddr_2x_div,
|
||||
)
|
||||
};
|
||||
let dci_clk_cfg = ll::calculate_dci_divisors(&ddr_clks);
|
||||
|
||||
ddrc_regs.modify_ddrc_ctrl(|mut val| {
|
||||
val.set_soft_reset(zynq7000::ddrc::regs::SoftReset::Reset);
|
||||
val
|
||||
});
|
||||
|
||||
// Safety: This is only called once during DDR initialization.
|
||||
unsafe {
|
||||
ll::configure_iob(ddriob_cfg);
|
||||
// Do not wait for completion, it takes a bit of time. We can set all the DDR config registers
|
||||
// before polling for completion.
|
||||
ll::calibrate_iob_impedance_for_ddr3(dci_clk_cfg, false);
|
||||
}
|
||||
ll::configure_ddr_config(&mut ddrc_regs, ddr_cfg);
|
||||
// Safety: This is only called once during DDR initialization, and we only modify DDR related
|
||||
// registers.
|
||||
let slcr = unsafe { crate::slcr::Slcr::steal() };
|
||||
let ddriob_shared = slcr.regs().ddriob_shared();
|
||||
// Wait for DDR IOB impedance calibration to complete first.
|
||||
while !ddriob_shared.read_dci_status().done() {
|
||||
cortex_ar::asm::nop();
|
||||
}
|
||||
log::debug!("DDR IOB impedance calib done");
|
||||
|
||||
// Now take the DDR out of reset.
|
||||
ddrc_regs.modify_ddrc_ctrl(|mut val| {
|
||||
val.set_soft_reset(zynq7000::ddrc::regs::SoftReset::Active);
|
||||
val
|
||||
});
|
||||
// Wait until the DDR setup has completed.
|
||||
while ddrc_regs.read_mode_status().operating_mode()
|
||||
!= zynq7000::ddrc::regs::OperatingMode::NormalOperation
|
||||
{
|
||||
// Wait for the soft reset to complete.
|
||||
cortex_ar::asm::nop();
|
||||
}
|
||||
}
|
||||
|
||||
pub mod memtest {
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum MemTestError {
|
||||
#[error("memory address is not aligned to 4 bytes")]
|
||||
AddrNotAligned,
|
||||
#[error("memory test error")]
|
||||
Memory {
|
||||
addr: usize,
|
||||
expected: u32,
|
||||
found: u32,
|
||||
},
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This tests writes and reads on a memory block starting at the base address
|
||||
/// with the size `words` times 4.
|
||||
pub unsafe fn walking_zero_test(base_addr: usize, words: usize) -> Result<(), MemTestError> {
|
||||
unsafe { walking_value_test(true, base_addr, words) }
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This tests writes and reads on a memory block starting at the base address
|
||||
/// with the size `words` times 4.
|
||||
pub unsafe fn walking_one_test(base_addr: usize, words: usize) -> Result<(), MemTestError> {
|
||||
unsafe { walking_value_test(true, base_addr, words) }
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This tests writes and reads on a memory block starting at the base address
|
||||
/// with the size `words` times 4.
|
||||
pub unsafe fn walking_value_test(
|
||||
walking_zero: bool,
|
||||
base_addr: usize,
|
||||
words: usize,
|
||||
) -> Result<(), MemTestError> {
|
||||
if words == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
if !base_addr.is_multiple_of(4) {
|
||||
return Err(MemTestError::AddrNotAligned);
|
||||
}
|
||||
let base_ptr = base_addr as *mut u32;
|
||||
|
||||
// For each bit position 0..31 generate pattern = 1 << bit
|
||||
for bit in 0..32 {
|
||||
let pattern = if walking_zero {
|
||||
!(1u32 << bit)
|
||||
} else {
|
||||
1u32 << bit
|
||||
};
|
||||
|
||||
// write pass
|
||||
for i in 0..words {
|
||||
unsafe {
|
||||
let p = base_ptr.add(i);
|
||||
core::ptr::write_volatile(p, pattern);
|
||||
}
|
||||
}
|
||||
|
||||
// read/verify pass
|
||||
for i in 0..words {
|
||||
let val;
|
||||
unsafe {
|
||||
let p = base_ptr.add(i) as *const u32;
|
||||
val = core::ptr::read_volatile(p);
|
||||
}
|
||||
if val != pattern {
|
||||
return Err(MemTestError::Memory {
|
||||
addr: base_addr + i * 4,
|
||||
expected: pattern,
|
||||
found: val,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This tests writes and reads on a memory block starting at the base address
|
||||
/// with the size `words` times 4.
|
||||
pub unsafe fn checkerboard_test(base_addr: usize, words: usize) -> Result<(), MemTestError> {
|
||||
if words == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
if !base_addr.is_multiple_of(4) {
|
||||
return Err(MemTestError::AddrNotAligned);
|
||||
}
|
||||
|
||||
let base_ptr = base_addr as *mut u32;
|
||||
let patterns = [0xAAAAAAAAu32, 0x55555555u32];
|
||||
|
||||
for &pattern in &patterns {
|
||||
// Write pass
|
||||
for i in 0..words {
|
||||
let value = if i % 2 == 0 { pattern } else { !pattern };
|
||||
unsafe {
|
||||
core::ptr::write_volatile(base_ptr.add(i), value);
|
||||
}
|
||||
}
|
||||
|
||||
// Read/verify pass
|
||||
for i in 0..words {
|
||||
let expected = if i % 2 == 0 { pattern } else { !pattern };
|
||||
let val = unsafe { core::ptr::read_volatile(base_ptr.add(i)) };
|
||||
|
||||
if val != expected {
|
||||
return Err(MemTestError::Memory {
|
||||
addr: base_addr + i * 4,
|
||||
expected,
|
||||
found: val,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
64
zynq7000-hal/src/devcfg.rs
Normal file
64
zynq7000-hal/src/devcfg.rs
Normal 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(())
|
||||
}
|
@@ -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].
|
||||
|
@@ -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()
|
||||
|
@@ -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);
|
||||
|
@@ -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::{
|
||||
|
@@ -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> {}
|
||||
|
@@ -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),
|
||||
|
@@ -156,7 +156,7 @@ impl GlobalTimerCounter {
|
||||
}
|
||||
}
|
||||
|
||||
/// GTC can be used for blocking delays.
|
||||
/// GTC can also be used for blocking delays.
|
||||
impl embedded_hal::delay::DelayNs for GlobalTimerCounter {
|
||||
fn delay_ns(&mut self, ns: u32) {
|
||||
if self.cpu_3x2x_clock.is_none() {
|
||||
|
@@ -11,7 +11,7 @@ use crate::gpio::mio::{
|
||||
Mio41, Mio42, Mio43, Mio44, Mio45, Mio46, Mio47, Mio50, Mio51,
|
||||
};
|
||||
use crate::{
|
||||
enable_amba_periph_clk,
|
||||
enable_amba_peripheral_clock,
|
||||
gpio::{
|
||||
IoPeriphPin,
|
||||
mio::{
|
||||
@@ -348,7 +348,7 @@ impl I2c {
|
||||
I2cId::I2c0 => crate::PeriphSelect::I2c0,
|
||||
I2cId::I2c1 => crate::PeriphSelect::I2c1,
|
||||
};
|
||||
enable_amba_periph_clk(periph_sel);
|
||||
enable_amba_peripheral_clock(periph_sel);
|
||||
//reset(id);
|
||||
regs.write_cr(
|
||||
Control::builder()
|
||||
|
@@ -13,10 +13,15 @@
|
||||
extern crate alloc;
|
||||
|
||||
use slcr::Slcr;
|
||||
use zynq7000::slcr::LevelShifterRegister;
|
||||
use zynq7000::{
|
||||
SpiClockPhase, SpiClockPolarity,
|
||||
slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister},
|
||||
};
|
||||
|
||||
pub mod 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 {}
|
||||
}
|
||||
|
108
zynq7000-hal/src/priv_tim.rs
Normal file
108
zynq7000-hal/src/priv_tim.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use core::{marker::PhantomData, sync::atomic::AtomicBool};
|
||||
|
||||
use zynq7000::priv_tim::InterruptStatus;
|
||||
|
||||
use crate::{clocks::ArmClocks, time::Hertz};
|
||||
|
||||
static CORE_0_TIM_TAKEN: AtomicBool = AtomicBool::new(false);
|
||||
static CORE_1_TIM_TAKEN: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// High-level CPU private timer driver.
|
||||
pub struct CpuPrivateTimer {
|
||||
regs: zynq7000::priv_tim::MmioCpuPrivateTimer<'static>,
|
||||
cpu_3x2x_clock: Hertz,
|
||||
// Add this marker to explicitely opt-out of Send and Sync.
|
||||
//
|
||||
// This is a CPU private timer and thus should not be sent to other threads.
|
||||
_not_send: PhantomData<*const ()>,
|
||||
}
|
||||
|
||||
impl CpuPrivateTimer {
|
||||
/// Take the CPU private timer for a given core.
|
||||
///
|
||||
/// This function can only be called once for each given core.
|
||||
pub fn take(clocks: &ArmClocks) -> Option<Self> {
|
||||
let mpidr = cortex_ar::register::mpidr::Mpidr::read();
|
||||
let core = mpidr.0 & 0xff;
|
||||
if core != 0 && core != 1 {
|
||||
return None;
|
||||
}
|
||||
if (core == 0 && CORE_0_TIM_TAKEN.swap(true, core::sync::atomic::Ordering::Relaxed))
|
||||
|| (core == 1 && CORE_1_TIM_TAKEN.swap(true, core::sync::atomic::Ordering::Relaxed))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self::steal(clocks))
|
||||
}
|
||||
|
||||
/// Create a new CPU private timer driver.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function allows to potentially create an arbitrary amount of timers for both cores.
|
||||
/// It also does not check the current core ID.
|
||||
pub fn steal(clocks: &ArmClocks) -> Self {
|
||||
Self {
|
||||
regs: unsafe { zynq7000::priv_tim::CpuPrivateTimer::new_mmio_fixed() },
|
||||
cpu_3x2x_clock: clocks.cpu_3x2x_clk(),
|
||||
_not_send: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Write reload value which is set by the hardware when the timer reaches zero and
|
||||
/// auto reload is enabled.
|
||||
#[inline]
|
||||
pub fn write_reload(&mut self, value: u32) {
|
||||
self.regs.write_reload(value);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_counter(&mut self, value: u32) {
|
||||
self.regs.write_counter(value);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn counter(&self) -> u32 {
|
||||
self.regs.read_counter()
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal::delay::DelayNs for CpuPrivateTimer {
|
||||
fn delay_ns(&mut self, ns: u32) {
|
||||
// Even for a value of 1000 MHz for CPU 3x2x and u32::MAX for nanoseconds, this will
|
||||
// never overflow.
|
||||
let ticks = (ns as u64 * self.cpu_3x2x_clock.raw() as u64) / 1_000_000_000;
|
||||
|
||||
// Split the total delay into manageable chunks (u32::MAX ticks max).
|
||||
let mut remaining = ticks;
|
||||
|
||||
self.regs.modify_control(|mut val| {
|
||||
val.set_enable(false);
|
||||
// The event flag is still set, which is all we care about.
|
||||
val.set_interrupt_enable(false);
|
||||
val.set_auto_reload(false);
|
||||
val
|
||||
});
|
||||
while remaining > 0 {
|
||||
let chunk = (remaining as u32).min(u32::MAX - 1);
|
||||
|
||||
// Clear the timer flag by writing 1 to it.
|
||||
self.regs
|
||||
.write_interrupt_status(InterruptStatus::builder().with_event_flag(true).build());
|
||||
|
||||
// Load the timer with the chunk value and start it.
|
||||
self.write_reload(chunk);
|
||||
self.write_counter(chunk);
|
||||
self.regs.modify_control(|mut val| {
|
||||
val.set_enable(true);
|
||||
val
|
||||
});
|
||||
|
||||
// Wait for the timer to count down to zero.
|
||||
while !self.regs.read_interrupt_status().event_flag() {}
|
||||
|
||||
remaining -= chunk as u64;
|
||||
}
|
||||
}
|
||||
}
|
234
zynq7000-hal/src/qspi/lqspi_configs.rs
Normal file
234
zynq7000-hal/src/qspi/lqspi_configs.rs
Normal file
@@ -0,0 +1,234 @@
|
||||
// The constants here are also checked/tested at compile time against table 12-3 in the TRM
|
||||
// p.368.
|
||||
use arbitrary_int::u3;
|
||||
use zynq7000::qspi::{InstructionCode, LinearQspiConfig};
|
||||
|
||||
pub const RD_ONE: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(false)
|
||||
.with_separate_memory_bus(false)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(false)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0x0)
|
||||
.with_num_dummy_bytes(u3::new(0x0))
|
||||
.with_instruction_code(InstructionCode::Read)
|
||||
.build();
|
||||
const RD_ONE_DEV_RAW: u32 = RD_ONE.raw_value();
|
||||
static_assertions::const_assert_eq!(RD_ONE_DEV_RAW, 0x8000_0003);
|
||||
|
||||
pub const RD_TWO: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(true)
|
||||
.with_separate_memory_bus(true)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(false)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0x0)
|
||||
.with_num_dummy_bytes(u3::new(0x0))
|
||||
.with_instruction_code(InstructionCode::Read)
|
||||
.build();
|
||||
const RD_TWO_RAW: u32 = RD_TWO.raw_value();
|
||||
static_assertions::const_assert_eq!(RD_TWO_RAW, 0xE000_0003);
|
||||
|
||||
pub const FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(false)
|
||||
.with_separate_memory_bus(false)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(false)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0x0)
|
||||
.with_num_dummy_bytes(u3::new(0x1))
|
||||
.with_instruction_code(InstructionCode::FastRead)
|
||||
.build();
|
||||
const FAST_RD_ONE_RAW: u32 = FAST_RD_ONE.raw_value();
|
||||
static_assertions::const_assert_eq!(FAST_RD_ONE_RAW, 0x8000_010B);
|
||||
|
||||
pub const FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(true)
|
||||
.with_separate_memory_bus(true)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(false)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0x0)
|
||||
.with_num_dummy_bytes(u3::new(0x1))
|
||||
.with_instruction_code(InstructionCode::FastRead)
|
||||
.build();
|
||||
const FAST_RD_TWO_RAW: u32 = FAST_RD_TWO.raw_value();
|
||||
static_assertions::const_assert_eq!(FAST_RD_TWO_RAW, 0xE000_010B);
|
||||
|
||||
pub const DUAL_OUT_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(false)
|
||||
.with_separate_memory_bus(false)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(false)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0x0)
|
||||
.with_num_dummy_bytes(u3::new(0x1))
|
||||
.with_instruction_code(InstructionCode::FastReadDualOutput)
|
||||
.build();
|
||||
const DUAL_OUT_FAST_RD_ONE_RAW: u32 = DUAL_OUT_FAST_RD_ONE.raw_value();
|
||||
static_assertions::const_assert_eq!(DUAL_OUT_FAST_RD_ONE_RAW, 0x8000_013B);
|
||||
|
||||
pub const DUAL_OUT_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(true)
|
||||
.with_separate_memory_bus(true)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(false)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0x0)
|
||||
.with_num_dummy_bytes(u3::new(0x1))
|
||||
.with_instruction_code(InstructionCode::FastReadDualOutput)
|
||||
.build();
|
||||
const DUAL_OUT_FAST_RD_TWO_RAW: u32 = DUAL_OUT_FAST_RD_TWO.raw_value();
|
||||
static_assertions::const_assert_eq!(DUAL_OUT_FAST_RD_TWO_RAW, 0xE000_013B);
|
||||
|
||||
pub const QUAD_OUT_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(false)
|
||||
.with_separate_memory_bus(false)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(false)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0x0)
|
||||
.with_num_dummy_bytes(u3::new(0x1))
|
||||
.with_instruction_code(InstructionCode::FastReadQuadOutput)
|
||||
.build();
|
||||
const QUAD_OUT_FAST_RD_ONE_RAW: u32 = QUAD_OUT_FAST_RD_ONE.raw_value();
|
||||
static_assertions::const_assert_eq!(QUAD_OUT_FAST_RD_ONE_RAW, 0x8000_016B);
|
||||
|
||||
pub const QUAD_OUT_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(true)
|
||||
.with_separate_memory_bus(true)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(false)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0x0)
|
||||
.with_num_dummy_bytes(u3::new(0x1))
|
||||
.with_instruction_code(InstructionCode::FastReadQuadOutput)
|
||||
.build();
|
||||
const QUAD_OUT_FAST_RD_TWO_RAW: u32 = QUAD_OUT_FAST_RD_TWO.raw_value();
|
||||
static_assertions::const_assert_eq!(QUAD_OUT_FAST_RD_TWO_RAW, 0xE000_016B);
|
||||
|
||||
pub(crate) mod winbond_spansion {
|
||||
use super::*;
|
||||
pub const DUAL_IO_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(false)
|
||||
.with_separate_memory_bus(false)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(true)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0xff)
|
||||
.with_num_dummy_bytes(u3::new(0x0))
|
||||
.with_instruction_code(InstructionCode::FastReadDualIo)
|
||||
.build();
|
||||
const DUAL_IO_FAST_RD_ONE_RAW: u32 = DUAL_IO_FAST_RD_ONE.raw_value();
|
||||
static_assertions::const_assert_eq!(DUAL_IO_FAST_RD_ONE_RAW, 0x82FF_00BB);
|
||||
|
||||
pub const DUAL_IO_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(true)
|
||||
.with_separate_memory_bus(true)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(true)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0xff)
|
||||
.with_num_dummy_bytes(u3::new(0x0))
|
||||
.with_instruction_code(InstructionCode::FastReadDualIo)
|
||||
.build();
|
||||
const DUAL_IO_FAST_RD_TWO_RAW: u32 = DUAL_IO_FAST_RD_TWO.raw_value();
|
||||
static_assertions::const_assert_eq!(DUAL_IO_FAST_RD_TWO_RAW, 0xE2FF_00BB);
|
||||
|
||||
pub const QUAD_IO_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(false)
|
||||
.with_separate_memory_bus(false)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(true)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0xff)
|
||||
.with_num_dummy_bytes(u3::new(0x2))
|
||||
.with_instruction_code(InstructionCode::FastReadQuadIo)
|
||||
.build();
|
||||
const QUAD_IO_FAST_RD_ONE_RAW: u32 = QUAD_IO_FAST_RD_ONE.raw_value();
|
||||
static_assertions::const_assert_eq!(QUAD_IO_FAST_RD_ONE_RAW, 0x82FF_02EB);
|
||||
|
||||
pub const QUAD_IO_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(true)
|
||||
.with_separate_memory_bus(true)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(true)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0xff)
|
||||
.with_num_dummy_bytes(u3::new(0x2))
|
||||
.with_instruction_code(InstructionCode::FastReadQuadIo)
|
||||
.build();
|
||||
const QUAD_IO_FAST_RD_TWO_RAW: u32 = QUAD_IO_FAST_RD_TWO.raw_value();
|
||||
static_assertions::const_assert_eq!(QUAD_IO_FAST_RD_TWO_RAW, 0xE2FF_02EB);
|
||||
}
|
||||
|
||||
pub(crate) mod micron {
|
||||
use super::*;
|
||||
pub const DUAL_IO_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(false)
|
||||
.with_separate_memory_bus(false)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(true)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0xff)
|
||||
.with_num_dummy_bytes(u3::new(0x1))
|
||||
.with_instruction_code(InstructionCode::FastReadDualIo)
|
||||
.build();
|
||||
const DUAL_IO_FAST_RD_ONE_RAW: u32 = DUAL_IO_FAST_RD_ONE.raw_value();
|
||||
static_assertions::const_assert_eq!(DUAL_IO_FAST_RD_ONE_RAW, 0x82FF_01BB);
|
||||
|
||||
pub const DUAL_IO_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(true)
|
||||
.with_separate_memory_bus(true)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(true)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0xff)
|
||||
.with_num_dummy_bytes(u3::new(0x1))
|
||||
.with_instruction_code(InstructionCode::FastReadDualIo)
|
||||
.build();
|
||||
const DUAL_IO_FAST_RD_TWO_RAW: u32 = DUAL_IO_FAST_RD_TWO.raw_value();
|
||||
static_assertions::const_assert_eq!(DUAL_IO_FAST_RD_TWO_RAW, 0xE2FF_01BB);
|
||||
|
||||
pub const QUAD_IO_FAST_RD_ONE: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(false)
|
||||
.with_separate_memory_bus(false)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(true)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0xff)
|
||||
.with_num_dummy_bytes(u3::new(0x4))
|
||||
.with_instruction_code(InstructionCode::FastReadQuadIo)
|
||||
.build();
|
||||
const QUAD_IO_FAST_RD_ONE_RAW: u32 = QUAD_IO_FAST_RD_ONE.raw_value();
|
||||
static_assertions::const_assert_eq!(QUAD_IO_FAST_RD_ONE_RAW, 0x82FF_04EB);
|
||||
|
||||
pub const QUAD_IO_FAST_RD_TWO: LinearQspiConfig = LinearQspiConfig::builder()
|
||||
.with_enable_linear_mode(true)
|
||||
.with_both_memories(true)
|
||||
.with_separate_memory_bus(true)
|
||||
.with_upper_memory_page(false)
|
||||
.with_mode_enable(true)
|
||||
.with_mode_on(false)
|
||||
.with_mode_bits(0xff)
|
||||
.with_num_dummy_bytes(u3::new(0x4))
|
||||
.with_instruction_code(InstructionCode::FastReadQuadIo)
|
||||
.build();
|
||||
const QUAD_IO_FAST_RD_TWO_RAW: u32 = QUAD_IO_FAST_RD_TWO.raw_value();
|
||||
static_assertions::const_assert_eq!(QUAD_IO_FAST_RD_TWO_RAW, 0xE2FF_04EB);
|
||||
}
|
681
zynq7000-hal/src/qspi/mod.rs
Normal file
681
zynq7000-hal/src/qspi/mod.rs
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
@@ -40,8 +40,8 @@ impl Slcr {
|
||||
/// Returns a mutable reference to the SLCR MMIO block.
|
||||
///
|
||||
/// The MMIO block will not be unlocked. However, the registers can still be read.
|
||||
pub fn regs(&mut self) -> &mut MmioSlcr<'static> {
|
||||
&mut self.0
|
||||
pub fn regs(&self) -> &MmioSlcr<'static> {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Modify the SLCR register.
|
||||
|
@@ -2,7 +2,6 @@
|
||||
use core::convert::Infallible;
|
||||
|
||||
use crate::clocks::Clocks;
|
||||
use crate::enable_amba_periph_clk;
|
||||
use crate::gpio::IoPeriphPin;
|
||||
use crate::gpio::mio::{
|
||||
Mio10, Mio11, Mio12, Mio13, Mio14, Mio15, Mio28, Mio29, Mio30, Mio31, Mio32, Mio33, Mio34,
|
||||
@@ -13,12 +12,13 @@ use crate::gpio::mio::{
|
||||
Mio16, Mio17, Mio18, Mio19, Mio20, Mio21, Mio22, Mio23, Mio24, Mio25, Mio26, Mio27, Mio40,
|
||||
Mio41, Mio42, Mio43, Mio44, Mio45, Mio46, Mio47, Mio48, Mio49, Mio50, Mio51,
|
||||
};
|
||||
use crate::{enable_amba_peripheral_clock, spi_mode_const_to_cpol_cpha};
|
||||
|
||||
use crate::{clocks::IoClocks, slcr::Slcr, time::Hertz};
|
||||
use arbitrary_int::{Number, u3, u4, u6};
|
||||
use 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);
|
||||
|
@@ -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 {
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
@@ -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]
|
||||
|
@@ -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,
|
||||
|
@@ -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
880
zynq7000/src/ddrc.rs
Normal file
@@ -0,0 +1,880 @@
|
||||
pub const DDRC_BASE_ADDR: usize = 0xF800_6000;
|
||||
|
||||
pub mod regs {
|
||||
use arbitrary_int::{u2, u3, u4, u5, u6, u7, u9, u10, u11, u12, u20};
|
||||
|
||||
#[bitbybit::bitenum(u2)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum DataBusWidth {
|
||||
_32Bit = 0b00,
|
||||
_16Bit = 0b01,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SoftReset {
|
||||
Reset = 0,
|
||||
Active = 1,
|
||||
}
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DdrcControl {
|
||||
#[bit(16, rw)]
|
||||
disable_auto_refresh: bool,
|
||||
#[bit(15, rw)]
|
||||
disable_active_bypass: bool,
|
||||
#[bit(14, rw)]
|
||||
disable_read_bypass: bool,
|
||||
#[bits(7..=13, rw)]
|
||||
read_write_idle_gap: u7,
|
||||
#[bits(4..=6, rw)]
|
||||
burst8_refresh: u3,
|
||||
#[bits(2..=3, rw)]
|
||||
data_bus_width: Option<DataBusWidth>,
|
||||
#[bit(1, rw)]
|
||||
power_down_enable: bool,
|
||||
#[bit(0, rw)]
|
||||
soft_reset: SoftReset,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct TwoRankConfig {
|
||||
#[bits(14..=18, rw)]
|
||||
addrmap_cs_bit0: u5,
|
||||
/// Reserved register, but for some reason, Xilinx tooling writes a 1 here?
|
||||
#[bits(12..=13, rw)]
|
||||
ddrc_active_ranks: u2,
|
||||
/// tREFI - Average time between refreshes, in multiples of 32 clocks.
|
||||
#[bits(0..=11, rw)]
|
||||
rfc_nom_x32: u12,
|
||||
}
|
||||
|
||||
/// Queue control for the low priority and high priority read queues.
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct LprHprQueueControl {
|
||||
#[bits(22..=25, rw)]
|
||||
xact_run_length: u4,
|
||||
#[bits(11..=21, rw)]
|
||||
max_starve_x32: u11,
|
||||
#[bits(0..=10, rw)]
|
||||
min_non_critical_x32: u11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct WriteQueueControl {
|
||||
#[bits(15..=25, rw)]
|
||||
max_starve_x32: u11,
|
||||
#[bits(11..=14, rw)]
|
||||
xact_run_length: u4,
|
||||
#[bits(0..=10, rw)]
|
||||
min_non_critical_x32: u11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramParamReg0 {
|
||||
/// Minimum time to wait after coming out of self refresh before doing anything. This must be
|
||||
/// bigger than all the constraints that exist.
|
||||
#[bits(14..=20, rw)]
|
||||
post_selfref_gap_x32: u7,
|
||||
/// tRFC(min) - Minimum time from refresh to refresh or activate in clock
|
||||
/// cycles.
|
||||
#[bits(6..=13, rw)]
|
||||
t_rfc_min: u8,
|
||||
/// tRC - Min time between activates to the same bank.
|
||||
#[bits(0..=5, rw)]
|
||||
t_rc: u6,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramParamReg1 {
|
||||
#[bits(28..=31, rw)]
|
||||
t_cke: u4,
|
||||
#[bits(22..=26, rw)]
|
||||
t_ras_min: u5,
|
||||
#[bits(16..=21, rw)]
|
||||
t_ras_max: u6,
|
||||
#[bits(10..=15, rw)]
|
||||
t_faw: u6,
|
||||
#[bits(5..=9, rw)]
|
||||
powerdown_to_x32: u5,
|
||||
#[bits(0..=4, rw)]
|
||||
wr2pre: u5,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramParamReg2 {
|
||||
#[bits(28..=31, rw)]
|
||||
t_rcd: u4,
|
||||
#[bits(23..=27, rw)]
|
||||
rd2pre: u5,
|
||||
#[bits(20..=22, rw)]
|
||||
pad_pd: u3,
|
||||
#[bits(15..=19, rw)]
|
||||
t_xp: u5,
|
||||
#[bits(10..=14, rw)]
|
||||
wr2rd: u5,
|
||||
#[bits(5..=9, rw)]
|
||||
rd2wr: u5,
|
||||
#[bits(0..=4, rw)]
|
||||
write_latency: u5,
|
||||
}
|
||||
|
||||
/// Weird naming.
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
pub enum MobileSetting {
|
||||
Ddr2Ddr3 = 0,
|
||||
Lpddr2 = 1,
|
||||
}
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramParamReg3 {
|
||||
#[bit(30, rw)]
|
||||
disable_pad_pd_feature: bool,
|
||||
#[bits(24..=28, rw)]
|
||||
read_latency: u5,
|
||||
#[bit(23, rw)]
|
||||
enable_dfi_dram_clk_disable: bool,
|
||||
/// 0: DDR2 or DDR3. 1: LPDDR2.
|
||||
#[bit(22, rw)]
|
||||
mobile: MobileSetting,
|
||||
/// Must be set to 0.
|
||||
#[bit(21, rw)]
|
||||
sdram: bool,
|
||||
#[bits(16..=20, rw)]
|
||||
refresh_to_x32: u5,
|
||||
#[bits(12..=15, rw)]
|
||||
t_rp: u4,
|
||||
#[bits(8..=11, rw)]
|
||||
refresh_margin: u4,
|
||||
#[bits(5..=7, rw)]
|
||||
t_rrd: u3,
|
||||
#[bits(2..=4, rw)]
|
||||
t_ccd: u3,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
pub enum ModeRegisterType {
|
||||
Write = 0,
|
||||
Read = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramParamReg4 {
|
||||
#[bit(27, rw)]
|
||||
mr_rdata_valid: bool,
|
||||
#[bit(26, rw)]
|
||||
mr_type: ModeRegisterType,
|
||||
#[bit(25, rw)]
|
||||
mr_wr_busy: bool,
|
||||
#[bits(9..=24, rw)]
|
||||
mr_data: u16,
|
||||
#[bits(7..=8, rw)]
|
||||
mr_addr: u2,
|
||||
#[bit(6, rw)]
|
||||
mr_wr: bool,
|
||||
#[bit(1, rw)]
|
||||
prefer_write: bool,
|
||||
#[bit(0, rw)]
|
||||
enable_2t_timing_mode: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramInitParam {
|
||||
#[bits(11..=13, rw)]
|
||||
t_mrd: u3,
|
||||
#[bits(7..=10, rw)]
|
||||
pre_ocd_x32: u4,
|
||||
#[bits(0..=6, rw)]
|
||||
final_wait_x32: u7,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramEmr {
|
||||
#[bits(16..=31, rw)]
|
||||
emr3: u16,
|
||||
#[bits(0..=15, rw)]
|
||||
emr2: u16,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramEmrMr {
|
||||
#[bits(16..=31, rw)]
|
||||
emr: u16,
|
||||
#[bits(0..=15, rw)]
|
||||
mr: u16,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramBurst8ReadWrite {
|
||||
#[bits(0..=3, rw)]
|
||||
burst_rdwr: u4,
|
||||
#[bits(4..=13, rw)]
|
||||
pre_cke_x1024: u10,
|
||||
#[bits(16..=25, rw)]
|
||||
post_cke_x1024: u10,
|
||||
#[bit(26, rw)]
|
||||
burstchop: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DisableDq {
|
||||
#[bit(1, rw)]
|
||||
dis_dq: bool,
|
||||
#[bit(0, rw)]
|
||||
force_low_pri_n: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramAddrMapBank {
|
||||
#[bits(16..=19, rw)]
|
||||
addrmap_bank_b6: u4,
|
||||
#[bits(12..=15, rw)]
|
||||
addrmap_bank_b5: u4,
|
||||
#[bits(8..=11, rw)]
|
||||
addrmap_bank_b2: u4,
|
||||
#[bits(4..=7, rw)]
|
||||
addrmap_bank_b1: u4,
|
||||
#[bits(0..=3, rw)]
|
||||
addrmap_bank_b0: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramAddrMapColumn {
|
||||
#[bits(28..=31, rw)]
|
||||
addrmap_col_b11: u4,
|
||||
#[bits(24..=27, rw)]
|
||||
addrmap_col_b10: u4,
|
||||
#[bits(20..=23, rw)]
|
||||
addrmap_col_b9: u4,
|
||||
#[bits(16..=19, rw)]
|
||||
addrmap_col_b8: u4,
|
||||
#[bits(12..=15, rw)]
|
||||
addrmap_col_b7: u4,
|
||||
#[bits(8..=11, rw)]
|
||||
addrmap_col_b4: u4,
|
||||
#[bits(4..=7, rw)]
|
||||
addrmap_col_b3: u4,
|
||||
#[bits(0..=3, rw)]
|
||||
addrmap_col_b2: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramAddrMapRow {
|
||||
#[bits(24..=27, rw)]
|
||||
addrmap_row_b15: u4,
|
||||
#[bits(20..=23, rw)]
|
||||
addrmap_row_b14: u4,
|
||||
#[bits(16..=19, rw)]
|
||||
addrmap_row_b13: u4,
|
||||
#[bits(12..=15, rw)]
|
||||
addrmap_row_b12: u4,
|
||||
#[bits(8..=11, rw)]
|
||||
addrmap_row_b2_11: u4,
|
||||
#[bits(4..=7, rw)]
|
||||
addrmap_row_b1: u4,
|
||||
#[bits(0..=3, rw)]
|
||||
addrmap_row_b0: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DramOdt {
|
||||
#[bits(16..=17, rw)]
|
||||
phy_idle_local_odt: u2,
|
||||
#[bits(14..=15, rw)]
|
||||
phy_write_local_odt: u2,
|
||||
#[bits(12..=13, rw)]
|
||||
phy_read_local_odt: u2,
|
||||
#[bits(3..=5, rw)]
|
||||
rank0_wr_odt: u3,
|
||||
#[bits(0..=2, rw)]
|
||||
rank0_rd_odt: u3,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct PhyCmdTimeoutRdDataCpt {
|
||||
#[bits(28..=31, rw)]
|
||||
wrlvl_num_of_dq0: u4,
|
||||
#[bits(24..=27, rw)]
|
||||
gatelvl_num_of_dq0: u4,
|
||||
#[bit(19, rw)]
|
||||
clk_stall_level: bool,
|
||||
#[bit(18, rw)]
|
||||
dis_phy_ctrl_rstn: bool,
|
||||
#[bit(17, rw)]
|
||||
rdc_fifo_rst_err_cnt_clr: bool,
|
||||
#[bit(16, rw)]
|
||||
use_fixed_re: bool,
|
||||
#[bits(8..=11, rw)]
|
||||
rdc_we_to_re_delay: u4,
|
||||
#[bits(4..=7, rw)]
|
||||
wr_cmd_to_data: u4,
|
||||
#[bits(0..=3, rw)]
|
||||
rd_cmd_to_data: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
pub enum DllCalibSel {
|
||||
Periodic = 0,
|
||||
Manual = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DllCalib {
|
||||
#[bit(16, rw)]
|
||||
sel: DllCalibSel,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct OdtDelayHold {
|
||||
#[bits(12..=15, rw)]
|
||||
wr_odt_hold: u4,
|
||||
#[bits(8..=11, rw)]
|
||||
rd_odt_hold: u4,
|
||||
#[bits(4..=7, rw)]
|
||||
wr_odt_delay: u4,
|
||||
#[bits(0..=3, rw)]
|
||||
rd_odt_delay: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CtrlReg1 {
|
||||
#[bit(12, rw)]
|
||||
selfref_enable: bool,
|
||||
#[bit(10, rw)]
|
||||
dis_collision_page_opt: bool,
|
||||
#[bit(9, rw)]
|
||||
dis_wc: bool,
|
||||
#[bit(8, rw)]
|
||||
refresh_update_level: bool,
|
||||
#[bit(7, rw)]
|
||||
auto_pre_en: bool,
|
||||
#[bits(1..=6, rw)]
|
||||
lpr_num_entries: u6,
|
||||
#[bit(0, rw)]
|
||||
pageclose: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CtrlReg2 {
|
||||
#[bit(17, rw)]
|
||||
go_2_critcal_enable: bool,
|
||||
#[bits(5..=12, rw)]
|
||||
go_2_critical_hysteresis: u8,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CtrlReg3 {
|
||||
#[bits(16..=25, rw)]
|
||||
dfi_t_wlmrd: u10,
|
||||
#[bits(8..=15, rw)]
|
||||
rdlvl_rr: u8,
|
||||
#[bits(0..=7, rw)]
|
||||
wrlvl_ww: u8,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CtrlReg4 {
|
||||
#[bits(8..=15, rw)]
|
||||
dfi_t_ctrlupd_interval_max_x1024: u8,
|
||||
#[bits(0..=7, rw)]
|
||||
dfi_t_ctrlupd_interval_min_x1024: u8,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CtrlReg5 {
|
||||
#[bits(20..=25, rw)]
|
||||
t_ckesr: u6,
|
||||
#[bits(16..=19, rw)]
|
||||
t_cksrx: u4,
|
||||
#[bits(12..=15, rw)]
|
||||
t_ckrse: u4,
|
||||
#[bits(8..=11, rw)]
|
||||
dfi_t_dram_clk_enable: u4,
|
||||
#[bits(4..=7, rw)]
|
||||
dfi_t_dram_clk_disable: u4,
|
||||
#[bits(0..=3, rw)]
|
||||
dfi_t_ctrl_delay: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CtrlReg6 {
|
||||
#[bits(16..=19, rw)]
|
||||
t_cksx: u4,
|
||||
#[bits(12..=15, rw)]
|
||||
t_ckdpdx: u4,
|
||||
#[bits(8..=11, rw)]
|
||||
t_ckdpde: u4,
|
||||
#[bits(4..=7, rw)]
|
||||
t_ckpdx: u4,
|
||||
#[bits(0..=3, rw)]
|
||||
t_ckpde: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CheTZq {
|
||||
#[bits(22..=31, rw)]
|
||||
t_zq_short_nop: u10,
|
||||
#[bits(12..=21, rw)]
|
||||
t_zq_long_nop: u10,
|
||||
#[bits(2..=11, rw)]
|
||||
t_mode: u10,
|
||||
#[bit(1, rw)]
|
||||
ddr3: bool,
|
||||
#[bit(0, rw)]
|
||||
dis_auto_zq: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CheTZqShortInterval {
|
||||
#[bits(20..=27, rw)]
|
||||
dram_rstn_x1024: u8,
|
||||
#[bits(0..=19, rw)]
|
||||
t_zq_short_interval: u20,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DeepPowerdown {
|
||||
#[bits(1..=8, rw)]
|
||||
deep_powerdown_to_x1024: u8,
|
||||
#[bit(0, rw)]
|
||||
enable: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct Reg2c {
|
||||
#[bit(28, rw)]
|
||||
dfi_rd_data_eye_train: bool,
|
||||
#[bit(27, rw)]
|
||||
dfi_rd_dqs_gate_level: bool,
|
||||
#[bit(26, rw)]
|
||||
dfi_wr_level_enable: bool,
|
||||
#[bit(25, rw)]
|
||||
trdlvl_max_error: bool,
|
||||
#[bit(24, rw)]
|
||||
twrlvl_max_error: bool,
|
||||
#[bits(12..=23, rw)]
|
||||
dfi_rdlvl_max_x1024: u12,
|
||||
#[bits(0..=11, rw)]
|
||||
dfi_wrlvl_max_x1024: u12,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct Reg2d {
|
||||
#[bit(9, rw)]
|
||||
skip_ocd: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DfiTiming {
|
||||
#[bits(15..=24, rw)]
|
||||
dfi_t_ctrlup_max: u10,
|
||||
#[bits(5..=14, rw)]
|
||||
dfi_t_ctrlup_min: u10,
|
||||
#[bits(0..=4, rw)]
|
||||
dfi_t_rddata_enable: u5,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct CheEccControl {
|
||||
#[bit(1, rw)]
|
||||
clear_correctable_errors: bool,
|
||||
#[bit(0, rw)]
|
||||
clear_uncorrectable_errors: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u3, exhaustive = false)]
|
||||
pub enum EccMode {
|
||||
NoEcc = 0b000,
|
||||
SecDecOverOneBeat = 0b100,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct EccScrub {
|
||||
#[bit(3, rw)]
|
||||
disable_scrub: bool,
|
||||
#[bits(0..=2, rw)]
|
||||
ecc_mode: Option<EccMode>,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct PhyReceiverEnable {
|
||||
#[bits(4..=7, rw)]
|
||||
phy_dif_off: u4,
|
||||
#[bits(0..=3, rw)]
|
||||
phy_dif_on: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct PhyConfig {
|
||||
#[bits(24..=30, rw)]
|
||||
dq_offset: u7,
|
||||
#[bit(3, rw)]
|
||||
wrlvl_inc_mode: bool,
|
||||
#[bit(2, rw)]
|
||||
gatelvl_inc_mode: bool,
|
||||
#[bit(1, rw)]
|
||||
rdlvl_inc_mode: bool,
|
||||
#[bit(0, rw)]
|
||||
data_slice_in_use: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct PhyInitRatio {
|
||||
#[bits(10..=19, rw)]
|
||||
gatelvl_init_ratio: u10,
|
||||
#[bits(0..=9, rw)]
|
||||
wrlvl_init_ratio: u10,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct PhyDqsConfig {
|
||||
#[bits(11..=19, rw)]
|
||||
dqs_slave_delay: u9,
|
||||
#[bit(10, rw)]
|
||||
dqs_slave_force: bool,
|
||||
#[bits(0..=9, rw)]
|
||||
dqs_slave_ratio: u10,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct PhyWriteEnableConfig {
|
||||
#[bits(12..=20, rw)]
|
||||
fifo_we_in_delay: u9,
|
||||
#[bit(11, rw)]
|
||||
fifo_we_in_force: bool,
|
||||
#[bits(0..=10, rw)]
|
||||
fifo_we_slave_ratio: u11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct PhyWriteDataSlaveConfig {
|
||||
#[bits(11..=19, rw)]
|
||||
wr_data_slave_delay: u9,
|
||||
#[bit(10, rw)]
|
||||
wr_data_slave_force: bool,
|
||||
#[bits(0..=9, rw)]
|
||||
wr_data_slave_ratio: u10,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct Reg64 {
|
||||
#[bit(30, rw)]
|
||||
cmd_latency: bool,
|
||||
#[bit(29, rw)]
|
||||
lpddr: bool,
|
||||
#[bits(21..=27, rw)]
|
||||
ctrl_slave_delay: u7,
|
||||
#[bit(20, rw)]
|
||||
ctrl_slave_force: bool,
|
||||
#[bits(10..=19, rw)]
|
||||
ctrl_slave_ratio: u10,
|
||||
#[bit(9, rw)]
|
||||
sel_logic: bool,
|
||||
#[bit(7, rw)]
|
||||
invert_clkout: bool,
|
||||
#[bit(1, rw)]
|
||||
bl2: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct Reg65 {
|
||||
#[bits(18..=19, rw)]
|
||||
ctrl_slave_delay: u2,
|
||||
#[bit(17, rw)]
|
||||
dis_calib_rst: bool,
|
||||
#[bit(16, rw)]
|
||||
use_rd_data_eye_level: bool,
|
||||
#[bit(15, rw)]
|
||||
use_rd_dqs_gate_level: bool,
|
||||
#[bit(14, rw)]
|
||||
use_wr_level: bool,
|
||||
#[bits(10..=13, rw)]
|
||||
dll_lock_diff: u4,
|
||||
#[bits(5..=9, rw)]
|
||||
rd_rl_delay: u5,
|
||||
#[bits(0..=4, rw)]
|
||||
wr_rl_delay: u5,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct AxiPriorityWritePort {
|
||||
#[bit(18, rw)]
|
||||
disable_page_match: bool,
|
||||
#[bit(17, rw)]
|
||||
disable_urgent: bool,
|
||||
#[bit(16, rw)]
|
||||
disable_aging: bool,
|
||||
#[bits(0..=9, rw)]
|
||||
pri_wr_port: u10,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct AxiPriorityReadPort {
|
||||
#[bit(19, rw)]
|
||||
enable_hpr: bool,
|
||||
#[bit(18, rw)]
|
||||
disable_page_match: bool,
|
||||
#[bit(17, rw)]
|
||||
disable_urgent: bool,
|
||||
#[bit(16, rw)]
|
||||
disable_aging: bool,
|
||||
#[bits(0..=9, rw)]
|
||||
pri_rd_port_n: u10,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct ExclusiveAccessConfig {
|
||||
#[bits(9..=17, rw)]
|
||||
access_id1_port: u9,
|
||||
#[bits(0..=8, rw)]
|
||||
access_id0_port: u9,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
pub enum LpddrBit {
|
||||
Ddr2Ddr3 = 0,
|
||||
Lpddr2 = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct LpddrControl0 {
|
||||
#[bits(4..=11, rw)]
|
||||
mr4_margin: u8,
|
||||
#[bit(2, rw)]
|
||||
derate_enable: bool,
|
||||
#[bit(1, rw)]
|
||||
per_bank_refresh: bool,
|
||||
#[bit(0, rw)]
|
||||
lpddr2: LpddrBit,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct LpddrControl1 {
|
||||
#[bits(0..=31, rw)]
|
||||
mr4_read_interval: u32,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct LpddrControl2 {
|
||||
#[bits(12..=21, rw)]
|
||||
t_mrw: u10,
|
||||
#[bits(4..=11, rw)]
|
||||
idle_after_reset_x32: u8,
|
||||
#[bits(0..=3, rw)]
|
||||
min_stable_clock_x1: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct LpddrControl3 {
|
||||
#[bits(8..=17, rw)]
|
||||
dev_zqinit_x32: u10,
|
||||
#[bits(0..=7, rw)]
|
||||
max_auto_init_x1024: u8,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u3, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum OperatingMode {
|
||||
DdrcInit = 0,
|
||||
NormalOperation = 1,
|
||||
Powerdown = 2,
|
||||
SelfRefresh = 3,
|
||||
DeepPowerdown = 4,
|
||||
DeepPowerdownAlt1 = 5,
|
||||
DeepPowerdownAlt2 = 6,
|
||||
DeepPowerdownAlt3 = 7,
|
||||
}
|
||||
|
||||
impl OperatingMode {
|
||||
pub fn is_deep_powerdown(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
OperatingMode::DeepPowerdown
|
||||
| OperatingMode::DeepPowerdownAlt1
|
||||
| OperatingMode::DeepPowerdownAlt2
|
||||
| OperatingMode::DeepPowerdownAlt3
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum DebugStallBit {
|
||||
CommandsAccepted = 0,
|
||||
CommandsNotAccepted = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct ModeStatus {
|
||||
#[bits(16..=20, r)]
|
||||
dbg_hpr_queue_depth: u5,
|
||||
#[bits(10..=15, r)]
|
||||
dbg_lpr_queue_depth: u6,
|
||||
#[bits(4..=9, r)]
|
||||
dbg_wr_queue_depth: u6,
|
||||
#[bit(3, r)]
|
||||
dbg_stall: DebugStallBit,
|
||||
#[bits(0..=2, r)]
|
||||
operating_mode: OperatingMode,
|
||||
}
|
||||
}
|
||||
|
||||
use regs::*;
|
||||
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[repr(C)]
|
||||
pub struct DdrController {
|
||||
ddrc_ctrl: DdrcControl,
|
||||
two_rank_cfg: TwoRankConfig,
|
||||
hpr_queue_ctrl: LprHprQueueControl,
|
||||
lpr_queue_ctrl: LprHprQueueControl,
|
||||
wr_reg: WriteQueueControl,
|
||||
dram_param_reg0: DramParamReg0,
|
||||
dram_param_reg1: DramParamReg1,
|
||||
dram_param_reg2: DramParamReg2,
|
||||
dram_param_reg3: DramParamReg3,
|
||||
dram_param_reg4: DramParamReg4,
|
||||
dram_init_param: DramInitParam,
|
||||
dram_emr: DramEmr,
|
||||
dram_emr_mr: DramEmrMr,
|
||||
dram_burst8_rdwr: DramBurst8ReadWrite,
|
||||
dram_disable_dq: DisableDq,
|
||||
dram_addr_map_bank: DramAddrMapBank,
|
||||
dram_addr_map_col: DramAddrMapColumn,
|
||||
dram_addr_map_row: DramAddrMapRow,
|
||||
dram_odt_reg: DramOdt,
|
||||
#[mmio(PureRead)]
|
||||
phy_debug_reg: u32,
|
||||
phy_cmd_timeout_rddata_cpt: PhyCmdTimeoutRdDataCpt,
|
||||
#[mmio(PureRead)]
|
||||
mode_status: ModeStatus,
|
||||
dll_calib: DllCalib,
|
||||
odt_delay_hold: OdtDelayHold,
|
||||
ctrl_reg1: CtrlReg1,
|
||||
ctrl_reg2: CtrlReg2,
|
||||
ctrl_reg3: CtrlReg3,
|
||||
ctrl_reg4: CtrlReg4,
|
||||
_reserved0: [u32; 0x2],
|
||||
ctrl_reg5: CtrlReg5,
|
||||
ctrl_reg6: CtrlReg6,
|
||||
|
||||
_reserved1: [u32; 0x8],
|
||||
|
||||
che_refresh_timer_01: u32,
|
||||
che_t_zq: CheTZq,
|
||||
che_t_zq_short_interval_reg: CheTZqShortInterval,
|
||||
deep_powerdown_reg: DeepPowerdown,
|
||||
reg_2c: Reg2c,
|
||||
reg_2d: Reg2d,
|
||||
dfi_timing: DfiTiming,
|
||||
_reserved2: [u32; 0x2],
|
||||
che_ecc_control: CheEccControl,
|
||||
#[mmio(PureRead)]
|
||||
che_corr_ecc_log: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_corr_ecc_addr: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_corr_ecc_data_31_0: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_corr_ecc_data_63_32: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_corr_ecc_data_71_64: u32,
|
||||
/// Clear on write, but the write is performed on another register.
|
||||
#[mmio(PureRead)]
|
||||
che_uncorr_ecc_log: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_uncorr_ecc_addr: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_uncorr_ecc_data_31_0: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_uncorr_ecc_data_63_32: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_uncorr_ecc_data_71_64: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_ecc_stats: u32,
|
||||
ecc_scrub: EccScrub,
|
||||
#[mmio(PureRead)]
|
||||
che_ecc_corr_bit_mask_31_0: u32,
|
||||
#[mmio(PureRead)]
|
||||
che_ecc_corr_bit_mask_63_32: u32,
|
||||
|
||||
_reserved3: [u32; 0x5],
|
||||
|
||||
phy_receiver_enable: PhyReceiverEnable,
|
||||
phy_config: [PhyConfig; 0x4],
|
||||
_reserved4: u32,
|
||||
phy_init_ratio: [PhyInitRatio; 4],
|
||||
_reserved5: u32,
|
||||
phy_rd_dqs_cfg: [PhyDqsConfig; 4],
|
||||
_reserved6: u32,
|
||||
phy_wr_dqs_cfg: [PhyDqsConfig; 4],
|
||||
_reserved7: u32,
|
||||
phy_we_cfg: [PhyWriteEnableConfig; 4],
|
||||
_reserved8: u32,
|
||||
phy_wr_data_slave: [PhyWriteDataSlaveConfig; 4],
|
||||
|
||||
_reserved9: u32,
|
||||
|
||||
reg_64: Reg64,
|
||||
reg_65: Reg65,
|
||||
_reserved10: [u32; 3],
|
||||
#[mmio(PureRead)]
|
||||
reg69_6a0: u32,
|
||||
#[mmio(PureRead)]
|
||||
reg69_6a1: u32,
|
||||
_reserved11: u32,
|
||||
#[mmio(PureRead)]
|
||||
reg69_6d2: u32,
|
||||
#[mmio(PureRead)]
|
||||
reg69_6d3: u32,
|
||||
#[mmio(PureRead)]
|
||||
reg69_710: u32,
|
||||
#[mmio(PureRead)]
|
||||
reg6e_711: u32,
|
||||
#[mmio(PureRead)]
|
||||
reg6e_712: u32,
|
||||
#[mmio(PureRead)]
|
||||
reg6e_713: u32,
|
||||
_reserved12: u32,
|
||||
#[mmio(PureRead)]
|
||||
phy_dll_status: [u32; 4],
|
||||
_reserved13: u32,
|
||||
#[mmio(PureRead)]
|
||||
dll_lock_status: u32,
|
||||
#[mmio(PureRead)]
|
||||
phy_control_status: u32,
|
||||
#[mmio(PureRead)]
|
||||
phy_control_status_2: u32,
|
||||
|
||||
_reserved14: [u32; 0x5],
|
||||
|
||||
// DDRI registers.
|
||||
#[mmio(PureRead)]
|
||||
axi_id: u32,
|
||||
page_mask: u32,
|
||||
axi_priority_wr_port: [AxiPriorityWritePort; 0x4],
|
||||
axi_priority_rd_port: [AxiPriorityReadPort; 0x4],
|
||||
|
||||
_reserved15: [u32; 0x1B],
|
||||
|
||||
excl_access_cfg: [ExclusiveAccessConfig; 0x4],
|
||||
#[mmio(PureRead)]
|
||||
mode_reg_read: u32,
|
||||
lpddr_ctrl_0: LpddrControl0,
|
||||
lpddr_ctrl_1: LpddrControl1,
|
||||
lpddr_ctrl_2: LpddrControl2,
|
||||
lpddr_ctrl_3: LpddrControl3,
|
||||
}
|
||||
|
||||
static_assertions::const_assert_eq!(core::mem::size_of::<DdrController>(), 0x2B8);
|
||||
|
||||
impl DdrController {
|
||||
/// Create a new DDR MMIO instance for the DDR controller at address [DDRC_BASE_ADDR].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This API can be used to potentially create a driver to the same peripheral structure
|
||||
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
|
||||
/// interfere with each other.
|
||||
pub const unsafe fn new_mmio_fixed() -> MmioDdrController<'static> {
|
||||
unsafe { Self::new_mmio_at(DDRC_BASE_ADDR) }
|
||||
}
|
||||
}
|
303
zynq7000/src/devcfg.rs
Normal file
303
zynq7000/src/devcfg.rs
Normal 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) }
|
||||
}
|
||||
}
|
@@ -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
48
zynq7000/src/priv_tim.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
//! # CPU private timer module.
|
||||
|
||||
pub const CPU_PRIV_TIM_BASE_ADDR: usize = super::mpcore::MPCORE_BASE_ADDR + 0x0000_0600;
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct Control {
|
||||
#[bits(8..=15, rw)]
|
||||
prescaler: u8,
|
||||
#[bit(2, rw)]
|
||||
interrupt_enable: bool,
|
||||
#[bit(1, rw)]
|
||||
auto_reload: bool,
|
||||
#[bit(0, rw)]
|
||||
enable: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct InterruptStatus {
|
||||
/// Cleared by writing a one.
|
||||
#[bit(0, rw)]
|
||||
event_flag: bool,
|
||||
}
|
||||
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[repr(C)]
|
||||
pub struct CpuPrivateTimer {
|
||||
reload: u32,
|
||||
counter: u32,
|
||||
control: Control,
|
||||
interrupt_status: InterruptStatus,
|
||||
}
|
||||
|
||||
impl CpuPrivateTimer {
|
||||
/// Create a new CPU Private Timer MMIO instance at the fixed base address.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This API can be used to potentially create a driver to the same peripheral structure
|
||||
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
|
||||
/// interfere with each other.
|
||||
///
|
||||
/// It should also be noted that the calls to this MMIO structure are private for each CPU
|
||||
/// core, which might lead to unexpected results when using this in a SMP system.
|
||||
#[inline]
|
||||
pub const unsafe fn new_mmio_fixed() -> MmioCpuPrivateTimer<'static> {
|
||||
unsafe { CpuPrivateTimer::new_mmio_at(CPU_PRIV_TIM_BASE_ADDR) }
|
||||
}
|
||||
}
|
278
zynq7000/src/qspi.rs
Normal file
278
zynq7000/src/qspi.rs
Normal file
@@ -0,0 +1,278 @@
|
||||
use arbitrary_int::{u2, u3};
|
||||
|
||||
pub use crate::{SpiClockPhase, SpiClockPolarity};
|
||||
|
||||
const QSPI_BASE_ADDR: usize = 0xE000D000;
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum InterfaceMode {
|
||||
LegacySpi = 0,
|
||||
FlashMemoryInterface = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Endianness {
|
||||
Little = 0,
|
||||
Big = 1,
|
||||
}
|
||||
|
||||
/// Baud rate divisor register values.
|
||||
#[bitbybit::bitenum(u3, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BaudRateDivisor {
|
||||
_2 = 0b000,
|
||||
_4 = 0b001,
|
||||
_8 = 0b010,
|
||||
_16 = 0b011,
|
||||
_32 = 0b100,
|
||||
_64 = 0b101,
|
||||
_128 = 0b110,
|
||||
_256 = 0b111,
|
||||
}
|
||||
|
||||
impl BaudRateDivisor {
|
||||
/// Actual divisor value.
|
||||
pub fn divisor(&self) -> usize {
|
||||
match self {
|
||||
BaudRateDivisor::_2 => 2,
|
||||
BaudRateDivisor::_4 => 4,
|
||||
BaudRateDivisor::_8 => 8,
|
||||
BaudRateDivisor::_16 => 16,
|
||||
BaudRateDivisor::_32 => 32,
|
||||
BaudRateDivisor::_64 => 64,
|
||||
BaudRateDivisor::_128 => 128,
|
||||
BaudRateDivisor::_256 => 256,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct Config {
|
||||
#[bit(31, rw)]
|
||||
interface_mode: InterfaceMode,
|
||||
#[bit(26, rw)]
|
||||
edianness: Endianness,
|
||||
#[bit(19, rw)]
|
||||
holdb_dr: bool,
|
||||
#[bit(16, w)]
|
||||
manual_start_command: bool,
|
||||
#[bit(15, rw)]
|
||||
manual_start_enable: bool,
|
||||
#[bit(14, rw)]
|
||||
manual_cs: bool,
|
||||
/// Directly drives the chip select line when CS is driven manually (bit 14 is set)
|
||||
#[bit(10, rw)]
|
||||
peripheral_chip_select: bool,
|
||||
/// The only valid value is 0b11 (32 bits)
|
||||
#[bits(6..=7, rw)]
|
||||
fifo_width: u2,
|
||||
#[bits(3..=5, rw)]
|
||||
baud_rate_div: BaudRateDivisor,
|
||||
#[bit(2, rw)]
|
||||
clock_phase: SpiClockPhase,
|
||||
#[bit(1, rw)]
|
||||
clock_polarity: SpiClockPolarity,
|
||||
/// Must be set to 1 before using QSPI, 0 is a reserved value.
|
||||
#[bit(0, rw)]
|
||||
mode_select: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct InterruptStatus {
|
||||
/// Write-to-clear bit.
|
||||
#[bit(6, rw)]
|
||||
tx_underflow: bool,
|
||||
#[bit(5, r)]
|
||||
rx_full: bool,
|
||||
#[bit(4, r)]
|
||||
rx_above_threshold: bool,
|
||||
#[bit(3, r)]
|
||||
tx_full: bool,
|
||||
#[bit(2, r)]
|
||||
tx_below_threshold: bool,
|
||||
/// Write-to-clear bit.
|
||||
#[bit(0, rw)]
|
||||
rx_overrun: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct InterruptControl {
|
||||
#[bit(6, w)]
|
||||
tx_underflow: bool,
|
||||
#[bit(5, w)]
|
||||
rx_full: bool,
|
||||
#[bit(4, w)]
|
||||
rx_not_empty: bool,
|
||||
#[bit(3, w)]
|
||||
tx_full: bool,
|
||||
#[bit(2, w)]
|
||||
tx_not_full: bool,
|
||||
#[bit(0, w)]
|
||||
rx_overrun: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct InterruptMask {
|
||||
#[bit(6, r)]
|
||||
tx_underflow: bool,
|
||||
#[bit(5, r)]
|
||||
rx_full: bool,
|
||||
#[bit(4, r)]
|
||||
rx_not_empty: bool,
|
||||
#[bit(3, r)]
|
||||
tx_full: bool,
|
||||
#[bit(2, r)]
|
||||
tx_not_full: bool,
|
||||
#[bit(0, r)]
|
||||
rx_overrun: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct SpiEnable {
|
||||
#[bit(0, rw)]
|
||||
enable: bool,
|
||||
}
|
||||
|
||||
/// All the delays are in SPI reference block or external clock cycles.
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct Delay {
|
||||
/// Length of the master mode chip select output de-asserts between words when CPHA = 0.
|
||||
#[bits(24..=31, rw)]
|
||||
deassert: u8,
|
||||
/// Delay between one chip select being de-activated and another being activated.
|
||||
#[bits(16..=23, rw)]
|
||||
between: u8,
|
||||
/// Length between last bit of current word and first bit of next word.
|
||||
#[bits(8..=15, rw)]
|
||||
after: u8,
|
||||
/// Delay between setting chip select low and first bit transfer.
|
||||
#[bits(0..=7, rw)]
|
||||
init: u8,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct Gpio {
|
||||
/// Active low write-protect bit.
|
||||
#[bit(0, rw)]
|
||||
write_protect_n: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct LoopbackMasterClockDelay {
|
||||
/// Use internal loopback master clock for read data capturing when the baud rate divisor
|
||||
/// is 2.
|
||||
#[bit(5, rw)]
|
||||
use_loopback: bool,
|
||||
#[bits(3..=4,rw)]
|
||||
delay_1: u2,
|
||||
#[bits(0..=2 ,rw)]
|
||||
delay_0: u3,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u8, exhaustive = false)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum InstructionCode {
|
||||
Read = 0x03,
|
||||
FastRead = 0x0B,
|
||||
FastReadDualOutput = 0x3B,
|
||||
FastReadQuadOutput = 0x6B,
|
||||
FastReadDualIo = 0xBB,
|
||||
FastReadQuadIo = 0xEB,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct LinearQspiConfig {
|
||||
#[bit(31, rw)]
|
||||
enable_linear_mode: bool,
|
||||
#[bit(30, rw)]
|
||||
both_memories: bool,
|
||||
/// Only has a meaning is bit 30 is set (both memories).
|
||||
#[bit(29, rw)]
|
||||
separate_memory_bus: bool,
|
||||
/// Upper memory page, if set. Only has a meaning if bit 30 is set and bit 29 / bit 31 are
|
||||
/// cleared.
|
||||
///
|
||||
/// In LQSPI mode, address bit 25 will indicate the lower (0) or upper (1) page.
|
||||
/// In IO mode, this bit selects the lower or upper memory.
|
||||
#[bit(28, rw)]
|
||||
upper_memory_page: bool,
|
||||
#[bit(25, rw)]
|
||||
mode_enable: bool,
|
||||
#[bit(24, rw)]
|
||||
mode_on: bool,
|
||||
#[bits(16..=23, rw)]
|
||||
mode_bits: u8,
|
||||
#[bits(8..=10, rw)]
|
||||
num_dummy_bytes: u3,
|
||||
#[bits(0..=7, rw)]
|
||||
instruction_code: Option<InstructionCode>,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct LinearQspiStatus {
|
||||
#[bit(2, rw)]
|
||||
data_fsm_error: bool,
|
||||
#[bit(1, rw)]
|
||||
axi_write_command_received: bool,
|
||||
}
|
||||
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[repr(C)]
|
||||
pub struct Qspi {
|
||||
config: Config,
|
||||
interrupt_status: InterruptStatus,
|
||||
#[mmio(Write)]
|
||||
interrupt_enable: InterruptControl,
|
||||
#[mmio(Write)]
|
||||
interrupt_disable: InterruptControl,
|
||||
#[mmio(PureRead)]
|
||||
interupt_mask: InterruptMask,
|
||||
spi_enable: SpiEnable,
|
||||
delay: Delay,
|
||||
/// Transmits 1-byte command and 3-byte data OR 4-byte data.
|
||||
#[mmio(Write)]
|
||||
tx_data_00: u32,
|
||||
#[mmio(PureRead)]
|
||||
rx_data: u32,
|
||||
slave_idle_count: u32,
|
||||
/// Defines the level at which the TX FIFO not full interrupt is generated.
|
||||
tx_fifo_threshold: u32,
|
||||
/// Defines the level at which the RX FIFO not empty interrupt is generated.
|
||||
rx_fifo_threshold: u32,
|
||||
gpio: Gpio,
|
||||
_reserved0: u32,
|
||||
loopback_master_clock_delay: LoopbackMasterClockDelay,
|
||||
_reserved1: [u32; 0x11],
|
||||
/// Transmits 1-byte command.
|
||||
#[mmio(Write)]
|
||||
tx_data_01: u32,
|
||||
/// Transmits 1-byte command and 1-byte data.
|
||||
#[mmio(Write)]
|
||||
tx_data_10: u32,
|
||||
/// Transmits 1-byte command and 2-byte data.
|
||||
#[mmio(Write)]
|
||||
tx_data_11: u32,
|
||||
_reserved2: [u32; 0x5],
|
||||
linear_qspi_config: LinearQspiConfig,
|
||||
linear_qspi_status: LinearQspiStatus,
|
||||
_reserved3: [u32; 0x15],
|
||||
/// Module ID value with reset value 0x1090101.
|
||||
module_id: u32,
|
||||
}
|
||||
|
||||
static_assertions::const_assert_eq!(core::mem::size_of::<Qspi>(), 0x100);
|
||||
|
||||
impl Qspi {
|
||||
/// Create a new QSPI MMIO instance for for QSPI controller at address [QSPI_BASE_ADDR].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This API can be used to potentially create a driver to the same peripheral structure
|
||||
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
|
||||
/// interfere with each other.
|
||||
pub const unsafe fn new_mmio_fixed() -> MmioQspi<'static> {
|
||||
unsafe { Self::new_mmio_at(QSPI_BASE_ADDR) }
|
||||
}
|
||||
}
|
@@ -4,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
139
zynq7000/src/slcr/ddriob.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use arbitrary_int::{u2, u3};
|
||||
|
||||
#[bitbybit::bitenum(u4, exhaustive = false)]
|
||||
pub enum VRefSel {
|
||||
/// VREF = 0.6 V
|
||||
Lpddr2 = 0b0001,
|
||||
/// VREF = 0.675 V
|
||||
Ddr3l = 0b0010,
|
||||
/// VREF = 0.75 V
|
||||
Ddr3 = 0b0100,
|
||||
/// VREF = 0.9 V
|
||||
Ddr2 = 0b1000,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct DdrControl {
|
||||
/// Enables VRP/VRN.
|
||||
#[bit(9, rw)]
|
||||
refio_enable: bool,
|
||||
#[bit(6, rw)]
|
||||
vref_ext_en_upper_bits: bool,
|
||||
#[bit(5, rw)]
|
||||
vref_ext_en_lower_bits: bool,
|
||||
#[bits(1..=4, rw)]
|
||||
vref_sel: Option<VRefSel>,
|
||||
#[bit(0, rw)]
|
||||
vref_int_en: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x00)]
|
||||
pub struct DciControl {
|
||||
#[bit(20, rw)]
|
||||
update_control: bool,
|
||||
#[bits(17..=19, rw)]
|
||||
pref_opt2: u3,
|
||||
#[bits(14..=15, rw)]
|
||||
pref_opt1: u2,
|
||||
#[bits(11..=13, rw)]
|
||||
nref_opt4: u3,
|
||||
#[bits(8..=10, rw)]
|
||||
nref_opt2: u3,
|
||||
#[bits(6..=7, rw)]
|
||||
nref_opt1: u2,
|
||||
#[bit(1, rw)]
|
||||
enable: bool,
|
||||
/// Reset value 0. Should be toggled once to initialize flops in DCI system.
|
||||
#[bit(0, rw)]
|
||||
reset: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct DciStatus {
|
||||
#[bit(13, rw)]
|
||||
done: bool,
|
||||
#[bit(0, rw)]
|
||||
lock: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u2, exhaustive = true)]
|
||||
#[derive(Debug)]
|
||||
pub enum OutputEnable {
|
||||
IBuf = 0b00,
|
||||
__Reserved0 = 0b01,
|
||||
__Reserved1 = 0b10,
|
||||
OBuf = 0b11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u2, exhaustive = true)]
|
||||
#[derive(Debug)]
|
||||
pub enum InputType {
|
||||
Off = 0b00,
|
||||
VRefBasedDifferentialReceiverForSstlHstl = 0b01,
|
||||
DifferentialInputReceiver = 0b10,
|
||||
LvcmosReceiver = 0b11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u2, exhaustive = true)]
|
||||
#[derive(Debug)]
|
||||
pub enum DciType {
|
||||
Disabled = 0b00,
|
||||
DciDrive = 0b01,
|
||||
__Reserved = 0b10,
|
||||
DciTermination = 0b11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct DdriobConfig {
|
||||
#[bit(11, rw)]
|
||||
pullup_enable: bool,
|
||||
#[bits(9..=10, rw)]
|
||||
output_enable: OutputEnable,
|
||||
#[bit(8, rw)]
|
||||
term_disable_mode: bool,
|
||||
#[bit(7, rw)]
|
||||
ibuf_disable_mode: bool,
|
||||
#[bits(5..=6, rw)]
|
||||
dci_type: DciType,
|
||||
#[bit(4, rw)]
|
||||
termination_enable: bool,
|
||||
#[bit(3, rw)]
|
||||
dci_update_enable: bool,
|
||||
#[bits(1..=2, rw)]
|
||||
inp_type: InputType,
|
||||
}
|
||||
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[repr(C)]
|
||||
pub struct DdrIoB {
|
||||
ddriob_addr0: DdriobConfig,
|
||||
ddriob_addr1: DdriobConfig,
|
||||
ddriob_data0: DdriobConfig,
|
||||
ddriob_data1: DdriobConfig,
|
||||
ddriob_diff0: DdriobConfig,
|
||||
ddriob_diff1: DdriobConfig,
|
||||
ddriob_clock: DdriobConfig,
|
||||
ddriob_drive_slew_addr: u32,
|
||||
ddriob_drive_slew_data: u32,
|
||||
ddriob_drive_slew_diff: u32,
|
||||
ddriob_drive_slew_clock: u32,
|
||||
ddr_ctrl: DdrControl,
|
||||
dci_ctrl: DciControl,
|
||||
dci_status: DciStatus,
|
||||
}
|
||||
|
||||
impl DdrIoB {
|
||||
/// Create a new handle to this peripheral.
|
||||
///
|
||||
/// Writing to this register requires unlocking the SLCR registers first.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// If you create multiple instances of this handle at the same time, you are responsible for
|
||||
/// ensuring that there are no read-modify-write races on any of the registers.
|
||||
pub unsafe fn new_mmio_fixed() -> MmioDdrIoB<'static> {
|
||||
unsafe { Self::new_mmio_at(super::SLCR_BASE_ADDR + super::DDRIOB_OFFSET) }
|
||||
}
|
||||
}
|
||||
|
||||
static_assertions::const_assert_eq!(core::mem::size_of::<DdrIoB>(), 0x38);
|
@@ -12,44 +12,10 @@ const GPIOB_OFFSET: usize = 0xB00;
|
||||
const DDRIOB_OFFSET: usize = 0xB40;
|
||||
|
||||
pub mod clocks;
|
||||
pub mod ddriob;
|
||||
pub mod mio;
|
||||
pub mod reset;
|
||||
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[repr(C)]
|
||||
pub struct DdrIoB {
|
||||
ddriob_addr0: u32,
|
||||
ddriob_addr1: u32,
|
||||
ddriob_data0: u32,
|
||||
ddriob_data1: u32,
|
||||
ddriob_diff0: u32,
|
||||
ddriob_diff1: u32,
|
||||
ddriob_clock: u32,
|
||||
ddriob_drive_slew_addr: u32,
|
||||
ddriob_drive_slew_data: u32,
|
||||
ddriob_drive_slew_diff: u32,
|
||||
ddriob_drive_slew_clock: u32,
|
||||
ddriob_ddr_ctrl: u32,
|
||||
ddriob_dci_ctrl: u32,
|
||||
ddriob_dci_status: u32,
|
||||
}
|
||||
|
||||
impl DdrIoB {
|
||||
/// Create a new handle to this peripheral.
|
||||
///
|
||||
/// Writing to this register requires unlocking the SLCR registers first.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// If you create multiple instances of this handle at the same time, you are responsible for
|
||||
/// ensuring that there are no read-modify-write races on any of the registers.
|
||||
pub unsafe fn new_mmio_fixed() -> MmioDdrIoB<'static> {
|
||||
unsafe { Self::new_mmio_at(SLCR_BASE_ADDR + DDRIOB_OFFSET) }
|
||||
}
|
||||
}
|
||||
|
||||
static_assertions::const_assert_eq!(core::mem::size_of::<DdrIoB>(), 0x38);
|
||||
|
||||
#[bitbybit::bitenum(u3, exhaustive = false)]
|
||||
pub enum VrefSel {
|
||||
Disabled = 0b000,
|
||||
@@ -93,11 +59,19 @@ impl GpiobRegisters {
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BootPllConfig {
|
||||
Enabled = 0,
|
||||
/// Disabled and bypassed.
|
||||
Bypassed = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct BootModeRegister {
|
||||
#[bit(4, r)]
|
||||
pll_bypass: bool,
|
||||
pll_config: BootPllConfig,
|
||||
#[bits(0..=3, r)]
|
||||
boot_mode: u4,
|
||||
}
|
||||
@@ -205,7 +179,7 @@ pub struct Slcr {
|
||||
gpiob: GpiobRegisters,
|
||||
|
||||
#[mmio(Inner)]
|
||||
ddriob: DdrIoB,
|
||||
ddriob: ddriob::DdrIoB,
|
||||
}
|
||||
|
||||
static_assertions::const_assert_eq!(core::mem::size_of::<Slcr>(), 0xB78);
|
||||
|
@@ -52,6 +52,15 @@ pub struct EthernetReset {
|
||||
gem0_cpu1x_rst: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
#[derive(Debug)]
|
||||
pub struct QspiResetControl {
|
||||
#[bit(2, rw)]
|
||||
qspi_ref_reset: bool,
|
||||
#[bit(1, rw)]
|
||||
cpu_1x_reset: bool,
|
||||
}
|
||||
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[repr(C)]
|
||||
pub struct ResetControl {
|
||||
@@ -69,7 +78,7 @@ pub struct ResetControl {
|
||||
i2c: DualClockReset,
|
||||
uart: DualRefAndClockReset,
|
||||
gpio: GpioClockReset,
|
||||
lqspi: u32,
|
||||
lqspi: QspiResetControl,
|
||||
smc: u32,
|
||||
ocm: u32,
|
||||
_gap0: u32,
|
||||
|
@@ -1,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,
|
||||
|
@@ -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
29
zynq7000/src/xadc.rs
Normal 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);
|
Reference in New Issue
Block a user