diff --git a/firmware/zynq7000-hal/src/spi/asynch.rs b/firmware/zynq7000-hal/src/spi/asynch.rs index cd230f8..2308ca9 100644 --- a/firmware/zynq7000-hal/src/spi/asynch.rs +++ b/firmware/zynq7000-hal/src/spi/asynch.rs @@ -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,6 +15,17 @@ static TRANSFER_CONTEXTS: [Mutex>; 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. /// @@ -23,18 +33,27 @@ static DONE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2]; /// the given SPI bank. pub 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_isr(); 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. @@ -43,92 +62,82 @@ pub fn on_interrupt(peripheral: SpiId) { } 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 +145,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 +164,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 +197,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 +213,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 +221,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 +262,7 @@ pub struct TransferContext { rx_progress: usize, tx_slice: RawBufSlice, rx_slice: RawBufSliceMut, + rx_overrun: bool, } #[allow(clippy::new_without_default)] @@ -253,145 +274,163 @@ 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, } -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()); (0..write_idx).for_each(|idx| { @@ -402,24 +441,31 @@ impl SpiFuture { // 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 0. 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 +473,23 @@ impl Future for SpiFuture { ) -> core::task::Poll { 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 @@ -459,65 +510,73 @@ impl SpiAsync { Self(spi) } - async fn read(&mut self, words: &mut [u8]) { + pub fn read(&mut self, words: &mut [u8]) -> Option> { 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> { 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> { 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> { 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 +606,7 @@ impl SpiWithHwCsAsync { impl embedded_hal_async::spi::ErrorType for SpiWithHwCsAsync { - type Error = Infallible; + type Error = RxOverrunError; } impl embedded_hal_async::spi::SpiDevice @@ -561,16 +620,24 @@ impl 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; diff --git a/firmware/zynq7000-hal/src/spi/mod.rs b/firmware/zynq7000-hal/src/spi/mod.rs index 6b9f996..4ad1e29 100644 --- a/firmware/zynq7000-hal/src/spi/mod.rs +++ b/firmware/zynq7000-hal/src/spi/mod.rs @@ -20,7 +20,7 @@ use embedded_hal::delay::DelayNs; pub use embedded_hal::spi::Mode; use zynq7000::slcr::reset::DualRefAndClockResetSpiUart; use zynq7000::spi::{ - BaudDivSel, DelayControl, FifoWrite, InterruptControl, InterruptMask, InterruptStatus, + BaudDivSel, DelayControl, FifoWrite, InterruptControl, InterruptEnabled, InterruptStatus, MmioRegisters, SPI_0_BASE_ADDR, SPI_1_BASE_ADDR, }; @@ -538,12 +538,12 @@ impl SpiLowLevel { #[inline] pub fn read_isr(&self) -> InterruptStatus { - self.regs.read_isr() + 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] @@ -573,13 +573,13 @@ impl SpiLowLevel { /// 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(), @@ -589,14 +589,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(), @@ -607,20 +607,34 @@ 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, @@ -908,7 +922,7 @@ impl 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; @@ -929,7 +943,7 @@ impl Spi { let mut read_idx = 0; while written < words.len() { - let status = self.regs().read_isr(); + let status = self.regs().read_interrupt_status(); // We empty the FIFO to prevent it filling up completely, as long as we have to write // bytes if status.rx_not_empty() { @@ -958,7 +972,7 @@ impl 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(); diff --git a/firmware/zynq7000/src/spi.rs b/firmware/zynq7000/src/spi.rs index 6ed250a..e424952 100644 --- a/firmware/zynq7000/src/spi.rs +++ b/firmware/zynq7000/src/spi.rs @@ -87,15 +87,18 @@ pub struct InterruptStatus { rx_full: bool, #[bit(4, rw)] rx_not_empty: bool, + /// Switches to 1 when the FIFO becomes full and then remains asserted until the FIFO falls + /// below the configured threshold level. #[bit(3, rw)] tx_full: bool, + /// TX FIFO level below configured threshold. #[bit(2, rw)] - tx_not_full: bool, + tx_below_threshold: bool, #[bit(1, rw)] mode_fault: bool, /// Receiver overflow interrupt. #[bit(0, rw)] - rx_ovr: bool, + rx_overrun: bool, } #[bitbybit::bitfield( @@ -114,8 +117,9 @@ pub struct InterruptControl { rx_not_empty: bool, #[bit(3, w)] tx_full: bool, + /// Interrupt when TX FIFO level below configured threshold. #[bit(2, w)] - tx_trig: bool, + tx_below_threshold: bool, #[bit(1, w)] mode_fault: bool, /// Receiver overflow interrupt. @@ -124,17 +128,20 @@ pub struct InterruptControl { } #[bitbybit::bitfield(u32, debug, defmt_bitfields(feature = "defmt"), forbid_overlaps)] -pub struct InterruptMask { +pub struct InterruptEnabled { #[bit(6, r)] tx_underflow: bool, #[bit(5, r)] rx_full: bool, #[bit(4, r)] rx_not_empty: bool, + /// Switches to 1 when the FIFO becomes full and then remains asserted until the FIFO falls + /// below the configured threshold level. #[bit(3, r)] tx_full: bool, + /// Interrupt when TX FIFO level below configured threshold. #[bit(2, r)] - tx_trig: bool, + tx_below_threshold: bool, #[bit(1, r)] mode_fault: bool, /// Receiver overflow interrupt. @@ -208,16 +215,16 @@ pub struct DelayControl { pub struct Registers { cr: Config, #[mmio(PureRead, Write)] - isr: InterruptStatus, + interrupt_status: InterruptStatus, /// Interrupt Enable Register. #[mmio(Write)] - ier: InterruptControl, + interrupt_enable: InterruptControl, /// Interrupt Disable Register. #[mmio(Write)] - idr: InterruptControl, + interupt_disable: InterruptControl, /// Interrupt Mask Register. #[mmio(PureRead)] - imr: InterruptMask, + enabled_interrupts: InterruptEnabled, enable: u32, delay_control: DelayControl, #[mmio(Write)]