From a1489285c9992a92fb139ef5dead1d1fde91e0a7 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 23 Apr 2026 11:13:54 +0200 Subject: [PATCH] added asynch SPI implementation --- va108xx/va108xx-hal/src/sysconfig.rs | 3 +- vorago-shared-hal/src/lib.rs | 7 + vorago-shared-hal/src/spi/asynch.rs | 584 +++++++++++++++++++++++++++ vorago-shared-hal/src/spi/mod.rs | 7 +- vorago-shared-hal/src/spi/regs.rs | 41 +- 5 files changed, 625 insertions(+), 17 deletions(-) create mode 100644 vorago-shared-hal/src/spi/asynch.rs diff --git a/va108xx/va108xx-hal/src/sysconfig.rs b/va108xx/va108xx-hal/src/sysconfig.rs index bf70601..06e0aef 100644 --- a/va108xx/va108xx-hal/src/sysconfig.rs +++ b/va108xx/va108xx-hal/src/sysconfig.rs @@ -39,5 +39,6 @@ pub fn disable_ram_scrubbing() { } pub use vorago_shared_hal::sysconfig::{ - assert_peripheral_reset, disable_peripheral_clock, enable_peripheral_clock, + assert_peripheral_reset, deassert_peripheral_reset, disable_peripheral_clock, + enable_peripheral_clock, reset_peripheral_for_cycles, }; diff --git a/vorago-shared-hal/src/lib.rs b/vorago-shared-hal/src/lib.rs index f0ccf26..36d316e 100644 --- a/vorago-shared-hal/src/lib.rs +++ b/vorago-shared-hal/src/lib.rs @@ -225,4 +225,11 @@ pub(crate) mod shared { #[bit(0, w)] rx_fifo: bool, } + + impl FifoClear { + pub const ALL: Self = Self::builder() + .with_tx_fifo(true) + .with_rx_fifo(true) + .build(); + } } diff --git a/vorago-shared-hal/src/spi/asynch.rs b/vorago-shared-hal/src/spi/asynch.rs new file mode 100644 index 0000000..7e97150 --- /dev/null +++ b/vorago-shared-hal/src/spi/asynch.rs @@ -0,0 +1,584 @@ +use core::{cell::RefCell, convert::Infallible}; + +use arbitrary_int::u5; +use critical_section::Mutex; +use embassy_sync::waitqueue::AtomicWaker; +use portable_atomic::AtomicBool; +use raw_slice::{RawBufSlice, RawBufSliceMut}; + +use crate::{ + shared::{FifoClear, TriggerLevel}, + spi::regs::{Data, InterruptClear, InterruptControl, InterruptStatus}, +}; + +#[cfg(feature = "vor1x")] +pub const NUM_SPIS: usize = 3; +#[cfg(feature = "vor4x")] +pub const NUM_SPIS: usize = 4; + +static WAKERS: [AtomicWaker; NUM_SPIS] = [const { AtomicWaker::new() }; NUM_SPIS]; +static TRANSFER_CONTEXTS: [Mutex>; NUM_SPIS] = + [const { Mutex::new(RefCell::new(TransferContext::new())) }; NUM_SPIS]; +// Completion flag. Kept outside of the context structure as an atomic to avoid +// critical section. +static DONE: [AtomicBool; NUM_SPIS] = [const { AtomicBool::new(false) }; NUM_SPIS]; + +/// 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 +/// the given SPI bank. +pub fn on_interrupt(peripheral: super::Bank) { + let mut spi = unsafe { peripheral.steal_regs() }; + let idx = peripheral as usize; + let interrupt_enabled = spi.read_interrupt_control(); + let isr = spi.read_interrupt_status(); + // IRQ is not related. + if interrupt_enabled.raw_value() == 0 { + return; + } + // Prevent spurious interrupts from messing with out logic here. + spi.write_interrupt_control(InterruptControl::DISABLE_ALL); + spi.write_interrupt_clear(InterruptClear::ALL); + let mut context = critical_section::with(|cs| { + let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs); + *context_ref.borrow() + }); + // No transfer active. + if context.transfer_type.is_none() { + return; + } + 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::TransferInPlace => { + on_interrupt_transfer_in_place(idx, &mut context, &mut spi, isr) + } + }; +} + +fn on_interrupt_read( + idx: usize, + context: &mut TransferContext, + spi: &mut super::regs::MmioSpi<'static>, + isr: InterruptStatus, +) { + 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(|_| { + read_slice[context.rx_progress] = (spi.read_data().data() & 0xFF) as u8; + context.rx_progress += 1; + }); + + // The FIFO still needs to be pumped. + while context.tx_progress < read_slice.len() && spi.read_status().tx_not_full() { + spi.write_data(Data::new_with_raw_value(0)); + context.tx_progress += 1; + } + + isr_finish_handler(idx, spi, context, transfer_len) +} + +fn on_interrupt_write( + idx: usize, + context: &mut TransferContext, + spi: &mut super::regs::MmioSpi<'static>, + isr: InterruptStatus, +) { + 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(|_| { + spi.read_data(); + context.rx_progress += 1; + }); + + // Data still needs to be sent + while context.tx_progress < transfer_len && spi.read_status().tx_not_full() { + spi.write_data(Data::new_with_raw_value( + write_slice[context.tx_progress] as u32, + )); + context.tx_progress += 1; + } + + isr_finish_handler(idx, spi, context, transfer_len) +} + +fn on_interrupt_transfer( + idx: usize, + context: &mut TransferContext, + spi: &mut super::regs::MmioSpi<'static>, + isr: InterruptStatus, +) { + 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); + + // Send data first to avoid overwriting data that still needs to be sent. + while context.tx_progress < transfer_len && spi.read_status().tx_not_full() { + if context.tx_progress < write_len { + spi.write_data(Data::new_with_raw_value( + write_slice[context.tx_progress] as u32, + )); + } else { + // Dummy write. + spi.write_data(Data::new_with_raw_value(0)); + } + context.tx_progress += 1; + } + + // Read data from RX FIFO. + 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_data().data() & 0xFF) as u8; + } else { + spi.read_data(); + } + context.rx_progress += 1; + }); + + isr_finish_handler(idx, spi, context, transfer_len) +} + +fn on_interrupt_transfer_in_place( + idx: usize, + context: &mut TransferContext, + spi: &mut super::regs::MmioSpi<'static>, + isr: InterruptStatus, +) { + let transfer_slice = unsafe { context.rx_slice.get_mut().unwrap() }; + let transfer_len = transfer_slice.len(); + // Send data first to avoid overwriting data that still needs to be sent. + while context.tx_progress < transfer_len && spi.read_status().tx_not_full() { + spi.write_data(Data::new_with_raw_value( + transfer_slice[context.tx_progress] as u32, + )); + context.tx_progress += 1; + } + // Read data from RX FIFO. + 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_data().data() & 0xFF) as u8; + context.rx_progress += 1; + }); + + isr_finish_handler(idx, spi, context, transfer_len) +} + +fn calculate_read_len( + spi: &mut super::regs::MmioSpi<'static>, + isr: InterruptStatus, + total_read_len: usize, + rx_progress: usize, +) -> usize { + if isr.rx() { + core::cmp::min(super::FIFO_DEPTH, total_read_len - rx_progress) + } else if spi.read_status().rx_not_empty() { + let fifo_level = spi.read_state().rx_fifo(); + core::cmp::min(total_read_len - rx_progress, fifo_level 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( + idx: usize, + spi: &mut super::regs::MmioSpi<'static>, + context: &mut TransferContext, + transfer_len: usize, +) { + // Transfer finish condition. + if context.rx_progress == context.tx_progress && context.rx_progress == transfer_len { + finish_transfer(idx, context, spi); + return; + } + + unfinished_transfer(spi, transfer_len, context.rx_progress); + + // If the transfer is done, the context structure was already written back. + // Write back updated context structure. + critical_section::with(|cs| { + let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs); + *context_ref.borrow_mut() = *context; + }); +} + +fn finish_transfer( + idx: usize, + context: &mut TransferContext, + spi: &mut super::regs::MmioSpi<'static>, +) { + // Write back updated context structure. + critical_section::with(|cs| { + let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs); + *context_ref.borrow_mut() = *context; + }); + spi.write_rx_fifo_trigger(TriggerLevel::new(u5::new(0x08))); + spi.write_tx_fifo_trigger(TriggerLevel::new(u5::new(0x00))); + // Interrupts were already disabled and cleared. + DONE[idx].store(true, core::sync::atomic::Ordering::Relaxed); + WAKERS[idx].wake(); +} + +fn unfinished_transfer( + spi: &mut super::regs::MmioSpi<'static>, + transfer_len: usize, + rx_progress: usize, +) { + let new_trig_level = core::cmp::min(super::FIFO_DEPTH, transfer_len - rx_progress); + spi.write_rx_fifo_trigger(TriggerLevel::new(u5::new(new_trig_level as u8))); + // Re-enable interrupts with the new RX FIFO trigger level. + spi.write_interrupt_control(InterruptControl::ENABLE_ALL); +} + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TransferType { + Read, + Write, + Transfer, + TransferInPlace, +} + +#[derive(Default, Debug, Copy, Clone)] +pub struct TransferContext { + transfer_type: Option, + tx_progress: usize, + rx_progress: usize, + tx_slice: RawBufSlice, + rx_slice: RawBufSliceMut, +} + +#[allow(clippy::new_without_default)] +impl TransferContext { + pub const fn new() -> Self { + Self { + transfer_type: None, + tx_progress: 0, + rx_progress: 0, + tx_slice: RawBufSlice::new_nulled(), + rx_slice: RawBufSliceMut::new_nulled(), + } + } +} + +pub struct SpiFuture<'spi> { + bank: super::Bank, + spi: &'spi mut super::Spi, + finished_regularly: core::cell::Cell, +} + +impl<'spi> SpiFuture<'spi> { + fn new_for_read(spi: &'spi mut super::Spi, bank: super::Bank, words: &mut [u8]) -> Self { + if words.is_empty() { + panic!("words length unexpectedly 0"); + } + let idx = bank as usize; + DONE[idx].store(false, core::sync::atomic::Ordering::Relaxed); + spi.regs + .write_interrupt_control(InterruptControl::DISABLE_ALL); + spi.regs.write_fifo_clear(FifoClear::ALL); + spi.regs.modify_ctrl1(|v| v.with_mtxpause(true)); + let write_idx = core::cmp::min(super::FIFO_DEPTH, words.len()); + // Send dummy bytes. + (0..write_idx).for_each(|_| { + spi.regs.write_data(Data::new_with_raw_value(0)); + }); + + Self::set_triggers(spi, write_idx, words.len()); + + critical_section::with(|cs| { + let context_ref = TRANSFER_CONTEXTS[idx].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.rx_progress = 0; + spi.regs.write_interrupt_clear(InterruptClear::ALL); + spi.regs + .write_interrupt_control(InterruptControl::ENABLE_ALL); + spi.regs.modify_ctrl1(|v| v.with_mtxpause(false)); + }); + Self { + bank, + spi, + finished_regularly: core::cell::Cell::new(false), + } + } + + fn new_for_write(spi: &'spi mut super::Spi, bank: super::Bank, words: &[u8]) -> Self { + if words.is_empty() { + panic!("words length unexpectedly 0"); + } + let (idx, write_idx) = Self::generic_init_transfer(spi, bank, words); + critical_section::with(|cs| { + let context_ref = TRANSFER_CONTEXTS[idx].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.rx_progress = 0; + spi.regs.write_interrupt_clear(InterruptClear::ALL); + spi.regs + .write_interrupt_control(InterruptControl::ENABLE_ALL); + spi.regs.modify_ctrl1(|v| v.with_mtxpause(false)); + }); + Self { + bank, + spi, + finished_regularly: core::cell::Cell::new(false), + } + } + + fn new_for_transfer( + spi: &'spi mut super::Spi, + spi_id: super::Bank, + 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); + critical_section::with(|cs| { + let context_ref = TRANSFER_CONTEXTS[idx].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.rx_progress = 0; + spi.regs.write_interrupt_clear(InterruptClear::ALL); + spi.regs + .write_interrupt_control(InterruptControl::ENABLE_ALL); + spi.regs.modify_ctrl1(|v| v.with_mtxpause(false)); + }); + Self { + bank: spi_id, + spi, + finished_regularly: core::cell::Cell::new(false), + } + } + + fn new_for_transfer_in_place( + spi: &'spi mut super::Spi, + spi_id: super::Bank, + 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); + critical_section::with(|cs| { + let context_ref = TRANSFER_CONTEXTS[idx].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.rx_progress = 0; + spi.regs.write_interrupt_clear(InterruptClear::ALL); + spi.regs + .write_interrupt_control(InterruptControl::ENABLE_ALL); + spi.regs.modify_ctrl1(|v| v.with_mtxpause(false)); + }); + Self { + bank: spi_id, + spi, + finished_regularly: core::cell::Cell::new(false), + } + } + + fn generic_init_transfer( + spi: &mut super::Spi, + bank: super::Bank, + write: &[u8], + ) -> (usize, usize) { + let idx = bank as usize; + DONE[idx].store(false, core::sync::atomic::Ordering::Relaxed); + spi.regs + .write_interrupt_control(InterruptControl::DISABLE_ALL); + spi.regs.write_fifo_clear(FifoClear::ALL); + spi.regs.modify_ctrl1(|v| v.with_mtxpause(true)); + + let write_idx = core::cmp::min(super::FIFO_DEPTH, write.len()); + (0..write_idx).for_each(|idx| { + spi.regs + .write_data(Data::new_with_raw_value(write[idx] as u32)); + }); + + Self::set_triggers(spi, write_idx, write.len()); + (idx, write_idx) + } + + fn set_triggers(spi: &mut super::Spi, write_idx: usize, write_len: usize) { + // This should never fail because it is never larger than the FIFO depth. + spi.regs + .write_rx_fifo_trigger(TriggerLevel::new(u5::new(write_idx as u8))); + // 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 PG says that this interrupt is triggered when the FIFO has less than + // threshold entries. + if write_len > super::FIFO_DEPTH { + spi.regs + .write_tx_fifo_trigger(TriggerLevel::new(u5::new(2))); + } else { + spi.regs + .write_tx_fifo_trigger(TriggerLevel::new(u5::new(0))); + } + } +} + +impl<'spi> Future for SpiFuture<'spi> { + type Output = (); + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + WAKERS[self.bank as usize].register(cx.waker()); + if DONE[self.bank as usize].swap(false, core::sync::atomic::Ordering::Relaxed) { + critical_section::with(|cs| { + let mut ctx = TRANSFER_CONTEXTS[self.bank as usize] + .borrow(cs) + .borrow_mut(); + *ctx = TransferContext::default(); + }); + self.finished_regularly.set(true); + return core::task::Poll::Ready(()); + } + core::task::Poll::Pending + } +} + +impl<'spi> Drop for SpiFuture<'spi> { + fn drop(&mut self) { + if !self.finished_regularly.get() { + // It might be sufficient to disable and enable the SPI.. But this definitely + // ensures the SPI is fully reset. + self.spi.regs.write_interrupt_clear(InterruptClear::ALL); + self.spi + .regs + .write_interrupt_control(InterruptControl::DISABLE_ALL); + self.spi.regs.write_fifo_clear(FifoClear::ALL); + } + } +} + +/// Asynchronous SPI driver. +/// +/// This is the primary data structure used to perform non-blocking SPI operations. +/// It implements the [embedded_hal_async::spi::SpiBus] as well. +pub struct SpiAsync(pub super::Spi); + +impl SpiAsync { + pub fn new( + mut spi: super::Spi, + #[cfg(feature = "vor1x")] opt_irq_cfg: Option, + ) -> Self { + #[cfg(feature = "vor1x")] + if let Some(irq_cfg) = opt_irq_cfg { + spi.regs + .write_interrupt_control(InterruptControl::DISABLE_ALL); + spi.regs.write_interrupt_clear(InterruptClear::ALL); + if irq_cfg.route { + crate::enable_peripheral_clock(crate::PeripheralSelect::Irqsel); + unsafe { va108xx::Irqsel::steal() } + .spi(spi.id as usize) + .write(|w| unsafe { w.bits(irq_cfg.id as u32) }); + } + if irq_cfg.enable_in_nvic { + // Safety: User has specifically configured this. + unsafe { crate::enable_nvic_interrupt(irq_cfg.id) }; + } + } + // Disable blockmode for asynchronous mode. + spi.regs + .modify_ctrl1(|v| v.with_bm_stall(false).with_blockmode(false)); + Self(spi) + } + + fn read(&mut self, words: &mut [u8]) -> Option> { + if words.is_empty() { + return None; + } + let id = self.0.id; + Some(SpiFuture::new_for_read(&mut self.0, id, words)) + } + + fn write(&mut self, words: &[u8]) -> Option> { + if words.is_empty() { + return None; + } + let id = self.0.id; + Some(SpiFuture::new_for_write(&mut self.0, id, words)) + } + + fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Option> { + if read.is_empty() || write.is_empty() { + return None; + } + let id = self.0.id; + Some(SpiFuture::new_for_transfer(&mut self.0, id, read, write)) + } + + fn transfer_in_place(&mut self, words: &mut [u8]) -> Option> { + if words.is_empty() { + return None; + } + let id = self.0.id; + Some(SpiFuture::new_for_transfer_in_place(&mut self.0, id, words)) + } +} + +impl embedded_hal_async::spi::ErrorType for SpiAsync { + type Error = Infallible; +} + +impl embedded_hal_async::spi::SpiBus for SpiAsync { + async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.read(words).unwrap().await; + Ok(()) + } + + async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.write(words).unwrap().await; + Ok(()) + } + + async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + 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).unwrap().await; + Ok(()) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/vorago-shared-hal/src/spi/mod.rs b/vorago-shared-hal/src/spi/mod.rs index 817045c..58ef440 100644 --- a/vorago-shared-hal/src/spi/mod.rs +++ b/vorago-shared-hal/src/spi/mod.rs @@ -12,8 +12,11 @@ use va416xx as pac; pub use regs::{Bank, HwChipSelectId}; +pub mod asynch; pub mod regs; +pub const FIFO_DEPTH: usize = 16; + pub fn configure_pin_as_hw_cs_pin(_pin: P) -> HwChipSelectId { IoPeriphPin::new(P::ID, P::FUN_SEL, None); P::CS_ID @@ -520,7 +523,6 @@ pub fn clk_div_for_target_clock(sys_clk: Hertz, spi_clk: Hertz) -> Option { pub struct Spi { id: Bank, regs: regs::MmioSpi<'static>, - cfg: SpiConfig, /// Fill word for read-only SPI transactions. fill_word: Word, blockmode: bool, @@ -653,7 +655,6 @@ where Spi { id: spi_sel, regs: regs::Spi::new_mmio(spi_sel), - cfg: spi_cfg, fill_word: Default::default(), bmstall: spi_cfg.bmstall, blockmode: spi_cfg.blockmode, @@ -1031,7 +1032,6 @@ impl From> for Spi { Spi { id: old_spi.id, regs: old_spi.regs, - cfg: old_spi.cfg, blockmode: old_spi.blockmode, fill_word: Default::default(), bmstall: old_spi.bmstall, @@ -1049,7 +1049,6 @@ impl From> for Spi { Spi { id: old_spi.id, regs: old_spi.regs, - cfg: old_spi.cfg, blockmode: old_spi.blockmode, fill_word: Default::default(), bmstall: old_spi.bmstall, diff --git a/vorago-shared-hal/src/spi/regs.rs b/vorago-shared-hal/src/spi/regs.rs index 99d7d50..451ac3a 100644 --- a/vorago-shared-hal/src/spi/regs.rs +++ b/vorago-shared-hal/src/spi/regs.rs @@ -25,11 +25,11 @@ cfg_if::cfg_if! { #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Bank { - Spi0, - Spi1, - Spi2, + Spi0 = 0, + Spi1 = 1, + Spi2 = 2, #[cfg(feature = "vor4x")] - Spi3, + Spi3 = 3, } impl Bank { @@ -157,7 +157,7 @@ impl ClockPrescaler { } } -#[bitbybit::bitfield(u32, debug, defmt_bitfields(feature = "defmt"))] +#[bitbybit::bitfield(u32, debug, default = 0x0, defmt_bitfields(feature = "defmt"))] pub struct InterruptControl { /// TX FIFO count <= TX FIFO trigger level. #[bit(3, rw)] @@ -174,9 +174,19 @@ pub struct InterruptControl { rx_overrun: bool, } +impl InterruptControl { + pub const DISABLE_ALL: Self = Self::ZERO; + pub const ENABLE_ALL: Self = Self::builder() + .with_tx(true) + .with_rx(true) + .with_rx_timeout(true) + .with_rx_overrun(true) + .build(); +} + #[bitbybit::bitfield(u32, debug, defmt_bitfields(feature = "defmt"))] pub struct InterruptStatus { - /// TX FIFO count <= TX FIFO trigger level. + /// TX FIFO count < TX FIFO trigger level. #[bit(3, r)] tx: bool, /// RX FIFO count >= RX FIFO trigger level. @@ -191,7 +201,7 @@ pub struct InterruptStatus { rx_overrun: bool, } -#[bitbybit::bitfield(u32)] +#[bitbybit::bitfield(u32, default = 0x0)] #[derive(Debug)] pub struct InterruptClear { /// Clearing the RX interrupt or reading data from the FIFO resets the timeout counter. @@ -201,6 +211,13 @@ pub struct InterruptClear { rx_overrun: bool, } +impl InterruptClear { + pub const ALL: Self = Self::builder() + .with_rx_timeout(true) + .with_rx_overrun(true) + .build(); +} + #[bitbybit::bitfield(u32, debug, defmt_bitfields(feature = "defmt"))] pub struct State { #[bits(0..=7, r)] @@ -221,21 +238,21 @@ pub struct Spi { #[mmio(PureRead)] status: Status, clkprescale: ClockPrescaler, - irq_enb: InterruptControl, + interrupt_control: InterruptControl, /// Raw interrupt status. #[mmio(PureRead)] - irq_raw: InterruptStatus, + interrupt_status_raw: InterruptStatus, /// Enabled interrupt status. #[mmio(PureRead)] - irq_status: InterruptStatus, + interrupt_status: InterruptStatus, #[mmio(Write)] - irq_clear: InterruptClear, + interrupt_clear: InterruptClear, rx_fifo_trigger: TriggerLevel, tx_fifo_trigger: TriggerLevel, #[mmio(Write)] fifo_clear: FifoClear, #[mmio(PureRead)] - state: u32, + state: State, #[cfg(feature = "vor1x")] _reserved: [u32; 0x3F2], #[cfg(feature = "vor4x")] -- 2.43.0