feat(lcd): support buffer switch interrupt

This commit is contained in:
Chen Jichang
2026-04-20 18:16:49 +08:00
parent fb2e11f070
commit f8ff130959
34 changed files with 417 additions and 119 deletions

View File

@@ -63,6 +63,7 @@ typedef bool (*gdma_event_callback_t)(gdma_channel_handle_t dma_chan, gdma_event
typedef struct {
gdma_event_callback_t on_trans_eof; /*!< Invoked when TX engine meets EOF descriptor */
gdma_event_callback_t on_descr_err; /*!< Invoked when DMA encounters a descriptor error */
gdma_event_callback_t on_link_switch; /*!< Invoked when TX link list switches to a new descriptor chain */
} gdma_tx_event_callbacks_t;
/**
@@ -305,6 +306,26 @@ esp_err_t gdma_get_group_channel_id(gdma_channel_handle_t dma_chan, int *group_i
*/
esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_tx_event_callbacks_t *cbs, void *user_data);
/**
* @brief Request an interrupt when the TX channel switches to a new descriptor chain
*
* @note This API is only available on targets that support TX link switch interrupt.
* @note Register the `on_link_switch` callback by `gdma_register_tx_event_callbacks()` before calling this API.
* @note The TX EOF event indicates that GDMA has reached an EOF descriptor. By contrast, the TX link switch
* event indicates that GDMA has started using the next descriptor chain after a link update.
* GDMA can prefetch descriptors from the updated link before the EOF event, so EOF doesn't always mean
* the previous descriptor chain is no longer referenced by GDMA. The link switch event is needed when
* the caller must know that the previous descriptor chain can be reused safely.
*
* @param[in] dma_tx_chan GDMA TX channel handle
* @return
* - ESP_OK: Request link switch event successfully
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_NOT_SUPPORTED: Link switch event is not supported
* - ESP_ERR_INVALID_STATE: Link switch callback is not registered
*/
esp_err_t gdma_request_link_switch_event(gdma_channel_handle_t dma_tx_chan);
/**
* @brief Set GDMA event callbacks for RX channel
* @note This API will install GDMA interrupt service for the channel internally

View File

@@ -20,24 +20,28 @@ entries:
gdma_hal_top: gdma_hal_clear_intr (noflash)
gdma_hal_top: gdma_hal_read_intr_status (noflash)
gdma_hal_top: gdma_hal_get_eof_desc_addr (noflash)
gdma_hal_top: gdma_hal_is_tx_link_switch_event_supported (noflash)
# GDMA implementation layer for AHB-DMA version 1
if SOC_AHB_GDMA_VERSION = 1:
gdma_hal_ahb_v1: gdma_ahb_hal_clear_intr (noflash)
gdma_hal_ahb_v1: gdma_ahb_hal_read_intr_status (noflash)
gdma_hal_ahb_v1: gdma_ahb_hal_get_eof_desc_addr (noflash)
gdma_hal_ahb_v1: gdma_ahb_hal_is_tx_link_switch_event_supported (noflash)
# GDMA implementation layer for AHB-DMA version 2
if SOC_AHB_GDMA_VERSION = 2:
gdma_hal_ahb_v2: gdma_ahb_hal_clear_intr (noflash)
gdma_hal_ahb_v2: gdma_ahb_hal_read_intr_status (noflash)
gdma_hal_ahb_v2: gdma_ahb_hal_get_eof_desc_addr (noflash)
gdma_hal_ahb_v2: gdma_ahb_hal_is_tx_link_switch_event_supported (noflash)
# GDMA implementation layer for AXI-DMA
if SOC_AXI_GDMA_SUPPORTED = y:
gdma_hal_axi: gdma_axi_hal_clear_intr (noflash)
gdma_hal_axi: gdma_axi_hal_read_intr_status (noflash)
gdma_hal_axi: gdma_axi_hal_get_eof_desc_addr (noflash)
gdma_hal_axi: gdma_axi_hal_is_tx_link_switch_event_supported (noflash)
# put GDMA control HAL functions in IRAM
if GDMA_CTRL_FUNC_IN_IRAM = y:

View File

@@ -550,6 +550,12 @@ esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_
gdma_group_t *group = pair->group;
gdma_hal_context_t *hal = &group->hal;
gdma_tx_channel_t *tx_chan = __containerof(dma_chan, gdma_tx_channel_t, base);
bool link_switch_event_supported = gdma_hal_is_tx_link_switch_event_supported(hal);
if (cbs->on_link_switch) {
ESP_RETURN_ON_FALSE(link_switch_event_supported,
ESP_ERR_NOT_SUPPORTED, TAG, "on_link_switch not supported");
}
if (dma_chan->flags.isr_cache_safe) {
if (cbs->on_trans_eof) {
@@ -560,6 +566,10 @@ esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_descr_err), ESP_ERR_INVALID_ARG,
TAG, "on_descr_err not in IRAM");
}
if (cbs->on_link_switch) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_link_switch), ESP_ERR_INVALID_ARG,
TAG, "on_link_switch not in IRAM");
}
if (user_data) {
ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG,
TAG, "user context not in internal RAM");
@@ -571,8 +581,16 @@ esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_
// enable/disable GDMA interrupt events for TX channel
esp_os_enter_critical(&pair->spinlock);
gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_EOF, cbs->on_trans_eof != NULL);
gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_DESC_ERROR, cbs->on_descr_err != NULL);
gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_EOF,
cbs->on_trans_eof != NULL);
gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_DESC_ERROR,
cbs->on_descr_err != NULL);
#if GDMA_LL_EVENT_TX_LINK_SWITCH
if (link_switch_event_supported) {
gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_LINK_SWITCH,
cbs->on_link_switch != NULL);
}
#endif // GDMA_LL_EVENT_TX_LINK_SWITCH
esp_os_exit_critical(&pair->spinlock);
memcpy(&tx_chan->cbs, cbs, sizeof(gdma_tx_event_callbacks_t));
@@ -703,6 +721,30 @@ esp_err_t gdma_reset(gdma_channel_handle_t dma_chan)
return ESP_OK;
}
esp_err_t gdma_request_link_switch_event(gdma_channel_handle_t dma_tx_chan)
{
if (!dma_tx_chan || dma_tx_chan->direction != GDMA_CHANNEL_DIRECTION_TX) {
return ESP_ERR_INVALID_ARG;
}
gdma_pair_t *pair = dma_tx_chan->pair;
gdma_group_t *group = pair->group;
gdma_hal_context_t *hal = &group->hal;
gdma_tx_channel_t *tx_chan = __containerof(dma_tx_chan, gdma_tx_channel_t, base);
if (!gdma_hal_is_tx_link_switch_event_supported(hal)) {
return ESP_ERR_NOT_SUPPORTED;
}
if (!tx_chan->cbs.on_link_switch) {
return ESP_ERR_INVALID_STATE;
}
esp_os_enter_critical_safe(&dma_tx_chan->spinlock);
gdma_hal_request_link_switch_event(hal, pair->pair_id, dma_tx_chan->direction);
esp_os_exit_critical_safe(&dma_tx_chan->spinlock);
return ESP_OK;
}
static void gdma_try_free_group_handle(gdma_group_t *group)
{
int group_id = group->group_id;
@@ -1000,6 +1042,12 @@ void gdma_default_tx_isr(void *args)
if ((intr_status & GDMA_LL_EVENT_TX_DESC_ERROR) && tx_chan->cbs.on_descr_err) {
need_yield |= tx_chan->cbs.on_descr_err(&tx_chan->base, NULL, tx_chan->user_data);
}
#if GDMA_LL_EVENT_TX_LINK_SWITCH
if (gdma_hal_is_tx_link_switch_event_supported(hal) &&
(intr_status & GDMA_LL_EVENT_TX_LINK_SWITCH) && tx_chan->cbs.on_link_switch) {
need_yield |= tx_chan->cbs.on_link_switch(&tx_chan->base, NULL, tx_chan->user_data);
}
#endif // GDMA_LL_EVENT_TX_LINK_SWITCH
if (need_yield) {
portYIELD_FROM_ISR();
}
@@ -1031,7 +1079,7 @@ static esp_err_t gdma_install_rx_interrupt(gdma_rx_channel_t *rx_chan)
.source = pair_signals->rx_irq_id,
.flags = isr_flags,
.intrstatusreg = gdma_hal_get_intr_status_reg(hal, pair_id, GDMA_CHANNEL_DIRECTION_RX),
.intrstatusmask = GDMA_LL_RX_EVENT_MASK,
.intrstatusmask = hal->priv_data->rx_event_mask,
.handler = gdma_default_rx_isr,
.arg = rx_chan,
.bind_by.name = tx_rx_share_irq ? pair_signals->name : NULL,
@@ -1077,7 +1125,7 @@ static esp_err_t gdma_install_tx_interrupt(gdma_tx_channel_t *tx_chan)
.source = pair_signals->tx_irq_id,
.flags = isr_flags,
.intrstatusreg = gdma_hal_get_intr_status_reg(hal, pair_id, GDMA_CHANNEL_DIRECTION_TX),
.intrstatusmask = GDMA_LL_TX_EVENT_MASK,
.intrstatusmask = hal->priv_data->tx_event_mask,
.handler = gdma_default_tx_isr,
.arg = tx_chan,
.bind_by.name = tx_rx_share_irq ? pair_signals->name : NULL,

View File

@@ -24,8 +24,8 @@ extern "C" {
#define GDMA_LL_CHANNEL_MAX_PRIORITY 5 // supported priority levels: [0,5]
#define GDMA_LL_CHANNEL_MAX_WEIGHT 15 // supported weight levels: [0,15]
#define GDMA_LL_RX_EVENT_MASK (0x7F)
#define GDMA_LL_TX_EVENT_MASK (0x3F)
#define AHB_DMA_LL_RX_EVENT_MASK (0x7F)
#define AHB_DMA_LL_TX_EVENT_MASK (0x3F)
// for M2M mode, hardware will automatically assign peri_sel ID depends on the channel number (ch0: 10, ch1: 11, ch2: 12)
#define AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0x1C00)

View File

@@ -24,8 +24,8 @@ extern "C" {
#define GDMA_LL_CHANNEL_MAX_PRIORITY 5 // supported priority levels: [0,5]
#define GDMA_LL_CHANNEL_MAX_WEIGHT 15 // supported weight levels: [0,15]
#define GDMA_LL_RX_EVENT_MASK (0x7F)
#define GDMA_LL_TX_EVENT_MASK (0x3F)
#define AHB_DMA_LL_RX_EVENT_MASK (0x7F)
#define AHB_DMA_LL_TX_EVENT_MASK (0x3F)
// any "dummy" peripheral ID can be used for M2M mode
#define AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFE75)

View File

@@ -23,8 +23,8 @@ extern "C" {
#define GDMA_LL_CHANNEL_MAX_PRIORITY 5 // supported priority levels: [0,5]
#define GDMA_LL_RX_EVENT_MASK (0x7F)
#define GDMA_LL_TX_EVENT_MASK (0x3F)
#define AHB_DMA_LL_RX_EVENT_MASK (0x7F)
#define AHB_DMA_LL_TX_EVENT_MASK (0x3F)
// any "dummy" peripheral ID can be used for M2M mode
#define AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFC00)

View File

@@ -24,6 +24,8 @@ extern "C" {
// any "dummy" peripheral ID can be used for M2M mode
#define AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFAC2)
#define AHB_DMA_LL_RX_EVENT_MASK (0x1F)
#define AHB_DMA_LL_TX_EVENT_MASK (0x0F)
///////////////////////////////////// Common /////////////////////////////////////////
/**

View File

@@ -13,6 +13,7 @@
#include "hal/hal_utils.h"
#include "hal/gdma_types.h"
#include "hal/gdma_ll.h"
#include "hal/config.h"
#include "soc/axi_dma_struct.h"
#include "soc/axi_dma_reg.h"
@@ -24,6 +25,13 @@ extern "C" {
// any "dummy" peripheral ID can be used for M2M mode
#define AXI_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFFC0)
#define AXI_DMA_LL_RX_EVENT_MASK (0x1F)
#if HAL_CONFIG(CHIP_SUPPORT_MIN_REV) >= 300
#define AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH_EVENT 1
#define AXI_DMA_LL_TX_EVENT_MASK (0x40F)
#else
#define AXI_DMA_LL_TX_EVENT_MASK (0x0F)
#endif
///////////////////////////////////// Common /////////////////////////////////////////
/**
@@ -476,6 +484,42 @@ static inline void axi_dma_ll_tx_restart(axi_dma_dev_t *dev, uint32_t channel)
dev->out[channel].conf.out_link1.outlink_restart_chn = 1;
}
#if AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH_EVENT
/**
* @brief Request link switch done indication for TX channel
*/
static inline void axi_dma_ll_tx_request_link_switch_event(axi_dma_dev_t *dev, uint32_t channel)
{
switch (channel) {
case 0:
dev->link_switch_state.link_switch_state_ch0 = 1;
break;
case 1:
dev->link_switch_state.link_switch_state_ch1 = 1;
break;
case 2:
dev->link_switch_state.link_switch_state_ch2 = 1;
break;
default:
break;
}
}
#endif
/**
* @brief Check if TX link switch done indication is supported
*/
__attribute__((always_inline))
static inline bool axi_dma_ll_tx_is_link_switch_event_supported(axi_dma_dev_t *dev)
{
(void)dev;
#if AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH_EVENT
return true;
#else
return false;
#endif
}
/**
* @brief Check if DMA TX descriptor FSM is in IDLE state
*/

