From 501d3942e8b6b04fd1cb4c14728ba4c2e118d8b5 Mon Sep 17 00:00:00 2001
From: Dave Marples <dave@marples.net>
Date: Thu, 5 Dec 2024 16:35:56 +0000
Subject: [PATCH 1/2] Add support for stm32u595/5a5 OTG_HS in client mode

---
 embassy-stm32/src/usb/mod.rs              |  20 ++++
 embassy-stm32/src/usb/otg.rs              |  32 ++++--
 examples/stm32u5/src/bin/usb_hs_serial.rs | 129 ++++++++++++++++++++++
 3 files changed, 174 insertions(+), 7 deletions(-)
 create mode 100644 examples/stm32u5/src/bin/usb_hs_serial.rs

diff --git a/embassy-stm32/src/usb/mod.rs b/embassy-stm32/src/usb/mod.rs
index a473285bf8..9737b9b3f8 100644
--- a/embassy-stm32/src/usb/mod.rs
+++ b/embassy-stm32/src/usb/mod.rs
@@ -26,6 +26,7 @@ fn common_init<T: Instance>() {
     // 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).
     #[cfg(not(stm32h7rs))]
+    #[cfg(not(all(stm32u5, peri_usb_otg_hs)))]
     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.",
@@ -33,6 +34,15 @@ fn common_init<T: Instance>() {
         )
     }
 
+    // For OTG-HS on STM32U5 only the 32MHz clock is fast enough (Table 762, Sect 73.14.4)
+    #[cfg(all(stm32u5, peri_usb_otg_hs))]
+    if freq.0.abs_diff(32_000_000) > 120_000 {
+        panic!(
+            "USB clock should be 32Mhz but is {} Hz. Please double-check your RCC settings.",
+            freq.0
+        )
+    }
+
     #[cfg(any(stm32l4, stm32l5, stm32wb, stm32u0))]
     critical_section::with(|_| crate::pac::PWR.cr2().modify(|w| w.set_usv(true)));
 
@@ -90,6 +100,16 @@ fn common_init<T: Instance>() {
 
         // Wait for USB power to stabilize
         while !crate::pac::PWR.svmsr().read().vddusbrdy() {}
+
+        // Now set up transceiver power if it's a OTG-HS
+        #[cfg(peri_usb_otg_hs)]
+        {
+            crate::pac::PWR.vosr().modify(|w| {
+                w.set_usbpwren(true);
+                w.set_usbboosten(true);
+            });
+            while !crate::pac::PWR.vosr().read().usbboostrdy() {}
+        }
     }
 
     T::Interrupt::unpend();
diff --git a/embassy-stm32/src/usb/otg.rs b/embassy-stm32/src/usb/otg.rs
index 6ca28b1563..d1b38a558a 100644
--- a/embassy-stm32/src/usb/otg.rs
+++ b/embassy-stm32/src/usb/otg.rs
@@ -26,7 +26,6 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl
     unsafe fn on_interrupt() {
         let r = T::regs();
         let state = T::state();
-
         on_interrupt_impl(r, state, T::ENDPOINT_COUNT);
     }
 }
@@ -103,15 +102,18 @@ impl<'d, T: Instance> Driver<'d, T> {
     pub fn new_hs(
         _peri: impl Peripheral<P = T> + 'd,
         _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
-        dp: impl Peripheral<P = impl DpPin<T>> + 'd,
-        dm: impl Peripheral<P = impl DmPin<T>> + 'd,
+        _dp: impl Peripheral<P = impl DpPin<T>> + 'd,
+        _dm: impl Peripheral<P = impl DmPin<T>> + 'd,
         ep_out_buffer: &'d mut [u8],
         config: Config,
     ) -> Self {
-        into_ref!(dp, dm);
-
-        dp.set_as_af(dp.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh));
-        dm.set_as_af(dm.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh));
+        // For STM32U5 High speed pins need to be left in analog mode
+        #[cfg(not(all(stm32u5, peri_usb_otg_hs)))]
+        {
+            into_ref!(_dp, _dm);
+            _dp.set_as_af(_dp.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh));
+            _dm.set_as_af(_dm.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh));
+        }
 
         let instance = OtgInstance {
             regs: T::regs(),
@@ -311,6 +313,22 @@ impl<'d, T: Instance> Bus<'d, T> {
             });
         });
 
