diff --git a/components/esp_driver_parlio/src/parlio_tx.c b/components/esp_driver_parlio/src/parlio_tx.c index 7c2515ac51..ca488632b8 100644 --- a/components/esp_driver_parlio/src/parlio_tx.c +++ b/components/esp_driver_parlio/src/parlio_tx.c @@ -61,6 +61,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 @@ -283,6 +284,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; } @@ -453,6 +455,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, @@ -462,12 +476,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); @@ -476,6 +499,9 @@ static void IRAM_ATTR parlio_tx_do_transaction(parlio_tx_unit_t *tx_unit, parlio while (parlio_ll_tx_is_ready(hal->regs) == false); // turn on the core clock after we start the TX unit parlio_ll_tx_start(hal->regs, true); + PARLIO_CLOCK_SRC_ATOMIC() { + parlio_ll_tx_enable_clock(hal->regs, true); + } } esp_err_t parlio_tx_unit_enable(parlio_tx_unit_handle_t tx_unit) @@ -488,14 +514,13 @@ esp_err_t parlio_tx_unit_enable(parlio_tx_unit_handle_t tx_unit) if (tx_unit->pm_lock) { esp_pm_lock_acquire(tx_unit->pm_lock); } - parlio_hal_context_t *hal = &tx_unit->base.group->hal; parlio_ll_enable_interrupt(hal->regs, PARLIO_LL_EVENT_TX_MASK, true); atomic_store(&tx_unit->fsm, PARLIO_TX_FSM_ENABLE); } else { ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_STATE, TAG, "unit not in init state"); } - // enable clock output + // the chip may resumes from light-sleep, in which case the register configuration needs to be resynchronized PARLIO_CLOCK_SRC_ATOMIC() { parlio_ll_tx_enable_clock(hal->regs, true); } @@ -540,13 +565,18 @@ esp_err_t parlio_tx_unit_disable(parlio_tx_unit_handle_t tx_unit) } ESP_RETURN_ON_FALSE(valid_state, ESP_ERR_INVALID_STATE, TAG, "unit can't be disabled in state %d", expected_fsm); - // stop the TX engine + // stop the DMA engine, reset the peripheral state parlio_hal_context_t *hal = &tx_unit->base.group->hal; - // disable clock output + // to stop the undergoing transaction, disable and reset clock PARLIO_CLOCK_SRC_ATOMIC() { parlio_ll_tx_enable_clock(hal->regs, false); } + PARLIO_RCC_ATOMIC() { + parlio_ll_tx_reset_clock(hal->regs); + } gdma_stop(tx_unit->dma_chan); + gdma_reset(tx_unit->dma_chan); + parlio_ll_tx_reset_fifo(hal->regs); parlio_ll_tx_start(hal->regs, false); parlio_ll_enable_interrupt(hal->regs, PARLIO_LL_EVENT_TX_MASK, false); 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 36c4931acb..0147f39e42 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 @@ -33,6 +34,7 @@ extern "C" { #define TEST_DATA7_GPIO 7 #elif CONFIG_IDF_TARGET_ESP32C5 #define TEST_CLK_GPIO 25 +#define TEST_EXT_CLK_GPIO 10 #define TEST_VALID_GPIO 26 #define TEST_DATA0_GPIO 0 #define TEST_DATA1_GPIO 1 @@ -43,27 +45,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 33 -#define TEST_VALID_GPIO 36 -#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 6 -#define TEST_DATA7_GPIO 7 +#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 a556c8a407..e922e2cf09 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]") { @@ -143,7 +145,7 @@ TEST_CASE("parallel_tx_unit_enable_disable", "[parlio_tx]") TEST_DATA7_GPIO, }, .output_clk_freq_hz = 1 * 1000 * 1000, - .trans_queue_depth = 64, + .trans_queue_depth = 4, .max_transfer_size = 256, .bit_pack_order = PARLIO_BIT_PACK_ORDER_LSB, .sample_edge = PARLIO_SAMPLE_EDGE_POS, @@ -155,19 +157,19 @@ TEST_CASE("parallel_tx_unit_enable_disable", "[parlio_tx]") parlio_transmit_config_t transmit_config = { .idle_value = 0x00, }; - __attribute__((aligned(64))) uint8_t payload[128] = {0}; - for (int i = 0; i < 128; i++) { + __attribute__((aligned(64))) uint8_t payload[256] = {0}; + for (int i = 0; i < 256; i++) { payload[i] = i; } - for (int j = 0; j < 64; j++) { - TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload, 128 * sizeof(uint8_t) * 8, &transmit_config)); + for (int j = 0; j < 3; j++) { + TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload, 256 * sizeof(uint8_t) * 8, &transmit_config)); } printf("disable the transaction in the middle\r\n"); while (parlio_tx_unit_disable(tx_unit) != ESP_OK) { esp_rom_delay_us(1000); } - vTaskDelay(pdMS_TO_TICKS(100)); + vTaskDelay(pdMS_TO_TICKS(10)); printf("resume the transaction and pending packets should continue\r\n"); TEST_ESP_OK(parlio_tx_unit_enable(tx_unit)); @@ -288,3 +290,111 @@ TEST_CASE("parallel_tx_clock_gating", "[paralio_tx]") TEST_ESP_OK(gpio_reset_pin(TEST_CLK_GPIO)); } #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, this is a hardware limitation + // the following rising edge count is not a magic value, we just need it to be larger than the clock divider value in the previous test case + test_gpio_simulate_rising_edge(TEST_EXT_CLK_GPIO, 100); + esp_rom_delay_us(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/esp_driver_parlio/test_apps/parlio/sdkconfig.ci.release b/components/esp_driver_parlio/test_apps/parlio/sdkconfig.ci.release index 17aaee1e8e..b9a047d536 100644 --- a/components/esp_driver_parlio/test_apps/parlio/sdkconfig.ci.release +++ b/components/esp_driver_parlio/test_apps/parlio/sdkconfig.ci.release @@ -1,5 +1,6 @@ CONFIG_PM_ENABLE=y CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_PM_DFS_INIT_AUTO=y CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP=y CONFIG_COMPILER_OPTIMIZATION_SIZE=y CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y diff --git a/components/hal/esp32c5/include/hal/parlio_ll.h b/components/hal/esp32c5/include/hal/parlio_ll.h index 5f6d7b0242..da576bace3 100644 --- a/components/hal/esp32c5/include/hal/parlio_ll.h +++ b/components/hal/esp32c5/include/hal/parlio_ll.h @@ -385,6 +385,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; @@ -488,8 +489,11 @@ static inline void parlio_ll_tx_enable_clock_gating(parl_io_dev_t *dev, bool en) /** * @brief Start TX unit to transmit data * + * @note The hardware monitors the rising edge of tx_start as the trigger signal. + * Once the transmission starts, it cannot be stopped by clearing tx_start. + * * @param dev Parallel IO register base address - * @param en True to start, False to stop + * @param en True to start, False to reset the reg state (not meaning the TX unit will be stopped) */ __attribute__((always_inline)) static inline void parlio_ll_tx_start(parl_io_dev_t *dev, bool en) diff --git a/components/hal/esp32c6/include/hal/parlio_ll.h b/components/hal/esp32c6/include/hal/parlio_ll.h index 78d126a33f..1679dc35c2 100644 --- a/components/hal/esp32c6/include/hal/parlio_ll.h +++ b/components/hal/esp32c6/include/hal/parlio_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -30,7 +30,7 @@ #define PARLIO_LL_EVENT_TX_FIFO_EMPTY (1 << 0) #define PARLIO_LL_EVENT_RX_FIFO_FULL (1 << 1) #define PARLIO_LL_EVENT_TX_EOF (1 << 2) -#define PARLIO_LL_EVENT_TX_MASK (PARLIO_LL_EVENT_TX_FIFO_EMPTY | PARLIO_LL_EVENT_TX_EOF) +#define PARLIO_LL_EVENT_TX_MASK (PARLIO_LL_EVENT_TX_EOF) // On C6, TX FIFO EMPTY event always comes with TX EOF event. We don't enable it #define PARLIO_LL_EVENT_RX_MASK (PARLIO_LL_EVENT_RX_FIFO_FULL) #define PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG 15 // TXD[15] can be used a valid signal @@ -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; @@ -450,7 +451,7 @@ static inline void parlio_ll_tx_set_trans_bit_len(parl_io_dev_t *dev, uint32_t b } /** - * @brief Wether to enable the TX clock gating + * @brief Whether to enable the TX clock gating * * @note The TXD[7] will be taken as the gating enable signal * @@ -465,8 +466,11 @@ static inline void parlio_ll_tx_enable_clock_gating(parl_io_dev_t *dev, bool en) /** * @brief Start TX unit to transmit data * + * @note The hardware monitors the rising edge of tx_start as the trigger signal. + * Once the transmission starts, it cannot be stopped by clearing tx_start. + * * @param dev Parallel IO register base address - * @param en True to start, False to stop + * @param en True to start, False to reset the reg state (not meaning the TX unit will be stopped) */ __attribute__((always_inline)) static inline void parlio_ll_tx_start(parl_io_dev_t *dev, bool en) diff --git a/components/hal/esp32h2/include/hal/parlio_ll.h b/components/hal/esp32h2/include/hal/parlio_ll.h index ac3f5d7470..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; @@ -472,8 +473,11 @@ static inline void parlio_ll_tx_enable_clock_gating(parl_io_dev_t *dev, bool en) /** * @brief Start TX unit to transmit data * + * @note The hardware monitors the rising edge of tx_start as the trigger signal. + * Once the transmission starts, it cannot be stopped by clearing tx_start. + * * @param dev Parallel IO register base address - * @param en True to start, False to stop + * @param en True to start, False to reset the reg state (not meaning the TX unit will be stopped) */ __attribute__((always_inline)) static inline void parlio_ll_tx_start(parl_io_dev_t *dev, bool en) diff --git a/components/hal/esp32p4/include/hal/parlio_ll.h b/components/hal/esp32p4/include/hal/parlio_ll.h index c982049870..3d559537d3 100644 --- a/components/hal/esp32p4/include/hal/parlio_ll.h +++ b/components/hal/esp32p4/include/hal/parlio_ll.h @@ -415,6 +415,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; @@ -537,8 +538,11 @@ static inline void parlio_ll_tx_enable_clock_gating(parl_io_dev_t *dev, bool en) /** * @brief Start TX unit to transmit data * + * @note The hardware monitors the rising edge of tx_start as the trigger signal. + * Once the transmission starts, it cannot be stopped by clearing tx_start. + * * @param dev Parallel IO register base address - * @param en True to start, False to stop + * @param en True to start, False to reset the reg state (not meaning the TX unit will be stopped) */ __attribute__((always_inline)) static inline void parlio_ll_tx_start(parl_io_dev_t *dev, bool en)