aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2024-03-14 21:21:33 +0000
committerGitHub <[email protected]>2024-03-14 21:21:33 +0000
commit963fda240484e0105408185f955703e2c02f9187 (patch)
tree93cd3d8bfbbeb5a584eb57e010ca9e70f7ce20b8
parent23e8fd8337df379fd22df5839a5b7cde3892eb47 (diff)
parent7b80de5e3db69a3d348bc74f6294e8642a11785e (diff)
Merge pull request #2652 from timokroeger/stm32-ucpd
STM32 USB Type-C/USB Power Delivery Interface (UCPD)
-rw-r--r--embassy-stm32/build.rs4
-rw-r--r--embassy-stm32/src/lib.rs43
-rw-r--r--embassy-stm32/src/ucpd.rs609
-rw-r--r--examples/stm32g4/src/bin/usb_c_pd.rs86
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;
73pub mod sdmmc; 73pub mod sdmmc;
74#[cfg(spi)] 74#[cfg(spi)]
75pub mod spi; 75pub mod spi;
76#[cfg(ucpd)]
77pub mod ucpd;
76#[cfg(uid)] 78#[cfg(uid)]
77pub mod uid; 79pub 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)
338unsafe 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
17use core::future::poll_fn;
18use core::marker::PhantomData;
19use core::sync::atomic::Ordering;
20use core::task::Poll;
21
22use embassy_hal_internal::drop::OnDrop;
23use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
24
25use crate::dma::{AnyChannel, Request, Transfer, TransferOptions};
26use crate::interrupt;
27use crate::interrupt::typelevel::Interrupt;
28use crate::pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk, Txmode};
29pub use crate::pac::ucpd::vals::{Phyccsel as CcSel, TypecVstateCc as CcVState};
30use crate::rcc::RccPeripheral;
31
32pub(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))]
71pub 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.
89pub struct Ucpd<'d, T: Instance> {
90 cc_phy: CcPhy<'d, T>,
91}
92
93impl<'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.
191pub struct CcPhy<'d, T: Instance> {
192 _lifetime: PhantomData<&'d mut T>,
193}
194
195impl<'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
216impl<'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))]
286pub 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))]
300pub 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.
309pub 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
317impl<'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
331impl<'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.
515pub struct InterruptHandler<T: Instance> {
516 _phantom: PhantomData<T>,
517}
518
519impl<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.
559pub trait Instance: sealed::Instance + RccPeripheral {}
560
561mod 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
588foreach_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
605pin_trait!(Cc1Pin, Instance);
606pin_trait!(Cc2Pin, Instance);
607
608dma_trait!(TxDma, Instance);
609dma_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
4use defmt::{error, info, Format};
5use embassy_executor::Spawner;
6use embassy_stm32::ucpd::{self, CcPhy, CcPull, CcSel, CcVState, Ucpd};
7use embassy_stm32::{bind_interrupts, peripherals, Config};
8use embassy_time::{with_timeout, Duration};
9use {defmt_rtt as _, panic_probe as _};
10
11bind_interrupts!(struct Irqs {
12 UCPD1 => ucpd::InterruptHandler<peripherals::UCPD1>;
13});
14
15#[derive(Debug, Format)]
16enum CableOrientation {
17 Normal,
18 Flipped,
19 DebugAccessoryMode,
20}
21
22// Returns true when the cable
23async 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]
51async 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}