+        #[cfg(all(stm32u5, peri_usb_otg_hs))]
+        {
+            // Only the 32MHz clock is suitable here, which the magic number represents
+            crate::pac::SYSCFG.otghsphycr().modify(|w| {
+                w.set_clksel(11);
+                w.set_en(true);
+            });
+
+            critical_section::with(|_| {
+                crate::pac::RCC.ahb2enr1().modify(|w| {
+                    w.set_usb_otg_hsen(true);
+                    w.set_usb_otg_hs_phyen(true);
+                });
+            });
+        }
+
         let r = T::regs();
         let core_id = r.cid().read().0;
         trace!("Core id {:08x}", core_id);
diff --git a/examples/stm32u5/src/bin/usb_hs_serial.rs b/examples/stm32u5/src/bin/usb_hs_serial.rs
new file mode 100644
index 0000000000..5549e2cbb6
--- /dev/null
+++ b/examples/stm32u5/src/bin/usb_hs_serial.rs
@@ -0,0 +1,129 @@
+#![no_std]
+#![no_main]
+
+use defmt::{panic, *};
+use defmt_rtt as _; // global logger
+use embassy_executor::Spawner;
+use embassy_futures::join::join;
+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;
+use panic_probe as _;
+
+bind_interrupts!(struct Irqs {
+    OTG_HS => usb::InterruptHandler<peripherals::USB_OTG_HS>;
+});
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+    info!("Hello World!");
+
+    let mut config = Config::default();
+    {
+        use embassy_stm32::rcc::*;
+        use embassy_stm32::time::Hertz;
+        config.rcc.hse = Some(Hse {
+            freq: Hertz(16_000_000),
+            mode: HseMode::Oscillator,
+        });
+        config.rcc.pll1 = Some(Pll {
+            source: PllSource::HSE,
+            prediv: PllPreDiv::DIV2,   // HSE / 2 = 8MHz
+            mul: PllMul::MUL60,        // 8MHz * 60 = 480MHz
+            divr: Some(PllDiv::DIV3),  // 480MHz / 3 = 160MHz (sys_ck)
+            divq: Some(PllDiv::DIV10), // 480MHz / 10 = 48MHz (USB)
+            divp: Some(PllDiv::DIV15), // 480MHz / 15 = 32MHz (USBOTG)
+        });
+        config.rcc.mux.otghssel = mux::Otghssel::PLL1_P;
+        config.rcc.voltage_range = VoltageScale::RANGE1;
+        config.rcc.sys = Sysclk::PLL1_R;
+    }
+
+    let p = embassy_stm32::init(config);
+
+    // Create the driver, from the HAL.
+    let mut ep_out_buffer = [0u8; 256];
+    let mut config = embassy_stm32::usb::Config::default();
+    // Do not enable vbus_detection. This is a safe default that works in all boards.
+    // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need
+    // to enable vbus_detection to comply with the USB spec. If you enable it, the board
+    // has to support it or USB won't work at all. See docs on `vbus_detection` for details.
+    config.vbus_detection = false;
+    let driver = Driver::new_hs(p.USB_OTG_HS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config);
+
+    // Create embassy-usb Config
+    let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
+    config.manufacturer = Some("Embassy");
+    config.product = Some("USB-serial example");
+    config.serial_number = Some("12345678");
+
+    // Required for windows compatibility.
+    // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
+    config.device_class = 0xEF;
+    config.device_sub_class = 0x02;
+    config.device_protocol = 0x01;
+    config.composite_with_iads = true;
+
+    // Create embassy-usb DeviceBuilder using the driver and config.
+    // It needs some buffers for building the descriptors.
+    let mut config_descriptor = [0; 256];
+    let mut bos_descriptor = [0; 256];
+    let mut control_buf = [0; 64];
+
+    let mut state = State::new();
+
+    let mut builder = Builder::new(
+        driver,
+        config,
+        &mut config_descriptor,
+        &mut bos_descriptor,
+        &mut [], // no msos descriptors
+        &mut control_buf,
+    );
+
+    // Create classes on the builder.
+    let mut class = CdcAcmClass::new(&mut builder, &mut state, 64);
+
+    // Build the builder.
+    let mut usb = builder.build();
+
+    // Run the USB device.
+    let usb_fut = usb.run();
+
+    // Do stuff with the class!
+    let echo_fut = async {
+        loop {
+            class.wait_connection().await;
+            info!("Connected");
+            let _ = echo(&mut class).await;
+            info!("Disconnected");
+        }
+    };
+
+    // Run everything concurrently.
+    // If we had made everything `'static` above instead, we could do this using separate tasks instead.
+    join(usb_fut, echo_fut).await;
+}
+
+struct Disconnected {}
+
+impl From<EndpointError> for Disconnected {
+    fn from(val: EndpointError) -> Self {
+        match val {
+            EndpointError::BufferOverflow => panic!("Buffer overflow"),
+            EndpointError::Disabled => Disconnected {},
+        }
+    }
+}
+
+async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> {
+    let mut buf = [0; 64];
+    loop {
+        let n = class.read_packet(&mut buf).await?;
+        let data = &buf[..n];
+        info!("data: {:x}", data);
+        class.write_packet(data).await?;
+    }
+}

