From af23c47fc7682fddf6e0c7d6ca1a3c9b15112d7b Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 15 May 2026 13:53:02 +0200 Subject: [PATCH] continue SPI slave --- .../embassy/src/bin/dht22-open-drain-pins.rs | 8 +- .../examples/embassy/src/bin/embassy-hello.rs | 9 +- .../embassy/src/bin/logger-non-blocking.rs | 21 +- firmware/examples/embassy/src/bin/pwm.rs | 8 +- firmware/examples/simple/src/bin/gtc-ticks.rs | 9 +- firmware/examples/simple/src/bin/logger.rs | 8 +- .../examples/zedboard/src/bin/ethernet.rs | 2 +- .../zedboard/src/bin/l3gd20h-i2c-mio.rs | 10 +- .../zedboard/src/bin/l3gd20h-spi-mio.rs | 30 +- .../examples/zedboard/src/bin/oled-async.rs | 34 +- firmware/examples/zedboard/src/bin/oled.rs | 36 +- firmware/examples/zedboard/src/bin/qspi.rs | 9 +- firmware/examples/zedboard/src/bin/sdio.rs | 8 +- .../examples/zedboard/src/bin/spi-slave.rs | 587 ++++++++++++++++++ .../zedboard/src/bin/uart-blocking.rs | 8 +- .../zedboard/src/bin/uart-non-blocking.rs | 16 +- firmware/examples/zedboard/src/main.rs | 10 +- firmware/zedboard-fsbl/src/main.rs | 11 +- firmware/zedboard-qspi-flasher/src/main.rs | 9 +- firmware/zynq7000-hal/src/log.rs | 118 +++- firmware/zynq7000-hal/src/sd/mod.rs | 33 +- firmware/zynq7000-hal/src/spi/asynch.rs | 37 +- firmware/zynq7000-hal/src/spi/mod.rs | 116 +++- firmware/zynq7000-hal/src/spi/slave.rs | 76 +++ firmware/zynq7000-hal/src/uart/mod.rs | 53 +- firmware/zynq7000/src/slcr/mod.rs | 20 +- firmware/zynq7000/src/spi.rs | 35 +- zedboard-fpga-design/src/zedboard-bd.tcl | 1 - 28 files changed, 1055 insertions(+), 267 deletions(-) create mode 100644 firmware/examples/zedboard/src/bin/spi-slave.rs create mode 100644 firmware/zynq7000-hal/src/spi/slave.rs diff --git a/firmware/examples/embassy/src/bin/dht22-open-drain-pins.rs b/firmware/examples/embassy/src/bin/dht22-open-drain-pins.rs index 54ccd2b..b2997ae 100644 --- a/firmware/examples/embassy/src/bin/dht22-open-drain-pins.rs +++ b/firmware/examples/embassy/src/bin/dht22-open-drain-pins.rs @@ -72,13 +72,7 @@ async fn main(_spawner: Spawner) -> ! { uart.write_all(b"-- Zynq 7000 DHT22 --\n\r").unwrap(); // Safety: We are not multi-threaded yet. - unsafe { - zynq7000_hal::log::uart_blocking::init_unsafe_single_core( - uart, - log::LevelFilter::Trace, - false, - ) - }; + zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false); let mut delay = Delay; diff --git a/firmware/examples/embassy/src/bin/embassy-hello.rs b/firmware/examples/embassy/src/bin/embassy-hello.rs index a76a7c7..f0e503a 100644 --- a/firmware/examples/embassy/src/bin/embassy-hello.rs +++ b/firmware/examples/embassy/src/bin/embassy-hello.rs @@ -51,14 +51,7 @@ async fn main(_spawner: Spawner) -> ! { .unwrap(); uart.write_all(b"-- Zynq 7000 Embassy Hello World --\n\r") .unwrap(); - // Safety: We are not multi-threaded yet. - unsafe { - zynq7000_hal::log::uart_blocking::init_unsafe_single_core( - uart, - log::LevelFilter::Trace, - false, - ) - }; + zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false); let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); diff --git a/firmware/examples/embassy/src/bin/logger-non-blocking.rs b/firmware/examples/embassy/src/bin/logger-non-blocking.rs index bb75480..6c3762f 100644 --- a/firmware/examples/embassy/src/bin/logger-non-blocking.rs +++ b/firmware/examples/embassy/src/bin/logger-non-blocking.rs @@ -8,7 +8,7 @@ use embassy_executor::Spawner; use embassy_time::{Duration, Ticker}; use embedded_hal::digital::StatefulOutputPin; use embedded_io::Write; -use log::{error, info}; +use log::info; use zynq7000::Peripherals; use zynq7000_hal::{ BootMode, @@ -19,7 +19,7 @@ use zynq7000_hal::{ gtc::GlobalTimerCounter, l2_cache, time::Hertz, - uart::{ClockConfig, Config, TxAsync, Uart}, + uart::{self, ClockConfig, Config, TxAsync, Uart}, }; use zynq7000_rt as _; @@ -69,9 +69,10 @@ async fn main(spawner: Spawner) -> ! { uart.flush().unwrap(); let (tx, _rx) = uart.split(); - let mut logger = TxAsync::new(tx, true); + let logger = TxAsync::new(tx, true); - let log_reader = zynq7000_hal::log::asynch::init(log::LevelFilter::Trace).unwrap(); + let mut log_runner = + zynq7000_hal::log::asynch::init_with_uart_tx(log::LevelFilter::Trace, logger).unwrap(); let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); @@ -80,14 +81,7 @@ async fn main(spawner: Spawner) -> ! { spawner.spawn(led_task(led).unwrap()); spawner.spawn(hello_task().unwrap()); - let mut log_buf: [u8; 2048] = [0; 2048]; - loop { - 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; - } - } + log_runner.run().await } #[embassy_executor::task] @@ -153,6 +147,7 @@ fn prefetch_handler(_faulting_addr: usize) -> ! { /// Panic handler #[panic_handler] fn panic(info: &PanicInfo) -> ! { - error!("Panic: {info:?}"); + let mut uart = unsafe { uart::Uart::steal(uart::UartId::Uart1) }; + writeln!(uart, "panic: {}\r", info).ok(); loop {} } diff --git a/firmware/examples/embassy/src/bin/pwm.rs b/firmware/examples/embassy/src/bin/pwm.rs index 7813f46..60b0182 100644 --- a/firmware/examples/embassy/src/bin/pwm.rs +++ b/firmware/examples/embassy/src/bin/pwm.rs @@ -79,13 +79,7 @@ async fn main(_spawner: Spawner) -> ! { .unwrap(); uart.write_all(b"-- Zynq 7000 PWM example--\n\r").unwrap(); // Safety: We are not multi-threaded yet. - unsafe { - zynq7000_hal::log::uart_blocking::init_unsafe_single_core( - uart, - log::LevelFilter::Trace, - false, - ) - }; + zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false); let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); diff --git a/firmware/examples/simple/src/bin/gtc-ticks.rs b/firmware/examples/simple/src/bin/gtc-ticks.rs index ca9bafe..49fe537 100644 --- a/firmware/examples/simple/src/bin/gtc-ticks.rs +++ b/firmware/examples/simple/src/bin/gtc-ticks.rs @@ -60,14 +60,7 @@ fn main() -> ! { .unwrap(); uart.write_all(b"-- Zynq 7000 GTC Ticks example --\n\r") .unwrap(); - // Safety: We are not multi-threaded yet. - unsafe { - zynq7000_hal::log::uart_blocking::init_unsafe_single_core( - uart, - log::LevelFilter::Trace, - false, - ) - }; + zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false); let mut led = Output::new_for_mio(mio_pins.mio7, PinState::Low); loop { diff --git a/firmware/examples/simple/src/bin/logger.rs b/firmware/examples/simple/src/bin/logger.rs index 0daac34..14ee8d1 100644 --- a/firmware/examples/simple/src/bin/logger.rs +++ b/firmware/examples/simple/src/bin/logger.rs @@ -60,13 +60,7 @@ fn main() -> ! { uart.write_all(b"-- Zynq 7000 Logging example --\n\r") .unwrap(); // Safety: We are not multi-threaded yet. - unsafe { - zynq7000_hal::log::uart_blocking::init_unsafe_single_core( - uart, - log::LevelFilter::Trace, - false, - ) - }; + zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false); let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {boot_mode:?}"); diff --git a/firmware/examples/zedboard/src/bin/ethernet.rs b/firmware/examples/zedboard/src/bin/ethernet.rs index ec5e262..fd3a28b 100644 --- a/firmware/examples/zedboard/src/bin/ethernet.rs +++ b/firmware/examples/zedboard/src/bin/ethernet.rs @@ -242,7 +242,7 @@ async fn main(spawner: Spawner) -> ! { .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_LEVEL, false) }; + zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, LOG_LEVEL, false); let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); diff --git a/firmware/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs b/firmware/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs index 747a463..961afcb 100644 --- a/firmware/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs +++ b/firmware/examples/zedboard/src/bin/l3gd20h-i2c-mio.rs @@ -80,14 +80,8 @@ async fn main(_spawner: Spawner) -> ! { .unwrap(); uart.write_all(b"-- Zynq 7000 Zedboard I2C L3GD20H example --\n\r") .unwrap(); - // Safety: We are not multi-threaded yet. - unsafe { - zynq7000_hal::log::uart_blocking::init_unsafe_single_core( - uart, - log::LevelFilter::Trace, - false, - ) - }; + + zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false); let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); diff --git a/firmware/examples/zedboard/src/bin/l3gd20h-spi-mio.rs b/firmware/examples/zedboard/src/bin/l3gd20h-spi-mio.rs index cb94f5a..9581595 100644 --- a/firmware/examples/zedboard/src/bin/l3gd20h-spi-mio.rs +++ b/firmware/examples/zedboard/src/bin/l3gd20h-spi-mio.rs @@ -12,7 +12,6 @@ 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; @@ -26,6 +25,7 @@ use zynq7000_hal::{ gpio::{GpioPins, Output, PinState}, gtc::GlobalTimerCounter, l2_cache, + log::asynch::UartLoggerRunner, spi::{self, SpiAsync, SpiWithHwCs, SpiWithHwCsAsync}, time::Hertz, uart::{self, TxAsync}, @@ -97,7 +97,10 @@ async fn main(spawner: Spawner) -> ! { uart.write_all(b"-- Zynq 7000 Zedboard SPI L3GD20H example --\n\r") .unwrap(); - let log_reader = zynq7000_hal::log::asynch::init(log::LevelFilter::Trace).unwrap(); + let (tx, _) = uart.split(); + let tx_async = TxAsync::new(tx, true); + let log_runner = + zynq7000_hal::log::asynch::init_with_uart_tx(log::LevelFilter::Trace, tx_async).unwrap(); let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); @@ -116,7 +119,7 @@ async fn main(spawner: Spawner) -> ! { spi::Config::new( // 10 MHz maximum rating of the sensor. zynq7000::spi::BaudDivSel::By64, - //l3gd20::MODE, + // l3gd20::MODE, embedded_hal::spi::MODE_3, spi::SlaveSelectConfig::AutoCsAutoStart, ), @@ -163,28 +166,17 @@ async fn main(spawner: Spawner) -> ! { } } - spawner.spawn(logger_task(uart, log_reader).unwrap()); + spawner.spawn(logger_task(log_runner).unwrap()); if BLOCKING { - blocking_application(mio_led, emio_leds, spi).await; + blocking_application(mio_led, emio_leds, spi).await } else { - non_blocking_application(mio_led, emio_leds, spi).await; + non_blocking_application(mio_led, emio_leds, spi).await } } #[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; - } - } +pub async fn logger_task(mut log_runner: UartLoggerRunner) -> ! { + log_runner.run().await } pub async fn blocking_application( diff --git a/firmware/examples/zedboard/src/bin/oled-async.rs b/firmware/examples/zedboard/src/bin/oled-async.rs index cca0fab..bb21808 100644 --- a/firmware/examples/zedboard/src/bin/oled-async.rs +++ b/firmware/examples/zedboard/src/bin/oled-async.rs @@ -5,18 +5,18 @@ 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 log::info; use ssd1306::{Ssd1306Async, prelude::*}; use zedboard::PS_CLOCK_FREQUENCY; use zynq7000_hal::{ BootMode, clocks, generic_interrupt_handler, gpio, gtc, + log::asynch::UartLoggerRunner, spi::{self, SpiAsync}, time::Hertz, uart::{self, TxAsync}, @@ -27,7 +27,7 @@ use tinybmp::Bmp; use zynq7000_rt as _; -const INIT_STRING: &str = "-- Zynq 7000 Zedboard OLED example --\n\r"; +const INIT_STRING: &str = "-- Zynq 7000 Zedboard Async OLED example --\n\r"; /// Entry point which calls the embassy main method. #[zynq7000_rt::entry] @@ -77,9 +77,11 @@ async fn main(spawner: Spawner) -> ! { 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 (tx, _) = uart.split(); + let tx_async = TxAsync::new(tx, true); + let log_runner = + zynq7000_hal::log::asynch::init_with_uart_tx(log::LevelFilter::Trace, tx_async) + .expect("Failed to initialize async logger"); let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); @@ -88,7 +90,7 @@ async fn main(spawner: Spawner) -> ! { clocks.io_clocks().spi_clk() ); - spawner.spawn(logger_task(uart, log_reader).unwrap()); + spawner.spawn(logger_task(log_runner).unwrap()); let mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low); let emio_leds: [gpio::Output; 8] = [ @@ -216,19 +218,8 @@ impl FerrisMovement { } #[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; - } - } +pub async fn logger_task(mut log_runner: UartLoggerRunner) -> ! { + log_runner.run().await } #[embassy_executor::task] @@ -279,6 +270,7 @@ fn prefetch_handler(_faulting_addr: usize) -> ! { /// Panic handler #[panic_handler] fn panic(info: &PanicInfo) -> ! { - error!("Panic: {info:?}"); + let mut uart = unsafe { uart::Uart::steal(uart::UartId::Uart1) }; + writeln!(uart, "panic: {}\r", info).ok(); loop {} } diff --git a/firmware/examples/zedboard/src/bin/oled.rs b/firmware/examples/zedboard/src/bin/oled.rs index a1b2f71..899ecc8 100644 --- a/firmware/examples/zedboard/src/bin/oled.rs +++ b/firmware/examples/zedboard/src/bin/oled.rs @@ -5,18 +5,19 @@ 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 log::info; use ssd1306::{Ssd1306, prelude::*}; use zedboard::PS_CLOCK_FREQUENCY; use zynq7000_hal::{ - BootMode, clocks, generic_interrupt_handler, gpio, gtc, spi, + BootMode, clocks, generic_interrupt_handler, gpio, gtc, + log::asynch::UartLoggerRunner, + spi, time::Hertz, uart::{self, TxAsync}, }; @@ -76,9 +77,12 @@ async fn main(spawner: Spawner) -> ! { 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 (tx, _) = uart.split(); + let tx_async = TxAsync::new(tx, true); + let log_runner = + zynq7000_hal::log::asynch::init_with_uart_tx(log::LevelFilter::Trace, tx_async) + .expect("Failed to initialize async logger"); + spawner.spawn(logger_task(log_runner).unwrap()); let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); @@ -87,8 +91,6 @@ async fn main(spawner: Spawner) -> ! { 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), @@ -211,19 +213,8 @@ impl FerrisMovement { } #[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; - } - } +pub async fn logger_task(mut log_runner: UartLoggerRunner) -> ! { + log_runner.run().await } #[embassy_executor::task] @@ -274,6 +265,7 @@ fn prefetch_handler(_faulting_addr: usize) -> ! { /// Panic handler #[panic_handler] fn panic(info: &PanicInfo) -> ! { - error!("Panic: {info:?}"); + let mut uart = unsafe { uart::Uart::steal(uart::UartId::Uart1) }; + writeln!(uart, "panic: {}\r", info).ok(); loop {} } diff --git a/firmware/examples/zedboard/src/bin/qspi.rs b/firmware/examples/zedboard/src/bin/qspi.rs index e3ec846..8ad182f 100644 --- a/firmware/examples/zedboard/src/bin/qspi.rs +++ b/firmware/examples/zedboard/src/bin/qspi.rs @@ -62,14 +62,7 @@ async fn main(_spawner: Spawner) -> ! { ) .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, - ) - }; + zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false); let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); diff --git a/firmware/examples/zedboard/src/bin/sdio.rs b/firmware/examples/zedboard/src/bin/sdio.rs index d764740..dd02970 100644 --- a/firmware/examples/zedboard/src/bin/sdio.rs +++ b/firmware/examples/zedboard/src/bin/sdio.rs @@ -67,13 +67,7 @@ async fn main(_spawner: Spawner) -> ! { .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, - ) - }; + zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false); let sdio_clock_config = SdClockConfig::calculate_for_io_clock(clocks.io_clocks(), 100.MHz(), 10.MHz()).unwrap(); diff --git a/firmware/examples/zedboard/src/bin/spi-slave.rs b/firmware/examples/zedboard/src/bin/spi-slave.rs new file mode 100644 index 0000000..b1ed4ed --- /dev/null +++ b/firmware/examples/zedboard/src/bin/spi-slave.rs @@ -0,0 +1,587 @@ +#![no_std] +#![no_main] + +use aarch32_cpu::asm::nop; +use core::{cell::RefCell, panic::PanicInfo, sync::atomic::AtomicU8}; +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 as _; +use embedded_io::Write; +use embedded_io_async::Read as _; +use log::info; +use zedboard::PS_CLOCK_FREQUENCY; +use zynq7000::spi::FifoWrite; +use zynq7000_hal::{ + BootMode, clocks, generic_interrupt_handler, gpio, gtc, + log::asynch::UartLoggerRunner, + spi::{self, SpiAsync}, + uart::{self, TxAsync}, +}; + +use zynq7000_rt as _; + +#[derive(Debug, PartialEq, Eq)] +pub enum TestType { + Blocking, + Async, +} + +const TEST_TYPE: TestType = TestType::Async; + +const INIT_STRING: &str = "-- Zynq 7000 SPI slave 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); + + 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(); + Delay.delay_ms(1).await; + + let (tx, _rx) = uart.split(); + let tx_asynch = TxAsync::new(tx, true); + let logger_runner = + zynq7000_hal::log::asynch::init_with_uart_tx(log::LevelFilter::Trace, tx_asynch) + .expect("Failed to initialize async logger"); + spawner.spawn(logger_task(logger_runner).unwrap()); + + // This pin is used to select between a routing from SPI0 to OLED, or SPI0 to SPI1. Low selects + // the SPI0 to OLED interface. + let _spi_mux_pin = + gpio::Output::new_for_emio(gpio_pins.emio.take(15).unwrap(), gpio::PinState::High); + + let boot_mode = BootMode::new_from_regs(); + info!("Boot mode: {:?}", boot_mode); + + 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()); + + // Enable loopback. + spi::enable_spi0_to_spi1_loopback(); + + let mut spi_slave = spi::SpiSlave::new( + spi::SpiId::Spi1, + periphs.spi_1, + spi::SlaveConfig { + mode: spi::MODE_0, + enable_modefail: true, + }, + ); + zynq7000_hal::register_interrupt(spi_slave.interrupt_id(), spi_interrupt_handler); + + let spi_master = spi::Spi::new_for_emio( + periphs.spi_0, + spi::Config::new( + // 10 MHz maximum rating of the sensor. + zynq7000::spi::BaudDivSel::By64, + // l3gd20::MODE, + embedded_hal::spi::MODE_0, + spi::SlaveSelectConfig::AutoCsManualStart, + ), + ) + .unwrap(); + let cs_pin = spi::ChipSelectPin::new(spi_master.id(), spi::ChipSelect::Slave0); + + // We pre-load the FIFO with a fixed, known sequence. + for i in 0..spi::FIFO_DEPTH / 2 { + spi_slave.write_tx_data(i as u8); + } + log::info!("SPI slave initialized, enabling interrupts and peripheral"); + spi_slave.enable_interrupts(true); + spi_slave.enable(); + + static RX_DATA_PIPE: static_cell::ConstStaticCell< + embassy_sync::pipe::Pipe, + > = static_cell::ConstStaticCell::new(embassy_sync::pipe::Pipe::new()); + + let pipe = RX_DATA_PIPE.take(); + let (mut slave_rx_reader, writer) = pipe.split(); + critical_section::with(|cs| { + RX_DATA_WRITER.borrow(cs).replace(Some(writer)); + }); + + if TEST_TYPE == TestType::Blocking { + let mut spi_master = + embedded_hal_bus::spi::ExclusiveDevice::new(spi_master, cs_pin, embassy_time::Delay) + .expect("creating exlusive SPI master failed"); + + let current_rx_val = blocking_tests_small(&mut spi_master, &mut slave_rx_reader).await; + log::info!("blocking tests with small data blocks done"); + Delay.delay_ms(10).await; + blocking_tests_large(&mut spi_master, current_rx_val, &mut slave_rx_reader).await; + log::info!("blocking tests with large data blocks done"); + } else { + let spi_master = SpiAsync::new(spi_master); + let mut spi_master = + embedded_hal_bus::spi::ExclusiveDevice::new(spi_master, cs_pin, embassy_time::Delay) + .expect("creating exlusive SPI master failed"); + let current_rx_val = + blocking_tests_small_async(&mut spi_master, &mut slave_rx_reader).await; + log::info!("async tests with small data blocks done"); + Delay.delay_ms(10).await; + blocking_tests_large_async(&mut spi_master, current_rx_val, &mut slave_rx_reader).await; + log::info!("blocking tests with large data blocks done"); + } + + log::info!("SPI slave tests done"); + + let mut ticker = Ticker::every(Duration::from_millis(1000)); + loop { + ticker.next().await; // Wait for the next cycle of the ticker + } +} + +pub async fn blocking_tests_small( + spi_master: &mut impl embedded_hal::spi::SpiDevice, + slave_rx_reader: &mut embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 256>, +) -> u8 { + let mut tx_buf = [0; 64]; + let mut buf = [0; 64]; + + // Write test. + // We should read back 0,1,2,3 here but the sent values are ignored. + spi_master.write(&[1, 2, 3, 4]).unwrap(); + slave_rx_reader.read_exact(&mut buf[0..4]).await.unwrap(); + assert_eq!( + &buf[0..4], + &[1, 2, 3, 4], + "slave did not receive the expected data" + ); + + // Read test. + spi_master.read(&mut buf[0..4]).unwrap(); + // Now we expect 4,5,6,7 sent by the SPI slave + assert_eq!( + &buf[0..4], + &[4, 5, 6, 7], + "slave did not send the expected data" + ); + // Slave should receive dummy data + slave_rx_reader.read_exact(&mut buf[0..4]).await.unwrap(); + assert_eq!( + &buf[0..4], + &[0, 0, 0, 0], + "slave did not receive the expected data" + ); + + // Transfer test. + for (i, item) in tx_buf.iter_mut().enumerate().take(16) { + *item = (i * 2) as u8; + } + spi_master + .transfer(&mut buf[0..16], &tx_buf[0..16]) + .unwrap(); + for i in 8..24 { + assert_eq!( + buf[i - 8], + i as u8, + "slave did not receive the expected data" + ); + } + slave_rx_reader.read_exact(&mut buf[0..16]).await.unwrap(); + for (i, item) in buf.iter().enumerate().take(16) { + assert_eq!( + *item, + (i * 2) as u8, + "slave did not send the expected data" + ); + } + + // Transfer in place test. + for (i, item) in buf.iter_mut().enumerate().take(16) { + *item = (i * 2) as u8; + } + spi_master.transfer_in_place(&mut buf[0..16]).unwrap(); + for i in 24..40 { + assert_eq!( + buf[i - 24], + i as u8, + "slave did not receive the expected data" + ); + } + slave_rx_reader.read_exact(&mut buf[0..16]).await.unwrap(); + for (i, item) in buf.iter().enumerate().take(16) { + assert_eq!( + *item, + (i * 2) as u8, + "slave did not send the expected data" + ); + } + 40 +} + +pub async fn blocking_tests_large( + spi_master: &mut impl embedded_hal::spi::SpiDevice, + mut current_rx_val: u8, + slave_rx_reader: &mut embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 256>, +) -> u8 { + const BUF_LEN: usize = 164; + let mut buf = [0; BUF_LEN]; + + // Write test. + for (i, item) in buf.iter_mut().enumerate() { + *item = i as u8; + } + spi_master.write(&buf).unwrap(); + slave_rx_reader + .read_exact(&mut buf[0..BUF_LEN]) + .await + .unwrap(); + for (i, item) in buf.iter().enumerate() { + assert_eq!(*item, i as u8, "slave did not receive the expected data"); + } + current_rx_val = current_rx_val.wrapping_add(buf.len() as u8); + + // Read test. + spi_master.read(&mut buf).unwrap(); + for item in &buf { + assert_eq!( + *item, current_rx_val, + "slave did not send the expected data" + ); + current_rx_val = current_rx_val.wrapping_add(1); + } + slave_rx_reader + .read_exact(&mut buf[0..BUF_LEN]) + .await + .unwrap(); + for item in &buf { + assert_eq!(*item, 0, "slave did not receive the expected data"); + } + + // Transfer test. + let mut tx_buf = [0; BUF_LEN]; + for (i, item) in tx_buf.iter_mut().enumerate() { + *item = i as u8; + } + spi_master.transfer(&mut buf, &tx_buf).unwrap(); + for item in &buf { + assert_eq!( + *item, current_rx_val, + "slave did not send the expected data" + ); + current_rx_val = current_rx_val.wrapping_add(1); + } + slave_rx_reader + .read_exact(&mut buf[0..BUF_LEN]) + .await + .unwrap(); + for (i, item) in buf.iter().enumerate() { + assert_eq!(*item, i as u8, "slave did not receive the expected data"); + } + + // Transfer in place test. + for (i, item) in buf.iter_mut().enumerate() { + *item = i as u8; + } + spi_master.transfer_in_place(&mut buf).unwrap(); + for item in &buf { + assert_eq!( + *item, current_rx_val, + "slave did not send the expected data" + ); + current_rx_val = current_rx_val.wrapping_add(1); + } + slave_rx_reader + .read_exact(&mut buf[0..BUF_LEN]) + .await + .unwrap(); + for (i, item) in buf.iter().enumerate() { + assert_eq!(*item, i as u8, "slave did not receive the expected data"); + } + current_rx_val +} + +pub async fn blocking_tests_small_async( + spi_master: &mut impl embedded_hal_async::spi::SpiDevice, + slave_rx_reader: &mut embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 256>, +) -> u8 { + let mut tx_buf = [0; 64]; + let mut buf = [0; 64]; + + // Write test. + // We should read back 0,1,2,3 here but the sent values are ignored. + spi_master.write(&[1, 2, 3, 4]).await.unwrap(); + slave_rx_reader.read_exact(&mut buf[0..4]).await.unwrap(); + assert_eq!( + &buf[0..4], + &[1, 2, 3, 4], + "slave did not receive the expected data" + ); + + // Read test. + spi_master.read(&mut buf[0..4]).await.unwrap(); + // Now we expect 4,5,6,7 sent by the SPI slave + assert_eq!( + &buf[0..4], + &[4, 5, 6, 7], + "slave did not send the expected data" + ); + // Slave should receive dummy data + slave_rx_reader.read_exact(&mut buf[0..4]).await.unwrap(); + assert_eq!( + &buf[0..4], + &[0, 0, 0, 0], + "slave did not receive the expected data" + ); + + // Transfer test. + for (i, item) in tx_buf.iter_mut().enumerate().take(16) { + *item = (i * 2) as u8; + } + spi_master + .transfer(&mut buf[0..16], &tx_buf[0..16]) + .await + .unwrap(); + for i in 8..24 { + assert_eq!( + buf[i - 8], + i as u8, + "slave did not receive the expected data" + ); + } + slave_rx_reader.read_exact(&mut buf[0..16]).await.unwrap(); + for (i, item) in buf.iter().enumerate().take(16) { + assert_eq!( + *item, + (i * 2) as u8, + "slave did not send the expected data" + ); + } + + // Transfer in place test. + for (i, item) in buf.iter_mut().enumerate().take(16) { + *item = (i * 2) as u8; + } + spi_master.transfer_in_place(&mut buf[0..16]).await.unwrap(); + for i in 24..40 { + assert_eq!( + buf[i - 24], + i as u8, + "slave did not receive the expected data" + ); + } + slave_rx_reader.read_exact(&mut buf[0..16]).await.unwrap(); + for (i, item) in buf.iter().enumerate().take(16) { + assert_eq!( + *item, + (i * 2) as u8, + "slave did not send the expected data" + ); + } + 40 +} + +pub async fn blocking_tests_large_async( + spi_master: &mut impl embedded_hal_async::spi::SpiDevice, + mut current_rx_val: u8, + slave_rx_reader: &mut embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 256>, +) -> u8 { + const BUF_LEN: usize = 164; + let mut buf = [0; BUF_LEN]; + + // Write test. + for (i, item) in buf.iter_mut().enumerate() { + *item = i as u8; + } + spi_master.write(&buf).await.unwrap(); + slave_rx_reader + .read_exact(&mut buf[0..BUF_LEN]) + .await + .unwrap(); + for (i, &item) in buf.iter().enumerate() { + assert_eq!(item, i as u8, "slave did not receive the expected data"); + } + current_rx_val = current_rx_val.wrapping_add(buf.len() as u8); + + // Read test. + spi_master.read(&mut buf).await.unwrap(); + for &item in &buf { + assert_eq!(item, current_rx_val, "slave did not send the expected data"); + current_rx_val = current_rx_val.wrapping_add(1); + } + slave_rx_reader + .read_exact(&mut buf[0..BUF_LEN]) + .await + .unwrap(); + for &item in &buf { + assert_eq!(item, 0, "slave did not receive the expected data"); + } + + // Transfer test. + let mut tx_buf = [0; BUF_LEN]; + for (i, item) in tx_buf.iter_mut().enumerate() { + *item = i as u8; + } + spi_master.transfer(&mut buf, &tx_buf).await.unwrap(); + for &item in &buf { + assert_eq!(item, current_rx_val, "slave did not send the expected data"); + current_rx_val = current_rx_val.wrapping_add(1); + } + slave_rx_reader + .read_exact(&mut buf[0..BUF_LEN]) + .await + .unwrap(); + for (i, &item) in buf.iter().enumerate() { + assert_eq!(item, i as u8, "slave did not receive the expected data"); + } + + // Transfer in place test. + for (i, item) in buf.iter_mut().enumerate() { + *item = i as u8; + } + spi_master.transfer_in_place(&mut buf).await.unwrap(); + for &item in &buf { + assert_eq!(item, current_rx_val, "slave did not send the expected data"); + current_rx_val = current_rx_val.wrapping_add(1); + } + slave_rx_reader + .read_exact(&mut buf[0..BUF_LEN]) + .await + .unwrap(); + for (i, &item) in buf.iter().enumerate() { + assert_eq!(item, i as u8, "slave did not receive the expected data"); + } + current_rx_val +} + +#[embassy_executor::task] +pub async fn logger_task(mut logger_task: UartLoggerRunner) { + logger_task.run().await; +} + +static RX_DATA_WRITER: critical_section::Mutex< + RefCell>>, +> = critical_section::Mutex::new(RefCell::new(None)); + +unsafe fn spi_interrupt_handler() { + static CURRENT_TX_VAL: AtomicU8 = AtomicU8::new((spi::FIFO_DEPTH / 2) as u8); + + let mut buf = [0; 64]; + let mut current_val = CURRENT_TX_VAL.load(core::sync::atomic::Ordering::Relaxed); + let mut spi = unsafe { spi::SpiLowLevel::steal(spi::SpiId::Spi1) }; + + let status = spi.read_interrupt_status(); + spi.write_interrupt_status(status); + + let mut index = 0; + while spi.read_interrupt_status().rx_not_empty() && index < buf.len() { + buf[index] = spi.read_rx_data().value(); + index += 1; + } + while !spi.read_interrupt_status().tx_full() { + spi.write_tx_data(FifoWrite::new(current_val)); + current_val = current_val.wrapping_add(1); + } + CURRENT_TX_VAL.store(current_val, core::sync::atomic::Ordering::Relaxed); + + critical_section::with(|cs| { + let opt_writer = RX_DATA_WRITER.borrow(cs).borrow(); + if let Some(writer) = opt_writer.as_ref() { + // In a real application, you would read the received data from the SPI peripheral here. + // For this example, we just write a dummy value to the pipe to demonstrate the concept. + let _ = writer.try_write(&buf[0..index]); + } + }); +} + +#[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) -> ! { + let mut uart = unsafe { uart::Uart::steal(uart::UartId::Uart1) }; + writeln!(uart, "panic: {}\r", info).ok(); + loop {} +} diff --git a/firmware/examples/zedboard/src/bin/uart-blocking.rs b/firmware/examples/zedboard/src/bin/uart-blocking.rs index 11842e7..e48cc74 100644 --- a/firmware/examples/zedboard/src/bin/uart-blocking.rs +++ b/firmware/examples/zedboard/src/bin/uart-blocking.rs @@ -129,13 +129,7 @@ async fn main(_spawner: Spawner) -> ! { log_uart.write_all(INIT_STRING.as_bytes()).unwrap(); // Safety: Co-operative multi-tasking is used. - unsafe { - zynq7000_hal::log::uart_blocking::init_unsafe_single_core( - log_uart, - log::LevelFilter::Trace, - false, - ) - }; + zynq7000_hal::log::uart_blocking::init_with_busy_flag(log_uart, log::LevelFilter::Trace, false); // UART0 routed through EMIO to PL pins. let mut uart_0 = diff --git a/firmware/examples/zedboard/src/bin/uart-non-blocking.rs b/firmware/examples/zedboard/src/bin/uart-non-blocking.rs index 6a0115b..47180f8 100644 --- a/firmware/examples/zedboard/src/bin/uart-non-blocking.rs +++ b/firmware/examples/zedboard/src/bin/uart-non-blocking.rs @@ -37,7 +37,7 @@ use embedded_alloc::LlffHeap as Heap; use embedded_hal::digital::StatefulOutputPin; use embedded_io::Write as _; use heapless::spsc::Queue; -use log::{error, info, warn}; +use log::{info, warn}; use zynq7000_hal::{ BootMode, clocks::Clocks, @@ -47,7 +47,7 @@ use zynq7000_hal::{ gtc::GlobalTimerCounter, l2_cache, time::Hertz, - uart::{ClockConfig, Config, Uart}, + uart::{self, ClockConfig, Config, Uart}, }; #[derive(Debug, Copy, Clone, PartialEq)] @@ -233,14 +233,7 @@ async fn main(spawner: Spawner) -> ! { on_interrupt_uart_0, ); - // Safety: We are not multi-threaded yet. - unsafe { - zynq7000_hal::log::uart_blocking::init_unsafe_single_core( - log_uart, - log::LevelFilter::Trace, - false, - ) - }; + zynq7000_hal::log::uart_blocking::init_with_busy_flag(log_uart, log::LevelFilter::Trace, false); // Set up UART multiplexing before creating and configuring the UARTs. let mut uart_mux = UartMultiplexer::new([ @@ -584,6 +577,7 @@ fn prefetch_handler(_faulting_addr: usize) -> ! { /// Panic handler #[panic_handler] fn panic(info: &PanicInfo) -> ! { - error!("Panic: {info:?}"); + let mut uart = unsafe { uart::Uart::steal(uart::UartId::Uart1) }; + writeln!(uart, "panic: {}\r", info).ok(); loop {} } diff --git a/firmware/examples/zedboard/src/main.rs b/firmware/examples/zedboard/src/main.rs index e531ba2..6879346 100644 --- a/firmware/examples/zedboard/src/main.rs +++ b/firmware/examples/zedboard/src/main.rs @@ -49,14 +49,8 @@ async fn main(_spawner: Spawner) -> ! { ) .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, - ) - }; + + zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false); let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); diff --git a/firmware/zedboard-fsbl/src/main.rs b/firmware/zedboard-fsbl/src/main.rs index 278b083..7274f6f 100644 --- a/firmware/zedboard-fsbl/src/main.rs +++ b/firmware/zedboard-fsbl/src/main.rs @@ -17,7 +17,6 @@ use log::{error, info}; use zedboard_bsp::qspi_spansion::{self, QspiSpansionS25Fl256SLinearMode}; use zynq7000_boot_image::DestinationDevice; use zynq7000_hal::clocks::ArmClocks; -use zynq7000_hal::{generic_interrupt_handler, priv_tim}; use zynq7000_hal::{ BootMode, clocks::{ @@ -31,6 +30,7 @@ use zynq7000_hal::{ time::Hertz, uart::{ClockConfig, Config, Uart}, }; +use zynq7000_hal::{generic_interrupt_handler, priv_tim}; // PS clock input frequency. const PS_CLK: Hertz = Hertz::from_raw(33_333_333); @@ -109,14 +109,7 @@ fn main() -> ! { logger_uart .write_all(b"-- Zedboard Rust FSBL --\n\r") .unwrap(); - // Safety: We are not multi-threaded yet. - unsafe { - zynq7000_hal::log::uart_blocking::init_unsafe_single_core( - logger_uart, - log::LevelFilter::Trace, - false, - ) - }; + zynq7000_hal::log::uart_blocking::init_with_busy_flag(logger_uart, log::LevelFilter::Trace, true); // Set up the global interrupt controller. let mut gic = gic::Configurator::new_with_init(periphs.gicc, periphs.gicd); diff --git a/firmware/zedboard-qspi-flasher/src/main.rs b/firmware/zedboard-qspi-flasher/src/main.rs index 37393f3..3d812a6 100644 --- a/firmware/zedboard-qspi-flasher/src/main.rs +++ b/firmware/zedboard-qspi-flasher/src/main.rs @@ -65,14 +65,7 @@ fn main() -> ! { ) .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::Info, - false, - ) - }; + zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Info, true); let boot_mode = BootMode::new_from_regs(); info!("Boot mode: {:?}", boot_mode); diff --git a/firmware/zynq7000-hal/src/log.rs b/firmware/zynq7000-hal/src/log.rs index 27f084b..6c3fcdd 100644 --- a/firmware/zynq7000-hal/src/log.rs +++ b/firmware/zynq7000-hal/src/log.rs @@ -12,10 +12,9 @@ static LOG_SEL: AtomicU8 = AtomicU8::new(0); /// Blocking UART loggers. pub mod uart_blocking { use super::*; - use core::cell::{Cell, RefCell, UnsafeCell}; + use core::cell::{RefCell, UnsafeCell}; use embedded_io::Write as _; - use aarch32_cpu::register::Cpsr; use critical_section::Mutex; use log::{LevelFilter, Log, set_logger, set_max_level}; @@ -78,28 +77,64 @@ pub mod uart_blocking { } } - pub struct UartLoggerUnsafeSingleThread { - skip_in_isr: Cell, + pub struct UartLoggerWithBusyFlag { + busy: AtomicBool, + skip_in_isr: AtomicBool, uart: UnsafeCell>, } - unsafe impl Send for UartLoggerUnsafeSingleThread {} - unsafe impl Sync for UartLoggerUnsafeSingleThread {} + unsafe impl Send for UartLoggerWithBusyFlag {} + unsafe impl Sync for UartLoggerWithBusyFlag {} - static UART_LOGGER_UNSAFE_SINGLE_THREAD: UartLoggerUnsafeSingleThread = - UartLoggerUnsafeSingleThread { - skip_in_isr: Cell::new(false), - uart: UnsafeCell::new(None), - }; + static UART_LOGGER_UNSAFE_SINGLE_THREAD: UartLoggerWithBusyFlag = UartLoggerWithBusyFlag { + busy: AtomicBool::new(false), + skip_in_isr: AtomicBool::new(false), + uart: UnsafeCell::new(None), + }; - /// Initialize the logger with a blocking UART instance which does not use locks. + struct UartGuard<'lock>(&'lock AtomicBool); + + impl<'lock> UartGuard<'lock> { + pub fn new(flag: &'lock AtomicBool) -> Option { + let proc_mode = aarch32_cpu::register::Cpsr::read().mode().ok()?; + + let is_irq = proc_mode == aarch32_cpu::register::cpsr::ProcessorMode::Fiq + || proc_mode == aarch32_cpu::register::cpsr::ProcessorMode::Irq; + + // For IRQs, only try once. + if is_irq { + if UART_LOGGER_UNSAFE_SINGLE_THREAD + .skip_in_isr + .load(core::sync::atomic::Ordering::Relaxed) + { + return None; + } + if flag.swap(true, core::sync::atomic::Ordering::AcqRel) { + return None; + } + return Some(Self(flag)); + } + + // For threaded code, spinning is allowed. + while flag.swap(true, core::sync::atomic::Ordering::AcqRel) {} + Some(Self(flag)) + } + } + + impl Drop for UartGuard<'_> { + fn drop(&mut self) { + self.0.store(false, core::sync::atomic::Ordering::Release); + } + } + + /// Initialize the logger with a blocking UART instance which spins on a busy flag in threaded + /// mode, and does not log in interrupt contexts if the main task was busy with logging. /// - /// # Safety + /// It should be noted that this is still a blocking logger, and using it in an ISR might + /// invalidate application logic and introduce problematic delays in the system. /// - /// This is a blocking logger which performs a write WITHOUT a critical section. This logger is - /// NOT thread-safe, which might lead to garbled output. Log output in ISRs can optionally be - /// surpressed. - pub unsafe fn init_unsafe_single_core(uart: Uart, level: LevelFilter, skip_in_isr: bool) { + /// Therefore, the initialization also allows skipping logging in ISRs completely. + pub fn init_with_busy_flag(uart: Uart, level: LevelFilter, skip_in_isr: bool) { if LOGGER_INIT_DONE.swap(true, core::sync::atomic::Ordering::Relaxed) { return; } @@ -109,34 +144,31 @@ pub mod uart_blocking { ); let opt_uart = unsafe { &mut *UART_LOGGER_UNSAFE_SINGLE_THREAD.uart.get() }; opt_uart.replace(uart); + UART_LOGGER_UNSAFE_SINGLE_THREAD .skip_in_isr - .set(skip_in_isr); + .store(skip_in_isr, core::sync::atomic::Ordering::Relaxed); set_logger(&UART_LOGGER_UNSAFE_SINGLE_THREAD).unwrap(); set_max_level(level); // Adjust as needed } - impl log::Log for UartLoggerUnsafeSingleThread { + impl log::Log for UartLoggerWithBusyFlag { fn enabled(&self, _metadata: &log::Metadata) -> bool { true } fn log(&self, record: &log::Record) { - if self.skip_in_isr.get() { - match Cpsr::read().mode().unwrap() { - aarch32_cpu::register::cpsr::ProcessorMode::Fiq - | aarch32_cpu::register::cpsr::ProcessorMode::Irq => { - return; - } - _ => {} - } + let guard = UartGuard::new(&self.busy); + if guard.is_none() { + return; } let uart_mut = unsafe { &mut *self.uart.get() }.as_mut(); if uart_mut.is_none() { return; } + writeln!( uart_mut.unwrap(), "{} - {}\r", @@ -147,6 +179,11 @@ pub mod uart_blocking { } fn flush(&self) { + let guard = UartGuard::new(&self.busy); + if guard.is_none() { + return; + } + let uart_mut = unsafe { &mut *self.uart.get() }.as_mut(); if uart_mut.is_none() { return; @@ -172,6 +209,8 @@ pub mod asynch { use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use log::{LevelFilter, set_logger, set_max_level}; + use crate::uart::TxAsync; + /// Logger implementation which logs frames via a ring buffer and sends the frame sizes /// as messages. /// @@ -228,9 +267,8 @@ pub mod asynch { fn flush(&self) {} } - pub fn init( + pub fn init_generic( level: LevelFilter, - //writer: embassy_sync::pipe::Writer<'static, CriticalSectionRawMutex, 4096>, ) -> Option> { if super::LOGGER_INIT_DONE.swap(true, core::sync::atomic::Ordering::Relaxed) { return None; @@ -241,4 +279,26 @@ pub mod asynch { set_max_level(level); // Adjust as needed Some(reader) } + + pub fn init_with_uart_tx(level: LevelFilter, tx: TxAsync) -> Option { + init_generic(level).map(|reader| UartLoggerRunner { reader, tx }) + } + + pub struct UartLoggerRunner { + reader: embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 4096>, + tx: TxAsync, + } + + impl UartLoggerRunner { + pub async fn run(&mut self) -> ! { + let mut log_buf = [0u8; 1024]; + + loop { + let read_bytes = self.reader.read(&mut log_buf).await; + if read_bytes > 0 { + self.tx.write(&log_buf[..read_bytes]).unwrap().await; + } + } + } + } } diff --git a/firmware/zynq7000-hal/src/sd/mod.rs b/firmware/zynq7000-hal/src/sd/mod.rs index 9e8d9fd..46a779f 100644 --- a/firmware/zynq7000-hal/src/sd/mod.rs +++ b/firmware/zynq7000-hal/src/sd/mod.rs @@ -1212,7 +1212,7 @@ pub fn initialize_card( } let responded_to_cmd8 = !status_cmd8.0.command_timeout_error(); - let acmd41_arg = if responded_to_cmd8 { + let hsc = if responded_to_cmd8 { let r7 = response::R7::new_with_raw_value(ll.read_u32_response()); if !r7.voltage_accepted().is_ok_and(|val| { val == embedded_sdmmc::sdcard::argument::VoltageSuppliedSelect::_2_7To3_6V @@ -1223,31 +1223,26 @@ pub fn initialize_card( error: InitializationError::ResponseError(status.response_errors()), }); } - argument::Acmd41::builder() - .with_host_capacity_support( - embedded_sdmmc::sdcard::argument::HostCapacitySupport::SdhcOrSdxc, - ) - .with_fast_boot(false) - .with_xpc(embedded_sdmmc::sdcard::argument::PowerControl::MaximumPerformance) - .with_s18r(false) - .with_ocr(VOLTAGE_LEVEL_CAPABILITIES) - .build() + embedded_sdmmc::sdcard::argument::HostCapacitySupport::SdhcOrSdxc } else { + embedded_sdmmc::sdcard::argument::HostCapacitySupport::SdscOnly + }; + send_acmd( + ll, + commands::ACMD41_SEND_IF_COND, argument::Acmd41::builder() - .with_host_capacity_support( - embedded_sdmmc::sdcard::argument::HostCapacitySupport::SdscOnly, - ) + .with_host_capacity_support(hsc) .with_fast_boot(false) .with_xpc(embedded_sdmmc::sdcard::argument::PowerControl::MaximumPerformance) .with_s18r(false) .with_ocr(VOLTAGE_LEVEL_CAPABILITIES) .build() - }; - send_acmd(ll, commands::ACMD41_SEND_IF_COND, acmd41_arg.raw_value(), 0).map_err(|e| { - InitializationErrorWithStep { - step: InitStep::SendingIfCondAcmd41, - error: e, - } + .raw_value(), + 0, + ) + .map_err(|e| InitializationErrorWithStep { + step: InitStep::SendingIfCondAcmd41, + error: e, })?; let mut r3 = embedded_sdmmc::sdcard::response::R3::new_with_raw_value(ll.read_u32_response()); diff --git a/firmware/zynq7000-hal/src/spi/asynch.rs b/firmware/zynq7000-hal/src/spi/asynch.rs index 750782f..9984a94 100644 --- a/firmware/zynq7000-hal/src/spi/asynch.rs +++ b/firmware/zynq7000-hal/src/spi/asynch.rs @@ -38,9 +38,9 @@ pub unsafe fn on_interrupt(peripheral: SpiId) { let index = peripheral as usize; let enabled_irqs = spi.read_enabled_interrupts(); // Prevent spurious interrupts from messing with out logic here. - spi.disable_interrupts(); + spi.disable_interrupts_master_mode(); let interrupt_status = spi.read_interrupt_status(); - spi.clear_interrupts(); + spi.clear_interrupts_master_mode(); // IRQ is not related. if !enabled_irqs.tx_below_threshold() && !enabled_irqs.tx_full() @@ -155,8 +155,7 @@ fn on_interrupt_transfer(idx: usize, context: &mut TransferContext, spi: &mut Sp context.tx_progress += 1; } - // Read data from RX FIFO first. - while spi.read_interrupt_status().rx_not_empty() { + while context.rx_progress < transfer_len && spi.read_interrupt_status().rx_not_empty() { if context.rx_progress < read_len { read_slice[context.rx_progress] = spi.read_fifo_unchecked(); } else { @@ -252,7 +251,7 @@ fn unfinished_transfer(spi: &mut SpiLowLevel, transfer_len: usize, context: &Tra } // 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); + spi.enable_interrupts_master_mode(tx_pending); } #[derive(Debug, Clone, Copy)] @@ -308,6 +307,7 @@ impl<'spi> SpiFuture<'spi> { }); 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(); @@ -322,8 +322,9 @@ impl<'spi> SpiFuture<'spi> { context.tx_slice.set_null(); context.tx_progress = write_index; context.rx_progress = 0; - spi.inner.clear_interrupts(); - spi.inner.enable_interrupts(write_index < words.len()); + spi.inner.clear_interrupts_master_mode(); + spi.inner + .enable_interrupts_master_mode(write_index < words.len()); spi.inner.enable(); }); Self { @@ -349,8 +350,9 @@ impl<'spi> SpiFuture<'spi> { context.rx_slice.set_null(); context.tx_progress = write_index; context.rx_progress = 0; - spi.inner.clear_interrupts(); - spi.inner.enable_interrupts(write_index < words.len()); + spi.inner.clear_interrupts_master_mode(); + spi.inner + .enable_interrupts_master_mode(write_index < words.len()); spi.inner.enable(); }); Self { @@ -376,6 +378,11 @@ impl<'spi> SpiFuture<'spi> { } Self::set_triggers(spi, fifo_prefill, full_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(); + critical_section::with(|cs| { let context_ref = TRANSFER_CONTEXTS[spi_id as usize].borrow(cs); let mut context = context_ref.borrow_mut(); @@ -386,8 +393,9 @@ impl<'spi> SpiFuture<'spi> { } context.tx_progress = fifo_prefill; context.rx_progress = 0; - spi.inner.clear_interrupts(); - spi.inner.enable_interrupts(fifo_prefill < write.len()); + spi.inner.clear_interrupts_master_mode(); + spi.inner + .enable_interrupts_master_mode(full_write_len > FIFO_DEPTH); spi.inner.enable(); }); Self { @@ -413,8 +421,9 @@ impl<'spi> SpiFuture<'spi> { context.tx_slice.set_null(); context.tx_progress = write_index; context.rx_progress = 0; - spi.inner.clear_interrupts(); - spi.inner.enable_interrupts(write_index < words.len()); + spi.inner.clear_interrupts_master_mode(); + spi.inner + .enable_interrupts_master_mode(write_index < words.len()); spi.inner.enable(); }); Self { @@ -429,7 +438,7 @@ impl<'spi> SpiFuture<'spi> { let idx = id as usize; DONE[idx].store(false, core::sync::atomic::Ordering::Relaxed); spi.inner.disable(); - spi.inner.disable_interrupts(); + spi.inner.disable_interrupts_master_mode(); } // Returns amount of bytes written to FIFO. diff --git a/firmware/zynq7000-hal/src/spi/mod.rs b/firmware/zynq7000-hal/src/spi/mod.rs index a06f022..7c6771b 100644 --- a/firmware/zynq7000-hal/src/spi/mod.rs +++ b/firmware/zynq7000-hal/src/spi/mod.rs @@ -17,7 +17,7 @@ use crate::{enable_amba_peripheral_clock, spi_mode_const_to_cpol_cpha}; 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; +pub use embedded_hal::spi::{MODE_0, MODE_1, MODE_2, MODE_3, Mode}; use zynq7000::slcr::reset::DualRefAndClockResetSpiUart; pub use zynq7000::spi::DelayControl; use zynq7000::spi::{ @@ -31,12 +31,40 @@ pub const MODULE_ID: u32 = 0x90106; pub mod asynch; pub use asynch::*; +pub mod slave; +pub use slave::*; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SpiId { Spi0 = 0, Spi1 = 1, } +impl SpiId { + #[inline] + pub const fn interrupt_id(&self) -> crate::Interrupt { + match self { + SpiId::Spi0 => crate::Interrupt::Spi(crate::SpiInterrupt::Spi0), + SpiId::Spi1 => crate::Interrupt::Spi(crate::SpiInterrupt::Spi1), + } + } + + /// Unsafely steal the register block. + /// + /// # Safety + /// + /// This API can be used to potentially create a driver to the same peripheral structure + /// from multiple threads. The user must ensure that concurrent accesses are safe and do not + /// interfere with each other. + #[inline] + pub unsafe fn steal_regs(&self) -> MmioRegisters<'static> { + match self { + SpiId::Spi0 => unsafe { zynq7000::spi::Registers::new_mmio_fixed_0() }, + SpiId::Spi1 => unsafe { zynq7000::spi::Registers::new_mmio_fixed_1() }, + } + } +} + pub trait PsSpi { fn reg_block(&self) -> MmioRegisters<'static>; fn id(&self) -> Option; @@ -349,6 +377,42 @@ impl ChipSelect { } } +/// This abstraction which can be used to map a hardware chip select pin +/// to [embedded_hal::digital::OutputPin]. This is useful for creating physical chip select +/// pins required by the [embedded_hal_bus](https://docs.rs/embedded-hal-bus/latest/embedded_hal_bus/) +/// API. +pub struct ChipSelectPin { + spi_id: SpiId, + cs: ChipSelect, +} + +impl ChipSelectPin { + /// Chip select pin constructor. + pub const fn new(spi_id: SpiId, cs: ChipSelect) -> Self { + Self { spi_id, cs } + } +} + +impl embedded_hal::digital::ErrorType for ChipSelectPin { + type Error = Infallible; +} + +impl embedded_hal::digital::OutputPin for ChipSelectPin { + fn set_low(&mut self) -> Result<(), Self::Error> { + // Safety: We only touch the CS register bits of the specified peripheral. + let mut spi_regs = unsafe { SpiLowLevel::steal(self.spi_id).regs }; + spi_regs.modify_config(|val| val.with_cs_raw(self.cs.raw_reg())); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + // Safety: We only touch the CS register bits of the specified peripheral. + let mut spi_regs = unsafe { SpiLowLevel::steal(self.spi_id).regs }; + spi_regs.modify_config(|val| val.with_cs_raw(u4::MAX)); + Ok(()) + } +} + #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] /// Slave select configuration. pub enum SlaveSelectConfig { @@ -533,7 +597,7 @@ impl SpiLowLevel { .with_baud_rate_div(config.baud_div) .with_cpha(cpha) .with_cpol(cpol) - .with_master_ern(true) + .with_mode(zynq7000::spi::Mode::Master) .build(), ); // Configures for polling mode by default: TX trigger by one will lead to the @@ -561,12 +625,12 @@ impl SpiLowLevel { #[inline(always)] pub fn write_fifo_unchecked(&mut self, data: u8) { - self.regs.write_txd(FifoWrite::new(data)); + self.regs.write_tx_data(FifoWrite::new(data)); } #[inline(always)] pub fn read_fifo_unchecked(&mut self) -> u8 { - self.regs.read_rxd().value() + self.regs.read_rx_data().value() } #[inline] @@ -615,7 +679,7 @@ impl SpiLowLevel { /// This disables all interrupts relevant for non-blocking interrupt driven SPI operation /// in SPI master mode. #[inline] - pub fn disable_interrupts(&mut self) { + pub fn disable_interrupts_master_mode(&mut self) { self.regs.write_interupt_disable( InterruptControl::builder() .with_tx_underflow(true) @@ -632,7 +696,7 @@ 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, tx_below_threshold: bool) { + pub fn enable_interrupts_master_mode(&mut self, tx_below_threshold: bool) { self.regs.write_interrupt_enable( InterruptControl::builder() .with_tx_underflow(true) @@ -646,10 +710,27 @@ impl SpiLowLevel { ); } + /// This enables all interrupts relevant for non-blocking interrupt driven SPI operation + /// in SPI slave mode. + #[inline] + pub fn enable_interrupts_slave_mode(&mut self, mode_fault: 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_below_threshold(true) + .with_mode_fault(mode_fault) + .with_rx_ovr(true) + .build(), + ); + } + /// This clears all interrupts relevant for non-blocking interrupt driven SPI operation /// in SPI master mode. #[inline] - pub fn clear_interrupts(&mut self) { + pub fn clear_interrupts_master_mode(&mut self) { self.regs.write_interrupt_status( InterruptStatus::builder() .with_tx_underflow(true) @@ -859,6 +940,16 @@ impl Spi { spi } + #[inline] + pub const fn id(&self) -> SpiId { + self.inner.id + } + + #[inline] + pub const fn interrupt_id(&self) -> crate::Interrupt { + self.inner.id.interrupt_id() + } + pub fn write_delay_control(&mut self, delay_control: DelayControl) { self.inner.write_delay_control(delay_control); } @@ -1251,3 +1342,14 @@ pub fn configure_spi_ref_clock_with_divisor(clks: &mut Clocks, divisor: u6) { }; clks.io_clocks_mut().update_spi_clk(new_clk); } + +/// Connects SPI0 output signals to SPI1 input signals and vice-versa. +#[inline] +pub fn enable_spi0_to_spi1_loopback() { + // Safety: We only modify the SPI bit. + unsafe { + Slcr::with(|slcr| { + slcr.modify_mio_loopback(|val| val.with_spi0_loop_spi1(true)); + }); + } +} diff --git a/firmware/zynq7000-hal/src/spi/slave.rs b/firmware/zynq7000-hal/src/spi/slave.rs new file mode 100644 index 0000000..9f91bb4 --- /dev/null +++ b/firmware/zynq7000-hal/src/spi/slave.rs @@ -0,0 +1,76 @@ +use zynq7000::spi::{Config, FifoWrite, MmioRegisters}; + +use crate::{ + enable_amba_peripheral_clock, + spi::{SpiId, SpiLowLevel}, + spi_mode_const_to_cpol_cpha, +}; + +pub struct SpiSlave(pub SpiLowLevel); + +pub struct SlaveConfig { + pub mode: embedded_hal::spi::Mode, + pub enable_modefail: bool, +} + +impl SpiSlave { + /// Creates a very simple SPI slave driver. + /// + /// The SPI slave will not start in the enabled state to allow pre-loading the FIFO. + pub fn new(id: SpiId, regs: MmioRegisters<'static>, config: SlaveConfig) -> 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 (cpol, cpha) = spi_mode_const_to_cpol_cpha(config.mode); + ll.write_config( + Config::ZERO + .with_modefail_gen_en(config.enable_modefail) + .with_cpha(cpha) + .with_cpol(cpol), + ); + Self(ll) + } + + #[inline] + pub const fn id(&self) -> SpiId { + self.0.id + } + + #[inline] + pub const fn interrupt_id(&self) -> crate::Interrupt { + self.id().interrupt_id() + } + + #[inline] + pub fn enable_interrupts(&mut self, mode_error: bool) { + self.0.enable_interrupts_slave_mode(mode_error); + } + + /// Enable the SPI slave. This will allow it to respond to transfers initiated by the master. + /// + /// Please refer to the word detection in the TRM p.568 for more details. + #[inline] + pub fn enable(&mut self) { + self.0.enable(); + } + + /// This register is used for IDLE state detection on the SCLK line. + /// + /// Please refer to the word detection in the TRM p.568 for more details how this is useful. + #[inline] + pub fn write_slave_idle_counter(&mut self, count: u8) { + self.0.write_sicr(count as u32); + } + + /// Write data to the TX FIFO, which allows pre-loading data which will be sent via the + /// MISO line when the master initiates a transfer. + #[inline] + pub fn write_tx_data(&mut self, data: u8) { + self.0.write_tx_data(FifoWrite::new(data)); + } +} diff --git a/firmware/zynq7000-hal/src/uart/mod.rs b/firmware/zynq7000-hal/src/uart/mod.rs index 482322d..76731a4 100644 --- a/firmware/zynq7000-hal/src/uart/mod.rs +++ b/firmware/zynq7000-hal/src/uart/mod.rs @@ -66,6 +66,32 @@ pub enum UartId { Uart1 = 1, } +impl UartId { + /// Interrupt ID. + #[inline] + pub const fn interrupt_id(&self) -> crate::Interrupt { + match self { + UartId::Uart0 => crate::Interrupt::Spi(crate::SpiInterrupt::Uart0), + UartId::Uart1 => crate::Interrupt::Spi(crate::SpiInterrupt::Uart1), + } + } + + /// Unsafely steal the register block. + /// + /// # Safety + /// + /// This API can be used to potentially create a driver to the same peripheral structure + /// from multiple threads. The user must ensure that concurrent accesses are safe and do not + /// interfere with each other. + #[inline] + pub unsafe fn steal_regs(&self) -> MmioRegisters<'static> { + match self { + UartId::Uart0 => unsafe { zynq7000::uart::Registers::new_mmio_fixed_0() }, + UartId::Uart1 => unsafe { zynq7000::uart::Registers::new_mmio_fixed_1() }, + } + } +} + /// Common trait for PS UART peripherals. pub trait PsUart { /// UART register block. @@ -466,7 +492,6 @@ impl Config { pub struct Uart { rx: Rx, tx: Tx, - cfg: Config, } /// Invalid PS UART error. @@ -638,7 +663,25 @@ impl Uart { regs: reg_block, id: uart_id, }, - cfg, + } + } + + /// Steal a UART without doing ANY configuration. + /// + /// # Safety + /// + /// Circumvents ownership and safety guarantees by the HAL. Also, the driver will not work + /// unless the UART was configured beforehand. + pub unsafe fn steal(id: UartId) -> Uart { + let reg_block = unsafe { id.steal_regs() }; + Self { + rx: Rx { + regs: unsafe { reg_block.clone() }, + }, + tx: Tx { + regs: reg_block, + id, + }, } } @@ -657,12 +700,6 @@ impl Uart { &mut self.rx.regs } - /// Configuration. - #[inline] - pub const fn cfg(&self) -> &Config { - &self.cfg - } - /// Split into TX and RX halves. #[inline] pub const fn split(self) -> (Tx, Rx) { diff --git a/firmware/zynq7000/src/slcr/mod.rs b/firmware/zynq7000/src/slcr/mod.rs index dbec18e..7206364 100644 --- a/firmware/zynq7000/src/slcr/mod.rs +++ b/firmware/zynq7000/src/slcr/mod.rs @@ -98,6 +98,24 @@ pub struct LevelShifterRegister { user_lvl_shftr_en: Option, } +#[bitbybit::bitfield( + u32, + default = 0, + debug, + defmt_fields(feature = "defmt"), + forbid_overlaps +)] +pub struct MioLoopback { + #[bit(3, rw)] + i2c0_loop_i2c1: bool, + #[bit(2, rw)] + can0_loop_can1: bool, + #[bit(1, rw)] + ua0_loop_ua1: bool, + #[bit(0, rw)] + spi0_loop_spi1: bool, +} + /// System Level Control Registers access. #[derive(derive_mmio::Mmio)] #[repr(C)] @@ -158,7 +176,7 @@ pub struct Registers { _gap10: [u32; 0x0B], - mio_loopback: u32, + mio_loopback: MioLoopback, _gap11: u32, mio_mst_tri_0: u32, mio_mst_tri_1: u32, diff --git a/firmware/zynq7000/src/spi.rs b/firmware/zynq7000/src/spi.rs index 0fd8400..3cafe24 100644 --- a/firmware/zynq7000/src/spi.rs +++ b/firmware/zynq7000/src/spi.rs @@ -34,13 +34,15 @@ impl BaudDivSel { } } -#[bitbybit::bitfield( - u32, - default = 0x0, - debug, - defmt_bitfields(feature = "defmt"), - forbid_overlaps -)] +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Mode { + Slave = 0, + Master = 1, +} + +#[bitbybit::bitfield(u32, default = 0x0, debug, defmt_bitfields(feature = "defmt"))] pub struct Config { #[bit(17, rw)] modefail_gen_en: bool, @@ -70,6 +72,9 @@ pub struct Config { cpol: SpiClockPolarity, /// Master mode enable. 1 is master mode. #[bit(0, rw)] + mode: Mode, + // Deprecated access to bit 0. + #[bit(0, rw)] master_ern: bool, } @@ -127,6 +132,18 @@ pub struct InterruptControl { rx_ovr: bool, } +impl InterruptControl { + pub const ALL: Self = Self::builder() + .with_tx_underflow(true) + .with_rx_full(true) + .with_rx_not_empty(true) + .with_tx_full(true) + .with_tx_below_threshold(true) + .with_mode_fault(true) + .with_rx_ovr(true) + .build(); +} + #[bitbybit::bitfield(u32, debug, defmt_bitfields(feature = "defmt"), forbid_overlaps)] pub struct InterruptEnabled { #[bit(6, r)] @@ -228,9 +245,9 @@ pub struct Registers { enable: u32, delay_control: DelayControl, #[mmio(Write)] - txd: FifoWrite, + tx_data: FifoWrite, #[mmio(Read)] - rxd: FifoRead, + rx_data: FifoRead, sicr: u32, tx_trig: u32, rx_trig: u32, diff --git a/zedboard-fpga-design/src/zedboard-bd.tcl b/zedboard-fpga-design/src/zedboard-bd.tcl index 307d7f0..a636e92 100644 --- a/zedboard-fpga-design/src/zedboard-bd.tcl +++ b/zedboard-fpga-design/src/zedboard-bd.tcl @@ -741,7 +741,6 @@ proc create_root_design { parentCell } { connect_bd_net -net ilconstant_0_dout [get_bd_pins ilconstant_0/dout] \ [get_bd_pins EMIO_I_0/In2] connect_bd_net -net ilconstant_2_dout [get_bd_pins ilconstant_2/dout] \ - [get_bd_pins processing_system7_0/SPI0_MISO_I] \ [get_bd_pins processing_system7_0/SPI0_SS_I] connect_bd_net -net ilconstant_3_dout [get_bd_pins ilconstant_3/dout] \ [get_bd_pins processing_system7_0/SPI0_MOSI_I] \ -- 2.43.0