Merge branch 'feature/mipi_dpi_2ddma' into 'master'

Use DMA2D in MIPI DSI driver

See merge request espressif/esp-idf!28605
This commit is contained in:
morris
2024-01-31 11:25:32 +08:00
11 changed files with 491 additions and 29 deletions
+4
View File
@@ -14,6 +14,10 @@ set(includes "include" "interface")
set(priv_requires "esp_mm" "esp_psram" "esp_pm" "esp_driver_spi" "esp_driver_i2s") set(priv_requires "esp_mm" "esp_psram" "esp_pm" "esp_driver_spi" "esp_driver_i2s")
set(public_requires "driver" "esp_driver_gpio" "esp_driver_i2c") set(public_requires "driver" "esp_driver_gpio" "esp_driver_i2c")
if(CONFIG_SOC_DMA2D_SUPPORTED)
list(APPEND srcs "src/esp_async_fbcpy.c")
endif()
if(CONFIG_SOC_I2C_SUPPORTED) if(CONFIG_SOC_I2C_SUPPORTED)
list(APPEND srcs "i2c/esp_lcd_panel_io_i2c_v1.c" "i2c/esp_lcd_panel_io_i2c_v2.c") list(APPEND srcs "i2c/esp_lcd_panel_io_i2c_v1.c" "i2c/esp_lcd_panel_io_i2c_v2.c")
endif() endif()
+106 -14
View File
@@ -5,6 +5,7 @@
*/ */
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/semphr.h"
#include "soc/soc_caps.h" #include "soc/soc_caps.h"
#include "esp_check.h" #include "esp_check.h"
#include "esp_lcd_panel_interface.h" #include "esp_lcd_panel_interface.h"
@@ -12,6 +13,7 @@
#include "esp_clk_tree.h" #include "esp_clk_tree.h"
#include "esp_cache.h" #include "esp_cache.h"
#include "mipi_dsi_priv.h" #include "mipi_dsi_priv.h"
#include "esp_async_fbcpy.h"
#include "esp_private/dw_gdma.h" #include "esp_private/dw_gdma.h"
#include "hal/cache_hal.h" #include "hal/cache_hal.h"
#include "hal/cache_ll.h" #include "hal/cache_ll.h"
@@ -33,10 +35,38 @@ struct esp_lcd_dpi_panel_t {
uint32_t v_pixels; // Vertical pixels uint32_t v_pixels; // Vertical pixels
size_t frame_buffer_size; // Frame buffer size size_t frame_buffer_size; // Frame buffer size
size_t bytes_per_pixel; // Bytes per pixel size_t bytes_per_pixel; // Bytes per pixel
lcd_color_rgb_pixel_format_t pixel_format; // RGB Pixel format
dw_gdma_channel_handle_t dma_chan; // DMA channel dw_gdma_channel_handle_t dma_chan; // DMA channel
dw_gdma_link_list_handle_t link_list; // DMA link list dw_gdma_link_list_handle_t link_list; // DMA link list
esp_async_fbcpy_handle_t fbcpy_handle; // Use DMA2D to do frame buffer copy
SemaphoreHandle_t draw_sem; // A semaphore used to synchronize the draw operations when DMA2D is used
esp_lcd_dpi_panel_color_trans_done_cb_t on_color_trans_done; // Callback invoked when color data transfer has finished
void* user_ctx; // User context for the callback
}; };
IRAM_ATTR
static bool async_fbcpy_done_cb(esp_async_fbcpy_handle_t mcp, esp_async_fbcpy_event_data_t *event, void *cb_args)
{
bool need_yield = false;
esp_lcd_dpi_panel_t* dpi_panel = (esp_lcd_dpi_panel_t*)cb_args;
// release the draw semaphore first
BaseType_t task_woken = pdFALSE;
xSemaphoreGiveFromISR(dpi_panel->draw_sem, &task_woken);
if (task_woken == pdTRUE) {
need_yield = true;
}
if (dpi_panel->on_color_trans_done) {
if (dpi_panel->on_color_trans_done(&dpi_panel->base, NULL, dpi_panel->user_ctx)) {
need_yield = true;
}
}
return need_yield;
}
IRAM_ATTR
static bool dma_list_invalid_block_cb(dw_gdma_channel_handle_t chan, const dw_gdma_break_event_data_t *event_data, void *user_data) static bool dma_list_invalid_block_cb(dw_gdma_channel_handle_t chan, const dw_gdma_break_event_data_t *event_data, void *user_data)
{ {
dw_gdma_lli_handle_t lli = event_data->invalid_lli; dw_gdma_lli_handle_t lli = event_data->invalid_lli;
@@ -104,15 +134,20 @@ esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_
esp_err_t ret = ESP_OK; esp_err_t ret = ESP_OK;
void *frame_buffer = NULL; void *frame_buffer = NULL;
esp_lcd_dpi_panel_t *dpi_panel = NULL; esp_lcd_dpi_panel_t *dpi_panel = NULL;
esp_async_fbcpy_handle_t fbcpy_ctx = NULL;
ESP_RETURN_ON_FALSE(bus && panel_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); ESP_RETURN_ON_FALSE(bus && panel_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(panel_config->virtual_channel < 4, ESP_ERR_INVALID_ARG, TAG, "invalid virtual channel %d", panel_config->virtual_channel); ESP_RETURN_ON_FALSE(panel_config->virtual_channel < 4, ESP_ERR_INVALID_ARG, TAG, "invalid virtual channel %d", panel_config->virtual_channel);
ESP_RETURN_ON_FALSE(panel_config->dpi_clock_freq_mhz, ESP_ERR_INVALID_ARG, TAG, "invalid DPI clock frequency %"PRIu32, panel_config->dpi_clock_freq_mhz); ESP_RETURN_ON_FALSE(panel_config->dpi_clock_freq_mhz, ESP_ERR_INVALID_ARG, TAG, "invalid DPI clock frequency %"PRIu32, panel_config->dpi_clock_freq_mhz);
#if !SOC_DMA2D_SUPPORTED
ESP_RETURN_ON_FALSE(!panel_config->flags.use_dma2d, ESP_ERR_NOT_SUPPORTED, TAG, "DMA2D is not supported");
#endif // !SOC_DMA2D_SUPPORTED
int bus_id = bus->bus_id; int bus_id = bus->bus_id;
mipi_dsi_hal_context_t *hal = &bus->hal; mipi_dsi_hal_context_t *hal = &bus->hal;
dpi_panel = heap_caps_calloc(1, sizeof(esp_lcd_dpi_panel_t), DSI_MEM_ALLOC_CAPS); dpi_panel = heap_caps_calloc(1, sizeof(esp_lcd_dpi_panel_t), DSI_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(dpi_panel, ESP_ERR_NO_MEM, err, TAG, "no memory for DPI panel"); ESP_GOTO_ON_FALSE(dpi_panel, ESP_ERR_NO_MEM, err, TAG, "no memory for DPI panel");
dpi_panel->virtual_channel = panel_config->virtual_channel; dpi_panel->virtual_channel = panel_config->virtual_channel;
dpi_panel->pixel_format = panel_config->pixel_format;
dpi_panel->bus = bus; dpi_panel->bus = bus;
// allocate frame buffer from PSRAM // allocate frame buffer from PSRAM
@@ -142,6 +177,17 @@ esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_
// preset the frame buffer with black color // preset the frame buffer with black color
ESP_GOTO_ON_ERROR(esp_cache_msync(frame_buffer, frame_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M), err, TAG, "cache write back failed"); ESP_GOTO_ON_ERROR(esp_cache_msync(frame_buffer, frame_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M), err, TAG, "cache write back failed");
#if SOC_DMA2D_SUPPORTED
if (panel_config->flags.use_dma2d) {
esp_async_fbcpy_config_t fbcpy_config = {};
ESP_GOTO_ON_ERROR(esp_async_fbcpy_install(&fbcpy_config, &fbcpy_ctx), err, TAG, "install async memcpy 2d failed");
dpi_panel->fbcpy_handle = fbcpy_ctx;
dpi_panel->draw_sem = xSemaphoreCreateBinaryWithCaps(DSI_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(dpi_panel->draw_sem, ESP_ERR_NO_MEM, err, TAG, "no memory for draw semaphore");
xSemaphoreGive(dpi_panel->draw_sem);
}
#endif // SOC_DMA2D_SUPPORTED
// if the clock source is not assigned, fallback to the default clock source // if the clock source is not assigned, fallback to the default clock source
mipi_dsi_dpi_clock_source_t dpi_clk_src = panel_config->dpi_clk_src; mipi_dsi_dpi_clock_source_t dpi_clk_src = panel_config->dpi_clk_src;
if (dpi_clk_src == 0) { if (dpi_clk_src == 0) {
@@ -211,9 +257,6 @@ err:
if (dpi_panel) { if (dpi_panel) {
dpi_panel_del(&dpi_panel->base); dpi_panel_del(&dpi_panel->base);
} }
if (frame_buffer) {
free(frame_buffer);
}
return ret; return ret;
} }
@@ -229,6 +272,7 @@ static esp_err_t dpi_panel_del(esp_lcd_panel_t *panel)
} }
// disable the DSI bridge // disable the DSI bridge
mipi_dsi_brg_ll_enable(hal->bridge, false); mipi_dsi_brg_ll_enable(hal->bridge, false);
// free memory
if (dpi_panel->dma_chan) { if (dpi_panel->dma_chan) {
dw_gdma_del_channel(dpi_panel->dma_chan); dw_gdma_del_channel(dpi_panel->dma_chan);
} }
@@ -238,6 +282,12 @@ static esp_err_t dpi_panel_del(esp_lcd_panel_t *panel)
if (dpi_panel->link_list) { if (dpi_panel->link_list) {
dw_gdma_del_link_list(dpi_panel->link_list); dw_gdma_del_link_list(dpi_panel->link_list);
} }
if (dpi_panel->fbcpy_handle) {
esp_async_fbcpy_uninstall(dpi_panel->fbcpy_handle);
}
if (dpi_panel->draw_sem) {
vSemaphoreDelete(dpi_panel->draw_sem);
}
free(dpi_panel); free(dpi_panel);
return ESP_OK; return ESP_OK;
} }
@@ -293,18 +343,50 @@ static esp_err_t dpi_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
uint8_t *frame_buffer = dpi_panel->frame_buffer; uint8_t *frame_buffer = dpi_panel->frame_buffer;
size_t frame_buffer_size = dpi_panel->frame_buffer_size; size_t frame_buffer_size = dpi_panel->frame_buffer_size;
// TODO: memory copy by 2D-DMA if (!dpi_panel->fbcpy_handle) {
size_t bytes_per_pixel = dpi_panel->bytes_per_pixel; size_t bytes_per_pixel = dpi_panel->bytes_per_pixel;
const uint8_t *from = (const uint8_t *)color_data; const uint8_t *from = (const uint8_t *)color_data;
uint8_t *to = frame_buffer + (y_start * dpi_panel->h_pixels + x_start) * bytes_per_pixel; uint8_t *to = frame_buffer + (y_start * dpi_panel->h_pixels + x_start) * bytes_per_pixel;
uint32_t copy_bytes_per_line = (x_end - x_start) * bytes_per_pixel; uint32_t copy_bytes_per_line = (x_end - x_start) * bytes_per_pixel;
uint32_t bytes_per_line = bytes_per_pixel * dpi_panel->h_pixels; uint32_t bytes_per_line = bytes_per_pixel * dpi_panel->h_pixels;
for (int y = y_start; y < y_end; y++) { for (int y = y_start; y < y_end; y++) {
memcpy(to, from, copy_bytes_per_line); memcpy(to, from, copy_bytes_per_line);
to += bytes_per_line; to += bytes_per_line;
from += copy_bytes_per_line; from += copy_bytes_per_line;
}
ESP_RETURN_ON_ERROR(esp_cache_msync(frame_buffer, frame_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M), TAG, "cache write back failed");
// invoke the trans done callback
if (dpi_panel->on_color_trans_done) {
dpi_panel->on_color_trans_done(&dpi_panel->base, NULL, dpi_panel->user_ctx);
}
} else {
// endure the previous draw operation has finish
ESP_RETURN_ON_FALSE(xSemaphoreTake(dpi_panel->draw_sem, 0) == pdTRUE, ESP_ERR_INVALID_STATE, TAG, "previous draw operation is not finished");
// write back the user's draw buffer, so that the DMA can see the correct data
size_t color_data_size = (x_end - x_start) * (y_end - y_start) * dpi_panel->bytes_per_pixel;
esp_cache_msync((void*)color_data, color_data_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
esp_async_fbcpy_trans_desc_t fbcpy_trans_config = {
.src_buffer = color_data,
.dst_buffer = dpi_panel->frame_buffer,
.src_buffer_size_x = x_end - x_start,
.src_buffer_size_y = y_end - y_start,
.dst_buffer_size_x = dpi_panel->h_pixels,
.dst_buffer_size_y = dpi_panel->v_pixels,
.src_offset_x = 0,
.src_offset_y = 0,
.dst_offset_x = x_start,
.dst_offset_y = y_start,
.copy_size_x = x_end - x_start,
.copy_size_y = y_end - y_start,
.pixel_format_unique_id = {
.color_space = COLOR_SPACE_RGB,
.pixel_format = dpi_panel->pixel_format,
},
};
ESP_RETURN_ON_ERROR(esp_async_fbcpy(dpi_panel->fbcpy_handle, &fbcpy_trans_config, async_fbcpy_done_cb, dpi_panel), TAG, "async memcpy failed");
} }
ESP_RETURN_ON_ERROR(esp_cache_msync(frame_buffer, frame_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M), TAG, "cache write back failed");
return ESP_OK; return ESP_OK;
} }
@@ -333,3 +415,13 @@ esp_err_t esp_lcd_dpi_panel_set_pattern(esp_lcd_panel_handle_t panel, mipi_dsi_p
return ESP_OK; return ESP_OK;
} }
esp_err_t esp_lcd_dpi_panel_register_event_callbacks(esp_lcd_panel_handle_t panel, const esp_lcd_dpi_panel_event_callbacks_t *cbs, void *user_ctx)
{
ESP_RETURN_ON_FALSE(panel && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
esp_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base);
dpi_panel->on_color_trans_done = cbs->on_color_trans_done;
dpi_panel->user_ctx = user_ctx;
return ESP_OK;
}
@@ -83,8 +83,12 @@ typedef struct {
uint8_t virtual_channel; /*!< Virtual channel ID, index from 0 */ uint8_t virtual_channel; /*!< Virtual channel ID, index from 0 */
mipi_dsi_dpi_clock_source_t dpi_clk_src; /*!< MIPI DSI DPI clock source */ mipi_dsi_dpi_clock_source_t dpi_clk_src; /*!< MIPI DSI DPI clock source */
uint32_t dpi_clock_freq_mhz; /*!< DPI clock frequency in MHz */ uint32_t dpi_clock_freq_mhz; /*!< DPI clock frequency in MHz */
lcd_color_rgb_pixel_format_t pixel_format; /*!< Pixel format */ lcd_color_rgb_pixel_format_t pixel_format; /*!< Pixel format that used by the MIPI LCD device */
esp_lcd_video_timing_t video_timing; /*!< Video timing */ esp_lcd_video_timing_t video_timing; /*!< Video timing */
/// Extra configuration flags for MIPI DSI DPI panel
struct extra_flags {
uint32_t use_dma2d: 1; /*!< Use DMA2D to copy user buffer to the frame buffer when necessary */
} flags; /*!< Extra configuration flags */
} esp_lcd_dpi_panel_config_t; } esp_lcd_dpi_panel_config_t;
/** /**
@@ -97,6 +101,7 @@ typedef struct {
* - ESP_OK: Create MIPI DSI data panel successfully * - ESP_OK: Create MIPI DSI data panel successfully
* - ESP_ERR_INVALID_ARG: Create MIPI DSI data panel failed because of invalid argument * - ESP_ERR_INVALID_ARG: Create MIPI DSI data panel failed because of invalid argument
* - ESP_ERR_NO_MEM: Create MIPI DSI data panel failed because of out of memory * - ESP_ERR_NO_MEM: Create MIPI DSI data panel failed because of out of memory
* - ESP_ERR_NOT_SUPPORTED: Create MIPI DSI data panel failed because of unsupported feature
* - ESP_FAIL: Create MIPI DSI data panel failed because of other error * - ESP_FAIL: Create MIPI DSI data panel failed because of other error
*/ */
esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_panel_config_t *panel_config, esp_lcd_panel_handle_t *ret_panel); esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_panel_config_t *panel_config, esp_lcd_panel_handle_t *ret_panel);
@@ -104,14 +109,49 @@ esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_
/** /**
* @brief Set pre-defined pattern to the screen for testing or debugging purpose * @brief Set pre-defined pattern to the screen for testing or debugging purpose
* *
* @param[in] dbi_panel MIPI DBI panel handle, returned from `esp_lcd_new_panel_dpi` * @param[in] dpi_panel MIPI DPI panel handle, returned from esp_lcd_new_panel_dpi()
* @param[in] pattern Pattern type * @param[in] pattern Pattern type
* @return * @return
* - ESP_OK: Set pattern successfully * - ESP_OK: Set pattern successfully
* - ESP_ERR_INVALID_ARG: Set pattern failed because of invalid argument * - ESP_ERR_INVALID_ARG: Set pattern failed because of invalid argument
* - ESP_FAIL: Set pattern failed because of other error * - ESP_FAIL: Set pattern failed because of other error
*/ */
esp_err_t esp_lcd_dpi_panel_set_pattern(esp_lcd_panel_handle_t dbi_panel, mipi_dsi_pattern_type_t pattern); esp_err_t esp_lcd_dpi_panel_set_pattern(esp_lcd_panel_handle_t dpi_panel, mipi_dsi_pattern_type_t pattern);
/**
* @brief Type of LCD DPI panel event data
*/
typedef struct {
} esp_lcd_dpi_panel_event_data_t;
/**
* @brief Declare the prototype of the function that will be invoked when DPI panel finishes transferring color data
*
* @param[in] panel LCD panel handle, which is created by factory API like esp_lcd_new_panel_dpi()
* @param[in] edata DPI panel event data, fed by driver
* @param[in] user_ctx User data
* @return Whether a high priority task has been waken up by this function
*/
typedef bool (*esp_lcd_dpi_panel_color_trans_done_cb_t)(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx);
/**
* @brief Type of LCD DPI panel callbacks
*/
typedef struct {
esp_lcd_dpi_panel_color_trans_done_cb_t on_color_trans_done; /*!< Callback invoked when color data transfer has finished */
} esp_lcd_dpi_panel_event_callbacks_t;
/**
* @brief Register LCD DPI panel callbacks
*
* @param[in] dpi_panel LCD DPI panel handle, which is returned from esp_lcd_new_panel_dpi()
* @param[in] cbs structure with all LCD panel callbacks
* @param[in] user_ctx User private data, passed directly to callback's user_ctx
* @return
* - ESP_ERR_INVALID_ARG: Register callbacks failed because of invalid argument
* - ESP_OK: Register callbacks successfully
*/
esp_err_t esp_lcd_dpi_panel_register_event_callbacks(esp_lcd_panel_handle_t dpi_panel, const esp_lcd_dpi_panel_event_callbacks_t *cbs, void *user_ctx);
#ifdef __cplusplus #ifdef __cplusplus
} }
@@ -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
*/ */
@@ -33,15 +33,7 @@
#define LCD_CMD_VSCRDEF 0x33 // Vertical scrolling definition #define LCD_CMD_VSCRDEF 0x33 // Vertical scrolling definition
#define LCD_CMD_TEOFF 0x34 // Turns off tearing effect #define LCD_CMD_TEOFF 0x34 // Turns off tearing effect
#define LCD_CMD_TEON 0x35 // Turns on tearing effect #define LCD_CMD_TEON 0x35 // Turns on tearing effect
#define LCD_CMD_MADCTL 0x36 // Memory data access control
#define LCD_CMD_MADCTL 0x36 // Memory data access control
#define LCD_CMD_MH_BIT (1 << 2) // Display data latch order, 0: refresh left to right, 1: refresh right to left
#define LCD_CMD_BGR_BIT (1 << 3) // RGB/BGR order, 0: RGB, 1: BGR
#define LCD_CMD_ML_BIT (1 << 4) // Line address order, 0: refresh top to bottom, 1: refresh bottom to top
#define LCD_CMD_MV_BIT (1 << 5) // Row/Column order, 0: normal mode, 1: reverse mode
#define LCD_CMD_MX_BIT (1 << 6) // Column address order, 0: left to right, 1: right to left
#define LCD_CMD_MY_BIT (1 << 7) // Row address order, 0: top to bottom, 1: bottom to top
#define LCD_CMD_VSCSAD 0x37 // Vertical scroll start address #define LCD_CMD_VSCSAD 0x37 // Vertical scroll start address
#define LCD_CMD_IDMOFF 0x38 // Recover from IDLE mode #define LCD_CMD_IDMOFF 0x38 // Recover from IDLE mode
#define LCD_CMD_IDMON 0x39 // Fall into IDLE mode (8 color depth is displayed) #define LCD_CMD_IDMON 0x39 // Fall into IDLE mode (8 color depth is displayed)
@@ -52,3 +44,13 @@
#define LCD_CMD_GDCAN 0x45 // Get scan line #define LCD_CMD_GDCAN 0x45 // Get scan line
#define LCD_CMD_WRDISBV 0x51 // Write display brightness #define LCD_CMD_WRDISBV 0x51 // Write display brightness
#define LCD_CMD_RDDISBV 0x52 // Read display brightness value #define LCD_CMD_RDDISBV 0x52 // Read display brightness value
/////////// Warning, It turns out that, the following bitmask is not defined as a standard, some manufacturer may use different bit position
/////////// Please check the datasheet of your LCD panel before using the following bitmask
/////////// IDF will remove them in the next major release (esp-idf 6.0)
#define LCD_CMD_MH_BIT (1 << 2) // Display data latch order, 0: refresh left to right, 1: refresh right to left
#define LCD_CMD_BGR_BIT (1 << 3) // RGB/BGR order, 0: RGB, 1: BGR
#define LCD_CMD_ML_BIT (1 << 4) // Line address order, 0: refresh top to bottom, 1: refresh bottom to top
#define LCD_CMD_MV_BIT (1 << 5) // Row/Column order, 0: normal mode, 1: reverse mode
#define LCD_CMD_MX_BIT (1 << 6) // Column address order, 0: left to right, 1: right to left
#define LCD_CMD_MY_BIT (1 << 7) // Row address order, 0: top to bottom, 1: bottom to top
@@ -0,0 +1,90 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include "hal/color_types.h"
/**
* @brief Async FrameBuffer copy context
*/
typedef struct esp_async_fbcpy_context_t *esp_async_fbcpy_handle_t;
/**
* @brief Async FrameBuffer copy configuration
*/
typedef struct {
} esp_async_fbcpy_config_t;
/**
* @brief Install Async FrameBuffer copy driver
*
* @param config Async FrameBuffer copy configuration
* @param mcp Returned Async FrameBuffer copy handle
* @return
* - ESP_OK: Install Async FrameBuffer copy driver successfully
* - ESP_ERR_INVALID_ARG: Install Async FrameBuffer copy driver failed because of invalid argument
* - ESP_ERR_NO_MEM: Install Async FrameBuffer copy driver failed because of out of memory
* - ESP_FAIL: Install Async FrameBuffer copy driver failed because of other error
*/
esp_err_t esp_async_fbcpy_install(const esp_async_fbcpy_config_t *config, esp_async_fbcpy_handle_t *mcp);
/**
* @brief Uninstall Async FrameBuffer copy driver
*
* @param mcp Async FrameBuffer copy handle
* @return
* - ESP_OK: Uninstall Async FrameBuffer copy driver successfully
* - ESP_ERR_INVALID_ARG: Uninstall Async FrameBuffer copy driver failed because of invalid argument
* - ESP_FAIL: Uninstall Async FrameBuffer copy driver failed because of other error
*/
esp_err_t esp_async_fbcpy_uninstall(esp_async_fbcpy_handle_t mcp);
/**
* @brief Async FrameBuffer copy transaction descriptor
*/
typedef struct {
const void *src_buffer; /*!< Source buffer */
void *dst_buffer; /*!< Destination buffer */
size_t src_buffer_size_x; /*!< Source buffer size in x direction, size count in the number of pixels */
size_t src_buffer_size_y; /*!< Source buffer size in y direction, size count in the number of pixels */
size_t dst_buffer_size_x; /*!< Destination buffer size in x direction, size count in the number of pixels */
size_t dst_buffer_size_y; /*!< Destination buffer size in y direction, size count in the number of pixels */
size_t src_offset_x; /*!< Copy action will start from this offset in source buffer in the x direction, offset count in the number of pixels */
size_t src_offset_y; /*!< Copy action will start from this offset in source buffer in the y direction, offset count in the number of pixels */
size_t dst_offset_x; /*!< Copy action will start from this offset in destination buffer in the x direction, offset count in the number of pixels */
size_t dst_offset_y; /*!< Copy action will start from this offset in destination buffer in the y direction, offset count in the number of pixels */
size_t copy_size_x; /*!< Copy size in the x direction, size count in the number of pixels */
size_t copy_size_y; /*!< Copy size in the y direction, size count in the number of pixels */
color_space_pixel_format_t pixel_format_unique_id; /*!< Pixel format unique ID */
} esp_async_fbcpy_trans_desc_t;
/**
* @brief Async FrameBuffer copy event data
*/
typedef struct {
} esp_async_fbcpy_event_data_t;
/**
* @brief Async FrameBuffer copy event callback prototype
*/
typedef bool (*esp_async_fbcpy_event_callback_t)(esp_async_fbcpy_handle_t mcp, esp_async_fbcpy_event_data_t *event_data, void *cb_args);
/**
* @brief Start Async FrameBuffer copy transaction
*
* @param mcp Async FrameBuffer copy handle
* @param transaction Async FrameBuffer copy transaction descriptor
* @param memcpy_done_cb Callback function that will be invoked when Async FrameBuffer copy transaction finishes
* @param cb_args User data
* @return
* - ESP_OK: Start Async FrameBuffer copy transaction successfully
* - ESP_ERR_INVALID_ARG: Start Async FrameBuffer copy transaction failed because of invalid argument
* - ESP_FAIL: Start Async FrameBuffer copy transaction failed because of other error
*/
esp_err_t esp_async_fbcpy(esp_async_fbcpy_handle_t mcp, esp_async_fbcpy_trans_desc_t* transaction,
esp_async_fbcpy_event_callback_t memcpy_done_cb, void *cb_args);
+204
View File
@@ -0,0 +1,204 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_check.h"
#include "esp_cache.h"
#include "esp_heap_caps.h"
#include "soc/dma2d_channel.h"
#include "hal/cache_hal.h"
#include "hal/cache_ll.h"
#include "hal/dma2d_ll.h"
#include "esp_private/dma2d.h"
#include "esp_async_fbcpy.h"
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
static const char *TAG = "async_fbcpy";
typedef struct esp_async_fbcpy_context_t {
dma2d_pool_handle_t client; // DMA2D client
dma2d_descriptor_t* tx_desc; // DMA2D TX descriptor
dma2d_descriptor_t* rx_desc; // DMA2D RX descriptor
dma2d_trans_t* trans_desc; // DMA2D transaction descriptor
size_t dma_desc_size; // DMA2D descriptor size
esp_async_fbcpy_event_callback_t memcpy_done_cb; // memory copy done callback
void *cb_args; // callback arguments
} esp_async_fbcpy_context_t;
static esp_err_t async_fbcpy_del_context(esp_async_fbcpy_context_t* ctx)
{
if (ctx->tx_desc) {
free(ctx->tx_desc);
}
if (ctx->rx_desc) {
free(ctx->rx_desc);
}
if (ctx->trans_desc) {
free(ctx->trans_desc);
}
if (ctx->client) {
dma2d_release_pool(ctx->client);
}
free(ctx);
return ESP_OK;
}
esp_err_t esp_async_fbcpy_install(const esp_async_fbcpy_config_t *config, esp_async_fbcpy_handle_t *mcp)
{
esp_err_t ret = ESP_OK;
esp_async_fbcpy_context_t *ctx = NULL;
dma2d_trans_t* trans_desc = NULL;
dma2d_descriptor_t* dma_tx_desc = NULL;
dma2d_descriptor_t* dma_rx_desc = NULL;
dma2d_pool_handle_t dma2d_client = NULL;
ESP_RETURN_ON_FALSE(config && mcp, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
// allocate context memory
ctx = heap_caps_calloc(1, sizeof(esp_async_fbcpy_context_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ESP_GOTO_ON_FALSE(ctx, ESP_ERR_NO_MEM, err, TAG, "no mem for esp_async_fbcpy_context_t");
// according to the dma2d design, the transaction descriptor is also saved by the user
trans_desc = heap_caps_calloc(1, dma2d_get_trans_elm_size(), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ESP_GOTO_ON_FALSE(trans_desc, ESP_ERR_NO_MEM, err, TAG, "no mem for trans_desc");
ctx->trans_desc = trans_desc;
// allocate memory for DMA descriptor, the descriptor must be allocated from the internal memory, and alignment to the cache line size
uint32_t data_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
size_t alignment = MAX(DMA2D_LL_DESC_ALIGNMENT, data_cache_line_size);
size_t dma_desc_mem_size = ALIGN_UP(sizeof(dma2d_descriptor_align8_t), alignment);
dma_tx_desc = heap_caps_aligned_calloc(alignment, 1, dma_desc_mem_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
dma_rx_desc = heap_caps_aligned_calloc(alignment, 1, dma_desc_mem_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ESP_GOTO_ON_FALSE(dma_tx_desc && dma_rx_desc, ESP_ERR_NO_MEM, err, TAG, "no memory for DMA2D descriptors");
ctx->tx_desc = dma_tx_desc;
ctx->rx_desc = dma_rx_desc;
ctx->dma_desc_size = dma_desc_mem_size;
// initialize DMA2D client
dma2d_pool_config_t dma2d_client_config = {}; // all follow default configurations
ESP_GOTO_ON_ERROR(dma2d_acquire_pool(&dma2d_client_config, &dma2d_client), err, TAG, "create DMA2D client failed");
ctx->client = dma2d_client;
*mcp = ctx;
return ESP_OK;
err:
if (ctx) {
async_fbcpy_del_context(ctx);
}
return ret;
}
esp_err_t esp_async_fbcpy_uninstall(esp_async_fbcpy_handle_t mcp)
{
ESP_RETURN_ON_FALSE(mcp, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return async_fbcpy_del_context(mcp);
}
static void async_memcpy_setup_dma2d_descriptor(esp_async_fbcpy_context_t* mcp_ctx, esp_async_fbcpy_trans_desc_t* transaction)
{
dma2d_descriptor_t* tx_desc = mcp_ctx->tx_desc;
dma2d_descriptor_t* rx_desc = mcp_ctx->rx_desc;
size_t dma_desc_size = mcp_ctx->dma_desc_size;
uint8_t dma2d_pbyte = dma2d_desc_pixel_format_to_pbyte_value(transaction->pixel_format_unique_id);
tx_desc->buffer = (void*)transaction->src_buffer;
tx_desc->next = NULL;
tx_desc->dma2d_en = 1;
tx_desc->suc_eof = 1;
tx_desc->ha_length = transaction->src_buffer_size_x;
tx_desc->va_size = transaction->src_buffer_size_y;
tx_desc->hb_length = transaction->copy_size_x;
tx_desc->vb_size = transaction->copy_size_y;
tx_desc->x = transaction->src_offset_x;
tx_desc->y = transaction->src_offset_y;
tx_desc->pbyte = dma2d_pbyte;
tx_desc->mode = DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE;
tx_desc->owner = DMA2D_DESCRIPTOR_BUFFER_OWNER_DMA;
rx_desc->buffer = transaction->dst_buffer;
rx_desc->next = NULL;
rx_desc->dma2d_en = 1;
rx_desc->suc_eof = 1;
rx_desc->ha_length = transaction->dst_buffer_size_x;
rx_desc->va_size = transaction->dst_buffer_size_y;
rx_desc->hb_length = transaction->copy_size_x;
rx_desc->vb_size = transaction->copy_size_y;
rx_desc->x = transaction->dst_offset_x;
rx_desc->y = transaction->dst_offset_y;
rx_desc->pbyte = dma2d_pbyte;
rx_desc->mode = DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE;
rx_desc->owner = DMA2D_DESCRIPTOR_BUFFER_OWNER_DMA;
esp_cache_msync(tx_desc, dma_desc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE);
esp_cache_msync(rx_desc, dma_desc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE);
}
static bool dma2d_memcpy_done_cb(dma2d_channel_handle_t dma2d_chan, dma2d_event_data_t *event_data, void *user_data)
{
bool need_yield = false;
esp_async_fbcpy_context_t* mcp = (esp_async_fbcpy_context_t*)user_data;
if (mcp->memcpy_done_cb) {
need_yield = mcp->memcpy_done_cb(mcp, NULL, mcp->cb_args);
}
return need_yield;
}
static bool dma2d_job_picked_cb(uint32_t num_chans, const dma2d_trans_channel_info_t *dma2d_chans, void *user_data)
{
esp_async_fbcpy_context_t* mcp = (esp_async_fbcpy_context_t*)user_data;
dma2d_channel_handle_t tx_chan = NULL;
dma2d_channel_handle_t rx_chan = NULL;
for (uint32_t i = 0; i < num_chans; i++) {
if (dma2d_chans[i].dir == DMA2D_CHANNEL_DIRECTION_TX) {
tx_chan = dma2d_chans[i].chan;
}
if (dma2d_chans[i].dir == DMA2D_CHANNEL_DIRECTION_RX) {
rx_chan = dma2d_chans[i].chan;
}
}
dma2d_trigger_t trig_periph = {
.periph = DMA2D_TRIG_PERIPH_M2M,
.periph_sel_id = SOC_DMA2D_TRIG_PERIPH_M2M_TX,
};
dma2d_connect(tx_chan, &trig_periph);
trig_periph.periph_sel_id = SOC_DMA2D_TRIG_PERIPH_M2M_RX;
dma2d_connect(rx_chan, &trig_periph);
dma2d_rx_event_callbacks_t dma_cbs = {
.on_recv_eof = dma2d_memcpy_done_cb,
};
dma2d_register_rx_event_callbacks(rx_chan, &dma_cbs, mcp);
dma2d_set_desc_addr(tx_chan, (intptr_t)(mcp->tx_desc));
dma2d_set_desc_addr(rx_chan, (intptr_t)(mcp->rx_desc));
dma2d_start(tx_chan);
dma2d_start(rx_chan);
return false;
}
esp_err_t esp_async_fbcpy(esp_async_fbcpy_handle_t mcp, esp_async_fbcpy_trans_desc_t* transaction, esp_async_fbcpy_event_callback_t memcpy_done_cb, void *cb_args)
{
ESP_RETURN_ON_FALSE(mcp && transaction, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
mcp->memcpy_done_cb = memcpy_done_cb;
mcp->cb_args = cb_args;
// mount the data to the DMA descriptor
async_memcpy_setup_dma2d_descriptor(mcp, transaction);
// submit the DMA2D request
dma2d_trans_config_t dma2d_trans_conf = {
.tx_channel_num = 1,
.rx_channel_num = 1,
.channel_flags = DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING,
.on_job_picked = dma2d_job_picked_cb,
.user_config = mcp,
};
ESP_RETURN_ON_ERROR(dma2d_enqueue(mcp->client, &dma2d_trans_conf, mcp->trans_desc), TAG, "DMA2D enqueue failed");
return ESP_OK;
}
@@ -69,6 +69,8 @@ extern "C" {
// Peripheral selection ID register field width // Peripheral selection ID register field width
#define DMA2D_LL_CHANNEL_PERIPH_SEL_BIT_WIDTH (3) #define DMA2D_LL_CHANNEL_PERIPH_SEL_BIT_WIDTH (3)
#define DMA2D_LL_DESC_ALIGNMENT 8 // Descriptor must be aligned to 8 bytes
///////////////////////////////////// Common ///////////////////////////////////////// ///////////////////////////////////// Common /////////////////////////////////////////
/** /**
* @brief Enable the bus clock for 2D-DMA module * @brief Enable the bus clock for 2D-DMA module
@@ -45,6 +45,12 @@ The connection between ESP Board and the LCD is as follows:
Before testing your LCD, you also need to read your LCD spec carefully, and then adjust the values like "resolution" and "blank time" in the [main](./main/mipi_dsi_lcd_example_main.c) file. Before testing your LCD, you also need to read your LCD spec carefully, and then adjust the values like "resolution" and "blank time" in the [main](./main/mipi_dsi_lcd_example_main.c) file.
### Configure
Run `idf.py menuconfig` and go to `Example Configuration`:
* Choose whether to `Use DMA2D to copy draw buffer to frame buffer` asynchronously. If you choose `No`, the draw buffer will be copied to the frame buffer synchronously by CPU.
### Build and Flash ### Build and Flash
Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project. A LVGL widget should show up on the LCD as expected. Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project. A LVGL widget should show up on the LCD as expected.
@@ -0,0 +1,8 @@
menu "Example Configuration"
config EXAMPLE_USE_DMA2D_COPY_FRAME
bool "Use DMA2D to copy draw buffer to frame buffer"
default y
help
Enable this option, DMA2D will be used to copy the LVGL draw buffer to the target frame buffer.
This can save some CPU time and improve the performance.
endmenu
@@ -1,3 +1,3 @@
dependencies: dependencies:
lvgl/lvgl: "~8.3.0" lvgl/lvgl: "~8.3.0"
esp_lcd_ili9881c: ">=0.1.0" esp_lcd_ili9881c: "*"
@@ -75,7 +75,6 @@ static void example_lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_
int offsety2 = area->y2; int offsety2 = area->y2;
// pass the draw buffer to the driver // pass the draw buffer to the driver
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map); esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
lv_disp_flush_ready(drv);
} }
static void example_increase_lvgl_tick(void *arg) static void example_increase_lvgl_tick(void *arg)
@@ -117,6 +116,13 @@ static void example_lvgl_port_task(void *arg)
} }
} }
static bool example_notify_lvgl_flush_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
{
lv_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx;
lv_disp_flush_ready(disp_driver);
return false;
}
static void example_bsp_enable_dsi_phy_power(void) static void example_bsp_enable_dsi_phy_power(void)
{ {
// Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state // Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state
@@ -210,8 +216,16 @@ void app_main(void)
.vsync_pulse_width = EXAMPLE_MIPI_DSI_LCD_VSYNC, .vsync_pulse_width = EXAMPLE_MIPI_DSI_LCD_VSYNC,
.vsync_front_porch = EXAMPLE_MIPI_DSI_LCD_VFP, .vsync_front_porch = EXAMPLE_MIPI_DSI_LCD_VFP,
}, },
#if CONFIG_EXAMPLE_USE_DMA2D_COPY_FRAME
.flags.use_dma2d = true,
#endif
}; };
ESP_ERROR_CHECK(esp_lcd_new_panel_dpi(mipi_dsi_bus, &dpi_config, &mipi_dpi_panel)); ESP_ERROR_CHECK(esp_lcd_new_panel_dpi(mipi_dsi_bus, &dpi_config, &mipi_dpi_panel));
// register event callbacks
esp_lcd_dpi_panel_event_callbacks_t cbs = {
.on_color_trans_done = example_notify_lvgl_flush_ready,
};
ESP_ERROR_CHECK(esp_lcd_dpi_panel_register_event_callbacks(mipi_dpi_panel, &cbs, &disp_drv));
ESP_ERROR_CHECK(esp_lcd_panel_init(mipi_dpi_panel)); ESP_ERROR_CHECK(esp_lcd_panel_init(mipi_dpi_panel));
// turn on backlight // turn on backlight