View File

@@ -26,9 +26,10 @@
#define GDMA_LL_CHANNEL_MAX_PRIORITY 5 // supported priority levels: [0,5]
#define GDMA_LL_RX_EVENT_MASK (0x1F)
#define GDMA_LL_TX_EVENT_MASK (0x0F)
// the following event bits are only supported by axi-dma
#if HAL_CONFIG(CHIP_SUPPORT_MIN_REV) >= 300
#define GDMA_LL_EVENT_TX_LINK_SWITCH (1<<10)
#endif
// the following event bits are identical for ahb-dma and axi-dma
#define GDMA_LL_EVENT_TX_TOTAL_EOF (1<<3)
#define GDMA_LL_EVENT_TX_DESC_ERROR (1<<2)

View File

@@ -22,8 +22,12 @@ extern "C" {
// any "dummy" peripheral ID can be used for M2M mode
#define AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0x8200)
#define LP_AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFFFC)
#define AHB_DMA_LL_RX_EVENT_MASK (0x7F)
#define AHB_DMA_LL_TX_EVENT_MASK (0x3F)
#define LP_AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFFFC)
#define AHB_DMA_LL_RX_EVENT_MASK (0x7F)
#define AHB_DMA_LL_TX_EVENT_MASK (0x3F)
///////////////////////////////////// Common /////////////////////////////////////////
/**

View File

@@ -24,6 +24,9 @@ extern "C" {
// any "dummy" peripheral ID can be used for M2M mode
#define AXI_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFFC0)
#define AXI_DMA_LL_RX_EVENT_MASK (0x7F)
#define AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH_EVENT 1
#define AXI_DMA_LL_TX_EVENT_MASK (0x43F)
///////////////////////////////////// Common /////////////////////////////////////////
/**
@@ -473,6 +476,36 @@ static inline void axi_dma_ll_tx_restart(axi_dma_dev_t *dev, uint32_t channel)
dev->out[channel].conf.out_link1.outlink_restart_chn = 1;
}
/**
* @brief Request link switch done indication for TX channel
*/
static inline void axi_dma_ll_tx_request_link_switch_event(axi_dma_dev_t *dev, uint32_t channel)
{
switch (channel) {
case 0:
dev->link_switch_state.link_switch_state_ch0 = 1;
break;
case 1:
dev->link_switch_state.link_switch_state_ch1 = 1;
break;
case 2:
dev->link_switch_state.link_switch_state_ch2 = 1;
break;
default:
break;
}
}
/**
* @brief Check if TX link switch done indication is supported
*/
__attribute__((always_inline))
static inline bool axi_dma_ll_tx_is_link_switch_event_supported(axi_dma_dev_t *dev)
{
(void)dev;
return true;
}
/**
* @brief Check if DMA TX descriptor FSM is in IDLE state
*/

View File

@@ -20,9 +20,8 @@
#define GDMA_LL_CHANNEL_MAX_PRIORITY 5 // supported priority levels: [0,5]
#define GDMA_LL_RX_EVENT_MASK (0x7F)
#define GDMA_LL_TX_EVENT_MASK (0x3F)
// the following event bits are only supported by axi-dma
#define GDMA_LL_EVENT_TX_LINK_SWITCH (1<<10)
// the following event bits are identical for ahb-dma, axi-dma and lp-ahb-dma
#define GDMA_LL_EVENT_TX_FIFO_UDF (1<<5)
#define GDMA_LL_EVENT_TX_FIFO_OVF (1<<4)

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -11,6 +11,8 @@
static gdma_hal_priv_data_t gdma_ahb_hal_priv_data = {
.m2m_free_periph_mask = GDMA_LL_M2M_FREE_PERIPH_ID_MASK,
.tx_event_mask = GDMA_LL_TX_EVENT_MASK,
.rx_event_mask = GDMA_LL_RX_EVENT_MASK,
};
void gdma_ahb_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr)
@@ -182,6 +184,12 @@ void gdma_ahb_hal_enable_etm_task(gdma_hal_context_t *hal, int chan_id, gdma_cha
}
#endif // SOC_GDMA_SUPPORT_ETM
bool gdma_ahb_hal_is_tx_link_switch_event_supported(gdma_hal_context_t *hal)
{
(void)hal;
return false;
}
void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config)
{
hal->dev = GDMA_LL_GET_HW(config->group_id - GDMA_LL_AHB_GROUP_START_ID);
@@ -206,5 +214,6 @@ void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config)
#if GDMA_LL_GET(AHB_BURST_SIZE_ADJUSTABLE)
hal->set_burst_size = gdma_ahb_hal_set_burst_size;
#endif // GDMA_LL_GET(AHB_BURST_SIZE_ADJUSTABLE)
hal->is_tx_link_switch_event_supported = gdma_ahb_hal_is_tx_link_switch_event_supported;
hal->priv_data = &gdma_ahb_hal_priv_data;
}

