diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index b43cbf688..cd1e634f1 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Rng` and `Trng` now implement `Peripheral

` (#2992) - SPI, UART, I2C: `with_` functions of peripheral drivers now disconnect the previously assigned pins from the peripheral. (#3012) - SPI, UART, I2C: Dropping a driver now disconnects pins from their peripherals. (#3012) +- Migrate PARL_IO driver to DMA move API (#3033) - `Async` drivers are no longer `Send` (#2980) - GPIO drivers now take configuration structs (#2990, #3029) - `flip-link` feature is now a config option (`ESP_HAL_CONFIG_FLIP_LINK`) (#3001) diff --git a/esp-hal/MIGRATING-0.23.md b/esp-hal/MIGRATING-0.23.md index b1a9ea1ae..c0e94c58f 100644 --- a/esp-hal/MIGRATING-0.23.md +++ b/esp-hal/MIGRATING-0.23.md @@ -174,6 +174,35 @@ config/config.toml + ESP_HAL_CONFIG_PSRAM_MODE = "octal" ``` +## PARL_IO changes +Parallel IO now uses the newer DMA Move API. + +Changes on the TX side +```diff + let (_, _, tx_buffer, tx_descriptors) = dma_buffers!(0, 32000); ++ let mut dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap(); + +- let transfer = parl_io_tx.write_dma(&tx_buffer).unwrap(); +- transfer.wait().unwrap(); ++ let transfer = parl_io_tx.write(dma_tx_buf.len(), dma_tx_buf).unwrap(); ++ (result, parl_io_tx, dma_tx_buf) = transfer.wait(); ++ result.unwrap(); +``` + +Changes on the RX side +```diff + let (rx_buffer, rx_descriptors, _, _) = dma_buffers!(32000, 0); ++ let mut dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap(); +- let transfer = parl_io_rx.read_dma(&mut rx_buffer).unwrap(); +- transfer.wait().unwrap(); ++ let transfer = parl_io_rx.read(Some(dma_rx_buf.len()), dma_rx_buf).unwrap(); ++ (_, parl_io_rx, dma_rx_buf) = transfer.wait(); +``` + +On the RX side, the `EofMode` is now decided at transfer time, rather than config time. +- `EofMode::ByteLen` -> `Some()` +- `EofMode::EnableSignal` -> `None` + ## GPIO changes GPIO drivers now take configuration structs. diff --git a/esp-hal/src/parl_io.rs b/esp-hal/src/parl_io.rs index 2ce44afd2..bc43033be 100644 --- a/esp-hal/src/parl_io.rs +++ b/esp-hal/src/parl_io.rs @@ -17,11 +17,13 @@ #![doc = crate::before_snippet!()] //! # use esp_hal::delay::Delay; //! # use esp_hal::dma_buffers; +//! # use esp_hal::dma::DmaRxBuf; //! # use esp_hal::gpio::NoPin; //! # use esp_hal::parl_io::{BitPackOrder, ParlIoRxOnly, RxFourBits}; //! //! // Initialize DMA buffer and descriptors for data reception //! let (rx_buffer, rx_descriptors, _, _) = dma_buffers!(32000, 0); +//! let mut dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer)?; //! let dma_channel = peripherals.DMA_CH0; //! //! // Configure the 4-bit input pins and clock pin @@ -38,7 +40,6 @@ //! let parl_io = ParlIoRxOnly::new( //! peripherals.PARL_IO, //! dma_channel, -//! rx_descriptors, //! 1.MHz(), //! )?; //! @@ -52,14 +53,13 @@ //! )?; //! //! // Initialize the buffer and delay -//! let mut buffer = rx_buffer; -//! buffer.fill(0u8); +//! dma_rx_buf.as_mut_slice().fill(0u8); //! let delay = Delay::new(); //! //! loop { //! // Read data via DMA and print received values -//! let transfer = parl_io_rx.read_dma(&mut buffer)?; -//! transfer.wait()?; +//! let transfer = parl_io_rx.read(Some(dma_rx_buf.len()), dma_rx_buf)?; +//! (_, parl_io_rx, dma_rx_buf) = transfer.wait(); //! //! delay.delay_millis(500); //! } @@ -70,11 +70,11 @@ //! ```rust, no_run #![doc = crate::before_snippet!()] //! # use esp_hal::delay::Delay; -//! # use esp_hal::dma_buffers; +//! # use esp_hal::dma_tx_buffer; //! # use esp_hal::parl_io::{BitPackOrder, ParlIoTxOnly, TxFourBits, SampleEdge, ClkOutPin, TxPinConfigWithValidPin}; //! //! // Initialize DMA buffer and descriptors for data reception -//! let (_, _, tx_buffer, tx_descriptors) = dma_buffers!(0, 32000); +//! let mut dma_tx_buf = dma_tx_buffer!(32000).unwrap(); //! let dma_channel = peripherals.DMA_CH0; //! //! // Configure the 4-bit input pins and clock pin @@ -92,7 +92,6 @@ //! let parl_io = ParlIoTxOnly::new( //! peripherals.PARL_IO, //! dma_channel, -//! tx_descriptors, //! 1.MHz(), //! )?; //! @@ -107,20 +106,24 @@ //! BitPackOrder::Msb, //! )?; //! -//! let buffer = tx_buffer; -//! for i in 0..buffer.len() { -//! buffer[i] = (i % 255) as u8; +//! for i in 0..dma_tx_buf.len() { +//! dma_tx_buf.as_mut_slice()[i] = (i % 255) as u8; //! } //! //! let delay = Delay::new(); //! loop { -//! let transfer = parl_io_tx.write_dma(&buffer)?; -//! transfer.wait()?; +//! let transfer = parl_io_tx.write(dma_tx_buf.len(), dma_tx_buf)?; +//! (_, parl_io_tx, dma_tx_buf) = transfer.wait(); //! delay.delay_millis(500); //! } //! # } //! ``` +use core::{ + mem::ManuallyDrop, + ops::{Deref, DerefMut}, +}; + use enumset::{EnumSet, EnumSetType}; use fugit::HertzU32; use peripheral::PeripheralRef; @@ -128,31 +131,27 @@ use private::*; use crate::{ dma::{ - dma_private::{DmaSupport, DmaSupportRx, DmaSupportTx}, Channel, ChannelRx, ChannelTx, - DescriptorChain, DmaChannelFor, - DmaDescriptor, DmaError, DmaPeripheral, - DmaTransferRx, - DmaTransferTx, + DmaRxBuffer, + DmaTxBuffer, PeripheralRxChannel, PeripheralTxChannel, - ReadBuffer, Rx, RxChannelFor, Tx, TxChannelFor, - WriteBuffer, }, gpio::{ interconnect::{InputConnection, OutputConnection, PeripheralInput, PeripheralOutput}, NoPin, }, interrupt::InterruptHandler, + parl_io::asynch::interrupt_handler, peripheral::{self, Peripheral}, peripherals::{Interrupt, PARL_IO, PCR}, system::{self, GenericPeripheralGuard}, @@ -351,16 +350,6 @@ impl EnableMode { } } -/// Generation of GDMA SUC EOF -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum EofMode { - /// Generate GDMA SUC EOF by data byte length - ByteLen, - /// Generate GDMA SUC EOF by the external enable signal - EnableSignal, -} - /// Used to configure no pin as clock output impl TxClkPin for NoPin { fn configure(&mut self) { @@ -605,6 +594,7 @@ impl FullDuplex for TxOneBit<'_> {} impl FullDuplex for TxTwoBits<'_> {} impl FullDuplex for TxFourBits<'_> {} impl FullDuplex for TxEightBits<'_> {} +impl FullDuplex for TxPinConfigWithValidPin<'_, TxFourBits<'_>> {} impl NotContainsValidSignalPin for TxOneBit<'_> {} impl NotContainsValidSignalPin for TxTwoBits<'_> {} @@ -627,7 +617,6 @@ where rx_pins: P, valid_pin: PeripheralRef<'d, InputConnection>, enable_mode: EnableMode, - eof_mode: EofMode, } impl<'d, P> RxPinConfigWithValidPin<'d, P> @@ -639,14 +628,12 @@ where rx_pins: P, valid_pin: impl Peripheral

