diff --git a/components/esp_driver_dma/include/esp_private/gdma.h b/components/esp_driver_dma/include/esp_private/gdma.h index 1e3b7844640..23819760bbb 100644 --- a/components/esp_driver_dma/include/esp_private/gdma.h +++ b/components/esp_driver_dma/include/esp_private/gdma.h @@ -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 diff --git a/components/esp_driver_dma/linker.lf b/components/esp_driver_dma/linker.lf index d21636fd89e..1f3f199872f 100644 --- a/components/esp_driver_dma/linker.lf +++ b/components/esp_driver_dma/linker.lf @@ -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: diff --git a/components/esp_driver_dma/src/gdma.c b/components/esp_driver_dma/src/gdma.c index b248ba2bad0..3b7fd8d5831 100644 --- a/components/esp_driver_dma/src/gdma.c +++ b/components/esp_driver_dma/src/gdma.c @@ -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, diff --git a/components/esp_hal_dma/esp32c5/include/hal/ahb_dma_ll.h b/components/esp_hal_dma/esp32c5/include/hal/ahb_dma_ll.h index acaeb488d9d..f757652c380 100644 --- a/components/esp_hal_dma/esp32c5/include/hal/ahb_dma_ll.h +++ b/components/esp_hal_dma/esp32c5/include/hal/ahb_dma_ll.h @@ -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) diff --git a/components/esp_hal_dma/esp32c61/include/hal/ahb_dma_ll.h b/components/esp_hal_dma/esp32c61/include/hal/ahb_dma_ll.h index 0bde87a1fdc..1372aa552d4 100644 --- a/components/esp_hal_dma/esp32c61/include/hal/ahb_dma_ll.h +++ b/components/esp_hal_dma/esp32c61/include/hal/ahb_dma_ll.h @@ -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) diff --git a/components/esp_hal_dma/esp32h4/include/hal/ahb_dma_ll.h b/components/esp_hal_dma/esp32h4/include/hal/ahb_dma_ll.h index 2daf20ea895..30a97a2fc10 100644 --- a/components/esp_hal_dma/esp32h4/include/hal/ahb_dma_ll.h +++ b/components/esp_hal_dma/esp32h4/include/hal/ahb_dma_ll.h @@ -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) diff --git a/components/esp_hal_dma/esp32p4/include/hal/ahb_dma_ll.h b/components/esp_hal_dma/esp32p4/include/hal/ahb_dma_ll.h index 9eab7826232..b96f2267e9e 100644 --- a/components/esp_hal_dma/esp32p4/include/hal/ahb_dma_ll.h +++ b/components/esp_hal_dma/esp32p4/include/hal/ahb_dma_ll.h @@ -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 ///////////////////////////////////////// /** diff --git a/components/esp_hal_dma/esp32p4/include/hal/axi_dma_ll.h b/components/esp_hal_dma/esp32p4/include/hal/axi_dma_ll.h index 9b8b32d0416..a2a156db78b 100644 --- a/components/esp_hal_dma/esp32p4/include/hal/axi_dma_ll.h +++ b/components/esp_hal_dma/esp32p4/include/hal/axi_dma_ll.h @@ -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 */ diff --git a/components/esp_hal_dma/esp32p4/include/hal/gdma_ll.h b/components/esp_hal_dma/esp32p4/include/hal/gdma_ll.h index b6c8e7494f3..d023bb0f348 100644 --- a/components/esp_hal_dma/esp32p4/include/hal/gdma_ll.h +++ b/components/esp_hal_dma/esp32p4/include/hal/gdma_ll.h @@ -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) diff --git a/components/esp_hal_dma/esp32s31/include/hal/ahb_dma_ll.h b/components/esp_hal_dma/esp32s31/include/hal/ahb_dma_ll.h index d4dd3f83536..4528aaa3640 100644 --- a/components/esp_hal_dma/esp32s31/include/hal/ahb_dma_ll.h +++ b/components/esp_hal_dma/esp32s31/include/hal/ahb_dma_ll.h @@ -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 ///////////////////////////////////////// /** diff --git a/components/esp_hal_dma/esp32s31/include/hal/axi_dma_ll.h b/components/esp_hal_dma/esp32s31/include/hal/axi_dma_ll.h index 14a5b2cc561..a93308aa427 100644 --- a/components/esp_hal_dma/esp32s31/include/hal/axi_dma_ll.h +++ b/components/esp_hal_dma/esp32s31/include/hal/axi_dma_ll.h @@ -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 */ diff --git a/components/esp_hal_dma/esp32s31/include/hal/gdma_ll.h b/components/esp_hal_dma/esp32s31/include/hal/gdma_ll.h index 089a74167e0..ccaac9bc904 100644 --- a/components/esp_hal_dma/esp32s31/include/hal/gdma_ll.h +++ b/components/esp_hal_dma/esp32s31/include/hal/gdma_ll.h @@ -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) diff --git a/components/esp_hal_dma/gdma_hal_ahb_v1.c b/components/esp_hal_dma/gdma_hal_ahb_v1.c index 9485a6e2915..10b7c1a668e 100644 --- a/components/esp_hal_dma/gdma_hal_ahb_v1.c +++ b/components/esp_hal_dma/gdma_hal_ahb_v1.c @@ -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; } diff --git a/components/esp_hal_dma/gdma_hal_ahb_v2.c b/components/esp_hal_dma/gdma_hal_ahb_v2.c index 7d01f4259cd..69e1a48323e 100644 --- a/components/esp_hal_dma/gdma_hal_ahb_v2.c +++ b/components/esp_hal_dma/gdma_hal_ahb_v2.c @@ -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); } diff --git a/components/esp_hal_dma/gdma_hal_axi.c b/components/esp_hal_dma/gdma_hal_axi.c index c3a6b9ad116..abd3f29cf1b 100644 --- a/components/esp_hal_dma/gdma_hal_axi.c +++ b/components/esp_hal_dma/gdma_hal_axi.c @@ -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); } diff --git a/components/esp_hal_dma/gdma_hal_top.c b/components/esp_hal_dma/gdma_hal_top.c index b1ebc075b26..71bc6ef6de6 100644 --- a/components/esp_hal_dma/gdma_hal_top.c +++ b/components/esp_hal_dma/gdma_hal_top.c @@ -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); +} diff --git a/components/esp_hal_dma/include/hal/gdma_hal.h b/components/esp_hal_dma/include/hal/gdma_hal.h index 9c670c87d52..60fe7b970ed 100644 --- a/components/esp_hal_dma/include/hal/gdma_hal.h +++ b/components/esp_hal_dma/include/hal/gdma_hal.h @@ -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 diff --git a/components/esp_hal_dma/include/hal/gdma_hal_ahb.h b/components/esp_hal_dma/include/hal/gdma_hal_ahb.h index d8bcddd3b1e..4269f76247e 100644 --- a/components/esp_hal_dma/include/hal/gdma_hal_ahb.h +++ b/components/esp_hal_dma/include/hal/gdma_hal_ahb.h @@ -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); diff --git a/components/esp_hal_dma/include/hal/gdma_hal_axi.h b/components/esp_hal_dma/include/hal/gdma_hal_axi.h index 2e63e322da1..443cb87cd8f 100644 --- a/components/esp_hal_dma/include/hal/gdma_hal_axi.h +++ b/components/esp_hal_dma/include/hal/gdma_hal_axi.h @@ -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 diff --git a/components/esp_lcd/dsi/esp_lcd_panel_dpi.c b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c index 024d1f967d0..246f1509ad2 100644 --- a/components/esp_lcd/dsi/esp_lcd_panel_dpi.c +++ b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c @@ -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; } diff --git a/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h b/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h index 99887f93abe..e03b5cc6052 100644 --- a/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h +++ b/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h @@ -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; /** diff --git a/components/esp_lcd/rgb/esp_lcd_panel_rgb.c b/components/esp_lcd/rgb/esp_lcd_panel_rgb.c index bf2122ef9a0..476dab03da8 100644 --- a/components/esp_lcd/rgb/esp_lcd_panel_rgb.c +++ b/components/esp_lcd/rgb/esp_lcd_panel_rgb.c @@ -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; } diff --git a/components/esp_lcd/rgb/include/esp_lcd_panel_rgb.h b/components/esp_lcd/rgb/include/esp_lcd_panel_rgb.h index 2831052be49..7d9c8990bda 100644 --- a/components/esp_lcd/rgb/include/esp_lcd_panel_rgb.h +++ b/components/esp_lcd/rgb/include/esp_lcd_panel_rgb.h @@ -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; /** diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_iram.c b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_iram.c index 41c27348d79..2f3833a165c 100644 --- a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_iram.c +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_iram.c @@ -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)); diff --git a/components/soc/esp32s31/register/soc/axi_dma_struct.h b/components/soc/esp32s31/register/soc/axi_dma_struct.h index 9ff160a9fee..40048d78e1a 100644 --- a/components/soc/esp32s31/register/soc/axi_dma_struct.h +++ b/components/soc/esp32s31/register/soc/axi_dma_struct.h @@ -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; diff --git a/docs/en/migration-guides/release-6.x/6.0/peripherals.rst b/docs/en/migration-guides/release-6.x/6.0/peripherals.rst index ec153607ee8..2447bc97b72 100644 --- a/docs/en/migration-guides/release-6.x/6.0/peripherals.rst +++ b/docs/en/migration-guides/release-6.x/6.0/peripherals.rst @@ -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. diff --git a/docs/en/migration-guides/release-6.x/6.1/peripherals.rst b/docs/en/migration-guides/release-6.x/6.1/peripherals.rst index 0cfbe45f793..b9fab2807aa 100644 --- a/docs/en/migration-guides/release-6.x/6.1/peripherals.rst +++ b/docs/en/migration-guides/release-6.x/6.1/peripherals.rst @@ -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 ----- diff --git a/docs/zh_CN/migration-guides/release-6.x/6.0/peripherals.rst b/docs/zh_CN/migration-guides/release-6.x/6.0/peripherals.rst index 9bced71b8e3..b2f30cc72a8 100644 --- a/docs/zh_CN/migration-guides/release-6.x/6.0/peripherals.rst +++ b/docs/zh_CN/migration-guides/release-6.x/6.0/peripherals.rst @@ -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` 成员决定。 diff --git a/docs/zh_CN/migration-guides/release-6.x/6.1/peripherals.rst b/docs/zh_CN/migration-guides/release-6.x/6.1/peripherals.rst index 0a718edac2d..a844504c89d 100644 --- a/docs/zh_CN/migration-guides/release-6.x/6.1/peripherals.rst +++ b/docs/zh_CN/migration-guides/release-6.x/6.1/peripherals.rst @@ -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 ------ diff --git a/examples/peripherals/lcd/mipi_dsi/main/idf_component.yml b/examples/peripherals/lcd/mipi_dsi/main/idf_component.yml index 047ec83cd94..5e11de7c083 100644 --- a/examples/peripherals/lcd/mipi_dsi/main/idf_component.yml +++ b/examples/peripherals/lcd/mipi_dsi/main/idf_component.yml @@ -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" diff --git a/examples/peripherals/lcd/mipi_dsi/main/mipi_dsi_lcd_example_main.c b/examples/peripherals/lcd/mipi_dsi/main/mipi_dsi_lcd_example_main.c index 5f236ca1c57..38918f0351d 100644 --- a/examples/peripherals/lcd/mipi_dsi/main/mipi_dsi_lcd_example_main.c +++ b/examples/peripherals/lcd/mipi_dsi/main/mipi_dsi_lcd_example_main.c @@ -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)); diff --git a/examples/peripherals/lcd/rgb_panel/main/CMakeLists.txt b/examples/peripherals/lcd/rgb_panel/main/CMakeLists.txt index 0b88bc41fb8..2c95073c568 100644 --- a/examples/peripherals/lcd/rgb_panel/main/CMakeLists.txt +++ b/examples/peripherals/lcd/rgb_panel/main/CMakeLists.txt @@ -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 ".") diff --git a/examples/peripherals/lcd/rgb_panel/main/idf_component.yml b/examples/peripherals/lcd/rgb_panel/main/idf_component.yml index 9bcf75b4fe4..3cee1989669 100644 --- a/examples/peripherals/lcd/rgb_panel/main/idf_component.yml +++ b/examples/peripherals/lcd/rgb_panel/main/idf_component.yml @@ -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 diff --git a/examples/peripherals/lcd/rgb_panel/main/rgb_lcd_example_main.c b/examples/peripherals/lcd/rgb_panel/main/rgb_lcd_example_main.c index b9d724906e5..c09e135a217 100644 --- a/examples/peripherals/lcd/rgb_panel/main/rgb_lcd_example_main.c +++ b/examples/peripherals/lcd/rgb_panel/main/rgb_lcd_example_main.c @@ -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