/* * SPDX-FileCopyrightText: Copyright (c) 2021, Ha Thach (tinyusb.org) * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ #ifndef TUSB_DWC2_ESP32_H_ #define TUSB_DWC2_ESP32_H_ #ifdef __cplusplus extern "C" { #endif #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_intr_alloc.h" #include "soc/periph_defs.h" // ESP32-S31 does not have USB_WRAP peripheral (HS-only with UTMI PHY) #if !TU_CHECK_MCU(OPT_MCU_ESP32S31) #include "soc/usb_wrap_struct.h" #endif #if TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3) #define DWC2_FS_REG_BASE 0x60080000UL #define DWC2_EP_MAX 7 static const dwc2_controller_t _dwc2_controller[] = { { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .otg_dfifo_depth = 256 } }; #elif TU_CHECK_MCU(OPT_MCU_ESP32H4) // H4's USB_WRAP register block uses "wrap_*" field names. Map them to the // names used by TinyUSB's DWC2 port to keep the source unchanged. #define otg_conf wrap_otg_conf #define pad_pull_override wrap_pad_pull_override #define dp_pullup wrap_dp_pullup #define dp_pulldown wrap_dp_pulldown #define dm_pullup wrap_dm_pullup #define dm_pulldown wrap_dm_pulldown #define DWC2_FS_REG_BASE 0x60040000UL #define DWC2_EP_MAX 7 static const dwc2_controller_t _dwc2_controller[] = { { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_OTG11_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .otg_dfifo_depth = 256 } }; #elif TU_CHECK_MCU(OPT_MCU_ESP32P4) #define DWC2_FS_REG_BASE 0x50040000UL #define DWC2_HS_REG_BASE 0x50000000UL #define DWC2_EP_MAX 16 // On ESP32 for consistency we associate // - Port0 to OTG_FS, and Port1 to OTG_HS static const dwc2_controller_t _dwc2_controller[] = { { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_OTG11_CH0_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .otg_dfifo_depth = 256 }, { .reg_base = DWC2_HS_REG_BASE, .irqnum = ETS_USB_OTG_INTR_SOURCE, .ep_count = 16, .ep_in_count = 8, .otg_dfifo_depth = 1024 } }; #elif TU_CHECK_MCU(OPT_MCU_ESP32S31) #define DWC2_HS_REG_BASE 0x20300000UL #define DWC2_EP_MAX 16 static const dwc2_controller_t _dwc2_controller[] = { { .reg_base = DWC2_HS_REG_BASE, .irqnum = ETS_USB_OTGHS_INTR_SOURCE, .ep_count = 16, .ep_in_count = 8, .otg_dfifo_depth = 1024 } }; #endif //--------------------------------------------------------------------+ // //--------------------------------------------------------------------+ static intr_handle_t usb_ih[TU_ARRAY_SIZE(_dwc2_controller)]; static void dwc2_int_handler_wrap(void* arg) { const uint8_t rhport = tu_u16_low((uint16_t)(uintptr_t)arg); const tusb_role_t role = (tusb_role_t) tu_u16_high((uint16_t)(uintptr_t)arg); #if CFG_TUD_ENABLED if (role == TUSB_ROLE_DEVICE) { dcd_int_handler(rhport); } #endif #if CFG_TUH_ENABLED && !CFG_TUH_MAX3421 if (role == TUSB_ROLE_HOST) { hcd_int_handler(rhport, true); } #endif } // MCU specific to enable dwc2 clock/power before any access to register TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) { (void) rhport; (void) role; } TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) { if (enabled) { esp_intr_alloc(_dwc2_controller[rhport].irqnum, ESP_INTR_FLAG_LOWMED, dwc2_int_handler_wrap, (void*)(uintptr_t)tu_u16(role, rhport), &usb_ih[rhport]); } else { esp_intr_free(usb_ih[rhport]); } } #define dwc2_dcd_int_enable(_rhport) dwc2_int_set(_rhport, TUSB_ROLE_DEVICE, true) #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)); } // 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; (void)hs_phy_type; // maybe usb_utmi_hal_init() } // MCU specific PHY deinit, disable PHY power TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { (void)dwc2; (void)hs_phy_type; // 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; (void)hs_phy_type; // maybe usb_utmi_hal_disable() } //--------------------------------------------------------------------+ // Data Cache //--------------------------------------------------------------------+ #if CFG_TUD_DWC2_DMA_ENABLE || CFG_TUH_DWC2_DMA_ENABLE #if defined(SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE) && SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE #include "esp_cache.h" #if CFG_TUD_MEM_DCACHE_LINE_SIZE != CONFIG_CACHE_L1_CACHE_LINE_SIZE || \ CFG_TUH_MEM_DCACHE_LINE_SIZE != CONFIG_CACHE_L1_CACHE_LINE_SIZE #error "CFG_TUD/TUH_MEM_DCACHE_LINE_SIZE must match CONFIG_CACHE_L1_CACHE_LINE_SIZE" #endif TU_ATTR_ALWAYS_INLINE static inline uint32_t round_up_to_cache_line_size(uint32_t size) { if (size & (CONFIG_CACHE_L1_CACHE_LINE_SIZE-1)) { size = (size & ~(CONFIG_CACHE_L1_CACHE_LINE_SIZE-1)) + CONFIG_CACHE_L1_CACHE_LINE_SIZE; } return size; } TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_clean(const void* addr, uint32_t data_size) { const int flag = ESP_CACHE_MSYNC_FLAG_TYPE_DATA | ESP_CACHE_MSYNC_FLAG_DIR_C2M; data_size = round_up_to_cache_line_size(data_size); return ESP_OK == esp_cache_msync((void*)addr, data_size, flag); } TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_invalidate(const void* addr, uint32_t data_size) { const int flag = ESP_CACHE_MSYNC_FLAG_TYPE_DATA | ESP_CACHE_MSYNC_FLAG_DIR_M2C; data_size = round_up_to_cache_line_size(data_size); return ESP_OK == esp_cache_msync((void*)addr, data_size, flag); } TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_clean_invalidate(const void* addr, uint32_t data_size) { const int flag = ESP_CACHE_MSYNC_FLAG_TYPE_DATA | ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_DIR_M2C; data_size = round_up_to_cache_line_size(data_size); return ESP_OK == esp_cache_msync((void*)addr, data_size, flag); } #endif #endif #ifdef __cplusplus } #endif #endif