Merge branch 'feat/support_cs_signal_in_parlio_tx' into 'master'

feat(parlio_tx): support cs signal on esp32c5 v1.0

Closes IDF-12836 and IDF-12633

See merge request espressif/esp-idf!38646
This commit is contained in:
Chen Ji Chang
2025-04-27 11:10:26 +08:00
19 changed files with 221 additions and 32 deletions

View File

@@ -30,6 +30,8 @@ typedef struct {
gpio_num_t clk_out_gpio_num; /*!< GPIO number of the output clock signal, the clock is synced with TX data */
gpio_num_t valid_gpio_num; /*!< GPIO number of the valid signal, which stays high when transferring data.
Note that, the valid signal will always occupy the MSB data bit */
uint16_t valid_start_delay; /*!< The clock cycles that the valid signal becomes active before data start */
uint16_t valid_stop_delay; /*!< The clock cycles that the valid signal keeps active after data end */
size_t trans_queue_depth; /*!< Depth of internal transaction queue */
size_t max_transfer_size; /*!< Maximum transfer 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 */

View File

@@ -130,7 +130,7 @@ static esp_err_t parlio_tx_unit_configure_gpio(parlio_tx_unit_t *tx_unit, const
parlio_periph_signals.groups[group_id].tx_units[unit_id].data_sigs[i], false, false);
}
}
// Note: the valid signal will override TXD[PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG]
if (config->valid_gpio_num >= 0) {
gpio_func_sel(config->valid_gpio_num, PIN_FUNC_GPIO);
@@ -138,11 +138,20 @@ static esp_err_t parlio_tx_unit_configure_gpio(parlio_tx_unit_t *tx_unit, const
if (config->flags.io_loop_back) {
gpio_input_enable(config->valid_gpio_num);
}
#if !PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG
// Configure CS signal if supported
// Note: the default value of CS signal is low, so we need to invert the CS to keep compatible with the default value
// connect the signal to the GPIO by matrix, it will also enable the output path properly
esp_rom_gpio_connect_out_signal(config->valid_gpio_num,
parlio_periph_signals.groups[group_id].tx_units[unit_id].cs_sig,
!config->flags.invert_valid_out, false);
#else
// connect the signal to the GPIO by matrix, it will also enable the output path properly
// Note: the valid signal will override TXD[PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG]
esp_rom_gpio_connect_out_signal(config->valid_gpio_num,
parlio_periph_signals.groups[group_id].tx_units[unit_id].data_sigs[PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG],
config->flags.invert_valid_out, false);
#endif // !PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG
}
if (config->clk_out_gpio_num >= 0) {
gpio_func_sel(config->clk_out_gpio_num, PIN_FUNC_GPIO);
@@ -295,11 +304,16 @@ esp_err_t parlio_new_tx_unit(const parlio_tx_unit_config_t *config, parlio_tx_un
// data_width must be power of 2 and less than or equal to SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH
ESP_RETURN_ON_FALSE(data_width && (data_width <= SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH) && ((data_width & (data_width - 1)) == 0),
ESP_ERR_INVALID_ARG, TAG, "invalid data width");
// No need to check data width conflict with valid signal when CS is supported
#if PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG
// data_width must not conflict with the valid signal
ESP_RETURN_ON_FALSE(!(config->valid_gpio_num >= 0 && data_width > PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG),
ESP_ERR_INVALID_ARG, TAG, "valid signal conflicts with data signal");
#endif // PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG
#if SOC_PARLIO_TX_CLK_SUPPORT_GATING
// clock gating is controlled by either the MSB bit of data bus or the valid signal
// clock gating is controlled by either the MSB bit of data bus or the valid signal(or CS signal when supported)
ESP_RETURN_ON_FALSE(!(config->flags.clk_gate_en && config->valid_gpio_num < 0 && config->data_width <= PARLIO_LL_TX_DATA_LINE_AS_CLK_GATE),
ESP_ERR_INVALID_ARG, TAG, "no gpio can control the clock gating");
#else
@@ -351,13 +365,25 @@ esp_err_t parlio_new_tx_unit(const parlio_tx_unit_config_t *config, parlio_tx_un
// set data width
parlio_ll_tx_set_bus_width(hal->regs, data_width);
unit->idle_value_mask = (1 << data_width) - 1;
// set valid delay
ESP_GOTO_ON_FALSE(parlio_ll_tx_set_valid_delay(hal->regs, config->valid_start_delay, config->valid_stop_delay), ESP_ERR_INVALID_ARG, err, TAG, "invalid valid delay");
// whether to use the valid signal
#if !PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG
// the clock gating source is actual selectable, it doesn't rely on the available of valid GPIO.
// but there is no use case that valid signal is used, but clocking gating is still controlled by data.
if (config->valid_gpio_num >= 0) {
parlio_ll_tx_clock_gating_from_valid(hal->regs, true);
} else {
parlio_ll_tx_clock_gating_from_valid(hal->regs, false);
}
#else
if (config->valid_gpio_num >= 0) {
parlio_ll_tx_treat_msb_as_valid(hal->regs, true);
unit->idle_value_mask &= ~(1 << PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG);
} else {
parlio_ll_tx_treat_msb_as_valid(hal->regs, false);
}
#endif // !PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG
// set data byte packing order
if (data_width < 8) {
parlio_ll_tx_set_bit_pack_order(hal->regs, config->bit_pack_order);

View File

@@ -50,8 +50,10 @@ TEST_CASE("parallel_tx_unit_install_uninstall", "[parlio_tx]")
config.input_clk_src_freq_hz = 1000000;
config.valid_gpio_num = 0;
#if PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG
// failed because of data line conflict with valid signal
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, parlio_new_tx_unit(&config, &units[0]));
#endif // !PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG
config.data_width = 4;
TEST_ESP_OK(parlio_new_tx_unit(&config, &units[0]));
@@ -288,6 +290,73 @@ TEST_CASE("parallel_tx_clock_gating", "[paralio_tx]")
TEST_ESP_OK(parlio_del_tx_unit(tx_unit));
TEST_ESP_OK(gpio_reset_pin(TEST_CLK_GPIO));
}
#if !PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG
TEST_CASE("parallel_tx_clock_gating_and_msb_coexist", "[paralio_tx]")
{
printf("init a gpio to read parlio_tx clk output\r\n");
gpio_config_t test_gpio_conf = {
.mode = GPIO_MODE_INPUT,
.pin_bit_mask = BIT64(TEST_CLK_GPIO) | BIT64(TEST_DATA7_GPIO),
};
TEST_ESP_OK(gpio_config(&test_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 = -1, // use internal clock source
.valid_gpio_num = TEST_VALID_GPIO, // generate the 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,
.trans_queue_depth = 4,
.max_transfer_size = 256,
.bit_pack_order = PARLIO_BIT_PACK_ORDER_MSB,
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
.valid_start_delay = 5,
.valid_stop_delay = 5,
.flags.clk_gate_en = true, // enable clock gating, controlled by the CS signal
};
TEST_ESP_OK(parlio_new_tx_unit(&config, &tx_unit));
TEST_ESP_OK(parlio_tx_unit_enable(tx_unit));
printf("send packets and see if the clock is gated when there's no transaction on line\r\n");
parlio_transmit_config_t transmit_config = {
// set the idle value to 0x80, so that the MSB is high when there's no transaction
.idle_value = 0x80,
};
uint32_t size = 256;
__attribute__((aligned(64))) uint8_t payload[size];
for (int i = 0; i < size; i++) {
payload[i] = i;
}
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload, size * sizeof(uint8_t) * 8, &transmit_config));
TEST_ESP_OK(parlio_tx_unit_wait_all_done(tx_unit, -1));
// check if the level on the clock line is low
TEST_ASSERT_EQUAL(0, gpio_get_level(TEST_CLK_GPIO));
TEST_ASSERT_EQUAL(1, gpio_get_level(TEST_DATA7_GPIO));
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload, size * sizeof(uint8_t) * 8, &transmit_config));
TEST_ESP_OK(parlio_tx_unit_wait_all_done(tx_unit, -1));
TEST_ASSERT_EQUAL(0, gpio_get_level(TEST_CLK_GPIO));
TEST_ASSERT_EQUAL(0, gpio_get_level(TEST_CLK_GPIO));
TEST_ASSERT_EQUAL(1, gpio_get_level(TEST_DATA7_GPIO));
TEST_ESP_OK(parlio_tx_unit_disable(tx_unit));
TEST_ESP_OK(parlio_del_tx_unit(tx_unit));
TEST_ESP_OK(gpio_reset_pin(TEST_CLK_GPIO));
}
#endif // !PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG
#endif // SOC_PARLIO_TX_CLK_SUPPORT_GATING
#if SOC_PSRAM_DMA_CAPABLE

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -34,12 +34,20 @@ extern "C" {
#define TEST_LCD_PCLK_GPIO (5)
#define TEST_LCD_DATA0_GPIO (4)
#elif CONFIG_IDF_TARGET_ESP32C5
#define TEST_LCD_BK_LIGHT_GPIO (1)
#define TEST_LCD_RST_GPIO (7)
#define TEST_LCD_CS_GPIO (27)
#define TEST_LCD_DC_GPIO (6)
#define TEST_LCD_PCLK_GPIO (25)
#define TEST_LCD_DATA0_GPIO (26)
#define TEST_LCD_BK_LIGHT_GPIO (5)
#define TEST_LCD_RST_GPIO (2)
#define TEST_LCD_CS_GPIO (0)
#define TEST_LCD_DC_GPIO (3)
#define TEST_LCD_PCLK_GPIO (1)
#define TEST_LCD_DATA0_GPIO (4)
#define TEST_LCD_DATA1_GPIO (9)
#define TEST_LCD_DATA2_GPIO (28)
#define TEST_LCD_DATA3_GPIO (24)
#define TEST_LCD_DATA4_GPIO (14)
#define TEST_LCD_DATA5_GPIO (23)
#define TEST_LCD_DATA6_GPIO (13)
#define TEST_LCD_DATA7_GPIO (27)
#endif
#define TEST_LCD_PIXEL_CLOCK_HZ (10 * 1000 * 1000)

View File

@@ -32,11 +32,8 @@
#define PARLIO_LL_EVENT_TX_MASK (PARLIO_LL_EVENT_TX_FIFO_EMPTY | PARLIO_LL_EVENT_TX_EOF)
#define PARLIO_LL_EVENT_RX_MASK (PARLIO_LL_EVENT_RX_FIFO_FULL)
#define PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG 7 // TXD[7] can be used a valid signal
#define PARLIO_LL_TX_DATA_LINE_AS_CLK_GATE 7 // TXD[7] can be used as clock gate signal
#define PARLIO_LL_CLK_DIVIDER_MAX (0) // Not support fractional divider
#define PARLIO_LL_TX_VALID_MAX_DELAY 32767
#ifdef __cplusplus
extern "C" {
#endif
@@ -526,18 +523,34 @@ static inline void parlio_ll_tx_start(parl_io_dev_t *dev, bool en)
}
/**
* @brief Whether to treat the MSB of TXD as the valid signal
*
* @note If enabled, TXD[7] will work as valid signal, which stay high during data transmission.
* @brief Set the clock gating from the valid signal
*
* @param dev Parallel IO register base address
* @param en True to enable, False to disable
* @param en If set to true, the clock is gated by the valid signal, otherwise it is gated by the MSB of the data line.
*/
static inline void parlio_ll_tx_treat_msb_as_valid(parl_io_dev_t *dev, bool en)
static inline void parlio_ll_tx_clock_gating_from_valid(parl_io_dev_t *dev, bool en)
{
dev->tx_genrl_cfg.tx_valid_output_en = en;
}
/**
* @brief Set TX valid signal delay
*
* @param dev Parallel IO register base address
* @param start_delay Number of clock cycles to delay
* @param stop_delay Number of clock cycles to delay
* @return true: success, false: valid delay is not supported
*/
static inline bool parlio_ll_tx_set_valid_delay(parl_io_dev_t *dev, uint32_t start_delay, uint32_t stop_delay)
{
if (start_delay > PARLIO_LL_TX_VALID_MAX_DELAY || stop_delay > PARLIO_LL_TX_VALID_MAX_DELAY) {
return false;
}
HAL_FORCE_MODIFY_U32_REG_FIELD(dev->tx_cs_cfg, tx_cs_start_delay, start_delay);
HAL_FORCE_MODIFY_U32_REG_FIELD(dev->tx_cs_cfg, tx_cs_stop_delay, stop_delay);
return true;
}
/**
* @brief Set the sample clock edge
*

View File

@@ -509,6 +509,23 @@ static inline void parlio_ll_tx_treat_msb_as_valid(parl_io_dev_t *dev, bool en)
dev->tx_cfg0.tx_hw_valid_en = en;
}
/**
* @brief Set TX valid signal delay
*
* @param dev Parallel IO register base address
* @param start_delay Number of clock cycles to delay
* @param stop_delay Number of clock cycles to delay
* @return true: success, false: valid delay is not supported
*/
static inline bool parlio_ll_tx_set_valid_delay(parl_io_dev_t *dev, uint32_t start_delay, uint32_t stop_delay)
{
(void)dev;
if (start_delay == 0 && stop_delay == 0) {
return true;
}
return false;
}
/**
* @brief Set the sample clock edge
*

View File

@@ -37,8 +37,6 @@
#define PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG 7 // TXD[7] can be used a valid signal
#define PARLIO_LL_TX_DATA_LINE_AS_CLK_GATE 7 // TXD[7] can be used as clock gate signal
#define PARLIO_LL_CLK_DIVIDER_MAX (0) // Not support fractional divider
#ifdef __cplusplus
extern "C" {
#endif
@@ -534,7 +532,7 @@ static inline void parlio_ll_tx_start(parl_io_dev_t *dev, bool en)
/**
* @brief Whether to treat the MSB of TXD as the valid signal
*
* @note If enabled, TXD[15] will work as valid signal, which stay high during data transmission.
* @note If enabled, TXD[7] will work as valid signal, which stay high during data transmission.
*
* @param dev Parallel IO register base address
* @param en True to enable, False to disable
@@ -544,6 +542,23 @@ static inline void parlio_ll_tx_treat_msb_as_valid(parl_io_dev_t *dev, bool en)
dev->tx_genrl_cfg.tx_valid_output_en = en;
}
/**
* @brief Set TX valid signal delay
*
* @param dev Parallel IO register base address
* @param start_delay Number of clock cycles to delay
* @param stop_delay Number of clock cycles to delay
* @return true: success, false: valid delay is not supported
*/
static inline bool parlio_ll_tx_set_valid_delay(parl_io_dev_t *dev, uint32_t start_delay, uint32_t stop_delay)
{
(void)dev;
if (start_delay == 0 && stop_delay == 0) {
return true;
}
return false;
}
/**
* @brief Set the sample clock edge
*

View File

@@ -523,6 +523,23 @@ static inline void parlio_ll_tx_set_trans_bit_len(parl_io_dev_t *dev, uint32_t b
dev->tx_data_cfg.tx_bitlen = bitlen;
}
/**
* @brief Set TX valid signal delay
*
* @param dev Parallel IO register base address
* @param start_delay Number of clock cycles to delay
* @param stop_delay Number of clock cycles to delay
* @return true: success, false: valid delay is not supported
*/
static inline bool parlio_ll_tx_set_valid_delay(parl_io_dev_t *dev, uint32_t start_delay, uint32_t stop_delay)
{
(void)dev;
if (start_delay == 0 && stop_delay == 0) {
return true;
}
return false;
}
/**
* @brief Check if tx size can be determined by DMA
*

View File

@@ -991,6 +991,10 @@ config SOC_PARLIO_SUPPORT_SPI_LCD
bool
default y
config SOC_PARLIO_SUPPORT_I80_LCD
bool
default y
config SOC_MPI_MEM_BLOCKS_NUM
int
default 4

View File

@@ -389,6 +389,7 @@
#define SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION 1 /*!< Support loop transmission */
#define SOC_PARLIO_SUPPORT_SLEEP_RETENTION 1 /*!< Support back up registers before sleep */
#define SOC_PARLIO_SUPPORT_SPI_LCD 1 /*!< Support to drive SPI interfaced LCD */
#define SOC_PARLIO_SUPPORT_I80_LCD 1 /*!< Support to drive I80 interfaced LCD */
/*--------------------------- MPI CAPS ---------------------------------------*/
#define SOC_MPI_MEM_BLOCKS_NUM (4)

View File

@@ -27,6 +27,7 @@ const parlio_signal_conn_t parlio_periph_signals = {
},
.clk_out_sig = PARL_TX_CLK_OUT_IDX,
.clk_in_sig = PARL_TX_CLK_IN_IDX,
.cs_sig = PARL_TX_CS_O_IDX,
}
},
.rx_units = {

View File

@@ -35,6 +35,7 @@ const parlio_signal_conn_t parlio_periph_signals = {
},
.clk_out_sig = PARL_TX_CLK_OUT_IDX,
.clk_in_sig = PARL_TX_CLK_IN_IDX,
.cs_sig = -1,
}
},
.rx_units = {

View File

@@ -27,6 +27,7 @@ const parlio_signal_conn_t parlio_periph_signals = {
},
.clk_out_sig = PARL_TX_CLK_OUT_IDX,
.clk_in_sig = PARL_TX_CLK_IN_IDX,
.cs_sig = -1,
}
},
.rx_units = {

View File

@@ -35,6 +35,7 @@ const parlio_signal_conn_t parlio_periph_signals = {
},
.clk_out_sig = PARLIO_TX_CLK_PAD_OUT_IDX,
.clk_in_sig = PARLIO_TX_CLK_PAD_IN_IDX,
.cs_sig = -1,
}
},
.rx_units = {

View File

@@ -29,6 +29,7 @@ typedef struct {
const int data_sigs[SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH];
const int clk_out_sig;
const int clk_in_sig;
const int cs_sig;
} tx_units[SOC_PARLIO_TX_UNITS_PER_GROUP];
struct {
const int data_sigs[SOC_PARLIO_RX_UNIT_MAX_DATA_WIDTH];