View File

@@ -12,6 +12,8 @@
static gdma_hal_priv_data_t gdma_ahb_hal_priv_data = {
.m2m_free_periph_mask = AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK,
.tx_event_mask = AHB_DMA_LL_TX_EVENT_MASK,
.rx_event_mask = AHB_DMA_LL_RX_EVENT_MASK,
};
void gdma_ahb_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr)
@@ -250,6 +252,12 @@ void gdma_ahb_hal_set_weight(gdma_hal_context_t *hal, int chan_id, gdma_channel_
}
#endif // SOC_GDMA_SUPPORT_WEIGHTED_ARBITRATION
bool gdma_ahb_hal_is_tx_link_switch_event_supported(gdma_hal_context_t *hal)
{
(void)hal;
return false;
}
void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config)
{
hal->ahb_dma_dev = AHB_DMA_LL_GET_HW(config->group_id - GDMA_LL_AHB_GROUP_START_ID);
@@ -280,6 +288,7 @@ void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config)
#if GDMA_LL_GET(AHB_BURST_SIZE_ADJUSTABLE)
hal->set_burst_size = gdma_ahb_hal_set_burst_size;
#endif // GDMA_LL_GET(AHB_BURST_SIZE_ADJUSTABLE)
hal->is_tx_link_switch_event_supported = gdma_ahb_hal_is_tx_link_switch_event_supported;
#if SOC_GDMA_SUPPORT_WEIGHTED_ARBITRATION
hal->set_weight = gdma_ahb_hal_set_weight;
if (config->flags.enable_weighted_arbitration) {
@@ -304,6 +313,8 @@ void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config)
*/
static gdma_hal_priv_data_t gdma_lp_ahb_hal_priv_data = {
.m2m_free_periph_mask = LP_AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK,
.tx_event_mask = AHB_DMA_LL_TX_EVENT_MASK,
.rx_event_mask = AHB_DMA_LL_RX_EVENT_MASK,
};
void gdma_lp_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config)
@@ -333,6 +344,7 @@ void gdma_lp_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *conf
#if GDMA_LL_GET(AHB_BURST_SIZE_ADJUSTABLE)
hal->set_burst_size = gdma_ahb_hal_set_burst_size;
#endif // GDMA_LL_GET(AHB_BURST_SIZE_ADJUSTABLE)
hal->is_tx_link_switch_event_supported = gdma_ahb_hal_is_tx_link_switch_event_supported;
// LP AHB GDMA has a different memory range
lp_ahb_dma_ll_set_default_memory_range(hal->ahb_dma_dev);
}

