Skip to content

Commit f05d47c

Browse files
committed
Merge branch 'feat/parlio_tx_use_dma_eof' into 'master'
feat(parlio_tx): Supported to transmit infinitely long buffer on ESP32-C5 Closes IDF-12732 and IDF-10974 See merge request espressif/esp-idf!38919
2 parents 42682b1 + 00b1d66 commit f05d47c

File tree

10 files changed

+183
-5
lines changed

10 files changed

+183
-5
lines changed

components/esp_driver_parlio/src/parlio_priv.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ typedef esp_err_t (*parlio_tx_bs_disable_fn_t)(parlio_tx_unit_handle_t tx_unit);
165165
typedef struct parlio_tx_unit_t {
166166
struct parlio_unit_t base; // base unit
167167
size_t data_width; // data width
168+
gpio_num_t data_gpio_nums[SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH]; // data GPIO numbers
169+
gpio_num_t valid_gpio_num; // valid signal GPIO number
170+
gpio_num_t clk_out_gpio_num; // output clock GPIO number
171+
gpio_num_t clk_in_gpio_num; // input clock GPIO number
168172
intr_handle_t intr; // allocated interrupt handle
169173
gdma_channel_handle_t dma_chan; // DMA channel
170174
gdma_link_list_handle_t dma_link[PARLIO_DMA_LINK_NUM]; // DMA link list handle

components/esp_driver_parlio/src/parlio_tx.c

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ static esp_err_t parlio_tx_unit_configure_gpio(parlio_tx_unit_t *tx_unit, const
9393
// connect the signal to the GPIO by matrix, it will also enable the output path properly
9494
esp_rom_gpio_connect_out_signal(config->data_gpio_nums[i],
9595
parlio_periph_signals.groups[group_id].tx_units[unit_id].data_sigs[i], false, false);
96+
tx_unit->data_gpio_nums[i] = config->data_gpio_nums[i];
9697
}
9798
}
9899

@@ -113,17 +114,20 @@ static esp_err_t parlio_tx_unit_configure_gpio(parlio_tx_unit_t *tx_unit, const
113114
parlio_periph_signals.groups[group_id].tx_units[unit_id].data_sigs[PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG],
114115
config->flags.invert_valid_out, false);
115116
#endif // !PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG
117+
tx_unit->valid_gpio_num = config->valid_gpio_num;
116118
}
117119
if (config->clk_out_gpio_num >= 0) {
118120
gpio_func_sel(config->clk_out_gpio_num, PIN_FUNC_GPIO);
119121
// connect the signal to the GPIO by matrix, it will also enable the output path properly
120122
esp_rom_gpio_connect_out_signal(config->clk_out_gpio_num,
121123
parlio_periph_signals.groups[group_id].tx_units[unit_id].clk_out_sig, false, false);
124+
tx_unit->clk_out_gpio_num = config->clk_out_gpio_num;
122125
}
123126
if (config->clk_in_gpio_num >= 0) {
124127
gpio_input_enable(config->clk_in_gpio_num);
125128
esp_rom_gpio_connect_in_signal(config->clk_in_gpio_num,
126129
parlio_periph_signals.groups[group_id].tx_units[unit_id].clk_in_sig, false);
130+
tx_unit->clk_in_gpio_num = config->clk_in_gpio_num;
127131
}
128132
return ESP_OK;
129133
}
@@ -338,8 +342,13 @@ esp_err_t parlio_new_tx_unit(const parlio_tx_unit_config_t *config, parlio_tx_un
338342
// set sample clock edge
339343
parlio_ll_tx_set_sample_clock_edge(hal->regs, config->sample_edge);
340344

345+
#if SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
346+
// always use DMA EOF as the Parlio TX EOF if supported
347+
parlio_ll_tx_set_eof_condition(hal->regs, PARLIO_LL_TX_EOF_COND_DMA_EOF);
348+
#else
341349
// In default, use DATA LEN EOF as the Parlio TX EOF
342350
parlio_ll_tx_set_eof_condition(hal->regs, PARLIO_LL_TX_EOF_COND_DATA_LEN);
351+
#endif // SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
343352

344353
// clear any pending interrupt
345354
parlio_ll_clear_interrupt_status(hal->regs, PARLIO_LL_EVENT_TX_MASK);
@@ -373,6 +382,22 @@ esp_err_t parlio_del_tx_unit(parlio_tx_unit_handle_t unit)
373382
ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
374383
ESP_RETURN_ON_FALSE(atomic_load(&unit->fsm) == PARLIO_TX_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "unit not in init state");
375384
ESP_LOGD(TAG, "del tx unit(%d,%d)", unit->base.group->group_id, unit->base.unit_id);
385+
for (size_t i = 0; i < unit->data_width; i++) {
386+
if (unit->data_gpio_nums[i] >= 0) {
387+
gpio_output_disable(unit->data_gpio_nums[i]);
388+
}
389+
}
390+
if (unit->valid_gpio_num >= 0) {
391+
gpio_output_disable(unit->valid_gpio_num);
392+
}
393+
if (unit->clk_out_gpio_num >= 0) {
394+
gpio_output_disable(unit->clk_out_gpio_num);
395+
}
396+
if (unit->clk_in_gpio_num >= 0) {
397+
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT,
398+
parlio_periph_signals.groups[unit->base.group->group_id].tx_units[unit->base.unit_id].clk_in_sig,
399+
false);
400+
}
376401
return parlio_destroy_tx_unit(unit);
377402
}
378403

