diff options
| author | Timo Kröger <[email protected]> | 2024-03-03 15:09:53 +0100 |
|---|---|---|
| committer | Timo Kröger <[email protected]> | 2024-03-12 08:14:42 +0100 |
| commit | d99fcfd0c285be220c8f0004974567d7d4e2607b (patch) | |
| tree | d8ede531bccce63766bb44c6a4ecdbcde8ff6729 | |
| parent | aa1411e2c772fd332417ca258b58c75b35d5b7ac (diff) | |
[UCPD] Configuration Channel (CC) handling
| -rw-r--r-- | embassy-stm32/src/ucpd.rs | 169 | ||||
| -rw-r--r-- | examples/stm32g4/src/bin/usb_c_pd.rs | 62 |
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 | |||
| 17 | use core::future::poll_fn; | ||
| 3 | use core::marker::PhantomData; | 18 | use core::marker::PhantomData; |
| 19 | use core::task::Poll; | ||
| 4 | 20 | ||
| 5 | use crate::interrupt; | ||
| 6 | use crate::rcc::RccPeripheral; | 21 | use crate::rcc::RccPeripheral; |
| 22 | use crate::{interrupt, pac}; | ||
| 23 | use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; | ||
| 7 | use embassy_sync::waitqueue::AtomicWaker; | 24 | use embassy_sync::waitqueue::AtomicWaker; |
| 25 | use pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk}; | ||
| 26 | |||
| 27 | pub 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)] | ||
| 31 | pub 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. | ||
| 52 | pub struct Ucpd<'d, T: Instance> { | ||
| 53 | _peri: PeripheralRef<'d, T>, | ||
| 54 | } | ||
| 55 | |||
| 56 | impl<'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. |
| 10 | pub struct InterruptHandler<T: Instance> { | 165 | pub struct InterruptHandler<T: Instance> { |
| @@ -13,11 +168,17 @@ pub struct InterruptHandler<T: Instance> { | |||
| 13 | 168 | ||
| 14 | impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> { | 169 | impl<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 | |||
| 4 | use defmt::{info, Format}; | ||
| 5 | use embassy_executor::Spawner; | ||
| 6 | use embassy_stm32::{ | ||
| 7 | ucpd::{self, CcPull, CcVState, Ucpd}, | ||
| 8 | Config, | ||
| 9 | }; | ||
| 10 | use embassy_time::{with_timeout, Duration}; | ||
| 11 | use {defmt_rtt as _, panic_probe as _}; | ||
| 12 | |||
| 13 | #[derive(Debug, Format)] | ||
| 14 | enum CableOrientation { | ||
| 15 | Normal, | ||
| 16 | Flipped, | ||
| 17 | DebugAccessoryMode, | ||
| 18 | } | ||
| 19 | |||
| 20 | // Returns true when the cable | ||
| 21 | async 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] | ||
| 49 | async 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 | } | ||
