Merge branch 'refactor/cleanup_usb_phy' into 'master'

Cleanup USB PHY

See merge request espressif/esp-idf!35783
This commit is contained in:
Tomas Rezucha 2025-01-08 16:11:54 +08:00
commit b92e993d56
20 changed files with 395 additions and 120 deletions

View File

@ -238,7 +238,7 @@ FORCE_INLINE_ATTR void usb_wrap_ll_phy_test_mode_set_signals(usb_wrap_dev_t *hw,
* Enable the bus clock for USB Wrap module and USB_DWC_FS controller
* @param clk_en True if enable the clock of USB Wrap module
*/
FORCE_INLINE_ATTR void usb_wrap_ll_enable_bus_clock(bool clk_en)
FORCE_INLINE_ATTR void _usb_wrap_ll_enable_bus_clock(bool clk_en)
{
// Enable/disable system clock for USB_WRAP and USB_DWC_FS
HP_SYS_CLKRST.soc_clk_ctrl1.reg_usb_otg11_sys_clk_en = clk_en;
@ -247,12 +247,12 @@ FORCE_INLINE_ATTR void usb_wrap_ll_enable_bus_clock(bool clk_en)
}
// HP_SYS_CLKRST.soc_clk_ctrlx and LP_AON_CLKRST.hp_usb_clkrst_ctrlx are shared registers, so this function must be used in an atomic way
#define usb_wrap_ll_enable_bus_clock(...) (void)__DECLARE_RCC_ATOMIC_ENV; usb_wrap_ll_enable_bus_clock(__VA_ARGS__)
#define usb_wrap_ll_enable_bus_clock(...) (void)__DECLARE_RCC_ATOMIC_ENV; _usb_wrap_ll_enable_bus_clock(__VA_ARGS__)
/**
* @brief Reset the USB Wrap module and USB_DWC_FS controller
*/
FORCE_INLINE_ATTR void usb_wrap_ll_reset_register(void)
FORCE_INLINE_ATTR void _usb_wrap_ll_reset_register(void)
{
// Reset the USB_WRAP and USB_DWC_FS
LP_AON_CLKRST.hp_usb_clkrst_ctrl1.rst_en_usb_otg11 = 1;
@ -260,7 +260,7 @@ FORCE_INLINE_ATTR void usb_wrap_ll_reset_register(void)
}
// P_AON_CLKRST.hp_usb_clkrst_ctrlx are shared registers, so this function must be used in an atomic way
#define usb_wrap_ll_reset_register(...) (void)__DECLARE_RCC_ATOMIC_ENV; usb_wrap_ll_reset_register(__VA_ARGS__)
#define usb_wrap_ll_reset_register(...) (void)__DECLARE_RCC_ATOMIC_ENV; _usb_wrap_ll_reset_register(__VA_ARGS__)
#ifdef __cplusplus
}

View File

@ -207,25 +207,25 @@ FORCE_INLINE_ATTR void usb_wrap_ll_phy_test_mode_set_signals(usb_wrap_dev_t *hw,
* Enable the bus clock for USB Wrap module
* @param clk_en True if enable the clock of USB Wrap module
*/
FORCE_INLINE_ATTR void usb_wrap_ll_enable_bus_clock(bool clk_en)
FORCE_INLINE_ATTR void _usb_wrap_ll_enable_bus_clock(bool clk_en)
{
REG_SET_FIELD(DPORT_PERIP_CLK_EN0_REG, DPORT_USB_CLK_EN, clk_en);
}
// SYSTEM.perip_clk_enx are shared registers, so this function must be used in an atomic way
#define usb_wrap_ll_enable_bus_clock(...) (void)__DECLARE_RCC_ATOMIC_ENV; usb_wrap_ll_enable_bus_clock(__VA_ARGS__)
#define usb_wrap_ll_enable_bus_clock(...) (void)__DECLARE_RCC_ATOMIC_ENV; _usb_wrap_ll_enable_bus_clock(__VA_ARGS__)
/**
* @brief Reset the USB Wrap module
*/
FORCE_INLINE_ATTR void usb_wrap_ll_reset_register(void)
FORCE_INLINE_ATTR void _usb_wrap_ll_reset_register(void)
{
REG_SET_FIELD(DPORT_PERIP_RST_EN0_REG, DPORT_USB_RST, 1);
REG_SET_FIELD(DPORT_PERIP_RST_EN0_REG, DPORT_USB_RST, 0);
}
// SYSTEM.perip_rst_enx are shared registers, so this function must be used in an atomic way
#define usb_wrap_ll_reset_register(...) (void)__DECLARE_RCC_ATOMIC_ENV; usb_wrap_ll_reset_register(__VA_ARGS__)
#define usb_wrap_ll_reset_register(...) (void)__DECLARE_RCC_ATOMIC_ENV; _usb_wrap_ll_reset_register(__VA_ARGS__)
#ifdef __cplusplus
}

View File