View File

@@ -12,6 +12,8 @@
static gdma_hal_priv_data_t gdma_axi_hal_priv_data = {
.m2m_free_periph_mask = AXI_DMA_LL_M2M_FREE_PERIPH_ID_MASK,
.tx_event_mask = AXI_DMA_LL_TX_EVENT_MASK,
.rx_event_mask = AXI_DMA_LL_RX_EVENT_MASK,
};
void gdma_axi_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr)
@@ -170,6 +172,15 @@ uint32_t gdma_axi_hal_get_eof_desc_addr(gdma_hal_context_t *hal, int chan_id, gd
}
}
#if AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH_EVENT
void gdma_axi_hal_request_link_switch_event(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir)
{
if (dir == GDMA_CHANNEL_DIRECTION_TX) {
axi_dma_ll_tx_request_link_switch_event(hal->axi_dma_dev, chan_id);
}
}
#endif // AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH_EVENT
#if SOC_GDMA_SUPPORT_CRC
void gdma_axi_hal_clear_crc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir)
{
@@ -237,6 +248,11 @@ void gdma_axi_hal_enable_etm_task(gdma_hal_context_t *hal, int chan_id, gdma_cha
}
#endif // SOC_GDMA_SUPPORT_ETM
bool gdma_axi_hal_is_tx_link_switch_event_supported(gdma_hal_context_t *hal)
{
return axi_dma_ll_tx_is_link_switch_event_supported(hal->axi_dma_dev);
}
void gdma_axi_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config)
{
hal->axi_dma_dev = AXI_DMA_LL_GET_HW(config->group_id - GDMA_LL_AXI_GROUP_START_ID);
@@ -265,5 +281,10 @@ void gdma_axi_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config)
#if SOC_GDMA_SUPPORT_ETM
hal->enable_etm_task = gdma_axi_hal_enable_etm_task;
#endif // SOC_GDMA_SUPPORT_ETM
hal->is_tx_link_switch_event_supported = gdma_axi_hal_is_tx_link_switch_event_supported;
#if AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH_EVENT
hal->request_link_switch_event = gdma_axi_hal_request_link_switch_event;
#endif // AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH_EVENT
axi_dma_ll_set_default_memory_range(hal->axi_dma_dev);
}

View File

