mirror of
https://github.com/espressif/esp-idf
synced 2025-03-09 17:19:09 -04:00
rgb_lcd: support double buffer
also cleaned up the bounce buffer code
This commit is contained in:
parent
9ea3f6f3e7
commit
a33a183365
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user