@@ -440,6 +465,7 @@ static void parlio_tx_do_transaction(parlio_tx_unit_t *tx_unit, parlio_tx_trans_
440465

441466
if (t->flags.loop_transmission) {
442467
// Once a loop transmission is started, it cannot be stopped until it is disabled
468+
// If SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA is supported, setting the eof condition to PARLIO_LL_TX_EOF_COND_DMA_EOF again is harmless
443469
parlio_ll_tx_set_eof_condition(hal->regs, PARLIO_LL_TX_EOF_COND_DMA_EOF);
444470
}
445471

@@ -566,9 +592,11 @@ esp_err_t parlio_tx_unit_disable(parlio_tx_unit_handle_t tx_unit)
566592
parlio_ll_tx_start(hal->regs, false);
567593
parlio_ll_enable_interrupt(hal->regs, PARLIO_LL_EVENT_TX_MASK, false);
568594

595+
#if !SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
569596
// Once a loop teansmission transaction is started, it can only be stopped in disable function
570-
// change the EOF condition to be the data length, so the EOF will be triggered normally
597+
// change the EOF condition to be the data length, so the EOF will be triggered normally in the following transaction
571598
parlio_ll_tx_set_eof_condition(hal->regs, PARLIO_LL_TX_EOF_COND_DATA_LEN);
599+
#endif // !SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
572600

573601
#if CONFIG_PM_ENABLE
574602
// release power management lock
@@ -600,12 +628,13 @@ esp_err_t parlio_tx_unit_transmit(parlio_tx_unit_handle_t tx_unit, const void *p
600628
ESP_RETURN_ON_FALSE(config->flags.loop_transmission == false, ESP_ERR_NOT_SUPPORTED, TAG, "loop transmission is not supported on this chip");
601629
#endif
602630

603-
// check the max payload size if it's not a loop transmission
604-
// workaround for EOF limitation, when DMA EOF issue is fixed, we can remove this check
631+
#if !SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
632+
// check the max payload size if it's not a loop transmission and the DMA EOF is not supported
605633
if (!config->flags.loop_transmission) {
606634
ESP_RETURN_ON_FALSE(tx_unit->max_transfer_bits <= PARLIO_LL_TX_MAX_BITS_PER_FRAME,
607-
ESP_ERR_INVALID_ARG, TAG, "invalid transfer size");
635+
ESP_ERR_INVALID_ARG, TAG, "invalid transfer size, max transfer size should be less than %d", PARLIO_LL_TX_MAX_BITS_PER_FRAME / 8);
608636
}
637+
#endif // !SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
609638

610639
size_t cache_line_size = 0;
611640
size_t alignment = 0;

components/esp_driver_parlio/test_apps/parlio/main/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ if(CONFIG_SOC_BITSCRAMBLER_SUPPORTED)
1010
list(APPEND srcs "test_parlio_bitscrambler.c")
1111
endif()
1212

13+
if(CONFIG_PARLIO_TX_ISR_CACHE_SAFE)
14+
list(APPEND srcs "test_parlio_tx_cache_safe.c")
15+
endif()
16+
1317
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
1418
# the component can be registered as WHOLE_ARCHIVE
1519
idf_component_register(SRCS ${srcs}

components/esp_driver_parlio/test_apps/parlio/main/test_parlio_tx.c

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,3 +598,51 @@ TEST_CASE("parlio_tx_loop_transmission", "[parlio_tx]")
598598
TEST_ESP_OK(parlio_del_tx_unit(tx_unit));
599599
}
600600
#endif // SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION
601+
602+
#if SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
603+
TEST_CASE("parlio_tx can transmit buffer larger than max_size decided by datalen_eof", "[parlio_tx]")
604+
{
605+
printf("install parlio tx unit\r\n");
606+
parlio_tx_unit_handle_t tx_unit = NULL;
607+
parlio_tx_unit_config_t config = {
608+
.clk_src = PARLIO_CLK_SRC_DEFAULT,
609+
.data_width = 4,
610+
.clk_in_gpio_num = -1, // use internal clock source
611+
.valid_gpio_num = TEST_VALID_GPIO, // generate the valid signal
612+
.clk_out_gpio_num = TEST_CLK_GPIO,
613+
.data_gpio_nums = {
614+
TEST_DATA0_GPIO,
615+
TEST_DATA1_GPIO,
616+
TEST_DATA2_GPIO,
617+
TEST_DATA3_GPIO,
618+
},
619+
.output_clk_freq_hz = 10 * 1000 * 1000,
620+
.trans_queue_depth = 1,
621+
.max_transfer_size = 100 * 1024,
622+
.bit_pack_order = PARLIO_BIT_PACK_ORDER_LSB,
623+
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
624+
.flags.clk_gate_en = true,
625+
};
626+
627+
TEST_ESP_OK(parlio_new_tx_unit(&config, &tx_unit));
628+
TEST_ESP_OK(parlio_tx_unit_enable(tx_unit));
629+
630+
const size_t buffer_size = 100 * 1024; // 100KB, larger than the 65535 bytes limit
631+
uint8_t *buffer = heap_caps_malloc(buffer_size, MALLOC_CAP_8BIT | MALLOC_CAP_DMA);
632+
TEST_ASSERT_NOT_NULL(buffer);
633+
for (int i = 0; i < buffer_size; i++) {
634+
buffer[i] = i;
635+
}
636+
637+
parlio_transmit_config_t transmit_config = {
638+
.idle_value = 0x00,
639+
};
640+
641+
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, buffer, buffer_size * 8, &transmit_config));
642+
TEST_ESP_OK(parlio_tx_unit_wait_all_done(tx_unit, -1));
643+
644+
TEST_ESP_OK(parlio_tx_unit_disable(tx_unit));
645+
TEST_ESP_OK(parlio_del_tx_unit(tx_unit));
646+
free(buffer);
647+
}
648+
#endif // SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <stdio.h>
8+
#include "sdkconfig.h"
9+
#include "freertos/FreeRTOS.h"
10+
#include "freertos/task.h"
11+
#include "unity.h"
12+
#include "unity_test_utils.h"
13+
#include "driver/parlio_tx.h"
14+
#include "driver/gpio.h"
15+
#include "hal/parlio_ll.h"
16+
#include "soc/soc_caps.h"
17+
#include "esp_attr.h"
18+
#include "test_board.h"
19+
20+
static void IRAM_ATTR test_delay_post_cache_disable(void *args)
21+
{
22+
esp_rom_delay_us(100000);
23+
}
24+
25+
static void test_parlio_tx_cache_safe(void)
26+
{
27+
printf("install parlio tx unit\r\n");
28+
parlio_tx_unit_handle_t tx_unit = NULL;
29+
parlio_tx_unit_config_t config = {
30+
.clk_src = PARLIO_CLK_SRC_DEFAULT,
31+
.data_width = 1,
32+
.clk_in_gpio_num = -1, // use internal clock source
33+
.valid_gpio_num = TEST_VALID_GPIO, // generate the valid signal
34+
.clk_out_gpio_num = TEST_CLK_GPIO,
35+
.data_gpio_nums = {
36+
TEST_DATA0_GPIO,
37+
},
38+
.output_clk_freq_hz = 10 * 1000 * 1000,
39+
.trans_queue_depth = 4,
40+
.max_transfer_size = 65535,
41+
.bit_pack_order = PARLIO_BIT_PACK_ORDER_LSB,
42+
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
43+
};
44+
45+
TEST_ESP_OK(parlio_new_tx_unit(&config, &tx_unit));
46+
TEST_ESP_OK(parlio_tx_unit_enable(tx_unit));
47+
48+
const size_t buffer_size = 160 * 1000;
49+
const size_t chunk_size = buffer_size / 4; // 40KB per trunk
50+
uint8_t *buffer = heap_caps_malloc(buffer_size, MALLOC_CAP_8BIT | MALLOC_CAP_DMA);
51+
TEST_ASSERT_NOT_NULL(buffer);
52+
for (int i = 0; i < buffer_size; i++) {
53+
buffer[i] = i;
54+
}
55+
56+
parlio_transmit_config_t transmit_config = {
57+
.idle_value = 0x00,
58+
};
59+
60+
for (int i = 0; i < 20; i++) {
61+
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, buffer + (i % 4) * chunk_size, chunk_size * 8, &transmit_config));
62+
}
63+
// during the cache disabled period, the parlio tx unit can be scheduled as well
64+
unity_utils_run_cache_disable_stub(test_delay_post_cache_disable, NULL);
65+
TEST_ESP_OK(parlio_tx_unit_wait_all_done(tx_unit, -1));
66+
67+
TEST_ESP_OK(parlio_tx_unit_disable(tx_unit));
68+
TEST_ESP_OK(parlio_del_tx_unit(tx_unit));
69+
free(buffer);
70+
71+
}
72+
73+
TEST_CASE("parlio tx works with cache disabled", "[parlio]")
74+
{
75+
test_parlio_tx_cache_safe();
76+
}

