feat(lcd): support rgb lcd driver for esp32p4

This commit is contained in:
morris
2024-10-16 19:06:40 +08:00
parent 5b8db196f8
commit efcb91b47e
25 changed files with 683 additions and 394 deletions

View File

@@ -115,6 +115,10 @@ uintptr_t gdma_link_get_head_addr(gdma_link_list_handle_t list);
* v item_index
* Link B: B1 --> B2 --> B3 --> B4
*
* After concatenation:
* Link A: A1 --> B3 --> B4
* Link B: B1 --> B2 --> B3 --> B4
*
* @param[in] first_link First link list handle, allocated by `gdma_new_link_list`
* @param[in] first_link_item_index Index of the item in the first link list (-1 means the last item)
* @param[in] second_link Second link list handle, allocated by `gdma_new_link_list`

View File

@@ -1,47 +1,43 @@
menu "LCD and Touch Panel"
comment "LCD Touch Drivers are maintained in the ESP Component Registry"
menu "ESP-Driver:LCD Controller Configurations"
config LCD_ENABLE_DEBUG_LOG
bool "Enable debug log"
default n
help
whether to enable the debug log message for LCD driver.
Note that, this option only controls the LCD driver log, won't affect other drivers.
menu "LCD Peripheral Configuration"
config LCD_ENABLE_DEBUG_LOG
bool "Enable debug log"
if SOC_LCD_RGB_SUPPORTED
config LCD_RGB_ISR_IRAM_SAFE
bool "RGB LCD ISR IRAM-Safe"
select GDMA_ISR_IRAM_SAFE # bounce buffer mode relies on GDMA EOF interrupt
default n
help
whether to enable the debug log message for LCD driver.
Note that, this option only controls the LCD driver log, won't affect other drivers.
Ensure the LCD interrupt is IRAM-Safe by allowing the interrupt handler to be
executable when the cache is disabled (e.g. SPI Flash write).
If you want the LCD driver to keep flushing the screen even when cache ops disabled,
you can enable this option. Note, this will also increase the IRAM usage.
if SOC_LCD_RGB_SUPPORTED
config LCD_RGB_ISR_IRAM_SAFE
bool "RGB LCD ISR IRAM-Safe"
select GDMA_ISR_IRAM_SAFE # bounce buffer mode relies on GDMA EOF interrupt
default n
help
Ensure the LCD interrupt is IRAM-Safe by allowing the interrupt handler to be
executable when the cache is disabled (e.g. SPI Flash write).
If you want the LCD driver to keep flushing the screen even when cache ops disabled,
you can enable this option. Note, this will also increase the IRAM usage.
config LCD_RGB_RESTART_IN_VSYNC
bool "Always restart RGB LCD transmission in VSYNC"
default n
help
Reset the GDMA channel every VBlank to stop permanent desyncs from happening.
Only need to enable it when in your application, the DMA can't deliver data
as fast as the LCD consumes it.
endif # SOC_LCD_RGB_SUPPORTED
config LCD_RGB_RESTART_IN_VSYNC
bool "Restart transmission in VSYNC"
default n
help
Reset the GDMA channel every VBlank to stop permanent desyncs from happening.
Only need to enable it when in your application, the DMA can't deliver data
as fast as the LCD consumes it.
endif # SOC_LCD_RGB_SUPPORTED
if SOC_MIPI_DSI_SUPPORTED
config LCD_DSI_ISR_IRAM_SAFE
bool "DSI LCD ISR IRAM-Safe"
default n
select DW_GDMA_ISR_IRAM_SAFE
select DW_GDMA_CTRL_FUNC_IN_IRAM
select DW_GDMA_SETTER_FUNC_IN_IRAM
select DW_GDMA_GETTER_FUNC_IN_IRAM
help
Ensure the LCD interrupt is IRAM-Safe by allowing the interrupt handler to be
executable when the cache is disabled (e.g. SPI Flash write).
If you want the LCD driver to keep flushing the screen even when cache ops disabled,
you can enable this option. Note, this will also increase the IRAM usage.
endif # SOC_MIPI_DSI_SUPPORTED
endmenu
if SOC_MIPI_DSI_SUPPORTED
config LCD_DSI_ISR_IRAM_SAFE
bool "DSI LCD ISR IRAM-Safe"
default n
select DW_GDMA_ISR_IRAM_SAFE
select DW_GDMA_CTRL_FUNC_IN_IRAM
select DW_GDMA_SETTER_FUNC_IN_IRAM
select DW_GDMA_GETTER_FUNC_IN_IRAM
help
Ensure the LCD interrupt is IRAM-Safe by allowing the interrupt handler to be
executable when the cache is disabled (e.g. SPI Flash write).
If you want the LCD driver to keep flushing the screen even when cache ops disabled,
you can enable this option. Note, this will also increase the IRAM usage.
endif # SOC_MIPI_DSI_SUPPORTED
endmenu

View File

