feat(uhci): Add uhci (uart-dma) support on esp32c3, esp32c6, esp32s3, esp32p4

This commit is contained in:
C.S.M
2025-04-24 14:08:57 +08:00
parent 182b33efb2
commit 1a3db8e025
30 changed files with 1353 additions and 126 deletions

View File

@@ -4,12 +4,16 @@ set(srcs)
set(public_include "include")
if(CONFIG_SOC_UART_SUPPORTED)
list(APPEND srcs "src/uart.c" "src/uart_wakeup.c")
if(CONFIG_SOC_UHCI_SUPPORTED)
list(APPEND srcs "src/uhci.c")
endif()
endif()
if(${target} STREQUAL "linux")
set(priv_requires esp_ringbuf)
else()
set(priv_requires esp_pm esp_driver_gpio esp_ringbuf)
set(priv_requires esp_pm esp_driver_gpio esp_ringbuf esp_mm)
endif()
idf_component_register(

View File

@@ -10,3 +10,31 @@ menu "ESP-Driver:UART Configurations"
may cause data lost when doing spi flash operation.
endmenu
menu "ESP-Driver:UHCI Configurations"
config UHCI_ISR_HANDLER_IN_IRAM
bool "Place UHCI ISR function into IRAM"
default n
help
If this option is not selected, UHCI interrupt will be disabled for a long time and
may cause data lost when doing spi flash operation.
config UHCI_ISR_CACHE_SAFE
bool "Allow UHCI ISR to execute when cache is disabled" if !SPI_FLASH_AUTO_SUSPEND
select UHCI_ISR_HANDLER_IN_IRAM
select GDMA_ISR_HANDLER_IN_IRAM if SOC_GDMA_SUPPORTED
default n
help
Enable this option to allow the ISR for UHCI to execute even when the cache is disabled.
This can be useful in scenarios where the cache might be turned off, but the UHCI
functionality is still required to operate correctly.
config UHCI_ENABLE_DEBUG_LOG
bool "Enable debug log"
default n
help
whether to enable the debug log message for UHCI driver.
Note that, this option only controls the UHCI driver log, won't affect other drivers.
endmenu

View File

@@ -0,0 +1,161 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "driver/uhci_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief UHCI controller specific configurations
*/
typedef struct {
uart_port_t uart_port; /*!< UART port that connect to UHCI controller */
size_t tx_trans_queue_depth; /*!< Depth of internal transfer queue, increase this value can support more transfers pending in the background */
size_t max_transmit_size; /*!< Maximum transfer size in one transaction, in bytes. This decides the number of DMA nodes will be used for each transaction */
size_t max_receive_internal_mem; /*!< Maximum transfer size in one transaction, in bytes. Each DMA node can point to a maximum of 4096 bytes. This value determines the number of DMA nodes used for each transaction. When your transfer size is large enough, it is recommended to set this value greater than 4096 to facilitate efficient ping-pong operations, such as 10 * 1024. */
size_t dma_burst_size; /*!< DMA burst size, in bytes */
struct {
uint16_t rx_brk_eof: 1; /*!< UHCI will end payload receive process when NULL frame is received by UART. */
uint16_t idle_eof: 1; /*!< UHCI will end payload receive process when UART has been in idle state. */
uint16_t length_eof: 1; /*!< UHCI will end payload receive process when the receiving byte count has reached the specific value. */
} rx_eof_flags; /*!< UHCI eof flags */
} uhci_controller_config_t;
/**
* @brief Structure for defining callback functions for UHCI events.
*/
typedef struct {
uhci_rx_event_callback_t on_rx_trans_event; /*!< Callback function for handling the completion of a reception. */
uhci_tx_done_callback_t on_tx_trans_done; /*!< Callback function for handling the completion of a transmission. */
} uhci_event_callbacks_t;
/**
* @brief Create and initialize a new UHCI controller.
*
* This function initializes a new UHCI controller instance based on the provided configuration.
* It allocates and configures resources required for the UHCI controller, such as DMA and
* communication settings. The created controller handle is returned through the output parameter.
*
* @param[in] config Pointer to a `uhci_controller_config_t` structure containing the
* configuration parameters for the UHCI controller.
* @param[out] ret_uhci_ctrl Pointer to a variable where the handle to the newly created UHCI controller
* will be stored. This handle is used in subsequent operations involving the
* controller.
*
* @return
* - `ESP_OK`: Controller successfully created and initialized.
* - `ESP_ERR_INVALID_ARG`: One or more arguments are invalid (e.g., null pointers or invalid config).
* - `ESP_ERR_NO_MEM`: Memory allocation for the controller failed.
* - Other error codes: Indicate failure in the underlying hardware or driver initialization.
*/
esp_err_t uhci_new_controller(const uhci_controller_config_t *config, uhci_controller_handle_t *ret_uhci_ctrl);
/**
* @brief Receive data from the UHCI controller.
*
* This function retrieves data from the UHCI controller into the provided buffer. It is typically
* used for receiving data that was transmitted via UART and processed by the UHCI DMA controller.
*
* @param[in] uhci_ctrl Handle to the UHCI controller, which was previously created using
* `uhci_new_controller()`.
* @param[out] read_buffer Pointer to the buffer where the received data will be stored.
* The buffer must be pre-allocated by the caller.
* @param[in] buffer_size The size of read buffer.
*
* @note @note The function is non-blocking, it just mounts the user buffer to the DMA.
* The return from the function doesn't mean a finished receive. You need to register corresponding
* callback function to get notification.
*
* @return
* - `ESP_OK`: Data successfully received and written to the buffer.
* - `ESP_ERR_INVALID_ARG`: Invalid arguments (e.g., null buffer or invalid controller handle).
*/
esp_err_t uhci_receive(uhci_controller_handle_t uhci_ctrl, uint8_t *read_buffer, size_t buffer_size);
/**
* @brief Transmit data using the UHCI controller.
*
* This function sends data from the provided buffer through the UHCI controller. It uses the DMA
* capabilities of UHCI to efficiently handle data transmission via UART.
*
* @param[in] uhci_ctrl Handle to the UHCI controller, which was previously created using
* `uhci_new_controller()`.
* @param[in] write_buffer Pointer to the buffer containing the data to be transmitted.
* The buffer must remain valid until the transmission is complete.
* @param[in] write_size The number of bytes to transmit from the buffer.
*
* @note The function is an non-blocking api, which means this function will return immediately. You can
* get corresponding event from callbacks.
*
* @return
* - `ESP_OK`: Data successfully queued for transmission.
* - `ESP_ERR_INVALID_ARG`: Invalid arguments (e.g., null buffer, invalid handle, or zero `write_size`).
*/
esp_err_t uhci_transmit(uhci_controller_handle_t uhci_ctrl, uint8_t *write_buffer, size_t write_size);
/**
* @brief Uninstall the UHCI (UART Host Controller Interface) driver and release resources.
*
* This function deinitializes the UHCI controller and frees any resources allocated during its
* initialization. It ensures proper cleanup and prevents resource leaks when the UHCI controller
* is no longer needed.
*
* @param[in] uhci_ctrl Handle to the UHCI controller, which was previously created using
* `uhci_new_controller()`. Passing an invalid or uninitialized handle
* may result in undefined behavior.
*
* @return
* - `ESP_OK`: The UHCI driver was successfully uninstalled, and resources were released.
* - `ESP_ERR_INVALID_ARG`: The provided `uhci_ctrl` handle is invalid or null.
*/
esp_err_t uhci_del_controller(uhci_controller_handle_t uhci_ctrl);
/**
* @brief Register event callback functions for a UHCI controller.
*
* This function allows the user to register callback functions to handle specific UHCI events, such as
* transmission or reception completion. The callbacks provide a mechanism to handle asynchronous events
* generated by the UHCI controller.
*
* @param[in] uhci_ctrl Handle to the UHCI controller, which was previously created using
* `uhci_new_controller()`.
* @param[in] cbs Pointer to a `uhci_event_callbacks_t` structure that defines the callback
* functions to be registered. This structure includes pointers to the callback
* functions for handling UHCI events.
* @param[in] user_data Pointer to user-defined data that will be passed to the callback functions
* when they are invoked. This can be used to provide context or state information
* specific to the application.
*
* @return
* - `ESP_OK`: Event callbacks were successfully registered.
* - `ESP_ERR_INVALID_ARG`: Invalid arguments (e.g., null `uhci_ctrl` handle or `cbs` pointer).
*/
esp_err_t uhci_register_event_callbacks(uhci_controller_handle_t uhci_ctrl, const uhci_event_callbacks_t *cbs, void *user_data);
/**
* @brief Wait for all pending TX transactions done
*
* @param[in] uhci_ctrl UHCI controller that created by `uhci_new_controller`
* @param[in] timeout_ms Timeout in milliseconds, `-1` means to wait forever
* @return
* - ESP_OK: All pending TX transactions is finished and recycled
* - ESP_ERR_INVALID_ARG: Wait for all pending TX transactions done failed because of invalid argument
* - ESP_ERR_TIMEOUT: Wait for all pending TX transactions done timeout
* - ESP_FAIL: Wait for all pending TX transactions done failed because of other error
*/
esp_err_t uhci_wait_all_tx_transaction_done(uhci_controller_handle_t uhci_ctrl, int timeout_ms);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,73 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stddef.h>
#include "hal/uart_types.h"
#include "hal/uhci_types.h"
#include "soc/soc_caps.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief UHCI Controller Handle Type
*/
typedef struct uhci_controller_t *uhci_controller_handle_t;
/**
* @brief UHCI TX Done Event Data
*/
typedef struct {
uint8_t *buffer; /**< Pointer to the which data buffer has been finished the transaction */
size_t sent_size; /**< Size has been sent out */
} uhci_tx_done_event_data_t;
/**
* @brief UHCI TX Done Callback Function Type
* @param uhci_ctrl Handle to the UHCI controller that initiated the transmission.
* @param edata Pointer to a structure containing event data related to the completed transmission.
* This structure provides details such as the number of bytes transmitted and any
* status information relevant to the operation.
* @param user_ctx User-defined context passed during the callback registration.
* It can be used to maintain application-specific state or data.
*
* @return Whether a high priority task has been waken up by this callback function
*/
typedef bool (*uhci_tx_done_callback_t)(uhci_controller_handle_t uhci_ctrl, const uhci_tx_done_event_data_t *edata, void *user_ctx);
/**
* @brief UHCI RX Done Event Data Structure
*/
typedef struct {
uint8_t *data; /*!< Pointer to the received data buffer */
size_t recv_size; /*!< Number of bytes received */
struct {
uint32_t totally_received: 1; /*!< When callback is invoked, while this bit is not set, means the current event gives partial of whole data, the transaction has not been finished. If set, means the current event gives whole data, the transaction finished. */
} flags; /*!< I2C master config flags */
} uhci_rx_event_data_t;
/**
* @brief UHCI RX Done Callback Function Type
* @param uhci_ctrl Handle to the UHCI controller that initiated the transmission.
* @param edata Pointer to a structure containing event data related to receive event.
* This structure provides details such as the number of bytes received and any
* status information relevant to the operation.
* @param user_ctx User-defined context passed during the callback registration.
* It can be used to maintain application-specific state or data.
*
* @return Whether a high priority task has been waken up by this callback function
*/
typedef bool (*uhci_rx_event_callback_t)(uhci_controller_handle_t uhci_ctrl, const uhci_rx_event_data_t *edata, void *user_ctx);
#ifdef __cplusplus
}
#endif