+ 'd, enable_mode: EnableMode, - eof_mode: EofMode, ) -> Self { crate::into_mapped_ref!(valid_pin); Self { rx_pins, valid_pin, enable_mode, - eof_mode, } } } @@ -674,7 +661,6 @@ where if let Some(sel) = self.enable_mode.smp_model_sel() { Instance::set_rx_sample_mode(sel); } - Instance::set_eof_gen_sel(self.eof_mode); Ok(()) } @@ -687,7 +673,6 @@ where { rx_pins: P, enable_mode: EnableMode, - eof_mode: EofMode, } impl

RxPinConfigIncludingValidPin

@@ -695,11 +680,10 @@ where P: ContainsValidSignalPin + RxPins + ConfigurePins, { /// Create a new [RxPinConfigIncludingValidPin] - pub fn new(rx_pins: P, enable_mode: EnableMode, eof_mode: EofMode) -> Self { + pub fn new(rx_pins: P, enable_mode: EnableMode) -> Self { Self { rx_pins, enable_mode, - eof_mode, } } } @@ -725,7 +709,6 @@ where if let Some(sel) = self.enable_mode.smp_model_sel() { Instance::set_rx_sample_mode(sel); } - Instance::set_eof_gen_sel(self.eof_mode); Ok(()) } @@ -823,6 +806,7 @@ impl FullDuplex for RxOneBit<'_> {} impl FullDuplex for RxTwoBits<'_> {} impl FullDuplex for RxFourBits<'_> {} impl FullDuplex for RxEightBits<'_> {} +impl FullDuplex for RxPinConfigWithValidPin<'_, RxFourBits<'_>> {} impl NotContainsValidSignalPin for RxOneBit<'_> {} impl NotContainsValidSignalPin for RxTwoBits<'_> {} @@ -863,7 +847,6 @@ where Ok(ParlIoTx { tx_channel: self.tx_channel, - tx_chain: DescriptorChain::new(self.descriptors), _guard: self._guard, }) } @@ -895,7 +878,6 @@ where Ok(ParlIoTx { tx_channel: self.tx_channel, - tx_chain: DescriptorChain::new(self.descriptors), _guard: self._guard, }) } @@ -908,7 +890,6 @@ where Dm: DriverMode, { tx_channel: ChannelTx<'d, Dm, PeripheralTxChannel>, - tx_chain: DescriptorChain, _guard: GenericPeripheralGuard<{ crate::system::Peripheral::ParlIo as u8 }>, } @@ -947,7 +928,6 @@ where Ok(ParlIoRx { rx_channel: self.rx_channel, - rx_chain: DescriptorChain::new(self.descriptors), _guard: guard, }) } @@ -977,7 +957,6 @@ where Ok(ParlIoRx { rx_channel: self.rx_channel, - rx_chain: DescriptorChain::new(self.descriptors), _guard: self._guard, }) } @@ -990,7 +969,6 @@ where Dm: DriverMode, { rx_channel: ChannelRx<'d, Dm, PeripheralRxChannel>, - rx_chain: DescriptorChain, _guard: GenericPeripheralGuard<{ crate::system::Peripheral::ParlIo as u8 }>, } @@ -1105,8 +1083,6 @@ impl<'d> ParlIoFullDuplex<'d, Blocking> { pub fn new( _parl_io: impl Peripheral

+ 'd, dma_channel: impl Peripheral

+ 'd, - tx_descriptors: &'static mut [DmaDescriptor], - rx_descriptors: &'static mut [DmaDescriptor], frequency: HertzU32, ) -> Result where @@ -1120,12 +1096,10 @@ impl<'d> ParlIoFullDuplex<'d, Blocking> { Ok(Self { tx: TxCreatorFullDuplex { tx_channel: dma_channel.tx, - descriptors: tx_descriptors, _guard: tx_guard, }, rx: RxCreatorFullDuplex { rx_channel: dma_channel.rx, - descriptors: rx_descriptors, _guard: rx_guard, }, }) @@ -1144,15 +1118,38 @@ impl<'d> ParlIoFullDuplex<'d, Blocking> { crate::interrupt::disable(core, Interrupt::PARL_IO_TX); } } + + #[cfg(esp32c6)] + { + unsafe { + crate::interrupt::bind_interrupt(Interrupt::PARL_IO, interrupt_handler.handler()); + } + unwrap!(crate::interrupt::enable( + Interrupt::PARL_IO, + interrupt_handler.priority() + )); + } + #[cfg(esp32h2)] + { + unsafe { + crate::interrupt::bind_interrupt( + Interrupt::PARL_IO_TX, + interrupt_handler.handler(), + ); + } + unwrap!(crate::interrupt::enable( + Interrupt::PARL_IO_TX, + interrupt_handler.priority() + )); + } + ParlIoFullDuplex { tx: TxCreatorFullDuplex { tx_channel: self.tx.tx_channel.into_async(), - descriptors: self.tx.descriptors, _guard: self.tx._guard, }, rx: RxCreatorFullDuplex { rx_channel: self.rx.rx_channel.into_async(), - descriptors: self.rx.descriptors, _guard: self.rx._guard, }, } @@ -1203,12 +1200,10 @@ impl<'d> ParlIoFullDuplex<'d, Async> { ParlIoFullDuplex { tx: TxCreatorFullDuplex { tx_channel: self.tx.tx_channel.into_blocking(), - descriptors: self.tx.descriptors, _guard: self.tx._guard, }, rx: RxCreatorFullDuplex { rx_channel: self.rx.rx_channel.into_blocking(), - descriptors: self.rx.descriptors, _guard: self.rx._guard, }, } @@ -1230,7 +1225,6 @@ impl<'d> ParlIoTxOnly<'d, Blocking> { pub fn new( _parl_io: impl Peripheral

+ 'd, dma_channel: impl Peripheral

+ 'd, - descriptors: &'static mut [DmaDescriptor], frequency: HertzU32, ) -> Result where @@ -1243,7 +1237,6 @@ impl<'d> ParlIoTxOnly<'d, Blocking> { Ok(Self { tx: TxCreator { tx_channel, - descriptors, _guard: guard, }, }) @@ -1262,10 +1255,33 @@ impl<'d> ParlIoTxOnly<'d, Blocking> { crate::interrupt::disable(core, Interrupt::PARL_IO_TX); } } + #[cfg(esp32c6)] + { + unsafe { + crate::interrupt::bind_interrupt(Interrupt::PARL_IO, interrupt_handler.handler()); + } + unwrap!(crate::interrupt::enable( + Interrupt::PARL_IO, + interrupt_handler.priority() + )); + } + #[cfg(esp32h2)] + { + unsafe { + crate::interrupt::bind_interrupt( + Interrupt::PARL_IO_TX, + interrupt_handler.handler(), + ); + } + unwrap!(crate::interrupt::enable( + Interrupt::PARL_IO_TX, + interrupt_handler.priority() + )); + } + ParlIoTxOnly { tx: TxCreator { tx_channel: self.tx.tx_channel.into_async(), - descriptors: self.tx.descriptors, _guard: self.tx._guard, }, } @@ -1307,7 +1323,6 @@ impl<'d> ParlIoTxOnly<'d, Async> { ParlIoTxOnly { tx: TxCreator { tx_channel: self.tx.tx_channel.into_blocking(), - descriptors: self.tx.descriptors, _guard: self.tx._guard, }, } @@ -1338,7 +1353,6 @@ impl<'d> ParlIoRxOnly<'d, Blocking> { pub fn new( _parl_io: impl Peripheral

