diff --git a/components/esp_lcd/CMakeLists.txt b/components/esp_lcd/CMakeLists.txt index 6010623ef4..b35a4becd5 100644 --- a/components/esp_lcd/CMakeLists.txt +++ b/components/esp_lcd/CMakeLists.txt @@ -14,6 +14,10 @@ set(includes "include" "interface") 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") +if(CONFIG_SOC_DMA2D_SUPPORTED) + list(APPEND srcs "src/esp_async_fbcpy.c") +endif() + if(CONFIG_SOC_I2C_SUPPORTED) list(APPEND srcs "i2c/esp_lcd_panel_io_i2c_v1.c" "i2c/esp_lcd_panel_io_i2c_v2.c") endif() diff --git a/components/esp_lcd/priv_include/esp_async_fbcpy.h b/components/esp_lcd/priv_include/esp_async_fbcpy.h new file mode 100644 index 0000000000..7b2f861a67 --- /dev/null +++ b/components/esp_lcd/priv_include/esp_async_fbcpy.h @@ -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); diff --git a/components/esp_lcd/src/esp_async_fbcpy.c b/components/esp_lcd/src/esp_async_fbcpy.c new file mode 100644 index 0000000000..4f1c6e1fe8 --- /dev/null +++ b/components/esp_lcd/src/esp_async_fbcpy.c @@ -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; +} diff --git a/components/hal/esp32p4/include/hal/dma2d_ll.h b/components/hal/esp32p4/include/hal/dma2d_ll.h index 558ef79671..2edd10a35e 100644 --- a/components/hal/esp32p4/include/hal/dma2d_ll.h +++ b/components/hal/esp32p4/include/hal/dma2d_ll.h @@ -69,6 +69,8 @@ extern "C" { // Peripheral selection ID register field width #define DMA2D_LL_CHANNEL_PERIPH_SEL_BIT_WIDTH (3) +#define DMA2D_LL_DESC_ALIGNMENT 8 // Descriptor must be aligned to 8 bytes + ///////////////////////////////////// Common ///////////////////////////////////////// /** * @brief Enable the bus clock for 2D-DMA module