Merge branch 'feat/esp32s31_usb_support' into 'master'

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

See merge request espressif/esp-idf!46329
This commit is contained in:
Igor Masar
2026-04-09 01:44:30 +08:00
30 changed files with 3097 additions and 69 deletions
@@ -1,4 +1,4 @@
| Supported Targets | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- | -------- | -------- |
| Supported Targets | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
| ----------------- | -------- | -------- | -------- | -------- | --------- |
# USB: PHY sanity checks
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
@@ -8,12 +8,12 @@
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"
#include "esp_private/usb_phy.h"
#include "hal/usb_wrap_ll.h" // For USB_WRAP_LL_EXT_PHY_SUPPORTED symbol
#include "soc/soc_caps.h" // For SOC_USB_UTMI_PHY_NUM symbol
#include "soc/soc_caps.h"
#include "sdkconfig.h" // For CONFIG_IDF_TARGET_***
#if USB_WRAP_LL_EXT_PHY_SUPPORTED
#define EXT_PHY_SUPPORTED 1
#if (SOC_USB_FSLS_PHY_NUM > 0)
#include "hal/usb_wrap_ll.h"
#define EXT_PHY_SUPPORTED USB_WRAP_LL_EXT_PHY_SUPPORTED
#else
#define EXT_PHY_SUPPORTED 0
#endif
@@ -24,6 +24,18 @@
#define UTMI_PHY_SUPPORTED 0
#endif
#if CONFIG_IDF_TARGET_ESP32S31
#define INT_PHY_ALIASES_UTMI 1
#else
#define INT_PHY_ALIASES_UTMI 0
#endif
#if (SOC_USB_FSLS_PHY_NUM > 0) || INT_PHY_ALIASES_UTMI
#define INT_PHY_SUPPORTED 1
#else
#define INT_PHY_SUPPORTED 0
#endif
void setUp(void)
{
unity_utils_record_free_mem();
@@ -55,12 +67,15 @@ void app_main(void)
}
/**
* Test init and deinit of internal FSLS PHY
* Test init and deinit of the internal PHY target
*
* On UTMI-only targets, the internal PHY target can be mapped to UTMI for
* backward compatibility.
*
* 1. Init + deinit in Host mode
* 2. Init + deinit in Device mode
*/
TEST_CASE("Init internal FSLS PHY", "[phy]")
TEST_CASE("Init internal PHY target", "[phy]")
{
// Host mode
usb_phy_handle_t phy_handle = NULL;
@@ -72,9 +87,14 @@ TEST_CASE("Init internal FSLS PHY", "[phy]")
.ext_io_conf = NULL,
.otg_io_conf = NULL,
};
#if INT_PHY_SUPPORTED
TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_handle));
TEST_ASSERT_NOT_NULL(phy_handle);
TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_handle));
#else
TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_handle));
TEST_ASSERT_NULL(phy_handle);
#endif
// Device mode
usb_phy_handle_t phy_handle_2 = NULL;
@@ -86,9 +106,14 @@ TEST_CASE("Init internal FSLS PHY", "[phy]")
.ext_io_conf = NULL,
.otg_io_conf = NULL,
};
#if INT_PHY_SUPPORTED
TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(&phy_config_2, &phy_handle_2));
TEST_ASSERT_NOT_NULL(phy_handle_2);
TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_handle_2));
#else
TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_new_phy(&phy_config_2, &phy_handle_2));
TEST_ASSERT_NULL(phy_handle_2);
#endif
}
/**
@@ -156,10 +181,14 @@ TEST_CASE("Init internal UTMI PHY", "[phy]")
}
/**
* Test init and deinit of all PHYs at the same time multiple times
* Test init and deinit of all available PHY targets multiple times
*/
TEST_CASE("Init all PHYs in a loop", "[phy]")
{
#if !INT_PHY_SUPPORTED
TEST_IGNORE_MESSAGE("Internal PHY target is not supported on this target");
#endif
for (int i = 0; i < 2; i++) {
usb_phy_handle_t phy_handle = NULL;
usb_phy_handle_t phy_handle_2 = NULL;
@@ -174,11 +203,14 @@ TEST_CASE("Init all PHYs in a loop", "[phy]")
TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_handle));
TEST_ASSERT_NOT_NULL(phy_handle);
// Our current targets support either UTMI or external PHY
// so if/else suffice here
#if UTMI_PHY_SUPPORTED
// UTMI-only targets can alias the internal PHY target to UTMI, in
// which case a second UTMI allocation must fail because it is the same
// physical PHY instance.
#if UTMI_PHY_SUPPORTED && !INT_PHY_ALIASES_UTMI
phy_config.target = USB_PHY_TARGET_UTMI;
#else
TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_handle_2));
TEST_ASSERT_NOT_NULL(phy_handle_2);
#elif EXT_PHY_SUPPORTED
phy_config.target = USB_PHY_TARGET_EXT;
const usb_phy_ext_io_conf_t ext_io_conf = { // Some random values
.vp_io_num = 1,
@@ -191,12 +223,18 @@ TEST_CASE("Init all PHYs in a loop", "[phy]")
.fs_edge_sel_io_num = 1,
};
phy_config.ext_io_conf = &ext_io_conf;
#endif
TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_handle_2));
TEST_ASSERT_NOT_NULL(phy_handle_2);
#elif INT_PHY_ALIASES_UTMI
phy_config.target = USB_PHY_TARGET_UTMI;
TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_handle_2));
TEST_ASSERT_NULL(phy_handle_2);
#endif
TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_handle));
TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_handle_2));
if (phy_handle_2) {
TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_handle_2));
}
}
}
+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;
}