From 530ff9d4d33abff34edb718d650c71a41dd68f8c Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 19 Mar 2024 20:44:33 +0100 Subject: stm32/usb: merge usb and usb_otg into single module. --- embassy-stm32/build.rs | 28 +- embassy-stm32/src/lib.rs | 8 +- embassy-stm32/src/usb/mod.rs | 39 +- embassy-stm32/src/usb/otg.rs | 1626 ++++++++++++++++++++++++++ embassy-stm32/src/usb/usb.rs | 31 +- embassy-stm32/src/usb_otg/mod.rs | 163 --- embassy-stm32/src/usb_otg/usb.rs | 1471 ----------------------- examples/stm32f4/src/bin/usb_ethernet.rs | 8 +- examples/stm32f4/src/bin/usb_hid_keyboard.rs | 8 +- examples/stm32f4/src/bin/usb_hid_mouse.rs | 8 +- examples/stm32f4/src/bin/usb_raw.rs | 8 +- examples/stm32f4/src/bin/usb_serial.rs | 8 +- examples/stm32f7/src/bin/usb_serial.rs | 8 +- examples/stm32h7/src/bin/usb_serial.rs | 8 +- examples/stm32l4/src/bin/usb_serial.rs | 8 +- examples/stm32u5/src/bin/usb_serial.rs | 8 +- 16 files changed, 1713 insertions(+), 1725 deletions(-) create mode 100644 embassy-stm32/src/usb/otg.rs delete mode 100644 embassy-stm32/src/usb_otg/mod.rs delete mode 100644 embassy-stm32/src/usb_otg/usb.rs diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index fe5236ed6..ee224da67 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -826,20 +826,20 @@ fn main() { (("dcmi", "PIXCLK"), quote!(crate::dcmi::PixClkPin)), (("usb", "DP"), quote!(crate::usb::DpPin)), (("usb", "DM"), quote!(crate::usb::DmPin)), - (("otg", "DP"), quote!(crate::usb_otg::DpPin)), - (("otg", "DM"), quote!(crate::usb_otg::DmPin)), - (("otg", "ULPI_CK"), quote!(crate::usb_otg::UlpiClkPin)), - (("otg", "ULPI_DIR"), quote!(crate::usb_otg::UlpiDirPin)), - (("otg", "ULPI_NXT"), quote!(crate::usb_otg::UlpiNxtPin)), - (("otg", "ULPI_STP"), quote!(crate::usb_otg::UlpiStpPin)), - (("otg", "ULPI_D0"), quote!(crate::usb_otg::UlpiD0Pin)), - (("otg", "ULPI_D1"), quote!(crate::usb_otg::UlpiD1Pin)), - (("otg", "ULPI_D2"), quote!(crate::usb_otg::UlpiD2Pin)), - (("otg", "ULPI_D3"), quote!(crate::usb_otg::UlpiD3Pin)), - (("otg", "ULPI_D4"), quote!(crate::usb_otg::UlpiD4Pin)), - (("otg", "ULPI_D5"), quote!(crate::usb_otg::UlpiD5Pin)), - (("otg", "ULPI_D6"), quote!(crate::usb_otg::UlpiD6Pin)), - (("otg", "ULPI_D7"), quote!(crate::usb_otg::UlpiD7Pin)), + (("otg", "DP"), quote!(crate::usb::DpPin)), + (("otg", "DM"), quote!(crate::usb::DmPin)), + (("otg", "ULPI_CK"), quote!(crate::usb::UlpiClkPin)), + (("otg", "ULPI_DIR"), quote!(crate::usb::UlpiDirPin)), + (("otg", "ULPI_NXT"), quote!(crate::usb::UlpiNxtPin)), + (("otg", "ULPI_STP"), quote!(crate::usb::UlpiStpPin)), + (("otg", "ULPI_D0"), quote!(crate::usb::UlpiD0Pin)), + (("otg", "ULPI_D1"), quote!(crate::usb::UlpiD1Pin)), + (("otg", "ULPI_D2"), quote!(crate::usb::UlpiD2Pin)), + (("otg", "ULPI_D3"), quote!(crate::usb::UlpiD3Pin)), + (("otg", "ULPI_D4"), quote!(crate::usb::UlpiD4Pin)), + (("otg", "ULPI_D5"), quote!(crate::usb::UlpiD5Pin)), + (("otg", "ULPI_D6"), quote!(crate::usb::UlpiD6Pin)), + (("otg", "ULPI_D7"), quote!(crate::usb::UlpiD7Pin)), (("can", "TX"), quote!(crate::can::TxPin)), (("can", "RX"), quote!(crate::can::RxPin)), (("eth", "REF_CLK"), quote!(crate::eth::RefClkPin)), diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index b38b5c29d..9e26a3513 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -79,10 +79,8 @@ pub mod ucpd; pub mod uid; #[cfg(usart)] pub mod usart; -#[cfg(usb)] +#[cfg(any(usb, otg))] pub mod usb; -#[cfg(otg)] -pub mod usb_otg; #[cfg(iwdg)] pub mod wdg; @@ -107,10 +105,10 @@ pub use crate::_generated::interrupt; /// Example of how to bind one interrupt: /// /// ```rust,ignore -/// use embassy_stm32::{bind_interrupts, usb_otg, peripherals}; +/// use embassy_stm32::{bind_interrupts, usb, peripherals}; /// /// bind_interrupts!(struct Irqs { -/// OTG_FS => usb_otg::InterruptHandler; +/// OTG_FS => usb::InterruptHandler; /// }); /// ``` /// diff --git a/embassy-stm32/src/usb/mod.rs b/embassy-stm32/src/usb/mod.rs index 4debd4e54..974880900 100644 --- a/embassy-stm32/src/usb/mod.rs +++ b/embassy-stm32/src/usb/mod.rs @@ -1,37 +1,6 @@ //! Universal Serial Bus (USB) -use crate::interrupt; -use crate::rcc::RccPeripheral; - -mod usb; -pub use usb::*; - -pub(crate) mod sealed { - pub trait Instance { - fn regs() -> crate::pac::usb::Usb; - } -} - -/// USB instance trait. -pub trait Instance: sealed::Instance + RccPeripheral + 'static { - /// Interrupt for this USB instance. - type Interrupt: interrupt::typelevel::Interrupt; -} - -// Internal PHY pins -pin_trait!(DpPin, Instance); -pin_trait!(DmPin, Instance); - -foreach_interrupt!( - ($inst:ident, usb, $block:ident, LP, $irq:ident) => { - impl sealed::Instance for crate::peripherals::$inst { - fn regs() -> crate::pac::usb::Usb { - crate::pac::$inst - } - } - - impl Instance for crate::peripherals::$inst { - type Interrupt = crate::interrupt::typelevel::$irq; - } - }; -); +#[cfg_attr(usb, path = "usb.rs")] +#[cfg_attr(otg, path = "otg.rs")] +mod _version; +pub use _version::*; diff --git a/embassy-stm32/src/usb/otg.rs b/embassy-stm32/src/usb/otg.rs new file mode 100644 index 000000000..d15d929f6 --- /dev/null +++ b/embassy-stm32/src/usb/otg.rs @@ -0,0 +1,1626 @@ +use core::cell::UnsafeCell; +use core::marker::PhantomData; +use core::sync::atomic::{AtomicBool, AtomicU16, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::{into_ref, Peripheral}; +use embassy_sync::waitqueue::AtomicWaker; +use embassy_usb_driver::{ + self, Bus as _, Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointIn, EndpointInfo, + EndpointOut, EndpointType, Event, Unsupported, +}; +use futures::future::poll_fn; + +use crate::gpio::sealed::AFType; +use crate::interrupt; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::otg::{regs, vals}; +use crate::rcc::sealed::RccPeripheral; +use crate::time::Hertz; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + trace!("irq"); + let r = T::regs(); + let state = T::state(); + + let ints = r.gintsts().read(); + if ints.wkupint() || ints.usbsusp() || ints.usbrst() || ints.enumdne() || ints.otgint() || ints.srqint() { + // Mask interrupts and notify `Bus` to process them + r.gintmsk().write(|_| {}); + T::state().bus_waker.wake(); + } + + // Handle RX + while r.gintsts().read().rxflvl() { + let status = r.grxstsp().read(); + trace!("=== status {:08x}", status.0); + let ep_num = status.epnum() as usize; + let len = status.bcnt() as usize; + + assert!(ep_num < T::ENDPOINT_COUNT); + + match status.pktstsd() { + vals::Pktstsd::SETUP_DATA_RX => { + trace!("SETUP_DATA_RX"); + assert!(len == 8, "invalid SETUP packet length={}", len); + assert!(ep_num == 0, "invalid SETUP packet endpoint={}", ep_num); + + // flushing TX if something stuck in control endpoint + if r.dieptsiz(ep_num).read().pktcnt() != 0 { + r.grstctl().modify(|w| { + w.set_txfnum(ep_num as _); + w.set_txfflsh(true); + }); + while r.grstctl().read().txfflsh() {} + } + + if state.ep0_setup_ready.load(Ordering::Relaxed) == false { + // SAFETY: exclusive access ensured by atomic bool + let data = unsafe { &mut *state.ep0_setup_data.get() }; + data[0..4].copy_from_slice(&r.fifo(0).read().0.to_ne_bytes()); + data[4..8].copy_from_slice(&r.fifo(0).read().0.to_ne_bytes()); + state.ep0_setup_ready.store(true, Ordering::Release); + state.ep_out_wakers[0].wake(); + } else { + error!("received SETUP before previous finished processing"); + // discard FIFO + r.fifo(0).read(); + r.fifo(0).read(); + } + } + vals::Pktstsd::OUT_DATA_RX => { + trace!("OUT_DATA_RX ep={} len={}", ep_num, len); + + if state.ep_out_size[ep_num].load(Ordering::Acquire) == EP_OUT_BUFFER_EMPTY { + // SAFETY: Buffer size is allocated to be equal to endpoint's maximum packet size + // We trust the peripheral to not exceed its configured MPSIZ + let buf = unsafe { core::slice::from_raw_parts_mut(*state.ep_out_buffers[ep_num].get(), len) }; + + for chunk in buf.chunks_mut(4) { + // RX FIFO is shared so always read from fifo(0) + let data = r.fifo(0).read().0; + chunk.copy_from_slice(&data.to_ne_bytes()[0..chunk.len()]); + } + + state.ep_out_size[ep_num].store(len as u16, Ordering::Release); + state.ep_out_wakers[ep_num].wake(); + } else { + error!("ep_out buffer overflow index={}", ep_num); + + // discard FIFO data + let len_words = (len + 3) / 4; + for _ in 0..len_words { + r.fifo(0).read().data(); + } + } + } + vals::Pktstsd::OUT_DATA_DONE => { + trace!("OUT_DATA_DONE ep={}", ep_num); + } + vals::Pktstsd::SETUP_DATA_DONE => { + trace!("SETUP_DATA_DONE ep={}", ep_num); + + if quirk_setup_late_cnak(r) { + // Clear NAK to indicate we are ready to receive more data + r.doepctl(ep_num).modify(|w| w.set_cnak(true)); + } + } + x => trace!("unknown PKTSTS: {}", x.to_bits()), + } + } + + // IN endpoint interrupt + if ints.iepint() { + let mut ep_mask = r.daint().read().iepint(); + let mut ep_num = 0; + + // Iterate over endpoints while there are non-zero bits in the mask + while ep_mask != 0 { + if ep_mask & 1 != 0 { + let ep_ints = r.diepint(ep_num).read(); + + // clear all + r.diepint(ep_num).write_value(ep_ints); + + // TXFE is cleared in DIEPEMPMSK + if ep_ints.txfe() { + critical_section::with(|_| { + r.diepempmsk().modify(|w| { + w.set_ineptxfem(w.ineptxfem() & !(1 << ep_num)); + }); + }); + } + + state.ep_in_wakers[ep_num].wake(); + trace!("in ep={} irq val={:08x}", ep_num, ep_ints.0); + } + + ep_mask >>= 1; + ep_num += 1; + } + } + + // not needed? reception handled in rxflvl + // OUT endpoint interrupt + // if ints.oepint() { + // let mut ep_mask = r.daint().read().oepint(); + // let mut ep_num = 0; + + // while ep_mask != 0 { + // if ep_mask & 1 != 0 { + // let ep_ints = r.doepint(ep_num).read(); + // // clear all + // r.doepint(ep_num).write_value(ep_ints); + // state.ep_out_wakers[ep_num].wake(); + // trace!("out ep={} irq val={:08x}", ep_num, ep_ints.0); + // } + + // ep_mask >>= 1; + // ep_num += 1; + // } + // } + } +} + +macro_rules! config_ulpi_pins { + ($($pin:ident),*) => { + into_ref!($($pin),*); + critical_section::with(|_| { + $( + $pin.set_as_af($pin.af_num(), AFType::OutputPushPull); + #[cfg(gpio_v2)] + $pin.set_speed(crate::gpio::Speed::VeryHigh); + )* + }) + }; +} + +// From `synopsys-usb-otg` crate: +// This calculation doesn't correspond to one in a Reference Manual. +// In fact, the required number of words is higher than indicated in RM. +// The following numbers are pessimistic and were figured out empirically. +const RX_FIFO_EXTRA_SIZE_WORDS: u16 = 30; + +/// USB PHY type +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum PhyType { + /// Internal Full-Speed PHY + /// + /// Available on most High-Speed peripherals. + InternalFullSpeed, + /// Internal High-Speed PHY + /// + /// Available on a few STM32 chips. + InternalHighSpeed, + /// External ULPI High-Speed PHY + ExternalHighSpeed, +} + +impl PhyType { + /// Get whether this PHY is any of the internal types. + pub fn internal(&self) -> bool { + match self { + PhyType::InternalFullSpeed | PhyType::InternalHighSpeed => true, + PhyType::ExternalHighSpeed => false, + } + } + + /// Get whether this PHY is any of the high-speed types. + pub fn high_speed(&self) -> bool { + match self { + PhyType::InternalFullSpeed => false, + PhyType::ExternalHighSpeed | PhyType::InternalHighSpeed => true, + } + } + + fn to_dspd(&self) -> vals::Dspd { + match self { + PhyType::InternalFullSpeed => vals::Dspd::FULL_SPEED_INTERNAL, + PhyType::InternalHighSpeed => vals::Dspd::HIGH_SPEED, + PhyType::ExternalHighSpeed => vals::Dspd::HIGH_SPEED, + } + } +} + +/// Indicates that [State::ep_out_buffers] is empty. +const EP_OUT_BUFFER_EMPTY: u16 = u16::MAX; + +/// USB OTG driver state. +pub struct State { + /// Holds received SETUP packets. Available if [State::ep0_setup_ready] is true. + ep0_setup_data: UnsafeCell<[u8; 8]>, + ep0_setup_ready: AtomicBool, + ep_in_wakers: [AtomicWaker; EP_COUNT], + ep_out_wakers: [AtomicWaker; EP_COUNT], + /// RX FIFO is shared so extra buffers are needed to dequeue all data without waiting on each endpoint. + /// Buffers are ready when associated [State::ep_out_size] != [EP_OUT_BUFFER_EMPTY]. + ep_out_buffers: [UnsafeCell<*mut u8>; EP_COUNT], + ep_out_size: [AtomicU16; EP_COUNT], + bus_waker: AtomicWaker, +} + +unsafe impl Send for State {} +unsafe impl Sync for State {} + +impl State { + /// Create a new State. + pub const fn new() -> Self { + const NEW_AW: AtomicWaker = AtomicWaker::new(); + const NEW_BUF: UnsafeCell<*mut u8> = UnsafeCell::new(0 as _); + const NEW_SIZE: AtomicU16 = AtomicU16::new(EP_OUT_BUFFER_EMPTY); + + Self { + ep0_setup_data: UnsafeCell::new([0u8; 8]), + ep0_setup_ready: AtomicBool::new(false), + ep_in_wakers: [NEW_AW; EP_COUNT], + ep_out_wakers: [NEW_AW; EP_COUNT], + ep_out_buffers: [NEW_BUF; EP_COUNT], + ep_out_size: [NEW_SIZE; EP_COUNT], + bus_waker: NEW_AW, + } + } +} + +#[derive(Debug, Clone, Copy)] +struct EndpointData { + ep_type: EndpointType, + max_packet_size: u16, + fifo_size_words: u16, +} + +/// USB driver config. +#[non_exhaustive] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Config { + /// Enable VBUS detection. + /// + /// The USB spec requires USB devices monitor for USB cable plug/unplug and react accordingly. + /// This is done by checkihg whether there is 5V on the VBUS pin or not. + /// + /// If your device is bus-powered (powers itself from the USB host via VBUS), then this is optional. + /// (if there's no power in VBUS your device would be off anyway, so it's fine to always assume + /// there's power in VBUS, i.e. the USB cable is always plugged in.) + /// + /// If your device is self-powered (i.e. it gets power from a source other than the USB cable, and + /// therefore can stay powered through USB cable plug/unplug) then you MUST set this to true. + /// + /// If you set this to true, you must connect VBUS to PA9 for FS, PB13 for HS, possibly with a + /// voltage divider. See ST application note AN4879 and the reference manual for more details. + pub vbus_detection: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { vbus_detection: true } + } +} + +/// USB driver. +pub struct Driver<'d, T: Instance> { + config: Config, + phantom: PhantomData<&'d mut T>, + ep_in: [Option; MAX_EP_COUNT], + ep_out: [Option; MAX_EP_COUNT], + ep_out_buffer: &'d mut [u8], + ep_out_buffer_offset: usize, + phy_type: PhyType, +} + +impl<'d, T: Instance> Driver<'d, T> { + /// Initializes USB OTG peripheral with internal Full-Speed PHY. + /// + /// # Arguments + /// + /// * `ep_out_buffer` - An internal buffer used to temporarily store recevied packets. + /// Must be large enough to fit all OUT endpoint max packet sizes. + /// Endpoint allocation will fail if it is too small. + pub fn new_fs( + _peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + dp: impl Peripheral

> + 'd, + dm: impl Peripheral

> + 'd, + ep_out_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(dp, dm); + + dp.set_as_af(dp.af_num(), AFType::OutputPushPull); + dm.set_as_af(dm.af_num(), AFType::OutputPushPull); + + Self { + config, + phantom: PhantomData, + ep_in: [None; MAX_EP_COUNT], + ep_out: [None; MAX_EP_COUNT], + ep_out_buffer, + ep_out_buffer_offset: 0, + phy_type: PhyType::InternalFullSpeed, + } + } + + /// Initializes USB OTG peripheral with external High-Speed PHY. + /// + /// # Arguments + /// + /// * `ep_out_buffer` - An internal buffer used to temporarily store recevied packets. + /// Must be large enough to fit all OUT endpoint max packet sizes. + /// Endpoint allocation will fail if it is too small. + pub fn new_hs_ulpi( + _peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + ulpi_clk: impl Peripheral

> + 'd, + ulpi_dir: impl Peripheral

> + 'd, + ulpi_nxt: impl Peripheral

> + 'd, + ulpi_stp: impl Peripheral

> + 'd, + ulpi_d0: impl Peripheral

> + 'd, + ulpi_d1: impl Peripheral

> + 'd, + ulpi_d2: impl Peripheral

> + 'd, + ulpi_d3: impl Peripheral

> + 'd, + ulpi_d4: impl Peripheral

> + 'd, + ulpi_d5: impl Peripheral

> + 'd, + ulpi_d6: impl Peripheral

> + 'd, + ulpi_d7: impl Peripheral

> + 'd, + ep_out_buffer: &'d mut [u8], + config: Config, + ) -> Self { + assert!(T::HIGH_SPEED == true, "Peripheral is not capable of high-speed USB"); + + config_ulpi_pins!( + ulpi_clk, ulpi_dir, ulpi_nxt, ulpi_stp, ulpi_d0, ulpi_d1, ulpi_d2, ulpi_d3, ulpi_d4, ulpi_d5, ulpi_d6, + ulpi_d7 + ); + + Self { + config, + phantom: PhantomData, + ep_in: [None; MAX_EP_COUNT], + ep_out: [None; MAX_EP_COUNT], + ep_out_buffer, + ep_out_buffer_offset: 0, + phy_type: PhyType::ExternalHighSpeed, + } + } + + // Returns total amount of words (u32) allocated in dedicated FIFO + fn allocated_fifo_words(&self) -> u16 { + RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out) + ep_fifo_size(&self.ep_in) + } + + fn alloc_endpoint( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result, EndpointAllocError> { + trace!( + "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", + ep_type, + max_packet_size, + interval_ms, + D::dir() + ); + + if D::dir() == Direction::Out { + if self.ep_out_buffer_offset + max_packet_size as usize >= self.ep_out_buffer.len() { + error!("Not enough endpoint out buffer capacity"); + return Err(EndpointAllocError); + } + }; + + let fifo_size_words = match D::dir() { + Direction::Out => (max_packet_size + 3) / 4, + // INEPTXFD requires minimum size of 16 words + Direction::In => u16::max((max_packet_size + 3) / 4, 16), + }; + + if fifo_size_words + self.allocated_fifo_words() > T::FIFO_DEPTH_WORDS { + error!("Not enough FIFO capacity"); + return Err(EndpointAllocError); + } + + let eps = match D::dir() { + Direction::Out => &mut self.ep_out, + Direction::In => &mut self.ep_in, + }; + + // Find free endpoint slot + let slot = eps.iter_mut().enumerate().find(|(i, ep)| { + if *i == 0 && ep_type != EndpointType::Control { + // reserved for control pipe + false + } else { + ep.is_none() + } + }); + + let index = match slot { + Some((index, ep)) => { + *ep = Some(EndpointData { + ep_type, + max_packet_size, + fifo_size_words, + }); + index + } + None => { + error!("No free endpoints available"); + return Err(EndpointAllocError); + } + }; + + trace!(" index={}", index); + + if D::dir() == Direction::Out { + // Buffer capacity check was done above, now allocation cannot fail + unsafe { + *T::state().ep_out_buffers[index].get() = + self.ep_out_buffer.as_mut_ptr().offset(self.ep_out_buffer_offset as _); + } + self.ep_out_buffer_offset += max_packet_size as usize; + } + + Ok(Endpoint { + _phantom: PhantomData, + info: EndpointInfo { + addr: EndpointAddress::from_parts(index, D::dir()), + ep_type, + max_packet_size, + interval_ms, + }, + }) + } +} + +impl<'d, T: Instance> embassy_usb_driver::Driver<'d> for Driver<'d, T> { + type EndpointOut = Endpoint<'d, T, Out>; + type EndpointIn = Endpoint<'d, T, In>; + type ControlPipe = ControlPipe<'d, T>; + type Bus = Bus<'d, T>; + + fn alloc_endpoint_in( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) + } + + fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) + } + + fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { + let ep_out = self + .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0) + .unwrap(); + let ep_in = self + .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0) + .unwrap(); + assert_eq!(ep_out.info.addr.index(), 0); + assert_eq!(ep_in.info.addr.index(), 0); + + trace!("start"); + + ( + Bus { + config: self.config, + phantom: PhantomData, + ep_in: self.ep_in, + ep_out: self.ep_out, + phy_type: self.phy_type, + inited: false, + }, + ControlPipe { + _phantom: PhantomData, + max_packet_size: control_max_packet_size, + ep_out, + ep_in, + }, + ) + } +} + +/// USB bus. +pub struct Bus<'d, T: Instance> { + config: Config, + phantom: PhantomData<&'d mut T>, + ep_in: [Option; MAX_EP_COUNT], + ep_out: [Option; MAX_EP_COUNT], + phy_type: PhyType, + inited: bool, +} + +impl<'d, T: Instance> Bus<'d, T> { + fn restore_irqs() { + T::regs().gintmsk().write(|w| { + w.set_usbrst(true); + w.set_enumdnem(true); + w.set_usbsuspm(true); + w.set_wuim(true); + w.set_iepint(true); + w.set_oepint(true); + w.set_rxflvlm(true); + w.set_srqim(true); + w.set_otgint(true); + }); + } +} + +impl<'d, T: Instance> Bus<'d, T> { + fn init(&mut self) { + #[cfg(stm32l4)] + critical_section::with(|_| crate::pac::PWR.cr2().modify(|w| w.set_usv(true))); + + #[cfg(stm32f7)] + { + // Enable ULPI clock if external PHY is used + let ulpien = !self.phy_type.internal(); + critical_section::with(|_| { + crate::pac::RCC.ahb1enr().modify(|w| { + if T::HIGH_SPEED { + w.set_usb_otg_hsulpien(ulpien); + } else { + w.set_usb_otg_hsen(ulpien); + } + }); + + // Low power mode + crate::pac::RCC.ahb1lpenr().modify(|w| { + if T::HIGH_SPEED { + w.set_usb_otg_hsulpilpen(ulpien); + } else { + w.set_usb_otg_hslpen(ulpien); + } + }); + }); + } + + #[cfg(stm32h7)] + { + // If true, VDD33USB is generated by internal regulator from VDD50USB + // If false, VDD33USB and VDD50USB must be suplied directly with 3.3V (default on nucleo) + // TODO: unhardcode + let internal_regulator = false; + + // Enable USB power + critical_section::with(|_| { + crate::pac::PWR.cr3().modify(|w| { + w.set_usb33den(true); + w.set_usbregen(internal_regulator); + }) + }); + + // Wait for USB power to stabilize + while !crate::pac::PWR.cr3().read().usb33rdy() {} + + // Enable ULPI clock if external PHY is used + let ulpien = !self.phy_type.internal(); + critical_section::with(|_| { + crate::pac::RCC.ahb1enr().modify(|w| { + if T::HIGH_SPEED { + w.set_usb_otg_hs_ulpien(ulpien); + } else { + w.set_usb_otg_fs_ulpien(ulpien); + } + }); + crate::pac::RCC.ahb1lpenr().modify(|w| { + if T::HIGH_SPEED { + w.set_usb_otg_hs_ulpilpen(ulpien); + } else { + w.set_usb_otg_fs_ulpilpen(ulpien); + } + }); + }); + } + + #[cfg(stm32u5)] + { + // Enable USB power + critical_section::with(|_| { + crate::pac::PWR.svmcr().modify(|w| { + w.set_usv(true); + w.set_uvmen(true); + }) + }); + + // Wait for USB power to stabilize + while !crate::pac::PWR.svmsr().read().vddusbrdy() {} + } + + ::enable_and_reset(); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + let r = T::regs(); + let core_id = r.cid().read().0; + trace!("Core id {:08x}", core_id); + + // Wait for AHB ready. + while !r.grstctl().read().ahbidl() {} + + // Configure as device. + r.gusbcfg().write(|w| { + // Force device mode + w.set_fdmod(true); + // Enable internal full-speed PHY + w.set_physel(self.phy_type.internal() && !self.phy_type.high_speed()); + }); + + // Configuring Vbus sense and SOF output + match core_id { + 0x0000_1200 | 0x0000_1100 => { + assert!(self.phy_type != PhyType::InternalHighSpeed); + + r.gccfg_v1().modify(|w| { + // Enable internal full-speed PHY, logic is inverted + w.set_pwrdwn(self.phy_type.internal()); + }); + + // F429-like chips have the GCCFG.NOVBUSSENS bit + r.gccfg_v1().modify(|w| { + w.set_novbussens(!self.config.vbus_detection); + w.set_vbusasen(false); + w.set_vbusbsen(self.config.vbus_detection); + w.set_sofouten(false); + }); + } + 0x0000_2000 | 0x0000_2100 | 0x0000_2300 | 0x0000_3000 | 0x0000_3100 => { + // F446-like chips have the GCCFG.VBDEN bit with the opposite meaning + r.gccfg_v2().modify(|w| { + // Enable internal full-speed PHY, logic is inverted + w.set_pwrdwn(self.phy_type.internal() && !self.phy_type.high_speed()); + w.set_phyhsen(self.phy_type.internal() && self.phy_type.high_speed()); + }); + + r.gccfg_v2().modify(|w| { + w.set_vbden(self.config.vbus_detection); + }); + + // Force B-peripheral session + r.gotgctl().modify(|w| { + w.set_bvaloen(!self.config.vbus_detection); + w.set_bvaloval(true); + }); + } + _ => unimplemented!("Unknown USB core id {:X}", core_id), + } + + // Soft disconnect. + r.dctl().write(|w| w.set_sdis(true)); + + // Set speed. + r.dcfg().write(|w| { + w.set_pfivl(vals::Pfivl::FRAME_INTERVAL_80); + w.set_dspd(self.phy_type.to_dspd()); + }); + + // Unmask transfer complete EP interrupt + r.diepmsk().write(|w| { + w.set_xfrcm(true); + }); + + // Unmask and clear core interrupts + Bus::::restore_irqs(); + r.gintsts().write_value(regs::Gintsts(0xFFFF_FFFF)); + + // Unmask global interrupt + r.gahbcfg().write(|w| { + w.set_gint(true); // unmask global interrupt + }); + + // Connect + r.dctl().write(|w| w.set_sdis(false)); + } + + fn init_fifo(&mut self) { + trace!("init_fifo"); + + let r = T::regs(); + + // Configure RX fifo size. All endpoints share the same FIFO area. + let rx_fifo_size_words = RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out); + trace!("configuring rx fifo size={}", rx_fifo_size_words); + + r.grxfsiz().modify(|w| w.set_rxfd(rx_fifo_size_words)); + + // Configure TX (USB in direction) fifo size for each endpoint + let mut fifo_top = rx_fifo_size_words; + for i in 0..T::ENDPOINT_COUNT { + if let Some(ep) = self.ep_in[i] { + trace!( + "configuring tx fifo ep={}, offset={}, size={}", + i, + fifo_top, + ep.fifo_size_words + ); + + let dieptxf = if i == 0 { r.dieptxf0() } else { r.dieptxf(i - 1) }; + + dieptxf.write(|w| { + w.set_fd(ep.fifo_size_words); + w.set_sa(fifo_top); + }); + + fifo_top += ep.fifo_size_words; + } + } + + assert!( + fifo_top <= T::FIFO_DEPTH_WORDS, + "FIFO allocations exceeded maximum capacity" + ); + + // Flush fifos + r.grstctl().write(|w| { + w.set_rxfflsh(true); + w.set_txfflsh(true); + w.set_txfnum(0x10); + }); + loop { + let x = r.grstctl().read(); + if !x.rxfflsh() && !x.txfflsh() { + break; + } + } + } + + fn configure_endpoints(&mut self) { + trace!("configure_endpoints"); + + let r = T::regs(); + + // Configure IN endpoints + for (index, ep) in self.ep_in.iter().enumerate() { + if let Some(ep) = ep { + critical_section::with(|_| { + r.diepctl(index).write(|w| { + if index == 0 { + w.set_mpsiz(ep0_mpsiz(ep.max_packet_size)); + } else { + w.set_mpsiz(ep.max_packet_size); + w.set_eptyp(to_eptyp(ep.ep_type)); + w.set_sd0pid_sevnfrm(true); + w.set_txfnum(index as _); + w.set_snak(true); + } + }); + }); + } + } + + // Configure OUT endpoints + for (index, ep) in self.ep_out.iter().enumerate() { + if let Some(ep) = ep { + critical_section::with(|_| { + r.doepctl(index).write(|w| { + if index == 0 { + w.set_mpsiz(ep0_mpsiz(ep.max_packet_size)); + } else { + w.set_mpsiz(ep.max_packet_size); + w.set_eptyp(to_eptyp(ep.ep_type)); + w.set_sd0pid_sevnfrm(true); + } + }); + + r.doeptsiz(index).modify(|w| { + w.set_xfrsiz(ep.max_packet_size as _); + if index == 0 { + w.set_rxdpid_stupcnt(1); + } else { + w.set_pktcnt(1); + } + }); + }); + } + } + + // Enable IRQs for allocated endpoints + r.daintmsk().modify(|w| { + w.set_iepm(ep_irq_mask(&self.ep_in)); + // OUT interrupts not used, handled in RXFLVL + // w.set_oepm(ep_irq_mask(&self.ep_out)); + }); + } + + fn disable_all_endpoints(&mut self) { + for i in 0..T::ENDPOINT_COUNT { + self.endpoint_set_enabled(EndpointAddress::from_parts(i, Direction::In), false); + self.endpoint_set_enabled(EndpointAddress::from_parts(i, Direction::Out), false); + } + } + + fn disable(&mut self) { + T::Interrupt::disable(); + + ::disable(); + + #[cfg(stm32l4)] + crate::pac::PWR.cr2().modify(|w| w.set_usv(false)); + // Cannot disable PWR, because other peripherals might be using it + } +} + +impl<'d, T: Instance> embassy_usb_driver::Bus for Bus<'d, T> { + async fn poll(&mut self) -> Event { + poll_fn(move |cx| { + if !self.inited { + self.init(); + self.inited = true; + + // If no vbus detection, just return a single PowerDetected event at startup. + if !self.config.vbus_detection { + return Poll::Ready(Event::PowerDetected); + } + } + + let r = T::regs(); + + T::state().bus_waker.register(cx.waker()); + + let ints = r.gintsts().read(); + + if ints.srqint() { + trace!("vbus detected"); + + r.gintsts().write(|w| w.set_srqint(true)); // clear + Self::restore_irqs(); + + if self.config.vbus_detection { + return Poll::Ready(Event::PowerDetected); + } + } + + if ints.otgint() { + let otgints = r.gotgint().read(); + r.gotgint().write_value(otgints); // clear all + Self::restore_irqs(); + + if otgints.sedet() { + trace!("vbus removed"); + if self.config.vbus_detection { + self.disable_all_endpoints(); + return Poll::Ready(Event::PowerRemoved); + } + } + } + + if ints.usbrst() { + trace!("reset"); + + self.init_fifo(); + self.configure_endpoints(); + + // Reset address + critical_section::with(|_| { + r.dcfg().modify(|w| { + w.set_dad(0); + }); + }); + + r.gintsts().write(|w| w.set_usbrst(true)); // clear + Self::restore_irqs(); + } + + if ints.enumdne() { + trace!("enumdne"); + + let speed = r.dsts().read().enumspd(); + let trdt = calculate_trdt(speed, T::frequency()); + trace!(" speed={} trdt={}", speed.to_bits(), trdt); + r.gusbcfg().modify(|w| w.set_trdt(trdt)); + + r.gintsts().write(|w| w.set_enumdne(true)); // clear + Self::restore_irqs(); + + return Poll::Ready(Event::Reset); + } + + if ints.usbsusp() { + trace!("suspend"); + r.gintsts().write(|w| w.set_usbsusp(true)); // clear + Self::restore_irqs(); + return Poll::Ready(Event::Suspend); + } + + if ints.wkupint() { + trace!("resume"); + r.gintsts().write(|w| w.set_wkupint(true)); // clear + Self::restore_irqs(); + return Poll::Ready(Event::Resume); + } + + Poll::Pending + }) + .await + } + + fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { + trace!("endpoint_set_stalled ep={:?} en={}", ep_addr, stalled); + + assert!( + ep_addr.index() < T::ENDPOINT_COUNT, + "endpoint_set_stalled index {} out of range", + ep_addr.index() + ); + + let regs = T::regs(); + match ep_addr.direction() { + Direction::Out => { + critical_section::with(|_| { + regs.doepctl(ep_addr.index()).modify(|w| { + w.set_stall(stalled); + }); + }); + + T::state().ep_out_wakers[ep_addr.index()].wake(); + } + Direction::In => { + critical_section::with(|_| { + regs.diepctl(ep_addr.index()).modify(|w| { + w.set_stall(stalled); + }); + }); + + T::state().ep_in_wakers[ep_addr.index()].wake(); + } + } + } + + fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool { + assert!( + ep_addr.index() < T::ENDPOINT_COUNT, + "endpoint_is_stalled index {} out of range", + ep_addr.index() + ); + + let regs = T::regs(); + + match ep_addr.direction() { + Direction::Out => regs.doepctl(ep_addr.index()).read().stall(), + Direction::In => regs.diepctl(ep_addr.index()).read().stall(), + } + } + + fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) { + trace!("endpoint_set_enabled ep={:?} en={}", ep_addr, enabled); + + assert!( + ep_addr.index() < T::ENDPOINT_COUNT, + "endpoint_set_enabled index {} out of range", + ep_addr.index() + ); + + let r = T::regs(); + match ep_addr.direction() { + Direction::Out => { + critical_section::with(|_| { + // cancel transfer if active + if !enabled && r.doepctl(ep_addr.index()).read().epena() { + r.doepctl(ep_addr.index()).modify(|w| { + w.set_snak(true); + w.set_epdis(true); + }) + } + + r.doepctl(ep_addr.index()).modify(|w| { + w.set_usbaep(enabled); + }); + + // Flush tx fifo + r.grstctl().write(|w| { + w.set_txfflsh(true); + w.set_txfnum(ep_addr.index() as _); + }); + loop { + let x = r.grstctl().read(); + if !x.txfflsh() { + break; + } + } + }); + + // Wake `Endpoint::wait_enabled()` + T::state().ep_out_wakers[ep_addr.index()].wake(); + } + Direction::In => { + critical_section::with(|_| { + // cancel transfer if active + if !enabled && r.diepctl(ep_addr.index()).read().epena() { + r.diepctl(ep_addr.index()).modify(|w| { + w.set_snak(true); // set NAK + w.set_epdis(true); + }) + } + + r.diepctl(ep_addr.index()).modify(|w| { + w.set_usbaep(enabled); + w.set_cnak(enabled); // clear NAK that might've been set by SNAK above. + }) + }); + + // Wake `Endpoint::wait_enabled()` + T::state().ep_in_wakers[ep_addr.index()].wake(); + } + } + } + + async fn enable(&mut self) { + trace!("enable"); + // TODO: enable the peripheral once enable/disable semantics are cleared up in embassy-usb + } + + async fn disable(&mut self) { + trace!("disable"); + + // TODO: disable the peripheral once enable/disable semantics are cleared up in embassy-usb + //Bus::disable(self); + } + + async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { + Err(Unsupported) + } +} + +impl<'d, T: Instance> Drop for Bus<'d, T> { + fn drop(&mut self) { + Bus::disable(self); + } +} + +trait Dir { + fn dir() -> Direction; +} + +/// Marker type for the "IN" direction. +pub enum In {} +impl Dir for In { + fn dir() -> Direction { + Direction::In + } +} + +/// Marker type for the "OUT" direction. +pub enum Out {} +impl Dir for Out { + fn dir() -> Direction { + Direction::Out + } +} + +/// USB endpoint. +pub struct Endpoint<'d, T: Instance, D> { + _phantom: PhantomData<(&'d mut T, D)>, + info: EndpointInfo, +} + +impl<'d, T: Instance> embassy_usb_driver::Endpoint for Endpoint<'d, T, In> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + async fn wait_enabled(&mut self) { + poll_fn(|cx| { + let ep_index = self.info.addr.index(); + + T::state().ep_in_wakers[ep_index].register(cx.waker()); + + if T::regs().diepctl(ep_index).read().usbaep() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await + } +} + +impl<'d, T: Instance> embassy_usb_driver::Endpoint for Endpoint<'d, T, Out> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + async fn wait_enabled(&mut self) { + poll_fn(|cx| { + let ep_index = self.info.addr.index(); + + T::state().ep_out_wakers[ep_index].register(cx.waker()); + + if T::regs().doepctl(ep_index).read().usbaep() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await + } +} + +impl<'d, T: Instance> embassy_usb_driver::EndpointOut for Endpoint<'d, T, Out> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + trace!("read start len={}", buf.len()); + + poll_fn(|cx| { + let r = T::regs(); + let index = self.info.addr.index(); + let state = T::state(); + + state.ep_out_wakers[index].register(cx.waker()); + + let doepctl = r.doepctl(index).read(); + trace!("read ep={:?}: doepctl {:08x}", self.info.addr, doepctl.0,); + if !doepctl.usbaep() { + trace!("read ep={:?} error disabled", self.info.addr); + return Poll::Ready(Err(EndpointError::Disabled)); + } + + let len = state.ep_out_size[index].load(Ordering::Relaxed); + if len != EP_OUT_BUFFER_EMPTY { + trace!("read ep={:?} done len={}", self.info.addr, len); + + if len as usize > buf.len() { + return Poll::Ready(Err(EndpointError::BufferOverflow)); + } + + // SAFETY: exclusive access ensured by `ep_out_size` atomic variable + let data = unsafe { core::slice::from_raw_parts(*state.ep_out_buffers[index].get(), len as usize) }; + buf[..len as usize].copy_from_slice(data); + + // Release buffer + state.ep_out_size[index].store(EP_OUT_BUFFER_EMPTY, Ordering::Release); + + critical_section::with(|_| { + // Receive 1 packet + T::regs().doeptsiz(index).modify(|w| { + w.set_xfrsiz(self.info.max_packet_size as _); + w.set_pktcnt(1); + }); + + // Clear NAK to indicate we are ready to receive more data + T::regs().doepctl(index).modify(|w| { + w.set_cnak(true); + }); + }); + + Poll::Ready(Ok(len as usize)) + } else { + Poll::Pending + } + }) + .await + } +} + +impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> { + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { + trace!("write ep={:?} data={:?}", self.info.addr, buf); + + if buf.len() > self.info.max_packet_size as usize { + return Err(EndpointError::BufferOverflow); + } + + let r = T::regs(); + let index = self.info.addr.index(); + let state = T::state(); + + // Wait for previous transfer to complete and check if endpoint is disabled + poll_fn(|cx| { + state.ep_in_wakers[index].register(cx.waker()); + + let diepctl = r.diepctl(index).read(); + let dtxfsts = r.dtxfsts(index).read(); + trace!( + "write ep={:?}: diepctl {:08x} ftxfsts {:08x}", + self.info.addr, + diepctl.0, + dtxfsts.0 + ); + if !diepctl.usbaep() { + trace!("write ep={:?} wait for prev: error disabled", self.info.addr); + Poll::Ready(Err(EndpointError::Disabled)) + } else if !diepctl.epena() { + trace!("write ep={:?} wait for prev: ready", self.info.addr); + Poll::Ready(Ok(())) + } else { + trace!("write ep={:?} wait for prev: pending", self.info.addr); + Poll::Pending + } + }) + .await?; + + if buf.len() > 0 { + poll_fn(|cx| { + state.ep_in_wakers[index].register(cx.waker()); + + let size_words = (buf.len() + 3) / 4; + + let fifo_space = r.dtxfsts(index).read().ineptfsav() as usize; + if size_words > fifo_space { + // Not enough space in fifo, enable tx fifo empty interrupt + critical_section::with(|_| { + r.diepempmsk().modify(|w| { + w.set_ineptxfem(w.ineptxfem() | (1 << index)); + }); + }); + + trace!("tx fifo for ep={} full, waiting for txfe", index); + + Poll::Pending + } else { + trace!("write ep={:?} wait for fifo: ready", self.info.addr); + Poll::Ready(()) + } + }) + .await + } + + // Setup transfer size + r.dieptsiz(index).write(|w| { + w.set_mcnt(1); + w.set_pktcnt(1); + w.set_xfrsiz(buf.len() as _); + }); + + critical_section::with(|_| { + // Enable endpoint + r.diepctl(index).modify(|w| { + w.set_cnak(true); + w.set_epena(true); + }); + }); + + // Write data to FIFO + for chunk in buf.chunks(4) { + let mut tmp = [0u8; 4]; + tmp[0..chunk.len()].copy_from_slice(chunk); + r.fifo(index).write_value(regs::Fifo(u32::from_ne_bytes(tmp))); + } + + trace!("write done ep={:?}", self.info.addr); + + Ok(()) + } +} + +/// USB control pipe. +pub struct ControlPipe<'d, T: Instance> { + _phantom: PhantomData<&'d mut T>, + max_packet_size: u16, + ep_in: Endpoint<'d, T, In>, + ep_out: Endpoint<'d, T, Out>, +} + +impl<'d, T: Instance> embassy_usb_driver::ControlPipe for ControlPipe<'d, T> { + fn max_packet_size(&self) -> usize { + usize::from(self.max_packet_size) + } + + async fn setup(&mut self) -> [u8; 8] { + poll_fn(|cx| { + let state = T::state(); + + state.ep_out_wakers[0].register(cx.waker()); + + let r = T::regs(); + + if state.ep0_setup_ready.load(Ordering::Relaxed) { + let data = unsafe { *state.ep0_setup_data.get() }; + state.ep0_setup_ready.store(false, Ordering::Release); + + // EP0 should not be controlled by `Bus` so this RMW does not need a critical section + // Receive 1 SETUP packet + r.doeptsiz(self.ep_out.info.addr.index()).modify(|w| { + w.set_rxdpid_stupcnt(1); + }); + + // Clear NAK to indicate we are ready to receive more data + if !quirk_setup_late_cnak(r) { + r.doepctl(self.ep_out.info.addr.index()).modify(|w| w.set_cnak(true)); + } + + trace!("SETUP received: {:?}", data); + Poll::Ready(data) + } else { + trace!("SETUP waiting"); + Poll::Pending + } + }) + .await + } + + async fn data_out(&mut self, buf: &mut [u8], _first: bool, _last: bool) -> Result { + trace!("control: data_out"); + let len = self.ep_out.read(buf).await?; + trace!("control: data_out read: {:?}", &buf[..len]); + Ok(len) + } + + async fn data_in(&mut self, data: &[u8], _first: bool, last: bool) -> Result<(), EndpointError> { + trace!("control: data_in write: {:?}", data); + self.ep_in.write(data).await?; + + // wait for status response from host after sending the last packet + if last { + trace!("control: data_in waiting for status"); + self.ep_out.read(&mut []).await?; + trace!("control: complete"); + } + + Ok(()) + } + + async fn accept(&mut self) { + trace!("control: accept"); + + self.ep_in.write(&[]).await.ok(); + + trace!("control: accept OK"); + } + + async fn reject(&mut self) { + trace!("control: reject"); + + // EP0 should not be controlled by `Bus` so this RMW does not need a critical section + let regs = T::regs(); + regs.diepctl(self.ep_in.info.addr.index()).modify(|w| { + w.set_stall(true); + }); + regs.doepctl(self.ep_out.info.addr.index()).modify(|w| { + w.set_stall(true); + }); + } + + async fn accept_set_address(&mut self, addr: u8) { + trace!("setting addr: {}", addr); + critical_section::with(|_| { + T::regs().dcfg().modify(|w| { + w.set_dad(addr); + }); + }); + + // synopsys driver requires accept to be sent after changing address + self.accept().await + } +} + +/// Translates HAL [EndpointType] into PAC [vals::Eptyp] +fn to_eptyp(ep_type: EndpointType) -> vals::Eptyp { + match ep_type { + EndpointType::Control => vals::Eptyp::CONTROL, + EndpointType::Isochronous => vals::Eptyp::ISOCHRONOUS, + EndpointType::Bulk => vals::Eptyp::BULK, + EndpointType::Interrupt => vals::Eptyp::INTERRUPT, + } +} + +/// Calculates total allocated FIFO size in words +fn ep_fifo_size(eps: &[Option]) -> u16 { + eps.iter().map(|ep| ep.map(|ep| ep.fifo_size_words).unwrap_or(0)).sum() +} + +/// Generates IRQ mask for enabled endpoints +fn ep_irq_mask(eps: &[Option]) -> u16 { + eps.iter().enumerate().fold( + 0, + |mask, (index, ep)| { + if ep.is_some() { + mask | (1 << index) + } else { + mask + } + }, + ) +} + +/// Calculates MPSIZ value for EP0, which uses special values. +fn ep0_mpsiz(max_packet_size: u16) -> u16 { + match max_packet_size { + 8 => 0b11, + 16 => 0b10, + 32 => 0b01, + 64 => 0b00, + other => panic!("Unsupported EP0 size: {}", other), + } +} + +fn calculate_trdt(speed: vals::Dspd, ahb_freq: Hertz) -> u8 { + match speed { + vals::Dspd::HIGH_SPEED => { + // From RM0431 (F72xx), RM0090 (F429), RM0390 (F446) + if ahb_freq.0 >= 30_000_000 { + 0x9 + } else { + panic!("AHB frequency is too low") + } + } + vals::Dspd::FULL_SPEED_EXTERNAL | vals::Dspd::FULL_SPEED_INTERNAL => { + // From RM0431 (F72xx), RM0090 (F429) + match ahb_freq.0 { + 0..=14_199_999 => panic!("AHB frequency is too low"), + 14_200_000..=14_999_999 => 0xF, + 15_000_000..=15_999_999 => 0xE, + 16_000_000..=17_199_999 => 0xD, + 17_200_000..=18_499_999 => 0xC, + 18_500_000..=19_999_999 => 0xB, + 20_000_000..=21_799_999 => 0xA, + 21_800_000..=23_999_999 => 0x9, + 24_000_000..=27_499_999 => 0x8, + 27_500_000..=31_999_999 => 0x7, // 27.7..32 in code from CubeIDE + 32_000_000..=u32::MAX => 0x6, + } + } + _ => unimplemented!(), + } +} + +fn quirk_setup_late_cnak(r: crate::pac::otg::Otg) -> bool { + r.cid().read().0 & 0xf000 == 0x1000 +} + +// Using Instance::ENDPOINT_COUNT requires feature(const_generic_expr) so just define maximum eps +const MAX_EP_COUNT: usize = 9; + +pub(crate) mod sealed { + pub trait Instance { + const HIGH_SPEED: bool; + const FIFO_DEPTH_WORDS: u16; + const ENDPOINT_COUNT: usize; + + fn regs() -> crate::pac::otg::Otg; + fn state() -> &'static super::State<{ super::MAX_EP_COUNT }>; + } +} + +/// USB instance trait. +pub trait Instance: sealed::Instance + RccPeripheral + 'static { + /// Interrupt for this USB instance. + type Interrupt: interrupt::typelevel::Interrupt; +} + +// Internal PHY pins +pin_trait!(DpPin, Instance); +pin_trait!(DmPin, Instance); + +// External PHY pins +pin_trait!(UlpiClkPin, Instance); +pin_trait!(UlpiDirPin, Instance); +pin_trait!(UlpiNxtPin, Instance); +pin_trait!(UlpiStpPin, Instance); +pin_trait!(UlpiD0Pin, Instance); +pin_trait!(UlpiD1Pin, Instance); +pin_trait!(UlpiD2Pin, Instance); +pin_trait!(UlpiD3Pin, Instance); +pin_trait!(UlpiD4Pin, Instance); +pin_trait!(UlpiD5Pin, Instance); +pin_trait!(UlpiD6Pin, Instance); +pin_trait!(UlpiD7Pin, Instance); + +foreach_interrupt!( + (USB_OTG_FS, otg, $block:ident, GLOBAL, $irq:ident) => { + impl sealed::Instance for crate::peripherals::USB_OTG_FS { + const HIGH_SPEED: bool = false; + + cfg_if::cfg_if! { + if #[cfg(stm32f1)] { + const FIFO_DEPTH_WORDS: u16 = 128; + const ENDPOINT_COUNT: usize = 8; + } else if #[cfg(any( + stm32f2, + stm32f401, + stm32f405, + stm32f407, + stm32f411, + stm32f415, + stm32f417, + stm32f427, + stm32f429, + stm32f437, + stm32f439, + ))] { + const FIFO_DEPTH_WORDS: u16 = 320; + const ENDPOINT_COUNT: usize = 4; + } else if #[cfg(any( + stm32f412, + stm32f413, + stm32f423, + stm32f446, + stm32f469, + stm32f479, + stm32f7, + stm32l4, + stm32u5, + ))] { + const FIFO_DEPTH_WORDS: u16 = 320; + const ENDPOINT_COUNT: usize = 6; + } else if #[cfg(stm32g0x1)] { + const FIFO_DEPTH_WORDS: u16 = 512; + const ENDPOINT_COUNT: usize = 8; + } else if #[cfg(stm32h7)] { + const FIFO_DEPTH_WORDS: u16 = 1024; + const ENDPOINT_COUNT: usize = 9; + } else if #[cfg(stm32u5)] { + const FIFO_DEPTH_WORDS: u16 = 320; + const ENDPOINT_COUNT: usize = 6; + } else { + compile_error!("USB_OTG_FS peripheral is not supported by this chip."); + } + } + + fn regs() -> crate::pac::otg::Otg { + crate::pac::USB_OTG_FS + } + + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + } + + impl Instance for crate::peripherals::USB_OTG_FS { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; + + (USB_OTG_HS, otg, $block:ident, GLOBAL, $irq:ident) => { + impl sealed::Instance for crate::peripherals::USB_OTG_HS { + const HIGH_SPEED: bool = true; + + cfg_if::cfg_if! { + if #[cfg(any( + stm32f2, + stm32f405, + stm32f407, + stm32f415, + stm32f417, + stm32f427, + stm32f429, + stm32f437, + stm32f439, + ))] { + const FIFO_DEPTH_WORDS: u16 = 1024; + const ENDPOINT_COUNT: usize = 6; + } else if #[cfg(any( + stm32f446, + stm32f469, + stm32f479, + stm32f7, + stm32h7, + ))] { + const FIFO_DEPTH_WORDS: u16 = 1024; + const ENDPOINT_COUNT: usize = 9; + } else if #[cfg(stm32u5)] { + const FIFO_DEPTH_WORDS: u16 = 1024; + const ENDPOINT_COUNT: usize = 9; + } else { + compile_error!("USB_OTG_HS peripheral is not supported by this chip."); + } + } + + fn regs() -> crate::pac::otg::Otg { + // OTG HS registers are a superset of FS registers + unsafe { crate::pac::otg::Otg::from_ptr(crate::pac::USB_OTG_HS.as_ptr()) } + } + + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + } + + impl Instance for crate::peripherals::USB_OTG_HS { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +); diff --git a/embassy-stm32/src/usb/usb.rs b/embassy-stm32/src/usb/usb.rs index be321a19b..b3b3470e4 100644 --- a/embassy-stm32/src/usb/usb.rs +++ b/embassy-stm32/src/usb/usb.rs @@ -12,7 +12,6 @@ use embassy_usb_driver::{ Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointInfo, EndpointType, Event, Unsupported, }; -use super::{DmPin, DpPin, Instance}; use crate::interrupt::typelevel::Interrupt; use crate::pac::usb::regs; use crate::pac::usb::vals::{EpType, Stat}; @@ -1057,3 +1056,33 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { }); } } + +pub(crate) mod sealed { + pub trait Instance { + fn regs() -> crate::pac::usb::Usb; + } +} + +/// USB instance trait. +pub trait Instance: sealed::Instance + RccPeripheral + 'static { + /// Interrupt for this USB instance. + type Interrupt: interrupt::typelevel::Interrupt; +} + +// Internal PHY pins +pin_trait!(DpPin, Instance); +pin_trait!(DmPin, Instance); + +foreach_interrupt!( + ($inst:ident, usb, $block:ident, LP, $irq:ident) => { + impl sealed::Instance for crate::peripherals::$inst { + fn regs() -> crate::pac::usb::Usb { + crate::pac::$inst + } + } + + impl Instance for crate::peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +); diff --git a/embassy-stm32/src/usb_otg/mod.rs b/embassy-stm32/src/usb_otg/mod.rs deleted file mode 100644 index 0649e684b..000000000 --- a/embassy-stm32/src/usb_otg/mod.rs +++ /dev/null @@ -1,163 +0,0 @@ -//! USB On The Go (OTG) - -use crate::rcc::RccPeripheral; -use crate::{interrupt, peripherals}; - -mod usb; -pub use usb::*; - -// Using Instance::ENDPOINT_COUNT requires feature(const_generic_expr) so just define maximum eps -const MAX_EP_COUNT: usize = 9; - -pub(crate) mod sealed { - pub trait Instance { - const HIGH_SPEED: bool; - const FIFO_DEPTH_WORDS: u16; - const ENDPOINT_COUNT: usize; - - fn regs() -> crate::pac::otg::Otg; - fn state() -> &'static super::State<{ super::MAX_EP_COUNT }>; - } -} - -/// USB OTG instance. -pub trait Instance: sealed::Instance + RccPeripheral { - /// Interrupt for this USB OTG instance. - type Interrupt: interrupt::typelevel::Interrupt; -} - -// Internal PHY pins -pin_trait!(DpPin, Instance); -pin_trait!(DmPin, Instance); - -// External PHY pins -pin_trait!(UlpiClkPin, Instance); -pin_trait!(UlpiDirPin, Instance); -pin_trait!(UlpiNxtPin, Instance); -pin_trait!(UlpiStpPin, Instance); -pin_trait!(UlpiD0Pin, Instance); -pin_trait!(UlpiD1Pin, Instance); -pin_trait!(UlpiD2Pin, Instance); -pin_trait!(UlpiD3Pin, Instance); -pin_trait!(UlpiD4Pin, Instance); -pin_trait!(UlpiD5Pin, Instance); -pin_trait!(UlpiD6Pin, Instance); -pin_trait!(UlpiD7Pin, Instance); - -foreach_interrupt!( - (USB_OTG_FS, otg, $block:ident, GLOBAL, $irq:ident) => { - impl sealed::Instance for peripherals::USB_OTG_FS { - const HIGH_SPEED: bool = false; - - cfg_if::cfg_if! { - if #[cfg(stm32f1)] { - const FIFO_DEPTH_WORDS: u16 = 128; - const ENDPOINT_COUNT: usize = 8; - } else if #[cfg(any( - stm32f2, - stm32f401, - stm32f405, - stm32f407, - stm32f411, - stm32f415, - stm32f417, - stm32f427, - stm32f429, - stm32f437, - stm32f439, - ))] { - const FIFO_DEPTH_WORDS: u16 = 320; - const ENDPOINT_COUNT: usize = 4; - } else if #[cfg(any( - stm32f412, - stm32f413, - stm32f423, - stm32f446, - stm32f469, - stm32f479, - stm32f7, - stm32l4, - stm32u5, - ))] { - const FIFO_DEPTH_WORDS: u16 = 320; - const ENDPOINT_COUNT: usize = 6; - } else if #[cfg(stm32g0x1)] { - const FIFO_DEPTH_WORDS: u16 = 512; - const ENDPOINT_COUNT: usize = 8; - } else if #[cfg(stm32h7)] { - const FIFO_DEPTH_WORDS: u16 = 1024; - const ENDPOINT_COUNT: usize = 9; - } else if #[cfg(stm32u5)] { - const FIFO_DEPTH_WORDS: u16 = 320; - const ENDPOINT_COUNT: usize = 6; - } else { - compile_error!("USB_OTG_FS peripheral is not supported by this chip."); - } - } - - fn regs() -> crate::pac::otg::Otg { - crate::pac::USB_OTG_FS - } - - fn state() -> &'static State { - static STATE: State = State::new(); - &STATE - } - } - - impl Instance for peripherals::USB_OTG_FS { - type Interrupt = crate::interrupt::typelevel::$irq; - } - }; - - (USB_OTG_HS, otg, $block:ident, GLOBAL, $irq:ident) => { - impl sealed::Instance for peripherals::USB_OTG_HS { - const HIGH_SPEED: bool = true; - - cfg_if::cfg_if! { - if #[cfg(any( - stm32f2, - stm32f405, - stm32f407, - stm32f415, - stm32f417, - stm32f427, - stm32f429, - stm32f437, - stm32f439, - ))] { - const FIFO_DEPTH_WORDS: u16 = 1024; - const ENDPOINT_COUNT: usize = 6; - } else if #[cfg(any( - stm32f446, - stm32f469, - stm32f479, - stm32f7, - stm32h7, - ))] { - const FIFO_DEPTH_WORDS: u16 = 1024; - const ENDPOINT_COUNT: usize = 9; - } else if #[cfg(stm32u5)] { - const FIFO_DEPTH_WORDS: u16 = 1024; - const ENDPOINT_COUNT: usize = 9; - } else { - compile_error!("USB_OTG_HS peripheral is not supported by this chip."); - } - } - - fn regs() -> crate::pac::otg::Otg { - // OTG HS registers are a superset of FS registers - unsafe { crate::pac::otg::Otg::from_ptr(crate::pac::USB_OTG_HS.as_ptr()) } - } - - fn state() -> &'static State { - static STATE: State = State::new(); - &STATE - } - } - - impl Instance for peripherals::USB_OTG_HS { - type Interrupt = crate::interrupt::typelevel::$irq; - } - }; -); diff --git a/embassy-stm32/src/usb_otg/usb.rs b/embassy-stm32/src/usb_otg/usb.rs deleted file mode 100644 index 373697ec8..000000000 --- a/embassy-stm32/src/usb_otg/usb.rs +++ /dev/null @@ -1,1471 +0,0 @@ -use core::cell::UnsafeCell; -use core::marker::PhantomData; -use core::sync::atomic::{AtomicBool, AtomicU16, Ordering}; -use core::task::Poll; - -use embassy_hal_internal::{into_ref, Peripheral}; -use embassy_sync::waitqueue::AtomicWaker; -use embassy_usb_driver::{ - self, Bus as _, Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointIn, EndpointInfo, - EndpointOut, EndpointType, Event, Unsupported, -}; -use futures::future::poll_fn; - -use super::*; -use crate::gpio::sealed::AFType; -use crate::interrupt; -use crate::interrupt::typelevel::Interrupt; -use crate::pac::otg::{regs, vals}; -use crate::rcc::sealed::RccPeripheral; -use crate::time::Hertz; - -/// Interrupt handler. -pub struct InterruptHandler { - _phantom: PhantomData, -} - -impl interrupt::typelevel::Handler for InterruptHandler { - unsafe fn on_interrupt() { - trace!("irq"); - let r = T::regs(); - let state = T::state(); - - let ints = r.gintsts().read(); - if ints.wkupint() || ints.usbsusp() || ints.usbrst() || ints.enumdne() || ints.otgint() || ints.srqint() { - // Mask interrupts and notify `Bus` to process them - r.gintmsk().write(|_| {}); - T::state().bus_waker.wake(); - } - - // Handle RX - while r.gintsts().read().rxflvl() { - let status = r.grxstsp().read(); - trace!("=== status {:08x}", status.0); - let ep_num = status.epnum() as usize; - let len = status.bcnt() as usize; - - assert!(ep_num < T::ENDPOINT_COUNT); - - match status.pktstsd() { - vals::Pktstsd::SETUP_DATA_RX => { - trace!("SETUP_DATA_RX"); - assert!(len == 8, "invalid SETUP packet length={}", len); - assert!(ep_num == 0, "invalid SETUP packet endpoint={}", ep_num); - - // flushing TX if something stuck in control endpoint - if r.dieptsiz(ep_num).read().pktcnt() != 0 { - r.grstctl().modify(|w| { - w.set_txfnum(ep_num as _); - w.set_txfflsh(true); - }); - while r.grstctl().read().txfflsh() {} - } - - if state.ep0_setup_ready.load(Ordering::Relaxed) == false { - // SAFETY: exclusive access ensured by atomic bool - let data = unsafe { &mut *state.ep0_setup_data.get() }; - data[0..4].copy_from_slice(&r.fifo(0).read().0.to_ne_bytes()); - data[4..8].copy_from_slice(&r.fifo(0).read().0.to_ne_bytes()); - state.ep0_setup_ready.store(true, Ordering::Release); - state.ep_out_wakers[0].wake(); - } else { - error!("received SETUP before previous finished processing"); - // discard FIFO - r.fifo(0).read(); - r.fifo(0).read(); - } - } - vals::Pktstsd::OUT_DATA_RX => { - trace!("OUT_DATA_RX ep={} len={}", ep_num, len); - - if state.ep_out_size[ep_num].load(Ordering::Acquire) == EP_OUT_BUFFER_EMPTY { - // SAFETY: Buffer size is allocated to be equal to endpoint's maximum packet size - // We trust the peripheral to not exceed its configured MPSIZ - let buf = unsafe { core::slice::from_raw_parts_mut(*state.ep_out_buffers[ep_num].get(), len) }; - - for chunk in buf.chunks_mut(4) { - // RX FIFO is shared so always read from fifo(0) - let data = r.fifo(0).read().0; - chunk.copy_from_slice(&data.to_ne_bytes()[0..chunk.len()]); - } - - state.ep_out_size[ep_num].store(len as u16, Ordering::Release); - state.ep_out_wakers[ep_num].wake(); - } else { - error!("ep_out buffer overflow index={}", ep_num); - - // discard FIFO data - let len_words = (len + 3) / 4; - for _ in 0..len_words { - r.fifo(0).read().data(); - } - } - } - vals::Pktstsd::OUT_DATA_DONE => { - trace!("OUT_DATA_DONE ep={}", ep_num); - } - vals::Pktstsd::SETUP_DATA_DONE => { - trace!("SETUP_DATA_DONE ep={}", ep_num); - - if quirk_setup_late_cnak(r) { - // Clear NAK to indicate we are ready to receive more data - r.doepctl(ep_num).modify(|w| w.set_cnak(true)); - } - } - x => trace!("unknown PKTSTS: {}", x.to_bits()), - } - } - - // IN endpoint interrupt - if ints.iepint() { - let mut ep_mask = r.daint().read().iepint(); - let mut ep_num = 0; - - // Iterate over endpoints while there are non-zero bits in the mask - while ep_mask != 0 { - if ep_mask & 1 != 0 { - let ep_ints = r.diepint(ep_num).read(); - - // clear all - r.diepint(ep_num).write_value(ep_ints); - - // TXFE is cleared in DIEPEMPMSK - if ep_ints.txfe() { - critical_section::with(|_| { - r.diepempmsk().modify(|w| { - w.set_ineptxfem(w.ineptxfem() & !(1 << ep_num)); - }); - }); - } - - state.ep_in_wakers[ep_num].wake(); - trace!("in ep={} irq val={:08x}", ep_num, ep_ints.0); - } - - ep_mask >>= 1; - ep_num += 1; - } - } - - // not needed? reception handled in rxflvl - // OUT endpoint interrupt - // if ints.oepint() { - // let mut ep_mask = r.daint().read().oepint(); - // let mut ep_num = 0; - - // while ep_mask != 0 { - // if ep_mask & 1 != 0 { - // let ep_ints = r.doepint(ep_num).read(); - // // clear all - // r.doepint(ep_num).write_value(ep_ints); - // state.ep_out_wakers[ep_num].wake(); - // trace!("out ep={} irq val={:08x}", ep_num, ep_ints.0); - // } - - // ep_mask >>= 1; - // ep_num += 1; - // } - // } - } -} - -macro_rules! config_ulpi_pins { - ($($pin:ident),*) => { - into_ref!($($pin),*); - critical_section::with(|_| { - $( - $pin.set_as_af($pin.af_num(), AFType::OutputPushPull); - #[cfg(gpio_v2)] - $pin.set_speed(crate::gpio::Speed::VeryHigh); - )* - }) - }; -} - -// From `synopsys-usb-otg` crate: -// This calculation doesn't correspond to one in a Reference Manual. -// In fact, the required number of words is higher than indicated in RM. -// The following numbers are pessimistic and were figured out empirically. -const RX_FIFO_EXTRA_SIZE_WORDS: u16 = 30; - -/// USB PHY type -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum PhyType { - /// Internal Full-Speed PHY - /// - /// Available on most High-Speed peripherals. - InternalFullSpeed, - /// Internal High-Speed PHY - /// - /// Available on a few STM32 chips. - InternalHighSpeed, - /// External ULPI High-Speed PHY - ExternalHighSpeed, -} - -impl PhyType { - /// Get whether this PHY is any of the internal types. - pub fn internal(&self) -> bool { - match self { - PhyType::InternalFullSpeed | PhyType::InternalHighSpeed => true, - PhyType::ExternalHighSpeed => false, - } - } - - /// Get whether this PHY is any of the high-speed types. - pub fn high_speed(&self) -> bool { - match self { - PhyType::InternalFullSpeed => false, - PhyType::ExternalHighSpeed | PhyType::InternalHighSpeed => true, - } - } - - fn to_dspd(&self) -> vals::Dspd { - match self { - PhyType::InternalFullSpeed => vals::Dspd::FULL_SPEED_INTERNAL, - PhyType::InternalHighSpeed => vals::Dspd::HIGH_SPEED, - PhyType::ExternalHighSpeed => vals::Dspd::HIGH_SPEED, - } - } -} - -/// Indicates that [State::ep_out_buffers] is empty. -const EP_OUT_BUFFER_EMPTY: u16 = u16::MAX; - -/// USB OTG driver state. -pub struct State { - /// Holds received SETUP packets. Available if [State::ep0_setup_ready] is true. - ep0_setup_data: UnsafeCell<[u8; 8]>, - ep0_setup_ready: AtomicBool, - ep_in_wakers: [AtomicWaker; EP_COUNT], - ep_out_wakers: [AtomicWaker; EP_COUNT], - /// RX FIFO is shared so extra buffers are needed to dequeue all data without waiting on each endpoint. - /// Buffers are ready when associated [State::ep_out_size] != [EP_OUT_BUFFER_EMPTY]. - ep_out_buffers: [UnsafeCell<*mut u8>; EP_COUNT], - ep_out_size: [AtomicU16; EP_COUNT], - bus_waker: AtomicWaker, -} - -unsafe impl Send for State {} -unsafe impl Sync for State {} - -impl State { - /// Create a new State. - pub const fn new() -> Self { - const NEW_AW: AtomicWaker = AtomicWaker::new(); - const NEW_BUF: UnsafeCell<*mut u8> = UnsafeCell::new(0 as _); - const NEW_SIZE: AtomicU16 = AtomicU16::new(EP_OUT_BUFFER_EMPTY); - - Self { - ep0_setup_data: UnsafeCell::new([0u8; 8]), - ep0_setup_ready: AtomicBool::new(false), - ep_in_wakers: [NEW_AW; EP_COUNT], - ep_out_wakers: [NEW_AW; EP_COUNT], - ep_out_buffers: [NEW_BUF; EP_COUNT], - ep_out_size: [NEW_SIZE; EP_COUNT], - bus_waker: NEW_AW, - } - } -} - -#[derive(Debug, Clone, Copy)] -struct EndpointData { - ep_type: EndpointType, - max_packet_size: u16, - fifo_size_words: u16, -} - -/// USB driver config. -#[non_exhaustive] -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct Config { - /// Enable VBUS detection. - /// - /// The USB spec requires USB devices monitor for USB cable plug/unplug and react accordingly. - /// This is done by checkihg whether there is 5V on the VBUS pin or not. - /// - /// If your device is bus-powered (powers itself from the USB host via VBUS), then this is optional. - /// (if there's no power in VBUS your device would be off anyway, so it's fine to always assume - /// there's power in VBUS, i.e. the USB cable is always plugged in.) - /// - /// If your device is self-powered (i.e. it gets power from a source other than the USB cable, and - /// therefore can stay powered through USB cable plug/unplug) then you MUST set this to true. - /// - /// If you set this to true, you must connect VBUS to PA9 for FS, PB13 for HS, possibly with a - /// voltage divider. See ST application note AN4879 and the reference manual for more details. - pub vbus_detection: bool, -} - -impl Default for Config { - fn default() -> Self { - Self { vbus_detection: true } - } -} - -/// USB driver. -pub struct Driver<'d, T: Instance> { - config: Config, - phantom: PhantomData<&'d mut T>, - ep_in: [Option; MAX_EP_COUNT], - ep_out: [Option; MAX_EP_COUNT], - ep_out_buffer: &'d mut [u8], - ep_out_buffer_offset: usize, - phy_type: PhyType, -} - -impl<'d, T: Instance> Driver<'d, T> { - /// Initializes USB OTG peripheral with internal Full-Speed PHY. - /// - /// # Arguments - /// - /// * `ep_out_buffer` - An internal buffer used to temporarily store recevied packets. - /// Must be large enough to fit all OUT endpoint max packet sizes. - /// Endpoint allocation will fail if it is too small. - pub fn new_fs( - _peri: impl Peripheral

