rgb_lcd: support double buffer

also cleaned up the bounce buffer code
This commit is contained in:
morris 2022-07-06 17:01:06 +08:00
parent 9ea3f6f3e7
commit a33a183365
4 changed files with 353 additions and 303 deletions

View File

@ -18,12 +18,20 @@ menu "LCD and Touch Panel"
config LCD_RGB_ISR_IRAM_SAFE
bool "RGB LCD ISR IRAM-Safe"
default n
select GDMA_CTRL_FUNC_IN_IRAM # need to restart GDMA in the LCD ISR
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 "Restart transmission in VSYNC"
default n
select GDMA_CTRL_FUNC_IN_IRAM # need to restart GDMA in the LCD ISR
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
endmenu
endmenu

View File

@ -5,6 +5,7 @@
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "esp_lcd_types.h"
@ -51,22 +52,22 @@ extern "C" {
* @endverbatim
*/
typedef struct {
unsigned int pclk_hz; /*!< Frequency of pixel clock */
unsigned int h_res; /*!< Horizontal resolution, i.e. the number of pixels in a line */
unsigned int v_res; /*!< Vertical resolution, i.e. the number of lines in the frame */
unsigned int hsync_pulse_width; /*!< Horizontal sync width, unit: PCLK period */
unsigned int hsync_back_porch; /*!< Horizontal back porch, number of PCLK between hsync and start of line active data */
unsigned int hsync_front_porch; /*!< Horizontal front porch, number of PCLK between the end of active data and the next hsync */
unsigned int vsync_pulse_width; /*!< Vertical sync width, unit: number of lines */
unsigned int vsync_back_porch; /*!< Vertical back porch, number of invalid lines between vsync and start of frame */
unsigned int vsync_front_porch; /*!< Vertical front porch, number of invalid lines between the end of frame and the next vsync */
uint32_t pclk_hz; /*!< Frequency of pixel clock */
uint32_t h_res; /*!< Horizontal resolution, i.e. the number of pixels in a line */
uint32_t v_res; /*!< Vertical resolution, i.e. the number of lines in the frame */
uint32_t hsync_pulse_width; /*!< Horizontal sync width, unit: PCLK period */
uint32_t hsync_back_porch; /*!< Horizontal back porch, number of PCLK between hsync and start of line active data */
uint32_t hsync_front_porch; /*!< Horizontal front porch, number of PCLK between the end of active data and the next hsync */
uint32_t vsync_pulse_width; /*!< Vertical sync width, unit: number of lines */
uint32_t vsync_back_porch; /*!< Vertical back porch, number of invalid lines between vsync and start of frame */
uint32_t vsync_front_porch; /*!< Vertical front porch, number of invalid lines between the end of frame and the next vsync */
struct {
unsigned int hsync_idle_low: 1; /*!< The hsync signal is low in IDLE state */
unsigned int vsync_idle_low: 1; /*!< The vsync signal is low in IDLE state */
unsigned int de_idle_high: 1; /*!< The de signal is high in IDLE state */
unsigned int pclk_active_neg: 1; /*!< Whether the display data is clocked out on the falling edge of PCLK */
unsigned int pclk_idle_high: 1; /*!< The PCLK stays at high level in IDLE phase */
} flags; /*!< LCD RGB timing flags */
uint32_t hsync_idle_low: 1; /*!< The hsync signal is low in IDLE state */
uint32_t vsync_idle_low: 1; /*!< The vsync signal is low in IDLE state */
uint32_t de_idle_high: 1; /*!< The de signal is high in IDLE state */
uint32_t pclk_active_neg: 1; /*!< Whether the display data is clocked out on the falling edge of PCLK */
uint32_t pclk_idle_high: 1; /*!< The PCLK stays at high level in IDLE phase */
} flags; /*!< LCD RGB timing flags */
} esp_lcd_rgb_timing_t;
/**
@ -112,37 +113,43 @@ typedef struct {
*/
typedef struct {
lcd_clock_source_t clk_src; /*!< Clock source for the RGB LCD peripheral */
esp_lcd_rgb_timing_t timings; /*!< RGB timing parameters */
esp_lcd_rgb_timing_t timings; /*!< RGB timing parameters, including the screen resolution */
size_t data_width; /*!< Number of data lines */
size_t sram_trans_align; /*!< Alignment for framebuffer that allocated in SRAM */
size_t psram_trans_align; /*!< Alignment for framebuffer that allocated in PSRAM */
size_t bits_per_pixel; /*!< Color depth, in bpp, specially, if set to zero, it will default to `data_width`.
When using a Serial RGB interface, this value could be different from `data_width` */
size_t bounce_buffer_size_px; /*!< If it's non-zero, the driver allocates two DRAM bounce buffers for DMA use.
DMA fetching from DRAM bounce buffer is much faster than PSRAM frame buffer. */
size_t sram_trans_align; /*!< Alignment of buffers (frame buffer or bounce buffer) that allocated in SRAM */
size_t psram_trans_align; /*!< Alignment of buffers (frame buffer) that allocated in PSRAM */
int hsync_gpio_num; /*!< GPIO used for HSYNC signal */
int vsync_gpio_num; /*!< GPIO used for VSYNC signal */
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 */
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 disp_gpio_num; /*!< GPIO used for display control signal, set to -1 if it's not used */
int bounce_buffer_size_px; /*!< If not-zero, the driver uses a bounce buffer in internal memory to DMA from. Value is in pixels. */
struct {
unsigned int disp_active_low: 1; /*!< If this flag is enabled, a low level of display control signal can turn the screen on; vice versa */
unsigned int fb_in_psram: 1; /*!< If this flag is enabled, the frame buffer will be allocated from PSRAM preferentially */
unsigned int bb_do_cache_invalidate:1; /*!< If this flag is enabled, in bounceback mode we'll do a cache invalidate on the read data, freeing the cache. Can be dangerous if data is written from other core. */
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 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 */
uint32_t no_fb: 1; /*!< If this flag is enabled, the driver won't allocate frame buffer.
Instead, user should fill in the bounce buffer manually in the `on_bounce_empty` callback */
uint32_t bb_invalidate_cache: 1; /*!< If this flag is enabled, in bounce back mode we'll do a cache invalidate on the read data, freeing the cache.
Can be dangerous if data is written from other core(s). */
} flags; /*!< LCD RGB panel configuration flags */
} esp_lcd_rgb_panel_config_t;
/**
* @brief Create RGB LCD panel
*
* @param rgb_panel_config RGB panel configuration
* @param ret_panel Returned LCD panel handle
* @param[in] rgb_panel_config RGB panel configuration
* @param[out] ret_panel Returned LCD panel handle
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_NO_MEM if out of memory
* - ESP_ERR_NOT_FOUND if no free RGB panel is available
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG: Create RGB LCD panel failed because of invalid argument
* - ESP_ERR_NO_MEM: Create RGB LCD panel failed because of out of memory
* - ESP_ERR_NOT_FOUND: Create RGB LCD panel failed because some mandatory hardware resources are not found
* - ESP_OK: Create RGB LCD panel successfully
*/
esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_config, esp_lcd_panel_handle_t *ret_panel);
@ -163,13 +170,18 @@ esp_err_t esp_lcd_rgb_panel_register_event_callbacks(esp_lcd_panel_handle_t pane
* @brief Set frequency of PCLK for RGB LCD panel
*
* @note The PCLK frequency is set in the `esp_lcd_rgb_timing_t` and gets configured during LCD panel initialization.
* Usually you don't need to call this function to set the PCLK again, but in some cases, you may need to change the PCLK frequency.
* e.g. to slow down the PCLK frequency to reduce power consumption or to reduce the memory throughput.
* Usually you don't need to call this function to set the PCLK again, but in some cases, you might want to change the PCLK frequency.
* e.g. slow down the PCLK frequency to reduce power consumption or to reduce the memory throughput during OTA.
* @note This function doesn't cause the hardware to update the PCLK immediately but to record the new frequency and set a flag internally.
* Next time when start a new transaction, the driver will update the PCLK automatically.
* Only in the next VSYNC event handler, will the driver attempt to update the PCLK frequency.
*
* @param panel LCD panel handle, returned from `esp_lcd_new_rgb_panel()`
* @param freq_hz Frequency of pixel clock, in Hz
* @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel()`
* @param[in] freq_hz Frequency of pixel clock, in Hz
* @return
* - ESP_ERR_INVALID_ARG: Set PCLK frequency failed because of invalid argument
* - ESP_OK: Set PCLK frequency successfully
*/
esp_err_t esp_lcd_rgb_panel_set_pclk(esp_lcd_panel_handle_t panel, uint32_t freq_hz);
/**
* @brief Get the address of the frame buffer(s) that allocated by the driver

View File

@ -3,4 +3,3 @@ archive: libesp_lcd.a
entries:
if LCD_RGB_ISR_IRAM_SAFE = y:
esp_lcd_common: lcd_com_mount_dma_data (noflash)
esp_lcd_rgb_panel: lcd_rgb_panel_start_transmission (noflash)

View File

@ -39,6 +39,7 @@
#include "soc/lcd_periph.h"
#include "hal/lcd_hal.h"
#include "hal/lcd_ll.h"
#include "hal/gdma_ll.h"
#include "rom/cache.h"
#if CONFIG_LCD_RGB_ISR_IRAM_SAFE
@ -78,16 +79,16 @@ struct esp_rgb_panel_t {
intr_handle_t intr; // LCD peripheral interrupt handle
esp_pm_lock_handle_t pm_lock; // Power management lock
size_t num_dma_nodes; // Number of DMA descriptors that used to carry the frame buffer
uint8_t *fb; // Frame buffer
uint8_t *fbs[2]; // Frame buffers
uint8_t cur_fb_index; // Current frame buffer index (0 or 1)
size_t fb_size; // Size of frame buffer
int data_gpio_nums[SOC_LCD_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)
size_t bb_size; // If not-zero, the driver uses two bounce buffers allocated from internal memory
int bounce_pos_px; // Position in whatever source material is used for the bounce buffer, in pixels
uint8_t *bounce_buffer[2]; // Pointer to the bounce buffers
gdma_channel_handle_t dma_chan; // DMA channel handle
int bounce_buffer_size_bytes; //If not-zero, the driver uses a bounce buffer in internal memory to DMA from. It's in bytes here.
uint8_t *bounce_buffer[2]; //Pointer to the bounce buffers
int bounce_buf_frame_start; //If frame restarts, which bb has the initial frame data?
int bounce_pos_px; // Position in whatever source material is used for the bounce buffer, in pixels
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
@ -95,18 +96,94 @@ struct esp_rgb_panel_t {
int y_gap; // Extra gap in y coordinate, it's used when calculate the flush window
portMUX_TYPE spinlock; // to protect panel specific resource from concurrent access (e.g. between task and ISR)
struct {
unsigned int disp_en_level: 1; // The level which can turn on the screen by `disp_gpio_num`
unsigned int stream_mode: 1; // If set, the LCD transfers data continuously, otherwise, it stops refreshing the LCD when transaction done
unsigned int fb_in_psram: 1; // Whether the frame buffer is in PSRAM
unsigned int need_update_pclk: 1; // Whether to update the PCLK before start a new transaction
unsigned int bb_do_cache_invalidate: 1; //If we do cache invalidate in bb psram fb mode
uint32_t disp_en_level: 1; // The level which can turn on the screen by `disp_gpio_num`
uint32_t stream_mode: 1; // If set, the LCD transfers data continuously, otherwise, it stops refreshing the LCD when transaction done
uint32_t no_fb: 1; // No frame buffer allocated in the driver
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 bb_invalidate_cache: 1; // Whether to do cache invalidation in bounce buffer mode
} flags;
dma_descriptor_t dma_restart_node; //DMA descriptor used to restart the transfer
dma_descriptor_t dma_nodes[]; // DMA descriptor pool of size `num_dma_nodes`
dma_descriptor_t *dma_links[2]; // fbs[0] <-> dma_links[0], fbs[1] <-> dma_links[1]
dma_descriptor_t dma_restart_node; // DMA descriptor used to restart the transfer
dma_descriptor_t dma_nodes[]; // DMA descriptors pool
};
static esp_err_t lcd_rgb_panel_alloc_frame_buffers(const esp_lcd_rgb_panel_config_t *rgb_panel_config, esp_rgb_panel_t *rgb_panel)
{
bool fb_in_psram = false;
size_t psram_trans_align = rgb_panel_config->psram_trans_align ? rgb_panel_config->psram_trans_align : 64;
size_t sram_trans_align = rgb_panel_config->sram_trans_align ? rgb_panel_config->sram_trans_align : 4;
rgb_panel->psram_trans_align = psram_trans_align;
rgb_panel->sram_trans_align = sram_trans_align;
// alloc frame buffer
if (!rgb_panel_config->flags.no_fb) {
// fb_in_psram is only an option, if there's no PSRAM on board, we fallback to alloc from SRAM
if (rgb_panel_config->flags.fb_in_psram) {
#if CONFIG_SPIRAM_USE_MALLOC || CONFIG_SPIRAM_USE_CAPS_ALLOC
if (esp_psram_is_initialized()) {
fb_in_psram = true;
}
#endif
}
for (int i = 0; i < (rgb_panel_config->flags.double_fb ? 2 : 1); i++) {
if (fb_in_psram) {
// the low level malloc function will help check the validation of alignment
rgb_panel->fbs[i] = heap_caps_aligned_calloc(psram_trans_align, 1, rgb_panel->fb_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
} else {
rgb_panel->fbs[i] = heap_caps_aligned_calloc(sram_trans_align, 1, rgb_panel->fb_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
}
ESP_RETURN_ON_FALSE(rgb_panel->fbs[i], ESP_ERR_NO_MEM, TAG, "no mem for frame buffer");
}
}
// alloc bounce buffer
if (rgb_panel->bb_size) {
for (int i = 0; i < 2; i++) {
// bounce buffer must come from SRAM
rgb_panel->bounce_buffer[i] = heap_caps_aligned_calloc(sram_trans_align, 1, rgb_panel->bb_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
ESP_RETURN_ON_FALSE(rgb_panel->bounce_buffer[i], ESP_ERR_NO_MEM, TAG, "no mem for bounce buffer");
}
}
rgb_panel->cur_fb_index = 0;
rgb_panel->flags.fb_in_psram = fb_in_psram;
return ESP_OK;
}
static esp_err_t lcd_rgb_panel_destory(esp_rgb_panel_t *rgb_panel)
{
lcd_ll_enable_clock(rgb_panel->hal.dev, false);
if (rgb_panel->panel_id >= 0) {
periph_module_disable(lcd_periph_signals.panels[rgb_panel->panel_id].module);
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id);
}
if (rgb_panel->fbs[0]) {
free(rgb_panel->fbs[0]);
}
if (rgb_panel->fbs[1]) {
free(rgb_panel->fbs[1]);
}
if (rgb_panel->bounce_buffer[0]) {
free(rgb_panel->bounce_buffer[0]);
}
if (rgb_panel->bounce_buffer[1]) {
free(rgb_panel->bounce_buffer[1]);
}
if (rgb_panel->dma_chan) {
gdma_disconnect(rgb_panel->dma_chan);
gdma_del_channel(rgb_panel->dma_chan);
}
if (rgb_panel->intr) {
esp_intr_free(rgb_panel->intr);
}
if (rgb_panel->pm_lock) {
esp_pm_lock_release(rgb_panel->pm_lock);
esp_pm_lock_delete(rgb_panel->pm_lock);
}
free(rgb_panel);
return ESP_OK;
}
esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_config, esp_lcd_panel_handle_t *ret_panel)
{
@ -116,109 +193,65 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
esp_err_t ret = ESP_OK;
esp_rgb_panel_t *rgb_panel = NULL;
ESP_GOTO_ON_FALSE(rgb_panel_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid parameter");
ESP_GOTO_ON_FALSE(rgb_panel_config->data_width == 16, ESP_ERR_NOT_SUPPORTED, err, TAG,
"unsupported data width %d", rgb_panel_config->data_width);
ESP_GOTO_ON_FALSE(!(rgb_panel_config->bounce_buffer_size_px == 0 && rgb_panel_config->on_bounce_empty != NULL),
ESP_ERR_INVALID_ARG, err, TAG, "cannot have bounce empty callback without having a bounce buffer");
ESP_GOTO_ON_FALSE(!(rgb_panel_config->bounce_buffer_size_px != 0 && rgb_panel_config->on_bounce_empty == NULL &&
!rgb_panel_config->flags.fb_in_psram), ESP_ERR_INVALID_ARG, err, TAG,
"bounce buffer without callback and main fb not in psram does not make sense");
ESP_GOTO_ON_FALSE(rgb_panel_config->data_width == 16 || rgb_panel_config->data_width == 8,
ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported data width %d", rgb_panel_config->data_width);
ESP_GOTO_ON_FALSE(!(rgb_panel_config->flags.double_fb && rgb_panel_config->flags.no_fb),
ESP_ERR_INVALID_ARG, err, TAG, "invalid frame buffer number");
ESP_GOTO_ON_FALSE(!(rgb_panel_config->flags.no_fb && rgb_panel_config->bounce_buffer_size_px == 0),
ESP_ERR_INVALID_ARG, err, TAG, "must set bounce buffer if there's no frame buffer");
ESP_GOTO_ON_FALSE(!(rgb_panel_config->flags.refresh_on_demand && rgb_panel_config->bounce_buffer_size_px),
ESP_ERR_INVALID_ARG, err, TAG, "refresh on demand is not supported under bounce buffer mode");
#if CONFIG_LCD_RGB_ISR_IRAM_SAFE
if (rgb_panel_config->on_frame_trans_done) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(rgb_panel_config->on_frame_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_frame_trans_done callback not in IRAM");
ESP_GOTO_ON_FALSE(rgb_panel_config->bounce_buffer_size_px == 0,
ESP_ERR_INVALID_ARG, err, TAG, "bounce buffer mode is not IRAM Safe");
#endif
// bpp defaults to the number of data lines, but for serial RGB interface, they're not equal
size_t bits_per_pixel = rgb_panel_config->data_width;
if (rgb_panel_config->bits_per_pixel) { // override bpp if it's set
bits_per_pixel = rgb_panel_config->bits_per_pixel;
}
if (rgb_panel_config->user_ctx) {
ESP_RETURN_ON_FALSE(esp_ptr_internal(rgb_panel_config->user_ctx), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
// calculate buffer size
size_t fb_size = rgb_panel_config->timings.h_res * rgb_panel_config->timings.v_res * bits_per_pixel / 8;
size_t bb_size = rgb_panel_config->bounce_buffer_size_px * bits_per_pixel / 8;
if (bb_size) {
// we want the bounce can always end in the second buffer
ESP_GOTO_ON_FALSE(fb_size % (2 * bb_size) == 0, ESP_ERR_INVALID_ARG, err, TAG,
"fb size must be even multiple of bounce buffer size");
}
#endif
// calculate the number of DMA descriptors
size_t fb_size = rgb_panel_config->timings.h_res * rgb_panel_config->timings.v_res * rgb_panel_config->data_width / 8;
size_t num_dma_nodes;
int bounce_bytes = 0;
if (rgb_panel_config->bounce_buffer_size_px == 0) {
// No bounce buffers. DMA descriptors need to fit entire framebuffer
num_dma_nodes = (fb_size + DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1) / DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
size_t num_dma_nodes = 0;
if (bb_size) {
// in bounce buffer mode, DMA is used to convey the bounce buffer, not the frame buffer.
// frame buffer is copied to bounce buffer by CPU
num_dma_nodes = (bb_size + DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1) / DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
} else {
//The FB needs to be an integer multiple of the size of a bounce buffer (so
//we end on the end of the second bounce buffer). Adjust the size of the
//bounce buffers if it is not.
int pixel_data_bytes = rgb_panel_config->data_width / 8; //size of one pixel, in bytes
int no_pixels = rgb_panel_config->timings.h_res * rgb_panel_config->timings.v_res;
bounce_bytes = rgb_panel_config->bounce_buffer_size_px * pixel_data_bytes;
if (no_pixels % (rgb_panel_config->bounce_buffer_size_px * pixel_data_bytes)) {
//Search for some value that does work. Yes, this is a stupidly simple algo, but it only
//needs to run on startup.
for (int a=rgb_panel_config->bounce_buffer_size_px; a>0; a--) {
if ((no_pixels % (a * pixel_data_bytes))==0) {
bounce_bytes = a * pixel_data_bytes;
ESP_LOGW(TAG, "Frame buffer is not an integer multiple of bounce buffers.");
ESP_LOGW(TAG, "Adjusted bounce buffer size from %d to %d pixels to fix this.",
rgb_panel_config->bounce_buffer_size_px, bounce_bytes/pixel_data_bytes);
break;
}
}
}
// DMA descriptors need to fit both bounce buffers
num_dma_nodes = (bounce_bytes + DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1) / DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
num_dma_nodes = num_dma_nodes * 2; //as we have two bounce buffers
// Not bounce buffer mode, DMA descriptors need to fit the entire frame buffer
num_dma_nodes = (fb_size + DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1) / DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
}
// DMA descriptors must be placed in internal SRAM (requested by DMA)
rgb_panel = heap_caps_calloc(1, sizeof(esp_rgb_panel_t) + num_dma_nodes * sizeof(dma_descriptor_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
// multiply 2 because of double frame buffer mode (two frame buffer) and bounce buffer mode (two bounce buffer)
rgb_panel = heap_caps_calloc(1, sizeof(esp_rgb_panel_t) + num_dma_nodes * sizeof(dma_descriptor_t) * 2,
MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
ESP_GOTO_ON_FALSE(rgb_panel, ESP_ERR_NO_MEM, err, TAG, "no mem for rgb panel");
rgb_panel->num_dma_nodes = num_dma_nodes;
rgb_panel->fb_size = fb_size;
rgb_panel->bb_size = bb_size;
rgb_panel->panel_id = -1;
// register to platform
int panel_id = lcd_com_register_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel);
ESP_GOTO_ON_FALSE(panel_id >= 0, ESP_ERR_NOT_FOUND, err, TAG, "no free rgb panel slot");
rgb_panel->panel_id = panel_id;
rgb_panel->bounce_buffer_size_bytes = bounce_bytes;
// enable APB to access LCD registers
periph_module_enable(lcd_periph_signals.panels[panel_id].module);
periph_module_reset(lcd_periph_signals.panels[panel_id].module);
// alloc frame buffer
bool alloc_from_psram = false;
// fb_in_psram is only an option, if there's no PSRAM on board, we still alloc from SRAM
if (rgb_panel_config->flags.fb_in_psram) {
#if CONFIG_SPIRAM_USE_MALLOC || CONFIG_SPIRAM_USE_CAPS_ALLOC
if (esp_psram_is_initialized()) {
alloc_from_psram = true;
}
#endif
}
size_t psram_trans_align = rgb_panel_config->psram_trans_align ? rgb_panel_config->psram_trans_align : 64;
size_t sram_trans_align = rgb_panel_config->sram_trans_align ? rgb_panel_config->sram_trans_align : 4;
rgb_panel->fb_size = fb_size;
if (!rgb_panel_config->on_bounce_empty) {
//We need to allocate a framebuffer.
if (alloc_from_psram) {
// the low level malloc function will help check the validation of alignment
rgb_panel->fb = heap_caps_aligned_calloc(psram_trans_align, 1, fb_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
} else {
rgb_panel->fb = heap_caps_aligned_calloc(sram_trans_align, 1, fb_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
}
ESP_GOTO_ON_FALSE(rgb_panel->fb, ESP_ERR_NO_MEM, err, TAG, "no mem for frame buffer");
rgb_panel->psram_trans_align = psram_trans_align;
rgb_panel->sram_trans_align = sram_trans_align;
rgb_panel->flags.fb_in_psram = alloc_from_psram;
}
if (rgb_panel->bounce_buffer_size_bytes) {
//We need to allocate bounce buffers.
rgb_panel->bounce_buffer[0] = heap_caps_aligned_calloc(sram_trans_align, 1,
rgb_panel->bounce_buffer_size_bytes, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
rgb_panel->bounce_buffer[1] = heap_caps_aligned_calloc(sram_trans_align, 1,
rgb_panel->bounce_buffer_size_bytes, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
}
if (rgb_panel_config->on_bounce_empty) {
rgb_panel->on_bounce_empty = rgb_panel_config->on_bounce_empty;
rgb_panel->bounce_buffer_cb_user_ctx = rgb_panel_config->bounce_buffer_cb_user_ctx;
}
// allocate frame buffers + bounce buffers
ESP_GOTO_ON_ERROR(lcd_rgb_panel_alloc_frame_buffers(rgb_panel_config, rgb_panel), err, TAG, "alloc frame buffers failed");
// initialize HAL layer, so we can call LL APIs later
lcd_hal_init(&rgb_panel->hal, panel_id);
// enable clock gating
@ -249,9 +282,8 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
rgb_panel->bits_per_pixel = bits_per_pixel;
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_do_cache_invalidate = rgb_panel_config->flags.bb_do_cache_invalidate;
rgb_panel->on_frame_trans_done = rgb_panel_config->on_frame_trans_done;
rgb_panel->user_ctx = rgb_panel_config->user_ctx;
rgb_panel->flags.no_fb = rgb_panel_config->flags.no_fb;
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;
@ -265,35 +297,18 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
rgb_panel->base.set_gap = rgb_panel_set_gap;
// return base class
*ret_panel = &(rgb_panel->base);
ESP_LOGD(TAG, "new rgb panel(%d) @%p, fb @%p, size=%zu", rgb_panel->panel_id, rgb_panel, rgb_panel->fb, rgb_panel->fb_size);
ESP_LOGD(TAG, "new rgb panel(%d) @%p, fb0 @%p, fb1 @%p, fb_size=%zu, bb0 @%p, bb1 @%p, bb_size=%zu",
rgb_panel->panel_id, rgb_panel, rgb_panel->fbs[0], rgb_panel->fbs[1], rgb_panel->fb_size,
rgb_panel->bounce_buffer[0], rgb_panel->bounce_buffer[1], rgb_panel->bb_size);
return ESP_OK;
err:
if (rgb_panel) {
if (rgb_panel->panel_id >= 0) {
periph_module_disable(lcd_periph_signals.panels[rgb_panel->panel_id].module);
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id);
}
if (rgb_panel->fb) {
free(rgb_panel->fb);
}
if (rgb_panel->dma_chan) {
gdma_disconnect(rgb_panel->dma_chan);
gdma_del_channel(rgb_panel->dma_chan);
}
if (rgb_panel->intr) {
esp_intr_free(rgb_panel->intr);
}
if (rgb_panel->pm_lock) {
esp_pm_lock_release(rgb_panel->pm_lock);
esp_pm_lock_delete(rgb_panel->pm_lock);
}
free(rgb_panel);
lcd_rgb_panel_destory(rgb_panel);
}
return ret;
}
esp_err_t esp_rgb_panel_set_pclk(esp_lcd_panel_handle_t panel, uint32_t freq_hz)
esp_err_t esp_lcd_rgb_panel_register_event_callbacks(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_callbacks_t *callbacks, void *user_ctx)
{
ESP_RETURN_ON_FALSE(panel && callbacks, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
@ -315,10 +330,11 @@ esp_err_t esp_lcd_rgb_panel_register_event_callbacks(esp_lcd_panel_handle_t pane
return ESP_OK;
}
esp_err_t esp_lcd_rgb_panel_set_pclk(esp_lcd_panel_handle_t panel, uint32_t freq_hz)
{
ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
// the pclk frequency will be updated in `lcd_rgb_panel_start_transmission()`
// the pclk frequency will be updated in the `LCD_LL_EVENT_VSYNC_END` event handler
portENTER_CRITICAL(&rgb_panel->spinlock);
rgb_panel->flags.need_update_pclk = true;
rgb_panel->timings.pclk_hz = freq_hz;
@ -357,20 +373,7 @@ static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel)
{
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
int panel_id = rgb_panel->panel_id;
gdma_disconnect(rgb_panel->dma_chan);
gdma_del_channel(rgb_panel->dma_chan);
esp_intr_free(rgb_panel->intr);
lcd_ll_enable_clock(rgb_panel->hal.dev, false);
periph_module_disable(lcd_periph_signals.panels[panel_id].module);
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id);
free(rgb_panel->bounce_buffer[0]);
free(rgb_panel->bounce_buffer[1]);
free(rgb_panel->fb);
if (rgb_panel->pm_lock) {
esp_pm_lock_release(rgb_panel->pm_lock);
esp_pm_lock_delete(rgb_panel->pm_lock);
}
free(rgb_panel);
ESP_RETURN_ON_ERROR(lcd_rgb_panel_destory(rgb_panel), TAG, "destroy rgb panel(%d) failed", panel_id);
ESP_LOGD(TAG, "del rgb panel(%d)", panel_id);
return ESP_OK;
}
@ -404,17 +407,17 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
// configure blank region timing
lcd_ll_set_blank_cycles(rgb_panel->hal.dev, 1, 1); // RGB panel always has a front and back blank (porch region)
lcd_ll_set_horizontal_timing(rgb_panel->hal.dev, rgb_panel->timings.hsync_pulse_width,
rgb_panel->timings.hsync_back_porch, rgb_panel->timings.h_res,
rgb_panel->timings.hsync_back_porch, rgb_panel->timings.h_res * rgb_panel->bits_per_pixel / rgb_panel->data_width,
rgb_panel->timings.hsync_front_porch);
lcd_ll_set_vertical_timing(rgb_panel->hal.dev, rgb_panel->timings.vsync_pulse_width,
rgb_panel->timings.vsync_back_porch, rgb_panel->timings.v_res,
rgb_panel->timings.vsync_front_porch);
// output hsync even in porch region
lcd_ll_enable_output_hsync_in_porch_region(rgb_panel->hal.dev, true);
// generate the hsync at the very begining of line
// generate the hsync at the very beginning of line
lcd_ll_set_hsync_position(rgb_panel->hal.dev, 0);
// restart flush by hardware has some limitation, instead, the driver will restart the flush in the VSYNC end interrupt by software
lcd_ll_enable_auto_next_frame(rgb_panel->hal.dev, false);
// send next frame automatically in stream mode
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);
// enable intr
@ -430,12 +433,20 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
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);
if (rgb_panel->fb == NULL) {
//Can't draw a bitmap to a non-existing framebuffer.
//This happens when e.g. we use an external callback to refill the bounce buffers.
return ESP_ERR_NOT_SUPPORTED;
}
ESP_RETURN_ON_FALSE(!rgb_panel->flags.no_fb, ESP_ERR_NOT_SUPPORTED, TAG, "no frame buffer installed");
assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position");
// 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 {
// we do the copy only if the color_data is different from either frame buffer
do_copy = true;
}
// adjust the flush window by adding extra gap
x_start += rgb_panel->x_gap;
y_start += rgb_panel->y_gap;
@ -447,29 +458,36 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
y_start = MIN(y_start, rgb_panel->timings.v_res);
y_end = MIN(y_end, rgb_panel->timings.v_res);
// convert the frame buffer to 3D array
int bytes_per_pixel = rgb_panel->data_width / 8;
int bytes_per_pixel = rgb_panel->bits_per_pixel / 8;
int pixels_per_line = rgb_panel->timings.h_res;
uint32_t bytes_per_line = bytes_per_pixel * pixels_per_line;
const uint8_t *from = (const uint8_t *)color_data;
// manipulate the frame buffer
uint32_t copy_bytes_per_line = (x_end - x_start) * bytes_per_pixel;
uint8_t *to = rgb_panel->fb + (y_start * pixels_per_line + 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;
}
if (rgb_panel->flags.fb_in_psram && !rgb_panel->bounce_buffer_size_bytes) {
// CPU writes data to PSRAM through DCache, data in PSRAM might not get updated, so write back
// Note that if we use a bounce buffer, the data gets read by the CPU as well so no need to write back.
uint32_t bytes_to_flush = (y_end - y_start) * bytes_per_line;
Cache_WriteBack_Addr((uint32_t)(rgb_panel->fb + y_start * bytes_per_line), bytes_to_flush);
uint8_t *fb = rgb_panel->fbs[rgb_panel->cur_fb_index];
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;
uint8_t *to = fb + (y_start * pixels_per_line + 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;
}
}
// restart the new transmission
if (!rgb_panel->flags.stream_mode) {
lcd_rgb_panel_start_transmission(rgb_panel);
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
// Note that if we use a bounce buffer, the data gets read by the CPU as well so no need to write back
uint32_t bytes_to_flush = (y_end - y_start) * bytes_per_line;
Cache_WriteBack_Addr((uint32_t)(fb + y_start * bytes_per_line), bytes_to_flush);
}
if (!rgb_panel->bb_size) {
if (rgb_panel->flags.stream_mode) {
// the DMA will convey the new frame buffer next time
rgb_panel->dma_nodes[rgb_panel->num_dma_nodes - 1].next = rgb_panel->dma_links[rgb_panel->cur_fb_index];
rgb_panel->dma_nodes[rgb_panel->num_dma_nodes * 2 - 1].next = rgb_panel->dma_links[rgb_panel->cur_fb_index];
}
}
return ESP_OK;
@ -501,7 +519,7 @@ static esp_err_t rgb_panel_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
{
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
rgb_panel->x_gap = x_gap;
rgb_panel->x_gap = y_gap;
rgb_panel->y_gap = y_gap;
return ESP_OK;
}
@ -601,173 +619,181 @@ static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *panel, lcd_cloc
static IRAM_ATTR bool lcd_rgb_panel_fill_bounce_buffer(esp_rgb_panel_t *panel, uint8_t *buffer)
{
bool need_yield = false;
int bytes_per_pixel = panel->data_width / 8;
if (panel->on_bounce_empty) {
//We don't have a framebuffer here; we need to call a callback to refill the bounce buffer
//for us.
need_yield=panel->on_bounce_empty((void*)buffer, panel->bounce_pos_px,
panel->bounce_buffer_size_bytes, panel->bounce_buffer_cb_user_ctx);
int bytes_per_pixel = panel->bits_per_pixel / 8;
if (panel->flags.no_fb) {
if (panel->on_bounce_empty) {
// We don't have a frame buffer here; we need to call a callback to refill the bounce buffer
need_yield = panel->on_bounce_empty(&panel->base, buffer, panel->bounce_pos_px, panel->bb_size, panel->user_ctx);
}
} else {
//We do have a framebuffer; copy from there.
memcpy(buffer, &panel->fb[panel->bounce_pos_px * bytes_per_pixel], panel->bounce_buffer_size_bytes);
if (panel->flags.bb_do_cache_invalidate) {
//We don't need the bytes we copied from psram anymore.
//Make sure that if anything happened to have changed (because the line already was in cache) we write
//the data back.
Cache_WriteBack_Addr((uint32_t)&panel->fb[panel->bounce_pos_px * bytes_per_pixel], panel->bounce_buffer_size_bytes);
//Invalidate the data. Note: possible race: perhaps something on the other core can squeeze a write
//between this and the writeback, in which case that data gets discarded.
Cache_Invalidate_Addr((uint32_t)&panel->fb[panel->bounce_pos_px * bytes_per_pixel], panel->bounce_buffer_size_bytes);
// We do have frame buffer; copy from there.
// Note: if the cache is diabled, and accessing the PSRAM by DCACHE will crash.
memcpy(buffer, &panel->fbs[panel->cur_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.
Cache_WriteBack_Addr((uint32_t)&panel->fbs[panel->cur_fb_index][panel->bounce_pos_px * bytes_per_pixel], panel->bb_size);
// Invalidate the data.
// Note: possible race: perhaps something on the other core can squeeze a write between this and the writeback,
// in which case that data gets discarded.
Cache_Invalidate_Addr((uint32_t)&panel->fbs[panel->cur_fb_index][panel->bounce_pos_px * bytes_per_pixel], panel->bb_size);
}
}
panel->bounce_pos_px+=panel->bounce_buffer_size_bytes / bytes_per_pixel;
//If the bounce pos is larger than the framebuffer size, wrap around so the next isr starts pre-loading
//the next frame.
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->bounce_pos_px = 0;
}
if (!panel->on_bounce_empty) {
//Preload the next bit of buffer into psram.
Cache_Start_DCache_Preload((uint32_t)&panel->fb[panel->bounce_pos_px * bytes_per_pixel],
panel->bounce_buffer_size_bytes, 0);
if (!panel->flags.no_fb) {
// Preload the next bit of buffer from psram
Cache_Start_DCache_Preload((uint32_t)&panel->fbs[panel->cur_fb_index][panel->bounce_pos_px * bytes_per_pixel],
panel->bb_size, 0);
}
return need_yield;
}
//This is called in bounce buffer mode, when one bounce buffer has been fully sent to the LCD peripheral.
// 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;
dma_descriptor_t *desc = (dma_descriptor_t*)event_data->tx_eof_desc_addr;
//Figure out which bounce buffer to write to.
//Note: what we receive is the *last* descriptor of this bounce buffer.
int bb=(desc==&panel->dma_nodes[panel->num_dma_nodes - 1])?1:0;
esp_rgb_panel_t *panel = (esp_rgb_panel_t *)user_data;
dma_descriptor_t *desc = (dma_descriptor_t *)event_data->tx_eof_desc_addr;
// Figure out which bounce buffer to write to.
// Note: what we receive is the *last* descriptor of this bounce buffer.
int bb = (desc == &panel->dma_nodes[panel->num_dma_nodes - 1]) ? 0 : 1;
return lcd_rgb_panel_fill_bounce_buffer(panel, panel->bounce_buffer[bb]);
}
//If we restart GDMA, this many pixels will already have been transfered to the
//LCD peripheral. Looks like that has 16 pixels of FIFO plus one holding register.
#define LCD_FIFO_SIZE_PX 17
// If we restart GDMA, many pixels already have been transferred to the LCD peripheral.
// Looks like that has 16 pixels of FIFO plus one holding register.
#define LCD_FIFO_PRESERVE_SIZE_PX (GDMA_LL_L2FIFO_BASE_SIZE + 1)
static esp_err_t lcd_rgb_panel_create_trans_link(esp_rgb_panel_t *panel)
{
esp_err_t ret = ESP_OK;
if (panel->bounce_buffer_size_bytes == 0) {
// Create DMA descriptors for main framebuffer:
// chain DMA descriptors
for (int i = 0; i < panel->num_dma_nodes; i++) {
panel->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU;
panel->dma_nodes[i].next = &panel->dma_nodes[i + 1];
}
// loop end back to start (note: this is needed as we restart the DMA transaction in the VBlank,
// but it should already have LCD_FIFO_PIX bytes from the start of the buffer at that time)
panel->dma_nodes[panel->num_dma_nodes - 1].next = &panel->dma_nodes[0];
// mount the frame buffer to the DMA descriptors
lcd_com_mount_dma_data(panel->dma_nodes, panel->fb, panel->fb_size);
} else {
//Create DMA descriptors for bounce buffers:
// chain DMA descriptors
for (int i = 0; i < panel->num_dma_nodes; i++) {
panel->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU;
panel->dma_nodes[i].next = &panel->dma_nodes[i + 1];
}
// loop end back to start
panel->dma_nodes[panel->num_dma_nodes - 1].next = &panel->dma_nodes[0];
// set eof on end of 1st and 2nd bounce buffer so we get an interrupt when they're fully sent
panel->dma_nodes[(panel->num_dma_nodes/2) - 1].dw0.suc_eof=1;
panel->dma_nodes[panel->num_dma_nodes - 1].dw0.suc_eof=1;
// mount the bounce buffers to the DMA descriptors
lcd_com_mount_dma_data(&panel->dma_nodes[0], panel->bounce_buffer[0], panel->bounce_buffer_size_bytes);
lcd_com_mount_dma_data(&panel->dma_nodes[(panel->num_dma_nodes/2)], panel->bounce_buffer[1], panel->bounce_buffer_size_bytes);
panel->dma_links[0] = &panel->dma_nodes[0];
panel->dma_links[1] = &panel->dma_nodes[panel->num_dma_nodes];
// chain DMA descriptors
for (int i = 0; i < panel->num_dma_nodes * 2; i++) {
panel->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU;
panel->dma_nodes[i].next = &panel->dma_nodes[i + 1];
}
//On restart, the data sent to the LCD peripheral needs to start 17 pixels after the FB start (the length of the
//lcd fifo) so we use a special DMA node to restart the DMA transaction.
if (panel->bb_size) {
// loop end back to start
panel->dma_nodes[panel->num_dma_nodes * 2 - 1].next = &panel->dma_nodes[0];
// mount the bounce buffers to the DMA descriptors
lcd_com_mount_dma_data(panel->dma_links[0], panel->bounce_buffer[0], panel->bb_size);
lcd_com_mount_dma_data(panel->dma_links[1], panel->bounce_buffer[1], panel->bb_size);
} else {
if (panel->flags.stream_mode) {
// circle DMA descriptors chain for each frame buffer
panel->dma_nodes[panel->num_dma_nodes - 1].next = &panel->dma_nodes[0];
panel->dma_nodes[panel->num_dma_nodes * 2 - 1].next = &panel->dma_nodes[panel->num_dma_nodes];
} else {
// one-off DMA descriptors chain
panel->dma_nodes[panel->num_dma_nodes - 1].next = NULL;
panel->dma_nodes[panel->num_dma_nodes * 2 - 1].next = NULL;
}
// mount the frame buffer to the DMA descriptors
lcd_com_mount_dma_data(panel->dma_links[0], panel->fbs[0], panel->fb_size);
if (panel->fbs[1]) {
lcd_com_mount_dma_data(panel->dma_links[1], panel->fbs[1], panel->fb_size);
}
}
#if CONFIG_LCD_RGB_RESTART_IN_VSYNC
// On restart, 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 node to restart the DMA transaction
memcpy(&panel->dma_restart_node, &panel->dma_nodes[0], sizeof(panel->dma_restart_node));
int restart_skip_bytes=LCD_FIFO_SIZE_PX*sizeof(uint16_t);
uint8_t *p=(uint8_t*)panel->dma_restart_node.buffer;
panel->dma_restart_node.buffer=&p[restart_skip_bytes];
panel->dma_restart_node.dw0.length-=restart_skip_bytes;
panel->dma_restart_node.dw0.size-=restart_skip_bytes;
int restart_skip_bytes = LCD_FIFO_PRESERVE_SIZE_PX * sizeof(uint16_t);
uint8_t *p = (uint8_t *)panel->dma_restart_node.buffer;
panel->dma_restart_node.buffer = &p[restart_skip_bytes];
panel->dma_restart_node.dw0.length -= restart_skip_bytes;
panel->dma_restart_node.dw0.size -= restart_skip_bytes;
#endif
// alloc DMA channel and connect to LCD peripheral
gdma_channel_alloc_config_t dma_chan_config = {
.direction = GDMA_CHANNEL_DIRECTION_TX,
};
ret = gdma_new_channel(&dma_chan_config, &panel->dma_chan);
ESP_GOTO_ON_ERROR(ret, err, TAG, "alloc DMA channel failed");
ESP_RETURN_ON_ERROR(gdma_new_channel(&dma_chan_config, &panel->dma_chan), TAG, "alloc DMA channel failed");
gdma_connect(panel->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0));
gdma_transfer_ability_t ability = {
.psram_trans_align = panel->psram_trans_align,
.sram_trans_align = panel->sram_trans_align,
};
gdma_set_transfer_ability(panel->dma_chan, &ability);
if (panel->bounce_buffer_size_bytes != 0) {
// register callback to re-fill bounce buffers once they're fully sent
gdma_tx_event_callbacks_t cbs={0};
cbs.on_trans_eof = lcd_rgb_panel_eof_handler;
// we need to refill the bounce buffer in the DMA EOF interrupt, so only register the callback for bounce buffer mode
if (panel->bb_size) {
gdma_tx_event_callbacks_t cbs = {
.on_trans_eof = lcd_rgb_panel_eof_handler,
};
gdma_register_tx_event_callbacks(panel->dma_chan, &cbs, panel);
}
// the start of DMA should be prior to the start of LCD engine
gdma_start(panel->dma_chan, (intptr_t)panel->dma_nodes);
err:
return ret;
return ESP_OK;
}
static IRAM_ATTR void lcd_rgb_panel_restart_transmission(esp_rgb_panel_t *panel)
#if CONFIG_LCD_RGB_RESTART_IN_VSYNC
static IRAM_ATTR void lcd_rgb_panel_restart_transmission_in_isr(esp_rgb_panel_t *panel)
{
if (panel->bounce_buffer_size_bytes != 0) {
//Catch de-synced framebuffer and reset if needed.
if (panel->bounce_pos_px > panel->bounce_buffer_size_bytes) panel->bounce_pos_px=0;
//Pre-fill bounce buffer 0, if the EOF ISR didn't do that already
if (panel->bounce_pos_px < panel->bounce_buffer_size_bytes/2) {
if (panel->bb_size) {
// Catch de-synced frame buffer and reset if needed.
if (panel->bounce_pos_px > panel->bb_size) {
panel->bounce_pos_px = 0;
}
// Pre-fill bounce buffer 0, if the EOF ISR didn't do that already
if (panel->bounce_pos_px < panel->bb_size / 2) {
lcd_rgb_panel_fill_bounce_buffer(panel, panel->bounce_buffer[0]);
}
}
gdma_reset(panel->dma_chan);
// restart the DMA by a special DMA node
gdma_start(panel->dma_chan, (intptr_t)&panel->dma_restart_node);
if (panel->bounce_buffer_size_bytes != 0) {
//Fill 2nd bounce buffer while 1st is being sent out, if needed.
if (panel->bounce_pos_px < panel->bounce_buffer_size_bytes) {
if (panel->bb_size) {
// Fill 2nd bounce buffer while 1st is being sent out, if needed.
if (panel->bounce_pos_px < panel->bb_size) {
lcd_rgb_panel_fill_bounce_buffer(panel, panel->bounce_buffer[0]);
}
}
}
#endif
static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel)
{
// reset FIFO of DMA and LCD, incase there remains old frame data
gdma_reset(rgb_panel->dma_chan);
lcd_ll_stop(rgb_panel->hal.dev);
// check whether to update the PCLK frequency
portENTER_CRITICAL_SAFE(&rgb_panel->spinlock);
if (unlikely(rgb_panel->flags.need_update_pclk)) {
rgb_panel->flags.need_update_pclk = false;
rgb_panel->timings.pclk_hz = lcd_hal_cal_pclk_freq(&rgb_panel->hal, rgb_panel->src_clk_hz, rgb_panel->timings.pclk_hz);
}
portEXIT_CRITICAL_SAFE(&rgb_panel->spinlock);
lcd_ll_fifo_reset(rgb_panel->hal.dev);
//pre-fill bounce buffers if needed
if (rgb_panel->bounce_buffer_size_bytes != 0) {
// pre-fill bounce buffers if needed
if (rgb_panel->bb_size) {
rgb_panel->bounce_pos_px = 0;
lcd_rgb_panel_fill_bounce_buffer(rgb_panel, rgb_panel->bounce_buffer[0]);
lcd_rgb_panel_fill_bounce_buffer(rgb_panel, rgb_panel->bounce_buffer[1]);
}
gdma_start(rgb_panel->dma_chan, (intptr_t)rgb_panel->dma_nodes);
// the start of DMA should be prior to the start of LCD engine
gdma_start(rgb_panel->dma_chan, (intptr_t)rgb_panel->dma_links[rgb_panel->cur_fb_index]);
// delay 1us is sufficient for DMA to pass data to LCD FIFO
// in fact, this is only needed when LCD pixel clock is set too high
esp_rom_delay_us(1);
// start LCD engine
lcd_ll_enable_auto_next_frame(rgb_panel->hal.dev, rgb_panel->flags.stream_mode);
lcd_ll_start(rgb_panel->hal.dev);
}
IRAM_ATTR static void lcd_rgb_panel_try_update_pclk(esp_rgb_panel_t *rgb_panel)
{
portENTER_CRITICAL_ISR(&rgb_panel->spinlock);
if (unlikely(rgb_panel->flags.need_update_pclk)) {
rgb_panel->flags.need_update_pclk = false;
rgb_panel->timings.pclk_hz = lcd_hal_cal_pclk_freq(&rgb_panel->hal, rgb_panel->src_clk_hz, rgb_panel->timings.pclk_hz);
}
portEXIT_CRITICAL_ISR(&rgb_panel->spinlock);
}
IRAM_ATTR static void lcd_default_isr_handler(void *args)
{
esp_rgb_panel_t *rgb_panel = (esp_rgb_panel_t *)args;
@ -777,24 +803,29 @@ IRAM_ATTR static void lcd_default_isr_handler(void *args)
lcd_ll_clear_interrupt_status(rgb_panel->hal.dev, intr_status);
if (intr_status & LCD_LL_EVENT_VSYNC_END) {
// call user registered callback
if (rgb_panel->on_frame_trans_done) {
if (rgb_panel->on_frame_trans_done(&rgb_panel->base, NULL, rgb_panel->user_ctx)) {
if (rgb_panel->on_vsync) {
if (rgb_panel->on_vsync(&rgb_panel->base, NULL, rgb_panel->user_ctx)) {
need_yield = true;
}
}
// check whether to update the PCLK frequency, it should be safe to update the PCLK frequency in the VSYNC interrupt
lcd_rgb_panel_try_update_pclk(rgb_panel);
if (rgb_panel->flags.stream_mode) {
// As described above, we reset the GDMA channel every VBlank to stop permanent
// desyncs from happening.
#if CONFIG_LCD_RGB_RESTART_IN_VSYNC
// reset the GDMA channel every VBlank to stop permanent desyncs from happening.
// Note that this fix can lead to single-frame desyncs itself, as in: if this interrupt
// is late enough, the display will shift as the LCD controller already read out the
// first data bytes, and resetting DMA will re-send those. However, the single-frame
// desync this leads to is preferable to the permanent desync that could otherwise
// happen. It's also not super-likely as this interrupt has the entirety of the VBlank
// time to reset DMA.
lcd_rgb_panel_restart_transmission(rgb_panel);
lcd_rgb_panel_restart_transmission_in_isr(rgb_panel);
#endif
}
}
}
if (need_yield) {
portYIELD_FROM_ISR();
}