@ -216,25 +216,25 @@ FORCE_INLINE_ATTR void usb_wrap_ll_phy_test_mode_set_signals(usb_wrap_dev_t *hw,
* Enable the bus clock for USB Wrap module
* @param clk_en True if enable the clock of USB Wrap module
*/
FORCE_INLINE_ATTR void usb_wrap_ll_enable_bus_clock(bool clk_en)
FORCE_INLINE_ATTR void _usb_wrap_ll_enable_bus_clock(bool clk_en)
{
SYSTEM.perip_clk_en0.usb_clk_en = clk_en;
}
// SYSTEM.perip_clk_enx are shared registers, so this function must be used in an atomic way
#define usb_wrap_ll_enable_bus_clock(...) (void)__DECLARE_RCC_ATOMIC_ENV; usb_wrap_ll_enable_bus_clock(__VA_ARGS__)
#define usb_wrap_ll_enable_bus_clock(...) (void)__DECLARE_RCC_ATOMIC_ENV; _usb_wrap_ll_enable_bus_clock(__VA_ARGS__)
/**
* @brief Reset the USB Wrap module
*/
FORCE_INLINE_ATTR void usb_wrap_ll_reset_register(void)
FORCE_INLINE_ATTR void _usb_wrap_ll_reset_register(void)
{
SYSTEM.perip_rst_en0.usb_rst = 1;
SYSTEM.perip_rst_en0.usb_rst = 0;
}
// SYSTEM.perip_rst_enx are shared registers, so this function must be used in an atomic way
#define usb_wrap_ll_reset_register(...) (void)__DECLARE_RCC_ATOMIC_ENV; usb_wrap_ll_reset_register(__VA_ARGS__)
#define usb_wrap_ll_reset_register(...) (void)__DECLARE_RCC_ATOMIC_ENV; _usb_wrap_ll_reset_register(__VA_ARGS__)
#ifdef __cplusplus
}

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -22,7 +22,8 @@ extern "C"
* @brief USB PHY target
*/
typedef enum {
USB_PHY_TARGET_INT, /**< USB target is internal PHY */
USB_PHY_TARGET_INT, /**< USB target is internal FSLS PHY */
USB_PHY_TARGET_UTMI, /**< USB target is internal UTMI PHY */
USB_PHY_TARGET_EXT, /**< USB target is external PHY */
USB_PHY_TARGET_MAX,
} usb_phy_target_t;

View File

