From de2d0ea8c61d767e1789b99cc0e251cb872a63b9 Mon Sep 17 00:00:00 2001 From: Justin Beaurivage Date: Wed, 31 Jan 2024 17:16:47 -0500 Subject: [PATCH] Implement SPI --- hal/src/sercom/spi.rs | 35 +++++++-- hal/src/sercom/spi/impl_ehal.rs | 132 ++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 hal/src/sercom/spi/impl_ehal.rs diff --git a/hal/src/sercom/spi.rs b/hal/src/sercom/spi.rs index c96a29a75a9a..3c8b9837275c 100644 --- a/hal/src/sercom/spi.rs +++ b/hal/src/sercom/spi.rs @@ -318,6 +318,7 @@ use core::marker::PhantomData; use crate::ehal_02::spi; pub use crate::ehal_02::spi::{Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; use bitflags::bitflags; +use num_traits::AsPrimitive; use crate::sercom::*; use crate::time::Hertz; @@ -363,12 +364,6 @@ pub mod lengths { }); } -#[cfg(feature = "thumbv6")] -#[path = "spi/impl_ehal_thumbv6m.rs"] -pub mod impl_ehal; - -#[cfg(feature = "thumbv7")] -#[path = "spi/impl_ehal_thumbv7em.rs"] pub mod impl_ehal; //============================================================================= @@ -646,6 +641,7 @@ where mode: PhantomData, size: PhantomData, freq: Hertz, + nop_word: DataWidth, } impl Config

{ @@ -666,6 +662,7 @@ impl Config

{ mode: PhantomData, size: PhantomData, freq: freq.into(), + nop_word: 0x00.as_(), } } @@ -716,6 +713,7 @@ where mode: PhantomData, size: PhantomData, freq: self.freq, + nop_word: self.nop_word, } } @@ -867,6 +865,31 @@ where self } + /// Get the NOP word + /// + /// This word is used when reading in Duplex mode, since an equal number of + /// words must be sent in order to avoid overflow errors. + pub fn get_nop_word(&self) -> DataWidth { + self.nop_word + } + + /// Set the NOP word + /// + /// This word is used when reading in Duplex mode, since an equal number of + /// words must be sent in order to avoid overflow errors. + pub fn set_nop_word(&mut self, nop_word: DataWidth) { + self.nop_word = nop_word; + } + + /// Set the NOP word using the builder pattern + /// + /// This word is used when reading in Duplex mode, since an equal number of + /// words must be sent in order to avoid overflow errors. + pub fn nop_word(mut self, nop_word: DataWidth) -> Self { + self.nop_word = nop_word; + self + } + /// Get the baud rate /// /// The returned baud rate may not exactly match what was set. diff --git a/hal/src/sercom/spi/impl_ehal.rs b/hal/src/sercom/spi/impl_ehal.rs new file mode 100644 index 000000000000..490a933652e2 --- /dev/null +++ b/hal/src/sercom/spi/impl_ehal.rs @@ -0,0 +1,132 @@ +use super::*; +use crate::ehal::spi::{self, ErrorKind, ErrorType, SpiBus}; +use num_traits::{AsPrimitive, PrimInt}; + +#[cfg(feature = "thumbv6")] +#[path = "impl_ehal_thumbv6m.rs"] +pub mod impl_ehal_02; + +#[cfg(feature = "thumbv7")] +#[path = "impl_ehal_thumbv7em.rs"] +pub mod impl_ehal_02; + +impl spi::Error for Error { + fn kind(&self) -> ErrorKind { + match self { + Error::Overflow => ErrorKind::Overrun, + Error::LengthError => ErrorKind::Other, + } + } +} + +impl ErrorType for Spi, Duplex> +where + Config: ValidConfig, + P: ValidPads, + M: MasterMode, + C: Size, +{ + type Error = Error; +} + +impl Spi, Duplex> +where + Config: ValidConfig, + P: ValidPads, + M: MasterMode, + C: Size + Copy + 'static, + C::Word: PrimInt + AsPrimitive, + DataWidth: AsPrimitive, +{ + /// Read and write a single word to the bus simultaneously. + fn transfer_word_in_place(&mut self, word: C::Word) -> Result { + while self.read_flags().contains(Flags::DRE) { + core::hint::spin_loop(); + } + self.read_flags_errors()?; + unsafe { + self.write_data(word.as_()); + } + + while self.read_flags().contains(Flags::TXC | Flags::RXC) { + core::hint::spin_loop(); + } + let word = unsafe { self.read_data().as_() }; + Ok(word) + } + + /// Perform a transfer, word by word. + /// + /// No-op words will be written if `read` is longer than `write`. Extra + /// words are ignored if `write` is longer than `read`. + fn transfer_word_by_word( + &mut self, + read: &mut [C::Word], + write: &[C::Word], + ) -> Result<(), Error> { + let nop_word = self.config.nop_word; + for (r, w) in read + .iter_mut() + .zip(write.iter().chain(core::iter::repeat(&nop_word.as_()))) + { + *r = self.transfer_word_in_place(*w)?; + } + + Ok(()) + } +} + +impl SpiBus> for Spi, Duplex> +where + Config: ValidConfig, + P: ValidPads, + M: MasterMode, + C: Size + Copy + 'static, + C::Word: PrimInt + AsPrimitive, + DataWidth: AsPrimitive, +{ + fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { + for word in words.iter_mut() { + // Should replace todo with nop_word + *word = self.transfer_word_in_place(self.config.nop_word.as_())?; + } + + Ok(()) + } + + #[inline] + fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { + // We are `Duplex`, so we must receive as many words as we send, + // otherwise we could trigger an overflow + for word in words { + let _ = self.transfer_word_in_place(*word)?; + } + Ok(()) + } + + #[inline] + fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { + self.transfer_word_by_word(read, write) + } + + #[inline] + fn transfer_in_place<'w>(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { + // Can only ever do word-by-word to avoid DMA race conditions + for word in words { + let read = self.transfer_word_in_place(*word)?; + *word = read; + } + + Ok(()) + } + + #[inline] + fn flush(&mut self) -> Result<(), Error> { + // Ignore buffer overflow errors + while !self.read_flags().contains(Flags::TXC) { + core::hint::spin_loop(); + } + + Ok(()) + } +}