@@ -125,3 +125,13 @@ void gdma_hal_set_weight(gdma_hal_context_t *hal, int chan_id, gdma_channel_dire
hal->set_weight(hal, chan_id, dir, weight);
}
#endif // SOC_GDMA_SUPPORT_WEIGHTED_ARBITRATION
void gdma_hal_request_link_switch_event(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir)
{
hal->request_link_switch_event(hal, chan_id, dir);
}
bool gdma_hal_is_tx_link_switch_event_supported(gdma_hal_context_t *hal)
{
return hal->is_tx_link_switch_event_supported(hal);
}

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -52,9 +52,9 @@ typedef struct {
typedef struct {
// The bitmap of the IDs that can be used by M2M are different between AXI DMA and AHB DMA, so we need to save a copy for each of them
uint32_t m2m_free_periph_mask;
// TODO: we can add more private data here, e.g. the interrupt event mask of interest
// for now, the AXI DMA and AHB DMA are sharing the same interrupt mask, so we don't need to store it here
// If one day they become incompatible, we shall save a copy for each of them as a private data
// Supported interrupt events can vary across DMA instances (e.g. AHB vs AXI)
uint32_t tx_event_mask;
uint32_t rx_event_mask;
} gdma_hal_priv_data_t;
/**
@@ -102,6 +102,8 @@ struct gdma_hal_context_t {
#if SOC_GDMA_SUPPORT_WEIGHTED_ARBITRATION
void (*set_weight)(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t weight); /// Set the channel weight
#endif // SOC_GDMA_SUPPORT_WEIGHTED_ARBITRATION
bool (*is_tx_link_switch_event_supported)(gdma_hal_context_t *hal); /// Check if TX link-switch interrupt is supported
void (*request_link_switch_event)(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir); /// Raise the interrupt when tx link switch complete
};
void gdma_hal_deinit(gdma_hal_context_t *hal);
@@ -161,6 +163,10 @@ void gdma_hal_enable_etm_task(gdma_hal_context_t *hal, int chan_id, gdma_channel
void gdma_hal_set_weight(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t weight);
#endif //SOC_GDMA_SUPPORT_WEIGHTED_ARBITRATION
void gdma_hal_request_link_switch_event(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
bool gdma_hal_is_tx_link_switch_event_supported(gdma_hal_context_t *hal);
#ifdef __cplusplus
}
#endif

View File

@@ -12,38 +12,6 @@
extern "C" {
#endif
void gdma_ahb_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr);
void gdma_ahb_hal_stop(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
void gdma_ahb_hal_append(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
void gdma_ahb_hal_reset(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
void gdma_ahb_hal_set_priority(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t priority);
void gdma_ahb_hal_connect_peri(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, int periph_id);
void gdma_ahb_hal_connect_mem(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, int dummy_id);
void gdma_ahb_hal_disconnect_all(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
void gdma_ahb_hal_enable_burst(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_data_burst, bool en_desc_burst);
void gdma_ahb_hal_set_burst_size(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t burst_sz);
void gdma_ahb_hal_set_strategy(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_owner_check, bool en_desc_write_back, bool eof_till_popped);
void gdma_ahb_hal_enable_intr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t intr_event_mask, bool en_or_dis);
void gdma_ahb_hal_clear_intr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t intr_event_mask);
uint32_t gdma_ahb_hal_read_intr_status(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool raw);
uint32_t gdma_ahb_hal_get_intr_status_reg(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
uint32_t gdma_ahb_hal_get_eof_desc_addr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool is_success);
void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config);
void gdma_lp_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config);

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -12,38 +12,6 @@
extern "C" {
#endif
void gdma_axi_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr);
void gdma_axi_hal_stop(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
void gdma_axi_hal_append(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
void gdma_axi_hal_reset(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
void gdma_axi_hal_set_priority(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t priority);
void gdma_axi_hal_connect_peri(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, int periph_id);
void gdma_axi_hal_connect_mem(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, int dummy_id);
void gdma_axi_hal_disconnect_all(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
void gdma_axi_hal_enable_burst(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_data_burst, bool en_desc_burst);
void gdma_axi_hal_set_burst_size(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t burst_sz);
void gdma_axi_hal_set_strategy(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_owner_check, bool en_desc_write_back, bool eof_till_popped);
void gdma_axi_hal_enable_intr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t intr_event_mask, bool en_or_dis);
void gdma_axi_hal_clear_intr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t intr_event_mask);
uint32_t gdma_axi_hal_read_intr_status(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool raw);
uint32_t gdma_axi_hal_get_intr_status_reg(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
uint32_t gdma_axi_hal_get_eof_desc_addr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool is_success);
void gdma_axi_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config);
#ifdef __cplusplus

View File

@@ -52,7 +52,8 @@ struct esp_lcd_dpi_panel_t {
esp_pm_lock_handle_t pm_lock; // Power management lock
#endif
esp_lcd_dpi_panel_color_trans_done_cb_t on_color_trans_done; // Callback invoked when color data transfer has finished
esp_lcd_dpi_panel_refresh_done_cb_t on_refresh_done; // Callback invoked when one refresh operation finished (kinda like a vsync end)
esp_lcd_dpi_panel_frame_buf_complete_cb_t on_frame_buf_complete; // Callback invoked when the frame buffer can be reused safely
esp_lcd_dpi_panel_vsync_cb_t on_vsync; // VSYNC event callback
void *user_ctx; // User context for the callback
};
@@ -102,10 +103,16 @@ bool mipi_dsi_dma_trans_done_cb(dw_gdma_channel_handle_t chan, const dw_gdma_tra
dw_gdma_channel_use_link_list(chan, link_list);
dw_gdma_channel_enable_ctrl(chan, true);
if (dpi_panel->on_frame_buf_complete) {
if (dpi_panel->on_frame_buf_complete(&dpi_panel->base, NULL, dpi_panel->user_ctx)) {
yield_needed = true;
}
}
#if !MIPI_DSI_BRG_LL_EVENT_VSYNC
// the DMA descriptor is large enough to carry a whole frame buffer, so this event can also be treated as a fake "vsync end"
if (dpi_panel->on_refresh_done) {
if (dpi_panel->on_refresh_done(&dpi_panel->base, NULL, dpi_panel->user_ctx)) {
if (dpi_panel->on_vsync) {
if (dpi_panel->on_vsync(&dpi_panel->base, NULL, dpi_panel->user_ctx)) {
yield_needed = true;
}
}
@@ -128,8 +135,8 @@ void mipi_dsi_bridge_isr_handler(void *args)
ESP_DRAM_LOGE(TAG, "can't fetch data from external memory fast enough, underrun happens");
}
if (intr_status & MIPI_DSI_BRG_LL_EVENT_VSYNC) {
if (dpi_panel->on_refresh_done) {
if (dpi_panel->on_refresh_done(&dpi_panel->base, NULL, dpi_panel->user_ctx)) {
if (dpi_panel->on_vsync) {
if (dpi_panel->on_vsync(&dpi_panel->base, NULL, dpi_panel->user_ctx)) {
portYIELD_FROM_ISR();
}
}
@@ -733,19 +740,23 @@ esp_err_t esp_lcd_dpi_panel_register_event_callbacks(esp_lcd_panel_handle_t pane
if (cbs->on_color_trans_done) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_color_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_color_trans_done callback not in IRAM");
}
if (cbs->on_refresh_done) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_refresh_done), ESP_ERR_INVALID_ARG, TAG, "on_refresh_done callback not in IRAM");
if (cbs->on_vsync) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_vsync), ESP_ERR_INVALID_ARG, TAG, "on_vsync callback not in IRAM");
}
if (cbs->on_frame_buf_complete) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_frame_buf_complete), ESP_ERR_INVALID_ARG, TAG, "on_frame_buf_complete callback not in IRAM");
}
if (user_ctx) {
ESP_RETURN_ON_FALSE(esp_ptr_internal(user_ctx), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
}
#endif // CONFIG_LCD_DSI_ISR_CACHE_SAFE
dpi_panel->on_color_trans_done = cbs->on_color_trans_done;
dpi_panel->on_refresh_done = cbs->on_refresh_done;
dpi_panel->on_vsync = cbs->on_vsync;
dpi_panel->on_frame_buf_complete = cbs->on_frame_buf_complete;
dpi_panel->user_ctx = user_ctx;
// enable the vsync interrupt if the callback is provided
mipi_dsi_brg_ll_enable_interrupt(dpi_panel->bus->hal.bridge, MIPI_DSI_BRG_LL_EVENT_VSYNC, cbs->on_refresh_done != NULL);
mipi_dsi_brg_ll_enable_interrupt(dpi_panel->bus->hal.bridge, MIPI_DSI_BRG_LL_EVENT_VSYNC, cbs->on_vsync != NULL);
return ESP_OK;
}

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -177,18 +177,36 @@ typedef esp_lcd_dpi_panel_general_cb_t esp_lcd_dpi_panel_color_trans_done_cb_t;
/**
* @brief Declare the prototype of the function that will be invoked
* when driver finishes refreshing the frame buffer to the screen
* when the frame buffer can be reused safely
*
* @deprecated Use esp_lcd_dpi_panel_frame_buf_complete_cb_t instead.
*/
typedef esp_lcd_dpi_panel_general_cb_t esp_lcd_dpi_panel_refresh_done_cb_t;
/**
* @brief Declare the prototype of the function that will be invoked when the LCD controller sends the VSYNC signal.
*/
typedef esp_lcd_dpi_panel_general_cb_t esp_lcd_dpi_panel_vsync_cb_t;
/**
* @brief Declare the prototype of the function that will be invoked
* when the frame buffer can be reused safely
*/
typedef esp_lcd_dpi_panel_general_cb_t esp_lcd_dpi_panel_frame_buf_complete_cb_t;
/**
* @brief Type of LCD DPI panel callbacks
*/
typedef struct {
esp_lcd_dpi_panel_color_trans_done_cb_t on_color_trans_done; /*!< Invoked when user's color buffer copied to the internal frame buffer.
esp_lcd_dpi_panel_color_trans_done_cb_t on_color_trans_done; /*!< Invoked when user's draw buffer copied to the frame buffer.
This is an indicator that the draw buffer can be recycled safely.
But doesn't mean the draw buffer finishes the refreshing to the screen. */
esp_lcd_dpi_panel_refresh_done_cb_t on_refresh_done; /*!< Invoked when the internal frame buffer finishes refreshing to the screen */
union {
esp_lcd_dpi_panel_refresh_done_cb_t on_refresh_done __attribute__((deprecated("Deprecated, use on_frame_buf_complete instead"))); /*!< Deprecated, use on_frame_buf_complete instead */
esp_lcd_dpi_panel_frame_buf_complete_cb_t on_frame_buf_complete; /*!< Invoked when the frame buffer can be reused safely
when the frame buffer is the draw buffer. */
};
esp_lcd_dpi_panel_vsync_cb_t on_vsync; /*!< VSYNC event callback */
} esp_lcd_dpi_panel_event_callbacks_t;
/**

View File

@@ -49,6 +49,14 @@
#include "rgb_lcd_rotation_sw.h"
#include "esp_private/sleep_retention.h"
#if SOC_HAS(AXI_DMA)
#include "hal/axi_dma_ll.h"
#endif
#if AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH_EVENT
#define RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT 1
#endif
// hardware issue workaround
#if CONFIG_IDF_TARGET_ESP32S3
#define RGB_LCD_NEEDS_SEPARATE_RESTART_LINK 1
@@ -99,6 +107,9 @@ static esp_err_t lcd_rgb_panel_configure_gpio_matrix(const esp_lcd_rgb_panel_con
static bool lcd_rgb_panel_can_use_iomux(const esp_lcd_rgb_panel_config_t *panel_config, const soc_lcd_rgb_iomux_desc_t *iomux_desc);
static esp_err_t lcd_rgb_panel_configure_iomux(const esp_lcd_rgb_panel_config_t *panel_config, const soc_lcd_rgb_iomux_desc_t *iomux_desc, uint64_t *gpio_reserve_mask);
#endif // LCD_LL_SUPPORT(IOMUX)
#if RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
static bool lcd_rgb_panel_link_switch_handler(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data);
#endif // RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
struct esp_rgb_panel_t {
esp_lcd_panel_t base; // Base class of generic lcd panel
@@ -143,7 +154,7 @@ struct esp_rgb_panel_t {
size_t bb_eof_count; // record the number we received the DMA EOF event, compare with `expect_eof_count` in the VSYNC_END ISR
size_t expect_eof_count; // record the number of DMA EOF event we expected to receive
esp_lcd_rgb_panel_draw_buf_complete_cb_t on_color_trans_done; // draw buffer completes
esp_lcd_rgb_panel_frame_buf_complete_cb_t on_frame_buf_complete; // callback used to notify when the bounce buffer finish copying the entire frame
esp_lcd_rgb_panel_frame_buf_complete_cb_t on_frame_buf_complete; // callback used to notify when the buffer can be reused safely
esp_lcd_rgb_panel_vsync_cb_t on_vsync; // VSYNC event callback
esp_lcd_rgb_panel_bounce_buf_fill_cb_t on_bounce_empty; // callback used to fill a bounce buffer rather than copying from the frame buffer
void *user_ctx; // Reserved user's data of callback functions
@@ -760,7 +771,9 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
// it's hard to know the time when the new frame buffer starts
gdma_link_concat(rgb_panel->dma_fb_links[i], -1, rgb_panel->dma_fb_links[rgb_panel->cur_fb_index], 0);
}
#if RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
ESP_RETURN_ON_ERROR(gdma_request_link_switch_event(rgb_panel->dma_chan), TAG, "request link switch event failed");
#endif // RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
}
}
return ESP_OK;
@@ -1061,7 +1074,7 @@ static IRAM_ATTR bool lcd_rgb_panel_eof_handler(gdma_channel_handle_t dma_chan,
portEXIT_CRITICAL_ISR(&rgb_panel->spinlock);
need_yield = lcd_rgb_panel_fill_bounce_buffer(rgb_panel, rgb_panel->bounce_buffer[bb]);
} else {
// if not bounce buffer, the DMA EOF event means the end of a frame has been sent out to the LCD controller
// Once the preload has already done, the buffer complete callback is not reliable.
if (rgb_panel->on_frame_buf_complete) {
if (rgb_panel->on_frame_buf_complete(&rgb_panel->base, NULL, rgb_panel->user_ctx)) {
need_yield = true;
@@ -1071,6 +1084,24 @@ static IRAM_ATTR bool lcd_rgb_panel_eof_handler(gdma_channel_handle_t dma_chan,
return need_yield;
}
#if RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
static IRAM_ATTR bool lcd_rgb_panel_link_switch_handler(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
{
(void)dma_chan;
(void)event_data;
bool need_yield = false;
esp_rgb_panel_t *rgb_panel = (esp_rgb_panel_t *)user_data;
if (rgb_panel->on_frame_buf_complete) {
if (rgb_panel->on_frame_buf_complete(&rgb_panel->base, NULL, rgb_panel->user_ctx)) {
need_yield = true;
}
}
return need_yield;
}
#endif // RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *rgb_panel)
{
// alloc DMA channel and connect to LCD peripheral
@@ -1097,11 +1128,19 @@ static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *rgb_panel)
// get the memory alignment required by the DMA
gdma_get_alignment_constraints(rgb_panel->dma_chan, &rgb_panel->int_mem_align, &rgb_panel->ext_mem_align);
// register DMA EOF callback
// register DMA event callbacks
gdma_tx_event_callbacks_t cbs = {
#if RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
// if no bounce buffer, the DMA EOF event means the end of a frame has been sent out to the LCD controller.
// But the dma link may have preloaded the next frame with the current buffer.
// So we need to wait for the GDMA link switch event to invoke the on_frame_buf_complete callback.
.on_trans_eof = (rgb_panel->flags.stream_mode && !rgb_panel->bb_size) ? NULL : lcd_rgb_panel_eof_handler,
.on_link_switch = lcd_rgb_panel_link_switch_handler,
#else
.on_trans_eof = lcd_rgb_panel_eof_handler,
#endif // RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
};
ESP_RETURN_ON_ERROR(gdma_register_tx_event_callbacks(rgb_panel->dma_chan, &cbs, rgb_panel), TAG, "register DMA EOF callback failed");
ESP_RETURN_ON_ERROR(gdma_register_tx_event_callbacks(rgb_panel->dma_chan, &cbs, rgb_panel), TAG, "register DMA event callbacks failed");
return ESP_OK;
}

View File

@@ -97,7 +97,7 @@ typedef bool (*esp_lcd_rgb_panel_general_cb_t)(esp_lcd_panel_handle_t panel, con
typedef esp_lcd_rgb_panel_general_cb_t esp_lcd_rgb_panel_draw_buf_complete_cb_t;
/**
* @brief Declare the prototype of the function that will be invoked when a whole frame buffer is sent to the LCD DMA.
* @brief Declare the prototype of the function that will be invoked when a whole frame buffer can be reused safely.
* The LCD hardware may still need some blank time to finish the refresh.
*/
typedef esp_lcd_rgb_panel_general_cb_t esp_lcd_rgb_panel_frame_buf_complete_cb_t;
@@ -132,7 +132,7 @@ typedef struct {
But doesn't mean the draw buffer finishes the refreshing to the screen. */
esp_lcd_rgb_panel_vsync_cb_t on_vsync; /*!< VSYNC event callback */
esp_lcd_rgb_panel_bounce_buf_fill_cb_t on_bounce_empty; /*!< Bounce buffer empty callback. */
esp_lcd_rgb_panel_frame_buf_complete_cb_t on_frame_buf_complete; /*!< A whole frame buffer was just sent to the LCD DMA */
esp_lcd_rgb_panel_frame_buf_complete_cb_t on_frame_buf_complete; /*!< Invoked when the frame buffer can be reused safely */
} esp_lcd_rgb_panel_event_callbacks_t;
/**

View File

@@ -91,7 +91,7 @@ TEST_CASE("MIPI DSI draw bitmap (EK79007) IRAM Safe", "[mipi_dsi]")
uint32_t callback_calls = 0;
esp_lcd_dpi_panel_event_callbacks_t cbs = {
.on_refresh_done = test_dpi_panel_count_in_callback,
.on_frame_buf_complete = test_dpi_panel_count_in_callback,
};
TEST_ESP_OK(esp_lcd_dpi_panel_register_event_callbacks(mipi_dpi_panel, &cbs, &callback_calls));

View File

@@ -648,7 +648,12 @@ typedef union {
* underflow.
*/
uint32_t outfifo_l3_udf_chn_int_raw: 1;
uint32_t reserved_10: 22;
/** out_link_switch_chn_int_raw : R/WTC/SS; bitpos: [10]; default: 0;
* The raw interrupt bit turns to high level when the dma switch to new link for Tx
* channel0.
*/
uint32_t out_link_switch_chn_int_raw: 1;
uint32_t reserved_11: 21;
};
uint32_t val;
} axi_dma_out_int_raw_chn_reg_t;
@@ -698,7 +703,11 @@ typedef union {
* The raw interrupt status bit for the OUTFIFO_UDF_L3_CH_INT interrupt.
*/
uint32_t outfifo_l3_udf_chn_int_st: 1;
uint32_t reserved_10: 22;
/** out_link_switch_chn_int_st : RO; bitpos: [10]; default: 0;
* The raw interrupt status bit for the OUT_LINK_SWITCH_CH_INT interrupt.
*/
uint32_t out_link_switch_chn_int_st: 1;
uint32_t reserved_11: 21;
};
uint32_t val;
} axi_dma_out_int_st_chn_reg_t;
@@ -748,7 +757,11 @@ typedef union {
* The interrupt enable bit for the OUTFIFO_UDF_L3_CH_INT interrupt.
*/
uint32_t outfifo_l3_udf_chn_int_ena: 1;
uint32_t reserved_10: 22;
/** out_link_switch_chn_int_ena : R/W; bitpos: [10]; default: 0;
* The interrupt enable bit for the OUT_LINK_SWITCH_CH_INT interrupt.
*/
uint32_t out_link_switch_chn_int_ena: 1;
uint32_t reserved_11: 21;
};
uint32_t val;
} axi_dma_out_int_ena_chn_reg_t;
@@ -798,7 +811,11 @@ typedef union {
* Set this bit to clear the OUTFIFO_UDF_L3_CH_INT interrupt.
*/
uint32_t outfifo_l3_udf_chn_int_clr: 1;
uint32_t reserved_10: 22;
/** out_link_switch_chn_int_clr : WT; bitpos: [10]; default: 0;
* Set this bit to clear the OUT_LINK_SWITCH_CH_INT interrupt.
*/
uint32_t out_link_switch_chn_int_clr: 1;
uint32_t reserved_11: 21;
};
uint32_t val;
} axi_dma_out_int_clr_chn_reg_t;