components/soc/esp32c5/include/soc/Kconfig.soc_caps.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,10 @@ config SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION
10191019
bool
10201020
default y
10211021

1022+
config SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
1023+
bool
1024+
default y
1025+
10221026
config SOC_PARLIO_SUPPORT_SLEEP_RETENTION
10231027
bool
10241028
default y

components/soc/esp32c5/include/soc/soc_caps.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@
392392
#define SOC_PARLIO_RX_CLK_SUPPORT_OUTPUT 1 /*!< Support output RX clock to a GPIO */
393393
#define SOC_PARLIO_TRANS_BIT_ALIGN 1 /*!< Support bit alignment in transaction */
394394
#define SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION 1 /*!< Support loop transmission */
395+
#define SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA 1 /*!< Support to treat DMA EOF as TX unit EOF */
395396
#define SOC_PARLIO_SUPPORT_SLEEP_RETENTION 1 /*!< Support back up registers before sleep */
396397
#define SOC_PARLIO_SUPPORT_SPI_LCD 1 /*!< Support to drive SPI interfaced LCD */
397398
#define SOC_PARLIO_SUPPORT_I80_LCD 1 /*!< Support to drive I80 interfaced LCD */

docs/en/api-reference/peripherals/parlio/parlio_tx.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,12 @@ The waveform of the external clock input is shown below:
320320

