diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index 1cb7e6e201..40d20cc412 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -72,6 +72,14 @@ typedef struct { static dcd_data_t _dcd_data; +static dcd_dwc2_link_state_t _dcd_link_state[DWC2_CONTROLLER_COUNT]; + +TU_ATTR_ALWAYS_INLINE static inline void dcd_dwc2_link_set(uint8_t rhport, dcd_dwc2_link_state_t state) { + if (rhport < DWC2_CONTROLLER_COUNT) { + _dcd_link_state[rhport] = state; + } +} + CFG_TUD_MEM_SECTION static struct { TUD_EPBUF_DEF(setup_packet, 8); } _dcd_usbbuf; @@ -390,6 +398,49 @@ static void edpt_schedule_packets(uint8_t rhport, const uint8_t epnum, const uin } } +//-------------------------------------------------------------------- +// Power / clock management +//-------------------------------------------------------------------- + +// Power / clock gating (PCGCCTL): Synopsys DWC2 programming guide — on suspend stop PHY +// clock then gate HCLK; on resume ungate HCLK first, then restore PHY clock. +// The clock gating sequence is following DWC2 programming guide: +// - 14.2.2.2 Internal Clock Gating when the DWC_otg Core is in Device Mode + +// Put controller in clock gating. +// According to the linux kernel driver for dwc2 in gadeget mode, after each register write (GATEHCLK or STOPPCLK), +// a delay is applied before the next register access. +TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcd_enter_clock_gating(dwc2_regs_t* dwc2) { + dwc2->pcgcctl |= PCGCCTL_STOPPCLK; + dwc2_pcgcctl_step_delay(); + dwc2->pcgcctl |= PCGCCTL_GATEHCLK; + dwc2_pcgcctl_step_delay(); + + if ((dwc2->pcgcctl & (PCGCCTL_STOPPCLK | PCGCCTL_GATEHCLK)) == (PCGCCTL_STOPPCLK | PCGCCTL_GATEHCLK)) { + return true; // Successfully gated + } else { + return false; // Failed to gate + } + // TODO for me: Handle return value +} + +// Exit controller from device clock gating. +// According to the linux kernel driver for dwc2 in gadeget mode, after each register write (GATEHCLK or STOPPCLK), +// a delay is applied before the next register access. +TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcd_exit_clock_gating(dwc2_regs_t* dwc2) { + dwc2->pcgcctl &= ~PCGCCTL_GATEHCLK; + dwc2_pcgcctl_step_delay(); + dwc2->pcgcctl &= ~PCGCCTL_STOPPCLK; + dwc2_pcgcctl_step_delay(); + + if ((dwc2->pcgcctl & (PCGCCTL_GATEHCLK | PCGCCTL_STOPPCLK)) == 0) { + return true; // Successfully ungated + } else { + return false; // Failed to ungate + } + // TODO for me: Handle return value +} + //-------------------------------------------------------------------- // Controller API //-------------------------------------------------------------------- @@ -471,6 +522,8 @@ void dcd_remote_wakeup(uint8_t rhport) { dwc2_regs_t* dwc2 = DWC2_REG(rhport); + dwc2_dcd_exit_clock_gating(dwc2); + // set remote wakeup dwc2->dctl |= DCTL_RWUSIG; @@ -488,6 +541,8 @@ void dcd_connect(uint8_t rhport) { (void) rhport; dwc2_regs_t* dwc2 = DWC2_REG(rhport); + dcd_dwc2_link_set(rhport, DCD_DWC2_LNK_IDLE); + #ifdef TUP_USBIP_DWC2_ESP32 usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf; conf.pad_pull_override = 0; @@ -505,6 +560,8 @@ void dcd_disconnect(uint8_t rhport) { (void) rhport; dwc2_regs_t* dwc2 = DWC2_REG(rhport); + dcd_dwc2_link_set(rhport, DCD_DWC2_LNK_OFF); + #ifdef TUP_USBIP_DWC2_ESP32 usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf; conf.pad_pull_override = 1; @@ -678,6 +735,11 @@ static void handle_bus_reset(uint8_t rhport) { dwc2_regs_t *dwc2 = DWC2_REG(rhport); const uint8_t ep_count = dwc2_ep_count(dwc2); + // Ungate clocks before accessing any registers, otherwise the core will be unresponsive + dwc2_dcd_exit_clock_gating(dwc2); + + dcd_dwc2_link_set(rhport, DCD_DWC2_LNK_DEFAULT); + tu_memclr(xfer_status, sizeof(xfer_status)); _dcd_data.sof_en = false; @@ -856,6 +918,7 @@ static void handle_epout_slave(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doe if (epin0->diepctl & DIEPCTL_EPENA) { edpt_disable(rhport, 0x80, false); } + dcd_dwc2_link_set(rhport, DCD_DWC2_LNK_ACTIVE); dcd_event_setup_received(rhport, _dcd_usbbuf.setup_packet, true); return; } @@ -941,6 +1004,7 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi } dma_setup_prepare(rhport); dcd_dcache_invalidate(_dcd_usbbuf.setup_packet, 8); + dcd_dwc2_link_set(rhport, DCD_DWC2_LNK_ACTIVE); dcd_event_setup_received(rhport, _dcd_usbbuf.setup_packet, true); return; } @@ -1107,10 +1171,19 @@ void dcd_int_handler(uint8_t rhport) { if (gintsts & GINTSTS_USBSUSP) { dwc2->gintsts = GINTSTS_USBSUSP; dwc2->gintmsk &= ~GINTMSK_USBSUSPM; + // Plug/unplug noise can look like bus idle (suspend). Only gate when link is ACTIVE (SETUP seen). + if (_dcd_link_state[rhport] == DCD_DWC2_LNK_ACTIVE) { + dwc2_dcd_enter_clock_gating(dwc2); + dcd_dwc2_link_set(rhport, DCD_DWC2_LNK_SUSPENDED); + } dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true); } if (gintsts & GINTSTS_WKUINT) { + dwc2_dcd_exit_clock_gating(dwc2); + if (_dcd_link_state[rhport] == DCD_DWC2_LNK_SUSPENDED) { + dcd_dwc2_link_set(rhport, DCD_DWC2_LNK_ACTIVE); + } dwc2->gintsts = GINTSTS_WKUINT; dwc2->gintmsk |= GINTMSK_USBSUSPM; dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); @@ -1124,6 +1197,7 @@ void dcd_int_handler(uint8_t rhport) { const uint32_t otg_int = dwc2->gotgint; if (otg_int & GOTGINT_SEDET) { + dcd_dwc2_link_set(rhport, DCD_DWC2_LNK_OFF); dcd_event_bus_signal(rhport, DCD_EVENT_UNPLUGGED, true); } @@ -1185,4 +1259,11 @@ void dcd_enter_test_mode(uint8_t rhport, tusb_feature_test_mode_t test_selector) } #endif +dcd_dwc2_link_state_t dcd_dwc2_link_state_get(uint8_t rhport) { + if (rhport >= DWC2_CONTROLLER_COUNT) { + return DCD_DWC2_LNK_OFF; + } + return _dcd_link_state[rhport]; +} + #endif diff --git a/src/portable/synopsys/dwc2/dwc2_at32.h b/src/portable/synopsys/dwc2/dwc2_at32.h index 37b6592c47..d338606415 100644 --- a/src/portable/synopsys/dwc2/dwc2_at32.h +++ b/src/portable/synopsys/dwc2/dwc2_at32.h @@ -97,6 +97,10 @@ while (count--) __asm volatile("nop"); } +TU_ATTR_ALWAYS_INLINE static inline void dwc2_pcgcctl_step_delay(void) { + // Clock gating step delay + } + // MCU specific PHY init, called BEFORE core reset TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t *dwc2, uint8_t hs_phy_type) { (void) dwc2; diff --git a/src/portable/synopsys/dwc2/dwc2_bcm.h b/src/portable/synopsys/dwc2/dwc2_bcm.h index e5824606ad..b2b4a88e89 100644 --- a/src/portable/synopsys/dwc2/dwc2_bcm.h +++ b/src/portable/synopsys/dwc2/dwc2_bcm.h @@ -64,6 +64,12 @@ static inline void dwc2_remote_wakeup_delay(void) // TODO implement later } +TU_ATTR_ALWAYS_INLINE +static inline void dwc2_pcgcctl_step_delay(void) +{ + // Clock gating step delay +} + // MCU specific PHY init, called BEFORE core reset static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { diff --git a/src/portable/synopsys/dwc2/dwc2_common.h b/src/portable/synopsys/dwc2/dwc2_common.h index 33219f786c..d33d3f81f6 100644 --- a/src/portable/synopsys/dwc2/dwc2_common.h +++ b/src/portable/synopsys/dwc2/dwc2_common.h @@ -100,4 +100,19 @@ void dfifo_write_packet(dwc2_regs_t* dwc2, uint8_t fifo_num, uint8_t const* src, // DMA //--------------------------------------------------------------------+ +//--------------------------------------------------------------------+ +// Device link state (dcd_dwc2) +//--------------------------------------------------------------------+ +#if CFG_TUD_ENABLED +typedef enum { + DCD_DWC2_LNK_OFF = 0, // Soft-disconnected (DCTL_SDIS) or session end (e.g. OTG SEDET) + DCD_DWC2_LNK_IDLE, // Pull-up enabled, waiting for USB bus reset + DCD_DWC2_LNK_DEFAULT, // After USB reset; default device state (address 0) + DCD_DWC2_LNK_ACTIVE, // SETUP received from host; bus suspend can be trusted (not plug noise) + DCD_DWC2_LNK_SUSPENDED, // USB suspend (PHY/HCLK may be clock-gated) +} dcd_dwc2_link_state_t; + +dcd_dwc2_link_state_t dcd_dwc2_link_state_get(uint8_t rhport); +#endif + #endif diff --git a/src/portable/synopsys/dwc2/dwc2_efm32.h b/src/portable/synopsys/dwc2/dwc2_efm32.h index 0e3570cbb1..eb2cf1c967 100644 --- a/src/portable/synopsys/dwc2/dwc2_efm32.h +++ b/src/portable/synopsys/dwc2/dwc2_efm32.h @@ -62,6 +62,12 @@ static inline void dwc2_remote_wakeup_delay(void) // while ( count-- ) __NOP(); } +TU_ATTR_ALWAYS_INLINE +static inline void dwc2_pcgcctl_step_delay(void) +{ + // Clock gating step delay +} + // MCU specific PHY init, called BEFORE core reset static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { diff --git a/src/portable/synopsys/dwc2/dwc2_esp32.h b/src/portable/synopsys/dwc2/dwc2_esp32.h index a4e0d1770e..3f8a457d9c 100644 --- a/src/portable/synopsys/dwc2/dwc2_esp32.h +++ b/src/portable/synopsys/dwc2/dwc2_esp32.h @@ -36,6 +36,7 @@ #include "freertos/task.h" #include "esp_intr_alloc.h" +#include "esp_rom_sys.h" #include "soc/periph_defs.h" #include "soc/usb_wrap_struct.h" @@ -110,7 +111,13 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_ #define dwc2_dcd_int_disable(_rhport) dwc2_int_set(_rhport, TUSB_ROLE_DEVICE, false) TU_ATTR_ALWAYS_INLINE static inline void dwc2_remote_wakeup_delay(void) { - vTaskDelay(pdMS_TO_TICKS(1)); + ///* USB remote wakeup K-state must be 1–15 ms; use busy-wait so duration does not depend on tick Hz. */ + esp_rom_delay_us(10 * 1000); +} + +TU_ATTR_ALWAYS_INLINE static inline void dwc2_pcgcctl_step_delay(void) { + // Clock gating step delay + esp_rom_delay_us(5); } // MCU specific PHY init, called BEFORE core reset diff --git a/src/portable/synopsys/dwc2/dwc2_gd32.h b/src/portable/synopsys/dwc2/dwc2_gd32.h index 0375fffe4e..61df8943b1 100644 --- a/src/portable/synopsys/dwc2/dwc2_gd32.h +++ b/src/portable/synopsys/dwc2/dwc2_gd32.h @@ -76,6 +76,12 @@ static inline void dwc2_remote_wakeup_delay(void) while ( count-- ) __asm volatile ("nop"); } +TU_ATTR_ALWAYS_INLINE +static inline void dwc2_pcgcctl_step_delay(void) +{ + // Clock gating step delay +} + // MCU specific PHY init, called BEFORE core reset static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { diff --git a/src/portable/synopsys/dwc2/dwc2_stm32.h b/src/portable/synopsys/dwc2/dwc2_stm32.h index 08950ccc0c..aa41bbc5e2 100644 --- a/src/portable/synopsys/dwc2/dwc2_stm32.h +++ b/src/portable/synopsys/dwc2/dwc2_stm32.h @@ -179,6 +179,12 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_remote_wakeup_delay(void) { while (count--) __NOP(); } +TU_ATTR_ALWAYS_INLINE +static inline void dwc2_pcgcctl_step_delay(void) +{ + // Clock gating step delay +} + // MCU specific PHY init, called BEFORE core reset // - dwc2 3.30a (H5) use USB_HS_PHYC // - dwc2 4.11a (U5) use femtoPHY diff --git a/src/portable/synopsys/dwc2/dwc2_xmc.h b/src/portable/synopsys/dwc2/dwc2_xmc.h index 63419abf79..a5487acf08 100644 --- a/src/portable/synopsys/dwc2/dwc2_xmc.h +++ b/src/portable/synopsys/dwc2/dwc2_xmc.h @@ -61,6 +61,12 @@ static inline void dwc2_remote_wakeup_delay(void) // while ( count-- ) __NOP(); } +TU_ATTR_ALWAYS_INLINE +static inline void dwc2_pcgcctl_step_delay(void) +{ + // Clock gating step delay +} + // MCU specific PHY init, called BEFORE core reset static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type) {