aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--embassy-stm32/src/ucpd.rs169
-rw-r--r--examples/stm32g4/src/bin/usb_c_pd.rs62
2 files changed, 227 insertions, 4 deletions
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 @@
1//! USB Type-C/USB Power Delivery Interface (UCPD) 1//! USB Type-C/USB Power Delivery Interface (UCPD)
2 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;
3use core::marker::PhantomData; 18use core::marker::PhantomData;
19use core::task::Poll;
4 20
5use crate::interrupt;
6use crate::rcc::RccPeripheral; 21use crate::rcc::RccPeripheral;
22use crate::{interrupt, pac};
23use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
7use embassy_sync::waitqueue::AtomicWaker; 24use embassy_sync::waitqueue::AtomicWaker;
25use pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk};
26
27pub use pac::ucpd::vals::TypecVstateCc as CcVState;
28
29/// Pull-up or Pull-down resistor state of both CC lines.
30#[derive(Debug, Clone, Copy, PartialEq)]
31pub enum CcPull {
32 /// Analog PHY for CC pin disabled.
33 Disabled,
34
35 /// Rd=5.1k pull-down resistor enabled when the corresponding DBCC pin is high.
36 SinkDeadBattery,
37
38 /// Rd=5.1k pull-down resistor.
39 Sink,
40
41 /// Rp=56k pull-up resistor to indicate default USB power.
42 SourceDefaultUsb,
43
44 /// Rp=22k pull-up resistor to indicate support for up to 1.5A.
45 Source1_5A,
46
47 /// Rp=10k pull-up resistor to indicate support for up to 3.0A.
48 Source3_0A,
49}
50
51/// UCPD driver.
52pub struct Ucpd<'d, T: Instance> {
53 _peri: PeripheralRef<'d, T>,
54}
55
56impl<'d, T: Instance> Ucpd<'d, T> {
57 /// Creates a new UCPD driver instance.
58 pub fn new(
59 peri: impl Peripheral<P = T> + 'd,
60 _cc1: impl Peripheral<P = impl Cc1Pin<T>> + 'd,
61 _cc2: impl Peripheral<P = impl Cc2Pin<T>> + 'd,
62 cc_pull: CcPull,
63 ) -> Self {
64 T::enable_and_reset();
65
66 let r = T::REGS;
67 r.cfgr1().write(|w| {
68 // "The receiver is designed to work in the clock frequency range from 6 to 18 MHz.
69 // However, the optimum performance is ensured in the range from 6 to 12 MHz"
70 // UCPD is driven by HSI16 (16MHz internal oscillator), which we need to divide by 2.
71 w.set_psc_usbpdclk(PscUsbpdclk::DIV2);
72
73 // Prescaler to produce a target half-bit frequency of 600kHz which is required
74 // to produce transmit with a nominal nominal bit rate of 300Kbps+-10% using
75 // biphase mark coding (BMC, aka differential manchester coding).
76 // A divider of 13 gives the target frequency closest to spec (~615kHz, 1.625us)
77 // but we go with the (hopefully well tested) default value used by the Cube HAL
78 // which is 14 divides the clock down to ~571kHz, 1.75us.
79 w.set_hbitclkdiv(14 - 1);
80
81 // Time window for detecting non-idle (12-20us).
82 // 1.75us * 8 = 14us.
83 w.set_transwin(8 - 1);
84
85 // Time from the end of last bit of a Frame until the start of the first bit of the
86 // next Preamble (min 25us).
87 // 1.75us * 17 = ~30us
88 w.set_ifrgap(17 - 1);
89
90 // TODO: Only receive SOP messages
91 w.set_rxordseten(0x1);
92
93 // Enable DMA and the peripheral
94 w.set_txdmaen(true);
95 w.set_rxdmaen(true);
96 w.set_ucpden(true);
97 });
98
99 r.cr().write(|w| {
100 w.set_anamode(if cc_pull == CcPull::Sink {
101 Anamode::SINK
102 } else {
103 Anamode::SOURCE
104 });
105 w.set_anasubmode(match cc_pull {
106 CcPull::SourceDefaultUsb => 1,
107 CcPull::Source1_5A => 2,
108 CcPull::Source3_0A => 3,
109 _ => 0,
110 });
111 w.set_ccenable(if cc_pull != CcPull::SinkDeadBattery {
112 Ccenable::BOTH
113 } else {
114 Ccenable::DISABLED
115 });
116 });
117
118 // Disable dead-battery pull-down resistors which are enabled by default on boot.
119 critical_section::with(|_| {
120 // TODO: other families
121 #[cfg(stm32g4)]
122 pac::PWR
123 .cr3()
124 .modify(|w| w.set_ucpd1_dbdis(cc_pull != CcPull::SinkDeadBattery));
125 });
126
127 into_ref!(peri);
128 Self { _peri: peri }
129 }
130
131 /// Returns the current voltage level of CC1 and CC2 pin as tuple.
132 ///
133 /// Interpretation of the voltage levels depends on the configured CC line
134 /// pull-up/pull-down resistance.
135 pub fn cc_vstate(&self) -> (CcVState, CcVState) {
136 let sr = T::REGS.sr().read();
137 (sr.typec_vstate_cc1(), sr.typec_vstate_cc2())
138 }
139
140 /// Waits for a change in voltage state on either CC line.
141 pub async fn wait_for_cc_change(&mut self) {
142 let r = T::REGS;
143 poll_fn(|cx| {
144 let sr = r.sr().read();
145 if sr.typecevt1() || sr.typecevt2() {
146 r.icr().write(|w| {
147 w.set_typecevt1cf(true);
148 w.set_typecevt2cf(true);
149 });
150 Poll::Ready(())
151 } else {
152 T::waker().register(cx.waker());
153 r.imr().modify(|w| {
154 w.set_typecevt1ie(true);
155 w.set_typecevt2ie(true);
156 });
157 Poll::Pending
158 }
159 })
160 .await;
161 }
162}
8 163
9/// Interrupt handler. 164/// Interrupt handler.
10pub struct InterruptHandler<T: Instance> { 165pub struct InterruptHandler<T: Instance> {
@@ -13,11 +168,17 @@ pub struct InterruptHandler<T: Instance> {
13 168
14impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> { 169impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
15 unsafe fn on_interrupt() { 170 unsafe fn on_interrupt() {
16 let sr = T::REGS.sr().read(); 171 let r = T::REGS;
172 let sr = r.sr().read();
17 173
18 // TODO: Disable interrupt which have fired. 174 if sr.typecevt1() || sr.typecevt2() {
175 r.imr().modify(|w| {
176 w.set_typecevt1ie(true);
177 w.set_typecevt2ie(true);
178 });
179 }
19 180
20 // Wake the task to handle and re-enabled interrupts. 181 // Wake the task to clear and re-enabled interrupts.
21 T::waker().wake(); 182 T::waker().wake();
22 } 183 }
23} 184}
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 @@
1#![no_std]
2#![no_main]
3
4use defmt::{info, Format};
5use embassy_executor::Spawner;
6use embassy_stm32::{
7 ucpd::{self, CcPull, CcVState, Ucpd},
8 Config,
9};
10use embassy_time::{with_timeout, Duration};
11use {defmt_rtt as _, panic_probe as _};
12
13#[derive(Debug, Format)]
14enum CableOrientation {
15 Normal,
16 Flipped,
17 DebugAccessoryMode,
18}
19
20// Returns true when the cable
21async fn wait_attached<'d, T: ucpd::Instance>(ucpd: &mut Ucpd<'d, T>) -> CableOrientation {
22 loop {
23 let (cc1, cc2) = ucpd.cc_vstate();
24 if cc1 == CcVState::LOWEST && cc2 == CcVState::LOWEST {
25 // Detached, wait until attached by monitoring the CC lines.
26 ucpd.wait_for_cc_change().await;
27 continue;
28 }
29
30 // Attached, wait for CC lines to be stable for tCCDebounce (100..200ms).
31 if with_timeout(Duration::from_millis(100), ucpd.wait_for_cc_change())
32 .await
33 .is_ok()
34 {
35 // State has changed, restart detection procedure.
36 continue;
37 };
38
39 // State was stable for the complete debounce period, check orientation.
40 return match (cc1, cc2) {
41 (_, CcVState::LOWEST) => CableOrientation::Normal, // CC1 connected
42 (CcVState::LOWEST, _) => CableOrientation::Flipped, // CC2 connected
43 _ => CableOrientation::DebugAccessoryMode, // Both connected (special cable)
44 };
45 }
46}
47
48#[embassy_executor::main]
49async fn main(_spawner: Spawner) {
50 // TODO: Disable DBCC pin functionality by default but have flag in the config to keep it enabled when required.
51 let p = embassy_stm32::init(Config::default());
52
53 info!("Hello World!");
54
55 let mut ucpd = Ucpd::new(p.UCPD1, p.PB6, p.PB4, CcPull::Sink);
56
57 info!("Waiting for USB connection...");
58 let cable_orientation = wait_attached(&mut ucpd).await;
59 info!("USB cable connected, orientation: {}", cable_orientation);
60
61 loop {}
62}