View File

@@ -9,3 +9,20 @@ archive: libhal.a
entries:
if UART_ISR_IN_IRAM = y:
uart_hal_iram (noflash)
[mapping:uhci]
archive: libesp_driver_uart.a
entries:
if UHCI_ISR_HANDLER_IN_IRAM = y:
uhci: uhci_gdma_rx_callback_done (noflash)
uhci: uhci_gdma_tx_callback_eof (noflash)
uhci: uhci_do_transmit (noflash)
[mapping:uhci_driver_gdma]
archive: libesp_hw_support.a
entries:
if UHCI_ISR_HANDLER_IN_IRAM = y:
gdma_link: gdma_link_count_buffer_size_till_eof (noflash)
gdma_link: gdma_link_mount_buffers (noflash)
gdma_link: gdma_link_get_head_addr (noflash)
gdma: gdma_start (noflash)

View File

@@ -0,0 +1,574 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdint.h>
#include "esp_intr_alloc.h"
#if CONFIG_UHCI_ENABLE_DEBUG_LOG
// The local log level must be defined before including esp_log.h
// Set the maximum log level for this source file
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#endif
#include "sdkconfig.h"
#include "esp_attr.h"
#include "esp_log.h"
#include "esp_check.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "driver/uart.h"
#include "driver/uhci.h"
#include "driver/uhci_types.h"
#include "soc/uhci_periph.h"
#include "soc/soc_caps.h"
#include "hal/uhci_hal.h"
#include "hal/uhci_ll.h"
#include "hal/dma_types.h"
#include "hal/cache_hal.h"
#include "hal/cache_ll.h"
#include "esp_private/periph_ctrl.h"
#include "esp_private/gdma.h"
#include "esp_private/esp_dma_utils.h"
#include "esp_private/gdma_link.h"
#include "esp_private/esp_cache_private.h"
#include "uhci_private.h"
#include "esp_memory_utils.h"
#include "esp_cache.h"
static const char* TAG = "uhci";
typedef struct uhci_platform_t {
_lock_t mutex; // platform level mutex lock.
uhci_controller_handle_t controller[SOC_UHCI_NUM]; // array of UHCI instances.
} uhci_platform_t;
static uhci_platform_t s_uhci_platform = {}; // singleton platform
static bool uhci_ctrl_occupied(int port_num)
{
return s_uhci_platform.controller[port_num] != NULL;
}
static void uhci_do_transmit(uhci_controller_handle_t uhci_ctrl, uhci_transaction_desc_t *trans);
static bool uhci_gdma_tx_callback_eof(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
{
BaseType_t do_yield = pdFALSE;
uhci_controller_handle_t uhci_ctrl = (uhci_controller_handle_t) user_data;
uhci_transaction_desc_t *trans_desc = NULL;
bool need_yield = false;
uhci_tx_fsm_t expected_fsm = UHCI_TX_FSM_RUN;
if (atomic_compare_exchange_strong(&uhci_ctrl->tx_dir.tx_fsm, &expected_fsm, UHCI_TX_FSM_ENABLE_WAIT)) {
trans_desc = uhci_ctrl->tx_dir.cur_trans;
xQueueSendFromISR(uhci_ctrl->tx_dir.trans_queues[UHCI_TRANS_QUEUE_COMPLETE], &trans_desc, &do_yield);
if (do_yield) {
need_yield = true;
}
atomic_store(&uhci_ctrl->tx_dir.tx_fsm, UHCI_TX_FSM_ENABLE);
}
if (uhci_ctrl->pm_lock) {
esp_pm_lock_release(uhci_ctrl->pm_lock);
}
if (uhci_ctrl->tx_dir.on_tx_trans_done) {
uhci_tx_done_event_data_t evt_data = {
.buffer = uhci_ctrl->tx_dir.cur_trans->buffer,
.sent_size = uhci_ctrl->tx_dir.cur_trans->buffer_size,
};
if (uhci_ctrl->tx_dir.on_tx_trans_done(uhci_ctrl, &evt_data, uhci_ctrl->user_data)) {
need_yield |= true;
}
}
expected_fsm = UHCI_TX_FSM_ENABLE;
if (atomic_compare_exchange_strong(&uhci_ctrl->tx_dir.tx_fsm, &expected_fsm, UHCI_TX_FSM_RUN_WAIT)) {
if (xQueueReceiveFromISR(uhci_ctrl->tx_dir.trans_queues[UHCI_TRANS_QUEUE_PROGRESS], &trans_desc, &do_yield) == pdTRUE) {
atomic_store(&uhci_ctrl->tx_dir.tx_fsm, UHCI_TX_FSM_RUN);
uhci_do_transmit(uhci_ctrl, trans_desc);
if (do_yield) {
need_yield |= true;
}
} else {
atomic_store(&uhci_ctrl->tx_dir.tx_fsm, UHCI_TX_FSM_ENABLE);
}
}
return need_yield;
}
static bool uhci_gdma_rx_callback_done(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
{
bool need_yield = false;
uhci_controller_handle_t uhci_ctrl = (uhci_controller_handle_t) user_data;
// If the data is not all received, handle it in not normal_eof block. Otherwise, in eof block.
if (!event_data->flags.normal_eof) {
size_t rx_size = uhci_ctrl->rx_dir.buffer_size_per_desc_node[uhci_ctrl->rx_dir.node_index];
uhci_rx_event_data_t evt_data = {
.data = uhci_ctrl->rx_dir.buffer_pointers[uhci_ctrl->rx_dir.node_index],
.recv_size = rx_size,
.flags.totally_received = false,
};
bool need_cache_sync = esp_ptr_internal(uhci_ctrl->rx_dir.buffer_pointers[uhci_ctrl->rx_dir.node_index]) ? (uhci_ctrl->int_mem_cache_line_size > 0) : (uhci_ctrl->ext_mem_cache_line_size > 0);
if (need_cache_sync) {
esp_cache_msync(uhci_ctrl->rx_dir.buffer_pointers[uhci_ctrl->rx_dir.node_index], rx_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
}
if (uhci_ctrl->rx_dir.on_rx_trans_event) {
need_yield |= uhci_ctrl->rx_dir.on_rx_trans_event(uhci_ctrl, &evt_data, uhci_ctrl->user_data);
}
uhci_ctrl->rx_dir.node_index++;
// Go back to 0 as its a circle descriptor link
if (uhci_ctrl->rx_dir.node_index >= uhci_ctrl->rx_dir.rx_num_dma_nodes) {
uhci_ctrl->rx_dir.node_index = 0;
}
} else {
// eof event
size_t rx_size = gdma_link_count_buffer_size_till_eof(uhci_ctrl->rx_dir.dma_link, uhci_ctrl->rx_dir.node_index);
uhci_rx_event_data_t evt_data = {
.data = uhci_ctrl->rx_dir.buffer_pointers[uhci_ctrl->rx_dir.node_index],
.recv_size = rx_size,
.flags.totally_received = true,
};
bool need_cache_sync = esp_ptr_internal(uhci_ctrl->rx_dir.buffer_pointers[uhci_ctrl->rx_dir.node_index]) ? (uhci_ctrl->int_mem_cache_line_size > 0) : (uhci_ctrl->ext_mem_cache_line_size > 0);
size_t m2c_size = UHCI_ALIGN_UP(rx_size, uhci_ctrl->rx_dir.cache_line);
if (need_cache_sync) {
esp_cache_msync(uhci_ctrl->rx_dir.buffer_pointers[uhci_ctrl->rx_dir.node_index], m2c_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
}
// release power manager lock
if (uhci_ctrl->pm_lock) {
esp_pm_lock_release(uhci_ctrl->pm_lock);
}
if (uhci_ctrl->rx_dir.on_rx_trans_event) {
need_yield |= uhci_ctrl->rx_dir.on_rx_trans_event(uhci_ctrl, &evt_data, uhci_ctrl->user_data);
}
uhci_ctrl->rx_dir.rx_fsm = UHCI_RX_FSM_ENABLE;
}
if (event_data->flags.abnormal_eof) {
esp_rom_printf(DRAM_STR("An abnormal eof on uhci detected\n"));
}
return need_yield;
}
static esp_err_t uhci_gdma_initialize(uhci_controller_handle_t uhci_ctrl, const uhci_controller_config_t *config)
{
// Initialize DMA TX channel
gdma_channel_alloc_config_t tx_alloc_config = {
.direction = GDMA_CHANNEL_DIRECTION_TX,
#if CONFIG_UHCI_ISR_CACHE_SAFE
.flags.isr_cache_safe = true,
#endif
};
ESP_RETURN_ON_ERROR(gdma_new_ahb_channel(&tx_alloc_config, &uhci_ctrl->tx_dir.dma_chan), TAG, "DMA tx channel alloc failed");
gdma_connect(uhci_ctrl->tx_dir.dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_UHCI, 0));
gdma_transfer_config_t transfer_cfg = {
.access_ext_mem = true,
.max_data_burst_size = config->dma_burst_size,
};
ESP_RETURN_ON_ERROR(gdma_config_transfer(uhci_ctrl->tx_dir.dma_chan, &transfer_cfg), TAG, "Config DMA tx channel transfer failed");
gdma_strategy_config_t strategy_config = {
.auto_update_desc = true,
.owner_check = true,
.eof_till_data_popped = true,
};
gdma_apply_strategy(uhci_ctrl->tx_dir.dma_chan, &strategy_config);
// create DMA link list
gdma_get_alignment_constraints(uhci_ctrl->tx_dir.dma_chan, &uhci_ctrl->tx_dir.int_mem_align, &uhci_ctrl->tx_dir.ext_mem_align);
size_t buffer_alignment = UHCI_MAX(uhci_ctrl->tx_dir.int_mem_align, uhci_ctrl->tx_dir.ext_mem_align);
size_t num_dma_nodes = esp_dma_calculate_node_count(config->max_transmit_size, buffer_alignment, DMA_DESCRIPTOR_BUFFER_MAX_SIZE);
gdma_link_list_config_t dma_link_config = {
.buffer_alignment = buffer_alignment,
.item_alignment = 4,
.num_items = num_dma_nodes,
};
ESP_RETURN_ON_ERROR(gdma_new_link_list(&dma_link_config, &uhci_ctrl->tx_dir.dma_link), TAG, "DMA tx link list alloc failed");
ESP_LOGD(TAG, "tx_dma node number is %d", num_dma_nodes);
// Initialize DMA RX channel
gdma_channel_alloc_config_t rx_alloc_config = {
.direction = GDMA_CHANNEL_DIRECTION_RX,
.sibling_chan = uhci_ctrl->tx_dir.dma_chan,
#if CONFIG_UHCI_ISR_CACHE_SAFE
.flags.isr_cache_safe = true,
#endif
};
ESP_RETURN_ON_ERROR(gdma_new_ahb_channel(&rx_alloc_config, &uhci_ctrl->rx_dir.dma_chan), TAG, "DMA rx channel alloc failed");
gdma_connect(uhci_ctrl->rx_dir.dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_UHCI, 0));
ESP_RETURN_ON_ERROR(gdma_config_transfer(uhci_ctrl->rx_dir.dma_chan, &transfer_cfg), TAG, "Config DMA rx channel transfer failed");
gdma_get_alignment_constraints(uhci_ctrl->rx_dir.dma_chan, &uhci_ctrl->rx_dir.int_mem_align, &uhci_ctrl->rx_dir.ext_mem_align);
buffer_alignment = UHCI_MAX(uhci_ctrl->rx_dir.int_mem_align, uhci_ctrl->rx_dir.ext_mem_align);
uhci_ctrl->rx_dir.rx_num_dma_nodes = esp_dma_calculate_node_count(config->max_receive_internal_mem, buffer_alignment, DMA_DESCRIPTOR_BUFFER_MAX_SIZE);
dma_link_config.buffer_alignment = buffer_alignment;
dma_link_config.num_items = uhci_ctrl->rx_dir.rx_num_dma_nodes;
ESP_RETURN_ON_ERROR(gdma_new_link_list(&dma_link_config, &uhci_ctrl->rx_dir.dma_link), TAG, "DMA rx link list alloc failed");
ESP_LOGD(TAG, "rx_dma node number is %d", uhci_ctrl->rx_dir.rx_num_dma_nodes);
uhci_ctrl->rx_dir.buffer_size_per_desc_node = heap_caps_calloc(uhci_ctrl->rx_dir.rx_num_dma_nodes, sizeof(uhci_ctrl->rx_dir.buffer_size_per_desc_node), UHCI_MEM_ALLOC_CAPS);
ESP_RETURN_ON_FALSE(uhci_ctrl->rx_dir.buffer_size_per_desc_node, ESP_ERR_NO_MEM, TAG, "no memory for recording buffer size for desc node");
uhci_ctrl->rx_dir.buffer_pointers = heap_caps_calloc(uhci_ctrl->rx_dir.rx_num_dma_nodes, sizeof(*uhci_ctrl->rx_dir.buffer_pointers), UHCI_MEM_ALLOC_CAPS);
ESP_RETURN_ON_FALSE(uhci_ctrl->rx_dir.buffer_pointers, ESP_ERR_NO_MEM, TAG, "no memory for recording buffer pointers for desc node");
// Register callbacks
gdma_tx_event_callbacks_t tx_cbk = {
.on_trans_eof = uhci_gdma_tx_callback_eof,
};
ESP_RETURN_ON_ERROR(gdma_register_tx_event_callbacks(uhci_ctrl->tx_dir.dma_chan, &tx_cbk, uhci_ctrl), TAG, "register DMA callbacks failed");
gdma_rx_event_callbacks_t rx_cbk = {
.on_recv_done = uhci_gdma_rx_callback_done,
};
ESP_RETURN_ON_ERROR(gdma_register_rx_event_callbacks(uhci_ctrl->rx_dir.dma_chan, &rx_cbk, uhci_ctrl), TAG, "register DMA callbacks failed");
return ESP_OK;
}
static esp_err_t uhci_gdma_deinitialize(uhci_controller_handle_t uhci_ctrl)
{
if (uhci_ctrl->tx_dir.dma_chan) {
ESP_RETURN_ON_ERROR(gdma_disconnect(uhci_ctrl->tx_dir.dma_chan), TAG, "disconnect tx dma channel failed");
ESP_RETURN_ON_ERROR(gdma_del_channel(uhci_ctrl->tx_dir.dma_chan), TAG, "delete tx dma channel failed");
}
if (uhci_ctrl->rx_dir.dma_chan) {
ESP_RETURN_ON_ERROR(gdma_disconnect(uhci_ctrl->rx_dir.dma_chan), TAG, "disconnect rx dma channel failed");
ESP_RETURN_ON_ERROR(gdma_del_channel(uhci_ctrl->rx_dir.dma_chan), TAG, "delete rx dma channel failed");
}
if (uhci_ctrl->tx_dir.dma_link) {
ESP_RETURN_ON_ERROR(gdma_del_link_list(uhci_ctrl->tx_dir.dma_link), TAG, "dma delete link list failed");
}
if (uhci_ctrl->rx_dir.dma_link) {
ESP_RETURN_ON_ERROR(gdma_del_link_list(uhci_ctrl->rx_dir.dma_link), TAG, "dma delete link list failed");
}
return ESP_OK;
}
static void uhci_do_transmit(uhci_controller_handle_t uhci_ctrl, uhci_transaction_desc_t *trans)
{
uhci_ctrl->tx_dir.cur_trans = trans;
gdma_buffer_mount_config_t mount_config = {
.buffer = trans->buffer,
.length = trans->buffer_size,
.flags = {
.mark_eof = true,
.mark_final = true,
}
};
// acquire power manager lock
if (uhci_ctrl->pm_lock) {
esp_pm_lock_acquire(uhci_ctrl->pm_lock);
}
gdma_link_mount_buffers(uhci_ctrl->tx_dir.dma_link, 0, &mount_config, 1, NULL);
gdma_start(uhci_ctrl->tx_dir.dma_chan, gdma_link_get_head_addr(uhci_ctrl->tx_dir.dma_link));
}
esp_err_t uhci_receive(uhci_controller_handle_t uhci_ctrl, uint8_t *read_buffer, size_t buffer_size)
{
ESP_RETURN_ON_FALSE(uhci_ctrl, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE((read_buffer != NULL), ESP_ERR_INVALID_ARG, TAG, "read buffer null");
uint32_t mem_cache_line_size = esp_ptr_external_ram(read_buffer) ? uhci_ctrl->ext_mem_cache_line_size : uhci_ctrl->int_mem_cache_line_size;
// Must take cache line into consideration for C2M operation.
uint32_t max_alignment_needed = UHCI_MAX(UHCI_MAX(uhci_ctrl->rx_dir.int_mem_align, uhci_ctrl->rx_dir.ext_mem_align), mem_cache_line_size);
// Align the read_buffer pointer to mem_cache_line_size
if (max_alignment_needed > 0 && (((uintptr_t)read_buffer) & (max_alignment_needed - 1)) != 0) {
uintptr_t aligned_address = ((uintptr_t)read_buffer + max_alignment_needed - 1) & ~(max_alignment_needed - 1);
size_t offset = aligned_address - (uintptr_t)read_buffer;
ESP_RETURN_ON_FALSE(buffer_size > offset, ESP_ERR_INVALID_ARG, TAG, "buffer size too small to align");
read_buffer = (uint8_t *)aligned_address;
buffer_size -= offset;
}
uhci_ctrl->rx_dir.cache_line = mem_cache_line_size;
uhci_rx_fsm_t expected_fsm = UHCI_RX_FSM_ENABLE;
ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&uhci_ctrl->rx_dir.rx_fsm, &expected_fsm, UHCI_RX_FSM_RUN_WAIT), ESP_ERR_INVALID_STATE, TAG, "controller not in enable state");
size_t node_count = uhci_ctrl->rx_dir.rx_num_dma_nodes;
// Initialize the mount configurations for each DMA node, making sure every no
size_t usable_size = (max_alignment_needed == 0) ? buffer_size : (buffer_size / max_alignment_needed) * max_alignment_needed;
size_t base_size = (max_alignment_needed == 0) ? usable_size / node_count : (usable_size / node_count / max_alignment_needed) * max_alignment_needed;
size_t remaining_size = usable_size - (base_size * node_count);
gdma_buffer_mount_config_t mount_configs[node_count];
memset(mount_configs, 0, node_count);
for (size_t i = 0; i < node_count; i++) {
uhci_ctrl->rx_dir.buffer_size_per_desc_node[i] = base_size;
uhci_ctrl->rx_dir.buffer_pointers[i] = read_buffer;
// Distribute the remaining size to the first few nodes
if (remaining_size >= max_alignment_needed) {
uhci_ctrl->rx_dir.buffer_size_per_desc_node[i] += max_alignment_needed;
remaining_size -= max_alignment_needed;
}
mount_configs[i] = (gdma_buffer_mount_config_t) {
.buffer = read_buffer,
.length = uhci_ctrl->rx_dir.buffer_size_per_desc_node[i],
.flags = {
.mark_final = false,
}
};
ESP_LOGD(TAG, "The DMA node %d has %d byte", i, uhci_ctrl->rx_dir.buffer_size_per_desc_node[i]);
read_buffer += uhci_ctrl->rx_dir.buffer_size_per_desc_node[i];
}
// acquire power manager lock
if (uhci_ctrl->pm_lock) {
esp_pm_lock_acquire(uhci_ctrl->pm_lock);
}
gdma_link_mount_buffers(uhci_ctrl->rx_dir.dma_link, 0, mount_configs, node_count, NULL);
gdma_reset(uhci_ctrl->rx_dir.dma_chan);
gdma_start(uhci_ctrl->rx_dir.dma_chan, gdma_link_get_head_addr(uhci_ctrl->rx_dir.dma_link));
atomic_store(&uhci_ctrl->rx_dir.rx_fsm, UHCI_RX_FSM_RUN);
return ESP_OK;
}
esp_err_t uhci_transmit(uhci_controller_handle_t uhci_ctrl, uint8_t *write_buffer, size_t write_size)
{
ESP_RETURN_ON_FALSE(uhci_ctrl, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE((write_buffer != NULL), ESP_ERR_INVALID_ARG, TAG, "write buffer null");
size_t alignment = 0;
size_t cache_line_size = 0;
esp_ptr_external_ram(write_buffer) ? (alignment = uhci_ctrl->tx_dir.ext_mem_align, cache_line_size = uhci_ctrl->ext_mem_cache_line_size) : (alignment = uhci_ctrl->tx_dir.int_mem_align, cache_line_size = uhci_ctrl->int_mem_cache_line_size);
ESP_RETURN_ON_FALSE(((((uintptr_t)write_buffer) & (alignment - 1)) == 0) && (((write_size) & (alignment - 1)) == 0), ESP_ERR_INVALID_ARG,
TAG, "buffer address or size are not %d bytes aligned", alignment);
if (cache_line_size > 0) {
// Write back to cache to synchronize the cache before DMA start
ESP_RETURN_ON_ERROR(esp_cache_msync((void *)write_buffer, write_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED), TAG, "cache sync failed");
}
uhci_transaction_desc_t *t = NULL;
if (xQueueReceive(uhci_ctrl->tx_dir.trans_queues[UHCI_TRANS_QUEUE_READY], &t, 0) != pdTRUE) {
if (xQueueReceive(uhci_ctrl->tx_dir.trans_queues[UHCI_TRANS_QUEUE_COMPLETE], &t, 0) == pdTRUE) {
atomic_fetch_sub(&uhci_ctrl->tx_dir.num_trans_inflight, 1);
}
}
ESP_RETURN_ON_FALSE(t, ESP_ERR_INVALID_STATE, TAG, "no free transaction descriptor, please consider increasing trans_queue_depth");
memset(t, 0, sizeof(uhci_transaction_desc_t));
t->buffer = write_buffer;
t->buffer_size = write_size;
ESP_RETURN_ON_FALSE(xQueueSend(uhci_ctrl->tx_dir.trans_queues[UHCI_TRANS_QUEUE_PROGRESS], &t, 0) == pdTRUE, ESP_ERR_NO_MEM, TAG, "uhci tx transaction queue full");
atomic_fetch_add(&uhci_ctrl->tx_dir.num_trans_inflight, 1);
uhci_tx_fsm_t expected_fsm = UHCI_TX_FSM_ENABLE;
if (atomic_compare_exchange_strong(&uhci_ctrl->tx_dir.tx_fsm, &expected_fsm, UHCI_TX_FSM_RUN_WAIT)) {
if (xQueueReceive(uhci_ctrl->tx_dir.trans_queues[UHCI_TRANS_QUEUE_PROGRESS], &t, 0)) {
atomic_store(&uhci_ctrl->tx_dir.tx_fsm, UHCI_TX_FSM_RUN);
uhci_do_transmit(uhci_ctrl, t);
} else {
atomic_store(&uhci_ctrl->tx_dir.tx_fsm, UHCI_TX_FSM_ENABLE);
}
}
return ESP_OK;
}
esp_err_t uhci_del_controller(uhci_controller_handle_t uhci_ctrl)
{
ESP_RETURN_ON_FALSE(uhci_ctrl, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
if (uhci_ctrl->rx_dir.rx_fsm != UHCI_RX_FSM_ENABLE) {
ESP_LOGE(TAG, "RX transaction is not finished, delete controller failed");
return ESP_ERR_INVALID_STATE;
}
if (uhci_ctrl->tx_dir.tx_fsm != UHCI_TX_FSM_ENABLE) {
ESP_LOGE(TAG, "TX transaction is not finished, delete controller failed");
return ESP_ERR_INVALID_STATE;
}
UHCI_RCC_ATOMIC() {
uhci_ll_enable_bus_clock(uhci_ctrl->uhci_num, false);
}
for (int i = 0; i < UHCI_TRANS_QUEUE_MAX; i++) {
if (uhci_ctrl->tx_dir.trans_queues[i]) {
vQueueDeleteWithCaps(uhci_ctrl->tx_dir.trans_queues[i]);
}
}
if (uhci_ctrl->tx_dir.trans_desc_pool) {
heap_caps_free(uhci_ctrl->tx_dir.trans_desc_pool);
}
if (uhci_ctrl->rx_dir.buffer_size_per_desc_node) {
free(uhci_ctrl->rx_dir.buffer_size_per_desc_node);
}
if (uhci_ctrl->rx_dir.buffer_pointers) {
free(uhci_ctrl->rx_dir.buffer_pointers);
}
if (uhci_ctrl->pm_lock) {
ESP_RETURN_ON_ERROR(esp_pm_lock_delete(uhci_ctrl->pm_lock), TAG, "delete rx pm_lock failed");
}
ESP_RETURN_ON_ERROR(uhci_gdma_deinitialize(uhci_ctrl), TAG, "deinitialize uhci dam channel failed");
uhci_hal_deinit(&uhci_ctrl->hal);
s_uhci_platform.controller[uhci_ctrl->uhci_num] = NULL;
heap_caps_free(uhci_ctrl);
return ESP_OK;
}
esp_err_t uhci_new_controller(const uhci_controller_config_t *config, uhci_controller_handle_t *ret_uhci_ctrl)
{
ESP_RETURN_ON_FALSE((config && ret_uhci_ctrl), ESP_ERR_INVALID_ARG, TAG, "UHCI invalid argument");
esp_err_t ret = ESP_OK;
uhci_controller_handle_t uhci_ctrl = (uhci_controller_handle_t)heap_caps_calloc(1, sizeof(uhci_controller_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ESP_RETURN_ON_FALSE(uhci_ctrl, ESP_ERR_NO_MEM, TAG, "no mem for uhci controller handle");
atomic_init(&uhci_ctrl->tx_dir.tx_fsm, UHCI_TX_FSM_ENABLE);
atomic_init(&uhci_ctrl->rx_dir.rx_fsm, UHCI_TX_FSM_ENABLE);
// Auto search a free controller
bool ctrl_found = false;
_lock_acquire(&s_uhci_platform.mutex);
for (int i = 0; i < SOC_UHCI_NUM; i++) {
if (uhci_ctrl_occupied(i) == false) {
s_uhci_platform.controller[i] = uhci_ctrl;
ctrl_found = true;
uhci_ctrl->uhci_num = i;
break;
}
}
_lock_release(&s_uhci_platform.mutex);
ESP_GOTO_ON_FALSE(ctrl_found, ESP_ERR_NOT_FOUND, err, TAG, "no free controller");
for (int i = 0; i < UHCI_TRANS_QUEUE_MAX; i++) {
uhci_ctrl->tx_dir.trans_queues[i] = xQueueCreateWithCaps(config->tx_trans_queue_depth, sizeof(uhci_transaction_desc_t *), UHCI_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(uhci_ctrl->tx_dir.trans_queues[i], ESP_ERR_NO_MEM, err, TAG, "no mem for transaction queue");
}
uhci_ctrl->tx_dir.trans_desc_pool = heap_caps_calloc(config->tx_trans_queue_depth, sizeof(uhci_transaction_desc_t), UHCI_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(uhci_ctrl->tx_dir.trans_desc_pool, ESP_ERR_NO_MEM, err, TAG, "no mem for transaction desc pool");
uhci_transaction_desc_t *p_trans_desc = NULL;
for (int i = 0; i < config->tx_trans_queue_depth; i++) {
p_trans_desc = &uhci_ctrl->tx_dir.trans_desc_pool[i];
xQueueSend(uhci_ctrl->tx_dir.trans_queues[UHCI_TRANS_QUEUE_READY], &p_trans_desc, 0);
}
UHCI_RCC_ATOMIC() {
uhci_ll_enable_bus_clock(uhci_ctrl->uhci_num, true);
uhci_ll_reset_register(uhci_ctrl->uhci_num);
}
uhci_hal_init(&uhci_ctrl->hal, uhci_ctrl->uhci_num);
uhci_ll_attach_uart_port(uhci_ctrl->hal.dev, config->uart_port);
#if CONFIG_PM_ENABLE
esp_pm_lock_type_t pm_lock_type = ESP_PM_NO_LIGHT_SLEEP;
sprintf(uhci_ctrl->pm_lock_name, "uhci_%d", uhci_ctrl->uhci_num); // e.g. uhci_0
ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_lock_type, 0, uhci_ctrl->pm_lock_name, &uhci_ctrl->pm_lock), err, TAG, "create pm lock failed");
#endif
// Must set separate character as 0, otherwise uhci will lose data.
uhci_seper_chr_t seper_chr = {
.sub_chr_en = 0,
};
uhci_ll_set_seper_chr(uhci_ctrl->hal.dev, &seper_chr);
if (config->rx_eof_flags.idle_eof) {
uhci_ll_rx_set_eof_mode(uhci_ctrl->hal.dev, UHCI_RX_IDLE_EOF);
}
if (config->rx_eof_flags.length_eof) {
uhci_ll_rx_set_eof_mode(uhci_ctrl->hal.dev, UHCI_RX_LEN_EOF);
}
if (config->rx_eof_flags.rx_brk_eof) {
uhci_ll_rx_set_eof_mode(uhci_ctrl->hal.dev, UHCI_RX_BREAK_CHR_EOF);
}
esp_cache_get_alignment(MALLOC_CAP_SPIRAM, &uhci_ctrl->ext_mem_cache_line_size);
esp_cache_get_alignment(MALLOC_CAP_INTERNAL, &uhci_ctrl->int_mem_cache_line_size);
ESP_GOTO_ON_ERROR(uhci_gdma_initialize(uhci_ctrl, config), err, TAG, "uhci gdma initialize failed");
*ret_uhci_ctrl = uhci_ctrl;
return ESP_OK;
err:
uhci_del_controller(uhci_ctrl);
return ret;
}
esp_err_t uhci_register_event_callbacks(uhci_controller_handle_t uhci_ctrl, const uhci_event_callbacks_t *cbs, void *user_data)
{
ESP_RETURN_ON_FALSE(uhci_ctrl && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
#if CONFIG_UHCI_ISR_CACHE_SAFE
if (cbs->on_rx_trans_event) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_rx_trans_event), ESP_ERR_INVALID_ARG, TAG, "on_rx_trans_event callback not in IRAM");
}
if (cbs->on_tx_trans_done) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_tx_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_tx_trans_done callback not in IRAM");
}
if (user_data) {
ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
}
#endif
uhci_ctrl->rx_dir.on_rx_trans_event = cbs->on_rx_trans_event;
uhci_ctrl->tx_dir.on_tx_trans_done = cbs->on_tx_trans_done;
uhci_ctrl->user_data = user_data;
return ESP_OK;
}
esp_err_t uhci_wait_all_tx_transaction_done(uhci_controller_handle_t uhci_ctrl, int timeout_ms)
{
ESP_RETURN_ON_FALSE(uhci_ctrl, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
TickType_t wait_ticks = timeout_ms < 0 ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
uhci_transaction_desc_t *t = NULL;
size_t cnt = uhci_ctrl->tx_dir.num_trans_inflight;
for (size_t i = 0; i < cnt; i++) {
ESP_RETURN_ON_FALSE(xQueueReceive(uhci_ctrl->tx_dir.trans_queues[UHCI_TRANS_QUEUE_COMPLETE], &t, wait_ticks) == pdTRUE,
ESP_ERR_TIMEOUT, TAG, "flush timeout");
ESP_RETURN_ON_FALSE(xQueueSend(uhci_ctrl->tx_dir.trans_queues[UHCI_TRANS_QUEUE_READY], &t, 0) == pdTRUE,
ESP_ERR_INVALID_STATE, TAG, "ready queue full");
atomic_fetch_sub(&uhci_ctrl->tx_dir.num_trans_inflight, 1);
}
return ESP_OK;
}
#if CONFIG_UHCI_ENABLE_DEBUG_LOG
__attribute__((constructor))
static void uhci_override_default_log_level(void)
{
esp_log_level_set(TAG, ESP_LOG_VERBOSE);
}
#endif

View File

@@ -0,0 +1,114 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdatomic.h>
#include <sys/queue.h>
#include "esp_dma_utils.h"
#include "esp_private/gdma.h"
#include "esp_private/gdma_link.h"
#include "esp_private/periph_ctrl.h"
#include "esp_pm.h"
#include "sdkconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct uhci_controller_t uhci_controller_t;
#define UHCI_ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
#define UHCI_MAX(a, b) (((a)>(b))?(a):(b))
#define UHCI_PM_LOCK_NAME_LEN_MAX 16
#if CONFIG_UHCI_ISR_HANDLER_IN_IRAM
#define UHCI_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
#else
#define UHCI_MEM_ALLOC_CAPS (MALLOC_CAP_DEFAULT)
#endif
#if SOC_PERIPH_CLK_CTRL_SHARED
#define UHCI_CLOCK_SRC_ATOMIC() PERIPH_RCC_ATOMIC()
#else
#define UHCI_CLOCK_SRC_ATOMIC()
#endif
#if !SOC_RCC_IS_INDEPENDENT
#define UHCI_RCC_ATOMIC() PERIPH_RCC_ATOMIC()
#else
#define UHCI_RCC_ATOMIC()
#endif
typedef struct {
void *buffer; // buffer for saving the received symbols
size_t buffer_size; // size of the buffer, in bytes
} uhci_transaction_desc_t;
typedef enum {
UHCI_TX_FSM_ENABLE_WAIT, /**< FSM is waiting to enable the UHCI system. */
UHCI_TX_FSM_ENABLE, /**< FSM is enabling the UHCI system. */
UHCI_TX_FSM_RUN_WAIT, /**< FSM is waiting to transition to the running state. */
UHCI_TX_FSM_RUN, /**< FSM is in the running state, actively handling UHCI operations. */
} uhci_tx_fsm_t;
typedef enum {
UHCI_TRANS_QUEUE_READY, /**< The transaction queue is ready to accept new transactions. */
UHCI_TRANS_QUEUE_PROGRESS, /**< A transaction is currently in progress. */
UHCI_TRANS_QUEUE_COMPLETE, /**< All transactions in the queue are completed. */
UHCI_TRANS_QUEUE_MAX, /**< Placeholder for the maximum number of states (not a valid state). */
} uhci_trans_queue_enum_t;
typedef enum {
UHCI_RX_FSM_ENABLE_WAIT, /**< FSM is waiting to enable the UHCI system. */
UHCI_RX_FSM_ENABLE, /**< FSM is enabling the UHCI system. */
UHCI_RX_FSM_RUN_WAIT, /**< FSM is waiting to transition to the running state. */
UHCI_RX_FSM_RUN, /**< FSM is in the running state, actively handling UHCI operations. */
} uhci_rx_fsm_t;
typedef struct {
gdma_channel_handle_t dma_chan; // GDMA channel
uhci_tx_done_callback_t on_tx_trans_done; // tx transaction done callback function
gdma_link_list_handle_t dma_link; // GDMA link handle
uhci_transaction_desc_t *trans_desc_pool; // transaction descriptor pool
uhci_transaction_desc_t *cur_trans; // pointer to current transaction
QueueHandle_t trans_queues[UHCI_TRANS_QUEUE_MAX]; // transaction queue
_Atomic uhci_tx_fsm_t tx_fsm; // channel life cycle specific FSM
size_t int_mem_align; // Alignment for internal memory
size_t ext_mem_align; // Alignment for external memory
atomic_int num_trans_inflight; // Indicates the number of transactions that are undergoing but not recycled to ready_queue
} uhci_tx_dir;
typedef struct {
gdma_channel_handle_t dma_chan; // GDMA channel
uhci_rx_event_callback_t on_rx_trans_event; // rx transaction done callback function
gdma_link_list_handle_t dma_link; // GDMA link handle
size_t *buffer_size_per_desc_node; // Pointer to buffer size per dma descriptor node
size_t node_index; // node index in receive
uint8_t **buffer_pointers; // Pointer for saving buffer pointer
_Atomic uhci_rx_fsm_t rx_fsm; // channel life cycle specific FSM
size_t cache_line; // cache line size need to be aligned up.
size_t int_mem_align; // Alignment for internal memory
size_t ext_mem_align; // Alignment for external memory
size_t rx_num_dma_nodes; // rx dma number nodes
} uhci_rx_dir;
struct uhci_controller_t {
int uhci_num; // UHCI port number
uhci_hal_context_t hal; // uhci hal layer context
uhci_tx_dir tx_dir; // tx direction structure
uhci_rx_dir rx_dir; // rx direction structure
void *user_data; // user data
size_t int_mem_cache_line_size; // internal memory cache line size
size_t ext_mem_cache_line_size; // external memory cache line size
esp_pm_lock_handle_t pm_lock; // power management lock
char pm_lock_name[UHCI_PM_LOCK_NAME_LEN_MAX]; // pm lock name
};
#ifdef __cplusplus
}
#endif

View File

@@ -28,3 +28,9 @@ components/esp_driver_uart/test_apps/uart_vfs:
depends_components:
- esp_driver_uart
- vfs
components/esp_driver_uart/test_apps/uhci:
disable:
- if: SOC_UHCI_SUPPORTED != 1
depends_components:
- esp_driver_uart

View File

@@ -126,6 +126,10 @@ elseif(NOT BOOTLOADER_BUILD)
list(APPEND srcs "mcpwm_hal.c")
endif()
if(CONFIG_SOC_UHCI_SUPPORTED)
list(APPEND srcs "uhci_hal.c")
endif()
if(CONFIG_SOC_TWAI_SUPPORTED)
list(APPEND srcs "twai_hal.c" "twai_hal_iram.c")
endif()

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -12,6 +12,7 @@
#include "hal/uhci_types.h"
#include "soc/uhci_struct.h"
#include "soc/system_struct.h"
#include "hal/misc.h"
#ifdef __cplusplus
extern "C" {
@@ -77,27 +78,22 @@ static inline void uhci_ll_attach_uart_port(uhci_dev_t *hw, int uart_num)
static inline void uhci_ll_set_seper_chr(uhci_dev_t *hw, uhci_seper_chr_t *seper_char)
{
if (seper_char->sub_chr_en) {
hw->conf0.seper_en = 1;
typeof(hw->esc_conf0) esc_conf0_reg;
esc_conf0_reg.val = hw->esc_conf0.val;
esc_conf0_reg.seper_char = seper_char->seper_chr;
esc_conf0_reg.seper_esc_char0 = seper_char->sub_chr1;
esc_conf0_reg.seper_esc_char1 = seper_char->sub_chr2;
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf0_reg, seper_char, seper_char->seper_chr);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf0_reg, seper_esc_char0, seper_char->sub_chr1);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf0_reg, seper_esc_char1, seper_char->sub_chr2);
hw->esc_conf0.val = esc_conf0_reg.val;
hw->escape_conf.tx_c0_esc_en = 1;
hw->escape_conf.rx_c0_esc_en = 1;
} else {
hw->escape_conf.tx_c0_esc_en = 0;
hw->escape_conf.rx_c0_esc_en = 0;
hw->conf0.seper_en = 0;
hw->escape_conf.val = 0;
}
}
static inline void uhci_ll_get_seper_chr(uhci_dev_t *hw, uhci_seper_chr_t *seper_chr)
{
(void)hw;
(void)seper_chr;
}
static inline void uhci_ll_set_swflow_ctrl_sub_chr(uhci_dev_t *hw, uhci_swflow_ctrl_sub_chr_t *sub_ctr)
{
typeof(hw->escape_conf) escape_conf_reg;
@@ -109,12 +105,12 @@ static inline void uhci_ll_set_swflow_ctrl_sub_chr(uhci_dev_t *hw, uhci_swflow_c
typeof(hw->esc_conf3) esc_conf3_reg;
esc_conf3_reg.val = hw->esc_conf3.val;
esc_conf2_reg.seq1 = sub_ctr->xon_chr;
esc_conf2_reg.seq1_char0 = sub_ctr->xon_sub1;
esc_conf2_reg.seq1_char1 = sub_ctr->xon_sub2;
esc_conf3_reg.seq2 = sub_ctr->xoff_chr;
esc_conf3_reg.seq2_char0 = sub_ctr->xoff_sub1;
esc_conf3_reg.seq2_char1 = sub_ctr->xoff_sub2;
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf2_reg, seq1, sub_ctr->xon_chr);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf2_reg, seq1_char0, sub_ctr->xon_sub1);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf2_reg, seq1_char1, sub_ctr->xon_sub2);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf3_reg, seq2, sub_ctr->xoff_chr);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf3_reg, seq2_char0, sub_ctr->xoff_sub1);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf3_reg, seq2_char1, sub_ctr->xoff_sub2);
escape_conf_reg.tx_11_esc_en = 1;
escape_conf_reg.tx_13_esc_en = 1;
escape_conf_reg.rx_11_esc_en = 1;
@@ -150,7 +146,7 @@ static inline uint32_t uhci_ll_get_intr(uhci_dev_t *hw)
return hw->int_st.val;
}
static inline void uhci_ll_set_eof_mode(uhci_dev_t *hw, uint32_t eof_mode)
static inline void uhci_ll_rx_set_eof_mode(uhci_dev_t *hw, uint32_t eof_mode)
{
if (eof_mode & UHCI_RX_BREAK_CHR_EOF) {
hw->conf0.uart_rx_brk_eof_en = 1;

View File

@@ -1,13 +1,18 @@
/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// The LL layer for UHCI register operations.
// Note that most of the register operations in this layer are non-atomic operations.
#pragma once
#include <stdio.h>
#include "hal/uhci_types.h"
#include "soc/uhci_struct.h"
#include "soc/pcr_struct.h"
#include "hal/misc.h"
#ifdef __cplusplus
extern "C" {
@@ -22,6 +27,30 @@ typedef enum {
UHCI_RX_EOF_MAX = 0x7,
} uhci_rxeof_cfg_t;
/**
* @brief Enable the bus clock for UHCI module
*
* @param group_id Group ID
* @param enable true to enable, false to disable
*/
static inline void uhci_ll_enable_bus_clock(int group_id, bool enable)
{
(void)group_id;
PCR.uhci_conf.uhci_clk_en = enable;
}
/**
* @brief Reset the UHCI module
*
* @param group_id Group ID
*/
static inline void uhci_ll_reset_register(int group_id)
{
(void)group_id;
PCR.uhci_conf.uhci_rst_en = 1;
PCR.uhci_conf.uhci_rst_en = 0;
}
static inline void uhci_ll_init(uhci_dev_t *hw)
{
typeof(hw->conf0) conf0_reg;
@@ -34,34 +63,29 @@ static inline void uhci_ll_init(uhci_dev_t *hw)
static inline void uhci_ll_attach_uart_port(uhci_dev_t *hw, int uart_num)
{
hw->conf0.uart0_ce = (uart_num == 0)? 1: 0;
hw->conf0.uart1_ce = (uart_num == 1)? 1: 0;
hw->conf0.uart0_ce = (uart_num == 0) ? 1 : 0;
hw->conf0.uart1_ce = (uart_num == 1) ? 1 : 0;
}
static inline void uhci_ll_set_seper_chr(uhci_dev_t *hw, uhci_seper_chr_t *seper_char)
{
if (seper_char->sub_chr_en) {
hw->conf0.seper_en = 1;
typeof(hw->esc_conf0) esc_conf0_reg;
esc_conf0_reg.val = hw->esc_conf0.val;
esc_conf0_reg.seper_char = seper_char->seper_chr;
esc_conf0_reg.seper_esc_char0 = seper_char->sub_chr1;
esc_conf0_reg.seper_esc_char1 = seper_char->sub_chr2;
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf0_reg, seper_char, seper_char->seper_chr);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf0_reg, seper_esc_char0, seper_char->sub_chr1);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf0_reg, seper_esc_char1, seper_char->sub_chr2);
hw->esc_conf0.val = esc_conf0_reg.val;
hw->escape_conf.tx_c0_esc_en = 1;
hw->escape_conf.rx_c0_esc_en = 1;
} else {
hw->escape_conf.tx_c0_esc_en = 0;
hw->escape_conf.rx_c0_esc_en = 0;
hw->conf0.seper_en = 0;
hw->escape_conf.val = 0;
}
}
static inline void uhci_ll_get_seper_chr(uhci_dev_t *hw, uhci_seper_chr_t *seper_chr)
{
(void)hw;
(void)seper_chr;
}
static inline void uhci_ll_set_swflow_ctrl_sub_chr(uhci_dev_t *hw, uhci_swflow_ctrl_sub_chr_t *sub_ctr)
{
typeof(hw->escape_conf) escape_conf_reg;
@@ -73,12 +97,12 @@ static inline void uhci_ll_set_swflow_ctrl_sub_chr(uhci_dev_t *hw, uhci_swflow_c
typeof(hw->esc_conf3) esc_conf3_reg;
esc_conf3_reg.val = hw->esc_conf3.val;
esc_conf2_reg.esc_seq1 = sub_ctr->xon_chr;
esc_conf2_reg.esc_seq1_char0 = sub_ctr->xon_sub1;
esc_conf2_reg.esc_seq1_char1 = sub_ctr->xon_sub2;
esc_conf3_reg.esc_seq2 = sub_ctr->xoff_chr;
esc_conf3_reg.esc_seq2_char0 = sub_ctr->xoff_sub1;
esc_conf3_reg.esc_seq2_char1 = sub_ctr->xoff_sub2;
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf2_reg, esc_seq1, sub_ctr->xon_chr);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf2_reg, esc_seq1_char0, sub_ctr->xon_sub1);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf2_reg, esc_seq1_char1, sub_ctr->xon_sub2);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf3_reg, esc_seq2, sub_ctr->xoff_chr);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf3_reg, esc_seq2_char0, sub_ctr->xoff_sub1);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf3_reg, esc_seq2_char1, sub_ctr->xoff_sub2);
escape_conf_reg.tx_11_esc_en = 1;
escape_conf_reg.tx_13_esc_en = 1;
escape_conf_reg.rx_11_esc_en = 1;
@@ -114,8 +138,7 @@ static inline uint32_t uhci_ll_get_intr(uhci_dev_t *hw)
return hw->int_st.val;
}
static inline void uhci_ll_set_eof_mode(uhci_dev_t *hw, uint32_t eof_mode)
static inline void uhci_ll_rx_set_eof_mode(uhci_dev_t *hw, uint32_t eof_mode)
{
if (eof_mode & UHCI_RX_BREAK_CHR_EOF) {
hw->conf0.uart_rx_brk_eof_en = 1;

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -8,6 +8,7 @@
#include <stdio.h>
#include "hal/uhci_types.h"
#include "soc/uhci_struct.h"
#include "hal/misc.h"
#ifdef __cplusplus
extern "C" {
@@ -41,27 +42,22 @@ static inline void uhci_ll_attach_uart_port(uhci_dev_t *hw, int uart_num)
static inline void uhci_ll_set_seper_chr(uhci_dev_t *hw, uhci_seper_chr_t *seper_char)
{
if (seper_char->sub_chr_en) {
hw->conf0.seper_en = 1;
typeof(hw->esc_conf0) esc_conf0_reg;
esc_conf0_reg.val = hw->esc_conf0.val;
esc_conf0_reg.seper_char = seper_char->seper_chr;
esc_conf0_reg.seper_esc_char0 = seper_char->sub_chr1;
esc_conf0_reg.seper_esc_char1 = seper_char->sub_chr2;
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf0_reg, seper_char, seper_char->seper_chr);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf0_reg, seper_esc_char0, seper_char->sub_chr1);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf0_reg, seper_esc_char1, seper_char->sub_chr2);
hw->esc_conf0.val = esc_conf0_reg.val;
hw->escape_conf.tx_c0_esc_en = 1;
hw->escape_conf.rx_c0_esc_en = 1;
} else {
hw->escape_conf.tx_c0_esc_en = 0;
hw->escape_conf.rx_c0_esc_en = 0;
hw->conf0.seper_en = 0;
hw->escape_conf.val = 0;
}
}
static inline void uhci_ll_get_seper_chr(uhci_dev_t *hw, uhci_seper_chr_t *seper_chr)
{
(void)hw;
(void)seper_chr;
}
static inline void uhci_ll_set_swflow_ctrl_sub_chr(uhci_dev_t *hw, uhci_swflow_ctrl_sub_chr_t *sub_ctr)
{
typeof(hw->escape_conf) escape_conf_reg;
@@ -115,7 +111,7 @@ static inline uint32_t uhci_ll_get_intr(uhci_dev_t *hw)
}
static inline void uhci_ll_set_eof_mode(uhci_dev_t *hw, uint32_t eof_mode)
static inline void uhci_ll_rx_set_eof_mode(uhci_dev_t *hw, uint32_t eof_mode)
{
if (eof_mode & UHCI_RX_BREAK_CHR_EOF) {
hw->conf0.uart_rx_brk_eof_en = 1;

View File

@@ -0,0 +1,112 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// The LL layer for UHCI register operations.
// Note that most of the register operations in this layer are non-atomic operations.
#pragma once
#include <stdio.h>
#include "hal/uhci_types.h"
#include "soc/uhci_struct.h"
#include "soc/hp_sys_clkrst_struct.h"
#include "hal/misc.h"
#ifdef __cplusplus
extern "C" {
#endif
#define UHCI_LL_GET_HW(num) (((num) == 0) ? (&UHCI0) : (NULL))
typedef enum {
UHCI_RX_BREAK_CHR_EOF = 0x1,
UHCI_RX_IDLE_EOF = 0x2,
UHCI_RX_LEN_EOF = 0x4,
UHCI_RX_EOF_MAX = 0x7,
} uhci_rxeof_cfg_t;
/**
* @brief Enable the bus clock for UHCI module
*
* @param group_id Group ID
* @param enable true to enable, false to disable
*/
static inline void _uhci_ll_enable_bus_clock(int group_id, bool enable)
{
(void)group_id;
HP_SYS_CLKRST.soc_clk_ctrl2.reg_uhci_apb_clk_en = enable;
HP_SYS_CLKRST.soc_clk_ctrl1.reg_uhci_sys_clk_en = enable;
}
/// 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 uhci_ll_enable_bus_clock(...) (void)__DECLARE_RCC_ATOMIC_ENV; _uhci_ll_enable_bus_clock(__VA_ARGS__)
/**
* @brief Reset the UHCI module
*
* @param group_id Group ID
*/
static inline void _uhci_ll_reset_register(int group_id)
{
(void)group_id;
HP_SYS_CLKRST.hp_rst_en1.reg_rst_en_uhci = 1;
HP_SYS_CLKRST.hp_rst_en1.reg_rst_en_uhci = 0;
}
/// 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 uhci_ll_reset_register(...) (void)__DECLARE_RCC_ATOMIC_ENV; _uhci_ll_reset_register(__VA_ARGS__)
static inline void uhci_ll_init(uhci_dev_t *hw)
{
typeof(hw->conf0) conf0_reg;
hw->conf0.clk_en = 1;
conf0_reg.val = 0;
conf0_reg.clk_en = 1;
hw->conf0.val = conf0_reg.val;
hw->conf1.val = 0;
}
static inline void uhci_ll_attach_uart_port(uhci_dev_t *hw, int uart_num)
{
hw->conf0.uart_sel = uart_num;
}
static inline void uhci_ll_set_seper_chr(uhci_dev_t *hw, uhci_seper_chr_t *seper_char)
{
if (seper_char->sub_chr_en) {
hw->conf0.seper_en = 1;
typeof(hw->esc_conf0) esc_conf0_reg;
esc_conf0_reg.val = hw->esc_conf0.val;
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf0_reg, seper_char, seper_char->seper_chr);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf0_reg, seper_esc_char0, seper_char->sub_chr1);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf0_reg, seper_esc_char1, seper_char->sub_chr2);
hw->esc_conf0.val = esc_conf0_reg.val;
hw->escape_conf.tx_c0_esc_en = 1;
hw->escape_conf.rx_c0_esc_en = 1;
} else {
hw->conf0.seper_en = 0;
hw->escape_conf.val = 0;
}
}
static inline void uhci_ll_rx_set_eof_mode(uhci_dev_t *hw, uint32_t eof_mode)
{
if (eof_mode & UHCI_RX_BREAK_CHR_EOF) {
hw->conf0.uart_rx_brk_eof_en = 1;
}
if (eof_mode & UHCI_RX_IDLE_EOF) {
hw->conf0.uart_idle_eof_en = 1;
}
if (eof_mode & UHCI_RX_LEN_EOF) {
hw->conf0.len_eof_en = 1;
}
}
#ifdef __cplusplus
}
#endif

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -12,6 +12,7 @@
#include "hal/uhci_types.h"
#include "soc/uhci_struct.h"
#include "soc/system_struct.h"
#include "hal/misc.h"
#ifdef __cplusplus
extern "C" {
@@ -78,27 +79,22 @@ static inline void uhci_ll_attach_uart_port(uhci_dev_t *hw, int uart_num)
static inline void uhci_ll_set_seper_chr(uhci_dev_t *hw, uhci_seper_chr_t *seper_char)
{
if (seper_char->sub_chr_en) {
hw->conf0.seper_en = 1;
typeof(hw->esc_conf0) esc_conf0_reg;
esc_conf0_reg.val = hw->esc_conf0.val;
esc_conf0_reg.seper_char = seper_char->seper_chr;
esc_conf0_reg.seper_esc_char0 = seper_char->sub_chr1;
esc_conf0_reg.seper_esc_char1 = seper_char->sub_chr2;
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf0_reg, seper_char, seper_char->seper_chr);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf0_reg, seper_esc_char0, seper_char->sub_chr1);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf0_reg, seper_esc_char1, seper_char->sub_chr2);
hw->esc_conf0.val = esc_conf0_reg.val;
hw->escape_conf.tx_c0_esc_en = 1;
hw->escape_conf.rx_c0_esc_en = 1;
} else {
hw->escape_conf.tx_c0_esc_en = 0;
hw->escape_conf.rx_c0_esc_en = 0;
hw->conf0.seper_en = 0;
hw->escape_conf.val = 0;
}
}
static inline void uhci_ll_get_seper_chr(uhci_dev_t *hw, uhci_seper_chr_t *seper_chr)
{
(void)hw;
(void)seper_chr;
}
static inline void uhci_ll_set_swflow_ctrl_sub_chr(uhci_dev_t *hw, uhci_swflow_ctrl_sub_chr_t *sub_ctr)
{
typeof(hw->escape_conf) escape_conf_reg;
@@ -110,12 +106,12 @@ static inline void uhci_ll_set_swflow_ctrl_sub_chr(uhci_dev_t *hw, uhci_swflow_c
typeof(hw->esc_conf3) esc_conf3_reg;
esc_conf3_reg.val = hw->esc_conf3.val;
esc_conf2_reg.seq1 = sub_ctr->xon_chr;
esc_conf2_reg.seq1_char0 = sub_ctr->xon_sub1;
esc_conf2_reg.seq1_char1 = sub_ctr->xon_sub2;
esc_conf3_reg.seq2 = sub_ctr->xoff_chr;
esc_conf3_reg.seq2_char0 = sub_ctr->xoff_sub1;
esc_conf3_reg.seq2_char1 = sub_ctr->xoff_sub2;
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf2_reg, seq1, sub_ctr->xon_chr);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf2_reg, seq1_char0, sub_ctr->xon_sub1);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf2_reg, seq1_char1, sub_ctr->xon_sub2);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf3_reg, seq2, sub_ctr->xoff_chr);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf3_reg, seq2_char0, sub_ctr->xoff_sub1);
HAL_FORCE_MODIFY_U32_REG_FIELD(esc_conf3_reg, seq2_char1, sub_ctr->xoff_sub2);
escape_conf_reg.tx_11_esc_en = 1;
escape_conf_reg.tx_13_esc_en = 1;
escape_conf_reg.rx_11_esc_en = 1;
@@ -151,7 +147,7 @@ static inline uint32_t uhci_ll_get_intr(uhci_dev_t *hw)
return hw->int_st.val;
}
static inline void uhci_ll_set_eof_mode(uhci_dev_t *hw, uint32_t eof_mode)
static inline void uhci_ll_rx_set_eof_mode(uhci_dev_t *hw, uint32_t eof_mode)
{
if (eof_mode & UHCI_RX_BREAK_CHR_EOF) {
hw->conf0.uart_rx_brk_eof_en = 1;

View File

@@ -0,0 +1,59 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*******************************************************************************
* NOTICE
* The hal is not public api, don't use in application code.
* See readme.md in hal/readme.md
******************************************************************************/
// The HAL layer for UHCI.
// There is no parameter check in the hal layer, so the caller must ensure the correctness of the parameters.
#pragma once
#include "hal/uhci_types.h"
#include "soc/soc_caps.h"
#ifdef __cplusplus
extern "C" {
#endif
#if SOC_UHCI_SUPPORTED
typedef struct uhci_dev_t *uhci_soc_handle_t; // UHCI SOC layer handle
/**
* Context that should be maintained by both the driver and the HAL
*/
typedef struct {
uhci_soc_handle_t dev;
} uhci_hal_context_t;
/**
* @brief Init UHCI hardware instance.
*
* @param hal Context of the HAL layer
* @param uhci_num UHCI number
*
* @return None
*/
void uhci_hal_init(uhci_hal_context_t *hal, int uhci_num);
/**
* @brief Deinit UHCI hardware instance.
*
* @param hal Context of the HAL layer
* @param uhci_num UHCI number
*
* @return None
*/
void uhci_hal_deinit(uhci_hal_context_t *hal);
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -1,14 +1,9 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// Though the UHCI driver hasn't been published, some types are defined here
// for users to develop over the HAL. See example: controller_hci_uart_esp32c3
#pragma once
#include <stdint.h>

20
components/hal/uhci_hal.c Normal file
View File

@@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// The HAL layer for UHCI (common part)
#include "hal/uhci_hal.h"
#include "hal/uhci_ll.h"
void uhci_hal_init(uhci_hal_context_t *hal, int uhci_num)
{
hal->dev = UHCI_LL_GET_HW(uhci_num);
uhci_ll_init(hal->dev);
}
void uhci_hal_deinit(uhci_hal_context_t *hal)
{
hal->dev = NULL;
}

View File

@@ -19,6 +19,10 @@ config SOC_GDMA_SUPPORTED
bool
default y
config SOC_UHCI_SUPPORTED
bool
default y
config SOC_AHB_GDMA_SUPPORTED
bool
default y
@@ -1015,6 +1019,10 @@ config SOC_UART_WAKEUP_SUPPORT_ACTIVE_THRESH_MODE
bool
default y
config SOC_UHCI_NUM
int
default 1
config SOC_COEX_HW_PTI
bool
default y

View File

@@ -21,6 +21,7 @@
#define SOC_DEDICATED_GPIO_SUPPORTED 1
#define SOC_UART_SUPPORTED 1
#define SOC_GDMA_SUPPORTED 1
#define SOC_UHCI_SUPPORTED 1
#define SOC_AHB_GDMA_SUPPORTED 1
#define SOC_GPTIMER_SUPPORTED 1
#define SOC_TWAI_SUPPORTED 1
@@ -424,6 +425,9 @@
#define SOC_UART_WAKEUP_SUPPORT_ACTIVE_THRESH_MODE (1)
/*--------------------------- UHCI CAPS -------------------------------------*/
#define SOC_UHCI_NUM (1UL)
/*-------------------------- COEXISTENCE HARDWARE PTI CAPS -------------------------------*/
#define SOC_COEX_HW_PTI (1)

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -10,8 +10,8 @@
extern "C" {
#endif
typedef volatile struct uhci_dev_s {
union {
typedef struct uhci_dev_t {
volatile union {
struct {
uint32_t tx_rst: 1; /*Write 1 then write 0 to this bit to reset decode state machine.*/
uint32_t rx_rst: 1; /*Write 1 then write 0 to this bit to reset encode state machine.*/
@@ -30,7 +30,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} conf0;
union {
volatile union {
struct {
uint32_t rx_start: 1; /*a*/
uint32_t tx_start: 1; /*a*/
@@ -45,7 +45,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} int_raw;
union {
volatile union {
struct {
uint32_t rx_start: 1; /*a*/
uint32_t tx_start: 1; /*a*/
@@ -60,7 +60,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} int_st;
union {
volatile union {
struct {
uint32_t rx_start: 1; /*a*/
uint32_t tx_start: 1; /*a*/
@@ -75,7 +75,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} int_ena;
union {
volatile union {
struct {
uint32_t rx_start: 1; /*a*/
uint32_t tx_start: 1; /*a*/
@@ -90,7 +90,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} int_clr;
union {
volatile union {
struct {
uint32_t check_sum_en: 1; /*a*/
uint32_t check_seq_en: 1; /*a*/
@@ -106,7 +106,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} conf1;
union {
volatile union {
struct {
uint32_t rx_err_cause: 3; /*a*/
uint32_t decode_state: 3; /*a*/
@@ -114,14 +114,14 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} state0;
union {
volatile union {
struct {
uint32_t encode_state: 3; /*a*/
uint32_t reserved3: 29;
};
uint32_t val;
} state1;
union {
volatile union {
struct {
uint32_t tx_c0_esc_en: 1; /*a*/
uint32_t tx_db_esc_en: 1; /*a*/
@@ -135,7 +135,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} escape_conf;
union {
volatile union {
struct {
uint32_t txfifo_timeout: 8; /*a*/
uint32_t txfifo_timeout_shift: 3; /*a*/
@@ -147,7 +147,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} hung_conf;
union {
volatile union {
struct {
uint32_t ack_num: 3;
uint32_t ack_num_load: 1; /*a*/
@@ -156,7 +156,7 @@ typedef volatile struct uhci_dev_s {
uint32_t val;
} ack_num;
uint32_t rx_head; /*a*/
union {
volatile union {
struct {
uint32_t single_send_num: 3; /*a*/
uint32_t single_send_en: 1; /*a*/
@@ -166,10 +166,10 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} quick_sent;
struct {
volatile struct {
uint32_t w_data[2]; /*a*/
} q_data[7];
union {
volatile union {
struct {
uint32_t seper_char: 8; /*a*/
uint32_t seper_esc_char0: 8; /*a*/
@@ -178,7 +178,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} esc_conf0;
union {
volatile union {
struct {
uint32_t seq0: 8; /*a*/
uint32_t seq0_char0: 8; /*a*/
@@ -187,7 +187,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} esc_conf1;
union {
volatile union {
struct {
uint32_t seq1: 8; /*a*/
uint32_t seq1_char0: 8; /*a*/
@@ -196,7 +196,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} esc_conf2;
union {
volatile union {
struct {
uint32_t seq2: 8; /*a*/
uint32_t seq2_char0: 8; /*a*/
@@ -205,14 +205,14 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} esc_conf3;
union {
volatile union {
struct {
uint32_t thrs: 13; /*a*/
uint32_t reserved13:19;
};
uint32_t val;
} pkt_thres;
uint32_t date; /*a*/
volatile uint32_t date; /*a*/
} uhci_dev_t;
extern uhci_dev_t UHCI0;
#ifdef __cplusplus

View File

@@ -15,6 +15,10 @@ config SOC_UART_SUPPORTED
bool
default y
config SOC_UHCI_SUPPORTED
bool
default y
config SOC_GDMA_SUPPORTED
bool
default y
@@ -1367,6 +1371,10 @@ config SOC_UART_WAKEUP_SUPPORT_CHAR_SEQ_MODE
bool
default y
config SOC_UHCI_NUM
int
default 1
config SOC_COEX_HW_PTI
bool
default y

View File

@@ -20,6 +20,7 @@
#define SOC_ADC_SUPPORTED 1
#define SOC_DEDICATED_GPIO_SUPPORTED 1
#define SOC_UART_SUPPORTED 1
#define SOC_UHCI_SUPPORTED 1
#define SOC_GDMA_SUPPORTED 1
#define SOC_AHB_GDMA_SUPPORTED 1
#define SOC_GPTIMER_SUPPORTED 1
@@ -526,6 +527,9 @@
#define SOC_UART_WAKEUP_SUPPORT_START_BIT_MODE (1)
#define SOC_UART_WAKEUP_SUPPORT_CHAR_SEQ_MODE (1)
/*--------------------------- UHCI CAPS -------------------------------------*/
#define SOC_UHCI_NUM (1UL)
// TODO: IDF-5679 (Copy from esp32c3, need check)
/*-------------------------- COEXISTENCE HARDWARE PTI CAPS -------------------------------*/

View File

@@ -618,7 +618,7 @@ typedef union {
} uhci_date_reg_t;
typedef struct uhci_dev_s {
typedef struct uhci_dev_t {
volatile uhci_conf0_reg_t conf0;
volatile uhci_int_raw_reg_t int_raw;
volatile uhci_int_st_reg_t int_st;

View File

@@ -618,7 +618,7 @@ typedef union {
} uhci_date_reg_t;
typedef struct uhci_dev_s {
typedef struct uhci_dev_t {
volatile uhci_conf0_reg_t conf0;
volatile uhci_int_raw_reg_t int_raw;
volatile uhci_int_st_reg_t int_st;

View File

@@ -23,6 +23,10 @@ config SOC_GDMA_SUPPORTED
bool
default y
config SOC_UHCI_SUPPORTED
bool
default y
config SOC_AHB_GDMA_SUPPORTED
bool
default y
@@ -1943,6 +1947,10 @@ config SOC_LP_I2S_SUPPORT_VAD
bool
default y
config SOC_UHCI_NUM
int
default 1
config SOC_COEX_HW_PTI
bool
default y

View File

@@ -22,6 +22,7 @@
#define SOC_DEDICATED_GPIO_SUPPORTED 1
#define SOC_UART_SUPPORTED 1
#define SOC_GDMA_SUPPORTED 1
#define SOC_UHCI_SUPPORTED 1
#define SOC_AHB_GDMA_SUPPORTED 1
#define SOC_AXI_GDMA_SUPPORTED 1
#define SOC_DW_GDMA_SUPPORTED 1
@@ -712,6 +713,9 @@
/*-------------------------- LP_VAD CAPS -------------------------------------*/
#define SOC_LP_I2S_SUPPORT_VAD (1)
/*--------------------------- UHCI CAPS -------------------------------------*/
#define SOC_UHCI_NUM (1UL)
// TODO: IDF-5679 (Copy from esp32c3, need check)
/*-------------------------- COEXISTENCE HARDWARE PTI CAPS -------------------------------*/
#define SOC_COEX_HW_PTI (1)

View File

@@ -797,7 +797,7 @@ typedef union {
} uhci_date_reg_t;
typedef struct {
typedef struct uhci_dev_t{
volatile uhci_conf0_reg_t conf0;
volatile uhci_int_raw_reg_t int_raw;
volatile uhci_int_st_reg_t int_st;
@@ -833,6 +833,7 @@ typedef struct {
volatile uhci_date_reg_t date;
} uhci_dev_t;
extern uhci_dev_t UHCI0;
#ifndef __cplusplus
_Static_assert(sizeof(uhci_dev_t) == 0x84, "Invalid size of uhci_dev_t structure");

View File

@@ -31,6 +31,10 @@ config SOC_GDMA_SUPPORTED
bool
default y
config SOC_UHCI_SUPPORTED
bool
default y
config SOC_AHB_GDMA_SUPPORTED
bool
default y
@@ -1127,6 +1131,10 @@ config SOC_UART_WAKEUP_SUPPORT_ACTIVE_THRESH_MODE
bool
default y
config SOC_UHCI_NUM
int
default 1
config SOC_USB_OTG_PERIPH_NUM
int
default 1

View File

@@ -29,6 +29,7 @@
#define SOC_WIFI_SUPPORTED 1
#define SOC_TWAI_SUPPORTED 1
#define SOC_GDMA_SUPPORTED 1
#define SOC_UHCI_SUPPORTED 1
#define SOC_AHB_GDMA_SUPPORTED 1
#define SOC_GPTIMER_SUPPORTED 1
#define SOC_LCDCAM_SUPPORTED 1
@@ -433,6 +434,9 @@
#define SOC_UART_WAKEUP_SUPPORT_ACTIVE_THRESH_MODE (1)
/*--------------------------- UHCI CAPS -------------------------------------*/
#define SOC_UHCI_NUM (1UL)
/*-------------------------- USB CAPS ----------------------------------------*/
#define SOC_USB_OTG_PERIPH_NUM (1U)

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -12,8 +12,8 @@
extern "C" {
#endif
typedef volatile struct uhci_dev_s {
union {
typedef struct uhci_dev_t {
volatile union {
struct {
uint32_t tx_rst : 1;
uint32_t rx_rst : 1;
@@ -32,7 +32,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} conf0;
union {
volatile union {
struct {
uint32_t rx_start : 1;
uint32_t tx_start : 1;
@@ -47,7 +47,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} int_raw;
union {
volatile union {
struct {
uint32_t rx_start : 1;
uint32_t tx_start : 1;
@@ -62,7 +62,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} int_st;
union {
volatile union {
struct {
uint32_t rx_start : 1;
uint32_t tx_start : 1;
@@ -77,7 +77,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} int_ena;
union {
volatile union {
struct {
uint32_t rx_start : 1;
uint32_t tx_start : 1;
@@ -92,7 +92,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} int_clr;
union {
volatile union {
struct {
uint32_t app_ctrl0_int_set : 1;
uint32_t app_ctrl1_int_set : 1;
@@ -100,7 +100,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} app_int_set;
union {
volatile union {
struct {
uint32_t check_sum_en : 1;
uint32_t check_seq_en : 1;
@@ -116,7 +116,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} conf1;
union {
volatile union {
struct {
uint32_t rx_err_cause : 3;
uint32_t decode_state : 3;
@@ -124,14 +124,14 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} state0;
union {
volatile union {
struct {
uint32_t encode_state : 3;
uint32_t reserved3 : 29;
};
uint32_t val;
} state1;
union {
volatile union {
struct {
uint32_t tx_c0_esc_en : 1;
uint32_t tx_db_esc_en : 1;
@@ -145,7 +145,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} escape_conf;
union {
volatile union {
struct {
uint32_t txfifo_timeout : 8;
uint32_t txfifo_timeout_shift : 3;
@@ -157,7 +157,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} hung_conf;
union {
volatile union {
struct {
uint32_t ack_num : 3;
uint32_t ack_num_load : 1;
@@ -166,7 +166,7 @@ typedef volatile struct uhci_dev_s {
uint32_t val;
} ack_num;
uint32_t rx_head;
union {
volatile union {
struct {
uint32_t single_send_num : 3;
uint32_t single_send_en : 1;
@@ -176,10 +176,10 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} quick_sent;
struct {
volatile struct {
uint32_t word[2];
} q_data[7];
union {
volatile union {
struct {
uint32_t seper_char : 8;
uint32_t seper_esc_char0 : 8;
@@ -188,7 +188,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} esc_conf0;
union {
volatile union {
struct {
uint32_t seq0 : 8;
uint32_t seq0_char0 : 8;
@@ -197,7 +197,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} esc_conf1;
union {
volatile union {
struct {
uint32_t seq1 : 8;
uint32_t seq1_char0 : 8;
@@ -206,7 +206,7 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} esc_conf2;
union {
volatile union {
struct {
uint32_t seq2 : 8;
uint32_t seq2_char0 : 8;
@@ -215,14 +215,14 @@ typedef volatile struct uhci_dev_s {
};
uint32_t val;
} esc_conf3;
union {
volatile union {
struct {
uint32_t thrs : 13;
uint32_t reserved13 : 19;
};
uint32_t val;
} pkt_thres;
uint32_t date;
volatile uint32_t date;
} uhci_dev_t;
extern uhci_dev_t UHCI0;
#ifdef __cplusplus