diff --git a/components/esp_hw_support/dma/gdma.c b/components/esp_hw_support/dma/gdma.c index fbda00f2de4..7d38620971a 100644 --- a/components/esp_hw_support/dma/gdma.c +++ b/components/esp_hw_support/dma/gdma.c @@ -465,6 +465,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) { @@ -475,6 +481,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"); @@ -488,6 +498,12 @@ esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_ portENTER_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); +#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 portEXIT_CRITICAL(&pair->spinlock); memcpy(&tx_chan->cbs, cbs, sizeof(gdma_tx_event_callbacks_t)); @@ -601,6 +617,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; + } + + portENTER_CRITICAL_SAFE(&dma_tx_chan->spinlock); + gdma_hal_request_link_switch_event(hal, pair->pair_id, dma_tx_chan->direction); + portEXIT_CRITICAL_SAFE(&dma_tx_chan->spinlock); + + return ESP_OK; +} + static void gdma_release_group_handle(gdma_group_t *group) { int group_id = group->group_id; @@ -868,6 +908,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(); } @@ -890,7 +936,7 @@ static esp_err_t gdma_install_rx_interrupt(gdma_rx_channel_t *rx_chan) #endif intr_handle_t intr = NULL; ret = esp_intr_alloc_intrstatus(gdma_periph_signals.groups[group->group_id].pairs[pair_id].rx_irq_id, isr_flags, - gdma_hal_get_intr_status_reg(hal, pair_id, GDMA_CHANNEL_DIRECTION_RX), GDMA_LL_RX_EVENT_MASK, + gdma_hal_get_intr_status_reg(hal, pair_id, GDMA_CHANNEL_DIRECTION_RX), hal->priv_data->rx_event_mask, gdma_default_rx_isr, rx_chan, &intr); ESP_GOTO_ON_ERROR(ret, err, TAG, "alloc interrupt failed"); rx_chan->base.intr = intr; @@ -922,7 +968,7 @@ static esp_err_t gdma_install_tx_interrupt(gdma_tx_channel_t *tx_chan) #endif intr_handle_t intr = NULL; ret = esp_intr_alloc_intrstatus(gdma_periph_signals.groups[group->group_id].pairs[pair_id].tx_irq_id, isr_flags, - gdma_hal_get_intr_status_reg(hal, pair_id, GDMA_CHANNEL_DIRECTION_TX), GDMA_LL_TX_EVENT_MASK, + gdma_hal_get_intr_status_reg(hal, pair_id, GDMA_CHANNEL_DIRECTION_TX), hal->priv_data->tx_event_mask, gdma_default_tx_isr, tx_chan, &intr); ESP_GOTO_ON_ERROR(ret, err, TAG, "alloc interrupt failed"); tx_chan->base.intr = intr; diff --git a/components/esp_hw_support/dma/include/esp_private/gdma.h b/components/esp_hw_support/dma/include/esp_private/gdma.h index af595f7c629..552f56dc434 100644 --- a/components/esp_hw_support/dma/include/esp_private/gdma.h +++ b/components/esp_hw_support/dma/include/esp_private/gdma.h @@ -68,6 +68,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; /** @@ -281,6 +282,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_hw_support/dma/linker.lf b/components/esp_hw_support/dma/linker.lf index 0f3faf6cb37..9511071ec2e 100644 --- a/components/esp_hw_support/dma/linker.lf +++ b/components/esp_hw_support/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_lcd/dsi/esp_lcd_panel_dpi.c b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c index f9e2585ba8b..6e9b78bdd2b 100644 --- a/components/esp_lcd/dsi/esp_lcd_panel_dpi.c +++ b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c @@ -41,7 +41,8 @@ struct esp_lcd_dpi_panel_t { SemaphoreHandle_t draw_sem; // A semaphore used to synchronize the draw operations when DMA2D is used esp_pm_lock_handle_t pm_lock; // Power management lock 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 }; @@ -83,10 +84,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; } } @@ -109,8 +116,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(); } } @@ -626,19 +633,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 efc2b395501..2846beccdae 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 */ @@ -178,18 +178,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 8584bada50a..6c9d4d7fcf8 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_AXI_GDMA_SUPPORTED +#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 @@ -95,6 +103,9 @@ static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *rgb_panel, const static void lcd_rgb_panel_release_gpio(esp_rgb_panel_t *rgb_panel); static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel); static void rgb_lcd_default_isr_handler(void *args); +#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 @@ -135,7 +146,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 @@ -703,7 +714,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; @@ -947,7 +960,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; @@ -957,6 +970,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 @@ -984,11 +1015,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 389d49ef2f8..3afed1d2c72 100644 --- a/components/esp_lcd/rgb/include/esp_lcd_panel_rgb.h +++ b/components/esp_lcd/rgb/include/esp_lcd_panel_rgb.h @@ -93,7 +93,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; @@ -135,7 +135,7 @@ typedef struct { esp_lcd_rgb_panel_bounce_buf_fill_cb_t on_bounce_empty; /*!< Bounce buffer empty callback. */ union { esp_lcd_rgb_panel_frame_buf_complete_cb_t on_bounce_frame_finish __attribute__((deprecated)); /*!< Bounce buffer finish 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 724fd369b1c..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 @@ -18,7 +18,7 @@ #include "test_mipi_dsi_board.h" #include "esp_lcd_ek79007.h" -IRAM_ATTR static bool test_rgb_panel_count_in_callback(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) +IRAM_ATTR static bool test_dpi_panel_count_in_callback(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) { uint32_t *count = (uint32_t *)user_ctx; *count = *count + 1; @@ -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_rgb_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/hal/esp32c5/include/hal/ahb_dma_ll.h b/components/hal/esp32c5/include/hal/ahb_dma_ll.h index 4ea87aef8c0..cc34ec43bb0 100644 --- a/components/hal/esp32c5/include/hal/ahb_dma_ll.h +++ b/components/hal/esp32c5/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) // 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/hal/esp32c61/include/hal/ahb_dma_ll.h b/components/hal/esp32c61/include/hal/ahb_dma_ll.h index 67a789970ad..899582358e8 100644 --- a/components/hal/esp32c61/include/hal/ahb_dma_ll.h +++ b/components/hal/esp32c61/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 (0xFE75) diff --git a/components/hal/esp32h4/include/hal/ahb_dma_ll.h b/components/hal/esp32h4/include/hal/ahb_dma_ll.h index addac62de00..c200e239403 100644 --- a/components/hal/esp32h4/include/hal/ahb_dma_ll.h +++ b/components/hal/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/hal/esp32p4/include/hal/ahb_dma_ll.h b/components/hal/esp32p4/include/hal/ahb_dma_ll.h index 2b089021467..8776931b09a 100644 --- a/components/hal/esp32p4/include/hal/ahb_dma_ll.h +++ b/components/hal/esp32p4/include/hal/ahb_dma_ll.h @@ -25,6 +25,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_INVALID_PERIPH_ID (0x3F) +#define AHB_DMA_LL_RX_EVENT_MASK (0x1F) +#define AHB_DMA_LL_TX_EVENT_MASK (0x0F) ///////////////////////////////////// Common ///////////////////////////////////////// /** diff --git a/components/hal/esp32p4/include/hal/axi_dma_ll.h b/components/hal/esp32p4/include/hal/axi_dma_ll.h index 5bd6eb601a7..9f4423ed389 100644 --- a/components/hal/esp32p4/include/hal/axi_dma_ll.h +++ b/components/hal/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" @@ -25,6 +26,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_INVALID_PERIPH_ID (0x3F) +#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 ///////////////////////////////////////// /** @@ -468,6 +476,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/hal/esp32p4/include/hal/gdma_ll.h b/components/hal/esp32p4/include/hal/gdma_ll.h index 6316a33f945..be1d794a546 100644 --- a/components/hal/esp32p4/include/hal/gdma_ll.h +++ b/components/hal/esp32p4/include/hal/gdma_ll.h @@ -18,11 +18,13 @@ #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) - #define GDMA_LL_INVALID_PERIPH_ID (0x3F) +// 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) #define GDMA_LL_EVENT_TX_EOF (1<<1) diff --git a/components/hal/gdma_hal_ahb_v1.c b/components/hal/gdma_hal_ahb_v1.c index adb85332bb2..dd9cd551137 100644 --- a/components/hal/gdma_hal_ahb_v1.c +++ b/components/hal/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) @@ -175,6 +177,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); @@ -198,5 +206,6 @@ void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config) #if GDMA_LL_AHB_BURST_SIZE_ADJUSTABLE hal->set_burst_size = gdma_ahb_hal_set_burst_size; #endif // GDMA_LL_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/hal/gdma_hal_ahb_v2.c b/components/hal/gdma_hal_ahb_v2.c index 0acb2b21c7c..04f40826c97 100644 --- a/components/hal/gdma_hal_ahb_v2.c +++ b/components/hal/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) @@ -232,6 +234,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->ahb_dma_dev = AHB_DMA_LL_GET_HW(config->group_id - GDMA_LL_AHB_GROUP_START_ID); @@ -261,5 +269,6 @@ void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config) #if GDMA_LL_AHB_BURST_SIZE_ADJUSTABLE hal->set_burst_size = gdma_ahb_hal_set_burst_size; #endif // GDMA_LL_AHB_BURST_SIZE_ADJUSTABLE + hal->is_tx_link_switch_event_supported = gdma_ahb_hal_is_tx_link_switch_event_supported; ahb_dma_ll_set_default_memory_range(hal->ahb_dma_dev); } diff --git a/components/hal/gdma_hal_axi.c b/components/hal/gdma_hal_axi.c index 9e2da3ac9f0..d0fe58fbe07 100644 --- a/components/hal/gdma_hal_axi.c +++ b/components/hal/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) @@ -163,6 +165,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) { @@ -230,6 +241,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); @@ -257,5 +273,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/hal/gdma_hal_top.c b/components/hal/gdma_hal_top.c index fce58d443c8..307767dc10f 100644 --- a/components/hal/gdma_hal_top.c +++ b/components/hal/gdma_hal_top.c @@ -113,3 +113,13 @@ void gdma_hal_enable_etm_task(gdma_hal_context_t *hal, int chan_id, gdma_channel hal->enable_etm_task(hal, chan_id, dir, en_or_dis); } #endif // SOC_GDMA_SUPPORT_ETM + +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/hal/include/hal/gdma_hal.h b/components/hal/include/hal/gdma_hal.h index a9af957a12f..b7c90585326 100644 --- a/components/hal/include/hal/gdma_hal.h +++ b/components/hal/include/hal/gdma_hal.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 */ @@ -49,9 +49,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; /** @@ -95,6 +95,8 @@ struct gdma_hal_context_t { #if SOC_GDMA_SUPPORT_ETM void (*enable_etm_task)(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_or_dis); /// Enable the ETM task #endif // SOC_GDMA_SUPPORT_ETM + 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); @@ -148,6 +150,10 @@ uint32_t gdma_hal_get_crc_result(gdma_hal_context_t *hal, int chan_id, gdma_chan void gdma_hal_enable_etm_task(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_or_dis); #endif +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/hal/include/hal/gdma_hal_ahb.h b/components/hal/include/hal/gdma_hal_ahb.h index 034792b733c..d4fe2e1dd29 100644 --- a/components/hal/include/hal/gdma_hal_ahb.h +++ b/components/hal/include/hal/gdma_hal_ahb.h @@ -12,36 +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, gdma_trigger_peripheral_t periph, int periph_sub_id); - -void gdma_ahb_hal_disconnect_peri(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); #ifdef __cplusplus diff --git a/components/hal/include/hal/gdma_hal_axi.h b/components/hal/include/hal/gdma_hal_axi.h index 37866aad51f..443cb87cd8f 100644 --- a/components/hal/include/hal/gdma_hal_axi.h +++ b/components/hal/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,36 +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, gdma_trigger_peripheral_t periph, int periph_sub_id); - -void gdma_axi_hal_disconnect_peri(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/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 704c0700382..9d34fe01241 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 @@ -331,7 +331,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 4ca86dce7ec..0a082a3aeca 100644 --- a/examples/peripherals/lcd/rgb_panel/main/idf_component.yml +++ b/examples/peripherals/lcd/rgb_panel/main/idf_component.yml @@ -1,2 +1,2 @@ dependencies: - lvgl/lvgl: "9.2.0" + lvgl/lvgl: "9.5.0" 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 0b0366158b8..3782575899b 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 @@ -101,15 +101,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) { @@ -118,6 +146,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); } @@ -264,10 +306,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)); @@ -282,7 +333,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