Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions src/portable/synopsys/dwc2/dcd_dwc2.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
//--------------------------------------------------------------------
Expand Down Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}

Expand Down Expand Up @@ -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
4 changes: 4 additions & 0 deletions src/portable/synopsys/dwc2/dwc2_at32.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions src/portable/synopsys/dwc2/dwc2_bcm.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
15 changes: 15 additions & 0 deletions src/portable/synopsys/dwc2/dwc2_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 6 additions & 0 deletions src/portable/synopsys/dwc2/dwc2_efm32.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
9 changes: 8 additions & 1 deletion src/portable/synopsys/dwc2/dwc2_esp32.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/portable/synopsys/dwc2/dwc2_gd32.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
6 changes: 6 additions & 0 deletions src/portable/synopsys/dwc2/dwc2_stm32.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/portable/synopsys/dwc2/dwc2_xmc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Loading