View File

@@ -313,7 +313,7 @@ LCD
- The ``psram_trans_align`` and ``sram_trans_align`` members in the :cpp:type:`esp_lcd_rgb_panel_config_t` structure have also been replaced by the :cpp:member:`esp_lcd_rgb_panel_config_t::dma_burst_size` member for configuring the DMA burst transfer size.
- The ``color_space`` and ``rgb_endian`` configuration options in the :cpp:type:`esp_lcd_panel_dev_config_t` structure have been replaced by the :cpp:member:`esp_lcd_panel_dev_config_t::rgb_ele_order` member, which sets the RGB element order. The corresponding types ``lcd_color_rgb_endian_t`` and ``esp_lcd_color_space_t`` have also been removed; use :cpp:type:`lcd_rgb_element_order_t` instead.
- The ``esp_lcd_panel_disp_off`` function has been removed. Please use the :func:`esp_lcd_panel_disp_on_off` function to control display on/off.
- The ``on_bounce_frame_finish`` member in :cpp:type:`esp_lcd_rgb_panel_event_callbacks_t` has been replaced by :cpp:member:`esp_lcd_rgb_panel_event_callbacks_t::on_frame_buf_complete`, which indicates that a complete frame buffer has been sent to the LCD controller.
- The ``on_bounce_frame_finish`` member in :cpp:type:`esp_lcd_rgb_panel_event_callbacks_t` has been replaced by :cpp:member:`esp_lcd_rgb_panel_event_callbacks_t::on_frame_buf_complete`, which indicates that a complete frame buffer can be safely reused.
- The LCD IO layer driver for the I2C interface previously had two implementations, based on the new and legacy I2C master bus drivers. As the legacy I2C driver is being deprecated, support for it in the LCD IO layer has been removed. Only the APIs provided in ``driver/i2c_master.h`` are now used.
- ``pixel_format`` member in the :cpp:type:`esp_lcd_dpi_panel_config_t` structure has been removed. It is recommended to only use :cpp:member:`esp_lcd_dpi_panel_config_t::in_color_format` to set the MIPI DSI driver's input pixel data format.
- ``bits_per_pixel`` member in the :cpp:type:`esp_lcd_rgb_panel_config_t` structure has been removed. The color depth of the internal framebuffer is now determined by the :cpp:member:`esp_lcd_rgb_panel_config_t::in_color_format` member.