From a0e056a629c166b38c536c68afbf852819f10143 Mon Sep 17 00:00:00 2001
From: Marvin Drees <marvin.drees@9elements.com>
Date: Fri, 6 Dec 2024 16:22:08 +0100
Subject: [PATCH 2/2] Update STM32U5 OTG HS clock handling

Signed-off-by: Marvin Drees <marvin.drees@9elements.com>
---
 embassy-stm32/src/rcc/u5.rs  | 29 +++++++++++++++++++++++++++++
 embassy-stm32/src/usb/mod.rs | 14 ++------------
 embassy-stm32/src/usb/otg.rs |  2 --
 3 files changed, 31 insertions(+), 14 deletions(-)

diff --git a/embassy-stm32/src/rcc/u5.rs b/embassy-stm32/src/rcc/u5.rs
index af99c77bc8..dc77dc540b 100644
--- a/embassy-stm32/src/rcc/u5.rs
+++ b/embassy-stm32/src/rcc/u5.rs
@@ -1,9 +1,13 @@
 pub use crate::pac::pwr::vals::Vos as VoltageScale;
+#[cfg(all(peri_usb_otg_hs))]
+pub use crate::pac::rcc::vals::Otghssel;
 pub use crate::pac::rcc::vals::{
     Hpre as AHBPrescaler, Msirange, Msirange as MSIRange, Plldiv as PllDiv, Pllm as PllPreDiv, Plln as PllMul,
     Pllsrc as PllSource, Ppre as APBPrescaler, Sw as Sysclk,
 };
 use crate::pac::rcc::vals::{Hseext, Msirgsel, Pllmboost, Pllrge};
+#[cfg(all(peri_usb_otg_hs))]
+pub use crate::pac::{syscfg::vals::Usbrefcksel, SYSCFG};
 use crate::pac::{FLASH, PWR, RCC};
 use crate::rcc::LSI_FREQ;
 use crate::time::Hertz;
