summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-03-04 17:30:40 +0700
committerhathach <[email protected]>2026-03-04 17:30:40 +0700
commit30af158af9ada161d31e02d08548f78390183920 (patch)
treed686ea5453d25fa746afa3eb5e5b40a76ab34f80 /src
parenta8abc31bf48bba2db76304137f12e7380164d5c9 (diff)
add PHY deinitialization support for DWC2 driver across all MCUs
Diffstat (limited to 'src')
-rw-r--r--src/portable/synopsys/dwc2/dcd_dwc2.c6
-rw-r--r--src/portable/synopsys/dwc2/dwc2_at32.h5
-rw-r--r--src/portable/synopsys/dwc2/dwc2_bcm.h6
-rw-r--r--src/portable/synopsys/dwc2/dwc2_common.c27
-rw-r--r--src/portable/synopsys/dwc2/dwc2_common.h1
-rw-r--r--src/portable/synopsys/dwc2/dwc2_efm32.h8
-rw-r--r--src/portable/synopsys/dwc2/dwc2_esp32.h6
-rw-r--r--src/portable/synopsys/dwc2/dwc2_gd32.h6
-rw-r--r--src/portable/synopsys/dwc2/dwc2_nrf.h5
-rw-r--r--src/portable/synopsys/dwc2/dwc2_stm32.h14
-rw-r--r--src/portable/synopsys/dwc2/dwc2_xmc.h7
-rw-r--r--src/portable/synopsys/dwc2/hcd_dwc2.c16
12 files changed, 85 insertions, 22 deletions
diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c
index 2c76098a4..8685ec6dc 100644
--- a/src/portable/synopsys/dwc2/dcd_dwc2.c
+++ b/src/portable/synopsys/dwc2/dcd_dwc2.c
@@ -493,13 +493,7 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
}
bool dcd_deinit(uint8_t rhport) {
- dwc2_regs_t* dwc2 = DWC2_REG(rhport);
-
- // Disable global interrupt
- dwc2->gahbcfg &= ~GAHBCFG_GINT;
-
dcd_disconnect(rhport);
-
dwc2_core_deinit(rhport);
return true;
}
diff --git a/src/portable/synopsys/dwc2/dwc2_at32.h b/src/portable/synopsys/dwc2/dwc2_at32.h
index 10824ae92..95ee8a8e1 100644
--- a/src/portable/synopsys/dwc2/dwc2_at32.h
+++ b/src/portable/synopsys/dwc2/dwc2_at32.h
@@ -112,6 +112,11 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t *dwc2, uint8_
}
}
+// MCU specific PHY deinit, disable PHY power
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t *dwc2) {
+ dwc2->stm32_gccfg &= ~(STM32_GCCFG_PWRDWN | STM32_GCCFG_DCDEN | STM32_GCCFG_PDEN);
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(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 df6d4a852..852db15e5 100644
--- a/src/portable/synopsys/dwc2/dwc2_bcm.h
+++ b/src/portable/synopsys/dwc2/dwc2_bcm.h
@@ -73,6 +73,12 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
// nothing to do
}
+// MCU specific PHY deinit, disable PHY power
+static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2) {
+ (void) dwc2;
+ // nothing to do
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
{
diff --git a/src/portable/synopsys/dwc2/dwc2_common.c b/src/portable/synopsys/dwc2/dwc2_common.c
index a6afc3154..4e8e1ff04 100644
--- a/src/portable/synopsys/dwc2/dwc2_common.c
+++ b/src/portable/synopsys/dwc2/dwc2_common.c
@@ -39,14 +39,11 @@ static void reset_core(dwc2_regs_t* dwc2) {
while (!(dwc2->grstctl & GRSTCTL_AHBIDL)) {
}
- // load gsnpsid (it is not readable after reset is asserted)
- const uint32_t gsnpsid = dwc2->gsnpsid;
-
- // reset core
- dwc2->grstctl |= GRSTCTL_CSRST;
+ const uint32_t gsnpsid = dwc2->gsnpsid; // preload gsnpsid which is not readable while resetting
+ dwc2->grstctl |= GRSTCTL_CSRST; // reset core
if ((gsnpsid & DWC2_CORE_REV_MASK) < (DWC2_CORE_REV_4_20a & DWC2_CORE_REV_MASK)) {
- // prior v4.20a: CSRST is self-clearing and the core clears this bit after all the necessary logic is reset in
+ // prior v4.20a: CSRST is self-clearing, and the core clears this bit after all the necessary logic is reset in
// the core, which can take several clocks, depending on the current state of the core. Once this bit has been
// cleared, the software must wait at least 3 PHY clocks before accessing the PHY domain (synchronization delay).
while (dwc2->grstctl & GRSTCTL_CSRST) {}
@@ -88,8 +85,7 @@ static void phy_fs_init(dwc2_regs_t* dwc2) {
}
/* dwc2 has 2 highspeed PHYs options
- * - UTMI+ is internal highspeed PHY, can be clocked at 30/60 Mhz for fullspeed or 60 Mhz for highspeed. Can be either
- * 8 or 16-bit interface.
+ * - UTMI+ is internal highspeed PHY, can be clocked at 30 Mhz (8-bit) or 60 Mhz (16-bit).
* - ULPI is external highspeed PHY, clocked at 60Mhz with 8-bit interface.
*
* In addition, UTMI+/ULPI can be shared to run at fullspeed mode with 48Mhz
@@ -250,14 +246,17 @@ bool dwc2_core_init(uint8_t rhport, bool is_hs_phy, bool is_dma) {
void dwc2_core_deinit(uint8_t rhport) {
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
- // Soft disconnect
- dwc2->dctl |= DCTL_SDIS;
-
- // Reset global registers
- dwc2->gotgctl = 0;
+ // Disable global interrupt
+ dwc2->gahbcfg &= ~GAHBCFG_GINT;
- // Reset core
+ // Reset core: this also flushes FIFOs and clears all interrupt registers
reset_core(dwc2);
+
+ // Stop PHY clock and gate HCLK for power saving (per databook chapter 14)
+ dwc2->pcgcctl |= PCGCCTL_STOPPCLK | PCGCCTL_GATEHCLK;
+
+ // MCU-specific PHY deinit (disable PHY power)
+ dwc2_phy_deinit(dwc2);
}
// void dwc2_core_handle_common_irq(uint8_t rhport, bool in_isr) {
diff --git a/src/portable/synopsys/dwc2/dwc2_common.h b/src/portable/synopsys/dwc2/dwc2_common.h
index c74ad2233..ac97ab3d5 100644
--- a/src/portable/synopsys/dwc2/dwc2_common.h
+++ b/src/portable/synopsys/dwc2/dwc2_common.h
@@ -42,6 +42,7 @@
// - _dwc2_controller[]: array of controllers
// - DWC2_EP_MAX: largest EP counts of all controllers
// - dwc2_phy_init/dwc2_phy_update: phy init called before and after core reset
+// - dwc2_phy_deinit: phy deinit to disable PHY power
// - dwc2_dcd_int_enable/dwc2_dcd_int_disable
// - dwc2_remote_wakeup_delay
diff --git a/src/portable/synopsys/dwc2/dwc2_efm32.h b/src/portable/synopsys/dwc2/dwc2_efm32.h
index 0e3570cbb..f808b567c 100644
--- a/src/portable/synopsys/dwc2/dwc2_efm32.h
+++ b/src/portable/synopsys/dwc2/dwc2_efm32.h
@@ -72,6 +72,14 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
USB->ROUTE = USB_ROUTE_PHYPEN;
}
+// MCU specific PHY deinit, disable PHY power
+static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2) {
+ (void) dwc2;
+
+ // Disable PHY pin
+ USB->ROUTE = 0;
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
static inline void dwc2_phy_update(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 a4e0d1770..f4fa0bf8b 100644
--- a/src/portable/synopsys/dwc2/dwc2_esp32.h
+++ b/src/portable/synopsys/dwc2/dwc2_esp32.h
@@ -121,6 +121,12 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_
}
+// MCU specific PHY deinit, disable PHY power
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2) {
+ (void)dwc2;
+ // PHY managed by ESP-IDF
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
(void)dwc2;
diff --git a/src/portable/synopsys/dwc2/dwc2_gd32.h b/src/portable/synopsys/dwc2/dwc2_gd32.h
index 0375fffe4..26b924161 100644
--- a/src/portable/synopsys/dwc2/dwc2_gd32.h
+++ b/src/portable/synopsys/dwc2/dwc2_gd32.h
@@ -85,6 +85,12 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
// nothing to do
}
+// MCU specific PHY deinit, disable PHY power
+static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2) {
+ (void) dwc2;
+ // nothing to do
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
{
diff --git a/src/portable/synopsys/dwc2/dwc2_nrf.h b/src/portable/synopsys/dwc2/dwc2_nrf.h
index b93571f16..17d21518b 100644
--- a/src/portable/synopsys/dwc2/dwc2_nrf.h
+++ b/src/portable/synopsys/dwc2/dwc2_nrf.h
@@ -52,6 +52,11 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_
(void)hs_phy_type;
}
+// MCU specific PHY deinit, disable PHY power
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2) {
+ (void)dwc2;
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
(void)dwc2;
diff --git a/src/portable/synopsys/dwc2/dwc2_stm32.h b/src/portable/synopsys/dwc2/dwc2_stm32.h
index 753917a20..cc972e957 100644
--- a/src/portable/synopsys/dwc2/dwc2_stm32.h
+++ b/src/portable/synopsys/dwc2/dwc2_stm32.h
@@ -264,6 +264,20 @@ static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
}
}
+// MCU specific PHY deinit, disable PHY power
+static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2) {
+ // Disable on-chip FS PHY
+ dwc2->stm32_gccfg &= ~STM32_GCCFG_PWRDWN;
+
+ // Disable HS PHY if present
+ #ifdef USB_HS_PHYC
+ dwc2->stm32_gccfg &= ~STM32_GCCFG_PHYHSEN;
+ // Disable PLL and LDO
+ USB_HS_PHYC->USB_HS_PHYC_PLL &= ~USB_HS_PHYC_PLL_PLLEN;
+ USB_HS_PHYC->USB_HS_PHYC_LDO &= ~USB_HS_PHYC_LDO_ENABLE;
+ #endif
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
// used to set turnaround time for fullspeed, nothing to do in highspeed mode
diff --git a/src/portable/synopsys/dwc2/dwc2_xmc.h b/src/portable/synopsys/dwc2/dwc2_xmc.h
index 63419abf7..e38935e9c 100644
--- a/src/portable/synopsys/dwc2/dwc2_xmc.h
+++ b/src/portable/synopsys/dwc2/dwc2_xmc.h
@@ -71,6 +71,13 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
//USB->ROUTE = USB_ROUTE_PHYPEN;
}
+// MCU specific PHY deinit, disable PHY power
+static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2) {
+ (void) dwc2;
+
+ // nothing to do
+}
+
// MCU specific PHY update, it is called AFTER init() and core reset
static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type)
{
diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c
index 420b3fe4b..e12e44a41 100644
--- a/src/portable/synopsys/dwc2/hcd_dwc2.c
+++ b/src/portable/synopsys/dwc2/hcd_dwc2.c
@@ -448,8 +448,9 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
bool hcd_deinit(uint8_t rhport) {
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
- // Disable global interrupt
- dwc2->gahbcfg &= ~GAHBCFG_GINT;
+ // Turn off VBUS
+ dwc2->hprt = HPRT_W1_MASK; // clear w1c bits without side effects
+ // HPRT_POWER is not set -> VBUS off
dwc2_core_deinit(rhport);
return true;
@@ -1359,6 +1360,17 @@ static bool handle_sof_irq(uint8_t rhport, bool in_isr) {
}
// Config HCFG FS/LS clock and HFIR for SOF interval according to link speed (value is in PHY clock unit)
+// Databook Table 2-2: System Clock Speeds
+// +-----------+------------------+----------+-----------+-------------------+
+// | PHY | PHY Clock (MHz) | Width | HCFG.Sel | HFIR (clk cycles) |
+// +-----------+------------------+----------+-----------+-------------------+
+// | HS UTMI+ | 30 | 16-bit | 30_60 | HS:3749 FS:29999 |
+// | HS UTMI+ | 60 | 8-bit | 30_60 | HS:7499 FS:59999 |
+// | HS ULPI | 60 | 8-bit | 30_60 | HS:7499 FS:59999 |
+// | FS (dead.) | 48 | internal | 48 | FS:47999 |
+// | LS via FS | 48 (6 effective) | internal | 6 | LS:47999 |
+// +-----------+------------------+----------+-----------+-------------------+
+// HFIR = (interval_us * phy_clock) - 1, where interval is 125us (HS) or 1000us (FS/LS)
static void port0_enable(dwc2_regs_t* dwc2, tusb_speed_t speed) {
uint32_t hcfg = dwc2->hcfg & ~HCFG_FSLS_PHYCLK_SEL;