diff --git a/build.rs b/build.rs index d20a0c4..4bb250e 100644 --- a/build.rs +++ b/build.rs @@ -388,6 +388,9 @@ fn main() { // USB is splitted into multiple impls (("usbd", "DP"), quote!(crate::usbd::DpPin)), (("usbd", "DM"), quote!(crate::usbd::DmPin)), + // USBPD + (("usbpd", "CC1"), quote!(crate::usbpd::Cc1Pin)), + (("usbpd", "CC2"), quote!(crate::usbpd::Cc2Pin)), ] .into(); diff --git a/src/lib.rs b/src/lib.rs index 226963b..50118c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,6 +75,9 @@ pub mod usb; #[cfg(usbd)] pub mod usbd; +#[cfg(usbpd)] +pub mod usbpd; + #[cfg(feature = "embassy")] pub mod embassy; diff --git a/src/macros.rs b/src/macros.rs index 02b3f41..608906a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -14,7 +14,7 @@ macro_rules! dma_trait { macro_rules! pin_trait { ($signal:ident, $instance:path) => { - pub trait $signal: crate::gpio::Pin {} + pub trait $signal: crate::gpio::Pin {} }; } macro_rules! pin_trait_impl { diff --git a/src/usart/mod.rs b/src/usart/mod.rs index af7625c..5c513b1 100644 --- a/src/usart/mod.rs +++ b/src/usart/mod.rs @@ -1027,6 +1027,20 @@ fn configure( Ok(()) } +impl<'d, T: Instance> core::fmt::Write for UartTx<'d, T, Blocking> { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + self.blocking_write(s.as_bytes()).map_err(|_| core::fmt::Error)?; + Ok(()) + } +} +impl<'d, T: Instance> core::fmt::Write for Uart<'d, T, Blocking> { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + self.blocking_write(s.as_bytes()).map_err(|_| core::fmt::Error)?; + Ok(()) + } +} + + // Peripheral traits struct State { rx_waker: AtomicWaker, diff --git a/src/usbpd/mod.rs b/src/usbpd/mod.rs new file mode 100644 index 0000000..9a6969e --- /dev/null +++ b/src/usbpd/mod.rs @@ -0,0 +1,405 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::AtomicBool; +use core::task::Poll; + +use embassy_sync::waitqueue::AtomicWaker; +use embedded_hal::delay::DelayNs; +use pac::InterruptNumber; + +use crate::gpio::Pull; +use crate::pac::usbpd::vals; +use crate::{interrupt, into_ref, pac, println, Peripheral, RccPeripheral}; + +#[derive(Debug)] +pub enum Error { + Rejected, + Timeout, + CCNotConnected, + NotSupported, + HardReset, + /// Unexpeted message type + Protocol(u8), + MaxRetry, +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let usbpd = T::REGS; + + let status = usbpd.status().read(); + + println!("irq 0x{:02x}", status.0); + + if status.if_tx_end() { + println!(">"); + // T::REGS.port_cc1().modify(|w| w.set_cc_lve(false)); + // T::REGS.port_cc2().modify(|w| w.set_cc_lve(false)); + + T::REGS.config().modify(|w| w.set_ie_tx_end(false)); + } + + if status.if_rx_act() { + // println!("< {}", T::REGS.bmc_byte_cnt().read().bmc_byte_cnt()); + T::REGS.control().modify(|w| w.set_bmc_start(false)); // stop + T::REGS.config().modify(|w| w.set_ie_rx_act(false)); + } + + if status.if_rx_reset() { + T::REGS.config().modify(|w| w.set_ie_rx_reset(false)); + + crate::println!("TODO: reset"); + } + + if status.buf_err() { + crate::println!("TODO: buf_err"); + } + + T::REGS.status().write_value(status); + + // Wake the task to clear and re-enabled interrupts. + T::state().waker.wake(); + } +} + +pub struct UsbPdPhy<'d, T: Instance> { + _marker: PhantomData<&'d mut T>, +} + +impl<'d, T: Instance> UsbPdPhy<'d, T> { + /// Create a new SPI driver. + pub fn new( + _peri: impl Peripheral

+ 'd, + cc1: impl Peripheral

> + 'd, + cc2: impl Peripheral

> + 'd, + ) -> Result { + into_ref!(cc1, cc2); + + let afio = crate::pac::AFIO; + + T::enable_and_reset(); + + cc1.set_as_input(Pull::None); + cc2.set_as_input(Pull::None); + + // PD 引脚 PC14/PC15 高阈值输入模式 + // PD 收发器 PHY 上拉限幅配置位: USBPD_PHY_V33 + #[cfg(ch32x0)] + afio.ctlr().modify(|w| { + w.set_usbpd_in_hvt(true); + w.set_usbpd_phy_v33(true); + }); + #[cfg(ch32l1)] + afio.cr().modify(|w| { + w.set_usbpd_in_hvt(true); + }); + + T::REGS.config().write(|w| { + w.set_pd_dma_en(true); + // w.set_pd_filt_en(true); + // w.set_pd_rst_en(true); + }); + T::REGS.status().write(|w| w.0 = 0b111111_00); // write 1 to clear + + // pd_phy_reset + T::REGS.port_cc1().write(|w| w.set_cc_ce(vals::PortCcCe::V0_66)); + T::REGS.port_cc2().write(|w| w.set_cc_ce(vals::PortCcCe::V0_66)); + + let mut this = Self { _marker: PhantomData }; + this.detect_cc()?; + + Ok(this) + } + + pub fn reset(&mut self) -> Result<(), Error> { + T::enable_and_reset(); + + T::REGS.config().write(|w| { + w.set_pd_dma_en(true); + // w.set_pd_filt_en(true); + }); + T::REGS.status().write(|w| w.0 = 0b111111_00); // write 1 to clear + + T::REGS.port_cc1().modify(|w| w.set_cc_lve(false)); + T::REGS.port_cc2().modify(|w| w.set_cc_lve(false)); + + self.detect_cc()?; + + // pd_phy_reset + T::REGS.port_cc1().write(|w| w.set_cc_ce(vals::PortCcCe::V0_66)); + T::REGS.port_cc2().write(|w| w.set_cc_ce(vals::PortCcCe::V0_66)); + + Ok(()) + } + + fn detect_cc(&mut self) -> Result<(), Error> { + // CH32X035 has no internal CC pull down support + // The detection voltage is 0.22V, sufficient to detect the default power(500mA/900mA) + + T::REGS.port_cc1().modify(|w| w.set_cc_ce(vals::PortCcCe::V0_22)); + embassy_time::Delay.delay_us(2); + + if T::REGS.port_cc1().read().pa_cc_ai() { + // CC1 is connected + T::REGS.config().modify(|w| w.set_cc_sel(vals::CcSel::CC1)); + + crate::println!("CC1 connected"); + Ok(()) + } else { + T::REGS.port_cc2().modify(|w| w.set_cc_ce(vals::PortCcCe::V0_22)); + embassy_time::Delay.delay_us(2); + + if T::REGS.port_cc2().read().pa_cc_ai() { + // CC2 is connected + T::REGS.config().modify(|w| w.set_cc_sel(vals::CcSel::CC2)); + crate::println!("CC2 connected"); + Ok(()) + } else { + crate::println!("CC not connected"); + + Err(Error::CCNotConnected) + } + } + } + + fn enable_rx_interrupt(&mut self) { + T::REGS.config().modify(|w| { + w.set_ie_rx_act(true); + w.set_ie_rx_reset(true); + w.set_ie_tx_end(false); + }); + } + + fn enable_tx_interrupt(&mut self) { + T::REGS.config().modify(|w| { + w.set_ie_rx_act(false); + w.set_ie_rx_reset(true); + w.set_ie_tx_end(true); + }); + } + + /// 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 { + // set rx mode + // println!("before clear {}", T::REGS.status().read().0); + T::REGS.config().modify(|w| w.set_pd_all_clr(true)); + // println!("after clear0 {}", T::REGS.status().read().0); + T::REGS.config().modify(|w| w.set_pd_all_clr(false)); + // println!("after clear1 {}", T::REGS.status().read().0); + + T::REGS.dma().write_value((buf.as_mut_ptr() as u32 & 0xFFFF) as u16); + + T::REGS.control().modify(|w| w.set_pd_tx_en(false)); // RX + T::REGS + .bmc_clk_cnt() + .modify(|w| w.set_bmc_clk_cnt(calc_bmc_clk_for_rx())); + + self.enable_rx_interrupt(); + T::REGS.control().modify(|w| w.set_bmc_start(true)); + + poll_fn(|cx| { + T::state().waker.register(cx.waker()); + + if !T::REGS.config().read().ie_rx_reset() { + return Poll::Ready(Err(Error::HardReset)); + } + + if !T::REGS.config().read().ie_rx_act() { + match T::REGS.status().read().bmc_aux() { + vals::BmcAux::SOP0 => { + // println!("ctrl {}", T::REGS.control().read().0); + // println!("=> {}", T::REGS.bmc_byte_cnt().read().bmc_byte_cnt()); + return Poll::Ready(Ok(T::REGS.bmc_byte_cnt().read().bmc_byte_cnt() as usize)); + } + vals::BmcAux::SOP1 => { + // hard reset + return Poll::Ready(Err(Error::HardReset)); + } + _ => { + self.enable_rx_interrupt(); + return Poll::Pending; + } + } + } + Poll::Pending + }) + .await + } + + pub fn blocking_receive(&mut self, buf: &mut [u8]) -> Result { + // set rx mode + // println!("before clear {}", T::REGS.status().read().0); + T::REGS.config().modify(|w| w.set_pd_all_clr(true)); + // println!("after clear0 {}", T::REGS.status().read().0); + T::REGS.config().modify(|w| w.set_pd_all_clr(false)); + // println!("after clear1 {}", T::REGS.status().read().0); + + T::REGS.dma().write_value((buf.as_mut_ptr() as u32 & 0xFFFF) as u16); + + T::REGS.control().modify(|w| w.set_pd_tx_en(false)); // RX + T::REGS + .bmc_clk_cnt() + .modify(|w| w.set_bmc_clk_cnt(calc_bmc_clk_for_rx())); + + self.enable_rx_interrupt(); + unsafe { + qingke::pfic::disable_interrupt(interrupt::USBPD.number() as _); + } + println!("begin blocking recv"); + + T::REGS.control().modify(|w| w.set_bmc_start(true)); + + while !T::REGS.status().read().if_rx_act() { + // println!("wait"); + } + + unsafe { + qingke::pfic::enable_interrupt(interrupt::USBPD.number() as _); + } + + Ok(10) + } + + fn transmit(&mut self, sop: u8, buf: &[u8]) -> Result<(), Error> { + if T::REGS.config().read().cc_sel() == vals::CcSel::CC1 { + T::REGS.port_cc1().modify(|w| w.set_cc_lve(true)); + } else { + T::REGS.port_cc2().modify(|w| w.set_cc_lve(true)); + } + + T::REGS + .bmc_clk_cnt() + .write(|w| w.set_bmc_clk_cnt(calc_bmc_clk_for_tx())); + + if buf.is_empty() { + T::REGS.dma().write_value(0); + } else { + T::REGS.dma().write_value(buf.as_ptr() as u16); + } + + T::REGS.tx_sel().write(|w| w.0 = sop); + + T::REGS.bmc_tx_sz().write(|w| w.set_bmc_tx_sz(buf.len() as _)); + T::REGS.control().modify(|w| w.set_pd_tx_en(true)); // TX + + T::REGS.status().write(|w| w.0 = 0b11111100); + + T::REGS.control().modify(|w| w.set_bmc_start(true)); + + Ok(()) + } + + /// Transmit a hard reset. + pub async fn transmit_hardreset(&mut self) { + const TX_SEL_HARD_RESET: u8 = 0b10_10_10_01; + + // self.enable_tx_interrupt(); + // self.transmit(TX_SEL_HARD_RESET, &[])?; + // send_phy_empty_playload + + if T::REGS.config().read().cc_sel() == vals::CcSel::CC1 { + T::REGS.port_cc1().modify(|w| w.set_cc_lve(true)); + } else { + T::REGS.port_cc2().modify(|w| w.set_cc_lve(true)); + } + + T::REGS + .bmc_clk_cnt() + .write(|w| w.set_bmc_clk_cnt(calc_bmc_clk_for_tx())); + + T::REGS.dma().write_value(0); + T::REGS.tx_sel().write(|w| w.0 = TX_SEL_HARD_RESET); + T::REGS.bmc_byte_cnt().write(|w| w.set_bmc_byte_cnt(0)); + + T::REGS.control().modify(|w| w.set_pd_tx_en(true)); // TX + + T::REGS.status().write(|w| w.0 = 0b11111100); + + T::REGS.control().modify(|w| w.set_bmc_start(true)); + + /* + + poll_fn(|cx| { + T::state().waker.register(cx.waker()); + let config = T::REGS.config().read(); + if !config.ie_tx_end() { + return Poll::Ready(()); + } + Poll::Pending + }) + .await; + + // T::REGS.port_cc1().modify(|w| w.set_cc_lve(false)); + // T::REGS.port_cc2().modify(|w| w.set_cc_lve(false)); + + // T::REGS.port_cc1().write(|w| w.set_cc_ce(vals::PortCcCe::V0_66)); + // T::REGS.port_cc2().write(|w| w.set_cc_ce(vals::PortCcCe::V0_66)); + + Ok(()) + */ + } +} + +struct State { + waker: AtomicWaker, + // Inverted logic for a default state of 0 so that the data goes into the .bss section. + drop_not_ready: AtomicBool, +} + +impl State { + pub const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + drop_not_ready: AtomicBool::new(false), + } + } +} + +trait SealedInstance { + const REGS: crate::pac::usbpd::Usbpd; + + fn state() -> &'static State; +} + +#[allow(private_bounds)] +pub trait Instance: SealedInstance + RccPeripheral { + type Interrupt: crate::interrupt::typelevel::Interrupt; +} + +// catch GLOBAL irq +foreach_interrupt!( + ($inst:ident, usbpd, USBPD, GLOBAL, $irq:ident) => { + impl SealedInstance for crate::peripherals::$inst { + const REGS: crate::pac::usbpd::Usbpd = crate::pac::$inst; + + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + } + + impl Instance for crate::peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +); + +pin_trait!(Cc1Pin, Instance); +pin_trait!(Cc2Pin, Instance); + +#[inline] +fn calc_bmc_clk_for_tx() -> u16 { + (crate::rcc::clocks().hclk.0 / 1_000_000 * 80 / 48 - 1) as u16 +} + +#[inline] +fn calc_bmc_clk_for_rx() -> u16 { + (crate::rcc::clocks().hclk.0 / 1_000_000 * 120 / 48 - 1) as u16 +}