diff --git a/src/dma.rs b/src/dma.rs index 2cc2dc0b..b93e1e3d 100644 --- a/src/dma.rs +++ b/src/dma.rs @@ -351,6 +351,19 @@ where } } +impl Transfer +where + PAYLOAD: TransferPayload, +{ + pub(crate) fn rw(buffer: BUFFER, payload: PAYLOAD) -> Self { + Transfer { + _mode: PhantomData, + buffer, + payload, + } + } +} + impl Drop for Transfer where PAYLOAD: TransferPayload, @@ -367,6 +380,86 @@ pub struct R; /// Write transfer pub struct W; +/// Read/Write transfer +pub struct RW; + +macro_rules! for_all_pairs { + ($mac:ident: $($x:ident)*) => { + // Duplicate the list + for_all_pairs!(@inner $mac: $($x)*; $($x)*); + }; + + // The end of iteration: we exhausted the list + (@inner $mac:ident: ; $($x:ident)*) => {}; + + // The head/tail recursion: pick the first element of the first list + // and recursively do it for the tail. + (@inner $mac:ident: $head:ident $($tail:ident)*; $($x:ident)*) => { + $( + $mac!($head $x); + )* + for_all_pairs!(@inner $mac: $($tail)*; $($x)*); + }; +} + +macro_rules! rx_tx_channel_mapping { + ($CH_A:ident $CH_B:ident) => { + impl Transfer> + where + RxTxDma: TransferPayload, + { + pub fn is_done(&self) -> bool { + !self.payload.rx_channel.in_progress() && !self.payload.tx_channel.in_progress() + } + + pub fn wait(mut self) -> (BUFFER, RxTxDma) { + // XXX should we check for transfer errors here? + // The manual says "A DMA transfer error can be generated by reading + // from or writing to a reserved address space". I think it's impossible + // to get to that state with our type safe API and *safe* Rust. + while !self.is_done() {} + + self.payload.stop(); + + // TODO can we weaken this compiler barrier? + // NOTE(compiler_fence) operations on `buffer` should not be reordered + // before the previous statement, which marks the DMA transfer as done + atomic::compiler_fence(Ordering::SeqCst); + + // `Transfer` needs to have a `Drop` implementation, because we accept + // managed buffers that can free their memory on drop. Because of that + // we can't move out of the `Transfer`'s fields, so we use `ptr::read` + // and `mem::forget`. + // + // NOTE(unsafe) There is no panic branch between getting the resources + // and forgetting `self`. + unsafe { + let buffer = ptr::read(&self.buffer); + let payload = ptr::read(&self.payload); + core::mem::forget(self); + (buffer, payload) + } + } + } + + impl Transfer> + where + RxTxDma: TransferPayload, + { + pub fn peek(&self) -> &[T] + where + BUFFER: AsRef<[T]>, + { + let pending = self.payload.rx_channel.get_cndtr() as usize; + + let capacity = self.buffer.as_ref().len(); + + &self.buffer.as_ref()[..(capacity - pending)] + } + } + }; +} + macro_rules! dma { ($($DMAX:ident: ($dmaX:ident, $dmaXen:ident, $dmaXrst:ident, { $($CX:ident: ( @@ -395,12 +488,14 @@ macro_rules! dma { use core::ptr; use stable_deref_trait::StableDeref; - use crate::dma::{CircBuffer, FrameReader, FrameSender, DMAFrame, DmaExt, Error, Event, Half, Transfer, W, R, RxDma, TxDma, TransferPayload}; + use crate::dma::{CircBuffer, FrameReader, FrameSender, DMAFrame, DmaExt, Error, Event, Half, Transfer, W, R, RW, RxDma, RxTxDma, TxDma, TransferPayload}; use crate::rcc::AHB1; #[allow(clippy::manual_non_exhaustive)] pub struct Channels((), $(pub $CX),+); + for_all_pairs!(rx_tx_channel_mapping: $($CX)+); + $( /// A singleton that represents a single DMAx channel (channel X in this case) /// @@ -1096,8 +1191,8 @@ pub struct TxDma { /// DMA Receiver/Transmitter pub struct RxTxDma { pub(crate) payload: PAYLOAD, - pub rxchannel: RXCH, - pub txchannel: TXCH, + pub rx_channel: RXCH, + pub tx_channel: TXCH, } pub trait Receive { @@ -1110,6 +1205,12 @@ pub trait Transmit { type ReceivedWord; } +pub trait ReceiveTransmit { + type RxChannel; + type TxChannel; + type TransferedWord; +} + /// Trait for circular DMA readings from peripheral to memory. pub trait CircReadDma: Receive where @@ -1137,3 +1238,12 @@ where { fn write(self, buffer: B) -> Transfer; } + +/// Trait for DMA simultaneously writing and reading between memory and peripheral. +pub trait TransferDma: ReceiveTransmit +where + B: StaticWriteBuffer, + Self: core::marker::Sized + TransferPayload, +{ + fn transfer(self, buffer: B) -> Transfer; +} diff --git a/src/spi.rs b/src/spi.rs index 634e2249..2b02c7f9 100644 --- a/src/spi.rs +++ b/src/spi.rs @@ -4,12 +4,14 @@ use core::ptr; use core::sync::atomic; use core::sync::atomic::Ordering; -use crate::dma::{Receive, RxDma, Transfer, TransferPayload, W}; +use crate::dma::{self, dma1, dma2, TransferPayload}; use crate::gpio::{Alternate, Floating, Input, AF5}; use crate::hal::spi::{FullDuplex, Mode, Phase, Polarity}; use crate::rcc::{Clocks, APB1R1, APB2}; use crate::time::Hertz; +use embedded_dma::{StaticReadBuffer, StaticWriteBuffer}; + /// SPI error #[non_exhaustive] #[derive(Debug)] @@ -364,30 +366,77 @@ pub struct SpiPayload { spi: Spi, } -pub type SpiRxDma = RxDma, CHANNEL>; +pub type SpiRxDma = dma::RxDma, CHANNEL>; + +pub type SpiTxDma = dma::TxDma, CHANNEL>; + +pub type SpiRxTxDma = dma::RxTxDma, RXCH, TXCH>; -macro_rules! spi_rx_dma { - ($SPIi:ident, $TCi:ident, $chanX:ident, $mapX:ident) => { - impl Receive for SpiRxDma<$SPIi, PINS, $TCi> { - type RxChannel = $TCi; - type TransmittedWord = (); +macro_rules! spi_dma { + ($SPIX:ident, $RX_CH:path, $RX_CHX:ident, $RX_MAPX:ident, $TX_CH:path, $TX_CHX:ident, $TX_MAPX:ident) => { + impl dma::Receive for SpiRxDma<$SPIX, PINS, $RX_CH> { + type RxChannel = $RX_CH; + type TransmittedWord = u8; } - impl Spi<$SPIi, PINS> { - pub fn with_rx_dma(self, channel: $TCi) -> SpiRxDma<$SPIi, PINS, $TCi> { + impl dma::Transmit for SpiTxDma<$SPIX, PINS, $TX_CH> { + type TxChannel = $TX_CH; + type ReceivedWord = u8; + } + + impl dma::ReceiveTransmit for SpiRxTxDma<$SPIX, PINS, $RX_CH, $TX_CH> { + type RxChannel = $RX_CH; + type TxChannel = $TX_CH; + type TransferedWord = u8; + } + + impl Spi<$SPIX, PINS> { + pub fn with_rx_dma(self, channel: $RX_CH) -> SpiRxDma<$SPIX, PINS, $RX_CH> { let payload = SpiPayload { spi: self }; SpiRxDma { payload, channel } } + + pub fn with_tx_dma(self, channel: $TX_CH) -> SpiTxDma<$SPIX, PINS, $TX_CH> { + let payload = SpiPayload { spi: self }; + SpiTxDma { payload, channel } + } + + pub fn with_rxtx_dma( + self, + rx_channel: $RX_CH, + tx_channel: $TX_CH, + ) -> SpiRxTxDma<$SPIX, PINS, $RX_CH, $TX_CH> { + let payload = SpiPayload { spi: self }; + SpiRxTxDma { + payload, + rx_channel, + tx_channel, + } + } } - impl SpiRxDma<$SPIi, PINS, $TCi> { - pub fn split(mut self) -> (Spi<$SPIi, PINS>, $TCi) { + impl SpiRxDma<$SPIX, PINS, $RX_CH> { + pub fn split(mut self) -> (Spi<$SPIX, PINS>, $RX_CH) { self.stop(); (self.payload.spi, self.channel) } } - impl TransferPayload for SpiRxDma<$SPIi, PINS, $TCi> { + impl SpiTxDma<$SPIX, PINS, $TX_CH> { + pub fn split(mut self) -> (Spi<$SPIX, PINS>, $TX_CH) { + self.stop(); + (self.payload.spi, self.channel) + } + } + + impl SpiRxTxDma<$SPIX, PINS, $RX_CH, $TX_CH> { + pub fn split(mut self) -> (Spi<$SPIX, PINS>, $RX_CH, $TX_CH) { + self.stop(); + (self.payload.spi, self.rx_channel, self.tx_channel) + } + } + + impl dma::TransferPayload for SpiRxDma<$SPIX, PINS, $RX_CH> { fn start(&mut self) { self.payload .spi @@ -396,6 +445,7 @@ macro_rules! spi_rx_dma { .modify(|_, w| w.rxdmaen().set_bit()); self.channel.start(); } + fn stop(&mut self) { self.channel.stop(); self.payload @@ -406,22 +456,64 @@ macro_rules! spi_rx_dma { } } - impl crate::dma::ReadDma for SpiRxDma<$SPIi, PINS, $TCi> + impl dma::TransferPayload for SpiTxDma<$SPIX, PINS, $TX_CH> { + fn start(&mut self) { + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.txdmaen().set_bit()); + self.channel.start(); + } + + fn stop(&mut self) { + self.channel.stop(); + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.txdmaen().clear_bit()); + } + } + + impl dma::TransferPayload for SpiRxTxDma<$SPIX, PINS, $RX_CH, $TX_CH> { + fn start(&mut self) { + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.rxdmaen().set_bit().txdmaen().set_bit()); + self.rx_channel.start(); + self.tx_channel.start(); + } + + fn stop(&mut self) { + self.tx_channel.stop(); + self.rx_channel.stop(); + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.rxdmaen().clear_bit().txdmaen().clear_bit()); + } + } + + impl dma::ReadDma for SpiRxDma<$SPIX, PINS, $RX_CH> where B: StaticWriteBuffer, { - fn read(mut self, mut buffer: B) -> Transfer { + fn read(mut self, mut buffer: B) -> dma::Transfer { // NOTE(unsafe) We own the buffer now and we won't call other `&mut` on it // until the end of the transfer. let (ptr, len) = unsafe { buffer.static_write_buffer() }; self.channel.set_peripheral_address( - unsafe { &(*$SPIi::ptr()).dr as *const _ as u32 }, + unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, false, ); self.channel.set_memory_address(ptr as u32, true); self.channel.set_transfer_length(len as u16); - self.channel.cselr().modify(|_, w| w.$chanX().$mapX()); + self.channel.cselr().modify(|_, w| w.$RX_CHX().$RX_MAPX()); atomic::compiler_fence(Ordering::Release); self.channel.ccr().modify(|_, w| { @@ -448,44 +540,166 @@ macro_rules! spi_rx_dma { atomic::compiler_fence(Ordering::Release); self.start(); - Transfer::w(buffer, self) + dma::Transfer::w(buffer, self) + } + } + + impl dma::WriteDma for SpiTxDma<$SPIX, PINS, $TX_CH> + where + B: StaticReadBuffer, + { + fn write(mut self, buffer: B) -> dma::Transfer { + // NOTE(unsafe) We own the buffer now and we won't call other `&mut` on it + // until the end of the transfer. + let (ptr, len) = unsafe { buffer.static_read_buffer() }; + self.channel.set_peripheral_address( + unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, + false, + ); + self.channel.set_memory_address(ptr as u32, true); + self.channel.set_transfer_length(len as u16); + + self.channel.cselr().modify(|_, w| w.$TX_CHX().$TX_MAPX()); + + atomic::compiler_fence(Ordering::Release); + self.channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .medium() + // 8-bit memory size + .msize() + .bits8() + // 8-bit peripheral size + .psize() + .bits8() + // circular mode disabled + .circ() + .clear_bit() + // write to peripheral + .dir() + .set_bit() + }); + atomic::compiler_fence(Ordering::Release); + self.start(); + + dma::Transfer::r(buffer, self) + } + } + + impl dma::TransferDma for SpiRxTxDma<$SPIX, PINS, $RX_CH, $TX_CH> + where + B: StaticWriteBuffer, + { + fn transfer(mut self, mut buffer: B) -> dma::Transfer { + // Transfer: we use the same buffer for RX and TX + + // NOTE(unsafe) We own the buffer now and we won't call other `&mut` on it + // until the end of the transfer. + let (ptr, len) = unsafe { buffer.static_write_buffer() }; + + // + // Setup RX channel + // + self.rx_channel.set_peripheral_address( + unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, + false, + ); + self.rx_channel.set_memory_address(ptr as u32, true); + self.rx_channel.set_transfer_length(len as u16); + + self.rx_channel + .cselr() + .modify(|_, w| w.$RX_CHX().$RX_MAPX()); + + atomic::compiler_fence(Ordering::Release); + self.rx_channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .medium() + // 8-bit memory size + .msize() + .bits8() + // 8-bit peripheral size + .psize() + .bits8() + // circular mode disabled + .circ() + .clear_bit() + // write to memory + .dir() + .clear_bit() + }); + + // + // Setup TX channel + // + self.tx_channel.set_peripheral_address( + unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, + false, + ); + self.tx_channel.set_memory_address(ptr as u32, true); + self.tx_channel.set_transfer_length(len as u16); + + self.tx_channel + .cselr() + .modify(|_, w| w.$TX_CHX().$TX_MAPX()); + + atomic::compiler_fence(Ordering::Release); + self.tx_channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .medium() + // 8-bit memory size + .msize() + .bits8() + // 8-bit peripheral size + .psize() + .bits8() + // circular mode disabled + .circ() + .clear_bit() + // write to peripheral + .dir() + .set_bit() + }); + + // + // Fences and start + // + atomic::compiler_fence(Ordering::Release); + self.start(); + + dma::Transfer::rw(buffer, self) } } }; } +spi_dma!(SPI1, dma1::C2, c2s, map1, dma1::C3, c3s, map1); #[cfg(any( feature = "stm32l4x1", - feature = "stm32l4x2", feature = "stm32l4x3", feature = "stm32l4x5", - feature = "stm32l4x6" -))] -use embedded_dma::StaticWriteBuffer; - -#[cfg(any( - feature = "stm32l4x1", - feature = "stm32l4x2", - feature = "stm32l4x3", - feature = "stm32l4x5", - feature = "stm32l4x6" + feature = "stm32l4x6", ))] -use crate::dma::dma1::C2; +spi_dma!(SPI2, dma1::C4, c4s, map1, dma1::C5, c5s, map1); +// spi_dma!(SPI1, dma2::C3, c3s, map4, dma2::C4, c4s, map4); #[cfg(any( feature = "stm32l4x1", feature = "stm32l4x2", - feature = "stm32l4x3", feature = "stm32l4x5", - feature = "stm32l4x6" + feature = "stm32l4x6", ))] -spi_rx_dma!(SPI1, C2, c2s, map1); - -#[cfg(any(feature = "stm32l4x3", feature = "stm32l4x5", feature = "stm32l4x6",))] -use crate::dma::dma1::C4; -#[cfg(any(feature = "stm32l4x3", feature = "stm32l4x5", feature = "stm32l4x6",))] -spi_rx_dma!(SPI2, C4, c4s, map1); - -#[cfg(any(feature = "stm32l4x5", feature = "stm32l4x6",))] -use crate::dma::dma2::C1; -#[cfg(any(feature = "stm32l4x5", feature = "stm32l4x6",))] -spi_rx_dma!(SPI3, C1, c1s, map3); +spi_dma!(SPI3, dma2::C1, c1s, map3, dma2::C2, c2s, map3);