@@ -44,6 +44,12 @@
#include "hal/lcd_ll.h"
#include "hal/cache_hal.h"
#include "hal/cache_ll.h"
#include "rgb_lcd_rotation_sw.h"
// hardware issue workaround
#if CONFIG_IDF_TARGET_ESP32S3
#define RGB_LCD_NEEDS_SEPARATE_RESTART_LINK 1
#endif
#if CONFIG_LCD_RGB_ISR_IRAM_SAFE
#define LCD_RGB_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED)
@@ -66,16 +72,6 @@
#define RGB_LCD_PANEL_MAX_FB_NUM 3 // maximum supported frame buffer number
#define RGB_LCD_PANEL_BOUNCE_BUF_NUM 2 // bounce buffer number
#define RGB_PANEL_SWAP_XY 0
#define RGB_PANEL_MIRROR_Y 1
#define RGB_PANEL_MIRROR_X 2
typedef enum {
ROTATE_MASK_SWAP_XY = BIT(RGB_PANEL_SWAP_XY),
ROTATE_MASK_MIRROR_Y = BIT(RGB_PANEL_MIRROR_Y),
ROTATE_MASK_MIRROR_X = BIT(RGB_PANEL_MIRROR_X),
} panel_rotate_mask_t;
static const char *TAG = "lcd_panel.rgb";
typedef struct esp_rgb_panel_t esp_rgb_panel_t;
@@ -89,10 +85,10 @@ static esp_err_t rgb_panel_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mi
static esp_err_t rgb_panel_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
static esp_err_t rgb_panel_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
static esp_err_t rgb_panel_disp_on_off(esp_lcd_panel_t *panel, bool off);
static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src);
static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *rgb_panel, lcd_clock_source_t clk_src);
static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *rgb_panel);
static esp_err_t lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *rgb_panel);
static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *panel, const esp_lcd_rgb_panel_config_t *panel_config);
static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *rgb_panel, const esp_lcd_rgb_panel_config_t *panel_config);
static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel);
static void rgb_lcd_default_isr_handler(void *args);
@@ -112,7 +108,9 @@ struct esp_rgb_panel_t {
gdma_channel_handle_t dma_chan; // DMA channel handle
gdma_link_list_handle_t dma_fb_links[RGB_LCD_PANEL_MAX_FB_NUM]; // DMA link lists for multiple frame buffers
gdma_link_list_handle_t dma_bb_link; // DMA link list for bounce buffer
#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
gdma_link_list_handle_t dma_restart_link; // DMA link list for restarting the DMA
#endif
uint8_t *fbs[RGB_LCD_PANEL_MAX_FB_NUM]; // Frame buffers
uint8_t *bounce_buffer[RGB_LCD_PANEL_BOUNCE_BUF_NUM]; // Pointer to the bounce buffers
size_t fb_size; // Size of frame buffer, in bytes
@@ -121,15 +119,16 @@ struct esp_rgb_panel_t {
uint8_t bb_fb_index; // Current frame buffer index which used by bounce buffer
size_t int_mem_align; // DMA buffer alignment for internal memory
size_t ext_mem_align; // DMA buffer alignment for external memory
int data_gpio_nums[SOC_LCD_RGB_DATA_WIDTH]; // GPIOs used for data lines, we keep these GPIOs for action like "invert_color"
int data_gpio_nums[SOC_LCDCAM_RGB_DATA_WIDTH]; // GPIOs used for data lines, we keep these GPIOs for action like "invert_color"
uint32_t src_clk_hz; // Peripheral source clock resolution
esp_lcd_rgb_timing_t timings; // RGB timing parameters (e.g. pclk, sync pulse, porch width)
int bounce_pos_px; // Position in whatever source material is used for the bounce buffer, in pixels
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_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
esp_lcd_rgb_panel_bounce_buf_finish_cb_t on_bounce_frame_finish; // callback used to notify when the bounce buffer finish copying the entire frame
void *user_ctx; // Reserved user's data of callback functions
int x_gap; // Extra gap in x coordinate, it's used when calculate the flush window
int y_gap; // Extra gap in y coordinate, it's used when calculate the flush window
@@ -142,7 +141,6 @@ struct esp_rgb_panel_t {
uint32_t fb_in_psram: 1; // Whether the frame buffer is in PSRAM
uint32_t need_update_pclk: 1; // Whether to update the PCLK before start a new transaction
uint32_t need_restart: 1; // Whether to restart the LCD controller and the DMA
uint32_t bb_invalidate_cache: 1; // Whether to do cache invalidation in bounce buffer mode
uint32_t fb_behind_cache: 1; // Whether the frame buffer is behind the cache
uint32_t bb_behind_cache: 1; // Whether the bounce buffer is behind the cache
} flags;
@@ -229,9 +227,11 @@ static esp_err_t lcd_rgb_panel_destroy(esp_rgb_panel_t *rgb_panel)
if (rgb_panel->dma_bb_link) {
gdma_del_link_list(rgb_panel->dma_bb_link);
}
#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
if (rgb_panel->dma_restart_link) {
gdma_del_link_list(rgb_panel->dma_restart_link);
}
#endif
if (rgb_panel->intr) {
esp_intr_free(rgb_panel->intr);
}
@@ -262,8 +262,6 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
ESP_ERR_INVALID_ARG, TAG, "num_fbs conflicts with no_fb");
ESP_RETURN_ON_FALSE(!(rgb_panel_config->flags.no_fb && rgb_panel_config->bounce_buffer_size_px == 0),
ESP_ERR_INVALID_ARG, TAG, "must set bounce buffer if there's no frame buffer");
ESP_RETURN_ON_FALSE(!(rgb_panel_config->flags.refresh_on_demand && rgb_panel_config->bounce_buffer_size_px),
ESP_ERR_INVALID_ARG, TAG, "refresh on demand is not supported under bounce buffer mode");
// determine number of framebuffers
size_t num_fbs = 1;
@@ -287,9 +285,7 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
size_t bb_size = rgb_panel_config->bounce_buffer_size_px * fb_bits_per_pixel / 8;
size_t expect_bb_eof_count = 0;
if (bb_size) {
// we want the bounce can always end in the second buffer
ESP_RETURN_ON_FALSE(fb_size % (2 * bb_size) == 0, ESP_ERR_INVALID_ARG, TAG,
"fb size must be even multiple of bounce buffer size");
ESP_RETURN_ON_FALSE(fb_size % bb_size == 0, ESP_ERR_INVALID_ARG, TAG, "frame buffer size must be multiple of bounce buffer size");
expect_bb_eof_count = fb_size / bb_size;
}
@@ -364,7 +360,6 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
rgb_panel->output_bits_per_pixel = fb_bits_per_pixel; // by default, the output bpp is the same as the frame buffer bpp
rgb_panel->disp_gpio_num = rgb_panel_config->disp_gpio_num;
rgb_panel->flags.disp_en_level = !rgb_panel_config->flags.disp_active_low;
rgb_panel->flags.bb_invalidate_cache = rgb_panel_config->flags.bb_invalidate_cache;
rgb_panel->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
// fill function table
rgb_panel->base.del = rgb_panel_del;
@@ -401,19 +396,23 @@ esp_err_t esp_lcd_rgb_panel_register_event_callbacks(esp_lcd_panel_handle_t pane
if (callbacks->on_vsync) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_vsync), ESP_ERR_INVALID_ARG, TAG, "on_vsync callback not in IRAM");
}
if (callbacks->on_color_trans_done) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_color_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_color_trans_done callback not in IRAM");
}
if (callbacks->on_frame_buf_complete) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_frame_buf_complete), ESP_ERR_INVALID_ARG, TAG, "on_frame_buf_complete callback not in IRAM");
}
if (callbacks->on_bounce_empty) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_bounce_empty), ESP_ERR_INVALID_ARG, TAG, "on_bounce_empty callback not in IRAM");
}
if (callbacks->on_bounce_frame_finish) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_bounce_frame_finish), ESP_ERR_INVALID_ARG, TAG, "on_bounce_frame_finish 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_RGB_ISR_IRAM_SAFE
rgb_panel->on_vsync = callbacks->on_vsync;
rgb_panel->on_color_trans_done = callbacks->on_color_trans_done;
rgb_panel->on_frame_buf_complete = callbacks->on_frame_buf_complete;
rgb_panel->on_bounce_empty = callbacks->on_bounce_empty;
rgb_panel->on_bounce_frame_finish = callbacks->on_bounce_frame_finish;
rgb_panel->user_ctx = user_ctx;
return ESP_OK;
}
@@ -554,7 +553,8 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
// enable RGB mode and set data width
lcd_ll_enable_rgb_mode(rgb_panel->hal.dev, true);
lcd_ll_set_dma_read_stride(rgb_panel->hal.dev, rgb_panel->data_width);
lcd_ll_set_phase_cycles(rgb_panel->hal.dev, 0, 0, 1); // enable data phase only
// enable data phase only
lcd_ll_set_phase_cycles(rgb_panel->hal.dev, 0, 0, 1);
// number of data cycles is controlled by DMA buffer size
lcd_ll_enable_output_always_on(rgb_panel->hal.dev, true);
// configure HSYNC, VSYNC, DE signal idle state level
@@ -572,7 +572,8 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
lcd_ll_enable_output_hsync_in_porch_region(rgb_panel->hal.dev, true);
// generate the hsync at the very beginning of line
lcd_ll_set_hsync_position(rgb_panel->hal.dev, 0);
// send next frame automatically in stream mode
// in stream mode, after finish one frame, the LCD controller will ask for data automatically from the DMA
// DMA should prepare the next frame data within porch region
lcd_ll_enable_auto_next_frame(rgb_panel->hal.dev, rgb_panel->flags.stream_mode);
// trigger interrupt on the end of frame
lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_VSYNC_END, true);
@@ -586,157 +587,18 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
return ret;
}
__attribute__((always_inline))
static inline void copy_pixel_8bpp(uint8_t *to, const uint8_t *from)
{
*to++ = *from++;
}
__attribute__((always_inline))
static inline void copy_pixel_16bpp(uint8_t *to, const uint8_t *from)
{
*to++ = *from++;
*to++ = *from++;
}
__attribute__((always_inline))
static inline void copy_pixel_24bpp(uint8_t *to, const uint8_t *from)
{
*to++ = *from++;
*to++ = *from++;
*to++ = *from++;
}
#define COPY_PIXEL_CODE_BLOCK(_bpp) \
switch (rgb_panel->rotate_mask) \
{ \
case 0: \
{ \
uint8_t *to = fb + (y_start * h_res + x_start) * bytes_per_pixel; \
for (int y = y_start; y < y_end; y++) \
{ \
memcpy(to, from, copy_bytes_per_line); \
to += bytes_per_line; \
from += copy_bytes_per_line; \
} \
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
flush_ptr = fb + y_start * bytes_per_line; \
} \
break; \
case ROTATE_MASK_MIRROR_X: \
for (int y = y_start; y < y_end; y++) \
{ \
uint32_t index = (y * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \
for (size_t x = x_start; x < x_end; x++) \
{ \
copy_pixel_##_bpp##bpp(to + index, from); \
index -= bytes_per_pixel; \
from += bytes_per_pixel; \
} \
} \
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
flush_ptr = fb + y_start * bytes_per_line; \
break; \
case ROTATE_MASK_MIRROR_Y: \
{ \
uint8_t *to = fb + ((v_res - 1 - y_start) * h_res + x_start) * bytes_per_pixel; \
for (int y = y_start; y < y_end; y++) \
{ \
memcpy(to, from, copy_bytes_per_line); \
to -= bytes_per_line; \
from += copy_bytes_per_line; \
} \
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
flush_ptr = fb + (v_res - y_end) * bytes_per_line; \
} \
break; \
case ROTATE_MASK_MIRROR_X | ROTATE_MASK_MIRROR_Y: \
for (int y = y_start; y < y_end; y++) \
{ \
uint32_t index = ((v_res - 1 - y) * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \
for (size_t x = x_start; x < x_end; x++) \
{ \
copy_pixel_##_bpp##bpp(to + index, from); \
index -= bytes_per_pixel; \
from += bytes_per_pixel; \
} \
} \
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
flush_ptr = fb + (v_res - y_end) * bytes_per_line; \
break; \
case ROTATE_MASK_SWAP_XY: \
for (int y = y_start; y < y_end; y++) \
{ \
for (int x = x_start; x < x_end; x++) \
{ \
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
uint32_t i = (x * h_res + y) * bytes_per_pixel; \
copy_pixel_##_bpp##bpp(to + i, from + j); \
} \
} \
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
flush_ptr = fb + x_start * bytes_per_line; \
break; \
case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_X: \
for (int y = y_start; y < y_end; y++) \
{ \
for (int x = x_start; x < x_end; x++) \
{ \
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
uint32_t i = (x * h_res + h_res - 1 - y) * bytes_per_pixel; \
copy_pixel_##_bpp##bpp(to + i, from + j); \
} \
} \
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
flush_ptr = fb + x_start * bytes_per_line; \
break; \
case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_Y: \
for (int y = y_start; y < y_end; y++) \
{ \
for (int x = x_start; x < x_end; x++) \
{ \
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
uint32_t i = ((v_res - 1 - x) * h_res + y) * bytes_per_pixel; \
copy_pixel_##_bpp##bpp(to + i, from + j); \
} \
} \
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
flush_ptr = fb + (v_res - x_end) * bytes_per_line; \
break; \
case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_X | ROTATE_MASK_MIRROR_Y: \
for (int y = y_start; y < y_end; y++) \
{ \
for (int x = x_start; x < x_end; x++) \
{ \
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
uint32_t i = ((v_res - 1 - x) * h_res + h_res - 1 - y) * bytes_per_pixel; \
copy_pixel_##_bpp##bpp(to + i, from + j); \
} \
} \
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
flush_ptr = fb + (v_res - x_end) * bytes_per_line; \
break; \
default: \
break; \
}
static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
{
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
ESP_RETURN_ON_FALSE(rgb_panel->num_fbs > 0, ESP_ERR_NOT_SUPPORTED, TAG, "no frame buffer installed");
esp_lcd_rgb_panel_draw_buf_complete_cb_t cb = rgb_panel->on_color_trans_done;
// check if we need to copy the draw buffer (pointed by the color_data) to the driver's frame buffer
bool do_copy = false;
if (color_data == rgb_panel->fbs[0]) {
rgb_panel->cur_fb_index = 0;
} else if (color_data == rgb_panel->fbs[1]) {
rgb_panel->cur_fb_index = 1;
} else if (color_data == rgb_panel->fbs[2]) {
rgb_panel->cur_fb_index = 2;
} else {
// we do the copy only if the color_data is different from either frame buffer
do_copy = true;
}
uint8_t *draw_buffer = (uint8_t *)color_data;
size_t fb_size = rgb_panel->fb_size;
int h_res = rgb_panel->timings.h_res;
int v_res = rgb_panel->timings.v_res;
int bytes_per_pixel = rgb_panel->fb_bits_per_pixel / 8;
uint32_t bytes_per_line = bytes_per_pixel * h_res;
// adjust the flush window by adding extra gap
x_start += rgb_panel->x_gap;
@@ -745,8 +607,6 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
y_end += rgb_panel->y_gap;
// clip to boundaries
int h_res = rgb_panel->timings.h_res;
int v_res = rgb_panel->timings.v_res;
if (rgb_panel->rotate_mask & ROTATE_MASK_SWAP_XY) {
x_start = MAX(x_start, 0);
x_end = MIN(x_end, v_res);
@@ -759,15 +619,24 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
y_end = MIN(y_end, v_res);
}
int bytes_per_pixel = rgb_panel->fb_bits_per_pixel / 8;
int pixels_per_line = rgb_panel->timings.h_res;
uint32_t bytes_per_line = bytes_per_pixel * pixels_per_line;
uint8_t *fb = rgb_panel->fbs[rgb_panel->cur_fb_index];
size_t bytes_to_flush = v_res * h_res * bytes_per_pixel;
uint8_t *flush_ptr = fb;
// check if we want to copy the draw buffer to the internal frame buffer
bool draw_buf_copy_to_fb = true;
uint8_t draw_buf_fb_index = 0;
for (int i = 0; i < rgb_panel->num_fbs; i++) {
if (draw_buffer >= rgb_panel->fbs[i] && draw_buffer < rgb_panel->fbs[i] + fb_size) {
draw_buf_fb_index = i;
draw_buf_copy_to_fb = false;
break;
}
}
if (draw_buf_copy_to_fb) {
// sync the draw buffer with the frame buffer by CPU copy
ESP_LOGV(TAG, "copy draw buffer to frame buffer by CPU");
uint8_t *fb = rgb_panel->fbs[rgb_panel->cur_fb_index];
size_t bytes_to_flush = v_res * bytes_per_line;
uint8_t *flush_ptr = fb;
if (do_copy) {
// copy the UI draw buffer into internal frame buffer
const uint8_t *from = (const uint8_t *)color_data;
uint32_t copy_bytes_per_line = (x_end - x_start) * bytes_per_pixel;
size_t offset = y_start * copy_bytes_per_line + x_start * bytes_per_pixel;
@@ -779,12 +648,29 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
} else if (3 == bytes_per_pixel) {
COPY_PIXEL_CODE_BLOCK(24)
}
}
// Note that if we use a bounce buffer, the data gets read by the CPU as well so no need to write back
if (rgb_panel->flags.fb_in_psram && !rgb_panel->bb_size) {
// CPU writes data to PSRAM through DCache, data in PSRAM might not get updated, so write back
ESP_RETURN_ON_ERROR(esp_cache_msync(flush_ptr, bytes_to_flush, 0), TAG, "flush cache buffer failed");
// do memory sync only when the frame buffer is mounted to the DMA link list and behind the cache
if (!rgb_panel->bb_size && rgb_panel->flags.fb_behind_cache) {
esp_cache_msync(flush_ptr, bytes_to_flush, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
}
// after the draw buffer finished copying, notify the user to recycle the draw buffer
if (cb) {
cb(&rgb_panel->base, NULL, rgb_panel->user_ctx);
}
} else {
ESP_LOGV(TAG, "draw buffer is part of the frame buffer");
// the new frame buffer index is changed
rgb_panel->cur_fb_index = draw_buf_fb_index;
// when this function is called, the frame buffer already reflects the draw buffer changes
// if the frame buffer is also mounted to the DMA, we need to do the sync between them
if (!rgb_panel->bb_size && rgb_panel->flags.fb_behind_cache) {
uint8_t *cache_sync_start = rgb_panel->fbs[draw_buf_fb_index] + (y_start * h_res) * bytes_per_pixel;
size_t cache_sync_size = (y_end - y_start) * bytes_per_line;
esp_cache_msync(cache_sync_start, cache_sync_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
}
// after the draw buffer finished copying, notify the user to recycle the draw buffer
if (cb) {
cb(&rgb_panel->base, NULL, rgb_panel->user_ctx);
}
}
if (!rgb_panel->bb_size) {
@@ -895,24 +781,24 @@ static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *rgb_panel, const
return ESP_OK;
}
static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src)
static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *rgb_panel, lcd_clock_source_t clk_src)
{
// get clock source frequency
uint32_t src_clk_hz = 0;
ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_hz),
TAG, "get clock source frequency failed");
panel->src_clk_hz = src_clk_hz;
rgb_panel->src_clk_hz = src_clk_hz;
esp_clk_tree_enable_src((soc_module_clk_t)clk_src, true);
LCD_CLOCK_SRC_ATOMIC() {
lcd_ll_select_clk_src(panel->hal.dev, clk_src);
lcd_ll_select_clk_src(rgb_panel->hal.dev, clk_src);
}
// create pm lock based on different clock source
// clock sources like PLL and XTAL will be turned off in light sleep
#if CONFIG_PM_ENABLE
ESP_RETURN_ON_ERROR(esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "rgb_panel", &panel->pm_lock), TAG, "create pm lock failed");
ESP_RETURN_ON_ERROR(esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "rgb_panel", &rgb_panel->pm_lock), TAG, "create pm lock failed");
// hold the lock during the whole lifecycle of RGB panel
esp_pm_lock_acquire(panel->pm_lock);
esp_pm_lock_acquire(rgb_panel->pm_lock);
ESP_LOGD(TAG, "installed pm lock and hold the lock during the whole panel lifecycle");
#endif
@@ -923,52 +809,73 @@ static IRAM_ATTR bool lcd_rgb_panel_fill_bounce_buffer(esp_rgb_panel_t *panel, u
{
bool need_yield = false;
int bytes_per_pixel = panel->fb_bits_per_pixel / 8;
if (panel->num_fbs == 0) {
if (unlikely(panel->num_fbs == 0)) {
// driver doesn't maintain a frame buffer, so ask the user to fill the bounce buffer
if (panel->on_bounce_empty) {
// We don't have a frame buffer here; we need to call a callback to refill the bounce buffer
if (panel->on_bounce_empty(&panel->base, buffer, panel->bounce_pos_px, panel->bb_size, panel->user_ctx)) {
need_yield = true;
}
}
} else {
// We do have frame buffer; copy from there.
// Note: if the cache is disabled, and accessing the PSRAM by DCACHE will crash.
// copy partial frame buffer to the bounce buffer
// Note: if the frame buffer is behind a cache, and the cache is disabled, crash would happen here when auto write back happens
memcpy(buffer, &panel->fbs[panel->bb_fb_index][panel->bounce_pos_px * bytes_per_pixel], panel->bb_size);
if (panel->flags.bb_invalidate_cache) {
// We don't need the bytes we copied from the psram anymore
// Make sure that if anything happened to have changed (because the line already was in cache) we write the data back.
esp_cache_msync(&panel->fbs[panel->bb_fb_index][panel->bounce_pos_px * bytes_per_pixel], (size_t)panel->bb_size, ESP_CACHE_MSYNC_FLAG_INVALIDATE);
}
}
// do memory sync if the bounce buffer is behind the cache
if (panel->flags.bb_behind_cache) {
esp_cache_msync(buffer, panel->bb_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
}
panel->bounce_pos_px += panel->bb_size / bytes_per_pixel;
// If the bounce pos is larger than the frame buffer size, wrap around so the next isr starts pre-loading the next frame.
if (panel->bounce_pos_px >= panel->fb_size / bytes_per_pixel) {
panel->bounce_pos_px = 0;
panel->bb_fb_index = panel->cur_fb_index;
if (panel->on_bounce_frame_finish) {
if (panel->on_bounce_frame_finish(&panel->base, NULL, panel->user_ctx)) {
esp_lcd_rgb_panel_frame_buf_complete_cb_t cb = panel->on_frame_buf_complete;
if (cb) {
if (cb(&panel->base, NULL, panel->user_ctx)) {
need_yield = true;
}
}
}
if (panel->num_fbs > 0) {
// Preload the next bit of buffer from psram
// Preload the next bit of buffer to the cache memory, this can improve the performance
if (panel->num_fbs > 0 && panel->flags.fb_behind_cache) {
#if CONFIG_IDF_TARGET_ESP32S3
Cache_Start_DCache_Preload((uint32_t)&panel->fbs[panel->bb_fb_index][panel->bounce_pos_px * bytes_per_pixel],
panel->bb_size, 0);
#elif CONFIG_IDF_TARGET_ESP32P4
Cache_Start_L2_Cache_Preload((uint32_t)&panel->fbs[panel->bb_fb_index][panel->bounce_pos_px * bytes_per_pixel],
panel->bb_size, 0);
#else
#error "Unsupported target"
#endif
}
return need_yield;
}
// This is called in bounce buffer mode, when one bounce buffer has been fully sent to the LCD peripheral.
static IRAM_ATTR bool lcd_rgb_panel_eof_handler(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
{
esp_rgb_panel_t *panel = (esp_rgb_panel_t *)user_data;
// Figure out which bounce buffer to write to.
portENTER_CRITICAL_ISR(&panel->spinlock);
int bb = panel->bb_eof_count % RGB_LCD_PANEL_BOUNCE_BUF_NUM;
panel->bb_eof_count++;
portEXIT_CRITICAL_ISR(&panel->spinlock);
return lcd_rgb_panel_fill_bounce_buffer(panel, panel->bounce_buffer[bb]);
bool need_yield = false;
esp_rgb_panel_t *rgb_panel = (esp_rgb_panel_t *)user_data;
if (rgb_panel->bb_size) {
// in bounce buffer mode, the DMA EOF means time to fill the finished bounce buffer
// Figure out which bounce buffer to write to
portENTER_CRITICAL_ISR(&rgb_panel->spinlock);
int bb = rgb_panel->bb_eof_count % RGB_LCD_PANEL_BOUNCE_BUF_NUM;
rgb_panel->bb_eof_count++;
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
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;
}
static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *rgb_panel)
@@ -995,25 +902,27 @@ 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);
// we need to refill the bounce buffer in the DMA EOF interrupt, so only register the callback for bounce buffer mode
if (rgb_panel->bb_size) {
gdma_tx_event_callbacks_t cbs = {
.on_trans_eof = lcd_rgb_panel_eof_handler,
};
gdma_register_tx_event_callbacks(rgb_panel->dma_chan, &cbs, rgb_panel);
}
// register DMA EOF callback
gdma_tx_event_callbacks_t cbs = {
.on_trans_eof = lcd_rgb_panel_eof_handler,
};
ESP_RETURN_ON_ERROR(gdma_register_tx_event_callbacks(rgb_panel->dma_chan, &cbs, rgb_panel), TAG, "register DMA EOF callback failed");
return ESP_OK;
}
#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
// If we restart GDMA, the data sent to the LCD peripheral needs to start LCD_FIFO_PRESERVE_SIZE_PX pixels after the FB start
// so we use a dedicated DMA link (called restart link) to restart the transaction
#define LCD_FIFO_PRESERVE_SIZE_PX (LCD_LL_FIFO_DEPTH + 1)
#endif
static esp_err_t lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *rgb_panel)
{
#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
// the restart link shares the same buffer with the frame/bounce buffer but start from a different offset
int restart_skip_bytes = LCD_FIFO_PRESERVE_SIZE_PX * (rgb_panel->fb_bits_per_pixel / 8);
#endif
if (rgb_panel->bb_size) {
// DMA is used to convey the bounce buffer
size_t num_dma_nodes_per_bounce_buffer = (rgb_panel->bb_size + LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1) / LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
@@ -1035,6 +944,7 @@ static esp_err_t lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *rgb_panel)
}
ESP_RETURN_ON_ERROR(gdma_link_mount_buffers(rgb_panel->dma_bb_link, 0, mount_cfgs, RGB_LCD_PANEL_BOUNCE_BUF_NUM, NULL),
TAG, "mount DMA bounce buffers failed");
#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
// create restart link
gdma_link_list_config_t restart_link_cfg = {
.buffer_alignment = rgb_panel->int_mem_align,
@@ -1054,6 +964,7 @@ static esp_err_t lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *rgb_panel)
// Magic here: we use the restart link to restart the bounce buffer link list, so concat them
gdma_link_concat(rgb_panel->dma_restart_link, 0, rgb_panel->dma_bb_link, 1);
#endif
} else {
// DMA is used to convey the frame buffer
size_t num_dma_nodes = (rgb_panel->fb_size + LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1) / LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
@@ -1079,6 +990,7 @@ static esp_err_t lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *rgb_panel)
ESP_RETURN_ON_ERROR(gdma_link_mount_buffers(rgb_panel->dma_fb_links[i], 0, &mount_cfg, 1, NULL),
TAG, "mount DMA frame buffer failed");
}
#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
// create restart link
gdma_link_list_config_t restart_link_cfg = {
.buffer_alignment = rgb_panel->flags.fb_in_psram ? rgb_panel->ext_mem_align : rgb_panel->int_mem_align,
@@ -1098,6 +1010,7 @@ static esp_err_t lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *rgb_panel)
// Magic here: we use the restart link to restart the frame buffer link list, so concat them
gdma_link_concat(rgb_panel->dma_restart_link, 0, rgb_panel->dma_fb_links[0], 1);
#endif
}
return ESP_OK;
@@ -1145,8 +1058,17 @@ static IRAM_ATTR void lcd_rgb_panel_try_restart_transmission(esp_rgb_panel_t *pa
}
gdma_reset(panel->dma_chan);
lcd_ll_fifo_reset(panel->hal.dev);
#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
// restart the DMA by a special DMA node
gdma_start(panel->dma_chan, gdma_link_get_head_addr(panel->dma_restart_link));
#else
if (panel->bb_size) {
gdma_start(panel->dma_chan, gdma_link_get_head_addr(panel->dma_bb_link));
} else {
gdma_start(panel->dma_chan, gdma_link_get_head_addr(panel->dma_fb_links[panel->cur_fb_index]));
}
#endif
if (panel->bb_size) {
// Fill 2nd bounce buffer while 1st is being sent out, if needed.
@@ -1162,6 +1084,7 @@ static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel)
// reset FIFO of DMA and LCD, in case there remains old frame data
gdma_reset(rgb_panel->dma_chan);
lcd_ll_stop(rgb_panel->hal.dev);
lcd_ll_reset(rgb_panel->hal.dev);
lcd_ll_fifo_reset(rgb_panel->hal.dev);
// pre-fill bounce buffers if needed
@@ -1203,8 +1126,11 @@ IRAM_ATTR static void rgb_lcd_default_isr_handler(void *args)
esp_rgb_panel_t *rgb_panel = (esp_rgb_panel_t *)args;
bool need_yield = false;
// clear the interrupt status
uint32_t intr_status = lcd_ll_get_interrupt_status(rgb_panel->hal.dev);
lcd_ll_clear_interrupt_status(rgb_panel->hal.dev, intr_status);
// VSYNC event happened
if (intr_status & LCD_LL_EVENT_VSYNC_END) {
// call user registered callback
if (rgb_panel->on_vsync) {
@@ -1222,6 +1148,7 @@ IRAM_ATTR static void rgb_lcd_default_isr_handler(void *args)
}
}
// yield if needed
if (need_yield) {
portYIELD_FROM_ISR();
}

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -77,14 +77,32 @@ typedef struct {
} esp_lcd_rgb_panel_event_data_t;
/**
* @brief RGB LCD VSYNC event callback prototype
* @brief A general function callback prototype for RGB panel driver
*
* @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel`
* @param[in] edata Panel event data, fed by driver
* @param[in] panel LCD panel handle, which is created by factory API like `esp_lcd_new_rgb_panel`
* @param[in] edata RGB panel event data, provided by driver
* @param[in] user_ctx User data, passed from `esp_lcd_rgb_panel_register_event_callbacks()`
* @return Whether a high priority task has been waken up by this function
*/
typedef bool (*esp_lcd_rgb_panel_vsync_cb_t)(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx);
typedef bool (*esp_lcd_rgb_panel_general_cb_t)(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx);
/**
* @brief Declare the prototype of the function that will be invoked when the user draw buffer is complete.
* The draw buffer can be recycled after this event.
*/
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.
* 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;
/**
* @brief Declare the prototype of the function that will be invoked when the LCD controller sends the VSYNC signal.
* It means, the LCD hardware should be ready, and after some blank time, the next frame will be flushed to the LCD controller.
*/
typedef esp_lcd_rgb_panel_general_cb_t esp_lcd_rgb_panel_vsync_cb_t;
/**
* @brief Prototype for function to re-fill a bounce buffer, rather than copying from the frame buffer
@@ -99,15 +117,10 @@ typedef bool (*esp_lcd_rgb_panel_vsync_cb_t)(esp_lcd_panel_handle_t panel, const
*/
typedef bool (*esp_lcd_rgb_panel_bounce_buf_fill_cb_t)(esp_lcd_panel_handle_t panel, void *bounce_buf, int pos_px, int len_bytes, void *user_ctx);
/**
* @brief Prototype for the function to be called when the bounce buffer finish copying the entire frame.
*
* @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel`
* @param[in] edata Panel event data, fed by driver
* @param[in] user_ctx User data, passed from `esp_lcd_rgb_panel_register_event_callbacks()`
* @return Whether a high priority task has been waken up by this function
*/
typedef bool (*esp_lcd_rgb_panel_bounce_buf_finish_cb_t)(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx);
/** @cond */
/// for backward compatible
typedef esp_lcd_rgb_panel_frame_buf_complete_cb_t esp_lcd_rgb_panel_bounce_buf_finish_cb_t __attribute__((deprecated("esp_lcd_rgb_panel_bounce_buf_finish_cb_t is deprecated, use esp_lcd_rgb_panel_frame_buf_complete_cb_t instead")));
/** @endcond */
/**
* @brief Group of supported RGB LCD panel callbacks
@@ -115,9 +128,15 @@ typedef bool (*esp_lcd_rgb_panel_bounce_buf_finish_cb_t)(esp_lcd_panel_handle_t
* @note When CONFIG_LCD_RGB_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM.
*/
typedef struct {
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_bounce_buf_finish_cb_t on_bounce_frame_finish; /*!< Bounce buffer finish callback. */
esp_lcd_rgb_panel_draw_buf_complete_cb_t on_color_trans_done; /*!< Invoked when user's color buffer copied to the internal 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_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. */
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_event_callbacks_t;
/**
@@ -134,7 +153,7 @@ typedef struct {
DMA fetching from DRAM bounce buffer is much faster than PSRAM frame buffer. */
size_t sram_trans_align __attribute__((deprecated)); /*!< Alignment of buffers (frame buffer or bounce buffer) that allocated in SRAM */
union {
size_t psram_trans_align; /*!< Alignment of buffers (frame buffer) that allocated in PSRAM */
size_t psram_trans_align __attribute__((deprecated)); /*!< Alignment of buffers (frame buffer) that allocated in PSRAM */
size_t dma_burst_size; /*!< DMA burst size, in bytes */
};
int hsync_gpio_num; /*!< GPIO used for HSYNC signal */
@@ -142,11 +161,10 @@ typedef struct {
int de_gpio_num; /*!< GPIO used for DE signal, set to -1 if it's not used */
int pclk_gpio_num; /*!< GPIO used for PCLK signal, set to -1 if it's not used */
int disp_gpio_num; /*!< GPIO used for display control signal, set to -1 if it's not used */
int data_gpio_nums[SOC_LCD_RGB_DATA_WIDTH]; /*!< GPIOs used for data lines */
int data_gpio_nums[SOC_LCDCAM_RGB_DATA_WIDTH]; /*!< GPIOs used for data lines */
struct {
uint32_t disp_active_low: 1; /*!< If this flag is enabled, a low level of display control signal can turn the screen on; vice versa */
uint32_t refresh_on_demand: 1; /*!< If this flag is enabled, the host only refresh the frame buffer when `esp_lcd_panel_draw_bitmap` is called.
This is useful when the LCD screen has a GRAM and can refresh the LCD by itself. */
uint32_t refresh_on_demand: 1; /*!< If this flag is enabled, the host only refresh the frame buffer in `esp_lcd_panel_draw_bitmap` and `esp_lcd_rgb_panel_refresh`. */
uint32_t fb_in_psram: 1; /*!< If this flag is enabled, the frame buffer will be allocated from PSRAM, preferentially */
uint32_t double_fb: 1; /*!< If this flag is enabled, the driver will allocate two screen sized frame buffer, same as num_fbs=2 */
uint32_t no_fb: 1; /*!< If this flag is enabled, the driver won't allocate frame buffer.

View File

@@ -0,0 +1,162 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "esp_bit_defs.h"
#ifdef __cplusplus
extern "C" {
#endif
#define RGB_PANEL_SWAP_XY 0
#define RGB_PANEL_MIRROR_Y 1
#define RGB_PANEL_MIRROR_X 2
typedef enum {
ROTATE_MASK_SWAP_XY = BIT(RGB_PANEL_SWAP_XY),
ROTATE_MASK_MIRROR_Y = BIT(RGB_PANEL_MIRROR_Y),
ROTATE_MASK_MIRROR_X = BIT(RGB_PANEL_MIRROR_X),
} panel_rotate_mask_t;
__attribute__((always_inline))
static inline void copy_pixel_8bpp(uint8_t *to, const uint8_t *from)
{
*to++ = *from++;
}
__attribute__((always_inline))
static inline void copy_pixel_16bpp(uint8_t *to, const uint8_t *from)
{
*to++ = *from++;
*to++ = *from++;
}
__attribute__((always_inline))
static inline void copy_pixel_24bpp(uint8_t *to, const uint8_t *from)
{
*to++ = *from++;
*to++ = *from++;
*to++ = *from++;
}
#define COPY_PIXEL_CODE_BLOCK(_bpp) \
switch (rgb_panel->rotate_mask) \
{ \
case 0: \
{ \
uint8_t *to = fb + (y_start * h_res + x_start) * bytes_per_pixel; \
for (int y = y_start; y < y_end; y++) \
{ \
memcpy(to, from, copy_bytes_per_line); \
to += bytes_per_line; \
from += copy_bytes_per_line; \
} \
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
flush_ptr = fb + y_start * bytes_per_line; \
} \
break; \
case ROTATE_MASK_MIRROR_X: \
for (int y = y_start; y < y_end; y++) \
{ \
uint32_t index = (y * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \
for (size_t x = x_start; x < x_end; x++) \
{ \
copy_pixel_##_bpp##bpp(to + index, from); \
index -= bytes_per_pixel; \
from += bytes_per_pixel; \
} \
} \
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
flush_ptr = fb + y_start * bytes_per_line; \
break; \
case ROTATE_MASK_MIRROR_Y: \
{ \
uint8_t *to = fb + ((v_res - 1 - y_start) * h_res + x_start) * bytes_per_pixel; \
for (int y = y_start; y < y_end; y++) \
{ \
memcpy(to, from, copy_bytes_per_line); \
to -= bytes_per_line; \
from += copy_bytes_per_line; \
} \
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
flush_ptr = fb + (v_res - y_end) * bytes_per_line; \
} \
break; \
case ROTATE_MASK_MIRROR_X | ROTATE_MASK_MIRROR_Y: \
for (int y = y_start; y < y_end; y++) \
{ \
uint32_t index = ((v_res - 1 - y) * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \
for (size_t x = x_start; x < x_end; x++) \
{ \
copy_pixel_##_bpp##bpp(to + index, from); \
index -= bytes_per_pixel; \
from += bytes_per_pixel; \
} \
} \
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
flush_ptr = fb + (v_res - y_end) * bytes_per_line; \
break; \
case ROTATE_MASK_SWAP_XY: \
for (int y = y_start; y < y_end; y++) \
{ \
for (int x = x_start; x < x_end; x++) \
{ \
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
uint32_t i = (x * h_res + y) * bytes_per_pixel; \
copy_pixel_##_bpp##bpp(to + i, from + j); \
} \
} \
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
flush_ptr = fb + x_start * bytes_per_line; \
break; \
case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_X: \
for (int y = y_start; y < y_end; y++) \
{ \
for (int x = x_start; x < x_end; x++) \
{ \
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
uint32_t i = (x * h_res + h_res - 1 - y) * bytes_per_pixel; \
copy_pixel_##_bpp##bpp(to + i, from + j); \
} \
} \
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
flush_ptr = fb + x_start * bytes_per_line; \
break; \
case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_Y: \
for (int y = y_start; y < y_end; y++) \
{ \
for (int x = x_start; x < x_end; x++) \
{ \
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
uint32_t i = ((v_res - 1 - x) * h_res + y) * bytes_per_pixel; \
copy_pixel_##_bpp##bpp(to + i, from + j); \
} \
} \
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
flush_ptr = fb + (v_res - x_end) * bytes_per_line; \
break; \
case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_X | ROTATE_MASK_MIRROR_Y: \
for (int y = y_start; y < y_end; y++) \
{ \
for (int x = x_start; x < x_end; x++) \
{ \
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
uint32_t i = ((v_res - 1 - x) * h_res + h_res - 1 - y) * bytes_per_pixel; \
copy_pixel_##_bpp##bpp(to + i, from + j); \
} \
} \
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
flush_ptr = fb + (v_res - x_end) * bytes_per_line; \
break; \
default: \
break; \
}
#ifdef __cplusplus
}
#endif

View File

@@ -9,7 +9,7 @@
extern "C" {
#endif
// FPS = 80000000/(40+140+40+800)/(4+16+16+1280) = 60Hz
// Refresh Rate = 80000000/(40+140+40+800)/(4+16+16+1280) = 60Hz
#define MIPI_DSI_DPI_CLK_MHZ 80
#define MIPI_DSI_LCD_H_RES 800
#define MIPI_DSI_LCD_V_RES 1280

View File

@@ -1,4 +1,4 @@
| Supported Targets | ESP32-S3 |
| ----------------- | -------- |
| Supported Targets | ESP32-P4 | ESP32-S3 |
| ----------------- | -------- | -------- |
This test app is used to test RGB565 interfaced LCDs.

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/

View File

@@ -11,6 +11,16 @@ extern "C" {
#define TEST_LCD_H_RES 800
#define TEST_LCD_V_RES 480
#define TEST_LCD_HSYNC 1
#define TEST_LCD_HBP 40
#define TEST_LCD_HFP 20
#define TEST_LCD_VSYNC 1
#define TEST_LCD_VBP 10
#define TEST_LCD_VFP 5
#define TEST_LCD_PIXEL_CLOCK_HZ (18 * 1000 * 1000)
#if CONFIG_IDF_TARGET_ESP32S3
#define TEST_LCD_VSYNC_GPIO 3
#define TEST_LCD_HSYNC_GPIO 46
@@ -34,7 +44,33 @@ extern "C" {
#define TEST_LCD_DATA15_GPIO 40 // R4
#define TEST_LCD_DISP_EN_GPIO -1
#define TEST_LCD_PIXEL_CLOCK_HZ (18 * 1000 * 1000)
#elif CONFIG_IDF_TARGET_ESP32P4
#define TEST_LCD_VSYNC_GPIO 41
#define TEST_LCD_HSYNC_GPIO 39
#define TEST_LCD_DE_GPIO 43
#define TEST_LCD_PCLK_GPIO 33
#define TEST_LCD_DATA0_GPIO 40 // B0
#define TEST_LCD_DATA1_GPIO 42 // B1
#define TEST_LCD_DATA2_GPIO 27 // B2
#define TEST_LCD_DATA3_GPIO 29 // B3
#define TEST_LCD_DATA4_GPIO 31 // B4
#define TEST_LCD_DATA5_GPIO 21 // G0
#define TEST_LCD_DATA6_GPIO 23 // G1
#define TEST_LCD_DATA7_GPIO 26 // G2
#define TEST_LCD_DATA8_GPIO 28 // G3
#define TEST_LCD_DATA9_GPIO 30 // G4
#define TEST_LCD_DATA10_GPIO 32 // G5
#define TEST_LCD_DATA11_GPIO 6 // R0
#define TEST_LCD_DATA12_GPIO 0 // R1
#define TEST_LCD_DATA13_GPIO 15 // R2
#define TEST_LCD_DATA14_GPIO 17 // R3
#define TEST_LCD_DATA15_GPIO 19 // R4
#define TEST_LCD_DISP_EN_GPIO -1
#else
#error "Unsupported target"
#endif
#ifdef __cplusplus
}

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -62,12 +62,12 @@ static esp_lcd_panel_handle_t test_rgb_panel_initialization(size_t data_width, s
.pclk_hz = TEST_LCD_PIXEL_CLOCK_HZ,
.h_res = TEST_LCD_H_RES,
.v_res = TEST_LCD_V_RES,
.hsync_back_porch = 68,
.hsync_front_porch = 20,
.hsync_pulse_width = 5,
.vsync_back_porch = 18,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
.hsync_back_porch = TEST_LCD_HBP,
.hsync_front_porch = TEST_LCD_HFP,
.hsync_pulse_width = TEST_LCD_HSYNC,
.vsync_back_porch = TEST_LCD_VBP,
.vsync_front_porch = TEST_LCD_VFP,
.vsync_pulse_width = TEST_LCD_VSYNC,
},
.flags.fb_in_psram = 1, // allocate frame buffer in PSRAM
.flags.refresh_on_demand = refresh_on_demand,
@@ -99,6 +99,7 @@ TEST_CASE("lcd_rgb_panel_stream_mode", "[lcd]")
int y_start = esp_random() % (TEST_LCD_V_RES - 100);
memset(img, color_byte, TEST_IMG_SIZE);
esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img);
vTaskDelay(pdMS_TO_TICKS(10));
}
printf("delete RGB panel\r\n");
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));

View File

@@ -1,6 +1,5 @@
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@@ -15,5 +14,19 @@ from pytest_embedded import Dut
],
indirect=True,
)
def test_rgb_lcd(dut: Dut) -> None:
def test_rgb_lcd_esp32s3(dut: Dut) -> None:
dut.run_all_single_board_cases()
@pytest.mark.esp32p4
@pytest.mark.generic
@pytest.mark.parametrize(
'config',
[
'iram_safe',
'release',
],
indirect=True,
)
def test_rgb_lcd_esp32p4(dut: Dut) -> None:
dut.run_all_single_board_cases()

View File

@@ -0,0 +1,5 @@
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
CONFIG_SPIRAM=y
CONFIG_SPIRAM_MODE_HEX=y
CONFIG_SPIRAM_SPEED_200M=y

View File

@@ -59,6 +59,10 @@ config SOC_LCDCAM_I80_LCD_SUPPORTED
bool
default y
config SOC_LCDCAM_RGB_LCD_SUPPORTED
bool
default y
config SOC_MIPI_CSI_SUPPORTED
bool
default y
@@ -1175,6 +1179,10 @@ config SOC_LCD_I80_SUPPORTED
bool
default y
config SOC_LCD_RGB_SUPPORTED
bool
default y
config SOC_LCDCAM_I80_NUM_BUSES
int
default 1
@@ -1187,6 +1195,14 @@ config SOC_LCDCAM_RGB_NUM_PANELS
int
default 1
config SOC_LCDCAM_RGB_DATA_WIDTH
int
default 24
config SOC_LCD_SUPPORT_RGB_YUV_CONV
bool
default y
config SOC_MCPWM_GROUPS
int
default 2

View File

@@ -29,9 +29,9 @@
#define SOC_GPTIMER_SUPPORTED 1
#define SOC_PCNT_SUPPORTED 1
#define SOC_LCDCAM_SUPPORTED 1
#define SOC_LCDCAM_CAM_SUPPORTED 1
#define SOC_LCDCAM_CAM_SUPPORTED 1 // support the camera driver based on the LCD_CAM peripheral
#define SOC_LCDCAM_I80_LCD_SUPPORTED 1 // support the Intel 8080 bus driver based on the LCD_CAM peripheral
// #define SOC_LCDCAM_RGB_LCD_SUPPORTED 1 // TODO: IDF-7465
#define SOC_LCDCAM_RGB_LCD_SUPPORTED 1 // support the RGB LCD driver based on the LCD_CAM peripheral
#define SOC_MIPI_CSI_SUPPORTED 1
#define SOC_MIPI_DSI_SUPPORTED 1
#define SOC_MCPWM_SUPPORTED 1
@@ -428,11 +428,12 @@
/*-------------------------- LCD CAPS ----------------------------------------*/
/* I80 bus and RGB timing generator can't work at the same time in the LCD_CAM peripheral */
#define SOC_LCD_I80_SUPPORTED 1 /*!< support intel 8080 driver */
#define SOC_LCD_RGB_SUPPORTED 1 /*!< RGB LCD is supported */
#define SOC_LCDCAM_I80_NUM_BUSES 1U /*!< LCD_CAM peripheral provides one LCD Intel 8080 bus */
#define SOC_LCDCAM_I80_BUS_WIDTH 24 /*!< Intel 8080 bus max data width */
#define SOC_LCDCAM_RGB_NUM_PANELS 1U /*!< Support one RGB LCD panel */
// #define SOC_LCD_RGB_DATA_WIDTH 24 /*!< Number of LCD data lines */
// #define SOC_LCD_SUPPORT_RGB_YUV_CONV 1 /*!< Support color format conversion between RGB and YUV */
#define SOC_LCDCAM_RGB_DATA_WIDTH 24 /*!< Number of LCD data lines */
#define SOC_LCD_SUPPORT_RGB_YUV_CONV 1 /*!< Support color format conversion between RGB and YUV */
/*-------------------------- MCPWM CAPS --------------------------------------*/
#define SOC_MCPWM_GROUPS (2U) ///< 2 MCPWM groups on the chip (i.e., the number of independent MCPWM peripherals)