diff --git a/components/esp_lcd/Kconfig b/components/esp_lcd/Kconfig index 5e62153575..630f39fb22 100644 --- a/components/esp_lcd/Kconfig +++ b/components/esp_lcd/Kconfig @@ -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 diff --git a/components/esp_lcd/include/esp_lcd_panel_rgb.h b/components/esp_lcd/include/esp_lcd_panel_rgb.h index 992b7fdfe1..1c48856128 100644 --- a/components/esp_lcd/include/esp_lcd_panel_rgb.h +++ b/components/esp_lcd/include/esp_lcd_panel_rgb.h @@ -5,6 +5,7 @@ */ #pragma once +#include #include #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 diff --git a/components/esp_lcd/linker.lf b/components/esp_lcd/linker.lf index 51f703659e..149801e604 100644 --- a/components/esp_lcd/linker.lf +++ b/components/esp_lcd/linker.lf @@ -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) diff --git a/components/esp_lcd/src/esp_lcd_rgb_panel.c b/components/esp_lcd/src/esp_lcd_rgb_panel.c index b30e63d6d7..6f47c73aa0 100644 --- a/components/esp_lcd/src/esp_lcd_rgb_panel.c +++ b/components/esp_lcd/src/esp_lcd_rgb_panel.c @@ -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(); }