feat(lcd): support buffer switch interrupt

This commit is contained in:
Chen Jichang
2026-04-20 18:16:49 +08:00
parent 30e5a5d026
commit 9274289a62
28 changed files with 342 additions and 111 deletions

View File

@@ -51,7 +51,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
};
@@ -101,10 +102,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;
}
}
@@ -127,8 +134,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();
}
}
@@ -719,19 +726,23 @@ esp_err_t esp_lcd_dpi_panel_register_event_callbacks(esp_lcd_panel_handle_t pane
if (cbs->on_color_trans_done) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_color_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_color_trans_done callback not in IRAM");
}
if (cbs->on_refresh_done) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_refresh_done), ESP_ERR_INVALID_ARG, TAG, "on_refresh_done callback not in IRAM");
if (cbs->on_vsync) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_vsync), ESP_ERR_INVALID_ARG, TAG, "on_vsync callback not in IRAM");
}
if (cbs->on_frame_buf_complete) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_frame_buf_complete), ESP_ERR_INVALID_ARG, TAG, "on_frame_buf_complete callback not in IRAM");
}
if (user_ctx) {
ESP_RETURN_ON_FALSE(esp_ptr_internal(user_ctx), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
}
#endif // CONFIG_LCD_DSI_ISR_CACHE_SAFE
dpi_panel->on_color_trans_done = cbs->on_color_trans_done;
dpi_panel->on_refresh_done = cbs->on_refresh_done;
dpi_panel->on_vsync = cbs->on_vsync;
dpi_panel->on_frame_buf_complete = cbs->on_frame_buf_complete;
dpi_panel->user_ctx = user_ctx;
// enable the vsync interrupt if the callback is provided
mipi_dsi_brg_ll_enable_interrupt(dpi_panel->bus->hal.bridge, MIPI_DSI_BRG_LL_EVENT_VSYNC, cbs->on_refresh_done != NULL);
mipi_dsi_brg_ll_enable_interrupt(dpi_panel->bus->hal.bridge, MIPI_DSI_BRG_LL_EVENT_VSYNC, cbs->on_vsync != NULL);
return ESP_OK;
}

View File

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

View File

@@ -50,6 +50,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
@@ -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
@@ -139,7 +150,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
@@ -738,7 +749,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;
@@ -977,7 +990,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;
@@ -987,6 +1000,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
@@ -1013,11 +1044,19 @@ static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *rgb_panel)
// get the memory alignment required by the DMA
gdma_get_alignment_constraints(rgb_panel->dma_chan, &rgb_panel->int_mem_align, &rgb_panel->ext_mem_align);
// register DMA EOF callback
// register DMA event callbacks
gdma_tx_event_callbacks_t cbs = {
#if RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
// if no bounce buffer, the DMA EOF event means the end of a frame has been sent out to the LCD controller.
// But the dma link may have preloaded the next frame with the current buffer.
// So we need to wait for the GDMA link switch event to invoke the on_frame_buf_complete callback.
.on_trans_eof = (rgb_panel->flags.stream_mode && !rgb_panel->bb_size) ? NULL : lcd_rgb_panel_eof_handler,
.on_link_switch = lcd_rgb_panel_link_switch_handler,
#else
.on_trans_eof = lcd_rgb_panel_eof_handler,
#endif // RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
};
ESP_RETURN_ON_ERROR(gdma_register_tx_event_callbacks(rgb_panel->dma_chan, &cbs, rgb_panel), TAG, "register DMA EOF callback failed");
ESP_RETURN_ON_ERROR(gdma_register_tx_event_callbacks(rgb_panel->dma_chan, &cbs, rgb_panel), TAG, "register DMA event callbacks failed");
return ESP_OK;
}

View File

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

View File

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