View File

@@ -3,6 +3,12 @@ Peripherals
:link_to_translation:`zh_CN:[中文]`
LCD
---
- The :cpp:member:`esp_lcd_dpi_panel_event_callbacks_t::on_refresh_done` callback has been deprecated. Please use :cpp:member:`esp_lcd_dpi_panel_event_callbacks_t::on_frame_buf_complete` to know when a frame buffer can be safely reused.
- The VSYNC timing event for the MIPI DSI DPI panel is now reported by :cpp:member:`esp_lcd_dpi_panel_event_callbacks_t::on_vsync`.
UART
-----

View File

@@ -313,7 +313,7 @@ LCD
- :cpp:type:`esp_lcd_rgb_panel_config_t` 结构体中的 ``psram_trans_align````sram_trans_align`` 均已被 :cpp:member:`esp_lcd_rgb_panel_config_t::dma_burst_size` 成员取代,用来设置 DMA 的突发传输大小。
- :cpp:type:`esp_lcd_panel_dev_config_t` 结构体中的 ``color_space````rgb_endian`` 配置均已被 :cpp:member:`esp_lcd_panel_dev_config_t::rgb_ele_order` 成员取代,用来设置 RGB 元素的排列顺序。对应的类型 ``lcd_color_rgb_endian_t````esp_lcd_color_space_t`` 也已被移除,请使用 :cpp:type:`lcd_rgb_element_order_t` 替代。
- ``esp_lcd_panel_disp_off`` 函数已被移除。请使用 :func:`esp_lcd_panel_disp_on_off` 函数来控制显示内容的开关。
- :cpp:type:`esp_lcd_rgb_panel_event_callbacks_t` 中的 ``on_bounce_frame_finish`` 成员已被 :cpp:member:`esp_lcd_rgb_panel_event_callbacks_t::on_frame_buf_complete` 成员取代,用于指示一个完整的帧缓冲区已被发送给 LCD 控制器
- :cpp:type:`esp_lcd_rgb_panel_event_callbacks_t` 中的 ``on_bounce_frame_finish`` 成员已被 :cpp:member:`esp_lcd_rgb_panel_event_callbacks_t::on_frame_buf_complete` 成员取代,用于指示一个完整的帧缓冲区可以被安全复用
- I2C 接口的 LCD IO 层驱动有两套实现,分别基于新、旧 I2C Master 总线驱动。由于旧版的 I2C Master 驱动逐渐被弃用,遂 LCD 的 IO 层也移除对旧版的支持,只使用 ``driver/i2c_master.h`` 中提供的 API。
- :cpp:type:`esp_lcd_dpi_panel_config_t` 结构体中的 ``pixel_format`` 成员已经被删除。建议仅使用 :cpp:member:`esp_lcd_dpi_panel_config_t::in_color_format` 来设定 MIPI DSI 驱动输入的像素数据格式。
- :cpp:type:`esp_lcd_rgb_panel_config_t` 结构体中的 ``bits_per_pixel`` 成员已经被删除。内部帧缓冲区的色彩深度现在由 :cpp:member:`esp_lcd_rgb_panel_config_t::in_color_format` 成员决定。

