diff --git a/components/esp_driver_parlio/include/esp_private/parlio_rx_private.h b/components/esp_driver_parlio/include/esp_private/parlio_rx_private.h new file mode 100644 index 00000000000..657237642b4 --- /dev/null +++ b/components/esp_driver_parlio/include/esp_private/parlio_rx_private.h @@ -0,0 +1,30 @@ +/* + * 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 Force to trigger the EOF interrupt + * @note This function is a workaround for the case that level delimiter needs to receive more than 64KB data in one transaction. + * + * @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: Force to trigger the 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_force_trigger_eof(parlio_rx_unit_handle_t rx_unit, bool *need_yield); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_parlio/src/parlio_rx.c b/components/esp_driver_parlio/src/parlio_rx.c index cce214885e5..a8ec1513161 100644 --- a/components/esp_driver_parlio/src/parlio_rx.c +++ b/components/esp_driver_parlio/src/parlio_rx.c @@ -419,7 +419,9 @@ 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 */ @@ -479,6 +481,10 @@ static esp_err_t parlio_rx_unit_init_dma(parlio_rx_unit_handle_t rx_unit, size_t }; 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->dma_mem_align, NULL), 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->dma_mem_align = rx_unit->dma_mem_align > cache_line_size ? rx_unit->dma_mem_align : cache_line_size; +#endif /* Register callbacks */ gdma_rx_event_callbacks_t cbs = { @@ -1077,3 +1083,50 @@ err: xSemaphoreGive(rx_unit->mutex); return ret; } + +esp_err_t parlio_rx_unit_force_trigger_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; + /* 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); + } + /* 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; +} diff --git a/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_rx.c b/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_rx.c index e9cddeb20f5..b77bb8b4033 100644 --- a/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_rx.c +++ b/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_rx.c @@ -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)) { @@ -592,3 +615,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_force_trigger_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); +}