321321
After writing the BitScrambler program, we can enable it by calling :cpp:func:`parlio_tx_unit_decorate_bitscrambler`. And configure the :cpp:member:`parlio_transmit_config_t::bitscrambler_program` to point to the binary file of the BitScrambler program. Different transmission transactions can use different BitScrambler programs. The binary file must conform to the BitScrambler assembly language specification, and will be loaded into the BitScrambler's instruction memory at runtime. For details on how to write and compile the BitScrambler program, please refer to :doc:`BitScrambler Programming Guide </api-reference/peripherals/bitscrambler>`.
322322

323+
.. only:: not SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
324+
325+
.. note::
326+
327+
Due to hardware limitations, the bitstream generated by the BitScrambler cannot change the length compared to the original bitstream, otherwise transmission blocking or data loss may occur.
328+
323329
:cpp:func:`parlio_tx_unit_decorate_bitscrambler` and :cpp:func:`parlio_tx_unit_undecorate_bitscrambler` need to be used in pairs. When deleting the TX unit, you need to call :cpp:func:`parlio_tx_unit_undecorate_bitscrambler` first to remove the BitScrambler.
324330

325331
Power Management

docs/zh_CN/api-reference/peripherals/parlio/parlio_tx.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,12 @@ TX 单元可以选择各种不同的时钟源,其中外部时钟源较为特
320320