View File

@@ -3,6 +3,12 @@
:link_to_translation:`en:[English]`
LCD
---
- :cpp:member:`esp_lcd_dpi_panel_event_callbacks_t::on_refresh_done` 回调已废弃。请使用 :cpp:member:`esp_lcd_dpi_panel_event_callbacks_t::on_frame_buf_complete` 判断帧缓冲区何时可以被安全复用。
- MIPI DSI DPI 面板的 VSYNC 时序事件现在通过 :cpp:member:`esp_lcd_dpi_panel_event_callbacks_t::on_vsync` 回调上报。
UART
------

View File

@@ -1,4 +1,4 @@
dependencies:
lvgl/lvgl: "9.4.0"
lvgl/lvgl: "9.5.0"
esp_lcd_ili9881c: "^1.0.0"
esp_lcd_ek79007: "^1.0.0"

View File

@@ -334,7 +334,7 @@ void app_main(void)
esp_lcd_dpi_panel_event_callbacks_t cbs = {
.on_color_trans_done = example_notify_lvgl_flush_ready,
#if CONFIG_EXAMPLE_MONITOR_REFRESH_BY_GPIO
.on_refresh_done = example_monitor_refresh_rate,
.on_vsync = example_monitor_refresh_rate,
#endif
};
ESP_ERROR_CHECK(esp_lcd_dpi_panel_register_event_callbacks(mipi_dpi_panel, &cbs, display));

View File

@@ -1,3 +1,3 @@
idf_component_register(SRCS "rgb_lcd_example_main.c" "lvgl_demo_ui.c"
PRIV_REQUIRES esp_lcd
PRIV_REQUIRES esp_lcd esp_timer
INCLUDE_DIRS ".")

View File

@@ -1,4 +1,4 @@
dependencies:
lvgl/lvgl: "9.2.0"
lvgl/lvgl: "9.5.0"
rgb_panel_init:
path: ${IDF_PATH}/examples/peripherals/lcd/rgb_panel/common_components/rgb_panel_init

View File

@@ -43,15 +43,43 @@ static const char *TAG = "example";
// LVGL library is not thread-safe, this example will call LVGL APIs from different tasks, so use a mutex to protect it
static _lock_t lvgl_api_lock;
static TaskHandle_t lvgl_task_handle;
extern void example_lvgl_demo_ui(lv_display_t *disp);
#if CONFIG_EXAMPLE_USE_DOUBLE_FB
static bool example_on_frame_buf_complete(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *event_data, void *user_ctx)
{
(void)panel;
(void)event_data;
(void)user_ctx;
BaseType_t need_yield = pdFALSE;
if (lvgl_task_handle) {
vTaskNotifyGiveFromISR(lvgl_task_handle, &need_yield);
}
return need_yield == pdTRUE;
}
static void example_lvgl_flush_wait_cb(lv_display_t *disp)
{
// The flush callback only submits the rendered buffer to the LCD driver.
// With direct-mode double buffering, LVGL must wait until the RGB panel has
// switched away from the previous frame buffer before rendering into it again.
if (lv_display_flush_is_last(disp)) {
// Wait until the previous frame buffer is no longer referenced by DMA.
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
lv_display_flush_ready(disp);
}
#else
static bool example_notify_lvgl_flush_ready(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *event_data, void *user_ctx)
{
lv_display_t *disp = (lv_display_t *)user_ctx;
lv_display_flush_ready(disp);
return false;
}
#endif
static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
@@ -60,6 +88,20 @@ static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uin
int offsetx2 = area->x2;
int offsety1 = area->y1;
int offsety2 = area->y2;
#if CONFIG_EXAMPLE_USE_DOUBLE_FB
if (!lv_display_flush_is_last(disp)) {
lv_display_flush_ready(disp);
return;
}
// In direct mode, LVGL may flush multiple dirty areas. Switch the RGB panel to the new
// frame buffer only after the last dirty area has been rendered.
offsetx1 = 0;
offsety1 = 0;
offsetx2 = EXAMPLE_LCD_H_RES - 1;
offsety2 = EXAMPLE_LCD_V_RES - 1;
// Clear any stale completion from the previous frame before waiting for this frame.
ulTaskNotifyTake(pdTRUE, 0);
#endif
// pass the draw buffer to the driver
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, px_map);
}
@@ -130,10 +172,19 @@ void app_main(void)
// set the callback which can copy the rendered image to an area of the display
lv_display_set_flush_cb(display, example_lvgl_flush_cb);
#if CONFIG_EXAMPLE_USE_DOUBLE_FB
// The wait callback keeps LVGL from reusing a frame buffer until the panel driver
// reports that the buffer has finished refreshing.
lv_display_set_flush_wait_cb(display, example_lvgl_flush_wait_cb);
#endif
ESP_LOGI(TAG, "Register event callbacks");
esp_lcd_rgb_panel_event_callbacks_t cbs = {
#if CONFIG_EXAMPLE_USE_DOUBLE_FB
.on_frame_buf_complete = example_on_frame_buf_complete,
#else
.on_color_trans_done = example_notify_lvgl_flush_ready,
#endif
};
ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(panel_handle, &cbs, display));
@@ -148,7 +199,7 @@ void app_main(void)
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000));
ESP_LOGI(TAG, "Create LVGL task");
xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL);
xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, &lvgl_task_handle);
ESP_LOGI(TAG, "Display LVGL UI");
// Lock the mutex due to the LVGL APIs are not thread-safe