@ -32,7 +32,30 @@ typedef struct {
*
* @param hal USB WRAP HAL context
*/
void usb_wrap_hal_init(usb_wrap_hal_context_t *hal);
void _usb_wrap_hal_init(usb_wrap_hal_context_t *hal);
#if SOC_RCC_IS_INDEPENDENT
#define usb_wrap_hal_init(...) _usb_wrap_hal_init(__VA_ARGS__)
#else
// Use a macro to wrap the function, force the caller to use it in a critical section
// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define usb_wrap_hal_init(...) do {(void)__DECLARE_RCC_ATOMIC_ENV; _usb_wrap_hal_init(__VA_ARGS__);} while(0)
#endif
/**
* @brief Disable USB WRAP
*
* Disable clock to the peripheral
*/
void _usb_wrap_hal_disable(void);
#if SOC_RCC_IS_INDEPENDENT
#define usb_wrap_hal_disable(...) _usb_wrap_hal_disable(__VA_ARGS__)
#else
// Use a macro to wrap the function, force the caller to use it in a critical section
// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define usb_wrap_hal_disable(...) do {(void)__DECLARE_RCC_ATOMIC_ENV; _usb_wrap_hal_disable(__VA_ARGS__);} while(0)
#endif
/* ---------------------------- USB PHY Control ---------------------------- */

View File

@ -8,14 +8,21 @@
#include "hal/usb_wrap_ll.h"
#include "hal/usb_wrap_hal.h"
void usb_wrap_hal_init(usb_wrap_hal_context_t *hal)
void _usb_wrap_hal_init(usb_wrap_hal_context_t *hal)
{
hal->dev = &USB_WRAP;
_usb_wrap_ll_enable_bus_clock(true);
_usb_wrap_ll_reset_register();
#if !USB_WRAP_LL_EXT_PHY_SUPPORTED
usb_wrap_ll_phy_set_defaults(hal->dev);
#endif
}
void _usb_wrap_hal_disable(void)
{
_usb_wrap_ll_enable_bus_clock(false);
}
#if USB_WRAP_LL_EXT_PHY_SUPPORTED
void usb_wrap_hal_phy_set_external(usb_wrap_hal_context_t *hal, bool external)
{

View File

@ -25,7 +25,7 @@ Configuration Set ID: 11
#define OTG20_MULTI_PROC_INTRPT 1
/* 3.2 USB Physical Layer Interface Parameters */
#define OTG20_HSPHY_INTERFACE 3
#define OTG20_HSPHY_INTERFACE 3 // Although we support both UTMI+ and ULPI, the ULPI is not wired out of the USB-DWC. Hence only UTMI+ can be used
#define OTG20_HSPHY_DWIDTH 2
#define OTG20_FSPHY_INTERFACE 2
#define OTG20_ENABLE_IC_USB 0

View File

@ -12,7 +12,7 @@
/* -------------------------------- Private --------------------------------- */
static const usb_utmi_otg_signal_conn_t dwc_fs_otg_signals = {
static const usb_otg_signal_conn_t dwc_fs_otg_signals = {
// Inputs
.iddig = USB_OTG11_IDDIG_PAD_IN_IDX,
.avalid = USB_OTG11_AVALID_PAD_IN_IDX,

View File

@ -24,7 +24,7 @@ static const usb_fsls_serial_signal_conn_t fsls_signals = {
.fs_edge_sel = USB_EXTPHY_SPEED_IDX,
};
static const usb_utmi_otg_signal_conn_t otg_signals = {
static const usb_otg_signal_conn_t otg_signals = {
// Inputs
.iddig = USB_OTG_IDDIG_IN_IDX,
.avalid = USB_OTG_AVALID_IN_IDX,

View File

@ -25,7 +25,7 @@ static const usb_fsls_serial_signal_conn_t fsls_signals = {
.fs_edge_sel = USB_EXTPHY_SPEED_IDX,
};
static const usb_utmi_otg_signal_conn_t otg_signals = {
static const usb_otg_signal_conn_t otg_signals = {
// Inputs
.iddig = USB_OTG_IDDIG_IN_IDX,
.avalid = USB_OTG_AVALID_IN_IDX,

View File

@ -63,7 +63,7 @@ typedef struct {
int drvvbus;
int chrgvbus;
int dischrgvbus;
} usb_utmi_otg_signal_conn_t;
} usb_otg_signal_conn_t;
/**
* @brief USB Controller Information
@ -73,7 +73,7 @@ typedef struct {
typedef struct {
struct {
const usb_fsls_serial_signal_conn_t * const fsls_signals; // Must be set if external PHY is supported by controller
const usb_utmi_otg_signal_conn_t * const otg_signals;
const usb_otg_signal_conn_t * const otg_signals;
const int irq;
const int irq_2nd_cpu; // The USB-DWC can provide 2nd interrupt so each CPU can have its own interrupt line. Set to -1 if not supported
} controllers [SOC_USB_OTG_PERIPH_NUM];

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -54,12 +54,16 @@ typedef enum {
* @brief USB external PHY IO pins configuration structure
*/
typedef struct {
// Inputs
int vp_io_num; /**< GPIO pin to USB_EXTPHY_VP_IDX */
int vm_io_num; /**< GPIO pin to USB_EXTPHY_VM_IDX */
int rcv_io_num; /**< GPIO pin to USB_EXTPHY_RCV_IDX */
// Outputs
int suspend_n_io_num; /**< GPIO pin to USB_EXTPHY_SUSPND_IDX */
int oen_io_num; /**< GPIO pin to USB_EXTPHY_OEN_IDX */
int vpo_io_num; /**< GPIO pin to USB_EXTPHY_VPO_IDX */
int vmo_io_num; /**< GPIO pin to USB_EXTPHY_VMO_IDX */
int fs_edge_sel_io_num; /**< GPIO pin to USB_EXTPHY_SPEED_IDX */
} usb_phy_ext_io_conf_t;
/**
@ -101,14 +105,15 @@ typedef struct phy_context_t *usb_phy_handle_t; /**< USB PHY context handle *
*
* This function will enable the OTG Controller
*
* @param[in] config USB PHY configurtion struct
* @param[in] config USB PHY configuration struct
* @param[out] handle_ret USB PHY context handle
*
* @return
* - ESP_OK Success
* - ESP_FAIL USB PHY init error.
* - ESP_ERR_INVALID_STATE USB PHY not installed.
* - ESP_ERR_NO_MEM USB_OTG installation failed due to no mem.
* - ESP_OK Success
* - ESP_ERR_INVALID_STATE USB PHY already initialized.
* - ESP_ERR_NO_MEM USB_OTG Installation failed due to no mem.
* - ESP_ERR_NOT_SUPPORTED Selected PHY is not supported on this target.
* - ESP_ERR_INVALID_ARG Invalid input argument.
*/
esp_err_t usb_new_phy(const usb_phy_config_t *config, usb_phy_handle_t *handle_ret);

View File

@ -17,3 +17,21 @@ components/usb/test_apps:
- components/soc/include/soc/usb*.h
- components/soc/esp32*/include/soc/usb_dwc_*.h
- components/soc/esp32*/include/soc/usb_wrap_*.h
components/usb/test_apps/phy:
enable:
- if: SOC_USB_OTG_SUPPORTED == 1
disable_test:
- if: IDF_TARGET in ["esp32p4"]
temporary: true
reason: ESP32-P4 PHY driver not yet migrated
depends_components:
- usb
depends_filepatterns:
- components/hal/usb*.c
- components/hal/include/hal/usb*.h
- components/hal/esp32*/include/hal/usb*.h
- components/soc/esp32*/usb*.c
- components/soc/include/soc/usb*.h
- components/soc/esp32*/include/soc/usb_dwc_*.h
- components/soc/esp32*/include/soc/usb_wrap_*.h

View File

@ -0,0 +1,9 @@
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
set(COMPONENTS main)
project(test_app_usb_phy)

View File

@ -0,0 +1,4 @@
| Supported Targets | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- | -------- |
# USB: PHY sanity checks

View File

@ -0,0 +1,6 @@
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component can be registered as WHOLE_ARCHIVE
idf_component_register(SRC_DIRS "."
PRIV_INCLUDE_DIRS "."
REQUIRES usb unity
WHOLE_ARCHIVE)

View File

@ -0,0 +1,180 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "unity.h"
#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
#if USB_WRAP_LL_EXT_PHY_SUPPORTED
#define EXT_PHY_SUPPORTED 1
#else
#define EXT_PHY_SUPPORTED 0
#endif
#if SOC_USB_UTMI_PHY_NUM > 0
#define UTMI_PHY_SUPPORTED 1
#else
#define UTMI_PHY_SUPPORTED 0
#endif
void setUp(void)
{
unity_utils_record_free_mem();
}
void tearDown(void)
{
unity_utils_evaluate_leaks();
}
void app_main(void)
{
// ____ ___ ___________________ __ __
// | | \/ _____/\______ \ _/ |_ ____ _______/ |_
// | | /\_____ \ | | _/ \ __\/ __ \ / ___/\ __\.
// | | / / \ | | \ | | \ ___/ \___ \ | |
// |______/ /_______ / |______ / |__| \___ >____ > |__|
// \/ \/ \/ \/
printf(" ____ ___ ___________________ __ __ \r\n");
printf("| | \\/ _____/\\______ \\ _/ |_ ____ _______/ |_ \r\n");
printf("| | /\\_____ \\ | | _/ \\ __\\/ __ \\ / ___/\\ __\\\r\n");
printf("| | / / \\ | | \\ | | \\ ___/ \\___ \\ | | \r\n");
printf("|______/ /_______ / |______ / |__| \\___ >____ > |__| \r\n");
printf(" \\/ \\/ \\/ \\/ \r\n");
unity_utils_setup_heap_record(80);
unity_utils_set_leak_level(128);
unity_run_menu();
}
/**
* Test init and deinit of internal FSLS PHY
*/
TEST_CASE("Init internal FSLS PHY", "[phy]")
{
usb_phy_handle_t phy_handle = NULL;
const usb_phy_config_t phy_config = {
.controller = USB_PHY_CTRL_OTG,
.target = USB_PHY_TARGET_INT,
.otg_mode = USB_OTG_MODE_HOST,
.otg_speed = USB_PHY_SPEED_UNDEFINED,
.ext_io_conf = NULL,
.otg_io_conf = NULL,
};
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));
}
/**
* Test init and deinit of external FSLS PHY
*/
TEST_CASE("Init external FSLS PHY", "[phy]")
{
usb_phy_handle_t phy_handle = NULL;
usb_phy_config_t phy_config = {
.controller = USB_PHY_CTRL_OTG,
.target = USB_PHY_TARGET_EXT,
.otg_mode = USB_OTG_MODE_HOST,
.otg_speed = USB_PHY_SPEED_UNDEFINED,
.ext_io_conf = NULL,
.otg_io_conf = NULL,
};
#if EXT_PHY_SUPPORTED
// Init ext PHY without ext_io_conf -> FAIL
TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_handle));
TEST_ASSERT_NULL(phy_handle);
// Init ext PHY with ext_io_conf -> PASS
const usb_phy_ext_io_conf_t ext_io_conf = { // Some random values
.vp_io_num = 1,
.vm_io_num = 1,
.rcv_io_num = 1,
.suspend_n_io_num = 1,
.oen_io_num = 1,
.vpo_io_num = 1,
.vmo_io_num = 1,
.fs_edge_sel_io_num = 1,
};
phy_config.ext_io_conf = &ext_io_conf;
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
}
/**
* Test init and deinit of internal UTMI PHY
*/
TEST_CASE("Init internal UTMI PHY", "[phy]")
{
usb_phy_handle_t phy_handle = NULL;
const usb_phy_config_t phy_config = {
.controller = USB_PHY_CTRL_OTG,
.target = USB_PHY_TARGET_UTMI,
.otg_mode = USB_OTG_MODE_HOST,
.otg_speed = USB_PHY_SPEED_UNDEFINED,
.ext_io_conf = NULL,
.otg_io_conf = NULL,
};
#if UTMI_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
}
/**
* Test init and deinit of all PHYs at the same time
*/
TEST_CASE("Init all PHYs", "[phy]")
{
usb_phy_handle_t phy_handle = NULL;
usb_phy_handle_t phy_handle_2 = NULL;
usb_phy_config_t phy_config = {
.controller = USB_PHY_CTRL_OTG,
.target = USB_PHY_TARGET_INT,
.otg_mode = USB_OTG_MODE_HOST,
.otg_speed = USB_PHY_SPEED_UNDEFINED,
.ext_io_conf = NULL,
.otg_io_conf = NULL,
};
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
phy_config.target = USB_PHY_TARGET_UTMI;
#else
const usb_phy_ext_io_conf_t ext_io_conf = { // Some random values
.vp_io_num = 1,
.vm_io_num = 1,
.rcv_io_num = 1,
.suspend_n_io_num = 1,
.oen_io_num = 1,
.vpo_io_num = 1,
.vmo_io_num = 1,
.fs_edge_sel_io_num = 1,
};
phy_config.target = USB_PHY_TARGET_EXT;
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);
TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_handle));
TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_handle_2));
}

