Merge branch 'feat/rgb_lcd_gdma_link_v5.3' into 'release/v5.3'

refactor rgb_lcd driver to use gdma_link driver (v5.3)

See merge request espressif/esp-idf!34265
This commit is contained in:
morris
2024-10-22 11:07:46 +08:00
7 changed files with 232 additions and 184 deletions

View File

@ -227,11 +227,20 @@ uintptr_t gdma_link_get_head_addr(gdma_link_list_handle_t list)
return (uintptr_t)(list->items); return (uintptr_t)(list->items);
} }
esp_err_t gdma_link_concat(gdma_link_list_handle_t first_link, int first_link_item_index, gdma_link_list_handle_t second_link, int second_link_item_index)
{
ESP_RETURN_ON_FALSE(first_link && second_link, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
gdma_link_list_item_t *lli_nc = NULL;
lli_nc = (gdma_link_list_item_t *)(first_link->items_nc + (first_link->num_items + first_link_item_index) % first_link->num_items * first_link->item_size);
lli_nc->next = (gdma_link_list_item_t *)(second_link->items + (second_link->num_items + second_link_item_index) % second_link->num_items * second_link->item_size);
return ESP_OK;
}
esp_err_t gdma_link_set_owner(gdma_link_list_handle_t list, int item_index, gdma_lli_owner_t owner) esp_err_t gdma_link_set_owner(gdma_link_list_handle_t list, int item_index, gdma_lli_owner_t owner)
{ {
ESP_RETURN_ON_FALSE_ISR(list, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); ESP_RETURN_ON_FALSE_ISR(list, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE_ISR(item_index < list->num_items, ESP_ERR_INVALID_ARG, TAG, "invalid item index"); ESP_RETURN_ON_FALSE_ISR(item_index < list->num_items, ESP_ERR_INVALID_ARG, TAG, "invalid item index");
gdma_link_list_item_t *lli = (gdma_link_list_item_t *)(list->items_nc + item_index * list->item_size); gdma_link_list_item_t *lli = (gdma_link_list_item_t *)(list->items_nc + (list->num_items + item_index) % list->num_items * list->item_size);
lli->dw0.owner = owner; lli->dw0.owner = owner;
return ESP_OK; return ESP_OK;
} }
@ -240,7 +249,7 @@ esp_err_t gdma_link_get_owner(gdma_link_list_handle_t list, int item_index, gdma
{ {
ESP_RETURN_ON_FALSE_ISR(list && owner, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); ESP_RETURN_ON_FALSE_ISR(list && owner, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE_ISR(item_index < list->num_items, ESP_ERR_INVALID_ARG, TAG, "invalid item index"); ESP_RETURN_ON_FALSE_ISR(item_index < list->num_items, ESP_ERR_INVALID_ARG, TAG, "invalid item index");
gdma_link_list_item_t *lli = (gdma_link_list_item_t *)(list->items_nc + item_index * list->item_size); gdma_link_list_item_t *lli = (gdma_link_list_item_t *)(list->items_nc + (list->num_items + item_index) % list->num_items * list->item_size);
*owner = lli->dw0.owner; *owner = lli->dw0.owner;
return ESP_OK; return ESP_OK;
} }

View File

@ -71,7 +71,7 @@ typedef struct {
uint32_t mark_final: 1; /*!< Whether to terminate the DMA link list at this item. uint32_t mark_final: 1; /*!< Whether to terminate the DMA link list at this item.
Note, DMA engine will stop at this item and trigger an interrupt. Note, DMA engine will stop at this item and trigger an interrupt.
If `mark_final` is not set, this list item will point to the next item, and If `mark_final` is not set, this list item will point to the next item, and
wrap around to the head item if it's the one in the list. */ wrap around to the head item if it's the last one in the list. */
} flags; //!< Flags for buffer mount configurations } flags; //!< Flags for buffer mount configurations
} gdma_buffer_mount_config_t; } gdma_buffer_mount_config_t;
@ -105,6 +105,27 @@ esp_err_t gdma_link_mount_buffers(gdma_link_list_handle_t list, uint32_t start_i
*/ */
uintptr_t gdma_link_get_head_addr(gdma_link_list_handle_t list); uintptr_t gdma_link_get_head_addr(gdma_link_list_handle_t list);
/**
* @brief Concatenate two link lists as follows:
*
* Link A: A1 --> A2 --> A3 --> A4
* | item_index
* +-----+
* |
* v item_index
* Link B: B1 --> B2 --> B3 --> B4
*
* @param[in] first_link First link list handle, allocated by `gdma_new_link_list`
* @param[in] first_link_item_index Index of the item in the first link list (-1 means the last item)
* @param[in] second_link Second link list handle, allocated by `gdma_new_link_list`
* @param[in] second_link_item_index Index of the item in the second link list (-1 means the last item)
* @return
* - ESP_OK: Concatenate the link lists successfully
* - ESP_ERR_INVALID_ARG: Concatenate the link lists failed because of invalid argument
* - ESP_FAIL: Concatenate the link lists failed because of other error
*/
esp_err_t gdma_link_concat(gdma_link_list_handle_t first_link, int first_link_item_index, gdma_link_list_handle_t second_link, int second_link_item_index);
/** /**
* @brief GDMA link list item owner * @brief GDMA link list item owner
*/ */
@ -117,7 +138,7 @@ typedef enum {
* @brief Set the ownership for a DMA link list item * @brief Set the ownership for a DMA link list item
* *
* @param[in] list Link list handle, allocated by `gdma_new_link_list` * @param[in] list Link list handle, allocated by `gdma_new_link_list`
* @param[in] item_index Index of the link list item * @param[in] item_index Index of the link list item (-1 means the last item)
* @param[in] owner Ownership * @param[in] owner Ownership
* @return * @return
* - ESP_OK: Set the ownership successfully * - ESP_OK: Set the ownership successfully
@ -130,7 +151,7 @@ esp_err_t gdma_link_set_owner(gdma_link_list_handle_t list, int item_index, gdma
* @brief Get the ownership of a DMA link list item * @brief Get the ownership of a DMA link list item
* *
* @param[in] list Link list handle, allocated by `gdma_new_link_list` * @param[in] list Link list handle, allocated by `gdma_new_link_list`
* @param[in] item_index Index of the link list item * @param[in] item_index Index of the link list item (-1 means the last item)
* @param[out] owner Ownership * @param[out] owner Ownership
* @return * @return
* - ESP_OK: Get the ownership successfully * - ESP_OK: Get the ownership successfully

View File

@ -4,6 +4,7 @@ entries:
if LCD_RGB_ISR_IRAM_SAFE = y: if LCD_RGB_ISR_IRAM_SAFE = y:
gdma: gdma_reset (noflash) gdma: gdma_reset (noflash)
gdma: gdma_start (noflash) gdma: gdma_start (noflash)
gdma_link: gdma_link_get_head_addr (noflash)
[mapping:esp_lcd_hal] [mapping:esp_lcd_hal]
archive: libhal.a archive: libhal.a

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -60,15 +60,6 @@ int lcd_com_register_device(lcd_com_device_type_t device_type, void *device_obj)
void lcd_com_remove_device(lcd_com_device_type_t device_type, int member_id); void lcd_com_remove_device(lcd_com_device_type_t device_type, int member_id);
#endif // SOC_LCDCAM_SUPPORTED #endif // SOC_LCDCAM_SUPPORTED
/**
* @brief Mount data to DMA descriptors
*
* @param desc_head Point to the head of DMA descriptor chain
* @param buffer Data buffer
* @param len Size of the data buffer, in bytes
*/
void lcd_com_mount_dma_data(dma_descriptor_t *desc_head, const void *buffer, size_t len);
/** /**
* @brief Reverse the bytes in the buffer * @brief Reverse the bytes in the buffer
* *

View File

@ -17,7 +17,6 @@
#endif #endif
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_attr.h" #include "esp_attr.h"
#include "esp_check.h" #include "esp_check.h"
#include "esp_pm.h" #include "esp_pm.h"
@ -29,31 +28,43 @@
#include "esp_clk_tree.h" #include "esp_clk_tree.h"
#include "hal/dma_types.h" #include "hal/dma_types.h"
#include "hal/gpio_hal.h" #include "hal/gpio_hal.h"
#include "esp_private/gdma.h"
#include "driver/gpio.h" #include "driver/gpio.h"
#include "esp_bit_defs.h" #include "esp_bit_defs.h"
#include "esp_private/esp_clk_tree_common.h"
#include "esp_private/gdma.h"
#include "esp_private/gdma_link.h"
#include "esp_private/periph_ctrl.h" #include "esp_private/periph_ctrl.h"
#include "esp_private/gpio.h" #include "esp_private/gpio.h"
#include "esp_psram.h" #include "esp_psram.h"
#include "esp_lcd_common.h" #include "esp_lcd_common.h"
#include "esp_cache.h"
#include "esp_memory_utils.h" #include "esp_memory_utils.h"
#include "soc/lcd_periph.h" #include "soc/lcd_periph.h"
#include "hal/lcd_hal.h" #include "hal/lcd_hal.h"
#include "hal/lcd_ll.h" #include "hal/lcd_ll.h"
#include "hal/cache_hal.h" #include "hal/cache_hal.h"
#include "hal/cache_ll.h" #include "hal/cache_ll.h"
#include "rom/cache.h"
#include "esp_cache.h"
#if CONFIG_LCD_RGB_ISR_IRAM_SAFE #if CONFIG_LCD_RGB_ISR_IRAM_SAFE
#define LCD_RGB_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED) #define LCD_RGB_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED)
#define LCD_RGB_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
#else #else
#define LCD_RGB_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED #define LCD_RGB_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED
#define LCD_RGB_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
#endif
#if defined(SOC_GDMA_TRIG_PERIPH_LCD0_BUS) && (SOC_GDMA_TRIG_PERIPH_LCD0_BUS == SOC_GDMA_BUS_AHB)
#define LCD_GDMA_NEW_CHANNEL gdma_new_ahb_channel
#define LCD_GDMA_DESCRIPTOR_ALIGN 4
#elif defined(SOC_GDMA_TRIG_PERIPH_LCD0_BUS) && (SOC_GDMA_TRIG_PERIPH_LCD0_BUS == SOC_GDMA_BUS_AXI)
#define LCD_GDMA_NEW_CHANNEL gdma_new_axi_channel
#define LCD_GDMA_DESCRIPTOR_ALIGN 8
#else
#error "Unsupported GDMA bus type for RGB LCD"
#endif #endif
#define RGB_LCD_PANEL_MAX_FB_NUM 3 // maximum supported frame buffer number #define RGB_LCD_PANEL_MAX_FB_NUM 3 // maximum supported frame buffer number
#define RGB_LCD_PANEL_BOUNCE_BUF_NUM 2 // bounce buffer number #define RGB_LCD_PANEL_BOUNCE_BUF_NUM 2 // bounce buffer number
#define RGB_LCD_PANEL_DMA_LINKS_REPLICA MAX(RGB_LCD_PANEL_MAX_FB_NUM, RGB_LCD_PANEL_BOUNCE_BUF_NUM)
#define RGB_PANEL_SWAP_XY 0 #define RGB_PANEL_SWAP_XY 0
#define RGB_PANEL_MIRROR_Y 1 #define RGB_PANEL_MIRROR_Y 1
@ -79,8 +90,8 @@ static esp_err_t rgb_panel_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
static esp_err_t rgb_panel_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); static esp_err_t rgb_panel_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
static esp_err_t rgb_panel_disp_on_off(esp_lcd_panel_t *panel, bool off); static esp_err_t rgb_panel_disp_on_off(esp_lcd_panel_t *panel, bool off);
static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src); static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src);
static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *panel); static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *rgb_panel);
static void lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *panel); static esp_err_t lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *rgb_panel);
static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *panel, const esp_lcd_rgb_panel_config_t *panel_config); static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *panel, const esp_lcd_rgb_panel_config_t *panel_config);
static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel); static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel);
static void rgb_lcd_default_isr_handler(void *args); static void rgb_lcd_default_isr_handler(void *args);
@ -98,19 +109,24 @@ struct esp_rgb_panel_t {
intr_handle_t intr; // LCD peripheral interrupt handle intr_handle_t intr; // LCD peripheral interrupt handle
esp_pm_lock_handle_t pm_lock; // Power management lock 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 size_t num_dma_nodes; // Number of DMA descriptors that used to carry the frame buffer
gdma_channel_handle_t dma_chan; // DMA channel handle
gdma_link_list_handle_t dma_fb_links[RGB_LCD_PANEL_MAX_FB_NUM]; // DMA link lists for multiple frame buffers
gdma_link_list_handle_t dma_bb_link; // DMA link list for bounce buffer
gdma_link_list_handle_t dma_restart_link; // DMA link list for restarting the DMA
uint8_t *fbs[RGB_LCD_PANEL_MAX_FB_NUM]; // Frame buffers uint8_t *fbs[RGB_LCD_PANEL_MAX_FB_NUM]; // Frame buffers
uint8_t *bounce_buffer[RGB_LCD_PANEL_BOUNCE_BUF_NUM]; // Pointer to the bounce buffers
size_t fb_size; // Size of frame buffer, in bytes
size_t bb_size; // Size of the bounce buffer, in bytes. If not-zero, the driver uses two bounce buffers allocated from internal memory
uint8_t cur_fb_index; // Current frame buffer index uint8_t cur_fb_index; // Current frame buffer index
uint8_t bb_fb_index; // Current frame buffer index which used by bounce buffer uint8_t bb_fb_index; // Current frame buffer index which used by bounce buffer
size_t fb_size; // Size of frame buffer, in bytes size_t int_mem_align; // DMA buffer alignment for internal memory
size_t ext_mem_align; // DMA buffer alignment for external memory
int data_gpio_nums[SOC_LCD_RGB_DATA_WIDTH]; // GPIOs used for data lines, we keep these GPIOs for action like "invert_color" int data_gpio_nums[SOC_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 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) esp_lcd_rgb_timing_t timings; // RGB timing parameters (e.g. pclk, sync pulse, porch width)
size_t bb_size; // Size of the bounce buffer, in bytes. 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 int bounce_pos_px; // Position in whatever source material is used for the bounce buffer, in pixels
uint8_t *bounce_buffer[RGB_LCD_PANEL_BOUNCE_BUF_NUM]; // Pointer to the bounce buffers
size_t bb_eof_count; // record the number we received the DMA EOF event, compare with `expect_eof_count` in the VSYNC_END ISR size_t bb_eof_count; // record the number we received the DMA EOF event, compare with `expect_eof_count` in the VSYNC_END ISR
size_t expect_eof_count; // record the number of DMA EOF event we expected to receive size_t expect_eof_count; // record the number of DMA EOF event we expected to receive
gdma_channel_handle_t dma_chan; // DMA channel handle
esp_lcd_rgb_panel_vsync_cb_t on_vsync; // VSYNC event callback esp_lcd_rgb_panel_vsync_cb_t on_vsync; // VSYNC event callback
esp_lcd_rgb_panel_bounce_buf_fill_cb_t on_bounce_empty; // callback used to fill a bounce buffer rather than copying from the frame buffer esp_lcd_rgb_panel_bounce_buf_fill_cb_t on_bounce_empty; // callback used to fill a bounce buffer rather than copying from the frame buffer
esp_lcd_rgb_panel_bounce_buf_finish_cb_t on_bounce_frame_finish; // callback used to notify when the bounce buffer finish copying the entire frame esp_lcd_rgb_panel_bounce_buf_finish_cb_t on_bounce_frame_finish; // callback used to notify when the bounce buffer finish copying the entire frame
@ -127,66 +143,55 @@ struct esp_rgb_panel_t {
uint32_t need_update_pclk: 1; // Whether to update the PCLK before start a new transaction uint32_t need_update_pclk: 1; // Whether to update the PCLK before start a new transaction
uint32_t need_restart: 1; // Whether to restart the LCD controller and the DMA uint32_t need_restart: 1; // Whether to restart the LCD controller and the DMA
uint32_t bb_invalidate_cache: 1; // Whether to do cache invalidation in bounce buffer mode uint32_t bb_invalidate_cache: 1; // Whether to do cache invalidation in bounce buffer mode
uint32_t fb_behind_cache: 1; // Whether the frame buffer is behind the cache
uint32_t bb_behind_cache: 1; // Whether the bounce buffer is behind the cache
} flags; } flags;
dma_descriptor_t *dma_links[RGB_LCD_PANEL_DMA_LINKS_REPLICA]; // fbs[0] <-> dma_links[0], fbs[1] <-> dma_links[1], etc
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) static esp_err_t lcd_rgb_panel_alloc_frame_buffers(esp_rgb_panel_t *rgb_panel)
{ {
bool fb_in_psram = false; bool fb_in_psram = rgb_panel->flags.fb_in_psram;
size_t ext_mem_align = 0;
size_t int_mem_align = 0;
gdma_get_alignment_constraints(rgb_panel->dma_chan, &int_mem_align, &ext_mem_align);
// also take the cache line size into account when allocating the frame buffer // read the cache line size of internal and external memory, we use this information to check if the allocated memory is behind the cache
uint32_t ext_mem_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA);
uint32_t int_mem_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA); uint32_t int_mem_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
// The buffer must be aligned to the cache line size uint32_t ext_mem_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA);
if (ext_mem_cache_line_size) {
ext_mem_align = MAX(ext_mem_align, ext_mem_cache_line_size);
}
if (int_mem_cache_line_size) {
int_mem_align = MAX(int_mem_align, int_mem_cache_line_size);
}
// alloc frame buffer // alloc frame buffer
if (rgb_panel->num_fbs > 0) { for (int i = 0; i < rgb_panel->num_fbs; i++) {
// fb_in_psram is only an option, if there's no PSRAM on board, we fallback to alloc from SRAM if (fb_in_psram) {
if (rgb_panel_config->flags.fb_in_psram) { // the allocated buffer is also aligned to the cache line size
#if CONFIG_SPIRAM_USE_MALLOC || CONFIG_SPIRAM_USE_CAPS_ALLOC rgb_panel->fbs[i] = heap_caps_aligned_calloc(rgb_panel->ext_mem_align, 1, rgb_panel->fb_size,
if (esp_psram_is_initialized()) { MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
fb_in_psram = true; ESP_RETURN_ON_FALSE(rgb_panel->fbs[i], ESP_ERR_NO_MEM, TAG, "no mem for frame buffer");
} rgb_panel->flags.fb_behind_cache = ext_mem_cache_line_size > 0;
#endif } else {
rgb_panel->fbs[i] = heap_caps_aligned_calloc(rgb_panel->int_mem_align, 1, rgb_panel->fb_size,
MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
ESP_RETURN_ON_FALSE(rgb_panel->fbs[i], ESP_ERR_NO_MEM, TAG, "no mem for frame buffer");
rgb_panel->flags.fb_behind_cache = int_mem_cache_line_size > 0;
} }
for (int i = 0; i < rgb_panel->num_fbs; i++) { // flush data from cache to the physical memory
if (fb_in_psram) { if (rgb_panel->flags.fb_behind_cache) {
// the low level malloc function will help check the validation of alignment esp_cache_msync(rgb_panel->fbs[i], rgb_panel->fb_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
rgb_panel->fbs[i] = heap_caps_aligned_calloc(ext_mem_align, 1, rgb_panel->fb_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
ESP_RETURN_ON_FALSE(rgb_panel->fbs[i], ESP_ERR_NO_MEM, TAG, "no mem for frame buffer");
// calloc not only allocates but also zero's the buffer. We have to make sure this is
// properly committed to the PSRAM, otherwise all sorts of visual corruption will happen.
ESP_RETURN_ON_ERROR(esp_cache_msync(rgb_panel->fbs[i], rgb_panel->fb_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M), TAG, "cache write back failed");
} else {
rgb_panel->fbs[i] = heap_caps_aligned_calloc(int_mem_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 // alloc bounce buffer
if (rgb_panel->bb_size) { if (rgb_panel->bb_size) {
for (int i = 0; i < RGB_LCD_PANEL_BOUNCE_BUF_NUM; i++) { for (int i = 0; i < RGB_LCD_PANEL_BOUNCE_BUF_NUM; i++) {
// bounce buffer must come from SRAM // bounce buffer must be allocated from internal memory for performance
rgb_panel->bounce_buffer[i] = heap_caps_aligned_calloc(int_mem_align, 1, rgb_panel->bb_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); rgb_panel->bounce_buffer[i] = heap_caps_aligned_calloc(rgb_panel->int_mem_align, 1, rgb_panel->bb_size,
MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
ESP_RETURN_ON_FALSE(rgb_panel->bounce_buffer[i], ESP_ERR_NO_MEM, TAG, "no mem for bounce buffer"); ESP_RETURN_ON_FALSE(rgb_panel->bounce_buffer[i], ESP_ERR_NO_MEM, TAG, "no mem for bounce buffer");
if (int_mem_cache_line_size > 0) {
// flush data from cache to the physical memory
esp_cache_msync(rgb_panel->bounce_buffer[i], rgb_panel->bb_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
rgb_panel->flags.bb_behind_cache = true;
}
} }
} }
rgb_panel->cur_fb_index = 0; rgb_panel->cur_fb_index = 0;
rgb_panel->bb_fb_index = 0; rgb_panel->bb_fb_index = 0;
rgb_panel->flags.fb_in_psram = fb_in_psram;
return ESP_OK; return ESP_OK;
} }
@ -204,21 +209,29 @@ static esp_err_t lcd_rgb_panel_destroy(esp_rgb_panel_t *rgb_panel)
} }
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id); lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id);
} }
for (size_t i = 0; i < rgb_panel->num_fbs; i++) {
if (rgb_panel->fbs[i]) {
free(rgb_panel->fbs[i]);
}
}
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) { if (rgb_panel->dma_chan) {
gdma_disconnect(rgb_panel->dma_chan); gdma_disconnect(rgb_panel->dma_chan);
gdma_del_channel(rgb_panel->dma_chan); gdma_del_channel(rgb_panel->dma_chan);
} }
for (size_t i = 0; i < RGB_LCD_PANEL_MAX_FB_NUM; i++) {
if (rgb_panel->fbs[i]) {
free(rgb_panel->fbs[i]);
}
if (rgb_panel->dma_fb_links[i]) {
gdma_del_link_list(rgb_panel->dma_fb_links[i]);
}
}
for (int i = 0; i < RGB_LCD_PANEL_BOUNCE_BUF_NUM; i++) {
if (rgb_panel->bounce_buffer[i]) {
free(rgb_panel->bounce_buffer[i]);
}
}
if (rgb_panel->dma_bb_link) {
gdma_del_link_list(rgb_panel->dma_bb_link);
}
if (rgb_panel->dma_restart_link) {
gdma_del_link_list(rgb_panel->dma_restart_link);
}
if (rgb_panel->intr) { if (rgb_panel->intr) {
esp_intr_free(rgb_panel->intr); esp_intr_free(rgb_panel->intr);
} }
@ -239,7 +252,7 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
esp_rgb_panel_t *rgb_panel = NULL; esp_rgb_panel_t *rgb_panel = NULL;
ESP_RETURN_ON_FALSE(rgb_panel_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid parameter"); ESP_RETURN_ON_FALSE(rgb_panel_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid parameter");
size_t data_width = rgb_panel_config->data_width; size_t data_width = rgb_panel_config->data_width;
ESP_RETURN_ON_FALSE((data_width >= 8) && (data_width <= SOC_LCD_RGB_DATA_WIDTH) && ((data_width & (data_width - 1)) == 0), ESP_ERR_INVALID_ARG, ESP_RETURN_ON_FALSE((data_width > 0) && (data_width <= SOC_LCDCAM_RGB_DATA_WIDTH) && ((data_width % 8) == 0), ESP_ERR_INVALID_ARG,
TAG, "unsupported data width %d", data_width); TAG, "unsupported data width %d", data_width);
ESP_RETURN_ON_FALSE(!(rgb_panel_config->flags.double_fb && rgb_panel_config->flags.no_fb), ESP_RETURN_ON_FALSE(!(rgb_panel_config->flags.double_fb && rgb_panel_config->flags.no_fb),
ESP_ERR_INVALID_ARG, TAG, "double_fb conflicts with no_fb"); ESP_ERR_INVALID_ARG, TAG, "double_fb conflicts with no_fb");
@ -282,25 +295,16 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
// calculate the number of DMA descriptors // calculate the number of DMA descriptors
size_t num_dma_nodes = 0; size_t num_dma_nodes = 0;
if (bb_size) { // allocate memory for rgb panel
// in bounce buffer mode, DMA is used to convey the bounce buffer, not the frame buffer. rgb_panel = heap_caps_calloc(1, sizeof(esp_rgb_panel_t), LCD_RGB_MEM_ALLOC_CAPS);
// 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 {
// 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) * RGB_LCD_PANEL_DMA_LINKS_REPLICA,
MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
ESP_GOTO_ON_FALSE(rgb_panel, ESP_ERR_NO_MEM, err, TAG, "no mem for rgb panel"); ESP_GOTO_ON_FALSE(rgb_panel, ESP_ERR_NO_MEM, err, TAG, "no mem for rgb panel");
rgb_panel->panel_id = -1;
rgb_panel->num_dma_nodes = num_dma_nodes; rgb_panel->num_dma_nodes = num_dma_nodes;
rgb_panel->num_fbs = num_fbs; rgb_panel->num_fbs = num_fbs;
rgb_panel->fb_size = fb_size; rgb_panel->fb_size = fb_size;
rgb_panel->bb_size = bb_size; rgb_panel->bb_size = bb_size;
rgb_panel->fb_bits_per_pixel = fb_bits_per_pixel;
rgb_panel->expect_eof_count = expect_bb_eof_count; rgb_panel->expect_eof_count = expect_bb_eof_count;
rgb_panel->panel_id = -1;
// register to platform // register to platform
int panel_id = lcd_com_register_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel); 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"); ESP_GOTO_ON_FALSE(panel_id >= 0, ESP_ERR_NOT_FOUND, err, TAG, "no free rgb panel slot");
@ -342,13 +346,13 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
// install DMA service // install DMA service
rgb_panel->flags.stream_mode = !rgb_panel_config->flags.refresh_on_demand; rgb_panel->flags.stream_mode = !rgb_panel_config->flags.refresh_on_demand;
rgb_panel->fb_bits_per_pixel = fb_bits_per_pixel;
rgb_panel->dma_burst_size = rgb_panel_config->dma_burst_size ? rgb_panel_config->dma_burst_size : 64; rgb_panel->dma_burst_size = rgb_panel_config->dma_burst_size ? rgb_panel_config->dma_burst_size : 64;
rgb_panel->flags.fb_in_psram = rgb_panel_config->flags.fb_in_psram;
ESP_GOTO_ON_ERROR(lcd_rgb_create_dma_channel(rgb_panel), err, TAG, "install DMA failed"); ESP_GOTO_ON_ERROR(lcd_rgb_create_dma_channel(rgb_panel), err, TAG, "install DMA failed");
// allocate frame buffers + bounce buffers // 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"); ESP_GOTO_ON_ERROR(lcd_rgb_panel_alloc_frame_buffers(rgb_panel), err, TAG, "alloc frame buffers failed");
// initialize DMA descriptor link // initialize DMA descriptor link
lcd_rgb_panel_init_trans_link(rgb_panel); ESP_GOTO_ON_ERROR(lcd_rgb_panel_init_trans_link(rgb_panel), err, TAG, "init DMA link failed");
// configure GPIO // configure GPIO
ret = lcd_rgb_panel_configure_gpio(rgb_panel, rgb_panel_config); ret = lcd_rgb_panel_configure_gpio(rgb_panel, rgb_panel_config);
@ -785,13 +789,14 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
if (!rgb_panel->bb_size) { if (!rgb_panel->bb_size) {
if (rgb_panel->flags.stream_mode) { if (rgb_panel->flags.stream_mode) {
// the DMA will convey the new frame buffer next time for (int i = 0; i < rgb_panel->num_fbs; i++) {
for (int i = 0; i < RGB_LCD_PANEL_DMA_LINKS_REPLICA; i++) { // Note, because of DMA prefetch, there's possibility that the old frame buffer might be sent out again
rgb_panel->dma_nodes[rgb_panel->num_dma_nodes * (i + 1) - 1].next = rgb_panel->dma_links[rgb_panel->cur_fb_index]; // it's hard to know the time when the new frame buffer starts
gdma_link_concat(rgb_panel->dma_fb_links[i], -1, rgb_panel->dma_fb_links[rgb_panel->cur_fb_index], 0);
} }
} }
} }
return ESP_OK; return ESP_OK;
} }
@ -958,95 +963,138 @@ static IRAM_ATTR bool lcd_rgb_panel_fill_bounce_buffer(esp_rgb_panel_t *panel, u
static IRAM_ATTR bool lcd_rgb_panel_eof_handler(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data) 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; 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. // 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;
portENTER_CRITICAL_ISR(&panel->spinlock); portENTER_CRITICAL_ISR(&panel->spinlock);
int bb = panel->bb_eof_count % RGB_LCD_PANEL_BOUNCE_BUF_NUM;
panel->bb_eof_count++; panel->bb_eof_count++;
portEXIT_CRITICAL_ISR(&panel->spinlock); portEXIT_CRITICAL_ISR(&panel->spinlock);
return lcd_rgb_panel_fill_bounce_buffer(panel, panel->bounce_buffer[bb]); return lcd_rgb_panel_fill_bounce_buffer(panel, panel->bounce_buffer[bb]);
} }
static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *panel) static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *rgb_panel)
{ {
// alloc DMA channel and connect to LCD peripheral // alloc DMA channel and connect to LCD peripheral
gdma_channel_alloc_config_t dma_chan_config = { gdma_channel_alloc_config_t dma_chan_config = {
.direction = GDMA_CHANNEL_DIRECTION_TX, .direction = GDMA_CHANNEL_DIRECTION_TX,
}; };
#if SOC_GDMA_TRIG_PERIPH_LCD0_BUS == SOC_GDMA_BUS_AHB ESP_RETURN_ON_ERROR(LCD_GDMA_NEW_CHANNEL(&dma_chan_config, &rgb_panel->dma_chan), TAG, "alloc DMA channel failed");
ESP_RETURN_ON_ERROR(gdma_new_ahb_channel(&dma_chan_config, &panel->dma_chan), TAG, "alloc DMA channel failed"); gdma_connect(rgb_panel->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0));
#elif SOC_GDMA_TRIG_PERIPH_LCD0_BUS == SOC_GDMA_BUS_AXI
ESP_RETURN_ON_ERROR(gdma_new_axi_channel(&dma_chan_config, &panel->dma_chan), TAG, "alloc DMA channel failed");
#endif
gdma_connect(panel->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0));
// configure DMA transfer parameters // configure DMA transfer
gdma_transfer_config_t trans_cfg = { gdma_transfer_config_t trans_cfg = {
.max_data_burst_size = panel->dma_burst_size, .max_data_burst_size = rgb_panel->dma_burst_size,
.access_ext_mem = true, // frame buffer was allocated from external memory .access_ext_mem = rgb_panel->flags.fb_in_psram,
}; };
ESP_RETURN_ON_ERROR(gdma_config_transfer(panel->dma_chan, &trans_cfg), TAG, "config DMA transfer failed"); ESP_RETURN_ON_ERROR(gdma_config_transfer(rgb_panel->dma_chan, &trans_cfg), TAG, "config DMA transfer failed");
// get the memory alignment required by the DMA
gdma_get_alignment_constraints(rgb_panel->dma_chan, &rgb_panel->int_mem_align, &rgb_panel->ext_mem_align);
// we need to refill the bounce buffer in the DMA EOF interrupt, so only register the callback for bounce buffer mode // 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) { if (rgb_panel->bb_size) {
gdma_tx_event_callbacks_t cbs = { gdma_tx_event_callbacks_t cbs = {
.on_trans_eof = lcd_rgb_panel_eof_handler, .on_trans_eof = lcd_rgb_panel_eof_handler,
}; };
gdma_register_tx_event_callbacks(panel->dma_chan, &cbs, panel); gdma_register_tx_event_callbacks(rgb_panel->dma_chan, &cbs, rgb_panel);
} }
return ESP_OK; return ESP_OK;
} }
// If we restart GDMA, many pixels already have been transferred to the LCD peripheral. // If we restart GDMA, the data sent to the LCD peripheral needs to start LCD_FIFO_PRESERVE_SIZE_PX pixels after the FB start
// Looks like that has 16 pixels of FIFO plus one holding register. // so we use a dedicated DMA link (called restart link) to restart the transaction
#define LCD_FIFO_PRESERVE_SIZE_PX (LCD_LL_FIFO_DEPTH + 1) #define LCD_FIFO_PRESERVE_SIZE_PX (LCD_LL_FIFO_DEPTH + 1)
static void lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *panel) static esp_err_t lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *rgb_panel)
{ {
for (int i = 0; i < RGB_LCD_PANEL_DMA_LINKS_REPLICA; i++) { // the restart link shares the same buffer with the frame/bounce buffer but start from a different offset
panel->dma_links[i] = &panel->dma_nodes[panel->num_dma_nodes * i]; int restart_skip_bytes = LCD_FIFO_PRESERVE_SIZE_PX * (rgb_panel->fb_bits_per_pixel / 8);
} if (rgb_panel->bb_size) {
// chain DMA descriptors // DMA is used to convey the bounce buffer
for (int i = 0; i < panel->num_dma_nodes * RGB_LCD_PANEL_DMA_LINKS_REPLICA; i++) { size_t num_dma_nodes_per_bounce_buffer = (rgb_panel->bb_size + LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1) / LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
panel->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU; gdma_link_list_config_t link_cfg = {
panel->dma_nodes[i].next = &panel->dma_nodes[i + 1]; .buffer_alignment = rgb_panel->int_mem_align,
} .item_alignment = LCD_GDMA_DESCRIPTOR_ALIGN,
.num_items = num_dma_nodes_per_bounce_buffer * RGB_LCD_PANEL_BOUNCE_BUF_NUM,
.flags = {
.check_owner = true,
}
};
ESP_RETURN_ON_ERROR(gdma_new_link_list(&link_cfg, &rgb_panel->dma_bb_link), TAG, "create bounce buffer DMA link failed");
// mount bounce buffers to the DMA link list
gdma_buffer_mount_config_t mount_cfgs[RGB_LCD_PANEL_BOUNCE_BUF_NUM] = {0};
for (int i = 0; i < RGB_LCD_PANEL_BOUNCE_BUF_NUM; i++) {
mount_cfgs[i].buffer = rgb_panel->bounce_buffer[i];
mount_cfgs[i].length = rgb_panel->bb_size;
mount_cfgs[i].flags.mark_eof = true; // we use the DMA EOF interrupt to copy the frame buffer (partially) to the bounce buffer
}
ESP_RETURN_ON_ERROR(gdma_link_mount_buffers(rgb_panel->dma_bb_link, 0, mount_cfgs, RGB_LCD_PANEL_BOUNCE_BUF_NUM, NULL),
TAG, "mount DMA bounce buffers failed");
// create restart link
gdma_link_list_config_t restart_link_cfg = {
.buffer_alignment = rgb_panel->int_mem_align,
.item_alignment = LCD_GDMA_DESCRIPTOR_ALIGN,
.num_items = 1, // the restart link only contains one node
.flags = {
.check_owner = true,
}
};
ESP_RETURN_ON_ERROR(gdma_new_link_list(&restart_link_cfg, &rgb_panel->dma_restart_link), TAG, "create DMA restart link list failed");
gdma_buffer_mount_config_t restart_buffer_mount_cfg = {
.buffer = rgb_panel->bounce_buffer[0] + restart_skip_bytes,
.length = MIN(LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE, rgb_panel->bb_size) - restart_skip_bytes,
};
ESP_RETURN_ON_ERROR(gdma_link_mount_buffers(rgb_panel->dma_restart_link, 0, &restart_buffer_mount_cfg, 1, NULL),
TAG, "mount DMA restart buffer failed");
if (panel->bb_size) { // Magic here: we use the restart link to restart the bounce buffer link list, so concat them
// loop end back to start gdma_link_concat(rgb_panel->dma_restart_link, 0, rgb_panel->dma_bb_link, 1);
panel->dma_nodes[panel->num_dma_nodes * RGB_LCD_PANEL_BOUNCE_BUF_NUM - 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 { } else {
if (panel->flags.stream_mode) { // DMA is used to convey the frame buffer
// circle DMA descriptors chain for each frame buffer size_t num_dma_nodes = (rgb_panel->fb_size + LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1) / LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
for (int i = 0; i < RGB_LCD_PANEL_DMA_LINKS_REPLICA; i++) { gdma_link_list_config_t link_cfg = {
panel->dma_nodes[panel->num_dma_nodes * (i + 1) - 1].next = &panel->dma_nodes[panel->num_dma_nodes * i]; .buffer_alignment = rgb_panel->flags.fb_in_psram ? rgb_panel->ext_mem_align : rgb_panel->int_mem_align,
} .item_alignment = LCD_GDMA_DESCRIPTOR_ALIGN,
} else { .num_items = num_dma_nodes,
// one-off DMA descriptors chain .flags = {
for (int i = 0; i < RGB_LCD_PANEL_DMA_LINKS_REPLICA; i++) { .check_owner = true,
panel->dma_nodes[panel->num_dma_nodes * (i + 1) - 1].next = NULL; },
} };
} gdma_buffer_mount_config_t mount_cfg = {
// mount the frame buffer to the DMA descriptors .length = rgb_panel->fb_size,
for (size_t i = 0; i < panel->num_fbs; i++) { .flags = {
lcd_com_mount_dma_data(panel->dma_links[i], panel->fbs[i], panel->fb_size); .mark_final = rgb_panel->flags.stream_mode ? false : true,
.mark_eof = true,
},
};
for (size_t i = 0; i < rgb_panel->num_fbs; i++) {
ESP_RETURN_ON_ERROR(gdma_new_link_list(&link_cfg, &rgb_panel->dma_fb_links[i]), TAG, "create frame buffer DMA link failed");
// mount bounce buffers to the DMA link list
mount_cfg.buffer = rgb_panel->fbs[i];
ESP_RETURN_ON_ERROR(gdma_link_mount_buffers(rgb_panel->dma_fb_links[i], 0, &mount_cfg, 1, NULL),
TAG, "mount DMA frame buffer failed");
} }
// create restart link
gdma_link_list_config_t restart_link_cfg = {
.buffer_alignment = rgb_panel->flags.fb_in_psram ? rgb_panel->ext_mem_align : rgb_panel->int_mem_align,
.item_alignment = LCD_GDMA_DESCRIPTOR_ALIGN,
.num_items = 1, // the restart link only contains one node
.flags = {
.check_owner = true,
}
};
ESP_RETURN_ON_ERROR(gdma_new_link_list(&restart_link_cfg, &rgb_panel->dma_restart_link), TAG, "create DMA restart link list failed");
gdma_buffer_mount_config_t restart_buffer_mount_cfg = {
.buffer = rgb_panel->fbs[0] + restart_skip_bytes,
.length = MIN(LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE, rgb_panel->fb_size) - restart_skip_bytes,
};
ESP_RETURN_ON_ERROR(gdma_link_mount_buffers(rgb_panel->dma_restart_link, 0, &restart_buffer_mount_cfg, 1, NULL),
TAG, "mount DMA restart buffer failed");
// Magic here: we use the restart link to restart the frame buffer link list, so concat them
gdma_link_concat(rgb_panel->dma_restart_link, 0, rgb_panel->dma_fb_links[0], 1);
} }
// On restart, the data sent to the LCD peripheral needs to start LCD_FIFO_PRESERVE_SIZE_PX pixels after the FB start return ESP_OK;
// so we use a dedicated DMA node to restart the DMA transaction
// see also `lcd_rgb_panel_try_restart_transmission`
memcpy(&panel->dma_restart_node, &panel->dma_nodes[0], sizeof(panel->dma_restart_node));
int restart_skip_bytes = LCD_FIFO_PRESERVE_SIZE_PX * (panel->fb_bits_per_pixel / 8);
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;
} }
// reset the GDMA channel every VBlank to stop permanent desyncs from happening. // reset the GDMA channel every VBlank to stop permanent desyncs from happening.
@ -1092,7 +1140,7 @@ static IRAM_ATTR void lcd_rgb_panel_try_restart_transmission(esp_rgb_panel_t *pa
gdma_reset(panel->dma_chan); gdma_reset(panel->dma_chan);
// restart the DMA by a special DMA node // restart the DMA by a special DMA node
gdma_start(panel->dma_chan, (intptr_t)&panel->dma_restart_node); gdma_start(panel->dma_chan, gdma_link_get_head_addr(panel->dma_restart_link));
if (panel->bb_size) { if (panel->bb_size) {
// Fill 2nd bounce buffer while 1st is being sent out, if needed. // Fill 2nd bounce buffer while 1st is being sent out, if needed.
@ -1118,7 +1166,11 @@ static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel)
} }
// the start of DMA should be prior to the start of LCD engine // 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]); if (rgb_panel->bb_size) {
gdma_start(rgb_panel->dma_chan, gdma_link_get_head_addr(rgb_panel->dma_bb_link));
} else {
gdma_start(rgb_panel->dma_chan, gdma_link_get_head_addr(rgb_panel->dma_fb_links[rgb_panel->cur_fb_index]));
}
// delay 1us is sufficient for DMA to pass data to LCD FIFO // 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 // in fact, this is only needed when LCD pixel clock is set too high
esp_rom_delay_us(1); esp_rom_delay_us(1);

View File

@ -78,29 +78,3 @@ void lcd_com_remove_device(lcd_com_device_type_t device_type, int member_id)
} }
} }
#endif // SOC_LCDCAM_SUPPORTED #endif // SOC_LCDCAM_SUPPORTED
void lcd_com_mount_dma_data(dma_descriptor_t *desc_head, const void *buffer, size_t len)
{
size_t prepared_length = 0;
uint8_t *data = (uint8_t *)buffer;
dma_descriptor_t *desc = desc_head;
while (len > DMA_DESCRIPTOR_BUFFER_MAX_SIZE) {
desc->dw0.suc_eof = 0; // not the end of the transaction
desc->dw0.size = DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
desc->dw0.length = DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
desc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
desc->buffer = &data[prepared_length];
desc = desc->next; // move to next descriptor
prepared_length += DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
len -= DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
}
if (len) {
desc->dw0.suc_eof = 1; // end of the transaction
desc->dw0.size = len;
desc->dw0.length = len;
desc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
desc->buffer = &data[prepared_length];
desc = desc->next; // move to next descriptor
prepared_length += len;
}
}

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -34,7 +34,7 @@ extern "C" {
#define TEST_LCD_DATA15_GPIO 40 // R4 #define TEST_LCD_DATA15_GPIO 40 // R4
#define TEST_LCD_DISP_EN_GPIO -1 #define TEST_LCD_DISP_EN_GPIO -1
#define TEST_LCD_PIXEL_CLOCK_HZ (10 * 1000 * 1000) #define TEST_LCD_PIXEL_CLOCK_HZ (18 * 1000 * 1000)
#ifdef __cplusplus #ifdef __cplusplus
} }