Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5acb8043bf | |||
| a9c5c8de57 | |||
| 32e0e27ca7 | |||
| bdc4780bcc | |||
| 7df10e6ea1 | |||
| 306ef90094 | |||
| fcd971a7d3 | |||
| c09d75b602 | |||
| c72ba780d4 | |||
| 5472293907 | |||
| 80ad791061 | |||
| f78f159fde | |||
| 1d66fcd077 | |||
| 7bf6322fc2 | |||
| 90e4604187 | |||
| 34727bf48e | |||
| 3bd62fc1eb | |||
| 11b37e9c2b | |||
| 9ed6ac32ce | |||
| 7eeedec527 | |||
| d30273aa0c | |||
| 79c6aea160 | |||
| b85e2cf1a3 | |||
| 78e2dd23f9 | |||
| df887d5665 | |||
| 62eebc6770 | |||
| 5a44b3f658 | |||
| c85f492c03 | |||
| 20102a2b7a | |||
| 110adc8a63 | |||
| 6fe5ac5edb | |||
| 923f988a32 | |||
| a6c40c5fa9 | |||
| 21a930dc09 | |||
| 8215eec0cf | |||
| bf6f492c49 | |||
| f7074bcf7d | |||
| 349509c90c | |||
| c38c98dfb0 | |||
| 69537126ec | |||
| 004e862715 | |||
| f1312c1b17 | |||
| 602448b456 | |||
| 21cafe4668 | |||
| 00f64ad2be | |||
| 41af66433a | |||
| 7dc471d687 | |||
| 41078f9150 | |||
| e9a56f73af | |||
| 7f91e039cb | |||
| 9e185fe353 | |||
| b17470a12b | |||
| 2a6012cbc3 | |||
| b786a67d38 | |||
| b9b0d7b1bc | |||
| 56692b7dae | |||
| 06f08b2378 | |||
| 0722681674 | |||
| caf9c49422 | |||
| 3890795aa9 | |||
| b9a64b76a4 | |||
| c51e0c782a | |||
| 70fa1db75f | |||
| a196d6c139 | |||
| 530fc7a8dc | |||
| d2372ad41c | |||
| 9cdec1ffba | |||
| 5e4e687403 | |||
| d07ffb3ae1 | |||
| 332779326a | |||
| 558f38682d | |||
| 3263b9964d | |||
| 0d11210173 | |||
| 9c187bd657 | |||
| a2d1efde46 | |||
| 16ecc6e71c | |||
| ecabeb1a4b | |||
| 7494036099 | |||
| 21427bf019 | |||
| fc1fdada3e | |||
| 3f01b61770 | |||
| dd97bc3177 | |||
| efd55042b2 | |||
| 8bafdc20e7 | |||
| 401643f8ec | |||
| 2b11516bf5 | |||
| 703af93bb9 | |||
| 1fe16466ab | |||
| 47a17e7550 | |||
| af025c5490 | |||
| c15e3828b7 | |||
| 9ef548e1bc | |||
| 3eb8467bf5 | |||
| 4112e336ef | |||
| a58d398d82 | |||
| fd178e1d3b | |||
| e8a6c88e2b | |||
| 8606bcbd63 | |||
| 2b6e27875f | |||
| fa6f7d836e | |||
| b7093706f5 | |||
| 56131100e7 | |||
| 9357464db0 | |||
| 79161ffe58 | |||
| 7c3051db46 | |||
| 89ee6d7451 | |||
| 3b23e9be05 | |||
| d34143ceef | |||
| 1abbe7b9c9 | |||
| 802ba665c7 | |||
| 3ba6c63554 | |||
| e3a05cc650 | |||
| 0583c9231d | |||
| 4ec2f2bae3 | |||
| 0d6713f248 |
@@ -10,3 +10,4 @@
|
||||
# running the application. You only need to do this once for unchanged bitstream as long as you
|
||||
# do not reset the whole board.
|
||||
# ZYNQ_BITSTREAM = "/home/$user/$project/$sdt_dir/bitstream.bit"
|
||||
# HW_SERVER_IP = "localhost"
|
||||
|
||||
@@ -11,8 +11,8 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: "armv7a-none-eabihf"
|
||||
- run: just check firmware
|
||||
- run: just check host
|
||||
- run: just check-dir firmware
|
||||
- run: just check-dir host
|
||||
|
||||
build:
|
||||
name: Check build
|
||||
@@ -24,8 +24,8 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: "armv7a-none-eabihf"
|
||||
- run: just check firmware
|
||||
- run: just check host
|
||||
- run: just build-zynq
|
||||
- run: just build-dir host
|
||||
|
||||
fmt:
|
||||
name: Check formatting
|
||||
@@ -37,8 +37,8 @@ jobs:
|
||||
with:
|
||||
components: rustfmt
|
||||
targets: "armv7a-none-eabihf"
|
||||
- run: just check-fmt firmware
|
||||
- run: just check-fmt host
|
||||
- run: just check-fmt-dir firmware
|
||||
- run: just check-fmt-dir host
|
||||
|
||||
docs:
|
||||
name: Check Documentation Build
|
||||
@@ -62,9 +62,9 @@ jobs:
|
||||
with:
|
||||
components: clippy, rust-src
|
||||
targets: "armv7a-none-eabihf"
|
||||
- run: just clippy firmware
|
||||
- run: just clippy-dir firmware
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
- run: just clippy host
|
||||
- run: just clippy-dir host
|
||||
|
||||
@@ -4,52 +4,55 @@ Zynq 7000 Bare-Metal Rust Support
|
||||
This crate collection provides support to write bare-metal Rust applications for the AMD Zynq 7000
|
||||
family of SoCs.
|
||||
|
||||
<p align="center">
|
||||
<img src="./ferris-zedboard.jpeg" alt="Ferris on the Zedboard" width="400" />
|
||||
</p>
|
||||
|
||||
# List of crates
|
||||
|
||||
This project contains the following crates:
|
||||
|
||||
## [Firmware Workspace](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware)
|
||||
## [Firmware Workspace](./firmware)
|
||||
|
||||
This workspace contains libraries and application which can only be run on the target system.
|
||||
|
||||
- The [`zynq7000-rt`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/zynq7000-rt)
|
||||
- The [`zynq7000-rt`](./firmware/zynq7000-rt)
|
||||
run-time crate containing basic low-level startup code necessary to boot a Rust app on the
|
||||
Zynq7000.
|
||||
- The [`zynq7000`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/zynq7000) PAC
|
||||
crate containing basic low-level register definitions.
|
||||
- The [`zynq7000-mmu`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/zynq7000-hal)
|
||||
- The [`zynq7000`](./firmware/zynq7000) PAC crate containing basic low-level register access API.
|
||||
- The [`zynq7000-mmu`](./firmware/zynq7000-mmu)
|
||||
crate containing common MMU abstractions used by both the HAL and the run-time crate.
|
||||
- The [`zynq7000-hal`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/zynq7000-hal)
|
||||
HAL crate containing higher-level abstractions on top of the PAC register crate.
|
||||
- The [`zynq7000-embassy`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/zynq7000-embassy)
|
||||
crate containing support for running the embassy-rs asynchronous run-time.
|
||||
- The [`zynq7000-hal`](./firmware/zynq7000-hal) HAL crate containing higher-level abstractions on
|
||||
top of the PAC register crate.
|
||||
- The [`zynq7000-embassy`](./firmware/zynq7000-embassy) crate containing an embassy-rs time driver
|
||||
using the global timer counter peripheral.
|
||||
|
||||
This project was developed using a Zedboard, so there are several crates available targeted towards
|
||||
this board:
|
||||
|
||||
- The [`zedboard-bsp`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/zedboard-bsp)
|
||||
- The [`zedboard-bsp`](./firmware/zedboard-bsp)
|
||||
crate containing board specific components for the Zedboard.
|
||||
- The [`zedboard-fsbl`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/zedboard-fsbl)
|
||||
- The [`zedboard-fsbl`](./firmware/zedboard-fsbl)
|
||||
contains a simple first-stage bootloader application for the Zedboard.
|
||||
- The [`zedboard-qspi-flasher`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/zedboard-qspi-flasher)
|
||||
- The [`zedboard-qspi-flasher`](./firmware/zedboard-qspi-flasher)
|
||||
contains an application which is able to flash a boot binary from DDR to the QSPI.
|
||||
|
||||
It also contains the following helper crates:
|
||||
|
||||
- The [`examples`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/examples)
|
||||
- The [`examples`](./firmware/examples)
|
||||
folder contains various example applications crates using the HAL and the PAC.
|
||||
This folder also contains dedicated example applications using the
|
||||
[`embassy`](https://github.com/embassy-rs/embassy) native Rust RTOS.
|
||||
|
||||
## Other libraries and tools
|
||||
|
||||
- The [`zedboard-fpga-design`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zedboard-fpga-design)
|
||||
- The [`zedboard-fpga-design`](./zedboard-fpga-design)
|
||||
folder contains a sample FPGA design and block design which was used in some of the provided software examples. The project was created with Vivado version 2024.1.
|
||||
The folder contains a README with all the steps required to load this project from a TCL script.
|
||||
- The [`zynq7000-boot-image`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/host/zynq7000-boot-image)
|
||||
- The [`zynq7000-boot-image`](./host/zynq7000-boot-image)
|
||||
library contains generic helpers to interface with the AMD
|
||||
[boot binary](https://docs.amd.com/r/en-US/ug1283-bootgen-user-guide).
|
||||
- The [`tools/zynq7000-ps7init-extract`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/host/zynq7000-ps7init-extract)
|
||||
- The [`zynq7000-ps7init-extract`](./host/zynq7000-ps7init-extract)
|
||||
tool allows extracting configuration from the AMD generated `ps7init.tcl` file which contains
|
||||
static configuration parameters for DDR initialization.
|
||||
|
||||
@@ -157,7 +160,7 @@ Zedboard are configured for JTAG boot.
|
||||
|
||||
You can use the `-tui` argument to also have a terminal UI.
|
||||
This repository provides a `scripts/runner.sh` which performs all the steps specified above.
|
||||
The `.cargo/def-config.toml` script contains the runner and some template environmental
|
||||
The `.cargo/config.toml.template` script contains the runner and some template environmental
|
||||
variables that need to be set for this to work. The command above also loaded the app, but
|
||||
this task can be performed by the `zynq7000-init.py` wrapper as well.
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 414 KiB |
@@ -9,7 +9,7 @@ repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
aarch32-cpu = { version = "0.1" }
|
||||
aarch32-cpu = { version = "0.3", features = ["critical-section-single-core"] }
|
||||
zynq7000-rt = { path = "../../zynq7000-rt" }
|
||||
zynq7000 = { path = "../../zynq7000" }
|
||||
zynq7000-hal = { path = "../../zynq7000-hal", features = ["defmt"] }
|
||||
@@ -17,5 +17,5 @@ defmt = "1"
|
||||
defmt-rtt = "1"
|
||||
embedded-io = "0.7"
|
||||
embedded-hal = "1"
|
||||
fugit = "0.3"
|
||||
fugit = "0.4"
|
||||
log = "0.4"
|
||||
|
||||
@@ -4,12 +4,17 @@ MEMORY
|
||||
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*/
|
||||
/* CODE(rx) : ORIGIN = 0x00100000, LENGTH = 62M */
|
||||
CODE(rx) : ORIGIN = 0x00000000, LENGTH = 192K
|
||||
/* STACK: ORIGIN = 0x3F00000, LENGTH = 1M */
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
||||
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
|
||||
}
|
||||
|
||||
REGION_ALIAS("VECTORS", CODE);
|
||||
REGION_ALIAS("DATA", CODE);
|
||||
/* Use the upper OCM as the stack */
|
||||
REGION_ALIAS("STACKS", OCM_UPPER);
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
@@ -22,3 +27,10 @@ SECTIONS
|
||||
_ebss_uncached = .;
|
||||
} > UNCACHED
|
||||
}
|
||||
|
||||
|
||||
PROVIDE(_und_stack_size = 2K);
|
||||
PROVIDE(_svc_stack_size = 2K);
|
||||
PROVIDE(_abt_stack_size = 2K);
|
||||
PROVIDE(_hyp_stack_size = 1K);
|
||||
PROVIDE(_sys_stack_size = 32K);
|
||||
|
||||
@@ -11,23 +11,24 @@ keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
|
||||
categories = ["embedded", "no-std", "hardware-support"]
|
||||
|
||||
[dependencies]
|
||||
aarch32-cpu = { version = "0.1", features = ["critical-section-single-core"] }
|
||||
aarch32-cpu = { version = "0.3", features = ["critical-section-single-core"] }
|
||||
zynq7000-rt = { path = "../../zynq7000-rt" }
|
||||
zynq7000 = { path = "../../zynq7000" }
|
||||
zynq7000-hal = { path = "../../zynq7000-hal" }
|
||||
zynq7000-embassy = { path = "../../zynq7000-embassy" }
|
||||
dht-sensor = { git = "https://github.com/michaelbeaumont/dht-sensor.git", rev = "10319bdeae9ace3bb0fc79a15da2869c5bf50f52", features = ["async"] }
|
||||
embedded-hal-async = "1"
|
||||
embassy-sync = "0.8"
|
||||
static_cell = "2"
|
||||
critical-section = "1"
|
||||
heapless = "0.9"
|
||||
embedded-io = "0.7"
|
||||
embedded-hal = "1"
|
||||
fugit = "0.3"
|
||||
fugit = "0.4"
|
||||
log = "0.4"
|
||||
|
||||
embassy-executor = { version = "0.9", features = [
|
||||
"arch-cortex-ar",
|
||||
embassy-executor = { version = "0.10", features = [
|
||||
"platform-cortex-ar",
|
||||
"executor-thread",
|
||||
]}
|
||||
# TODO: Remove generic-queue-16 feature as soon as upstream executor is used again.
|
||||
embassy-time = { version = "0.5", features = ["tick-hz-1_000_000", "generic-queue-16"] }
|
||||
embassy-time = { version = "0.5", features = ["tick-hz-1_000_000"] }
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
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
|
||||
/* Zedboard: 512 MB DDR3. Only use 62 MB for now, should be plenty for a bare-metal app.
|
||||
1 MB stack memory and 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 = 62M
|
||||
STACKS : ORIGIN = 0x3F00000, LENGTH = 1M
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
||||
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
|
||||
}
|
||||
|
||||
REGION_ALIAS("VECTORS", CODE);
|
||||
|
||||
@@ -12,7 +12,8 @@ use log::{error, info, warn};
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
generic_interrupt_handler,
|
||||
gic::Configurator,
|
||||
gpio::{Flex, Output, PinState, mio},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
@@ -45,7 +46,7 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
// 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);
|
||||
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -157,23 +158,12 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Spi(_spi_interrupt) => (),
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
|
||||
@@ -8,7 +8,9 @@ use embassy_time::{Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write;
|
||||
use log::{error, info};
|
||||
use zynq7000_hal::{BootMode, InteruptConfig, clocks, gic, gpio, gtc, time::Hertz, uart};
|
||||
use zynq7000_hal::{
|
||||
BootMode, InteruptConfig, clocks, generic_interrupt_handler, gpio, gtc, time::Hertz, uart,
|
||||
};
|
||||
|
||||
use zynq7000_rt as _;
|
||||
|
||||
@@ -71,23 +73,12 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
}
|
||||
|
||||
#[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 == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
gic::Interrupt::Spi(_spi_interrupt) => (),
|
||||
gic::Interrupt::Invalid(_) => (),
|
||||
gic::Interrupt::Spurious => (),
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
|
||||
@@ -13,12 +13,13 @@ use zynq7000::Peripherals;
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
generic_interrupt_handler,
|
||||
gic::Configurator,
|
||||
gpio::{Output, PinState, mio},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
time::Hertz,
|
||||
uart::{ClockConfig, Config, TxAsync, Uart, on_interrupt_tx},
|
||||
uart::{ClockConfig, Config, TxAsync, Uart},
|
||||
};
|
||||
|
||||
use zynq7000_rt as _;
|
||||
@@ -40,7 +41,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
// 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);
|
||||
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -68,56 +69,64 @@ async fn main(spawner: Spawner) -> ! {
|
||||
uart.flush().unwrap();
|
||||
|
||||
let (tx, _rx) = uart.split();
|
||||
let mut logger = TxAsync::new(tx);
|
||||
let mut logger = TxAsync::new(tx, true);
|
||||
|
||||
zynq7000_hal::log::rb::init(log::LevelFilter::Trace);
|
||||
let log_reader = zynq7000_hal::log::asynch::init(log::LevelFilter::Trace).unwrap();
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
|
||||
let led = Output::new_for_mio(mio_pins.mio7, PinState::Low);
|
||||
spawner.spawn(led_task(led)).unwrap();
|
||||
spawner.spawn(led_task(led).unwrap());
|
||||
spawner.spawn(hello_task().unwrap());
|
||||
|
||||
let mut log_buf: [u8; 2048] = [0; 2048];
|
||||
let frame_queue = zynq7000_hal::log::rb::get_frame_queue();
|
||||
loop {
|
||||
let next_frame_len = frame_queue.receive().await;
|
||||
zynq7000_hal::log::rb::read_next_frame(next_frame_len, &mut log_buf);
|
||||
logger.write(&log_buf[0..next_frame_len]).await;
|
||||
let read_bytes = log_reader.read(&mut log_buf).await;
|
||||
if read_bytes > 0 {
|
||||
// Unwrap okay, checked that size is larger than 0.
|
||||
logger.write(&log_buf[0..read_bytes]).unwrap().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn led_task(mut mio_led: Output) {
|
||||
static ATOMIC_COUNTER: core::sync::atomic::AtomicUsize =
|
||||
core::sync::atomic::AtomicUsize::new(0);
|
||||
|
||||
let mut ticker = Ticker::every(Duration::from_millis(1000));
|
||||
loop {
|
||||
mio_led.toggle().unwrap();
|
||||
info!("Toggling LED");
|
||||
info!(
|
||||
"Toggling LED ({})",
|
||||
ATOMIC_COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed)
|
||||
);
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
#[embassy_executor::task]
|
||||
async fn hello_task() {
|
||||
static ATOMIC_COUNTER: core::sync::atomic::AtomicUsize =
|
||||
core::sync::atomic::AtomicUsize::new(0);
|
||||
|
||||
let mut ticker = Ticker::every(Duration::from_millis(1000));
|
||||
loop {
|
||||
info!(
|
||||
"Hello from another task ({})",
|
||||
ATOMIC_COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed)
|
||||
);
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Spi(spi_interrupt) => {
|
||||
if spi_interrupt == zynq7000_hal::gic::SpiInterrupt::Uart1 {
|
||||
on_interrupt_tx(zynq7000_hal::uart::UartId::Uart1);
|
||||
}
|
||||
}
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
|
||||
@@ -18,7 +18,8 @@ use log::{error, info};
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
generic_interrupt_handler,
|
||||
gic::Configurator,
|
||||
gpio::{Output, PinState, mio},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
@@ -46,7 +47,7 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
// 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);
|
||||
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -107,23 +108,12 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Spi(_spi_interrupt) => (),
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
|
||||
@@ -7,7 +7,7 @@ use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use log::error;
|
||||
use zynq7000_hal::{InteruptConfig, clocks, gic, gpio, gtc, time::Hertz};
|
||||
use zynq7000_hal::{InteruptConfig, clocks, generic_interrupt_handler, gpio, gtc, time::Hertz};
|
||||
|
||||
// Define the clock frequency as a constant
|
||||
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
|
||||
@@ -42,22 +42,11 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
|
||||
#[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() {
|
||||
gic::Interrupt::Sgi(_) => (),
|
||||
gic::Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
gic::Interrupt::Spi(_spi_interrupt) => (),
|
||||
gic::Interrupt::Invalid(_) => (),
|
||||
gic::Interrupt::Spurious => (),
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
|
||||
@@ -9,11 +9,11 @@ repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
aarch32-cpu = { version = "0.1" }
|
||||
aarch32-cpu = { version = "0.3" }
|
||||
zynq7000-rt = { path = "../../zynq7000-rt" }
|
||||
zynq7000 = { path = "../../zynq7000" }
|
||||
zynq7000-hal = { path = "../../zynq7000-hal" }
|
||||
embedded-io = "0.7"
|
||||
embedded-hal = "1"
|
||||
fugit = "0.3"
|
||||
fugit = "0.4"
|
||||
log = "0.4"
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
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
|
||||
/* Zedboard: 512 MB DDR3. Only use 62 MB for now, should be plenty for a bare-metal app.
|
||||
1 MB stack memory and 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 = 62M
|
||||
STACKS : ORIGIN = 0x3F00000, LENGTH = 1M
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
||||
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
|
||||
}
|
||||
|
||||
REGION_ALIAS("VECTORS", CODE);
|
||||
@@ -21,3 +23,9 @@ SECTIONS
|
||||
_ebss_uncached = .;
|
||||
} > UNCACHED
|
||||
}
|
||||
|
||||
PROVIDE(_und_stack_size = 2K);
|
||||
PROVIDE(_svc_stack_size = 2K);
|
||||
PROVIDE(_abt_stack_size = 2K);
|
||||
PROVIDE(_hyp_stack_size = 1K);
|
||||
PROVIDE(_sys_stack_size = 32K);
|
||||
|
||||
@@ -8,8 +8,9 @@ use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write;
|
||||
use log::{error, info};
|
||||
use zynq7000_hal::{
|
||||
Interrupt,
|
||||
clocks::Clocks,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
gic,
|
||||
gpio::{Output, PinState, mio},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
@@ -31,7 +32,7 @@ fn main() -> ! {
|
||||
// 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);
|
||||
let mut gic = gic::Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -83,8 +84,8 @@ fn main() -> ! {
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
let mut gic_helper = gic::InterruptGuard::new();
|
||||
let irq_info = gic_helper.interrupt_info();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
|
||||
@@ -10,7 +10,7 @@ use log::{error, info};
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
gic::{self, Configurator, Interrupt},
|
||||
gpio::{Output, PinState, mio},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
@@ -32,7 +32,7 @@ fn main() -> ! {
|
||||
// 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);
|
||||
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -85,13 +85,12 @@ fn main() -> ! {
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
let mut gic_helper = gic::InterruptGuard::new();
|
||||
let irq_info = gic_helper.interrupt_info();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
// TODO: Call embassy on interrupt handler here soon.
|
||||
MS_TICKS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
|
||||
categories = ["embedded", "no-std", "hardware-support"]
|
||||
|
||||
[dependencies]
|
||||
aarch32-cpu = { version = "0.1" }
|
||||
aarch32-cpu = { version = "0.3", features = ["critical-section-single-core"] }
|
||||
zynq7000-rt = { path = "../../zynq7000-rt" }
|
||||
zynq7000 = { path = "../../zynq7000" }
|
||||
zynq7000-hal = { path = "../../zynq7000-hal" }
|
||||
@@ -20,27 +20,29 @@ 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.7"
|
||||
bitbybit = "1.4"
|
||||
bitbybit = "2"
|
||||
arbitrary-int = "2"
|
||||
embedded-io-async = "0.6"
|
||||
embedded-io-async = "0.7"
|
||||
critical-section = "1"
|
||||
static_cell = "2"
|
||||
embedded-alloc = "0.6"
|
||||
embedded-alloc = "0.7"
|
||||
embedded-hal = "1"
|
||||
embedded-hal-bus = { version = "0.3", features = ["async"] }
|
||||
embedded-hal-async = "1"
|
||||
fugit = "0.3"
|
||||
dummy-pin = "1"
|
||||
fugit = "0.4"
|
||||
fugit-03 = { version = "0.3", package = "fugit" }
|
||||
embedded-graphics = "0.8"
|
||||
log = "0.4"
|
||||
rand = { version = "0.9", default-features = false, features = ["small_rng"] }
|
||||
ssd1306 = { version = "0.10", features = ["async"] }
|
||||
tinybmp = "0.7"
|
||||
rand = { version = "0.10", default-features = false }
|
||||
|
||||
embassy-executor = { git = "https://github.com/us-irs/embassy.git", branch = "cortex-ar-update", features = [
|
||||
"arch-cortex-ar",
|
||||
"executor-thread",
|
||||
]}
|
||||
# TODO: Remove generic-queue-16 feature as soon as upstream executor is used again.
|
||||
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"
|
||||
embassy-executor = { version = "0.10", features = ["platform-cortex-ar", "executor-thread"] }
|
||||
embassy-time = { version = "0.5", features = ["tick-hz-1_000_000"] }
|
||||
embassy-net = { version = "0.9", features = ["dhcpv4", "packet-trace", "medium-ethernet", "icmp", "tcp", "udp", "auto-icmp-echo-reply"] }
|
||||
embedded-sdmmc = { git = "https://github.com/robamu/embedded-sdmmc-rs.git", branch = "all-features" }
|
||||
embassy-sync = { version = "0.8" }
|
||||
heapless = "0.9"
|
||||
axi-uartlite = { version = "0.1" }
|
||||
axi-uart16550 = { version = "0.1" }
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 402 B |
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width="0.16in" height="0.106667in"
|
||||
viewBox="0 0 48 32">
|
||||
<defs>
|
||||
</defs>
|
||||
<image id="raster0"
|
||||
x="0"
|
||||
y="0"
|
||||
width="48"
|
||||
height="32"
|
||||
opacity="1.000000"
|
||||
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAgCAQAAAD+3TOXAAAHDmVYSWZJSSoACAAAAAAADgAAAAkA/gAEAAEAAAABAAAAAAEEAAEAAAAAAQAAAQEEAAEAAACqAAAAAgEDAAMAAACAAAAAAwEDAAEAAAAGAAAABgEDAAEAAAAGAAAAFQEDAAEAAAADAAAAAQIEAAEAAACGAAAAAgIEAAEAAACIBgAAAAAAAAgACAAIAP/Y/+AAEEpGSUYAAQEAAAEAAQAA/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAqgEAAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A+f6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr6/+CX/ACSHQv8At4/9KJK+QK+v/gl/ySHQv+3j/wBKJKAPnzxr8Jte8B6NDqmqXemzQS3C26rayOzBirNk7kUYwh7+lcHX0/8AtHf8k80//sKx/wDoqWvmCgAroPAn/JQ/DX/YVtf/AEatc/XQeBP+Sh+Gv+wra/8Ao1aAPqP4s+CtS8eeFbXS9LntIZ4r1LhmunZVKhHXA2qxzlx29a+VPFPhu88I+I7vQ7+SCS6tdm94GJQ7kVxgkA9GHavuevkD42/8le13/t3/APSeOgDz+iiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK+g/hL8WdB0rw74f8Hz2mpNqD3BtxIkaGLdLOxU5Lg4+cZ49etfPlWLC+uNM1G2v7OTy7q1lSaF9oO11IKnB4OCB1oA+968j+J3xZ0HSofEXg+e01JtQeye3EiRoYt0sOVOS4OPnGePXrXjn/AAu34h/9DD/5JW//AMbrj9b1vUfEesT6tq1x9ovp9vmS7FTdtUKOFAA4AHAoAz6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/9mZmF3EAAAAAXNSR0IB2cksfwAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAhhJREFUSMft1T1I1WEUBvDfVQn1hoHoDQlMqCSlaLAiCKMhbIpqKLCtTQKJoLWh76GgpQ+isKWMgoiiocgpKoKEoCHCoHJJ1GjRAvu4p8GPrt57Ta/W5HmW9/w553ne9z3nf14WbJ6tXMu/or7vPM76aoVavbbMH3W/NtXSwnU/hJd6hA5LPXZ0btQbJNQLYVBMQdqgcBc1agoVCD1uZ1FnYtAt313+O9WiPAIzw8UpeUnnbEZy1C1WpskzqzJCLvnozowF3nnkm4ax3OU+CJU4IQU3bFMuDGuaEDgyY/I/GN1vwhshUKbf3hJb7TOiy4CUbgmstER61vXqsxpvbZo4yXEpQZuQtt4LIdTjaQG7H0erPWOrDmFENc1C6NUlhEYcKJj+s3IpPyf8U6NHuTDxoQ8NDhUs8FqzSmfGvIdKxu+vzZAQ2rXnS45pvEkYViuE04oyS1TlldAs6WM+gcjrTcJJXcL27C6o0yvtis45CaQ9Fw7mbrRlHsyhf/7gZiZpccZ6SNKOeZi+a5R6kutPapEWwq95OMW+cdLMSo9IgGO+ueTLdNMvhw1M8u7JOz2HVQuh1G7XsnsqR3n7ddqpGKFp6g4SWRKw2LAKQ6DGOiusVaF1SuxVfd7q1pORW+mT0izWHLdQ53ABz1OxEMr/HhiSNs6SvkSjIu9zFyiXRCG2KzuvKGdgwn4VBQhU5eHLI7Jg/8d+A9OUD/TqXTt5AAAAAElFTkSuQmCCAA==" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 80 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 274 B |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.4 KiB |
Binary file not shown.
@@ -1,10 +1,12 @@
|
||||
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
|
||||
/* Zedboard: 512 MB DDR3. Only use 62 MB for now, should be plenty for a bare-metal app.
|
||||
1 MB stack memory and 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 = 62M
|
||||
STACKS : ORIGIN = 0x3F00000, LENGTH = 1M
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
||||
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
|
||||
}
|
||||
|
||||
REGION_ALIAS("VECTORS", CODE);
|
||||
@@ -21,3 +23,10 @@ SECTIONS
|
||||
_ebss_uncached = .;
|
||||
} > UNCACHED
|
||||
}
|
||||
|
||||
PROVIDE(_und_stack_size = 2K);
|
||||
PROVIDE(_svc_stack_size = 2K);
|
||||
PROVIDE(_abt_stack_size = 2K);
|
||||
PROVIDE(_hyp_stack_size = 1K);
|
||||
PROVIDE(_irq_stack_size = 2K);
|
||||
PROVIDE(_sys_stack_size = 32K);
|
||||
|
||||
@@ -32,7 +32,7 @@ use embassy_time::{Duration, Timer};
|
||||
use embedded_io::Write;
|
||||
use embedded_io_async::Write as _;
|
||||
use log::{LevelFilter, debug, error, info, warn};
|
||||
use rand::{RngCore, SeedableRng};
|
||||
use rand::{Rng, SeedableRng};
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zedboard_bsp::phy_marvell;
|
||||
use zynq7000_hal::{
|
||||
@@ -42,7 +42,8 @@ use zynq7000_hal::{
|
||||
eth::{
|
||||
AlignedBuffer, ClockDivSet, EthernetConfig, EthernetLowLevel, embassy_net::InterruptResult,
|
||||
},
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
generic_interrupt_handler,
|
||||
gic::{Configurator, Interrupt},
|
||||
gpio::{GpioPins, Output, PinState},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
@@ -216,7 +217,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
// 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);
|
||||
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -278,6 +279,12 @@ async fn main(spawner: Spawner) -> ! {
|
||||
"Calculated RGMII clock configuration: {:?}, errors (missmatch from ideal rate in hertz): {:?}",
|
||||
clk_divs, clk_errors
|
||||
);
|
||||
|
||||
zynq7000_hal::register_interrupt(
|
||||
Interrupt::Spi(zynq7000_hal::gic::SpiInterrupt::Eth0),
|
||||
custom_eth_interupt_handler,
|
||||
);
|
||||
|
||||
// Unwrap okay, we use a standard clock config, and the clock config should never fail.
|
||||
let eth_cfg = EthernetConfig::new(
|
||||
zynq7000_hal::eth::ClockConfig::new(clk_divs.cfg_1000_mbps),
|
||||
@@ -373,9 +380,9 @@ async fn main(spawner: Spawner) -> ! {
|
||||
let tcp_socket = TcpSocket::new(stack, RX_TCP_BUFS.take(), TX_TCP_BUFS.take());
|
||||
|
||||
// Spawn all embassy tasks.
|
||||
spawner.spawn(embassy_net_task(runner)).unwrap();
|
||||
spawner.spawn(udp_task(udp_socket)).unwrap();
|
||||
spawner.spawn(tcp_task(tcp_socket)).unwrap();
|
||||
spawner.spawn(embassy_net_task(runner).unwrap());
|
||||
spawner.spawn(udp_task(udp_socket).unwrap());
|
||||
spawner.spawn(tcp_task(tcp_socket).unwrap());
|
||||
|
||||
let mut mio_led = Output::new_for_mio(gpio_pins.mio.mio7, PinState::Low);
|
||||
|
||||
@@ -461,36 +468,24 @@ async fn main(spawner: Spawner) -> ! {
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Spi(spi_interrupt) => {
|
||||
if spi_interrupt == zynq7000_hal::gic::SpiInterrupt::Eth0 {
|
||||
// This generic library provided interrupt handler takes care of waking
|
||||
// the driver on received or sent frames while also reporting anomalies
|
||||
// and errors.
|
||||
let result = zynq7000_hal::eth::embassy_net::on_interrupt(
|
||||
zynq7000_hal::eth::EthernetId::Eth0,
|
||||
);
|
||||
if result.has_errors() {
|
||||
ETH_ERR_QUEUE.try_send(result).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
// Safety: Only called by interrupt handler, registered in global interrupt handler map.
|
||||
unsafe fn custom_eth_interupt_handler() {
|
||||
// This generic library provided interrupt handler takes care of waking
|
||||
// the driver on received or sent frames while also reporting anomalies
|
||||
// and errors.
|
||||
let result = zynq7000_hal::eth::embassy_net::on_interrupt(zynq7000_hal::eth::EthernetId::Eth0);
|
||||
if result.has_errors() {
|
||||
ETH_ERR_QUEUE.try_send(result).ok();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
|
||||
@@ -20,8 +20,8 @@ use log::{error, info};
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
configure_level_shifter,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
configure_level_shifter, generic_interrupt_handler,
|
||||
gic::Configurator,
|
||||
gpio::{GpioPins, Output, PinState},
|
||||
gtc::GlobalTimerCounter,
|
||||
i2c, l2_cache,
|
||||
@@ -54,7 +54,7 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
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);
|
||||
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -153,24 +153,13 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Spi(_spi_interrupt) => (),
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_time::{Delay, Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_hal_async::delay::DelayNs;
|
||||
@@ -20,14 +21,14 @@ use log::{error, info};
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
configure_level_shifter,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
configure_level_shifter, generic_interrupt_handler,
|
||||
gic::Configurator,
|
||||
gpio::{GpioPins, Output, PinState},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
spi::{self, SpiAsync, SpiId, SpiWithHwCs, SpiWithHwCsAsync, on_interrupt},
|
||||
spi::{self, SpiAsync, SpiWithHwCs, SpiWithHwCsAsync},
|
||||
time::Hertz,
|
||||
uart::{self, TxAsync, on_interrupt_tx},
|
||||
uart::{self, TxAsync},
|
||||
};
|
||||
|
||||
use zynq7000::{Peripherals, slcr::LevelShifterConfig, spi::DelayControl};
|
||||
@@ -56,15 +57,20 @@ async fn main(spawner: Spawner) -> ! {
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let mut clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
|
||||
// SPI reference clock must be larger than the CPU 1x clock.
|
||||
let spi_ref_clk_div = spi::calculate_largest_allowed_spi_ref_clk_divisor(&clocks)
|
||||
.unwrap()
|
||||
.value()
|
||||
- 1;
|
||||
spi::configure_spi_ref_clk(&mut clocks, arbitrary_int::u6::new(spi_ref_clk_div as u8));
|
||||
let target_spi_ref_clock = clocks.arm_clocks().cpu_1x_clk() * 2;
|
||||
// SPI reference clock must be larger than the CPU 1x clock. Also, taking the largest value
|
||||
// actually seems to be problematic. We take 200 MHz here, which is significantly larger than
|
||||
// the CPU 1x clock which is around 110 MHz.
|
||||
spi::configure_spi_ref_clock(&mut clocks, target_spi_ref_clock);
|
||||
|
||||
assert!(
|
||||
clocks.io_clocks().spi_clk().to_raw()
|
||||
> (clocks.arm_clocks().cpu_1x_clk().to_raw() as f32 * 1.2) as u32,
|
||||
"SPI reference clock must be larger than CPU 1x clock"
|
||||
);
|
||||
|
||||
// Set up the global interrupt controller.
|
||||
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
|
||||
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -90,30 +96,29 @@ async fn main(spawner: Spawner) -> ! {
|
||||
.unwrap();
|
||||
uart.write_all(b"-- Zynq 7000 Zedboard SPI L3GD20H example --\n\r")
|
||||
.unwrap();
|
||||
zynq7000_hal::log::rb::init(log::LevelFilter::Trace);
|
||||
|
||||
let log_reader = zynq7000_hal::log::asynch::init(log::LevelFilter::Trace).unwrap();
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
|
||||
if DEBUG_SPI_CLK_CONFIG {
|
||||
info!(
|
||||
"SPI Clock Information: CPU 1x: {:?}, IO Ref Clk: {:?}, SPI Ref Clk: {:?}, DIV: {:?}",
|
||||
"SPI Clock Information: CPU 1x: {:?}, IO Ref Clk: {:?}, SPI Ref Clk: {:?}",
|
||||
clocks.arm_clocks().cpu_1x_clk(),
|
||||
clocks.io_clocks().ref_clk(),
|
||||
clocks.io_clocks().spi_clk(),
|
||||
spi_ref_clk_div
|
||||
);
|
||||
}
|
||||
|
||||
let mut spi = spi::Spi::new_one_hw_cs(
|
||||
dp.spi_1,
|
||||
clocks.io_clocks(),
|
||||
spi::Config::new(
|
||||
// 10 MHz maximum rating of the sensor.
|
||||
zynq7000::spi::BaudDivSel::By64,
|
||||
//l3gd20::MODE,
|
||||
embedded_hal::spi::MODE_3,
|
||||
spi::SlaveSelectConfig::AutoWithAutoStart,
|
||||
spi::SlaveSelectConfig::AutoCsAutoStart,
|
||||
),
|
||||
(
|
||||
gpio_pins.mio.mio12,
|
||||
@@ -123,10 +128,13 @@ async fn main(spawner: Spawner) -> ! {
|
||||
gpio_pins.mio.mio13,
|
||||
)
|
||||
.unwrap();
|
||||
let sclk = Hertz::from_raw(
|
||||
clocks.io_clocks().spi_clk().to_raw() / zynq7000::spi::BaudDivSel::By64.div_value() as u32,
|
||||
);
|
||||
let mod_id = spi.regs().read_mod_id();
|
||||
assert_eq!(mod_id, spi::MODULE_ID);
|
||||
assert!(spi.sclk() <= Hertz::from_raw(10_000_000));
|
||||
let min_delay = (spi.sclk().raw() * 5) / 1_000_000_000;
|
||||
assert!(sclk <= Hertz::from_raw(10_000_000));
|
||||
let min_delay = (sclk.to_raw() * 5) / 1_000_000_000;
|
||||
spi.inner().configure_delays(
|
||||
DelayControl::builder()
|
||||
.with_inter_word_cs_deassert(0)
|
||||
@@ -155,7 +163,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
}
|
||||
}
|
||||
|
||||
spawner.spawn(logger_task(uart)).unwrap();
|
||||
spawner.spawn(logger_task(uart, log_reader).unwrap());
|
||||
if BLOCKING {
|
||||
blocking_application(mio_led, emio_leds, spi).await;
|
||||
} else {
|
||||
@@ -164,15 +172,18 @@ async fn main(spawner: Spawner) -> ! {
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn logger_task(uart: uart::Uart) {
|
||||
pub async fn logger_task(
|
||||
uart: uart::Uart,
|
||||
reader: embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 4096>,
|
||||
) -> ! {
|
||||
let (tx, _) = uart.split();
|
||||
let mut tx_async = TxAsync::new(tx);
|
||||
let frame_queue = zynq7000_hal::log::rb::get_frame_queue();
|
||||
let mut tx_async = TxAsync::new(tx, true);
|
||||
let mut log_buf: [u8; 2048] = [0; 2048];
|
||||
loop {
|
||||
let next_frame_len = frame_queue.receive().await;
|
||||
zynq7000_hal::log::rb::read_next_frame(next_frame_len, &mut log_buf);
|
||||
tx_async.write(&log_buf[0..next_frame_len]).await;
|
||||
let read_bytes = reader.read(&mut log_buf).await;
|
||||
if read_bytes > 0 {
|
||||
tx_async.write(&log_buf[0..read_bytes]).unwrap().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,30 +248,13 @@ pub async fn non_blocking_application(
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Spi(spi_interrupt) => {
|
||||
if spi_interrupt == zynq7000_hal::gic::SpiInterrupt::Spi1 {
|
||||
on_interrupt(SpiId::Spi1);
|
||||
} else if spi_interrupt == zynq7000_hal::gic::SpiInterrupt::Uart1 {
|
||||
on_interrupt_tx(zynq7000_hal::uart::UartId::Uart1);
|
||||
}
|
||||
}
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use dummy_pin::DummyPin;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_time::{Delay, Duration, Ticker};
|
||||
use embedded_graphics::{Drawable as _, geometry::Point};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_hal_async::delay::DelayNs as _;
|
||||
use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay};
|
||||
use embedded_io::Write;
|
||||
use log::{error, info};
|
||||
use ssd1306::{Ssd1306Async, prelude::*};
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zynq7000_hal::{
|
||||
BootMode, clocks, generic_interrupt_handler, gpio, gtc,
|
||||
spi::{self, SpiAsync},
|
||||
time::Hertz,
|
||||
uart::{self, TxAsync},
|
||||
};
|
||||
|
||||
use embedded_graphics::image::Image;
|
||||
use tinybmp::Bmp;
|
||||
|
||||
use zynq7000_rt as _;
|
||||
|
||||
const INIT_STRING: &str = "-- Zynq 7000 Zedboard OLED example --\n\r";
|
||||
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[embassy_executor::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 mut clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
|
||||
let target_spi_ref_clock = clocks.arm_clocks().cpu_1x_clk() * 2;
|
||||
// SPI reference clock must be larger than the CPU 1x clock. Also, taking the largest value
|
||||
// actually seems to be problematic. We take 200 MHz here, which is significantly larger than
|
||||
// the CPU 1x clock which is around 110 MHz.
|
||||
spi::configure_spi_ref_clock(&mut clocks, target_spi_ref_clock);
|
||||
|
||||
assert!(
|
||||
clocks.io_clocks().spi_clk().to_raw()
|
||||
> (clocks.arm_clocks().cpu_1x_clk().to_raw() as f32 * 1.2) as u32,
|
||||
"SPI reference clock must be larger than CPU 1x clock"
|
||||
);
|
||||
|
||||
let mut 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_for_uart_1(
|
||||
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();
|
||||
uart.flush().unwrap();
|
||||
|
||||
// Safety: We are not multi-threaded yet.
|
||||
let log_reader = zynq7000_hal::log::asynch::init(log::LevelFilter::Trace)
|
||||
.expect("Failed to initialize async logger");
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
info!(
|
||||
"SPI reference clock speed: {:?}",
|
||||
clocks.io_clocks().spi_clk()
|
||||
);
|
||||
|
||||
spawner.spawn(logger_task(uart, log_reader).unwrap());
|
||||
|
||||
let mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
|
||||
let 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),
|
||||
];
|
||||
spawner.spawn(blinky_task(mio_led, emio_leds).unwrap());
|
||||
|
||||
let dc_pin = gpio::Output::new_for_emio(gpio_pins.emio.take(11).unwrap(), gpio::PinState::High);
|
||||
|
||||
let mut reset_pin =
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(12).unwrap(), gpio::PinState::High);
|
||||
let mut oled_vdd_switch =
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(13).unwrap(), gpio::PinState::High);
|
||||
let mut oled_vbat_switch =
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(14).unwrap(), gpio::PinState::High);
|
||||
Delay.delay_ms(100).await;
|
||||
|
||||
let spi = spi::Spi::new_for_emio(
|
||||
periphs.spi_0,
|
||||
spi::Config::calculate_for_io_clock(
|
||||
Hertz::MHz(8),
|
||||
clocks.io_clocks(),
|
||||
embedded_hal::spi::MODE_0,
|
||||
spi::SlaveSelectConfig::AutoCsManualStart,
|
||||
),
|
||||
)
|
||||
.expect("Failed to initialize SPI");
|
||||
let spi_asynch = SpiAsync::new(spi);
|
||||
let exclusive_device = ExclusiveDevice::new(spi_asynch, DummyPin::new_high(), NoDelay)
|
||||
.expect("Failed to create exclusive SPI device");
|
||||
let spi_if = SPIInterface::new(exclusive_device, dc_pin);
|
||||
let mut ssd1306 = Ssd1306Async::new(spi_if, DisplaySize128x32, DisplayRotation::Rotate180);
|
||||
|
||||
oled_vdd_switch.set_low();
|
||||
oled_vbat_switch.set_low();
|
||||
Delay.delay_ms(100).await;
|
||||
|
||||
ssd1306
|
||||
.reset(&mut reset_pin, &mut embassy_time::Delay {})
|
||||
.await
|
||||
.expect("display reset error");
|
||||
let mut display = ssd1306.into_buffered_graphics_mode();
|
||||
display.init().await.expect("display init error");
|
||||
|
||||
// Include the BMP file data.
|
||||
let ferris_data = include_bytes!("../../assets/ferris-flat-happy-small.bmp");
|
||||
let rust_logo_data = include_bytes!("../../assets/rust-logo-single-path.bmp");
|
||||
// Parse the BMP file.
|
||||
let bmp_rust = Bmp::from_slice(rust_logo_data).expect("BMP loading error");
|
||||
let bmp_ferris = Bmp::from_slice(ferris_data).expect("BMP loading error");
|
||||
// Draw the image with the top left corner at (10, 20) by wrapping it in
|
||||
// an embedded-graphics `Image`.
|
||||
Image::new(&bmp_rust, Point::new(0, 0))
|
||||
.draw(&mut display)
|
||||
.expect("image drawing error");
|
||||
Image::new(&bmp_ferris, Point::new(32, 0))
|
||||
.draw(&mut display)
|
||||
.expect("image drawing error");
|
||||
display.flush().await.unwrap();
|
||||
let mut ticker = Ticker::every(Duration::from_millis(50));
|
||||
let mut ferris = FerrisMovement::new(32, Direction::Right, 32, 76);
|
||||
|
||||
loop {
|
||||
Image::new(&bmp_ferris, Point::new(ferris.pos as i32, 0))
|
||||
.draw(&mut display)
|
||||
.expect("image drawing error");
|
||||
display.flush().await.expect("flush error");
|
||||
ferris.step();
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Direction {
|
||||
Right,
|
||||
Left,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct FerrisMovement {
|
||||
pub pos: u16,
|
||||
pub dir: Direction,
|
||||
pub left_threshold: u16,
|
||||
pub right_threshold: u16,
|
||||
}
|
||||
|
||||
impl FerrisMovement {
|
||||
/// Creates a new movement controller.
|
||||
/// Assumes: left_threshold <= right_threshold and pos is within that range.
|
||||
pub fn new(pos: u16, dir: Direction, left_threshold: u16, right_threshold: u16) -> Self {
|
||||
Self {
|
||||
pos,
|
||||
dir,
|
||||
left_threshold,
|
||||
right_threshold,
|
||||
}
|
||||
}
|
||||
|
||||
/// Move one tick and "bounce" between thresholds.
|
||||
pub fn step(&mut self) {
|
||||
match self.dir {
|
||||
Direction::Right => {
|
||||
if self.pos >= self.right_threshold {
|
||||
self.dir = Direction::Left; // flip at right boundary
|
||||
} else {
|
||||
self.pos += 1;
|
||||
}
|
||||
}
|
||||
Direction::Left => {
|
||||
if self.pos <= self.left_threshold {
|
||||
self.dir = Direction::Right; // flip at left boundary
|
||||
} else {
|
||||
self.pos -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn logger_task(
|
||||
uart: uart::Uart,
|
||||
reader: embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 4096>,
|
||||
) -> ! {
|
||||
let (tx, _) = uart.split();
|
||||
let mut tx_async = TxAsync::new(tx, true);
|
||||
let mut log_buf: [u8; 2048] = [0; 2048];
|
||||
loop {
|
||||
let read_bytes = reader.read(&mut log_buf).await;
|
||||
if read_bytes > 0 {
|
||||
tx_async.write(&log_buf[0..read_bytes]).unwrap().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn blinky_task(mut mio_led: gpio::Output, mut emio_leds: [gpio::Output; 8]) {
|
||||
let mut ticker = Ticker::every(Duration::from_millis(200));
|
||||
loop {
|
||||
mio_led.toggle().unwrap();
|
||||
|
||||
// Create a wave pattern for emio_leds
|
||||
for led in emio_leds.iter_mut() {
|
||||
led.toggle().unwrap();
|
||||
ticker.next().await; // Wait for the next ticker for each toggle
|
||||
}
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use dummy_pin::DummyPin;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_time::{Delay, Duration, Ticker};
|
||||
use embedded_graphics::{Drawable as _, geometry::Point};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_hal_async::delay::DelayNs as _;
|
||||
use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay};
|
||||
use embedded_io::Write;
|
||||
use log::{error, info};
|
||||
use ssd1306::{Ssd1306, prelude::*};
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zynq7000_hal::{
|
||||
BootMode, clocks, generic_interrupt_handler, gpio, gtc, spi,
|
||||
time::Hertz,
|
||||
uart::{self, TxAsync},
|
||||
};
|
||||
|
||||
use embedded_graphics::image::Image;
|
||||
use tinybmp::Bmp;
|
||||
|
||||
use zynq7000_rt as _;
|
||||
|
||||
const INIT_STRING: &str = "-- Zynq 7000 Zedboard OLED example --\n\r";
|
||||
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[embassy_executor::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 mut clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
|
||||
let target_spi_ref_clock = clocks.arm_clocks().cpu_1x_clk() * 2;
|
||||
// SPI reference clock must be larger than the CPU 1x clock. Also, taking the largest value
|
||||
// actually seems to be problematic. We take 200 MHz here, which is significantly larger than
|
||||
// the CPU 1x clock which is around 110 MHz.
|
||||
spi::configure_spi_ref_clock(&mut clocks, target_spi_ref_clock);
|
||||
|
||||
assert!(
|
||||
clocks.io_clocks().spi_clk().to_raw()
|
||||
> (clocks.arm_clocks().cpu_1x_clk().to_raw() as f32 * 1.2) as u32,
|
||||
"SPI reference clock must be larger than CPU 1x clock"
|
||||
);
|
||||
|
||||
let mut 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_for_uart_1(
|
||||
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();
|
||||
uart.flush().unwrap();
|
||||
|
||||
// Safety: We are not multi-threaded yet.
|
||||
let log_reader = zynq7000_hal::log::asynch::init(log::LevelFilter::Trace)
|
||||
.expect("Failed to initialize async logger");
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
info!(
|
||||
"SPI reference clock speed: {:?}",
|
||||
clocks.io_clocks().spi_clk()
|
||||
);
|
||||
|
||||
spawner.spawn(logger_task(uart, log_reader).unwrap());
|
||||
|
||||
let mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
|
||||
let 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),
|
||||
];
|
||||
spawner.spawn(blinky_task(mio_led, emio_leds).unwrap());
|
||||
|
||||
let dc_pin = gpio::Output::new_for_emio(gpio_pins.emio.take(11).unwrap(), gpio::PinState::High);
|
||||
|
||||
let mut reset_pin =
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(12).unwrap(), gpio::PinState::High);
|
||||
let mut oled_vdd_switch =
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(13).unwrap(), gpio::PinState::High);
|
||||
let mut oled_vbat_switch =
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(14).unwrap(), gpio::PinState::High);
|
||||
Delay.delay_ms(100).await;
|
||||
|
||||
let spi = spi::Spi::new_for_emio(
|
||||
periphs.spi_0,
|
||||
spi::Config::calculate_for_io_clock(
|
||||
Hertz::MHz(8),
|
||||
clocks.io_clocks(),
|
||||
embedded_hal::spi::MODE_0,
|
||||
spi::SlaveSelectConfig::AutoCsManualStart,
|
||||
),
|
||||
)
|
||||
.expect("Failed to initialize SPI");
|
||||
let exclusive_device = ExclusiveDevice::new(spi, DummyPin::new_high(), NoDelay)
|
||||
.expect("Failed to create exclusive SPI device");
|
||||
let spi_if = SPIInterface::new(exclusive_device, dc_pin);
|
||||
let mut ssd1306 = Ssd1306::new(spi_if, DisplaySize128x32, DisplayRotation::Rotate180);
|
||||
|
||||
oled_vdd_switch.set_low();
|
||||
oled_vbat_switch.set_low();
|
||||
Delay.delay_ms(100).await;
|
||||
|
||||
ssd1306.reset(&mut reset_pin, &mut embassy_time::Delay {});
|
||||
let mut display = ssd1306.into_buffered_graphics_mode();
|
||||
display.init().expect("display init error");
|
||||
|
||||
// Include the BMP file data.
|
||||
let ferris_data = include_bytes!("../../assets/ferris-flat-happy-small.bmp");
|
||||
let rust_logo_data = include_bytes!("../../assets/rust-logo-single-path.bmp");
|
||||
// Parse the BMP file.
|
||||
let bmp_rust = Bmp::from_slice(rust_logo_data).unwrap();
|
||||
let bmp_ferris = Bmp::from_slice(ferris_data).unwrap();
|
||||
// Draw the image with the top left corner at (10, 20) by wrapping it in
|
||||
// an embedded-graphics `Image`.
|
||||
Image::new(&bmp_rust, Point::new(0, 0))
|
||||
.draw(&mut display)
|
||||
.expect("image draw error");
|
||||
Image::new(&bmp_ferris, Point::new(32, 0))
|
||||
.draw(&mut display)
|
||||
.expect("image draw error");
|
||||
display.flush().expect("display flush error");
|
||||
let mut ticker = Ticker::every(Duration::from_millis(50));
|
||||
let mut ferris = FerrisMovement::new(32, Direction::Right, 32, 76);
|
||||
|
||||
loop {
|
||||
Image::new(&bmp_ferris, Point::new(ferris.pos as i32, 0))
|
||||
.draw(&mut display)
|
||||
.expect("image draw error");
|
||||
display.flush().expect("display flush error");
|
||||
ferris.step();
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Direction {
|
||||
Right,
|
||||
Left,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct FerrisMovement {
|
||||
pub pos: u16,
|
||||
pub dir: Direction,
|
||||
pub left_threshold: u16,
|
||||
pub right_threshold: u16,
|
||||
}
|
||||
|
||||
impl FerrisMovement {
|
||||
/// Creates a new movement controller.
|
||||
/// Assumes: left_threshold <= right_threshold and pos is within that range.
|
||||
pub fn new(pos: u16, dir: Direction, left_threshold: u16, right_threshold: u16) -> Self {
|
||||
Self {
|
||||
pos,
|
||||
dir,
|
||||
left_threshold,
|
||||
right_threshold,
|
||||
}
|
||||
}
|
||||
|
||||
/// Move one tick and "bounce" between thresholds.
|
||||
pub fn step(&mut self) {
|
||||
match self.dir {
|
||||
Direction::Right => {
|
||||
if self.pos >= self.right_threshold {
|
||||
self.dir = Direction::Left; // flip at right boundary
|
||||
} else {
|
||||
self.pos += 1;
|
||||
}
|
||||
}
|
||||
Direction::Left => {
|
||||
if self.pos <= self.left_threshold {
|
||||
self.dir = Direction::Right; // flip at left boundary
|
||||
} else {
|
||||
self.pos -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn logger_task(
|
||||
uart: uart::Uart,
|
||||
reader: embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 4096>,
|
||||
) -> ! {
|
||||
let (tx, _) = uart.split();
|
||||
let mut tx_async = TxAsync::new(tx, true);
|
||||
let mut log_buf: [u8; 2048] = [0; 2048];
|
||||
loop {
|
||||
let read_bytes = reader.read(&mut log_buf).await;
|
||||
if read_bytes > 0 {
|
||||
tx_async.write(&log_buf[0..read_bytes]).unwrap().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn blinky_task(mut mio_led: gpio::Output, mut emio_leds: [gpio::Output; 8]) {
|
||||
let mut ticker = Ticker::every(Duration::from_millis(200));
|
||||
loop {
|
||||
mio_led.toggle().unwrap();
|
||||
|
||||
// Create a wave pattern for emio_leds
|
||||
for led in emio_leds.iter_mut() {
|
||||
led.toggle().unwrap();
|
||||
ticker.next().await; // Wait for the next ticker for each toggle
|
||||
}
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use arbitrary_int::{traits::Integer as _, u2};
|
||||
use core::panic::PanicInfo;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Ticker};
|
||||
@@ -10,10 +11,13 @@ 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_hal::{
|
||||
BootMode, clocks, generic_interrupt_handler, gpio, gtc, prelude::*, qspi, uart,
|
||||
};
|
||||
|
||||
use zynq7000_rt as _;
|
||||
|
||||
const DISPLAY_CLOCK_CONFIG: bool = false;
|
||||
const INIT_STRING: &str = "-- Zynq 7000 Zedboard QSPI example --\n\r";
|
||||
const QSPI_DEV_COMBINATION: qspi::QspiDeviceCombination = qspi::QspiDeviceCombination {
|
||||
vendor: qspi::QspiVendor::WinbondAndSpansion,
|
||||
@@ -28,6 +32,7 @@ fn entry_point() -> ! {
|
||||
}
|
||||
|
||||
const ERASE_PROGRAM_READ_TEST: bool = false;
|
||||
const TEST_QSPI_BASE: u32 = 0x20000;
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
@@ -68,6 +73,9 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
if DISPLAY_CLOCK_CONFIG {
|
||||
log::debug!("clock config: {:?}", clocks);
|
||||
}
|
||||
|
||||
let qspi_clock_config =
|
||||
qspi::ClockConfig::calculate_with_loopback(qspi::SrcSelIo::IoPll, &clocks, 100.MHz())
|
||||
@@ -90,7 +98,14 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
|
||||
let qspi_io_mode = qspi.into_io_mode(false);
|
||||
|
||||
let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
|
||||
let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(
|
||||
qspi_io_mode,
|
||||
qspi_spansion::Config {
|
||||
set_quad_bit_if_necessary: true,
|
||||
latency_config: Some(u2::ZERO),
|
||||
clear_write_protection: true,
|
||||
},
|
||||
);
|
||||
|
||||
let rdid = spansion_qspi.read_rdid_extended();
|
||||
info!(
|
||||
@@ -103,27 +118,48 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
);
|
||||
let cr1 = spansion_qspi.read_configuration_register();
|
||||
info!("QSPI Configuration Register 1: {:?}", cr1);
|
||||
let sr = spansion_qspi.read_status_register_1();
|
||||
info!("QSPI Status Register: {:?}", sr);
|
||||
|
||||
let mut write_buf: [u8; u8::MAX as usize + 1] = [0x0; u8::MAX as usize + 1];
|
||||
let mut write_buf: [u8; 100 * qspi_spansion::PAGE_SIZE] = [0x0; 100 * qspi_spansion::PAGE_SIZE];
|
||||
for (idx, byte) in write_buf.iter_mut().enumerate() {
|
||||
*byte = idx as u8;
|
||||
*byte = (idx % u8::MAX as usize) as u8;
|
||||
}
|
||||
let mut read_buf = [0u8; 256];
|
||||
let mut read_buf = [0u8; 100 * qspi_spansion::PAGE_SIZE];
|
||||
|
||||
if ERASE_PROGRAM_READ_TEST {
|
||||
info!("performing erase, program, read test");
|
||||
spansion_qspi
|
||||
.erase_sector(0x10000)
|
||||
.erase_sector(TEST_QSPI_BASE)
|
||||
.expect("erasing sector failed");
|
||||
spansion_qspi.read_page_fast_read(0x10000, &mut read_buf, true);
|
||||
spansion_qspi.read_fast_read(TEST_QSPI_BASE, &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);
|
||||
let mut current_offset = 0_usize;
|
||||
let mut current_qspi_offset = TEST_QSPI_BASE;
|
||||
for (idx, chunk) in write_buf
|
||||
.chunks(qspi_spansion::RECOMMENDED_PROGRAM_PAGE_SIZE)
|
||||
.enumerate()
|
||||
{
|
||||
spansion_qspi
|
||||
.program(current_qspi_offset, chunk)
|
||||
.expect("programming failed");
|
||||
spansion_qspi.read_fast_read(
|
||||
current_qspi_offset,
|
||||
&mut read_buf[current_offset..current_offset + chunk.len()],
|
||||
true,
|
||||
);
|
||||
assert_eq!(
|
||||
chunk,
|
||||
&read_buf[current_offset..current_offset + chunk.len()],
|
||||
"read and write missmatch at chunk index {}, data offset {}",
|
||||
idx,
|
||||
current_offset
|
||||
);
|
||||
current_offset += chunk.len();
|
||||
current_qspi_offset += chunk.len() as u32;
|
||||
}
|
||||
info!("test successful");
|
||||
}
|
||||
@@ -133,7 +169,7 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
let guard = spansion_lqspi.read_guard();
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(
|
||||
(qspi::QSPI_START_ADDRESS + 0x10000) as *const u8,
|
||||
(qspi::QSPI_START_ADDRESS + TEST_QSPI_BASE as usize) as *const u8,
|
||||
read_buf.as_mut_ptr(),
|
||||
256,
|
||||
);
|
||||
@@ -159,23 +195,12 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
}
|
||||
|
||||
#[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 => (),
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write;
|
||||
use log::error;
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zynq7000_hal::gpio::Input;
|
||||
use zynq7000_hal::sd::SdClockConfig;
|
||||
use zynq7000_hal::{BootMode, clocks, gpio, gtc, sd::SdCardUninit, uart};
|
||||
use zynq7000_hal::{generic_interrupt_handler, prelude::*};
|
||||
|
||||
use zynq7000_rt as _;
|
||||
|
||||
const INIT_STRING: &str = "-- Zynq 7000 Zedboard SDIO example --\n\r";
|
||||
|
||||
// These are off by default because they write to the SD card as well.
|
||||
const LOW_LEVEL_TESTS: bool = false;
|
||||
const SDMMC_RS_TESTS: bool = false;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DummyTimeSource;
|
||||
|
||||
impl embedded_sdmmc::TimeSource for DummyTimeSource {
|
||||
fn get_timestamp(&self) -> embedded_sdmmc::Timestamp {
|
||||
embedded_sdmmc::Timestamp::from_calendar(1970, 1, 1, 0, 0, 0).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[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_for_uart_1(
|
||||
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 sdio_clock_config =
|
||||
SdClockConfig::calculate_for_io_clock(clocks.io_clocks(), 100.MHz(), 10.MHz()).unwrap();
|
||||
log::info!("SDIO clock config: {:?}", sdio_clock_config);
|
||||
let sd_card_uninit = SdCardUninit::new_for_sdio_0(
|
||||
periphs.sdio_0,
|
||||
sdio_clock_config,
|
||||
// On the zedboard, the bank has a 1.8 V voltage which is shifted up to 3.3 V by a
|
||||
// level shifter.
|
||||
zynq7000_hal::sd::IoType::LvCmos18,
|
||||
gpio_pins.mio.mio40,
|
||||
gpio_pins.mio.mio41,
|
||||
(
|
||||
gpio_pins.mio.mio42,
|
||||
gpio_pins.mio.mio43,
|
||||
gpio_pins.mio.mio44,
|
||||
gpio_pins.mio.mio45,
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
let card_detect = Input::new_for_mio(gpio_pins.mio.mio47).unwrap();
|
||||
let write_protect = Input::new_for_mio(gpio_pins.mio.mio46).unwrap();
|
||||
// The card detect being active low makes sense according to the Zedboard docs. Not sure
|
||||
// about write-protect though.. It seems that write protect on means that the
|
||||
// the pin is pulled high.
|
||||
log::info!("Card detect state: {:?}", card_detect.is_low());
|
||||
log::info!("Write protect state: {:?}", write_protect.is_high());
|
||||
|
||||
let capabilities = sd_card_uninit.ll().capabilities();
|
||||
log::debug!("SDIO Capabilities: {:?}", capabilities);
|
||||
|
||||
let present_state = sd_card_uninit.ll().read_present_state();
|
||||
log::debug!("SD present state: {:?}", present_state);
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
log::info!("Boot mode: {:?}", boot_mode);
|
||||
|
||||
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);
|
||||
|
||||
let sd_result = sd_card_uninit.initialize();
|
||||
let sd_card = match sd_result {
|
||||
Ok(card) => {
|
||||
log::info!("SD card info: {:?}", card.card_info());
|
||||
card
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("SDIO init error: {e:?}");
|
||||
}
|
||||
};
|
||||
|
||||
let mut buf: [u8; 4096] = [0; 4096];
|
||||
|
||||
if LOW_LEVEL_TESTS {
|
||||
log::info!("doing SD card low-level tests");
|
||||
|
||||
let mut cache_buf: [u8; 4096] = [0; 4096];
|
||||
|
||||
// cache the data, will be written back later..
|
||||
sd_card
|
||||
.read_multiple_blocks(&mut cache_buf, 0x1000)
|
||||
.unwrap();
|
||||
|
||||
let mut write_data: [u8; 4096] = [0; 4096];
|
||||
for chunk in write_data.chunks_mut(u8::MAX as usize) {
|
||||
for (idx, byte) in chunk.iter_mut().enumerate() {
|
||||
*byte = idx as u8;
|
||||
}
|
||||
}
|
||||
sd_card.write_multiple_blocks(&write_data, 0x1000).unwrap();
|
||||
|
||||
sd_card.read_multiple_blocks(&mut buf, 0x1000).unwrap();
|
||||
for chunk in buf.chunks(u8::MAX as usize) {
|
||||
for (idx, byte) in chunk.iter().enumerate() {
|
||||
assert_eq!(idx as u8, *byte);
|
||||
}
|
||||
}
|
||||
|
||||
sd_card.write_multiple_blocks(&cache_buf, 0x1000).unwrap();
|
||||
|
||||
log::info!("SD card low-level tests success");
|
||||
}
|
||||
|
||||
buf.fill(0);
|
||||
|
||||
if SDMMC_RS_TESTS {
|
||||
log::info!("doing SD card embedded-sdmmc-rs tests");
|
||||
|
||||
// Now let's look for volumes (also known as partitions) on our block device.
|
||||
// To do this we need a Volume Manager. It will take ownership of the block device.
|
||||
let volume_mgr = embedded_sdmmc::VolumeManager::new(sd_card, DummyTimeSource);
|
||||
// Try and access Volume 0 (i.e. the first partition).
|
||||
// The volume object holds information about the filesystem on that volume.
|
||||
let volume0 = volume_mgr
|
||||
.open_volume(embedded_sdmmc::VolumeIdx(0))
|
||||
.unwrap();
|
||||
|
||||
// Open the root directory (mutably borrows from the volume).
|
||||
let mut current_dir = volume0.open_root_dir().unwrap();
|
||||
log::info!("iterating root directory");
|
||||
current_dir
|
||||
.iterate_dir(|entry| {
|
||||
log::info!("{:?}", entry);
|
||||
core::ops::ControlFlow::Continue(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let new_file = current_dir
|
||||
.open_file_in_dir("__T.TXT", embedded_sdmmc::Mode::ReadWriteCreateOrTruncate)
|
||||
.unwrap();
|
||||
let string = "test string\n";
|
||||
new_file.write(string.as_bytes()).unwrap();
|
||||
new_file.close().unwrap();
|
||||
|
||||
let read_new = current_dir
|
||||
.open_file_in_dir("__T.TXT", embedded_sdmmc::Mode::ReadOnly)
|
||||
.unwrap();
|
||||
assert_eq!(read_new.length(), string.len() as u32);
|
||||
read_new.read(&mut buf).unwrap();
|
||||
let buf_as_str = core::str::from_utf8(&buf[0..string.len()]).unwrap();
|
||||
assert_eq!(buf_as_str, string);
|
||||
read_new.close().unwrap();
|
||||
|
||||
current_dir.delete_entry_in_dir("__T.TXT").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
current_dir.find_directory_entry("__T.TXT").unwrap_err(),
|
||||
embedded_sdmmc::Error::NotFound
|
||||
);
|
||||
|
||||
if current_dir.find_directory_entry("_TDIR").is_ok() {
|
||||
current_dir.delete_entry_in_dir("_TDIR").unwrap();
|
||||
}
|
||||
|
||||
current_dir.make_dir_in_dir("_TDIR").unwrap();
|
||||
current_dir.change_dir("_TDIR").unwrap();
|
||||
current_dir.change_dir("..").unwrap();
|
||||
current_dir.delete_entry_in_dir("_TDIR").unwrap();
|
||||
|
||||
current_dir.close().unwrap();
|
||||
|
||||
log::info!("SD card embedded-sdmmc-rs success");
|
||||
}
|
||||
|
||||
loop {
|
||||
mio_led.toggle().unwrap();
|
||||
|
||||
ticker.next().await; // Wait for the next cycle of the ticker
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {}
|
||||
}
|
||||
@@ -9,14 +9,12 @@ use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write;
|
||||
use fugit::RateExtU32;
|
||||
use log::{error, info};
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
configure_level_shifter,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
configure_level_shifter, generic_interrupt_handler, gic,
|
||||
gpio::{GpioPins, Output, PinState},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
@@ -105,7 +103,7 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
// 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);
|
||||
let mut gic = gic::Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -147,7 +145,8 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
|
||||
// TODO: Can we determine/read the clock frequency to the FPGAs as well?
|
||||
let (clk_config, error) =
|
||||
axi_uart16550::ClockConfig::new_autocalc_with_error(100.MHz(), 115200).unwrap();
|
||||
axi_uart16550::ClockConfig::new_autocalc_with_error(fugit_03::HertzU32::MHz(100), 115200)
|
||||
.unwrap();
|
||||
assert!(error < 0.02);
|
||||
let mut uart_16550 = unsafe {
|
||||
AxiUart16550::new(
|
||||
@@ -215,23 +214,12 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Spi(_spi_interrupt) => (),
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
|
||||
@@ -41,8 +41,8 @@ use log::{error, info, warn};
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
configure_level_shifter,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
configure_level_shifter, generic_interrupt_handler,
|
||||
gic::{Configurator, Interrupt},
|
||||
gpio::{GpioPins, Output, PinState},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
@@ -50,6 +50,7 @@ use zynq7000_hal::{
|
||||
uart::{ClockConfig, Config, Uart},
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum UartMode {
|
||||
Uart0ToUartlite,
|
||||
Uart0ToUart16550,
|
||||
@@ -74,6 +75,14 @@ const AXI_UAR16550_BASE_ADDR: u32 = 0x43C0_0000;
|
||||
pub const UARTLITE_PL_INT_ID: usize = 0;
|
||||
pub const UART16550_PL_INT_ID: usize = 1;
|
||||
|
||||
pub const UART_SPEED: u32 = 115_200;
|
||||
|
||||
// Other common baud rates to test with:
|
||||
|
||||
// pub const UART_SPEED: u32 = 9600;
|
||||
// pub const UART_SPEED: u32 = 230_400;
|
||||
// pub const UART_SPEED: u32 = 912_600;
|
||||
|
||||
const RB_SIZE: usize = 512;
|
||||
|
||||
// These queues are used to send all data received in the UART interrupt handlers to the main
|
||||
@@ -88,11 +97,11 @@ static QUEUE_UART16550: static_cell::ConstStaticCell<heapless::spsc::Queue<u8, R
|
||||
// Those are all used by the interrupt handler, so we have to do the Mutex dance.
|
||||
static RX_UART_0: Mutex<RefCell<Option<zynq7000_hal::uart::Rx>>> = Mutex::new(RefCell::new(None));
|
||||
|
||||
static UART_0_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8, RB_SIZE>>>> =
|
||||
static UART_0_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8>>>> =
|
||||
Mutex::new(RefCell::new(None));
|
||||
static UARTLITE_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8, RB_SIZE>>>> =
|
||||
static UARTLITE_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8>>>> =
|
||||
Mutex::new(RefCell::new(None));
|
||||
static UART16550_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8, RB_SIZE>>>> =
|
||||
static UART16550_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8>>>> =
|
||||
Mutex::new(RefCell::new(None));
|
||||
|
||||
/// Entry point which calls the embassy main method.
|
||||
@@ -171,7 +180,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
// 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);
|
||||
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
// AXI UARTLite documentation mentions that a rising-edge sensitive interrupt is generated,
|
||||
@@ -210,6 +219,20 @@ async fn main(spawner: Spawner) -> ! {
|
||||
unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) }
|
||||
}
|
||||
|
||||
// Register the interrupts for the PL.
|
||||
zynq7000_hal::register_interrupt(
|
||||
Interrupt::Spi(zynq7000_hal::gic::SpiInterrupt::Pl0),
|
||||
on_interrupt_axi_uartlite,
|
||||
);
|
||||
zynq7000_hal::register_interrupt(
|
||||
Interrupt::Spi(zynq7000_hal::gic::SpiInterrupt::Pl1),
|
||||
on_interrupt_axi_16550,
|
||||
);
|
||||
zynq7000_hal::register_interrupt(
|
||||
Interrupt::Spi(zynq7000_hal::gic::SpiInterrupt::Uart0),
|
||||
on_interrupt_uart_0,
|
||||
);
|
||||
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
@@ -225,24 +248,42 @@ async fn main(spawner: Spawner) -> ! {
|
||||
Output::new_for_emio(gpio_pins.emio.take(9).unwrap(), PinState::Low),
|
||||
Output::new_for_emio(gpio_pins.emio.take(10).unwrap(), PinState::Low),
|
||||
]);
|
||||
let mut uart_speed = UART_SPEED;
|
||||
match UART_MODE {
|
||||
UartMode::Uart0ToUartlite => uart_mux.select(UartSel::Uart0ToUartlite),
|
||||
UartMode::Uart0ToUart16550 => uart_mux.select(UartSel::Uart0ToUart16550),
|
||||
UartMode::UartliteToUart16550 => uart_mux.select(UartSel::UartliteToUart16550),
|
||||
}
|
||||
if (UART_MODE == UartMode::Uart0ToUartlite || UART_MODE == UartMode::UartliteToUart16550)
|
||||
&& uart_speed != 115200
|
||||
{
|
||||
log::warn!("UARTLITE speed is not configurable. Hardcoding UART speed to 115200");
|
||||
uart_speed = 115200;
|
||||
}
|
||||
|
||||
let uart0_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), uart_speed)
|
||||
.unwrap()
|
||||
.0;
|
||||
// UART0 routed through EMIO to PL pins.
|
||||
let uart_0 =
|
||||
Uart::new_with_emio(dp.uart_0, Config::new_with_clk_config(uart_clk_config)).unwrap();
|
||||
Uart::new_with_emio(dp.uart_0, Config::new_with_clk_config(uart0_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
|
||||
// used for both TX and RX, so the API is only exposed for this structure.
|
||||
uartlite.enable_interrupt();
|
||||
|
||||
let (clk_config, error) =
|
||||
axi_uart16550::ClockConfig::new_autocalc_with_error(clocks.pl_clocks()[0], 115200).unwrap();
|
||||
assert!(error < 0.02);
|
||||
let (clk_config, error) = axi_uart16550::ClockConfig::new_autocalc_with_error(
|
||||
fugit_03::HertzU32::from_raw(clocks.pl_clocks()[0].to_raw()),
|
||||
uart_speed,
|
||||
)
|
||||
.unwrap();
|
||||
if error > 0.02 {
|
||||
log::warn!(
|
||||
"Calculated clock config for AXI UART16550 has error of {} %, which is higher than 2%. This may lead to incorrect baud rate. Consider changing the input clock or the target baud rate.",
|
||||
(error * 100.0)
|
||||
);
|
||||
}
|
||||
let _uart_16550 = unsafe {
|
||||
AxiUart16550::new(
|
||||
AXI_UAR16550_BASE_ADDR,
|
||||
@@ -272,7 +313,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
let (uartlite_prod, mut uartlite_cons) = QUEUE_UARTLITE.take().split();
|
||||
let (uart16550_prod, mut uart16550_cons) = QUEUE_UART16550.take().split();
|
||||
// Use our helper function to start RX handling.
|
||||
uart_0_rx.start_interrupt_driven_reception();
|
||||
uart_0_rx.start_interrupt_driven_reception(0xFF);
|
||||
// Use our helper function to start RX handling.
|
||||
uart_16550_rx.start_interrupt_driven_reception();
|
||||
critical_section::with(|cs| {
|
||||
@@ -284,20 +325,20 @@ async fn main(spawner: Spawner) -> ! {
|
||||
.replace(uart16550_prod);
|
||||
RX_UART_0.borrow(cs).borrow_mut().replace(uart_0_rx);
|
||||
});
|
||||
spawner.spawn(led_task(mio_led, emio_leds)).unwrap();
|
||||
spawner.spawn(led_task(mio_led, emio_leds).unwrap());
|
||||
|
||||
match UART_MODE {
|
||||
UartMode::Uart0ToUartlite => {
|
||||
spawner.spawn(uartlite_task(uartlite_tx)).unwrap();
|
||||
spawner.spawn(uart_0_task(uart_0_tx)).unwrap();
|
||||
spawner.spawn(uartlite_task(uartlite_tx).unwrap());
|
||||
spawner.spawn(uart_0_task(uart_0_tx).unwrap());
|
||||
}
|
||||
UartMode::Uart0ToUart16550 => {
|
||||
spawner.spawn(uart_0_task(uart_0_tx)).unwrap();
|
||||
spawner.spawn(uart_16550_task(uart_16550_tx)).unwrap();
|
||||
spawner.spawn(uart_0_task(uart_0_tx).unwrap());
|
||||
spawner.spawn(uart_16550_task(uart_16550_tx).unwrap());
|
||||
}
|
||||
UartMode::UartliteToUart16550 => {
|
||||
spawner.spawn(uartlite_task(uartlite_tx)).unwrap();
|
||||
spawner.spawn(uart_16550_task(uart_16550_tx)).unwrap();
|
||||
spawner.spawn(uartlite_task(uartlite_tx).unwrap());
|
||||
spawner.spawn(uart_16550_task(uart_16550_tx).unwrap());
|
||||
}
|
||||
}
|
||||
let mut read_buf: [u8; RB_SIZE] = [0; RB_SIZE];
|
||||
@@ -387,7 +428,8 @@ async fn uartlite_task(uartlite: axi_uartlite::Tx) {
|
||||
#[embassy_executor::task]
|
||||
async fn uart_0_task(uart_tx: zynq7000_hal::uart::Tx) {
|
||||
let mut ticker = Ticker::every(Duration::from_millis(1000));
|
||||
let mut tx_async = zynq7000_hal::uart::TxAsync::new(uart_tx);
|
||||
let mut tx_async = zynq7000_hal::uart::TxAsync::new(uart_tx, false);
|
||||
|
||||
let str0 = build_print_string("UART0:", "Hello World");
|
||||
let str1 = build_print_string(
|
||||
"UART0:",
|
||||
@@ -396,7 +438,7 @@ async fn uart_0_task(uart_tx: zynq7000_hal::uart::Tx) {
|
||||
let mut idx = 0;
|
||||
let print_strs = [str0.as_bytes(), str1.as_bytes()];
|
||||
loop {
|
||||
tx_async.write(print_strs[idx]).await;
|
||||
tx_async.write(print_strs[idx]).unwrap().await;
|
||||
idx += 1;
|
||||
if idx == 2 {
|
||||
idx = 0;
|
||||
@@ -429,36 +471,12 @@ async fn uart_16550_task(uart_tx: axi_uart16550::Tx) {
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Spi(spi_interrupt) => match spi_interrupt {
|
||||
zynq7000_hal::gic::SpiInterrupt::Pl0 => {
|
||||
on_interrupt_axi_uartlite();
|
||||
}
|
||||
zynq7000_hal::gic::SpiInterrupt::Pl1 => {
|
||||
on_interrupt_axi_16550();
|
||||
}
|
||||
zynq7000_hal::gic::SpiInterrupt::Uart0 => {
|
||||
on_interrupt_uart_0();
|
||||
}
|
||||
|
||||
_ => (),
|
||||
},
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
fn on_interrupt_axi_uartlite() {
|
||||
@@ -527,8 +545,9 @@ fn on_interrupt_uart_0() {
|
||||
.on_interrupt(&mut buf, true)
|
||||
.read_bytes();
|
||||
});
|
||||
// Safety: This function is only called once inside the interrupt handler.
|
||||
// Handle TX next: Handle pending asynchronous TX operations.
|
||||
zynq7000_hal::uart::on_interrupt_tx(zynq7000_hal::uart::UartId::Uart0);
|
||||
unsafe { zynq7000_hal::uart::on_interrupt_tx(zynq7000_hal::uart::UartId::Uart0) };
|
||||
// Send received RX data to main task.
|
||||
if read_bytes > 0 {
|
||||
critical_section::with(|cs| {
|
||||
|
||||
@@ -9,7 +9,7 @@ use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write;
|
||||
use log::{error, info};
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, uart};
|
||||
use zynq7000_hal::{BootMode, clocks, generic_interrupt_handler, gpio, gtc, uart};
|
||||
|
||||
use zynq7000_rt as _;
|
||||
|
||||
@@ -88,23 +88,12 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
}
|
||||
|
||||
#[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 => (),
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
|
||||
+1
-1
@@ -5,4 +5,4 @@ break main
|
||||
|
||||
load
|
||||
|
||||
continue
|
||||
# continue
|
||||
|
||||
@@ -8,6 +8,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
## Fixed
|
||||
|
||||
- QSPI robustness fixes. Read, fast-read and write operations are now chunked according to the 252
|
||||
byte limit specified in the TRM.
|
||||
|
||||
## Added
|
||||
|
||||
- QSPI constructor can now optionally clear block protection and set latency configuration.
|
||||
|
||||
## Changed
|
||||
|
||||
- Alignment rules of Spansion QSPI page program now only require 4 byte aligned size.
|
||||
|
||||
# [v0.1.0]
|
||||
|
||||
Initial release
|
||||
|
||||
@@ -10,9 +10,14 @@ keywords = ["no-std", "zedboard", "bare-metal", "amd", "zynq7000"]
|
||||
categories = ["embedded", "no-std", "hardware-support"]
|
||||
|
||||
[dependencies]
|
||||
zynq7000 = { path = "../zynq7000", version = "0.1" }
|
||||
zynq7000 = { path = "../zynq7000", version = "0.4" }
|
||||
zynq7000-hal = { path = "../zynq7000-hal", version = "0.1" }
|
||||
bitbybit = "1.4"
|
||||
bitbybit = "2"
|
||||
log = "0.4"
|
||||
arbitrary-int = "2"
|
||||
num_enum = { version = "0.7", default-features = false }
|
||||
thiserror = { version = "2", default-features = false }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["armv7a-none-eabihf"]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# program."]
|
||||
# program."]
|
||||
#![doc = r""]
|
||||
#![doc = r"This configuration file contains static DDR configuration parameters extracted from the"]
|
||||
#![doc = r"AMD ps7init.tcl file. It was generated for the MT41K128M16JT-125 DDR chip."]
|
||||
@@ -34,7 +34,7 @@ pub const DDRC_CONFIG_ZEDBOARD: DdrcConfigSet = DdrcConfigSet {
|
||||
ctrl_reg5: regs::CtrlReg5::new_with_raw_value(0x00466111),
|
||||
ctrl_reg6: regs::CtrlReg6::new_with_raw_value(0x00032222),
|
||||
che_t_zq: regs::CheTZq::new_with_raw_value(0x10200802),
|
||||
che_t_zq_short_interval_reg: regs::CheTZqShortInterval::new_with_raw_value(0x10200802),
|
||||
che_t_zq_short_interval_reg: regs::CheTZqShortInterval::new_with_raw_value(0x0690cb73),
|
||||
deep_powerdown: regs::DeepPowerdown::new_with_raw_value(0x000001fe),
|
||||
reg_2c: regs::Reg2c::new_with_raw_value(0x1cffffff),
|
||||
reg_2d: regs::Reg2d::new_with_raw_value(0x00000200),
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# program."]
|
||||
# program."]
|
||||
#![doc = r""]
|
||||
#![doc = r"This configuration file contains static DDRIOB configuration parameters extracted from the"]
|
||||
#![doc = r"AMD ps7init.tcl file. It was generated for the MT41K128M16JT-125 DDR chip."]
|
||||
use zynq7000::ddrc::regs;
|
||||
use zynq7000_hal::ddr::DdriobConfigSet;
|
||||
pub const DDRIOB_CONFIG_SET_ZEDBOARD: DdriobConfigSet = DdriobConfigSet {
|
||||
ddr_control: zynq7000::slcr::ddriob::DdrControl::new_with_raw_value(0x00000260),
|
||||
addr0: regs::DdriobConfig::new_with_raw_value(0x00000600),
|
||||
addr1: regs::DdriobConfig::new_with_raw_value(0x00000600),
|
||||
data0: regs::DdriobConfig::new_with_raw_value(0x00000672),
|
||||
|
||||
@@ -2,10 +2,17 @@ use core::cell::RefCell;
|
||||
|
||||
use arbitrary_int::{prelude::*, u24};
|
||||
use zynq7000_hal::qspi::{
|
||||
FIFO_DEPTH, LinearQspiConfig, QspiIoMode, QspiIoTransferGuard, QspiLinearAddressing,
|
||||
FIFO_DEPTH, LinearQspiConfig, MAX_BYTES_PER_TRANSFER_IO_MODE, QspiIoMode, QspiLinearAddressing,
|
||||
QspiLinearReadGuard,
|
||||
};
|
||||
|
||||
/// 4 bytes are reserved for command byte and address. Rounded down at a 16 byte boundary,
|
||||
/// recommended by flash memory datasheet.
|
||||
pub const MAX_DATA_BYTES_PER_WRITE: usize = 240;
|
||||
/// Probably the most performant chunk/program size to program the chip without crossing page
|
||||
/// boundaries without exceeding the FIFO size.
|
||||
pub const RECOMMENDED_PROGRAM_PAGE_SIZE: usize = 128;
|
||||
|
||||
pub const QSPI_DEV_COMBINATION_REV_F: zynq7000_hal::qspi::QspiDeviceCombination =
|
||||
zynq7000_hal::qspi::QspiDeviceCombination {
|
||||
vendor: zynq7000_hal::qspi::QspiVendor::WinbondAndSpansion,
|
||||
@@ -13,7 +20,8 @@ pub const QSPI_DEV_COMBINATION_REV_F: zynq7000_hal::qspi::QspiDeviceCombination
|
||||
two_devices: false,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum RegisterId {
|
||||
/// WRR
|
||||
WriteRegisters = 0x01,
|
||||
@@ -64,7 +72,8 @@ pub enum SectorArchictecture {
|
||||
Hybrid = 0x01,
|
||||
}
|
||||
|
||||
pub const PAGE_SIZE: usize = 256;
|
||||
pub const PAGE_SIZE: usize = 0x100;
|
||||
pub const SECTOR_SIZE: usize = 0x10000;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BaseDeviceId {
|
||||
@@ -159,8 +168,7 @@ impl ExtendedDeviceId {
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u8)]
|
||||
#[derive(Debug)]
|
||||
#[bitbybit::bitfield(u8, debug, forbid_overlaps)]
|
||||
pub struct StatusRegister1 {
|
||||
#[bit(7, rw)]
|
||||
status_register_write_disable: bool,
|
||||
@@ -168,25 +176,18 @@ pub struct StatusRegister1 {
|
||||
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,
|
||||
#[bits(2..=4, rw)]
|
||||
block_protection: u3,
|
||||
#[bit(1, r)]
|
||||
write_enable_latch: bool,
|
||||
#[bit(0, r)]
|
||||
write_in_progress: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u8)]
|
||||
#[derive(Debug)]
|
||||
#[bitbybit::bitfield(u8, debug, forbid_overlaps)]
|
||||
pub struct ConfigRegister1 {
|
||||
#[bit(7, rw)]
|
||||
latency_code_1: bool,
|
||||
#[bit(6, rw)]
|
||||
latency_code_0: bool,
|
||||
#[bits(6..=7, rw)]
|
||||
latency_code: u2,
|
||||
/// 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,
|
||||
@@ -223,27 +224,60 @@ pub enum ProgramPageError {
|
||||
ProgrammingErrorBitSet,
|
||||
#[error("address error: {0}")]
|
||||
Addr(#[from] AddrError),
|
||||
#[error("data is larger than page size {PAGE_SIZE}")]
|
||||
DataLargerThanPage,
|
||||
#[error("program data is larger than page size {PAGE_SIZE}")]
|
||||
DataTooLarge,
|
||||
#[error("program data is not aligned to 4 bytes")]
|
||||
NotAligned,
|
||||
#[error("program data crosses page boundary")]
|
||||
CrossesPageBoundary,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub struct Config {
|
||||
pub set_quad_bit_if_necessary: bool,
|
||||
pub latency_config: Option<u2>,
|
||||
pub clear_write_protection: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn sr_or_cr_update_possibly_required(&self) -> bool {
|
||||
self.set_quad_bit_if_necessary
|
||||
|| self.latency_config.is_some()
|
||||
|| self.clear_write_protection
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QspiSpansionS25Fl256SIoMode(RefCell<QspiIoMode>);
|
||||
|
||||
impl QspiSpansionS25Fl256SIoMode {
|
||||
pub fn new(qspi: QspiIoMode, set_quad_bit_if_necessary: bool) -> Self {
|
||||
pub fn new(qspi: QspiIoMode, config: Config) -> Self {
|
||||
let mut spansion_qspi = QspiSpansionS25Fl256SIoMode(RefCell::new(qspi));
|
||||
if set_quad_bit_if_necessary {
|
||||
spansion_qspi.clear_status();
|
||||
let mut write_required = false;
|
||||
if config.sr_or_cr_update_possibly_required() {
|
||||
let mut cr1 = spansion_qspi.read_configuration_register();
|
||||
if cr1.quad() {
|
||||
// Quad bit is already set.
|
||||
return spansion_qspi;
|
||||
if config.set_quad_bit_if_necessary && !cr1.quad() {
|
||||
cr1.set_quad(true);
|
||||
write_required = true;
|
||||
}
|
||||
if let Some(latency_config) = config.latency_config
|
||||
&& cr1.latency_code() != latency_config
|
||||
{
|
||||
cr1.set_latency_code(latency_config);
|
||||
write_required = true;
|
||||
}
|
||||
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);
|
||||
let mut sr1 = spansion_qspi.read_status_register_1();
|
||||
if config.clear_write_protection && sr1.block_protection() != u3::ZERO {
|
||||
sr1.set_status_register_write_disable(false);
|
||||
sr1.set_block_protection(u3::ZERO);
|
||||
write_required = true;
|
||||
}
|
||||
if write_required {
|
||||
// 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
|
||||
@@ -257,6 +291,16 @@ impl QspiSpansionS25Fl256SIoMode {
|
||||
QspiSpansionS25Fl256SLinearMode(qspi)
|
||||
}
|
||||
|
||||
pub fn set_write_protection(&mut self, write_protection: u3) {
|
||||
unsafe {
|
||||
self.modify_status_and_config_register(|mut sr, cr| {
|
||||
sr.set_status_register_write_disable(false);
|
||||
sr.set_block_protection(write_protection);
|
||||
(sr, cr)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_enable(&mut self) {
|
||||
let qspi = self.0.get_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
@@ -301,7 +345,38 @@ impl QspiSpansionS25Fl256SIoMode {
|
||||
/// # 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
|
||||
/// configuration register, which is OTP bits. Changing these bits from 0 to 1 is an
|
||||
/// irreversible operation.
|
||||
pub unsafe fn modify_status_and_config_register(
|
||||
&mut self,
|
||||
f: impl FnOnce(StatusRegister1, ConfigRegister1) -> (StatusRegister1, ConfigRegister1),
|
||||
) {
|
||||
self.write_enable();
|
||||
let mut qspi = self.0.borrow_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
let sr1 = self.read_status_register_1();
|
||||
let cr1 = self.read_configuration_register();
|
||||
let (sr1, cr1) = f(sr1, cr1);
|
||||
transfer.write_word_txd_11(u32::from_ne_bytes([
|
||||
RegisterId::WriteRegisters as u8,
|
||||
sr1.raw_value(),
|
||||
cr1.raw_value(),
|
||||
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 is OTP bits. Changing these bits from 0 to 1 is an
|
||||
/// irreversible operation.
|
||||
pub unsafe fn write_status_and_config_register(
|
||||
&mut self,
|
||||
@@ -388,10 +463,10 @@ impl QspiSpansionS25Fl256SIoMode {
|
||||
|
||||
/// 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() {
|
||||
if addr + SECTOR_SIZE as u32 > u24::MAX.as_u32() {
|
||||
return Err(AddrError::OutOfRange.into());
|
||||
}
|
||||
if !addr.is_multiple_of(0x10000) {
|
||||
if !addr.is_multiple_of(SECTOR_SIZE as u32) {
|
||||
return Err(AddrError::Alignment.into());
|
||||
}
|
||||
self.write_enable();
|
||||
@@ -429,19 +504,35 @@ impl QspiSpansionS25Fl256SIoMode {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
pub fn write_pages(&mut self, mut 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());
|
||||
for chunk in data.chunks(RECOMMENDED_PROGRAM_PAGE_SIZE) {
|
||||
self.program(addr, chunk)?;
|
||||
addr += chunk.len() as u32;
|
||||
}
|
||||
if data.len() > PAGE_SIZE {
|
||||
return Err(ProgramPageError::DataLargerThanPage);
|
||||
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 may not exceed [MAX_DATA_BYTES_PER_WRITE]. Furthermore, the data needs
|
||||
/// to be aligned to 4 bytes and the programming operation is not allowed to cross a page.
|
||||
/// boundary. It is recommended to program in 128 byte chunks.
|
||||
pub fn program(&mut self, addr: u32, data: &[u8]) -> Result<(), ProgramPageError> {
|
||||
if addr + data.len() as u32 > u24::MAX.as_u32() {
|
||||
return Err(AddrError::OutOfRange.into());
|
||||
}
|
||||
if data.len() > MAX_DATA_BYTES_PER_WRITE {
|
||||
return Err(ProgramPageError::DataTooLarge);
|
||||
}
|
||||
if !data.len().is_multiple_of(4) {
|
||||
return Err(ProgramPageError::NotAligned);
|
||||
}
|
||||
if (addr as usize % PAGE_SIZE) + data.len() > PAGE_SIZE {
|
||||
return Err(ProgramPageError::CrossesPageBoundary);
|
||||
}
|
||||
self.write_enable();
|
||||
let qspi = self.0.get_mut();
|
||||
@@ -455,7 +546,8 @@ impl QspiSpansionS25Fl256SIoMode {
|
||||
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);
|
||||
// Full four byte writes.
|
||||
let fifo_writes = data.len() / 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(
|
||||
@@ -465,52 +557,14 @@ impl QspiSpansionS25Fl256SIoMode {
|
||||
));
|
||||
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;
|
||||
}
|
||||
|
||||
// Wait until the transfer is done by waiting until all RX bytes have been received.
|
||||
while read_index < data.len() as u32 {
|
||||
if transfer.read_status().rx_above_threshold() {
|
||||
// Double read to avoid RX underflows as specified in TRM.
|
||||
let status_read = transfer.read_status();
|
||||
if status_read.rx_above_threshold() && transfer.read_status().rx_above_threshold() {
|
||||
transfer.read_rx_data();
|
||||
read_index = read_index.wrapping_add(4);
|
||||
}
|
||||
@@ -533,84 +587,134 @@ impl QspiSpansionS25Fl256SIoMode {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_page_fast_read(&self, addr: u32, buf: &mut [u8], dummy_byte: bool) {
|
||||
fn generic_read(&self, addr: u32, buf: &mut [u8], dummy_byte: bool, fast_read: bool) {
|
||||
let mut offset = 0;
|
||||
let reg_id = if fast_read {
|
||||
RegisterId::FastRead
|
||||
} else {
|
||||
RegisterId::Read
|
||||
};
|
||||
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();
|
||||
let mut max_chunk_size = MAX_BYTES_PER_TRANSFER_IO_MODE - 4;
|
||||
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;
|
||||
max_chunk_size -= 1;
|
||||
}
|
||||
|
||||
transfer.start();
|
||||
let mut reply_word_index = 0;
|
||||
while offset < buf.len() {
|
||||
// Calculate the size of the current chunk (max 248 bytes)
|
||||
let chunk_size = core::cmp::min(max_chunk_size, buf.len() - offset);
|
||||
let current_addr = addr + offset as u32;
|
||||
|
||||
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;
|
||||
// Create a mutable slice for the current chunk
|
||||
let chunk_slice = &mut buf[offset..offset + chunk_size];
|
||||
|
||||
// This ensures the hardware transaction (Chip Select, etc.) restarts for each chunk.
|
||||
{
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
|
||||
let raw_word: [u8; 4] = [
|
||||
reg_id as u8,
|
||||
((current_addr >> 16) & 0xff) as u8,
|
||||
((current_addr >> 8) & 0xff) as u8,
|
||||
(current_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;
|
||||
// Use chunk_size instead of the full buffer length
|
||||
let mut bytes_to_write = chunk_size;
|
||||
|
||||
if dummy_byte {
|
||||
bytes_to_write += 1;
|
||||
}
|
||||
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
|
||||
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;
|
||||
|
||||
// Loop based on the current chunk's size
|
||||
while read_index < chunk_size {
|
||||
let rx_is_above_threshold = transfer.read_status().rx_above_threshold();
|
||||
|
||||
// See p.374 of the TRM: Do a double read to ensure this is correct information.
|
||||
if rx_is_above_threshold && 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();
|
||||
// Calculate remaining bytes in this specific chunk
|
||||
let reply_size = core::cmp::min(chunk_size - read_index, 4);
|
||||
read_index += match (reply_size, reply_word_index == 1 && dummy_byte) {
|
||||
(1, false) => {
|
||||
chunk_slice[read_index] = reply_as_bytes[0];
|
||||
1
|
||||
}
|
||||
(1, true) => {
|
||||
chunk_slice[read_index] = reply_as_bytes[1];
|
||||
1
|
||||
}
|
||||
(2, false) => {
|
||||
chunk_slice[read_index..read_index + 2]
|
||||
.copy_from_slice(&reply_as_bytes[0..2]);
|
||||
2
|
||||
}
|
||||
(2, true) => {
|
||||
chunk_slice[read_index..read_index + 2]
|
||||
.copy_from_slice(&reply_as_bytes[1..3]);
|
||||
2
|
||||
}
|
||||
(3, false) => {
|
||||
chunk_slice[read_index..read_index + 3]
|
||||
.copy_from_slice(&reply_as_bytes[0..3]);
|
||||
3
|
||||
}
|
||||
(3, true) => {
|
||||
chunk_slice[read_index..read_index + 3]
|
||||
.copy_from_slice(&reply_as_bytes[1..4]);
|
||||
3
|
||||
}
|
||||
(4, false) => {
|
||||
chunk_slice[read_index..read_index + 4]
|
||||
.copy_from_slice(&reply_as_bytes[0..4]);
|
||||
4
|
||||
}
|
||||
(4, true) => {
|
||||
chunk_slice[read_index..read_index + 3]
|
||||
.copy_from_slice(&reply_as_bytes[1..4]);
|
||||
3
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
reply_word_index += 1;
|
||||
}
|
||||
(1, true) => {
|
||||
buf[read_index] = reply_as_bytes[1];
|
||||
1
|
||||
if written_words < fifo_writes && !transfer.read_status().tx_full() {
|
||||
transfer.write_word_txd_00(0);
|
||||
written_words += 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;
|
||||
}
|
||||
}
|
||||
|
||||
offset += chunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_fast_read(&self, addr: u32, buf: &mut [u8], dummy_byte: bool) {
|
||||
self.generic_read(addr, buf, dummy_byte, true)
|
||||
}
|
||||
|
||||
/// Only works if the clock speed is slower than 50 MHz according to datasheet.
|
||||
pub fn read_page_read(&self, addr: u32, buf: &mut [u8]) {
|
||||
self.generic_read(addr, buf, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
/// If the Spansion QSPI is used in linear addressed mode, no IO operations are allowed.
|
||||
@@ -618,10 +722,12 @@ pub struct QspiSpansionS25Fl256SLinearMode(QspiLinearAddressing);
|
||||
|
||||
impl QspiSpansionS25Fl256SLinearMode {
|
||||
pub const BASE_ADDR: usize = QspiLinearAddressing::BASE_ADDRESS;
|
||||
pub const PAGE_SIZE: usize = PAGE_SIZE;
|
||||
pub const SECTOR_SIZE: usize = SECTOR_SIZE;
|
||||
|
||||
pub fn into_io_mode(self, dual_flash: bool) -> QspiSpansionS25Fl256SIoMode {
|
||||
let qspi = self.0.into_io_mode(dual_flash);
|
||||
QspiSpansionS25Fl256SIoMode::new(qspi, false)
|
||||
QspiSpansionS25Fl256SIoMode::new(qspi, Config::default())
|
||||
}
|
||||
|
||||
pub fn read_guard(&mut self) -> QspiLinearReadGuard<'_> {
|
||||
|
||||
@@ -9,7 +9,7 @@ repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
aarch32-cpu = { version = "0.1", features = ["critical-section-single-core"] }
|
||||
aarch32-cpu = { version = "0.3", features = ["critical-section-single-core"] }
|
||||
zynq7000-rt = { path = "../zynq7000-rt" }
|
||||
zynq7000 = { path = "../zynq7000" }
|
||||
zynq7000-hal = { path = "../zynq7000-hal" }
|
||||
@@ -17,7 +17,7 @@ zynq7000-boot-image = { path = "../../host/zynq7000-boot-image" }
|
||||
zedboard-bsp = { path = "../zedboard-bsp" }
|
||||
embedded-io = "0.7"
|
||||
embedded-hal = "1"
|
||||
fugit = "0.3"
|
||||
fugit = "0.4"
|
||||
log = "0.4"
|
||||
arbitrary-int = "2"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
MEMORY
|
||||
{
|
||||
/* The Zynq7000 has 192 kB of OCM memory which can be used for the FSBL */
|
||||
CODE(rx) : ORIGIN = 0x00000000, LENGTH = 192K
|
||||
/* The Zynq7000 has 256 kB of OCM memory of which 196 kB can be used for the FSBL */
|
||||
CODE(rx) : ORIGIN = 0x00000000, LENGTH = 196K
|
||||
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
|
||||
@@ -11,6 +11,8 @@ MEMORY
|
||||
|
||||
REGION_ALIAS("VECTORS", CODE);
|
||||
REGION_ALIAS("DATA", CODE);
|
||||
/* Use the upper OCM as the stack */
|
||||
REGION_ALIAS("STACKS", OCM_UPPER);
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
@@ -23,3 +25,9 @@ SECTIONS
|
||||
_ebss_uncached = .;
|
||||
} > UNCACHED
|
||||
}
|
||||
|
||||
PROVIDE(_und_stack_size = 2K);
|
||||
PROVIDE(_svc_stack_size = 2K);
|
||||
PROVIDE(_abt_stack_size = 2K);
|
||||
PROVIDE(_hyp_stack_size = 2K);
|
||||
PROVIDE(_sys_stack_size = 32K);
|
||||
|
||||
@@ -8,14 +8,16 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use arbitrary_int::u6;
|
||||
use core::panic::PanicInfo;
|
||||
use aarch32_cpu::asm::nop;
|
||||
use arbitrary_int::traits::Integer as _;
|
||||
use arbitrary_int::{u2, u6};
|
||||
use core::panic::PanicInfo;
|
||||
use embedded_io::Write as _;
|
||||
use log::{error, info};
|
||||
use zedboard_bsp::qspi_spansion::{self, QspiSpansionS25Fl256SLinearMode};
|
||||
use zynq7000_boot_image::DestinationDevice;
|
||||
use zynq7000_hal::priv_tim;
|
||||
use zynq7000_hal::clocks::ArmClocks;
|
||||
use zynq7000_hal::{generic_interrupt_handler, priv_tim};
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::{
|
||||
@@ -23,7 +25,7 @@ use zynq7000_hal::{
|
||||
pll::{PllConfig, configure_arm_pll, configure_io_pll},
|
||||
},
|
||||
ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest},
|
||||
devcfg, gic, gpio, l2_cache,
|
||||
gic, gpio, l2_cache,
|
||||
prelude::*,
|
||||
qspi::{self, QSPI_START_ADDRESS},
|
||||
time::Hertz,
|
||||
@@ -42,7 +44,7 @@ const IO_CLK: Hertz = Hertz::from_raw(1_000_000_000);
|
||||
const DDR_FREQUENCY: Hertz = Hertz::from_raw(533_333_333);
|
||||
|
||||
/// 1067 MHz.
|
||||
const DDR_CLK: Hertz = Hertz::from_raw(2 * DDR_FREQUENCY.raw());
|
||||
const DDR_CLK: Hertz = Hertz::from_raw(2 * DDR_FREQUENCY.to_raw());
|
||||
|
||||
const PERFORM_DDR_MEMTEST: bool = false;
|
||||
|
||||
@@ -74,6 +76,20 @@ fn main() -> ! {
|
||||
);
|
||||
|
||||
let mut periphs = zynq7000::Peripherals::take().unwrap();
|
||||
l2_cache::disable();
|
||||
|
||||
// Initialize the ARM clock. Safety: We only run this once.
|
||||
unsafe {
|
||||
ArmClocks::new_with_cpu_clock_init(
|
||||
ARM_CLK,
|
||||
zynq7000_hal::clocks::CpuClockRatio::SixToTwoToOne,
|
||||
u6::new(2),
|
||||
);
|
||||
// This is done by the AMD FSBL.
|
||||
zynq7000_hal::Slcr::with(|val| {
|
||||
val.gpiob().modify_ctrl(|val| val.with_vref_en(true));
|
||||
});
|
||||
}
|
||||
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let clocks = Clocks::new_from_regs(PS_CLK).unwrap();
|
||||
@@ -103,7 +119,7 @@ fn main() -> ! {
|
||||
};
|
||||
|
||||
// Set up the global interrupt controller.
|
||||
let mut gic = gic::GicConfigurator::new_with_init(periphs.gicc, periphs.gicd);
|
||||
let mut gic = gic::Configurator::new_with_init(periphs.gicc, periphs.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -162,11 +178,19 @@ fn main() -> ! {
|
||||
);
|
||||
|
||||
let qspi_io_mode = qspi.into_io_mode(false);
|
||||
let spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
|
||||
let spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(
|
||||
qspi_io_mode,
|
||||
qspi_spansion::Config {
|
||||
set_quad_bit_if_necessary: true,
|
||||
latency_config: Some(u2::ZERO),
|
||||
clear_write_protection: true,
|
||||
},
|
||||
);
|
||||
let spansion_lqspi =
|
||||
spansion_qspi.into_linear_addressed(qspi_spansion::QSPI_DEV_COMBINATION_REV_F.into());
|
||||
qspi_boot(spansion_lqspi, priv_tim);
|
||||
}
|
||||
|
||||
loop {
|
||||
aarch32_cpu::asm::nop();
|
||||
}
|
||||
@@ -256,7 +280,7 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode, _priv_tim: priv_tim::Cpu
|
||||
};
|
||||
// The DMA will read from the linear mapped QSPI directly, so it
|
||||
// has to be configured for reads using the guard!
|
||||
devcfg::configure_bitstream_non_secure(true, boot_bin_slice)
|
||||
zynq7000_hal::pl::configure_bitstream_non_secure(true, boot_bin_slice)
|
||||
.expect("unexpected unaligned address");
|
||||
log::info!("loaded bitstream successfully");
|
||||
}
|
||||
@@ -304,6 +328,10 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode, _priv_tim: priv_tim::Cpu
|
||||
}
|
||||
}
|
||||
|
||||
// The PL is in reset state after power-up. This method needs to be called in the first-stage
|
||||
// bootloader to put it out of reset.
|
||||
zynq7000_hal::pl::deassert_reset();
|
||||
|
||||
match opt_jump_addr {
|
||||
Some(jump_addr) => {
|
||||
log::info!("jumping to address {}", jump_addr);
|
||||
@@ -313,6 +341,7 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode, _priv_tim: priv_tim::Cpu
|
||||
zynq7000_hal::cache::clean_and_invalidate_data_cache();
|
||||
aarch32_cpu::register::TlbIAll::write();
|
||||
aarch32_cpu::register::BpIAll::write();
|
||||
l2_cache::disable();
|
||||
aarch32_cpu::asm::dsb();
|
||||
aarch32_cpu::asm::isb();
|
||||
|
||||
@@ -323,6 +352,15 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode, _priv_tim: priv_tim::Cpu
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
log::warn!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
fn data_abort_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/boot.bin
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
aarch32-cpu = { version = "0.1", features = ["critical-section-single-core"] }
|
||||
aarch32-cpu = { version = "0.2", features = ["critical-section-single-core"] }
|
||||
zynq7000-rt = { path = "../zynq7000-rt" }
|
||||
zynq7000 = { path = "../zynq7000" }
|
||||
zynq7000-hal = { path = "../zynq7000-hal" }
|
||||
@@ -12,5 +12,6 @@ zynq7000-boot-image = { path = "../../host/zynq7000-boot-image" }
|
||||
zedboard-bsp = { path = "../zedboard-bsp" }
|
||||
embedded-io = "0.7"
|
||||
embedded-hal = "1"
|
||||
arbitrary-int = "2"
|
||||
log = "0.4"
|
||||
libm = "0.2"
|
||||
|
||||
@@ -3,4 +3,17 @@ Zedboard QSPI flasher
|
||||
|
||||
This application flashes a boot binary generated by the AMD `bootgen` utility from DDR
|
||||
to the Zedboard QSPI. This project contains a `qspi-flasher.tcl` script which can be invoked
|
||||
with `xsct` to flash a `boot.bin` and the QSPI flasher to DDR adn then run the application.
|
||||
with `xsct` to flash a `boot.bin` and the QSPI flasher to DDR and then run the application.
|
||||
|
||||
The main `justfile` provides a convenience runner:
|
||||
|
||||
```sh
|
||||
just flash-nor-zedboard <path to my boot.bin>
|
||||
```
|
||||
|
||||
Please note that `xsct` must be callable for this to be usable which is part of a Xilinx Vitis installation.
|
||||
|
||||
If the hardware server is running on a remote target the IP address can be specified by setting the environment variable ip_address_hw_server.
|
||||
````sh
|
||||
$ export ip_address_hw_server=<ip-address>
|
||||
````
|
||||
@@ -1,9 +1,11 @@
|
||||
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
|
||||
/* Zedboard: 512 MB DDR3. Only use 62 MB for now, should be plenty for a bare-metal app.
|
||||
1 MB stack memory and 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 = 62M
|
||||
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
|
||||
STACKS : ORIGIN = 0x3F00000, LENGTH = 1M
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use arbitrary_int::{traits::Integer as _, u2};
|
||||
use core::panic::PanicInfo;
|
||||
use embedded_hal::{delay::DelayNs as _, digital::StatefulOutputPin as _};
|
||||
use embedded_io::Write as _;
|
||||
@@ -97,7 +98,14 @@ fn main() -> ! {
|
||||
|
||||
let qspi_io_mode = qspi.into_io_mode(false);
|
||||
|
||||
let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
|
||||
let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(
|
||||
qspi_io_mode,
|
||||
qspi_spansion::Config {
|
||||
set_quad_bit_if_necessary: true,
|
||||
latency_config: Some(u2::ZERO),
|
||||
clear_write_protection: true,
|
||||
},
|
||||
);
|
||||
|
||||
let mut boot_bin_slice = unsafe {
|
||||
core::slice::from_raw_parts(BOOT_BIN_BASE_ADDR as *const _, BootHeader::FIXED_SIZED_PART)
|
||||
@@ -121,7 +129,7 @@ fn main() -> ! {
|
||||
);
|
||||
|
||||
let mut current_addr = 0;
|
||||
let mut read_buf = [0u8; 256];
|
||||
let mut read_buf = [0u8; qspi_spansion::PAGE_SIZE];
|
||||
let mut next_checkpoint = 0.05;
|
||||
while current_addr < boot_bin_size {
|
||||
if current_addr % 0x10000 == 0 {
|
||||
@@ -137,10 +145,13 @@ fn main() -> ! {
|
||||
}
|
||||
}
|
||||
}
|
||||
let write_size = core::cmp::min(256, boot_bin_size - current_addr);
|
||||
let write_size = core::cmp::min(
|
||||
qspi_spansion::RECOMMENDED_PROGRAM_PAGE_SIZE,
|
||||
boot_bin_size - current_addr,
|
||||
);
|
||||
let write_slice = &boot_bin_slice[current_addr..current_addr + write_size];
|
||||
log::debug!("Programming address {:#x}", current_addr);
|
||||
match spansion_qspi.program_page(current_addr as u32, write_slice) {
|
||||
match spansion_qspi.program(current_addr as u32, write_slice) {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
@@ -152,11 +163,7 @@ fn main() -> ! {
|
||||
}
|
||||
}
|
||||
if VERIFY_PROGRAMMING {
|
||||
spansion_qspi.read_page_fast_read(
|
||||
current_addr as u32,
|
||||
&mut read_buf[0..write_size],
|
||||
true,
|
||||
);
|
||||
spansion_qspi.read_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?}",
|
||||
|
||||
@@ -8,9 +8,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
# [v0.1.0] 2025-10-09
|
||||
# [v0.1.1] 2026-03-13
|
||||
|
||||
- Try to fix docs build for docs.rs
|
||||
|
||||
# [v0.1.0] 2026-02-14
|
||||
|
||||
Initial release
|
||||
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/v0.1.0...HEAD
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-embassy-v0.1.0...HEAD
|
||||
[v0.1.1]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-embassy-v0.1.0...zynq7000-embassy-v0.1.1
|
||||
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/tag/zynq7000-embassy-v0.1.0
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "zynq7000-embassy"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
edition = "2024"
|
||||
description = "Embassy-rs support for the Zynq7000 family of SoCs"
|
||||
description = "Embassy time support for the Zynq7000 family of SoCs"
|
||||
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"
|
||||
@@ -17,3 +17,7 @@ zynq7000-hal = { path = "../zynq7000-hal", version = "0.1" }
|
||||
|
||||
embassy-time-driver = "0.2"
|
||||
embassy-time-queue-utils = "0.3"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["armv7a-none-eabihf"]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
[](https://docs.rs/zynq7000-embassy)
|
||||
[](https://github.com/us-irs/zynq7000-rs/actions/workflows/ci.yml)
|
||||
|
||||
# Embassy-rs support for the AMD Zynq7000 SoC family
|
||||
# Embassy time support for the AMD Zynq7000 SoC family
|
||||
|
||||
This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) support for the
|
||||
AMD Zynq7000 SoC family. Currently, it contains the time driver to allow using embassy-rs. It
|
||||
currently provides one driver using the global timer peripheral provided by the Zynq7000 PS for
|
||||
this purpose.
|
||||
This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) time support for
|
||||
the AMD Zynq7000 SoC family. It currently provides one driver using the global timer peripheral
|
||||
provided by the Zynq7000 PS for this purpose.
|
||||
|
||||
The documentation contains more information on how to use this crate.
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
//! # Embassy time support for the AMD Zynq7000 SoC family
|
||||
//!
|
||||
//! This project contains the [embassy-rs](https://github.com/embassy-rs/embassy) time support for
|
||||
//! the AMD Zynq7000 SoC family. It currently provides one driver using the global timer peripheral
|
||||
//! provided by the Zynq7000 PS for this purpose.
|
||||
//!
|
||||
//! The [crate::init] method must be called once for the time driver to work properly.
|
||||
#![no_std]
|
||||
use core::cell::{Cell, RefCell};
|
||||
|
||||
@@ -59,9 +66,19 @@ impl GtcTimerDriver {
|
||||
///
|
||||
/// This has to be called ONCE at system initialization.
|
||||
pub unsafe fn init(&'static self, arm_clock: &ArmClocks, mut gtc: GlobalTimerCounter) {
|
||||
fn safe_interrupt_handler() {
|
||||
// Safety: See safety notes of [zynq7000_hal::generic_interrupt_handler].
|
||||
unsafe {
|
||||
on_interrupt();
|
||||
}
|
||||
}
|
||||
zynq7000_hal::register_interrupt(
|
||||
zynq7000_hal::gic::Interrupt::Ppi(zynq7000_hal::gic::PpiInterrupt::GlobalTimer),
|
||||
safe_interrupt_handler,
|
||||
);
|
||||
CPU_3X2X_CLK.set(arm_clock.cpu_3x2x_clk()).unwrap();
|
||||
SCALE
|
||||
.set(arm_clock.cpu_3x2x_clk().raw() as u64 / TICK_HZ)
|
||||
.set(arm_clock.cpu_3x2x_clk().to_raw() as u64 / TICK_HZ)
|
||||
.unwrap();
|
||||
gtc.set_cpu_3x2x_clock(arm_clock.cpu_3x2x_clk());
|
||||
gtc.set_prescaler(0);
|
||||
|
||||
@@ -8,13 +8,40 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
## Changed
|
||||
## Fixed
|
||||
|
||||
- Increased UART type safety by providing dedicated MIO constructors for UART 0 and UART 1
|
||||
respectively.
|
||||
- Bugfix for DDR initialization: `calibrate_iob_impedance_for_ddr3` and `calibrate_iob_impedance`
|
||||
now expect a `zynq7000::slcr::ddriob::DdrControl` input argument. This register write was
|
||||
missing
|
||||
- Several bugfixes and improvements for GIC module. Some of the registers previously were
|
||||
completely overwritten instead of only modifying their own bit portions. Also allow targeting
|
||||
interrupts without clearing other CPU target.
|
||||
- Do not reset the UART on TX future creation anymore, which lead to glitches and invalid data.
|
||||
- Robustness improvements for the asynchronous UART TX module.
|
||||
- SPI1 AMBA clock control bits are now enabled and disabled properly
|
||||
|
||||
## Changed
|
||||
|
||||
- Increased reliabily of PS UART interrupt reception, which was proven to be buggy for higher baud
|
||||
rates: Force user to configure RTO value, encouraging non-zero values, and use a RX FIFO trigger
|
||||
value of FIFO depth divided by 2 by default.
|
||||
- `devcfg` moved to `pl` module
|
||||
- Added division by zero check in gtc frequency_to_ticks to avoid runtime panic
|
||||
- Increased UART type safety by providing dedicated MIO constructors for UART 0 and UART 1
|
||||
respectively.
|
||||
- `log::rb` module replaced by `log::asynch` module which uses an asynchronous embassy pipe
|
||||
for logging.
|
||||
- GIC data structures: Removed the `Gic` prefix which already is part of the module name.
|
||||
- Renamed `GicInterruptHelper` to `InterruptGuard`. It acknowledges the end of interrupts on drop.
|
||||
|
||||
## Added
|
||||
|
||||
- Method to de-assert PL reset.
|
||||
- ARM clock initialization for the `ArmClocks` structure
|
||||
- The `ArmClocks` structure now caches the CPU clock ratio
|
||||
- New generic interrupt registry and generic interrupt handler which uses the registry.
|
||||
Primary interface is the `crate::generic_interrupt_handler` function and the
|
||||
`crate::register_interrupt` function.
|
||||
|
||||
# [v0.1.1] 2025-10-10
|
||||
|
||||
|
||||
@@ -11,17 +11,15 @@ keywords = ["no-std", "hal", "amd", "zynq7000", "bare-metal"]
|
||||
categories = ["embedded", "no-std", "hardware-support"]
|
||||
|
||||
[dependencies]
|
||||
aarch32-cpu = { version = "0.1" }
|
||||
zynq7000 = { path = "../zynq7000", version = "0.1" }
|
||||
zynq7000-mmu = { path = "../zynq7000-mmu", version = "0.1" }
|
||||
|
||||
aarch32-cpu = { version = "0.3" }
|
||||
zynq7000 = { path = "../zynq7000", version = "0.4" }
|
||||
zynq7000-mmu = { path = "../zynq7000-mmu", version = "0.2" }
|
||||
static_assertions = "1.1"
|
||||
bitbybit = "1.4"
|
||||
bitbybit = "2"
|
||||
arbitrary-int = "2"
|
||||
thiserror = { version = "2", default-features = false }
|
||||
num_enum = { version = "0.7", default-features = false }
|
||||
bitflags = "2"
|
||||
ringbuf = { version = "0.4.8", default-features = false }
|
||||
embedded-hal-nb = "1"
|
||||
embedded-io = "0.7"
|
||||
embedded-hal = "1"
|
||||
@@ -29,25 +27,27 @@ embedded-hal-async = "1"
|
||||
heapless = "0.9"
|
||||
static_cell = "2"
|
||||
delegate = "0.13"
|
||||
paste = "1"
|
||||
pastey = "0.2.1"
|
||||
nb = "1"
|
||||
fugit = "0.3"
|
||||
fugit = "0.4"
|
||||
critical-section = "1"
|
||||
libm = "0.2"
|
||||
log = "0.4"
|
||||
embassy-sync = "0.7"
|
||||
embassy-sync = "0.8"
|
||||
embassy-net-driver = "0.2"
|
||||
smoltcp = { version = "0.12", default-features = false, features = ["proto-ipv4", "medium-ethernet", "socket-raw"] }
|
||||
smoltcp = { version = "0.13", default-features = false, features = ["proto-ipv4", "medium-ethernet", "socket-raw"] }
|
||||
vcell = "0.1"
|
||||
raw-slicee = "0.1"
|
||||
embedded-io-async = "0.7"
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
defmt = { version = "1", optional = true }
|
||||
embedded-sdmmc = { git = "https://github.com/robamu/embedded-sdmmc-rs.git", branch = "all-features" }
|
||||
bytemuck = "1.25"
|
||||
|
||||
[features]
|
||||
std = ["thiserror/std", "alloc"]
|
||||
alloc = []
|
||||
defmt = ["dep:defmt", "fugit/defmt"]
|
||||
defmt = ["dep:defmt", "fugit/defmt", "zynq7000/defmt"]
|
||||
# These devices have a lower pin count.
|
||||
7z010-7z007s-clg225 = []
|
||||
|
||||
@@ -57,5 +57,4 @@ approx = "0.5"
|
||||
[package.metadata.docs.rs]
|
||||
features = ["alloc"]
|
||||
targets = ["armv7a-none-eabihf"]
|
||||
cargo-args = ["-Z", "build-std=core,alloc"]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
# HAL for the AMD Zynq 7000 SoC family
|
||||
|
||||
This repository contains the **H**ardware **A**bstraction **L**ayer (HAL), which is an additional
|
||||
hardware abstraction on top of the [peripheral access API](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000).
|
||||
hardware abstraction on top of the [peripheral access API](../zynq7000).
|
||||
|
||||
It is the result of reading the datasheet for the device and encoding a type-safe layer over the
|
||||
raw PAC. This crate also implements traits specified by the
|
||||
[embedded-hal](https://github.com/rust-embedded/embedded-hal) project, making it compatible with
|
||||
various drivers in the embedded rust ecosystem.
|
||||
|
||||
The [top-level README](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs) and the documentation
|
||||
The [top-level README](../../README.md) and the documentation
|
||||
contain more information on how to use this crate.
|
||||
|
||||
@@ -3,11 +3,12 @@ use arbitrary_int::{prelude::*, u6};
|
||||
|
||||
pub mod pll;
|
||||
|
||||
pub use zynq7000::slcr::clocks::CpuClockRatio;
|
||||
use zynq7000::slcr::{
|
||||
ClockControlRegisters,
|
||||
clocks::{
|
||||
ClockkRatioSelect, DualCommonPeriphIoClockControl, FpgaClockControl, GigEthClockControl,
|
||||
SingleCommonPeriphIoClockControl,
|
||||
ArmClockControl, ClockRatioSelectReg, DualCommonPeriphIoClockControl, FpgaClockControl,
|
||||
GigEthClockControl, SingleCommonPeriphIoClockControl,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -17,6 +18,7 @@ use super::time::Hertz;
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct ArmClocks {
|
||||
ref_clk: Hertz,
|
||||
ratio: CpuClockRatio,
|
||||
cpu_1x_clk: Hertz,
|
||||
cpu_2x_clk: Hertz,
|
||||
cpu_3x2x_clk: Hertz,
|
||||
@@ -24,23 +26,82 @@ pub struct ArmClocks {
|
||||
}
|
||||
|
||||
impl ArmClocks {
|
||||
/// Configure the ARM clocks based on the ARM PLL input clock.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This changes the CPU clock frequency. You must pass the ARM PLL clock frequency and
|
||||
/// you must ensure that this is only run once during system initialization, for example
|
||||
/// in the first-stage bootloader.
|
||||
pub unsafe fn new_with_cpu_clock_init(
|
||||
arm_pll_clk: Hertz,
|
||||
clock_ratio: CpuClockRatio,
|
||||
divisor: u6,
|
||||
) -> Self {
|
||||
unsafe {
|
||||
crate::slcr::Slcr::with(|slcr| {
|
||||
slcr.clk_ctrl().write_clk_ratio_select(
|
||||
ClockRatioSelectReg::builder().with_sel(clock_ratio).build(),
|
||||
);
|
||||
slcr.clk_ctrl().write_arm_clk_ctrl(
|
||||
ArmClockControl::builder()
|
||||
.with_cpu_peri_clk_act(true)
|
||||
.with_cpu_1x_clk_act(true)
|
||||
.with_cpu_2x_clk_act(true)
|
||||
.with_cpu_3or2x_clk_act(true)
|
||||
.with_cpu_6or4x_clk_act(true)
|
||||
.with_divisor(divisor)
|
||||
.with_srcsel(zynq7000::slcr::clocks::SrcSelArm::ArmPll)
|
||||
.build(),
|
||||
);
|
||||
});
|
||||
}
|
||||
let cpu_6x4x_clk = arm_pll_clk / divisor.as_u32();
|
||||
let cpu_1x_clk = match clock_ratio {
|
||||
CpuClockRatio::FourToTwoToOne => cpu_6x4x_clk / 4,
|
||||
CpuClockRatio::SixToTwoToOne => cpu_6x4x_clk / 6,
|
||||
};
|
||||
|
||||
Self {
|
||||
ref_clk: arm_pll_clk,
|
||||
ratio: clock_ratio,
|
||||
cpu_1x_clk,
|
||||
cpu_2x_clk: cpu_1x_clk * 2,
|
||||
cpu_3x2x_clk: match clock_ratio {
|
||||
CpuClockRatio::SixToTwoToOne => cpu_1x_clk * 3,
|
||||
CpuClockRatio::FourToTwoToOne => cpu_1x_clk * 2,
|
||||
},
|
||||
cpu_6x4x_clk,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn ratio(&self) -> CpuClockRatio {
|
||||
self.ratio
|
||||
}
|
||||
|
||||
/// Reference clock provided by ARM PLL which is used to calculate all other clock frequencies.
|
||||
#[inline]
|
||||
pub const fn ref_clk(&self) -> Hertz {
|
||||
self.ref_clk
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn cpu_1x_clk(&self) -> Hertz {
|
||||
self.cpu_1x_clk
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn cpu_2x_clk(&self) -> Hertz {
|
||||
self.cpu_2x_clk
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn cpu_3x2x_clk(&self) -> Hertz {
|
||||
self.cpu_3x2x_clk
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn cpu_6x4x_clk(&self) -> Hertz {
|
||||
self.cpu_6x4x_clk
|
||||
}
|
||||
@@ -197,6 +258,7 @@ impl IoClocks {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Display impl for clock config.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Clocks {
|
||||
@@ -277,25 +339,27 @@ impl Clocks {
|
||||
zynq7000::slcr::clocks::SrcSelArm::DdrPll => ddr_pll_out,
|
||||
zynq7000::slcr::clocks::SrcSelArm::IoPll => io_pll_out,
|
||||
};
|
||||
let clk_sel = clk_regs.read_clk_621_true();
|
||||
let clk_sel = clk_regs.read_clk_ratio_select();
|
||||
if arm_clk_ctrl.divisor().as_u32() == 0 {
|
||||
return Err(ClockReadError::DivisorZero(DivisorZero(ClockModuleId::Arm)));
|
||||
}
|
||||
let arm_clk_divided = arm_base_clk / arm_clk_ctrl.divisor().as_u32();
|
||||
let arm_clks = match clk_sel.sel() {
|
||||
ClockkRatioSelect::FourToTwoToOne => ArmClocks {
|
||||
CpuClockRatio::FourToTwoToOne => ArmClocks {
|
||||
ref_clk: arm_pll_out,
|
||||
cpu_1x_clk: arm_clk_divided / 4,
|
||||
cpu_2x_clk: arm_clk_divided / 2,
|
||||
cpu_3x2x_clk: arm_clk_divided / 2,
|
||||
cpu_6x4x_clk: arm_clk_divided,
|
||||
ratio: clk_sel.sel(),
|
||||
},
|
||||
ClockkRatioSelect::SixToTwoToOne => ArmClocks {
|
||||
CpuClockRatio::SixToTwoToOne => ArmClocks {
|
||||
ref_clk: arm_pll_out,
|
||||
cpu_1x_clk: arm_clk_divided / 6,
|
||||
cpu_2x_clk: arm_clk_divided / 3,
|
||||
cpu_3x2x_clk: arm_clk_divided / 2,
|
||||
cpu_6x4x_clk: arm_clk_divided,
|
||||
ratio: clk_sel.sel(),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ impl PllConfig {
|
||||
ps_clk: Hertz,
|
||||
target_clk: Hertz,
|
||||
) -> Result<Self, PllConfigCtorError> {
|
||||
if ps_clk.raw() == 0 {
|
||||
if ps_clk.to_raw() == 0 {
|
||||
return Err(PllConfigCtorError::InvalidInput);
|
||||
}
|
||||
let mul = target_clk / ps_clk;
|
||||
@@ -196,7 +196,7 @@ impl PllConfig {
|
||||
|
||||
/// 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) {
|
||||
if ARM_PLL_INIT.swap(true, core::sync::atomic::Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
// Safety: This will only run at most once because of the atomic boolean check.
|
||||
@@ -205,20 +205,20 @@ pub fn configure_arm_pll(boot_mode: BootMode, pll_config: PllConfig) {
|
||||
|
||||
/// 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) {
|
||||
if IO_PLL_INIT.swap(true, core::sync::atomic::Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
// Safety: This will only run at most once because of the atomic boolean check.
|
||||
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
|
||||
unsafe { configure_io_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) {
|
||||
if DDR_PLL_INIT.swap(true, core::sync::atomic::Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
// Safety: This will only run at most once because of the atomic boolean check.
|
||||
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
|
||||
unsafe { configure_ddr_pll_unchecked(boot_mode, pll_config) };
|
||||
}
|
||||
|
||||
/// This function configures the ARM PLL based on the provided [PllConfig].
|
||||
|
||||
@@ -28,7 +28,7 @@ pub fn calculate_dci_divisors(ddr_clks: &DdrClocks) -> DciClkConfig {
|
||||
|
||||
/// 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 target_div = ddr_clk.to_raw().div_ceil(DCI_MAX_FREQ.to_raw());
|
||||
let mut config = DciClkConfig {
|
||||
div0: u6::new(u6::MAX.value()),
|
||||
div1: u6::new(u6::MAX.value()),
|
||||
@@ -86,20 +86,42 @@ pub unsafe fn configure_dci(ddr_clk: &DdrClocks) {
|
||||
///
|
||||
/// 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) {
|
||||
pub unsafe fn calibrate_iob_impedance_for_ddr3(
|
||||
ddr_control: zynq7000::slcr::ddriob::DdrControl,
|
||||
dci_clk_cfg: DciClkConfig,
|
||||
poll_for_done: bool,
|
||||
) {
|
||||
unsafe {
|
||||
calibrate_iob_impedance(
|
||||
ddr_control,
|
||||
dci_clk_cfg,
|
||||
u3::new(0),
|
||||
u2::new(0),
|
||||
u3::new(0b001),
|
||||
u3::new(0),
|
||||
u2::new(0),
|
||||
CalibrationParams::new_ddr3(),
|
||||
poll_for_done,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// DDR IOB impedance calibration parameters.
|
||||
pub struct CalibrationParams {
|
||||
pub pref_opt2: u3,
|
||||
pub pref_opt1: u2,
|
||||
pub nref_opt4: u3,
|
||||
pub nref_opt2: u3,
|
||||
pub nref_opt1: u2,
|
||||
}
|
||||
|
||||
impl CalibrationParams {
|
||||
pub const fn new_ddr3() -> Self {
|
||||
Self {
|
||||
pref_opt2: u3::new(0),
|
||||
pref_opt1: u2::new(0),
|
||||
nref_opt4: u3::new(0b001),
|
||||
nref_opt2: u3::new(0),
|
||||
nref_opt1: u2::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@@ -115,12 +137,9 @@ pub unsafe fn calibrate_iob_impedance_for_ddr3(dci_clk_cfg: DciClkConfig, poll_f
|
||||
/// This function writes to the DDR IOB related registers. It should only be called once during
|
||||
/// DDR initialization.
|
||||
pub unsafe fn calibrate_iob_impedance(
|
||||
ddr_control: zynq7000::slcr::ddriob::DdrControl,
|
||||
dci_clk_cfg: DciClkConfig,
|
||||
pref_opt2: u3,
|
||||
pref_opt1: u2,
|
||||
nref_opt4: u3,
|
||||
nref_opt2: u3,
|
||||
nref_opt1: u2,
|
||||
calibration_params: CalibrationParams,
|
||||
poll_for_done: bool,
|
||||
) {
|
||||
// Safety: Only writes to DDR IOB related registers.
|
||||
@@ -134,31 +153,23 @@ pub unsafe fn calibrate_iob_impedance(
|
||||
.build(),
|
||||
);
|
||||
let mut ddriob = slcr.ddriob();
|
||||
ddriob.modify_dci_ctrl(|mut val| {
|
||||
val.set_reset(true);
|
||||
ddriob.write_ddr_control(ddr_control);
|
||||
ddriob.modify_dci_control(|val| val.with_reset(true));
|
||||
ddriob.modify_dci_control(|val| val.with_reset(false));
|
||||
ddriob.modify_dci_control(|val| val.with_reset(true));
|
||||
ddriob.modify_dci_control(|mut val| {
|
||||
val.set_pref_opt2(calibration_params.pref_opt2);
|
||||
val.set_pref_opt1(calibration_params.pref_opt1);
|
||||
val.set_nref_opt4(calibration_params.nref_opt4);
|
||||
val.set_nref_opt2(calibration_params.nref_opt2);
|
||||
val.set_nref_opt1(calibration_params.nref_opt1);
|
||||
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| {
|
||||
ddriob.modify_dci_control(|mut val| {
|
||||
val.set_update_control(false);
|
||||
val
|
||||
});
|
||||
ddriob.modify_dci_ctrl(|mut val| {
|
||||
ddriob.modify_dci_control(|mut val| {
|
||||
val.set_enable(true);
|
||||
val
|
||||
});
|
||||
@@ -170,6 +181,7 @@ pub unsafe fn calibrate_iob_impedance(
|
||||
|
||||
/// Static configuration for DDR IOBs.
|
||||
pub struct DdriobConfigSet {
|
||||
pub ddr_control: zynq7000::slcr::ddriob::DdrControl,
|
||||
pub addr0: DdriobConfig,
|
||||
pub addr1: DdriobConfig,
|
||||
pub data0: DdriobConfig,
|
||||
|
||||
@@ -76,7 +76,7 @@ pub fn configure_ddr_for_ddr3(
|
||||
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::calibrate_iob_impedance_for_ddr3(ddriob_cfg.ddr_control, 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
|
||||
@@ -129,7 +129,7 @@ pub mod memtest {
|
||||
/// 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) }
|
||||
unsafe { walking_value_test(false, base_addr, words) }
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
|
||||
@@ -8,11 +8,6 @@ use crate::{clocks::IoClocks, enable_amba_peripheral_clock, slcr::Slcr, time::He
|
||||
|
||||
use super::{EthernetId, PsEthernet as _};
|
||||
|
||||
pub struct EthernetLowLevel {
|
||||
id: EthernetId,
|
||||
pub regs: zynq7000::eth::MmioRegisters<'static>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Speed {
|
||||
Mbps10,
|
||||
@@ -52,7 +47,10 @@ impl ClockDivisors {
|
||||
|
||||
/// Calls [Self::calculate_for_rgmii], assuming that the IO clock is the reference clock,
|
||||
/// which is the default clock for the Ethernet module.
|
||||
pub fn calculate_for_rgmii_and_io_clock(io_clks: IoClocks, target_speed: Speed) -> (Self, u32) {
|
||||
pub fn calculate_for_rgmii_and_io_clock(
|
||||
io_clks: &IoClocks,
|
||||
target_speed: Speed,
|
||||
) -> (Self, u32) {
|
||||
Self::calculate_for_rgmii(io_clks.ref_clk(), target_speed)
|
||||
}
|
||||
|
||||
@@ -71,8 +69,8 @@ impl ClockDivisors {
|
||||
let mut best_div_1 = u6::new(0);
|
||||
for div_1 in 1..=u6::MAX.as_usize() {
|
||||
for div_0 in 1..=u6::MAX.as_usize() {
|
||||
let clk_rate = ref_clk.raw() / div_0 as u32 / div_1 as u32;
|
||||
let diff = (target_speed.raw() as i64 - clk_rate as i64).unsigned_abs() as u32;
|
||||
let clk_rate = ref_clk.to_raw() / div_0 as u32 / div_1 as u32;
|
||||
let diff = (target_speed.to_raw() as i64 - clk_rate as i64).unsigned_abs() as u32;
|
||||
if diff < smallest_diff {
|
||||
smallest_diff = diff;
|
||||
best_div_0 = u6::new(div_0 as u8);
|
||||
@@ -174,8 +172,17 @@ impl ClockDivSet {
|
||||
/// Ethernet low-level interface.
|
||||
///
|
||||
/// Basic building block for higher-level abstraction.
|
||||
pub struct EthernetLowLevel {
|
||||
id: EthernetId,
|
||||
/// Register block. Direct public access is allowed to allow low-level operations.
|
||||
pub regs: zynq7000::eth::MmioRegisters<'static>,
|
||||
}
|
||||
|
||||
impl EthernetLowLevel {
|
||||
/// Creates a new instance of the Ethernet low-level interface.
|
||||
///
|
||||
/// Returns [None] if the given registers block base address does not correspond to a valid
|
||||
/// Ethernet peripheral.
|
||||
#[inline]
|
||||
pub fn new(regs: zynq7000::eth::MmioRegisters<'static>) -> Option<Self> {
|
||||
regs.id()?;
|
||||
@@ -204,33 +211,7 @@ impl EthernetLowLevel {
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, cycles: usize) {
|
||||
let assert_reset = match self.id {
|
||||
EthernetId::Eth0 => EthernetReset::builder()
|
||||
.with_gem1_ref_rst(false)
|
||||
.with_gem0_ref_rst(true)
|
||||
.with_gem1_rx_rst(false)
|
||||
.with_gem0_rx_rst(true)
|
||||
.with_gem1_cpu1x_rst(false)
|
||||
.with_gem0_cpu1x_rst(true)
|
||||
.build(),
|
||||
EthernetId::Eth1 => EthernetReset::builder()
|
||||
.with_gem1_ref_rst(true)
|
||||
.with_gem0_ref_rst(false)
|
||||
.with_gem1_rx_rst(true)
|
||||
.with_gem0_rx_rst(false)
|
||||
.with_gem1_cpu1x_rst(true)
|
||||
.with_gem0_cpu1x_rst(false)
|
||||
.build(),
|
||||
};
|
||||
unsafe {
|
||||
Slcr::with(|regs| {
|
||||
regs.reset_ctrl().write_eth(assert_reset);
|
||||
for _ in 0..cycles {
|
||||
aarch32_cpu::asm::nop();
|
||||
}
|
||||
regs.reset_ctrl().write_eth(EthernetReset::DEFAULT);
|
||||
});
|
||||
}
|
||||
reset(self.id, cycles);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -383,3 +364,34 @@ impl EthernetLowLevel {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets the Ethernet peripheral with the given ID.
|
||||
pub fn reset(id: EthernetId, cycles: usize) {
|
||||
let assert_reset = match id {
|
||||
EthernetId::Eth0 => EthernetReset::builder()
|
||||
.with_gem1_ref_rst(false)
|
||||
.with_gem0_ref_rst(true)
|
||||
.with_gem1_rx_rst(false)
|
||||
.with_gem0_rx_rst(true)
|
||||
.with_gem1_cpu1x_rst(false)
|
||||
.with_gem0_cpu1x_rst(true)
|
||||
.build(),
|
||||
EthernetId::Eth1 => EthernetReset::builder()
|
||||
.with_gem1_ref_rst(true)
|
||||
.with_gem0_ref_rst(false)
|
||||
.with_gem1_rx_rst(true)
|
||||
.with_gem0_rx_rst(false)
|
||||
.with_gem1_cpu1x_rst(true)
|
||||
.with_gem0_cpu1x_rst(false)
|
||||
.build(),
|
||||
};
|
||||
unsafe {
|
||||
Slcr::with(|regs| {
|
||||
regs.reset_ctrl().write_eth(assert_reset);
|
||||
for _ in 0..cycles {
|
||||
aarch32_cpu::asm::nop();
|
||||
}
|
||||
regs.reset_ctrl().write_eth(EthernetReset::DEFAULT);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,10 @@ impl Eth1RxData3Pin for Pin<Mio38> {}
|
||||
/// Calculate the CPU 1x clock divisor required to achieve a clock speed which is below
|
||||
/// 2.5 MHz, as specified by the 802.3 standard.
|
||||
pub fn calculate_mdc_clk_div(arm_clks: &ArmClocks) -> Option<MdcClockDivisor> {
|
||||
let div = arm_clks.cpu_1x_clk().raw().div_ceil(MAX_MDC_SPEED.raw());
|
||||
let div = arm_clks
|
||||
.cpu_1x_clk()
|
||||
.to_raw()
|
||||
.div_ceil(MAX_MDC_SPEED.to_raw());
|
||||
match div {
|
||||
0..8 => Some(MdcClockDivisor::Div8),
|
||||
8..16 => Some(MdcClockDivisor::Div16),
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
//! # Global Interrupt Controller (GIC) module
|
||||
//!
|
||||
//! The primary interface to configure and allow handling the interrupts are the
|
||||
//! [GicConfigurator] and the [GicInterruptHelper] structures.
|
||||
//! [Configurator] and the [InterruptGuard] structures.
|
||||
//!
|
||||
//! The HAL provides a more convenient interface through the [crate::register_interrupt] and
|
||||
//! [crate::generic_interrupt_handler] functions.
|
||||
#![deny(missing_docs)]
|
||||
use arbitrary_int::prelude::*;
|
||||
|
||||
@@ -85,8 +88,9 @@ bitflags::bitflags! {
|
||||
}
|
||||
|
||||
/// Private Peripheral Interrupt (PPI) which are private to the CPU.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, num_enum::TryFromPrimitive)]
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, num_enum::TryFromPrimitive)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum PpiInterrupt {
|
||||
/// Global timer.
|
||||
@@ -102,8 +106,9 @@ pub enum PpiInterrupt {
|
||||
}
|
||||
|
||||
/// Shared Peripheral Interrupt IDs.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, num_enum::TryFromPrimitive)]
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, num_enum::TryFromPrimitive)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum SpiInterrupt {
|
||||
/// CPU 0.
|
||||
@@ -231,8 +236,9 @@ pub enum SpiInterrupt {
|
||||
}
|
||||
|
||||
/// Interrupt ID wrapper.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Interrupt {
|
||||
/// Software-generated interrupt (SGI).
|
||||
Sgi(usize),
|
||||
@@ -247,8 +253,9 @@ pub enum Interrupt {
|
||||
}
|
||||
|
||||
/// Interrupt information structure.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct InterruptInfo {
|
||||
raw_reg: InterruptSignalRegister,
|
||||
interrupt: Interrupt,
|
||||
@@ -334,16 +341,16 @@ pub struct InvalidSgiInterruptId(pub usize);
|
||||
/// with [Self::enable] which assumes a certain configuration.
|
||||
/// 5. Enable interrupts for the Cortex-A core by calling [Self::enable_interrupts].
|
||||
///
|
||||
/// For the handling of the interrupts, you can use the [GicInterruptHelper] which assumes a
|
||||
/// For the handling of the interrupts, you can use the [InterruptGuard] which assumes a
|
||||
/// properly configured GIC.
|
||||
pub struct GicConfigurator {
|
||||
pub struct Configurator {
|
||||
/// GIC CPU interface registers.
|
||||
pub gicc: MmioCpuInterfaceRegisters<'static>,
|
||||
/// GIC Distributor interface registers.
|
||||
pub gicd: MmioDistributorRegisters<'static>,
|
||||
}
|
||||
|
||||
impl GicConfigurator {
|
||||
impl Configurator {
|
||||
/// Create a new GIC controller instance and calls [Self::initialize] to perform
|
||||
/// strongly recommended initialization routines for the GIC.
|
||||
#[inline]
|
||||
@@ -351,7 +358,7 @@ impl GicConfigurator {
|
||||
gicc: MmioCpuInterfaceRegisters<'static>,
|
||||
gicd: MmioDistributorRegisters<'static>,
|
||||
) -> Self {
|
||||
let mut gic = GicConfigurator { gicc, gicd };
|
||||
let mut gic = Configurator { gicc, gicd };
|
||||
gic.initialize();
|
||||
gic
|
||||
}
|
||||
@@ -365,7 +372,7 @@ impl GicConfigurator {
|
||||
/// used inside the interrupt handler.
|
||||
#[inline]
|
||||
pub unsafe fn steal() -> Self {
|
||||
GicConfigurator {
|
||||
Configurator {
|
||||
gicc: unsafe { CpuInterfaceRegisters::new_mmio_fixed() },
|
||||
gicd: unsafe { DistributorRegisters::new_mmio_fixed() },
|
||||
}
|
||||
@@ -648,21 +655,31 @@ impl GicConfigurator {
|
||||
}
|
||||
|
||||
/// Helper structure which should only be used inside the interrupt handler once the GIC has
|
||||
/// been configured with the [GicConfigurator].
|
||||
pub struct GicInterruptHelper(MmioCpuInterfaceRegisters<'static>);
|
||||
/// been configured with the [Configurator].
|
||||
pub struct InterruptGuard {
|
||||
regs: MmioCpuInterfaceRegisters<'static>,
|
||||
interrupt_info: InterruptInfo,
|
||||
acknowledged: bool,
|
||||
}
|
||||
|
||||
impl GicInterruptHelper {
|
||||
impl InterruptGuard {
|
||||
/// Create the interrupt helper with the fixed GICC MMIO instance.
|
||||
pub const fn new() -> Self {
|
||||
GicInterruptHelper(unsafe { CpuInterfaceRegisters::new_mmio_fixed() })
|
||||
pub fn new() -> Self {
|
||||
let mut regs = unsafe { CpuInterfaceRegisters::new_mmio_fixed() };
|
||||
let interrupt_info = Self::acknowledge_interrupt(&mut regs);
|
||||
InterruptGuard {
|
||||
regs: unsafe { CpuInterfaceRegisters::new_mmio_fixed() },
|
||||
interrupt_info,
|
||||
acknowledged: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Acknowledges an interrupt by reading the IAR register and returning the interrupt context
|
||||
/// information structure.
|
||||
///
|
||||
/// This should be called at the start of an interrupt handler.
|
||||
pub fn acknowledge_interrupt(&mut self) -> InterruptInfo {
|
||||
let iar = self.0.read_iar();
|
||||
fn acknowledge_interrupt(regs: &mut MmioCpuInterfaceRegisters<'static>) -> InterruptInfo {
|
||||
let iar = regs.read_iar();
|
||||
let int_id = iar.ack_int_id().as_u32();
|
||||
let interrupt = match int_id {
|
||||
0..=15 => Interrupt::Sgi(int_id as usize),
|
||||
@@ -678,16 +695,33 @@ impl GicInterruptHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the interrupt information structure which was read from the IAR register.
|
||||
///
|
||||
/// This is used to determine the cause of the interrupt.
|
||||
#[inline]
|
||||
pub fn interrupt_info(&self) -> InterruptInfo {
|
||||
self.interrupt_info
|
||||
}
|
||||
|
||||
/// Acknowledges the end of an interrupt by writing the EOIR register of the GICC.
|
||||
///
|
||||
/// This should be called at the end of an interrupt handler.
|
||||
pub fn end_of_interrupt(&mut self, irq_info: InterruptInfo) {
|
||||
self.0.write_eoir(irq_info.raw_reg())
|
||||
self.acknowledged = true;
|
||||
self.regs.write_eoir(irq_info.raw_reg())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GicInterruptHelper {
|
||||
impl Default for InterruptGuard {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for InterruptGuard {
|
||||
fn drop(&mut self) {
|
||||
if !self.acknowledged {
|
||||
self.regs.write_eoir(self.interrupt_info.raw_reg());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ pub trait PinId {
|
||||
macro_rules! pin_id {
|
||||
($Id:ident, $num:literal) => {
|
||||
// Need paste macro to use ident in doc attribute
|
||||
paste::paste! {
|
||||
pastey::paste! {
|
||||
#[doc = "Pin ID representing pin " $Id]
|
||||
#[derive(Debug)]
|
||||
pub enum $Id {}
|
||||
|
||||
@@ -18,7 +18,11 @@ unsafe impl Send for GlobalTimerCounter {}
|
||||
|
||||
/// Convert a frequency to GTC ticks given a clock frequency.
|
||||
pub const fn frequency_to_ticks(clock: Hertz, frequency: Hertz) -> u32 {
|
||||
clock.raw().div_ceil(frequency.raw())
|
||||
if frequency.to_raw() != 0 {
|
||||
clock.to_raw().div_ceil(frequency.to_raw())
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalTimerCounter {
|
||||
@@ -71,13 +75,13 @@ impl GlobalTimerCounter {
|
||||
/// Set the comparator which can be used to trigger an interrupt in the future.
|
||||
#[inline]
|
||||
pub fn set_comparator(&mut self, comparator: u64) {
|
||||
self.regs.modify_ctrl(|mut ctrl| {
|
||||
self.regs.modify_control(|mut ctrl| {
|
||||
ctrl.set_comparator_enable(false);
|
||||
ctrl
|
||||
});
|
||||
self.regs.write_comparator_upper((comparator >> 32) as u32);
|
||||
self.regs.write_comparator_lower(comparator as u32);
|
||||
self.regs.modify_ctrl(|mut ctrl| {
|
||||
self.regs.modify_control(|mut ctrl| {
|
||||
ctrl.set_comparator_enable(true);
|
||||
ctrl
|
||||
});
|
||||
@@ -108,7 +112,7 @@ impl GlobalTimerCounter {
|
||||
/// Enable the GTC.
|
||||
#[inline]
|
||||
pub fn enable(&mut self) {
|
||||
self.regs.modify_ctrl(|mut ctrl| {
|
||||
self.regs.modify_control(|mut ctrl| {
|
||||
ctrl.set_enable(true);
|
||||
ctrl
|
||||
});
|
||||
@@ -117,7 +121,7 @@ impl GlobalTimerCounter {
|
||||
/// Enable auto-increment.
|
||||
#[inline]
|
||||
pub fn enable_auto_increment(&mut self) {
|
||||
self.regs.modify_ctrl(|mut ctrl| {
|
||||
self.regs.modify_control(|mut ctrl| {
|
||||
ctrl.set_auto_increment(true);
|
||||
ctrl
|
||||
});
|
||||
@@ -126,7 +130,7 @@ impl GlobalTimerCounter {
|
||||
/// Set a pre-scaler.
|
||||
#[inline]
|
||||
pub fn set_prescaler(&mut self, prescaler: u8) {
|
||||
self.regs.modify_ctrl(|mut ctrl| {
|
||||
self.regs.modify_control(|mut ctrl| {
|
||||
ctrl.set_prescaler(prescaler);
|
||||
ctrl
|
||||
});
|
||||
@@ -135,7 +139,7 @@ impl GlobalTimerCounter {
|
||||
/// Disable the GTC.
|
||||
#[inline]
|
||||
pub fn disable(&mut self) {
|
||||
self.regs.modify_ctrl(|mut ctrl| {
|
||||
self.regs.modify_control(|mut ctrl| {
|
||||
ctrl.set_enable(false);
|
||||
ctrl
|
||||
});
|
||||
@@ -144,7 +148,7 @@ impl GlobalTimerCounter {
|
||||
/// Enable the comparator interrupt.
|
||||
#[inline]
|
||||
pub fn enable_interrupt(&mut self) {
|
||||
self.regs.modify_ctrl(|mut ctrl| {
|
||||
self.regs.modify_control(|mut ctrl| {
|
||||
ctrl.set_irq_enable(true);
|
||||
ctrl
|
||||
});
|
||||
@@ -153,7 +157,7 @@ impl GlobalTimerCounter {
|
||||
/// Disable the comparator interrupt.
|
||||
#[inline]
|
||||
pub fn disable_interrupt(&mut self) {
|
||||
self.regs.modify_ctrl(|mut ctrl| {
|
||||
self.regs.modify_control(|mut ctrl| {
|
||||
ctrl.set_irq_enable(false);
|
||||
ctrl
|
||||
});
|
||||
@@ -167,7 +171,7 @@ impl embedded_hal::delay::DelayNs for GlobalTimerCounter {
|
||||
return;
|
||||
}
|
||||
let end_of_delay = self.read_timer()
|
||||
+ (((ns as u64) * self.cpu_3x2x_clock.unwrap().raw() as u64) / 1_000_000_000);
|
||||
+ (((ns as u64) * self.cpu_3x2x_clock.unwrap().to_raw() as u64) / 1_000_000_000);
|
||||
while self.read_timer() < end_of_delay {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +266,8 @@ pub fn calculate_divisors(
|
||||
for divisor_a in 1..=4 {
|
||||
for divisor_b in 1..=64 {
|
||||
let i2c_clock = cpu_1x_clk / (22 * divisor_a * divisor_b);
|
||||
let deviation = (target_speed.raw() as i32 - i2c_clock.raw() as i32).unsigned_abs();
|
||||
let deviation =
|
||||
(target_speed.to_raw() as i32 - i2c_clock.to_raw() as i32).unsigned_abs();
|
||||
if deviation < smallest_deviation {
|
||||
smallest_deviation = deviation;
|
||||
best_div_a = divisor_a;
|
||||
@@ -352,8 +353,7 @@ impl I2c {
|
||||
I2cId::I2c1 => crate::PeriphSelect::I2c1,
|
||||
};
|
||||
enable_amba_peripheral_clock(periph_sel);
|
||||
//reset(id);
|
||||
regs.write_cr(
|
||||
regs.write_control(
|
||||
Control::builder()
|
||||
.with_div_a(u2::new(clk_cfg.div_a()))
|
||||
.with_div_b(u6::new(clk_cfg.div_b()))
|
||||
@@ -378,7 +378,7 @@ impl I2c {
|
||||
|
||||
#[inline]
|
||||
pub fn set_hold_bit(&mut self) {
|
||||
self.regs.modify_cr(|mut cr| {
|
||||
self.regs.modify_control(|mut cr| {
|
||||
cr.set_hold_bus(true);
|
||||
cr
|
||||
});
|
||||
@@ -386,7 +386,7 @@ impl I2c {
|
||||
|
||||
#[inline]
|
||||
pub fn clear_hold_bit(&mut self) {
|
||||
self.regs.modify_cr(|mut cr| {
|
||||
self.regs.modify_control(|mut cr| {
|
||||
cr.set_hold_bus(false);
|
||||
cr
|
||||
});
|
||||
@@ -398,7 +398,7 @@ impl I2c {
|
||||
data: &[u8],
|
||||
generate_stop: bool,
|
||||
) -> Result<(), I2cTxError> {
|
||||
self.regs.modify_cr(|mut cr| {
|
||||
self.regs.modify_control(|mut cr| {
|
||||
cr.set_acken(true);
|
||||
cr.set_mode(zynq7000::i2c::Mode::Master);
|
||||
cr.set_clear_fifo(true);
|
||||
@@ -412,7 +412,7 @@ impl I2c {
|
||||
let mut addr_set = false;
|
||||
let mut written = 0;
|
||||
// Clear the interrupt status register before using it to monitor the transfer.
|
||||
self.regs.modify_isr(|isr| isr);
|
||||
self.regs.modify_interrupt_status(|isr| isr);
|
||||
loop {
|
||||
let bytes_to_write = core::cmp::min(
|
||||
FIFO_DEPTH - self.regs.read_transfer_size().size() as usize,
|
||||
@@ -429,13 +429,13 @@ impl I2c {
|
||||
self.start_transfer(addr);
|
||||
addr_set = true;
|
||||
}
|
||||
let mut status = self.regs.read_sr();
|
||||
let mut status = self.regs.read_status();
|
||||
// While the hardware is busy sending out data, we poll for errors.
|
||||
while status.tx_busy() {
|
||||
let isr = self.regs.read_isr();
|
||||
let isr = self.regs.read_interrupt_status();
|
||||
self.check_and_handle_tx_errors(isr, first_write_cycle, bytes_to_write)?;
|
||||
// Re-read for next check.
|
||||
status = self.regs.read_sr();
|
||||
status = self.regs.read_status();
|
||||
}
|
||||
first_write_cycle = false;
|
||||
// Just need to poll to completion now.
|
||||
@@ -444,8 +444,8 @@ impl I2c {
|
||||
}
|
||||
}
|
||||
// Poll to completion.
|
||||
while !self.regs.read_isr().complete() {
|
||||
let isr = self.regs.read_isr();
|
||||
while !self.regs.read_interrupt_status().complete() {
|
||||
let isr = self.regs.read_interrupt_status();
|
||||
self.check_and_handle_tx_errors(isr, first_write_cycle, data.len())?;
|
||||
}
|
||||
if generate_stop {
|
||||
@@ -489,7 +489,7 @@ impl I2c {
|
||||
}
|
||||
|
||||
pub fn clean_up_after_transfer_or_on_error(&mut self) {
|
||||
self.regs.modify_cr(|mut cr| {
|
||||
self.regs.modify_control(|mut cr| {
|
||||
cr.set_acken(false);
|
||||
cr.set_clear_fifo(true);
|
||||
cr
|
||||
@@ -497,7 +497,7 @@ impl I2c {
|
||||
}
|
||||
|
||||
pub fn read_transfer_blocking(&mut self, addr: u8, data: &mut [u8]) -> Result<(), I2cRxError> {
|
||||
self.regs.modify_cr(|mut cr| {
|
||||
self.regs.modify_control(|mut cr| {
|
||||
cr.set_acken(true);
|
||||
cr.set_mode(zynq7000::i2c::Mode::Master);
|
||||
cr.set_clear_fifo(true);
|
||||
@@ -512,23 +512,23 @@ impl I2c {
|
||||
return Err(I2cRxError::ReadDataLenTooLarge);
|
||||
}
|
||||
// Clear the interrupt status register before using it to monitor the transfer.
|
||||
self.regs.modify_isr(|isr| isr);
|
||||
self.regs.modify_interrupt_status(|isr| isr);
|
||||
self.regs
|
||||
.write_transfer_size(TransferSize::new_with_raw_value(data.len() as u32));
|
||||
self.start_transfer(addr);
|
||||
loop {
|
||||
let mut status = self.regs.read_sr();
|
||||
let mut status = self.regs.read_status();
|
||||
loop {
|
||||
let isr = self.regs.read_isr();
|
||||
let isr = self.regs.read_interrupt_status();
|
||||
self.check_and_handle_rx_errors(read, isr)?;
|
||||
if status.rx_valid() {
|
||||
break;
|
||||
}
|
||||
// Re-read for next check.
|
||||
status = self.regs.read_sr();
|
||||
status = self.regs.read_status();
|
||||
}
|
||||
// Data to be read.
|
||||
while self.regs.read_sr().rx_valid() {
|
||||
while self.regs.read_status().rx_valid() {
|
||||
data[read] = self.regs.read_data().data();
|
||||
read += 1;
|
||||
}
|
||||
@@ -544,8 +544,8 @@ impl I2c {
|
||||
}
|
||||
|
||||
// Poll to completion.
|
||||
while !self.regs.read_isr().complete() {
|
||||
let isr = self.regs.read_isr();
|
||||
while !self.regs.read_interrupt_status().complete() {
|
||||
let isr = self.regs.read_interrupt_status();
|
||||
self.check_and_handle_rx_errors(read, isr)?
|
||||
}
|
||||
self.clear_hold_bit();
|
||||
@@ -678,8 +678,8 @@ mod tests {
|
||||
assert_eq!(clk_cfg.div_a(), 0);
|
||||
assert_eq!(clk_cfg.div_b(), 55);
|
||||
let speed = calculate_i2c_speed(111.MHz(), clk_cfg);
|
||||
assert!(speed.raw() < 100_000);
|
||||
assert!(speed.raw() > 85_000);
|
||||
assert!(speed.to_raw() < 100_000);
|
||||
assert!(speed.to_raw() > 85_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -688,8 +688,8 @@ mod tests {
|
||||
assert_eq!(clk_cfg.div_a(), 0);
|
||||
assert_eq!(clk_cfg.div_b(), 12);
|
||||
let speed = calculate_i2c_speed(111.MHz(), clk_cfg);
|
||||
assert!(speed.raw() < 400_000);
|
||||
assert!(speed.raw() > 360_000);
|
||||
assert!(speed.to_raw() < 400_000);
|
||||
assert!(speed.to_raw() > 360_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -698,8 +698,8 @@ mod tests {
|
||||
assert_eq!(clk_cfg.div_a(), 1);
|
||||
assert_eq!(clk_cfg.div_b(), 33);
|
||||
let speed = calculate_i2c_speed(133.MHz(), clk_cfg);
|
||||
assert!(speed.raw() < 100_000);
|
||||
assert!(speed.raw() > 85_000);
|
||||
assert!(speed.to_raw() < 100_000);
|
||||
assert!(speed.to_raw() > 85_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -708,7 +708,7 @@ mod tests {
|
||||
assert_eq!(clk_cfg.div_a(), 0);
|
||||
assert_eq!(clk_cfg.div_b(), 15);
|
||||
let speed = calculate_i2c_speed(133.MHz(), clk_cfg);
|
||||
assert!(speed.raw() < 400_000);
|
||||
assert!(speed.raw() > 360_000);
|
||||
assert!(speed.to_raw() < 400_000);
|
||||
assert!(speed.to_raw() > 360_000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
use crate::gic::{Interrupt, InterruptGuard};
|
||||
|
||||
pub type InterruptMap = heapless::index_map::FnvIndexMap<Interrupt, unsafe fn(), 128>;
|
||||
static INTERRUPT_MAP: critical_section::Mutex<core::cell::RefCell<InterruptMap>> =
|
||||
critical_section::Mutex::new(core::cell::RefCell::new(
|
||||
heapless::index_map::FnvIndexMap::new(),
|
||||
));
|
||||
|
||||
/// Register an interrupt handler for a specific [Interrupt].
|
||||
///
|
||||
/// It should be noted that the current implementation only allows one function for each interrupts.
|
||||
/// If the HAL provided interrupt handler does not fulfill all your requirements, you need
|
||||
/// to define your own interrupt handler and register it.
|
||||
/// For example, you might need to handle both UART RX and TX, and the HAL handler only handles TX.
|
||||
pub fn register_interrupt(interrupt: Interrupt, handler: unsafe fn()) {
|
||||
critical_section::with(|cs| {
|
||||
let mut map = INTERRUPT_MAP.borrow(cs).borrow_mut();
|
||||
map.insert(interrupt, handler).ok();
|
||||
});
|
||||
}
|
||||
|
||||
/// Generic interrupt handler which retrieves the interrupt handler for individual [Interrupt]s
|
||||
/// from a registry and calls it.
|
||||
///
|
||||
/// If no interrupt was registered or the number is [Interrupt::Invalid], returns the [Interrupt]
|
||||
/// ID as an error. In any case, the generic handler acknowledges the interrupt in the GIC.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This needs to be called ONCE in the interrupt handler function, which is any function annotated
|
||||
/// with the `irq` attribute provided by `aarch32-rt`.
|
||||
pub unsafe fn generic_interrupt_handler() -> Result<(), Interrupt> {
|
||||
let mut gic_guard = InterruptGuard::new();
|
||||
let irq_info = gic_guard.interrupt_info();
|
||||
let interrupt = irq_info.interrupt();
|
||||
if let Interrupt::Invalid(_) = interrupt {
|
||||
gic_guard.end_of_interrupt(irq_info);
|
||||
return Err(interrupt);
|
||||
}
|
||||
let opt_interrupt_handler = critical_section::with(|cs| {
|
||||
let map = INTERRUPT_MAP.borrow(cs).borrow_mut();
|
||||
map.get(&interrupt).copied()
|
||||
});
|
||||
if let Some(interrupt_handler) = opt_interrupt_handler {
|
||||
// Safety: The user made sure that this is only called once in the interrupt handler
|
||||
// function.
|
||||
unsafe {
|
||||
interrupt_handler();
|
||||
}
|
||||
}
|
||||
gic_guard.end_of_interrupt(irq_info);
|
||||
opt_interrupt_handler.ok_or(interrupt)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -81,3 +81,17 @@ pub fn init(
|
||||
}
|
||||
l2c_mmio.write_control(Control::new_enabled());
|
||||
}
|
||||
|
||||
/// Disable the L2 cache.
|
||||
#[inline]
|
||||
pub fn disable() {
|
||||
let mut l2c_mmio = unsafe { zynq7000::l2_cache::Registers::new_mmio_fixed() };
|
||||
l2c_mmio.write_control(Control::new_disabled());
|
||||
}
|
||||
|
||||
/// Function to invalidate l2 cache
|
||||
pub fn invalidate_all(l2c_mmio: &mut MmioRegisters<'static>) {
|
||||
l2c_mmio.write_clean_invalidate_by_way(0xffff);
|
||||
while l2c_mmio.read_cache_sync().busy() {}
|
||||
compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! All exaples can be found inside the [examples folder](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/examples)
|
||||
//! All examples can be found inside the [examples folder](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware/examples)
|
||||
//! and [firmware folder](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/firmware) of the project
|
||||
#![no_std]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
@@ -18,7 +18,7 @@
|
||||
#[cfg(feature = "alloc")]
|
||||
extern crate alloc;
|
||||
|
||||
use slcr::Slcr;
|
||||
pub use slcr::Slcr;
|
||||
use zynq7000::{
|
||||
SpiClockPhase, SpiClockPolarity,
|
||||
slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister},
|
||||
@@ -27,23 +27,27 @@ use zynq7000::{
|
||||
pub mod cache;
|
||||
pub mod clocks;
|
||||
pub mod ddr;
|
||||
pub mod devcfg;
|
||||
pub mod eth;
|
||||
pub mod gic;
|
||||
pub mod gpio;
|
||||
pub mod gtc;
|
||||
pub mod i2c;
|
||||
pub mod interrupt;
|
||||
pub mod l2_cache;
|
||||
pub mod log;
|
||||
pub mod pl;
|
||||
pub mod prelude;
|
||||
pub mod priv_tim;
|
||||
pub mod qspi;
|
||||
pub mod sd;
|
||||
pub mod slcr;
|
||||
pub mod spi;
|
||||
pub mod time;
|
||||
pub mod ttc;
|
||||
pub mod uart;
|
||||
|
||||
pub use gic::{Interrupt, PpiInterrupt, SpiInterrupt};
|
||||
pub use interrupt::{generic_interrupt_handler, register_interrupt};
|
||||
pub use zynq7000 as pac;
|
||||
pub use zynq7000::slcr::LevelShifterConfig;
|
||||
|
||||
@@ -79,7 +83,7 @@ pub fn init(config: Config) -> Result<zynq7000::Peripherals, InitError> {
|
||||
configure_level_shifter(config);
|
||||
}
|
||||
if let Some(interrupt_config) = config.interrupt_config {
|
||||
let mut gic = gic::GicConfigurator::new_with_init(periphs.gicc, periphs.gicd);
|
||||
let mut gic = gic::Configurator::new_with_init(periphs.gicc, periphs.gicd);
|
||||
match interrupt_config {
|
||||
InteruptConfig::AllInterruptsToCpu0 => {
|
||||
gic.enable_all_interrupts();
|
||||
@@ -207,7 +211,7 @@ pub fn enable_amba_peripheral_clock(select: PeriphSelect) {
|
||||
PeriphSelect::Can1 => val.set_can_1_1x_clk_act(true),
|
||||
PeriphSelect::Can0 => val.set_can_0_1x_clk_act(true),
|
||||
PeriphSelect::Spi1 => val.set_spi_1_1x_clk_act(true),
|
||||
PeriphSelect::Spi0 => val.set_spi_1_1x_clk_act(true),
|
||||
PeriphSelect::Spi0 => val.set_spi_0_1x_clk_act(true),
|
||||
PeriphSelect::Sdio1 => val.set_sdio_1_1x_clk_act(true),
|
||||
PeriphSelect::Sdio0 => val.set_sdio_0_1x_clk_act(true),
|
||||
PeriphSelect::Gem1 => val.set_gem_1_1x_clk_act(true),
|
||||
@@ -240,7 +244,7 @@ pub fn disable_amba_periph_clk(select: PeriphSelect) {
|
||||
PeriphSelect::Can1 => val.set_can_1_1x_clk_act(false),
|
||||
PeriphSelect::Can0 => val.set_can_0_1x_clk_act(false),
|
||||
PeriphSelect::Spi1 => val.set_spi_1_1x_clk_act(false),
|
||||
PeriphSelect::Spi0 => val.set_spi_1_1x_clk_act(false),
|
||||
PeriphSelect::Spi0 => val.set_spi_0_1x_clk_act(false),
|
||||
PeriphSelect::Sdio1 => val.set_sdio_1_1x_clk_act(false),
|
||||
PeriphSelect::Sdio0 => val.set_sdio_0_1x_clk_act(false),
|
||||
PeriphSelect::Gem1 => val.set_gem_1_1x_clk_act(false),
|
||||
|
||||
@@ -165,96 +165,80 @@ pub mod uart_blocking {
|
||||
}
|
||||
}
|
||||
|
||||
/// Logger module which logs into a ring buffer to allow asynchronous logging handling.
|
||||
pub mod rb {
|
||||
use core::cell::RefCell;
|
||||
/// Logger module which logs into a pipe to allow asynchronous logging handling.
|
||||
pub mod asynch {
|
||||
use core::fmt::Write as _;
|
||||
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use log::{LevelFilter, set_logger, set_max_level};
|
||||
use ringbuf::{
|
||||
StaticRb,
|
||||
traits::{Consumer, Producer},
|
||||
};
|
||||
|
||||
/// Logger implementation which logs frames via a ring buffer and sends the frame sizes
|
||||
/// as messages.
|
||||
///
|
||||
/// The logger does not require allocation and reserved a generous amount of 4096 bytes for
|
||||
/// both data buffer and ring buffer. This should be sufficient for most logging needs.
|
||||
/// The logger does not require allocation and reserves a generous amount of 4096 bytes for
|
||||
/// log data. This should be sufficient for most logging needs.
|
||||
pub struct Logger {
|
||||
frame_queue: embassy_sync::channel::Channel<CriticalSectionRawMutex, usize, 32>,
|
||||
data_buf: critical_section::Mutex<RefCell<heapless::String<4096>>>,
|
||||
ring_buf: critical_section::Mutex<RefCell<Option<StaticRb<u8, 4096>>>>,
|
||||
pipe: core::cell::RefCell<
|
||||
Option<embassy_sync::pipe::Writer<'static, CriticalSectionRawMutex, 4096>>,
|
||||
>,
|
||||
buf: critical_section::Mutex<core::cell::RefCell<heapless::String<4096>>>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Logger {}
|
||||
unsafe impl Sync for Logger {}
|
||||
|
||||
static LOGGER_RB: Logger = Logger {
|
||||
frame_queue: embassy_sync::channel::Channel::new(),
|
||||
data_buf: critical_section::Mutex::new(RefCell::new(heapless::String::new())),
|
||||
ring_buf: critical_section::Mutex::new(RefCell::new(None)),
|
||||
static LOGGER: Logger = Logger {
|
||||
pipe: core::cell::RefCell::new(None),
|
||||
buf: critical_section::Mutex::new(core::cell::RefCell::new(heapless::String::new())),
|
||||
};
|
||||
|
||||
static PIPE: static_cell::ConstStaticCell<
|
||||
embassy_sync::pipe::Pipe<CriticalSectionRawMutex, 4096>,
|
||||
> = static_cell::ConstStaticCell::new(embassy_sync::pipe::Pipe::new());
|
||||
|
||||
impl log::Log for Logger {
|
||||
fn enabled(&self, _metadata: &log::Metadata) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn log(&self, record: &log::Record) {
|
||||
if self.pipe.borrow().is_none() {
|
||||
return;
|
||||
}
|
||||
critical_section::with(|cs| {
|
||||
let ref_buf = self.data_buf.borrow(cs);
|
||||
let mut buf = ref_buf.borrow_mut();
|
||||
let mut buf = self.buf.borrow(cs).borrow_mut();
|
||||
buf.clear();
|
||||
let _ = writeln!(buf, "{} - {}\r", record.level(), record.args());
|
||||
let rb_ref = self.ring_buf.borrow(cs);
|
||||
let mut rb_opt = rb_ref.borrow_mut();
|
||||
if rb_opt.is_none() {
|
||||
panic!("log call on uninitialized logger");
|
||||
|
||||
let mut written = 0;
|
||||
|
||||
let pipe_writer_ref = self.pipe.borrow();
|
||||
let pipe_writer = pipe_writer_ref.as_ref().unwrap();
|
||||
while let Ok(written_in_this_call) =
|
||||
pipe_writer.try_write(&buf.as_bytes()[written..])
|
||||
{
|
||||
written += written_in_this_call;
|
||||
if written >= buf.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
rb_opt.as_mut().unwrap().push_slice(buf.as_bytes());
|
||||
let _ = self.frame_queue.try_send(buf.len());
|
||||
});
|
||||
}
|
||||
|
||||
fn flush(&self) {
|
||||
while !self.frame_queue().is_empty() {}
|
||||
}
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
impl Logger {
|
||||
pub fn frame_queue(
|
||||
&self,
|
||||
) -> &embassy_sync::channel::Channel<CriticalSectionRawMutex, usize, 32> {
|
||||
&self.frame_queue
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(level: LevelFilter) {
|
||||
pub fn init(
|
||||
level: LevelFilter,
|
||||
//writer: embassy_sync::pipe::Writer<'static, CriticalSectionRawMutex, 4096>,
|
||||
) -> Option<embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 4096>> {
|
||||
if super::LOGGER_INIT_DONE.swap(true, core::sync::atomic::Ordering::Relaxed) {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
critical_section::with(|cs| {
|
||||
let rb = StaticRb::<u8, 4096>::default();
|
||||
let rb_ref = LOGGER_RB.ring_buf.borrow(cs);
|
||||
rb_ref.borrow_mut().replace(rb);
|
||||
});
|
||||
set_logger(&LOGGER_RB).unwrap();
|
||||
let (reader, writer) = PIPE.take().split();
|
||||
LOGGER.pipe.borrow_mut().replace(writer);
|
||||
set_logger(&LOGGER).unwrap();
|
||||
set_max_level(level); // Adjust as needed
|
||||
}
|
||||
|
||||
pub fn read_next_frame(frame_len: usize, buf: &mut [u8]) {
|
||||
let read_len = core::cmp::min(frame_len, buf.len());
|
||||
critical_section::with(|cs| {
|
||||
let rb_ref = LOGGER_RB.ring_buf.borrow(cs);
|
||||
let mut rb = rb_ref.borrow_mut();
|
||||
rb.as_mut().unwrap().pop_slice(&mut buf[0..read_len]);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_frame_queue()
|
||||
-> &'static embassy_sync::channel::Channel<CriticalSectionRawMutex, usize, 32> {
|
||||
LOGGER_RB.frame_queue()
|
||||
Some(reader)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,33 @@
|
||||
//! # Device Configuration Module
|
||||
//! # Programmable Logic (PL) support module.
|
||||
//!
|
||||
//! Provides the [configure_bitstream_non_secure] method to program the PL using the device
|
||||
//! configuration (`devcfg`) peripheral.
|
||||
use arbitrary_int::{traits::Integer as _, u17};
|
||||
use zynq7000::slcr::reset::FpgaResetControl;
|
||||
|
||||
use crate::slcr::Slcr;
|
||||
|
||||
/// Put the PL out of reset.
|
||||
///
|
||||
/// The PL is in reset state after power-up. This method should be called in the first-stage
|
||||
/// bootloader to put it out of reset.
|
||||
pub fn deassert_reset() {
|
||||
// Safety: We only touch the PL reset register here.
|
||||
unsafe {
|
||||
Slcr::with(|slcr| {
|
||||
slcr.reset_ctrl().write_fpga(
|
||||
FpgaResetControl::builder()
|
||||
.with_zero_block_0(u17::ZERO)
|
||||
.with_fpga_3(false)
|
||||
.with_fpga_2(false)
|
||||
.with_fpga_1(false)
|
||||
.with_fpga_0(false)
|
||||
.build(),
|
||||
);
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("unaligned address: {0}")]
|
||||
pub struct UnalignedAddrError(usize);
|
||||
@@ -53,10 +82,14 @@ pub fn configure_bitstream_non_secure(
|
||||
val.set_pcap_rate_enable(false);
|
||||
val
|
||||
});
|
||||
devcfg.write_dma_source_addr(bitstream.as_ptr() as u32);
|
||||
|
||||
// As specified in the TMR,
|
||||
// Setting the two LSBs of the source and destination address to 2'b01 indicates to the DevC
|
||||
// DMA module the last DMA command of an overall transfer
|
||||
devcfg.write_dma_source_addr(bitstream.as_ptr() as u32 | 0b01);
|
||||
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);
|
||||
devcfg.write_dma_source_len(bitstream.len() as u32 / 4);
|
||||
devcfg.write_dma_dest_len(bitstream.len() as u32 / 4);
|
||||
|
||||
while !devcfg.read_interrupt_status().dma_done() {}
|
||||
// TODO: Check for errors.
|
||||
@@ -76,7 +76,7 @@ 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;
|
||||
let ticks = (ns as u64 * self.cpu_3x2x_clock.to_raw() as u64) / 1_000_000_000;
|
||||
|
||||
// Split the total delay into manageable chunks (u32::MAX ticks max).
|
||||
let mut remaining = ticks;
|
||||
|
||||
@@ -8,7 +8,7 @@ use zynq7000::{
|
||||
BaudRateDivisor, Config, InstructionCode, InterruptStatus, LoopbackMasterClockDelay,
|
||||
SpiEnable,
|
||||
},
|
||||
slcr::{clocks::SingleCommonPeriphIoClockControl, mio::Speed, reset::QspiResetControl},
|
||||
slcr::{clocks::SingleCommonPeriphIoClockControl, mio::Speed, reset::ResetControlQspiSmc},
|
||||
};
|
||||
|
||||
pub use embedded_hal::spi::{MODE_0, MODE_1, MODE_2, MODE_3, Mode};
|
||||
@@ -35,6 +35,12 @@ pub(crate) mod lqspi_configs;
|
||||
|
||||
pub const QSPI_MUX_CONFIG: MuxConfig = MuxConfig::new_with_l0();
|
||||
pub const FIFO_DEPTH: usize = 63;
|
||||
/// From the TRM:
|
||||
///
|
||||
/// > The maximum number of bytes per command sequence in this mode is limited by the depth of the
|
||||
/// > TxFIFO of 252 bytes.
|
||||
pub const MAX_BYTES_PER_TRANSFER_IO_MODE: usize = FIFO_DEPTH * 4;
|
||||
|
||||
/// In linear-addressed mode, the QSPI is memory-mapped, with the address starting here.
|
||||
pub const QSPI_START_ADDRESS: usize = 0xFC00_0000;
|
||||
|
||||
@@ -206,7 +212,7 @@ impl ClockConfig {
|
||||
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());
|
||||
let ref_clk_div = ref_clk.to_raw().div_ceil(target_ref_clock.to_raw());
|
||||
if ref_clk_div > u6::MAX.as_u32() {
|
||||
return Err(ClockCalculationError::RefDivOutOfRange);
|
||||
}
|
||||
@@ -233,24 +239,24 @@ impl ClockConfig {
|
||||
clocks
|
||||
.io_clocks()
|
||||
.ref_clk()
|
||||
.raw()
|
||||
.div_ceil(target_qspi_ref_clock.raw()),
|
||||
.to_raw()
|
||||
.div_ceil(target_qspi_ref_clock.to_raw()),
|
||||
clocks.io_clocks().ref_clk(),
|
||||
),
|
||||
SrcSelIo::ArmPll => (
|
||||
clocks
|
||||
.arm_clocks()
|
||||
.ref_clk()
|
||||
.raw()
|
||||
.div_ceil(target_qspi_ref_clock.raw()),
|
||||
.to_raw()
|
||||
.div_ceil(target_qspi_ref_clock.to_raw()),
|
||||
clocks.arm_clocks().ref_clk(),
|
||||
),
|
||||
SrcSelIo::DdrPll => (
|
||||
clocks
|
||||
.ddr_clocks()
|
||||
.ref_clk()
|
||||
.raw()
|
||||
.div_ceil(target_qspi_ref_clock.raw()),
|
||||
.to_raw()
|
||||
.div_ceil(target_qspi_ref_clock.to_raw()),
|
||||
clocks.ddr_clocks().ref_clk(),
|
||||
),
|
||||
};
|
||||
@@ -261,7 +267,9 @@ impl ClockConfig {
|
||||
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 qspi_baud_rate_div = qspi_ref_clk
|
||||
.to_raw()
|
||||
.div_ceil(target_qspi_interface_clock.to_raw());
|
||||
let baud_rate_div = match qspi_baud_rate_div {
|
||||
0..=2 => BaudRateDivisor::_2,
|
||||
3..=4 => BaudRateDivisor::_4,
|
||||
@@ -409,7 +417,7 @@ impl Qspi {
|
||||
.with_disable_hstl_rcvr(false)
|
||||
.with_pullup(true)
|
||||
.with_io_type(voltage)
|
||||
.with_speed(Speed::SlowCmosEdge)
|
||||
.with_speed(Speed::FastCmosEdge)
|
||||
.with_l3_sel(QSPI_MUX_CONFIG.l3_sel())
|
||||
.with_l2_sel(QSPI_MUX_CONFIG.l2_sel())
|
||||
.with_l1_sel(QSPI_MUX_CONFIG.l1_sel())
|
||||
@@ -421,7 +429,7 @@ impl Qspi {
|
||||
.with_disable_hstl_rcvr(false)
|
||||
.with_pullup(false)
|
||||
.with_io_type(voltage)
|
||||
.with_speed(Speed::SlowCmosEdge)
|
||||
.with_speed(Speed::FastCmosEdge)
|
||||
.with_l3_sel(QSPI_MUX_CONFIG.l3_sel())
|
||||
.with_l2_sel(QSPI_MUX_CONFIG.l2_sel())
|
||||
.with_l1_sel(QSPI_MUX_CONFIG.l1_sel())
|
||||
@@ -463,7 +471,7 @@ impl Qspi {
|
||||
.with_disable_hstl_rcvr(false)
|
||||
.with_pullup(false)
|
||||
.with_io_type(voltage)
|
||||
.with_speed(Speed::SlowCmosEdge)
|
||||
.with_speed(Speed::FastCmosEdge)
|
||||
.with_l3_sel(QSPI_MUX_CONFIG.l3_sel())
|
||||
.with_l2_sel(QSPI_MUX_CONFIG.l2_sel())
|
||||
.with_l1_sel(QSPI_MUX_CONFIG.l1_sel())
|
||||
@@ -667,16 +675,16 @@ pub fn reset() {
|
||||
unsafe {
|
||||
Slcr::with(|regs| {
|
||||
regs.reset_ctrl().write_lqspi(
|
||||
QspiResetControl::builder()
|
||||
.with_qspi_ref_reset(true)
|
||||
ResetControlQspiSmc::builder()
|
||||
.with_ref_reset(true)
|
||||
.with_cpu_1x_reset(true)
|
||||
.build(),
|
||||
);
|
||||
// Keep it in reset for some cycles.
|
||||
for _ in 0..3 {
|
||||
for _ in 0..10 {
|
||||
aarch32_cpu::asm::nop();
|
||||
}
|
||||
regs.reset_ctrl().write_lqspi(QspiResetControl::DEFAULT);
|
||||
regs.reset_ctrl().write_lqspi(ResetControlQspiSmc::DEFAULT);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
use arbitrary_int::u6;
|
||||
use zynq7000::sdio::{BlockSelect, CommandRegister, ResponseType};
|
||||
|
||||
use embedded_sdmmc::sdcard::{AcmdId, CmdId};
|
||||
|
||||
pub struct CommandConfig {
|
||||
pub id: u6,
|
||||
pub response_type: ResponseType,
|
||||
pub index_check: bool,
|
||||
pub crc_check: bool,
|
||||
}
|
||||
|
||||
impl CommandConfig {
|
||||
pub const fn new_no_response(id: u6) -> Self {
|
||||
Self {
|
||||
id,
|
||||
response_type: ResponseType::None,
|
||||
index_check: false,
|
||||
crc_check: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn new_with_r1_response(id: u6) -> Self {
|
||||
Self {
|
||||
id,
|
||||
response_type: ResponseType::_48bits,
|
||||
index_check: true,
|
||||
crc_check: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn new_with_r2_response(id: u6) -> Self {
|
||||
Self {
|
||||
id,
|
||||
response_type: ResponseType::_136bits,
|
||||
index_check: false,
|
||||
crc_check: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn new_with_r3_response(id: u6) -> Self {
|
||||
Self {
|
||||
id,
|
||||
response_type: ResponseType::_48bits,
|
||||
index_check: false,
|
||||
crc_check: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn new_with_r6_response(id: u6) -> Self {
|
||||
Self {
|
||||
id,
|
||||
response_type: ResponseType::_48bitsWithCheck,
|
||||
index_check: false,
|
||||
crc_check: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn build_command_without_data(config: CommandConfig) -> CommandRegister {
|
||||
CommandRegister::builder()
|
||||
.with_command_index(config.id)
|
||||
.with_command_type(zynq7000::sdio::CommandType::Normal)
|
||||
.with_data_is_present(false)
|
||||
.with_command_index_check_enable(config.index_check)
|
||||
.with_command_crc_check_enable(config.crc_check)
|
||||
.with_response_type_select(config.response_type)
|
||||
.with_block_select(zynq7000::sdio::BlockSelect::SingleBlock)
|
||||
.with_data_transfer_direction(zynq7000::sdio::TransferDirection::Write)
|
||||
.with_auto_cmd12_enable(false)
|
||||
.with_block_count_enable(false)
|
||||
.with_dma_enable(false)
|
||||
.build()
|
||||
}
|
||||
|
||||
pub const CMD0_GO_IDLE_MODE: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_no_response(CmdId::CMD0_GoIdleState.raw_value()),
|
||||
);
|
||||
pub const CMD2_ALL_SEND_CID: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r2_response(CmdId::CMD2_AllSendCid.raw_value()),
|
||||
);
|
||||
pub const CMD3_SEND_RELATIVE_ADDR: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r6_response(CmdId::CMD3_SendRelativeAddr.raw_value()),
|
||||
);
|
||||
pub const CMD7_SELECT_SD_CARD: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r1_response(CmdId::CMD7_SelectCard.raw_value()),
|
||||
);
|
||||
pub const CMD8_SEND_IF_COND: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r1_response(CmdId::CMD8_SendIfCond.raw_value()),
|
||||
);
|
||||
pub const CMD9_SEND_CSD: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r2_response(CmdId::CMD9_SendCsd.raw_value()),
|
||||
);
|
||||
pub const CMD13_SEND_STATUS: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r1_response(CmdId::CMD13_SendStatus.raw_value()),
|
||||
);
|
||||
pub const CMD17_READ_SINGLE_BLOCK: CommandRegister = CommandRegister::builder()
|
||||
.with_command_index(CmdId::CMD17_ReadSingleBlock.raw_value())
|
||||
.with_command_type(zynq7000::sdio::CommandType::Normal)
|
||||
.with_data_is_present(true)
|
||||
.with_command_index_check_enable(true)
|
||||
.with_command_crc_check_enable(true)
|
||||
.with_response_type_select(ResponseType::_48bits)
|
||||
.with_block_select(BlockSelect::SingleBlock)
|
||||
.with_data_transfer_direction(zynq7000::sdio::TransferDirection::Read)
|
||||
.with_auto_cmd12_enable(false)
|
||||
.with_block_count_enable(false)
|
||||
.with_dma_enable(false)
|
||||
.build();
|
||||
pub const CMD24_WRITE_BLOCK: CommandRegister = CommandRegister::builder()
|
||||
.with_command_index(CmdId::CMD24_WriteBlock.raw_value())
|
||||
.with_command_type(zynq7000::sdio::CommandType::Normal)
|
||||
.with_data_is_present(true)
|
||||
.with_command_index_check_enable(true)
|
||||
.with_command_crc_check_enable(true)
|
||||
.with_response_type_select(ResponseType::_48bits)
|
||||
.with_block_select(BlockSelect::SingleBlock)
|
||||
.with_data_transfer_direction(zynq7000::sdio::TransferDirection::Write)
|
||||
.with_auto_cmd12_enable(false)
|
||||
.with_block_count_enable(false)
|
||||
.with_dma_enable(false)
|
||||
.build();
|
||||
|
||||
pub const CMD55_APP_CMD: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r1_response(CmdId::CMD55_AppCmd.raw_value()),
|
||||
);
|
||||
pub const ACMD6_SET_BUS_WIDTH: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r1_response(AcmdId::ACMD6_SetBusWidth.raw_value()),
|
||||
);
|
||||
pub const ACMD41_SEND_IF_COND: CommandRegister = build_command_without_data(
|
||||
CommandConfig::new_with_r3_response(AcmdId::ACMD41_SdSendOpCond.raw_value()),
|
||||
);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,99 @@
|
||||
use crate::gpio::mio::{
|
||||
Mio10, Mio11, Mio12, Mio13, Mio14, Mio15, Mio28, Mio29, Mio30, Mio31, Mio32, Mio33, Mio34,
|
||||
Mio35, Mio36, Mio37, Mio38, Mio39, Mio48, Mio49, MioPin, Pin,
|
||||
};
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
use crate::gpio::mio::{
|
||||
Mio16, Mio17, Mio18, Mio19, Mio20, Mio21, Mio22, Mio23, Mio24, Mio25, Mio26, Mio27, Mio40,
|
||||
Mio41, Mio42, Mio43, Mio44, Mio45, Mio46, Mio47, Mio50, Mio51,
|
||||
};
|
||||
|
||||
pub trait Sdio0ClockPin: MioPin {}
|
||||
pub trait Sdio0CommandPin: MioPin {}
|
||||
pub trait Sdio0Data0Pin: MioPin {}
|
||||
pub trait Sdio0Data1Pin: MioPin {}
|
||||
pub trait Sdio0Data2Pin: MioPin {}
|
||||
pub trait Sdio0Data3Pin: MioPin {}
|
||||
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0ClockPin for Pin<Mio16> {}
|
||||
impl Sdio0ClockPin for Pin<Mio28> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0ClockPin for Pin<Mio40> {}
|
||||
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0CommandPin for Pin<Mio17> {}
|
||||
impl Sdio0CommandPin for Pin<Mio29> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0CommandPin for Pin<Mio41> {}
|
||||
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0Data0Pin for Pin<Mio18> {}
|
||||
impl Sdio0Data0Pin for Pin<Mio30> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0Data0Pin for Pin<Mio42> {}
|
||||
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0Data1Pin for Pin<Mio19> {}
|
||||
impl Sdio0Data1Pin for Pin<Mio31> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0Data1Pin for Pin<Mio43> {}
|
||||
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0Data2Pin for Pin<Mio20> {}
|
||||
impl Sdio0Data2Pin for Pin<Mio32> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0Data2Pin for Pin<Mio44> {}
|
||||
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0Data3Pin for Pin<Mio21> {}
|
||||
impl Sdio0Data3Pin for Pin<Mio33> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio0Data3Pin for Pin<Mio45> {}
|
||||
|
||||
pub trait Sdio1ClockPin: MioPin {}
|
||||
pub trait Sdio1CommandPin: MioPin {}
|
||||
pub trait Sdio1Data0Pin: MioPin {}
|
||||
pub trait Sdio1Data1Pin: MioPin {}
|
||||
pub trait Sdio1Data2Pin: MioPin {}
|
||||
pub trait Sdio1Data3Pin: MioPin {}
|
||||
|
||||
impl Sdio1ClockPin for Pin<Mio12> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1ClockPin for Pin<Mio24> {}
|
||||
impl Sdio1ClockPin for Pin<Mio36> {}
|
||||
impl Sdio1ClockPin for Pin<Mio48> {}
|
||||
|
||||
impl Sdio1CommandPin for Pin<Mio11> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1CommandPin for Pin<Mio23> {}
|
||||
impl Sdio1CommandPin for Pin<Mio35> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1CommandPin for Pin<Mio47> {}
|
||||
|
||||
impl Sdio1Data0Pin for Pin<Mio10> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1Data0Pin for Pin<Mio22> {}
|
||||
impl Sdio1Data0Pin for Pin<Mio34> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1Data0Pin for Pin<Mio46> {}
|
||||
|
||||
impl Sdio1Data1Pin for Pin<Mio13> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1Data1Pin for Pin<Mio25> {}
|
||||
impl Sdio1Data1Pin for Pin<Mio37> {}
|
||||
impl Sdio1Data1Pin for Pin<Mio49> {}
|
||||
|
||||
impl Sdio1Data2Pin for Pin<Mio14> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1Data2Pin for Pin<Mio26> {}
|
||||
impl Sdio1Data2Pin for Pin<Mio38> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1Data2Pin for Pin<Mio50> {}
|
||||
|
||||
impl Sdio1Data2Pin for Pin<Mio15> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1Data3Pin for Pin<Mio27> {}
|
||||
impl Sdio1Data3Pin for Pin<Mio39> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl Sdio1Data3Pin for Pin<Mio51> {}
|
||||
@@ -1,11 +1,10 @@
|
||||
//! Asynchronous PS SPI driver.
|
||||
use core::{cell::RefCell, convert::Infallible, sync::atomic::AtomicBool};
|
||||
use core::{cell::RefCell, sync::atomic::AtomicBool};
|
||||
|
||||
use critical_section::Mutex;
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use embedded_hal_async::spi::SpiBus;
|
||||
use raw_slice::{RawBufSlice, RawBufSliceMut};
|
||||
use zynq7000::spi::InterruptStatus;
|
||||
|
||||
use super::{ChipSelect, FIFO_DEPTH, Spi, SpiId, SpiLowLevel};
|
||||
|
||||
@@ -16,119 +15,137 @@ static TRANSFER_CONTEXTS: [Mutex<RefCell<TransferContext>>; 2] =
|
||||
// critical section.
|
||||
static DONE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
|
||||
|
||||
#[derive(Debug, Clone, Copy, thiserror::Error)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[error("SPI RX FIFO overrun")]
|
||||
pub struct RxOverrunError;
|
||||
|
||||
impl embedded_hal_async::spi::Error for RxOverrunError {
|
||||
fn kind(&self) -> embedded_hal::spi::ErrorKind {
|
||||
embedded_hal::spi::ErrorKind::Overrun
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a generic interrupt handler to handle asynchronous SPI operations for a given
|
||||
/// SPI peripheral.
|
||||
///
|
||||
/// The user has to call this once in the interrupt handler responsible for the SPI interrupts on
|
||||
/// # Safety
|
||||
///
|
||||
/// This needs to be called once in the interrupt handler responsible for the SPI interrupts on
|
||||
/// the given SPI bank.
|
||||
pub fn on_interrupt(peripheral: SpiId) {
|
||||
pub unsafe fn on_interrupt(peripheral: SpiId) {
|
||||
let mut spi = unsafe { SpiLowLevel::steal(peripheral) };
|
||||
let idx = peripheral as usize;
|
||||
let imr = spi.read_imr();
|
||||
// IRQ is not related.
|
||||
if !imr.tx_trig() && !imr.tx_full() && !imr.tx_underflow() && !imr.rx_ovr() && !imr.rx_full() {
|
||||
return;
|
||||
}
|
||||
let index = peripheral as usize;
|
||||
let enabled_irqs = spi.read_enabled_interrupts();
|
||||
// Prevent spurious interrupts from messing with out logic here.
|
||||
spi.disable_interrupts();
|
||||
let isr = spi.read_isr();
|
||||
let interrupt_status = spi.read_interrupt_status();
|
||||
spi.clear_interrupts();
|
||||
// IRQ is not related.
|
||||
if !enabled_irqs.tx_below_threshold()
|
||||
&& !enabled_irqs.tx_full()
|
||||
&& !enabled_irqs.tx_underflow()
|
||||
&& !enabled_irqs.rx_ovr()
|
||||
&& !enabled_irqs.rx_full()
|
||||
{
|
||||
return;
|
||||
}
|
||||
if interrupt_status.rx_overrun() {
|
||||
// Not sure how to otherwise handle this cleanly..
|
||||
return handle_rx_overrun(&mut spi, index);
|
||||
}
|
||||
let mut context = critical_section::with(|cs| {
|
||||
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
|
||||
let context_ref = TRANSFER_CONTEXTS[index].borrow(cs);
|
||||
*context_ref.borrow()
|
||||
});
|
||||
// No transfer active.
|
||||
if context.transfer_type.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Write the trigger to one, we want to empty the whole FIFO in the transfer handlers.
|
||||
// The trigger might have been set to a higher value, and the NOT FULL status bit will be
|
||||
// cleared when the FIFO falls below the threshold.
|
||||
spi.write_rx_trig(0x1);
|
||||
|
||||
let transfer_type = context.transfer_type.unwrap();
|
||||
match transfer_type {
|
||||
TransferType::Read => on_interrupt_read(idx, &mut context, &mut spi, isr),
|
||||
TransferType::Write => on_interrupt_write(idx, &mut context, &mut spi, isr),
|
||||
TransferType::Transfer => on_interrupt_transfer(idx, &mut context, &mut spi, isr),
|
||||
TransferType::Read => on_interrupt_read(index, &mut context, &mut spi),
|
||||
TransferType::Write => on_interrupt_write(index, &mut context, &mut spi),
|
||||
TransferType::Transfer => on_interrupt_transfer(index, &mut context, &mut spi),
|
||||
TransferType::TransferInPlace => {
|
||||
on_interrupt_transfer_in_place(idx, &mut context, &mut spi, isr)
|
||||
on_interrupt_transfer_in_place(index, &mut context, &mut spi)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn on_interrupt_read(
|
||||
idx: usize,
|
||||
context: &mut TransferContext,
|
||||
spi: &mut SpiLowLevel,
|
||||
mut isr: InterruptStatus,
|
||||
) {
|
||||
fn handle_rx_overrun(spi: &mut SpiLowLevel, idx: usize) {
|
||||
critical_section::with(|cs| {
|
||||
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
|
||||
context_ref.borrow_mut().rx_overrun = true;
|
||||
});
|
||||
// Clean state is re-configured by drop handler.
|
||||
reset_trigger_levels(spi);
|
||||
// At the very least disable the peripheral.
|
||||
spi.disable();
|
||||
|
||||
// Interrupts were already disabled and cleared.
|
||||
DONE[idx].store(true, core::sync::atomic::Ordering::Relaxed);
|
||||
WAKERS[idx].wake();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn reset_trigger_levels(spi: &mut SpiLowLevel) {
|
||||
spi.write_rx_trig(0x1);
|
||||
spi.write_tx_trig(0x1);
|
||||
}
|
||||
|
||||
fn on_interrupt_read(idx: usize, context: &mut TransferContext, spi: &mut SpiLowLevel) {
|
||||
let read_slice = unsafe { context.rx_slice.get_mut().unwrap() };
|
||||
let transfer_len = read_slice.len();
|
||||
|
||||
// Read data from RX FIFO first.
|
||||
let read_len = calculate_read_len(spi, isr, transfer_len, context.rx_progress);
|
||||
(0..read_len).for_each(|_| {
|
||||
while spi.read_interrupt_status().rx_not_empty() {
|
||||
read_slice[context.rx_progress] = spi.read_fifo_unchecked();
|
||||
context.rx_progress += 1;
|
||||
});
|
||||
}
|
||||
|
||||
// The FIFO still needs to be pumped.
|
||||
while context.tx_progress < read_slice.len() && !isr.tx_full() {
|
||||
while context.tx_progress < read_slice.len() && !spi.read_interrupt_status().tx_full() {
|
||||
spi.write_fifo_unchecked(0);
|
||||
context.tx_progress += 1;
|
||||
isr = spi.read_isr();
|
||||
}
|
||||
|
||||
isr_finish_handler(idx, spi, context, transfer_len)
|
||||
}
|
||||
|
||||
fn on_interrupt_write(
|
||||
idx: usize,
|
||||
context: &mut TransferContext,
|
||||
spi: &mut SpiLowLevel,
|
||||
mut isr: InterruptStatus,
|
||||
) {
|
||||
fn on_interrupt_write(idx: usize, context: &mut TransferContext, spi: &mut SpiLowLevel) {
|
||||
let write_slice = unsafe { context.tx_slice.get().unwrap() };
|
||||
let transfer_len = write_slice.len();
|
||||
|
||||
// Read data from RX FIFO first.
|
||||
let read_len = calculate_read_len(spi, isr, transfer_len, context.rx_progress);
|
||||
(0..read_len).for_each(|_| {
|
||||
while spi.read_interrupt_status().rx_not_empty() {
|
||||
spi.read_fifo_unchecked();
|
||||
context.rx_progress += 1;
|
||||
});
|
||||
}
|
||||
|
||||
// Data still needs to be sent
|
||||
while context.tx_progress < transfer_len && !isr.tx_full() {
|
||||
while context.tx_progress < transfer_len && !spi.read_interrupt_status().tx_full() {
|
||||
spi.write_fifo_unchecked(write_slice[context.tx_progress]);
|
||||
context.tx_progress += 1;
|
||||
isr = spi.read_isr();
|
||||
}
|
||||
|
||||
isr_finish_handler(idx, spi, context, transfer_len)
|
||||
}
|
||||
|
||||
fn on_interrupt_transfer(
|
||||
idx: usize,
|
||||
context: &mut TransferContext,
|
||||
spi: &mut SpiLowLevel,
|
||||
mut isr: InterruptStatus,
|
||||
) {
|
||||
fn on_interrupt_transfer(idx: usize, context: &mut TransferContext, spi: &mut SpiLowLevel) {
|
||||
let read_slice = unsafe { context.rx_slice.get_mut().unwrap() };
|
||||
let read_len = read_slice.len();
|
||||
let write_slice = unsafe { context.tx_slice.get().unwrap() };
|
||||
let write_len = write_slice.len();
|
||||
let transfer_len = core::cmp::max(read_len, write_len);
|
||||
|
||||
// Read data from RX FIFO first.
|
||||
let read_len = calculate_read_len(spi, isr, transfer_len, context.rx_progress);
|
||||
(0..read_len).for_each(|_| {
|
||||
if context.rx_progress < read_len {
|
||||
read_slice[context.rx_progress] = spi.read_fifo_unchecked();
|
||||
} else {
|
||||
spi.read_fifo_unchecked();
|
||||
}
|
||||
context.rx_progress += 1;
|
||||
});
|
||||
|
||||
// Data still needs to be sent
|
||||
while context.tx_progress < transfer_len && !isr.tx_full() {
|
||||
while context.tx_progress < transfer_len && !spi.read_interrupt_status().tx_full() {
|
||||
if context.tx_progress < write_len {
|
||||
spi.write_fifo_unchecked(write_slice[context.tx_progress]);
|
||||
} else {
|
||||
@@ -136,7 +153,16 @@ fn on_interrupt_transfer(
|
||||
spi.write_fifo_unchecked(0);
|
||||
}
|
||||
context.tx_progress += 1;
|
||||
isr = spi.read_isr();
|
||||
}
|
||||
|
||||
// Read data from RX FIFO first.
|
||||
while spi.read_interrupt_status().rx_not_empty() {
|
||||
if context.rx_progress < read_len {
|
||||
read_slice[context.rx_progress] = spi.read_fifo_unchecked();
|
||||
} else {
|
||||
spi.read_fifo_unchecked();
|
||||
}
|
||||
context.rx_progress += 1;
|
||||
}
|
||||
|
||||
isr_finish_handler(idx, spi, context, transfer_len)
|
||||
@@ -146,43 +172,25 @@ fn on_interrupt_transfer_in_place(
|
||||
idx: usize,
|
||||
context: &mut TransferContext,
|
||||
spi: &mut SpiLowLevel,
|
||||
mut isr: InterruptStatus,
|
||||
) {
|
||||
let transfer_slice = unsafe { context.rx_slice.get_mut().unwrap() };
|
||||
let transfer_len = transfer_slice.len();
|
||||
// Read data from RX FIFO first.
|
||||
let read_len = calculate_read_len(spi, isr, transfer_len, context.rx_progress);
|
||||
(0..read_len).for_each(|_| {
|
||||
transfer_slice[context.rx_progress] = spi.read_fifo_unchecked();
|
||||
context.rx_progress += 1;
|
||||
});
|
||||
|
||||
// Data still needs to be sent
|
||||
while context.tx_progress < transfer_len && !isr.tx_full() {
|
||||
// Send data first to avoid overwriting data that still needs to be sent.
|
||||
while context.tx_progress < transfer_len && !spi.read_interrupt_status().tx_full() {
|
||||
spi.write_fifo_unchecked(transfer_slice[context.tx_progress]);
|
||||
context.tx_progress += 1;
|
||||
isr = spi.read_isr();
|
||||
}
|
||||
|
||||
// Read data from RX FIFO.
|
||||
while spi.read_interrupt_status().rx_not_empty() {
|
||||
transfer_slice[context.rx_progress] = spi.read_fifo_unchecked();
|
||||
context.rx_progress += 1;
|
||||
}
|
||||
|
||||
isr_finish_handler(idx, spi, context, transfer_len)
|
||||
}
|
||||
|
||||
fn calculate_read_len(
|
||||
spi: &mut SpiLowLevel,
|
||||
isr: InterruptStatus,
|
||||
total_read_len: usize,
|
||||
rx_progress: usize,
|
||||
) -> usize {
|
||||
if isr.rx_full() {
|
||||
core::cmp::min(FIFO_DEPTH, total_read_len - rx_progress)
|
||||
} else if isr.rx_not_empty() {
|
||||
let trigger = spi.read_rx_not_empty_threshold();
|
||||
core::cmp::min(total_read_len - rx_progress, trigger as usize)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic handler after RX FIFO and TX FIFO were handled. Checks and handles finished
|
||||
/// and unfinished conditions.
|
||||
fn isr_finish_handler(
|
||||
@@ -197,7 +205,7 @@ fn isr_finish_handler(
|
||||
return;
|
||||
}
|
||||
|
||||
unfinished_transfer(spi, transfer_len, context.rx_progress);
|
||||
unfinished_transfer(spi, transfer_len, context);
|
||||
|
||||
// If the transfer is done, the context structure was already written back.
|
||||
// Write back updated context structure.
|
||||
@@ -213,6 +221,7 @@ fn finish_transfer(idx: usize, context: &mut TransferContext, spi: &mut SpiLowLe
|
||||
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
|
||||
*context_ref.borrow_mut() = *context;
|
||||
});
|
||||
// Default reset values.
|
||||
spi.set_rx_fifo_trigger(1).unwrap();
|
||||
spi.set_tx_fifo_trigger(1).unwrap();
|
||||
// Interrupts were already disabled and cleared.
|
||||
@@ -220,11 +229,30 @@ fn finish_transfer(idx: usize, context: &mut TransferContext, spi: &mut SpiLowLe
|
||||
WAKERS[idx].wake();
|
||||
}
|
||||
|
||||
fn unfinished_transfer(spi: &mut SpiLowLevel, transfer_len: usize, rx_progress: usize) {
|
||||
let new_trig_level = core::cmp::min(FIFO_DEPTH, transfer_len - rx_progress);
|
||||
spi.set_rx_fifo_trigger(new_trig_level as u32).unwrap();
|
||||
// Re-enable interrupts with the new RX FIFO trigger level.
|
||||
spi.enable_interrupts();
|
||||
fn unfinished_transfer(spi: &mut SpiLowLevel, transfer_len: usize, context: &TransferContext) {
|
||||
// Unwraps okay, checks ensure number is below FIFO depth.
|
||||
|
||||
// By using the FIFO depth divided by two, give the software some time and allow more
|
||||
// latency in the system.
|
||||
spi.set_rx_fifo_trigger(
|
||||
core::cmp::min(FIFO_DEPTH / 2, transfer_len - context.rx_progress) as u32,
|
||||
)
|
||||
.unwrap();
|
||||
let tx_pending = context.tx_progress < transfer_len;
|
||||
if tx_pending {
|
||||
let remaining_tx = transfer_len - context.tx_progress;
|
||||
let tx_trigger = if remaining_tx < FIFO_DEPTH / 2 {
|
||||
FIFO_DEPTH as u32 - remaining_tx as u32
|
||||
} else {
|
||||
FIFO_DEPTH as u32 / 2
|
||||
};
|
||||
spi.set_tx_fifo_trigger(tx_trigger).unwrap();
|
||||
} else {
|
||||
spi.set_tx_fifo_trigger(1).unwrap();
|
||||
}
|
||||
// Re-enable interrupts with the new RX FIFO trigger level. Only enable TX threshold interrupt
|
||||
// if we are not done yet with pushing all bytes to the FIFO.
|
||||
spi.enable_interrupts(tx_pending);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -242,6 +270,7 @@ pub struct TransferContext {
|
||||
rx_progress: usize,
|
||||
tx_slice: RawBufSlice,
|
||||
rx_slice: RawBufSliceMut,
|
||||
rx_overrun: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::new_without_default)]
|
||||
@@ -253,173 +282,198 @@ impl TransferContext {
|
||||
rx_progress: 0,
|
||||
tx_slice: RawBufSlice::new_nulled(),
|
||||
rx_slice: RawBufSliceMut::new_nulled(),
|
||||
rx_overrun: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpiFuture {
|
||||
pub struct SpiFuture<'spi> {
|
||||
id: super::SpiId,
|
||||
spi: super::SpiLowLevel,
|
||||
spi: &'spi mut super::SpiLowLevel,
|
||||
config: super::Config,
|
||||
finished_regularly: core::cell::Cell<bool>,
|
||||
}
|
||||
|
||||
impl SpiFuture {
|
||||
fn new_for_read(spi: &mut Spi, spi_id: SpiId, words: &mut [u8]) -> Self {
|
||||
impl<'spi> SpiFuture<'spi> {
|
||||
fn new_for_read(spi: &'spi mut Spi, spi_id: SpiId, words: &mut [u8]) -> Self {
|
||||
if words.is_empty() {
|
||||
panic!("words length unexpectedly 0");
|
||||
}
|
||||
let idx = spi_id as usize;
|
||||
DONE[idx].store(false, core::sync::atomic::Ordering::Relaxed);
|
||||
spi.inner.disable_interrupts();
|
||||
Self::generic_init_transfer(spi, spi_id);
|
||||
|
||||
let write_idx = core::cmp::min(super::FIFO_DEPTH, words.len());
|
||||
let write_index = core::cmp::min(super::FIFO_DEPTH, words.len());
|
||||
// Send dummy bytes.
|
||||
(0..write_idx).for_each(|_| {
|
||||
(0..write_index).for_each(|_| {
|
||||
spi.inner.write_fifo_unchecked(0);
|
||||
});
|
||||
|
||||
Self::set_triggers(spi, write_idx, words.len());
|
||||
Self::set_triggers(spi, write_index, words.len());
|
||||
// We assume that the slave select configuration was already performed, but we take
|
||||
// care of issuing a start if necessary.
|
||||
spi.issue_manual_start_for_manual_cfg();
|
||||
|
||||
critical_section::with(|cs| {
|
||||
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
|
||||
let context_ref = TRANSFER_CONTEXTS[spi_id as usize].borrow(cs);
|
||||
let mut context = context_ref.borrow_mut();
|
||||
context.transfer_type = Some(TransferType::Read);
|
||||
unsafe {
|
||||
context.rx_slice.set(words);
|
||||
}
|
||||
context.tx_slice.set_null();
|
||||
context.tx_progress = write_idx;
|
||||
context.tx_progress = write_index;
|
||||
context.rx_progress = 0;
|
||||
spi.inner.clear_interrupts();
|
||||
spi.inner.enable_interrupts();
|
||||
spi.inner.enable_interrupts(write_index < words.len());
|
||||
spi.inner.enable();
|
||||
});
|
||||
Self {
|
||||
id: spi_id,
|
||||
config: spi.config,
|
||||
spi: unsafe { spi.inner.clone() },
|
||||
spi: &mut spi.inner,
|
||||
finished_regularly: core::cell::Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_for_write(spi: &mut Spi, spi_id: SpiId, words: &[u8]) -> Self {
|
||||
fn new_for_write(spi: &'spi mut Spi, spi_id: SpiId, words: &[u8]) -> Self {
|
||||
if words.is_empty() {
|
||||
panic!("words length unexpectedly 0");
|
||||
}
|
||||
let (idx, write_idx) = Self::generic_init_transfer(spi, spi_id, words);
|
||||
let write_index = Self::generic_init_transfer_write_transfer_in_place(spi, spi_id, words);
|
||||
critical_section::with(|cs| {
|
||||
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
|
||||
let context_ref = TRANSFER_CONTEXTS[spi_id as usize].borrow(cs);
|
||||
let mut context = context_ref.borrow_mut();
|
||||
context.transfer_type = Some(TransferType::Write);
|
||||
unsafe {
|
||||
context.tx_slice.set(words);
|
||||
}
|
||||
context.rx_slice.set_null();
|
||||
context.tx_progress = write_idx;
|
||||
context.tx_progress = write_index;
|
||||
context.rx_progress = 0;
|
||||
spi.inner.clear_interrupts();
|
||||
spi.inner.enable_interrupts();
|
||||
spi.inner.enable_interrupts(write_index < words.len());
|
||||
spi.inner.enable();
|
||||
});
|
||||
Self {
|
||||
id: spi_id,
|
||||
config: spi.config,
|
||||
spi: unsafe { spi.inner.clone() },
|
||||
spi: &mut spi.inner,
|
||||
finished_regularly: core::cell::Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_for_transfer(spi: &mut Spi, spi_id: SpiId, read: &mut [u8], write: &[u8]) -> Self {
|
||||
fn new_for_transfer(spi: &'spi mut Spi, spi_id: SpiId, read: &mut [u8], write: &[u8]) -> Self {
|
||||
if read.is_empty() || write.is_empty() {
|
||||
panic!("read or write buffer unexpectedly empty");
|
||||
}
|
||||
let (idx, write_idx) = Self::generic_init_transfer(spi, spi_id, write);
|
||||
let full_write_len = core::cmp::max(read.len(), write.len());
|
||||
let fifo_prefill = core::cmp::min(super::FIFO_DEPTH, full_write_len);
|
||||
|
||||
Self::generic_init_transfer(spi, spi_id);
|
||||
|
||||
for write_index in 0..fifo_prefill {
|
||||
let value = write.get(write_index).copied().unwrap_or(0);
|
||||
spi.inner.write_fifo_unchecked(value);
|
||||
}
|
||||
|
||||
Self::set_triggers(spi, fifo_prefill, full_write_len);
|
||||
critical_section::with(|cs| {
|
||||
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
|
||||
let context_ref = TRANSFER_CONTEXTS[spi_id as usize].borrow(cs);
|
||||
let mut context = context_ref.borrow_mut();
|
||||
context.transfer_type = Some(TransferType::Transfer);
|
||||
unsafe {
|
||||
context.tx_slice.set(write);
|
||||
context.rx_slice.set(read);
|
||||
}
|
||||
context.tx_progress = write_idx;
|
||||
context.tx_progress = fifo_prefill;
|
||||
context.rx_progress = 0;
|
||||
spi.inner.clear_interrupts();
|
||||
spi.inner.enable_interrupts();
|
||||
spi.inner.enable_interrupts(fifo_prefill < write.len());
|
||||
spi.inner.enable();
|
||||
});
|
||||
Self {
|
||||
id: spi_id,
|
||||
config: spi.config,
|
||||
spi: unsafe { spi.inner.clone() },
|
||||
spi: &mut spi.inner,
|
||||
finished_regularly: core::cell::Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_for_transfer_in_place(spi: &mut Spi, spi_id: SpiId, words: &mut [u8]) -> Self {
|
||||
fn new_for_transfer_in_place(spi: &'spi mut Spi, spi_id: SpiId, words: &mut [u8]) -> Self {
|
||||
if words.is_empty() {
|
||||
panic!("read and write buffer unexpectedly empty");
|
||||
}
|
||||
let (idx, write_idx) = Self::generic_init_transfer(spi, spi_id, words);
|
||||
let write_index = Self::generic_init_transfer_write_transfer_in_place(spi, spi_id, words);
|
||||
critical_section::with(|cs| {
|
||||
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
|
||||
let context_ref = TRANSFER_CONTEXTS[spi_id as usize].borrow(cs);
|
||||
let mut context = context_ref.borrow_mut();
|
||||
context.transfer_type = Some(TransferType::TransferInPlace);
|
||||
unsafe {
|
||||
context.rx_slice.set(words);
|
||||
}
|
||||
context.tx_slice.set_null();
|
||||
context.tx_progress = write_idx;
|
||||
context.tx_progress = write_index;
|
||||
context.rx_progress = 0;
|
||||
spi.inner.clear_interrupts();
|
||||
spi.inner.enable_interrupts();
|
||||
spi.inner.enable_interrupts(write_index < words.len());
|
||||
spi.inner.enable();
|
||||
});
|
||||
Self {
|
||||
id: spi_id,
|
||||
config: spi.config,
|
||||
spi: unsafe { spi.inner.clone() },
|
||||
spi: &mut spi.inner,
|
||||
finished_regularly: core::cell::Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn generic_init_transfer(spi: &mut Spi, spi_id: SpiId, write: &[u8]) -> (usize, usize) {
|
||||
let idx = spi_id as usize;
|
||||
fn generic_init_transfer(spi: &mut Spi, id: SpiId) {
|
||||
let idx = id as usize;
|
||||
DONE[idx].store(false, core::sync::atomic::Ordering::Relaxed);
|
||||
spi.inner.disable();
|
||||
spi.inner.disable_interrupts();
|
||||
}
|
||||
|
||||
// Returns amount of bytes written to FIFO.
|
||||
fn generic_init_transfer_write_transfer_in_place(
|
||||
spi: &mut Spi,
|
||||
id: SpiId,
|
||||
write: &[u8],
|
||||
) -> usize {
|
||||
Self::generic_init_transfer(spi, id);
|
||||
|
||||
let write_idx = core::cmp::min(super::FIFO_DEPTH, write.len());
|
||||
Self::set_triggers(spi, write_idx, write.len());
|
||||
(0..write_idx).for_each(|idx| {
|
||||
spi.inner.write_fifo_unchecked(write[idx]);
|
||||
});
|
||||
|
||||
Self::set_triggers(spi, write_idx, write.len());
|
||||
// We assume that the slave select configuration was already performed, but we take
|
||||
// care of issuing a start if necessary.
|
||||
spi.issue_manual_start_for_manual_cfg();
|
||||
(idx, write_idx)
|
||||
write_idx
|
||||
}
|
||||
|
||||
fn set_triggers(spi: &mut Spi, write_idx: usize, write_len: usize) {
|
||||
// This should never fail because it is never larger than the FIFO depth.
|
||||
spi.inner.set_rx_fifo_trigger(write_idx as u32).unwrap();
|
||||
spi.inner
|
||||
.set_rx_fifo_trigger(core::cmp::min(
|
||||
super::FIFO_DEPTH as u32 / 2,
|
||||
write_idx as u32,
|
||||
))
|
||||
.unwrap();
|
||||
// We want to re-fill the TX FIFO before it is completely empty if the full transfer size
|
||||
// is larger than the FIFO depth. I am not sure whether the default value of 1 ensures
|
||||
// this because the TMR says that this interrupt is triggered when the FIFO has less than
|
||||
// threshold entries.
|
||||
// is larger than the FIFO depth. Otherwise, set it to 1. Not exactly sure what that does,
|
||||
// but we do not enable interrupts anyway.
|
||||
if write_len > super::FIFO_DEPTH {
|
||||
spi.inner.set_tx_fifo_trigger(2).unwrap();
|
||||
spi.inner
|
||||
.set_tx_fifo_trigger(super::FIFO_DEPTH as u32 / 2)
|
||||
.unwrap();
|
||||
} else {
|
||||
spi.inner.set_tx_fifo_trigger(1).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for SpiFuture {
|
||||
type Output = ();
|
||||
impl Future for SpiFuture<'_> {
|
||||
type Output = Result<(), RxOverrunError>;
|
||||
|
||||
fn poll(
|
||||
self: core::pin::Pin<&mut Self>,
|
||||
@@ -427,18 +481,23 @@ impl Future for SpiFuture {
|
||||
) -> core::task::Poll<Self::Output> {
|
||||
WAKERS[self.id as usize].register(cx.waker());
|
||||
if DONE[self.id as usize].swap(false, core::sync::atomic::Ordering::Relaxed) {
|
||||
critical_section::with(|cs| {
|
||||
let rx_overrun = critical_section::with(|cs| {
|
||||
let mut ctx = TRANSFER_CONTEXTS[self.id as usize].borrow(cs).borrow_mut();
|
||||
let overrun = ctx.rx_overrun;
|
||||
*ctx = TransferContext::default();
|
||||
overrun
|
||||
});
|
||||
self.finished_regularly.set(true);
|
||||
return core::task::Poll::Ready(());
|
||||
self.finished_regularly.set(!rx_overrun);
|
||||
if rx_overrun {
|
||||
return core::task::Poll::Ready(Err(RxOverrunError));
|
||||
}
|
||||
return core::task::Poll::Ready(Ok(()));
|
||||
}
|
||||
core::task::Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SpiFuture {
|
||||
impl Drop for SpiFuture<'_> {
|
||||
fn drop(&mut self) {
|
||||
if !self.finished_regularly.get() {
|
||||
// It might be sufficient to disable and enable the SPI.. But this definitely
|
||||
@@ -456,68 +515,100 @@ pub struct SpiAsync(pub Spi);
|
||||
|
||||
impl SpiAsync {
|
||||
pub fn new(spi: Spi) -> Self {
|
||||
match spi.inner.id {
|
||||
SpiId::Spi0 => {
|
||||
unsafe fn spi0_interrupt_handler() {
|
||||
unsafe {
|
||||
on_interrupt(SpiId::Spi0);
|
||||
}
|
||||
}
|
||||
crate::register_interrupt(
|
||||
crate::gic::Interrupt::Spi(crate::gic::SpiInterrupt::Spi0),
|
||||
spi0_interrupt_handler,
|
||||
)
|
||||
}
|
||||
SpiId::Spi1 => {
|
||||
unsafe fn spi1_interrupt_handler() {
|
||||
unsafe {
|
||||
on_interrupt(SpiId::Spi1);
|
||||
}
|
||||
}
|
||||
crate::register_interrupt(
|
||||
crate::gic::Interrupt::Spi(crate::gic::SpiInterrupt::Spi1),
|
||||
spi1_interrupt_handler,
|
||||
)
|
||||
}
|
||||
}
|
||||
Self(spi)
|
||||
}
|
||||
|
||||
async fn read(&mut self, words: &mut [u8]) {
|
||||
pub fn read(&mut self, words: &mut [u8]) -> Option<SpiFuture<'_>> {
|
||||
if words.is_empty() {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
let id = self.0.inner.id;
|
||||
let spi_fut = SpiFuture::new_for_read(&mut self.0, id, words);
|
||||
spi_fut.await;
|
||||
Some(SpiFuture::new_for_read(&mut self.0, id, words))
|
||||
}
|
||||
|
||||
async fn write(&mut self, words: &[u8]) {
|
||||
pub fn write(&mut self, words: &[u8]) -> Option<SpiFuture<'_>> {
|
||||
if words.is_empty() {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
let id = self.0.inner.id;
|
||||
let spi_fut = SpiFuture::new_for_write(&mut self.0, id, words);
|
||||
spi_fut.await;
|
||||
Some(SpiFuture::new_for_write(&mut self.0, id, words))
|
||||
}
|
||||
|
||||
async fn transfer(&mut self, read: &mut [u8], write: &[u8]) {
|
||||
pub fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Option<SpiFuture<'_>> {
|
||||
if read.is_empty() || write.is_empty() {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
let id = self.0.inner.id;
|
||||
let spi_fut = SpiFuture::new_for_transfer(&mut self.0, id, read, write);
|
||||
spi_fut.await;
|
||||
Some(SpiFuture::new_for_transfer(&mut self.0, id, read, write))
|
||||
}
|
||||
|
||||
async fn transfer_in_place(&mut self, words: &mut [u8]) {
|
||||
pub fn transfer_in_place(&mut self, words: &mut [u8]) -> Option<SpiFuture<'_>> {
|
||||
if words.is_empty() {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
let id = self.0.inner.id;
|
||||
let spi_fut = SpiFuture::new_for_transfer_in_place(&mut self.0, id, words);
|
||||
spi_fut.await;
|
||||
Some(SpiFuture::new_for_transfer_in_place(&mut self.0, id, words))
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal_async::spi::ErrorType for SpiAsync {
|
||||
type Error = Infallible;
|
||||
type Error = RxOverrunError;
|
||||
}
|
||||
|
||||
impl embedded_hal_async::spi::SpiBus for SpiAsync {
|
||||
async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.read(words).await;
|
||||
if words.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
self.read(words).unwrap().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
|
||||
self.write(words).await;
|
||||
if words.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
self.write(words).unwrap().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
|
||||
self.transfer(read, write).await;
|
||||
if read.is_empty() && write.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
self.transfer(read, write).unwrap().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.transfer_in_place(words).await;
|
||||
if words.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
self.transfer_in_place(words).unwrap().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -547,7 +638,7 @@ impl<Delay: embedded_hal_async::delay::DelayNs> SpiWithHwCsAsync<Delay> {
|
||||
impl<Delay: embedded_hal_async::delay::DelayNs> embedded_hal_async::spi::ErrorType
|
||||
for SpiWithHwCsAsync<Delay>
|
||||
{
|
||||
type Error = Infallible;
|
||||
type Error = RxOverrunError;
|
||||
}
|
||||
|
||||
impl<Delay: embedded_hal_async::delay::DelayNs> embedded_hal_async::spi::SpiDevice
|
||||
@@ -561,16 +652,24 @@ impl<Delay: embedded_hal_async::delay::DelayNs> embedded_hal_async::spi::SpiDevi
|
||||
for op in operations {
|
||||
match op {
|
||||
embedded_hal::spi::Operation::Read(items) => {
|
||||
self.spi.read(items).await;
|
||||
if let Some(fut) = self.spi.read(items) {
|
||||
fut.await?;
|
||||
}
|
||||
}
|
||||
embedded_hal::spi::Operation::Write(items) => {
|
||||
self.spi.write(items).await;
|
||||
if let Some(fut) = self.spi.write(items) {
|
||||
fut.await?;
|
||||
}
|
||||
}
|
||||
embedded_hal::spi::Operation::Transfer(read, write) => {
|
||||
self.spi.transfer(read, write).await;
|
||||
if let Some(fut) = self.spi.transfer(read, write) {
|
||||
fut.await?;
|
||||
}
|
||||
}
|
||||
embedded_hal::spi::Operation::TransferInPlace(items) => {
|
||||
self.spi.transfer_in_place(items).await;
|
||||
if let Some(fut) = self.spi.transfer_in_place(items) {
|
||||
fut.await?;
|
||||
}
|
||||
}
|
||||
embedded_hal::spi::Operation::DelayNs(delay) => {
|
||||
self.delay.delay_ns(*delay).await;
|
||||
|
||||
@@ -18,11 +18,11 @@ use crate::{clocks::IoClocks, slcr::Slcr, time::Hertz};
|
||||
use arbitrary_int::{prelude::*, u3, u4, u6};
|
||||
use embedded_hal::delay::DelayNs;
|
||||
pub use embedded_hal::spi::Mode;
|
||||
use embedded_hal::spi::SpiBus as _;
|
||||
use zynq7000::slcr::reset::DualRefAndClockReset;
|
||||
use zynq7000::slcr::reset::DualRefAndClockResetSpiUart;
|
||||
pub use zynq7000::spi::DelayControl;
|
||||
use zynq7000::spi::{
|
||||
BaudDivSel, DelayControl, FifoWrite, InterruptControl, InterruptMask, InterruptStatus,
|
||||
MmioRegisters, SPI_0_BASE_ADDR, SPI_1_BASE_ADDR,
|
||||
BaudDivSel, FifoWrite, InterruptControl, InterruptEnabled, InterruptStatus, MmioRegisters,
|
||||
SPI_0_BASE_ADDR, SPI_1_BASE_ADDR,
|
||||
};
|
||||
|
||||
pub const FIFO_DEPTH: usize = 128;
|
||||
@@ -353,13 +353,16 @@ impl ChipSelect {
|
||||
/// Slave select configuration.
|
||||
pub enum SlaveSelectConfig {
|
||||
/// User must take care of controlling slave select lines as well as issuing a start command.
|
||||
ManualWithManualStart = 0b11,
|
||||
ManualAutoStart = 0b10,
|
||||
ManualCsManualStart = 0b11,
|
||||
/// Software controls the slave select, but the controller hardware automatically starts to
|
||||
/// serialize data when there is data in the TxFIFO.
|
||||
ManualCsAutoStart = 0b10,
|
||||
/// Hardware slave select, but start needs to be issued manually.
|
||||
AutoWithManualStart = 0b01,
|
||||
/// Hardware slave select, auto serialiation if there is data in the TX FIFO.
|
||||
AutoCsManualStart = 0b01,
|
||||
/// Hardware slave select, auto serialiation if there is data in the TX FIFO. Might be
|
||||
/// problematic for higher SPI speeds, where the processor can not fill the TX FIFO fast enough.
|
||||
#[default]
|
||||
AutoWithAutoStart = 0b00,
|
||||
AutoCsAutoStart = 0b00,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
@@ -380,6 +383,31 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_for_io_clock(
|
||||
target_clock: Hertz,
|
||||
io_clock: &IoClocks,
|
||||
init_mode: Mode,
|
||||
ss_config: SlaveSelectConfig,
|
||||
) -> Self {
|
||||
let divisor_raw = io_clock.spi_clk().to_raw().div_ceil(target_clock.to_raw());
|
||||
let baud_div_sel = match divisor_raw {
|
||||
0..=4 => BaudDivSel::By4,
|
||||
5..=8 => BaudDivSel::By8,
|
||||
9..=16 => BaudDivSel::By16,
|
||||
17..=32 => BaudDivSel::By32,
|
||||
33..=64 => BaudDivSel::By64,
|
||||
65..=128 => BaudDivSel::By128,
|
||||
129..=256 => BaudDivSel::By256,
|
||||
_ => BaudDivSel::By256,
|
||||
};
|
||||
Self {
|
||||
baud_div: baud_div_sel,
|
||||
init_mode,
|
||||
ss_config,
|
||||
with_ext_decoding: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_external_decoding(&mut self) {
|
||||
self.with_ext_decoding = true;
|
||||
}
|
||||
@@ -423,6 +451,18 @@ impl SpiLowLevel {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_ref_clock(&mut self) {
|
||||
// Safety: We only touch register bits of the specified peripheral to enable the clock.
|
||||
unsafe {
|
||||
Slcr::with(|slcr| {
|
||||
slcr.clk_ctrl().modify_spi_clk_ctrl(|val| match self.id {
|
||||
SpiId::Spi0 => val.with_clk_0_act(true),
|
||||
SpiId::Spi1 => val.with_clk_1_act(true),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> SpiId {
|
||||
self.id
|
||||
}
|
||||
@@ -448,7 +488,7 @@ impl SpiLowLevel {
|
||||
/// the external decoding was enabled via the [Config::enable_external_decoding] option.
|
||||
#[inline]
|
||||
pub fn select_hw_cs(&mut self, chip_select: ChipSelect) {
|
||||
self.regs.modify_cr(|mut val| {
|
||||
self.regs.modify_config(|mut val| {
|
||||
val.set_cs_raw(chip_select.raw_reg());
|
||||
val
|
||||
});
|
||||
@@ -458,7 +498,7 @@ impl SpiLowLevel {
|
||||
#[inline]
|
||||
pub fn configure_mode(&mut self, mode: Mode) {
|
||||
let (cpol, cpha) = spi_mode_const_to_cpol_cpha(mode);
|
||||
self.regs.modify_cr(|mut val| {
|
||||
self.regs.modify_config(|mut val| {
|
||||
val.set_cpha(cpha);
|
||||
val.set_cpol(cpol);
|
||||
val
|
||||
@@ -475,14 +515,14 @@ impl SpiLowLevel {
|
||||
pub fn reconfigure(&mut self, config: Config) {
|
||||
self.regs.write_enable(0);
|
||||
let (man_ss, man_start) = match config.ss_config {
|
||||
SlaveSelectConfig::ManualWithManualStart => (true, true),
|
||||
SlaveSelectConfig::ManualAutoStart => (true, false),
|
||||
SlaveSelectConfig::AutoWithManualStart => (false, true),
|
||||
SlaveSelectConfig::AutoWithAutoStart => (false, false),
|
||||
SlaveSelectConfig::ManualCsManualStart => (true, true),
|
||||
SlaveSelectConfig::ManualCsAutoStart => (true, false),
|
||||
SlaveSelectConfig::AutoCsManualStart => (false, true),
|
||||
SlaveSelectConfig::AutoCsAutoStart => (false, false),
|
||||
};
|
||||
let (cpol, cpha) = spi_mode_const_to_cpol_cpha(config.init_mode);
|
||||
|
||||
self.regs.write_cr(
|
||||
self.regs.write_config(
|
||||
zynq7000::spi::Config::builder()
|
||||
.with_modefail_gen_en(false)
|
||||
.with_manual_start(false)
|
||||
@@ -531,20 +571,17 @@ impl SpiLowLevel {
|
||||
|
||||
#[inline]
|
||||
pub fn issue_manual_start(&mut self) {
|
||||
self.regs.modify_cr(|mut val| {
|
||||
val.set_manual_start(true);
|
||||
val
|
||||
});
|
||||
self.regs.modify_config(|val| val.with_manual_start(true));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_isr(&self) -> InterruptStatus {
|
||||
self.regs.read_isr()
|
||||
pub fn read_interrupt_status(&self) -> InterruptStatus {
|
||||
self.regs.read_interrupt_status()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_imr(&self) -> InterruptMask {
|
||||
self.regs.read_imr()
|
||||
pub fn read_enabled_interrupts(&self) -> InterruptEnabled {
|
||||
self.regs.read_enabled_interrupts()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -570,17 +607,22 @@ impl SpiLowLevel {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_delay_control(&mut self, delay_control: DelayControl) {
|
||||
self.regs.write_delay_control(delay_control);
|
||||
}
|
||||
|
||||
/// This disables all interrupts relevant for non-blocking interrupt driven SPI operation
|
||||
/// in SPI master mode.
|
||||
#[inline]
|
||||
pub fn disable_interrupts(&mut self) {
|
||||
self.regs.write_idr(
|
||||
self.regs.write_interupt_disable(
|
||||
InterruptControl::builder()
|
||||
.with_tx_underflow(true)
|
||||
.with_rx_full(true)
|
||||
.with_rx_not_empty(true)
|
||||
.with_tx_full(false)
|
||||
.with_tx_trig(true)
|
||||
.with_tx_below_threshold(true)
|
||||
.with_mode_fault(false)
|
||||
.with_rx_ovr(true)
|
||||
.build(),
|
||||
@@ -590,14 +632,14 @@ impl SpiLowLevel {
|
||||
/// This enables all interrupts relevant for non-blocking interrupt driven SPI operation
|
||||
/// in SPI master mode.
|
||||
#[inline]
|
||||
pub fn enable_interrupts(&mut self) {
|
||||
self.regs.write_ier(
|
||||
pub fn enable_interrupts(&mut self, tx_below_threshold: bool) {
|
||||
self.regs.write_interrupt_enable(
|
||||
InterruptControl::builder()
|
||||
.with_tx_underflow(true)
|
||||
.with_rx_full(true)
|
||||
.with_rx_not_empty(true)
|
||||
.with_tx_full(false)
|
||||
.with_tx_trig(true)
|
||||
.with_tx_below_threshold(tx_below_threshold)
|
||||
.with_mode_fault(false)
|
||||
.with_rx_ovr(true)
|
||||
.build(),
|
||||
@@ -608,26 +650,38 @@ impl SpiLowLevel {
|
||||
/// in SPI master mode.
|
||||
#[inline]
|
||||
pub fn clear_interrupts(&mut self) {
|
||||
self.regs.write_isr(
|
||||
self.regs.write_interrupt_status(
|
||||
InterruptStatus::builder()
|
||||
.with_tx_underflow(true)
|
||||
.with_rx_full(true)
|
||||
.with_rx_not_empty(true)
|
||||
.with_tx_full(false)
|
||||
.with_tx_not_full(true)
|
||||
.with_tx_below_threshold(true)
|
||||
.with_mode_fault(false)
|
||||
.with_rx_ovr(true)
|
||||
.with_rx_overrun(true)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Deref for SpiLowLevel {
|
||||
type Target = MmioRegisters<'static>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.regs
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::DerefMut for SpiLowLevel {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.regs
|
||||
}
|
||||
}
|
||||
|
||||
/// Blocking Driver for the PS SPI peripheral in master mode.
|
||||
pub struct Spi {
|
||||
inner: SpiLowLevel,
|
||||
sclk: Hertz,
|
||||
config: Config,
|
||||
outstanding_rx: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -656,7 +710,6 @@ pub enum SpiConstructionError {
|
||||
impl Spi {
|
||||
pub fn new_no_hw_ss<Sck: SckPin, Mosi: MosiPin, Miso: MisoPin>(
|
||||
spi: impl PsSpi,
|
||||
clocks: &IoClocks,
|
||||
config: Config,
|
||||
spi_pins: (Sck, Mosi, Miso),
|
||||
) -> Result<Self, SpiConstructionError> {
|
||||
@@ -674,17 +727,11 @@ impl Spi {
|
||||
IoPeriphPin::new(spi_pins.0, SPI_MUX_CONF, Some(false));
|
||||
IoPeriphPin::new(spi_pins.1, SPI_MUX_CONF, Some(false));
|
||||
IoPeriphPin::new(spi_pins.2, SPI_MUX_CONF, Some(false));
|
||||
Ok(Self::new_generic_unchecked(
|
||||
spi_id,
|
||||
spi.reg_block(),
|
||||
clocks,
|
||||
config,
|
||||
))
|
||||
Ok(Self::new_generic_unchecked(spi_id, spi.reg_block(), config))
|
||||
}
|
||||
|
||||
pub fn new_one_hw_cs<Sck: SckPin, Mosi: MosiPin, Miso: MisoPin, Ss: SsPin>(
|
||||
spi: impl PsSpi,
|
||||
clocks: &IoClocks,
|
||||
config: Config,
|
||||
spi_pins: (Sck, Mosi, Miso),
|
||||
ss_pin: Ss,
|
||||
@@ -704,17 +751,11 @@ impl Spi {
|
||||
IoPeriphPin::new(spi_pins.1, SPI_MUX_CONF, Some(false));
|
||||
IoPeriphPin::new(spi_pins.2, SPI_MUX_CONF, Some(false));
|
||||
IoPeriphPin::new(ss_pin, SPI_MUX_CONF, Some(false));
|
||||
Ok(Self::new_generic_unchecked(
|
||||
spi_id,
|
||||
spi.reg_block(),
|
||||
clocks,
|
||||
config,
|
||||
))
|
||||
Ok(Self::new_generic_unchecked(spi_id, spi.reg_block(), config))
|
||||
}
|
||||
|
||||
pub fn new_with_two_hw_cs<Sck: SckPin, Mosi: MosiPin, Miso: MisoPin, Ss0: SsPin, Ss1: SsPin>(
|
||||
spi: impl PsSpi,
|
||||
clocks: &IoClocks,
|
||||
config: Config,
|
||||
spi_pins: (Sck, Mosi, Miso),
|
||||
ss_pins: (Ss0, Ss1),
|
||||
@@ -744,12 +785,7 @@ impl Spi {
|
||||
IoPeriphPin::new(spi_pins.2, SPI_MUX_CONF, Some(false));
|
||||
IoPeriphPin::new(ss_pins.0, SPI_MUX_CONF, Some(false));
|
||||
IoPeriphPin::new(ss_pins.1, SPI_MUX_CONF, Some(false));
|
||||
Ok(Self::new_generic_unchecked(
|
||||
spi_id,
|
||||
spi.reg_block(),
|
||||
clocks,
|
||||
config,
|
||||
))
|
||||
Ok(Self::new_generic_unchecked(spi_id, spi.reg_block(), config))
|
||||
}
|
||||
|
||||
pub fn new_with_three_hw_cs<
|
||||
@@ -761,7 +797,6 @@ impl Spi {
|
||||
Ss2: SsPin,
|
||||
>(
|
||||
spi: impl PsSpi,
|
||||
clocks: &IoClocks,
|
||||
config: Config,
|
||||
spi_pins: (Sck, Mosi, Miso),
|
||||
ss_pins: (Ss0, Ss1, Ss2),
|
||||
@@ -794,36 +829,40 @@ impl Spi {
|
||||
IoPeriphPin::new(ss_pins.0, SPI_MUX_CONF, Some(false));
|
||||
IoPeriphPin::new(ss_pins.1, SPI_MUX_CONF, Some(false));
|
||||
IoPeriphPin::new(ss_pins.2, SPI_MUX_CONF, Some(false));
|
||||
Ok(Self::new_generic_unchecked(spi_id, spi.reg_block(), config))
|
||||
}
|
||||
|
||||
/// Constructor for usage with EMIO pins.
|
||||
pub fn new_for_emio(spi: impl PsSpi, config: Config) -> Result<Self, InvalidPsSpiError> {
|
||||
let spi_id = spi.id();
|
||||
if spi_id.is_none() {
|
||||
return Err(InvalidPsSpiError);
|
||||
}
|
||||
Ok(Self::new_generic_unchecked(
|
||||
spi_id,
|
||||
spi_id.unwrap(),
|
||||
spi.reg_block(),
|
||||
clocks,
|
||||
config,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_generic_unchecked(
|
||||
id: SpiId,
|
||||
regs: MmioRegisters<'static>,
|
||||
clocks: &IoClocks,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
pub fn new_generic_unchecked(id: SpiId, regs: MmioRegisters<'static>, config: Config) -> Self {
|
||||
let periph_sel = match id {
|
||||
SpiId::Spi0 => crate::PeriphSelect::Spi0,
|
||||
SpiId::Spi1 => crate::PeriphSelect::Spi1,
|
||||
};
|
||||
|
||||
let mut ll = SpiLowLevel { id, regs };
|
||||
ll.enable_ref_clock();
|
||||
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 },
|
||||
sclk,
|
||||
config,
|
||||
outstanding_rx: false,
|
||||
};
|
||||
let mut spi = Self { inner: ll, config };
|
||||
spi.reset_and_reconfigure();
|
||||
spi
|
||||
}
|
||||
|
||||
pub fn write_delay_control(&mut self, delay_control: DelayControl) {
|
||||
self.inner.write_delay_control(delay_control);
|
||||
}
|
||||
|
||||
/// Re-configures the SPI peripheral with the initial configuration.
|
||||
pub fn reconfigure(&mut self) {
|
||||
self.inner.reconfigure(self.config);
|
||||
@@ -839,19 +878,13 @@ impl Spi {
|
||||
|
||||
#[inline]
|
||||
pub fn issue_manual_start_for_manual_cfg(&mut self) {
|
||||
if self.config.ss_config == SlaveSelectConfig::AutoWithManualStart
|
||||
|| self.config.ss_config == SlaveSelectConfig::ManualWithManualStart
|
||||
if self.config.ss_config == SlaveSelectConfig::AutoCsManualStart
|
||||
|| self.config.ss_config == SlaveSelectConfig::ManualCsManualStart
|
||||
{
|
||||
self.inner.issue_manual_start();
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve SCLK clock frequency currently configured for this SPI.
|
||||
#[inline]
|
||||
pub const fn sclk(&self) -> Hertz {
|
||||
self.sclk
|
||||
}
|
||||
|
||||
/// Retrieve inner low-level helper.
|
||||
#[inline]
|
||||
pub const fn inner(&mut self) -> &mut SpiLowLevel {
|
||||
@@ -863,7 +896,7 @@ impl Spi {
|
||||
&mut self.inner.regs
|
||||
}
|
||||
|
||||
fn initial_fifo_fill(&mut self, words: &[u8]) -> usize {
|
||||
fn prefill_fifo(&mut self, words: &[u8]) -> usize {
|
||||
let write_len = core::cmp::min(FIFO_DEPTH, words.len());
|
||||
(0..write_len).for_each(|idx| {
|
||||
self.inner.write_fifo_unchecked(words[idx]);
|
||||
@@ -874,32 +907,26 @@ impl Spi {
|
||||
fn prepare_generic_blocking_transfer(&mut self, words: &[u8]) -> usize {
|
||||
// We want to ensure the FIFO is empty for a new transfer. This is the simpler
|
||||
// implementation for now.
|
||||
self.flush().unwrap();
|
||||
self.flush();
|
||||
// Write this to 1 in any case to allow polling, defensive programming.
|
||||
self.inner.regs.write_rx_trig(1);
|
||||
|
||||
// Fill the FIFO with initial data.
|
||||
let written = self.initial_fifo_fill(words);
|
||||
let written = self.prefill_fifo(words);
|
||||
|
||||
// We assume that the slave select configuration was already performed, but we take
|
||||
// care of issuing a start if necessary.
|
||||
self.issue_manual_start_for_manual_cfg();
|
||||
written
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal::spi::ErrorType for Spi {
|
||||
type Error = Infallible;
|
||||
}
|
||||
|
||||
impl embedded_hal::spi::SpiBus for Spi {
|
||||
fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
|
||||
pub fn read(&mut self, words: &mut [u8]) {
|
||||
if words.is_empty() {
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
// We want to ensure the FIFO is empty for a new transfer. This is the simpler
|
||||
// implementation for now.
|
||||
self.flush()?;
|
||||
self.flush();
|
||||
// Write this to 1 in any case to allow polling, defensive programming.
|
||||
self.regs().write_rx_trig(1);
|
||||
|
||||
@@ -915,7 +942,7 @@ impl embedded_hal::spi::SpiBus for Spi {
|
||||
|
||||
let mut read_idx = 0;
|
||||
while read_idx < words.len() {
|
||||
let status = self.regs().read_isr();
|
||||
let status = self.regs().read_interrupt_status();
|
||||
if status.rx_not_empty() {
|
||||
words[read_idx] = self.inner.read_fifo_unchecked();
|
||||
read_idx += 1;
|
||||
@@ -926,40 +953,42 @@ impl embedded_hal::spi::SpiBus for Spi {
|
||||
write_idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
|
||||
pub fn write(&mut self, words: &[u8]) {
|
||||
if words.is_empty() {
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
let mut written = self.prepare_generic_blocking_transfer(words);
|
||||
let mut read_idx = 0;
|
||||
if words.len() > FIFO_DEPTH {
|
||||
self.inner.regs.write_tx_trig(FIFO_DEPTH as u32 / 2);
|
||||
}
|
||||
|
||||
while written < words.len() {
|
||||
let status = self.regs().read_isr();
|
||||
loop {
|
||||
let status = self.regs().read_interrupt_status();
|
||||
let rx_pending = read_idx < words.len();
|
||||
let tx_pending = written < words.len();
|
||||
// We empty the FIFO to prevent it filling up completely, as long as we have to write
|
||||
// bytes
|
||||
if status.rx_not_empty() {
|
||||
if status.rx_not_empty() && rx_pending {
|
||||
self.inner.read_fifo_unchecked();
|
||||
read_idx += 1;
|
||||
}
|
||||
if !status.tx_full() {
|
||||
if !status.tx_full() && tx_pending {
|
||||
self.inner.write_fifo_unchecked(words[written]);
|
||||
written += 1;
|
||||
}
|
||||
if !rx_pending && !tx_pending {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// We exit once all bytes have been written, so some bytes to read might be outstanding.
|
||||
// We use the FIFO trigger mechanism to determine when we can read all the remaining bytes.
|
||||
self.regs().write_rx_trig((words.len() - read_idx) as u32);
|
||||
self.outstanding_rx = true;
|
||||
Ok(())
|
||||
self.inner.regs.write_tx_trig(1);
|
||||
}
|
||||
|
||||
fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
|
||||
pub fn transfer(&mut self, read: &mut [u8], write: &[u8]) {
|
||||
if read.is_empty() {
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
let mut write_idx = self.prepare_generic_blocking_transfer(write);
|
||||
let mut read_idx = 0;
|
||||
@@ -968,7 +997,7 @@ impl embedded_hal::spi::SpiBus for Spi {
|
||||
let mut writes_finished = write_idx == max_idx;
|
||||
let mut reads_finished = false;
|
||||
while !writes_finished || !reads_finished {
|
||||
let status = self.regs().read_isr();
|
||||
let status = self.regs().read_interrupt_status();
|
||||
if status.rx_not_empty() && !reads_finished {
|
||||
if read_idx < read.len() {
|
||||
read[read_idx] = self.inner.read_fifo_unchecked();
|
||||
@@ -991,13 +1020,11 @@ impl embedded_hal::spi::SpiBus for Spi {
|
||||
writes_finished = write_idx == max_idx;
|
||||
reads_finished = read_idx == max_idx;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
|
||||
pub fn transfer_in_place(&mut self, words: &mut [u8]) {
|
||||
if words.is_empty() {
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
let mut write_idx = self.prepare_generic_blocking_transfer(words);
|
||||
let mut read_idx = 0;
|
||||
@@ -1005,7 +1032,7 @@ impl embedded_hal::spi::SpiBus for Spi {
|
||||
let mut writes_finished = write_idx == words.len();
|
||||
let mut reads_finished = false;
|
||||
while !writes_finished || !reads_finished {
|
||||
let status = self.inner.read_isr();
|
||||
let status = self.inner.read_interrupt_status();
|
||||
if status.rx_not_empty() && !reads_finished {
|
||||
words[read_idx] = self.inner.read_fifo_unchecked();
|
||||
read_idx += 1;
|
||||
@@ -1018,22 +1045,51 @@ impl embedded_hal::spi::SpiBus for Spi {
|
||||
writes_finished = write_idx == words.len();
|
||||
reads_finished = read_idx == words.len();
|
||||
}
|
||||
}
|
||||
|
||||
/// Blocking flush implementation.
|
||||
fn flush(&mut self) {
|
||||
self.inner.write_tx_trig(1);
|
||||
let status = self.inner.read_interrupt_status();
|
||||
while self.inner.read_interrupt_status().rx_not_empty() {
|
||||
self.inner.read_fifo_unchecked();
|
||||
}
|
||||
while status.tx_full() {
|
||||
while self.inner.read_interrupt_status().rx_not_empty() {
|
||||
self.inner.read_fifo_unchecked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal::spi::ErrorType for Spi {
|
||||
type Error = Infallible;
|
||||
}
|
||||
|
||||
impl embedded_hal::spi::SpiBus for Spi {
|
||||
fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
|
||||
Self::read(self, words);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
|
||||
Self::write(self, words);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
|
||||
Self::transfer(self, read, write);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
|
||||
Self::transfer_in_place(self, words);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Blocking flush implementation.
|
||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
if !self.outstanding_rx {
|
||||
return Ok(());
|
||||
}
|
||||
let rx_trig = self.inner.read_rx_not_empty_threshold();
|
||||
while !self.inner.read_isr().rx_not_empty() {}
|
||||
(0..rx_trig).for_each(|_| {
|
||||
self.inner.read_fifo_unchecked();
|
||||
});
|
||||
self.inner.set_rx_fifo_trigger(1).unwrap();
|
||||
self.outstanding_rx = false;
|
||||
Self::flush(self);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1067,23 +1123,23 @@ impl<Delay: DelayNs> embedded_hal::spi::SpiDevice for SpiWithHwCs<Delay> {
|
||||
for op in operations {
|
||||
match op {
|
||||
embedded_hal::spi::Operation::Read(items) => {
|
||||
self.spi.read(items)?;
|
||||
self.spi.read(items);
|
||||
}
|
||||
embedded_hal::spi::Operation::Write(items) => {
|
||||
self.spi.write(items)?;
|
||||
self.spi.write(items);
|
||||
}
|
||||
embedded_hal::spi::Operation::Transfer(read, write) => {
|
||||
self.spi.transfer(read, write)?;
|
||||
self.spi.transfer(read, write);
|
||||
}
|
||||
embedded_hal::spi::Operation::TransferInPlace(items) => {
|
||||
self.spi.transfer_in_place(items)?;
|
||||
self.spi.transfer_in_place(items);
|
||||
}
|
||||
embedded_hal::spi::Operation::DelayNs(delay) => {
|
||||
self.delay.delay_ns(*delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.spi.flush()?;
|
||||
self.spi.flush();
|
||||
self.spi.inner.no_hw_cs();
|
||||
Ok(())
|
||||
}
|
||||
@@ -1096,13 +1152,13 @@ impl<Delay: DelayNs> embedded_hal::spi::SpiDevice for SpiWithHwCs<Delay> {
|
||||
#[inline]
|
||||
pub fn reset(id: SpiId) {
|
||||
let assert_reset = match id {
|
||||
SpiId::Spi0 => DualRefAndClockReset::builder()
|
||||
SpiId::Spi0 => DualRefAndClockResetSpiUart::builder()
|
||||
.with_periph1_ref_rst(false)
|
||||
.with_periph0_ref_rst(true)
|
||||
.with_periph1_cpu1x_rst(false)
|
||||
.with_periph0_cpu1x_rst(true)
|
||||
.build(),
|
||||
SpiId::Spi1 => DualRefAndClockReset::builder()
|
||||
SpiId::Spi1 => DualRefAndClockResetSpiUart::builder()
|
||||
.with_periph1_ref_rst(true)
|
||||
.with_periph0_ref_rst(false)
|
||||
.with_periph1_cpu1x_rst(true)
|
||||
@@ -1114,10 +1170,11 @@ pub fn reset(id: SpiId) {
|
||||
regs.reset_ctrl().write_spi(assert_reset);
|
||||
// Keep it in reset for some cycles.. The TMR just mentions some small delay,
|
||||
// no idea what is meant with that.
|
||||
for _ in 0..3 {
|
||||
for _ in 0..10 {
|
||||
aarch32_cpu::asm::nop();
|
||||
}
|
||||
regs.reset_ctrl().write_spi(DualRefAndClockReset::DEFAULT);
|
||||
regs.reset_ctrl()
|
||||
.write_spi(DualRefAndClockResetSpiUart::ZERO);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1127,23 +1184,23 @@ pub fn reset(id: SpiId) {
|
||||
/// The Zynq7000 SPI peripheral has the following requirement for the SPI reference clock:
|
||||
/// It must be larger than the CPU 1X clock. Therefore, the divisor used to calculate the reference
|
||||
/// clock has a maximum value, which can be calculated with this function.
|
||||
/// [configure_spi_ref_clock_with_divisor] can be used to configure the SPI reference clock with a
|
||||
/// divisor.
|
||||
///
|
||||
/// [configure_spi_ref_clk] can be used to configure the SPI reference clock with the calculated
|
||||
/// value.
|
||||
/// *NOTE* - It is recommended to avoid the largest theoretical value which was proven to be
|
||||
/// problematic for driving certain sensors and instead take a smaller value! Reduce the divisor
|
||||
/// calculated by this function subtracting a small value to get a functioning SPI clock.
|
||||
pub fn calculate_largest_allowed_spi_ref_clk_divisor(clks: &Clocks) -> Option<u6> {
|
||||
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() {
|
||||
let ref_clock = 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()
|
||||
}
|
||||
zynq7000::slcr::clocks::SrcSelIo::ArmPll => {
|
||||
clks.arm_clocks().ref_clk() / clks.arm_clocks().cpu_1x_clk()
|
||||
}
|
||||
zynq7000::slcr::clocks::SrcSelIo::DdrPll => {
|
||||
clks.ddr_clocks().ref_clk() / clks.arm_clocks().cpu_1x_clk()
|
||||
clks.io_clocks().ref_clk().to_raw()
|
||||
}
|
||||
zynq7000::slcr::clocks::SrcSelIo::ArmPll => clks.arm_clocks().ref_clk().to_raw(),
|
||||
zynq7000::slcr::clocks::SrcSelIo::DdrPll => clks.ddr_clocks().ref_clk().to_raw(),
|
||||
};
|
||||
let div = ref_clock.div_ceil(clks.arm_clocks().cpu_1x_clk().to_raw());
|
||||
if div > u6::MAX.value() as u32 {
|
||||
return None;
|
||||
}
|
||||
@@ -1151,7 +1208,28 @@ pub fn calculate_largest_allowed_spi_ref_clk_divisor(clks: &Clocks) -> Option<u6
|
||||
Some(u6::new(div as u8))
|
||||
}
|
||||
|
||||
pub fn configure_spi_ref_clk(clks: &mut Clocks, divisor: u6) {
|
||||
/// Configures the SPI reference clock.
|
||||
///
|
||||
/// It is strongly advised to take a clock value which is substantially higher than the CPU 1x
|
||||
/// clock. It was proven that taking values which are only slightly larger than the CPU 1x
|
||||
/// clock are problematic for driving ceratin devices.
|
||||
pub fn configure_spi_ref_clock(clks: &mut Clocks, target_clock: Hertz) {
|
||||
let slcr = unsafe { Slcr::steal() };
|
||||
let spi_clk_ctrl = slcr.regs().clk_ctrl_shared().read_spi_clk_ctrl();
|
||||
let ref_clk = match spi_clk_ctrl.srcsel() {
|
||||
zynq7000::slcr::clocks::SrcSelIo::IoPll | zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => {
|
||||
clks.io_clocks().ref_clk().to_raw()
|
||||
}
|
||||
zynq7000::slcr::clocks::SrcSelIo::ArmPll => clks.arm_clocks().ref_clk().to_raw(),
|
||||
zynq7000::slcr::clocks::SrcSelIo::DdrPll => clks.ddr_clocks().ref_clk().to_raw(),
|
||||
};
|
||||
let div = ref_clk.div_ceil(target_clock.to_raw());
|
||||
if div > u6::MAX.value() as u32 {
|
||||
configure_spi_ref_clock_with_divisor(clks, u6::new(div as u8));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure_spi_ref_clock_with_divisor(clks: &mut Clocks, divisor: u6) {
|
||||
let mut slcr = unsafe { Slcr::steal() };
|
||||
let spi_clk_ctrl = slcr.regs().clk_ctrl_shared().read_spi_clk_ctrl();
|
||||
slcr.modify(|regs| {
|
||||
|
||||
@@ -275,7 +275,7 @@ impl Pwm {
|
||||
ref_clk: Hertz,
|
||||
freq: Hertz,
|
||||
) -> Result<Self, FrequencyIsZeroError> {
|
||||
if freq.raw() == 0 {
|
||||
if freq.to_raw() == 0 {
|
||||
return Err(FrequencyIsZeroError);
|
||||
}
|
||||
let (prescaler_reg, tick_val) = calc_prescaler_reg_and_interval_ticks(ref_clk, freq);
|
||||
@@ -289,7 +289,7 @@ impl Pwm {
|
||||
///
|
||||
/// This resets the duty cycle to 0%.
|
||||
pub fn set_frequency(&mut self, freq: Hertz) -> Result<(), FrequencyIsZeroError> {
|
||||
if freq.raw() == 0 {
|
||||
if freq.to_raw() == 0 {
|
||||
return Err(FrequencyIsZeroError);
|
||||
}
|
||||
let id = self.channel.id() as usize;
|
||||
|
||||
@@ -13,7 +13,7 @@ use core::convert::Infallible;
|
||||
use arbitrary_int::u3;
|
||||
use libm::round;
|
||||
use zynq7000::{
|
||||
slcr::reset::DualRefAndClockReset,
|
||||
slcr::reset::DualRefAndClockResetSpiUart,
|
||||
uart::{
|
||||
BaudRateDivisor, Baudgen, ChMode, ClockSelect, FifoTrigger, InterruptControl,
|
||||
MmioRegisters, Mode, UART_0_BASE, UART_1_BASE,
|
||||
@@ -141,7 +141,7 @@ pub struct DivisorZero;
|
||||
macro_rules! pin_pairs {
|
||||
($index:literal, $UartPeriph:path, ($( [$(#[$meta:meta], )? $TxMio:ident, $RxMio:ident] ),+ $(,)? )) => {
|
||||
$(
|
||||
paste::paste! {
|
||||
pastey::paste! {
|
||||
$( #[$meta] )?
|
||||
impl [<TxPin $index>] for Pin<$TxMio> {}
|
||||
|
||||
@@ -254,8 +254,8 @@ pub fn calculate_viable_configs(
|
||||
}
|
||||
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;
|
||||
let cd = round(uart_clk.to_raw() as f64 / ((bdiv as u32 + 1) as f64 * target_baud as f64))
|
||||
as u64;
|
||||
if cd > u16::MAX as u64 {
|
||||
continue;
|
||||
}
|
||||
@@ -290,8 +290,8 @@ pub fn calculate_raw_baud_cfg_smallest_error(
|
||||
let mut best_clk_config = ClockConfig::default();
|
||||
let mut smallest_error: f64 = 100.0;
|
||||
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;
|
||||
let cd = round(uart_clk.to_raw() as f64 / ((bdiv as u32 + 1) as f64 * target_baud as f64))
|
||||
as u64;
|
||||
if cd > u16::MAX as u64 {
|
||||
continue;
|
||||
}
|
||||
@@ -369,7 +369,7 @@ impl ClockConfig {
|
||||
/// Actual baudrate.
|
||||
#[inline]
|
||||
pub fn actual_baud(&self, sel_clk: Hertz) -> f64 {
|
||||
sel_clk.raw() as f64 / (self.cd as f64 * (self.bdiv + 1) as f64)
|
||||
sel_clk.to_raw() as f64 / (self.cd as f64 * (self.bdiv + 1) as f64)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,35 +565,40 @@ impl Uart {
|
||||
UartId::Uart0 => crate::PeriphSelect::Uart0,
|
||||
UartId::Uart1 => crate::PeriphSelect::Uart1,
|
||||
};
|
||||
// Safety: We only touch register bits of the specified peripheral to enable the clock.
|
||||
unsafe {
|
||||
Slcr::with(|slcr| {
|
||||
slcr.clk_ctrl().modify_uart_clk_ctrl(|val| match uart_id {
|
||||
UartId::Uart0 => val.with_clk_0_act(true),
|
||||
UartId::Uart1 => val.with_clk_1_act(true),
|
||||
});
|
||||
});
|
||||
}
|
||||
enable_amba_peripheral_clock(periph_sel);
|
||||
reset(uart_id);
|
||||
reg_block.modify_cr(|mut v| {
|
||||
v.set_tx_dis(true);
|
||||
v.set_rx_dis(true);
|
||||
v
|
||||
});
|
||||
reg_block.modify_control(|v| v.with_tx_disable(true).with_rx_disable(true));
|
||||
// Disable all interrupts.
|
||||
reg_block.write_idr(InterruptControl::new_with_raw_value(0xFFFF_FFFF));
|
||||
reg_block.write_interrupt_disable(InterruptControl::new_with_raw_value(0xFFFF_FFFF));
|
||||
let mode = Mode::builder()
|
||||
.with_chmode(cfg.chmode)
|
||||
.with_nbstop(match cfg.stopbits {
|
||||
.with_stopbits(match cfg.stopbits {
|
||||
Stopbits::One => zynq7000::uart::Stopbits::One,
|
||||
Stopbits::OnePointFive => zynq7000::uart::Stopbits::OnePointFive,
|
||||
Stopbits::Two => zynq7000::uart::Stopbits::Two,
|
||||
})
|
||||
.with_par(match cfg.parity {
|
||||
.with_parity(match cfg.parity {
|
||||
Parity::Even => zynq7000::uart::Parity::Even,
|
||||
Parity::Odd => zynq7000::uart::Parity::Odd,
|
||||
Parity::None => zynq7000::uart::Parity::NoParity,
|
||||
})
|
||||
.with_chrl(match cfg.chrl {
|
||||
.with_charlen(match cfg.chrl {
|
||||
CharLen::SixBits => zynq7000::uart::CharLen::SixBits,
|
||||
CharLen::SevenBits => zynq7000::uart::CharLen::SevenBits,
|
||||
CharLen::EightBits => zynq7000::uart::CharLen::EightBits,
|
||||
})
|
||||
.with_clksel(cfg.clk_sel)
|
||||
.with_clock_select(cfg.clk_sel)
|
||||
.build();
|
||||
reg_block.write_mr(mode);
|
||||
reg_block.write_mode(mode);
|
||||
reg_block.write_baudgen(
|
||||
Baudgen::builder()
|
||||
.with_cd(cfg.raw_clk_config().cd())
|
||||
@@ -605,9 +610,9 @@ impl Uart {
|
||||
.build(),
|
||||
);
|
||||
// Soft reset for both TX and RX.
|
||||
reg_block.modify_cr(|mut v| {
|
||||
v.set_tx_rst(true);
|
||||
v.set_rx_rst(true);
|
||||
reg_block.modify_control(|mut v| {
|
||||
v.set_tx_reset(true);
|
||||
v.set_rx_reset(true);
|
||||
v
|
||||
});
|
||||
|
||||
@@ -617,11 +622,11 @@ impl Uart {
|
||||
));
|
||||
|
||||
// Enable TX and RX.
|
||||
reg_block.modify_cr(|mut v| {
|
||||
v.set_tx_dis(false);
|
||||
v.set_rx_dis(false);
|
||||
v.set_tx_en(true);
|
||||
v.set_rx_en(true);
|
||||
reg_block.modify_control(|mut v| {
|
||||
v.set_tx_disable(false);
|
||||
v.set_rx_disable(false);
|
||||
v.set_tx_enable(true);
|
||||
v.set_rx_enable(true);
|
||||
v
|
||||
});
|
||||
|
||||
@@ -631,7 +636,7 @@ impl Uart {
|
||||
},
|
||||
tx: Tx {
|
||||
regs: reg_block,
|
||||
idx: uart_id,
|
||||
id: uart_id,
|
||||
},
|
||||
cfg,
|
||||
}
|
||||
@@ -640,7 +645,7 @@ impl Uart {
|
||||
/// Set character mode.
|
||||
#[inline]
|
||||
pub fn set_mode(&mut self, mode: ChMode) {
|
||||
self.regs().modify_mr(|mut mr| {
|
||||
self.regs().modify_mode(|mut mr| {
|
||||
mr.set_chmode(mode);
|
||||
mr
|
||||
});
|
||||
@@ -720,13 +725,13 @@ impl embedded_io::Read for Uart {
|
||||
#[inline]
|
||||
pub fn reset(id: UartId) {
|
||||
let assert_reset = match id {
|
||||
UartId::Uart0 => DualRefAndClockReset::builder()
|
||||
UartId::Uart0 => DualRefAndClockResetSpiUart::builder()
|
||||
.with_periph1_ref_rst(false)
|
||||
.with_periph0_ref_rst(true)
|
||||
.with_periph1_cpu1x_rst(false)
|
||||
.with_periph0_cpu1x_rst(true)
|
||||
.build(),
|
||||
UartId::Uart1 => DualRefAndClockReset::builder()
|
||||
UartId::Uart1 => DualRefAndClockResetSpiUart::builder()
|
||||
.with_periph1_ref_rst(true)
|
||||
.with_periph0_ref_rst(false)
|
||||
.with_periph1_cpu1x_rst(true)
|
||||
@@ -736,9 +741,12 @@ pub fn reset(id: UartId) {
|
||||
unsafe {
|
||||
Slcr::with(|regs| {
|
||||
regs.reset_ctrl().write_uart(assert_reset);
|
||||
// Keep it in reset for one cycle.. not sure if this is necessary.
|
||||
aarch32_cpu::asm::nop();
|
||||
regs.reset_ctrl().write_uart(DualRefAndClockReset::DEFAULT);
|
||||
// Keep it in reset for a few cycles.. not sure if this is necessary.
|
||||
for _ in 0..5 {
|
||||
aarch32_cpu::asm::nop();
|
||||
}
|
||||
regs.reset_ctrl()
|
||||
.write_uart(DualRefAndClockResetSpiUart::ZERO);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use core::convert::Infallible;
|
||||
|
||||
use arbitrary_int::prelude::*;
|
||||
use zynq7000::uart::{InterruptControl, InterruptStatus, MmioRegisters};
|
||||
use zynq7000::uart::{FifoTrigger, InterruptControl, InterruptStatus, MmioRegisters};
|
||||
|
||||
use super::FIFO_DEPTH;
|
||||
|
||||
@@ -73,7 +73,7 @@ impl Rx {
|
||||
/// Read one byte from the FIFO in a non-blocking manner.
|
||||
#[inline]
|
||||
pub fn read_fifo(&mut self) -> nb::Result<u8, Infallible> {
|
||||
if self.regs.read_sr().rx_empty() {
|
||||
if self.regs.read_status().rx_empty() {
|
||||
return Err(nb::Error::WouldBlock);
|
||||
}
|
||||
Ok(self.regs.read_fifo().fifo())
|
||||
@@ -93,17 +93,17 @@ impl Rx {
|
||||
/// bit clock, so this value times 4 is the number of UART clock ticks until a timeout occurs.
|
||||
#[inline]
|
||||
pub fn set_rx_timeout_value(&mut self, rto: u8) {
|
||||
self.regs.write_rx_tout(rto as u32);
|
||||
self.regs.write_rx_timeout(rto as u32);
|
||||
}
|
||||
|
||||
/// Perform a soft-reset of the RX side of the UART.
|
||||
#[inline]
|
||||
pub fn soft_reset(&mut self) {
|
||||
self.regs.modify_cr(|mut cr| {
|
||||
cr.set_rx_rst(true);
|
||||
self.regs.modify_control(|mut cr| {
|
||||
cr.set_rx_reset(true);
|
||||
cr
|
||||
});
|
||||
while self.regs.read_cr().rx_rst() {}
|
||||
while self.regs.read_control().rx_reset() {}
|
||||
}
|
||||
|
||||
/// Helper function to start the interrupt driven reception of data.
|
||||
@@ -114,22 +114,34 @@ impl Rx {
|
||||
///
|
||||
/// This should be called once at system start-up. After that, you only need to call
|
||||
/// [Self::on_interrupt] in the interrupt handler for the UART peripheral.
|
||||
pub fn start_interrupt_driven_reception(&mut self) {
|
||||
///
|
||||
/// You can also configure a RX timeout by setting the RX timeout value `rto` which has a unit
|
||||
/// of bit periods times 4. Setting a value of 0 disables the timeout feature of the hardware,
|
||||
/// but this is strongly discouraged.
|
||||
pub fn start_interrupt_driven_reception(&mut self, rto: u8) {
|
||||
self.soft_reset();
|
||||
self.set_rx_fifo_trigger_level((FIFO_DEPTH / 2) as u8);
|
||||
self.set_rx_timeout_value(rto);
|
||||
self.clear_interrupts();
|
||||
self.enable_interrupts();
|
||||
}
|
||||
|
||||
/// Sets the RX FIFO trigger level.
|
||||
pub fn set_rx_fifo_trigger_level(&mut self, level: u8) {
|
||||
self.regs
|
||||
.write_rx_fifo_trigger(FifoTrigger::new_with_raw_value(level as u32));
|
||||
}
|
||||
|
||||
/// Enables all interrupts relevant for the RX side of the UART.
|
||||
///
|
||||
/// It is recommended to also clear all interrupts immediately after enabling them.
|
||||
#[inline]
|
||||
pub fn enable_interrupts(&mut self) {
|
||||
self.regs.write_ier(
|
||||
self.regs.write_interrupt_enable(
|
||||
InterruptControl::builder()
|
||||
.with_tx_over(false)
|
||||
.with_tx_near_full(false)
|
||||
.with_tx_trig(false)
|
||||
.with_tx_trigger(false)
|
||||
.with_rx_dms(false)
|
||||
.with_rx_timeout(true)
|
||||
.with_rx_parity(true)
|
||||
@@ -139,7 +151,7 @@ impl Rx {
|
||||
.with_tx_empty(false)
|
||||
.with_rx_full(true)
|
||||
.with_rx_empty(false)
|
||||
.with_rx_trg(true)
|
||||
.with_rx_trigger(true)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
@@ -153,9 +165,9 @@ impl Rx {
|
||||
reset_rx_timeout: bool,
|
||||
) -> RxInterruptResult {
|
||||
let mut result = RxInterruptResult::default();
|
||||
let imr = self.regs.read_imr();
|
||||
let imr = self.regs.read_enabled_interrupts();
|
||||
if !imr.rx_full()
|
||||
&& !imr.rx_trg()
|
||||
&& !imr.rx_trigger()
|
||||
&& !imr.rx_parity()
|
||||
&& !imr.rx_framing()
|
||||
&& !imr.rx_over()
|
||||
@@ -163,16 +175,10 @@ impl Rx {
|
||||
{
|
||||
return result;
|
||||
}
|
||||
let isr = self.regs.read_isr();
|
||||
if isr.rx_full() {
|
||||
// Read all bytes in the full RX fifo.
|
||||
for byte in buf.iter_mut() {
|
||||
*byte = self.read_fifo_unchecked();
|
||||
}
|
||||
result.read_bytes = FIFO_DEPTH;
|
||||
} else if isr.rx_trg() {
|
||||
let isr = self.regs.read_interrupt_status();
|
||||
if self.regs.read_interrupt_status().rx_trigger() {
|
||||
// It is guaranteed that we can read the FIFO level amount of data
|
||||
let fifo_trigger = self.regs.read_rx_fifo_trigger().trig().as_usize();
|
||||
let fifo_trigger = self.regs.read_rx_fifo_trigger().trigger().as_usize();
|
||||
(0..fifo_trigger).for_each(|i| {
|
||||
buf[i] = self.read_fifo_unchecked();
|
||||
});
|
||||
@@ -197,8 +203,8 @@ impl Rx {
|
||||
}
|
||||
// Handle timeout event.
|
||||
if isr.rx_timeout() && reset_rx_timeout {
|
||||
self.regs.modify_cr(|mut cr| {
|
||||
cr.set_rstto(true);
|
||||
self.regs.modify_control(|mut cr| {
|
||||
cr.set_restart_timeout(true);
|
||||
cr
|
||||
});
|
||||
}
|
||||
@@ -209,7 +215,7 @@ impl Rx {
|
||||
/// This clears all RX related interrupts.
|
||||
#[inline]
|
||||
pub fn clear_interrupts(&mut self) {
|
||||
self.regs.write_isr(
|
||||
self.regs.write_interrupt_status(
|
||||
InterruptStatus::builder()
|
||||
.with_tx_over(false)
|
||||
.with_tx_near_full(false)
|
||||
@@ -223,7 +229,7 @@ impl Rx {
|
||||
.with_tx_empty(false)
|
||||
.with_rx_full(true)
|
||||
.with_rx_empty(true)
|
||||
.with_rx_trg(true)
|
||||
.with_rx_trigger(true)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
@@ -256,7 +262,7 @@ impl embedded_io::Read for Rx {
|
||||
}
|
||||
let mut read = 0;
|
||||
loop {
|
||||
if !self.regs.read_sr().rx_empty() {
|
||||
if !self.regs.read_status().rx_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ use super::UartId;
|
||||
/// Transmitter (TX) driver.
|
||||
pub struct Tx {
|
||||
pub(crate) regs: MmioRegisters<'static>,
|
||||
pub(crate) idx: UartId,
|
||||
pub(crate) id: UartId,
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Tx {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("Tx").field("idx", &self.idx).finish()
|
||||
f.debug_struct("Tx").field("idx", &self.id).finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,17 +24,17 @@ impl Tx {
|
||||
///
|
||||
/// Circumvents safety guarantees provided by the compiler.
|
||||
#[inline]
|
||||
pub const unsafe fn steal(idx: UartId) -> Self {
|
||||
pub const unsafe fn steal(id: UartId) -> Self {
|
||||
Tx {
|
||||
regs: unsafe { idx.regs() },
|
||||
idx,
|
||||
regs: unsafe { id.regs() },
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
/// UART index.
|
||||
/// UART ID.
|
||||
#[inline]
|
||||
pub const fn uart_idx(&self) -> UartId {
|
||||
self.idx
|
||||
pub const fn uart_id(&self) -> UartId {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Direct access to the UART MMIO registers.
|
||||
@@ -48,7 +48,7 @@ impl Tx {
|
||||
/// [nb] API which returns [nb::Error::WouldBlock] if the FIFO is full.
|
||||
#[inline]
|
||||
pub fn write_fifo(&mut self, word: u8) -> nb::Result<(), Infallible> {
|
||||
if self.regs.read_sr().tx_full() {
|
||||
if self.regs.read_status().tx_full() {
|
||||
return Err(nb::Error::WouldBlock);
|
||||
}
|
||||
self.write_fifo_unchecked(word);
|
||||
@@ -61,9 +61,9 @@ impl Tx {
|
||||
if with_reset {
|
||||
self.soft_reset();
|
||||
}
|
||||
self.regs.modify_cr(|mut val| {
|
||||
val.set_tx_en(true);
|
||||
val.set_tx_dis(false);
|
||||
self.regs.modify_control(|mut val| {
|
||||
val.set_tx_enable(true);
|
||||
val.set_tx_disable(false);
|
||||
val
|
||||
});
|
||||
}
|
||||
@@ -71,9 +71,9 @@ impl Tx {
|
||||
/// Disables TX side of the UART.
|
||||
#[inline]
|
||||
pub fn disable(&mut self) {
|
||||
self.regs.modify_cr(|mut val| {
|
||||
val.set_tx_en(false);
|
||||
val.set_tx_dis(true);
|
||||
self.regs.modify_control(|mut val| {
|
||||
val.set_tx_enable(false);
|
||||
val.set_tx_disable(true);
|
||||
val
|
||||
});
|
||||
}
|
||||
@@ -81,12 +81,12 @@ impl Tx {
|
||||
/// Performs a soft-reset of the TX side of the UART.
|
||||
#[inline]
|
||||
pub fn soft_reset(&mut self) {
|
||||
self.regs.modify_cr(|mut val| {
|
||||
val.set_tx_rst(true);
|
||||
self.regs.modify_control(|mut val| {
|
||||
val.set_tx_reset(true);
|
||||
val
|
||||
});
|
||||
loop {
|
||||
if !self.regs.read_cr().tx_rst() {
|
||||
if !self.regs.read_control().tx_reset() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,7 @@ impl Tx {
|
||||
|
||||
/// Flushes the TX FIFO by blocking until it is empty.
|
||||
pub fn flush(&mut self) {
|
||||
while !self.regs.read_sr().tx_empty() {}
|
||||
while !self.regs.read_status().tx_empty() {}
|
||||
}
|
||||
|
||||
/// Write a byte to the TX FIFO without checking if there is space available.
|
||||
@@ -105,12 +105,12 @@ impl Tx {
|
||||
|
||||
/// Enables interrupts relevant for the TX side of the UART except the TX trigger interrupt.
|
||||
#[inline]
|
||||
pub fn enable_interrupts(&mut self) {
|
||||
self.regs.write_ier(
|
||||
pub fn enable_interrupts(&mut self, tx_trig: bool) {
|
||||
self.regs.write_interrupt_enable(
|
||||
InterruptControl::builder()
|
||||
.with_tx_over(true)
|
||||
.with_tx_near_full(true)
|
||||
.with_tx_trig(false)
|
||||
.with_tx_near_full(false)
|
||||
.with_tx_trigger(tx_trig)
|
||||
.with_rx_dms(false)
|
||||
.with_rx_timeout(false)
|
||||
.with_rx_parity(false)
|
||||
@@ -120,7 +120,7 @@ impl Tx {
|
||||
.with_tx_empty(true)
|
||||
.with_rx_full(false)
|
||||
.with_rx_empty(false)
|
||||
.with_rx_trg(false)
|
||||
.with_rx_trigger(false)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
@@ -128,11 +128,11 @@ impl Tx {
|
||||
/// Disable interrupts relevant for the TX side of the UART except the TX trigger interrupt.
|
||||
#[inline]
|
||||
pub fn disable_interrupts(&mut self) {
|
||||
self.regs.write_idr(
|
||||
self.regs.write_interrupt_disable(
|
||||
InterruptControl::builder()
|
||||
.with_tx_over(true)
|
||||
.with_tx_near_full(true)
|
||||
.with_tx_trig(false)
|
||||
.with_tx_near_full(false)
|
||||
.with_tx_trigger(true)
|
||||
.with_rx_dms(false)
|
||||
.with_rx_timeout(false)
|
||||
.with_rx_parity(false)
|
||||
@@ -142,7 +142,7 @@ impl Tx {
|
||||
.with_tx_empty(true)
|
||||
.with_rx_full(false)
|
||||
.with_rx_empty(false)
|
||||
.with_rx_trg(false)
|
||||
.with_rx_trigger(false)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
@@ -150,11 +150,11 @@ impl Tx {
|
||||
/// Clears interrupts relevant for the TX side of the UART except the TX trigger interrupt.
|
||||
#[inline]
|
||||
pub fn clear_interrupts(&mut self) {
|
||||
self.regs.write_isr(
|
||||
self.regs.write_interrupt_status(
|
||||
InterruptStatus::builder()
|
||||
.with_tx_over(true)
|
||||
.with_tx_near_full(true)
|
||||
.with_tx_trig(false)
|
||||
.with_tx_trig(true)
|
||||
.with_rx_dms(false)
|
||||
.with_rx_timeout(false)
|
||||
.with_rx_parity(false)
|
||||
@@ -164,7 +164,7 @@ impl Tx {
|
||||
.with_tx_empty(true)
|
||||
.with_rx_full(false)
|
||||
.with_rx_empty(false)
|
||||
.with_rx_trg(false)
|
||||
.with_rx_trigger(false)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
@@ -181,7 +181,7 @@ impl embedded_hal_nb::serial::Write for Tx {
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> nb::Result<(), Self::Error> {
|
||||
if self.regs.read_sr().tx_empty() {
|
||||
if self.regs.read_status().tx_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
Err(nb::Error::WouldBlock)
|
||||
@@ -199,7 +199,7 @@ impl embedded_io::Write for Tx {
|
||||
}
|
||||
let mut written = 0;
|
||||
loop {
|
||||
if !self.regs.read_sr().tx_full() {
|
||||
if !self.regs.read_status().tx_full() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
//! Asynchronous UART transmitter (TX) implementation.
|
||||
use core::{cell::RefCell, convert::Infallible, sync::atomic::AtomicBool};
|
||||
use core::{cell::RefCell, convert::Infallible, marker::PhantomData, sync::atomic::AtomicBool};
|
||||
|
||||
use arbitrary_int::u6;
|
||||
use critical_section::Mutex;
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use raw_slice::RawBufSlice;
|
||||
use zynq7000::uart::FifoTrigger;
|
||||
|
||||
use crate::uart::{FIFO_DEPTH, Tx, UartId};
|
||||
|
||||
@@ -17,20 +19,30 @@ static TX_DONE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
|
||||
/// This is a generic interrupt handler to handle asynchronous UART TX operations for a given
|
||||
/// UART peripheral.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The user has to call this once in the interrupt handler responsible for the TX interrupts on
|
||||
/// the given UART bank.
|
||||
pub fn on_interrupt_tx(peripheral: UartId) {
|
||||
pub unsafe fn on_interrupt_tx(peripheral: UartId) {
|
||||
let mut tx_with_irq = unsafe { Tx::steal(peripheral) };
|
||||
let idx = peripheral as usize;
|
||||
let imr = tx_with_irq.regs().read_imr();
|
||||
let enabled_irqs = tx_with_irq.regs().read_enabled_interrupts();
|
||||
// IRQ is not related to TX.
|
||||
if !imr.tx_over() && !imr.tx_near_full() && !imr.tx_full() && !imr.tx_empty() && !imr.tx_full()
|
||||
if !enabled_irqs.tx_over()
|
||||
&& !enabled_irqs.tx_near_full()
|
||||
&& !enabled_irqs.tx_full()
|
||||
&& !enabled_irqs.tx_empty()
|
||||
&& !enabled_irqs.tx_full()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let isr = tx_with_irq.regs().read_isr();
|
||||
let unexpected_overrun = isr.tx_over();
|
||||
let interrupt_status = tx_with_irq.regs().read_interrupt_status();
|
||||
// Disable interrupts, re-enable them later.
|
||||
tx_with_irq.disable_interrupts();
|
||||
// Clear interrupts.
|
||||
tx_with_irq.clear_interrupts();
|
||||
let unexpected_overrun = interrupt_status.tx_over();
|
||||
let mut context = critical_section::with(|cs| {
|
||||
let context_ref = TX_CONTEXTS[idx].borrow(cs);
|
||||
*context_ref.borrow()
|
||||
@@ -41,7 +53,7 @@ pub fn on_interrupt_tx(peripheral: UartId) {
|
||||
}
|
||||
let slice_len = context.slice.len().unwrap();
|
||||
context.tx_overrun = unexpected_overrun;
|
||||
if (context.progress >= slice_len && isr.tx_empty()) || slice_len == 0 {
|
||||
if (context.progress >= slice_len && interrupt_status.tx_empty()) || slice_len == 0 {
|
||||
// Write back updated context structure.
|
||||
critical_section::with(|cs| {
|
||||
let context_ref = TX_CONTEXTS[idx].borrow(cs);
|
||||
@@ -57,8 +69,10 @@ pub fn on_interrupt_tx(peripheral: UartId) {
|
||||
// Safety: We documented that the user provided slice must outlive the future, so we convert
|
||||
// the raw pointer back to the slice here.
|
||||
let slice = unsafe { context.slice.get() }.expect("slice is invalid");
|
||||
|
||||
// Pump the FIFO.
|
||||
while context.progress < slice_len {
|
||||
if tx_with_irq.regs().read_sr().tx_full() {
|
||||
if tx_with_irq.regs().read_status().tx_full() {
|
||||
break;
|
||||
}
|
||||
// Safety: TX structure is owned by the future which does not write into the the data
|
||||
@@ -66,14 +80,22 @@ pub fn on_interrupt_tx(peripheral: UartId) {
|
||||
tx_with_irq.write_fifo_unchecked(slice[context.progress]);
|
||||
context.progress += 1;
|
||||
}
|
||||
let remaining = slice_len - context.progress;
|
||||
if remaining > FIFO_DEPTH {
|
||||
tx_with_irq.regs.write_tx_fifo_trigger(
|
||||
FifoTrigger::builder()
|
||||
.with_trigger(u6::new((FIFO_DEPTH / 2) as u8))
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
// Write back updated context structure.
|
||||
critical_section::with(|cs| {
|
||||
let context_ref = TX_CONTEXTS[idx].borrow(cs);
|
||||
*context_ref.borrow_mut() = context;
|
||||
});
|
||||
// Clear interrupts.
|
||||
tx_with_irq.clear_interrupts();
|
||||
|
||||
tx_with_irq.enable_interrupts(remaining > FIFO_DEPTH);
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
@@ -95,17 +117,18 @@ impl TxContext {
|
||||
}
|
||||
|
||||
/// Transmission future for UART TX.
|
||||
pub struct TxFuture {
|
||||
pub struct TxFuture<'uart> {
|
||||
id: UartId,
|
||||
marker: core::marker::PhantomData<&'uart ()>,
|
||||
}
|
||||
|
||||
impl TxFuture {
|
||||
impl<'uart> TxFuture<'uart> {
|
||||
/// # Safety
|
||||
///
|
||||
/// This function stores the raw pointer of the passed data slice. The user MUST ensure
|
||||
/// that the slice outlives the data structure.
|
||||
pub unsafe fn new(tx_with_irq: &mut Tx, data: &[u8]) -> Self {
|
||||
let idx = tx_with_irq.uart_idx() as usize;
|
||||
pub unsafe fn new(tx_with_irq: &'uart mut Tx, data: &[u8]) -> TxFuture<'uart> {
|
||||
let idx = tx_with_irq.uart_id() as usize;
|
||||
TX_DONE[idx].store(false, core::sync::atomic::Ordering::Relaxed);
|
||||
tx_with_irq.disable_interrupts();
|
||||
tx_with_irq.disable();
|
||||
@@ -119,19 +142,29 @@ impl TxFuture {
|
||||
}
|
||||
context.progress = init_fill_count; // We fill the FIFO.
|
||||
});
|
||||
tx_with_irq.enable(true);
|
||||
// Apparently, we need to enable the UART before we are able to write something into
|
||||
// the FIFO.
|
||||
tx_with_irq.enable(false);
|
||||
if data.len() > FIFO_DEPTH {
|
||||
tx_with_irq.regs.write_tx_fifo_trigger(
|
||||
FifoTrigger::builder()
|
||||
.with_trigger(u6::new((FIFO_DEPTH / 2) as u8))
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
for data in data.iter().take(init_fill_count) {
|
||||
tx_with_irq.write_fifo_unchecked(*data);
|
||||
}
|
||||
tx_with_irq.enable_interrupts();
|
||||
tx_with_irq.enable_interrupts(data.len() > FIFO_DEPTH);
|
||||
|
||||
Self {
|
||||
id: tx_with_irq.uart_idx(),
|
||||
id: tx_with_irq.uart_id(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for TxFuture {
|
||||
impl Future for TxFuture<'_> {
|
||||
type Output = usize;
|
||||
|
||||
fn poll(
|
||||
@@ -151,7 +184,7 @@ impl Future for TxFuture {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TxFuture {
|
||||
impl Drop for TxFuture<'_> {
|
||||
fn drop(&mut self) {
|
||||
let mut tx = unsafe { Tx::steal(self.id) };
|
||||
tx.disable_interrupts();
|
||||
@@ -165,20 +198,52 @@ pub struct TxAsync {
|
||||
|
||||
impl TxAsync {
|
||||
/// Constructor.
|
||||
pub fn new(tx: Tx) -> Self {
|
||||
///
|
||||
/// The second argument specifies whether the [on_interrupt_tx] function will be registered
|
||||
/// in the HAL interrupt map. You might need to skip this in case you have your own
|
||||
/// interrupt handler which also handles RX interrupts.
|
||||
pub fn new(tx: Tx, register_interrupt_handler: bool) -> Self {
|
||||
if register_interrupt_handler {
|
||||
match tx.uart_id() {
|
||||
UartId::Uart0 => {
|
||||
unsafe fn uart0_interrupt_handler() {
|
||||
unsafe {
|
||||
on_interrupt_tx(UartId::Uart0);
|
||||
}
|
||||
}
|
||||
crate::register_interrupt(
|
||||
crate::gic::Interrupt::Spi(crate::gic::SpiInterrupt::Uart0),
|
||||
uart0_interrupt_handler,
|
||||
)
|
||||
}
|
||||
UartId::Uart1 => {
|
||||
unsafe fn uart1_interrupt_handler() {
|
||||
unsafe {
|
||||
on_interrupt_tx(UartId::Uart1);
|
||||
}
|
||||
}
|
||||
crate::register_interrupt(
|
||||
crate::gic::Interrupt::Spi(crate::gic::SpiInterrupt::Uart1),
|
||||
uart1_interrupt_handler,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self { tx }
|
||||
}
|
||||
|
||||
/// Write a buffer asynchronously.
|
||||
///
|
||||
/// Returns [None] if the passed buffer is empty.
|
||||
///
|
||||
/// This implementation is not side effect free, and a started future might have already
|
||||
/// written part of the passed buffer.
|
||||
pub async fn write(&mut self, buf: &[u8]) -> usize {
|
||||
pub fn write(&mut self, buf: &[u8]) -> Option<TxFuture<'_>> {
|
||||
if buf.is_empty() {
|
||||
return 0;
|
||||
return None;
|
||||
}
|
||||
let fut = unsafe { TxFuture::new(&mut self.tx, buf) };
|
||||
fut.await
|
||||
Some(unsafe { TxFuture::new(&mut self.tx, buf) })
|
||||
}
|
||||
|
||||
/// Release the underlying blocking TX driver.
|
||||
@@ -197,7 +262,10 @@ impl embedded_io_async::Write for TxAsync {
|
||||
/// This implementation is not side effect free, and a started future might have already
|
||||
/// written part of the passed buffer.
|
||||
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||
Ok(self.write(buf).await)
|
||||
if buf.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
Ok(self.write(buf).unwrap().await)
|
||||
}
|
||||
|
||||
/// This implementation does not do anything.
|
||||
|
||||
@@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
# [v0.2.0] 2026-05-08
|
||||
|
||||
- Bumped `aarch32-cpu` to v0.3
|
||||
|
||||
# [v0.1.2] 2026-02-14
|
||||
|
||||
- Bumped `aarch32-cpu` to v0.2
|
||||
|
||||
# [v0.1.1] 2025-10-10
|
||||
|
||||
Documentation fixes.
|
||||
@@ -16,6 +24,8 @@ Documentation fixes.
|
||||
|
||||
Initial release
|
||||
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-mmu-v0.1.0...HEAD
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-mmu-v0.2.0...HEAD
|
||||
[v0.2.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-mmu-v0.1.2...zynq7000-mmu-v0.2.0
|
||||
[v0.1.2]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-mmu-v0.1.1...zynq7000-mmu-v0.1.2
|
||||
[v0.1.1]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-mmu-v0.1.0...zynq7000-mmu-v0.1.1
|
||||
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/tag/zynq7000-mmu-v0.1.0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "zynq7000-mmu"
|
||||
description = "Zynq7000 MMU structures"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
homepage = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
|
||||
@@ -11,7 +11,7 @@ categories = ["embedded", "no-std", "hardware-support"]
|
||||
|
||||
[dependencies]
|
||||
thiserror = { version = "2", default-features = false }
|
||||
aarch32-cpu = { version = "0.1" }
|
||||
aarch32-cpu = { version = "0.3" }
|
||||
|
||||
[build-dependencies]
|
||||
arm-targets = { version = "0.4" }
|
||||
@@ -21,5 +21,4 @@ tools = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["armv7a-none-eabihf"]
|
||||
cargo-args = ["-Z", "build-std=core"]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
use aarch32_cpu::mmu::L1Section;
|
||||
#[cfg(all(not(feature = "tools"), arm_profile = "a"))]
|
||||
use aarch32_cpu::{
|
||||
asm::{dsb, isb},
|
||||
cache::clean_and_invalidate_l1_data_cache,
|
||||
@@ -39,7 +38,6 @@ impl L1TableRaw {
|
||||
self.0.as_mut_ptr() as *mut _
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "tools"), arm_profile = "a"))]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
addr: u32,
|
||||
@@ -92,7 +90,6 @@ impl<'a> L1TableWrapper<'a> {
|
||||
}
|
||||
|
||||
impl L1TableWrapper<'_> {
|
||||
#[cfg(all(not(feature = "tools"), arm_profile = "a"))]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
addr: u32,
|
||||
|
||||
@@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
# [v0.3.0] 2026-05-08
|
||||
|
||||
Bumped `aarch32-rt` and `aarch32-cpu` to v0.3.
|
||||
|
||||
# [v0.2.0] 2026-02-14
|
||||
|
||||
Bugfixes in startup assembler code.
|
||||
|
||||
## Changed
|
||||
@@ -17,6 +23,7 @@ Bugfixes in startup assembler code.
|
||||
- Runtime now calls a `kmain` method similar to the re-export `aarch32-rt` crate.
|
||||
Former `boot_core` method must be renamed to `kmain`, but it is recommended to use
|
||||
the `zynq7000-rt::entry` proc macro to annotate the main method.
|
||||
- Bumped `aarch32-rt` to v0.2 which now requires the `memory.x` file to place the `STACKS` segment
|
||||
|
||||
## Fixed
|
||||
|
||||
@@ -32,6 +39,8 @@ Documentation fixes.
|
||||
|
||||
Initial release
|
||||
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-rt-v0.1.0...HEAD
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-rt-v0.3.0...HEAD
|
||||
[v0.3.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-rt-v0.2.0...zynq7000-rt-v0.3.0
|
||||
[v0.2.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-rt-v0.1.1...zynq7000-rt-v0.2.0
|
||||
[v0.1.1]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-rt-v0.1.0...zynq7000-rt-v0.1.1
|
||||
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/tag/zynq7000-rt-v0.1.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "zynq7000-rt"
|
||||
version = "0.1.1"
|
||||
version = "0.3.0"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
edition = "2024"
|
||||
description = "Run-time support for the Zynq7000 family of SoCs for running bare-metal applications"
|
||||
@@ -11,10 +11,10 @@ keywords = ["no-std", "rt", "cortex-a", "amd", "zynq7000"]
|
||||
categories = ["embedded", "no-std", "hardware-support"]
|
||||
|
||||
[dependencies]
|
||||
aarch32-rt = { version = "0.1", optional = true, features = ["fpu-d32"] }
|
||||
aarch32-cpu = { version = "0.1" }
|
||||
aarch32-rt = { version = "0.3", optional = true, features = ["fpu-d32"] }
|
||||
aarch32-cpu = { version = "0.3" }
|
||||
arbitrary-int = "2"
|
||||
zynq7000-mmu = { path = "../zynq7000-mmu", version = "0.1" }
|
||||
zynq7000-mmu = { path = "../zynq7000-mmu", version = "0.2" }
|
||||
|
||||
[build-dependencies]
|
||||
arm-targets = { version = "0.4" }
|
||||
@@ -25,5 +25,4 @@ rt = ["dep:aarch32-rt"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["armv7a-none-eabihf"]
|
||||
cargo-args = ["-Z", "build-std=core"]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
@@ -85,7 +85,7 @@ pub mod segments {
|
||||
|
||||
pub mod section_attrs {
|
||||
use aarch32_cpu::mmu::{
|
||||
AccessPermissions, CacheableMemoryAttribute, MemoryRegionAttributes, SectionAttributes,
|
||||
AccessPermissions, CachePolicy, MemoryRegionAttributes, SectionAttributes,
|
||||
};
|
||||
use arbitrary_int::u4;
|
||||
|
||||
@@ -103,8 +103,8 @@ pub mod section_attrs {
|
||||
domain: DDR_DOMAIN,
|
||||
execute_never: false,
|
||||
memory_attrs: MemoryRegionAttributes::CacheableMemory {
|
||||
inner: CacheableMemoryAttribute::WriteBackWriteAlloc,
|
||||
outer: CacheableMemoryAttribute::WriteBackWriteAlloc,
|
||||
inner: CachePolicy::WriteBackWriteAlloc,
|
||||
outer: CachePolicy::WriteBackWriteAlloc,
|
||||
}
|
||||
.as_raw(),
|
||||
};
|
||||
@@ -157,8 +157,8 @@ pub mod section_attrs {
|
||||
domain: DEFAULT_DOMAIN,
|
||||
execute_never: false,
|
||||
memory_attrs: MemoryRegionAttributes::CacheableMemory {
|
||||
inner: CacheableMemoryAttribute::WriteThroughNoWriteAlloc,
|
||||
outer: CacheableMemoryAttribute::NonCacheable,
|
||||
inner: CachePolicy::WriteThroughNoWriteAlloc,
|
||||
outer: CachePolicy::NonCacheable,
|
||||
}
|
||||
.as_raw(),
|
||||
};
|
||||
|
||||
+114
-194
@@ -5,7 +5,6 @@
|
||||
//! but does NOT provide the L2 cache initialization.
|
||||
//!
|
||||
//! The boot routine includes stack, MMU and .bss/.data section initialization.
|
||||
use aarch32_cpu::register::{Cpsr, cpsr::ProcessorMode};
|
||||
use aarch32_rt as _;
|
||||
|
||||
// Start-up code for Armv7-A
|
||||
@@ -13,23 +12,23 @@ use aarch32_rt as _;
|
||||
// We set up our stacks and `kmain` in system mode.
|
||||
core::arch::global_asm!(
|
||||
r#"
|
||||
.set PSS_L2CC_BASE_ADDR, 0xF8F02000
|
||||
.set PSS_SLCR_BASE_ADDR, 0xF8000000
|
||||
.set PSS_L2CC_BASE_ADDR, 0xF8F02000
|
||||
.set PSS_SLCR_BASE_ADDR, 0xF8000000
|
||||
|
||||
.set SLCRlockReg, (PSS_SLCR_BASE_ADDR + 0x04) /*(PSS_SLCR_BASE_ADDR + XPSS_SLCR_LOCK_OFFSET)*/
|
||||
.set SLCRUnlockReg, (PSS_SLCR_BASE_ADDR + 0x08) /*(PSS_SLCR_BASE_ADDR + XPSS_SLCR_UNLOCK_OFFSET)*/
|
||||
.set SLCRL2cRamReg, (PSS_SLCR_BASE_ADDR + 0xA1C) /*(PSS_SLCR_BASE_ADDR + XPSS_SLCR_L2C_RAM_OFFSET)*/
|
||||
.set SLCRCPURSTReg, (0xF8000000 + 0x244) /*(XPS_SYS_CTRL_BASEADDR + A9_CPU_RST_CTRL_OFFSET)*/
|
||||
.set EFUSEStatus, (0xF800D000 + 0x10) /*(XPS_EFUSE_BASEADDR + EFUSE_STATUS_OFFSET)*/
|
||||
.set SLCRlockReg, (PSS_SLCR_BASE_ADDR + 0x04) /*(PSS_SLCR_BASE_ADDR + XPSS_SLCR_LOCK_OFFSET)*/
|
||||
.set SLCRUnlockReg, (PSS_SLCR_BASE_ADDR + 0x08) /*(PSS_SLCR_BASE_ADDR + XPSS_SLCR_UNLOCK_OFFSET)*/
|
||||
.set SLCRL2cRamReg, (PSS_SLCR_BASE_ADDR + 0xA1C) /*(PSS_SLCR_BASE_ADDR + XPSS_SLCR_L2C_RAM_OFFSET)*/
|
||||
.set SLCRCPURSTReg, (0xF8000000 + 0x244) /*(XPS_SYS_CTRL_BASEADDR + A9_CPU_RST_CTRL_OFFSET)*/
|
||||
.set EFUSEStatus, (0xF800D000 + 0x10) /*(XPS_EFUSE_BASEADDR + EFUSE_STATUS_OFFSET)*/
|
||||
|
||||
.set CRValMmuCac, 0b01000000000101 /* Enable IDC, and MMU */
|
||||
.set CRValHiVectorAddr, 0b10000000000000 /* Set the Vector address to high, 0xFFFF0000 */
|
||||
.set CRValMmuCac, 0b01000000000101 /* Enable IDC, and MMU */
|
||||
.set CRValHiVectorAddr, 0b10000000000000 /* Set the Vector address to high, 0xFFFF0000 */
|
||||
|
||||
.set SLCRlockKey, 0x767B /* SLCR lock key */
|
||||
.set SLCRUnlockKey, 0xDF0D /* SLCR unlock key */
|
||||
.set SLCRlockKey, 0x767B /* SLCR lock key */
|
||||
.set SLCRUnlockKey, 0xDF0D /* SLCR unlock key */
|
||||
.set SLCRL2cRamConfig, 0x00020202 /* SLCR L2C ram configuration */
|
||||
|
||||
.set FPEXC_EN, 0x40000000 /* FPU enable bit, (1 << 30) */
|
||||
.set FPEXC_EN, 0x40000000 /* FPU enable bit, (1 << 30) */
|
||||
|
||||
.section .text.startup
|
||||
.align 0
|
||||
@@ -39,137 +38,100 @@ core::arch::global_asm!(
|
||||
_start:
|
||||
// only allow cpu0 through
|
||||
// Read MPIDR
|
||||
mrc p15,0,r1,c0,c0,5
|
||||
mrc p15,0,r1,c0,c0,5
|
||||
// Extract CPU ID bits. For single-core systems, this should always be 0
|
||||
and r1, r1, #0x3
|
||||
cmp r1, #0
|
||||
beq check_efuse
|
||||
b initialize
|
||||
and r1, r1, #0x3
|
||||
cmp r1, #0
|
||||
beq check_efuse
|
||||
b initialize
|
||||
|
||||
// Zynq specific code. It is recommended to reset CPU1 according to page 160 of the datasheet
|
||||
check_efuse:
|
||||
ldr r0,=EFUSEStatus
|
||||
ldr r1,[r0] /* Read eFuse setting */
|
||||
ands r1,r1,#0x80 /* Check whether device is having single core */
|
||||
beq initialize
|
||||
ldr r0, =EFUSEStatus
|
||||
// Read eFuse setting
|
||||
ldr r1, [r0]
|
||||
// Check whether device is having single core
|
||||
ands r1,r1,#0x80
|
||||
beq initialize
|
||||
|
||||
/* single core device, reset cpu1 */
|
||||
ldr r0,=SLCRUnlockReg /* Load SLCR base address base + unlock register */
|
||||
ldr r1,=SLCRUnlockKey /* set unlock key */
|
||||
str r1, [r0] /* Unlock SLCR */
|
||||
|
||||
ldr r0,=SLCRCPURSTReg
|
||||
ldr r1,[r0] /* Read CPU Software Reset Control register */
|
||||
orr r1,r1,#0x22
|
||||
str r1,[r0] /* Reset CPU1 */
|
||||
ldr r0,=SLCRCPURSTReg
|
||||
ldr r1,[r0] /* Read CPU Software Reset Control register */
|
||||
orr r1,r1,#0x22
|
||||
str r1,[r0] /* Reset CPU1 */
|
||||
|
||||
ldr r0,=SLCRlockReg /* Load SLCR base address base + lock register */
|
||||
ldr r1,=SLCRlockKey /* set lock key */
|
||||
str r1, [r0] /* lock SLCR */
|
||||
ldr r0,=SLCRlockReg /* Load SLCR base address base + lock register */
|
||||
ldr r1,=SLCRlockKey /* set lock key */
|
||||
str r1, [r0] /* lock SLCR */
|
||||
initialize:
|
||||
mrc p15, 0, r0, c0, c0, 0 /* Get the revision */
|
||||
mrc p15, 0, r0, c0, c0, 0 /* Get the revision */
|
||||
and r5, r0, #0x00f00000
|
||||
and r6, r0, #0x0000000f
|
||||
orr r6, r6, r5, lsr #20-4
|
||||
|
||||
/* set VBAR to the _vector_table address in linker script */
|
||||
ldr r0, =_vector_table
|
||||
mcr p15, 0, r0, c12, c0, 0
|
||||
ldr r0, =_vector_table
|
||||
mcr p15, 0, r0, c12, c0, 0
|
||||
|
||||
/* Invalidate scu */
|
||||
ldr r7, =0xf8f0000c
|
||||
ldr r6, =0xffff
|
||||
str r6, [r7]
|
||||
ldr r7, =0xf8f0000c
|
||||
ldr r6, =0xffff
|
||||
str r6, [r7]
|
||||
|
||||
/* Invalidate caches and TLBs */
|
||||
mov r0,#0 /* r0 = 0 */
|
||||
mcr p15, 0, r0, c8, c7, 0 /* invalidate TLBs */
|
||||
mcr p15, 0, r0, c7, c5, 0 /* invalidate icache */
|
||||
mcr p15, 0, r0, c7, c5, 6 /* Invalidate branch predictor array */
|
||||
bl invalidate_dcache /* invalidate dcache */
|
||||
mov r0,#0 /* r0 = 0 */
|
||||
mcr p15, 0, r0, c8, c7, 0 /* invalidate TLBs */
|
||||
mcr p15, 0, r0, c7, c5, 0 /* invalidate icache */
|
||||
mcr p15, 0, r0, c7, c5, 6 /* Invalidate branch predictor array */
|
||||
bl invalidate_dcache /* invalidate dcache */
|
||||
|
||||
/* Disable MMU, if enabled */
|
||||
mrc p15, 0, r0, c1, c0, 0 /* read CP15 register 1 */
|
||||
bic r0, r0, #0x1 /* clear bit 0 */
|
||||
mcr p15, 0, r0, c1, c0, 0 /* write value back */
|
||||
mrc p15, 0, r0, c1, c0, 0 /* read CP15 register 1 */
|
||||
bic r0, r0, #0x1 /* clear bit 0 */
|
||||
mcr p15, 0, r0, c1, c0, 0 /* write value back */
|
||||
|
||||
// Set up stacks first.
|
||||
ldr r3, =_stack_top
|
||||
|
||||
// Set stack pointer (right after) and mask interrupts for IRQ mode (Mode 0x12)
|
||||
msr cpsr_c, {irq_mode}
|
||||
// IRQ stack pointer
|
||||
mov sp, r3
|
||||
ldr r1, =_irq_stack_size
|
||||
sub r3, r3, r1
|
||||
|
||||
// Set stack pointer (right after) and mask interrupts for Supervisor/SVC mode (Mode 0x13)
|
||||
msr cpsr_c, {svc_mode}
|
||||
// Supervisor stack pointer
|
||||
mov sp, r3
|
||||
ldr r1, =_svc_stack_size
|
||||
sub r3, r3, r1
|
||||
|
||||
// Set stack pointer (right after) and mask interrupts for Abort/ABT mode (Mode 0x17)
|
||||
msr cpsr_c, {abt_mode}
|
||||
// Abort stack pointer
|
||||
mov sp, r3
|
||||
ldr r1, =_abt_stack_size
|
||||
sub r3, r3, r1
|
||||
|
||||
// Set stack pointer (right after) and mask interrupts for FIQ mode (Mode 0x11)
|
||||
msr cpsr_c, {fiq_mode}
|
||||
// FIQ stack pointer
|
||||
mov sp, r3
|
||||
ldr r1, =_fiq_stack_size
|
||||
sub r3, r3, r1
|
||||
|
||||
// Set stack pointer (right after) and mask interrupts for Undefined/UND mode (Mode 0x1B)
|
||||
msr cpsr_c, {und_mode}
|
||||
// Undefined stack pointer
|
||||
mov sp, r3
|
||||
ldr r1, =_und_stack_size
|
||||
sub r3, r3, r1
|
||||
|
||||
// Set stack pointer (right after) and mask interrupts for System/SYS mode (Mode 0x1F)
|
||||
msr cpsr_c, {sys_mode}
|
||||
// System stack pointer (main stack)
|
||||
mov sp, r3
|
||||
/* We must set the core number for this function */
|
||||
mov r0,#0
|
||||
bl _stack_setup_preallocated
|
||||
|
||||
// set scu enable bit in scu
|
||||
ldr r7, =0xf8f00000
|
||||
ldr r0, [r7]
|
||||
orr r0, r0, #0x1
|
||||
str r0, [r7]
|
||||
ldr r7, =0xf8f00000
|
||||
ldr r0, [r7]
|
||||
orr r0, r0, #0x1
|
||||
str r0, [r7]
|
||||
|
||||
/* Write to ACTLR */
|
||||
mrc p15, 0, r0, c1, c0, 1 /* Read ACTLR*/
|
||||
orr r0, r0, #(0x01 << 6) /* set SMP bit */
|
||||
orr r0, r0, #(0x01 ) /* Cache/TLB maintenance broadcast */
|
||||
mcr p15, 0, r0, c1, c0, 1 /* Write ACTLR*/
|
||||
mrc p15, 0, r0, c1, c0, 1 /* Read ACTLR*/
|
||||
orr r0, r0, #(0x01 << 6) /* set SMP bit */
|
||||
orr r0, r0, #(0x01 ) /* Cache/TLB maintenance broadcast */
|
||||
mcr p15, 0, r0, c1, c0, 1 /* Write ACTLR*/
|
||||
|
||||
mov r0, r0
|
||||
mrc p15, 0, r1, c1, c0, 2 /* read cp access control register (CACR) into r1 */
|
||||
orr r1, r1, #(0xf << 20) /* enable full access for p10 & p11 */
|
||||
mcr p15, 0, r1, c1, c0, 2 /* write back into CACR */
|
||||
mov r0, r0
|
||||
mrc p15, 0, r1, c1, c0, 2 /* read cp access control register (CACR) into r1 */
|
||||
orr r1, r1, #(0xf << 20) /* enable full access for p10 & p11 */
|
||||
mcr p15, 0, r1, c1, c0, 2 /* write back into CACR */
|
||||
|
||||
/* enable vfp */
|
||||
fmrx r1, FPEXC /* read the exception register */
|
||||
orr r1,r1, #FPEXC_EN /* set VFP enable bit, leave the others in orig state */
|
||||
fmxr FPEXC, r1 /* write back the exception register */
|
||||
fmrx r1, FPEXC /* read the exception register */
|
||||
orr r1,r1, #FPEXC_EN /* set VFP enable bit, leave the others in orig state */
|
||||
fmxr FPEXC, r1 /* write back the exception register */
|
||||
|
||||
mrc p15,0,r0,c1,c0,0 /* flow prediction enable */
|
||||
orr r0, r0, #(0x01 << 11) /* #0x8000 */
|
||||
mcr p15,0,r0,c1,c0,0
|
||||
mrc p15,0,r0,c1,c0,0 /* flow prediction enable */
|
||||
orr r0, r0, #(0x01 << 11) /* #0x8000 */
|
||||
mcr p15,0,r0,c1,c0,0
|
||||
|
||||
mrc p15,0,r0,c1,c0,1 /* read Auxiliary Control Register */
|
||||
orr r0, r0, #(0x1 << 2) /* enable Dside prefetch */
|
||||
orr r0, r0, #(0x1 << 1) /* enable L2 Prefetch hint */
|
||||
mcr p15,0,r0,c1,c0,1 /* write Auxiliary Control Register */
|
||||
mrc p15,0,r0,c1,c0,1 /* read Auxiliary Control Register */
|
||||
orr r0, r0, #(0x1 << 2) /* enable Dside prefetch */
|
||||
orr r0, r0, #(0x1 << 1) /* enable L2 Prefetch hint */
|
||||
mcr p15,0,r0,c1,c0,1 /* write Auxiliary Control Register */
|
||||
|
||||
mrs r0, cpsr /* get the current PSR */
|
||||
bic r0, r0, #0x100 /* enable asynchronous abort exception */
|
||||
msr cpsr_xsf, r0
|
||||
mrs r0, cpsr /* get the current PSR */
|
||||
bic r0, r0, #0x100 /* enable asynchronous abort exception */
|
||||
msr cpsr_xsf, r0
|
||||
|
||||
/* Zero BSS and initialize data before calling any function which might require them. */
|
||||
|
||||
@@ -199,20 +161,20 @@ data_init_done:
|
||||
/* enable MMU and cache */
|
||||
/* MMU Table is in .data, so this needs to be performed after .data is relocated */
|
||||
/* (Even if in most cases, .data is already in RAM and relocation is a no-op) */
|
||||
bl load_mmu_table
|
||||
bl load_mmu_table
|
||||
|
||||
mvn r0,#0 /* Load MMU domains -- all ones=manager */
|
||||
mcr p15,0,r0,c3,c0,0
|
||||
mvn r0,#0 /* Load MMU domains -- all ones=manager */
|
||||
mcr p15,0,r0,c3,c0,0
|
||||
|
||||
/* Enable mmu, icache and dcache */
|
||||
ldr r0,=CRValMmuCac
|
||||
mcr p15,0,r0,c1,c0,0 /* Enable cache and MMU */
|
||||
dsb /* dsb allow the MMU to start up */
|
||||
isb /* isb flush prefetch buffer */
|
||||
ldr r0,=CRValMmuCac
|
||||
mcr p15,0,r0,c1,c0,0 /* Enable cache and MMU */
|
||||
dsb /* dsb allow the MMU to start up */
|
||||
isb /* isb flush prefetch buffer */
|
||||
|
||||
// Jump to application
|
||||
// Load CPU ID 0, which will be used as a function argument to the boot_core function.
|
||||
mov r0, #0x0
|
||||
mov r0, #0x0
|
||||
bl kmain
|
||||
// In case the application returns, loop forever
|
||||
b .
|
||||
@@ -220,90 +182,48 @@ data_init_done:
|
||||
|
||||
.type _invalidate_dcache, %function
|
||||
invalidate_dcache:
|
||||
mrc p15, 1, r0, c0, c0, 1 /* read CLIDR */
|
||||
ands r3, r0, #0x7000000
|
||||
mov r3, r3, lsr #23 /* cache level value (naturally aligned) */
|
||||
beq finished
|
||||
mov r10, #0 /* start with level 0 */
|
||||
mrc p15, 1, r0, c0, c0, 1 /* read CLIDR */
|
||||
ands r3, r0, #0x7000000
|
||||
mov r3, r3, lsr #23 /* cache level value (naturally aligned) */
|
||||
beq finished
|
||||
mov r10, #0 /* start with level 0 */
|
||||
loop1:
|
||||
add r2, r10, r10, lsr #1 /* work out 3xcachelevel */
|
||||
mov r1, r0, lsr r2 /* bottom 3 bits are the Cache type for this level */
|
||||
and r1, r1, #7 /* get those 3 bits alone */
|
||||
cmp r1, #2
|
||||
blt skip /* no cache or only instruction cache at this level */
|
||||
mcr p15, 2, r10, c0, c0, 0 /* write the Cache Size selection register */
|
||||
isb /* isb to sync the change to the CacheSizeID reg */
|
||||
mrc p15, 1, r1, c0, c0, 0 /* reads current Cache Size ID register */
|
||||
and r2, r1, #7 /* extract the line length field */
|
||||
add r2, r2, #4 /* add 4 for the line length offset (log2 16 bytes) */
|
||||
ldr r4, =0x3ff
|
||||
ands r4, r4, r1, lsr #3 /* r4 is the max number on the way size (right aligned) */
|
||||
clz r5, r4 /* r5 is the bit position of the way size increment */
|
||||
ldr r7, =0x7fff
|
||||
ands r7, r7, r1, lsr #13 /* r7 is the max number of the index size (right aligned) */
|
||||
add r2, r10, r10, lsr #1 /* work out 3xcachelevel */
|
||||
mov r1, r0, lsr r2 /* bottom 3 bits are the Cache type for this level */
|
||||
and r1, r1, #7 /* get those 3 bits alone */
|
||||
cmp r1, #2
|
||||
blt skip /* no cache or only instruction cache at this level */
|
||||
mcr p15, 2, r10, c0, c0, 0 /* write the Cache Size selection register */
|
||||
isb /* isb to sync the change to the CacheSizeID reg */
|
||||
mrc p15, 1, r1, c0, c0, 0 /* reads current Cache Size ID register */
|
||||
and r2, r1, #7 /* extract the line length field */
|
||||
add r2, r2, #4 /* add 4 for the line length offset (log2 16 bytes) */
|
||||
ldr r4, =0x3ff
|
||||
ands r4, r4, r1, lsr #3 /* r4 is the max number on the way size (right aligned) */
|
||||
clz r5, r4 /* r5 is the bit position of the way size increment */
|
||||
ldr r7, =0x7fff
|
||||
ands r7, r7, r1, lsr #13 /* r7 is the max number of the index size (right aligned) */
|
||||
loop2:
|
||||
mov r9, r4 /* r9 working copy of the max way size (right aligned) */
|
||||
mov r9, r4 /* r9 working copy of the max way size (right aligned) */
|
||||
loop3:
|
||||
orr r11, r10, r9, lsl r5 /* factor in the way number and cache number into r11 */
|
||||
orr r11, r11, r7, lsl r2 /* factor in the index number */
|
||||
mcr p15, 0, r11, c7, c6, 2 /* invalidate by set/way */
|
||||
subs r9, r9, #1 /* decrement the way number */
|
||||
bge loop3
|
||||
subs r7, r7, #1 /* decrement the index */
|
||||
bge loop2
|
||||
orr r11, r10, r9, lsl r5 /* factor in the way number and cache number into r11 */
|
||||
orr r11, r11, r7, lsl r2 /* factor in the index number */
|
||||
mcr p15, 0, r11, c7, c6, 2 /* invalidate by set/way */
|
||||
subs r9, r9, #1 /* decrement the way number */
|
||||
bge loop3
|
||||
subs r7, r7, #1 /* decrement the index */
|
||||
bge loop2
|
||||
skip:
|
||||
add r10, r10, #2 /* increment the cache number */
|
||||
cmp r3, r10
|
||||
bgt loop1
|
||||
add r10, r10, #2 /* increment the cache number */
|
||||
cmp r3, r10
|
||||
bgt loop1
|
||||
|
||||
finished:
|
||||
mov r10, #0 /* switch back to cache level 0 */
|
||||
mcr p15, 2, r10, c0, c0, 0 /* select current cache level in cssr */
|
||||
mov r10, #0 /* switch back to cache level 0 */
|
||||
mcr p15, 2, r10, c0, c0, 0 /* select current cache level in cssr */
|
||||
dsb
|
||||
isb
|
||||
bx lr
|
||||
bx lr
|
||||
.size invalidate_dcache, . - invalidate_dcache
|
||||
"#,
|
||||
fiq_mode = const {
|
||||
Cpsr::new_with_raw_value(0)
|
||||
.with_mode(ProcessorMode::Fiq)
|
||||
.with_i(true)
|
||||
.with_f(true)
|
||||
.raw_value()
|
||||
},
|
||||
irq_mode = const {
|
||||
Cpsr::new_with_raw_value(0)
|
||||
.with_mode(ProcessorMode::Irq)
|
||||
.with_i(true)
|
||||
.with_f(true)
|
||||
.raw_value()
|
||||
},
|
||||
svc_mode = const {
|
||||
Cpsr::new_with_raw_value(0)
|
||||
.with_mode(ProcessorMode::Svc)
|
||||
.with_i(true)
|
||||
.with_f(true)
|
||||
.raw_value()
|
||||
},
|
||||
und_mode = const {
|
||||
Cpsr::new_with_raw_value(0)
|
||||
.with_mode(ProcessorMode::Und)
|
||||
.with_i(true)
|
||||
.with_f(true)
|
||||
.raw_value()
|
||||
},
|
||||
abt_mode = const {
|
||||
Cpsr::new_with_raw_value(0)
|
||||
.with_mode(ProcessorMode::Abt)
|
||||
.with_i(true)
|
||||
.with_f(true)
|
||||
.raw_value()
|
||||
},
|
||||
sys_mode = const {
|
||||
Cpsr::new_with_raw_value(0)
|
||||
.with_mode(ProcessorMode::Sys)
|
||||
.with_i(true)
|
||||
.with_f(true)
|
||||
.raw_value()
|
||||
},
|
||||
);
|
||||
|
||||
@@ -8,9 +8,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
# [v0.4.0] 2026-05-15
|
||||
|
||||
- Better names for `uart` registers and register fields. Replaced various abbreviations.
|
||||
- Update `gic` module: Add some better type for SGIR field.
|
||||
|
||||
# [v0.3.0] 2026-05-08
|
||||
|
||||
- Better names for various registers. Replaced abbreviations like SR, MR, CR, IER, IMR etc.
|
||||
|
||||
# [v0.2.0] 2026-04-01
|
||||
|
||||
- Renamed all register blocks to `Registers` to subblocks to `<Subblock>Registers`.
|
||||
- Updated IPTR registers in the GIC module to use a custom register type instead of a raw u32.
|
||||
- Added SDIO registers.
|
||||
- Fixed wrong position in QSPI reset register in SLCR Module
|
||||
- Added some missing reset register definitions.
|
||||
- Added `defmt` support
|
||||
- Some other minor renaming of registers (e.g. `ctrl` replaced by `control`)
|
||||
|
||||
# [v0.1.1] 2025-10-09
|
||||
|
||||
@@ -20,6 +35,9 @@ Documentation fix
|
||||
|
||||
Initial release
|
||||
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-v0.1.1...HEAD
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-v0.4.0...HEAD
|
||||
[v0.4.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-v0.3.0...zynq7000-v0.4.0
|
||||
[v0.3.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-v0.2.0...zynq7000-v0.3.0
|
||||
[v0.2.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-v0.1.0...zynq7000-v0.2.0
|
||||
[v0.1.1]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-v0.1.0...zynq7000-v0.1.1
|
||||
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/tag/zynq7000-v0.1.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "zynq7000"
|
||||
version = "0.1.1"
|
||||
version = "0.4.0"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
edition = "2024"
|
||||
description = "Peripheral Access Crate (PAC) for the Zynq7000 family of SoCs"
|
||||
@@ -13,16 +13,19 @@ categories = ["embedded", "no-std", "hardware-support"]
|
||||
[dependencies]
|
||||
static_assertions = "1.1"
|
||||
derive-mmio = { version = "0.6", default-features = false }
|
||||
bitbybit = "1.4"
|
||||
bitbybit = "2"
|
||||
arbitrary-int = "2"
|
||||
rustversion = "1"
|
||||
thiserror = { version = "2", default-features = false }
|
||||
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
|
||||
defmt = { version = "1", optional = true }
|
||||
|
||||
[features]
|
||||
defmt = ["dep:defmt", "arbitrary-int/defmt"]
|
||||
|
||||
[dev-dependencies]
|
||||
approx = "0.5"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["armv7a-none-eabihf"]
|
||||
cargo-args = ["-Z", "build-std=core"]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
|
||||
This repository contains the Peripheral Access Crate (PAC) for the AMD Zynq7000 SoC family.
|
||||
|
||||
If you are interested in higher-level abstractions, it is recommended you visit
|
||||
the [`zynq7000-hal`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000-hal)
|
||||
HAL crate which build on top of this PAC.
|
||||
If you are interested in higher-level abstractions, it is recommended you visit the
|
||||
[`zynq7000-hal`](../zynq7000-hal) HAL crate which build on top of this PAC.
|
||||
|
||||
Check out the documentation for more details.
|
||||
|
||||
+371
-55
@@ -6,6 +6,7 @@ pub mod regs {
|
||||
|
||||
#[bitbybit::bitenum(u2)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum DataBusWidth {
|
||||
_32Bit = 0b00,
|
||||
_16Bit = 0b01,
|
||||
@@ -13,11 +14,18 @@ pub mod regs {
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum SoftReset {
|
||||
Reset = 0,
|
||||
Active = 1,
|
||||
}
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_fields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DdrcControl {
|
||||
#[bit(16, rw)]
|
||||
disable_auto_refresh: bool,
|
||||
@@ -37,7 +45,14 @@ pub mod regs {
|
||||
soft_reset: SoftReset,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct TwoRankConfig {
|
||||
#[bits(14..=18, rw)]
|
||||
addrmap_cs_bit0: u5,
|
||||
@@ -50,7 +65,13 @@ pub mod regs {
|
||||
}
|
||||
|
||||
/// Queue control for the low priority and high priority read queues.
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_fields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct LprHprQueueControl {
|
||||
#[bits(22..=25, rw)]
|
||||
xact_run_length: u4,
|
||||
@@ -60,7 +81,13 @@ pub mod regs {
|
||||
min_non_critical_x32: u11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_fields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct WriteQueueControl {
|
||||
#[bits(15..=25, rw)]
|
||||
max_starve_x32: u11,
|
||||
@@ -70,7 +97,13 @@ pub mod regs {
|
||||
min_non_critical_x32: u11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_fields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
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.
|
||||
@@ -85,7 +118,13 @@ pub mod regs {
|
||||
t_rc: u6,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_fields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DramParamReg1 {
|
||||
#[bits(28..=31, rw)]
|
||||
t_cke: u4,
|
||||
@@ -101,7 +140,13 @@ pub mod regs {
|
||||
wr2pre: u5,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_fields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DramParamReg2 {
|
||||
#[bits(28..=31, rw)]
|
||||
t_rcd: u4,
|
||||
@@ -121,12 +166,19 @@ pub mod regs {
|
||||
|
||||
/// Weird naming.
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum MobileSetting {
|
||||
Ddr2Ddr3 = 0,
|
||||
Lpddr2 = 1,
|
||||
}
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_fields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DramParamReg3 {
|
||||
#[bit(30, rw)]
|
||||
disable_pad_pd_feature: bool,
|
||||
@@ -154,12 +206,19 @@ pub mod regs {
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ModeRegisterType {
|
||||
Write = 0,
|
||||
Read = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_fields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DramParamReg4 {
|
||||
#[bit(27, rw)]
|
||||
mr_rdata_valid: bool,
|
||||
@@ -179,7 +238,13 @@ pub mod regs {
|
||||
enable_2t_timing_mode: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DramInitParam {
|
||||
#[bits(11..=13, rw)]
|
||||
t_mrd: u3,
|
||||
@@ -189,7 +254,13 @@ pub mod regs {
|
||||
final_wait_x32: u7,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DramEmr {
|
||||
#[bits(16..=31, rw)]
|
||||
emr3: u16,
|
||||
@@ -197,7 +268,13 @@ pub mod regs {
|
||||
emr2: u16,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DramEmrMr {
|
||||
#[bits(16..=31, rw)]
|
||||
emr: u16,
|
||||
@@ -205,7 +282,13 @@ pub mod regs {
|
||||
mr: u16,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DramBurst8ReadWrite {
|
||||
#[bits(0..=3, rw)]
|
||||
burst_rdwr: u4,
|
||||
@@ -217,7 +300,13 @@ pub mod regs {
|
||||
burstchop: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DisableDq {
|
||||
#[bit(1, rw)]
|
||||
dis_dq: bool,
|
||||
@@ -225,7 +314,13 @@ pub mod regs {
|
||||
force_low_pri_n: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DramAddrMapBank {
|
||||
#[bits(16..=19, rw)]
|
||||
addrmap_bank_b6: u4,
|
||||
@@ -239,7 +334,13 @@ pub mod regs {
|
||||
addrmap_bank_b0: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DramAddrMapColumn {
|
||||
#[bits(28..=31, rw)]
|
||||
addrmap_col_b11: u4,
|
||||
@@ -259,7 +360,13 @@ pub mod regs {
|
||||
addrmap_col_b2: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DramAddrMapRow {
|
||||
#[bits(24..=27, rw)]
|
||||
addrmap_row_b15: u4,
|
||||
@@ -277,7 +384,13 @@ pub mod regs {
|
||||
addrmap_row_b0: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DramOdt {
|
||||
#[bits(16..=17, rw)]
|
||||
phy_idle_local_odt: u2,
|
||||
@@ -291,7 +404,13 @@ pub mod regs {
|
||||
rank0_rd_odt: u3,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct PhyCmdTimeoutRdDataCpt {
|
||||
#[bits(28..=31, rw)]
|
||||
wrlvl_num_of_dq0: u4,
|
||||
@@ -314,19 +433,32 @@ pub mod regs {
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum DllCalibSel {
|
||||
Periodic = 0,
|
||||
Manual = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_fields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DllCalib {
|
||||
#[bit(16, rw)]
|
||||
sel: DllCalibSel,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct OdtDelayHold {
|
||||
#[bits(12..=15, rw)]
|
||||
wr_odt_hold: u4,
|
||||
@@ -338,7 +470,13 @@ pub mod regs {
|
||||
rd_odt_delay: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct CtrlReg1 {
|
||||
#[bit(12, rw)]
|
||||
selfref_enable: bool,
|
||||
@@ -356,7 +494,13 @@ pub mod regs {
|
||||
pageclose: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct CtrlReg2 {
|
||||
#[bit(17, rw)]
|
||||
go_2_critcal_enable: bool,
|
||||
@@ -364,7 +508,13 @@ pub mod regs {
|
||||
go_2_critical_hysteresis: u8,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct CtrlReg3 {
|
||||
#[bits(16..=25, rw)]
|
||||
dfi_t_wlmrd: u10,
|
||||
@@ -374,7 +524,13 @@ pub mod regs {
|
||||
wrlvl_ww: u8,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct CtrlReg4 {
|
||||
#[bits(8..=15, rw)]
|
||||
dfi_t_ctrlupd_interval_max_x1024: u8,
|
||||
@@ -382,7 +538,13 @@ pub mod regs {
|
||||
dfi_t_ctrlupd_interval_min_x1024: u8,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct CtrlReg5 {
|
||||
#[bits(20..=25, rw)]
|
||||
t_ckesr: u6,
|
||||
@@ -398,7 +560,13 @@ pub mod regs {
|
||||
dfi_t_ctrl_delay: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct CtrlReg6 {
|
||||
#[bits(16..=19, rw)]
|
||||
t_cksx: u4,
|
||||
@@ -412,7 +580,13 @@ pub mod regs {
|
||||
t_ckpde: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct CheTZq {
|
||||
#[bits(22..=31, rw)]
|
||||
t_zq_short_nop: u10,
|
||||
@@ -426,7 +600,13 @@ pub mod regs {
|
||||
dis_auto_zq: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct CheTZqShortInterval {
|
||||
#[bits(20..=27, rw)]
|
||||
dram_rstn_x1024: u8,
|
||||
@@ -434,7 +614,13 @@ pub mod regs {
|
||||
t_zq_short_interval: u20,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DeepPowerdown {
|
||||
#[bits(1..=8, rw)]
|
||||
deep_powerdown_to_x1024: u8,
|
||||
@@ -442,7 +628,13 @@ pub mod regs {
|
||||
enable: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct Reg2c {
|
||||
#[bit(28, rw)]
|
||||
dfi_rd_data_eye_train: bool,
|
||||
@@ -460,13 +652,25 @@ pub mod regs {
|
||||
dfi_wrlvl_max_x1024: u12,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct Reg2d {
|
||||
#[bit(9, rw)]
|
||||
skip_ocd: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DfiTiming {
|
||||
#[bits(15..=24, rw)]
|
||||
dfi_t_ctrlup_max: u10,
|
||||
@@ -476,7 +680,13 @@ pub mod regs {
|
||||
dfi_t_rddata_enable: u5,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct CheEccControl {
|
||||
#[bit(1, rw)]
|
||||
clear_correctable_errors: bool,
|
||||
@@ -485,13 +695,20 @@ pub mod regs {
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u3, exhaustive = false)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum EccMode {
|
||||
NoEcc = 0b000,
|
||||
SecDecOverOneBeat = 0b100,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_fields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct EccScrub {
|
||||
#[bit(3, rw)]
|
||||
disable_scrub: bool,
|
||||
@@ -499,7 +716,13 @@ pub mod regs {
|
||||
ecc_mode: Option<EccMode>,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct PhyReceiverEnable {
|
||||
#[bits(4..=7, rw)]
|
||||
phy_dif_off: u4,
|
||||
@@ -507,7 +730,13 @@ pub mod regs {
|
||||
phy_dif_on: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct PhyConfig {
|
||||
#[bits(24..=30, rw)]
|
||||
dq_offset: u7,
|
||||
@@ -521,7 +750,13 @@ pub mod regs {
|
||||
data_slice_in_use: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct PhyInitRatio {
|
||||
#[bits(10..=19, rw)]
|
||||
gatelvl_init_ratio: u10,
|
||||
@@ -529,7 +764,13 @@ pub mod regs {
|
||||
wrlvl_init_ratio: u10,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct PhyDqsConfig {
|
||||
#[bits(11..=19, rw)]
|
||||
dqs_slave_delay: u9,
|
||||
@@ -539,7 +780,13 @@ pub mod regs {
|
||||
dqs_slave_ratio: u10,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct PhyWriteEnableConfig {
|
||||
#[bits(12..=20, rw)]
|
||||
fifo_we_in_delay: u9,
|
||||
@@ -549,7 +796,13 @@ pub mod regs {
|
||||
fifo_we_slave_ratio: u11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct PhyWriteDataSlaveConfig {
|
||||
#[bits(11..=19, rw)]
|
||||
wr_data_slave_delay: u9,
|
||||
@@ -559,7 +812,13 @@ pub mod regs {
|
||||
wr_data_slave_ratio: u10,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct Reg64 {
|
||||
#[bit(30, rw)]
|
||||
cmd_latency: bool,
|
||||
@@ -579,7 +838,13 @@ pub mod regs {
|
||||
bl2: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct Reg65 {
|
||||
#[bits(18..=19, rw)]
|
||||
ctrl_slave_delay: u2,
|
||||
@@ -599,7 +864,13 @@ pub mod regs {
|
||||
wr_rl_delay: u5,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct AxiPriorityWritePort {
|
||||
#[bit(18, rw)]
|
||||
disable_page_match: bool,
|
||||
@@ -611,7 +882,13 @@ pub mod regs {
|
||||
pri_wr_port: u10,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct AxiPriorityReadPort {
|
||||
#[bit(19, rw)]
|
||||
enable_hpr: bool,
|
||||
@@ -625,7 +902,13 @@ pub mod regs {
|
||||
pri_rd_port_n: u10,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct ExclusiveAccessConfig {
|
||||
#[bits(9..=17, rw)]
|
||||
access_id1_port: u9,
|
||||
@@ -634,13 +917,20 @@ pub mod regs {
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum LpddrBit {
|
||||
Ddr2Ddr3 = 0,
|
||||
Lpddr2 = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_fields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct LpddrControl0 {
|
||||
#[bits(4..=11, rw)]
|
||||
mr4_margin: u8,
|
||||
@@ -652,13 +942,25 @@ pub mod regs {
|
||||
lpddr2: LpddrBit,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct LpddrControl1 {
|
||||
#[bits(0..=31, rw)]
|
||||
mr4_read_interval: u32,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct LpddrControl2 {
|
||||
#[bits(12..=21, rw)]
|
||||
t_mrw: u10,
|
||||
@@ -668,7 +970,13 @@ pub mod regs {
|
||||
min_stable_clock_x1: u4,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct LpddrControl3 {
|
||||
#[bits(8..=17, rw)]
|
||||
dev_zqinit_x32: u10,
|
||||
@@ -678,6 +986,7 @@ pub mod regs {
|
||||
|
||||
#[bitbybit::bitenum(u3, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum OperatingMode {
|
||||
DdrcInit = 0,
|
||||
NormalOperation = 1,
|
||||
@@ -703,12 +1012,19 @@ pub mod regs {
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum DebugStallBit {
|
||||
CommandsAccepted = 0,
|
||||
CommandsNotAccepted = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_fields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct ModeStatus {
|
||||
#[bits(16..=20, r)]
|
||||
dbg_hpr_queue_depth: u5,
|
||||
|
||||
@@ -4,6 +4,7 @@ pub const DEVCFG_BASE_ADDR: usize = 0xF8007000;
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PlConfigAccess {
|
||||
/// Used for JTAG access
|
||||
TapController = 0,
|
||||
@@ -13,6 +14,7 @@ pub enum PlConfigAccess {
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ConfigAccessPortSelect {
|
||||
/// Internal Configuration Access Port (ICAP), using PL or PS-based software.
|
||||
Icap = 0,
|
||||
@@ -22,6 +24,7 @@ pub enum ConfigAccessPortSelect {
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum TimerSelect {
|
||||
_64kTimer = 0,
|
||||
_4kTimer = 1,
|
||||
@@ -29,6 +32,7 @@ pub enum TimerSelect {
|
||||
|
||||
#[bitbybit::bitenum(u3, exhaustive = false)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum AesEnable {
|
||||
Disable = 0b000,
|
||||
Enable = 0b111,
|
||||
@@ -36,6 +40,7 @@ pub enum AesEnable {
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PsBootMode {
|
||||
NonSecure = 0,
|
||||
Secure = 1,
|
||||
@@ -43,11 +48,18 @@ pub enum PsBootMode {
|
||||
|
||||
#[bitbybit::bitenum(u3, exhaustive = false)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ArmDapEnable {
|
||||
Enabled = 0b111,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_fields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct Control {
|
||||
#[bit(31, rw)]
|
||||
force_reset: bool,
|
||||
@@ -95,7 +107,7 @@ pub struct Control {
|
||||
|
||||
/// 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)]
|
||||
#[bitbybit::bitfield(u32, debug, debug, defmt_fields(feature = "defmt"), forbid_overlaps)]
|
||||
pub struct Lock {
|
||||
#[bit(4, rw)]
|
||||
aes_fuse: bool,
|
||||
@@ -113,6 +125,7 @@ pub struct Lock {
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum EdgeConfig {
|
||||
Falling = 0,
|
||||
Rising = 1,
|
||||
@@ -121,6 +134,7 @@ pub enum EdgeConfig {
|
||||
/// Related to the full level for reads, and the empty level for writes.
|
||||
#[bitbybit::bitenum(u2, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum FifoThresholdConfig {
|
||||
OneFourth = 0b00,
|
||||
HalfEmpty = 0b01,
|
||||
@@ -128,7 +142,7 @@ pub enum FifoThresholdConfig {
|
||||
EmptyOrFull = 0b11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
#[bitbybit::bitfield(u32, debug, defmt_fields(feature = "defmt"), forbid_overlaps)]
|
||||
pub struct Config {
|
||||
#[bits(10..=11, rw)]
|
||||
read_fifo_threshhold: FifoThresholdConfig,
|
||||
@@ -144,7 +158,7 @@ pub struct Config {
|
||||
disable_dst_incremenet: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
#[bitbybit::bitfield(u32, debug, defmt_fields(feature = "defmt"), forbid_overlaps)]
|
||||
pub struct Interrupt {
|
||||
/// Tri-state PL IO during HIZ.
|
||||
#[bit(31, rw)]
|
||||
@@ -199,7 +213,7 @@ pub struct Interrupt {
|
||||
negative_edge_pl_init: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
#[bitbybit::bitfield(u32, debug, defmt_bitfields(feature = "defmt"), forbid_overlaps)]
|
||||
pub struct MiscControl {
|
||||
#[bits(28..=31, r)]
|
||||
ps_version: u4,
|
||||
@@ -213,6 +227,7 @@ pub struct MiscControl {
|
||||
|
||||
#[bitbybit::bitenum(u2, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum UnacknowledgedDmaTransfers {
|
||||
None = 0b00,
|
||||
One = 0b01,
|
||||
@@ -220,7 +235,7 @@ pub enum UnacknowledgedDmaTransfers {
|
||||
ThreeOrMore = 0b11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
#[bitbybit::bitfield(u32, debug, defmt_fields(feature = "defmt"), forbid_overlaps)]
|
||||
pub struct Status {
|
||||
#[bit(31, rw)]
|
||||
dma_command_queue_full: bool,
|
||||
|
||||
@@ -4,8 +4,7 @@ use arbitrary_int::{u2, u5};
|
||||
pub const GEM_0_BASE_ADDR: usize = 0xE000_B000;
|
||||
pub const GEM_1_BASE_ADDR: usize = 0xE000_C000;
|
||||
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
#[bitbybit::bitfield(u32, debug, defmt_bitfields(feature = "defmt"), forbid_overlaps)]
|
||||
pub struct NetworkControl {
|
||||
#[bit(18, w)]
|
||||
flush_next_rx_dpram_pkt: bool,
|
||||
@@ -41,6 +40,7 @@ pub struct NetworkControl {
|
||||
|
||||
/// The speed mode selects between 10 Mbps and 100 Mbps if the Gigabit enable bit is cleared.
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SpeedMode {
|
||||
Low10Mbps = 0,
|
||||
@@ -49,6 +49,7 @@ pub enum SpeedMode {
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PcsSelect {
|
||||
GmiiMii = 0,
|
||||
Tbi = 1,
|
||||
@@ -56,6 +57,7 @@ pub enum PcsSelect {
|
||||
|
||||
#[bitbybit::bitenum(u3, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum MdcClockDivisor {
|
||||
Div8 = 0,
|
||||
Div16 = 1,
|
||||
@@ -82,7 +84,13 @@ impl MdcClockDivisor {
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
forbid_overlaps,
|
||||
defmt_fields(feature = "defmt")
|
||||
)]
|
||||
pub struct NetworkConfig {
|
||||
#[bit(30, rw)]
|
||||
ignore_ipg_rx_error: bool,
|
||||
@@ -140,7 +148,7 @@ pub struct NetworkConfig {
|
||||
}
|
||||
|
||||
/// PHY management status information.
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
#[bitbybit::bitfield(u32, debug, forbid_overlaps, defmt_fields(feature = "defmt"))]
|
||||
pub struct NetworkStatus {
|
||||
#[bit(6, r)]
|
||||
pfc_pri_pause_neg: bool,
|
||||
@@ -159,6 +167,7 @@ pub struct NetworkStatus {
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum BurstLength {
|
||||
Single,
|
||||
#[default]
|
||||
@@ -180,12 +189,14 @@ impl BurstLength {
|
||||
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum AhbEndianess {
|
||||
Little = 0,
|
||||
Big = 1,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct DmaRxBufSize(u8);
|
||||
|
||||
impl DmaRxBufSize {
|
||||
@@ -213,7 +224,13 @@ impl DmaRxBufSize {
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
forbid_overlaps,
|
||||
defmt_fields(feature = "defmt")
|
||||
)]
|
||||
pub struct DmaConfig {
|
||||
#[bit(24, rw)]
|
||||
discard_when_ahb_full: bool,
|
||||
@@ -241,7 +258,7 @@ pub struct DmaConfig {
|
||||
burst_length: u5,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
#[bitbybit::bitfield(u32, debug, forbid_overlaps, defmt_bitfields(feature = "defmt"))]
|
||||
pub struct TxStatus {
|
||||
#[bit(8, rw)]
|
||||
hresp_not_ok: bool,
|
||||
@@ -272,7 +289,7 @@ impl TxStatus {
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
#[bitbybit::bitfield(u32, debug, forbid_overlaps, defmt_bitfields(feature = "defmt"))]
|
||||
pub struct RxStatus {
|
||||
#[bit(3, rw)]
|
||||
hresp_not_ok: bool,
|
||||
@@ -290,7 +307,13 @@ impl RxStatus {
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
forbid_overlaps,
|
||||
defmt_bitfields(feature = "defmt")
|
||||
)]
|
||||
pub struct InterruptStatus {
|
||||
#[bit(26, rw)]
|
||||
tsu_sec_incr: bool,
|
||||
@@ -381,13 +404,20 @@ impl InterruptControl {
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u2, exhaustive = false)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PhyOperation {
|
||||
Read = 0b10,
|
||||
Write = 0b01,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
forbid_overlaps,
|
||||
defmt_fields(feature = "defmt")
|
||||
)]
|
||||
pub struct PhyMaintenance {
|
||||
/// Must be 1 for Clause 22 operations.
|
||||
#[bit(30, rw)]
|
||||
@@ -404,13 +434,25 @@ pub struct PhyMaintenance {
|
||||
data: u16,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
forbid_overlaps,
|
||||
defmt_bitfields(feature = "defmt")
|
||||
)]
|
||||
pub struct PauseQuantum {
|
||||
#[bits(0..=15, rw)]
|
||||
value: u16,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
forbid_overlaps,
|
||||
defmt_bitfields(feature = "defmt")
|
||||
)]
|
||||
pub struct MatchRegister {
|
||||
#[bit(31, rw)]
|
||||
copy_enable: bool,
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
//! # GIC (Generic Interrupt Controller) register module.
|
||||
pub use crate::mpcore::{GICC_BASE_ADDR, GICD_BASE_ADDR};
|
||||
use arbitrary_int::{u2, u3, u5, u10};
|
||||
use arbitrary_int::{u2, u3, u4, u5, u10, u11};
|
||||
use static_assertions::const_assert_eq;
|
||||
|
||||
/// Distributor Control Register
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct DistributorControlRegister {
|
||||
#[bit(1, rw)]
|
||||
enable_non_secure: bool,
|
||||
@@ -13,7 +19,7 @@ pub struct DistributorControlRegister {
|
||||
}
|
||||
|
||||
/// Read only bit. This register only returns fixed constants.
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
#[bitbybit::bitfield(u32, debug, defmt_bitfields(feature = "defmt"), forbid_overlaps)]
|
||||
pub struct TypeRegister {
|
||||
#[bits(11..=15, r)]
|
||||
lspi: u5,
|
||||
@@ -40,19 +46,15 @@ impl TypeRegister {
|
||||
|
||||
pub type Typer = TypeRegister;
|
||||
|
||||
// TODO: Use bitbybit debug derive if new release was released.
|
||||
/// Interrupt processor target register (IPTR).
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct InterruptProcessorTargetRegister {
|
||||
/// Target array. Every register holds the information for 4 interrupts.
|
||||
#[bits(0..=1, rw, stride = 8)]
|
||||
targets: [u2; 4],
|
||||
}
|
||||
|
||||
#[deprecated(note = "Use DistributorRegisters instead")]
|
||||
pub type GicDistributorTyper = DistributorRegisters;
|
||||
|
||||
/// GIC Distributor registers.
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[repr(C, align(8))]
|
||||
@@ -87,7 +89,7 @@ pub struct DistributorRegisters {
|
||||
/// Interrupt Priority Registers
|
||||
pub ipr: [u32; 0x18],
|
||||
_reserved_11: [u32; 0xE8],
|
||||
/// Interrupt Processor Targes Registers
|
||||
/// Interrupt Processor Targets Registers
|
||||
pub iptr_sgi: [InterruptProcessorTargetRegister; 0x4],
|
||||
/// These are read-only because they always target their private CPU.
|
||||
#[mmio(PureRead)]
|
||||
@@ -97,6 +99,7 @@ pub struct DistributorRegisters {
|
||||
_reserved_12: [u32; 0xE8],
|
||||
/// Interrupt Configuration Registers
|
||||
/// Interupt sensitivity register for software generated interrupts (SGI)
|
||||
#[mmio(PureRead)]
|
||||
pub icfr_0_sgi: u32,
|
||||
/// Interupt sensitivity register for private peripheral interrupts (PPI)
|
||||
pub icfr_1_ppi: u32,
|
||||
@@ -110,7 +113,7 @@ pub struct DistributorRegisters {
|
||||
pub spi_status_1: u32,
|
||||
_reserved_14: [u32; 0x7D],
|
||||
/// Software Generated Interrupt Register.
|
||||
pub sgir: u32,
|
||||
pub sgir: SoftwareGeneratedInterruptRegister,
|
||||
_reserved_15: [u32; 0x33],
|
||||
pub pidr_4: u32,
|
||||
pub pidr_5: u32,
|
||||
@@ -141,7 +144,13 @@ impl DistributorRegisters {
|
||||
}
|
||||
|
||||
/// CPU interface control register.
|
||||
#[bitbybit::bitfield(u32, default = 0x0, debug)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct InterfaceControl {
|
||||
#[bit(4, rw)]
|
||||
sbpr: bool,
|
||||
@@ -156,14 +165,14 @@ pub struct InterfaceControl {
|
||||
}
|
||||
|
||||
/// Priority Mask Register
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
#[bitbybit::bitfield(u32, debug, defmt_bitfields(feature = "defmt"), forbid_overlaps)]
|
||||
pub struct PriorityRegister {
|
||||
#[bits(0..=7, rw)]
|
||||
priority: u8,
|
||||
}
|
||||
|
||||
/// Interrupt acknowledge register.
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
#[bitbybit::bitfield(u32, debug, defmt_bitfields(feature = "defmt"), forbid_overlaps)]
|
||||
pub struct InterruptSignalRegister {
|
||||
#[bits(10..=12, rw)]
|
||||
cpu_id: u3,
|
||||
@@ -171,8 +180,45 @@ pub struct InterruptSignalRegister {
|
||||
ack_int_id: u10,
|
||||
}
|
||||
|
||||
#[deprecated(note = "Use DistributorRegisters instead")]
|
||||
pub type GicCpuInterfaceIar = CpuInterfaceRegisters;
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SecurityCondition {
|
||||
IfConfiguredAsSecure = 0,
|
||||
IfConfiguredAsNonSecure = 1,
|
||||
}
|
||||
|
||||
#[bitbybit::bitenum(u2, exhaustive = true)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum TargetListFilter {
|
||||
SendToCpusInTargetList = 0b00,
|
||||
SendToAllOtherCpus = 0b01,
|
||||
SendToSelf = 0b10,
|
||||
Reserved = 0b11,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
debug,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
pub struct SoftwareGeneratedInterruptRegister {
|
||||
#[bits(24..=25, rw)]
|
||||
target_list_filter: TargetListFilter,
|
||||
#[bits(16..=23, rw)]
|
||||
cpu_target_list: u8,
|
||||
/// SATT field.
|
||||
#[bit(15, rw)]
|
||||
security_condition: SecurityCondition,
|
||||
/// Should be zero.
|
||||
#[bits(4..=14, rw)]
|
||||
sbz: u11,
|
||||
#[bits(0..=3, rw)]
|
||||
interrupt_id: u4,
|
||||
}
|
||||
|
||||
/// GIC CPU interface registers.
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
//! # GPIO register module.
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
#[bitbybit::bitfield(
|
||||
u32,
|
||||
default = 0x0,
|
||||
defmt_bitfields(feature = "defmt"),
|
||||
forbid_overlaps
|
||||
)]
|
||||
#[derive(Debug)]
|
||||
pub struct MaskedOutput {
|
||||
#[bits(16..=31, w)]
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
pub const GTC_BASE_ADDR: usize = super::mpcore::MPCORE_BASE_ADDR + 0x0000_0200;
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
pub struct GtcControl {
|
||||
#[bitbybit::bitfield(u32, debug, forbid_overlaps, defmt_bitfields(feature = "defmt"))]
|
||||
pub struct Control {
|
||||
#[bits(8..=15, rw)]
|
||||
prescaler: u8,
|
||||
#[bit(3, rw)]
|
||||
@@ -16,7 +16,7 @@ pub struct GtcControl {
|
||||
enable: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u32, debug)]
|
||||
#[bitbybit::bitfield(u32, debug, forbid_overlaps, defmt_bitfields(feature = "defmt"))]
|
||||
pub struct InterruptStatus {
|
||||
#[bit(0, rw)]
|
||||
event_flag: bool,
|
||||
@@ -31,10 +31,10 @@ pub struct Registers {
|
||||
/// Count register 1, upper 32 bits
|
||||
count_upper: u32,
|
||||
/// Control register
|
||||
ctrl: GtcControl,
|
||||
control: Control,
|
||||
/// Interrupt status register
|
||||
#[mmio(PureRead, Write)]
|
||||
isr: InterruptStatus,
|
||||
interrupt_status: InterruptStatus,
|
||||
/// Comparator 0, lower 32 bits
|
||||
comparator_lower: u32,
|
||||
/// Comparator 1, upper 32 bits
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user