From 984d5bbc720dfe9acf22400d2462c0ffce52a7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= <timokroeger93@gmail.com> Date: Thu, 7 Mar 2024 16:36:47 +0100 Subject: [PATCH] [UCPD] Implement PD receiver --- embassy-stm32/src/ucpd.rs | 99 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index 96cd92764..f3f225d0c 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -22,7 +22,7 @@ use embassy_hal_internal::drop::OnDrop; use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use crate::dma::AnyChannel; +use crate::dma::{AnyChannel, Request, Transfer, TransferOptions}; use crate::interrupt; use crate::pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk}; pub use crate::pac::ucpd::vals::{Phyccsel as CcSel, TypecVstateCc as CcVState}; @@ -99,7 +99,7 @@ impl<'d, T: Instance> Ucpd<'d, T> { // 1.75us * 17 = ~30us w.set_ifrgap(17 - 1); - // TODO: Only receive SOP messages + // TODO: Currently only SOP messages are supported. w.set_rxordseten(0x1); // Enable DMA and the peripheral @@ -186,6 +186,14 @@ impl<'d, T: Instance> Ucpd<'d, T> { tx_dma: impl Peripheral<P = impl TxDma<T>> + 'd, cc_sel: CcSel, ) -> (PdRx<'_, T>, PdTx<'_, T>) { + let r = T::REGS; + + // Enable the receiver on one of the two CC lines. + r.cr().modify(|w| { + w.set_phyccsel(cc_sel); + w.set_phyrxen(true); + }); + into_ref!(rx_dma, tx_dma); let rx_dma_req = rx_dma.request(); let tx_dma_req = tx_dma.request(); @@ -204,6 +212,16 @@ impl<'d, T: Instance> Ucpd<'d, T> { } } +/// Receive Error. +#[derive(Debug, Clone, Copy)] +pub enum RxError { + /// Incorrect CRC or truncated message (a line becoming static before EOP is met). + Crc, + + /// Provided buffer was too small for the received message. + Overrun, +} + /// Power Delivery (PD) Receiver. pub struct PdRx<'d, T: Instance> { _ucpd: &'d Ucpd<'d, T>, @@ -217,6 +235,79 @@ impl<'d, T: Instance> Drop for PdRx<'d, T> { } } +impl<'d, T: Instance> PdRx<'d, T> { + /// Receives a PD message into the provided buffer. + /// + /// Returns the number of received bytes or an error. + pub async fn receive(&mut self, buf: &mut [u8]) -> Result<usize, RxError> { + let r = T::REGS; + + // Check if a message is already being received. If yes, wait until its + // done, ignore errors and try to receive the next message. + if r.sr().read().rxorddet() { + let _ = self.wait_rx_done().await; + r.rxdr().read(); // Clear the RX buffer. + } + + // Keep the DMA transfer alive so its drop code does not stop it right away. + let dma = unsafe { + // Disable the DMA complete interrupt because the end of packet is + // signaled by the UCPD receiver. When the DMA buffer is too short + // DMA stops by itself and the overrun RXOVR flag of UCPD is set. + let mut transfer_options = TransferOptions::default(); + transfer_options.complete_transfer_ir = false; + + Transfer::new_read( + &self.dma_ch, + self.dma_req, + r.rxdr().as_ptr() as *mut u8, + buf, + transfer_options, + ) + }; + + self.wait_rx_done().await?; + + // Make sure the the last byte to byte was fetched by DMA. + while r.sr().read().rxne() { + if dma.get_remaining_transfers() == 0 { + return Err(RxError::Overrun); + } + } + + Ok(r.rx_payszr().read().rxpaysz().into()) + } + + async fn wait_rx_done(&self) -> Result<(), RxError> { + poll_fn(|cx| { + let r = T::REGS; + let sr = r.sr().read(); + if sr.rxmsgend() { + let ret = if sr.rxovr() { + Err(RxError::Overrun) + } else if sr.rxerr() { + Err(RxError::Crc) + } else { + Ok(()) + }; + // Message received, clear interrupt flags. + r.icr().write(|w| { + w.set_rxorddetcf(true); + w.set_rxovrcf(true); + w.set_rxmsgendcf(true); + }); + Poll::Ready(ret) + } else { + // Enable receiver interrupt. + T::waker().register(cx.waker()); + r.imr().modify(|w| w.set_rxmsgendie(true)); + Poll::Pending + } + }) + .await + } +} + /// Power Delivery (PD) Transmitter. pub struct PdTx<'d, T: Instance> { _ucpd: &'d Ucpd<'d, T>, @@ -241,6 +332,10 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl }); } + if sr.rxmsgend() { + r.imr().modify(|w| w.set_rxmsgendie(false)); + } + // Wake the task to clear and re-enabled interrupts. T::waker().wake(); }