321321
编写好比特调节器程序后,通过调用 :cpp:func:`parlio_tx_unit_decorate_bitscrambler` 启用比特调节器。并在 :cpp:member:`parlio_transmit_config_t::bitscrambler_program` 配置本次传输使用比特调节器程序的二进制文件。不同的传输事务可以使用不同的比特调节器程序。该二进制文件必须符合比特调节器的汇编语言规范,并且在运行时会被加载到比特调节器的指令存储器中。如何编写并编译比特调节器程序请参考 :doc:`比特调节器编程指南 </api-reference/peripherals/bitscrambler>`。
322322

323+
.. only:: not SOC_PARLIO_TX_SUPPORT_EOF_FROM_DMA
324+
325+
.. note::
326+
327+
由于硬件限制,使用比特调节器生成的比特流与原本比特流相比,长度不能发生变化,否则可能会发生传输阻塞或数据丢失。
328+
323329
:cpp:func:`parlio_tx_unit_decorate_bitscrambler` 和 :cpp:func:`parlio_tx_unit_undecorate_bitscrambler` 需要成对使用。在删除 TX 单元时,需要先调用 :cpp:func:`parlio_tx_unit_undecorate_bitscrambler` 移除比特调节器。
324330

325331
电源管理

examples/peripherals/lcd/parlio_simulate/main/parlio_simulate_example_main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ void example_init_parlio_panel(esp_lcd_panel_io_handle_t *io_handle)
126126
#endif
127127
},
128128
.data_width = CONFIG_EXAMPLE_LCD_PARLIO_DATA_WIDTH,
129-
.max_transfer_bytes = EXAMPLE_LCD_H_RES * 100 * sizeof(uint16_t),
129+
.max_transfer_bytes = EXAMPLE_LCD_H_RES * EXAMPLE_LVGL_DRAW_BUF_LINES * sizeof(uint16_t),
130130
.dma_burst_size = EXAMPLE_DMA_BURST_SIZE,
131131
.cs_gpio_num = EXAMPLE_PIN_NUM_CS,
132132
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,

0 commit comments

Comments
 (0)