+ 'd, - _irq: impl interrupt::typelevel::Binding> + 'd, - dp: impl Peripheral

> + 'd, - dm: impl Peripheral

> + 'd, - ep_out_buffer: &'d mut [u8], - config: Config, - ) -> Self { - into_ref!(dp, dm); - - dp.set_as_af(dp.af_num(), AFType::OutputPushPull); - dm.set_as_af(dm.af_num(), AFType::OutputPushPull); - - Self { - config, - phantom: PhantomData, - ep_in: [None; MAX_EP_COUNT], - ep_out: [None; MAX_EP_COUNT], - ep_out_buffer, - ep_out_buffer_offset: 0, - phy_type: PhyType::InternalFullSpeed, - } - } - - /// Initializes USB OTG peripheral with external High-Speed PHY. - /// - /// # Arguments - /// - /// * `ep_out_buffer` - An internal buffer used to temporarily store recevied packets. - /// Must be large enough to fit all OUT endpoint max packet sizes. - /// Endpoint allocation will fail if it is too small. - pub fn new_hs_ulpi( - _peri: impl Peripheral

+ 'd, - _irq: impl interrupt::typelevel::Binding> + 'd, - ulpi_clk: impl Peripheral

> + 'd, - ulpi_dir: impl Peripheral

> + 'd, - ulpi_nxt: impl Peripheral

