/* * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "esp_log.h" #include "esp_check.h" #include "esp_private/periph_ctrl.h" #include "esp_private/usb_phy.h" #include "esp_private/critical_section.h" #include "soc/usb_dwc_periph.h" #include "hal/usb_wrap_hal.h" #include "esp_rom_gpio.h" #include "driver/gpio.h" #include "soc/soc_caps.h" #if !SOC_RCC_IS_INDEPENDENT #define USB_PHY_RCC_ATOMIC() PERIPH_RCC_ATOMIC() #else #define USB_PHY_RCC_ATOMIC() #endif static const char *USBPHY_TAG = "usb_phy"; #define USBPHY_NOT_INIT_ERR_STR "USB_PHY is not initialized" typedef struct phy_context_t phy_context_t; struct phy_context_t { usb_phy_target_t target; /**< PHY target */ usb_phy_controller_t controller; /**< PHY controller */ usb_phy_status_t status; /**< PHY status */ usb_otg_mode_t otg_mode; /**< USB OTG mode */ 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 */ }; typedef struct { phy_context_t *internal_phy; /**< internal PHY context */ phy_context_t *external_phy; /**< external PHY context */ uint32_t ref_count; /**< reference count used to protect p_phy_ctrl_obj */ } phy_ctrl_obj_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_configure_pin_input(int gpio_pin, int signal_idx) { 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_configure_pin_output(int gpio_pin, int signal_idx) { 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_external_iopins_configure(const usb_phy_ext_io_conf_t *ext_io_conf, const usb_fsls_serial_signal_conn_t *fsls_sig) { 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) { ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, USBPHY_TAG, "handle argument is invalid"); ESP_RETURN_ON_FALSE(mode < USB_OTG_MODE_MAX, ESP_ERR_INVALID_ARG, USBPHY_TAG, "mode argument is invalid"); 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, 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 = { .dp_pu = false, .dm_pu = false, .dp_pd = true, .dm_pd = true, }; 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, 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; } esp_err_t usb_phy_otg_dev_set_speed(usb_phy_handle_t handle, usb_phy_speed_t speed) { ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, USBPHY_TAG, "handle argument is invalid"); ESP_RETURN_ON_FALSE(speed < USB_PHY_SPEED_MAX, ESP_ERR_INVALID_ARG, USBPHY_TAG, "speed argument is invalid"); ESP_RETURN_ON_FALSE(handle->controller == USB_PHY_CTRL_OTG, ESP_FAIL, USBPHY_TAG, "phy source is not USB_OTG"); ESP_RETURN_ON_FALSE((handle->target == USB_PHY_TARGET_INT && handle->otg_mode == USB_OTG_MODE_DEVICE), ESP_FAIL, USBPHY_TAG, "set speed not supported"); handle->otg_speed = speed; // Configure pull resistors for device usb_wrap_pull_override_vals_t vals = { .dp_pd = false, .dm_pd = false, }; if (speed == USB_PHY_SPEED_LOW) { vals.dp_pu = false; vals.dm_pu = true; } else { vals.dp_pu = true; vals.dm_pu = false; } usb_wrap_hal_phy_enable_pull_override(&handle->wrap_hal, &vals); return ESP_OK; } esp_err_t usb_phy_action(usb_phy_handle_t handle, usb_phy_action_t action) { ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, USBPHY_TAG, "handle argument is invalid"); ESP_RETURN_ON_FALSE(action < USB_PHY_ACTION_MAX, ESP_ERR_INVALID_ARG, USBPHY_TAG, "action argument is invalid"); ESP_RETURN_ON_FALSE((action == USB_PHY_ACTION_HOST_ALLOW_CONN && handle->controller == USB_PHY_CTRL_OTG) || (action == USB_PHY_ACTION_HOST_FORCE_DISCONN && handle->controller == USB_PHY_CTRL_OTG), 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) { usb_wrap_hal_phy_enable_test_mode(&handle->wrap_hal, false); } else { if (!handle->iopins) { ret = ESP_FAIL; ESP_LOGE(USBPHY_TAG, "no I/O pins provided for connection"); break; } /* 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, fsls_sig->rx_dp, false); esp_rom_gpio_connect_in_signal(handle->iopins->vm_io_num, fsls_sig->rx_dm, false); } break; case USB_PHY_ACTION_HOST_FORCE_DISCONN: if (handle->target == USB_PHY_TARGET_INT) { /* We mimic a disconnect by enabling the internal PHY's test mode, then forcing the output_enable to HIGH. This will cause the received VP and VM to be zero, thus mimicking a disconnection. */ const usb_wrap_test_mode_vals_t vals = { .tx_enable_n = true, .tx_dp = false, .tx_dm = false, .rx_dp = false, .rx_dm = false, .rx_rcv = false, }; usb_wrap_hal_phy_test_mode_set_signals(&handle->wrap_hal, &vals); usb_wrap_hal_phy_enable_test_mode(&handle->wrap_hal, true); } else { /* 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, fsls_sig->rx_dp, false); esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, fsls_sig->rx_dm, false); } break; default: break; } return ret; } static esp_err_t usb_phy_install(void) { PHY_ENTER_CRITICAL(); if (p_phy_ctrl_obj) { // p_phy_ctrl_obj already installed, return immediately PHY_EXIT_CRITICAL(); return ESP_OK; } PHY_EXIT_CRITICAL(); esp_err_t ret = ESP_OK; phy_ctrl_obj_t *phy_ctrl_obj = (phy_ctrl_obj_t *) calloc(1, sizeof(phy_ctrl_obj_t)); ESP_GOTO_ON_FALSE(phy_ctrl_obj, ESP_ERR_NO_MEM, cleanup, USBPHY_TAG, "no mem for USB_PHY driver"); PHY_ENTER_CRITICAL(); if (!p_phy_ctrl_obj) { p_phy_ctrl_obj = phy_ctrl_obj; p_phy_ctrl_obj->ref_count = 0; } else { // p_phy_ctrl_obj already installed, need to free resource PHY_EXIT_CRITICAL(); goto cleanup; } PHY_EXIT_CRITICAL(); return ESP_OK; cleanup: free(phy_ctrl_obj); return ret; } esp_err_t usb_new_phy(const usb_phy_config_t *config, usb_phy_handle_t *handle_ret) { 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; bool new_phy = false; phy_context_t *phy_context = (phy_context_t *) calloc(1, sizeof(phy_context_t)); ESP_GOTO_ON_FALSE(phy_context, ESP_ERR_NO_MEM, cleanup, USBPHY_TAG, "no mem for phy context"); PHY_ENTER_CRITICAL(); usb_phy_get_phy_status(config->target, &phy_context->status); if (phy_context->status == USB_PHY_STATUS_FREE) { new_phy = true; p_phy_ctrl_obj->ref_count++; if (config->target == USB_PHY_TARGET_EXT) { p_phy_ctrl_obj->external_phy = phy_context; } else { p_phy_ctrl_obj->internal_phy = phy_context; } } PHY_EXIT_CRITICAL(); ESP_GOTO_ON_FALSE(new_phy, ESP_ERR_INVALID_STATE, cleanup, USBPHY_TAG, "selected PHY is in use"); phy_context->target = config->target; phy_context->controller = config->controller; phy_context->status = USB_PHY_STATUS_IN_USE; 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 (config->target == USB_PHY_TARGET_INT) { gpio_set_drive_capability(USBPHY_DM_NUM, GPIO_DRIVE_CAP_3); gpio_set_drive_capability(USBPHY_DP_NUM, GPIO_DRIVE_CAP_3); } *handle_ret = (usb_phy_handle_t) phy_context; if (config->ext_io_conf && config->target == USB_PHY_TARGET_EXT) { 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)); 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)); } if (config->otg_speed != USB_PHY_SPEED_UNDEFINED) { 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)) { 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; cleanup: if (phy_context) { free(phy_context->iopins); free(phy_context); } if (p_phy_ctrl_obj->ref_count == 0) { free(p_phy_ctrl_obj); p_phy_ctrl_obj = NULL; } return ret; } static void phy_uninstall(void) { phy_ctrl_obj_t *p_phy_ctrl_obj_free = NULL; PHY_ENTER_CRITICAL(); if (p_phy_ctrl_obj->ref_count == 0) { p_phy_ctrl_obj_free = p_phy_ctrl_obj; p_phy_ctrl_obj = NULL; USB_PHY_RCC_ATOMIC() { // Disable USB peripheral without reset the module usb_wrap_hal_disable(); } } PHY_EXIT_CRITICAL(); free(p_phy_ctrl_obj_free); } esp_err_t usb_del_phy(usb_phy_handle_t handle) { ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, USBPHY_TAG, "handle argument is invalid"); PHY_ENTER_CRITICAL(); p_phy_ctrl_obj->ref_count--; if (handle->target == USB_PHY_TARGET_EXT) { p_phy_ctrl_obj->external_phy = NULL; } else { // 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->internal_phy = NULL; } PHY_EXIT_CRITICAL(); free(handle->iopins); free(handle); phy_uninstall(); return ESP_OK; } esp_err_t usb_phy_get_phy_status(usb_phy_target_t target, usb_phy_status_t *status) { ESP_RETURN_ON_FALSE(target < USB_PHY_TARGET_MAX, ESP_ERR_INVALID_ARG, USBPHY_TAG, "argument is invalid"); ESP_RETURN_ON_FALSE(p_phy_ctrl_obj, ESP_ERR_INVALID_STATE, USBPHY_TAG, USBPHY_NOT_INIT_ERR_STR); if (target == USB_PHY_TARGET_EXT && p_phy_ctrl_obj->external_phy) { *status = p_phy_ctrl_obj->external_phy->status; } else if (target == USB_PHY_TARGET_INT && p_phy_ctrl_obj->internal_phy) { *status = p_phy_ctrl_obj->internal_phy->status; } else { *status = USB_PHY_STATUS_FREE; } return ESP_OK; }