From 8a255b375b7598c2825535cb0d0729311a7b882d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kr=C3=B6ger?= Date: Fri, 1 Mar 2024 14:59:21 +0100 Subject: [PATCH 01/22] [UCPD] Instance and Pin Traits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Skip FRSTX pin for now. Its available twice in the device JSON as FRSTX1 and FRSTX2 both with the same pins as targets. I don’t know enough about the FRS (fast role switch) feature to understand if that is correct and how to handle the pins. --- embassy-stm32/build.rs | 4 ++++ embassy-stm32/src/lib.rs | 2 ++ embassy-stm32/src/ucpd.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 embassy-stm32/src/ucpd.rs diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 84e8be25d..680216034 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -764,6 +764,8 @@ fn main() { #[rustfmt::skip] let signals: HashMap<_, _> = [ // (kind, signal) => trait + (("ucpd", "CC1"), quote!(crate::ucpd::Cc1Pin)), + (("ucpd", "CC2"), quote!(crate::ucpd::Cc2Pin)), (("usart", "TX"), quote!(crate::usart::TxPin)), (("usart", "RX"), quote!(crate::usart::RxPin)), (("usart", "CTS"), quote!(crate::usart::CtsPin)), @@ -1102,6 +1104,8 @@ fn main() { let signals: HashMap<_, _> = [ // (kind, signal) => trait + (("ucpd", "RX"), quote!(crate::ucpd::RxDma)), + (("ucpd", "TX"), quote!(crate::ucpd::TxDma)), (("usart", "RX"), quote!(crate::usart::RxDma)), (("usart", "TX"), quote!(crate::usart::TxDma)), (("lpuart", "RX"), quote!(crate::usart::RxDma)), diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index b548a0343..361dc6f53 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -73,6 +73,8 @@ pub mod sai; pub mod sdmmc; #[cfg(spi)] pub mod spi; +#[cfg(ucpd)] +pub mod ucpd; #[cfg(uid)] pub mod uid; #[cfg(usart)] diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs new file mode 100644 index 000000000..a2bac7611 --- /dev/null +++ b/embassy-stm32/src/ucpd.rs @@ -0,0 +1,28 @@ +//! USB Type-C/USB Power Delivery Interface (UCPD) + +use crate::rcc::RccPeripheral; + +/// UCPD instance trait. +pub trait Instance: sealed::Instance + RccPeripheral {} + +pub(crate) mod sealed { + pub trait Instance { + const REGS: crate::pac::ucpd::Ucpd; + } +} + +foreach_peripheral!( + (ucpd, $inst:ident) => { + impl sealed::Instance for crate::peripherals::$inst { + const REGS: crate::pac::ucpd::Ucpd = crate::pac::$inst; + } + + impl Instance for crate::peripherals::$inst {} + }; +); + +pin_trait!(Cc1Pin, Instance); +pin_trait!(Cc2Pin, Instance); + +dma_trait!(TxDma, Instance); +dma_trait!(RxDma, Instance); From aa1411e2c772fd332417ca258b58c75b35d5b7ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Sun, 3 Mar 2024 15:02:00 +0100 Subject: [PATCH 02/22] [UCPD] Prepare interrupt handle --- embassy-stm32/src/ucpd.rs | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index a2bac7611..a3f01c629 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -1,20 +1,49 @@ //! USB Type-C/USB Power Delivery Interface (UCPD) +use core::marker::PhantomData; + +use crate::interrupt; use crate::rcc::RccPeripheral; +use embassy_sync::waitqueue::AtomicWaker; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let sr = T::REGS.sr().read(); + + // TODO: Disable interrupt which have fired. + + // Wake the task to handle and re-enabled interrupts. + T::waker().wake(); + } +} /// UCPD instance trait. pub trait Instance: sealed::Instance + RccPeripheral {} pub(crate) mod sealed { pub trait Instance { + type Interrupt: crate::interrupt::typelevel::Interrupt; const REGS: crate::pac::ucpd::Ucpd; + fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker; } } -foreach_peripheral!( - (ucpd, $inst:ident) => { +foreach_interrupt!( + ($inst:ident, ucpd, UCPD, GLOBAL, $irq:ident) => { impl sealed::Instance for crate::peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + const REGS: crate::pac::ucpd::Ucpd = crate::pac::$inst; + + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } } impl Instance for crate::peripherals::$inst {} From d99fcfd0c285be220c8f0004974567d7d4e2607b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Sun, 3 Mar 2024 15:09:53 +0100 Subject: [PATCH 03/22] [UCPD] Configuration Channel (CC) handling --- embassy-stm32/src/ucpd.rs | 171 ++++++++++++++++++++++++++- examples/stm32g4/src/bin/usb_c_pd.rs | 62 ++++++++++ 2 files changed, 228 insertions(+), 5 deletions(-) create mode 100644 examples/stm32g4/src/bin/usb_c_pd.rs diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index a3f01c629..98b82bacc 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -1,10 +1,165 @@ //! USB Type-C/USB Power Delivery Interface (UCPD) -use core::marker::PhantomData; +// Implementation Notes +// +// As of Feb. 2024 the UCPD peripheral is availalbe on: G0, G4, H5, L5, U5 +// +// Cube HAL LL Driver (g0): +// https://github.com/STMicroelectronics/stm32g0xx_hal_driver/blob/v1.4.6/Inc/stm32g0xx_ll_ucpd.h +// https://github.com/STMicroelectronics/stm32g0xx_hal_driver/blob/v1.4.6/Src/stm32g0xx_ll_ucpd.c +// Except for a the `LL_UCPD_RxAnalogFilterEnable/Disable()` functions the Cube HAL implementation of +// all families is the same. +// +// Dead battery pull-down resistors functionality is enabled by default on startup and must +// be disabled by setting a bit in PWR/SYSCFG registers. The exact name and location for that +// bit is different for each familily. + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; -use crate::interrupt; use crate::rcc::RccPeripheral; +use crate::{interrupt, pac}; +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; +use pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk}; + +pub use pac::ucpd::vals::TypecVstateCc as CcVState; + +/// Pull-up or Pull-down resistor state of both CC lines. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum CcPull { + /// Analog PHY for CC pin disabled. + Disabled, + + /// Rd=5.1k pull-down resistor enabled when the corresponding DBCC pin is high. + SinkDeadBattery, + + /// Rd=5.1k pull-down resistor. + Sink, + + /// Rp=56k pull-up resistor to indicate default USB power. + SourceDefaultUsb, + + /// Rp=22k pull-up resistor to indicate support for up to 1.5A. + Source1_5A, + + /// Rp=10k pull-up resistor to indicate support for up to 3.0A. + Source3_0A, +} + +/// UCPD driver. +pub struct Ucpd<'d, T: Instance> { + _peri: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Ucpd<'d, T> { + /// Creates a new UCPD driver instance. + pub fn new( + peri: impl Peripheral

+ 'd, + _cc1: impl Peripheral

> + 'd, + _cc2: impl Peripheral

> + 'd, + cc_pull: CcPull, + ) -> Self { + T::enable_and_reset(); + + let r = T::REGS; + r.cfgr1().write(|w| { + // "The receiver is designed to work in the clock frequency range from 6 to 18 MHz. + // However, the optimum performance is ensured in the range from 6 to 12 MHz" + // UCPD is driven by HSI16 (16MHz internal oscillator), which we need to divide by 2. + w.set_psc_usbpdclk(PscUsbpdclk::DIV2); + + // Prescaler to produce a target half-bit frequency of 600kHz which is required + // to produce transmit with a nominal nominal bit rate of 300Kbps+-10% using + // biphase mark coding (BMC, aka differential manchester coding). + // A divider of 13 gives the target frequency closest to spec (~615kHz, 1.625us) + // but we go with the (hopefully well tested) default value used by the Cube HAL + // which is 14 divides the clock down to ~571kHz, 1.75us. + w.set_hbitclkdiv(14 - 1); + + // Time window for detecting non-idle (12-20us). + // 1.75us * 8 = 14us. + w.set_transwin(8 - 1); + + // Time from the end of last bit of a Frame until the start of the first bit of the + // next Preamble (min 25us). + // 1.75us * 17 = ~30us + w.set_ifrgap(17 - 1); + + // TODO: Only receive SOP messages + w.set_rxordseten(0x1); + + // Enable DMA and the peripheral + w.set_txdmaen(true); + w.set_rxdmaen(true); + w.set_ucpden(true); + }); + + r.cr().write(|w| { + w.set_anamode(if cc_pull == CcPull::Sink { + Anamode::SINK + } else { + Anamode::SOURCE + }); + w.set_anasubmode(match cc_pull { + CcPull::SourceDefaultUsb => 1, + CcPull::Source1_5A => 2, + CcPull::Source3_0A => 3, + _ => 0, + }); + w.set_ccenable(if cc_pull != CcPull::SinkDeadBattery { + Ccenable::BOTH + } else { + Ccenable::DISABLED + }); + }); + + // Disable dead-battery pull-down resistors which are enabled by default on boot. + critical_section::with(|_| { + // TODO: other families + #[cfg(stm32g4)] + pac::PWR + .cr3() + .modify(|w| w.set_ucpd1_dbdis(cc_pull != CcPull::SinkDeadBattery)); + }); + + into_ref!(peri); + Self { _peri: peri } + } + + /// Returns the current voltage level of CC1 and CC2 pin as tuple. + /// + /// Interpretation of the voltage levels depends on the configured CC line + /// pull-up/pull-down resistance. + pub fn cc_vstate(&self) -> (CcVState, CcVState) { + let sr = T::REGS.sr().read(); + (sr.typec_vstate_cc1(), sr.typec_vstate_cc2()) + } + + /// Waits for a change in voltage state on either CC line. + pub async fn wait_for_cc_change(&mut self) { + let r = T::REGS; + poll_fn(|cx| { + let sr = r.sr().read(); + if sr.typecevt1() || sr.typecevt2() { + r.icr().write(|w| { + w.set_typecevt1cf(true); + w.set_typecevt2cf(true); + }); + Poll::Ready(()) + } else { + T::waker().register(cx.waker()); + r.imr().modify(|w| { + w.set_typecevt1ie(true); + w.set_typecevt2ie(true); + }); + Poll::Pending + } + }) + .await; + } +} /// Interrupt handler. pub struct InterruptHandler { @@ -13,11 +168,17 @@ pub struct InterruptHandler { impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { - let sr = T::REGS.sr().read(); + let r = T::REGS; + let sr = r.sr().read(); - // TODO: Disable interrupt which have fired. + if sr.typecevt1() || sr.typecevt2() { + r.imr().modify(|w| { + w.set_typecevt1ie(true); + w.set_typecevt2ie(true); + }); + } - // Wake the task to handle and re-enabled interrupts. + // Wake the task to clear and re-enabled interrupts. T::waker().wake(); } } diff --git a/examples/stm32g4/src/bin/usb_c_pd.rs b/examples/stm32g4/src/bin/usb_c_pd.rs new file mode 100644 index 000000000..c442ab0a7 --- /dev/null +++ b/examples/stm32g4/src/bin/usb_c_pd.rs @@ -0,0 +1,62 @@ +#![no_std] +#![no_main] + +use defmt::{info, Format}; +use embassy_executor::Spawner; +use embassy_stm32::{ + ucpd::{self, CcPull, CcVState, Ucpd}, + Config, +}; +use embassy_time::{with_timeout, Duration}; +use {defmt_rtt as _, panic_probe as _}; + +#[derive(Debug, Format)] +enum CableOrientation { + Normal, + Flipped, + DebugAccessoryMode, +} + +// Returns true when the cable +async fn wait_attached<'d, T: ucpd::Instance>(ucpd: &mut Ucpd<'d, T>) -> CableOrientation { + loop { + let (cc1, cc2) = ucpd.cc_vstate(); + if cc1 == CcVState::LOWEST && cc2 == CcVState::LOWEST { + // Detached, wait until attached by monitoring the CC lines. + ucpd.wait_for_cc_change().await; + continue; + } + + // Attached, wait for CC lines to be stable for tCCDebounce (100..200ms). + if with_timeout(Duration::from_millis(100), ucpd.wait_for_cc_change()) + .await + .is_ok() + { + // State has changed, restart detection procedure. + continue; + }; + + // State was stable for the complete debounce period, check orientation. + return match (cc1, cc2) { + (_, CcVState::LOWEST) => CableOrientation::Normal, // CC1 connected + (CcVState::LOWEST, _) => CableOrientation::Flipped, // CC2 connected + _ => CableOrientation::DebugAccessoryMode, // Both connected (special cable) + }; + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // TODO: Disable DBCC pin functionality by default but have flag in the config to keep it enabled when required. + let p = embassy_stm32::init(Config::default()); + + info!("Hello World!"); + + let mut ucpd = Ucpd::new(p.UCPD1, p.PB6, p.PB4, CcPull::Sink); + + info!("Waiting for USB connection..."); + let cable_orientation = wait_attached(&mut ucpd).await; + info!("USB cable connected, orientation: {}", cable_orientation); + + loop {} +} From a3b12226170d6b1a9ded47cc043cc09489cee278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Thu, 7 Mar 2024 09:17:05 +0100 Subject: [PATCH 04/22] [UCPD] Improve Type-C CC handling * Improved interrupt handling: Clear flags in ISR, check state change in future * Disable pull-up/pull-down resistors and voltage monitor on drop * nightly rustfmt --- embassy-stm32/src/ucpd.rs | 62 ++++++++++++++++++---------- examples/stm32g4/src/bin/usb_c_pd.rs | 12 +++--- 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index 98b82bacc..a366fadda 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -18,13 +18,14 @@ use core::future::poll_fn; use core::marker::PhantomData; use core::task::Poll; -use crate::rcc::RccPeripheral; -use crate::{interrupt, pac}; +use embassy_hal_internal::drop::OnDrop; use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk}; -pub use pac::ucpd::vals::TypecVstateCc as CcVState; +use crate::interrupt; +pub use crate::pac::ucpd::vals::TypecVstateCc as CcVState; +use crate::pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk}; +use crate::rcc::RccPeripheral; /// Pull-up or Pull-down resistor state of both CC lines. #[derive(Debug, Clone, Copy, PartialEq)] @@ -53,6 +54,16 @@ pub struct Ucpd<'d, T: Instance> { _peri: PeripheralRef<'d, T>, } +impl<'d, T: Instance> Drop for Ucpd<'d, T> { + fn drop(&mut self) { + T::REGS.cr().modify(|w| { + w.set_ccenable(Ccenable::DISABLED); + w.set_cc1tcdis(true); + w.set_cc2tcdis(true); + }); + } +} + impl<'d, T: Instance> Ucpd<'d, T> { /// Creates a new UCPD driver instance. pub fn new( @@ -113,13 +124,17 @@ impl<'d, T: Instance> Ucpd<'d, T> { } else { Ccenable::DISABLED }); + + // Make sure detector is enabled on both pins. + w.set_cc1tcdis(false); + w.set_cc2tcdis(false); }); // Disable dead-battery pull-down resistors which are enabled by default on boot. critical_section::with(|_| { // TODO: other families #[cfg(stm32g4)] - pac::PWR + crate::pac::PWR .cr3() .modify(|w| w.set_ucpd1_dbdis(cc_pull != CcPull::SinkDeadBattery)); }); @@ -138,26 +153,29 @@ impl<'d, T: Instance> Ucpd<'d, T> { } /// Waits for a change in voltage state on either CC line. - pub async fn wait_for_cc_change(&mut self) { - let r = T::REGS; + pub async fn wait_for_cc_vstate_change(&self) -> (CcVState, CcVState) { + let _on_drop = OnDrop::new(|| critical_section::with(|_| self.enable_cc_interrupts(false))); + let prev_vstate = self.cc_vstate(); poll_fn(|cx| { - let sr = r.sr().read(); - if sr.typecevt1() || sr.typecevt2() { - r.icr().write(|w| { - w.set_typecevt1cf(true); - w.set_typecevt2cf(true); - }); - Poll::Ready(()) + let vstate = self.cc_vstate(); + if vstate != prev_vstate { + Poll::Ready(vstate) } else { T::waker().register(cx.waker()); - r.imr().modify(|w| { - w.set_typecevt1ie(true); - w.set_typecevt2ie(true); - }); + self.enable_cc_interrupts(true); Poll::Pending } }) - .await; + .await + } + + fn enable_cc_interrupts(&self, enable: bool) { + critical_section::with(|_| { + T::REGS.imr().modify(|w| { + w.set_typecevt1ie(enable); + w.set_typecevt2ie(enable); + }) + }); } } @@ -172,9 +190,9 @@ impl interrupt::typelevel::Handler for InterruptHandl let sr = r.sr().read(); if sr.typecevt1() || sr.typecevt2() { - r.imr().modify(|w| { - w.set_typecevt1ie(true); - w.set_typecevt2ie(true); + r.icr().write(|w| { + w.set_typecevt1cf(true); + w.set_typecevt2cf(true); }); } diff --git a/examples/stm32g4/src/bin/usb_c_pd.rs b/examples/stm32g4/src/bin/usb_c_pd.rs index c442ab0a7..7a0065087 100644 --- a/examples/stm32g4/src/bin/usb_c_pd.rs +++ b/examples/stm32g4/src/bin/usb_c_pd.rs @@ -3,10 +3,8 @@ use defmt::{info, Format}; use embassy_executor::Spawner; -use embassy_stm32::{ - ucpd::{self, CcPull, CcVState, Ucpd}, - Config, -}; +use embassy_stm32::ucpd::{self, CcPull, CcVState, Ucpd}; +use embassy_stm32::Config; use embassy_time::{with_timeout, Duration}; use {defmt_rtt as _, panic_probe as _}; @@ -18,17 +16,17 @@ enum CableOrientation { } // Returns true when the cable -async fn wait_attached<'d, T: ucpd::Instance>(ucpd: &mut Ucpd<'d, T>) -> CableOrientation { +async fn wait_attached(ucpd: &mut Ucpd<'_, T>) -> CableOrientation { loop { let (cc1, cc2) = ucpd.cc_vstate(); if cc1 == CcVState::LOWEST && cc2 == CcVState::LOWEST { // Detached, wait until attached by monitoring the CC lines. - ucpd.wait_for_cc_change().await; + ucpd.wait_for_cc_vstate_change().await; continue; } // Attached, wait for CC lines to be stable for tCCDebounce (100..200ms). - if with_timeout(Duration::from_millis(100), ucpd.wait_for_cc_change()) + if with_timeout(Duration::from_millis(100), ucpd.wait_for_cc_vstate_change()) .await .is_ok() { From 4d0e3838168a7447b3580135ef78d65baa578933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Thu, 7 Mar 2024 11:17:39 +0100 Subject: [PATCH 05/22] [UCPD] Prepare for PD communication implementation --- embassy-stm32/src/ucpd.rs | 47 +++++++++++++++++++++++++++- examples/stm32g4/src/bin/usb_c_pd.rs | 15 ++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index a366fadda..96cd92764 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -22,9 +22,10 @@ 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::interrupt; -pub use crate::pac::ucpd::vals::TypecVstateCc as CcVState; use crate::pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk}; +pub use crate::pac::ucpd::vals::{Phyccsel as CcSel, TypecVstateCc as CcVState}; use crate::rcc::RccPeripheral; /// Pull-up or Pull-down resistor state of both CC lines. @@ -177,6 +178,50 @@ impl<'d, T: Instance> Ucpd<'d, T> { }) }); } + + /// Returns PD receiver and transmitter. + pub fn pd( + &mut self, + rx_dma: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

> + 'd, + cc_sel: CcSel, + ) -> (PdRx<'_, T>, PdTx<'_, T>) { + into_ref!(rx_dma, tx_dma); + let rx_dma_req = rx_dma.request(); + let tx_dma_req = tx_dma.request(); + ( + PdRx { + _ucpd: self, + dma_ch: rx_dma.map_into(), + dma_req: rx_dma_req, + }, + PdTx { + _ucpd: self, + dma_ch: tx_dma.map_into(), + dma_req: tx_dma_req, + }, + ) + } +} + +/// Power Delivery (PD) Receiver. +pub struct PdRx<'d, T: Instance> { + _ucpd: &'d Ucpd<'d, T>, + dma_ch: PeripheralRef<'d, AnyChannel>, + dma_req: Request, +} + +impl<'d, T: Instance> Drop for PdRx<'d, T> { + fn drop(&mut self) { + T::REGS.cr().modify(|w| w.set_phyrxen(false)); + } +} + +/// Power Delivery (PD) Transmitter. +pub struct PdTx<'d, T: Instance> { + _ucpd: &'d Ucpd<'d, T>, + dma_ch: PeripheralRef<'d, AnyChannel>, + dma_req: Request, } /// Interrupt handler. diff --git a/examples/stm32g4/src/bin/usb_c_pd.rs b/examples/stm32g4/src/bin/usb_c_pd.rs index 7a0065087..1443cb773 100644 --- a/examples/stm32g4/src/bin/usb_c_pd.rs +++ b/examples/stm32g4/src/bin/usb_c_pd.rs @@ -3,7 +3,7 @@ use defmt::{info, Format}; use embassy_executor::Spawner; -use embassy_stm32::ucpd::{self, CcPull, CcVState, Ucpd}; +use embassy_stm32::ucpd::{self, CcPull, CcSel, CcVState, Ucpd}; use embassy_stm32::Config; use embassy_time::{with_timeout, Duration}; use {defmt_rtt as _, panic_probe as _}; @@ -56,5 +56,18 @@ async fn main(_spawner: Spawner) { let cable_orientation = wait_attached(&mut ucpd).await; info!("USB cable connected, orientation: {}", cable_orientation); + let cc_sel = match cable_orientation { + CableOrientation::Normal => { + info!("Starting PD communication on CC1 pin"); + CcSel::CC1 + } + CableOrientation::Flipped => { + info!("Starting PD communication on CC2 pin"); + CcSel::CC2 + } + CableOrientation::DebugAccessoryMode => panic!("No PD communication in DAM"), + }; + let (mut _rx, mut _tx) = ucpd.pd(p.DMA1_CH1, p.DMA1_CH2, cc_sel); + loop {} } From 984d5bbc720dfe9acf22400d2462c0ffce52a7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Thu, 7 Mar 2024 16:36:47 +0100 Subject: [PATCH 06/22] [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

> + '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 { + 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 interrupt::typelevel::Handler 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(); } From 36a99189210bf15c93198a4bf9a0d2ab732c8bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Thu, 7 Mar 2024 18:35:05 +0100 Subject: [PATCH 07/22] [UCPD] Implement PD transmitter --- embassy-stm32/src/ucpd.rs | 81 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index f3f225d0c..0a6f7df71 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -24,7 +24,7 @@ use embassy_sync::waitqueue::AtomicWaker; use crate::dma::{AnyChannel, Request, Transfer, TransferOptions}; use crate::interrupt; -use crate::pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk}; +use crate::pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk, Txmode}; pub use crate::pac::ucpd::vals::{Phyccsel as CcSel, TypecVstateCc as CcVState}; use crate::rcc::RccPeripheral; @@ -194,6 +194,9 @@ impl<'d, T: Instance> Ucpd<'d, T> { w.set_phyrxen(true); }); + // TODO: Currently only SOP messages are supported. + r.tx_ordsetr().write(|w| w.set_txordset(0b10001_11000_11000_11000)); + into_ref!(rx_dma, tx_dma); let rx_dma_req = rx_dma.request(); let tx_dma_req = tx_dma.request(); @@ -308,6 +311,13 @@ impl<'d, T: Instance> PdRx<'d, T> { } } +/// Transmit Error. +#[derive(Debug, Clone, Copy)] +pub enum TxError { + /// Concurrent receive in progress or excessive noise on the line. + Discarded, +} + /// Power Delivery (PD) Transmitter. pub struct PdTx<'d, T: Instance> { _ucpd: &'d Ucpd<'d, T>, @@ -315,6 +325,68 @@ pub struct PdTx<'d, T: Instance> { dma_req: Request, } +impl<'d, T: Instance> PdTx<'d, T> { + /// Transmits a PD message. + pub async fn transmit(&mut self, buf: &[u8]) -> Result<(), TxError> { + let r = T::REGS; + + // When a previous transmission was dropped before it had finished it + // might still be running because there is no way to abort an ongoing + // message transmission. Wait for it to finish but ignore errors. + if r.cr().read().txsend() { + let _ = self.wait_tx_done().await; + } + + // Clear the TX interrupt flags. + T::REGS.icr().write(|w| { + w.set_txmsgdisccf(true); + w.set_txmsgsentcf(true); + }); + + // Start the DMA and let it do its thing in the background. + let _dma = unsafe { + Transfer::new_write( + &self.dma_ch, + self.dma_req, + buf, + r.txdr().as_ptr() as *mut u8, + TransferOptions::default(), + ) + }; + + // Configure and start the transmission. + r.tx_payszr().write(|w| w.set_txpaysz(buf.len() as _)); + r.cr().modify(|w| { + w.set_txmode(Txmode::PACKET); + w.set_txsend(true); + }); + + self.wait_tx_done().await + } + + async fn wait_tx_done(&self) -> Result<(), TxError> { + poll_fn(|cx| { + let r = T::REGS; + if r.sr().read().txmsgdisc() { + Poll::Ready(Err(TxError::Discarded)) + } else if r.sr().read().txmsgsent() { + Poll::Ready(Ok(())) + } else { + // Enable transmit interrupts. + T::waker().register(cx.waker()); + r.imr().modify(|w| { + w.set_txmsgdiscie(true); + w.set_txmsgsentie(true); + }); + Poll::Pending + } + }) + .await + } + + fn clear_tx_flags(&self) {} +} + /// Interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, @@ -336,6 +408,13 @@ impl interrupt::typelevel::Handler for InterruptHandl r.imr().modify(|w| w.set_rxmsgendie(false)); } + if sr.txmsgdisc() || sr.txmsgsent() { + r.imr().modify(|w| { + w.set_txmsgdiscie(false); + w.set_txmsgsentie(false); + }); + } + // Wake the task to clear and re-enabled interrupts. T::waker().wake(); } From 5e271ff31b55b339d4321af4b2c8a096bf153d4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Thu, 7 Mar 2024 19:47:13 +0100 Subject: [PATCH 08/22] [UCPD] Combine RX and TX `select(rx.receive(), tx.transmit()` had subtle interrupt enable race conditions. Combine receiver and transmitter into one new `PdPhy` struct to disallow the problematic pattern. Scanning through the USB PD 2.0 specification there is no need to have RX and TX running concurrently (after all the USB PD communication is half-duplex). --- embassy-stm32/src/ucpd.rs | 127 +++++++++++++-------------- examples/stm32g4/src/bin/usb_c_pd.rs | 2 +- 2 files changed, 60 insertions(+), 69 deletions(-) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index 0a6f7df71..02c81c2b6 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -57,11 +57,7 @@ pub struct Ucpd<'d, T: Instance> { impl<'d, T: Instance> Drop for Ucpd<'d, T> { fn drop(&mut self) { - T::REGS.cr().modify(|w| { - w.set_ccenable(Ccenable::DISABLED); - w.set_cc1tcdis(true); - w.set_cc2tcdis(true); - }); + T::REGS.cfgr1().write(|w| w.set_ucpden(false)); } } @@ -99,12 +95,6 @@ impl<'d, T: Instance> Ucpd<'d, T> { // 1.75us * 17 = ~30us w.set_ifrgap(17 - 1); - // TODO: Currently only SOP messages are supported. - w.set_rxordseten(0x1); - - // Enable DMA and the peripheral - w.set_txdmaen(true); - w.set_rxdmaen(true); w.set_ucpden(true); }); @@ -155,7 +145,7 @@ impl<'d, T: Instance> Ucpd<'d, T> { /// Waits for a change in voltage state on either CC line. pub async fn wait_for_cc_vstate_change(&self) -> (CcVState, CcVState) { - let _on_drop = OnDrop::new(|| critical_section::with(|_| self.enable_cc_interrupts(false))); + let _on_drop = OnDrop::new(|| self.enable_cc_interrupts(false)); let prev_vstate = self.cc_vstate(); poll_fn(|cx| { let vstate = self.cc_vstate(); @@ -171,47 +161,49 @@ impl<'d, T: Instance> Ucpd<'d, T> { } fn enable_cc_interrupts(&self, enable: bool) { - critical_section::with(|_| { - T::REGS.imr().modify(|w| { - w.set_typecevt1ie(enable); - w.set_typecevt2ie(enable); - }) + T::REGS.imr().modify(|w| { + w.set_typecevt1ie(enable); + w.set_typecevt2ie(enable); }); } /// Returns PD receiver and transmitter. - pub fn pd( + pub fn pd_phy( &mut self, rx_dma: impl Peripheral

> + 'd, tx_dma: impl Peripheral

> + 'd, cc_sel: CcSel, - ) -> (PdRx<'_, T>, PdTx<'_, T>) { + ) -> PdPhy<'_, T> { let r = T::REGS; + // TODO: Currently only SOP messages are supported. + r.tx_ordsetr().write(|w| w.set_txordset(0b10001_11000_11000_11000)); + + r.cfgr1().modify(|w| { + // TODO: Currently only SOP messages are supported. + w.set_rxordseten(0x1); + + // Enable DMA + w.set_txdmaen(true); + w.set_rxdmaen(true); + }); + // Enable the receiver on one of the two CC lines. r.cr().modify(|w| { w.set_phyccsel(cc_sel); w.set_phyrxen(true); }); - // TODO: Currently only SOP messages are supported. - r.tx_ordsetr().write(|w| w.set_txordset(0b10001_11000_11000_11000)); - into_ref!(rx_dma, tx_dma); let rx_dma_req = rx_dma.request(); let tx_dma_req = tx_dma.request(); - ( - PdRx { - _ucpd: self, - dma_ch: rx_dma.map_into(), - dma_req: rx_dma_req, - }, - PdTx { - _ucpd: self, - dma_ch: tx_dma.map_into(), - dma_req: tx_dma_req, - }, - ) + PdPhy { + _ucpd: self, + rx_dma_ch: rx_dma.map_into(), + rx_dma_req, + tx_dma_ch: tx_dma.map_into(), + tx_dma_req, + } } } @@ -225,20 +217,29 @@ pub enum RxError { Overrun, } -/// Power Delivery (PD) Receiver. -pub struct PdRx<'d, T: Instance> { - _ucpd: &'d Ucpd<'d, T>, - dma_ch: PeripheralRef<'d, AnyChannel>, - dma_req: Request, +/// Transmit Error. +#[derive(Debug, Clone, Copy)] +pub enum TxError { + /// Concurrent receive in progress or excessive noise on the line. + Discarded, } -impl<'d, T: Instance> Drop for PdRx<'d, T> { +/// Power Delivery (PD) PHY. +pub struct PdPhy<'d, T: Instance> { + _ucpd: &'d Ucpd<'d, T>, + rx_dma_ch: PeripheralRef<'d, AnyChannel>, + rx_dma_req: Request, + tx_dma_ch: PeripheralRef<'d, AnyChannel>, + tx_dma_req: Request, +} + +impl<'d, T: Instance> Drop for PdPhy<'d, T> { fn drop(&mut self) { T::REGS.cr().modify(|w| w.set_phyrxen(false)); } } -impl<'d, T: Instance> PdRx<'d, T> { +impl<'d, T: Instance> PdPhy<'d, T> { /// Receives a PD message into the provided buffer. /// /// Returns the number of received bytes or an error. @@ -261,8 +262,8 @@ impl<'d, T: Instance> PdRx<'d, T> { transfer_options.complete_transfer_ir = false; Transfer::new_read( - &self.dma_ch, - self.dma_req, + &self.rx_dma_ch, + self.rx_dma_req, r.rxdr().as_ptr() as *mut u8, buf, transfer_options, @@ -282,6 +283,7 @@ impl<'d, T: Instance> PdRx<'d, T> { } async fn wait_rx_done(&self) -> Result<(), RxError> { + let _on_drop = OnDrop::new(|| self.enable_rx_interrupt(false)); poll_fn(|cx| { let r = T::REGS; let sr = r.sr().read(); @@ -301,31 +303,18 @@ impl<'d, T: Instance> PdRx<'d, T> { }); Poll::Ready(ret) } else { - // Enable receiver interrupt. T::waker().register(cx.waker()); - r.imr().modify(|w| w.set_rxmsgendie(true)); + self.enable_rx_interrupt(true); Poll::Pending } }) .await } -} -/// Transmit Error. -#[derive(Debug, Clone, Copy)] -pub enum TxError { - /// Concurrent receive in progress or excessive noise on the line. - Discarded, -} + fn enable_rx_interrupt(&self, enable: bool) { + T::REGS.imr().modify(|w| w.set_rxmsgendie(enable)); + } -/// Power Delivery (PD) Transmitter. -pub struct PdTx<'d, T: Instance> { - _ucpd: &'d Ucpd<'d, T>, - dma_ch: PeripheralRef<'d, AnyChannel>, - dma_req: Request, -} - -impl<'d, T: Instance> PdTx<'d, T> { /// Transmits a PD message. pub async fn transmit(&mut self, buf: &[u8]) -> Result<(), TxError> { let r = T::REGS; @@ -346,8 +335,8 @@ impl<'d, T: Instance> PdTx<'d, T> { // Start the DMA and let it do its thing in the background. let _dma = unsafe { Transfer::new_write( - &self.dma_ch, - self.dma_req, + &self.tx_dma_ch, + self.tx_dma_req, buf, r.txdr().as_ptr() as *mut u8, TransferOptions::default(), @@ -365,6 +354,7 @@ impl<'d, T: Instance> PdTx<'d, T> { } async fn wait_tx_done(&self) -> Result<(), TxError> { + let _on_drop = OnDrop::new(|| self.enable_tx_interrupts(false)); poll_fn(|cx| { let r = T::REGS; if r.sr().read().txmsgdisc() { @@ -372,19 +362,20 @@ impl<'d, T: Instance> PdTx<'d, T> { } else if r.sr().read().txmsgsent() { Poll::Ready(Ok(())) } else { - // Enable transmit interrupts. T::waker().register(cx.waker()); - r.imr().modify(|w| { - w.set_txmsgdiscie(true); - w.set_txmsgsentie(true); - }); + self.enable_tx_interrupts(true); Poll::Pending } }) .await } - fn clear_tx_flags(&self) {} + fn enable_tx_interrupts(&self, enable: bool) { + T::REGS.imr().modify(|w| { + w.set_txmsgdiscie(enable); + w.set_txmsgsentie(enable); + }); + } } /// Interrupt handler. diff --git a/examples/stm32g4/src/bin/usb_c_pd.rs b/examples/stm32g4/src/bin/usb_c_pd.rs index 1443cb773..fd2400bd5 100644 --- a/examples/stm32g4/src/bin/usb_c_pd.rs +++ b/examples/stm32g4/src/bin/usb_c_pd.rs @@ -67,7 +67,7 @@ async fn main(_spawner: Spawner) { } CableOrientation::DebugAccessoryMode => panic!("No PD communication in DAM"), }; - let (mut _rx, mut _tx) = ucpd.pd(p.DMA1_CH1, p.DMA1_CH2, cc_sel); + let mut pd_phy = ucpd.pd_phy(p.DMA1_CH1, p.DMA1_CH2, cc_sel); loop {} } From b7972048a1642679392b2a1dfc976881205fd23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Thu, 7 Mar 2024 19:54:55 +0100 Subject: [PATCH 09/22] [UCPD] Improve example and defmt Format for enums --- embassy-stm32/src/ucpd.rs | 3 +++ examples/stm32g4/src/bin/usb_c_pd.rs | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index 02c81c2b6..071bfd1b1 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -30,6 +30,7 @@ use crate::rcc::RccPeripheral; /// Pull-up or Pull-down resistor state of both CC lines. #[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum CcPull { /// Analog PHY for CC pin disabled. Disabled, @@ -209,6 +210,7 @@ impl<'d, T: Instance> Ucpd<'d, T> { /// Receive Error. #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum RxError { /// Incorrect CRC or truncated message (a line becoming static before EOP is met). Crc, @@ -219,6 +221,7 @@ pub enum RxError { /// Transmit Error. #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum TxError { /// Concurrent receive in progress or excessive noise on the line. Discarded, diff --git a/examples/stm32g4/src/bin/usb_c_pd.rs b/examples/stm32g4/src/bin/usb_c_pd.rs index fd2400bd5..14abd542f 100644 --- a/examples/stm32g4/src/bin/usb_c_pd.rs +++ b/examples/stm32g4/src/bin/usb_c_pd.rs @@ -1,7 +1,7 @@ #![no_std] #![no_main] -use defmt::{info, Format}; +use defmt::{error, info, Format}; use embassy_executor::Spawner; use embassy_stm32::ucpd::{self, CcPull, CcSel, CcVState, Ucpd}; use embassy_stm32::Config; @@ -69,5 +69,12 @@ async fn main(_spawner: Spawner) { }; let mut pd_phy = ucpd.pd_phy(p.DMA1_CH1, p.DMA1_CH2, cc_sel); - loop {} + loop { + // Enough space for the longest non-extended data message. + let mut buf = [0_u8; 30]; + match pd_phy.receive(buf.as_mut()).await { + Ok(n) => info!("USB PD RX: {=[u8]:?}", &buf[..n]), + Err(e) => error!("USB PD RX: {}", e), + } + } } From c1efcbba2dc472948b294b474a9a1970f88cd96a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kr=C3=B6ger?= Date: Fri, 8 Mar 2024 15:01:55 +0100 Subject: [PATCH 10/22] [UCPD] Receive hard resets --- embassy-stm32/src/ucpd.rs | 42 ++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index 071bfd1b1..64969beb8 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -181,8 +181,8 @@ impl<'d, T: Instance> Ucpd<'d, T> { r.tx_ordsetr().write(|w| w.set_txordset(0b10001_11000_11000_11000)); r.cfgr1().modify(|w| { - // TODO: Currently only SOP messages are supported. - w.set_rxordseten(0x1); + // TODO: Currently only hard reset and SOP messages can be received. + w.set_rxordseten(0b1001); // Enable DMA w.set_txdmaen(true); @@ -195,6 +195,9 @@ impl<'d, T: Instance> Ucpd<'d, T> { w.set_phyrxen(true); }); + // Enable hard reset receive interrupt. + r.imr().modify(|w| w.set_rxhrstdetie(true)); + into_ref!(rx_dma, tx_dma); let rx_dma_req = rx_dma.request(); let tx_dma_req = tx_dma.request(); @@ -217,6 +220,9 @@ pub enum RxError { /// Provided buffer was too small for the received message. Overrun, + + /// Hard Reset received before or during reception. + HardReset, } /// Transmit Error. @@ -225,6 +231,9 @@ pub enum RxError { pub enum TxError { /// Concurrent receive in progress or excessive noise on the line. Discarded, + + /// Hard Reset received before or during transmission. + HardReset, } /// Power Delivery (PD) PHY. @@ -252,7 +261,9 @@ impl<'d, T: Instance> PdPhy<'d, T> { // 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; + if let Err(RxError::HardReset) = self.wait_rx_done().await { + return Err(RxError::HardReset); + } r.rxdr().read(); // Clear the RX buffer. } @@ -290,7 +301,12 @@ impl<'d, T: Instance> PdPhy<'d, T> { poll_fn(|cx| { let r = T::REGS; let sr = r.sr().read(); - if sr.rxmsgend() { + if sr.rxhrstdet() { + // Clean and re-enable hard reset receive interrupt. + r.icr().write(|w| w.set_rxhrstdetcf(true)); + r.imr().modify(|w| w.set_rxhrstdetie(true)); + Poll::Ready(Err(RxError::HardReset)) + } else if sr.rxmsgend() { let ret = if sr.rxovr() { Err(RxError::Overrun) } else if sr.rxerr() { @@ -326,7 +342,9 @@ impl<'d, T: Instance> PdPhy<'d, T> { // might still be running because there is no way to abort an ongoing // message transmission. Wait for it to finish but ignore errors. if r.cr().read().txsend() { - let _ = self.wait_tx_done().await; + if let Err(TxError::HardReset) = self.wait_tx_done().await { + return Err(TxError::HardReset); + } } // Clear the TX interrupt flags. @@ -360,9 +378,15 @@ impl<'d, T: Instance> PdPhy<'d, T> { let _on_drop = OnDrop::new(|| self.enable_tx_interrupts(false)); poll_fn(|cx| { let r = T::REGS; - if r.sr().read().txmsgdisc() { + let sr = r.sr().read(); + if sr.rxhrstdet() { + // Clean and re-enable hard reset receive interrupt. + r.icr().write(|w| w.set_rxhrstdetcf(true)); + r.imr().modify(|w| w.set_rxhrstdetie(true)); + Poll::Ready(Err(TxError::HardReset)) + } else if sr.txmsgdisc() { Poll::Ready(Err(TxError::Discarded)) - } else if r.sr().read().txmsgsent() { + } else if sr.txmsgsent() { Poll::Ready(Ok(())) } else { T::waker().register(cx.waker()); @@ -398,6 +422,10 @@ impl interrupt::typelevel::Handler for InterruptHandl }); } + if sr.rxhrstdet() { + r.imr().modify(|w| w.set_rxhrstdetie(false)); + } + if sr.rxmsgend() { r.imr().modify(|w| w.set_rxmsgendie(false)); } From ff8129a6a6845186bac92c6b6fcfefa03a7a7d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kr=C3=B6ger?= Date: Fri, 8 Mar 2024 15:15:55 +0100 Subject: [PATCH 11/22] [UCPD] Implement hard reset transmission --- embassy-stm32/src/ucpd.rs | 51 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index 64969beb8..05a61634a 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -403,6 +403,50 @@ impl<'d, T: Instance> PdPhy<'d, T> { w.set_txmsgsentie(enable); }); } + + /// Transmit a hard reset. + pub async fn transmit_hardreset(&mut self) -> Result<(), TxError> { + let r = T::REGS; + + // Clear the hardreset interrupt flags. + T::REGS.icr().write(|w| { + w.set_txmsgdisccf(true); + w.set_txmsgsentcf(true); + }); + + // Trigger hard reset transmission. + r.cr().modify(|w| { + w.set_txhrst(true); + }); + + let _on_drop = OnDrop::new(|| self.enable_hardreset_interrupts(false)); + poll_fn(|cx| { + let r = T::REGS; + let sr = r.sr().read(); + if sr.rxhrstdet() { + // Clean and re-enable hard reset receive interrupt. + r.icr().write(|w| w.set_rxhrstdetcf(true)); + r.imr().modify(|w| w.set_rxhrstdetie(true)); + Poll::Ready(Err(TxError::HardReset)) + } else if sr.hrstdisc() { + Poll::Ready(Err(TxError::Discarded)) + } else if sr.hrstsent() { + Poll::Ready(Ok(())) + } else { + T::waker().register(cx.waker()); + self.enable_hardreset_interrupts(true); + Poll::Pending + } + }) + .await + } + + fn enable_hardreset_interrupts(&self, enable: bool) { + T::REGS.imr().modify(|w| { + w.set_hrstdiscie(enable); + w.set_hrstsentie(enable); + }); + } } /// Interrupt handler. @@ -437,6 +481,13 @@ impl interrupt::typelevel::Handler for InterruptHandl }); } + if sr.hrstdisc() || sr.hrstsent() { + r.imr().modify(|w| { + w.set_hrstdiscie(false); + w.set_hrstsentie(false); + }); + } + // Wake the task to clear and re-enabled interrupts. T::waker().wake(); } From 99854ff840fdd12d7562923f6a9ef2d39e1c9f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Fri, 8 Mar 2024 19:26:49 +0100 Subject: [PATCH 12/22] [UCPD] Fix build for devices with GPDMA Do not use a flag that is DMA/BDMA only, not required anyway the transfer should run in the background nevertheless --- embassy-stm32/src/ucpd.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index 05a61634a..d1b793e26 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -269,18 +269,12 @@ impl<'d, T: Instance> PdPhy<'d, T> { // 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.rx_dma_ch, self.rx_dma_req, r.rxdr().as_ptr() as *mut u8, buf, - transfer_options, + TransferOptions::default(), ) }; From 89504f51629d0ab81070db91c3eb5b96b1e41fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Fri, 8 Mar 2024 20:36:37 +0100 Subject: [PATCH 13/22] [UCPD] Split into CC and PD phy PD3.0 spec requires concurrent control of CC resistors for collision avoidance. Needed to introduce some "ref counting" (its just a bool) for drop code. --- embassy-stm32/src/ucpd.rs | 251 +++++++++++++++++---------- examples/stm32g4/src/bin/usb_c_pd.rs | 17 +- 2 files changed, 164 insertions(+), 104 deletions(-) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index d1b793e26..2120716ef 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -16,11 +16,11 @@ use core::future::poll_fn; use core::marker::PhantomData; +use core::sync::atomic::Ordering; use core::task::Poll; use embassy_hal_internal::drop::OnDrop; use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; -use embassy_sync::waitqueue::AtomicWaker; use crate::dma::{AnyChannel, Request, Transfer, TransferOptions}; use crate::interrupt; @@ -53,22 +53,15 @@ pub enum CcPull { /// UCPD driver. pub struct Ucpd<'d, T: Instance> { - _peri: PeripheralRef<'d, T>, -} - -impl<'d, T: Instance> Drop for Ucpd<'d, T> { - fn drop(&mut self) { - T::REGS.cfgr1().write(|w| w.set_ucpden(false)); - } + cc_phy: CcPhy<'d, T>, } impl<'d, T: Instance> Ucpd<'d, T> { /// Creates a new UCPD driver instance. pub fn new( - peri: impl Peripheral

+ 'd, + _peri: impl Peripheral

+ 'd, _cc1: impl Peripheral

> + 'd, _cc2: impl Peripheral

> + 'd, - cc_pull: CcPull, ) -> Self { T::enable_and_reset(); @@ -99,82 +92,24 @@ impl<'d, T: Instance> Ucpd<'d, T> { w.set_ucpden(true); }); - r.cr().write(|w| { - w.set_anamode(if cc_pull == CcPull::Sink { - Anamode::SINK - } else { - Anamode::SOURCE - }); - w.set_anasubmode(match cc_pull { - CcPull::SourceDefaultUsb => 1, - CcPull::Source1_5A => 2, - CcPull::Source3_0A => 3, - _ => 0, - }); - w.set_ccenable(if cc_pull != CcPull::SinkDeadBattery { - Ccenable::BOTH - } else { - Ccenable::DISABLED - }); - - // Make sure detector is enabled on both pins. - w.set_cc1tcdis(false); - w.set_cc2tcdis(false); - }); - - // Disable dead-battery pull-down resistors which are enabled by default on boot. - critical_section::with(|_| { - // TODO: other families - #[cfg(stm32g4)] - crate::pac::PWR - .cr3() - .modify(|w| w.set_ucpd1_dbdis(cc_pull != CcPull::SinkDeadBattery)); - }); - - into_ref!(peri); - Self { _peri: peri } + Self { + cc_phy: CcPhy { _lifetime: PhantomData }, + } } - /// Returns the current voltage level of CC1 and CC2 pin as tuple. - /// - /// Interpretation of the voltage levels depends on the configured CC line - /// pull-up/pull-down resistance. - pub fn cc_vstate(&self) -> (CcVState, CcVState) { - let sr = T::REGS.sr().read(); - (sr.typec_vstate_cc1(), sr.typec_vstate_cc2()) + /// Returns the TypeC CC PHY. + pub fn cc_phy(&mut self) -> &mut CcPhy<'d, T> { + &mut self.cc_phy } - /// Waits for a change in voltage state on either CC line. - pub async fn wait_for_cc_vstate_change(&self) -> (CcVState, CcVState) { - let _on_drop = OnDrop::new(|| self.enable_cc_interrupts(false)); - let prev_vstate = self.cc_vstate(); - poll_fn(|cx| { - let vstate = self.cc_vstate(); - if vstate != prev_vstate { - Poll::Ready(vstate) - } else { - T::waker().register(cx.waker()); - self.enable_cc_interrupts(true); - Poll::Pending - } - }) - .await - } - - fn enable_cc_interrupts(&self, enable: bool) { - T::REGS.imr().modify(|w| { - w.set_typecevt1ie(enable); - w.set_typecevt2ie(enable); - }); - } - - /// Returns PD receiver and transmitter. - pub fn pd_phy( - &mut self, + /// Splits the UCPD driver into a TypeC PHY to control and monitor CC voltage + /// and a Power Delivery (PD) PHY with receiver and transmitter. + pub fn split_pd_phy( + self, rx_dma: impl Peripheral

> + 'd, tx_dma: impl Peripheral

> + 'd, cc_sel: CcSel, - ) -> PdPhy<'_, T> { + ) -> (CcPhy<'d, T>, PdPhy<'d, T>) { let r = T::REGS; // TODO: Currently only SOP messages are supported. @@ -198,19 +133,115 @@ impl<'d, T: Instance> Ucpd<'d, T> { // Enable hard reset receive interrupt. r.imr().modify(|w| w.set_rxhrstdetie(true)); + // Both parts must be dropped before the peripheral can be disabled. + T::state().drop_not_ready.store(true, Ordering::Relaxed); + into_ref!(rx_dma, tx_dma); let rx_dma_req = rx_dma.request(); let tx_dma_req = tx_dma.request(); - PdPhy { - _ucpd: self, - rx_dma_ch: rx_dma.map_into(), - rx_dma_req, - tx_dma_ch: tx_dma.map_into(), - tx_dma_req, + ( + self.cc_phy, + PdPhy { + _lifetime: PhantomData, + rx_dma_ch: rx_dma.map_into(), + rx_dma_req, + tx_dma_ch: tx_dma.map_into(), + tx_dma_req, + }, + ) + } +} + +/// Control and monitoring of TypeC CC pin functionailty. +pub struct CcPhy<'d, T: Instance> { + _lifetime: PhantomData<&'d mut T>, +} + +impl<'d, T: Instance> Drop for CcPhy<'d, T> { + fn drop(&mut self) { + let r = T::REGS; + r.cr().modify(|w| { + w.set_cc1tcdis(true); + w.set_cc2tcdis(true); + w.set_ccenable(Ccenable::DISABLED); + }); + + // Check if the PdPhy part was dropped already. + let drop_not_ready = &T::state().drop_not_ready; + if drop_not_ready.load(Ordering::Relaxed) { + drop_not_ready.store(true, Ordering::Relaxed); + } else { + r.cfgr1().write(|w| w.set_ucpden(false)); } } } +impl<'d, T: Instance> CcPhy<'d, T> { + /// Sets the pull-up/pull-down resistor values exposed on the CC pins. + pub fn set_pull(&mut self, cc_pull: CcPull) { + T::REGS.cr().write(|w| { + w.set_anamode(if cc_pull == CcPull::Sink { + Anamode::SINK + } else { + Anamode::SOURCE + }); + w.set_anasubmode(match cc_pull { + CcPull::SourceDefaultUsb => 1, + CcPull::Source1_5A => 2, + CcPull::Source3_0A => 3, + _ => 0, + }); + w.set_ccenable(if cc_pull != CcPull::SinkDeadBattery { + Ccenable::BOTH + } else { + Ccenable::DISABLED + }); + }); + + // Disable dead-battery pull-down resistors which are enabled by default on boot. + critical_section::with(|_| { + // TODO: other families + #[cfg(stm32g4)] + crate::pac::PWR + .cr3() + .modify(|w| w.set_ucpd1_dbdis(cc_pull != CcPull::SinkDeadBattery)); + }); + } + + /// Returns the current voltage level of CC1 and CC2 pin as tuple. + /// + /// Interpretation of the voltage levels depends on the configured CC line + /// pull-up/pull-down resistance. + pub fn vstate(&self) -> (CcVState, CcVState) { + let sr = T::REGS.sr().read(); + (sr.typec_vstate_cc1(), sr.typec_vstate_cc2()) + } + + /// Waits for a change in voltage state on either CC line. + pub async fn wait_for_vstate_change(&self) -> (CcVState, CcVState) { + let _on_drop = OnDrop::new(|| self.enable_cc_interrupts(false)); + let prev_vstate = self.vstate(); + poll_fn(|cx| { + let vstate = self.vstate(); + if vstate != prev_vstate { + Poll::Ready(vstate) + } else { + T::state().waker.register(cx.waker()); + self.enable_cc_interrupts(true); + Poll::Pending + } + }) + .await + } + + fn enable_cc_interrupts(&self, enable: bool) { + T::REGS.imr().modify(|w| { + w.set_typecevt1ie(enable); + w.set_typecevt2ie(enable); + }); + } +} + /// Receive Error. #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -238,7 +269,7 @@ pub enum TxError { /// Power Delivery (PD) PHY. pub struct PdPhy<'d, T: Instance> { - _ucpd: &'d Ucpd<'d, T>, + _lifetime: PhantomData<&'d mut T>, rx_dma_ch: PeripheralRef<'d, AnyChannel>, rx_dma_req: Request, tx_dma_ch: PeripheralRef<'d, AnyChannel>, @@ -247,7 +278,16 @@ pub struct PdPhy<'d, T: Instance> { impl<'d, T: Instance> Drop for PdPhy<'d, T> { fn drop(&mut self) { - T::REGS.cr().modify(|w| w.set_phyrxen(false)); + let r = T::REGS; + r.cr().modify(|w| w.set_phyrxen(false)); + + // Check if the Type-C part was dropped already. + let drop_not_ready = &T::state().drop_not_ready; + if drop_not_ready.load(Ordering::Relaxed) { + drop_not_ready.store(true, Ordering::Relaxed); + } else { + r.cfgr1().write(|w| w.set_ucpden(false)); + } } } @@ -316,7 +356,7 @@ impl<'d, T: Instance> PdPhy<'d, T> { }); Poll::Ready(ret) } else { - T::waker().register(cx.waker()); + T::state().waker.register(cx.waker()); self.enable_rx_interrupt(true); Poll::Pending } @@ -383,7 +423,7 @@ impl<'d, T: Instance> PdPhy<'d, T> { } else if sr.txmsgsent() { Poll::Ready(Ok(())) } else { - T::waker().register(cx.waker()); + T::state().waker.register(cx.waker()); self.enable_tx_interrupts(true); Poll::Pending } @@ -427,7 +467,7 @@ impl<'d, T: Instance> PdPhy<'d, T> { } else if sr.hrstsent() { Poll::Ready(Ok(())) } else { - T::waker().register(cx.waker()); + T::state().waker.register(cx.waker()); self.enable_hardreset_interrupts(true); Poll::Pending } @@ -483,18 +523,37 @@ impl interrupt::typelevel::Handler for InterruptHandl } // Wake the task to clear and re-enabled interrupts. - T::waker().wake(); + T::state().waker.wake(); } } /// UCPD instance trait. pub trait Instance: sealed::Instance + RccPeripheral {} -pub(crate) mod sealed { +mod sealed { + use core::sync::atomic::AtomicBool; + + use embassy_sync::waitqueue::AtomicWaker; + + pub struct State { + pub waker: AtomicWaker, + // Inverted logic for a default state of 0 so that the data goes into the .bss section. + pub drop_not_ready: AtomicBool, + } + + impl State { + pub const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + drop_not_ready: AtomicBool::new(false), + } + } + } + pub trait Instance { type Interrupt: crate::interrupt::typelevel::Interrupt; const REGS: crate::pac::ucpd::Ucpd; - fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker; + fn state() -> &'static crate::ucpd::sealed::State; } } @@ -505,9 +564,9 @@ foreach_interrupt!( const REGS: crate::pac::ucpd::Ucpd = crate::pac::$inst; - fn waker() -> &'static AtomicWaker { - static WAKER: AtomicWaker = AtomicWaker::new(); - &WAKER + fn state() -> &'static crate::ucpd::sealed::State { + static STATE: crate::ucpd::sealed::State = crate::ucpd::sealed::State::new(); + &STATE } } diff --git a/examples/stm32g4/src/bin/usb_c_pd.rs b/examples/stm32g4/src/bin/usb_c_pd.rs index 14abd542f..0e80840df 100644 --- a/examples/stm32g4/src/bin/usb_c_pd.rs +++ b/examples/stm32g4/src/bin/usb_c_pd.rs @@ -3,7 +3,7 @@ use defmt::{error, info, Format}; use embassy_executor::Spawner; -use embassy_stm32::ucpd::{self, CcPull, CcSel, CcVState, Ucpd}; +use embassy_stm32::ucpd::{self, CcPhy, CcPull, CcSel, CcVState, Ucpd}; use embassy_stm32::Config; use embassy_time::{with_timeout, Duration}; use {defmt_rtt as _, panic_probe as _}; @@ -16,17 +16,17 @@ enum CableOrientation { } // Returns true when the cable -async fn wait_attached(ucpd: &mut Ucpd<'_, T>) -> CableOrientation { +async fn wait_attached(cc_phy: &mut CcPhy<'_, T>) -> CableOrientation { loop { - let (cc1, cc2) = ucpd.cc_vstate(); + let (cc1, cc2) = cc_phy.vstate(); if cc1 == CcVState::LOWEST && cc2 == CcVState::LOWEST { // Detached, wait until attached by monitoring the CC lines. - ucpd.wait_for_cc_vstate_change().await; + cc_phy.wait_for_vstate_change().await; continue; } // Attached, wait for CC lines to be stable for tCCDebounce (100..200ms). - if with_timeout(Duration::from_millis(100), ucpd.wait_for_cc_vstate_change()) + if with_timeout(Duration::from_millis(100), cc_phy.wait_for_vstate_change()) .await .is_ok() { @@ -50,10 +50,11 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); - let mut ucpd = Ucpd::new(p.UCPD1, p.PB6, p.PB4, CcPull::Sink); + let mut ucpd = Ucpd::new(p.UCPD1, p.PB6, p.PB4); + ucpd.cc_phy().set_pull(CcPull::Sink); info!("Waiting for USB connection..."); - let cable_orientation = wait_attached(&mut ucpd).await; + let cable_orientation = wait_attached(ucpd.cc_phy()).await; info!("USB cable connected, orientation: {}", cable_orientation); let cc_sel = match cable_orientation { @@ -67,7 +68,7 @@ async fn main(_spawner: Spawner) { } CableOrientation::DebugAccessoryMode => panic!("No PD communication in DAM"), }; - let mut pd_phy = ucpd.pd_phy(p.DMA1_CH1, p.DMA1_CH2, cc_sel); + let (_cc_phy, mut pd_phy) = ucpd.split_pd_phy(p.DMA1_CH1, p.DMA1_CH2, cc_sel); loop { // Enough space for the longest non-extended data message. From eeb033caf06cbb3a1bc2d6804524097c61c6c793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Sat, 9 Mar 2024 15:52:20 +0100 Subject: [PATCH 14/22] [UCPD] Disable RCC clock on drop --- embassy-stm32/src/ucpd.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index 2120716ef..4b8124554 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -172,6 +172,7 @@ impl<'d, T: Instance> Drop for CcPhy<'d, T> { drop_not_ready.store(true, Ordering::Relaxed); } else { r.cfgr1().write(|w| w.set_ucpden(false)); + T::disable(); } } } @@ -287,6 +288,7 @@ impl<'d, T: Instance> Drop for PdPhy<'d, T> { drop_not_ready.store(true, Ordering::Relaxed); } else { r.cfgr1().write(|w| w.set_ucpden(false)); + T::disable(); } } } From 30cdc6c9c53837e91f92d24e9861b525f54bc65b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kr=C3=B6ger?= Date: Tue, 12 Mar 2024 08:47:08 +0100 Subject: [PATCH 15/22] [UCPD] Disable dead-battery resistor for all families Using the code from PR #2683, thank you @ExplodingWaffle Removes the dead-battery as selectable option because its unclear if it can be re-enabled. Also there is no use case for it because the same resistor can be configured with the sink option. --- embassy-stm32/src/lib.rs | 41 +-------------------------- embassy-stm32/src/ucpd.rs | 58 +++++++++++++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 52 deletions(-) diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 361dc6f53..b38b5c29d 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -283,9 +283,8 @@ pub fn init(config: Config) -> Peripherals { } unsafe { - // TODO: refactor into mod ucpd #[cfg(ucpd)] - ucpd_init( + ucpd::init( cs, #[cfg(peri_ucpd1)] config.enable_ucpd1_dead_battery, @@ -334,41 +333,3 @@ pub fn init(config: Config) -> Peripherals { p }) } - -#[cfg(ucpd)] -/// Safety: must only be called when all UCPDs are disabled (e.g. at startup) -unsafe fn ucpd_init( - _cs: critical_section::CriticalSection, - #[cfg(peri_ucpd1)] ucpd1_db_enable: bool, - #[cfg(peri_ucpd2)] ucpd2_db_enable: bool, -) { - #[cfg(stm32g0x1)] - { - // according to RM0444 (STM32G0x1) section 8.1.1: - // when UCPD is disabled setting the strobe will disable dead battery - // (which is enabled after reset) but if UCPD is enabled, setting the - // strobe will apply the CC pin configuration from the control register - // (which is why we need to be careful about when we call this) - crate::pac::SYSCFG.cfgr1().modify(|w| { - w.set_ucpd1_strobe(ucpd1_db_enable); - w.set_ucpd2_strobe(ucpd2_db_enable); - }); - } - - #[cfg(any(stm32g4, stm32l5))] - { - crate::pac::PWR.cr3().modify(|w| { - #[cfg(stm32g4)] - w.set_ucpd1_dbdis(!ucpd1_db_enable); - #[cfg(stm32l5)] - w.set_ucpd_dbdis(!ucpd1_db_enable); - }) - } - - #[cfg(any(stm32h5, stm32u5))] - { - crate::pac::PWR.ucpdr().modify(|w| { - w.set_ucpd_dbdis(!ucpd1_db_enable); - }) - } -} diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index 4b8124554..51cfe4a24 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -28,6 +28,42 @@ use crate::pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk, Txmode}; pub use crate::pac::ucpd::vals::{Phyccsel as CcSel, TypecVstateCc as CcVState}; use crate::rcc::RccPeripheral; +pub(crate) fn init( + _cs: critical_section::CriticalSection, + #[cfg(peri_ucpd1)] ucpd1_db_enable: bool, + #[cfg(peri_ucpd2)] ucpd2_db_enable: bool, +) { + #[cfg(stm32g0x1)] + { + // according to RM0444 (STM32G0x1) section 8.1.1: + // when UCPD is disabled setting the strobe will disable dead battery + // (which is enabled after reset) but if UCPD is enabled, setting the + // strobe will apply the CC pin configuration from the control register + // (which is why we need to be careful about when we call this) + crate::pac::SYSCFG.cfgr1().modify(|w| { + w.set_ucpd1_strobe(ucpd1_db_enable); + w.set_ucpd2_strobe(ucpd2_db_enable); + }); + } + + #[cfg(any(stm32g4, stm32l5))] + { + crate::pac::PWR.cr3().modify(|w| { + #[cfg(stm32g4)] + w.set_ucpd1_dbdis(!ucpd1_db_enable); + #[cfg(stm32l5)] + w.set_ucpd_dbdis(!ucpd1_db_enable); + }) + } + + #[cfg(any(stm32h5, stm32u5))] + { + crate::pac::PWR.ucpdr().modify(|w| { + w.set_ucpd_dbdis(!ucpd1_db_enable); + }) + } +} + /// Pull-up or Pull-down resistor state of both CC lines. #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -35,9 +71,6 @@ pub enum CcPull { /// Analog PHY for CC pin disabled. Disabled, - /// Rd=5.1k pull-down resistor enabled when the corresponding DBCC pin is high. - SinkDeadBattery, - /// Rd=5.1k pull-down resistor. Sink, @@ -192,20 +225,21 @@ impl<'d, T: Instance> CcPhy<'d, T> { CcPull::Source3_0A => 3, _ => 0, }); - w.set_ccenable(if cc_pull != CcPull::SinkDeadBattery { - Ccenable::BOTH - } else { + w.set_ccenable(if cc_pull == CcPull::Disabled { Ccenable::DISABLED + } else { + Ccenable::BOTH }); }); // Disable dead-battery pull-down resistors which are enabled by default on boot. - critical_section::with(|_| { - // TODO: other families - #[cfg(stm32g4)] - crate::pac::PWR - .cr3() - .modify(|w| w.set_ucpd1_dbdis(cc_pull != CcPull::SinkDeadBattery)); + critical_section::with(|cs| { + init( + cs, + false, + #[cfg(peri_ucpd2)] + false, + ); }); } From e95e95ac7a00ca014b23f6ac57ecffce7fc6ffa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Tue, 12 Mar 2024 20:38:37 +0100 Subject: [PATCH 16/22] [UCPD] Take interrupt in constructor and enable it --- embassy-stm32/src/ucpd.rs | 6 ++++++ examples/stm32g4/src/bin/usb_c_pd.rs | 8 ++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index 51cfe4a24..b7af877b7 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -24,6 +24,7 @@ use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; use crate::dma::{AnyChannel, Request, Transfer, TransferOptions}; use crate::interrupt; +use crate::interrupt::typelevel::Interrupt; use crate::pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk, Txmode}; pub use crate::pac::ucpd::vals::{Phyccsel as CcSel, TypecVstateCc as CcVState}; use crate::rcc::RccPeripheral; @@ -93,10 +94,13 @@ impl<'d, T: Instance> Ucpd<'d, T> { /// Creates a new UCPD driver instance. pub fn new( _peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, _cc1: impl Peripheral

> + 'd, _cc2: impl Peripheral

> + 'd, ) -> Self { T::enable_and_reset(); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; let r = T::REGS; r.cfgr1().write(|w| { @@ -206,6 +210,7 @@ impl<'d, T: Instance> Drop for CcPhy<'d, T> { } else { r.cfgr1().write(|w| w.set_ucpden(false)); T::disable(); + T::Interrupt::disable(); } } } @@ -323,6 +328,7 @@ impl<'d, T: Instance> Drop for PdPhy<'d, T> { } else { r.cfgr1().write(|w| w.set_ucpden(false)); T::disable(); + T::Interrupt::disable(); } } } diff --git a/examples/stm32g4/src/bin/usb_c_pd.rs b/examples/stm32g4/src/bin/usb_c_pd.rs index 0e80840df..c850b2753 100644 --- a/examples/stm32g4/src/bin/usb_c_pd.rs +++ b/examples/stm32g4/src/bin/usb_c_pd.rs @@ -4,10 +4,14 @@ use defmt::{error, info, Format}; use embassy_executor::Spawner; use embassy_stm32::ucpd::{self, CcPhy, CcPull, CcSel, CcVState, Ucpd}; -use embassy_stm32::Config; +use embassy_stm32::{bind_interrupts, peripherals, Config}; use embassy_time::{with_timeout, Duration}; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + UCPD1 => ucpd::InterruptHandler; +}); + #[derive(Debug, Format)] enum CableOrientation { Normal, @@ -50,7 +54,7 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); - let mut ucpd = Ucpd::new(p.UCPD1, p.PB6, p.PB4); + let mut ucpd = Ucpd::new(p.UCPD1, Irqs {}, p.PB6, p.PB4); ucpd.cc_phy().set_pull(CcPull::Sink); info!("Waiting for USB connection..."); From 6e5bb8003acf98e20592dd1e86fc1a5f1928e14f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Tue, 12 Mar 2024 20:40:00 +0100 Subject: [PATCH 17/22] [UCPD] Adjust TX clock divider --- embassy-stm32/src/ucpd.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index b7af877b7..01cf42672 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -112,10 +112,8 @@ impl<'d, T: Instance> Ucpd<'d, T> { // Prescaler to produce a target half-bit frequency of 600kHz which is required // to produce transmit with a nominal nominal bit rate of 300Kbps+-10% using // biphase mark coding (BMC, aka differential manchester coding). - // A divider of 13 gives the target frequency closest to spec (~615kHz, 1.625us) - // but we go with the (hopefully well tested) default value used by the Cube HAL - // which is 14 divides the clock down to ~571kHz, 1.75us. - w.set_hbitclkdiv(14 - 1); + // A divider of 13 gives the target frequency closest to spec (~615kHz, 1.625us). + w.set_hbitclkdiv(13 - 1); // Time window for detecting non-idle (12-20us). // 1.75us * 8 = 14us. From b634f8f5111001a51a8a80559aeab11e8209d65f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Tue, 12 Mar 2024 20:40:47 +0100 Subject: [PATCH 18/22] [UCPD] Fix hard reset interrupt disable flags --- embassy-stm32/src/ucpd.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index 01cf42672..54d4bb132 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -484,8 +484,8 @@ impl<'d, T: Instance> PdPhy<'d, T> { // Clear the hardreset interrupt flags. T::REGS.icr().write(|w| { - w.set_txmsgdisccf(true); - w.set_txmsgsentcf(true); + w.set_hrstdisccf(true); + w.set_hrstsentcf(true); }); // Trigger hard reset transmission. From 88d1d38be787ff7e742dfde53cf8b527a6a07dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Tue, 12 Mar 2024 22:43:36 +0100 Subject: [PATCH 19/22] [UCPD] RXORDSETEN can only be modified when disabled --- embassy-stm32/src/ucpd.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index 54d4bb132..d251b1c72 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -124,6 +124,14 @@ impl<'d, T: Instance> Ucpd<'d, T> { // 1.75us * 17 = ~30us w.set_ifrgap(17 - 1); + // TODO: Currently only hard reset and SOP messages can be received. + // UNDOCUMENTED: This register can only be written while UCPDEN=0 (found by testing). + w.set_rxordseten(0b1001); + + // Enable DMA + w.set_txdmaen(true); + w.set_rxdmaen(true); + w.set_ucpden(true); }); @@ -150,15 +158,6 @@ impl<'d, T: Instance> Ucpd<'d, T> { // TODO: Currently only SOP messages are supported. r.tx_ordsetr().write(|w| w.set_txordset(0b10001_11000_11000_11000)); - r.cfgr1().modify(|w| { - // TODO: Currently only hard reset and SOP messages can be received. - w.set_rxordseten(0b1001); - - // Enable DMA - w.set_txdmaen(true); - w.set_rxdmaen(true); - }); - // Enable the receiver on one of the two CC lines. r.cr().modify(|w| { w.set_phyccsel(cc_sel); @@ -216,7 +215,7 @@ impl<'d, T: Instance> Drop for CcPhy<'d, T> { impl<'d, T: Instance> CcPhy<'d, T> { /// Sets the pull-up/pull-down resistor values exposed on the CC pins. pub fn set_pull(&mut self, cc_pull: CcPull) { - T::REGS.cr().write(|w| { + T::REGS.cr().modify(|w| { w.set_anamode(if cc_pull == CcPull::Sink { Anamode::SINK } else { From 62b0410e865044080781765d9ccee06202a82dff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Wed, 13 Mar 2024 17:45:15 +0100 Subject: [PATCH 20/22] [UCPD] Set CC pins to analog mode Example: On STM32G431 CC2 has a pull-up (default JTAG signal) which needs to be disabled. --- embassy-stm32/src/ucpd.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index d251b1c72..dcc4454d3 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -95,9 +95,13 @@ impl<'d, T: Instance> Ucpd<'d, T> { pub fn new( _peri: impl Peripheral

+ 'd, _irq: impl interrupt::typelevel::Binding> + 'd, - _cc1: impl Peripheral

> + 'd, - _cc2: impl Peripheral

> + 'd, + cc1: impl Peripheral

> + 'd, + cc2: impl Peripheral

> + 'd, ) -> Self { + into_ref!(cc1, cc2); + cc1.set_as_analog(); + cc2.set_as_analog(); + T::enable_and_reset(); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; From 57ca072dc3807a868415d91b39f814c30c2e844b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Thu, 14 Mar 2024 22:05:22 +0100 Subject: [PATCH 21/22] [UCPD] Enable RX PHY only when receiving --- embassy-stm32/src/ucpd.rs | 61 +++++++++++++++------------------------ 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index dcc4454d3..2d119c973 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -163,10 +163,7 @@ impl<'d, T: Instance> Ucpd<'d, T> { r.tx_ordsetr().write(|w| w.set_txordset(0b10001_11000_11000_11000)); // Enable the receiver on one of the two CC lines. - r.cr().modify(|w| { - w.set_phyccsel(cc_sel); - w.set_phyrxen(true); - }); + r.cr().modify(|w| w.set_phyccsel(cc_sel)); // Enable hard reset receive interrupt. r.imr().modify(|w| w.set_rxhrstdetie(true)); @@ -319,15 +316,12 @@ pub struct PdPhy<'d, T: Instance> { impl<'d, T: Instance> Drop for PdPhy<'d, T> { fn drop(&mut self) { - let r = T::REGS; - r.cr().modify(|w| w.set_phyrxen(false)); - // Check if the Type-C part was dropped already. let drop_not_ready = &T::state().drop_not_ready; if drop_not_ready.load(Ordering::Relaxed) { drop_not_ready.store(true, Ordering::Relaxed); } else { - r.cfgr1().write(|w| w.set_ucpden(false)); + T::REGS.cfgr1().write(|w| w.set_ucpden(false)); T::disable(); T::Interrupt::disable(); } @@ -341,16 +335,6 @@ impl<'d, T: Instance> PdPhy<'d, T> { pub async fn receive(&mut self, buf: &mut [u8]) -> Result { 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() { - if let Err(RxError::HardReset) = self.wait_rx_done().await { - return Err(RxError::HardReset); - } - 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 { Transfer::new_read( &self.rx_dma_ch, @@ -361,22 +345,20 @@ impl<'d, T: Instance> PdPhy<'d, T> { ) }; - self.wait_rx_done().await?; + // Clear interrupt flags (possibly set from last receive). + r.icr().write(|w| { + w.set_rxorddetcf(true); + w.set_rxovrcf(true); + w.set_rxmsgendcf(true); + }); - // 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); - } - } + r.cr().modify(|w| w.set_phyrxen(true)); + let _on_drop = OnDrop::new(|| { + r.cr().modify(|w| w.set_phyrxen(false)); + self.enable_rx_interrupt(false); + }); - Ok(r.rx_payszr().read().rxpaysz().into()) - } - - async fn wait_rx_done(&self) -> Result<(), RxError> { - let _on_drop = OnDrop::new(|| self.enable_rx_interrupt(false)); poll_fn(|cx| { - let r = T::REGS; let sr = r.sr().read(); if sr.rxhrstdet() { // Clean and re-enable hard reset receive interrupt. @@ -391,12 +373,6 @@ impl<'d, T: Instance> PdPhy<'d, T> { } 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 { T::state().waker.register(cx.waker()); @@ -404,7 +380,16 @@ impl<'d, T: Instance> PdPhy<'d, T> { Poll::Pending } }) - .await + .await?; + + // Make sure that the last 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()) } fn enable_rx_interrupt(&self, enable: bool) { From 7b80de5e3db69a3d348bc74f6294e8642a11785e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kro=CC=88ger?= Date: Thu, 14 Mar 2024 22:14:20 +0100 Subject: [PATCH 22/22] [UCPD] Enable dead-battery support in example --- examples/stm32g4/src/bin/usb_c_pd.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/stm32g4/src/bin/usb_c_pd.rs b/examples/stm32g4/src/bin/usb_c_pd.rs index c850b2753..7caea634f 100644 --- a/examples/stm32g4/src/bin/usb_c_pd.rs +++ b/examples/stm32g4/src/bin/usb_c_pd.rs @@ -49,8 +49,9 @@ async fn wait_attached(cc_phy: &mut CcPhy<'_, T>) -> CableOri #[embassy_executor::main] async fn main(_spawner: Spawner) { - // TODO: Disable DBCC pin functionality by default but have flag in the config to keep it enabled when required. - let p = embassy_stm32::init(Config::default()); + let mut config = Config::default(); + config.enable_ucpd1_dead_battery = true; + let p = embassy_stm32::init(config); info!("Hello World!");