View File

@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
@pytest.mark.generic
@pytest.mark.temp_skip_ci(targets=['esp32p4'], reason='P4 PHY not yet migrated')
def test_usb_phy(dut: Dut) -> None:
dut.run_all_single_board_cases(group='phy')

View File

@ -0,0 +1,8 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
# CONFIG_ESP_TASK_WDT_INIT is not set
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
# CONFIG_UNITY_ENABLE_FLOAT is not set
# CONFIG_UNITY_ENABLE_DOUBLE is not set
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -13,17 +13,14 @@
#include "esp_private/critical_section.h"
#include "soc/usb_dwc_periph.h"
#include "hal/usb_wrap_hal.h"
#include "hal/usb_serial_jtag_hal.h"
#include "esp_rom_gpio.h"
#include "driver/gpio.h"
#include "hal/gpio_ll.h"
#include "soc/soc_caps.h"
#include "soc/usb_pins.h"
#if !SOC_RCC_IS_INDEPENDENT
#define USB_WRAP_RCC_ATOMIC() PERIPH_RCC_ATOMIC()
#define USB_PHY_RCC_ATOMIC() PERIPH_RCC_ATOMIC()
#else
#define USB_WRAP_RCC_ATOMIC()
#define USB_PHY_RCC_ATOMIC()
#endif
static const char *USBPHY_TAG = "usb_phy";
@ -40,9 +37,6 @@ struct phy_context_t {
usb_phy_speed_t otg_speed; /**< USB speed */
usb_phy_ext_io_conf_t *iopins; /**< external PHY I/O pins */
usb_wrap_hal_context_t wrap_hal; /**< USB WRAP HAL context */
#if SOC_USB_SERIAL_JTAG_SUPPORTED
usb_serial_jtag_hal_context_t usj_hal; /**< USJ HAL context */
#endif
};
typedef struct {
@ -51,72 +45,78 @@ typedef struct {
uint32_t ref_count; /**< reference count used to protect p_phy_ctrl_obj */
} phy_ctrl_obj_t;
/**
* @brief A pin descriptor for initialize external PHY I/O pins
*/
typedef struct {
int pin; /**< GPIO pin num */
const int func; /**< GPIO matrix signal */
const bool is_output; /**< input/output signal */
} usb_iopin_dsc_t;
static phy_ctrl_obj_t *p_phy_ctrl_obj = NULL;
DEFINE_CRIT_SECTION_LOCK_STATIC(phy_spinlock);
#define PHY_ENTER_CRITICAL() esp_os_enter_critical(&phy_spinlock)
#define PHY_EXIT_CRITICAL() esp_os_exit_critical(&phy_spinlock)
static esp_err_t phy_iopins_configure(const usb_iopin_dsc_t *usb_periph_iopins, int iopins_num)
static esp_err_t phy_configure_pin_input(int gpio_pin, int signal_idx)
{
for (int i = 0; i < iopins_num; i++) {
const usb_iopin_dsc_t iopin = usb_periph_iopins[i];
if (iopin.pin != GPIO_NUM_NC) {
ESP_RETURN_ON_FALSE((iopin.is_output && GPIO_IS_VALID_OUTPUT_GPIO(iopin.pin)) ||
(!iopin.is_output && GPIO_IS_VALID_GPIO(iopin.pin)),
ESP_ERR_INVALID_ARG, USBPHY_TAG, "io_num argument is invalid");
esp_rom_gpio_pad_select_gpio(iopin.pin);
if (iopin.is_output) {
esp_rom_gpio_connect_out_signal(iopin.pin, iopin.func, false, false);
} else {
esp_rom_gpio_connect_in_signal(iopin.pin, iopin.func, false);
gpio_ll_input_enable(&GPIO, iopin.pin);
}
esp_rom_gpio_pad_unhold(iopin.pin);
}
if (gpio_pin != GPIO_NUM_NC) {
ESP_RETURN_ON_FALSE(GPIO_IS_VALID_GPIO(gpio_pin),
ESP_ERR_INVALID_ARG, USBPHY_TAG, "io_num argument is invalid");
esp_rom_gpio_pad_select_gpio(gpio_pin);
esp_rom_gpio_connect_in_signal(gpio_pin, signal_idx, false);
gpio_input_enable(gpio_pin);
esp_rom_gpio_pad_unhold(gpio_pin);
}
return ESP_OK;
}
static esp_err_t phy_external_iopins_configure(const usb_phy_ext_io_conf_t *ext_io_conf)
static esp_err_t phy_configure_pin_output(int gpio_pin, int signal_idx)
{
const usb_iopin_dsc_t usb_periph_iopins[] = {
{ext_io_conf->vp_io_num, usb_otg_periph_signal.extphy_vp_in, false},
{ext_io_conf->vm_io_num, usb_otg_periph_signal.extphy_vm_in, false},
{ext_io_conf->rcv_io_num, usb_otg_periph_signal.extphy_rcv_in, false},
{ext_io_conf->oen_io_num, usb_otg_periph_signal.extphy_oen_out, true},
{ext_io_conf->vpo_io_num, usb_otg_periph_signal.extphy_vpo_out, true},
{ext_io_conf->vmo_io_num, usb_otg_periph_signal.extphy_vmo_out, true},
};
return phy_iopins_configure(usb_periph_iopins, sizeof(usb_periph_iopins) / sizeof(usb_iopin_dsc_t));
if (gpio_pin != GPIO_NUM_NC) {
ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(gpio_pin),
ESP_ERR_INVALID_ARG, USBPHY_TAG, "io_num argument is invalid");
esp_rom_gpio_pad_select_gpio(gpio_pin);
esp_rom_gpio_connect_out_signal(gpio_pin, signal_idx, false, false);
esp_rom_gpio_pad_unhold(gpio_pin);
}
return ESP_OK;
}
static esp_err_t phy_otg_iopins_configure(const usb_phy_otg_io_conf_t *otg_io_conf)
static esp_err_t phy_external_iopins_configure(const usb_phy_ext_io_conf_t *ext_io_conf, const usb_fsls_serial_signal_conn_t *fsls_sig)
{
const usb_iopin_dsc_t usb_periph_iopins[] = {
{otg_io_conf->iddig_io_num, usb_otg_periph_signal.otg_iddig_in, false},
{otg_io_conf->avalid_io_num, usb_otg_periph_signal.otg_avalid_in, false},
{otg_io_conf->vbusvalid_io_num, usb_otg_periph_signal.otg_vbusvalid_in, false},
{otg_io_conf->idpullup_io_num, usb_otg_periph_signal.otg_idpullup_out, true},
{otg_io_conf->dppulldown_io_num, usb_otg_periph_signal.otg_dppulldown_out, true},
{otg_io_conf->dmpulldown_io_num, usb_otg_periph_signal.otg_dmpulldown_out, true},
{otg_io_conf->drvvbus_io_num, usb_otg_periph_signal.otg_drvvbus_out, true},
{otg_io_conf->bvalid_io_num, usb_otg_periph_signal.srp_bvalid_in, false},
{otg_io_conf->sessend_io_num, usb_otg_periph_signal.srp_sessend_in, false},
{otg_io_conf->chrgvbus_io_num, usb_otg_periph_signal.srp_chrgvbus_out, true},
{otg_io_conf->dischrgvbus_io_num, usb_otg_periph_signal.srp_dischrgvbus_out, true},
};
return phy_iopins_configure(usb_periph_iopins, sizeof(usb_periph_iopins) / sizeof(usb_iopin_dsc_t));
ESP_RETURN_ON_FALSE(ext_io_conf && fsls_sig, ESP_ERR_INVALID_ARG, USBPHY_TAG, "argument is invalid");
esp_err_t ret = ESP_OK;
// Inputs
ret |= phy_configure_pin_input(ext_io_conf->vp_io_num, fsls_sig->rx_dp);
ret |= phy_configure_pin_input(ext_io_conf->vm_io_num, fsls_sig->rx_dm);
ret |= phy_configure_pin_input(ext_io_conf->rcv_io_num, fsls_sig->rx_rcv);
// Outputs
ret |= phy_configure_pin_output(ext_io_conf->suspend_n_io_num, fsls_sig->suspend_n);
ret |= phy_configure_pin_output(ext_io_conf->oen_io_num, fsls_sig->tx_enable_n);
ret |= phy_configure_pin_output(ext_io_conf->vpo_io_num, fsls_sig->tx_dp);
ret |= phy_configure_pin_output(ext_io_conf->vmo_io_num, fsls_sig->tx_dm);
ret |= phy_configure_pin_output(ext_io_conf->fs_edge_sel_io_num, fsls_sig->fs_edge_sel);
return ret;
}
static esp_err_t phy_otg_iopins_configure(const usb_phy_otg_io_conf_t *otg_io_conf, const usb_otg_signal_conn_t *otg_sig)
{
ESP_RETURN_ON_FALSE(otg_io_conf && otg_sig, ESP_ERR_INVALID_ARG, USBPHY_TAG, "argument is invalid");
esp_err_t ret = ESP_OK;
// Inputs
ret |= phy_configure_pin_input(otg_io_conf->iddig_io_num, otg_sig->iddig);
ret |= phy_configure_pin_input(otg_io_conf->avalid_io_num, otg_sig->avalid);
ret |= phy_configure_pin_input(otg_io_conf->vbusvalid_io_num, otg_sig->vbusvalid);
ret |= phy_configure_pin_input(otg_io_conf->bvalid_io_num, otg_sig->bvalid);
ret |= phy_configure_pin_input(otg_io_conf->sessend_io_num, otg_sig->sessend);
// Outputs
ret |= phy_configure_pin_output(otg_io_conf->idpullup_io_num, otg_sig->idpullup);
ret |= phy_configure_pin_output(otg_io_conf->dppulldown_io_num, otg_sig->dppulldown);
ret |= phy_configure_pin_output(otg_io_conf->dmpulldown_io_num, otg_sig->dmpulldown);
ret |= phy_configure_pin_output(otg_io_conf->drvvbus_io_num, otg_sig->drvvbus);
ret |= phy_configure_pin_output(otg_io_conf->chrgvbus_io_num, otg_sig->chrgvbus);
ret |= phy_configure_pin_output(otg_io_conf->dischrgvbus_io_num, otg_sig->dischrgvbus);
return ret;
}
esp_err_t usb_phy_otg_set_mode(usb_phy_handle_t handle, usb_otg_mode_t mode)
@ -126,11 +126,13 @@ esp_err_t usb_phy_otg_set_mode(usb_phy_handle_t handle, usb_otg_mode_t mode)
ESP_RETURN_ON_FALSE(handle->controller == USB_PHY_CTRL_OTG, ESP_FAIL, USBPHY_TAG, "phy source is not USB_OTG");
handle->otg_mode = mode;
const usb_otg_signal_conn_t *otg_sig = usb_dwc_info.controllers[0].otg_signals;
assert(otg_sig);
if (mode == USB_OTG_MODE_HOST) {
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_OTG_IDDIG_IN_IDX, false); // connected connector is A side
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_SRP_BVALID_IN_IDX, false);
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_OTG_VBUSVALID_IN_IDX, false); // receiving a valid Vbus from host
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_OTG_AVALID_IN_IDX, false); // HIGH to force USB host mode
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, otg_sig->iddig, false); // connected connector is A side
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, otg_sig->bvalid, false);
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, otg_sig->vbusvalid, false); // receiving a valid Vbus from host
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, otg_sig->avalid, false);
if (handle->target == USB_PHY_TARGET_INT) {
// Configure pull resistors for host
usb_wrap_pull_override_vals_t vals = {
@ -142,10 +144,10 @@ 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);
}
} else if (mode == USB_OTG_MODE_DEVICE) {
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_OTG_IDDIG_IN_IDX, false); // connected connector is mini-B side
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_SRP_BVALID_IN_IDX, false); // HIGH to force USB device mode
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_OTG_VBUSVALID_IN_IDX, false); // receiving a valid Vbus from device
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_OTG_AVALID_IN_IDX, false);
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, otg_sig->iddig, false); // connected connector is mini-B side
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, otg_sig->bvalid, false); // HIGH to force USB device mode
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, otg_sig->vbusvalid, false); // receiving a valid Vbus from device
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, otg_sig->avalid, false);
}
return ESP_OK;
@ -185,6 +187,9 @@ esp_err_t usb_phy_action(usb_phy_handle_t handle, usb_phy_action_t action)
ESP_ERR_INVALID_ARG, USBPHY_TAG, "wrong target for the action");
esp_err_t ret = ESP_OK;
const usb_fsls_serial_signal_conn_t *fsls_sig = usb_dwc_info.controllers[0].fsls_signals;
assert(fsls_sig);
switch (action) {
case USB_PHY_ACTION_HOST_ALLOW_CONN:
if (handle->target == USB_PHY_TARGET_INT) {
@ -198,8 +203,8 @@ esp_err_t usb_phy_action(usb_phy_handle_t handle, usb_phy_action_t action)
/*
Allow for connections on the external PHY by connecting the VP and VM signals to the external PHY.
*/
esp_rom_gpio_connect_in_signal(handle->iopins->vp_io_num, USB_EXTPHY_VP_IDX, false);
esp_rom_gpio_connect_in_signal(handle->iopins->vm_io_num, USB_EXTPHY_VM_IDX, false);
esp_rom_gpio_connect_in_signal(handle->iopins->vp_io_num, fsls_sig->rx_dp, false);
esp_rom_gpio_connect_in_signal(handle->iopins->vm_io_num, fsls_sig->rx_dm, false);
}
break;
@ -224,8 +229,8 @@ esp_err_t usb_phy_action(usb_phy_handle_t handle, usb_phy_action_t action)
/*
Disable connections on the external PHY by connecting the VP and VM signals to the constant LOW signal.
*/
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_EXTPHY_VP_IDX, false);
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_EXTPHY_VM_IDX, false);
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, fsls_sig->rx_dp, false);
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, fsls_sig->rx_dm, false);
}
break;
@ -259,12 +264,7 @@ static esp_err_t usb_phy_install(void)
PHY_EXIT_CRITICAL();
goto cleanup;
}
// Enable USB peripheral and reset the register
PHY_EXIT_CRITICAL();
USB_WRAP_RCC_ATOMIC() {
usb_wrap_ll_enable_bus_clock(true);
usb_wrap_ll_reset_register();
}
return ESP_OK;
cleanup:
@ -277,6 +277,13 @@ esp_err_t usb_new_phy(const usb_phy_config_t *config, usb_phy_handle_t *handle_r
ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, USBPHY_TAG, "config argument is invalid");
ESP_RETURN_ON_FALSE(config->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(config->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
ESP_RETURN_ON_FALSE(config->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
ESP_RETURN_ON_FALSE(config->target != USB_PHY_TARGET_UTMI, ESP_ERR_NOT_SUPPORTED, USBPHY_TAG, "UTMI PHY not supported on this target");
#endif
ESP_RETURN_ON_ERROR(usb_phy_install(), USBPHY_TAG, "usb_phy driver installation failed");
esp_err_t ret = ESP_OK;
@ -302,22 +309,14 @@ 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;
usb_wrap_hal_init(&phy_context->wrap_hal);
#if SOC_USB_SERIAL_JTAG_SUPPORTED
usb_serial_jtag_hal_init(&phy_context->usj_hal);
#endif
USB_PHY_RCC_ATOMIC() {
usb_wrap_hal_init(&phy_context->wrap_hal);
}
if (config->controller == USB_PHY_CTRL_OTG) {
#if USB_WRAP_LL_EXT_PHY_SUPPORTED
usb_wrap_hal_phy_set_external(&phy_context->wrap_hal, (config->target == USB_PHY_TARGET_EXT));
#endif
}
#if SOC_USB_SERIAL_JTAG_SUPPORTED
else if (config->controller == USB_PHY_CTRL_SERIAL_JTAG) {
usb_serial_jtag_hal_phy_set_external(&phy_context->usj_hal, (config->target == USB_PHY_TARGET_EXT));
phy_context->otg_mode = USB_OTG_MODE_DEVICE;
phy_context->otg_speed = USB_PHY_SPEED_FULL;
}
#endif
if (config->target == USB_PHY_TARGET_INT) {
gpio_set_drive_capability(USBPHY_DM_NUM, GPIO_DRIVE_CAP_3);
@ -329,7 +328,8 @@ esp_err_t usb_new_phy(const usb_phy_config_t *config, usb_phy_handle_t *handle_r
phy_context->iopins = (usb_phy_ext_io_conf_t *) calloc(1, sizeof(usb_phy_ext_io_conf_t));
ESP_GOTO_ON_FALSE(phy_context->iopins, ESP_ERR_NO_MEM, cleanup, USBPHY_TAG, "no mem for storing I/O pins");
memcpy(phy_context->iopins, config->ext_io_conf, sizeof(usb_phy_ext_io_conf_t));
ESP_ERROR_CHECK(phy_external_iopins_configure(phy_context->iopins));
const usb_fsls_serial_signal_conn_t *fsls_sig = usb_dwc_info.controllers[0].fsls_signals;
ESP_ERROR_CHECK(phy_external_iopins_configure(phy_context->iopins, fsls_sig));
}
if (config->otg_mode != USB_PHY_MODE_DEFAULT) {
ESP_ERROR_CHECK(usb_phy_otg_set_mode(*handle_ret, config->otg_mode));
@ -338,7 +338,8 @@ esp_err_t usb_new_phy(const usb_phy_config_t *config, usb_phy_handle_t *handle_r
ESP_ERROR_CHECK(usb_phy_otg_dev_set_speed(*handle_ret, config->otg_speed));
}
if (config->otg_io_conf && (phy_context->controller == USB_PHY_CTRL_OTG)) {
ESP_ERROR_CHECK(phy_otg_iopins_configure(config->otg_io_conf));
const usb_otg_signal_conn_t *otg_sig = usb_dwc_info.controllers[0].otg_signals;
ESP_ERROR_CHECK(phy_otg_iopins_configure(config->otg_io_conf, otg_sig));
}
return ESP_OK;
@ -361,9 +362,9 @@ static void phy_uninstall(void)
if (p_phy_ctrl_obj->ref_count == 0) {
p_phy_ctrl_obj_free = p_phy_ctrl_obj;
p_phy_ctrl_obj = NULL;
USB_WRAP_RCC_ATOMIC() {
USB_PHY_RCC_ATOMIC() {
// Disable USB peripheral without reset the module
usb_wrap_ll_enable_bus_clock(false);
usb_wrap_hal_disable();
}
}
PHY_EXIT_CRITICAL();