@@ -295,6 +299,31 @@ pub(crate) unsafe fn init(config: Config) {
 
     let rtc = config.ls.init();
 
+    #[cfg(all(stm32u5, peri_usb_otg_hs))]
+    let usb_refck = match config.mux.otghssel {
+        Otghssel::HSE => hse,
+        Otghssel::HSE_DIV_2 => hse.map(|hse_val| hse_val / 2u8),
+        Otghssel::PLL1_P => pll1.p,
+        Otghssel::PLL1_P_DIV_2 => pll1.p.map(|pll1p_val| pll1p_val / 2u8),
+    };
+    #[cfg(all(stm32u5, peri_usb_otg_hs))]
+    let usb_refck_sel = match usb_refck {
+        Some(clk_val) => match clk_val {
+            Hertz(16_000_000) => Usbrefcksel::MHZ16,
+            Hertz(19_200_000) => Usbrefcksel::MHZ19_2,
+            Hertz(20_000_000) => Usbrefcksel::MHZ20,
+            Hertz(24_000_000) => Usbrefcksel::MHZ24,
+            Hertz(26_000_000) => Usbrefcksel::MHZ26,
+            Hertz(32_000_000) => Usbrefcksel::MHZ32,
+            _ => panic!("cannot select OTG_HS reference clock with source frequency of {} Hz, must be one of 16, 19.2, 20, 24, 26, 32 MHz", clk_val),
+        },
+        None => Usbrefcksel::MHZ24,
+    };
+    #[cfg(all(stm32u5, peri_usb_otg_hs))]
+    SYSCFG.otghsphycr().modify(|w| {
+        w.set_clksel(usb_refck_sel);
+    });
+
     let lse = config.ls.lse.map(|l| l.frequency);
     let lsi = config.ls.lsi.then_some(LSI_FREQ);
 
diff --git a/embassy-stm32/src/usb/mod.rs b/embassy-stm32/src/usb/mod.rs
index 9737b9b3f8..ae5963420d 100644
--- a/embassy-stm32/src/usb/mod.rs
+++ b/embassy-stm32/src/usb/mod.rs
@@ -15,7 +15,7 @@ fn common_init<T: Instance>() {
     let freq = T::frequency();
 
     // On the H7RS, the USBPHYC embeds a PLL accepting one of the input frequencies listed below and providing 48MHz to OTG_FS and 60MHz to OTG_HS internally
-    #[cfg(stm32h7rs)]
+    #[cfg(any(stm32h7rs, all(stm32u5, peri_usb_otg_hs)))]
     if ![16_000_000, 19_200_000, 20_000_000, 24_000_000, 26_000_000, 32_000_000].contains(&freq.0) {
         panic!(
             "USB clock should be one of 16, 19.2, 20, 24, 26, 32Mhz but is {} Hz. Please double-check your RCC settings.",
@@ -25,8 +25,7 @@ fn common_init<T: Instance>() {
     // 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).
-    #[cfg(not(stm32h7rs))]
-    #[cfg(not(all(stm32u5, peri_usb_otg_hs)))]
+    #[cfg(not(any(stm32h7rs, all(stm32u5, peri_usb_otg_hs))))]
     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.",
@@ -34,15 +33,6 @@ fn common_init<T: Instance>() {
         )
     }
 
-    // For OTG-HS on STM32U5 only the 32MHz clock is fast enough (Table 762, Sect 73.14.4)
-    #[cfg(all(stm32u5, peri_usb_otg_hs))]
-    if freq.0.abs_diff(32_000_000) > 120_000 {
-        panic!(
-            "USB clock should be 32Mhz but is {} Hz. Please double-check your RCC settings.",
-            freq.0
-        )
-    }
-
     #[cfg(any(stm32l4, stm32l5, stm32wb, stm32u0))]
     critical_section::with(|_| crate::pac::PWR.cr2().modify(|w| w.set_usv(true)));
 
diff --git a/embassy-stm32/src/usb/otg.rs b/embassy-stm32/src/usb/otg.rs
index d1b38a558a..d3c7978e48 100644
--- a/embassy-stm32/src/usb/otg.rs
+++ b/embassy-stm32/src/usb/otg.rs
@@ -315,9 +315,7 @@ impl<'d, T: Instance> Bus<'d, T> {
 
         #[cfg(all(stm32u5, peri_usb_otg_hs))]
         {
-            // Only the 32MHz clock is suitable here, which the magic number represents
             crate::pac::SYSCFG.otghsphycr().modify(|w| {
-                w.set_clksel(11);
                 w.set_en(true);
             });