feat(usb): add ESP32-S31 DWC/UTMI support

Add ESP32-S31 USB DWC/UTMI LL headers, SoC register structures, peripheral
descriptors, capabilities, and linker mappings so the HS OTG controller and
UTMI PHY can be built in esp_hal_usb.

Introduce SOC_USB_FSLS_PHY_NUM on USB-OTG targets to separate FSLS USB_WRAP
support from OTG/UTMI support. Use it to gate usb_wrap, the USB PHY driver,
docs, and example build rules on targets without an FSLS PHY.

Also add UTMI data pulldown control to the HAL, clear the boot-time DWC
suspend state on ESP32-S31, alias the legacy internal PHY target to UTMI for
backward compatibility, and extend usb_phy tests for UTMI-only targets.
This commit is contained in:
igor.masar
2026-04-08 16:01:28 +08:00
committed by BOT
parent edb0e8dba9
commit 9ab7d5eb03
30 changed files with 3097 additions and 69 deletions
+49 -23
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -25,6 +25,12 @@
#include "esp_sleep.h"
#endif
#if (SOC_USB_FSLS_PHY_NUM > 0)
#define USB_PHY_FSLS_EXT_PHY_SUPPORTED USB_WRAP_LL_EXT_PHY_SUPPORTED
#else
#define USB_PHY_FSLS_EXT_PHY_SUPPORTED 0
#endif
static const char *USBPHY_TAG = "usb_phy";
#define USBPHY_NOT_INIT_ERR_STR "USB_PHY is not initialized"
@@ -37,7 +43,9 @@ struct phy_context_t {
usb_phy_status_t status; /**< PHY status */
usb_otg_mode_t otg_mode; /**< USB OTG mode */
usb_phy_ext_io_conf_t *iopins; /**< external PHY I/O pins */
#if (SOC_USB_FSLS_PHY_NUM > 0)
usb_wrap_hal_context_t wrap_hal; /**< USB WRAP HAL context */
#endif
};
typedef struct {
@@ -138,24 +146,15 @@ esp_err_t usb_phy_otg_set_mode(usb_phy_handle_t handle, usb_otg_mode_t mode)
// we support only fixed PHY to USB-DWC mapping:
// USB-DWC2.0 <-> UTMI PHY
// USB-DWC1.1 <-> FSLS PHY
#if (SOC_USB_UTMI_PHY_NUM > 0)
if (handle->target == USB_PHY_TARGET_UTMI) {
// ESP32-P4 v3 changed connection between USB-OTG peripheral and UTMI PHY.
// On v3 the 15k pulldown resistors on D+/D- are no longer controlled by USB-OTG,
// but must be controlled directly by this software driver.
#if CONFIG_IDF_TARGET_ESP32P4 && !CONFIG_ESP32P4_SELECTS_REV_LESS_V3
#include "soc/lp_system_struct.h"
if (mode == USB_OTG_MODE_HOST) {
// Host must connect 15k pulldown resistors on D+ / D-
LP_SYS.hp_usb_otghs_phy_ctrl.hp_utmiotg_dppulldown = 1;
LP_SYS.hp_usb_otghs_phy_ctrl.hp_utmiotg_dmpulldown = 1;
} else {
// Device must not connect any pulldown resistors on D+ / D-
LP_SYS.hp_usb_otghs_phy_ctrl.hp_utmiotg_dppulldown = 0;
LP_SYS.hp_usb_otghs_phy_ctrl.hp_utmiotg_dmpulldown = 0;
}
#endif // !CONFIG_ESP32P4_SELECTS_REV_LESS_V3
// On some targets, the 15k pulldown resistors on D+/D- are not controlled
// by the USB-OTG peripheral, but must be controlled by software.
// Host mode: connect pulldowns; Device mode: disconnect pulldowns.
usb_utmi_hal_enable_data_pulldowns(mode == USB_OTG_MODE_HOST);
return ESP_OK;
}
#endif
const usb_otg_signal_conn_t *otg_sig = usb_dwc_info.controllers[otg11_index].otg_signals;
assert(otg_sig);
@@ -164,6 +163,7 @@ esp_err_t usb_phy_otg_set_mode(usb_phy_handle_t handle, usb_otg_mode_t mode)
gpio_ll_set_input_signal_matrix_source(GPIO_LL_GET_HW(0), otg_sig->bvalid, GPIO_MATRIX_CONST_ZERO_INPUT, false);
gpio_ll_set_input_signal_matrix_source(GPIO_LL_GET_HW(0), otg_sig->vbusvalid, GPIO_MATRIX_CONST_ONE_INPUT, false); // receiving a valid Vbus from host
gpio_ll_set_input_signal_matrix_source(GPIO_LL_GET_HW(0), otg_sig->avalid, GPIO_MATRIX_CONST_ONE_INPUT, false); // HIGH to force USB host mode
#if (SOC_USB_FSLS_PHY_NUM > 0)
if (handle->target == USB_PHY_TARGET_INT) {
// Configure pull resistors for host
usb_wrap_pull_override_vals_t vals = {
@@ -174,6 +174,7 @@ esp_err_t usb_phy_otg_set_mode(usb_phy_handle_t handle, usb_otg_mode_t mode)
};
usb_wrap_hal_phy_enable_pull_override(&handle->wrap_hal, &vals);
}
#endif
} else if (mode == USB_OTG_MODE_DEVICE) {
gpio_ll_set_input_signal_matrix_source(GPIO_LL_GET_HW(0), otg_sig->iddig, GPIO_MATRIX_CONST_ONE_INPUT, false); // connected connector is mini-B side
gpio_ll_set_input_signal_matrix_source(GPIO_LL_GET_HW(0), otg_sig->bvalid, GPIO_MATRIX_CONST_ONE_INPUT, false); // HIGH to force USB device mode
@@ -233,6 +234,18 @@ esp_err_t usb_new_phy(const usb_phy_config_t *config, usb_phy_handle_t *handle_r
}
#endif
#if CONFIG_IDF_TARGET_ESP32S31
/*
* ESP32-S31 exposes only the UTMI PHY to the USB OTG controller.
* Keep backward compatibility with applications that still request
* the legacy internal PHY target by aliasing it to UTMI.
*/
if (config->controller == USB_PHY_CTRL_OTG && phy_target == USB_PHY_TARGET_INT) {
ESP_LOGW(USBPHY_TAG, "Using UTMI PHY instead of requested internal PHY");
phy_target = USB_PHY_TARGET_UTMI;
}
#endif
#if SOC_USB_UTMI_PHY_NO_POWER_OFF_ISO
if (phy_target == USB_PHY_TARGET_UTMI) {
esp_deep_sleep_register_hook(&sleep_usb_suppress_deepsleep_leakage);
@@ -243,7 +256,10 @@ esp_err_t usb_new_phy(const usb_phy_config_t *config, usb_phy_handle_t *handle_r
ESP_RETURN_ON_FALSE(phy_target < USB_PHY_TARGET_MAX, ESP_ERR_INVALID_ARG, USBPHY_TAG, "specified PHY argument is invalid");
ESP_RETURN_ON_FALSE(config->controller < USB_PHY_CTRL_MAX, ESP_ERR_INVALID_ARG, USBPHY_TAG, "specified source argument is invalid");
ESP_RETURN_ON_FALSE(phy_target != USB_PHY_TARGET_EXT || config->ext_io_conf, ESP_ERR_INVALID_ARG, USBPHY_TAG, "ext_io_conf must be provided for ext PHY");
#if !USB_WRAP_LL_EXT_PHY_SUPPORTED
#if !SOC_USB_FSLS_PHY_NUM
ESP_RETURN_ON_FALSE(phy_target != USB_PHY_TARGET_INT, ESP_ERR_NOT_SUPPORTED, USBPHY_TAG, "Internal FSLS PHY not supported on this target");
ESP_RETURN_ON_FALSE(phy_target != USB_PHY_TARGET_EXT, ESP_ERR_NOT_SUPPORTED, USBPHY_TAG, "Ext PHY not supported on this target");
#elif !USB_PHY_FSLS_EXT_PHY_SUPPORTED
ESP_RETURN_ON_FALSE(phy_target != USB_PHY_TARGET_EXT, ESP_ERR_NOT_SUPPORTED, USBPHY_TAG, "Ext PHY not supported on this target");
#endif
#if !SOC_USB_UTMI_PHY_NUM
@@ -276,20 +292,26 @@ esp_err_t usb_new_phy(const usb_phy_config_t *config, usb_phy_handle_t *handle_r
phy_context->controller = config->controller;
phy_context->status = USB_PHY_STATUS_IN_USE;
#if (SOC_USB_FSLS_PHY_NUM > 0)
if (phy_target != USB_PHY_TARGET_UTMI) {
PERIPH_RCC_ATOMIC() {
usb_wrap_hal_init(&phy_context->wrap_hal);
}
} else {
#if (SOC_USB_UTMI_PHY_NUM > 0)
usb_utmi_hal_context_t utmi_hal_context; // Unused for now
PERIPH_RCC_ATOMIC() {
usb_utmi_hal_init(&utmi_hal_context);
}
#endif
if (phy_target == USB_PHY_TARGET_UTMI) {
#if (SOC_USB_UTMI_PHY_NUM > 0)
usb_utmi_hal_context_t utmi_hal_context; // Unused for now
PERIPH_RCC_ATOMIC() {
usb_utmi_hal_init(&utmi_hal_context);
}
#endif
}
#if (SOC_USB_FSLS_PHY_NUM > 0)
}
#endif
if (config->controller == USB_PHY_CTRL_OTG) {
#if USB_WRAP_LL_EXT_PHY_SUPPORTED
#if USB_PHY_FSLS_EXT_PHY_SUPPORTED
usb_wrap_hal_phy_set_external(&phy_context->wrap_hal, (phy_target == USB_PHY_TARGET_EXT));
#endif
}
@@ -345,7 +367,9 @@ static void phy_uninstall(void)
p_phy_ctrl_obj = NULL;
PERIPH_RCC_ATOMIC() {
// Disable USB peripheral without reset the module
#if (SOC_USB_FSLS_PHY_NUM > 0)
usb_wrap_hal_disable();
#endif
#if (SOC_USB_UTMI_PHY_NUM > 0)
usb_utmi_hal_disable();
#endif
@@ -363,10 +387,12 @@ esp_err_t usb_del_phy(usb_phy_handle_t handle)
p_phy_ctrl_obj->ref_count--;
if (handle->target == USB_PHY_TARGET_EXT) {
p_phy_ctrl_obj->external_phy = NULL;
#if (SOC_USB_FSLS_PHY_NUM > 0)
} else if (handle->target == USB_PHY_TARGET_INT) {
// Clear pullup and pulldown loads on D+ / D-, and disable the pads
usb_wrap_hal_phy_disable_pull_override(&handle->wrap_hal);
p_phy_ctrl_obj->fsls_phy = NULL;
#endif
} else { // USB_PHY_TARGET_UTMI
p_phy_ctrl_obj->utmi_phy = NULL;
}