> + 'd, - ulpi_stp: impl Peripheral

> + 'd, - ulpi_d0: impl Peripheral

> + 'd, - ulpi_d1: impl Peripheral

> + 'd, - ulpi_d2: impl Peripheral

> + 'd, - ulpi_d3: impl Peripheral

> + 'd, - ulpi_d4: impl Peripheral

> + 'd, - ulpi_d5: impl Peripheral

> + 'd, - ulpi_d6: impl Peripheral

> + 'd, - ulpi_d7: impl Peripheral

> + 'd, - ep_out_buffer: &'d mut [u8], - config: Config, - ) -> Self { - assert!(T::HIGH_SPEED == true, "Peripheral is not capable of high-speed USB"); - - config_ulpi_pins!( - ulpi_clk, ulpi_dir, ulpi_nxt, ulpi_stp, ulpi_d0, ulpi_d1, ulpi_d2, ulpi_d3, ulpi_d4, ulpi_d5, ulpi_d6, - ulpi_d7 - ); - - Self { - config, - phantom: PhantomData, - ep_in: [None; MAX_EP_COUNT], - ep_out: [None; MAX_EP_COUNT], - ep_out_buffer, - ep_out_buffer_offset: 0, - phy_type: PhyType::ExternalHighSpeed, - } - } - - // Returns total amount of words (u32) allocated in dedicated FIFO - fn allocated_fifo_words(&self) -> u16 { - RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out) + ep_fifo_size(&self.ep_in) - } - - fn alloc_endpoint( - &mut self, - ep_type: EndpointType, - max_packet_size: u16, - interval_ms: u8, - ) -> Result, EndpointAllocError> { - trace!( - "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", - ep_type, - max_packet_size, - interval_ms, - D::dir() - ); - - if D::dir() == Direction::Out { - if self.ep_out_buffer_offset + max_packet_size as usize >= self.ep_out_buffer.len() { - error!("Not enough endpoint out buffer capacity"); - return Err(EndpointAllocError); - } - }; - - let fifo_size_words = match D::dir() { - Direction::Out => (max_packet_size + 3) / 4, - // INEPTXFD requires minimum size of 16 words - Direction::In => u16::max((max_packet_size + 3) / 4, 16), - }; - - if fifo_size_words + self.allocated_fifo_words() > T::FIFO_DEPTH_WORDS { - error!("Not enough FIFO capacity"); - return Err(EndpointAllocError); - } - - let eps = match D::dir() { - Direction::Out => &mut self.ep_out, - Direction::In => &mut self.ep_in, - }; - - // Find free endpoint slot - let slot = eps.iter_mut().enumerate().find(|(i, ep)| { - if *i == 0 && ep_type != EndpointType::Control { - // reserved for control pipe - false - } else { - ep.is_none() - } - }); - - let index = match slot { - Some((index, ep)) => { - *ep = Some(EndpointData { - ep_type, - max_packet_size, - fifo_size_words, - }); - index - } - None => { - error!("No free endpoints available"); - return Err(EndpointAllocError); - } - }; - - trace!(" index={}", index); - - if D::dir() == Direction::Out { - // Buffer capacity check was done above, now allocation cannot fail - unsafe { - *T::state().ep_out_buffers[index].get() = - self.ep_out_buffer.as_mut_ptr().offset(self.ep_out_buffer_offset as _); - } - self.ep_out_buffer_offset += max_packet_size as usize; - } - - Ok(Endpoint { - _phantom: PhantomData, - info: EndpointInfo { - addr: EndpointAddress::from_parts(index, D::dir()), - ep_type, - max_packet_size, - interval_ms, - }, - }) - } -} - -impl<'d, T: Instance> embassy_usb_driver::Driver<'d> for Driver<'d, T> { - type EndpointOut = Endpoint<'d, T, Out>; - type EndpointIn = Endpoint<'d, T, In>; - type ControlPipe = ControlPipe<'d, T>; - type Bus = Bus<'d, T>; - - fn alloc_endpoint_in( - &mut self, - ep_type: EndpointType, - max_packet_size: u16, - interval_ms: u8, - ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval_ms) - } - - fn alloc_endpoint_out( - &mut self, - ep_type: EndpointType, - max_packet_size: u16, - interval_ms: u8, - ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval_ms) - } - - fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { - let ep_out = self - .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0) - .unwrap(); - let ep_in = self - .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0) - .unwrap(); - assert_eq!(ep_out.info.addr.index(), 0); - assert_eq!(ep_in.info.addr.index(), 0); - - trace!("start"); - - ( - Bus { - config: self.config, - phantom: PhantomData, - ep_in: self.ep_in, - ep_out: self.ep_out, - phy_type: self.phy_type, - inited: false, - }, - ControlPipe { - _phantom: PhantomData, - max_packet_size: control_max_packet_size, - ep_out, - ep_in, - }, - ) - } -} - -/// USB bus. -pub struct Bus<'d, T: Instance> { - config: Config, - phantom: PhantomData<&'d mut T>, - ep_in: [Option; MAX_EP_COUNT], - ep_out: [Option; MAX_EP_COUNT], - phy_type: PhyType, - inited: bool, -} - -impl<'d, T: Instance> Bus<'d, T> { - fn restore_irqs() { - T::regs().gintmsk().write(|w| { - w.set_usbrst(true); - w.set_enumdnem(true); - w.set_usbsuspm(true); - w.set_wuim(true); - w.set_iepint(true); - w.set_oepint(true); - w.set_rxflvlm(true); - w.set_srqim(true); - w.set_otgint(true); - }); - } -} - -impl<'d, T: Instance> Bus<'d, T> { - fn init(&mut self) { - #[cfg(stm32l4)] - critical_section::with(|_| crate::pac::PWR.cr2().modify(|w| w.set_usv(true))); - - #[cfg(stm32f7)] - { - // Enable ULPI clock if external PHY is used - let ulpien = !self.phy_type.internal(); - critical_section::with(|_| { - crate::pac::RCC.ahb1enr().modify(|w| { - if T::HIGH_SPEED { - w.set_usb_otg_hsulpien(ulpien); - } else { - w.set_usb_otg_hsen(ulpien); - } - }); - - // Low power mode - crate::pac::RCC.ahb1lpenr().modify(|w| { - if T::HIGH_SPEED { - w.set_usb_otg_hsulpilpen(ulpien); - } else { - w.set_usb_otg_hslpen(ulpien); - } - }); - }); - } - - #[cfg(stm32h7)] - { - // If true, VDD33USB is generated by internal regulator from VDD50USB - // If false, VDD33USB and VDD50USB must be suplied directly with 3.3V (default on nucleo) - // TODO: unhardcode - let internal_regulator = false; - - // Enable USB power - critical_section::with(|_| { - crate::pac::PWR.cr3().modify(|w| { - w.set_usb33den(true); - w.set_usbregen(internal_regulator); - }) - }); - - // Wait for USB power to stabilize - while !crate::pac::PWR.cr3().read().usb33rdy() {} - - // Enable ULPI clock if external PHY is used - let ulpien = !self.phy_type.internal(); - critical_section::with(|_| { - crate::pac::RCC.ahb1enr().modify(|w| { - if T::HIGH_SPEED { - w.set_usb_otg_hs_ulpien(ulpien); - } else { - w.set_usb_otg_fs_ulpien(ulpien); - } - }); - crate::pac::RCC.ahb1lpenr().modify(|w| { - if T::HIGH_SPEED { - w.set_usb_otg_hs_ulpilpen(ulpien); - } else { - w.set_usb_otg_fs_ulpilpen(ulpien); - } - }); - }); - } - - #[cfg(stm32u5)] - { - // Enable USB power - critical_section::with(|_| { - crate::pac::PWR.svmcr().modify(|w| { - w.set_usv(true); - w.set_uvmen(true); - }) - }); - - // Wait for USB power to stabilize - while !crate::pac::PWR.svmsr().read().vddusbrdy() {} - } - - ::enable_and_reset(); - - T::Interrupt::unpend(); - unsafe { T::Interrupt::enable() }; - - let r = T::regs(); - let core_id = r.cid().read().0; - trace!("Core id {:08x}", core_id); - - // Wait for AHB ready. - while !r.grstctl().read().ahbidl() {} - - // Configure as device. - r.gusbcfg().write(|w| { - // Force device mode - w.set_fdmod(true); - // Enable internal full-speed PHY - w.set_physel(self.phy_type.internal() && !self.phy_type.high_speed()); - }); - - // Configuring Vbus sense and SOF output - match core_id { - 0x0000_1200 | 0x0000_1100 => { - assert!(self.phy_type != PhyType::InternalHighSpeed); - - r.gccfg_v1().modify(|w| { - // Enable internal full-speed PHY, logic is inverted - w.set_pwrdwn(self.phy_type.internal()); - }); - - // F429-like chips have the GCCFG.NOVBUSSENS bit - r.gccfg_v1().modify(|w| { - w.set_novbussens(!self.config.vbus_detection); - w.set_vbusasen(false); - w.set_vbusbsen(self.config.vbus_detection); - w.set_sofouten(false); - }); - } - 0x0000_2000 | 0x0000_2100 | 0x0000_2300 | 0x0000_3000 | 0x0000_3100 => { - // F446-like chips have the GCCFG.VBDEN bit with the opposite meaning - r.gccfg_v2().modify(|w| { - // Enable internal full-speed PHY, logic is inverted - w.set_pwrdwn(self.phy_type.internal() && !self.phy_type.high_speed()); - w.set_phyhsen(self.phy_type.internal() && self.phy_type.high_speed()); - }); - - r.gccfg_v2().modify(|w| { - w.set_vbden(self.config.vbus_detection); - }); - - // Force B-peripheral session - r.gotgctl().modify(|w| { - w.set_bvaloen(!self.config.vbus_detection); - w.set_bvaloval(true); - }); - } - _ => unimplemented!("Unknown USB core id {:X}", core_id), - } - - // Soft disconnect. - r.dctl().write(|w| w.set_sdis(true)); - - // Set speed. - r.dcfg().write(|w| { - w.set_pfivl(vals::Pfivl::FRAME_INTERVAL_80); - w.set_dspd(self.phy_type.to_dspd()); - }); - - // Unmask transfer complete EP interrupt - r.diepmsk().write(|w| { - w.set_xfrcm(true); - }); - - // Unmask and clear core interrupts - Bus::::restore_irqs(); - r.gintsts().write_value(regs::Gintsts(0xFFFF_FFFF)); - - // Unmask global interrupt - r.gahbcfg().write(|w| { - w.set_gint(true); // unmask global interrupt - }); - - // Connect - r.dctl().write(|w| w.set_sdis(false)); - } - - fn init_fifo(&mut self) { - trace!("init_fifo"); - - let r = T::regs(); - - // Configure RX fifo size. All endpoints share the same FIFO area. - let rx_fifo_size_words = RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out); - trace!("configuring rx fifo size={}", rx_fifo_size_words); - - r.grxfsiz().modify(|w| w.set_rxfd(rx_fifo_size_words)); - - // Configure TX (USB in direction) fifo size for each endpoint - let mut fifo_top = rx_fifo_size_words; - for i in 0..T::ENDPOINT_COUNT { - if let Some(ep) = self.ep_in[i] { - trace!( - "configuring tx fifo ep={}, offset={}, size={}", - i, - fifo_top, - ep.fifo_size_words - ); - - let dieptxf = if i == 0 { r.dieptxf0() } else { r.dieptxf(i - 1) }; - - dieptxf.write(|w| { - w.set_fd(ep.fifo_size_words); - w.set_sa(fifo_top); - }); - - fifo_top += ep.fifo_size_words; - } - } - - assert!( - fifo_top <= T::FIFO_DEPTH_WORDS, - "FIFO allocations exceeded maximum capacity" - ); - - // Flush fifos - r.grstctl().write(|w| { - w.set_rxfflsh(true); - w.set_txfflsh(true); - w.set_txfnum(0x10); - }); - loop { - let x = r.grstctl().read(); - if !x.rxfflsh() && !x.txfflsh() { - break; - } - } - } - - fn configure_endpoints(&mut self) { - trace!("configure_endpoints"); - - let r = T::regs(); - - // Configure IN endpoints - for (index, ep) in self.ep_in.iter().enumerate() { - if let Some(ep) = ep { - critical_section::with(|_| { - r.diepctl(index).write(|w| { - if index == 0 { - w.set_mpsiz(ep0_mpsiz(ep.max_packet_size)); - } else { - w.set_mpsiz(ep.max_packet_size); - w.set_eptyp(to_eptyp(ep.ep_type)); - w.set_sd0pid_sevnfrm(true); - w.set_txfnum(index as _); - w.set_snak(true); - } - }); - }); - } - } - - // Configure OUT endpoints - for (index, ep) in self.ep_out.iter().enumerate() { - if let Some(ep) = ep { - critical_section::with(|_| { - r.doepctl(index).write(|w| { - if index == 0 { - w.set_mpsiz(ep0_mpsiz(ep.max_packet_size)); - } else { - w.set_mpsiz(ep.max_packet_size); - w.set_eptyp(to_eptyp(ep.ep_type)); - w.set_sd0pid_sevnfrm(true); - } - }); - - r.doeptsiz(index).modify(|w| { - w.set_xfrsiz(ep.max_packet_size as _); - if index == 0 { - w.set_rxdpid_stupcnt(1); - } else { - w.set_pktcnt(1); - } - }); - }); - } - } - - // Enable IRQs for allocated endpoints - r.daintmsk().modify(|w| { - w.set_iepm(ep_irq_mask(&self.ep_in)); - // OUT interrupts not used, handled in RXFLVL - // w.set_oepm(ep_irq_mask(&self.ep_out)); - }); - } - - fn disable_all_endpoints(&mut self) { - for i in 0..T::ENDPOINT_COUNT { - self.endpoint_set_enabled(EndpointAddress::from_parts(i, Direction::In), false); - self.endpoint_set_enabled(EndpointAddress::from_parts(i, Direction::Out), false); - } - } - - fn disable(&mut self) { - T::Interrupt::disable(); - - ::disable(); - - #[cfg(stm32l4)] - crate::pac::PWR.cr2().modify(|w| w.set_usv(false)); - // Cannot disable PWR, because other peripherals might be using it - } -} - -impl<'d, T: Instance> embassy_usb_driver::Bus for Bus<'d, T> { - async fn poll(&mut self) -> Event { - poll_fn(move |cx| { - if !self.inited { - self.init(); - self.inited = true; - - // If no vbus detection, just return a single PowerDetected event at startup. - if !self.config.vbus_detection { - return Poll::Ready(Event::PowerDetected); - } - } - - let r = T::regs(); - - T::state().bus_waker.register(cx.waker()); - - let ints = r.gintsts().read(); - - if ints.srqint() { - trace!("vbus detected"); - - r.gintsts().write(|w| w.set_srqint(true)); // clear - Self::restore_irqs(); - - if self.config.vbus_detection { - return Poll::Ready(Event::PowerDetected); - } - } - - if ints.otgint() { - let otgints = r.gotgint().read(); - r.gotgint().write_value(otgints); // clear all - Self::restore_irqs(); - - if otgints.sedet() { - trace!("vbus removed"); - if self.config.vbus_detection { - self.disable_all_endpoints(); - return Poll::Ready(Event::PowerRemoved); - } - } - } - - if ints.usbrst() { - trace!("reset"); - - self.init_fifo(); - self.configure_endpoints(); - - // Reset address - critical_section::with(|_| { - r.dcfg().modify(|w| { - w.set_dad(0); - }); - }); - - r.gintsts().write(|w| w.set_usbrst(true)); // clear - Self::restore_irqs(); - } - - if ints.enumdne() { - trace!("enumdne"); - - let speed = r.dsts().read().enumspd(); - let trdt = calculate_trdt(speed, T::frequency()); - trace!(" speed={} trdt={}", speed.to_bits(), trdt); - r.gusbcfg().modify(|w| w.set_trdt(trdt)); - - r.gintsts().write(|w| w.set_enumdne(true)); // clear - Self::restore_irqs(); - - return Poll::Ready(Event::Reset); - } - - if ints.usbsusp() { - trace!("suspend"); - r.gintsts().write(|w| w.set_usbsusp(true)); // clear - Self::restore_irqs(); - return Poll::Ready(Event::Suspend); - } - - if ints.wkupint() { - trace!("resume"); - r.gintsts().write(|w| w.set_wkupint(true)); // clear - Self::restore_irqs(); - return Poll::Ready(Event::Resume); - } - - Poll::Pending - }) - .await - } - - fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { - trace!("endpoint_set_stalled ep={:?} en={}", ep_addr, stalled); - - assert!( - ep_addr.index() < T::ENDPOINT_COUNT, - "endpoint_set_stalled index {} out of range", - ep_addr.index() - ); - - let regs = T::regs(); - match ep_addr.direction() { - Direction::Out => { - critical_section::with(|_| { - regs.doepctl(ep_addr.index()).modify(|w| { - w.set_stall(stalled); - }); - }); - - T::state().ep_out_wakers[ep_addr.index()].wake(); - } - Direction::In => { - critical_section::with(|_| { - regs.diepctl(ep_addr.index()).modify(|w| { - w.set_stall(stalled); - }); - }); - - T::state().ep_in_wakers[ep_addr.index()].wake(); - } - } - } - - fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool { - assert!( - ep_addr.index() < T::ENDPOINT_COUNT, - "endpoint_is_stalled index {} out of range", - ep_addr.index() - ); - - let regs = T::regs(); - - match ep_addr.direction() { - Direction::Out => regs.doepctl(ep_addr.index()).read().stall(), - Direction::In => regs.diepctl(ep_addr.index()).read().stall(), - } - } - - fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) { - trace!("endpoint_set_enabled ep={:?} en={}", ep_addr, enabled); - - assert!( - ep_addr.index() < T::ENDPOINT_COUNT, - "endpoint_set_enabled index {} out of range", - ep_addr.index() - ); - - let r = T::regs(); - match ep_addr.direction() { - Direction::Out => { - critical_section::with(|_| { - // cancel transfer if active - if !enabled && r.doepctl(ep_addr.index()).read().epena() { - r.doepctl(ep_addr.index()).modify(|w| { - w.set_snak(true); - w.set_epdis(true); - }) - } - - r.doepctl(ep_addr.index()).modify(|w| { - w.set_usbaep(enabled); - }); - - // Flush tx fifo - r.grstctl().write(|w| { - w.set_txfflsh(true); - w.set_txfnum(ep_addr.index() as _); - }); - loop { - let x = r.grstctl().read(); - if !x.txfflsh() { - break; - } - } - }); - - // Wake `Endpoint::wait_enabled()` - T::state().ep_out_wakers[ep_addr.index()].wake(); - } - Direction::In => { - critical_section::with(|_| { - // cancel transfer if active - if !enabled && r.diepctl(ep_addr.index()).read().epena() { - r.diepctl(ep_addr.index()).modify(|w| { - w.set_snak(true); // set NAK - w.set_epdis(true); - }) - } - - r.diepctl(ep_addr.index()).modify(|w| { - w.set_usbaep(enabled); - w.set_cnak(enabled); // clear NAK that might've been set by SNAK above. - }) - }); - - // Wake `Endpoint::wait_enabled()` - T::state().ep_in_wakers[ep_addr.index()].wake(); - } - } - } - - async fn enable(&mut self) { - trace!("enable"); - // TODO: enable the peripheral once enable/disable semantics are cleared up in embassy-usb - } - - async fn disable(&mut self) { - trace!("disable"); - - // TODO: disable the peripheral once enable/disable semantics are cleared up in embassy-usb - //Bus::disable(self); - } - - async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { - Err(Unsupported) - } -} - -impl<'d, T: Instance> Drop for Bus<'d, T> { - fn drop(&mut self) { - Bus::disable(self); - } -} - -trait Dir { - fn dir() -> Direction; -} - -/// Marker type for the "IN" direction. -pub enum In {} -impl Dir for In { - fn dir() -> Direction { - Direction::In - } -} - -/// Marker type for the "OUT" direction. -pub enum Out {} -impl Dir for Out { - fn dir() -> Direction { - Direction::Out - } -} - -/// USB endpoint. -pub struct Endpoint<'d, T: Instance, D> { - _phantom: PhantomData<(&'d mut T, D)>, - info: EndpointInfo, -} - -impl<'d, T: Instance> embassy_usb_driver::Endpoint for Endpoint<'d, T, In> { - fn info(&self) -> &EndpointInfo { - &self.info - } - - async fn wait_enabled(&mut self) { - poll_fn(|cx| { - let ep_index = self.info.addr.index(); - - T::state().ep_in_wakers[ep_index].register(cx.waker()); - - if T::regs().diepctl(ep_index).read().usbaep() { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await - } -} - -impl<'d, T: Instance> embassy_usb_driver::Endpoint for Endpoint<'d, T, Out> { - fn info(&self) -> &EndpointInfo { - &self.info - } - - async fn wait_enabled(&mut self) { - poll_fn(|cx| { - let ep_index = self.info.addr.index(); - - T::state().ep_out_wakers[ep_index].register(cx.waker()); - - if T::regs().doepctl(ep_index).read().usbaep() { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await - } -} - -impl<'d, T: Instance> embassy_usb_driver::EndpointOut for Endpoint<'d, T, Out> { - async fn read(&mut self, buf: &mut [u8]) -> Result { - trace!("read start len={}", buf.len()); - - poll_fn(|cx| { - let r = T::regs(); - let index = self.info.addr.index(); - let state = T::state(); - - state.ep_out_wakers[index].register(cx.waker()); - - let doepctl = r.doepctl(index).read(); - trace!("read ep={:?}: doepctl {:08x}", self.info.addr, doepctl.0,); - if !doepctl.usbaep() { - trace!("read ep={:?} error disabled", self.info.addr); - return Poll::Ready(Err(EndpointError::Disabled)); - } - - let len = state.ep_out_size[index].load(Ordering::Relaxed); - if len != EP_OUT_BUFFER_EMPTY { - trace!("read ep={:?} done len={}", self.info.addr, len); - - if len as usize > buf.len() { - return Poll::Ready(Err(EndpointError::BufferOverflow)); - } - - // SAFETY: exclusive access ensured by `ep_out_size` atomic variable - let data = unsafe { core::slice::from_raw_parts(*state.ep_out_buffers[index].get(), len as usize) }; - buf[..len as usize].copy_from_slice(data); - - // Release buffer - state.ep_out_size[index].store(EP_OUT_BUFFER_EMPTY, Ordering::Release); - - critical_section::with(|_| { - // Receive 1 packet - T::regs().doeptsiz(index).modify(|w| { - w.set_xfrsiz(self.info.max_packet_size as _); - w.set_pktcnt(1); - }); - - // Clear NAK to indicate we are ready to receive more data - T::regs().doepctl(index).modify(|w| { - w.set_cnak(true); - }); - }); - - Poll::Ready(Ok(len as usize)) - } else { - Poll::Pending - } - }) - .await - } -} - -impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> { - async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { - trace!("write ep={:?} data={:?}", self.info.addr, buf); - - if buf.len() > self.info.max_packet_size as usize { - return Err(EndpointError::BufferOverflow); - } - - let r = T::regs(); - let index = self.info.addr.index(); - let state = T::state(); - - // Wait for previous transfer to complete and check if endpoint is disabled - poll_fn(|cx| { - state.ep_in_wakers[index].register(cx.waker()); - - let diepctl = r.diepctl(index).read(); - let dtxfsts = r.dtxfsts(index).read(); - trace!( - "write ep={:?}: diepctl {:08x} ftxfsts {:08x}", - self.info.addr, - diepctl.0, - dtxfsts.0 - ); - if !diepctl.usbaep() { - trace!("write ep={:?} wait for prev: error disabled", self.info.addr); - Poll::Ready(Err(EndpointError::Disabled)) - } else if !diepctl.epena() { - trace!("write ep={:?} wait for prev: ready", self.info.addr); - Poll::Ready(Ok(())) - } else { - trace!("write ep={:?} wait for prev: pending", self.info.addr); - Poll::Pending - } - }) - .await?; - - if buf.len() > 0 { - poll_fn(|cx| { - state.ep_in_wakers[index].register(cx.waker()); - - let size_words = (buf.len() + 3) / 4; - - let fifo_space = r.dtxfsts(index).read().ineptfsav() as usize; - if size_words > fifo_space { - // Not enough space in fifo, enable tx fifo empty interrupt - critical_section::with(|_| { - r.diepempmsk().modify(|w| { - w.set_ineptxfem(w.ineptxfem() | (1 << index)); - }); - }); - - trace!("tx fifo for ep={} full, waiting for txfe", index); - - Poll::Pending - } else { - trace!("write ep={:?} wait for fifo: ready", self.info.addr); - Poll::Ready(()) - } - }) - .await - } - - // Setup transfer size - r.dieptsiz(index).write(|w| { - w.set_mcnt(1); - w.set_pktcnt(1); - w.set_xfrsiz(buf.len() as _); - }); - - critical_section::with(|_| { - // Enable endpoint - r.diepctl(index).modify(|w| { - w.set_cnak(true); - w.set_epena(true); - }); - }); - - // Write data to FIFO - for chunk in buf.chunks(4) { - let mut tmp = [0u8; 4]; - tmp[0..chunk.len()].copy_from_slice(chunk); - r.fifo(index).write_value(regs::Fifo(u32::from_ne_bytes(tmp))); - } - - trace!("write done ep={:?}", self.info.addr); - - Ok(()) - } -} - -/// USB control pipe. -pub struct ControlPipe<'d, T: Instance> { - _phantom: PhantomData<&'d mut T>, - max_packet_size: u16, - ep_in: Endpoint<'d, T, In>, - ep_out: Endpoint<'d, T, Out>, -} - -impl<'d, T: Instance> embassy_usb_driver::ControlPipe for ControlPipe<'d, T> { - fn max_packet_size(&self) -> usize { - usize::from(self.max_packet_size) - } - - async fn setup(&mut self) -> [u8; 8] { - poll_fn(|cx| { - let state = T::state(); - - state.ep_out_wakers[0].register(cx.waker()); - - let r = T::regs(); - - if state.ep0_setup_ready.load(Ordering::Relaxed) { - let data = unsafe { *state.ep0_setup_data.get() }; - state.ep0_setup_ready.store(false, Ordering::Release); - - // EP0 should not be controlled by `Bus` so this RMW does not need a critical section - // Receive 1 SETUP packet - r.doeptsiz(self.ep_out.info.addr.index()).modify(|w| { - w.set_rxdpid_stupcnt(1); - }); - - // Clear NAK to indicate we are ready to receive more data - if !quirk_setup_late_cnak(r) { - r.doepctl(self.ep_out.info.addr.index()).modify(|w| w.set_cnak(true)); - } - - trace!("SETUP received: {:?}", data); - Poll::Ready(data) - } else { - trace!("SETUP waiting"); - Poll::Pending - } - }) - .await - } - - async fn data_out(&mut self, buf: &mut [u8], _first: bool, _last: bool) -> Result { - trace!("control: data_out"); - let len = self.ep_out.read(buf).await?; - trace!("control: data_out read: {:?}", &buf[..len]); - Ok(len) - } - - async fn data_in(&mut self, data: &[u8], _first: bool, last: bool) -> Result<(), EndpointError> { - trace!("control: data_in write: {:?}", data); - self.ep_in.write(data).await?; - - // wait for status response from host after sending the last packet - if last { - trace!("control: data_in waiting for status"); - self.ep_out.read(&mut []).await?; - trace!("control: complete"); - } - - Ok(()) - } - - async fn accept(&mut self) { - trace!("control: accept"); - - self.ep_in.write(&[]).await.ok(); - - trace!("control: accept OK"); - } - - async fn reject(&mut self) { - trace!("control: reject"); - - // EP0 should not be controlled by `Bus` so this RMW does not need a critical section - let regs = T::regs(); - regs.diepctl(self.ep_in.info.addr.index()).modify(|w| { - w.set_stall(true); - }); - regs.doepctl(self.ep_out.info.addr.index()).modify(|w| { - w.set_stall(true); - }); - } - - async fn accept_set_address(&mut self, addr: u8) { - trace!("setting addr: {}", addr); - critical_section::with(|_| { - T::regs().dcfg().modify(|w| { - w.set_dad(addr); - }); - }); - - // synopsys driver requires accept to be sent after changing address - self.accept().await - } -} - -/// Translates HAL [EndpointType] into PAC [vals::Eptyp] -fn to_eptyp(ep_type: EndpointType) -> vals::Eptyp { - match ep_type { - EndpointType::Control => vals::Eptyp::CONTROL, - EndpointType::Isochronous => vals::Eptyp::ISOCHRONOUS, - EndpointType::Bulk => vals::Eptyp::BULK, - EndpointType::Interrupt => vals::Eptyp::INTERRUPT, - } -} - -/// Calculates total allocated FIFO size in words -fn ep_fifo_size(eps: &[Option]) -> u16 { - eps.iter().map(|ep| ep.map(|ep| ep.fifo_size_words).unwrap_or(0)).sum() -} - -/// Generates IRQ mask for enabled endpoints -fn ep_irq_mask(eps: &[Option]) -> u16 { - eps.iter().enumerate().fold( - 0, - |mask, (index, ep)| { - if ep.is_some() { - mask | (1 << index) - } else { - mask - } - }, - ) -} - -/// Calculates MPSIZ value for EP0, which uses special values. -fn ep0_mpsiz(max_packet_size: u16) -> u16 { - match max_packet_size { - 8 => 0b11, - 16 => 0b10, - 32 => 0b01, - 64 => 0b00, - other => panic!("Unsupported EP0 size: {}", other), - } -} - -fn calculate_trdt(speed: vals::Dspd, ahb_freq: Hertz) -> u8 { - match speed { - vals::Dspd::HIGH_SPEED => { - // From RM0431 (F72xx), RM0090 (F429), RM0390 (F446) - if ahb_freq.0 >= 30_000_000 { - 0x9 - } else { - panic!("AHB frequency is too low") - } - } - vals::Dspd::FULL_SPEED_EXTERNAL | vals::Dspd::FULL_SPEED_INTERNAL => { - // From RM0431 (F72xx), RM0090 (F429) - match ahb_freq.0 { - 0..=14_199_999 => panic!("AHB frequency is too low"), - 14_200_000..=14_999_999 => 0xF, - 15_000_000..=15_999_999 => 0xE, - 16_000_000..=17_199_999 => 0xD, - 17_200_000..=18_499_999 => 0xC, - 18_500_000..=19_999_999 => 0xB, - 20_000_000..=21_799_999 => 0xA, - 21_800_000..=23_999_999 => 0x9, - 24_000_000..=27_499_999 => 0x8, - 27_500_000..=31_999_999 => 0x7, // 27.7..32 in code from CubeIDE - 32_000_000..=u32::MAX => 0x6, - } - } - _ => unimplemented!(), - } -} - -fn quirk_setup_late_cnak(r: crate::pac::otg::Otg) -> bool { - r.cid().read().0 & 0xf000 == 0x1000 -} diff --git a/examples/stm32f4/src/bin/usb_ethernet.rs b/examples/stm32f4/src/bin/usb_ethernet.rs index a196259a8..60e580609 100644 --- a/examples/stm32f4/src/bin/usb_ethernet.rs +++ b/examples/stm32f4/src/bin/usb_ethernet.rs @@ -7,8 +7,8 @@ use embassy_net::tcp::TcpSocket; use embassy_net::{Stack, StackResources}; use embassy_stm32::rng::{self, Rng}; use embassy_stm32::time::Hertz; -use embassy_stm32::usb_otg::Driver; -use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config}; +use embassy_stm32::usb::Driver; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState}; use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; use embassy_usb::{Builder, UsbDevice}; @@ -36,7 +36,7 @@ async fn net_task(stack: &'static Stack>) -> ! { } bind_interrupts!(struct Irqs { - OTG_FS => usb_otg::InterruptHandler; + OTG_FS => usb::InterruptHandler; HASH_RNG => rng::InterruptHandler; }); @@ -69,7 +69,7 @@ async fn main(spawner: Spawner) { // Create the driver, from the HAL. static OUTPUT_BUFFER: StaticCell<[u8; 256]> = StaticCell::new(); let ep_out_buffer = &mut OUTPUT_BUFFER.init([0; 256])[..]; - let mut config = embassy_stm32::usb_otg::Config::default(); + let mut config = embassy_stm32::usb::Config::default(); config.vbus_detection = true; let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, ep_out_buffer, config); diff --git a/examples/stm32f4/src/bin/usb_hid_keyboard.rs b/examples/stm32f4/src/bin/usb_hid_keyboard.rs index 19b5971fb..ae4ce6b6a 100644 --- a/examples/stm32f4/src/bin/usb_hid_keyboard.rs +++ b/examples/stm32f4/src/bin/usb_hid_keyboard.rs @@ -8,8 +8,8 @@ use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; use embassy_stm32::gpio::Pull; use embassy_stm32::time::Hertz; -use embassy_stm32::usb_otg::Driver; -use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config}; +use embassy_stm32::usb::Driver; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State}; use embassy_usb::control::OutResponse; use embassy_usb::{Builder, Handler}; @@ -18,7 +18,7 @@ use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { - OTG_FS => usb_otg::InterruptHandler; + OTG_FS => usb::InterruptHandler; }); #[embassy_executor::main] @@ -47,7 +47,7 @@ async fn main(_spawner: Spawner) { // Create the driver, from the HAL. let mut ep_out_buffer = [0u8; 256]; - let mut config = embassy_stm32::usb_otg::Config::default(); + let mut config = embassy_stm32::usb::Config::default(); config.vbus_detection = true; let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); diff --git a/examples/stm32f4/src/bin/usb_hid_mouse.rs b/examples/stm32f4/src/bin/usb_hid_mouse.rs index c98792880..5c64f4969 100644 --- a/examples/stm32f4/src/bin/usb_hid_mouse.rs +++ b/examples/stm32f4/src/bin/usb_hid_mouse.rs @@ -4,8 +4,8 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::time::Hertz; -use embassy_stm32::usb_otg::Driver; -use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config}; +use embassy_stm32::usb::Driver; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; use embassy_time::Timer; use embassy_usb::class::hid::{HidWriter, ReportId, RequestHandler, State}; use embassy_usb::control::OutResponse; @@ -15,7 +15,7 @@ use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { - OTG_FS => usb_otg::InterruptHandler; + OTG_FS => usb::InterruptHandler; }); #[embassy_executor::main] @@ -44,7 +44,7 @@ async fn main(_spawner: Spawner) { // Create the driver, from the HAL. let mut ep_out_buffer = [0u8; 256]; - let mut config = embassy_stm32::usb_otg::Config::default(); + let mut config = embassy_stm32::usb::Config::default(); config.vbus_detection = true; let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); diff --git a/examples/stm32f4/src/bin/usb_raw.rs b/examples/stm32f4/src/bin/usb_raw.rs index afff55187..c13fe051f 100644 --- a/examples/stm32f4/src/bin/usb_raw.rs +++ b/examples/stm32f4/src/bin/usb_raw.rs @@ -52,8 +52,8 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::time::Hertz; -use embassy_stm32::usb_otg::Driver; -use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config}; +use embassy_stm32::usb::Driver; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; use embassy_usb::control::{InResponse, OutResponse, Recipient, Request, RequestType}; use embassy_usb::msos::{self, windows_version}; use embassy_usb::types::InterfaceNumber; @@ -66,7 +66,7 @@ use {defmt_rtt as _, panic_probe as _}; const DEVICE_INTERFACE_GUIDS: &[&str] = &["{DAC2087C-63FA-458D-A55D-827C0762DEC7}"]; bind_interrupts!(struct Irqs { - OTG_FS => usb_otg::InterruptHandler; + OTG_FS => usb::InterruptHandler; }); #[embassy_executor::main] @@ -97,7 +97,7 @@ async fn main(_spawner: Spawner) { // Create the driver, from the HAL. let mut ep_out_buffer = [0u8; 256]; - let mut config = embassy_stm32::usb_otg::Config::default(); + let mut config = embassy_stm32::usb::Config::default(); config.vbus_detection = true; let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); diff --git a/examples/stm32f4/src/bin/usb_serial.rs b/examples/stm32f4/src/bin/usb_serial.rs index 58d994a61..21e255612 100644 --- a/examples/stm32f4/src/bin/usb_serial.rs +++ b/examples/stm32f4/src/bin/usb_serial.rs @@ -4,8 +4,8 @@ use defmt::{panic, *}; use embassy_executor::Spawner; use embassy_stm32::time::Hertz; -use embassy_stm32::usb_otg::{Driver, Instance}; -use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config}; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; use embassy_usb::Builder; @@ -13,7 +13,7 @@ use futures::future::join; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { - OTG_FS => usb_otg::InterruptHandler; + OTG_FS => usb::InterruptHandler; }); #[embassy_executor::main] @@ -44,7 +44,7 @@ async fn main(_spawner: Spawner) { // Create the driver, from the HAL. let mut ep_out_buffer = [0u8; 256]; - let mut config = embassy_stm32::usb_otg::Config::default(); + let mut config = embassy_stm32::usb::Config::default(); config.vbus_detection = true; let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); diff --git a/examples/stm32f7/src/bin/usb_serial.rs b/examples/stm32f7/src/bin/usb_serial.rs index 97daf6bd1..bdd7b76ff 100644 --- a/examples/stm32f7/src/bin/usb_serial.rs +++ b/examples/stm32f7/src/bin/usb_serial.rs @@ -4,8 +4,8 @@ use defmt::{panic, *}; use embassy_executor::Spawner; use embassy_stm32::time::Hertz; -use embassy_stm32::usb_otg::{Driver, Instance}; -use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config}; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; use embassy_usb::Builder; @@ -13,7 +13,7 @@ use futures::future::join; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { - OTG_FS => usb_otg::InterruptHandler; + OTG_FS => usb::InterruptHandler; }); #[embassy_executor::main] @@ -44,7 +44,7 @@ async fn main(_spawner: Spawner) { // Create the driver, from the HAL. let mut ep_out_buffer = [0u8; 256]; - let mut config = embassy_stm32::usb_otg::Config::default(); + let mut config = embassy_stm32::usb::Config::default(); config.vbus_detection = true; let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); diff --git a/examples/stm32h7/src/bin/usb_serial.rs b/examples/stm32h7/src/bin/usb_serial.rs index d81efb541..3d42a24f3 100644 --- a/examples/stm32h7/src/bin/usb_serial.rs +++ b/examples/stm32h7/src/bin/usb_serial.rs @@ -3,8 +3,8 @@ use defmt::{panic, *}; use embassy_executor::Spawner; -use embassy_stm32::usb_otg::{Driver, Instance}; -use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config}; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; use embassy_usb::Builder; @@ -12,7 +12,7 @@ use futures::future::join; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { - OTG_FS => usb_otg::InterruptHandler; + OTG_FS => usb::InterruptHandler; }); #[embassy_executor::main] @@ -45,7 +45,7 @@ async fn main(_spawner: Spawner) { // Create the driver, from the HAL. let mut ep_out_buffer = [0u8; 256]; - let mut config = embassy_stm32::usb_otg::Config::default(); + let mut config = embassy_stm32::usb::Config::default(); config.vbus_detection = true; let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); diff --git a/examples/stm32l4/src/bin/usb_serial.rs b/examples/stm32l4/src/bin/usb_serial.rs index 9247e56a1..c7f4add7f 100644 --- a/examples/stm32l4/src/bin/usb_serial.rs +++ b/examples/stm32l4/src/bin/usb_serial.rs @@ -5,8 +5,8 @@ use defmt::{panic, *}; use defmt_rtt as _; // global logger use embassy_executor::Spawner; use embassy_stm32::rcc::*; -use embassy_stm32::usb_otg::{Driver, Instance}; -use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config}; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; use embassy_usb::Builder; @@ -14,7 +14,7 @@ use futures::future::join; use panic_probe as _; bind_interrupts!(struct Irqs { - OTG_FS => usb_otg::InterruptHandler; + OTG_FS => usb::InterruptHandler; }); #[embassy_executor::main] @@ -38,7 +38,7 @@ async fn main(_spawner: Spawner) { // Create the driver, from the HAL. let mut ep_out_buffer = [0u8; 256]; - let mut config = embassy_stm32::usb_otg::Config::default(); + let mut config = embassy_stm32::usb::Config::default(); config.vbus_detection = true; let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); diff --git a/examples/stm32u5/src/bin/usb_serial.rs b/examples/stm32u5/src/bin/usb_serial.rs index 61851e5a2..98ae46b1c 100644 --- a/examples/stm32u5/src/bin/usb_serial.rs +++ b/examples/stm32u5/src/bin/usb_serial.rs @@ -4,8 +4,8 @@ use defmt::{panic, *}; use defmt_rtt as _; // global logger use embassy_executor::Spawner; -use embassy_stm32::usb_otg::{Driver, Instance}; -use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config}; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; use embassy_usb::Builder; @@ -13,7 +13,7 @@ use futures::future::join; use panic_probe as _; bind_interrupts!(struct Irqs { - OTG_FS => usb_otg::InterruptHandler; + OTG_FS => usb::InterruptHandler; }); #[embassy_executor::main] @@ -41,7 +41,7 @@ async fn main(_spawner: Spawner) { // Create the driver, from the HAL. let mut ep_out_buffer = [0u8; 256]; - let mut config = embassy_stm32::usb_otg::Config::default(); + let mut config = embassy_stm32::usb::Config::default(); config.vbus_detection = false; let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); -- cgit From daa64bd5400c4db7da25e3f538b4ee0c31435029 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 19 Mar 2024 21:32:22 +0100 Subject: stm32/usb: extract common init code. --- embassy-stm32/src/usb/mod.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++ embassy-stm32/src/usb/otg.rs | 38 +-------------------------------- embassy-stm32/src/usb/usb.rs | 13 ++---------- 3 files changed, 53 insertions(+), 48 deletions(-) diff --git a/embassy-stm32/src/usb/mod.rs b/embassy-stm32/src/usb/mod.rs index 974880900..130c728e6 100644 --- a/embassy-stm32/src/usb/mod.rs +++ b/embassy-stm32/src/usb/mod.rs @@ -4,3 +4,53 @@ #[cfg_attr(otg, path = "otg.rs")] mod _version; pub use _version::*; + +use crate::interrupt::typelevel::Interrupt; +use crate::rcc::sealed::RccPeripheral; + +/// clock, power initialization stuff that's common for USB and OTG. +fn common_init() { + #[cfg(any(stm32l4, stm32l5, stm32wb))] + critical_section::with(|_| crate::pac::PWR.cr2().modify(|w| w.set_usv(true))); + + #[cfg(pwr_h5)] + critical_section::with(|_| crate::pac::PWR.usbscr().modify(|w| w.set_usb33sv(true))); + + #[cfg(stm32h7)] + { + // If true, VDD33USB is generated by internal regulator from VDD50USB + // If false, VDD33USB and VDD50USB must be suplied directly with 3.3V (default on nucleo) + // TODO: unhardcode + let internal_regulator = false; + + // Enable USB power + critical_section::with(|_| { + crate::pac::PWR.cr3().modify(|w| { + w.set_usb33den(true); + w.set_usbregen(internal_regulator); + }) + }); + + // Wait for USB power to stabilize + while !crate::pac::PWR.cr3().read().usb33rdy() {} + } + + #[cfg(stm32u5)] + { + // Enable USB power + critical_section::with(|_| { + crate::pac::PWR.svmcr().modify(|w| { + w.set_usv(true); + w.set_uvmen(true); + }) + }); + + // Wait for USB power to stabilize + while !crate::pac::PWR.svmsr().read().vddusbrdy() {} + } + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + ::enable_and_reset(); +} diff --git a/embassy-stm32/src/usb/otg.rs b/embassy-stm32/src/usb/otg.rs index d15d929f6..80a08f3c5 100644 --- a/embassy-stm32/src/usb/otg.rs +++ b/embassy-stm32/src/usb/otg.rs @@ -560,8 +560,7 @@ impl<'d, T: Instance> Bus<'d, T> { impl<'d, T: Instance> Bus<'d, T> { fn init(&mut self) { - #[cfg(stm32l4)] - critical_section::with(|_| crate::pac::PWR.cr2().modify(|w| w.set_usv(true))); + super::common_init::(); #[cfg(stm32f7)] { @@ -589,22 +588,6 @@ impl<'d, T: Instance> Bus<'d, T> { #[cfg(stm32h7)] { - // If true, VDD33USB is generated by internal regulator from VDD50USB - // If false, VDD33USB and VDD50USB must be suplied directly with 3.3V (default on nucleo) - // TODO: unhardcode - let internal_regulator = false; - - // Enable USB power - critical_section::with(|_| { - crate::pac::PWR.cr3().modify(|w| { - w.set_usb33den(true); - w.set_usbregen(internal_regulator); - }) - }); - - // Wait for USB power to stabilize - while !crate::pac::PWR.cr3().read().usb33rdy() {} - // Enable ULPI clock if external PHY is used let ulpien = !self.phy_type.internal(); critical_section::with(|_| { @@ -625,25 +608,6 @@ impl<'d, T: Instance> Bus<'d, T> { }); } - #[cfg(stm32u5)] - { - // Enable USB power - critical_section::with(|_| { - crate::pac::PWR.svmcr().modify(|w| { - w.set_usv(true); - w.set_uvmen(true); - }) - }); - - // Wait for USB power to stabilize - while !crate::pac::PWR.svmsr().read().vddusbrdy() {} - } - - ::enable_and_reset(); - - T::Interrupt::unpend(); - unsafe { T::Interrupt::enable() }; - let r = T::regs(); let core_id = r.cid().read().0; trace!("Core id {:08x}", core_id); diff --git a/embassy-stm32/src/usb/usb.rs b/embassy-stm32/src/usb/usb.rs index b3b3470e4..1fb2c9ebb 100644 --- a/embassy-stm32/src/usb/usb.rs +++ b/embassy-stm32/src/usb/usb.rs @@ -12,7 +12,6 @@ use embassy_usb_driver::{ Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointInfo, EndpointType, Event, Unsupported, }; -use crate::interrupt::typelevel::Interrupt; use crate::pac::usb::regs; use crate::pac::usb::vals::{EpType, Stat}; use crate::pac::USBRAM; @@ -258,18 +257,10 @@ impl<'d, T: Instance> Driver<'d, T> { dm: impl Peripheral