+ 'd, dma_channel: impl Peripheral

+ 'd, - descriptors: &'static mut [DmaDescriptor], frequency: HertzU32, ) -> Result where @@ -1351,7 +1365,6 @@ impl<'d> ParlIoRxOnly<'d, Blocking> { Ok(Self { rx: RxCreator { rx_channel, - descriptors, _guard: guard, }, }) @@ -1370,11 +1383,33 @@ impl<'d> ParlIoRxOnly<'d, Blocking> { crate::interrupt::disable(core, Interrupt::PARL_IO_TX); } } + #[cfg(esp32c6)] + { + unsafe { + crate::interrupt::bind_interrupt(Interrupt::PARL_IO, interrupt_handler.handler()); + } + unwrap!(crate::interrupt::enable( + Interrupt::PARL_IO, + interrupt_handler.priority() + )); + } + #[cfg(esp32h2)] + { + unsafe { + crate::interrupt::bind_interrupt( + Interrupt::PARL_IO_TX, + interrupt_handler.handler(), + ); + } + unwrap!(crate::interrupt::enable( + Interrupt::PARL_IO_TX, + interrupt_handler.priority() + )); + } ParlIoRxOnly { rx: RxCreator { rx_channel: self.rx.rx_channel.into_async(), - descriptors: self.rx.descriptors, _guard: self.rx._guard, }, } @@ -1416,7 +1451,6 @@ impl<'d> ParlIoRxOnly<'d, Async> { ParlIoRxOnly { rx: RxCreator { rx_channel: self.rx.rx_channel.into_blocking(), - descriptors: self.rx.descriptors, _guard: self.rx._guard, }, } @@ -1460,48 +1494,41 @@ fn internal_init(frequency: HertzU32) -> Result<(), Error> { Ok(()) } -impl ParlIoTx<'_, Dm> +impl<'d, Dm> ParlIoTx<'d, Dm> where Dm: DriverMode, { /// Perform a DMA write. /// - /// This will return a [DmaTransferTx] + /// This will return a [ParlIoTxTransfer] /// /// The maximum amount of data to be sent is 32736 bytes. - pub fn write_dma<'t, TXBUF>( - &'t mut self, - words: &'t TXBUF, - ) -> Result, Error> + pub fn write( + mut self, + number_of_bytes: usize, + mut buffer: BUF, + ) -> Result, (Error, Self, BUF)> where - TXBUF: ReadBuffer, + BUF: DmaTxBuffer, { - let (ptr, len) = unsafe { words.read_buffer() }; - - if len > MAX_DMA_SIZE { - return Err(Error::MaxDmaTransferSizeExceeded); + if number_of_bytes > MAX_DMA_SIZE { + return Err((Error::MaxDmaTransferSizeExceeded, self, buffer)); } - self.start_write_bytes_dma(ptr, len)?; - - Ok(DmaTransferTx::new(self)) - } - - fn start_write_bytes_dma(&mut self, ptr: *const u8, len: usize) -> Result<(), Error> { PCR::regs() .parl_clk_tx_conf() .modify(|_, w| w.parl_tx_rst_en().set_bit()); Instance::clear_tx_interrupts(); - Instance::set_tx_bytes(len as u16); + Instance::set_tx_bytes(number_of_bytes as u16); - self.tx_channel.is_done(); - - unsafe { - self.tx_chain.fill_for_tx(false, ptr, len)?; + let result = unsafe { self.tx_channel - .prepare_transfer_without_start(DmaPeripheral::ParlIo, &self.tx_chain) - .and_then(|_| self.tx_channel.start_transfer())?; + .prepare_transfer(DmaPeripheral::ParlIo, &mut buffer) + .and_then(|_| self.tx_channel.start_transfer()) + }; + if let Err(err) = result { + return Err((Error::DmaError(err), self, buffer)); } while !Instance::is_tx_ready() {} @@ -1512,37 +1539,86 @@ where .parl_clk_tx_conf() .modify(|_, w| w.parl_tx_rst_en().clear_bit()); - Ok(()) + Ok(ParlIoTxTransfer { + parl_io: ManuallyDrop::new(self), + buf_view: ManuallyDrop::new(buffer.into_view()), + }) } } -impl DmaSupport for ParlIoTx<'_, Dm> -where - Dm: DriverMode, -{ - fn peripheral_wait_dma(&mut self, _is_rx: bool, _is_tx: bool) { - while !Instance::is_tx_eof() {} +/// Represents an ongoing (or potentially finished) transfer using the PARL_IO +/// TX. +pub struct ParlIoTxTransfer<'d, BUF: DmaTxBuffer, Dm: DriverMode> { + parl_io: ManuallyDrop>, + buf_view: ManuallyDrop, +} + +impl<'d, BUF: DmaTxBuffer, Dm: DriverMode> ParlIoTxTransfer<'d, BUF, Dm> { + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + Instance::is_tx_eof() + } + + /// Waits for the transfer to finish and returns the peripheral and buffer. + pub fn wait(mut self) -> (Result<(), DmaError>, ParlIoTx<'d, Dm>, BUF) { + while !self.is_done() {} Instance::set_tx_start(false); + Instance::clear_is_tx_done(); + + // Stop the DMA as it doesn't know that the parl io has stopped. + self.parl_io.tx_channel.stop_transfer(); + + let (parl_io, view) = self.release(); + + let result = if parl_io.tx_channel.has_error() { + Err(DmaError::DescriptorError) + } else { + Ok(()) + }; + + (result, parl_io, BUF::from_view(view)) } - fn peripheral_dma_stop(&mut self) { - unreachable!("unsupported") + fn release(mut self) -> (ParlIoTx<'d, Dm>, BUF::View) { + let (parl_io, view) = unsafe { + ( + ManuallyDrop::take(&mut self.parl_io), + ManuallyDrop::take(&mut self.buf_view), + ) + }; + core::mem::forget(self); + (parl_io, view) } } -impl<'d, Dm> DmaSupportTx for ParlIoTx<'d, Dm> -where - Dm: DriverMode, -{ - type TX = ChannelTx<'d, Dm, PeripheralTxChannel>; +impl Deref for ParlIoTxTransfer<'_, BUF, Dm> { + type Target = BUF::View; - fn tx(&mut self) -> &mut Self::TX { - &mut self.tx_channel + fn deref(&self) -> &Self::Target { + &self.buf_view } +} - fn chain(&mut self) -> &mut DescriptorChain { - &mut self.tx_chain +impl DerefMut for ParlIoTxTransfer<'_, BUF, Dm> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buf_view + } +} + +impl Drop for ParlIoTxTransfer<'_, BUF, Dm> { + fn drop(&mut self) { + // There's no documented way to cancel the PARL IO transfer, so we'll just stop + // the DMA to stop the memory access. + self.parl_io.tx_channel.stop_transfer(); + + // SAFETY: This is Drop, we know that self.parl_io and self.buf_view + // won't be touched again. + let view = unsafe { + ManuallyDrop::drop(&mut self.parl_io); + ManuallyDrop::take(&mut self.buf_view) + }; + let _ = BUF::from_view(view); } } @@ -1552,36 +1628,23 @@ where { /// Perform a DMA read. /// - /// This will return a [DmaTransferRx] + /// This will return a [ParlIoRxTransfer] /// - /// The maximum amount of data is 32736 bytes when using [EofMode::ByteLen]. + /// When the number of bytes is specified, the maximum amount of data is + /// 32736 bytes and the transfer ends when the number of specified bytes + /// is received. /// - /// It's only limited by the size of the DMA buffer when using - /// [EofMode::EnableSignal]. - pub fn read_dma<'t, RXBUF>( - &'t mut self, - words: &'t mut RXBUF, - ) -> Result, Error> + /// When the number of bytes is unspecified, there's no limit the amount of + /// data transferred and the transfer ends when the enable signal + /// signals the end or the DMA buffer runs out of space. + pub fn read( + mut self, + number_of_bytes: Option, + mut buffer: BUF, + ) -> Result, (Error, Self, BUF)> where - RXBUF: WriteBuffer, + BUF: DmaRxBuffer, { - let (ptr, len) = unsafe { words.write_buffer() }; - - if !Instance::is_suc_eof_generated_externally() && len > MAX_DMA_SIZE { - return Err(Error::MaxDmaTransferSizeExceeded); - } - - Self::start_receive_bytes_dma(&mut self.rx_channel, &mut self.rx_chain, ptr, len)?; - - Ok(DmaTransferRx::new(self)) - } - - fn start_receive_bytes_dma( - rx_channel: &mut ChannelRx<'d, Dm, PeripheralRxChannel>, - rx_chain: &mut DescriptorChain, - ptr: *mut u8, - len: usize, - ) -> Result<(), Error> { PCR::regs() .parl_clk_rx_conf() .modify(|_, w| w.parl_rx_rst_en().set_bit()); @@ -1590,56 +1653,116 @@ where .modify(|_, w| w.parl_rx_rst_en().clear_bit()); Instance::clear_rx_interrupts(); - Instance::set_rx_bytes(len as u16); + if let Some(number_of_bytes) = number_of_bytes { + if number_of_bytes > MAX_DMA_SIZE { + return Err((Error::MaxDmaTransferSizeExceeded, self, buffer)); + } + Instance::set_rx_bytes(number_of_bytes as u16); + Instance::set_eof_gen_sel(EofMode::ByteLen); + } else { + Instance::set_eof_gen_sel(EofMode::EnableSignal); + } - unsafe { - rx_chain.fill_for_rx(false, ptr, len)?; - rx_channel - .prepare_transfer_without_start(DmaPeripheral::ParlIo, rx_chain) - .and_then(|_| rx_channel.start_transfer())?; + let result = unsafe { + self.rx_channel + .prepare_transfer(DmaPeripheral::ParlIo, &mut buffer) + .and_then(|_| self.rx_channel.start_transfer()) + }; + if let Err(err) = result { + return Err((Error::DmaError(err), self, buffer)); } Instance::set_rx_reg_update(); Instance::set_rx_start(true); - Ok(()) + + Ok(ParlIoRxTransfer { + parl_io: ManuallyDrop::new(self), + buf_view: ManuallyDrop::new(buffer.into_view()), + dma_result: None, + }) } } -impl DmaSupport for ParlIoRx<'_, Dm> -where - Dm: DriverMode, -{ - fn peripheral_wait_dma(&mut self, _is_rx: bool, _is_tx: bool) { - loop { - if self.rx_channel.is_done() - || self.rx_channel.has_eof_error() - || self.rx_channel.has_dscr_empty_error() - { - break; - } +/// Represents an ongoing (or potentially finished) transfer using the PARL_IO +/// TX. +pub struct ParlIoRxTransfer<'d, BUF: DmaRxBuffer, Dm: DriverMode> { + parl_io: ManuallyDrop>, + buf_view: ManuallyDrop, + // Needed to use DmaRxFuture, which clear the bits we check in is_done() + dma_result: Option>, +} + +impl<'d, BUF: DmaRxBuffer, Dm: DriverMode> ParlIoRxTransfer<'d, BUF, Dm> { + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + if self.dma_result.is_some() { + return true; } + let ch = &self.parl_io.rx_channel; + ch.is_done() || ch.has_eof_error() || ch.has_dscr_empty_error() + } + + /// Waits for the transfer to finish and returns the peripheral and buffer. + pub fn wait(mut self) -> (Result<(), DmaError>, ParlIoRx<'d, Dm>, BUF) { + while !self.is_done() {} Instance::set_rx_start(false); + + // Stop the DMA as it doesn't know that the parl io has stopped. + self.parl_io.rx_channel.stop_transfer(); + + let dma_result = self.dma_result.take(); + let (parl_io, view) = self.release(); + + let result = if parl_io.rx_channel.has_error() { + Err(DmaError::DescriptorError) + } else { + dma_result.unwrap_or(Ok(())) + }; + + (result, parl_io, BUF::from_view(view)) } - fn peripheral_dma_stop(&mut self) { - unreachable!("unsupported") + fn release(mut self) -> (ParlIoRx<'d, Dm>, BUF::View) { + let (parl_io, view) = unsafe { + ( + ManuallyDrop::take(&mut self.parl_io), + ManuallyDrop::take(&mut self.buf_view), + ) + }; + core::mem::forget(self); + (parl_io, view) } } -impl<'d, Dm> DmaSupportRx for ParlIoRx<'d, Dm> -where - Dm: DriverMode, -{ - type RX = ChannelRx<'d, Dm, PeripheralRxChannel>; +impl Deref for ParlIoRxTransfer<'_, BUF, Dm> { + type Target = BUF::View; - fn rx(&mut self) -> &mut Self::RX { - &mut self.rx_channel + fn deref(&self) -> &Self::Target { + &self.buf_view } +} - fn chain(&mut self) -> &mut DescriptorChain { - &mut self.rx_chain +impl DerefMut for ParlIoRxTransfer<'_, BUF, Dm> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buf_view + } +} + +impl Drop for ParlIoRxTransfer<'_, BUF, Dm> { + fn drop(&mut self) { + // There's no documented way to cancel the PARL IO transfer, so we'll just stop + // the DMA to stop the memory access. + self.parl_io.rx_channel.stop_transfer(); + + // SAFETY: This is Drop, we know that self.parl_io and self.buf_view + // won't be touched again. + let view = unsafe { + ManuallyDrop::drop(&mut self.parl_io); + ManuallyDrop::take(&mut self.buf_view) + }; + let _ = BUF::from_view(view); } } @@ -1649,7 +1772,6 @@ where Dm: DriverMode, { tx_channel: ChannelTx<'d, Dm, PeripheralTxChannel>, - descriptors: &'static mut [DmaDescriptor], _guard: GenericPeripheralGuard<{ system::Peripheral::ParlIo as u8 }>, } @@ -1659,7 +1781,6 @@ where Dm: DriverMode, { rx_channel: ChannelRx<'d, Dm, PeripheralRxChannel>, - descriptors: &'static mut [DmaDescriptor], _guard: GenericPeripheralGuard<{ system::Peripheral::ParlIo as u8 }>, } @@ -1669,7 +1790,6 @@ where Dm: DriverMode, { tx_channel: ChannelTx<'d, Dm, PeripheralTxChannel>, - descriptors: &'static mut [DmaDescriptor], _guard: GenericPeripheralGuard<{ system::Peripheral::ParlIo as u8 }>, } @@ -1679,7 +1799,6 @@ where Dm: DriverMode, { rx_channel: ChannelRx<'d, Dm, PeripheralRxChannel>, - descriptors: &'static mut [DmaDescriptor], _guard: GenericPeripheralGuard<{ system::Peripheral::ParlIo as u8 }>, } @@ -1689,11 +1808,10 @@ pub mod asynch { use procmacros::handler; - use super::{private::Instance, Error, ParlIoRx, ParlIoTx, MAX_DMA_SIZE}; + use super::{private::Instance, ParlIoRxTransfer, ParlIoTxTransfer}; use crate::{ asynch::AtomicWaker, - dma::{asynch::DmaRxFuture, ReadBuffer, WriteBuffer}, - peripherals::{Interrupt, PARL_IO}, + dma::{asynch::DmaRxFuture, DmaRxBuffer, DmaTxBuffer}, }; static TX_WAKER: AtomicWaker = AtomicWaker::new(); @@ -1703,26 +1821,6 @@ pub mod asynch { impl TxDoneFuture { pub fn new() -> Self { - Instance::listen_tx_done(); - let mut parl_io = unsafe { PARL_IO::steal() }; - - #[cfg(esp32c6)] - { - parl_io.bind_parl_io_interrupt(interrupt_handler.handler()); - unwrap!(crate::interrupt::enable( - Interrupt::PARL_IO, - interrupt_handler.priority() - )); - } - #[cfg(esp32h2)] - { - parl_io.bind_parl_io_tx_interrupt(interrupt_handler.handler()); - unwrap!(crate::interrupt::enable( - Interrupt::PARL_IO_TX, - interrupt_handler.priority() - )); - } - Self {} } } @@ -1734,74 +1832,52 @@ pub mod asynch { self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, ) -> Poll { - TX_WAKER.register(cx.waker()); - if Instance::is_listening_tx_done() { - Poll::Pending - } else { + if Instance::is_tx_done_set() { Poll::Ready(()) + } else { + TX_WAKER.register(cx.waker()); + Instance::listen_tx_done(); + Poll::Pending } } } + impl Drop for TxDoneFuture { + fn drop(&mut self) { + Instance::unlisten_tx_done(); + } + } + #[handler] - fn interrupt_handler() { + pub(super) fn interrupt_handler() { if Instance::is_tx_done_set() { - Instance::clear_is_tx_done(); Instance::unlisten_tx_done(); TX_WAKER.wake() } } - impl ParlIoTx<'_, crate::Async> { - /// Perform a DMA write. - /// - /// The maximum amount of data to be sent is 32736 bytes. - pub async fn write_dma_async(&mut self, words: &TXBUF) -> Result<(), Error> - where - TXBUF: ReadBuffer, - { - let (ptr, len) = unsafe { words.read_buffer() }; - - if len > MAX_DMA_SIZE { - return Err(Error::MaxDmaTransferSizeExceeded); - } - + impl ParlIoTxTransfer<'_, BUF, crate::Async> { + /// Waits for [Self::is_done] to return true. + pub async fn wait_for_done(&mut self) { let future = TxDoneFuture::new(); - self.start_write_bytes_dma(ptr, len)?; future.await; - - Ok(()) } } - impl ParlIoRx<'_, crate::Async> { - /// Perform a DMA write. - /// - /// The maximum amount of data to be sent is 32736 bytes. - pub async fn read_dma_async<'t, RXBUF>( - &'t mut self, - words: &'t mut RXBUF, - ) -> Result<(), Error> - where - RXBUF: WriteBuffer, - { - let (ptr, len) = unsafe { words.write_buffer() }; - - if !Instance::is_suc_eof_generated_externally() && len > MAX_DMA_SIZE { - return Err(Error::MaxDmaTransferSizeExceeded); + impl ParlIoRxTransfer<'_, BUF, crate::Async> { + /// Waits for [Self::is_done] to return true. + pub async fn wait_for_done(&mut self) { + if self.dma_result.is_some() { + return; } - - let future = DmaRxFuture::new(&mut self.rx_channel); - Self::start_receive_bytes_dma(future.rx, &mut self.rx_chain, ptr, len)?; - future.await?; - - Ok(()) + let future = DmaRxFuture::new(&mut self.parl_io.rx_channel); + self.dma_result = Some(future.await); } } } mod private { - use super::{BitPackOrder, EofMode, Error, SampleEdge}; + use super::{BitPackOrder, Error, SampleEdge}; use crate::peripherals::PARL_IO; pub trait FullDuplex {} @@ -1849,6 +1925,14 @@ mod private { InternalSoftwareEnable = 2, } + /// Generation of GDMA SUC EOF + pub(super) enum EofMode { + /// Generate GDMA SUC EOF by data byte length + ByteLen, + /// Generate GDMA SUC EOF by the external enable signal + EnableSignal, + } + pub(super) struct Instance; #[cfg(esp32c6)] @@ -1976,9 +2060,10 @@ mod private { pub fn set_eof_gen_sel(mode: EofMode) { let reg_block = PARL_IO::regs(); - reg_block - .rx_cfg0() - .modify(|_, w| w.rx_eof_gen_sel().bit(mode == EofMode::EnableSignal)); + reg_block.rx_cfg0().modify(|_, w| { + w.rx_eof_gen_sel() + .bit(matches!(mode, EofMode::EnableSignal)) + }); } pub fn set_rx_pulse_submode_sel(sel: u8) { @@ -2036,12 +2121,6 @@ mod private { }); } - pub fn is_suc_eof_generated_externally() -> bool { - let reg_block = PARL_IO::regs(); - - reg_block.rx_cfg0().read().rx_eof_gen_sel().bit_is_set() - } - pub fn listen_tx_done() { let reg_block = PARL_IO::regs(); @@ -2054,12 +2133,6 @@ mod private { reg_block.int_ena().modify(|_, w| w.tx_eof().clear_bit()); } - pub fn is_listening_tx_done() -> bool { - let reg_block = PARL_IO::regs(); - - reg_block.int_ena().read().tx_eof().bit() - } - pub fn is_tx_done_set() -> bool { let reg_block = PARL_IO::regs(); @@ -2205,9 +2278,10 @@ mod private { pub fn set_eof_gen_sel(mode: EofMode) { let reg_block = PARL_IO::regs(); - reg_block - .rx_genrl_cfg() - .modify(|_, w| w.rx_eof_gen_sel().bit(mode == EofMode::EnableSignal)); + reg_block.rx_genrl_cfg().modify(|_, w| { + w.rx_eof_gen_sel() + .bit(matches!(mode, EofMode::EnableSignal)) + }); } pub fn set_rx_pulse_submode_sel(sel: u8) { @@ -2266,16 +2340,6 @@ mod private { }); } - pub fn is_suc_eof_generated_externally() -> bool { - let reg_block = PARL_IO::regs(); - - reg_block - .rx_genrl_cfg() - .read() - .rx_eof_gen_sel() - .bit_is_set() - } - pub fn listen_tx_done() { let reg_block = PARL_IO::regs(); @@ -2288,12 +2352,6 @@ mod private { reg_block.int_ena().modify(|_, w| w.tx_eof().clear_bit()); } - pub fn is_listening_tx_done() -> bool { - let reg_block = PARL_IO::regs(); - - reg_block.int_ena().read().tx_eof().bit() - } - pub fn is_tx_done_set() -> bool { let reg_block = PARL_IO::regs(); diff --git a/hil-test/Cargo.toml b/hil-test/Cargo.toml index 968b2ef8c..28e39980f 100644 --- a/hil-test/Cargo.toml +++ b/hil-test/Cargo.toml @@ -115,6 +115,10 @@ harness = false name = "spi_slave" harness = false +[[test]] +name = "parl_io" +harness = false + [[test]] name = "parl_io_tx" harness = false diff --git a/hil-test/tests/parl_io.rs b/hil-test/tests/parl_io.rs new file mode 100644 index 000000000..2d677f1a4 --- /dev/null +++ b/hil-test/tests/parl_io.rs @@ -0,0 +1,121 @@ +//! PARL_IO test + +//% CHIPS: esp32c6 esp32h2 +//% FEATURES: unstable + +#![no_std] +#![no_main] + +use esp_hal::{ + dma::{DmaChannel0, DmaRxBuf, DmaTxBuf}, + dma_buffers, + gpio::{AnyPin, Pin}, + parl_io::{ + BitPackOrder, + ClkOutPin, + EnableMode, + ParlIoFullDuplex, + RxClkInPin, + RxFourBits, + RxPinConfigWithValidPin, + SampleEdge, + TxFourBits, + TxPinConfigWithValidPin, + }, + peripherals::PARL_IO, + time::RateExtU32, +}; +use hil_test as _; + +struct Context { + parl_io: PARL_IO, + dma_channel: DmaChannel0, + clock_pin: AnyPin, + valid_pin: AnyPin, + data_pins: [AnyPin; 4], +} + +#[cfg(test)] +#[embedded_test::tests(default_timeout = 3)] +mod tests { + use super::*; + + #[init] + fn init() -> Context { + let peripherals = esp_hal::init(esp_hal::Config::default()); + + let dma_channel = peripherals.DMA_CH0; + + let parl_io = peripherals.PARL_IO; + + Context { + parl_io, + dma_channel, + clock_pin: peripherals.GPIO11.degrade(), + valid_pin: peripherals.GPIO10.degrade(), + data_pins: [ + peripherals.GPIO1.degrade(), + peripherals.GPIO0.degrade(), + peripherals.GPIO14.degrade(), + peripherals.GPIO23.degrade(), + ], + } + } + + #[test] + fn test_parl_io_rx_can_read_tx(ctx: Context) { + const BUFFER_SIZE: usize = 64; + + let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(BUFFER_SIZE); + let mut dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap(); + let mut dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap(); + + let (clock_rx, clock_tx) = ctx.clock_pin.split(); + let (valid_rx, valid_tx) = ctx.valid_pin.split(); + let [(d0_rx, d0_tx), (d1_rx, d1_tx), (d2_rx, d2_tx), (d3_rx, d3_tx)] = + ctx.data_pins.map(|pin| pin.split()); + + let tx_pins = TxFourBits::new(d0_tx, d1_tx, d2_tx, d3_tx); + let rx_pins = RxFourBits::new(d0_rx, d1_rx, d2_rx, d3_rx); + + let tx_pins = TxPinConfigWithValidPin::new(tx_pins, valid_tx); + let mut rx_pins = RxPinConfigWithValidPin::new(rx_pins, valid_rx, EnableMode::HighLevel); + + let clock_out_pin = ClkOutPin::new(clock_tx); + let mut clock_in_pin = RxClkInPin::new(clock_rx, SampleEdge::Normal); + + let pio = ParlIoFullDuplex::new(ctx.parl_io, ctx.dma_channel, 40.MHz()).unwrap(); + + let pio_tx = pio + .tx + .with_config( + tx_pins, + clock_out_pin, + 0, + SampleEdge::Invert, + BitPackOrder::Lsb, + ) + .unwrap(); + let pio_rx = pio + .rx + .with_config(&mut rx_pins, &mut clock_in_pin, BitPackOrder::Lsb, None) + .unwrap(); + + for (i, b) in dma_tx_buf.as_mut_slice().iter_mut().enumerate() { + *b = i as u8; + } + + let rx_transfer = pio_rx + .read(Some(dma_rx_buf.len()), dma_rx_buf) + .map_err(|e| e.0) + .unwrap(); + let tx_transfer = pio_tx + .write(dma_tx_buf.len(), dma_tx_buf) + .map_err(|e| e.0) + .unwrap(); + (_, _, dma_tx_buf) = tx_transfer.wait(); + (_, _, dma_rx_buf) = rx_transfer.wait(); + + assert_eq!(dma_rx_buf.as_slice(), dma_tx_buf.as_slice()); + } +} diff --git a/hil-test/tests/parl_io_tx.rs b/hil-test/tests/parl_io_tx.rs index e361f5dce..7a86b9eea 100644 --- a/hil-test/tests/parl_io_tx.rs +++ b/hil-test/tests/parl_io_tx.rs @@ -9,7 +9,8 @@ #[cfg(esp32c6)] use esp_hal::parl_io::{TxPinConfigWithValidPin, TxSixteenBits}; use esp_hal::{ - dma::DmaChannel0, + dma::{DmaChannel0, DmaTxBuf}, + dma_tx_buffer, gpio::{ interconnect::{InputSignal, OutputSignal}, NoPin, @@ -78,8 +79,8 @@ mod tests { #[test] fn test_parl_io_tx_16bit_valid_clock_count(ctx: Context) { const BUFFER_SIZE: usize = 64; - let tx_buffer = [0u16; BUFFER_SIZE]; - let (_, tx_descriptors) = esp_hal::dma_descriptors!(0, 2 * BUFFER_SIZE); + + let mut dma_tx_buf: DmaTxBuf = dma_tx_buffer!(2 * BUFFER_SIZE).unwrap(); let pins = TxSixteenBits::new( NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, @@ -88,8 +89,7 @@ mod tests { let mut pins = TxPinConfigIncludingValidPin::new(pins); let mut clock_pin = ClkOutPin::new(ctx.clock); - let pio = - ParlIoTxOnly::new(ctx.parl_io, ctx.dma_channel, tx_descriptors, 10.MHz()).unwrap(); + let pio = ParlIoTxOnly::new(ctx.parl_io, ctx.dma_channel, 10.MHz()).unwrap(); let mut pio = pio .tx @@ -102,7 +102,7 @@ mod tests { ) .unwrap(); // TODO: handle error - // use a PCNT unit to count the negitive clock edges only when valid is high + // use a PCNT unit to count the negative clock edges only when valid is high let clock_unit = ctx.pcnt_unit; clock_unit.channel0.set_edge_signal(ctx.clock_loopback); clock_unit.channel0.set_ctrl_signal(ctx.valid_loopback); @@ -115,8 +115,11 @@ mod tests { for _ in 0..100 { clock_unit.clear(); - let xfer = pio.write_dma(&tx_buffer).unwrap(); - xfer.wait().unwrap(); + let xfer = pio + .write(dma_tx_buf.len(), dma_tx_buf) + .map_err(|e| e.0) + .unwrap(); + (_, pio, dma_tx_buf) = xfer.wait(); info!("clock count: {}", clock_unit.value()); assert_eq!(clock_unit.value(), BUFFER_SIZE as _); } @@ -125,8 +128,7 @@ mod tests { #[test] fn test_parl_io_tx_8bit_valid_clock_count(ctx: Context) { const BUFFER_SIZE: usize = 64; - let tx_buffer = [0u8; BUFFER_SIZE]; - let (_, tx_descriptors) = esp_hal::dma_descriptors!(0, 2 * BUFFER_SIZE); + let mut dma_tx_buf: DmaTxBuf = dma_tx_buffer!(BUFFER_SIZE).unwrap(); let pins = TxEightBits::new( NoPin, @@ -149,8 +151,7 @@ mod tests { let mut clock_pin = ClkOutPin::new(ctx.clock); - let pio = - ParlIoTxOnly::new(ctx.parl_io, ctx.dma_channel, tx_descriptors, 10.MHz()).unwrap(); + let pio = ParlIoTxOnly::new(ctx.parl_io, ctx.dma_channel, 10.MHz()).unwrap(); let mut pio = pio .tx @@ -176,8 +177,11 @@ mod tests { for _ in 0..100 { clock_unit.clear(); - let xfer = pio.write_dma(&tx_buffer).unwrap(); - xfer.wait().unwrap(); + let xfer = pio + .write(dma_tx_buf.len(), dma_tx_buf) + .map_err(|e| e.0) + .unwrap(); + (_, pio, dma_tx_buf) = xfer.wait(); info!("clock count: {}", clock_unit.value()); assert_eq!(clock_unit.value(), BUFFER_SIZE as _); } diff --git a/hil-test/tests/parl_io_tx_async.rs b/hil-test/tests/parl_io_tx_async.rs index ecde95ae7..c8b09ec78 100644 --- a/hil-test/tests/parl_io_tx_async.rs +++ b/hil-test/tests/parl_io_tx_async.rs @@ -9,7 +9,8 @@ #[cfg(esp32c6)] use esp_hal::parl_io::{TxPinConfigWithValidPin, TxSixteenBits}; use esp_hal::{ - dma::DmaChannel0, + dma::{DmaChannel0, DmaTxBuf}, + dma_tx_buffer, gpio::{ interconnect::{InputSignal, OutputSignal}, NoPin, @@ -78,8 +79,7 @@ mod tests { #[test] async fn test_parl_io_tx_async_16bit_valid_clock_count(ctx: Context) { const BUFFER_SIZE: usize = 64; - let tx_buffer = [0u16; BUFFER_SIZE]; - let (_, tx_descriptors) = esp_hal::dma_descriptors!(0, 2 * BUFFER_SIZE); + let mut dma_tx_buf: DmaTxBuf = dma_tx_buffer!(2 * BUFFER_SIZE).unwrap(); let pins = TxSixteenBits::new( NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, @@ -88,7 +88,7 @@ mod tests { let mut pins = TxPinConfigIncludingValidPin::new(pins); let mut clock_pin = ClkOutPin::new(ctx.clock); - let pio = ParlIoTxOnly::new(ctx.parl_io, ctx.dma_channel, tx_descriptors, 10.MHz()) + let pio = ParlIoTxOnly::new(ctx.parl_io, ctx.dma_channel, 10.MHz()) .unwrap() .into_async(); @@ -116,7 +116,12 @@ mod tests { for _ in 0..100 { clock_unit.clear(); - pio.write_dma_async(&tx_buffer).await.unwrap(); + let mut xfer = pio + .write(dma_tx_buf.len(), dma_tx_buf) + .map_err(|e| e.0) + .unwrap(); + xfer.wait_for_done().await; + (_, pio, dma_tx_buf) = xfer.wait(); info!("clock count: {}", clock_unit.value()); assert_eq!(clock_unit.value(), BUFFER_SIZE as _); } @@ -125,8 +130,8 @@ mod tests { #[test] async fn test_parl_io_tx_async_8bit_valid_clock_count(ctx: Context) { const BUFFER_SIZE: usize = 64; - let tx_buffer = [0u8; BUFFER_SIZE]; - let (_, tx_descriptors) = esp_hal::dma_descriptors!(0, 2 * BUFFER_SIZE); + + let mut dma_tx_buf: DmaTxBuf = dma_tx_buffer!(BUFFER_SIZE).unwrap(); let pins = TxEightBits::new( NoPin, @@ -149,7 +154,7 @@ mod tests { let mut clock_pin = ClkOutPin::new(ctx.clock); - let pio = ParlIoTxOnly::new(ctx.parl_io, ctx.dma_channel, tx_descriptors, 10.MHz()) + let pio = ParlIoTxOnly::new(ctx.parl_io, ctx.dma_channel, 10.MHz()) .unwrap() .into_async(); @@ -178,7 +183,12 @@ mod tests { for _ in 0..100 { clock_unit.clear(); - pio.write_dma_async(&tx_buffer).await.unwrap(); + let mut xfer = pio + .write(dma_tx_buf.len(), dma_tx_buf) + .map_err(|e| e.0) + .unwrap(); + xfer.wait_for_done().await; + (_, pio, dma_tx_buf) = xfer.wait(); info!("clock count: {}", clock_unit.value()); assert_eq!(clock_unit.value(), BUFFER_SIZE as _); }