Merge branch 'backport/recent_backport_collection_v5.5' into 'release/v5.5'

backport: backport recent i2s/parlio rx/touch related to v5.5

See merge request espressif/esp-idf!43785
This commit is contained in:
morris
2025-12-26 09:52:26 +08:00
29 changed files with 758 additions and 228 deletions
@@ -237,7 +237,7 @@ esp_err_t bitscrambler_loopback_run(bitscrambler_handle_t bs, void *buffer_in, s
.length = length_bytes_in,
.flags = {
.mark_eof = true,
.mark_final = true,
.mark_final = GDMA_FINAL_LINK_TO_NULL,
}
};
gdma_link_mount_buffers(bsl->tx_link_list, 0, &in_buf_mount_config, 1, NULL);
@@ -247,7 +247,7 @@ esp_err_t bitscrambler_loopback_run(bitscrambler_handle_t bs, void *buffer_in, s
.length = length_bytes_out,
.flags = {
.mark_eof = false,
.mark_final = true,
.mark_final = GDMA_FINAL_LINK_TO_NULL,
}
};
gdma_link_mount_buffers(bsl->rx_link_list, 0, &out_buf_mount_config, 1, NULL);
+39
View File
@@ -371,6 +371,40 @@ err:
return ret;
}
#if SOC_I2S_HW_VERSION_1
esp_err_t i2s_channel_change_port(i2s_chan_handle_t handle, int id)
{
I2S_NULL_POINTER_CHECK(TAG, handle);
ESP_RETURN_ON_FALSE(id >= 0 && id < SOC_I2S_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid I2S port id");
if (id == handle->controller->id) {
return ESP_OK;
}
i2s_controller_t *i2s_obj = i2s_acquire_controller_obj(id);
if (!i2s_obj || !i2s_take_available_channel(i2s_obj, handle->dir)) {
return ESP_ERR_NOT_FOUND;
}
i2s_controller_t *old_i2s_obj = handle->controller;
portENTER_CRITICAL(&g_i2s.spinlock);
if (handle->dir == I2S_DIR_TX) {
i2s_obj->tx_chan = handle;
i2s_obj->chan_occupancy |= I2S_DIR_TX;
old_i2s_obj->tx_chan = NULL;
old_i2s_obj->full_duplex = false;
old_i2s_obj->chan_occupancy &= ~I2S_DIR_TX;
} else {
i2s_obj->rx_chan = handle;
i2s_obj->chan_occupancy |= I2S_DIR_RX;
old_i2s_obj->rx_chan = NULL;
old_i2s_obj->full_duplex = false;
old_i2s_obj->chan_occupancy &= ~I2S_DIR_RX;
}
handle->controller = i2s_obj;
portEXIT_CRITICAL(&g_i2s.spinlock);
return ESP_OK;
}
#endif
#ifndef __cplusplus
/* To make sure the i2s_event_callbacks_t is same size as i2s_event_callbacks_internal_t */
_Static_assert(sizeof(i2s_event_callbacks_t) == sizeof(i2s_event_callbacks_internal_t), "Invalid size of i2s_event_callbacks_t structure");
@@ -417,6 +451,9 @@ uint32_t i2s_get_buf_size(i2s_chan_handle_t handle, uint32_t data_bit_width, uin
uint32_t bytes_per_sample = (data_bit_width + 7) / 8;
#endif // CONFIG_IDF_TARGET_ESP32
uint32_t bytes_per_frame = bytes_per_sample * active_chan;
if (bytes_per_frame == 0) {
return 0;
}
uint32_t bufsize = dma_frame_num * bytes_per_frame;
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
/* bufsize need to align with cache line size */
@@ -998,6 +1035,7 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t *
ESP_GOTO_ON_ERROR(i2s_register_channel(i2s_obj, I2S_DIR_TX, chan_cfg->dma_desc_num),
err, TAG, "register I2S tx channel failed");
i2s_obj->tx_chan->role = chan_cfg->role;
i2s_obj->tx_chan->is_port_auto = id == I2S_NUM_AUTO;
i2s_obj->tx_chan->intr_prio_flags = chan_cfg->intr_priority ? BIT(chan_cfg->intr_priority) : ESP_INTR_FLAG_LOWMED;
i2s_obj->tx_chan->dma.auto_clear_after_cb = chan_cfg->auto_clear_after_cb;
i2s_obj->tx_chan->dma.auto_clear_before_cb = chan_cfg->auto_clear_before_cb;
@@ -1013,6 +1051,7 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t *
ESP_GOTO_ON_ERROR(i2s_register_channel(i2s_obj, I2S_DIR_RX, chan_cfg->dma_desc_num),
err, TAG, "register I2S rx channel failed");
i2s_obj->rx_chan->role = chan_cfg->role;
i2s_obj->rx_chan->is_port_auto = id == I2S_NUM_AUTO;
i2s_obj->rx_chan->intr_prio_flags = chan_cfg->intr_priority ? BIT(chan_cfg->intr_priority) : ESP_INTR_FLAG_LOWMED;
i2s_obj->rx_chan->dma.desc_num = chan_cfg->dma_desc_num;
i2s_obj->rx_chan->dma.frame_num = chan_cfg->dma_frame_num;
+10 -2
View File
@@ -95,11 +95,14 @@ static esp_err_t i2s_pdm_tx_set_clock(i2s_chan_handle_t handle, const i2s_pdm_tx
#endif
portEXIT_CRITICAL(&g_i2s.spinlock);
uint64_t tmp_div = (uint64_t)ret_mclk_div.integer * ret_mclk_div.denominator + ret_mclk_div.numerator;
ESP_RETURN_ON_FALSE(tmp_div != 0 && ret_mclk_div.denominator != 0, ESP_ERR_INVALID_ARG, TAG, "invalid mclk division result");
/* Update the mode info: clock configuration */
memcpy(&(pdm_tx_cfg->clk_cfg), clk_cfg, sizeof(i2s_pdm_tx_clk_config_t));
handle->clk_src = clk_cfg->clk_src;
handle->sclk_hz = clk_info.sclk;
handle->origin_mclk_hz = ((uint64_t)clk_info.sclk * ret_mclk_div.denominator) / (ret_mclk_div.integer * ret_mclk_div.denominator + ret_mclk_div.numerator);
handle->origin_mclk_hz = ((uint64_t)clk_info.sclk * ret_mclk_div.denominator) / tmp_div;
handle->curr_mclk_hz = handle->origin_mclk_hz;
ESP_LOGD(TAG, "Clock division info: [sclk] %"PRIu32" Hz [mdiv] %"PRIu32" %"PRIu32"/%"PRIu32" [mclk] %"PRIu32" Hz [bdiv] %d [bclk] %"PRIu32" Hz",
@@ -119,6 +122,7 @@ static esp_err_t i2s_pdm_tx_set_slot(i2s_chan_handle_t handle, const i2s_pdm_tx_
handle->active_slot = slot_cfg->slot_mode == I2S_SLOT_MODE_MONO ? 1 : 2;
uint32_t buf_size = i2s_get_buf_size(handle, slot_cfg->data_bit_width, handle->dma.frame_num);
ESP_RETURN_ON_FALSE(buf_size != 0, ESP_ERR_INVALID_ARG, TAG, "invalid data_bit_width");
/* The DMA buffer need to re-allocate if the buffer size changed */
if (handle->dma.buf_size != buf_size) {
ESP_RETURN_ON_ERROR(i2s_free_dma_desc(handle), TAG, "failed to free the old dma descriptor");
@@ -422,11 +426,14 @@ static esp_err_t i2s_pdm_rx_set_clock(i2s_chan_handle_t handle, const i2s_pdm_rx
}
portEXIT_CRITICAL(&g_i2s.spinlock);
uint64_t tmp_div = (uint64_t)ret_mclk_div.integer * ret_mclk_div.denominator + ret_mclk_div.numerator;
ESP_RETURN_ON_FALSE(tmp_div != 0 && ret_mclk_div.denominator != 0, ESP_ERR_INVALID_ARG, TAG, "invalid mclk division result");
/* Update the mode info: clock configuration */
memcpy(&(pdm_rx_cfg->clk_cfg), clk_cfg, sizeof(i2s_pdm_rx_clk_config_t));
handle->clk_src = clk_cfg->clk_src;
handle->sclk_hz = clk_info.sclk;
handle->origin_mclk_hz = ((uint64_t)clk_info.sclk * ret_mclk_div.denominator) / (ret_mclk_div.integer * ret_mclk_div.denominator + ret_mclk_div.numerator);
handle->origin_mclk_hz = ((uint64_t)clk_info.sclk * ret_mclk_div.denominator) / tmp_div;
handle->curr_mclk_hz = handle->origin_mclk_hz;
ESP_LOGD(TAG, "Clock division info: [sclk] %"PRIu32" Hz [mdiv] %"PRIu32" %"PRIu32"/%"PRIu32" [mclk] %"PRIu32" Hz [bdiv] %d [bclk] %"PRIu32" Hz",
clk_info.sclk, ret_mclk_div.integer, ret_mclk_div.numerator, ret_mclk_div.denominator, handle->origin_mclk_hz, clk_info.bclk_div, clk_info.bclk);
@@ -445,6 +452,7 @@ static esp_err_t i2s_pdm_rx_set_slot(i2s_chan_handle_t handle, const i2s_pdm_rx_
handle->active_slot = slot_cfg->slot_mode == I2S_SLOT_MODE_MONO ? 1 : 2;
uint32_t buf_size = i2s_get_buf_size(handle, slot_cfg->data_bit_width, handle->dma.frame_num);
ESP_RETURN_ON_FALSE(buf_size != 0, ESP_ERR_INVALID_ARG, TAG, "invalid data_bit_width");
/* The DMA buffer need to re-allocate if the buffer size changed */
if (handle->dma.buf_size != buf_size) {
ESP_RETURN_ON_ERROR(i2s_free_dma_desc(handle), TAG, "failed to free the old dma descriptor");
+14
View File
@@ -160,6 +160,7 @@ struct i2s_channel_obj_t {
int intr_prio_flags;/*!< i2s interrupt priority flags */
void *mode_info; /*!< Slot, clock and gpio information of each mode */
struct {
bool is_port_auto: 1; /*!< Whether the port is auto-assigned */
bool is_etm_start: 1; /*!< Whether start by etm tasks */
bool is_etm_stop: 1; /*!< Whether stop by etm tasks */
bool is_raw_pdm: 1; /*!< Flag of whether send/receive PDM in raw data, i.e., no PCM2PDM/PDM2PCM filter enabled */
@@ -340,6 +341,19 @@ void i2s_output_gpio_reserve(i2s_chan_handle_t handle, int gpio_num);
*/
void i2s_output_gpio_revoke(i2s_chan_handle_t handle, uint64_t gpio_mask);
#if SOC_I2S_HW_VERSION_1
/**
* @brief Change the port of the I2S channel
*
* @param handle I2S channel handle
* @param id I2S port id
* @return
* - ESP_OK Change port success
* - ESP_ERR_NOT_FOUND No available I2S port found
*/
esp_err_t i2s_channel_change_port(i2s_chan_handle_t handle, int id);
#endif
#ifdef __cplusplus
}
#endif
+30 -6
View File
@@ -96,12 +96,14 @@ static esp_err_t i2s_std_set_clock(i2s_chan_handle_t handle, const i2s_std_clk_c
}
}
portEXIT_CRITICAL(&g_i2s.spinlock);
uint64_t tmp_div = (uint64_t)ret_mclk_div.integer * ret_mclk_div.denominator + ret_mclk_div.numerator;
ESP_RETURN_ON_FALSE(tmp_div != 0 && ret_mclk_div.denominator != 0, ESP_ERR_INVALID_ARG, TAG, "invalid mclk division result");
/* Update the mode info: clock configuration */
memcpy(&(std_cfg->clk_cfg), clk_cfg, sizeof(i2s_std_clk_config_t));
handle->clk_src = clk_cfg->clk_src;
handle->sclk_hz = clk_info.sclk;
handle->origin_mclk_hz = ((uint64_t)clk_info.sclk * ret_mclk_div.denominator) / (ret_mclk_div.integer * ret_mclk_div.denominator + ret_mclk_div.numerator);
handle->origin_mclk_hz = ((uint64_t)clk_info.sclk * ret_mclk_div.denominator) / tmp_div;
handle->curr_mclk_hz = handle->origin_mclk_hz;
ESP_LOGD(TAG, "Clock division info: [sclk] %"PRIu32" Hz [mdiv] %"PRIu32" %"PRIu32"/%"PRIu32" [mclk] %"PRIu32" Hz [bdiv] %d [bclk] %"PRIu32" Hz",
@@ -117,6 +119,7 @@ static esp_err_t i2s_std_set_slot(i2s_chan_handle_t handle, const i2s_std_slot_c
handle->active_slot = slot_cfg->slot_mode == I2S_SLOT_MODE_MONO ? 1 : 2;
uint32_t buf_size = i2s_get_buf_size(handle, slot_cfg->data_bit_width, handle->dma.frame_num);
ESP_RETURN_ON_FALSE(buf_size != 0, ESP_ERR_INVALID_ARG, TAG, "invalid data_bit_width");
/* The DMA buffer need to re-allocate if the buffer size changed */
if (handle->dma.buf_size != buf_size) {
ESP_RETURN_ON_ERROR(i2s_free_dma_desc(handle), TAG, "failed to free the old dma descriptor");
@@ -248,13 +251,34 @@ static esp_err_t s_i2s_channel_try_to_constitude_std_duplex(i2s_chan_handle_t ha
if (memcmp(another_handle->mode_info, &curr_cfg, sizeof(i2s_std_config_t)) == 0) {
handle->controller->full_duplex = true;
ESP_LOGD(TAG, "Constitude full-duplex on port %d", handle->controller->id);
}
} else {
#if SOC_I2S_HW_VERSION_1
else {
ESP_LOGE(TAG, "Can't set different channel configurations on a same port");
return ESP_ERR_INVALID_ARG;
}
bool port_changed = false;
if (handle->is_port_auto) {
ESP_LOGD(TAG, "TX & RX on I2S%d are simplex", handle->controller->id);
for (int i = 0; i < SOC_I2S_NUM; i++) {
if (i == handle->controller->id) {
continue;
}
ESP_LOGD(TAG, "Trying to move %s channel from port %d to %d",
handle->dir == I2S_DIR_TX ? "TX" : "RX", handle->controller->id, i);
if (i2s_channel_change_port(handle, i) == ESP_OK) {
ESP_LOGD(TAG, "Move success!");
port_changed = true;
break;
} else {
ESP_LOGD(TAG, "Move failed...");
}
}
}
if (!port_changed) {
ESP_LOGE(TAG, "Can't set different channel configurations on a same port");
return ESP_ERR_INVALID_ARG;
}
#else
ESP_LOGD(TAG, "TX & RX on I2S%d are simplex", handle->controller->id);
#endif
}
}
/* Switch to the slave role if needed */
if (handle->controller->full_duplex &&
+6 -1
View File
@@ -93,12 +93,14 @@ static esp_err_t i2s_tdm_set_clock(i2s_chan_handle_t handle, const i2s_tdm_clk_c
}
}
portEXIT_CRITICAL(&g_i2s.spinlock);
uint64_t tmp_div = (uint64_t)ret_mclk_div.integer * ret_mclk_div.denominator + ret_mclk_div.numerator;
ESP_RETURN_ON_FALSE(tmp_div != 0 && ret_mclk_div.denominator != 0, ESP_ERR_INVALID_ARG, TAG, "invalid mclk division result");
/* Update the mode info: clock configuration */
memcpy(&(tdm_cfg->clk_cfg), clk_cfg, sizeof(i2s_tdm_clk_config_t));
handle->clk_src = clk_cfg->clk_src;
handle->sclk_hz = clk_info.sclk;
handle->origin_mclk_hz = ((uint64_t)clk_info.sclk * ret_mclk_div.denominator) / (ret_mclk_div.integer * ret_mclk_div.denominator + ret_mclk_div.numerator);
handle->origin_mclk_hz = ((uint64_t)clk_info.sclk * ret_mclk_div.denominator) / tmp_div;
handle->curr_mclk_hz = handle->origin_mclk_hz;
ESP_LOGD(TAG, "Clock division info: [sclk] %"PRIu32" Hz [mdiv] %"PRIu32" %"PRIu32"/%"PRIu32" [mclk] %"PRIu32" Hz [bdiv] %d [bclk] %"PRIu32" Hz",
@@ -121,6 +123,7 @@ static esp_err_t i2s_tdm_set_slot(i2s_chan_handle_t handle, const i2s_tdm_slot_c
"total slots(%"PRIu32") * slot_bit_width(%"PRIu32") exceeds the maximum %d",
handle->total_slot, slot_bits, (int)I2S_LL_SLOT_FRAME_BIT_MAX);
uint32_t buf_size = i2s_get_buf_size(handle, slot_cfg->data_bit_width, handle->dma.frame_num);
ESP_RETURN_ON_FALSE(buf_size != 0, ESP_ERR_INVALID_ARG, TAG, "invalid data_bit_width");
/* The DMA buffer need to re-allocate if the buffer size changed */
if (handle->dma.buf_size != buf_size) {
ESP_RETURN_ON_ERROR(i2s_free_dma_desc(handle), TAG, "failed to free the old dma descriptor");
@@ -252,6 +255,8 @@ static void s_i2s_channel_try_to_constitude_tdm_duplex(i2s_chan_handle_t handle,
if (memcmp(another_handle->mode_info, &curr_cfg, sizeof(i2s_tdm_config_t)) == 0) {
handle->controller->full_duplex = true;
ESP_LOGD(TAG, "Constitude full-duplex on port %d", handle->controller->id);
} else {
ESP_LOGD(TAG, "TX & RX on I2S%d are simplex", handle->controller->id);
}
}
/* Switch to the slave role if needed */
@@ -238,6 +238,8 @@ TEST_CASE("I2S_basic_channel_allocation_reconfig_deleting_test", "[i2s]")
}
static volatile bool task_run_flag;
static volatile bool read_task_success = true;
static volatile bool write_task_success = true;
#define TEST_I2S_DATA 0x78
@@ -270,6 +272,7 @@ static void i2s_read_task(void *args)
ret = i2s_channel_read(rx_handle, recv_buf, 2000, &recv_size, 300);
if (ret == ESP_ERR_TIMEOUT) {
printf("Read timeout count: %"PRIu32"\n", cnt++);
read_task_success = false;
}
}
@@ -291,6 +294,7 @@ static void i2s_write_task(void *args)
ret = i2s_channel_write(tx_handle, send_buf, 2000, &send_size, 300);
if (ret == ESP_ERR_TIMEOUT) {
printf("Write timeout count: %"PRIu32"\n", cnt++);
write_task_success = false;
}
}
@@ -431,6 +435,7 @@ TEST_CASE("I2S_lazy_duplex_test", "[i2s]")
},
},
};
/* Part 1: test common lazy duplex mode */
TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, NULL));
TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &std_cfg));
TEST_ESP_OK(i2s_channel_enable(tx_handle));
@@ -451,7 +456,7 @@ TEST_CASE("I2S_lazy_duplex_test", "[i2s]")
xTaskCreate(i2s_read_check_task, "i2s_read_check_task", 4096, rx_handle, 5, NULL);
printf("RX started\n");
/* Wait 3 seconds to see if any failures occur */
/* Wait 1 seconds to see if any failures occur */
vTaskDelay(pdMS_TO_TICKS(1000));
printf("Finished\n");
@@ -467,6 +472,72 @@ TEST_CASE("I2S_lazy_duplex_test", "[i2s]")
/* Delete the channels */
TEST_ESP_OK(i2s_del_channel(tx_handle));
TEST_ESP_OK(i2s_del_channel(rx_handle));
/* Part 2: Test no lazy duplex mode with port auto assignment */
chan_cfg.id = I2S_NUM_AUTO;
TEST_ESP_OK(i2s_new_channel(&chan_cfg, NULL, &rx_handle));
TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, NULL));
TEST_ESP_OK(i2s_channel_init_std_mode(rx_handle, &std_cfg));
/* Change the config to not constitute full-duplex */
std_cfg.gpio_cfg.mclk = I2S_GPIO_UNUSED;
std_cfg.gpio_cfg.bclk = I2S_GPIO_UNUSED;
std_cfg.gpio_cfg.ws = I2S_GPIO_UNUSED;
std_cfg.gpio_cfg.dout = I2S_GPIO_UNUSED;
std_cfg.gpio_cfg.din = I2S_GPIO_UNUSED;
#if CONFIG_IDF_TARGET_ESP32S2
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, i2s_channel_init_std_mode(tx_handle, &std_cfg));
/* Delete the channels */
TEST_ESP_OK(i2s_del_channel(tx_handle));
TEST_ESP_OK(i2s_del_channel(rx_handle));
return;
#else
TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &std_cfg));
#endif
#if CONFIG_IDF_TARGET_ESP32
/* On ESP32, if failed to constitute full-duplex with `I2S_NUM_AUTO`,
the channel will be re-assigned to the next availableport */
i2s_chan_info_t chan_info;
TEST_ESP_OK(i2s_channel_get_info(rx_handle, &chan_info));
TEST_ASSERT(chan_info.id == I2S_NUM_0);
TEST_ESP_OK(i2s_channel_get_info(tx_handle, &chan_info));
TEST_ASSERT(chan_info.id == I2S_NUM_1);
#endif
TEST_ESP_OK(i2s_channel_enable(tx_handle));
TEST_ESP_OK(i2s_channel_enable(rx_handle));
task_run_flag = true;
read_task_success = true;
write_task_success = true;
/* writing task to keep writing */
xTaskCreate(i2s_write_task, "i2s_write_task", 4096, tx_handle, 5, NULL);
printf("TX started\n");
vTaskDelay(pdMS_TO_TICKS(1000));
/* reading task to keep reading */
xTaskCreate(i2s_read_task, "i2s_read_task", 4096, rx_handle, 5, NULL);
printf("RX started\n");
/* Wait 1 seconds to see if any failures occur */
vTaskDelay(pdMS_TO_TICKS(1000));
printf("Finished\n");
/* Stop those three tasks */
task_run_flag = false;
/* Wait for the three thread deleted */
vTaskDelay(pdMS_TO_TICKS(1000));
/* Disable the channels, they will keep waiting until the current reading / writing finished */
TEST_ESP_OK(i2s_channel_disable(tx_handle));
TEST_ESP_OK(i2s_channel_disable(rx_handle));
/* Delete the channels */
TEST_ESP_OK(i2s_del_channel(tx_handle));
TEST_ESP_OK(i2s_del_channel(rx_handle));
/* Check if the reading and writing tasks are successful */
TEST_ASSERT(read_task_success);
TEST_ASSERT(write_task_success);
}
static bool whether_contains_exapected_data(uint16_t *src, uint32_t src_len, uint32_t src_step, uint32_t start_val, uint32_t val_step)
@@ -22,6 +22,7 @@ extern "C" {
typedef struct {
size_t trans_queue_depth; /*!< Depth of internal transaction queue */
size_t max_recv_size; /*!< Maximum receive size in one transaction, in bytes. This decides the number of DMA nodes will be used for each transaction */
size_t dma_burst_size; /*!< DMA burst size, in bytes */
size_t data_width; /*!< Parallel IO data width, can set to 1/2/4/8/..., but can't be greater than PARLIO_RX_UNIT_MAX_DATA_WIDTH */
parlio_clock_source_t clk_src; /*!< Parallel IO clock source */
uint32_t ext_clk_freq_hz; /*!< The external source clock frequency. Only be valid when select PARLIO_CLK_SRC_EXTERNAL as clock source */
@@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "driver/parlio_rx.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Trigger the fake EOF interrupt
* @note This function is a workaround for the case that level delimiter needs to receive more than 64KB data in one transaction.
* The hardware can't generate the EOF interrupt when the data length is greater than 64KB due to the limitation of the hardware,
* so this function is used to trigger the fake EOF interrupt.
* @note This function will reset the whole parlio module,
* If the pair tx unit is in using,
* the reset operation will affect the TX unit and lead to unknown behavior
* @usage If the application needs to receive more than 64KB data in one transaction, you can follow the steps below:
* 1. Create a level delimiter with a length greater than 64KB
* 2. Register the interrupt of the end edge on the valid GPIO
* 3. Call this function to trigger the fake EOF interrupt in the GPIO interrupt handler
* 4. Receive the transaction that is greater than 64KB
*
* @param rx_unit Parallel IO RX unit that created by `parlio_new_rx_unit`
* @param need_yield Pointer to a status flag to record whether a task switch is needed if this API is being called in an ISR
* @return
* - ESP_OK: Trigger the fake EOF interrupt successfully
* - ESP_ERR_INVALID_ARG: Invalid argument like NULL pointer
* - ESP_ERR_INVALID_STATE: Tx unit is in using, can't be called when pair tx unit is in using
*/
esp_err_t parlio_rx_unit_trigger_fake_eof(parlio_rx_unit_handle_t rx_unit, bool *need_yield);
#ifdef __cplusplus
}
#endif
+12
View File
@@ -25,3 +25,15 @@ entries:
if SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION = y:
gdma_link: gdma_link_get_buffer (noflash)
if PARLIO_RX_ISR_HANDLER_IN_IRAM = y:
gdma_link: gdma_link_mount_buffers (noflash)
gdma_link: gdma_link_get_buffer (noflash)
gdma_link: gdma_link_get_length (noflash)
esp_dma_utils: esp_dma_split_rx_buffer_to_cache_aligned (noflash)
esp_dma_utils: esp_dma_merge_aligned_rx_buffers (noflash)
[mapping:parlio_driver_soc_periph]
archive: libsoc.a
entries:
if PARLIO_RX_ISR_HANDLER_IN_IRAM = y:
parlio_periph: parlio_periph_signals (noflash)
+209 -121
View File
@@ -19,12 +19,6 @@
#define PARLIO_MAX_ALIGNED_DMA_BUF_SIZE DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED
#endif
#if defined(SOC_GDMA_BUS_AHB) && (SOC_GDMA_TRIG_PERIPH_PARLIO0_BUS == SOC_GDMA_BUS_AHB)
typedef dma_descriptor_align4_t parlio_dma_desc_t;
#elif defined(SOC_GDMA_BUS_AXI) && (SOC_GDMA_TRIG_PERIPH_PARLIO0_BUS == SOC_GDMA_BUS_AXI)
typedef dma_descriptor_align8_t parlio_dma_desc_t;
#endif
#define PARLIO_DMA_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT | MALLOC_CAP_DMA)
/**
@@ -32,13 +26,14 @@ typedef dma_descriptor_align8_t parlio_dma_desc_t;
*/
typedef struct {
parlio_rx_delimiter_handle_t delimiter; /*!< Delimiter of this transaction */
void *payload; /*!< The payload of this transaction, will be mounted to DMA descriptor */
size_t size; /*!< The payload size in byte */
dma_buffer_split_array_t aligned_payload; /*!< The aligned payload of this transaction, will be mounted to DMA link list node */
size_t tot_trans_size; /*!< The total size of the transaction */
size_t recv_bytes; /*!< The received bytes of this transaction
will be reset when all data filled in the infinite transaction */
size_t alignment; /*!< The alignment of the payload buffer */
struct {
uint32_t infinite : 1; /*!< Whether this is an infinite transaction */
uint32_t indirect_mount : 1; /*!< Whether the user payload mount to the descriptor indirectly via an internal DMA buffer */
uint32_t indirect_mount : 1; /*!< Whether the user payload mount to the link list node indirectly via an internal DMA buffer */
} flags;
} parlio_rx_transaction_t;
@@ -70,13 +65,18 @@ typedef struct parlio_rx_unit_t {
/* DMA Resources */
gdma_channel_handle_t dma_chan; /*!< DMA channel */
size_t max_recv_size; /*!< Maximum receive size for a normal transaction */
size_t desc_num; /*!< DMA descriptor number */
size_t desc_size; /*!< DMA descriptors total size */
parlio_dma_desc_t **dma_descs; /*!< DMA descriptor array pointer */
parlio_dma_desc_t *curr_desc; /*!< The pointer of the current descriptor */
void *usr_recv_buf; /*!< The pointe to the user's receiving buffer */
size_t dma_burst_size; /*!< DMA burst size, in bytes */
gdma_link_list_handle_t dma_link; /*!< DMA link list handle */
uint32_t node_num; /*!< The number of nodes in the DMA link list */
size_t int_mem_align; /*!< Alignment for internal memory */
size_t ext_mem_align; /*!< Alignment for external memory */
uint32_t curr_node_id; /*!< The index of the current node in the DMA link list */
void *usr_recv_buf; /*!< The point to the user's receiving buffer */
/* Infinite transaction specific */
void *dma_buf; /*!< Additional internal DMA buffer only for infinite transactions */
/* Unaligned DMA buffer management */
uint8_t *stash_buf[2]; /*!< The ping-pong stash buffer for unaligned DMA buffer */
uint8_t stash_buf_idx; /*!< The index of the current stash buffer */
/* Callback */
parlio_rx_event_callbacks_t cbs; /*!< The group of callback function pointers */
@@ -116,13 +116,13 @@ typedef struct parlio_rx_delimiter_t {
} flags;
} parlio_rx_delimiter_t;
#define PRALIO_RX_MOUNT_SIZE_CALC(total_size, div, align) ((((total_size) / (align)) / (div)) * (align))
#define PARLIO_RX_MOUNT_SIZE_CALC(total_size, div, align) ((((total_size) / (align)) / (div)) * (align))
#define PARLIO_RX_CHECK_ISR(condition, err) if (!(condition)) { return err; }
static portMUX_TYPE s_rx_spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
size_t parlio_rx_mount_transaction_buffer(parlio_rx_unit_handle_t rx_unit, parlio_rx_transaction_t *trans)
{
parlio_dma_desc_t **p_desc = rx_unit->dma_descs;
/* Update the current transaction to the next one, and declare the delimiter is under using of the rx unit */
memcpy(&rx_unit->curr_trans, trans, sizeof(parlio_rx_transaction_t));
portENTER_CRITICAL_SAFE(&s_rx_spinlock);
@@ -131,50 +131,66 @@ size_t parlio_rx_mount_transaction_buffer(parlio_rx_unit_handle_t rx_unit, parli
}
portEXIT_CRITICAL_SAFE(&s_rx_spinlock);
uint32_t desc_num = trans->size / PARLIO_MAX_ALIGNED_DMA_BUF_SIZE;
uint32_t remain_num = trans->size % PARLIO_MAX_ALIGNED_DMA_BUF_SIZE;
/* If there are still data remained, need one more descriptor */
desc_num += remain_num ? 1 : 0;
if (trans->flags.infinite && desc_num < 2) {
/* At least 2 descriptors needed */
desc_num = 2;
/* Calculate the number of nodes needed for the transaction */
uint32_t body_node_num = trans->aligned_payload.buf.body.length / PARLIO_MAX_ALIGNED_DMA_BUF_SIZE;
uint32_t body_remain_size = trans->aligned_payload.buf.body.length % PARLIO_MAX_ALIGNED_DMA_BUF_SIZE;
/* If there are still data remained, need one more node */
body_node_num += body_remain_size ? 1 : 0;
uint32_t head_node_num = trans->aligned_payload.buf.head.length ? 1 : 0;
uint32_t tail_node_num = trans->aligned_payload.buf.tail.length ? 1 : 0;
uint32_t required_node_num = body_node_num + head_node_num + tail_node_num;
if (trans->flags.infinite && required_node_num < 2) {
/* At least 2 nodes needed */
required_node_num = 2;
}
rx_unit->node_num = required_node_num;
gdma_buffer_mount_config_t mount_config[required_node_num] = {};
/* Mount head buffer */
if (head_node_num) {
mount_config[0].buffer = trans->aligned_payload.buf.head.aligned_buffer;
mount_config[0].buffer_alignment = trans->alignment;
mount_config[0].length = trans->aligned_payload.buf.head.length;
mount_config[0].flags.bypass_buffer_align_check = false;
mount_config[0].flags.mark_eof = false;
mount_config[0].flags.mark_final = GDMA_FINAL_LINK_TO_DEFAULT;
}
/* Mount body buffer */
size_t mount_size = 0;
size_t offset = 0;
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
uint32_t alignment = rx_unit->base.group->dma_align;
#else
uint32_t alignment = 4;
#endif
/* Loop the descriptors to assign the data */
for (int i = 0; i < desc_num; i++) {
size_t rest_size = trans->size - offset;
size_t rest_size = trans->aligned_payload.buf.body.length;
for (int i = head_node_num; i < required_node_num - tail_node_num; i++) {
if (rest_size >= 2 * PARLIO_MAX_ALIGNED_DMA_BUF_SIZE) {
mount_size = PRALIO_RX_MOUNT_SIZE_CALC(trans->size, desc_num, alignment);
mount_size = PARLIO_MAX_ALIGNED_DMA_BUF_SIZE;
} else if (rest_size <= PARLIO_MAX_ALIGNED_DMA_BUF_SIZE) {
mount_size = (desc_num == 2) && (i == 0) ? PRALIO_RX_MOUNT_SIZE_CALC(rest_size, 2, alignment) : rest_size;
mount_size = ((required_node_num - tail_node_num) == 2) && (i == 0) ? PARLIO_RX_MOUNT_SIZE_CALC(rest_size, 2, trans->alignment) : rest_size;
} else {
mount_size = PRALIO_RX_MOUNT_SIZE_CALC(rest_size, 2, alignment);
}
p_desc[i]->buffer = (void *)((uint8_t *)trans->payload + offset);
p_desc[i]->dw0.size = mount_size;
p_desc[i]->dw0.length = mount_size;
p_desc[i]->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
// Link the descriptor
if (i < desc_num - 1) {
p_desc[i]->next = p_desc[i + 1];
} else {
/* For infinite transaction, link the descriptor as a ring */
p_desc[i]->next = trans->flags.infinite ? p_desc[0] : NULL;
mount_size = PARLIO_RX_MOUNT_SIZE_CALC(rest_size, 2, trans->alignment);
}
mount_config[i].buffer = (void *)((uint8_t *)trans->aligned_payload.buf.body.aligned_buffer + offset);
mount_config[i].buffer_alignment = trans->alignment;
mount_config[i].length = mount_size;
mount_config[i].flags.bypass_buffer_align_check = false;
mount_config[i].flags.mark_eof = false;
mount_config[i].flags.mark_final = GDMA_FINAL_LINK_TO_DEFAULT;
offset += mount_size;
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
esp_cache_msync(p_desc[i], rx_unit->desc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
#endif
rest_size -= mount_size;
}
/* Mount tail buffer */
if (tail_node_num) {
mount_config[required_node_num - 1].buffer = trans->aligned_payload.buf.tail.aligned_buffer;
mount_config[required_node_num - 1].buffer_alignment = trans->alignment;
mount_config[required_node_num - 1].length = trans->aligned_payload.buf.tail.length;
mount_config[required_node_num - 1].flags.bypass_buffer_align_check = false;
}
/* For infinite transaction, link the node as a ring */
mount_config[required_node_num - 1].flags.mark_final = !trans->flags.infinite ? GDMA_FINAL_LINK_TO_NULL : GDMA_FINAL_LINK_TO_HEAD;
mount_config[required_node_num - 1].flags.mark_eof = true;
gdma_link_mount_buffers(rx_unit->dma_link, 0, mount_config, required_node_num, NULL);
/* Reset the current DMA node */
rx_unit->curr_desc = p_desc[0];
rx_unit->curr_node_id = 0;
return offset;
}
@@ -324,6 +340,7 @@ static bool parlio_rx_default_eof_callback(gdma_channel_handle_t dma_chan, gdma_
need_yield |= rx_unit->cbs.on_timeout(rx_unit, &evt_data, rx_unit->user_data);
}
} else {
esp_dma_merge_aligned_rx_buffers(&rx_unit->curr_trans.aligned_payload);
/* If received a normal EOF, it's a receive done event on parlio RX */
if (rx_unit->cbs.on_receive_done) {
evt_data.data = rx_unit->usr_recv_buf;
@@ -350,7 +367,7 @@ static bool parlio_rx_default_eof_callback(gdma_channel_handle_t dma_chan, gdma_
}
/* Mount the new transaction buffer and start the new transaction */
parlio_rx_mount_transaction_buffer(rx_unit, &next_trans);
gdma_start(rx_unit->dma_chan, (intptr_t)rx_unit->dma_descs[0]);
gdma_start(rx_unit->dma_chan, gdma_link_get_head_addr(rx_unit->dma_link));
if (rx_unit->cfg.flags.free_clk) {
parlio_ll_rx_start(rx_unit->base.group->hal.regs, true);
PARLIO_CLOCK_SRC_ATOMIC() {
@@ -381,20 +398,28 @@ static bool parlio_rx_default_desc_done_callback(gdma_channel_handle_t dma_chan,
return false;
}
/* Get the finished descriptor from the current descriptor */
parlio_dma_desc_t *finished_desc = rx_unit->curr_desc;
/* Get the finished node from the current node */
void *finished_buffer = gdma_link_get_buffer(rx_unit->dma_link, rx_unit->curr_node_id);
size_t finished_length = gdma_link_get_length(rx_unit->dma_link, rx_unit->curr_node_id);
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
esp_err_t ret = ESP_OK;
ret |= esp_cache_msync((void *)finished_desc, rx_unit->desc_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
ret |= esp_cache_msync((void *)(finished_desc->buffer), finished_desc->dw0.size, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
size_t sync_size = finished_length;
/* The sych length should be the cache line size for the un-aligned head and tail part */
for (int i = 0; i < 2; i++) {
if (finished_buffer == rx_unit->stash_buf[i]) {
sync_size = rx_unit->int_mem_align;
break;
}
}
ret = esp_cache_msync(finished_buffer, sync_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
if (ret != ESP_OK) {
ESP_EARLY_LOGW(TAG, "failed to sync dma buffer from memory to cache");
}
#endif
parlio_rx_event_data_t evt_data = {
.delimiter = rx_unit->curr_trans.delimiter,
.data = finished_desc->buffer,
.recv_bytes = finished_desc->dw0.length,
.data = finished_buffer,
.recv_bytes = finished_length,
};
if (rx_unit->cbs.on_partial_receive) {
need_yield |= rx_unit->cbs.on_partial_receive(rx_unit, &evt_data, rx_unit->user_data);
@@ -404,61 +429,43 @@ static bool parlio_rx_default_desc_done_callback(gdma_channel_handle_t dma_chan,
memcpy(rx_unit->usr_recv_buf + rx_unit->curr_trans.recv_bytes, evt_data.data, evt_data.recv_bytes);
} else {
portENTER_CRITICAL_ISR(&s_rx_spinlock);
rx_unit->curr_trans.delimiter->under_using = false;
if (rx_unit->curr_trans.delimiter) {
rx_unit->curr_trans.delimiter->under_using = false;
}
portEXIT_CRITICAL_ISR(&s_rx_spinlock);
}
/* Update received bytes */
if (rx_unit->curr_trans.recv_bytes >= rx_unit->curr_trans.size) {
if (rx_unit->curr_trans.recv_bytes >= rx_unit->curr_trans.tot_trans_size) {
rx_unit->curr_trans.recv_bytes = 0;
}
rx_unit->curr_trans.recv_bytes += evt_data.recv_bytes;
/* Move to the next DMA descriptor */
rx_unit->curr_desc = rx_unit->curr_desc->next;
/* Move to the next DMA node */
rx_unit->curr_node_id++;
rx_unit->curr_node_id %= rx_unit->node_num;
return need_yield;
}
static esp_err_t parlio_rx_create_dma_descriptors(parlio_rx_unit_handle_t rx_unit, uint32_t max_recv_size)
static esp_err_t parlio_rx_create_dma_link(parlio_rx_unit_handle_t rx_unit, uint32_t max_recv_size)
{
ESP_RETURN_ON_FALSE(rx_unit, ESP_ERR_INVALID_ARG, TAG, "invalid param");
esp_err_t ret = ESP_OK;
uint32_t desc_num = max_recv_size / DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED + 1;
/* set at least 2 descriptors */
if (desc_num < 2) {
desc_num = 2;
}
rx_unit->desc_num = desc_num;
/* Allocated and link the descriptor nodes */
rx_unit->dma_descs = heap_caps_calloc(desc_num, sizeof(parlio_dma_desc_t *), MALLOC_CAP_DMA);
ESP_RETURN_ON_FALSE(rx_unit->dma_descs, ESP_ERR_NO_MEM, TAG, "no memory for DMA descriptor array");
uint32_t cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
size_t alignment = MAX(cache_line_size, PARLIO_DMA_DESC_ALIGNMENT);
rx_unit->desc_size = ALIGN_UP(sizeof(parlio_dma_desc_t), alignment);
for (int i = 0; i < desc_num; i++) {
rx_unit->dma_descs[i] = heap_caps_aligned_calloc(alignment, 1, rx_unit->desc_size, PARLIO_DMA_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(rx_unit->dma_descs[i], ESP_ERR_NO_MEM, err, TAG, "no memory for DMA descriptors");
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
esp_cache_msync(rx_unit->dma_descs[i], rx_unit->desc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
#endif
}
// calculated the total node number, add 2 for the aligned stash buffer
size_t tot_node_num = esp_dma_calculate_node_count(max_recv_size, rx_unit->int_mem_align, PARLIO_DMA_DESCRIPTOR_BUFFER_MAX_SIZE) + 2;
gdma_link_list_config_t dma_link_config = {
.num_items = tot_node_num,
.item_alignment = PARLIO_DMA_DESC_ALIGNMENT,
};
// create DMA link list, throw the error to the caller if failed
ESP_RETURN_ON_ERROR(gdma_new_link_list(&dma_link_config, &rx_unit->dma_link), TAG, "create DMA link list failed");
rx_unit->max_recv_size = max_recv_size;
return ret;
err:
for (int i = 0; i < desc_num; i++) {
if (rx_unit->dma_descs[i]) {
free(rx_unit->dma_descs[i]);
rx_unit->dma_descs[i] = NULL;
}
}
free(rx_unit->dma_descs);
rx_unit->dma_descs = NULL;
return ret;
}
static esp_err_t parlio_rx_unit_init_dma(parlio_rx_unit_handle_t rx_unit)
static esp_err_t parlio_rx_unit_init_dma(parlio_rx_unit_handle_t rx_unit, size_t dma_burst_size)
{
/* Allocate and connect the GDMA channel */
gdma_channel_alloc_config_t dma_chan_config = {
@@ -477,6 +484,21 @@ static esp_err_t parlio_rx_unit_init_dma(parlio_rx_unit_handle_t rx_unit)
};
gdma_apply_strategy(rx_unit->dma_chan, &gdma_strategy_conf);
// configure DMA transfer parameters
rx_unit->dma_burst_size = dma_burst_size ? dma_burst_size : 16;
gdma_transfer_config_t trans_cfg = {
.max_data_burst_size = rx_unit->dma_burst_size, // Enable DMA burst transfer for better performance,
.access_ext_mem = true,
};
ESP_RETURN_ON_ERROR(gdma_config_transfer(rx_unit->dma_chan, &trans_cfg), TAG, "config DMA transfer failed");
ESP_RETURN_ON_ERROR(gdma_get_alignment_constraints(rx_unit->dma_chan, &rx_unit->int_mem_align, &rx_unit->ext_mem_align), TAG, "get alignment constraints failed");
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
uint32_t cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
rx_unit->int_mem_align = rx_unit->int_mem_align > cache_line_size ? rx_unit->int_mem_align : cache_line_size;
#endif
uint32_t ext_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA);
rx_unit->ext_mem_align = rx_unit->ext_mem_align > ext_cache_line_size ? rx_unit->ext_mem_align : ext_cache_line_size;
/* Register callbacks */
gdma_rx_event_callbacks_t cbs = {
.on_recv_eof = parlio_rx_default_eof_callback,
@@ -575,21 +597,22 @@ static esp_err_t parlio_destroy_rx_unit(parlio_rx_unit_handle_t rx_unit)
ESP_RETURN_ON_ERROR(gdma_disconnect(rx_unit->dma_chan), TAG, "disconnect dma channel failed");
ESP_RETURN_ON_ERROR(gdma_del_channel(rx_unit->dma_chan), TAG, "delete dma channel failed");
}
/* Free the DMA descriptors */
if (rx_unit->dma_descs) {
for (int i = 0; i < rx_unit->desc_num; i++) {
if (rx_unit->dma_descs[i]) {
free(rx_unit->dma_descs[i]);
rx_unit->dma_descs[i] = NULL;
}
}
free(rx_unit->dma_descs);
rx_unit->dma_descs = NULL;
/* Free the DMA link list */
if (rx_unit->dma_link) {
gdma_del_link_list(rx_unit->dma_link);
rx_unit->dma_link = NULL;
}
/* Free the internal DMA buffer */
if (rx_unit->dma_buf) {
free(rx_unit->dma_buf);
}
/* Free the stash buffer */
for (uint8_t i = 0; i < 2; i++) {
if (rx_unit->stash_buf[i]) {
free(rx_unit->stash_buf[i]);
rx_unit->stash_buf[i] = NULL;
}
}
/* Unregister the RX unit from the PARLIO group */
if (rx_unit->base.group) {
parlio_unregister_unit_from_group(&rx_unit->base);
@@ -638,7 +661,6 @@ esp_err_t parlio_new_rx_unit(const parlio_rx_unit_config_t *config, parlio_rx_un
unit->trans_que = xQueueCreateWithCaps(config->trans_queue_depth, sizeof(parlio_rx_transaction_t), PARLIO_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(unit->trans_que, ESP_ERR_NO_MEM, err, TAG, "no memory for transaction queue");
ESP_GOTO_ON_ERROR(parlio_rx_create_dma_descriptors(unit, config->max_recv_size), err, TAG, "create dma descriptor failed");
/* Register and attach the rx unit to the group */
ESP_GOTO_ON_ERROR(parlio_register_unit_to_group(&unit->base), err, TAG, "failed to register the rx unit to the group");
memcpy(&unit->cfg, config, sizeof(parlio_rx_unit_config_t));
@@ -652,7 +674,15 @@ esp_err_t parlio_new_rx_unit(const parlio_rx_unit_config_t *config, parlio_rx_un
/* Initialize GPIO */
ESP_GOTO_ON_ERROR(parlio_rx_unit_set_gpio(unit, config), err, TAG, "failed to set GPIO");
/* Install DMA service */
ESP_GOTO_ON_ERROR(parlio_rx_unit_init_dma(unit), err, TAG, "install rx DMA failed");
ESP_GOTO_ON_ERROR(parlio_rx_unit_init_dma(unit, config->dma_burst_size), err, TAG, "install rx DMA failed");
ESP_GOTO_ON_ERROR(parlio_rx_create_dma_link(unit, config->max_recv_size), err, TAG, "create dma link list failed");
for (uint8_t i = 0; i < 2; i++) {
uint32_t max_alignment = unit->int_mem_align > unit->ext_mem_align ? unit->int_mem_align : unit->ext_mem_align;
unit->stash_buf[i] = heap_caps_aligned_calloc(max_alignment, 2, max_alignment, PARLIO_MEM_ALLOC_CAPS | MALLOC_CAP_DMA);
ESP_GOTO_ON_FALSE(unit->stash_buf[i], ESP_ERR_NO_MEM, err, TAG, "no memory for stash buffer");
}
/* Reset RX module */
PARLIO_RCC_ATOMIC() {
parlio_ll_rx_reset_clock(hal->regs);
@@ -737,7 +767,16 @@ esp_err_t parlio_rx_unit_enable(parlio_rx_unit_handle_t rx_unit, bool reset_queu
xSemaphoreGive(rx_unit->trans_sem);
} else if (xQueueReceive(rx_unit->trans_que, &trans, 0) == pdTRUE) {
// The semaphore always supposed to be taken successfully
assert(xSemaphoreTake(rx_unit->trans_sem, 0) == pdTRUE);
BaseType_t res = xSemaphoreTake(rx_unit->trans_sem, 0);
(void)res;
assert(res == pdTRUE);
if (trans.flags.indirect_mount && trans.flags.infinite && rx_unit->dma_buf == NULL) {
rx_unit->dma_buf = heap_caps_aligned_calloc(rx_unit->int_mem_align, 1, trans.aligned_payload.buf.body.length, PARLIO_DMA_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(rx_unit->dma_buf, ESP_ERR_NO_MEM, err, TAG, "No memory for the internal DMA buffer");
trans.aligned_payload.buf.body.aligned_buffer = rx_unit->dma_buf;
trans.aligned_payload.buf.body.recovery_address = rx_unit->dma_buf;
}
if (rx_unit->cfg.flags.free_clk) {
PARLIO_CLOCK_SRC_ATOMIC() {
parlio_ll_rx_enable_clock(hal->regs, false);
@@ -746,7 +785,7 @@ esp_err_t parlio_rx_unit_enable(parlio_rx_unit_handle_t rx_unit, bool reset_queu
assert(trans.delimiter);
parlio_rx_set_delimiter_config(rx_unit, trans.delimiter);
parlio_rx_mount_transaction_buffer(rx_unit, &trans);
gdma_start(rx_unit->dma_chan, (intptr_t)rx_unit->curr_desc);
gdma_start(rx_unit->dma_chan, gdma_link_get_head_addr(rx_unit->dma_link));
if (rx_unit->cfg.flags.free_clk) {
parlio_ll_rx_start(hal->regs, true);
PARLIO_CLOCK_SRC_ATOMIC() {
@@ -935,7 +974,7 @@ static esp_err_t parlio_rx_unit_do_transaction(parlio_rx_unit_handle_t rx_unit,
parlio_rx_mount_transaction_buffer(rx_unit, trans);
// Take semaphore without block time here, only indicate there are transactions on receiving
xSemaphoreTake(rx_unit->trans_sem, 0);
gdma_start(rx_unit->dma_chan, (intptr_t)rx_unit->curr_desc);
gdma_start(rx_unit->dma_chan, gdma_link_get_head_addr(rx_unit->dma_link));
if (rx_unit->cfg.flags.free_clk) {
parlio_ll_rx_start(rx_unit->base.group->hal.regs, true);
PARLIO_CLOCK_SRC_ATOMIC() {
@@ -958,18 +997,11 @@ esp_err_t parlio_rx_unit_receive(parlio_rx_unit_handle_t rx_unit,
ESP_RETURN_ON_FALSE(rx_unit && payload && recv_cfg, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(recv_cfg->delimiter, ESP_ERR_INVALID_ARG, TAG, "no delimiter specified");
ESP_RETURN_ON_FALSE(payload_size <= rx_unit->max_recv_size, ESP_ERR_INVALID_ARG, TAG, "trans length too large");
uint32_t alignment = rx_unit->base.group->dma_align;
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
ESP_RETURN_ON_FALSE(payload_size % alignment == 0, ESP_ERR_INVALID_ARG, TAG, "The payload size should align with %"PRIu32, alignment);
size_t alignment = rx_unit->int_mem_align;
if (recv_cfg->flags.partial_rx_en) {
ESP_RETURN_ON_FALSE(payload_size >= 2 * alignment, ESP_ERR_INVALID_ARG, TAG, "The payload size should greater than %"PRIu32, 2 * alignment);
}
#endif
#if CONFIG_PARLIO_RX_ISR_CACHE_SAFE
ESP_RETURN_ON_FALSE(esp_ptr_internal(payload), ESP_ERR_INVALID_ARG, TAG, "payload not in internal RAM");
#else
ESP_RETURN_ON_FALSE(recv_cfg->flags.indirect_mount || esp_ptr_internal(payload), ESP_ERR_INVALID_ARG, TAG, "payload not in internal RAM");
#endif
if (recv_cfg->delimiter->eof_data_len) {
ESP_RETURN_ON_FALSE(payload_size >= recv_cfg->delimiter->eof_data_len, ESP_ERR_INVALID_ARG,
TAG, "payload size should be greater than eof_data_len");
@@ -985,8 +1017,8 @@ esp_err_t parlio_rx_unit_receive(parlio_rx_unit_handle_t rx_unit,
rx_units[rx_unit->base.unit_id].
data_sigs[recv_cfg->delimiter->valid_sig_line_id];
}
void *p_buffer = payload;
dma_buffer_split_array_t dma_buf_array = {0};
/* Create the internal DMA buffer for the infinite transaction if indirect_mount is set */
if (recv_cfg->flags.partial_rx_en && recv_cfg->flags.indirect_mount) {
ESP_RETURN_ON_FALSE(!rx_unit->dma_buf, ESP_ERR_INVALID_STATE, TAG, "infinite transaction is using the internal DMA buffer");
@@ -994,14 +1026,21 @@ esp_err_t parlio_rx_unit_receive(parlio_rx_unit_handle_t rx_unit,
rx_unit->dma_buf = heap_caps_aligned_calloc(alignment, 1, payload_size, PARLIO_DMA_MEM_ALLOC_CAPS);
ESP_RETURN_ON_FALSE(rx_unit->dma_buf, ESP_ERR_NO_MEM, TAG, "No memory for the internal DMA buffer");
/* Use the internal DMA buffer so that the user buffer can always be available */
p_buffer = rx_unit->dma_buf;
dma_buf_array.buf.body.aligned_buffer = rx_unit->dma_buf;
dma_buf_array.buf.body.recovery_address = rx_unit->dma_buf;
dma_buf_array.buf.body.length = payload_size;
} else {
ESP_RETURN_ON_ERROR(esp_dma_split_rx_buffer_to_cache_aligned(payload, payload_size, &dma_buf_array, &rx_unit->stash_buf[rx_unit->stash_buf_idx]),
TAG, "failed to split the unaligned DMA buffer");
rx_unit->stash_buf_idx = !rx_unit->stash_buf_idx;
}
/* Create the transaction */
parlio_rx_transaction_t transaction = {
.delimiter = recv_cfg->delimiter,
.payload = p_buffer,
.size = payload_size,
.aligned_payload = dma_buf_array,
.tot_trans_size = payload_size,
.alignment = alignment,
.recv_bytes = 0,
.flags.infinite = recv_cfg->flags.partial_rx_en,
.flags.indirect_mount = recv_cfg->flags.indirect_mount,
@@ -1053,3 +1092,52 @@ err:
xSemaphoreGive(rx_unit->mutex);
return ret;
}
esp_err_t parlio_rx_unit_trigger_fake_eof(parlio_rx_unit_handle_t rx_unit, bool *need_yield)
{
ESP_RETURN_ON_FALSE_ISR(rx_unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
int uint_id = rx_unit->base.unit_id;
parlio_unit_base_handle_t pair_tx_unit = rx_unit->base.group->tx_units[uint_id];
/* This function will reset the whole parlio module,
If the pair tx unit is in using,
the reset operation will affect the TX unit and lead to unknown behavior */
ESP_RETURN_ON_FALSE_ISR(!pair_tx_unit, ESP_ERR_INVALID_STATE, TAG, "can't be called when pair tx unit is in using");
/* Stop and reset the DMA channel first */
ESP_RETURN_ON_ERROR_ISR(gdma_stop(rx_unit->dma_chan), TAG, "stop DMA channel failed");
ESP_RETURN_ON_ERROR_ISR(gdma_reset(rx_unit->dma_chan), TAG, "reset DMA channel failed");
parlio_hal_context_t *hal = &rx_unit->base.group->hal;
portENTER_CRITICAL_SAFE(&s_rx_spinlock);
/* Save the current register values */
parl_io_dev_t save_curr_regs = *(parl_io_dev_t *)hal->regs;
/* Reset the hardware FSM of the parlio module */
PARLIO_RCC_ATOMIC() {
parlio_ll_reset_register(rx_unit->base.group->group_id);
}
/* Switch to the default clock source to ensure the register values can be written back successfully */
PARLIO_CLOCK_SRC_ATOMIC() {
parlio_ll_rx_set_clock_source(hal->regs, PARLIO_CLK_SRC_DEFAULT);
}
portEXIT_CRITICAL_SAFE(&s_rx_spinlock);
/* Restore the register values and clock source*/
memcpy(hal->regs, &save_curr_regs, sizeof(parl_io_dev_t));
parlio_ll_rx_update_config(hal->regs);
PARLIO_CLOCK_SRC_ATOMIC() {
parlio_ll_rx_set_clock_source(hal->regs, rx_unit->clk_src);
}
/* Force to trigger the EOF interrupt */
gdma_event_data_t event_data = {
.flags.normal_eof = 1
};
bool _need_yield = false;
_need_yield |= parlio_rx_default_desc_done_callback(rx_unit->dma_chan, &event_data, rx_unit);
_need_yield |= parlio_rx_default_eof_callback(rx_unit->dma_chan, &event_data, rx_unit);
if (need_yield) {
*need_yield |= _need_yield;
}
return ESP_OK;
}
+1 -1
View File
@@ -481,7 +481,7 @@ static void parlio_mount_buffer(parlio_tx_unit_t *tx_unit, parlio_tx_trans_desc_
.flags = {
// if transmission is loop, we don't need to generate the EOF for 1-bit data width, DIG-559
.mark_eof = tx_unit->data_width == 1 ? !t->flags.loop_transmission : true,
.mark_final = !t->flags.loop_transmission,
.mark_final = t->flags.loop_transmission ? GDMA_FINAL_LINK_TO_START : GDMA_FINAL_LINK_TO_NULL,
}
};
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -25,6 +25,7 @@
#include "soc/parlio_periph.h"
#include "esp_attr.h"
#include "test_board.h"
#include "esp_private/parlio_rx_private.h"
#define TEST_SPI_HOST SPI2_HOST
#define TEST_I2S_PORT I2S_NUM_0
@@ -57,6 +58,10 @@
#define TEST_TASK_DATA_READY_BIT 0x01
#define TEST_TASK_FINISHED_BIT 0x02
#define TEST_TASK_RECV_READY_BIT 0x04
#define TEST_TASK_LARGE_TRANS_BIT 0x08
#define TEST_TASK_LARGE_TRANS_SIZE 155584 // Use an unaligned size to ensure the reliability
typedef struct {
uint32_t partial_recv_cnt;
@@ -177,8 +182,6 @@ static void pulse_delimiter_sender_task_i2s(void *args)
}
}
#if CONFIG_IDF_TARGET_ESP32C6 // TODO: IDF-9806 fix the bit shift issue in other target
static void cs_high(spi_transaction_t *trans)
{
gpio_set_level(TEST_VALID_GPIO, 1);
@@ -194,6 +197,7 @@ static void cs_low(spi_transaction_t *trans)
static void level_delimiter_sender_task_spi(void *args)
{
uint32_t *task_flags = (uint32_t *)args;
bool is_large_trans = *task_flags & TEST_TASK_LARGE_TRANS_BIT;
spi_device_handle_t dev_handle;
spi_bus_config_t bus_cfg = {
@@ -210,11 +214,11 @@ static void level_delimiter_sender_task_spi(void *args)
.clock_speed_hz = TEST_SPI_CLK_FREQ,
.mode = 0,
.duty_cycle_pos = 128,
.spics_io_num = TEST_VALID_GPIO,
.spics_io_num = is_large_trans ? -1 : TEST_VALID_GPIO,
.queue_size = 5,
.flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_POSITIVE_CS,
.pre_cb = cs_high,
.post_cb = cs_low,
.pre_cb = is_large_trans ? NULL : cs_high,
.post_cb = is_large_trans ? NULL : cs_low,
};
//Initialize the SPI bus and add device
TEST_ESP_OK(spi_bus_initialize(TEST_SPI_HOST, &bus_cfg, SPI_DMA_CH_AUTO));
@@ -240,8 +244,14 @@ static void level_delimiter_sender_task_spi(void *args)
parlio_periph_signals.groups[0].rx_units[0].data_sigs[0]);
// Prepare the data the be transmitted
uint8_t *data = (uint8_t *)calloc(1, TEST_EOF_DATA_LEN);
for (int i = 0; i < TEST_EOF_DATA_LEN; i += 4) {
uint8_t *data = NULL;
size_t data_size = TEST_EOF_DATA_LEN;
if (*task_flags & TEST_TASK_LARGE_TRANS_BIT) {
data_size = 1024;
}
data = (uint8_t *)calloc(1, data_size);
TEST_ASSERT_NOT_NULL(data);
for (int i = 0; i < data_size; i += 4) {
data[i] = 0x12;
data[i + 1] = 0x34;
data[i + 2] = 0x56;
@@ -249,17 +259,31 @@ static void level_delimiter_sender_task_spi(void *args)
}
spi_transaction_t t = {
.cmd = 0,
.length = TEST_EOF_DATA_LEN * 8,
.length = data_size * 8,
.flags = 0,
.tx_buffer = data,
.user = NULL,
};
// Transmit data every 1ms, until the main test thread finished receiving
while (!((*task_flags) & TEST_TASK_FINISHED_BIT)) {
TEST_ESP_OK(spi_device_transmit(dev_handle, &t));
vTaskDelay(pdMS_TO_TICKS(1));
*task_flags |= TEST_TASK_DATA_READY_BIT;
if (is_large_trans) {
while (!((*task_flags) & TEST_TASK_FINISHED_BIT)) {
if (!((*task_flags) & TEST_TASK_RECV_READY_BIT)) {
gpio_set_level(TEST_VALID_GPIO, 1);
for (int i = 0; i < 80; i++) {
TEST_ESP_OK(spi_device_transmit(dev_handle, &t));
}
gpio_set_level(TEST_VALID_GPIO, 0);
*task_flags |= TEST_TASK_DATA_READY_BIT;
}
vTaskDelay(pdMS_TO_TICKS(2));
}
} else {
while (!((*task_flags) & TEST_TASK_FINISHED_BIT)) {
TEST_ESP_OK(spi_device_transmit(dev_handle, &t));
vTaskDelay(pdMS_TO_TICKS(2));
*task_flags |= TEST_TASK_DATA_READY_BIT;
}
}
// Remove the SPI device and free the bus
@@ -275,7 +299,6 @@ static void level_delimiter_sender_task_spi(void *args)
vTaskDelay(portMAX_DELAY);
}
}
#endif
static bool test_delimiter(parlio_rx_delimiter_handle_t deli, bool free_running_clk, void (*sender_task_thread)(void *args))
{
@@ -292,7 +315,7 @@ static bool test_delimiter(parlio_rx_delimiter_handle_t deli, bool free_running_
static uint32_t task_flags = 0;
xTaskCreate(sender_task_thread, "sender task", 4096, &task_flags, 5, &sender_task);
// Waiting for the data ready on line
while ((task_flags & TEST_TASK_DATA_READY_BIT)) {
while (!(task_flags & TEST_TASK_DATA_READY_BIT)) {
vTaskDelay(1);
}
@@ -477,7 +500,7 @@ TEST_CASE("parallel_rx_unit_receive_transaction_test", "[parlio_rx]")
TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload, TEST_PAYLOAD_SIZE, &recv_config));
TEST_ESP_OK(parlio_rx_unit_wait_all_done(rx_unit, 5000));
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, false));
TEST_ASSERT_EQUAL_UINT32(2, test_data.partial_recv_cnt);
TEST_ASSERT_GREATER_OR_EQUAL_UINT32(2, test_data.partial_recv_cnt);
TEST_ASSERT_EQUAL_UINT32(1, test_data.recv_done_cnt);
memset(&test_data, 0, sizeof(test_data_t));
@@ -489,7 +512,7 @@ TEST_CASE("parallel_rx_unit_receive_transaction_test", "[parlio_rx]")
}
TEST_ESP_OK(parlio_rx_unit_wait_all_done(rx_unit, 5000));
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, false));
TEST_ASSERT_EQUAL_UINT32(10, test_data.partial_recv_cnt);
TEST_ASSERT_GREATER_OR_EQUAL_UINT32(10, test_data.partial_recv_cnt);
TEST_ASSERT_EQUAL_UINT32(5, test_data.recv_done_cnt);
memset(&test_data, 0, sizeof(test_data_t));
@@ -525,6 +548,49 @@ TEST_CASE("parallel_rx_unit_receive_transaction_test", "[parlio_rx]")
free(payload);
};
#if SOC_PSRAM_DMA_CAPABLE
TEST_CASE("parallel_rx_unit_receive_external_memory_test", "[parlio_rx]")
{
parlio_rx_unit_handle_t rx_unit = NULL;
parlio_rx_delimiter_handle_t deli = NULL;
size_t payload_size = 1000;
parlio_rx_unit_config_t config = TEST_DEFAULT_UNIT_CONFIG(PARLIO_CLK_SRC_DEFAULT, 1000000);
config.flags.free_clk = 1;
TEST_ESP_OK(parlio_new_rx_unit(&config, &rx_unit));
parlio_rx_soft_delimiter_config_t sft_deli_cfg = {
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
.eof_data_len = payload_size,
.timeout_ticks = 0,
};
TEST_ESP_OK(parlio_new_rx_soft_delimiter(&sft_deli_cfg, &deli));
TEST_ESP_OK(parlio_rx_unit_enable(rx_unit, true));
parlio_receive_config_t recv_config = {
.delimiter = deli,
.flags.partial_rx_en = false,
};
/* Do not specify alignment, check if the driver can work correctly */
uint8_t *payload = heap_caps_calloc_prefer(1, payload_size, MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM, TEST_PARLIO_DMA_MEM_ALLOC_CAPS);
printf("payload addr: %p size: %u\n", payload, payload_size);
TEST_ASSERT(payload);
printf("Testing the external memory receive functionality...\n");
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, true));
TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload, payload_size, &recv_config));
TEST_ESP_OK(parlio_rx_unit_wait_all_done(rx_unit, 5000));
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, false));
TEST_ESP_OK(parlio_rx_unit_disable(rx_unit));
TEST_ESP_OK(parlio_del_rx_delimiter(deli));
TEST_ESP_OK(parlio_del_rx_unit(rx_unit));
free(payload);
}
#endif // SOC_PSRAM_DMA_CAPABLE
TEST_CASE("parallel_rx_unit_receive_timeout_test", "[parlio_rx]")
{
printf("init a gpio to simulate valid signal\r\n");
@@ -592,3 +658,122 @@ TEST_CASE("parallel_rx_unit_receive_timeout_test", "[parlio_rx]")
TEST_ESP_OK(gpio_reset_pin(TEST_VALID_GPIO));
free(payload);
}
typedef struct {
uint32_t partial_recv_cnt;
uint32_t recv_done_cnt;
uint32_t timeout_cnt;
uint32_t isr_send_cnt;
uint32_t isr_send_success_cnt;
parlio_rx_unit_handle_t rx_unit;
parlio_rx_delimiter_handle_t delimiter;
uint8_t *isr_payload;
size_t isr_payload_size;
bool enable_isr_send;
} test_isr_data_t;
/**
* @brief This ISR is to indicate the SPI transaction finished
*/
static void test_gpio_neg_edge_intr(void *arg)
{
parlio_rx_unit_handle_t rx_unit = (parlio_rx_unit_handle_t)arg;
bool need_yield = false;
parlio_rx_unit_trigger_fake_eof(rx_unit, &need_yield);
if (need_yield) {
portYIELD_FROM_ISR();
}
}
TEST_CASE("parallel_rx_unit_force_trigger_eof_test", "[parlio_rx]")
{
parlio_rx_unit_handle_t rx_unit = NULL;
parlio_rx_unit_config_t config = TEST_DEFAULT_UNIT_CONFIG(PARLIO_CLK_SRC_EXTERNAL, 1000000);
config.flags.free_clk = 0;
config.max_recv_size = TEST_TASK_LARGE_TRANS_SIZE;
TEST_ESP_OK(parlio_new_rx_unit(&config, &rx_unit));
parlio_rx_level_delimiter_config_t lvl_deli_cfg = {
.valid_sig_line_id = TEST_VALID_SIG,
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
.bit_pack_order = PARLIO_BIT_PACK_ORDER_MSB,
/* Normally the EOF won't be triggered for the level delimiter that eof_data_len larger than 64KB */
.eof_data_len = TEST_TASK_LARGE_TRANS_SIZE,
.timeout_ticks = 0,
.flags = {
.active_low_en = 0,
},
};
parlio_rx_delimiter_handle_t deli = NULL;
TEST_ESP_OK(parlio_new_rx_level_delimiter(&lvl_deli_cfg, &deli));
parlio_rx_event_callbacks_t cbs = {
.on_receive_done = test_parlio_rx_done_callback,
};
test_data_t test_data = {
.partial_recv_cnt = 0,
.recv_done_cnt = 0,
};
TEST_ESP_OK(parlio_rx_unit_register_event_callbacks(rx_unit, &cbs, &test_data));
TEST_ESP_OK(parlio_rx_unit_enable(rx_unit, true));
TaskHandle_t sender_task;
/* The flag to transport finish information between main test thread and the sender thread
* Set it as static to make sure it'll be valid in another thread */
static uint32_t task_flags = TEST_TASK_LARGE_TRANS_BIT;
xTaskCreate(level_delimiter_sender_task_spi, "sender task", 4096, &task_flags, 5, &sender_task);
parlio_receive_config_t recv_config = {
.delimiter = deli,
.flags.partial_rx_en = false,
};
uint8_t *recv_buff = NULL;
uint32_t alignment = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
alignment = alignment < 4 ? 4 : alignment;
size_t buff_size = ALIGN_UP(TEST_TASK_LARGE_TRANS_SIZE, alignment);
recv_buff = heap_caps_aligned_calloc(alignment, 1, buff_size, TEST_PARLIO_DMA_MEM_ALLOC_CAPS);
TEST_ASSERT_NOT_NULL(recv_buff);
gpio_set_intr_type(TEST_VALID_GPIO, GPIO_INTR_NEGEDGE);
gpio_install_isr_service(0);
gpio_isr_handler_add(TEST_VALID_GPIO, test_gpio_neg_edge_intr, rx_unit);
gpio_intr_enable(TEST_VALID_GPIO);
uint32_t recv_cnt = 3;
for (int i = 0; i < recv_cnt; i++) {
TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, recv_buff, buff_size, &recv_config));
printf("[%d] recv ready\n", i);
task_flags |= TEST_TASK_RECV_READY_BIT;
while (!task_flags & TEST_TASK_DATA_READY_BIT) {
vTaskDelay(1);
}
task_flags &= ~TEST_TASK_DATA_READY_BIT;
printf("[%d] send done\n", i);
TEST_ESP_OK(parlio_rx_unit_wait_all_done(rx_unit, 10000));
task_flags &= ~TEST_TASK_RECV_READY_BIT;
printf("[%d] recv done\n", i);
}
// Indicate the test finished, no need to send data
task_flags |= TEST_TASK_FINISHED_BIT;
bool is_success = true;
is_success &= test_data.recv_done_cnt == recv_cnt;
gpio_intr_disable(TEST_VALID_GPIO);
gpio_isr_handler_remove(TEST_VALID_GPIO);
gpio_uninstall_isr_service();
// Waiting for the sender task quit
while (task_flags) {
vTaskDelay(1);
}
// Delete the sender task
vTaskDelete(sender_task);
free(recv_buff);
TEST_ESP_OK(parlio_rx_unit_disable(rx_unit));
TEST_ESP_OK(parlio_del_rx_delimiter(deli));
TEST_ESP_OK(parlio_del_rx_unit(rx_unit));
TEST_ASSERT(is_success);
}
@@ -94,7 +94,7 @@ static void test_parlio_sleep_retention(bool allow_pd)
parlio_rx_level_delimiter_config_t lvl_deli_cfg = {
.valid_sig_line_id = PARLIO_RX_UNIT_MAX_DATA_WIDTH - 1,
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
.sample_edge = PARLIO_SAMPLE_EDGE_NEG, // opposite to tx unit in case of timing issue
.bit_pack_order = PARLIO_BIT_PACK_ORDER_MSB,
.eof_data_len = TEST_PAYLOAD_SIZE,
.timeout_ticks = 0,
@@ -43,12 +43,22 @@ static void touch_channel_pin_init(int id)
{
gpio_num_t pin = touch_sensor_channel_io_map[id];
assert(pin >= 0);
/* Touch pad will output the sawtooth wave on the pin,
so it needs to be reserved, in case of conflict with other output signals */
if (esp_gpio_reserve(BIT64(pin)) & BIT64(pin)) {
ESP_LOGW(TAG, "The GPIO%d is conflict with other module", (int)pin);
}
gpio_config_as_analog(pin);
}
static void touch_channel_pin_deinit(int id)
{
gpio_num_t pin = touch_sensor_channel_io_map[id];
assert(pin >= 0);
/* esp_gpio_revoke is called in gpio_reset_pin */
gpio_reset_pin(pin);
}
static void s_touch_free_resource(touch_sensor_handle_t sens_handle)
{
if (!sens_handle) {
@@ -211,6 +221,7 @@ esp_err_t touch_sensor_del_channel(touch_channel_handle_t chan_handle)
TOUCH_ENTER_CRITICAL(TOUCH_PERIPH_LOCK);
sens_handle->chan_mask &= ~(1UL << chan_handle->id);
TOUCH_EXIT_CRITICAL(TOUCH_PERIPH_LOCK);
touch_channel_pin_deinit(chan_handle->id);
free(g_touch->ch[ch_offset]);
g_touch->ch[ch_offset] = NULL;
+2 -2
View File
@@ -269,7 +269,7 @@ static void uhci_do_transmit(uhci_controller_handle_t uhci_ctrl, uhci_transactio
.length = trans->buffer_size,
.flags = {
.mark_eof = true,
.mark_final = true,
.mark_final = GDMA_FINAL_LINK_TO_NULL,
}
};
@@ -332,7 +332,7 @@ esp_err_t uhci_receive(uhci_controller_handle_t uhci_ctrl, uint8_t *read_buffer,
.buffer_alignment = buffer_alignment,
.length = uhci_ctrl->rx_dir.buffer_size_per_desc_node[i],
.flags = {
.mark_final = false,
.mark_final = GDMA_FINAL_LINK_TO_DEFAULT,
}
};
ESP_LOGD(TAG, "The DMA node %d has %d byte", i, uhci_ctrl->rx_dir.buffer_size_per_desc_node[i]);
@@ -232,7 +232,7 @@ static esp_err_t mcp_cpdma_memcpy(async_memcpy_context_t *ctx, void *dst, void *
.length = n,
.flags = {
.mark_eof = true, // mark the last item as EOF, so the RX channel can also received an EOF list item
.mark_final = true, // using singly list, so terminate the link here
.mark_final = GDMA_FINAL_LINK_TO_NULL, // using singly list, so terminate the link here
}
}
};
@@ -256,7 +256,7 @@ static esp_err_t mcp_cpdma_memcpy(async_memcpy_context_t *ctx, void *dst, void *
.length = n,
.flags = {
.mark_eof = false, // EOF is set by TX side
.mark_final = true, // using singly list, so terminate the link here
.mark_final = GDMA_FINAL_LINK_TO_NULL, // using singly list, so terminate the link here
}
}
};
@@ -365,7 +365,7 @@ static esp_err_t mcp_gdma_memcpy(async_memcpy_context_t *ctx, void *dst, void *s
.length = n,
.flags = {
.mark_eof = true, // mark the last item as EOF, so the RX channel can also received an EOF list item
.mark_final = true, // using singly list, so terminate the link here
.mark_final = GDMA_FINAL_LINK_TO_NULL, // using singly list, so terminate the link here
}
}
};
+45 -42
View File
@@ -29,7 +29,7 @@ esp_err_t esp_dma_split_rx_buffer_to_cache_aligned(void *rx_buffer, size_t buffe
{
esp_err_t ret = ESP_OK;
uint8_t* stash_buffer = NULL;
ESP_RETURN_ON_FALSE(rx_buffer && buffer_len && align_buf_array, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE_ISR(rx_buffer && buffer_len && align_buf_array, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
// read the cache line size of internal and external memory, we also use this information to check if a given memory is behind the cache
size_t int_mem_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
@@ -41,80 +41,83 @@ esp_err_t esp_dma_split_rx_buffer_to_cache_aligned(void *rx_buffer, size_t buffe
} else if (esp_ptr_internal(rx_buffer)) {
split_line_size = int_mem_cache_line_size;
}
ESP_LOGV(TAG, "split_line_size:%zu", split_line_size);
bool align_required = split_line_size > 0;
ESP_EARLY_LOGV(TAG, "split_line_size:%zu", split_line_size);
// allocate the stash buffer from internal RAM
// Note, the split_line_size can be 0, in this case, the stash_buffer is also NULL, which is fine
stash_buffer = heap_caps_calloc(2, split_line_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ESP_RETURN_ON_FALSE(!(split_line_size && !stash_buffer), ESP_ERR_NO_MEM, TAG, "no mem for stash buffer");
if (*ret_stash_buffer == NULL) {
// If the stash buffer is not offered by the caller, allocate the stash buffer from internal RAM
// Note, the split_line_size can be 0, in this case, the stash_buffer is also NULL, which is fine
stash_buffer = heap_caps_calloc(2, split_line_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ESP_RETURN_ON_FALSE_ISR(!(split_line_size && !stash_buffer), ESP_ERR_NO_MEM, TAG, "no mem for stash buffer");
} else {
// If the stash buffer is offered by the caller, check if it is aligned
ESP_RETURN_ON_FALSE_ISR(split_line_size == 0 || (uintptr_t)(*ret_stash_buffer) % split_line_size == 0,
ESP_ERR_INVALID_ARG, TAG, "the offered stash buffer is not aligned");
// If the stash buffer is offered by the caller, use it
stash_buffer = *ret_stash_buffer;
}
// clear align_array to avoid garbage data
memset(align_buf_array, 0, sizeof(dma_buffer_split_array_t));
bool need_cache_sync[3] = {false};
// if split_line_size is non-zero, split the buffer into head, body and tail
if (split_line_size > 0) {
// if align_required, split the buffer into head, body and tail
if (align_required) {
// calculate head_overflow_len
size_t head_overflow_len = (uintptr_t)rx_buffer % split_line_size;
head_overflow_len = head_overflow_len ? split_line_size - head_overflow_len : 0;
ESP_LOGV(TAG, "head_addr:%p head_overflow_len:%zu", rx_buffer, head_overflow_len);
ESP_EARLY_LOGV(TAG, "head_addr:%p head_overflow_len:%zu", rx_buffer, head_overflow_len);
// calculate tail_overflow_len
size_t tail_overflow_len = ((uintptr_t)rx_buffer + buffer_len) % split_line_size;
ESP_LOGV(TAG, "tail_addr:%p tail_overflow_len:%zu", rx_buffer + buffer_len - tail_overflow_len, tail_overflow_len);
uint8_t extra_buf_count = 0;
uint8_t* input_buffer = (uint8_t*)rx_buffer;
align_buf_array->buf.head.recovery_address = input_buffer;
align_buf_array->buf.head.aligned_buffer = stash_buffer + split_line_size * extra_buf_count++;
align_buf_array->buf.head.length = head_overflow_len;
need_cache_sync[0] = int_mem_cache_line_size > 0;
align_buf_array->buf.body.recovery_address = input_buffer + head_overflow_len;
align_buf_array->buf.body.aligned_buffer = input_buffer + head_overflow_len;
align_buf_array->buf.body.length = buffer_len - head_overflow_len - tail_overflow_len;
need_cache_sync[1] = true;
align_buf_array->buf.tail.recovery_address = input_buffer + buffer_len - tail_overflow_len;
align_buf_array->buf.tail.aligned_buffer = stash_buffer + split_line_size * extra_buf_count++;
align_buf_array->buf.tail.length = tail_overflow_len;
need_cache_sync[2] = int_mem_cache_line_size > 0;
ESP_EARLY_LOGV(TAG, "tail_addr:%p tail_overflow_len:%zu", rx_buffer + buffer_len - tail_overflow_len, tail_overflow_len);
// special handling when input_buffer length is no more than buffer alignment
if (head_overflow_len >= buffer_len || tail_overflow_len >= buffer_len) {
align_buf_array->buf.head.length = buffer_len ;
align_buf_array->buf.body.length = 0 ;
align_buf_array->buf.tail.length = 0 ;
bool is_small_buf = head_overflow_len >= buffer_len || tail_overflow_len >= buffer_len;
uint8_t extra_buf_count = 0;
uint8_t* input_buffer = (uint8_t*)rx_buffer;
if (head_overflow_len || is_small_buf) {
align_buf_array->buf.head.recovery_address = input_buffer;
align_buf_array->buf.head.aligned_buffer = stash_buffer + split_line_size * extra_buf_count++;
align_buf_array->buf.head.length = is_small_buf ? buffer_len : head_overflow_len;
need_cache_sync[0] = int_mem_cache_line_size > 0;
}
int body_len = (int)buffer_len - (int)head_overflow_len - (int)tail_overflow_len;
if (body_len > 0) {
align_buf_array->buf.body.recovery_address = input_buffer + head_overflow_len;
align_buf_array->buf.body.aligned_buffer = input_buffer + head_overflow_len;
align_buf_array->buf.body.length = body_len;
need_cache_sync[1] = true;
}
if (tail_overflow_len && !is_small_buf) {
align_buf_array->buf.tail.recovery_address = input_buffer + buffer_len - tail_overflow_len;
align_buf_array->buf.tail.aligned_buffer = stash_buffer + split_line_size * extra_buf_count++;
align_buf_array->buf.tail.length = tail_overflow_len;
need_cache_sync[2] = int_mem_cache_line_size > 0;
}
} else {
align_buf_array->buf.body.aligned_buffer = rx_buffer;
align_buf_array->buf.body.recovery_address = rx_buffer;
align_buf_array->buf.body.length = buffer_len;
need_cache_sync[1] = false;
}
for (int i = 0; i < 3; i++) {
if (align_buf_array->aligned_buffer[i].length == 0) {
align_buf_array->aligned_buffer[i].aligned_buffer = NULL;
align_buf_array->aligned_buffer[i].recovery_address = NULL;
need_cache_sync[i] = false;
}
}
// invalidate the aligned buffer if necessary
for (int i = 0; i < 3; i++) {
if (need_cache_sync[i]) {
size_t sync_size = align_buf_array->aligned_buffer[i].length;
size_t sync_size = align_buf_array->aligned_buffer[i].length;
if (need_cache_sync[i] && sync_size > 0) {
if (sync_size < split_line_size) {
// If the buffer is smaller than the cache line size, we need to sync the whole buffer
sync_size = split_line_size;
}
esp_err_t res = esp_cache_msync(align_buf_array->aligned_buffer[i].aligned_buffer, sync_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
ESP_GOTO_ON_ERROR(res, err, TAG, "failed to do cache sync");
ESP_GOTO_ON_ERROR_ISR(res, err, TAG, "failed to do cache sync");
}
}
*ret_stash_buffer = stash_buffer;
return ESP_OK;
err:
if (stash_buffer) {
// Only free the stash buffer if it is not offered by the caller
if (stash_buffer && *ret_stash_buffer == NULL) {
free(stash_buffer);
}
return ret;
+16 -3
View File
@@ -203,9 +203,22 @@ esp_err_t gdma_link_mount_buffers(gdma_link_list_handle_t list, int start_item_i
lli_nc->dw0.size = lli_nc->dw0.length;
// mark the EOF node
lli_nc->dw0.suc_eof = (config->flags.mark_eof == 1) && (i == num_items_need - 1);
// mark the final node
if ((config->flags.mark_final == 1) && (i == num_items_need - 1)) {
lli_nc->next = NULL;
if (i == num_items_need - 1) {
// mark the final node
switch (config->flags.mark_final) {
case GDMA_FINAL_LINK_TO_NULL:
lli_nc->next = NULL;
break;
case GDMA_FINAL_LINK_TO_HEAD:
lli_nc->next = (gdma_link_list_item_t *)(list->items);
break;
case GDMA_FINAL_LINK_TO_START:
lli_nc->next = (gdma_link_list_item_t *)(list->items + start_item_index * item_size);
break;
default:
lli_nc->next = (gdma_link_list_item_t *)(list->items + (i + begin_item_idx + 1) % list_item_capacity * item_size);
break;
}
} else {
lli_nc->next = (gdma_link_list_item_t *)(list->items + (i + begin_item_idx + 1) % list_item_capacity * item_size);
}
@@ -56,6 +56,17 @@ esp_err_t gdma_new_link_list(const gdma_link_list_config_t *config, gdma_link_li
*/
esp_err_t gdma_del_link_list(gdma_link_list_handle_t list);
/**
* @brief Types for the next node of the final item in the DMA link list
*
*/
typedef enum {
GDMA_FINAL_LINK_TO_DEFAULT = 0, /*!< The next node is linked to the default next item in the link list */
GDMA_FINAL_LINK_TO_NULL = 1, /*!< No next node is linked */
GDMA_FINAL_LINK_TO_HEAD = 2, /*!< The next node is linked to the head item in the link list */
GDMA_FINAL_LINK_TO_START = 3, /*!< The next node is linked to the start item in the link list */
} gdma_final_node_link_type_t;
/**
* @brief DMA buffer mount configurations
*/
@@ -66,12 +77,11 @@ typedef struct {
struct gdma_buffer_mount_flags {
uint32_t mark_eof: 1; /*!< Whether to mark the list item as the "EOF" item.
Note, an "EOF" descriptor can be interrupted differently by peripheral.
But it doesn't mean to terminate a DMA link (use `mark_final` instead).
But it doesn't mean to terminate a DMA link (set `mark_final` to GDMA_FINAL_LINK_TO_NULL instead).
EOF link list item can also trigger an interrupt. */
uint32_t mark_final: 1; /*!< Whether to terminate the DMA link list at this item.
Note, DMA engine will stop at this item and trigger an interrupt.
If `mark_final` is not set, this list item will point to the next item, and
wrap around to the head item if it's the last one in the list. */
gdma_final_node_link_type_t mark_final: 2; /*!< Specify the next item of the final item of this mount.
For the other items that not the final one, it will be linked to the next item automatically and this field takes no effect.
Note, the final item here does not mean the last item in the link list. It is `start_item_index + num_items - 1` */
uint32_t bypass_buffer_align_check: 1; /*!< Whether to bypass the buffer alignment check.
Only enable it when you know what you are doing. */
} flags; //!< Flags for buffer mount configurations
@@ -278,7 +278,7 @@ static void test_gdma_m2m_transaction(gdma_channel_handle_t tx_chan, gdma_channe
#if !SOC_DMA_CAN_ACCESS_FLASH
.flags = {
.mark_eof = true,
.mark_final = true, // using singly list, so terminate the link here
.mark_final = GDMA_FINAL_LINK_TO_NULL, // using singly list, so terminate the link here
}
#endif
},
@@ -289,7 +289,7 @@ static void test_gdma_m2m_transaction(gdma_channel_handle_t tx_chan, gdma_channe
.length = src_string_len,
.flags = {
.mark_eof = true,
.mark_final = true, // using singly list, so terminate the link here
.mark_final = GDMA_FINAL_LINK_TO_NULL, // using singly list, so terminate the link here
}
},
#endif
@@ -448,7 +448,7 @@ static void test_gdma_m2m_unaligned_buffer_test(uint8_t *dst_data, uint8_t *src_
.length = data_length,
.flags = {
.mark_eof = true,
.mark_final = true, // using singly list, so terminate the link here
.mark_final = GDMA_FINAL_LINK_TO_NULL, // using singly list, so terminate the link here
}
}
};
@@ -609,7 +609,7 @@ TEST_CASE("GDMA M2M Unaligned RX Buffer Test", "[GDMA][M2M]")
.length = COPY_SIZE,
.flags = {
.mark_eof = true,
.mark_final = true, // using singly list, so terminate the link here
.mark_final = GDMA_FINAL_LINK_TO_NULL, // using singly list, so terminate the link here
}
};
TEST_ESP_OK(gdma_link_mount_buffers(tx_link_list, 0, &tx_buf_mount_config, 1, NULL));
@@ -619,7 +619,7 @@ TEST_CASE("GDMA M2M Unaligned RX Buffer Test", "[GDMA][M2M]")
.buffer_alignment = 32,
.length = COPY_SIZE,
.flags = {
.mark_final = true, // using singly list, so terminate the link here
.mark_final = GDMA_FINAL_LINK_TO_NULL, // using singly list, so terminate the link here
}
};
TEST_ESP_OK(gdma_link_mount_buffers(rx_link_list, 0, &rx_buf_mount_config, 1, NULL));
@@ -523,7 +523,7 @@ static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, cons
gdma_buffer_mount_config_t mount_config = {
.flags = {
.mark_eof = true,
.mark_final = true, // singly link list, mark final descriptor
.mark_final = GDMA_FINAL_LINK_TO_NULL, // singly link list, mark final descriptor
}
};
@@ -599,7 +599,7 @@ static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons
gdma_buffer_mount_config_t mount_config = {
.flags = {
.mark_eof = true,
.mark_final = true, // singly link list, mark final descriptor
.mark_final = GDMA_FINAL_LINK_TO_NULL, // singly link list, mark final descriptor
}
};
@@ -724,7 +724,7 @@ static void i2s_lcd_trigger_quick_trans_done_event(esp_lcd_i80_bus_handle_t bus)
.length = 4,
.flags = {
.mark_eof = true, // mark the "EOF" flag to trigger I2S EOF interrupt
.mark_final = true, // singly link list, mark final descriptor
.mark_final = GDMA_FINAL_LINK_TO_NULL, // singly link list, mark final descriptor
}
};
gdma_link_mount_buffers(bus->dma_link, 0, &mount_config, 1, NULL);
@@ -811,7 +811,7 @@ static IRAM_ATTR void i2s_lcd_default_isr_handler(void *args)
.length = trans_desc->data_length,
.flags = {
.mark_eof = true,
.mark_final = true, // singly link list, mark final descriptor
.mark_final = GDMA_FINAL_LINK_TO_NULL, // singly link list, mark final descriptor
}
};
gdma_link_mount_buffers(bus->dma_link, 0, &mount_config, 1, NULL);
@@ -500,7 +500,7 @@ static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, cons
.length = trans_desc->data_length,
.flags = {
.mark_eof = true,
.mark_final = true, // singly link list, mark final descriptor
.mark_final = GDMA_FINAL_LINK_TO_NULL, // singly link list, mark final descriptor
}
};
gdma_link_mount_buffers(bus->dma_link, 0, &mount_config, 1, NULL);
@@ -821,7 +821,7 @@ IRAM_ATTR static void i80_lcd_default_isr_handler(void *args)
.length = trans_desc->data_length,
.flags = {
.mark_eof = true,
.mark_final = true, // singly link list, mark final descriptor
.mark_final = GDMA_FINAL_LINK_TO_NULL, // singly link list, mark final descriptor
}
};
gdma_link_mount_buffers(bus->dma_link, 0, &mount_config, 1, NULL);
@@ -33,7 +33,7 @@
#include "driver/parlio_tx.h"
#include "driver/parlio_types.h"
#include "esp_private/gpio.h"
#include "esp_private/parlio_private.h"
#include "esp_private/parlio_tx_private.h"
#include "esp_lcd_panel_io_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_common.h"
+1 -1
View File
@@ -1052,7 +1052,7 @@ static esp_err_t lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *rgb_panel)
gdma_buffer_mount_config_t mount_cfg = {
.length = rgb_panel->fb_size,
.flags = {
.mark_final = rgb_panel->flags.stream_mode ? false : true,
.mark_final = rgb_panel->flags.stream_mode ? GDMA_FINAL_LINK_TO_DEFAULT : GDMA_FINAL_LINK_TO_NULL,
.mark_eof = true,
},
};
@@ -102,10 +102,13 @@ Peripheral clock sources such as ``REF_TICK``, ``XTAL``, ``RC_FAST`` (i.e., ``RT
Currently, the following peripheral drivers are aware of DFS and use the ``ESP_PM_APB_FREQ_MAX`` lock for the duration of the transaction:
- SPI master
- I2C
- I2S (If the APLL clock is used, then it will use the ``ESP_PM_NO_LIGHT_SLEEP`` lock)
- SDMMC
.. list::
- SPI master
- I2C
:SOC_I2S_HW_VERSION_1 or not SOC_I2S_SUPPORTS_APLL: - I2S
:not SOC_I2S_HW_VERSION_1 and SOC_I2S_SUPPORTS_APLL: - I2S (if the APLL clock is used, then it will use the ``ESP_PM_NO_LIGHT_SLEEP`` lock)
- SDMMC
The following drivers hold the ``ESP_PM_APB_FREQ_MAX`` lock while the driver is enabled:
@@ -102,10 +102,13 @@ ESP-IDF 中集成的电源管理算法可以根据应用程序组件的需求,
目前以下外设驱动程序可感知动态调频,并在调频期间使用 ``ESP_PM_APB_FREQ_MAX`` 锁:
- SPI master
- I2C
- I2S(如果 APLL 锁在使用中,I2S 则会启用 ``ESP_PM_NO_LIGHT_SLEEP`` 锁)
- SDMMC
.. list::
- SPI master
- I2C
:SOC_I2S_HW_VERSION_1 or not SOC_I2S_SUPPORTS_APLL: - I2S
:not SOC_I2S_HW_VERSION_1 and SOC_I2S_SUPPORTS_APLL: - I2S(如果 APLL 锁在使用中,I2S 则会启用 ``ESP_PM_NO_LIGHT_SLEEP`` 锁)
- SDMMC
启用以下驱动程序时,将占用 ``ESP_PM_APB_FREQ_MAX`` 锁: