diff options
| author | Dario Nieuwenhuis <[email protected]> | 2024-03-14 21:21:33 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-03-14 21:21:33 +0000 |
| commit | 963fda240484e0105408185f955703e2c02f9187 (patch) | |
| tree | 93cd3d8bfbbeb5a584eb57e010ca9e70f7ce20b8 | |
| parent | 23e8fd8337df379fd22df5839a5b7cde3892eb47 (diff) | |
| parent | 7b80de5e3db69a3d348bc74f6294e8642a11785e (diff) | |
Merge pull request #2652 from timokroeger/stm32-ucpd
STM32 USB Type-C/USB Power Delivery Interface (UCPD)
| -rw-r--r-- | embassy-stm32/build.rs | 4 | ||||
| -rw-r--r-- | embassy-stm32/src/lib.rs | 43 | ||||
| -rw-r--r-- | embassy-stm32/src/ucpd.rs | 609 | ||||
| -rw-r--r-- | examples/stm32g4/src/bin/usb_c_pd.rs | 86 |
4 files changed, 702 insertions, 40 deletions
diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 70f4515db..40641a506 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs | |||
| @@ -764,6 +764,8 @@ fn main() { | |||
| 764 | #[rustfmt::skip] | 764 | #[rustfmt::skip] |
| 765 | let signals: HashMap<_, _> = [ | 765 | let signals: HashMap<_, _> = [ |
| 766 | // (kind, signal) => trait | 766 | // (kind, signal) => trait |
| 767 | (("ucpd", "CC1"), quote!(crate::ucpd::Cc1Pin)), | ||
| 768 | (("ucpd", "CC2"), quote!(crate::ucpd::Cc2Pin)), | ||
| 767 | (("usart", "TX"), quote!(crate::usart::TxPin)), | 769 | (("usart", "TX"), quote!(crate::usart::TxPin)), |
| 768 | (("usart", "RX"), quote!(crate::usart::RxPin)), | 770 | (("usart", "RX"), quote!(crate::usart::RxPin)), |
| 769 | (("usart", "CTS"), quote!(crate::usart::CtsPin)), | 771 | (("usart", "CTS"), quote!(crate::usart::CtsPin)), |
| @@ -1102,6 +1104,8 @@ fn main() { | |||
| 1102 | 1104 | ||
| 1103 | let signals: HashMap<_, _> = [ | 1105 | let signals: HashMap<_, _> = [ |
| 1104 | // (kind, signal) => trait | 1106 | // (kind, signal) => trait |
| 1107 | (("ucpd", "RX"), quote!(crate::ucpd::RxDma)), | ||
| 1108 | (("ucpd", "TX"), quote!(crate::ucpd::TxDma)), | ||
| 1105 | (("usart", "RX"), quote!(crate::usart::RxDma)), | 1109 | (("usart", "RX"), quote!(crate::usart::RxDma)), |
| 1106 | (("usart", "TX"), quote!(crate::usart::TxDma)), | 1110 | (("usart", "TX"), quote!(crate::usart::TxDma)), |
| 1107 | (("lpuart", "RX"), quote!(crate::usart::RxDma)), | 1111 | (("lpuart", "RX"), quote!(crate::usart::RxDma)), |
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index b548a0343..b38b5c29d 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs | |||
| @@ -73,6 +73,8 @@ pub mod sai; | |||
| 73 | pub mod sdmmc; | 73 | pub mod sdmmc; |
| 74 | #[cfg(spi)] | 74 | #[cfg(spi)] |
| 75 | pub mod spi; | 75 | pub mod spi; |
| 76 | #[cfg(ucpd)] | ||
| 77 | pub mod ucpd; | ||
| 76 | #[cfg(uid)] | 78 | #[cfg(uid)] |
| 77 | pub mod uid; | 79 | pub mod uid; |
| 78 | #[cfg(usart)] | 80 | #[cfg(usart)] |
| @@ -281,9 +283,8 @@ pub fn init(config: Config) -> Peripherals { | |||
| 281 | } | 283 | } |
| 282 | 284 | ||
| 283 | unsafe { | 285 | unsafe { |
| 284 | // TODO: refactor into mod ucpd | ||
| 285 | #[cfg(ucpd)] | 286 | #[cfg(ucpd)] |
| 286 | ucpd_init( | 287 | ucpd::init( |
| 287 | cs, | 288 | cs, |
| 288 | #[cfg(peri_ucpd1)] | 289 | #[cfg(peri_ucpd1)] |
| 289 | config.enable_ucpd1_dead_battery, | 290 | config.enable_ucpd1_dead_battery, |
| @@ -332,41 +333,3 @@ pub fn init(config: Config) -> Peripherals { | |||
| 332 | p | 333 | p |
| 333 | }) | 334 | }) |
| 334 | } | 335 | } |
| 335 | |||
| 336 | #[cfg(ucpd)] | ||
| 337 | /// Safety: must only be called when all UCPDs are disabled (e.g. at startup) | ||
| 338 | unsafe fn ucpd_init( | ||
| 339 | _cs: critical_section::CriticalSection, | ||
| 340 | #[cfg(peri_ucpd1)] ucpd1_db_enable: bool, | ||
| 341 | #[cfg(peri_ucpd2)] ucpd2_db_enable: bool, | ||
| 342 | ) { | ||
| 343 | #[cfg(stm32g0x1)] | ||
| 344 | { | ||
| 345 | // according to RM0444 (STM32G0x1) section 8.1.1: | ||
| 346 | // when UCPD is disabled setting the strobe will disable dead battery | ||
| 347 | // (which is enabled after reset) but if UCPD is enabled, setting the | ||
| 348 | // strobe will apply the CC pin configuration from the control register | ||
| 349 | // (which is why we need to be careful about when we call this) | ||
| 350 | crate::pac::SYSCFG.cfgr1().modify(|w| { | ||
| 351 | w.set_ucpd1_strobe(ucpd1_db_enable); | ||
| 352 | w.set_ucpd2_strobe(ucpd2_db_enable); | ||
| 353 | }); | ||
| 354 | } | ||
| 355 | |||
| 356 | #[cfg(any(stm32g4, stm32l5))] | ||
| 357 | { | ||
| 358 | crate::pac::PWR.cr3().modify(|w| { | ||
| 359 | #[cfg(stm32g4)] | ||
| 360 | w.set_ucpd1_dbdis(!ucpd1_db_enable); | ||
| 361 | #[cfg(stm32l5)] | ||
| 362 | w.set_ucpd_dbdis(!ucpd1_db_enable); | ||
| 363 | }) | ||
| 364 | } | ||
| 365 | |||
| 366 | #[cfg(any(stm32h5, stm32u5))] | ||
| 367 | { | ||
| 368 | crate::pac::PWR.ucpdr().modify(|w| { | ||
| 369 | w.set_ucpd_dbdis(!ucpd1_db_enable); | ||
| 370 | }) | ||
| 371 | } | ||
| 372 | } | ||
diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs new file mode 100644 index 000000000..2d119c973 --- /dev/null +++ b/embassy-stm32/src/ucpd.rs | |||
| @@ -0,0 +1,609 @@ | |||
| 1 | //! USB Type-C/USB Power Delivery Interface (UCPD) | ||
| 2 | |||
| 3 | // Implementation Notes | ||
| 4 | // | ||
| 5 | // As of Feb. 2024 the UCPD peripheral is availalbe on: G0, G4, H5, L5, U5 | ||
| 6 | // | ||
| 7 | // Cube HAL LL Driver (g0): | ||
| 8 | // https://github.com/STMicroelectronics/stm32g0xx_hal_driver/blob/v1.4.6/Inc/stm32g0xx_ll_ucpd.h | ||
| 9 | // https://github.com/STMicroelectronics/stm32g0xx_hal_driver/blob/v1.4.6/Src/stm32g0xx_ll_ucpd.c | ||
| 10 | // Except for a the `LL_UCPD_RxAnalogFilterEnable/Disable()` functions the Cube HAL implementation of | ||
| 11 | // all families is the same. | ||
| 12 | // | ||
| 13 | // Dead battery pull-down resistors functionality is enabled by default on startup and must | ||
| 14 | // be disabled by setting a bit in PWR/SYSCFG registers. The exact name and location for that | ||
| 15 | // bit is different for each familily. | ||
| 16 | |||
| 17 | use core::future::poll_fn; | ||
| 18 | use core::marker::PhantomData; | ||
| 19 | use core::sync::atomic::Ordering; | ||
| 20 | use core::task::Poll; | ||
| 21 | |||
| 22 | use embassy_hal_internal::drop::OnDrop; | ||
| 23 | use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; | ||
| 24 | |||
| 25 | use crate::dma::{AnyChannel, Request, Transfer, TransferOptions}; | ||
| 26 | use crate::interrupt; | ||
| 27 | use crate::interrupt::typelevel::Interrupt; | ||
| 28 | use crate::pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk, Txmode}; | ||
| 29 | pub use crate::pac::ucpd::vals::{Phyccsel as CcSel, TypecVstateCc as CcVState}; | ||
| 30 | use crate::rcc::RccPeripheral; | ||
| 31 | |||
| 32 | pub(crate) fn init( | ||
| 33 | _cs: critical_section::CriticalSection, | ||
| 34 | #[cfg(peri_ucpd1)] ucpd1_db_enable: bool, | ||
| 35 | #[cfg(peri_ucpd2)] ucpd2_db_enable: bool, | ||
| 36 | ) { | ||
| 37 | #[cfg(stm32g0x1)] | ||
| 38 | { | ||
| 39 | // according to RM0444 (STM32G0x1) section 8.1.1: | ||
| 40 | // when UCPD is disabled setting the strobe will disable dead battery | ||
| 41 | // (which is enabled after reset) but if UCPD is enabled, setting the | ||
| 42 | // strobe will apply the CC pin configuration from the control register | ||
| 43 | // (which is why we need to be careful about when we call this) | ||
| 44 | crate::pac::SYSCFG.cfgr1().modify(|w| { | ||
| 45 | w.set_ucpd1_strobe(ucpd1_db_enable); | ||
| 46 | w.set_ucpd2_strobe(ucpd2_db_enable); | ||
| 47 | }); | ||
| 48 | } | ||
| 49 | |||
| 50 | #[cfg(any(stm32g4, stm32l5))] | ||
| 51 | { | ||
| 52 | crate::pac::PWR.cr3().modify(|w| { | ||
| 53 | #[cfg(stm32g4)] | ||
| 54 | w.set_ucpd1_dbdis(!ucpd1_db_enable); | ||
| 55 | #[cfg(stm32l5)] | ||
| 56 | w.set_ucpd_dbdis(!ucpd1_db_enable); | ||
| 57 | }) | ||
| 58 | } | ||
| 59 | |||
| 60 | #[cfg(any(stm32h5, stm32u5))] | ||
| 61 | { | ||
| 62 | crate::pac::PWR.ucpdr().modify(|w| { | ||
| 63 | w.set_ucpd_dbdis(!ucpd1_db_enable); | ||
| 64 | }) | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | /// Pull-up or Pull-down resistor state of both CC lines. | ||
| 69 | #[derive(Debug, Clone, Copy, PartialEq)] | ||
| 70 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 71 | pub enum CcPull { | ||
| 72 | /// Analog PHY for CC pin disabled. | ||
| 73 | Disabled, | ||
| 74 | |||
| 75 | /// Rd=5.1k pull-down resistor. | ||
| 76 | Sink, | ||
| 77 | |||
| 78 | /// Rp=56k pull-up resistor to indicate default USB power. | ||
| 79 | SourceDefaultUsb, | ||
| 80 | |||
| 81 | /// Rp=22k pull-up resistor to indicate support for up to 1.5A. | ||
| 82 | Source1_5A, | ||
| 83 | |||
| 84 | /// Rp=10k pull-up resistor to indicate support for up to 3.0A. | ||
| 85 | Source3_0A, | ||
| 86 | } | ||
| 87 | |||
| 88 | /// UCPD driver. | ||
| 89 | pub struct Ucpd<'d, T: Instance> { | ||
| 90 | cc_phy: CcPhy<'d, T>, | ||
| 91 | } | ||
| 92 | |||
| 93 | impl<'d, T: Instance> Ucpd<'d, T> { | ||
| 94 | /// Creates a new UCPD driver instance. | ||
| 95 | pub fn new( | ||
| 96 | _peri: impl Peripheral<P = T> + 'd, | ||
| 97 | _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd, | ||
| 98 | cc1: impl Peripheral<P = impl Cc1Pin<T>> + 'd, | ||
| 99 | cc2: impl Peripheral<P = impl Cc2Pin<T>> + 'd, | ||
| 100 | ) -> Self { | ||
| 101 | into_ref!(cc1, cc2); | ||
| 102 | cc1.set_as_analog(); | ||
| 103 | cc2.set_as_analog(); | ||
| 104 | |||
| 105 | T::enable_and_reset(); | ||
| 106 | T::Interrupt::unpend(); | ||
| 107 | unsafe { T::Interrupt::enable() }; | ||
| 108 | |||
| 109 | let r = T::REGS; | ||
| 110 | r.cfgr1().write(|w| { | ||
| 111 | // "The receiver is designed to work in the clock frequency range from 6 to 18 MHz. | ||
| 112 | // However, the optimum performance is ensured in the range from 6 to 12 MHz" | ||
| 113 | // UCPD is driven by HSI16 (16MHz internal oscillator), which we need to divide by 2. | ||
| 114 | w.set_psc_usbpdclk(PscUsbpdclk::DIV2); | ||
| 115 | |||
| 116 | // Prescaler to produce a target half-bit frequency of 600kHz which is required | ||
| 117 | // to produce transmit with a nominal nominal bit rate of 300Kbps+-10% using | ||
| 118 | // biphase mark coding (BMC, aka differential manchester coding). | ||
| 119 | // A divider of 13 gives the target frequency closest to spec (~615kHz, 1.625us). | ||
| 120 | w.set_hbitclkdiv(13 - 1); | ||
| 121 | |||
| 122 | // Time window for detecting non-idle (12-20us). | ||
| 123 | // 1.75us * 8 = 14us. | ||
| 124 | w.set_transwin(8 - 1); | ||
| 125 | |||
| 126 | // Time from the end of last bit of a Frame until the start of the first bit of the | ||
| 127 | // next Preamble (min 25us). | ||
| 128 | // 1.75us * 17 = ~30us | ||
| 129 | w.set_ifrgap(17 - 1); | ||
| 130 | |||
| 131 | // TODO: Currently only hard reset and SOP messages can be received. | ||
| 132 | // UNDOCUMENTED: This register can only be written while UCPDEN=0 (found by testing). | ||
| 133 | w.set_rxordseten(0b1001); | ||
| 134 | |||
| 135 | // Enable DMA | ||
| 136 | w.set_txdmaen(true); | ||
| 137 | w.set_rxdmaen(true); | ||
| 138 | |||
| 139 | w.set_ucpden(true); | ||
| 140 | }); | ||
| 141 | |||
| 142 | Self { | ||
| 143 | cc_phy: CcPhy { _lifetime: PhantomData }, | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | /// Returns the TypeC CC PHY. | ||
| 148 | pub fn cc_phy(&mut self) -> &mut CcPhy<'d, T> { | ||
| 149 | &mut self.cc_phy | ||
| 150 | } | ||
| 151 | |||
| 152 | /// Splits the UCPD driver into a TypeC PHY to control and monitor CC voltage | ||
| 153 | /// and a Power Delivery (PD) PHY with receiver and transmitter. | ||
| 154 | pub fn split_pd_phy( | ||
| 155 | self, | ||
| 156 | rx_dma: impl Peripheral<P = impl RxDma<T>> + 'd, | ||
| 157 | tx_dma: impl Peripheral<P = impl TxDma<T>> + 'd, | ||
| 158 | cc_sel: CcSel, | ||
| 159 | ) -> (CcPhy<'d, T>, PdPhy<'d, T>) { | ||
| 160 | let r = T::REGS; | ||
| 161 | |||
| 162 | // TODO: Currently only SOP messages are supported. | ||
| 163 | r.tx_ordsetr().write(|w| w.set_txordset(0b10001_11000_11000_11000)); | ||
| 164 | |||
| 165 | // Enable the receiver on one of the two CC lines. | ||
| 166 | r.cr().modify(|w| w.set_phyccsel(cc_sel)); | ||
| 167 | |||
| 168 | // Enable hard reset receive interrupt. | ||
| 169 | r.imr().modify(|w| w.set_rxhrstdetie(true)); | ||
| 170 | |||
| 171 | // Both parts must be dropped before the peripheral can be disabled. | ||
| 172 | T::state().drop_not_ready.store(true, Ordering::Relaxed); | ||
| 173 | |||
| 174 | into_ref!(rx_dma, tx_dma); | ||
| 175 | let rx_dma_req = rx_dma.request(); | ||
| 176 | let tx_dma_req = tx_dma.request(); | ||
| 177 | ( | ||
| 178 | self.cc_phy, | ||
| 179 | PdPhy { | ||
| 180 | _lifetime: PhantomData, | ||
| 181 | rx_dma_ch: rx_dma.map_into(), | ||
| 182 | rx_dma_req, | ||
| 183 | tx_dma_ch: tx_dma.map_into(), | ||
| 184 | tx_dma_req, | ||
| 185 | }, | ||
| 186 | ) | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | /// Control and monitoring of TypeC CC pin functionailty. | ||
| 191 | pub struct CcPhy<'d, T: Instance> { | ||
| 192 | _lifetime: PhantomData<&'d mut T>, | ||
| 193 | } | ||
| 194 | |||
| 195 | impl<'d, T: Instance> Drop for CcPhy<'d, T> { | ||
| 196 | fn drop(&mut self) { | ||
| 197 | let r = T::REGS; | ||
| 198 | r.cr().modify(|w| { | ||
| 199 | w.set_cc1tcdis(true); | ||
| 200 | w.set_cc2tcdis(true); | ||
| 201 | w.set_ccenable(Ccenable::DISABLED); | ||
| 202 | }); | ||
| 203 | |||
| 204 | // Check if the PdPhy part was dropped already. | ||
| 205 | let drop_not_ready = &T::state().drop_not_ready; | ||
| 206 | if drop_not_ready.load(Ordering::Relaxed) { | ||
| 207 | drop_not_ready.store(true, Ordering::Relaxed); | ||
| 208 | } else { | ||
| 209 | r.cfgr1().write(|w| w.set_ucpden(false)); | ||
| 210 | T::disable(); | ||
| 211 | T::Interrupt::disable(); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | impl<'d, T: Instance> CcPhy<'d, T> { | ||
| 217 | /// Sets the pull-up/pull-down resistor values exposed on the CC pins. | ||
| 218 | pub fn set_pull(&mut self, cc_pull: CcPull) { | ||
| 219 | T::REGS.cr().modify(|w| { | ||
| 220 | w.set_anamode(if cc_pull == CcPull::Sink { | ||
| 221 | Anamode::SINK | ||
| 222 | } else { | ||
| 223 | Anamode::SOURCE | ||
| 224 | }); | ||
| 225 | w.set_anasubmode(match cc_pull { | ||
| 226 | CcPull::SourceDefaultUsb => 1, | ||
| 227 | CcPull::Source1_5A => 2, | ||
| 228 | CcPull::Source3_0A => 3, | ||
| 229 | _ => 0, | ||
| 230 | }); | ||
| 231 | w.set_ccenable(if cc_pull == CcPull::Disabled { | ||
| 232 | Ccenable::DISABLED | ||
| 233 | } else { | ||
| 234 | Ccenable::BOTH | ||
| 235 | }); | ||
| 236 | }); | ||
| 237 | |||
| 238 | // Disable dead-battery pull-down resistors which are enabled by default on boot. | ||
| 239 | critical_section::with(|cs| { | ||
| 240 | init( | ||
| 241 | cs, | ||
| 242 | false, | ||
| 243 | #[cfg(peri_ucpd2)] | ||
| 244 | false, | ||
| 245 | ); | ||
| 246 | }); | ||
| 247 | } | ||
| 248 | |||
| 249 | /// Returns the current voltage level of CC1 and CC2 pin as tuple. | ||
| 250 | /// | ||
| 251 | /// Interpretation of the voltage levels depends on the configured CC line | ||
| 252 | /// pull-up/pull-down resistance. | ||
| 253 | pub fn vstate(&self) -> (CcVState, CcVState) { | ||
| 254 | let sr = T::REGS.sr().read(); | ||
| 255 | (sr.typec_vstate_cc1(), sr.typec_vstate_cc2()) | ||
| 256 | } | ||
| 257 | |||
| 258 | /// Waits for a change in voltage state on either CC line. | ||
| 259 | pub async fn wait_for_vstate_change(&self) -> (CcVState, CcVState) { | ||
| 260 | let _on_drop = OnDrop::new(|| self.enable_cc_interrupts(false)); | ||
| 261 | let prev_vstate = self.vstate(); | ||
| 262 | poll_fn(|cx| { | ||
| 263 | let vstate = self.vstate(); | ||
| 264 | if vstate != prev_vstate { | ||
| 265 | Poll::Ready(vstate) | ||
| 266 | } else { | ||
| 267 | T::state().waker.register(cx.waker()); | ||
| 268 | self.enable_cc_interrupts(true); | ||
| 269 | Poll::Pending | ||
| 270 | } | ||
| 271 | }) | ||
| 272 | .await | ||
| 273 | } | ||
| 274 | |||
| 275 | fn enable_cc_interrupts(&self, enable: bool) { | ||
| 276 | T::REGS.imr().modify(|w| { | ||
| 277 | w.set_typecevt1ie(enable); | ||
| 278 | w.set_typecevt2ie(enable); | ||
| 279 | }); | ||
| 280 | } | ||
| 281 | } | ||
| 282 | |||
| 283 | /// Receive Error. | ||
| 284 | #[derive(Debug, Clone, Copy)] | ||
| 285 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 286 | pub enum RxError { | ||
| 287 | /// Incorrect CRC or truncated message (a line becoming static before EOP is met). | ||
| 288 | Crc, | ||
| 289 | |||
| 290 | /// Provided buffer was too small for the received message. | ||
| 291 | Overrun, | ||
| 292 | |||
| 293 | /// Hard Reset received before or during reception. | ||
| 294 | HardReset, | ||
| 295 | } | ||
| 296 | |||
| 297 | /// Transmit Error. | ||
| 298 | #[derive(Debug, Clone, Copy)] | ||
| 299 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 300 | pub enum TxError { | ||
| 301 | /// Concurrent receive in progress or excessive noise on the line. | ||
| 302 | Discarded, | ||
| 303 | |||
| 304 | /// Hard Reset received before or during transmission. | ||
| 305 | HardReset, | ||
| 306 | } | ||
| 307 | |||
| 308 | /// Power Delivery (PD) PHY. | ||
| 309 | pub struct PdPhy<'d, T: Instance> { | ||
| 310 | _lifetime: PhantomData<&'d mut T>, | ||
| 311 | rx_dma_ch: PeripheralRef<'d, AnyChannel>, | ||
| 312 | rx_dma_req: Request, | ||
| 313 | tx_dma_ch: PeripheralRef<'d, AnyChannel>, | ||
| 314 | tx_dma_req: Request, | ||
| 315 | } | ||
| 316 | |||
| 317 | impl<'d, T: Instance> Drop for PdPhy<'d, T> { | ||
| 318 | fn drop(&mut self) { | ||
| 319 | // Check if the Type-C part was dropped already. | ||
| 320 | let drop_not_ready = &T::state().drop_not_ready; | ||
| 321 | if drop_not_ready.load(Ordering::Relaxed) { | ||
| 322 | drop_not_ready.store(true, Ordering::Relaxed); | ||
| 323 | } else { | ||
| 324 | T::REGS.cfgr1().write(|w| w.set_ucpden(false)); | ||
| 325 | T::disable(); | ||
| 326 | T::Interrupt::disable(); | ||
| 327 | } | ||
| 328 | } | ||
| 329 | } | ||
| 330 | |||
| 331 | impl<'d, T: Instance> PdPhy<'d, T> { | ||
| 332 | /// Receives a PD message into the provided buffer. | ||
| 333 | /// | ||
| 334 | /// Returns the number of received bytes or an error. | ||
| 335 | pub async fn receive(&mut self, buf: &mut [u8]) -> Result<usize, RxError> { | ||
| 336 | let r = T::REGS; | ||
| 337 | |||
| 338 | let dma = unsafe { | ||
| 339 | Transfer::new_read( | ||
| 340 | &self.rx_dma_ch, | ||
| 341 | self.rx_dma_req, | ||
| 342 | r.rxdr().as_ptr() as *mut u8, | ||
| 343 | buf, | ||
| 344 | TransferOptions::default(), | ||
| 345 | ) | ||
| 346 | }; | ||
| 347 | |||
| 348 | // Clear interrupt flags (possibly set from last receive). | ||
| 349 | r.icr().write(|w| { | ||
| 350 | w.set_rxorddetcf(true); | ||
| 351 | w.set_rxovrcf(true); | ||
| 352 | w.set_rxmsgendcf(true); | ||
| 353 | }); | ||
| 354 | |||
| 355 | r.cr().modify(|w| w.set_phyrxen(true)); | ||
| 356 | let _on_drop = OnDrop::new(|| { | ||
| 357 | r.cr().modify(|w| w.set_phyrxen(false)); | ||
| 358 | self.enable_rx_interrupt(false); | ||
| 359 | }); | ||
| 360 | |||
| 361 | poll_fn(|cx| { | ||
| 362 | let sr = r.sr().read(); | ||
| 363 | if sr.rxhrstdet() { | ||
| 364 | // Clean and re-enable hard reset receive interrupt. | ||
| 365 | r.icr().write(|w| w.set_rxhrstdetcf(true)); | ||
| 366 | r.imr().modify(|w| w.set_rxhrstdetie(true)); | ||
| 367 | Poll::Ready(Err(RxError::HardReset)) | ||
| 368 | } else if sr.rxmsgend() { | ||
| 369 | let ret = if sr.rxovr() { | ||
| 370 | Err(RxError::Overrun) | ||
| 371 | } else if sr.rxerr() { | ||
| 372 | Err(RxError::Crc) | ||
| 373 | } else { | ||
| 374 | Ok(()) | ||
| 375 | }; | ||
| 376 | Poll::Ready(ret) | ||
| 377 | } else { | ||
| 378 | T::state().waker.register(cx.waker()); | ||
| 379 | self.enable_rx_interrupt(true); | ||
| 380 | Poll::Pending | ||
| 381 | } | ||
| 382 | }) | ||
| 383 | .await?; | ||
| 384 | |||
| 385 | // Make sure that the last byte was fetched by DMA. | ||
| 386 | while r.sr().read().rxne() { | ||
| 387 | if dma.get_remaining_transfers() == 0 { | ||
| 388 | return Err(RxError::Overrun); | ||
| 389 | } | ||
| 390 | } | ||
| 391 | |||
| 392 | Ok(r.rx_payszr().read().rxpaysz().into()) | ||
| 393 | } | ||
| 394 | |||
| 395 | fn enable_rx_interrupt(&self, enable: bool) { | ||
| 396 | T::REGS.imr().modify(|w| w.set_rxmsgendie(enable)); | ||
| 397 | } | ||
| 398 | |||
| 399 | /// Transmits a PD message. | ||
| 400 | pub async fn transmit(&mut self, buf: &[u8]) -> Result<(), TxError> { | ||
| 401 | let r = T::REGS; | ||
| 402 | |||
| 403 | // When a previous transmission was dropped before it had finished it | ||
| 404 | // might still be running because there is no way to abort an ongoing | ||
| 405 | // message transmission. Wait for it to finish but ignore errors. | ||
| 406 | if r.cr().read().txsend() { | ||
| 407 | if let Err(TxError::HardReset) = self.wait_tx_done().await { | ||
| 408 | return Err(TxError::HardReset); | ||
| 409 | } | ||
| 410 | } | ||
| 411 | |||
| 412 | // Clear the TX interrupt flags. | ||
| 413 | T::REGS.icr().write(|w| { | ||
| 414 | w.set_txmsgdisccf(true); | ||
| 415 | w.set_txmsgsentcf(true); | ||
| 416 | }); | ||
| 417 | |||
| 418 | // Start the DMA and let it do its thing in the background. | ||
| 419 | let _dma = unsafe { | ||
| 420 | Transfer::new_write( | ||
| 421 | &self.tx_dma_ch, | ||
| 422 | self.tx_dma_req, | ||
| 423 | buf, | ||
| 424 | r.txdr().as_ptr() as *mut u8, | ||
| 425 | TransferOptions::default(), | ||
| 426 | ) | ||
| 427 | }; | ||
| 428 | |||
| 429 | // Configure and start the transmission. | ||
| 430 | r.tx_payszr().write(|w| w.set_txpaysz(buf.len() as _)); | ||
| 431 | r.cr().modify(|w| { | ||
| 432 | w.set_txmode(Txmode::PACKET); | ||
| 433 | w.set_txsend(true); | ||
| 434 | }); | ||
| 435 | |||
| 436 | self.wait_tx_done().await | ||
| 437 | } | ||
| 438 | |||
| 439 | async fn wait_tx_done(&self) -> Result<(), TxError> { | ||
| 440 | let _on_drop = OnDrop::new(|| self.enable_tx_interrupts(false)); | ||
| 441 | poll_fn(|cx| { | ||
| 442 | let r = T::REGS; | ||
| 443 | let sr = r.sr().read(); | ||
| 444 | if sr.rxhrstdet() { | ||
| 445 | // Clean and re-enable hard reset receive interrupt. | ||
| 446 | r.icr().write(|w| w.set_rxhrstdetcf(true)); | ||
| 447 | r.imr().modify(|w| w.set_rxhrstdetie(true)); | ||
| 448 | Poll::Ready(Err(TxError::HardReset)) | ||
| 449 | } else if sr.txmsgdisc() { | ||
| 450 | Poll::Ready(Err(TxError::Discarded)) | ||
| 451 | } else if sr.txmsgsent() { | ||
| 452 | Poll::Ready(Ok(())) | ||
| 453 | } else { | ||
| 454 | T::state().waker.register(cx.waker()); | ||
| 455 | self.enable_tx_interrupts(true); | ||
| 456 | Poll::Pending | ||
| 457 | } | ||
| 458 | }) | ||
| 459 | .await | ||
| 460 | } | ||
| 461 | |||
| 462 | fn enable_tx_interrupts(&self, enable: bool) { | ||
| 463 | T::REGS.imr().modify(|w| { | ||
| 464 | w.set_txmsgdiscie(enable); | ||
| 465 | w.set_txmsgsentie(enable); | ||
| 466 | }); | ||
| 467 | } | ||
| 468 | |||
| 469 | /// Transmit a hard reset. | ||
| 470 | pub async fn transmit_hardreset(&mut self) -> Result<(), TxError> { | ||
| 471 | let r = T::REGS; | ||
| 472 | |||
| 473 | // Clear the hardreset interrupt flags. | ||
| 474 | T::REGS.icr().write(|w| { | ||
| 475 | w.set_hrstdisccf(true); | ||
| 476 | w.set_hrstsentcf(true); | ||
| 477 | }); | ||
| 478 | |||
| 479 | // Trigger hard reset transmission. | ||
| 480 | r.cr().modify(|w| { | ||
| 481 | w.set_txhrst(true); | ||
| 482 | }); | ||
| 483 | |||
| 484 | let _on_drop = OnDrop::new(|| self.enable_hardreset_interrupts(false)); | ||
| 485 | poll_fn(|cx| { | ||
| 486 | let r = T::REGS; | ||
| 487 | let sr = r.sr().read(); | ||
| 488 | if sr.rxhrstdet() { | ||
| 489 | // Clean and re-enable hard reset receive interrupt. | ||
| 490 | r.icr().write(|w| w.set_rxhrstdetcf(true)); | ||
| 491 | r.imr().modify(|w| w.set_rxhrstdetie(true)); | ||
| 492 | Poll::Ready(Err(TxError::HardReset)) | ||
| 493 | } else if sr.hrstdisc() { | ||
| 494 | Poll::Ready(Err(TxError::Discarded)) | ||
| 495 | } else if sr.hrstsent() { | ||
| 496 | Poll::Ready(Ok(())) | ||
| 497 | } else { | ||
| 498 | T::state().waker.register(cx.waker()); | ||
| 499 | self.enable_hardreset_interrupts(true); | ||
| 500 | Poll::Pending | ||
| 501 | } | ||
| 502 | }) | ||
| 503 | .await | ||
| 504 | } | ||
| 505 | |||
| 506 | fn enable_hardreset_interrupts(&self, enable: bool) { | ||
| 507 | T::REGS.imr().modify(|w| { | ||
| 508 | w.set_hrstdiscie(enable); | ||
| 509 | w.set_hrstsentie(enable); | ||
| 510 | }); | ||
| 511 | } | ||
| 512 | } | ||
| 513 | |||
| 514 | /// Interrupt handler. | ||
| 515 | pub struct InterruptHandler<T: Instance> { | ||
| 516 | _phantom: PhantomData<T>, | ||
| 517 | } | ||
| 518 | |||
| 519 | impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> { | ||
| 520 | unsafe fn on_interrupt() { | ||
| 521 | let r = T::REGS; | ||
| 522 | let sr = r.sr().read(); | ||
| 523 | |||
| 524 | if sr.typecevt1() || sr.typecevt2() { | ||
| 525 | r.icr().write(|w| { | ||
| 526 | w.set_typecevt1cf(true); | ||
| 527 | w.set_typecevt2cf(true); | ||
| 528 | }); | ||
| 529 | } | ||
| 530 | |||
| 531 | if sr.rxhrstdet() { | ||
| 532 | r.imr().modify(|w| w.set_rxhrstdetie(false)); | ||
| 533 | } | ||
| 534 | |||
| 535 | if sr.rxmsgend() { | ||
| 536 | r.imr().modify(|w| w.set_rxmsgendie(false)); | ||
| 537 | } | ||
| 538 | |||
| 539 | if sr.txmsgdisc() || sr.txmsgsent() { | ||
| 540 | r.imr().modify(|w| { | ||
| 541 | w.set_txmsgdiscie(false); | ||
| 542 | w.set_txmsgsentie(false); | ||
| 543 | }); | ||
| 544 | } | ||
| 545 | |||
| 546 | if sr.hrstdisc() || sr.hrstsent() { | ||
| 547 | r.imr().modify(|w| { | ||
| 548 | w.set_hrstdiscie(false); | ||
| 549 | w.set_hrstsentie(false); | ||
| 550 | }); | ||
| 551 | } | ||
| 552 | |||
| 553 | // Wake the task to clear and re-enabled interrupts. | ||
| 554 | T::state().waker.wake(); | ||
| 555 | } | ||
| 556 | } | ||
| 557 | |||
| 558 | /// UCPD instance trait. | ||
| 559 | pub trait Instance: sealed::Instance + RccPeripheral {} | ||
| 560 | |||
| 561 | mod sealed { | ||
| 562 | use core::sync::atomic::AtomicBool; | ||
| 563 | |||
| 564 | use embassy_sync::waitqueue::AtomicWaker; | ||
| 565 | |||
| 566 | pub struct State { | ||
| 567 | pub waker: AtomicWaker, | ||
| 568 | // Inverted logic for a default state of 0 so that the data goes into the .bss section. | ||
| 569 | pub drop_not_ready: AtomicBool, | ||
| 570 | } | ||
| 571 | |||
| 572 | impl State { | ||
| 573 | pub const fn new() -> Self { | ||
| 574 | Self { | ||
| 575 | waker: AtomicWaker::new(), | ||
| 576 | drop_not_ready: AtomicBool::new(false), | ||
| 577 | } | ||
| 578 | } | ||
| 579 | } | ||
| 580 | |||
| 581 | pub trait Instance { | ||
| 582 | type Interrupt: crate::interrupt::typelevel::Interrupt; | ||
| 583 | const REGS: crate::pac::ucpd::Ucpd; | ||
| 584 | fn state() -> &'static crate::ucpd::sealed::State; | ||
| 585 | } | ||
| 586 | } | ||
| 587 | |||
| 588 | foreach_interrupt!( | ||
| 589 | ($inst:ident, ucpd, UCPD, GLOBAL, $irq:ident) => { | ||
| 590 | impl sealed::Instance for crate::peripherals::$inst { | ||
| 591 | type Interrupt = crate::interrupt::typelevel::$irq; | ||
| 592 | |||
| 593 | const REGS: crate::pac::ucpd::Ucpd = crate::pac::$inst; | ||
| 594 | |||
| 595 | fn state() -> &'static crate::ucpd::sealed::State { | ||
| 596 | static STATE: crate::ucpd::sealed::State = crate::ucpd::sealed::State::new(); | ||
| 597 | &STATE | ||
| 598 | } | ||
| 599 | } | ||
| 600 | |||
| 601 | impl Instance for crate::peripherals::$inst {} | ||
| 602 | }; | ||
| 603 | ); | ||
| 604 | |||
| 605 | pin_trait!(Cc1Pin, Instance); | ||
| 606 | pin_trait!(Cc2Pin, Instance); | ||
| 607 | |||
| 608 | dma_trait!(TxDma, Instance); | ||
| 609 | dma_trait!(RxDma, Instance); | ||
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..7caea634f --- /dev/null +++ b/examples/stm32g4/src/bin/usb_c_pd.rs | |||
| @@ -0,0 +1,86 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use defmt::{error, info, Format}; | ||
| 5 | use embassy_executor::Spawner; | ||
| 6 | use embassy_stm32::ucpd::{self, CcPhy, CcPull, CcSel, CcVState, Ucpd}; | ||
| 7 | use embassy_stm32::{bind_interrupts, peripherals, Config}; | ||
| 8 | use embassy_time::{with_timeout, Duration}; | ||
| 9 | use {defmt_rtt as _, panic_probe as _}; | ||
| 10 | |||
| 11 | bind_interrupts!(struct Irqs { | ||
| 12 | UCPD1 => ucpd::InterruptHandler<peripherals::UCPD1>; | ||
| 13 | }); | ||
| 14 | |||
| 15 | #[derive(Debug, Format)] | ||
| 16 | enum CableOrientation { | ||
| 17 | Normal, | ||
| 18 | Flipped, | ||
| 19 | DebugAccessoryMode, | ||
| 20 | } | ||
| 21 | |||
| 22 | // Returns true when the cable | ||
| 23 | async fn wait_attached<T: ucpd::Instance>(cc_phy: &mut CcPhy<'_, T>) -> CableOrientation { | ||
| 24 | loop { | ||
| 25 | let (cc1, cc2) = cc_phy.vstate(); | ||
| 26 | if cc1 == CcVState::LOWEST && cc2 == CcVState::LOWEST { | ||
| 27 | // Detached, wait until attached by monitoring the CC lines. | ||
| 28 | cc_phy.wait_for_vstate_change().await; | ||
| 29 | continue; | ||
| 30 | } | ||
| 31 | |||
| 32 | // Attached, wait for CC lines to be stable for tCCDebounce (100..200ms). | ||
| 33 | if with_timeout(Duration::from_millis(100), cc_phy.wait_for_vstate_change()) | ||
| 34 | .await | ||
| 35 | .is_ok() | ||
| 36 | { | ||
| 37 | // State has changed, restart detection procedure. | ||
| 38 | continue; | ||
| 39 | }; | ||
| 40 | |||
| 41 | // State was stable for the complete debounce period, check orientation. | ||
| 42 | return match (cc1, cc2) { | ||
| 43 | (_, CcVState::LOWEST) => CableOrientation::Normal, // CC1 connected | ||
| 44 | (CcVState::LOWEST, _) => CableOrientation::Flipped, // CC2 connected | ||
| 45 | _ => CableOrientation::DebugAccessoryMode, // Both connected (special cable) | ||
| 46 | }; | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | #[embassy_executor::main] | ||
| 51 | async fn main(_spawner: Spawner) { | ||
| 52 | let mut config = Config::default(); | ||
| 53 | config.enable_ucpd1_dead_battery = true; | ||
| 54 | let p = embassy_stm32::init(config); | ||
| 55 | |||
| 56 | info!("Hello World!"); | ||
| 57 | |||
| 58 | let mut ucpd = Ucpd::new(p.UCPD1, Irqs {}, p.PB6, p.PB4); | ||
| 59 | ucpd.cc_phy().set_pull(CcPull::Sink); | ||
| 60 | |||
| 61 | info!("Waiting for USB connection..."); | ||
| 62 | let cable_orientation = wait_attached(ucpd.cc_phy()).await; | ||
| 63 | info!("USB cable connected, orientation: {}", cable_orientation); | ||
| 64 | |||
| 65 | let cc_sel = match cable_orientation { | ||
| 66 | CableOrientation::Normal => { | ||
| 67 | info!("Starting PD communication on CC1 pin"); | ||
| 68 | CcSel::CC1 | ||
| 69 | } | ||
| 70 | CableOrientation::Flipped => { | ||
| 71 | info!("Starting PD communication on CC2 pin"); | ||
| 72 | CcSel::CC2 | ||
| 73 | } | ||
| 74 | CableOrientation::DebugAccessoryMode => panic!("No PD communication in DAM"), | ||
| 75 | }; | ||
| 76 | let (_cc_phy, mut pd_phy) = ucpd.split_pd_phy(p.DMA1_CH1, p.DMA1_CH2, cc_sel); | ||
| 77 | |||
| 78 | loop { | ||
| 79 | // Enough space for the longest non-extended data message. | ||
| 80 | let mut buf = [0_u8; 30]; | ||
| 81 | match pd_phy.receive(buf.as_mut()).await { | ||
| 82 | Ok(n) => info!("USB PD RX: {=[u8]:?}", &buf[..n]), | ||
| 83 | Err(e) => error!("USB PD RX: {}", e), | ||
| 84 | } | ||
| 85 | } | ||
| 86 | } | ||
