diff --git a/components/esp_driver_parlio/src/parlio_tx.c b/components/esp_driver_parlio/src/parlio_tx.c index 916fe9365a..a693b02c58 100644 --- a/components/esp_driver_parlio/src/parlio_tx.c +++ b/components/esp_driver_parlio/src/parlio_tx.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 */ @@ -59,6 +59,7 @@ typedef struct parlio_tx_unit_t { #endif portMUX_TYPE spinlock; // prevent resource accessing by user and interrupt concurrently uint32_t out_clk_freq_hz; // output clock frequency + parlio_clock_source_t clk_src; // Parallel IO internal clock source size_t max_transfer_bits; // maximum transfer size in bits size_t queue_depth; // size of transaction queue size_t num_trans_inflight; // indicates the number of transactions that are undergoing but not recycled to ready_queue @@ -262,6 +263,7 @@ static esp_err_t parlio_select_periph_clock(parlio_tx_unit_t *tx_unit, const par if (tx_unit->out_clk_freq_hz != config->output_clk_freq_hz) { ESP_LOGW(TAG, "precision loss, real output frequency: %"PRIu32, tx_unit->out_clk_freq_hz); } + tx_unit->clk_src = clk_src; return ESP_OK; } @@ -422,6 +424,18 @@ static void IRAM_ATTR parlio_tx_do_transaction(parlio_tx_unit_t *tx_unit, parlio tx_unit->cur_trans = t; + // If the external clock is a non-free-running clock, it needs to be switched to the internal free-running clock first. + // And then switched back to the actual clock after the reset is completed. + bool switch_clk = tx_unit->clk_src == PARLIO_CLK_SRC_EXTERNAL ? true : false; + if (switch_clk) { + PARLIO_CLOCK_SRC_ATOMIC() { + parlio_ll_tx_set_clock_source(hal->regs, PARLIO_CLK_SRC_XTAL); + } + } + PARLIO_RCC_ATOMIC() { + parlio_ll_tx_reset_clock(hal->regs); + } + // DMA transfer data based on bytes not bits, so convert the bit length to bytes, round up gdma_buffer_mount_config_t mount_config = { .buffer = (void *)t->payload, @@ -431,14 +445,21 @@ static void IRAM_ATTR parlio_tx_do_transaction(parlio_tx_unit_t *tx_unit, parlio .mark_final = true, // singly link list, mark final descriptor } }; + // Since the threshold of the clock divider counter is not updated simultaneously with the clock source switching. + // The update of the threshold relies on the moment when the counter reaches the threshold each time. + // We place gdma_link_mount_buffers between reset clock and disable clock to ensure enough time for updating the threshold of the clock divider counter. gdma_link_mount_buffers(tx_unit->dma_link, 0, &mount_config, 1, NULL); - parlio_ll_tx_reset_fifo(hal->regs); - PARLIO_RCC_ATOMIC() { - parlio_ll_tx_reset_clock(hal->regs); + + if (switch_clk) { + PARLIO_CLOCK_SRC_ATOMIC() { + parlio_ll_tx_set_clock_source(hal->regs, PARLIO_CLK_SRC_EXTERNAL); + } } PARLIO_CLOCK_SRC_ATOMIC() { parlio_ll_tx_enable_clock(hal->regs, false); } + // reset tx fifo after disabling tx core clk to avoid unexpected rempty interrupt + parlio_ll_tx_reset_fifo(hal->regs); parlio_ll_tx_set_idle_data_value(hal->regs, t->idle_value); parlio_ll_tx_set_trans_bit_len(hal->regs, t->payload_bits); diff --git a/components/esp_driver_parlio/test_apps/parlio/main/test_board.h b/components/esp_driver_parlio/test_apps/parlio/main/test_board.h index 931cc957f7..6ca3437189 100644 --- a/components/esp_driver_parlio/test_apps/parlio/main/test_board.h +++ b/components/esp_driver_parlio/test_apps/parlio/main/test_board.h @@ -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 */ @@ -22,6 +22,7 @@ extern "C" { #if CONFIG_IDF_TARGET_ESP32C6 #define TEST_CLK_GPIO 10 +#define TEST_EXT_CLK_GPIO 12 #define TEST_VALID_GPIO 11 #define TEST_DATA0_GPIO 0 #define TEST_DATA1_GPIO 1 @@ -32,27 +33,29 @@ extern "C" { #define TEST_DATA6_GPIO 6 #define TEST_DATA7_GPIO 7 #elif CONFIG_IDF_TARGET_ESP32H2 -#define TEST_CLK_GPIO 10 -#define TEST_VALID_GPIO 11 -#define TEST_DATA0_GPIO 0 -#define TEST_DATA1_GPIO 1 -#define TEST_DATA2_GPIO 2 -#define TEST_DATA3_GPIO 3 -#define TEST_DATA4_GPIO 4 -#define TEST_DATA5_GPIO 5 -#define TEST_DATA6_GPIO 8 -#define TEST_DATA7_GPIO 9 +#define TEST_VALID_GPIO 2 +#define TEST_EXT_CLK_GPIO 4 +#define TEST_CLK_GPIO 3 +#define TEST_DATA0_GPIO 8 +#define TEST_DATA1_GPIO 5 +#define TEST_DATA2_GPIO 9 +#define TEST_DATA3_GPIO 10 +#define TEST_DATA4_GPIO 27 +#define TEST_DATA5_GPIO 11 +#define TEST_DATA6_GPIO 26 +#define TEST_DATA7_GPIO 12 #elif CONFIG_IDF_TARGET_ESP32P4 -#define TEST_CLK_GPIO 32 -#define TEST_VALID_GPIO 36 -#define TEST_DATA0_GPIO 20 -#define TEST_DATA1_GPIO 21 -#define TEST_DATA2_GPIO 22 -#define TEST_DATA3_GPIO 23 -#define TEST_DATA4_GPIO 45 -#define TEST_DATA5_GPIO 46 -#define TEST_DATA6_GPIO 47 -#define TEST_DATA7_GPIO 48 +#define TEST_CLK_GPIO 33 +#define TEST_EXT_CLK_GPIO 34 +#define TEST_VALID_GPIO 32 +#define TEST_DATA0_GPIO 24 +#define TEST_DATA1_GPIO 25 +#define TEST_DATA2_GPIO 26 +#define TEST_DATA3_GPIO 27 +#define TEST_DATA4_GPIO 28 +#define TEST_DATA5_GPIO 29 +#define TEST_DATA6_GPIO 30 +#define TEST_DATA7_GPIO 31 #else #error "Unsupported target" #endif diff --git a/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_tx.c b/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_tx.c index cb24b7a557..a71aeebbf6 100644 --- a/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_tx.c +++ b/components/esp_driver_parlio/test_apps/parlio/main/test_parlio_tx.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 */ @@ -11,9 +11,11 @@ #include "unity.h" #include "driver/parlio_tx.h" #include "driver/gpio.h" +#include "hal/parlio_ll.h" #include "soc/soc_caps.h" #include "esp_attr.h" #include "test_board.h" +#include "soc/parl_io_struct.h" TEST_CASE("parallel_tx_unit_install_uninstall", "[parlio_tx]") { @@ -274,3 +276,109 @@ TEST_CASE("parallel_tx_clock_gating", "[paralio_tx]") TEST_ESP_OK(parlio_del_tx_unit(tx_unit)); } #endif // SOC_PARLIO_TX_CLK_SUPPORT_GATING + +static void test_gpio_simulate_rising_edge(int gpio_sig, size_t times) +{ + while (times--) { + gpio_set_level(gpio_sig, 0); + gpio_set_level(gpio_sig, 1); + gpio_set_level(gpio_sig, 0); + } +} + +static uint8_t test_gpio_get_output_data(gpio_num_t* gpio, size_t gpio_num) +{ + uint8_t result = 0; + for (size_t i = 0; i < gpio_num; i++) { + int level = gpio_get_level(gpio[i]); + result |= level << i; + } + return result; +} + +static void test_use_external_non_free_running_clock(parlio_tx_unit_handle_t tx_unit, parlio_tx_unit_config_t config, int test_round) +{ + uint32_t clock_div = config.input_clk_src_freq_hz / config.output_clk_freq_hz; + TEST_ESP_OK(parlio_new_tx_unit(&config, &tx_unit)); + TEST_ESP_OK(parlio_tx_unit_enable(tx_unit)); + // let core clock running for a while to update the clock divider threshold + esp_rom_delay_us(100 * 1000); + parlio_transmit_config_t transmit_config = { + .idle_value = 0xAA, + }; + __attribute__((aligned(64))) uint8_t payload[256] = {0}; + for (int i = 0; i < 256; i++) { + payload[i] = i; + } + + for (int round = 0; round < test_round; round++) { + TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload, 256 * sizeof(uint8_t) * 8, &transmit_config)); + for (int i = 0; i < 256; i++) { + // After "clock_div" times external pulses pass through the internal frequency divider, the parlio core clock generates a single pulse. + test_gpio_simulate_rising_edge(TEST_EXT_CLK_GPIO, clock_div); + TEST_ASSERT_EQUAL(i, test_gpio_get_output_data(config.data_gpio_nums, config.data_width)); + } + // In order to update the idle value, an additional rising edge is required + test_gpio_simulate_rising_edge(TEST_EXT_CLK_GPIO, clock_div); + TEST_ASSERT_EQUAL(transmit_config.idle_value, test_gpio_get_output_data(config.data_gpio_nums, config.data_width)); + TEST_ESP_OK(parlio_tx_unit_wait_all_done(tx_unit, 100)); + } + TEST_ESP_OK(parlio_tx_unit_disable(tx_unit)); + TEST_ESP_OK(parlio_del_tx_unit(tx_unit)); +} + +TEST_CASE("parallel tx unit use external non-free running clock", "[parlio_tx]") +{ + printf("use gpio as external clock source\r\n"); + // configure the data gpio for loopback test + gpio_config_t gpio_conf = { + .mode = GPIO_MODE_INPUT, + .pin_bit_mask = BIT64(TEST_DATA0_GPIO) | BIT64(TEST_DATA1_GPIO) | BIT64(TEST_DATA2_GPIO) | BIT64(TEST_DATA3_GPIO) | + BIT64(TEST_DATA4_GPIO) | BIT64(TEST_DATA5_GPIO) | BIT64(TEST_DATA6_GPIO) | BIT64(TEST_DATA7_GPIO), + }; + TEST_ESP_OK(gpio_config(&gpio_conf)); + // configure the external clock output gpio + gpio_conf.mode = GPIO_MODE_OUTPUT; + gpio_conf.pin_bit_mask = BIT64(TEST_EXT_CLK_GPIO); + TEST_ESP_OK(gpio_config(&gpio_conf)); + + printf("install parlio tx unit\r\n"); + parlio_tx_unit_handle_t tx_unit = NULL; + parlio_tx_unit_config_t config = { + .clk_src = PARLIO_CLK_SRC_DEFAULT, + .data_width = 8, + .clk_in_gpio_num = TEST_EXT_CLK_GPIO, + .input_clk_src_freq_hz = 80 * 1000 * 1000, // Note that this is not the real input frequency, we just use it to calculate the clock divider + .valid_gpio_num = -1, // don't generate valid signal + .clk_out_gpio_num = TEST_CLK_GPIO, + .data_gpio_nums = { + TEST_DATA0_GPIO, + TEST_DATA1_GPIO, + TEST_DATA2_GPIO, + TEST_DATA3_GPIO, + TEST_DATA4_GPIO, + TEST_DATA5_GPIO, + TEST_DATA6_GPIO, + TEST_DATA7_GPIO, + }, + .output_clk_freq_hz = 1 * 1000 * 1000, // For the same reason, this is not the real output frequency + .trans_queue_depth = 8, + .max_transfer_size = 256, + .bit_pack_order = PARLIO_BIT_PACK_ORDER_LSB, + .sample_edge = PARLIO_SAMPLE_EDGE_POS, + }; + + uint8_t test_round = 50; + printf("test input clk freq is greater than output clk freq\r\n"); + test_use_external_non_free_running_clock(tx_unit, config, test_round); + + // changes input clk freq + config.input_clk_src_freq_hz = 1 * 1000 * 1000; + printf("test special condition, input clk freq equals to output clk freq\r\n"); + test_use_external_non_free_running_clock(tx_unit, config, test_round); + + TEST_ESP_OK(gpio_reset_pin(TEST_EXT_CLK_GPIO)); + for (int i = 0; i < 8; i++) { + TEST_ESP_OK(gpio_reset_pin(config.data_gpio_nums[i])); + } +}; diff --git a/components/hal/esp32c6/include/hal/parlio_ll.h b/components/hal/esp32c6/include/hal/parlio_ll.h index 3eb277d14f..1679dc35c2 100644 --- a/components/hal/esp32c6/include/hal/parlio_ll.h +++ b/components/hal/esp32c6/include/hal/parlio_ll.h @@ -373,6 +373,7 @@ static inline void parlio_ll_rx_update_config(parl_io_dev_t *dev) * @param dev Parallel IO register base address * @param src Clock source */ +__attribute__((always_inline)) static inline void parlio_ll_tx_set_clock_source(parl_io_dev_t *dev, parlio_clock_source_t src) { (void)dev; diff --git a/components/hal/esp32h2/include/hal/parlio_ll.h b/components/hal/esp32h2/include/hal/parlio_ll.h index dd284cb900..9cb8180898 100644 --- a/components/hal/esp32h2/include/hal/parlio_ll.h +++ b/components/hal/esp32h2/include/hal/parlio_ll.h @@ -380,6 +380,7 @@ static inline void parlio_ll_rx_update_config(parl_io_dev_t *dev) * @param dev Parallel IO register base address * @param src Clock source */ +__attribute__((always_inline)) static inline void parlio_ll_tx_set_clock_source(parl_io_dev_t *dev, parlio_clock_source_t src) { (void)dev; diff --git a/components/hal/esp32p4/include/hal/parlio_ll.h b/components/hal/esp32p4/include/hal/parlio_ll.h index db4731b20b..e32ae87e0f 100644 --- a/components/hal/esp32p4/include/hal/parlio_ll.h +++ b/components/hal/esp32p4/include/hal/parlio_ll.h @@ -413,6 +413,7 @@ static inline void parlio_ll_rx_update_config(parl_io_dev_t *dev) * @param dev Parallel IO register base address * @param src Clock source */ +__attribute__((always_inline)) static inline void _parlio_ll_tx_set_clock_source(parl_io_dev_t *dev, parlio_clock_source_t src) { (void)dev;