> + 'd, ) -> Self { into_ref!(dp, dm); - T::Interrupt::unpend(); - unsafe { T::Interrupt::enable() }; - let regs = T::regs(); - - #[cfg(any(stm32l4, stm32l5, stm32wb))] - crate::pac::PWR.cr2().modify(|w| w.set_usv(true)); + super::common_init::(); - #[cfg(pwr_h5)] - crate::pac::PWR.usbscr().modify(|w| w.set_usb33sv(true)); - - ::enable_and_reset(); + let regs = T::regs(); regs.cntr().write(|w| { w.set_pdwn(false); -- cgit From d90abb8ac91440602386b807edb221cae59e82f9 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 19 Mar 2024 21:33:20 +0100 Subject: stm32/usb: assert usb clock is okay. --- embassy-stm32/src/usb/mod.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/embassy-stm32/src/usb/mod.rs b/embassy-stm32/src/usb/mod.rs index 130c728e6..788f61f16 100644 --- a/embassy-stm32/src/usb/mod.rs +++ b/embassy-stm32/src/usb/mod.rs @@ -10,6 +10,19 @@ use crate::rcc::sealed::RccPeripheral; /// clock, power initialization stuff that's common for USB and OTG. fn common_init() { + // Check the USB clock is enabled and running at exactly 48 MHz. + // frequency() will panic if not enabled + let freq = T::frequency(); + // Check frequency is within the 0.25% tolerance allowed by the spec. + // Clock might not be exact 48Mhz due to rounding errors in PLL calculation, or if the user + // has tight clock restrictions due to something else (like audio). + if freq.0.abs_diff(48_000_000) > 120_000 { + panic!( + "USB clock should be 48Mhz but is {} Hz. Please double-check your RCC settings.", + freq.0 + ) + } + #[cfg(any(stm32l4, stm32l5, stm32wb))] critical_section::with(|_| crate::pac::PWR.cr2().modify(|w| w.set_usv(true))); -- cgit From 4858a53a397c6e96f68340417a19274dd25b4ddc Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 19 Mar 2024 21:49:47 +0100 Subject: stm32/usb: ensure mux is configured in examples. --- examples/stm32f4/src/bin/usb_ethernet.rs | 1 + examples/stm32f4/src/bin/usb_hid_keyboard.rs | 1 + examples/stm32f4/src/bin/usb_hid_mouse.rs | 1 + examples/stm32f4/src/bin/usb_raw.rs | 1 + examples/stm32f4/src/bin/usb_serial.rs | 1 + examples/stm32f7/src/bin/usb_serial.rs | 1 + examples/stm32h7/src/bin/usb_serial.rs | 1 + examples/stm32l4/src/bin/usb_serial.rs | 28 +++++++++++++++------------- examples/stm32l5/src/bin/usb_ethernet.rs | 28 ++++++++++++++++------------ examples/stm32l5/src/bin/usb_hid_mouse.rs | 28 ++++++++++++++++------------ examples/stm32l5/src/bin/usb_serial.rs | 28 ++++++++++++++++------------ examples/stm32u5/src/bin/usb_serial.rs | 1 + 12 files changed, 71 insertions(+), 49 deletions(-) diff --git a/examples/stm32f4/src/bin/usb_ethernet.rs b/examples/stm32f4/src/bin/usb_ethernet.rs index 60e580609..405cce6bc 100644 --- a/examples/stm32f4/src/bin/usb_ethernet.rs +++ b/examples/stm32f4/src/bin/usb_ethernet.rs @@ -63,6 +63,7 @@ async fn main(spawner: Spawner) { config.rcc.apb1_pre = APBPrescaler::DIV4; config.rcc.apb2_pre = APBPrescaler::DIV2; config.rcc.sys = Sysclk::PLL1_P; + config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; } let p = embassy_stm32::init(config); diff --git a/examples/stm32f4/src/bin/usb_hid_keyboard.rs b/examples/stm32f4/src/bin/usb_hid_keyboard.rs index ae4ce6b6a..6c25a0a64 100644 --- a/examples/stm32f4/src/bin/usb_hid_keyboard.rs +++ b/examples/stm32f4/src/bin/usb_hid_keyboard.rs @@ -42,6 +42,7 @@ async fn main(_spawner: Spawner) { config.rcc.apb1_pre = APBPrescaler::DIV4; config.rcc.apb2_pre = APBPrescaler::DIV2; config.rcc.sys = Sysclk::PLL1_P; + config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; } let p = embassy_stm32::init(config); diff --git a/examples/stm32f4/src/bin/usb_hid_mouse.rs b/examples/stm32f4/src/bin/usb_hid_mouse.rs index 5c64f4969..d4725d597 100644 --- a/examples/stm32f4/src/bin/usb_hid_mouse.rs +++ b/examples/stm32f4/src/bin/usb_hid_mouse.rs @@ -39,6 +39,7 @@ async fn main(_spawner: Spawner) { config.rcc.apb1_pre = APBPrescaler::DIV4; config.rcc.apb2_pre = APBPrescaler::DIV2; config.rcc.sys = Sysclk::PLL1_P; + config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; } let p = embassy_stm32::init(config); diff --git a/examples/stm32f4/src/bin/usb_raw.rs b/examples/stm32f4/src/bin/usb_raw.rs index c13fe051f..15a98ff8b 100644 --- a/examples/stm32f4/src/bin/usb_raw.rs +++ b/examples/stm32f4/src/bin/usb_raw.rs @@ -92,6 +92,7 @@ async fn main(_spawner: Spawner) { config.rcc.apb1_pre = APBPrescaler::DIV4; config.rcc.apb2_pre = APBPrescaler::DIV2; config.rcc.sys = Sysclk::PLL1_P; + config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; } let p = embassy_stm32::init(config); diff --git a/examples/stm32f4/src/bin/usb_serial.rs b/examples/stm32f4/src/bin/usb_serial.rs index 21e255612..6080b34a8 100644 --- a/examples/stm32f4/src/bin/usb_serial.rs +++ b/examples/stm32f4/src/bin/usb_serial.rs @@ -39,6 +39,7 @@ async fn main(_spawner: Spawner) { config.rcc.apb1_pre = APBPrescaler::DIV4; config.rcc.apb2_pre = APBPrescaler::DIV2; config.rcc.sys = Sysclk::PLL1_P; + config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; } let p = embassy_stm32::init(config); diff --git a/examples/stm32f7/src/bin/usb_serial.rs b/examples/stm32f7/src/bin/usb_serial.rs index bdd7b76ff..26ecf3bc8 100644 --- a/examples/stm32f7/src/bin/usb_serial.rs +++ b/examples/stm32f7/src/bin/usb_serial.rs @@ -39,6 +39,7 @@ async fn main(_spawner: Spawner) { config.rcc.apb1_pre = APBPrescaler::DIV4; config.rcc.apb2_pre = APBPrescaler::DIV2; config.rcc.sys = Sysclk::PLL1_P; + config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; } let p = embassy_stm32::init(config); diff --git a/examples/stm32h7/src/bin/usb_serial.rs b/examples/stm32h7/src/bin/usb_serial.rs index 3d42a24f3..c3ddda72a 100644 --- a/examples/stm32h7/src/bin/usb_serial.rs +++ b/examples/stm32h7/src/bin/usb_serial.rs @@ -40,6 +40,7 @@ async fn main(_spawner: Spawner) { config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz config.rcc.voltage_scale = VoltageScale::Scale1; + config.rcc.mux.usbsel = mux::Usbsel::HSI48; } let p = embassy_stm32::init(config); diff --git a/examples/stm32l4/src/bin/usb_serial.rs b/examples/stm32l4/src/bin/usb_serial.rs index c7f4add7f..047234d60 100644 --- a/examples/stm32l4/src/bin/usb_serial.rs +++ b/examples/stm32l4/src/bin/usb_serial.rs @@ -4,7 +4,6 @@ use defmt::{panic, *}; use defmt_rtt as _; // global logger use embassy_executor::Spawner; -use embassy_stm32::rcc::*; use embassy_stm32::usb::{Driver, Instance}; use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; @@ -22,18 +21,21 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); let mut config = Config::default(); - config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB - config.rcc.sys = Sysclk::PLL1_R; - config.rcc.hsi = true; - config.rcc.pll = Some(Pll { - source: PllSource::HSI, - prediv: PllPreDiv::DIV1, - mul: PllMul::MUL10, - divp: None, - divq: None, - divr: Some(PllRDiv::DIV2), // sysclk 80Mhz (16 / 1 * 10 / 2) - }); - + { + use embassy_stm32::rcc::*; + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL10, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), // sysclk 80Mhz (16 / 1 * 10 / 2) + }); + config.rcc.mux.clk48sel = mux::Clk48sel::HSI48; + } let p = embassy_stm32::init(config); // Create the driver, from the HAL. diff --git a/examples/stm32l5/src/bin/usb_ethernet.rs b/examples/stm32l5/src/bin/usb_ethernet.rs index f6d8b16d0..dc1e7022d 100644 --- a/examples/stm32l5/src/bin/usb_ethernet.rs +++ b/examples/stm32l5/src/bin/usb_ethernet.rs @@ -5,7 +5,6 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; use embassy_net::{Stack, StackResources}; -use embassy_stm32::rcc::*; use embassy_stm32::rng::Rng; use embassy_stm32::usb::Driver; use embassy_stm32::{bind_interrupts, peripherals, rng, usb, Config}; @@ -44,17 +43,22 @@ async fn net_task(stack: &'static Stack>) -> ! { #[embassy_executor::main] async fn main(spawner: Spawner) { let mut config = Config::default(); - config.rcc.hsi = true; - config.rcc.sys = Sysclk::PLL1_R; - config.rcc.pll = Some(Pll { - // 80Mhz clock (16 / 1 * 10 / 2) - source: PllSource::HSI, - prediv: PllPreDiv::DIV1, - mul: PllMul::MUL10, - divp: None, - divq: None, - divr: Some(PllRDiv::DIV2), - }); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = true; + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.pll = Some(Pll { + // 80Mhz clock (16 / 1 * 10 / 2) + source: PllSource::HSI, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL10, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), + }); + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.mux.clk48sel = mux::Clk48sel::HSI48; + } let p = embassy_stm32::init(config); // Create the driver, from the HAL. diff --git a/examples/stm32l5/src/bin/usb_hid_mouse.rs b/examples/stm32l5/src/bin/usb_hid_mouse.rs index c51ed96e0..b86fba455 100644 --- a/examples/stm32l5/src/bin/usb_hid_mouse.rs +++ b/examples/stm32l5/src/bin/usb_hid_mouse.rs @@ -4,7 +4,6 @@ use defmt::*; use embassy_executor::Spawner; use embassy_futures::join::join; -use embassy_stm32::rcc::*; use embassy_stm32::usb::Driver; use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; use embassy_time::Timer; @@ -21,17 +20,22 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut config = Config::default(); - config.rcc.hsi = true; - config.rcc.sys = Sysclk::PLL1_R; - config.rcc.pll = Some(Pll { - // 80Mhz clock (16 / 1 * 10 / 2) - source: PllSource::HSI, - prediv: PllPreDiv::DIV1, - mul: PllMul::MUL10, - divp: None, - divq: None, - divr: Some(PllRDiv::DIV2), - }); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = true; + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.pll = Some(Pll { + // 80Mhz clock (16 / 1 * 10 / 2) + source: PllSource::HSI, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL10, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), + }); + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.mux.clk48sel = mux::Clk48sel::HSI48; + } let p = embassy_stm32::init(config); // Create the driver, from the HAL. diff --git a/examples/stm32l5/src/bin/usb_serial.rs b/examples/stm32l5/src/bin/usb_serial.rs index 87987f2ce..5e2378b58 100644 --- a/examples/stm32l5/src/bin/usb_serial.rs +++ b/examples/stm32l5/src/bin/usb_serial.rs @@ -4,7 +4,6 @@ use defmt::{panic, *}; use embassy_executor::Spawner; use embassy_futures::join::join; -use embassy_stm32::rcc::*; use embassy_stm32::usb::{Driver, Instance}; use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; @@ -19,17 +18,22 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut config = Config::default(); - config.rcc.hsi = true; - config.rcc.sys = Sysclk::PLL1_R; - config.rcc.pll = Some(Pll { - // 80Mhz clock (16 / 1 * 10 / 2) - source: PllSource::HSI, - prediv: PllPreDiv::DIV1, - mul: PllMul::MUL10, - divp: None, - divq: None, - divr: Some(PllRDiv::DIV2), - }); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = true; + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.pll = Some(Pll { + // 80Mhz clock (16 / 1 * 10 / 2) + source: PllSource::HSI, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL10, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), + }); + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.mux.clk48sel = mux::Clk48sel::HSI48; + } let p = embassy_stm32::init(config); info!("Hello World!"); diff --git a/examples/stm32u5/src/bin/usb_serial.rs b/examples/stm32u5/src/bin/usb_serial.rs index 98ae46b1c..33e02ce3b 100644 --- a/examples/stm32u5/src/bin/usb_serial.rs +++ b/examples/stm32u5/src/bin/usb_serial.rs @@ -35,6 +35,7 @@ async fn main(_spawner: Spawner) { config.rcc.sys = Sysclk::PLL1_R; config.rcc.voltage_range = VoltageScale::RANGE1; config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.mux.iclksel = mux::Iclksel::HSI48; // USB uses ICLK } let p = embassy_stm32::init(config); -- cgit