esp_lcd: vendor the esp_lcd component (ESP-IDF v5.5.4)
What changed, and why it matters
This commit copies the ESP-IDF 'esp_lcd' display-driver component directly into the Blockstream Jade firmware repository (a process called 'vendoring') at version 5.5.4. It adds about 8,700 lines of display-interface code for LCD panels using I2C, SPI, I80, RGB, and MIPI-DSI buses. The change itself is a build/dependency packaging step, not a fix for a known security bug. There is no indication in the commit message or diff that this is intended to address a security issue, and no external security references are provided.
Treat this as a routine dependency/vendor update. Review the vendored ESP-IDF v5.5.4 esp_lcd component against Espressif's release notes for any known LCD/DMA issues, and ensure the imported version matches the rest of the ESP-IDF toolchain used by Jade. No immediate security response is indicated by the commit itself.
Security signals we found
Large vendored third-party driver import (+8,718 lines, 43 files)
Driver code runs in interrupt context and uses DMA, which historically carries memory-safety and cache-coherency risks
No explicit security fix or bug-fix narrative in commit title/message
No references to CVEs, advisories, or researcher attribution supplied
Evidence from the diff
Commit d89207bf vendors components/esp_lcd/ from ESP-IDF v5.5.4 into the Jade tree. The added code is a standard Espressif LCD driver: CMake/Kconfig plumbing, panel IO implementations for I2C (legacy and master), SPI, I80 (including an I2S-backed bitbang variant), RGB, and MIPI-DSI/DPI/DBI. The diff shows normal driver initialization, DMA descriptor setup, ISR handlers, cache maintenance, and clock/PM management. No patch to an existing vulnerability is visible; the files are newly introduced and largely mirror upstream ESP-IDF code. No CVE, advisory, or vendor security statement is present in the supplied materials.
Changed components
components/esp_lcd/CMakeLists.txtcomponents/esp_lcd/Kconfigcomponents/esp_lcd/src/*components/esp_lcd/i2c/*components/esp_lcd/spi/*components/esp_lcd/i80/*components/esp_lcd/rgb/*components/esp_lcd/dsi/*components/esp_lcd/include/*ESP-IDF LCD display subsystem integrationInspect captured patch +8718 / −0
### components/esp_lcd/CMakeLists.txt
@@ -0,0 +1,56 @@
+idf_build_get_property(target IDF_TARGET)
+
+if(${target} STREQUAL "linux")
+ return() # This component is not supported by the POSIX/Linux simulator
+endif()
+
+set(srcs "src/esp_lcd_common.c"
+ "src/esp_lcd_panel_io.c"
+ "src/esp_lcd_panel_nt35510.c"
+ "src/esp_lcd_panel_ssd1306.c"
+ "src/esp_lcd_panel_st7789.c"
+ "src/esp_lcd_panel_ops.c")
+set(includes "include" "interface")
+set(priv_requires "esp_mm" "esp_psram" "esp_pm" "esp_driver_i2s")
+set(public_requires "driver" "esp_driver_gpio" "esp_driver_i2c" "esp_driver_spi")
+
+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()
+
+if(CONFIG_SOC_GPSPI_SUPPORTED)
+ list(APPEND srcs "spi/esp_lcd_panel_io_spi.c")
+endif()
+
+if(CONFIG_SOC_I2S_SUPPORTS_LCD_CAMERA)
+ list(APPEND srcs "i80/esp_lcd_panel_io_i2s.c")
+endif()
+
+if(CONFIG_SOC_PARLIO_SUPPORT_SPI_LCD)
+ list(APPEND srcs "parl/esp_lcd_panel_io_parl.c")
+endif()
+
+if(CONFIG_SOC_LCDCAM_I80_LCD_SUPPORTED)
+ list(APPEND srcs "i80/esp_lcd_panel_io_i80.c")
+endif()
+
+if(CONFIG_SOC_LCDCAM_RGB_LCD_SUPPORTED)
+ list(APPEND includes "rgb/include")
+ list(APPEND srcs "rgb/esp_lcd_panel_rgb.c")
+endif()
+
+if(CONFIG_SOC_MIPI_DSI_SUPPORTED)
+ list(APPEND includes "dsi/include")
+ list(APPEND srcs "dsi/esp_lcd_mipi_dsi_bus.c" "dsi/esp_lcd_panel_io_dbi.c" "dsi/esp_lcd_panel_dpi.c")
+endif()
+
+idf_component_register(SRCS ${srcs}
+ INCLUDE_DIRS ${includes}
+ PRIV_INCLUDE_DIRS "priv_include"
+ PRIV_REQUIRES ${priv_requires}
+ REQUIRES ${public_requires}
+ LDFRAGMENTS linker.lf)
### components/esp_lcd/Kconfig
@@ -0,0 +1,58 @@
+menu "ESP-Driver:LCD Controller Configurations"
+
+ if SOC_LCD_RGB_SUPPORTED
+ config LCD_RGB_ISR_IRAM_SAFE
+ bool "RGB LCD ISR IRAM-Safe"
+ select GDMA_ISR_HANDLER_IN_IRAM # bounce buffer mode relies on GDMA EOF interrupt
+ select ESP_PERIPH_CTRL_FUNC_IN_IRAM
+ default n
+ 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 "Always restart RGB LCD transmission in VSYNC"
+ default n
+ 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
+
+ if SOC_MIPI_DSI_SUPPORTED
+ config LCD_DSI_ISR_HANDLER_IN_IRAM
+ bool "Place DSI ISR handler in IRAM to reduce latency"
+ default y
+ select LCD_DSI_OBJ_FORCE_INTERNAL
+ help
+ Place DSI ISR handler in IRAM to reduce latency caused by cache miss.
+
+ config LCD_DSI_ISR_CACHE_SAFE
+ bool "Allow DSI ISR to execute when cache is disabled" if !SPI_FLASH_AUTO_SUSPEND
+ select LCD_DSI_ISR_HANDLER_IN_IRAM
+ select DW_GDMA_ISR_IRAM_SAFE # relies on DW_GDMA Full trans done interrupt
+ default n
+ help
+ Enable this option to allow the DSI Interrupt Service Routine (ISR)
+ to execute even when the cache is disabled. This can be useful in scenarios where the cache
+ might be turned off, but the DSI functionality is still required to operate correctly.
+
+ config LCD_DSI_OBJ_FORCE_INTERNAL
+ bool
+ default n
+ help
+ This will ensure the DSI driver object will always be allocated in internal RAM.
+ endif # SOC_MIPI_DSI_SUPPORTED
+
+ config LCD_ENABLE_DEBUG_LOG
+ bool "Force enable debug log"
+ default n
+ help
+ If enabled, LCD driver component will:
+ 1. ignore the global logging settings
+ 2. compile all log messages into the binary
+ 3. set the runtime log level to VERBOSE
+ Please enable this option by caution, as it will increase the binary size.
+endmenu
### components/esp_lcd/README.md
@@ -0,0 +1,103 @@
+# esp_lcd Driver Design
+
+## Class Diagram
+
+`esp_lcd` driver focuses on two parts: panel driver and IO driver. The panel driver is a bunch of operations on the **frame-buffer**, no matter where the frame-buffer is located. The IO driver is mainly consumed by the controller-based LCD panel drivers (e.g. ST7789). Usually such LCD controller can support various IO interfaces (e.g. I80, SPI, I2C, etc). So we define an abstract interface for the IO driver.
+
+```mermaid
+classDiagram
+ class esp_lcd_panel_t {
+ <<interface>>
+ +reset() esp_err_t
+ +init() esp_err_t
+ +draw_bitmap(int x_start, int y_start, int x_end, int y_end, const void *color_data) esp_err_t
+ +mirror(bool x_axis, bool y_axis) esp_err_t
+ +swap_xy(bool swap_axes) esp_err_t
+ +set_gap(int x_gap, int y_gap) esp_err_t
+ +invert_color(bool invert_color_data) esp_err_t
+ +disp_on_off(bool on_off) esp_err_t
+ }
+
+ esp_lcd_rgb_panel_t --|> esp_lcd_panel_t : Inheritance
+ class esp_lcd_rgb_panel_t {
+ -int panel_id
+ -size_t data_width
+ -int disp_gpio
+ -intr_handle_t intr
+ -uint8_t* frame_buffer
+ -gdma_channel_handle_t gdma_channel
+ -dma_descriptor_t* dma_nodes
+ -on_vsync(void* user_data) bool
+ }
+
+ esp_lcd_panel_model_t --|> esp_lcd_panel_t : Inheritance
+ esp_lcd_panel_model_t "1" --> "1" esp_lcd_panel_io_t : Use
+ class esp_lcd_panel_model_t {
+ -esp_lcd_panel_io_t* io
+ -int reset_gpio_num
+ }
+
+ class esp_lcd_panel_io_t {
+ <<interface>>
+ +rx_param(int lcd_cmd, void *param, size_t param_size)
+ +tx_param(int lcd_cmd, const void *param, size_t param_size)
+ +tx_color(int lcd_cmd, const void *color, size_t color_size)
+ }
+
+ esp_lcd_panel_io_i2c_t --|> esp_lcd_panel_io_t : Inheritance
+ class esp_lcd_panel_io_i2c_t {
+ -int i2c_bus_id
+ -int ctrl_phase_cmd
+ -int ctrl_phase_data
+ -on_color_trans_done(void* user_data) bool
+ }
+
+ esp_lcd_panel_io_spi_t --|> esp_lcd_panel_io_t : Inheritance
+ class esp_lcd_panel_io_spi_t {
+ -spi_device_handle_t spi_dev
+ -int dc_gpio_num
+ -spi_transaction_t trans_worker
+ -on_color_trans_done(void* user_data) bool
+ }
+
+ esp_lcd_panel_io_dbi_t --|> esp_lcd_panel_io_t : Inheritance
+ class esp_lcd_panel_io_dbi_t {
+ -esp_lcd_dsi_bus_t* bus
+ -int virtual_channel
+ }
+
+ esp_lcd_dpi_panel_t --|> esp_lcd_panel_t : Inheritance
+ class esp_lcd_dpi_panel_t {
+ -esp_lcd_dsi_bus_t* bus
+ -int virtual_channel
+ -void *frame_buffer
+ -dw_gdma_channel_handle_t dma_chan
+ }
+
+ esp_lcd_dsi_bus_t "1" --> "1..*" esp_lcd_panel_io_dbi_t : Has
+ esp_lcd_dsi_bus_t "1" --> "1..*" esp_lcd_dpi_panel_t : Has
+ class esp_lcd_dsi_bus_t {
+ -int bus_id
+ }
+
+ esp_lcd_panel_io_i80_t --|> esp_lcd_panel_io_t : Inheritance
+ class esp_lcd_panel_io_i80_t {
+ -esp_lcd_i80_bus_t* bus
+ -int cs_gpio_num
+ -int dc_level
+ -size_t pclk_hz
+ -QueueHandle_t trans_queue
+ -QueueHandle_t done_queue
+ -on_color_trans_done(void* user_data) bool
+ }
+
+ esp_lcd_i80_bus_t "1" --> "1..*" esp_lcd_panel_io_i80_t : Has
+ class esp_lcd_i80_bus_t {
+ -int bus_id
+ -size_t data_width
+ -intr_handle_t intr
+ -gdma_cannel_handle_t dma_chan
+ -dma_descriptor_t* dma_nodes
+ -list_t i80_devices
+ }
+```
### components/esp_lcd/dsi/esp_lcd_mipi_dsi_bus.c
@@ -0,0 +1,164 @@
+/*
+ * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include <math.h>
+#include "esp_lcd_mipi_dsi.h"
+#include "esp_clk_tree.h"
+#include "mipi_dsi_priv.h"
+
+#define MIPI_DSI_DEFAULT_TIMEOUT_CLOCK_FREQ_MHZ 10
+// TxClkEsc frequency must be configured between 2 and 20 MHz
+#define MIPI_DSI_DEFAULT_ESCAPE_CLOCK_FREQ_MHZ 18
+
+esp_err_t esp_lcd_new_dsi_bus(const esp_lcd_dsi_bus_config_t *bus_config, esp_lcd_dsi_bus_handle_t *ret_bus)
+{
+ esp_err_t ret = ESP_OK;
+ ESP_RETURN_ON_FALSE(bus_config && ret_bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+ ESP_RETURN_ON_FALSE(bus_config->num_data_lanes <= MIPI_DSI_LL_MAX_DATA_LANES,
+ ESP_ERR_INVALID_ARG, TAG, "invalid number of data lanes %d", bus_config->num_data_lanes);
+ ESP_RETURN_ON_FALSE(bus_config->lane_bit_rate_mbps >= MIPI_DSI_LL_MIN_PHY_MBPS &&
+ bus_config->lane_bit_rate_mbps <= MIPI_DSI_LL_MAX_PHY_MBPS, ESP_ERR_INVALID_ARG, TAG,
+ "invalid lane bit rate %.2f", bus_config->lane_bit_rate_mbps);
+
+ // we don't use an bus allocator here, because different DSI bus uses different PHY.
+ // And each PHY has its own associated PINs, which is not changeable.
+ // So user HAS TO specify the bus ID by themselves, according to their PCB design.
+ int bus_id = bus_config->bus_id;
+ ESP_RETURN_ON_FALSE(bus_id >= 0 && bus_id < MIPI_DSI_LL_NUM_BUS, ESP_ERR_INVALID_ARG, TAG, "invalid bus ID %d", bus_id);
+ esp_lcd_dsi_bus_t *dsi_bus = heap_caps_calloc(1, sizeof(esp_lcd_dsi_bus_t), DSI_MEM_ALLOC_CAPS);
+ ESP_RETURN_ON_FALSE(dsi_bus, ESP_ERR_NO_MEM, TAG, "no memory for DSI bus");
+ dsi_bus->bus_id = bus_id;
+
+ // Enable the APB clock for accessing the DSI host and bridge registers
+ PERIPH_RCC_ATOMIC() {
+ mipi_dsi_ll_enable_bus_clock(bus_id, true);
+ mipi_dsi_ll_reset_register(bus_id);
+ }
+
+ // if the clock source is not assigned, fallback to the default clock source
+ mipi_dsi_phy_pllref_clock_source_t phy_clk_src = bus_config->phy_clk_src;
+ if (phy_clk_src == 0) {
+#if CONFIG_IDF_TARGET_ESP32P4 && HAL_CONFIG(CHIP_SUPPORT_MIN_REV) < 300
+ phy_clk_src = MIPI_DSI_PHY_PLLREF_CLK_SRC_DEFAULT_LEGACY;
+#else
+ phy_clk_src = MIPI_DSI_PHY_PLLREF_CLK_SRC_DEFAULT;
+#endif
+ }
+ ESP_GOTO_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)phy_clk_src, true), err, TAG, "clock source enable failed");
+
+ // always use the default clock source for the DSI PHY configuration
+ ESP_GOTO_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)MIPI_DSI_PHY_CFG_CLK_SRC_DEFAULT, true), err, TAG, "clock source enable failed");
+
+ // enable the clock source for DSI PHY
+ PERIPH_RCC_ATOMIC() {
+ // set the DSI PHY configuration clock
+ // the configuration clock is used for all modes except the shutdown mode
+ mipi_dsi_ll_set_phy_config_clock_source(bus_id, MIPI_DSI_PHY_CFG_CLK_SRC_DEFAULT);
+ mipi_dsi_ll_enable_phy_config_clock(bus_id, true);
+ // set the DSI PHY PLL reference clock
+ mipi_dsi_ll_set_phy_pllref_clock_source(bus_id, phy_clk_src);
+ mipi_dsi_ll_set_phy_pll_ref_clock_div(bus_id, 1); // no division
+ mipi_dsi_ll_enable_phy_pllref_clock(bus_id, true);
+ }
+
+#if CONFIG_PM_ENABLE
+ // When MIPI DSI is working, we don't expect the clock source would be turned off
+ esp_pm_lock_type_t pm_lock_type = ESP_PM_NO_LIGHT_SLEEP;
+ ret = esp_pm_lock_create(pm_lock_type, 0, "dsi_phy", &dsi_bus->pm_lock);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "create PM lock failed");
+ // before we configure the PLL, we want the clock source to be stable
+ esp_pm_lock_acquire(dsi_bus->pm_lock);
+#endif
+
+ // if the number of data lanes is not assigned, fallback to the maximum number of data lanes
+ int num_data_lanes = bus_config->num_data_lanes;
+ if (num_data_lanes == 0) {
+ num_data_lanes = MIPI_DSI_LL_MAX_DATA_LANES;
+ }
+ // initialize HAL context
+ mipi_dsi_hal_config_t hal_config = {
+ .bus_id = bus_id,
+ .lane_bit_rate_mbps = bus_config->lane_bit_rate_mbps,
+ .num_data_lanes = num_data_lanes,
+ };
+ mipi_dsi_hal_init(&dsi_bus->hal, &hal_config);
+ mipi_dsi_hal_context_t *hal = &dsi_bus->hal;
+
+ // get the frequency of the PHY clock source
+ uint32_t phy_clk_src_freq_hz = 0;
+ ESP_GOTO_ON_ERROR(esp_clk_tree_src_get_freq_hz(phy_clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED,
+ &phy_clk_src_freq_hz), err, TAG, "get phy clock source frequency failed");
+ // configure the PHY PLL
+ mipi_dsi_hal_configure_phy_pll(hal, phy_clk_src_freq_hz, bus_config->lane_bit_rate_mbps);
+
+ // wait for PHY initialization done
+ while (!mipi_dsi_phy_ll_is_pll_locked(hal->host)) {
+ vTaskDelay(pdMS_TO_TICKS(1));
+ }
+ while (!mipi_dsi_phy_ll_are_lanes_stopped(hal->host, num_data_lanes)) {
+ vTaskDelay(pdMS_TO_TICKS(1));
+ }
+
+ // initialize the DSI operation mode: command mode
+ mipi_dsi_host_ll_enable_video_mode(hal->host, false);
+ // place the clock lane in low power mode, we will switch to high speed mode later when DPI stream is ready
+ mipi_dsi_host_ll_set_clock_lane_state(hal->host, MIPI_DSI_LL_CLOCK_LANE_STATE_LP);
+ // Set the time that is required by the clock and data lanes to go from high-speed to low-power and from low-power to high-speed
+ mipi_dsi_phy_ll_set_switch_time(hal->host, 50, 104, 46, 128);
+
+ // enable CRC reception and ECC reception, error correction, and reporting
+ mipi_dsi_host_ll_enable_rx_crc(hal->host, true);
+ mipi_dsi_host_ll_enable_rx_ecc(hal->host, true);
+ // enable sending the EoTp packet at the end of each transmission for HS mode
+ mipi_dsi_host_ll_enable_tx_eotp(hal->host, true, false);
+
+ // Set the divider to get the Time Out clock, clock source is the high-speed byte clock
+ mipi_dsi_host_ll_set_timeout_clock_division(hal->host, (uint32_t)roundf(bus_config->lane_bit_rate_mbps / 8.0f / MIPI_DSI_DEFAULT_TIMEOUT_CLOCK_FREQ_MHZ));
+ // Set the divider to get the TX Escape clock, clock source is the high-speed byte clock
+ mipi_dsi_host_ll_set_escape_clock_division(hal->host, (uint32_t)roundf(bus_config->lane_bit_rate_mbps / 8.0f / MIPI_DSI_DEFAULT_ESCAPE_CLOCK_FREQ_MHZ));
+ // set the timeout intervals to zero, means to disable the timeout mechanism
+ mipi_dsi_host_ll_set_timeout_count(hal->host, 0, 0, 0, 0, 0, 0, 0);
+ // DSI host will wait indefinitely for a read response from the DSI device
+ mipi_dsi_phy_ll_set_max_read_time(hal->host, 6000);
+ // set how long the DSI host will wait before sending the next transmission
+ mipi_dsi_phy_ll_set_stop_wait_time(hal->host, 0x3F);
+
+ *ret_bus = dsi_bus;
+ return ESP_OK;
+err:
+ if (dsi_bus) {
+ esp_lcd_del_dsi_bus(dsi_bus);
+ }
+ return ret;
+}
+
+esp_err_t esp_lcd_del_dsi_bus(esp_lcd_dsi_bus_handle_t bus)
+{
+ ESP_RETURN_ON_FALSE(bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+ int bus_id = bus->bus_id;
+ // disable the clock source for DSI PHY
+ PERIPH_RCC_ATOMIC() {
+ mipi_dsi_ll_enable_phy_pllref_clock(bus_id, false);
+ mipi_dsi_ll_enable_phy_config_clock(bus_id, false);
+ }
+ // disable the APB clock for accessing the DSI peripheral registers
+ PERIPH_RCC_ATOMIC() {
+ mipi_dsi_ll_enable_bus_clock(bus_id, false);
+ }
+ if (bus->pm_lock) {
+ esp_pm_lock_release(bus->pm_lock);
+ esp_pm_lock_delete(bus->pm_lock);
+ }
+ free(bus);
+ return ESP_OK;
+}
+
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+__attribute__((constructor))
+static void mipi_dsi_override_default_log_level(void)
+{
+ esp_log_level_set(TAG, ESP_LOG_VERBOSE);
+}
+#endif
### components/esp_lcd/dsi/esp_lcd_panel_dpi.c
@@ -0,0 +1,646 @@
+/*
+ * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include <sys/param.h>
+#include "esp_lcd_panel_interface.h"
+#include "esp_lcd_mipi_dsi.h"
+#include "esp_intr_alloc.h"
+#include "esp_clk_tree.h"
+#include "esp_cache.h"
+#include "mipi_dsi_priv.h"
+#include "esp_async_fbcpy.h"
+#include "esp_memory_utils.h"
+#include "esp_private/dw_gdma.h"
+#include "hal/color_hal.h"
+
+typedef struct esp_lcd_dpi_panel_t esp_lcd_dpi_panel_t;
+
+static esp_err_t dpi_panel_del(esp_lcd_panel_t *panel);
+static esp_err_t dpi_panel_init(esp_lcd_panel_t *panel);
+static esp_err_t dpi_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
+
+struct esp_lcd_dpi_panel_t {
+ esp_lcd_panel_t base; // Base class of generic lcd panel
+ esp_lcd_dsi_bus_handle_t bus; // DSI bus handle
+ uint8_t virtual_channel; // Virtual channel ID, index from 0
+ uint8_t cur_fb_index; // Current frame buffer index
+ uint8_t num_fbs; // Number of frame buffers
+ uint8_t *fbs[DPI_PANEL_MAX_FB_NUM]; // Frame buffers
+ uint32_t h_pixels; // Horizontal pixels
+ uint32_t v_pixels; // Vertical pixels
+ size_t fb_size; // Frame buffer size, in bytes
+ size_t bits_per_pixel; // Bits per pixel
+ lcd_color_format_t in_color_format; // Input color format
+ lcd_color_format_t out_color_format; // Output color format
+ dw_gdma_channel_handle_t dma_chan; // DMA channel
+ intr_handle_t brg_intr; // DSI Bridge interrupt handle
+ dw_gdma_link_list_handle_t link_lists[DPI_PANEL_MAX_FB_NUM]; // 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_pm_lock_handle_t pm_lock; // Power management lock
+ 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_refresh_done_cb_t on_refresh_done; // Callback invoked when one refresh operation finished (kinda like a vsync end)
+ 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;
+}
+
+bool mipi_dsi_dma_trans_done_cb(dw_gdma_channel_handle_t chan, const dw_gdma_trans_done_event_data_t *event_data, void *user_data)
+{
+ bool yield_needed = false;
+ esp_lcd_dpi_panel_t *dpi_panel = (esp_lcd_dpi_panel_t *)user_data;
+ uint8_t fb_index = dpi_panel->cur_fb_index;
+ dw_gdma_link_list_handle_t link_list = dpi_panel->link_lists[fb_index];
+
+ // restart the DMA transfer, keep refreshing the LCD
+ dw_gdma_block_markers_t markers = {
+ .is_valid = true,
+ .is_last = true,
+ };
+ dw_gdma_lli_set_block_markers(dw_gdma_link_list_get_item(link_list, 0), markers);
+ dw_gdma_channel_use_link_list(chan, link_list);
+ dw_gdma_channel_enable_ctrl(chan, true);
+
+#if !MIPI_DSI_BRG_LL_EVENT_VSYNC
+ // the DMA descriptor is large enough to carry a whole frame buffer, so this event can also be treated as a fake "vsync end"
+ if (dpi_panel->on_refresh_done) {
+ if (dpi_panel->on_refresh_done(&dpi_panel->base, NULL, dpi_panel->user_ctx)) {
+ yield_needed = true;
+ }
+ }
+#endif
+ return yield_needed;
+}
+
+void mipi_dsi_bridge_isr_handler(void *args)
+{
+ esp_lcd_dpi_panel_t* dpi_panel = (esp_lcd_dpi_panel_t *)args;
+ mipi_dsi_hal_context_t *hal = &dpi_panel->bus->hal;
+ // clear the interrupt status
+ uint32_t intr_status = mipi_dsi_brg_ll_get_interrupt_status(hal->bridge);
+ mipi_dsi_brg_ll_clear_interrupt_status(hal->bridge, intr_status);
+
+ if (intr_status & MIPI_DSI_BRG_LL_EVENT_UNDERRUN) {
+ // when an underrun happens, the LCD display may already becomes blue
+ // it's too late to recover the display, so we just print an error message
+ // as a hint to the user that he should optimize the memory bandwidth (with AXI-ICM)
+ ESP_DRAM_LOGE(TAG, "can't fetch data from external memory fast enough, underrun happens");
+ }
+ if (intr_status & MIPI_DSI_BRG_LL_EVENT_VSYNC) {
+ if (dpi_panel->on_refresh_done) {
+ if (dpi_panel->on_refresh_done(&dpi_panel->base, NULL, dpi_panel->user_ctx)) {
+ portYIELD_FROM_ISR();
+ }
+ }
+ }
+}
+
+// Please note, errors happened in this function is just propagated to the caller
+// dpi_panel_del() is actually doing the error handling
+static esp_err_t dpi_panel_create_dma_link(esp_lcd_dpi_panel_t *dpi_panel)
+{
+ dw_gdma_channel_handle_t dma_chan = NULL;
+ dw_gdma_link_list_handle_t link_list = NULL;
+ // sending image stream from memory to the DSI bridge
+ dw_gdma_channel_alloc_config_t dma_alloc_config = {
+ .src = {
+ .block_transfer_type = DW_GDMA_BLOCK_TRANSFER_LIST,
+ .role = DW_GDMA_ROLE_MEM,
+ .handshake_type = DW_GDMA_HANDSHAKE_HW,
+ .num_outstanding_requests = 5,
+ },
+ .dst = {
+ .block_transfer_type = DW_GDMA_BLOCK_TRANSFER_LIST,
+ .role = DW_GDMA_ROLE_PERIPH_DSI,
+ .handshake_type = DW_GDMA_HANDSHAKE_HW,
+ .num_outstanding_requests = 2,
+ },
+ .flow_controller = DW_GDMA_FLOW_CTRL_SELF, // DMA as the flow controller
+ .chan_priority = 1,
+ };
+ ESP_RETURN_ON_ERROR(dw_gdma_new_channel(&dma_alloc_config, &dma_chan), TAG, "create DMA channel failed");
+ dpi_panel->dma_chan = dma_chan;
+
+ // create DMA link lists
+ dw_gdma_link_list_config_t link_list_config = {
+ .num_items = DPI_PANEL_MIN_DMA_NODES_PER_LINK,
+ .link_type = DW_GDMA_LINKED_LIST_TYPE_SINGLY,
+ };
+ for (int i = 0; i < dpi_panel->num_fbs; i++) {
+ ESP_RETURN_ON_ERROR(dw_gdma_new_link_list(&link_list_config, &link_list), TAG, "create DMA link list failed");
+ dpi_panel->link_lists[i] = link_list;
+ }
+
+ // register DMA ISR callbacks
+ dw_gdma_event_callbacks_t dsi_dma_cbs = {
+ .on_full_trans_done = mipi_dsi_dma_trans_done_cb,
+ };
+ ESP_RETURN_ON_ERROR(dw_gdma_channel_register_event_callbacks(dma_chan, &dsi_dma_cbs, dpi_panel), TAG, "register DMA callbacks failed");
+
+ return ESP_OK;
+}
+
+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 ret = ESP_OK;
+ 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(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 > 0, ESP_ERR_INVALID_ARG, TAG, "invalid DPI clock frequency %.2f", 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
+ size_t num_fbs = panel_config->num_fbs;
+ // if the user doesn't specify the number of frame buffers, then fallback to use one frame buffer
+ if (num_fbs == 0) {
+ num_fbs = 1;
+ }
+ ESP_RETURN_ON_FALSE(num_fbs <= DPI_PANEL_MAX_FB_NUM, ESP_ERR_INVALID_ARG, TAG, "num_fbs not within [1,%d]", DPI_PANEL_MAX_FB_NUM);
+
+ // by default, use RGB888 as the input color format
+ lcd_color_format_t in_color_format = LCD_COLOR_FMT_RGB888;
+ size_t bits_per_pixel = 24;
+ // the deprecated way to set the pixel format
+ switch (panel_config->pixel_format) {
+ case LCD_COLOR_PIXEL_FORMAT_RGB565:
+ bits_per_pixel = 16;
+ break;
+ case LCD_COLOR_PIXEL_FORMAT_RGB666:
+ // RGB data in the memory must be constructed in 6-6-6 (18 bits) for each pixel
+ bits_per_pixel = 18;
+ break;
+ case LCD_COLOR_PIXEL_FORMAT_RGB888:
+ bits_per_pixel = 24;
+ break;
+ }
+ in_color_format = COLOR_TYPE_ID(COLOR_SPACE_RGB, panel_config->pixel_format);
+ // the recommended way to set the input color format
+ if (panel_config->in_color_format) {
+ in_color_format = panel_config->in_color_format;
+ // if user sets the in_color_format, it can override the pixel format setting
+ color_space_pixel_format_t in_color_id = {
+ .color_type_id = in_color_format,
+ };
+ bits_per_pixel = color_hal_pixel_format_get_bit_depth(in_color_id);
+ }
+ // by default, out_color_format is the same as in_color_format (i.e. no color format conversion)
+ lcd_color_format_t out_color_format = in_color_format;
+ if (panel_config->out_color_format) {
+ out_color_format = panel_config->out_color_format;
+ }
+ ESP_RETURN_ON_FALSE(panel_config->video_timing.h_size * panel_config->video_timing.v_size * bits_per_pixel % 8 == 0,
+ ESP_ERR_INVALID_ARG, TAG, "frame buffer size not aligned to byte boundary");
+
+ int bus_id = bus->bus_id;
+ mipi_dsi_hal_context_t *hal = &bus->hal;
+
+ 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");
+ dpi_panel->virtual_channel = panel_config->virtual_channel;
+ dpi_panel->in_color_format = in_color_format;
+ dpi_panel->out_color_format = out_color_format;
+ dpi_panel->bus = bus;
+ dpi_panel->num_fbs = num_fbs;
+
+ // allocate frame buffer from PSRAM
+ size_t fb_size = panel_config->video_timing.h_size * panel_config->video_timing.v_size * bits_per_pixel / 8;
+ for (int i = 0; i < num_fbs; i++) {
+ uint8_t *frame_buffer = heap_caps_calloc(1, fb_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT | MALLOC_CAP_DMA);
+ ESP_GOTO_ON_FALSE(frame_buffer, ESP_ERR_NO_MEM, err, TAG, "no memory for frame buffer");
+ dpi_panel->fbs[i] = frame_buffer;
+ ESP_LOGD(TAG, "fb[%d] @%p", i, frame_buffer);
+ // preset the frame buffer with black color
+ // the frame buffer address alignment is ensured by `heap_caps_calloc`
+ // while the value of the fb_size may not be aligned to the cache line size
+ // but that's not a problem because the `heap_caps_calloc` internally allocated a buffer whose size is aligned up to the cache line size
+ ESP_GOTO_ON_ERROR(esp_cache_msync(frame_buffer, fb_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED),
+ err, TAG, "cache write back failed");
+ }
+ dpi_panel->fb_size = fb_size;
+ dpi_panel->bits_per_pixel = bits_per_pixel;
+ dpi_panel->h_pixels = panel_config->video_timing.h_size;
+ dpi_panel->v_pixels = panel_config->video_timing.v_size;
+
+#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
+ mipi_dsi_dpi_clock_source_t dpi_clk_src = panel_config->dpi_clk_src;
+ if (dpi_clk_src == 0) {
+ dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT;
+ }
+ // get the clock source frequency
+ uint32_t dpi_clk_src_freq_hz = 0;
+ ESP_GOTO_ON_ERROR(esp_clk_tree_src_get_freq_hz(dpi_clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED,
+ &dpi_clk_src_freq_hz), err, TAG, "get clock source frequency failed");
+ // divide the source clock to get the final DPI clock
+ float dpi_clk_src_freq_mhz = (float)dpi_clk_src_freq_hz / 1000.0f / 1000.0f;
+ uint32_t dpi_div = mipi_dsi_hal_host_dpi_calculate_divider(hal, dpi_clk_src_freq_mhz, panel_config->dpi_clock_freq_mhz);
+ ESP_GOTO_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)dpi_clk_src, true), err, TAG, "clock source enable failed");
+ // set the clock source, set the divider, and enable the dpi clock
+ PERIPH_RCC_ATOMIC() {
+ mipi_dsi_ll_set_dpi_clock_source(bus_id, dpi_clk_src);
+ mipi_dsi_ll_set_dpi_clock_div(bus_id, dpi_div);
+ mipi_dsi_ll_enable_dpi_clock(bus_id, true);
+ }
+
+#if CONFIG_PM_ENABLE
+ // When MIPI DSI is working, we don't expect the clock source would be turned off
+ // use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS
+ esp_pm_lock_type_t pm_lock_type = ESP_PM_CPU_FREQ_MAX;
+ ret = esp_pm_lock_create(pm_lock_type, 0, "dsi_dpi", &dpi_panel->pm_lock);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "create PM lock failed");
+ esp_pm_lock_acquire(dpi_panel->pm_lock);
+#endif
+
+ // install interrupt service
+ int isr_flags = ESP_INTR_FLAG_LOWMED;
+#if CONFIG_LCD_DSI_ISR_CACHE_SAFE
+ isr_flags |= ESP_INTR_FLAG_IRAM;
+#endif
+ ESP_GOTO_ON_ERROR(esp_intr_alloc(soc_mipi_dsi_signals[bus_id].brg_irq_id, isr_flags, mipi_dsi_bridge_isr_handler,
+ dpi_panel, &dpi_panel->brg_intr), err, TAG, "allocate DSI Bridge interrupt failed");
+
+ // create DMA resources
+ ESP_GOTO_ON_ERROR(dpi_panel_create_dma_link(dpi_panel), err, TAG, "initialize DMA link failed");
+
+ mipi_dsi_host_ll_dpi_set_vcid(hal->host, panel_config->virtual_channel);
+ mipi_dsi_host_ll_dpi_set_color_coding(hal->host, out_color_format, 0);
+ // these signals define how the DPI interface interacts with the controller
+ mipi_dsi_host_ll_dpi_set_timing_polarity(hal->host, false, false, false, false, false);
+
+ if (panel_config->flags.disable_lp) {
+ // configure the low-power transitions: defines the video periods which are NOT permitted to goto low-power
+ mipi_dsi_host_ll_dpi_enable_lp_horizontal_timing(hal->host, false, false);
+ mipi_dsi_host_ll_dpi_enable_lp_vertical_timing(hal->host, false, false, false, false);
+ // commands are NOT transmitted in low-power mode
+ mipi_dsi_host_ll_dpi_enable_lp_command(hal->host, false);
+ } else {
+ // configure the low-power transitions: defines the video periods which are permitted to goto low-power if the time available to do so
+ mipi_dsi_host_ll_dpi_enable_lp_horizontal_timing(hal->host, true, true);
+ mipi_dsi_host_ll_dpi_enable_lp_vertical_timing(hal->host, true, true, true, true);
+ // commands are transmitted in low-power mode
+ mipi_dsi_host_ll_dpi_enable_lp_command(hal->host, true);
+ }
+ // after sending a frame, the DSI device should return an ack
+ mipi_dsi_host_ll_dpi_enable_frame_ack(hal->host, true);
+ // using the burst mode because it's energy-efficient
+ mipi_dsi_host_ll_dpi_set_video_burst_type(hal->host, MIPI_DSI_LL_VIDEO_BURST_WITH_SYNC_PULSES);
+ // configure the size of the active lin period, measured in pixels
+ mipi_dsi_host_ll_dpi_set_video_packet_pixel_num(hal->host, panel_config->video_timing.h_size);
+ // disable multi-packets
+ mipi_dsi_host_ll_dpi_set_trunks_num(hal->host, 0);
+ // disable "null packets"
+ mipi_dsi_host_ll_dpi_set_null_packet_size(hal->host, 0);
+ // set horizontal and vertical timing configuration
+ mipi_dsi_hal_host_dpi_set_horizontal_timing(hal, panel_config->video_timing.hsync_pulse_width,
+ panel_config->video_timing.hsync_back_porch,
+ panel_config->video_timing.h_size,
+ panel_config->video_timing.hsync_front_porch);
+ mipi_dsi_hal_host_dpi_set_vertical_timing(hal, panel_config->video_timing.vsync_pulse_width,
+ panel_config->video_timing.vsync_back_porch,
+ panel_config->video_timing.v_size,
+ panel_config->video_timing.vsync_front_porch);
+ mipi_dsi_brg_ll_set_num_pixel_bits(hal->bridge, panel_config->video_timing.h_size * panel_config->video_timing.v_size * bits_per_pixel);
+ mipi_dsi_brg_ll_set_underrun_discard_count(hal->bridge, panel_config->video_timing.h_size);
+ // set the in/out color formats in the DSI bridge
+ mipi_dsi_brg_ll_set_input_color_format(hal->bridge, in_color_format);
+ mipi_dsi_brg_ll_set_output_color_format(hal->bridge, out_color_format, 0);
+ // use the DW_GDMA as the flow controller
+ mipi_dsi_brg_ll_set_flow_controller(hal->bridge, MIPI_DSI_LL_FLOW_CONTROLLER_DMA);
+ mipi_dsi_brg_ll_set_multi_block_number(hal->bridge, DPI_PANEL_MIN_DMA_NODES_PER_LINK);
+ mipi_dsi_brg_ll_set_burst_len(hal->bridge, 256);
+ mipi_dsi_brg_ll_set_empty_threshold(hal->bridge, 1024 - 256);
+ // enable DSI bridge
+ mipi_dsi_brg_ll_enable(hal->bridge, true);
+ mipi_dsi_brg_ll_update_dpi_config(hal->bridge);
+
+ dpi_panel->base.del = dpi_panel_del;
+ dpi_panel->base.init = dpi_panel_init;
+ dpi_panel->base.draw_bitmap = dpi_panel_draw_bitmap;
+ *ret_panel = &dpi_panel->base;
+ ESP_LOGD(TAG, "dpi panel created @%p", dpi_panel);
+ return ESP_OK;
+err:
+ if (dpi_panel) {
+ dpi_panel_del(&dpi_panel->base);
+ }
+ return ret;
+}
+
+static esp_err_t dpi_panel_del(esp_lcd_panel_t *panel)
+{
+ esp_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base);
+ esp_lcd_dsi_bus_handle_t bus = dpi_panel->bus;
+ int bus_id = bus->bus_id;
+ mipi_dsi_hal_context_t *hal = &bus->hal;
+ // disable the DPI clock
+ PERIPH_RCC_ATOMIC() {
+ mipi_dsi_ll_enable_dpi_clock(bus_id, false);
+ }
+ // disable the DSI bridge
+ mipi_dsi_brg_ll_enable(hal->bridge, false);
+ // free memory
+ if (dpi_panel->dma_chan) {
+ dw_gdma_del_channel(dpi_panel->dma_chan);
+ }
+ for (int i = 0; i < DPI_PANEL_MAX_FB_NUM; i++) {
+ if (dpi_panel->fbs[i]) {
+ free(dpi_panel->fbs[i]);
+ }
+ }
+ for (int i = 0; i < DPI_PANEL_MAX_FB_NUM; i++) {
+ if (dpi_panel->link_lists[i]) {
+ dw_gdma_del_link_list(dpi_panel->link_lists[i]);
+ }
+ }
+ if (dpi_panel->fbcpy_handle) {
+ esp_async_fbcpy_uninstall(dpi_panel->fbcpy_handle);
+ }
+ if (dpi_panel->draw_sem) {
+ vSemaphoreDeleteWithCaps(dpi_panel->draw_sem);
+ }
+ if (dpi_panel->brg_intr) {
+ esp_intr_free(dpi_panel->brg_intr);
+ }
+ if (dpi_panel->pm_lock) {
+ esp_pm_lock_release(dpi_panel->pm_lock);
+ esp_pm_lock_delete(dpi_panel->pm_lock);
+ }
+ free(dpi_panel);
+ return ESP_OK;
+}
+
+esp_err_t esp_lcd_dpi_panel_get_frame_buffer(esp_lcd_panel_handle_t panel, uint32_t fb_num, void **fb0, ...)
+{
+ ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+ esp_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base);
+ ESP_RETURN_ON_FALSE(fb_num && fb_num <= dpi_panel->num_fbs, ESP_ERR_INVALID_ARG, TAG, "invalid frame buffer number");
+ void **fb_itor = fb0;
+ va_list args;
+ va_start(args, fb0);
+ for (uint32_t i = 0; i < fb_num; i++) {
+ if (fb_itor) {
+ *fb_itor = dpi_panel->fbs[i];
+ fb_itor = va_arg(args, void **);
+ }
+ }
+ va_end(args);
+ return ESP_OK;
+}
+
+static esp_err_t dpi_panel_init(esp_lcd_panel_t *panel)
+{
+ esp_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base);
+ esp_lcd_dsi_bus_handle_t bus = dpi_panel->bus;
+ mipi_dsi_hal_context_t *hal = &bus->hal;
+ dw_gdma_channel_handle_t dma_chan = dpi_panel->dma_chan;
+ dw_gdma_link_list_handle_t link_list = NULL;
+
+ dw_gdma_block_transfer_config_t dma_transfer_config = {
+ .src = {
+ .burst_mode = DW_GDMA_BURST_MODE_INCREMENT,
+ .burst_items = DW_GDMA_BURST_ITEMS_512,
+ .burst_len = 16,
+ .width = DW_GDMA_TRANS_WIDTH_64,
+ },
+ .dst = {
+ .addr = MIPI_DSI_BRG_MEM_BASE,
+ .burst_mode = DW_GDMA_BURST_MODE_FIXED,
+ .burst_items = DW_GDMA_BURST_ITEMS_256,
+ .burst_len = 16,
+ .width = DW_GDMA_TRANS_WIDTH_64,
+ },
+ .size = dpi_panel->fb_size * 8 / 64,
+ };
+ for (int i = 0; i < dpi_panel->num_fbs; i++) {
+ link_list = dpi_panel->link_lists[i];
+ dma_transfer_config.src.addr = (uint32_t)(dpi_panel->fbs[i]);
+ dw_gdma_lli_config_transfer(dw_gdma_link_list_get_item(link_list, 0), &dma_transfer_config);
+ dw_gdma_block_markers_t markers = {
+ .is_valid = true,
+ .is_last = true,
+ };
+ dw_gdma_lli_set_block_markers(dw_gdma_link_list_get_item(link_list, 0), markers);
+ }
+
+ // by default, we use the fb0 as the first working frame buffer
+ dpi_panel->cur_fb_index = 0;
+ link_list = dpi_panel->link_lists[0];
+ dw_gdma_channel_use_link_list(dma_chan, link_list);
+ // enable the DMA channel
+ dw_gdma_channel_enable_ctrl(dma_chan, true);
+
+ // enable the video mode
+ mipi_dsi_host_ll_enable_video_mode(hal->host, true);
+ // switch the clock lane to high speed mode
+ mipi_dsi_host_ll_set_clock_lane_state(hal->host, MIPI_DSI_LL_CLOCK_LANE_STATE_AUTO);
+
+ // enable the DPI output of the DSI bridge
+ mipi_dsi_brg_ll_enable_dpi_output(hal->bridge, true);
+ mipi_dsi_brg_ll_update_dpi_config(hal->bridge);
+
+ // always enable the interrupt to detect the underflow condition
+ mipi_dsi_brg_ll_enable_interrupt(hal->bridge, MIPI_DSI_BRG_LL_EVENT_UNDERRUN, true);
+
+ return ESP_OK;
+}
+
+static esp_err_t dpi_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_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base);
+ uint8_t cur_fb_index = dpi_panel->cur_fb_index;
+ uint8_t *frame_buffer = dpi_panel->fbs[cur_fb_index];
+ uint8_t *draw_buffer = (uint8_t *)color_data;
+ size_t fb_size = dpi_panel->fb_size;
+ size_t bits_per_pixel = dpi_panel->bits_per_pixel;
+
+ // clip to boundaries
+ int h_res = dpi_panel->h_pixels;
+ int v_res = dpi_panel->v_pixels;
+ x_start = MAX(x_start, 0);
+ x_end = MIN(x_end, h_res);
+ y_start = MAX(y_start, 0);
+ y_end = MIN(y_end, v_res);
+
+ bool do_copy = false;
+ uint8_t draw_buf_fb_index = 0;
+ // check if the user draw buffer resides in any frame buffer's memory range
+ // if so, we don't need to copy the data, just do cache write back
+ if (draw_buffer >= dpi_panel->fbs[0] && draw_buffer < dpi_panel->fbs[0] + fb_size) {
+ draw_buf_fb_index = 0;
+ } else if (draw_buffer >= dpi_panel->fbs[1] && draw_buffer < dpi_panel->fbs[1] + fb_size) {
+ draw_buf_fb_index = 1;
+ } else if (draw_buffer >= dpi_panel->fbs[2] && draw_buffer < dpi_panel->fbs[2] + fb_size) {
+ draw_buf_fb_index = 2;
+ } else {
+ do_copy = true;
+ }
+
+ if (!do_copy) { // no copy, just do cache memory write back
+ ESP_LOGV(TAG, "draw buffer is in frame buffer memory range, do cache write back only");
+ // only write back the LCD lines that updated by the draw buffer
+ uint8_t *cache_sync_start = dpi_panel->fbs[draw_buf_fb_index] + (y_start * dpi_panel->h_pixels) * bits_per_pixel / 8;
+ size_t cache_sync_size = (y_end - y_start) * dpi_panel->h_pixels * bits_per_pixel / 8;
+ // the buffer to be flushed is still within the frame buffer, so even an unaligned address is OK
+ esp_cache_msync(cache_sync_start, cache_sync_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
+
+ dpi_panel->cur_fb_index = draw_buf_fb_index;
+ // 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 if (!dpi_panel->fbcpy_handle) { // copy by CPU
+ ESP_LOGV(TAG, "copy draw buffer by CPU");
+ const uint8_t *from = draw_buffer;
+ uint8_t *to = frame_buffer + (y_start * dpi_panel->h_pixels + x_start) * bits_per_pixel / 8;
+ uint32_t copy_bytes_per_line = (x_end - x_start) * bits_per_pixel / 8;
+ uint32_t bytes_per_line = bits_per_pixel * dpi_panel->h_pixels / 8;
+ // please note, we assume the user provided draw_buffer is compact,
+ // but the destination is a sub-window of the frame buffer, so we need to skip the stride
+ 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;
+ }
+ uint8_t *cache_sync_start = frame_buffer + (y_start * dpi_panel->h_pixels) * bits_per_pixel / 8;
+ size_t cache_sync_size = (y_end - y_start) * dpi_panel->h_pixels * bits_per_pixel / 8;
+ // the buffer to be flushed is still within the frame buffer, so even an unaligned address is OK
+ esp_cache_msync(cache_sync_start, cache_sync_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
+ // 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 { // copy by DMA2D
+ ESP_LOGV(TAG, "copy draw buffer by DMA2D");
+ // ensure the previous draw operation is finished
+ 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
+ // Note, the user draw buffer should be 1D array, and contiguous in memory, no stride
+ size_t color_data_size = (x_end - x_start) * (y_end - y_start) * bits_per_pixel / 8;
+ esp_cache_msync(draw_buffer, 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 = draw_buffer,
+ .dst_buffer = (void *)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_type_id = dpi_panel->in_color_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");
+ }
+
+ return ESP_OK;
+}
+
+esp_err_t esp_lcd_dpi_panel_set_color_conversion(esp_lcd_panel_handle_t panel, const esp_lcd_color_conv_config_t *config)
+{
+ ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+ esp_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base);
+ esp_lcd_dsi_bus_handle_t bus = dpi_panel->bus;
+ mipi_dsi_hal_context_t *hal = &bus->hal;
+
+ if (dpi_panel->in_color_format == COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_YUV422)
+ && COLOR_SPACE_TYPE(dpi_panel->out_color_format) == LCD_COLOR_SPACE_RGB) {
+ // YUV422->RGB
+ mipi_dsi_brg_ll_set_input_color_range(hal->bridge, config->in_color_range);
+ mipi_dsi_brg_ll_set_yuv_convert_std(hal->bridge, config->spec.yuv.conv_std);
+ mipi_dsi_brg_ll_set_yuv422_pack_order(hal->bridge, config->spec.yuv.yuv422.in_pack_order);
+ } else {
+ ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "unsupported conversion mode");
+ }
+ return ESP_OK;
+}
+
+esp_err_t esp_lcd_dpi_panel_set_pattern(esp_lcd_panel_handle_t panel, mipi_dsi_pattern_type_t pattern)
+{
+ ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+ esp_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base);
+ esp_lcd_dsi_bus_handle_t bus = dpi_panel->bus;
+ mipi_dsi_hal_context_t *hal = &bus->hal;
+
+ if (pattern != MIPI_DSI_PATTERN_NONE) {
+ // stop the DSI bridge from generating the DPI stream
+ mipi_dsi_brg_ll_enable_dpi_output(hal->bridge, false);
+ mipi_dsi_brg_ll_update_dpi_config(hal->bridge);
+ }
+
+ // set the pattern type and enable the pattern generator for the DSI host controller
+ mipi_dsi_host_ll_dpi_set_pattern_type(hal->host, pattern);
+
+ if (pattern == MIPI_DSI_PATTERN_NONE) {
+ // re-enable the DSI bridge to generate the DPI stream
+ mipi_dsi_brg_ll_enable_dpi_output(hal->bridge, true);
+ mipi_dsi_brg_ll_update_dpi_config(hal->bridge);
+ }
+
+ 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);
+#if CONFIG_LCD_DSI_ISR_CACHE_SAFE
+ if (cbs->on_color_trans_done) {
+ ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_color_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_color_trans_done callback not in IRAM");
+ }
+ if (cbs->on_refresh_done) {
+ ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_refresh_done), ESP_ERR_INVALID_ARG, TAG, "on_refresh_done callback not in IRAM");
+ }
+ if (user_ctx) {
+ ESP_RETURN_ON_FALSE(esp_ptr_internal(user_ctx), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
+ }
+#endif // CONFIG_LCD_DSI_ISR_CACHE_SAFE
+ dpi_panel->on_color_trans_done = cbs->on_color_trans_done;
+ dpi_panel->on_refresh_done = cbs->on_refresh_done;
+ dpi_panel->user_ctx = user_ctx;
+
+ // enable the vsync interrupt if the callback is provided
+ mipi_dsi_brg_ll_enable_interrupt(dpi_panel->bus->hal.bridge, MIPI_DSI_BRG_LL_EVENT_VSYNC, cbs->on_refresh_done != NULL);
+
+ return ESP_OK;
+}
### components/esp_lcd/dsi/esp_lcd_panel_io_dbi.c
@@ -0,0 +1,89 @@
+/*
+ * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "esp_lcd_panel_io_interface.h"
+#include "esp_lcd_mipi_dsi.h"
+#include "mipi_dsi_priv.h"
+
+typedef struct esp_lcd_dbi_io_t esp_lcd_dbi_io_t;
+
+struct esp_lcd_dbi_io_t {
+ esp_lcd_panel_io_t base; // Base class of generic lcd panel IO
+ esp_lcd_dsi_bus_handle_t bus; // DSI bus handle
+ uint8_t virtual_channel; // Virtual channel ID, index from 0
+ int lcd_cmd_bits; // Bit-width of LCD command
+ int lcd_param_bits; // Bit-width of LCD parameter
+};
+
+static esp_err_t panel_io_dbi_del(esp_lcd_panel_io_t *io);
+static esp_err_t panel_io_dbi_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size);
+static esp_err_t panel_io_dbi_rx_param(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size);
+
+esp_err_t esp_lcd_new_panel_io_dbi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dbi_io_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
+{
+ ESP_RETURN_ON_FALSE(bus && io_config && ret_io, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+ ESP_RETURN_ON_FALSE(io_config->virtual_channel < 4, ESP_ERR_INVALID_ARG, TAG, "invalid virtual channel %d", io_config->virtual_channel);
+ mipi_dsi_hal_context_t *hal = &bus->hal;
+
+ esp_lcd_dbi_io_t *dbi_io = heap_caps_calloc(1, sizeof(esp_lcd_dbi_io_t), DSI_MEM_ALLOC_CAPS);
+ ESP_RETURN_ON_FALSE(dbi_io, ESP_ERR_NO_MEM, TAG, "no memory for DBI IO");
+ dbi_io->virtual_channel = io_config->virtual_channel;
+ dbi_io->bus = bus;
+
+ // Tear Effect is not supported
+ mipi_dsi_host_ll_enable_te_ack(hal->host, false);
+ // enable command ack, to ensure the reliability and integrity of the data transmission
+ mipi_dsi_host_ll_enable_cmd_ack(hal->host, true);
+ // using low power mode for sending generic MIPI DSI packets
+ mipi_dsi_host_ll_set_gen_short_wr_speed_mode(hal->host, 0, MIPI_DSI_LL_TRANS_SPEED_LP);
+ mipi_dsi_host_ll_set_gen_short_wr_speed_mode(hal->host, 1, MIPI_DSI_LL_TRANS_SPEED_LP);
+ mipi_dsi_host_ll_set_gen_short_wr_speed_mode(hal->host, 2, MIPI_DSI_LL_TRANS_SPEED_LP);
+ mipi_dsi_host_ll_set_gen_long_wr_speed_mode(hal->host, MIPI_DSI_LL_TRANS_SPEED_LP);
+ mipi_dsi_host_ll_set_gen_short_rd_speed_mode(hal->host, 0, MIPI_DSI_LL_TRANS_SPEED_LP);
+ mipi_dsi_host_ll_set_gen_short_rd_speed_mode(hal->host, 1, MIPI_DSI_LL_TRANS_SPEED_LP);
+ mipi_dsi_host_ll_set_gen_short_rd_speed_mode(hal->host, 2, MIPI_DSI_LL_TRANS_SPEED_LP);
+ mipi_dsi_host_ll_set_dcs_short_wr_speed_mode(hal->host, 0, MIPI_DSI_LL_TRANS_SPEED_LP);
+ mipi_dsi_host_ll_set_dcs_short_wr_speed_mode(hal->host, 1, MIPI_DSI_LL_TRANS_SPEED_LP);
+ mipi_dsi_host_ll_set_dcs_long_wr_speed_mode(hal->host, MIPI_DSI_LL_TRANS_SPEED_LP);
+ mipi_dsi_host_ll_set_dcs_short_rd_speed_mode(hal->host, 0, MIPI_DSI_LL_TRANS_SPEED_LP);
+ mipi_dsi_host_ll_set_mrps_speed_mode(hal->host, MIPI_DSI_LL_TRANS_SPEED_LP);
+
+ dbi_io->base.del = panel_io_dbi_del;
+ dbi_io->base.tx_param = panel_io_dbi_tx_param;
+ dbi_io->base.rx_param = panel_io_dbi_rx_param;
+ dbi_io->lcd_cmd_bits = io_config->lcd_cmd_bits;
+ dbi_io->lcd_param_bits = io_config->lcd_param_bits;
+ *ret_io = &dbi_io->base;
+ return ESP_OK;
+}
+
+static esp_err_t panel_io_dbi_del(esp_lcd_panel_io_t *io)
+{
+ esp_lcd_dbi_io_t *dbi_io = __containerof(io, esp_lcd_dbi_io_t, base);
+ free(dbi_io);
+ return ESP_OK;
+}
+
+static esp_err_t panel_io_dbi_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size)
+{
+ esp_lcd_dbi_io_t *dbi_io = __containerof(io, esp_lcd_dbi_io_t, base);
+ esp_lcd_dsi_bus_handle_t bus = dbi_io->bus;
+ mipi_dsi_hal_context_t *hal = &bus->hal;
+
+ mipi_dsi_hal_host_gen_write_dcs_command(hal, dbi_io->virtual_channel, lcd_cmd, dbi_io->lcd_cmd_bits / 8, param, param_size);
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_io_dbi_rx_param(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size)
+{
+ esp_lcd_dbi_io_t *dbi_io = __containerof(io, esp_lcd_dbi_io_t, base);
+ esp_lcd_dsi_bus_handle_t bus = dbi_io->bus;
+ mipi_dsi_hal_context_t *hal = &bus->hal;
+
+ mipi_dsi_hal_host_gen_read_dcs_command(hal, dbi_io->virtual_channel, lcd_cmd, dbi_io->lcd_cmd_bits / 8, param, param_size);
+
+ return ESP_OK;
+}
### components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h
@@ -0,0 +1,205 @@
+/*
+ * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "esp_err.h"
+#include "esp_lcd_types.h"
+
+typedef struct esp_lcd_dsi_bus_t *esp_lcd_dsi_bus_handle_t; /*!< Type of MIPI DSI bus handle */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief MIPI DSI bus configuration structure
+ */
+typedef struct {
+ int bus_id; /*!< Select which DSI controller, index from 0 */
+ uint8_t num_data_lanes; /*!< Number of data lanes, if set to 0, the driver will fallback to use maximum number of lanes */
+ mipi_dsi_phy_pllref_clock_source_t phy_clk_src; /*!< The clock source for the PHY PLL */
+ float lane_bit_rate_mbps; /*!< Lane bit rate in Mbps */
+} esp_lcd_dsi_bus_config_t;
+
+/**
+ * @brief Create MIPI DSI bus handle
+ *
+ * @param[in] bus_config Bus configuration
+ * @param[out] ret_bus Returned bus handle
+ * @return
+ * - ESP_OK: Create MIPI DSI bus successfully
+ * - ESP_ERR_INVALID_ARG: Create MIPI DSI bus failed because of invalid argument
+ * - ESP_ERR_NO_MEM: Create MIPI DSI bus failed because of out of memory
+ * - ESP_ERR_NOT_FOUND: Create MIPI DSI bus failed because no more free DSI hardware instance
+ * - ESP_FAIL: Create MIPI DSI bus failed because of other error
+ */
+esp_err_t esp_lcd_new_dsi_bus(const esp_lcd_dsi_bus_config_t *bus_config, esp_lcd_dsi_bus_handle_t *ret_bus);
+
+/**
+ * @brief Destroy MIPI DSI bus handle
+ *
+ * @param[in] bus MIPI DSI bus handle, returned from `esp_lcd_new_dsi_bus`
+ * @return
+ * - ESP_OK: Destroy MIPI DSI bus successfully
+ * - ESP_ERR_INVALID_ARG: Destroy MIPI DSI bus failed because of invalid argument
+ * - ESP_FAIL: Destroy MIPI DSI bus failed because of other error
+ */
+esp_err_t esp_lcd_del_dsi_bus(esp_lcd_dsi_bus_handle_t bus);
+
+/**
+ * @brief Panel IO configuration structure, for MIPI DSI command interface
+ */
+typedef struct {
+ uint8_t virtual_channel; /*!< Virtual channel ID, index from 0 */
+ int lcd_cmd_bits; /*!< Bit-width of LCD command */
+ int lcd_param_bits; /*!< Bit-width of LCD parameter */
+} esp_lcd_dbi_io_config_t;
+
+/**
+ * @brief Create LCD panel IO, for MIPI DSI DBI interface
+ *
+ * @note Although we call it "DBI", internally the driver is using a co-called "generic" interface for transmitting/receiving LCD commands and parameters.
+ *
+ * @param[in] bus MIPI DSI bus handle, returned from `esp_lcd_new_dsi_bus`
+ * @param[in] io_config IO configuration
+ * @param[out] ret_io Returned panel IO handle
+ * @return
+ * - ESP_OK: Create MIPI DSI command IO successfully
+ * - ESP_ERR_INVALID_ARG: Create MIPI DSI command IO failed because of invalid argument
+ * - ESP_ERR_NO_MEM: Create MIPI DSI command IO failed because of out of memory
+ * - ESP_FAIL: Create MIPI DSI command IO failed because of other error
+ */
+esp_err_t esp_lcd_new_panel_io_dbi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dbi_io_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io);
+
+/**
+ * @brief MIPI DSI DPI panel configuration structure
+ */
+typedef struct {
+ uint8_t virtual_channel; /*!< Virtual channel ID, index from 0 */
+ mipi_dsi_dpi_clock_source_t dpi_clk_src; /*!< MIPI DSI DPI clock source */
+ float dpi_clock_freq_mhz; /*!< Pixel clock frequency in MHz */
+ lcd_color_rgb_pixel_format_t pixel_format; /*!< Pixel format that used by the MIPI LCD device */
+ lcd_color_format_t in_color_format; /*!< Format of the input data (color space and pixel format),
+ which is the format stored in the frame buffer */
+ lcd_color_format_t out_color_format; /*!< Format of the output data (color space and pixel format),
+ which is the format that the panel can accept */
+ uint8_t num_fbs; /*!< Number of screen-sized frame buffers that allocated by the driver
+ By default (set to either 0 or 1) only one frame buffer will be created */
+ esp_lcd_video_timing_t video_timing; /*!< Video timing */
+ /// Extra configuration flags for MIPI DSI DPI panel
+ struct extra_dpi_panel_flags {
+ uint32_t use_dma2d: 1; /*!< Use DMA2D to copy user buffer to the frame buffer when necessary */
+ uint32_t disable_lp: 1;/*!< Disable low-power for DPI */
+ } flags; /*!< Extra configuration flags */
+} esp_lcd_dpi_panel_config_t;
+
+/**
+ * @brief Create LCD panel for MIPI DSI DPI interface
+ *
+ * @param[in] bus MIPI DSI bus handle, returned from `esp_lcd_new_dsi_bus`
+ * @param[in] panel_config DSI data panel configuration
+ * @param[out] ret_panel Returned LCD panel handle
+ * @return
+ * - ESP_OK: Create MIPI DSI data panel successfully
+ * - 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_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_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);
+
+/**
+ * @brief Get the address of the frame buffer(s) that allocated by the driver
+ *
+ * @param[in] dpi_panel MIPI DPI panel handle, returned from esp_lcd_new_panel_dpi()
+ * @param[in] fb_num Number of frame buffer(s) to get. This value must be the same as the number of the followed parameters.
+ * @param[out] fb0 Address of the frame buffer 0 (first frame buffer)
+ * @param[out] ... List of other frame buffers if any
+ * @return
+ * - ESP_ERR_INVALID_ARG: Get frame buffer address failed because of invalid argument
+ * - ESP_OK: Get frame buffer address successfully
+ */
+esp_err_t esp_lcd_dpi_panel_get_frame_buffer(esp_lcd_panel_handle_t dpi_panel, uint32_t fb_num, void **fb0, ...);
+
+/**
+ * @brief Set pre-defined pattern to the screen for testing or debugging purpose
+ *
+ * @param[in] dpi_panel MIPI DPI panel handle, returned from esp_lcd_new_panel_dpi()
+ * @param[in] pattern Pattern type
+ * @return
+ * - ESP_OK: Set pattern successfully
+ * - ESP_ERR_INVALID_ARG: Set pattern failed because of invalid argument
+ * - ESP_FAIL: Set pattern failed because of other error
+ */
+esp_err_t esp_lcd_dpi_panel_set_pattern(esp_lcd_panel_handle_t dpi_panel, mipi_dsi_pattern_type_t pattern);
+
+/**
+ * @brief Set color conversion configuration for DPI panel
+ *
+ * @param[in] dpi_panel MIPI DPI panel handle, returned from esp_lcd_new_panel_dpi()
+ * @param[in] config Color conversion configuration
+ * @return
+ * - ESP_OK: Set color conversion configuration successfully
+ * - ESP_ERR_INVALID_ARG: Set color conversion configuration failed because of invalid argument
+ * - ESP_FAIL: Set color conversion configuration failed because of other error
+ */
+esp_err_t esp_lcd_dpi_panel_set_color_conversion(esp_lcd_panel_handle_t dpi_panel, const esp_lcd_color_conv_config_t *config);
+
+/**
+ * @brief Type of LCD DPI panel event data
+ */
+typedef struct {
+} esp_lcd_dpi_panel_event_data_t;
+
+/**
+ * @brief A general function callback prototype for DPI panel driver
+ *
+ * @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_general_cb_t)(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx);
+
+/**
+ * @brief Declare the prototype of the function that will be invoked
+ * when driver finishes coping user's color buffer to frame buffer
+ */
+typedef esp_lcd_dpi_panel_general_cb_t esp_lcd_dpi_panel_color_trans_done_cb_t;
+
+/**
+ * @brief Declare the prototype of the function that will be invoked
+ * when driver finishes refreshing the frame buffer to the screen
+ */
+typedef esp_lcd_dpi_panel_general_cb_t esp_lcd_dpi_panel_refresh_done_cb_t;
+
+/**
+ * @brief Type of LCD DPI panel callbacks
+ */
+typedef struct {
+ esp_lcd_dpi_panel_color_trans_done_cb_t on_color_trans_done; /*!< Invoked when user's color buffer copied to the internal frame buffer.
+ This is an indicator that the draw buffer can be recycled safely.
+ But doesn't mean the draw buffer finishes the refreshing to the screen. */
+ esp_lcd_dpi_panel_refresh_done_cb_t on_refresh_done; /*!< Invoked when the internal frame buffer finishes refreshing to the screen */
+} 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
+}
+#endif
### components/esp_lcd/dsi/mipi_dsi_priv.h
@@ -0,0 +1,54 @@
+/*
+ * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdint.h>
+#include "sdkconfig.h"
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+// The local log level must be defined before including esp_log.h
+// Set the maximum log level for gptimer driver
+#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
+#endif
+#include "soc/mipi_dsi_periph.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/semphr.h"
+#include "esp_err.h"
+#include "esp_log.h"
+#include "esp_check.h"
+#include "esp_attr.h"
+#include "esp_heap_caps.h"
+#include "esp_private/periph_ctrl.h"
+#include "esp_private/esp_clk_tree_common.h"
+#include "esp_pm.h"
+#include "hal/mipi_dsi_hal.h"
+#include "hal/mipi_dsi_ll.h"
+
+#if CONFIG_LCD_DSI_OBJ_FORCE_INTERNAL
+#define DSI_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
+#else
+#define DSI_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
+#endif
+
+#define DPI_PANEL_MAX_FB_NUM 3 // maximum number of frame buffers that can be maintained by the driver
+#define DPI_PANEL_MIN_DMA_NODES_PER_LINK 1 // NOTE: we assume 1 DMA link item can carry the WHOLE image
+
+///!< Logging settings
+#define TAG "lcd.dsi"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct esp_lcd_dsi_bus_t {
+ int bus_id;
+ mipi_dsi_hal_context_t hal;
+ esp_pm_lock_handle_t pm_lock;
+} esp_lcd_dsi_bus_t;
+
+#ifdef __cplusplus
+}
+#endif
### components/esp_lcd/i2c/esp_lcd_panel_io_i2c_v1.c
@@ -0,0 +1,212 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/cdefs.h>
+#include "sdkconfig.h"
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+// The local log level must be defined before including esp_log.h
+// Set the maximum log level for this source file
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#endif
+#include "esp_lcd_panel_io_interface.h"
+#include "esp_lcd_panel_io.h"
+#include "driver/i2c.h"
+#include "driver/gpio.h"
+#include "esp_log.h"
+#include "esp_check.h"
+#include "esp_compiler.h"
+
+static const char *TAG = "lcd_panel.io.i2c";
+
+#define CMD_HANDLER_BUFFER_SIZE I2C_LINK_RECOMMENDED_SIZE(2) // only 2 operations will be queued in the handler ATTOW
+#define BYTESHIFT(VAR, IDX) (((VAR) >> ((IDX) * 8)) & 0xFF)
+
+static esp_err_t panel_io_i2c_del(esp_lcd_panel_io_t *io);
+static esp_err_t panel_io_i2c_rx_param(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size);
+static esp_err_t panel_io_i2c_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size);
+static esp_err_t panel_io_i2c_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size);
+static esp_err_t panel_io_i2c_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx);
+
+typedef struct {
+ esp_lcd_panel_io_t base; // Base class of generic lcd panel io
+ uint32_t i2c_bus_id; // I2C bus id, indicating which I2C port
+ uint32_t dev_addr; // Device address
+ int lcd_cmd_bits; // Bit width of LCD command
+ int lcd_param_bits; // Bit width of LCD parameter
+ bool control_phase_enabled; // Is control phase enabled
+ uint32_t control_phase_cmd; // control byte when transferring command
+ uint32_t control_phase_data; // control byte when transferring data
+ esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; // User register's callback, invoked when color data trans done
+ void *user_ctx; // User's private data, passed directly to callback on_color_trans_done()
+ uint8_t cmdlink_buffer[]; // pre-alloc I2C command link buffer, to be reused in all transactions
+} lcd_panel_io_i2c_t;
+
+esp_err_t esp_lcd_new_panel_io_i2c_v1(uint32_t bus, const esp_lcd_panel_io_i2c_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
+{
+ // leak detection of i2c_panel_io because saving i2c_panel_io->base address
+ ESP_COMPILER_DIAGNOSTIC_PUSH_IGNORE("-Wanalyzer-malloc-leak")
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+ esp_log_level_set(TAG, ESP_LOG_DEBUG);
+#endif
+ esp_err_t ret = ESP_OK;
+ lcd_panel_io_i2c_t *i2c_panel_io = NULL;
+ ESP_GOTO_ON_FALSE(io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
+ ESP_GOTO_ON_FALSE(io_config->control_phase_bytes * 8 > io_config->dc_bit_offset, ESP_ERR_INVALID_ARG, err, TAG, "D/C bit exceeds control bytes");
+ ESP_GOTO_ON_FALSE(io_config->scl_speed_hz == 0, ESP_ERR_INVALID_ARG, err, TAG, "scl_speed_hz is not need to set in legacy i2c_lcd driver");
+ i2c_panel_io = calloc(1, sizeof(lcd_panel_io_i2c_t) + CMD_HANDLER_BUFFER_SIZE); // expand zero-length array cmdlink_buffer
+ ESP_GOTO_ON_FALSE(i2c_panel_io, ESP_ERR_NO_MEM, err, TAG, "no mem for i2c panel io");
+
+ i2c_panel_io->i2c_bus_id = bus;
+ i2c_panel_io->lcd_cmd_bits = io_config->lcd_cmd_bits;
+ i2c_panel_io->lcd_param_bits = io_config->lcd_param_bits;
+ i2c_panel_io->on_color_trans_done = io_config->on_color_trans_done;
+ i2c_panel_io->user_ctx = io_config->user_ctx;
+ i2c_panel_io->control_phase_enabled = (!io_config->flags.disable_control_phase);
+ i2c_panel_io->control_phase_data = (!io_config->flags.dc_low_on_data) << (io_config->dc_bit_offset);
+ i2c_panel_io->control_phase_cmd = (io_config->flags.dc_low_on_data) << (io_config->dc_bit_offset);
+ i2c_panel_io->dev_addr = io_config->dev_addr;
+ i2c_panel_io->base.del = panel_io_i2c_del;
+ i2c_panel_io->base.rx_param = panel_io_i2c_rx_param;
+ i2c_panel_io->base.tx_param = panel_io_i2c_tx_param;
+ i2c_panel_io->base.tx_color = panel_io_i2c_tx_color;
+ i2c_panel_io->base.register_event_callbacks = panel_io_i2c_register_event_callbacks;
+ *ret_io = &(i2c_panel_io->base);
+ ESP_LOGD(TAG, "new i2c lcd panel io @%p", i2c_panel_io);
+
+ return ESP_OK;
+err:
+ return ret;
+ ESP_COMPILER_DIAGNOSTIC_POP("-Wanalyzer-malloc-leak")
+}
+
+static esp_err_t panel_io_i2c_del(esp_lcd_panel_io_t *io)
+{
+ esp_err_t ret = ESP_OK;
+ lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base);
+
+ ESP_LOGD(TAG, "del lcd panel i2c @%p", i2c_panel_io);
+ free(i2c_panel_io);
+ return ret;
+}
+
+static esp_err_t panel_io_i2c_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx)
+{
+ lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base);
+
+ if (i2c_panel_io->on_color_trans_done != NULL) {
+ ESP_LOGW(TAG, "Callback on_color_trans_done was already set and now it was overwritten!");
+ }
+
+ i2c_panel_io->on_color_trans_done = cbs->on_color_trans_done;
+ i2c_panel_io->user_ctx = user_ctx;
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_io_i2c_rx_buffer(esp_lcd_panel_io_t *io, int lcd_cmd, void *buffer, size_t buffer_size)
+{
+ esp_err_t ret = ESP_OK;
+ lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base);
+ bool send_param = (lcd_cmd >= 0);
+
+ i2c_cmd_handle_t cmd_link = i2c_cmd_link_create_static(i2c_panel_io->cmdlink_buffer, CMD_HANDLER_BUFFER_SIZE);
+ ESP_GOTO_ON_FALSE(cmd_link, ESP_ERR_NO_MEM, err, TAG, "no mem for i2c cmd link");
+ ESP_GOTO_ON_ERROR(i2c_master_start(cmd_link), err, TAG, "issue start failed"); // start phase
+ ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, (i2c_panel_io->dev_addr << 1) | I2C_MASTER_WRITE, true), err, TAG, "write address failed"); // address phase
+ if (send_param) {
+ if (i2c_panel_io->control_phase_enabled) {
+ ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, i2c_panel_io->control_phase_cmd, true),
+ err, TAG, "write control phase failed"); // control phase
+ }
+ uint8_t cmds[4] = {BYTESHIFT(lcd_cmd, 3), BYTESHIFT(lcd_cmd, 2), BYTESHIFT(lcd_cmd, 1), BYTESHIFT(lcd_cmd, 0)};
+ size_t cmds_size = i2c_panel_io->lcd_cmd_bits / 8;
+ if (cmds_size > 0 && cmds_size <= sizeof(cmds)) {
+ ESP_GOTO_ON_ERROR(i2c_master_write(cmd_link, cmds + (sizeof(cmds) - cmds_size), cmds_size, true), err, TAG, "write LCD cmd failed");
+ }
+ }
+
+ if (buffer) {
+ ESP_GOTO_ON_ERROR(i2c_master_start(cmd_link), err, TAG, "issue start failed"); // start phase
+ ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, (i2c_panel_io->dev_addr << 1) | I2C_MASTER_READ, true), err, TAG, "write address failed"); // address phase
+ ESP_GOTO_ON_ERROR(i2c_master_read(cmd_link, buffer, buffer_size, I2C_MASTER_LAST_NACK), err, TAG, "read data failed");
+ }
+
+ ESP_GOTO_ON_ERROR(i2c_master_stop(cmd_link), err, TAG, "issue stop failed"); // stop phase
+ ESP_GOTO_ON_ERROR(i2c_master_cmd_begin(i2c_panel_io->i2c_bus_id, cmd_link, portMAX_DELAY), err, TAG, "i2c transaction failed");
+
+ i2c_cmd_link_delete_static(cmd_link);
+
+ return ESP_OK;
+err:
+ if (cmd_link) {
+ i2c_cmd_link_delete_static(cmd_link);
+ }
+ return ret;
+}
+
+static esp_err_t panel_io_i2c_tx_buffer(esp_lcd_panel_io_t *io, int lcd_cmd, const void *buffer, size_t buffer_size, bool is_param)
+{
+ esp_err_t ret = ESP_OK;
+ lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base);
+ bool send_param = (lcd_cmd >= 0);
+
+ i2c_cmd_handle_t cmd_link = i2c_cmd_link_create_static(i2c_panel_io->cmdlink_buffer, CMD_HANDLER_BUFFER_SIZE);
+ ESP_GOTO_ON_FALSE(cmd_link, ESP_ERR_NO_MEM, err, TAG, "no mem for i2c cmd link");
+ ESP_GOTO_ON_ERROR(i2c_master_start(cmd_link), err, TAG, "issue start failed"); // start phase
+ ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, (i2c_panel_io->dev_addr << 1) | I2C_MASTER_WRITE, true), err, TAG, "write address failed"); // address phase
+ if (i2c_panel_io->control_phase_enabled) {
+ ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, is_param ? i2c_panel_io->control_phase_cmd : i2c_panel_io->control_phase_data, true),
+ err, TAG, "write control phase failed"); // control phase
+ }
+
+ // some displays don't want any additional commands on data transfers
+ if (send_param) {
+ uint8_t cmds[4] = {BYTESHIFT(lcd_cmd, 3), BYTESHIFT(lcd_cmd, 2), BYTESHIFT(lcd_cmd, 1), BYTESHIFT(lcd_cmd, 0)};
+ size_t cmds_size = i2c_panel_io->lcd_cmd_bits / 8;
+ if (cmds_size > 0 && cmds_size <= sizeof(cmds)) {
+ ESP_GOTO_ON_ERROR(i2c_master_write(cmd_link, cmds + (sizeof(cmds) - cmds_size), cmds_size, true), err, TAG, "write LCD cmd failed");
+ }
+ }
+
+ if (buffer) {
+ ESP_GOTO_ON_ERROR(i2c_master_write(cmd_link, buffer, buffer_size, true), err, TAG, "write data failed");
+ }
+
+ ESP_GOTO_ON_ERROR(i2c_master_stop(cmd_link), err, TAG, "issue stop failed"); // stop phase
+ ESP_GOTO_ON_ERROR(i2c_master_cmd_begin(i2c_panel_io->i2c_bus_id, cmd_link, portMAX_DELAY), err, TAG, "i2c transaction failed");
+ i2c_cmd_link_delete_static(cmd_link);
+
+ if (!is_param) {
+ // trans done callback
+ if (i2c_panel_io->on_color_trans_done) {
+ i2c_panel_io->on_color_trans_done(&(i2c_panel_io->base), NULL, i2c_panel_io->user_ctx);
+ }
+ }
+
+ return ESP_OK;
+err:
+ if (cmd_link) {
+ i2c_cmd_link_delete_static(cmd_link);
+ }
+ return ret;
+}
+
+static esp_err_t panel_io_i2c_rx_param(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size)
+{
+ return panel_io_i2c_rx_buffer(io, lcd_cmd, param, param_size);
+}
+
+static esp_err_t panel_io_i2c_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size)
+{
+ return panel_io_i2c_tx_buffer(io, lcd_cmd, param, param_size, true);
+}
+
+static esp_err_t panel_io_i2c_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size)
+{
+ return panel_io_i2c_tx_buffer(io, lcd_cmd, color, color_size, false);
+}
### components/esp_lcd/i2c/esp_lcd_panel_io_i2c_v2.c
@@ -0,0 +1,219 @@
+/*
+ * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/cdefs.h>
+#include "sdkconfig.h"
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+// The local log level must be defined before including esp_log.h
+// Set the maximum log level for this source file
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#endif
+#include "esp_lcd_panel_io_interface.h"
+#include "esp_lcd_panel_io.h"
+#include "driver/i2c_master.h"
+#include "driver/gpio.h"
+#include "esp_log.h"
+#include "esp_check.h"
+#include "freertos/FreeRTOS.h"
+#include "esp_heap_caps.h"
+#include "esp_compiler.h"
+
+static const char *TAG = "lcd_panel.io.i2c";
+
+#define BYTESHIFT(VAR, IDX) (((VAR) >> ((IDX) * 8)) & 0xFF)
+#define CONTROL_PHASE_LENGTH (1)
+#define CMD_LENGTH (4)
+
+static esp_err_t panel_io_i2c_del(esp_lcd_panel_io_t *io);
+static esp_err_t panel_io_i2c_rx_param(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size);
+static esp_err_t panel_io_i2c_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size);
+static esp_err_t panel_io_i2c_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size);
+static esp_err_t panel_io_i2c_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx);
+
+typedef struct {
+ esp_lcd_panel_io_t base; // Base class of generic lcd panel io
+ i2c_master_dev_handle_t i2c_handle; // I2C master driver handle.
+ uint32_t dev_addr; // Device address
+ int lcd_cmd_bits; // Bit width of LCD command
+ int lcd_param_bits; // Bit width of LCD parameter
+ bool control_phase_enabled; // Is control phase enabled
+ uint32_t control_phase_cmd; // control byte when transferring command
+ uint32_t control_phase_data; // control byte when transferring data
+ esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; // User register's callback, invoked when color data trans done
+ void *user_ctx; // User's private data, passed directly to callback on_color_trans_done()
+} lcd_panel_io_i2c_t;
+
+esp_err_t esp_lcd_new_panel_io_i2c_v2(i2c_master_bus_handle_t bus, const esp_lcd_panel_io_i2c_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
+{
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+ esp_log_level_set(TAG, ESP_LOG_DEBUG);
+#endif
+ esp_err_t ret = ESP_OK;
+ lcd_panel_io_i2c_t *i2c_panel_io = NULL;
+ i2c_master_dev_handle_t i2c_handle = NULL;
+ ESP_GOTO_ON_FALSE(io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
+ ESP_GOTO_ON_FALSE(io_config->control_phase_bytes * 8 > io_config->dc_bit_offset, ESP_ERR_INVALID_ARG, err, TAG, "D/C bit exceeds control bytes");
+ // leak detection of i2c_panel_io because saving i2c_panel_io->base address
+ ESP_COMPILER_DIAGNOSTIC_PUSH_IGNORE("-Wanalyzer-malloc-leak")
+ i2c_panel_io = calloc(1, sizeof(lcd_panel_io_i2c_t));
+ ESP_GOTO_ON_FALSE(i2c_panel_io, ESP_ERR_NO_MEM, err, TAG, "no mem for i2c panel io");
+
+ i2c_device_config_t i2c_lcd_cfg = {
+ .device_address = io_config->dev_addr,
+ .scl_speed_hz = io_config->scl_speed_hz,
+ };
+ ESP_GOTO_ON_ERROR(i2c_master_bus_add_device(bus, &i2c_lcd_cfg, &i2c_handle), err, TAG, "i2c add device fail");
+
+ i2c_panel_io->i2c_handle = i2c_handle;
+ i2c_panel_io->lcd_cmd_bits = io_config->lcd_cmd_bits;
+ i2c_panel_io->lcd_param_bits = io_config->lcd_param_bits;
+ i2c_panel_io->on_color_trans_done = io_config->on_color_trans_done;
+ i2c_panel_io->user_ctx = io_config->user_ctx;
+ i2c_panel_io->control_phase_enabled = (!io_config->flags.disable_control_phase);
+ i2c_panel_io->control_phase_data = (!io_config->flags.dc_low_on_data) << (io_config->dc_bit_offset);
+ i2c_panel_io->control_phase_cmd = (io_config->flags.dc_low_on_data) << (io_config->dc_bit_offset);
+ i2c_panel_io->dev_addr = io_config->dev_addr;
+ i2c_panel_io->base.del = panel_io_i2c_del;
+ i2c_panel_io->base.rx_param = panel_io_i2c_rx_param;
+ i2c_panel_io->base.tx_param = panel_io_i2c_tx_param;
+ i2c_panel_io->base.tx_color = panel_io_i2c_tx_color;
+ i2c_panel_io->base.register_event_callbacks = panel_io_i2c_register_event_callbacks;
+ *ret_io = &(i2c_panel_io->base);
+ ESP_LOGD(TAG, "new i2c lcd panel io @%p", i2c_panel_io);
+
+ return ESP_OK;
+err:
+ if (i2c_panel_io) {
+ free(i2c_panel_io);
+ }
+ return ret;
+ ESP_COMPILER_DIAGNOSTIC_POP("-Wanalyzer-malloc-leak")
+}
+
+static esp_err_t panel_io_i2c_del(esp_lcd_panel_io_t *io)
+{
+ esp_err_t ret = ESP_OK;
+ lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base);
+
+ ESP_LOGD(TAG, "del lcd panel io i2c @%p", i2c_panel_io);
+ ESP_ERROR_CHECK(i2c_master_bus_rm_device(i2c_panel_io->i2c_handle));
+ free(i2c_panel_io);
+ return ret;
+}
+
+static esp_err_t panel_io_i2c_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx)
+{
+ lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base);
+
+ if (i2c_panel_io->on_color_trans_done != NULL) {
+ ESP_LOGW(TAG, "Callback on_color_trans_done was already set and now it was overwritten!");
+ }
+
+ i2c_panel_io->on_color_trans_done = cbs->on_color_trans_done;
+ i2c_panel_io->user_ctx = user_ctx;
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_io_i2c_rx_buffer(esp_lcd_panel_io_t *io, int lcd_cmd, void *buffer, size_t buffer_size)
+{
+ esp_err_t ret = ESP_OK;
+ lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base);
+ bool send_param = (lcd_cmd >= 0);
+
+ if (send_param) {
+ int write_size = 0;
+ uint8_t write_buffer[CONTROL_PHASE_LENGTH + CMD_LENGTH] = {0};
+
+ if (i2c_panel_io->control_phase_enabled) {
+ write_buffer[0] = i2c_panel_io->control_phase_cmd;
+ write_size += 1;
+
+ }
+ uint8_t cmds[4] = {BYTESHIFT(lcd_cmd, 3), BYTESHIFT(lcd_cmd, 2), BYTESHIFT(lcd_cmd, 1), BYTESHIFT(lcd_cmd, 0)};
+ size_t cmds_size = i2c_panel_io->lcd_cmd_bits / 8;
+ if (cmds_size > 0 && cmds_size <= sizeof(cmds)) {
+ memcpy(write_buffer + write_size, cmds + (sizeof(cmds) - cmds_size), cmds_size);
+ write_size += cmds_size;
+ }
+
+ ESP_GOTO_ON_ERROR(i2c_master_transmit_receive(i2c_panel_io->i2c_handle, write_buffer, write_size, buffer, buffer_size, -1), err, TAG, "i2c transaction failed");
+ } else {
+ ESP_GOTO_ON_ERROR(i2c_master_receive(i2c_panel_io->i2c_handle, buffer, buffer_size, -1), err, TAG, "i2c transaction failed");
+ }
+
+ return ESP_OK;
+err:
+ return ret;
+}
+
+static esp_err_t panel_io_i2c_tx_buffer(esp_lcd_panel_io_t *io, int lcd_cmd, const void *buffer, size_t buffer_size, bool is_param)
+{
+ esp_err_t ret = ESP_OK;
+ lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base);
+ bool send_param = (lcd_cmd >= 0);
+
+ uint8_t control_phase_byte = 0;
+ size_t control_phase_size = 0;
+ if (i2c_panel_io->control_phase_enabled) {
+ control_phase_byte = is_param ? i2c_panel_io->control_phase_cmd : i2c_panel_io->control_phase_data;
+ control_phase_size = 1;
+ }
+
+ uint8_t *cmd_buffer = NULL;
+ size_t cmd_buffer_size = 0;
+ // some displays don't want any additional commands on data transfers
+ uint8_t cmds[4] = {BYTESHIFT(lcd_cmd, 3), BYTESHIFT(lcd_cmd, 2), BYTESHIFT(lcd_cmd, 1), BYTESHIFT(lcd_cmd, 0)};
+ if (send_param) {
+ size_t cmds_size = i2c_panel_io->lcd_cmd_bits / 8;
+ if (cmds_size > 0 && cmds_size <= sizeof(cmds)) {
+ cmd_buffer = cmds + (sizeof(cmds) - cmds_size);
+ cmd_buffer_size = cmds_size;
+ }
+ }
+
+ uint8_t *lcd_buffer = NULL;
+ size_t lcd_buffer_size = 0;
+ if (buffer) {
+ lcd_buffer = (uint8_t*)buffer;
+ lcd_buffer_size = buffer_size;
+ }
+
+ i2c_master_transmit_multi_buffer_info_t lcd_i2c_buffer[3] = {
+ {.write_buffer = &control_phase_byte, .buffer_size = control_phase_size},
+ {.write_buffer = cmd_buffer, .buffer_size = cmd_buffer_size},
+ {.write_buffer = lcd_buffer, .buffer_size = lcd_buffer_size},
+ };
+
+ ESP_GOTO_ON_ERROR(i2c_master_multi_buffer_transmit(i2c_panel_io->i2c_handle, lcd_i2c_buffer, sizeof(lcd_i2c_buffer) / sizeof(i2c_master_transmit_multi_buffer_info_t), -1), err, TAG, "i2c transaction failed");
+ if (!is_param) {
+ // trans done callback
+ if (i2c_panel_io->on_color_trans_done) {
+ i2c_panel_io->on_color_trans_done(&(i2c_panel_io->base), NULL, i2c_panel_io->user_ctx);
+ }
+ }
+
+ return ESP_OK;
+err:
+ return ret;
+}
+
+static esp_err_t panel_io_i2c_rx_param(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size)
+{
+ return panel_io_i2c_rx_buffer(io, lcd_cmd, param, param_size);
+}
+
+static esp_err_t panel_io_i2c_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size)
+{
+ return panel_io_i2c_tx_buffer(io, lcd_cmd, param, param_size, true);
+}
+
+static esp_err_t panel_io_i2c_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size)
+{
+ return panel_io_i2c_tx_buffer(io, lcd_cmd, color, color_size, false);
+}
### components/esp_lcd/i80/esp_lcd_panel_io_i2s.c
@@ -0,0 +1,840 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Although we're manipulating I2S peripheral (on esp32/s2 target), it has nothing to do with the AUDIO BUS.
+// In fact, we're simulating the Intel 8080 bus with I2S peripheral, in a special parallel mode.
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include "sdkconfig.h"
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+// The local log level must be defined before including esp_log.h
+// Set the maximum log level for this source file
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#endif
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/queue.h"
+#include "esp_attr.h"
+#include "esp_check.h"
+#include "esp_intr_alloc.h"
+#include "esp_heap_caps.h"
+#include "esp_pm.h"
+#include "esp_lcd_panel_io_interface.h"
+#include "esp_lcd_panel_io.h"
+#include "esp_lcd_common.h"
+#include "esp_rom_gpio.h"
+#include "soc/soc_caps.h"
+#include "hal/gpio_hal.h"
+#include "driver/gpio.h"
+#include "esp_clk_tree.h"
+#include "esp_private/periph_ctrl.h"
+#include "esp_private/i2s_platform.h"
+#include "esp_private/gdma_link.h"
+#include "esp_private/esp_dma_utils.h"
+#include "esp_private/gpio.h"
+#include "soc/lcd_periph.h"
+#include "hal/i2s_hal.h"
+#include "hal/i2s_ll.h"
+#include "hal/i2s_types.h"
+
+static const char *TAG = "lcd_panel.io.i80";
+
+typedef struct esp_lcd_i80_bus_t esp_lcd_i80_bus_t;
+typedef struct lcd_panel_io_i80_t lcd_panel_io_i80_t;
+typedef struct lcd_i80_trans_descriptor_t lcd_i80_trans_descriptor_t;
+
+static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size);
+static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size);
+static esp_err_t panel_io_i80_del(esp_lcd_panel_io_t *io);
+static esp_err_t i2s_lcd_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_clock_source_t src);
+static esp_err_t i2s_lcd_init_dma_link(esp_lcd_i80_bus_handle_t bus);
+static esp_err_t i2s_lcd_configure_gpio(esp_lcd_i80_bus_handle_t bus, const esp_lcd_i80_bus_config_t *bus_config);
+static void i2s_lcd_trigger_quick_trans_done_event(esp_lcd_i80_bus_handle_t bus);
+static void lcd_i80_switch_devices(lcd_panel_io_i80_t *cur_device, lcd_panel_io_i80_t *next_device);
+static void i2s_lcd_default_isr_handler(void *args);
+static esp_err_t panel_io_i80_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx);
+
+struct esp_lcd_i80_bus_t {
+ int bus_id; // Bus ID, index from 0
+ portMUX_TYPE spinlock; // spinlock used to protect i80 bus members(hal, device_list, cur_trans)
+ i2s_hal_context_t hal; // Hal object
+ size_t bus_width; // Number of data lines
+ int dc_gpio_num; // GPIO used for DC line
+ int wr_gpio_num; // GPIO used for WR line
+ intr_handle_t intr; // LCD peripheral interrupt handle
+ esp_pm_lock_handle_t pm_lock; // lock APB frequency when necessary
+ size_t max_transfer_bytes; // Maximum number of bytes that can be transferred in one transaction
+ gdma_link_list_handle_t dma_link; // DMA link list handle
+ uint8_t *format_buffer;// The driver allocates an internal buffer for DMA to do data format transformer
+ unsigned long resolution_hz; // LCD_CLK resolution, determined by selected clock source
+ lcd_i80_trans_descriptor_t *cur_trans; // Current transaction
+ lcd_panel_io_i80_t *cur_device; // Current working device
+ LIST_HEAD(i80_device_list, lcd_panel_io_i80_t) device_list; // Head of i80 device list
+ struct {
+ unsigned int exclusive: 1; // Indicate whether the I80 bus is owned by one device (whose CS GPIO is not assigned) exclusively
+ } flags;
+};
+
+struct lcd_i80_trans_descriptor_t {
+ lcd_panel_io_i80_t *i80_device; // i80 device issuing this transaction
+ const void *data; // Data buffer
+ uint32_t data_length; // Data buffer size
+ esp_lcd_panel_io_color_trans_done_cb_t trans_done_cb; // transaction done callback
+ void *user_ctx; // private data used by trans_done_cb
+ struct {
+ unsigned int dc_level: 1; // Level of DC line for this transaction
+ } flags;
+};
+
+struct lcd_panel_io_i80_t {
+ esp_lcd_panel_io_t base; // Base class of generic lcd panel io
+ esp_lcd_i80_bus_t *bus; // Which bus the device is attached to
+ int cs_gpio_num; // GPIO used for CS line
+ uint32_t pclk_hz; // PCLK clock frequency
+ size_t clock_prescale; // Prescaler coefficient, determined by user's configured PCLK frequency
+ QueueHandle_t trans_queue; // Transaction queue, transactions in this queue are pending for scheduler to dispatch
+ QueueHandle_t done_queue; // Transaction done queue, transactions in this queue are finished but not recycled by the caller
+ size_t queue_size; // Size of transaction queue
+ size_t num_trans_inflight; // Number of transactions that are undergoing (the descriptor not recycled yet)
+ int lcd_cmd_bits; // Bit width of LCD command
+ int lcd_param_bits; // Bit width of LCD parameter
+ void *user_ctx; // private data used when transfer color data
+ esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; // color data trans done callback
+ LIST_ENTRY(lcd_panel_io_i80_t) device_list_entry; // Entry of i80 device list
+ struct {
+ unsigned int dc_cmd_level: 1; // Level of DC line in CMD phase
+ unsigned int dc_data_level: 1; // Level of DC line in DATA phase
+ } dc_levels;
+ struct {
+ unsigned int cs_active_high: 1; // Whether the CS line is active on high level
+ unsigned int swap_color_bytes: 1; // Swap adjacent two data bytes before sending out
+ unsigned int pclk_idle_low: 1; // The WR line keeps at low level in IDLE phase
+ } flags;
+ lcd_i80_trans_descriptor_t trans_pool[]; // Transaction pool
+};
+
+esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lcd_i80_bus_handle_t *ret_bus)
+{
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+ esp_log_level_set(TAG, ESP_LOG_DEBUG);
+#endif
+ esp_err_t ret = ESP_OK;
+ esp_lcd_i80_bus_t *bus = NULL;
+ ESP_GOTO_ON_FALSE(bus_config && ret_bus, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
+ // although I2S bus supports up to 24 parallel data lines, we restrict users to only use 8 or 16 bit width
+ ESP_GOTO_ON_FALSE(bus_config->bus_width == 8 || bus_config->bus_width == 16, ESP_ERR_INVALID_ARG, err,
+ TAG, "invalid bus width:%d", bus_config->bus_width);
+ size_t max_transfer_bytes = (bus_config->max_transfer_bytes + 3) & ~0x03; // align up to 4 bytes
+#if SOC_I2S_TRANS_SIZE_ALIGN_WORD
+ // double the size of the internal DMA buffer if bus_width is 8,
+ // because one I2S FIFO (4 bytes) will only contain two bytes of valid data
+ max_transfer_bytes = max_transfer_bytes * 16 / bus_config->bus_width + 4;
+#endif
+ // allocate i80 bus memory
+ bus = heap_caps_calloc(1, sizeof(esp_lcd_i80_bus_t), LCD_I80_MEM_ALLOC_CAPS);
+ ESP_GOTO_ON_FALSE(bus, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 bus");
+ size_t num_dma_nodes = esp_dma_calculate_node_count(max_transfer_bytes, 1, LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE);
+ // create DMA link list
+ gdma_link_list_config_t dma_link_config = {
+ .item_alignment = 4, // 4 bytes alignment for each DMA descriptor
+ .num_items = num_dma_nodes,
+ .flags = {
+ .check_owner = true,
+ },
+ };
+ ESP_GOTO_ON_ERROR(gdma_new_link_list(&dma_link_config, &bus->dma_link), err, TAG, "create DMA link list failed");
+ bus->bus_id = -1;
+ bus->max_transfer_bytes = max_transfer_bytes;
+#if SOC_I2S_TRANS_SIZE_ALIGN_WORD
+ // transform format for LCD commands, parameters and color data, so we need a big buffer
+ bus->format_buffer = heap_caps_calloc(1, max_transfer_bytes, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT | MALLOC_CAP_DMA);
+#else
+ // only transform format for LCD parameters, buffer size depends on specific LCD, set at compile time
+ bus->format_buffer = heap_caps_calloc(1, LCD_I80_IO_FORMAT_BUF_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT | MALLOC_CAP_DMA);
+#endif // SOC_I2S_TRANS_SIZE_ALIGN_WORD
+ ESP_GOTO_ON_FALSE(bus->format_buffer, ESP_ERR_NO_MEM, err, TAG, "no mem for format buffer");
+ // LCD mode can't work with other modes at the same time, we need to register the driver object to the I2S platform
+ int bus_id = -1;
+ for (int i = 0; i < SOC_LCD_I80_BUSES; i++) {
+ if (i2s_platform_acquire_occupation(I2S_CTLR_HP, i, "esp_lcd_panel_io_i2s") == ESP_OK) {
+ bus_id = i;
+ break;
+ }
+ }
+ ESP_GOTO_ON_FALSE(bus_id != -1, ESP_ERR_NOT_FOUND, err, TAG, "no free i80 bus slot");
+ bus->bus_id = bus_id;
+ // initialize HAL layer
+ i2s_hal_init(&bus->hal, bus->bus_id);
+ // set peripheral clock resolution
+ ret = i2s_lcd_select_periph_clock(bus, bus_config->clk_src);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "select periph clock failed");
+ // reset peripheral, DMA channel and FIFO
+ i2s_ll_tx_reset(bus->hal.dev);
+ i2s_ll_tx_reset_dma(bus->hal.dev);
+ i2s_ll_tx_reset_fifo(bus->hal.dev);
+ // install interrupt service, (I2S LCD mode only uses the "TX Unit", which leaves "RX Unit" for other purpose)
+ // So the interrupt should also be able to share with other functionality
+ int isr_flags = LCD_I80_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_LOWMED;
+ ret = esp_intr_alloc_intrstatus(lcd_periph_i2s_signals.buses[bus->bus_id].irq_id, isr_flags,
+ (uint32_t)i2s_ll_get_intr_status_reg(bus->hal.dev),
+ I2S_LL_EVENT_TX_EOF, i2s_lcd_default_isr_handler, bus, &bus->intr);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "install interrupt failed");
+ i2s_ll_enable_intr(bus->hal.dev, I2S_LL_EVENT_TX_EOF, false); // disable interrupt temporarily
+ i2s_ll_clear_intr_status(bus->hal.dev, I2S_LL_EVENT_TX_EOF); // clear pending interrupt
+ // initialize DMA link
+ i2s_lcd_init_dma_link(bus);
+ // enable I2S LCD master mode (refer to I2S TRM)
+ i2s_ll_enable_lcd(bus->hal.dev, true);
+ i2s_ll_tx_stop_on_fifo_empty(bus->hal.dev, true);
+ i2s_ll_tx_set_slave_mod(bus->hal.dev, false);
+ i2s_ll_tx_set_bits_mod(bus->hal.dev, bus_config->bus_width);
+ i2s_ll_tx_select_std_slot(bus->hal.dev, I2S_STD_SLOT_BOTH, true); // copy mono
+ bus->bus_width = bus_config->bus_width;
+ i2s_ll_tx_enable_right_first(bus->hal.dev, true);
+#if SOC_I2S_SUPPORTS_DMA_EQUAL
+ i2s_ll_tx_enable_dma_equal(bus->hal.dev, true);
+#endif
+ // enable trans done interrupt
+ i2s_ll_enable_intr(bus->hal.dev, I2S_LL_EVENT_TX_EOF, true);
+ // trigger a quick "trans done" event, and wait for the interrupt line goes active
+ // this could ensure we go into ISR handler next time we call `esp_intr_enable`
+ i2s_lcd_trigger_quick_trans_done_event(bus);
+ // configure GPIO
+ ret = i2s_lcd_configure_gpio(bus, bus_config);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "configure GPIO failed");
+ // fill other i80 bus runtime parameters
+ LIST_INIT(&bus->device_list); // initialize device list head
+ bus->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
+ bus->dc_gpio_num = bus_config->dc_gpio_num;
+ bus->wr_gpio_num = bus_config->wr_gpio_num;
+ *ret_bus = bus;
+ ESP_LOGD(TAG, "new i80 bus(%d) @%p, resolution %luHz", bus->bus_id, bus, bus->resolution_hz);
+ return ESP_OK;
+
+err:
+ if (bus) {
+ if (bus->dma_link) {
+ gdma_del_link_list(bus->dma_link);
+ }
+ if (bus->intr) {
+ esp_intr_free(bus->intr);
+ }
+ if (bus->bus_id >= 0) {
+ i2s_platform_release_occupation(I2S_CTLR_HP, bus->bus_id);
+ }
+ if (bus->format_buffer) {
+ free(bus->format_buffer);
+ }
+ if (bus->pm_lock) {
+ esp_pm_lock_delete(bus->pm_lock);
+ }
+ free(bus);
+ }
+ return ret;
+}
+
+esp_err_t esp_lcd_del_i80_bus(esp_lcd_i80_bus_handle_t bus)
+{
+ esp_err_t ret = ESP_OK;
+ ESP_GOTO_ON_FALSE(bus, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
+ ESP_GOTO_ON_FALSE(LIST_EMPTY(&bus->device_list), ESP_ERR_INVALID_STATE, err, TAG, "device list not empty");
+ int bus_id = bus->bus_id;
+ i2s_platform_release_occupation(I2S_CTLR_HP, bus_id);
+ esp_intr_free(bus->intr);
+ if (bus->pm_lock) {
+ esp_pm_lock_delete(bus->pm_lock);
+ }
+ free(bus->format_buffer);
+ gdma_del_link_list(bus->dma_link);
+ free(bus);
+ ESP_LOGD(TAG, "del i80 bus(%d)", bus_id);
+err:
+ return ret;
+}
+
+esp_err_t esp_lcd_new_panel_io_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_panel_io_i80_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
+{
+ esp_err_t ret = ESP_OK;
+ lcd_panel_io_i80_t *i80_device = NULL;
+ bool bus_exclusive = false;
+ ESP_GOTO_ON_FALSE(bus && io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
+ // check if the bus has been configured as exclusive
+ portENTER_CRITICAL(&bus->spinlock);
+ if (!bus->flags.exclusive) {
+ bus->flags.exclusive = io_config->cs_gpio_num < 0;
+ } else {
+ bus_exclusive = true;
+ }
+ portEXIT_CRITICAL(&bus->spinlock);
+ ESP_GOTO_ON_FALSE(!bus_exclusive, ESP_ERR_INVALID_STATE, err, TAG, "bus has been exclusively owned by device");
+ // because we set the I2S's left channel data same to right channel, so f_pclk = f_i2s/pclk_div/2
+ uint32_t pclk_prescale = bus->resolution_hz / 2 / io_config->pclk_hz;
+ ESP_GOTO_ON_FALSE(pclk_prescale > 0 && pclk_prescale <= I2S_LL_BCK_MAX_PRESCALE, ESP_ERR_NOT_SUPPORTED, err, TAG,
+ "prescaler can't satisfy PCLK clock %"PRIu32"Hz", io_config->pclk_hz);
+ i80_device = heap_caps_calloc(1, sizeof(lcd_panel_io_i80_t) + io_config->trans_queue_depth * sizeof(lcd_i80_trans_descriptor_t), LCD_I80_MEM_ALLOC_CAPS);
+ ESP_GOTO_ON_FALSE(i80_device, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 panel io");
+ // create two queues for i80 device
+ i80_device->trans_queue = xQueueCreate(io_config->trans_queue_depth, sizeof(lcd_i80_trans_descriptor_t *));
+ ESP_GOTO_ON_FALSE(i80_device->trans_queue, ESP_ERR_NO_MEM, err, TAG, "create trans queue failed");
+ i80_device->done_queue = xQueueCreate(io_config->trans_queue_depth, sizeof(lcd_i80_trans_descriptor_t *));
+ ESP_GOTO_ON_FALSE(i80_device->done_queue, ESP_ERR_NO_MEM, err, TAG, "create done queue failed");
+ // adding device to list
+ portENTER_CRITICAL(&bus->spinlock);
+ LIST_INSERT_HEAD(&bus->device_list, i80_device, device_list_entry);
+ portEXIT_CRITICAL(&bus->spinlock);
+ // we don't initialize the i80 bus at the memont, but initialize the bus when start a transaction for a new device
+ // so save these as i80 device runtime parameters
+ i80_device->bus = bus;
+ i80_device->queue_size = io_config->trans_queue_depth;
+ i80_device->clock_prescale = pclk_prescale;
+ i80_device->lcd_cmd_bits = io_config->lcd_cmd_bits;
+ i80_device->lcd_param_bits = io_config->lcd_param_bits;
+ i80_device->pclk_hz = bus->resolution_hz / pclk_prescale / 2;
+ i80_device->dc_levels.dc_cmd_level = io_config->dc_levels.dc_cmd_level;
+ i80_device->dc_levels.dc_data_level = io_config->dc_levels.dc_data_level;
+ i80_device->cs_gpio_num = io_config->cs_gpio_num;
+ i80_device->on_color_trans_done = io_config->on_color_trans_done;
+ i80_device->user_ctx = io_config->user_ctx;
+ i80_device->flags.cs_active_high = io_config->flags.cs_active_high;
+ i80_device->flags.swap_color_bytes = io_config->flags.swap_color_bytes;
+ i80_device->flags.pclk_idle_low = io_config->flags.pclk_idle_low;
+ // fill panel io function table
+ i80_device->base.del = panel_io_i80_del;
+ i80_device->base.tx_param = panel_io_i80_tx_param;
+ i80_device->base.tx_color = panel_io_i80_tx_color;
+ i80_device->base.register_event_callbacks = panel_io_i80_register_event_callbacks;
+ if (io_config->cs_gpio_num >= 0) {
+ // CS signal is controlled by software
+ gpio_set_level(io_config->cs_gpio_num, !io_config->flags.cs_active_high); // de-assert by default
+ gpio_func_sel(io_config->cs_gpio_num, PIN_FUNC_GPIO);
+ gpio_output_enable(io_config->cs_gpio_num);
+ }
+ *ret_io = &(i80_device->base);
+ ESP_LOGD(TAG, "new i80 lcd panel io @%p on bus(%d), pclk=%"PRIu32"Hz", i80_device, bus->bus_id, i80_device->pclk_hz);
+ return ESP_OK;
+
+err:
+ if (i80_device) {
+ if (i80_device->trans_queue) {
+ vQueueDelete(i80_device->trans_queue);
+ }
+ if (i80_device->done_queue) {
+ vQueueDelete(i80_device->done_queue);
+ }
+ free(i80_device);
+ }
+ return ret;
+}
+
+void *esp_lcd_i80_alloc_draw_buffer(esp_lcd_panel_io_handle_t io, size_t size, uint32_t caps)
+{
+ ESP_RETURN_ON_FALSE(io, NULL, TAG, "invalid argument");
+ ESP_RETURN_ON_FALSE((caps & MALLOC_CAP_SPIRAM) == 0, NULL, TAG, "external memory is not supported");
+ // DMA can only carry internal memory
+ return heap_caps_aligned_calloc(4, 1, size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT | MALLOC_CAP_DMA);
+}
+
+static esp_err_t panel_io_i80_del(esp_lcd_panel_io_t *io)
+{
+ lcd_panel_io_i80_t *i80_device = __containerof(io, lcd_panel_io_i80_t, base);
+ esp_lcd_i80_bus_t *bus = i80_device->bus;
+ lcd_i80_trans_descriptor_t *trans_desc = NULL;
+ size_t num_trans_inflight = i80_device->num_trans_inflight;
+ // wait all pending transaction to finish
+ for (size_t i = 0; i < num_trans_inflight; i++) {
+ ESP_RETURN_ON_FALSE(xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE,
+ ESP_FAIL, TAG, "recycle inflight transactions failed");
+ i80_device->num_trans_inflight--;
+ }
+ // remove from device list
+ portENTER_CRITICAL(&bus->spinlock);
+ LIST_REMOVE(i80_device, device_list_entry);
+ portEXIT_CRITICAL(&bus->spinlock);
+
+ if (i80_device->cs_gpio_num >= 0) {
+ gpio_output_disable(i80_device->cs_gpio_num);
+ }
+
+ ESP_LOGD(TAG, "del i80 lcd panel io @%p", i80_device);
+ vQueueDelete(i80_device->trans_queue);
+ vQueueDelete(i80_device->done_queue);
+ free(i80_device);
+ return ESP_OK;
+}
+
+static esp_err_t panel_io_i80_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx)
+{
+ lcd_panel_io_i80_t *i80_device = __containerof(io, lcd_panel_io_i80_t, base);
+
+ if (i80_device->on_color_trans_done != NULL) {
+ ESP_LOGW(TAG, "Callback on_color_trans_done was already set and now it was overwritten!");
+ }
+
+ i80_device->on_color_trans_done = cbs->on_color_trans_done;
+ i80_device->user_ctx = user_ctx;
+
+ return ESP_OK;
+}
+
+static void i2s_lcd_prepare_cmd_buffer(lcd_i80_trans_descriptor_t *trans_desc, const void *cmd)
+{
+ lcd_panel_io_i80_t *i80_device = trans_desc->i80_device;
+ esp_lcd_i80_bus_t *bus = i80_device->bus;
+ uint8_t *from = (uint8_t *)cmd;
+ // LCD is big-endian, e.g. to send command 0x1234, byte 0x12 should appear on the data bus first
+ // However, the I2S peripheral will send 0x34 first, so we reversed the order below
+ if (bus->bus_width < i80_device->lcd_cmd_bits) {
+ int start = 0;
+ int end = i80_device->lcd_cmd_bits / 8 - 1;
+ lcd_com_reverse_buffer_bytes(from, start, end);
+ }
+#if SOC_I2S_TRANS_SIZE_ALIGN_WORD
+ uint8_t *to = bus->format_buffer;
+ int cmd_cycle = i80_device->lcd_cmd_bits / bus->bus_width;
+ if (cmd_cycle * bus->bus_width < i80_device->lcd_cmd_bits) {
+ cmd_cycle++;
+ }
+ int bytes_to_copy = MIN(bus->bus_width, i80_device->lcd_cmd_bits) / 8;
+ int cnt_from = 0;
+ // format command buffer
+ for (int i = 0; i < cmd_cycle; i++) {
+ for (int j = 0; j < bytes_to_copy; j++) {
+ to[2 + j] = from[cnt_from++];
+ }
+ to += 4;
+ }
+ trans_desc->data = bus->format_buffer;
+ trans_desc->data_length = cmd_cycle * 4;
+#else
+ trans_desc->data = cmd;
+ trans_desc->data_length = MAX(i80_device->lcd_cmd_bits, bus->bus_width) / 8;
+#endif
+}
+
+static void i2s_lcd_prepare_param_buffer(lcd_i80_trans_descriptor_t *trans_desc, const void *param, size_t param_num)
+{
+ lcd_panel_io_i80_t *i80_device = trans_desc->i80_device;
+ esp_lcd_i80_bus_t *bus = i80_device->bus;
+ uint8_t *from = (uint8_t *)param;
+ int param_size = i80_device->lcd_param_bits / 8;
+ // LCD is big-endian, e.g. to send param 0x1234, byte 0x12 should appear on the data bus first
+ // However, the I2S peripheral will send 0x34 first, so we reversed the order below
+ if (bus->bus_width < i80_device->lcd_param_bits) {
+ for (size_t i = 0; i < param_num; i++) {
+ int start = i * param_size;
+ int end = start + param_size - 1;
+ lcd_com_reverse_buffer_bytes(from, start, end);
+ }
+ }
+#if SOC_I2S_TRANS_SIZE_ALIGN_WORD
+ uint8_t *to = bus->format_buffer;
+ int param_cycle = i80_device->lcd_param_bits / bus->bus_width;
+ if (param_cycle * bus->bus_width < i80_device->lcd_param_bits) {
+ param_cycle++;
+ }
+ int ele_cycles = param_cycle * param_num;
+ int bytes_to_copy = MIN(bus->bus_width, i80_device->lcd_param_bits) / 8;
+ int cnt_from = 0;
+ // format parameter buffer
+ for (int i = 0; i < ele_cycles; i++) {
+ for (int j = 0; j < bytes_to_copy; j++) {
+ to[2 + j] = from[cnt_from++];
+ }
+ to += 4;
+ }
+ trans_desc->data = bus->format_buffer;
+ trans_desc->data_length = ele_cycles * 4;
+#else
+ uint8_t *to = bus->format_buffer;
+ uint8_t step = bus->bus_width / 8;
+ int param_cycle = i80_device->lcd_param_bits / bus->bus_width;
+ if (param_cycle * bus->bus_width < i80_device->lcd_param_bits) {
+ param_cycle++;
+ }
+ int ele_cycles = param_cycle * param_num;
+ int bytes_to_copy = MIN(bus->bus_width, i80_device->lcd_param_bits) / 8;
+ int cnt_from = 0;
+ // format parameter buffer
+ for (int i = 0; i < ele_cycles; i++) {
+ for (int j = 0; j < bytes_to_copy; j++) {
+ to[j] = from[cnt_from++];
+ }
+ to += step;
+ }
+ trans_desc->data = bus->format_buffer;
+ trans_desc->data_length = to - bus->format_buffer;
+#endif
+}
+
+static void i2s_lcd_prepare_color_buffer(lcd_i80_trans_descriptor_t *trans_desc, const void *color, size_t color_size)
+{
+#if SOC_I2S_TRANS_SIZE_ALIGN_WORD
+ lcd_panel_io_i80_t *i80_device = trans_desc->i80_device;
+ esp_lcd_i80_bus_t *bus = i80_device->bus;
+ uint8_t *from = (uint8_t *)color;
+ uint8_t *to = bus->format_buffer;
+ int bytes_to_copy = bus->bus_width / 8;
+ int cnt_from = 0;
+ int first_half = i80_device->flags.swap_color_bytes ? 0 : 2;
+ int second_half = i80_device->flags.swap_color_bytes ? 2 : 0;
+ // format color buffer
+ while (cnt_from < color_size) {
+ for (int i = 0; i < bytes_to_copy; i++) {
+ to[first_half + i] = from[cnt_from++];
+ }
+ for (int i = 0; i < bytes_to_copy; i++) {
+ to[second_half + i] = from[cnt_from++];
+ }
+ to += 4;
+ }
+ trans_desc->data = bus->format_buffer;
+ trans_desc->data_length = to - bus->format_buffer;
+#else
+ trans_desc->data = color;
+ trans_desc->data_length = color_size;
+#endif
+}
+
+static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size)
+{
+ lcd_panel_io_i80_t *next_device = __containerof(io, lcd_panel_io_i80_t, base);
+ esp_lcd_i80_bus_t *bus = next_device->bus;
+ lcd_panel_io_i80_t *cur_device = bus->cur_device;
+ lcd_i80_trans_descriptor_t *trans_desc = NULL;
+ assert(param_size <= bus->max_transfer_bytes && "parameter bytes too long, enlarge max_transfer_bytes");
+ assert(param_size <= LCD_I80_IO_FORMAT_BUF_SIZE && "format buffer too small, increase LCD_I80_IO_FORMAT_BUF_SIZE");
+ size_t num_trans_inflight = next_device->num_trans_inflight;
+ // before issue a polling transaction, need to wait queued transactions finished
+ for (size_t i = 0; i < num_trans_inflight; i++) {
+ ESP_RETURN_ON_FALSE(xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE,
+ ESP_FAIL, TAG, "recycle inflight transactions failed");
+ next_device->num_trans_inflight--;
+ }
+
+ gdma_buffer_mount_config_t mount_config = {
+ .flags = {
+ .mark_eof = true,
+ .mark_final = GDMA_FINAL_LINK_TO_NULL, // singly link list, mark final descriptor
+ }
+ };
+
+ i2s_ll_clear_intr_status(bus->hal.dev, I2S_LL_EVENT_TX_EOF);
+ // switch devices if necessary
+ lcd_i80_switch_devices(cur_device, next_device);
+ trans_desc = &next_device->trans_pool[0];
+ trans_desc->i80_device = next_device;
+ trans_desc->trans_done_cb = NULL; // no callback for command transfer
+ bus->cur_trans = trans_desc;
+#if SOC_I2S_TRANS_SIZE_ALIGN_WORD
+ // switch to I2S 32bits mode, one WS cycle <=> one I2S FIFO
+ i2s_ll_tx_set_bits_mod(bus->hal.dev, 32);
+#endif
+ i2s_lcd_prepare_cmd_buffer(trans_desc, &lcd_cmd);
+ mount_config.buffer = (void *)trans_desc->data;
+ mount_config.length = trans_desc->data_length;
+ gdma_link_mount_buffers(bus->dma_link, 0, &mount_config, 1, NULL);
+ gpio_set_level(bus->dc_gpio_num, next_device->dc_levels.dc_cmd_level);
+ i2s_ll_tx_stop(bus->hal.dev);
+ i2s_ll_tx_reset(bus->hal.dev); // reset TX engine first
+ i2s_ll_start_out_link(bus->hal.dev);
+ // delay a while, wait for DMA data being feed to I2S FIFO
+ // in fact, this is only needed when LCD pixel clock is set too high
+ esp_rom_delay_us(1);
+ // increase the pm lock reference count before starting a new transaction
+ if (bus->pm_lock) {
+ esp_pm_lock_acquire(bus->pm_lock);
+ }
+ i2s_ll_tx_start(bus->hal.dev);
+ // polling the trans done event
+ while (!(i2s_ll_get_intr_status(bus->hal.dev) & I2S_LL_EVENT_TX_EOF)) {}
+
+ // parameter is usually short, using polling mode
+ if (param && param_size) {
+ i2s_ll_clear_intr_status(bus->hal.dev, I2S_LL_EVENT_TX_EOF);
+ i2s_lcd_prepare_param_buffer(trans_desc, param, param_size * 8 / next_device->lcd_param_bits);
+ mount_config.buffer = (void *)trans_desc->data;
+ mount_config.length = trans_desc->data_length;
+ gdma_link_mount_buffers(bus->dma_link, 0, &mount_config, 1, NULL);
+ gpio_set_level(bus->dc_gpio_num, next_device->dc_levels.dc_data_level);
+ i2s_ll_tx_stop(bus->hal.dev);
+ i2s_ll_tx_reset(bus->hal.dev); // reset TX engine first
+ i2s_ll_start_out_link(bus->hal.dev);
+ esp_rom_delay_us(1);
+ i2s_ll_tx_start(bus->hal.dev);
+ // polling the trans done event, but don't clear the event status
+ while (!(i2s_ll_get_intr_status(bus->hal.dev) & I2S_LL_EVENT_TX_EOF)) {}
+ }
+ // decrease pm lock reference count
+ if (bus->pm_lock) {
+ esp_pm_lock_release(bus->pm_lock);
+ }
+ bus->cur_trans = NULL;
+ return ESP_OK;
+}
+
+static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size)
+{
+ lcd_panel_io_i80_t *next_device = __containerof(io, lcd_panel_io_i80_t, base);
+ esp_lcd_i80_bus_t *bus = next_device->bus;
+ lcd_panel_io_i80_t *cur_device = bus->cur_device;
+ lcd_i80_trans_descriptor_t *trans_desc = NULL;
+ assert(color_size <= bus->max_transfer_bytes && "color bytes too long, enlarge max_transfer_bytes");
+ size_t num_trans_inflight = next_device->num_trans_inflight;
+ // before issue a polling transaction, need to wait queued transactions finished
+ for (size_t i = 0; i < num_trans_inflight; i++) {
+ ESP_RETURN_ON_FALSE(xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE,
+ ESP_FAIL, TAG, "recycle inflight transactions failed");
+ next_device->num_trans_inflight--;
+ }
+
+ gdma_buffer_mount_config_t mount_config = {
+ .flags = {
+ .mark_eof = true,
+ .mark_final = GDMA_FINAL_LINK_TO_NULL, // singly link list, mark final descriptor
+ }
+ };
+
+ i2s_ll_clear_intr_status(bus->hal.dev, I2S_LL_EVENT_TX_EOF);
+ // switch devices if necessary
+ lcd_i80_switch_devices(cur_device, next_device);
+ trans_desc = &next_device->trans_pool[0];
+ trans_desc->i80_device = next_device;
+ trans_desc->trans_done_cb = NULL; // no callback for command transfer
+ bus->cur_trans = trans_desc;
+#if SOC_I2S_TRANS_SIZE_ALIGN_WORD
+ // switch to I2S 32bits mode, one WS cycle <=> one I2S FIFO
+ i2s_ll_tx_set_bits_mod(bus->hal.dev, 32);
+#endif
+ i2s_lcd_prepare_cmd_buffer(trans_desc, &lcd_cmd);
+ mount_config.buffer = (void *)trans_desc->data;
+ mount_config.length = trans_desc->data_length;
+ gdma_link_mount_buffers(bus->dma_link, 0, &mount_config, 1, NULL);
+ gpio_set_level(bus->dc_gpio_num, next_device->dc_levels.dc_cmd_level);
+ i2s_ll_tx_stop(bus->hal.dev);
+ i2s_ll_tx_reset(bus->hal.dev); // reset TX engine first
+ i2s_ll_start_out_link(bus->hal.dev);
+ esp_rom_delay_us(1);
+ // increase the pm lock reference count before starting a new transaction
+ if (bus->pm_lock) {
+ esp_pm_lock_acquire(bus->pm_lock);
+ }
+ i2s_ll_tx_start(bus->hal.dev);
+ // polling the trans done event
+ while (!(i2s_ll_get_intr_status(bus->hal.dev) & I2S_LL_EVENT_TX_EOF)) {}
+ // decrease pm lock reference count
+ if (bus->pm_lock) {
+ esp_pm_lock_release(bus->pm_lock);
+ }
+ bus->cur_trans = NULL;
+
+ // sending LCD color data to queue
+ trans_desc->trans_done_cb = next_device->on_color_trans_done;
+ trans_desc->user_ctx = next_device->user_ctx;
+ trans_desc->flags.dc_level = next_device->dc_levels.dc_data_level; // DC level for data transaction
+ i2s_lcd_prepare_color_buffer(trans_desc, color, color_size);
+ // send transaction to trans_queue
+ xQueueSend(next_device->trans_queue, &trans_desc, portMAX_DELAY);
+ next_device->num_trans_inflight++;
+ // enable interrupt and go into isr handler, where we fetch the transactions from trans_queue and start it
+ // we will go into `i2s_lcd_default_isr_handler` almost at once, because the "trans done" event is active at the moment
+ esp_intr_enable(bus->intr);
+ return ESP_OK;
+}
+
+static esp_err_t i2s_lcd_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_clock_source_t src)
+{
+ // get clock source frequency
+ uint32_t src_clk_hz = 0;
+ ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_hz),
+ TAG, "get clock source frequency failed");
+
+ // I2S clock source is binary compatible with lcd_clock_source_t
+ i2s_ll_tx_clk_set_src(bus->hal.dev, (i2s_clock_src_t)src);
+ i2s_ll_set_raw_mclk_div(bus->hal.dev, LCD_PERIPH_CLOCK_PRE_SCALE, 1, 0);
+ // save the resolution of the i80 bus
+ bus->resolution_hz = src_clk_hz / LCD_PERIPH_CLOCK_PRE_SCALE;
+
+ // create pm lock based on different clock source
+ // clock sources like PLL and XTAL will be turned off in light sleep
+#if CONFIG_PM_ENABLE
+ ESP_RETURN_ON_ERROR(esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "i80_bus_lcd", &bus->pm_lock), TAG, "create pm lock failed");
+#endif
+ return ESP_OK;
+}
+
+static esp_err_t i2s_lcd_init_dma_link(esp_lcd_i80_bus_handle_t bus)
+{
+ i2s_ll_dma_enable_eof_on_fifo_empty(bus->hal.dev, true);
+ i2s_ll_dma_enable_owner_check(bus->hal.dev, true);
+ i2s_ll_dma_enable_auto_write_back(bus->hal.dev, true);
+ i2s_ll_set_out_link_addr(bus->hal.dev, gdma_link_get_head_addr(bus->dma_link));
+ i2s_ll_enable_dma(bus->hal.dev, true);
+ return ESP_OK;
+}
+
+static esp_err_t i2s_lcd_configure_gpio(esp_lcd_i80_bus_handle_t bus, const esp_lcd_i80_bus_config_t *bus_config)
+{
+ int bus_id = bus->bus_id;
+ // check validation of GPIO number
+ bool valid_gpio = (bus_config->wr_gpio_num >= 0) && (bus_config->dc_gpio_num >= 0);
+ for (size_t i = 0; i < bus_config->bus_width; i++) {
+ valid_gpio = valid_gpio && (bus_config->data_gpio_nums[i] >= 0);
+ }
+ if (!valid_gpio) {
+ return ESP_ERR_INVALID_ARG;
+ }
+ // connect peripheral signals via GPIO matrix
+ // data line
+ for (size_t i = 0; i < bus_config->bus_width; i++) {
+ gpio_func_sel(bus_config->data_gpio_nums[i], PIN_FUNC_GPIO);
+ // the esp_rom_gpio_connect_out_signal function will also help enable the output path properly
+#if SOC_I2S_TRANS_SIZE_ALIGN_WORD
+ esp_rom_gpio_connect_out_signal(bus_config->data_gpio_nums[i], lcd_periph_i2s_signals.buses[bus_id].data_sigs[i + 8], false, false);
+#else
+ esp_rom_gpio_connect_out_signal(bus_config->data_gpio_nums[i], lcd_periph_i2s_signals.buses[bus_id].data_sigs[i + SOC_I2S_MAX_DATA_WIDTH - bus_config->bus_width], false, false);
+#endif
+ }
+ // WR signal (pclk)
+ gpio_func_sel(bus_config->wr_gpio_num, PIN_FUNC_GPIO);
+ esp_rom_gpio_connect_out_signal(bus_config->wr_gpio_num, lcd_periph_i2s_signals.buses[bus_id].wr_sig, true, false);
+ // DC signal is controlled by software, set as general purpose IO
+ gpio_func_sel(bus_config->dc_gpio_num, PIN_FUNC_GPIO);
+ gpio_output_enable(bus_config->dc_gpio_num);
+ return ESP_OK;
+}
+
+static void i2s_lcd_trigger_quick_trans_done_event(esp_lcd_i80_bus_handle_t bus)
+{
+ // trigger a quick interrupt event by a dummy transaction, wait the LCD interrupt line goes active
+ // next time when esp_intr_enable is invoked, we can go into interrupt handler immediately
+ // where we dispatch transactions for i80 devices
+ static uint32_t fake_trigger = 0;
+ gdma_buffer_mount_config_t mount_config = {
+ .buffer = &fake_trigger,
+ .buffer_alignment = 1, // no special buffer alignment for fake trigger
+ .length = 4,
+ .flags = {
+ .mark_eof = true, // mark the "EOF" flag to trigger I2S EOF interrupt
+ .mark_final = GDMA_FINAL_LINK_TO_NULL, // singly link list, mark final descriptor
+ }
+ };
+ gdma_link_mount_buffers(bus->dma_link, 0, &mount_config, 1, NULL);
+ i2s_ll_start_out_link(bus->hal.dev);
+ i2s_ll_tx_start(bus->hal.dev);
+ while (!(i2s_ll_get_intr_status(bus->hal.dev) & I2S_LL_EVENT_TX_EOF)) {}
+}
+
+static void lcd_i80_switch_devices(lcd_panel_io_i80_t *cur_device, lcd_panel_io_i80_t *next_device)
+{
+ // the caller should make sure the next_device and cur_device are attached to the same bus
+ esp_lcd_i80_bus_t *bus = next_device->bus;
+ if (next_device != cur_device) {
+ // reconfigure PCLK for the new device
+ i2s_ll_tx_set_bck_div_num(bus->hal.dev, next_device->clock_prescale);
+ if (cur_device && cur_device->cs_gpio_num >= 0) { // de-assert current device
+ gpio_set_level(cur_device->cs_gpio_num, !cur_device->flags.cs_active_high);
+ }
+ if (next_device->cs_gpio_num >= 0) {
+ gpio_set_level(next_device->cs_gpio_num, next_device->flags.cs_active_high); // select the next device
+ }
+ // the WR signal (a.k.a the PCLK) generated by I2S is low level in idle stage
+ // but most of 8080 LCDs require the WR line to be in high level during idle stage
+ esp_rom_gpio_connect_out_signal(bus->wr_gpio_num, lcd_periph_i2s_signals.buses[bus->bus_id].wr_sig, !next_device->flags.pclk_idle_low, false);
+ }
+ bus->cur_device = next_device;
+}
+
+static IRAM_ATTR void i2s_lcd_default_isr_handler(void *args)
+{
+ esp_lcd_i80_bus_t *bus = (esp_lcd_i80_bus_t *)args;
+ lcd_i80_trans_descriptor_t *trans_desc = NULL;
+ lcd_panel_io_i80_t *cur_device = NULL;
+ lcd_panel_io_i80_t *next_device = NULL;
+ BaseType_t high_task_woken = pdFALSE;
+ bool need_yield = false;
+ uint32_t intr_status = i2s_ll_get_intr_status(bus->hal.dev);
+ if (intr_status & I2S_LL_EVENT_TX_EOF) { // trans done event
+ // disable interrupt temporarily, only re-enable when there be remained transaction in the queue
+ esp_intr_disable(bus->intr);
+ trans_desc = bus->cur_trans; // the finished transaction
+ cur_device = bus->cur_device;// the working device
+ // process finished transaction
+ if (trans_desc) {
+ assert(trans_desc->i80_device == cur_device && "transaction device mismatch");
+ // decrease pm lock reference count
+ if (bus->pm_lock) {
+ esp_pm_lock_release(bus->pm_lock);
+ }
+ // device callback
+ if (trans_desc->trans_done_cb) {
+ if (trans_desc->trans_done_cb(&cur_device->base, NULL, trans_desc->user_ctx)) {
+ need_yield = true;
+ }
+ }
+ // move transaction to done_queue
+ high_task_woken = pdFALSE;
+ xQueueSendFromISR(cur_device->done_queue, &trans_desc, &high_task_woken);
+ if (high_task_woken == pdTRUE) {
+ need_yield = true;
+ }
+ bus->cur_trans = NULL;
+ }
+ // fetch transactions from devices' trans_queue
+ // Note: the first registered device will have the highest priority to be scheduled
+ LIST_FOREACH(next_device, &bus->device_list, device_list_entry) {
+ high_task_woken = pdFALSE;
+ if (xQueueReceiveFromISR(next_device->trans_queue, &trans_desc, &high_task_woken) == pdTRUE) {
+ if (high_task_woken == pdTRUE) {
+ need_yield = true;
+ }
+ // sanity check
+ assert(trans_desc);
+ // only clear the interrupt status when we're sure there still remains transaction to handle
+ i2s_ll_clear_intr_status(bus->hal.dev, I2S_LL_EVENT_TX_EOF);
+ // switch devices if necessary
+ lcd_i80_switch_devices(cur_device, next_device);
+ bus->cur_trans = trans_desc;
+ gpio_set_level(bus->dc_gpio_num, trans_desc->flags.dc_level);
+ // mount data to DMA links
+ gdma_buffer_mount_config_t mount_config = {
+ .buffer = (void *)trans_desc->data,
+ .buffer_alignment = 1, // no special buffer alignment for LCD TX buffer
+ .length = trans_desc->data_length,
+ .flags = {
+ .mark_eof = true,
+ .mark_final = GDMA_FINAL_LINK_TO_NULL, // singly link list, mark final descriptor
+ }
+ };
+ gdma_link_mount_buffers(bus->dma_link, 0, &mount_config, 1, NULL);
+#if SOC_I2S_TRANS_SIZE_ALIGN_WORD
+ // switch to I2S 16bits mode, two WS cycle <=> one I2S FIFO
+ i2s_ll_tx_set_bits_mod(bus->hal.dev, 16);
+#endif
+ // enable interrupt again, because the new transaction can trigger new trans done event
+ esp_intr_enable(bus->intr);
+ i2s_ll_tx_stop(bus->hal.dev);
+ i2s_ll_tx_reset(bus->hal.dev); // reset TX engine first
+ i2s_ll_start_out_link(bus->hal.dev);
+ esp_rom_delay_us(1);
+ // increase the pm lock reference count before starting a new transaction
+ if (bus->pm_lock) {
+ esp_pm_lock_acquire(bus->pm_lock);
+ }
+ i2s_ll_tx_start(bus->hal.dev);
+ break; // exit for-each loop
+ }
+ }
+ }
+ if (need_yield) {
+ portYIELD_FROM_ISR();
+ }
+}
### components/esp_lcd/i80/esp_lcd_panel_io_i80.c
@@ -0,0 +1,842 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include "sdkconfig.h"
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+// The local log level must be defined before including esp_log.h
+// Set the maximum log level for this source file
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#endif
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/queue.h"
+#include "esp_attr.h"
+#include "esp_check.h"
+#include "esp_pm.h"
+#include "esp_lcd_panel_io_interface.h"
+#include "esp_lcd_panel_io.h"
+#include "esp_rom_gpio.h"
+#include "soc/soc_caps.h"
+#include "esp_clk_tree.h"
+#include "esp_memory_utils.h"
+#include "esp_cache.h"
+#include "driver/gpio.h"
+#include "esp_private/esp_clk_tree_common.h"
+#include "esp_private/gpio.h"
+#include "esp_private/gdma.h"
+#include "esp_private/gdma_link.h"
+#include "esp_private/esp_dma_utils.h"
+#include "esp_private/periph_ctrl.h"
+#include "esp_lcd_common.h"
+#include "soc/lcd_periph.h"
+#include "soc/io_mux_reg.h"
+#include "soc/gpio_sig_map.h"
+#include "hal/lcd_ll.h"
+#include "hal/lcd_hal.h"
+#include "hal/cache_ll.h"
+#include "hal/cache_hal.h"
+
+#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 LCD i80"
+#endif
+
+#if SOC_NON_CACHEABLE_OFFSET
+#define LCD_CACHE_ADDR_TO_NON_CACHE_ADDR(addr) ((addr) + SOC_NON_CACHEABLE_OFFSET)
+#else
+#define LCD_CACHE_ADDR_TO_NON_CACHE_ADDR(addr) (addr)
+#endif
+
+static const char *TAG = "lcd_panel.io.i80";
+
+typedef struct esp_lcd_i80_bus_t esp_lcd_i80_bus_t;
+typedef struct lcd_panel_io_i80_t lcd_panel_io_i80_t;
+typedef struct lcd_i80_trans_descriptor_t lcd_i80_trans_descriptor_t;
+
+static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size);
+static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size);
+static esp_err_t panel_io_i80_del(esp_lcd_panel_io_t *io);
+static esp_err_t lcd_i80_init_dma_link(esp_lcd_i80_bus_handle_t bus, const esp_lcd_i80_bus_config_t *bus_config);
+static void lcd_periph_trigger_quick_trans_done_event(esp_lcd_i80_bus_handle_t bus);
+static esp_err_t lcd_i80_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_clock_source_t clk_src);
+static esp_err_t lcd_i80_bus_configure_gpio(esp_lcd_i80_bus_handle_t bus, const esp_lcd_i80_bus_config_t *bus_config);
+static void lcd_i80_switch_devices(lcd_panel_io_i80_t *cur_device, lcd_panel_io_i80_t *next_device);
+static void lcd_start_transaction(esp_lcd_i80_bus_t *bus, lcd_i80_trans_descriptor_t *trans_desc);
+static void i80_lcd_default_isr_handler(void *args);
+static esp_err_t panel_io_i80_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx);
+
+struct esp_lcd_i80_bus_t {
+ int bus_id; // Bus ID, index from 0
+ portMUX_TYPE spinlock; // spinlock used to protect i80 bus members(hal, device_list, cur_trans)
+ lcd_hal_context_t hal; // Hal object
+ size_t bus_width; // Number of data lines
+ intr_handle_t intr; // LCD peripheral interrupt handle
+ esp_pm_lock_handle_t pm_lock; // Power management lock
+ uint8_t *format_buffer; // The driver allocates an internal buffer for DMA to do data format transformer
+ uint8_t *format_buffer_nc; // Non-cacheable version of format buffer
+ size_t resolution_hz; // LCD_CLK resolution, determined by selected clock source
+ size_t max_transfer_bytes; // Maximum number of bytes that can be transferred in one transaction
+ gdma_channel_handle_t dma_chan; // DMA channel handle
+ gdma_link_list_handle_t dma_link; // DMA link list handle
+ size_t int_mem_align; // Alignment for internal memory
+ size_t ext_mem_align; // Alignment for external memory
+ lcd_i80_trans_descriptor_t *cur_trans; // Current transaction
+ lcd_panel_io_i80_t *cur_device; // Current working device
+ LIST_HEAD(i80_device_list, lcd_panel_io_i80_t) device_list; // Head of i80 device list
+ struct {
+ uint32_t exclusive: 1; // Indicate whether the I80 bus is owned by one device (whose CS GPIO is not assigned) exclusively
+ } flags;
+};
+
+struct lcd_i80_trans_descriptor_t {
+ lcd_panel_io_i80_t *i80_device; // i80 device issuing this transaction
+ int cmd_value; // Command value
+ uint32_t cmd_cycles; // Command cycles
+ const void *data; // Data buffer
+ uint32_t data_length; // Data buffer size
+ void *user_ctx; // private data used by trans_done_cb
+ esp_lcd_panel_io_color_trans_done_cb_t trans_done_cb; // transaction done callback
+};
+
+struct lcd_panel_io_i80_t {
+ esp_lcd_panel_io_t base; // Base class of generic lcd panel io
+ esp_lcd_i80_bus_t *bus; // Which bus the device is attached to
+ int cs_gpio_num; // GPIO used for CS line
+ unsigned int pclk_hz; // PCLK clock frequency
+ size_t clock_prescale; // Prescaler coefficient, determined by user's configured PCLK frequency
+ QueueHandle_t trans_queue; // Transaction queue, transactions in this queue are pending for scheduler to dispatch
+ QueueHandle_t done_queue; // Transaction done queue, transactions in this queue are finished but not recycled by the caller
+ size_t queue_size; // Size of transaction queue
+ size_t num_trans_inflight; // Number of transactions that are undergoing (the descriptor not recycled yet)
+ int lcd_cmd_bits; // Bit width of LCD command
+ int lcd_param_bits; // Bit width of LCD parameter
+ void *user_ctx; // private data used when transfer color data
+ esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; // color data trans done callback
+ LIST_ENTRY(lcd_panel_io_i80_t) device_list_entry; // Entry of i80 device list
+ struct {
+ unsigned int dc_idle_level: 1; // Level of DC line in IDLE phase
+ unsigned int dc_cmd_level: 1; // Level of DC line in CMD phase
+ unsigned int dc_dummy_level: 1; // Level of DC line in DUMMY phase
+ unsigned int dc_data_level: 1; // Level of DC line in DATA phase
+ } dc_levels;
+ struct {
+ unsigned int cs_active_high: 1; // Whether the CS line is active on high level
+ unsigned int reverse_color_bits: 1; // Reverse the data bits, D[N:0] -> D[0:N]
+ unsigned int swap_color_bytes: 1; // Swap adjacent two data bytes before sending out
+ unsigned int pclk_active_neg: 1; // The display will write data lines when there's a falling edge on WR line
+ unsigned int pclk_idle_low: 1; // The WR line keeps at low level in IDLE phase
+ } flags;
+ lcd_i80_trans_descriptor_t trans_pool[]; // Transaction pool
+};
+
+esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lcd_i80_bus_handle_t *ret_bus)
+{
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+ esp_log_level_set(TAG, ESP_LOG_DEBUG);
+#endif
+ esp_err_t ret = ESP_OK;
+ esp_lcd_i80_bus_t *bus = NULL;
+ ESP_RETURN_ON_FALSE(bus_config && ret_bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+ // although LCD_CAM can support up to 24 data lines, we restrict users to only use 8 or 16 bit width
+ ESP_RETURN_ON_FALSE(bus_config->bus_width == 8 || bus_config->bus_width == 16, ESP_ERR_INVALID_ARG,
+ TAG, "invalid bus width:%d", bus_config->bus_width);
+ // allocate i80 bus memory
+ bus = heap_caps_calloc(1, sizeof(esp_lcd_i80_bus_t), LCD_I80_MEM_ALLOC_CAPS);
+ ESP_GOTO_ON_FALSE(bus, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 bus");
+ bus->bus_width = bus_config->bus_width;
+ bus->bus_id = -1;
+ // allocate the format buffer from internal memory, with DMA capability
+ bus->format_buffer = heap_caps_calloc(1, LCD_I80_IO_FORMAT_BUF_SIZE,
+ MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
+ ESP_GOTO_ON_FALSE(bus->format_buffer, ESP_ERR_NO_MEM, err, TAG, "no mem for format buffer");
+ // if the buffer is behind the cache, write it back to the underlying memory
+ if (cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA) > 0) {
+ esp_cache_msync(bus->format_buffer, LCD_I80_IO_FORMAT_BUF_SIZE,
+ ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
+ }
+ bus->format_buffer_nc = LCD_CACHE_ADDR_TO_NON_CACHE_ADDR(bus->format_buffer);
+ // register to platform
+ int bus_id = lcd_com_register_device(LCD_COM_DEVICE_TYPE_I80, bus);
+ ESP_GOTO_ON_FALSE(bus_id >= 0, ESP_ERR_NOT_FOUND, err, TAG, "no free i80 bus slot");
+ bus->bus_id = bus_id;
+ // enable APB to access LCD registers
+ PERIPH_RCC_ACQUIRE_ATOMIC(lcd_periph_i80_signals.buses[bus_id].module, ref_count) {
+ if (ref_count == 0) {
+ lcd_ll_enable_bus_clock(bus_id, true);
+ lcd_ll_reset_register(bus_id);
+ }
+ }
+ // initialize HAL layer, so we can call LL APIs later
+ lcd_hal_init(&bus->hal, bus_id);
+ LCD_CLOCK_SRC_ATOMIC() {
+ lcd_ll_enable_clock(bus->hal.dev, true);
+ }
+ // set peripheral clock resolution
+ ret = lcd_i80_select_periph_clock(bus, bus_config->clk_src);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "select periph clock %d failed", bus_config->clk_src);
+ // reset peripheral and FIFO after we select a correct clock source
+ lcd_ll_reset(bus->hal.dev);
+ lcd_ll_fifo_reset(bus->hal.dev);
+ // install interrupt service, (LCD peripheral shares the same interrupt source with Camera peripheral with different mask)
+ // interrupt is disabled by default
+ int isr_flags = LCD_I80_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_LOWMED;
+ ret = esp_intr_alloc_intrstatus(lcd_periph_i80_signals.buses[bus_id].irq_id, isr_flags,
+ (uint32_t)lcd_ll_get_interrupt_status_reg(bus->hal.dev),
+ LCD_LL_EVENT_I80, i80_lcd_default_isr_handler, bus, &bus->intr);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "install interrupt failed");
+ PERIPH_RCC_ATOMIC() {
+ lcd_ll_enable_interrupt(bus->hal.dev, LCD_LL_EVENT_I80, false); // disable all interrupts
+ }
+ lcd_ll_clear_interrupt_status(bus->hal.dev, UINT32_MAX); // clear pending interrupt
+ // install DMA service
+ bus->max_transfer_bytes = bus_config->max_transfer_bytes;
+ ret = lcd_i80_init_dma_link(bus, bus_config);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "install DMA failed");
+ // disable RGB-LCD mode
+ lcd_ll_enable_rgb_mode(bus->hal.dev, false);
+ // disable YUV-RGB converter
+ lcd_ll_enable_rgb_yuv_convert(bus->hal.dev, false);
+ // set how much data to read from DMA each time
+ lcd_ll_set_dma_read_stride(bus->hal.dev, bus->bus_width);
+ // sometime, we need to change the output data order: ABAB->BABA
+ lcd_ll_set_swizzle_mode(bus->hal.dev, LCD_LL_SWIZZLE_AB2BA);
+ // number of data cycles is controlled by DMA buffer size
+ lcd_ll_enable_output_always_on(bus->hal.dev, true);
+ PERIPH_RCC_ATOMIC() {
+ // enable all interrupts
+ lcd_ll_enable_interrupt(bus->hal.dev, LCD_LL_EVENT_I80, true);
+ }
+ // trigger a quick "trans done" event, and wait for the interrupt line goes active
+ // this could ensure we go into ISR handler next time we call `esp_intr_enable`
+ lcd_periph_trigger_quick_trans_done_event(bus);
+ // configure GPIO
+ ret = lcd_i80_bus_configure_gpio(bus, bus_config);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "configure GPIO failed");
+ // fill other i80 bus runtime parameters
+ LIST_INIT(&bus->device_list); // initialize device list head
+ bus->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
+ *ret_bus = bus;
+ ESP_LOGD(TAG, "new i80 bus(%d) @%p", bus_id, bus);
+ return ESP_OK;
+
+err:
+ if (bus) {
+ if (bus->intr) {
+ esp_intr_free(bus->intr);
+ }
+ if (bus->dma_chan) {
+ gdma_disconnect(bus->dma_chan);
+ gdma_del_channel(bus->dma_chan);
+ }
+ if (bus->dma_link) {
+ gdma_del_link_list(bus->dma_link);
+ }
+ if (bus->bus_id >= 0) {
+ PERIPH_RCC_RELEASE_ATOMIC(lcd_periph_i80_signals.buses[bus->bus_id].module, ref_count) {
+ if (ref_count == 0) {
+ lcd_ll_enable_bus_clock(bus->bus_id, false);
+ }
+ }
+ lcd_com_remove_device(LCD_COM_DEVICE_TYPE_I80, bus->bus_id);
+ }
+ if (bus->format_buffer) {
+ free(bus->format_buffer);
+ }
+ if (bus->pm_lock) {
+ esp_pm_lock_delete(bus->pm_lock);
+ }
+ free(bus);
+ }
+ return ret;
+}
+
+esp_err_t esp_lcd_del_i80_bus(esp_lcd_i80_bus_handle_t bus)
+{
+ esp_err_t ret = ESP_OK;
+ ESP_GOTO_ON_FALSE(bus, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
+ ESP_GOTO_ON_FALSE(LIST_EMPTY(&bus->device_list), ESP_ERR_INVALID_STATE, err, TAG, "device list not empty");
+ int bus_id = bus->bus_id;
+ lcd_com_remove_device(LCD_COM_DEVICE_TYPE_I80, bus_id);
+ PERIPH_RCC_RELEASE_ATOMIC(lcd_periph_i80_signals.buses[bus_id].module, ref_count) {
+ if (ref_count == 0) {
+ lcd_ll_enable_bus_clock(bus_id, false);
+ }
+ }
+ gdma_disconnect(bus->dma_chan);
+ gdma_del_channel(bus->dma_chan);
+ esp_intr_free(bus->intr);
+ free(bus->format_buffer);
+ if (bus->pm_lock) {
+ esp_pm_lock_delete(bus->pm_lock);
+ }
+ gdma_del_link_list(bus->dma_link);
+ free(bus);
+ ESP_LOGD(TAG, "del i80 bus(%d)", bus_id);
+err:
+ return ret;
+}
+
+esp_err_t esp_lcd_new_panel_io_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_panel_io_i80_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
+{
+ esp_err_t ret = ESP_OK;
+ lcd_panel_io_i80_t *i80_device = NULL;
+ bool bus_exclusive = false;
+ ESP_GOTO_ON_FALSE(bus && io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
+ // check if the bus has been configured as exclusive
+ portENTER_CRITICAL(&bus->spinlock);
+ if (!bus->flags.exclusive) {
+ bus->flags.exclusive = io_config->cs_gpio_num < 0;
+ } else {
+ bus_exclusive = true;
+ }
+ portEXIT_CRITICAL(&bus->spinlock);
+ ESP_GOTO_ON_FALSE(!bus_exclusive, ESP_ERR_INVALID_STATE, err, TAG, "bus has been exclusively owned by device");
+ // check if pixel clock setting is valid
+ uint32_t pclk_prescale = bus->resolution_hz / io_config->pclk_hz;
+ ESP_GOTO_ON_FALSE(pclk_prescale > 0 && pclk_prescale <= LCD_LL_PCLK_DIV_MAX, ESP_ERR_NOT_SUPPORTED, err, TAG,
+ "prescaler can't satisfy PCLK clock %"PRIu32"Hz", io_config->pclk_hz);
+ i80_device = heap_caps_calloc(1, sizeof(lcd_panel_io_i80_t) + io_config->trans_queue_depth * sizeof(lcd_i80_trans_descriptor_t), LCD_I80_MEM_ALLOC_CAPS);
+ ESP_GOTO_ON_FALSE(i80_device, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 panel io");
+ // create two queues for i80 device
+ i80_device->trans_queue = xQueueCreate(io_config->trans_queue_depth, sizeof(lcd_i80_trans_descriptor_t *));
+ ESP_GOTO_ON_FALSE(i80_device->trans_queue, ESP_ERR_NO_MEM, err, TAG, "create trans queue failed");
+ i80_device->done_queue = xQueueCreate(io_config->trans_queue_depth, sizeof(lcd_i80_trans_descriptor_t *));
+ ESP_GOTO_ON_FALSE(i80_device->done_queue, ESP_ERR_NO_MEM, err, TAG, "create done queue failed");
+ // adding device to list
+ portENTER_CRITICAL(&bus->spinlock);
+ LIST_INSERT_HEAD(&bus->device_list, i80_device, device_list_entry);
+ portEXIT_CRITICAL(&bus->spinlock);
+ // we don't initialize the i80 bus at the memont, but initialize the bus when start a transaction for a new device
+ // so save these as i80 device runtime parameters
+ i80_device->bus = bus;
+ i80_device->lcd_cmd_bits = io_config->lcd_cmd_bits;
+ i80_device->lcd_param_bits = io_config->lcd_param_bits;
+ i80_device->queue_size = io_config->trans_queue_depth;
+ i80_device->clock_prescale = pclk_prescale;
+ i80_device->pclk_hz = bus->resolution_hz / pclk_prescale;
+ i80_device->dc_levels.dc_cmd_level = io_config->dc_levels.dc_cmd_level;
+ i80_device->dc_levels.dc_data_level = io_config->dc_levels.dc_data_level;
+ i80_device->dc_levels.dc_dummy_level = io_config->dc_levels.dc_dummy_level;
+ i80_device->dc_levels.dc_idle_level = io_config->dc_levels.dc_idle_level;
+ i80_device->cs_gpio_num = io_config->cs_gpio_num;
+ i80_device->flags.reverse_color_bits = io_config->flags.reverse_color_bits;
+ i80_device->flags.swap_color_bytes = io_config->flags.swap_color_bytes;
+ i80_device->flags.cs_active_high = io_config->flags.cs_active_high;
+ i80_device->flags.pclk_idle_low = io_config->flags.pclk_idle_low;
+ i80_device->flags.pclk_active_neg = io_config->flags.pclk_active_neg;
+ i80_device->on_color_trans_done = io_config->on_color_trans_done;
+ i80_device->user_ctx = io_config->user_ctx;
+ // fill panel io function table
+ i80_device->base.del = panel_io_i80_del;
+ i80_device->base.tx_param = panel_io_i80_tx_param;
+ i80_device->base.tx_color = panel_io_i80_tx_color;
+ i80_device->base.register_event_callbacks = panel_io_i80_register_event_callbacks;
+ // we only configure the CS GPIO as output, don't connect to the peripheral signal at the moment
+ // we will connect the CS GPIO to peripheral signal when switching devices in lcd_i80_switch_devices()
+ if (io_config->cs_gpio_num >= 0) {
+ // CS signal is controlled by software
+ gpio_set_level(io_config->cs_gpio_num, !io_config->flags.cs_active_high); // de-assert by default
+ gpio_func_sel(io_config->cs_gpio_num, PIN_FUNC_GPIO);
+ gpio_output_enable(io_config->cs_gpio_num);
+ }
+ *ret_io = &(i80_device->base);
+ ESP_LOGD(TAG, "new i80 lcd panel io @%p on bus(%d)", i80_device, bus->bus_id);
+ return ESP_OK;
+
+err:
+ if (i80_device) {
+ if (i80_device->trans_queue) {
+ vQueueDelete(i80_device->trans_queue);
+ }
+ if (i80_device->done_queue) {
+ vQueueDelete(i80_device->done_queue);
+ }
+ free(i80_device);
+ }
+ return ret;
+}
+
+static esp_err_t panel_io_i80_del(esp_lcd_panel_io_t *io)
+{
+ lcd_panel_io_i80_t *i80_device = __containerof(io, lcd_panel_io_i80_t, base);
+ esp_lcd_i80_bus_t *bus = i80_device->bus;
+ lcd_i80_trans_descriptor_t *trans_desc = NULL;
+ // wait all pending transaction to finish
+ size_t num_trans_inflight = i80_device->num_trans_inflight;
+ for (size_t i = 0; i < num_trans_inflight; i++) {
+ ESP_RETURN_ON_FALSE(xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE,
+ ESP_FAIL, TAG, "recycle inflight transactions failed");
+ i80_device->num_trans_inflight--;
+ }
+ // remove from device list
+ portENTER_CRITICAL(&bus->spinlock);
+ LIST_REMOVE(i80_device, device_list_entry);
+ portEXIT_CRITICAL(&bus->spinlock);
+
+ if (i80_device->cs_gpio_num >= 0) {
+ gpio_output_disable(i80_device->cs_gpio_num);
+ }
+
+ ESP_LOGD(TAG, "del i80 lcd panel io @%p", i80_device);
+ vQueueDelete(i80_device->trans_queue);
+ vQueueDelete(i80_device->done_queue);
+ free(i80_device);
+ return ESP_OK;
+}
+
+static esp_err_t panel_io_i80_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx)
+{
+ lcd_panel_io_i80_t *i80_device = __containerof(io, lcd_panel_io_i80_t, base);
+
+ if (i80_device->on_color_trans_done != NULL) {
+ ESP_LOGW(TAG, "Callback on_color_trans_done was already set and now it was overwritten!");
+ }
+
+ i80_device->on_color_trans_done = cbs->on_color_trans_done;
+ i80_device->user_ctx = user_ctx;
+
+ return ESP_OK;
+}
+
+static void i80_lcd_prepare_cmd_buffer(esp_lcd_i80_bus_t *bus, lcd_panel_io_i80_t *i80_device, void *lcd_cmd)
+{
+ uint8_t *from = (uint8_t *)lcd_cmd;
+ if (bus->bus_width < i80_device->lcd_cmd_bits) {
+ // LCD is big-endian, e.g. to send command 0x1234, byte 0x12 should appear on the bus first
+ // However, the i80 peripheral will send 0x34 first, so we reversed the order below
+ int start = 0;
+ int end = i80_device->lcd_cmd_bits / 8 - 1;
+ lcd_com_reverse_buffer_bytes(from, start, end);
+ }
+}
+
+static uint32_t i80_lcd_prepare_param_buffer(esp_lcd_i80_bus_t *bus, lcd_panel_io_i80_t *i80_device, const void *lcd_param, size_t param_size)
+{
+ int param_per_size = i80_device->lcd_param_bits / 8;
+ int param_num = param_size / param_per_size;
+ const uint8_t *from = (const uint8_t *)lcd_param;
+ // we use non-cacheable address to write to the format buffer, for simplicity
+ uint8_t *to = bus->format_buffer_nc;
+ uint8_t step = bus->bus_width / 8;
+ int param_cycle = i80_device->lcd_param_bits / bus->bus_width;
+ // in case bus_width=16 and param_bits=8, we still need 1 param_cycle
+ if (param_cycle * bus->bus_width < i80_device->lcd_param_bits) {
+ param_cycle++;
+ }
+ int ele_cycles = param_cycle * param_num;
+ int bytes_to_copy = MIN(bus->bus_width, i80_device->lcd_param_bits) / 8;
+ int cnt_from = 0;
+ // expand the width of parameters when necessary
+ for (int i = 0; i < ele_cycles; i++) {
+ for (int j = 0; j < bytes_to_copy; j++) {
+ to[j] = from[cnt_from++];
+ }
+ to += step;
+ }
+ return to - bus->format_buffer_nc;
+}
+
+static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size)
+{
+ lcd_panel_io_i80_t *next_device = __containerof(io, lcd_panel_io_i80_t, base);
+ esp_lcd_i80_bus_t *bus = next_device->bus;
+ lcd_panel_io_i80_t *cur_device = bus->cur_device;
+ lcd_i80_trans_descriptor_t *trans_desc = NULL;
+ assert(param_size <= bus->max_transfer_bytes && "parameter bytes too long, enlarge max_transfer_bytes");
+ assert(param_size <= LCD_I80_IO_FORMAT_BUF_SIZE && "format buffer too small, increase LCD_I80_IO_FORMAT_BUF_SIZE");
+ uint32_t cmd_cycles = next_device->lcd_cmd_bits / bus->bus_width;
+ // in case bus_width=16 and cmd_bits=8, we still need 1 cmd_cycle
+ if (cmd_cycles * bus->bus_width < next_device->lcd_cmd_bits) {
+ cmd_cycles++;
+ }
+ i80_lcd_prepare_cmd_buffer(bus, next_device, &lcd_cmd);
+ uint32_t param_len = i80_lcd_prepare_param_buffer(bus, next_device, param, param_size);
+ // wait all pending transaction in the queue to finish
+ size_t num_trans_inflight = next_device->num_trans_inflight;
+ for (size_t i = 0; i < num_trans_inflight; i++) {
+ ESP_RETURN_ON_FALSE(xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE,
+ ESP_FAIL, TAG, "recycle inflight transactions failed");
+ next_device->num_trans_inflight--;
+ }
+
+ uint32_t intr_status = lcd_ll_get_interrupt_status(bus->hal.dev);
+ lcd_ll_clear_interrupt_status(bus->hal.dev, intr_status);
+ // switch devices if necessary
+ lcd_i80_switch_devices(cur_device, next_device);
+ // set data format
+ lcd_ll_reverse_dma_data_bit_order(bus->hal.dev, false);
+ // whether to swap the adjacent data bytes
+ lcd_ll_enable_swizzle(bus->hal.dev, next_device->lcd_param_bits > bus->bus_width);
+ bus->cur_trans = NULL;
+ bus->cur_device = next_device;
+ // package a transaction
+ trans_desc = &next_device->trans_pool[0];
+ trans_desc->i80_device = next_device;
+ trans_desc->cmd_cycles = cmd_cycles;
+ trans_desc->cmd_value = lcd_cmd;
+ // either the param is NULL or the param_size is zero, means there isn't a data phase in this transaction
+ trans_desc->data = (param && param_len) ? bus->format_buffer : NULL;
+ trans_desc->data_length = trans_desc->data ? param_len : 4;
+ trans_desc->trans_done_cb = NULL; // no callback for parameter transaction
+ size_t buffer_alignment = esp_ptr_internal(trans_desc->data) ? bus->int_mem_align : bus->ext_mem_align;
+ static uint32_t fake_trigger = 0;
+ // mount data to DMA links
+ gdma_buffer_mount_config_t mount_config = {
+ .buffer = trans_desc->data ? (void *)trans_desc->data : (&fake_trigger),
+ .buffer_alignment = buffer_alignment,
+ .length = trans_desc->data_length,
+ .flags = {
+ .mark_eof = true,
+ .mark_final = GDMA_FINAL_LINK_TO_NULL, // singly link list, mark final descriptor
+ }
+ };
+ gdma_link_mount_buffers(bus->dma_link, 0, &mount_config, 1, NULL);
+ // increase the pm lock reference count before starting a new transaction
+ if (bus->pm_lock) {
+ esp_pm_lock_acquire(bus->pm_lock);
+ }
+ lcd_start_transaction(bus, trans_desc);
+ // polling the trans done event, but don't clear the event status
+ while (!(lcd_ll_get_interrupt_status(bus->hal.dev) & LCD_LL_EVENT_TRANS_DONE)) {}
+ // decrease pm lock reference count
+ if (bus->pm_lock) {
+ esp_pm_lock_release(bus->pm_lock);
+ }
+ return ESP_OK;
+}
+
+static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size)
+{
+ lcd_panel_io_i80_t *i80_device = __containerof(io, lcd_panel_io_i80_t, base);
+ esp_lcd_i80_bus_t *bus = i80_device->bus;
+ lcd_i80_trans_descriptor_t *trans_desc = NULL;
+ assert(color_size <= bus->max_transfer_bytes && "color bytes too long, enlarge max_transfer_bytes");
+ uint32_t cache_line_size = 0;
+ if (esp_ptr_external_ram(color)) {
+ // check alignment
+ ESP_RETURN_ON_FALSE(((uint32_t)color & (bus->ext_mem_align - 1)) == 0, ESP_ERR_INVALID_ARG, TAG, "color address not aligned");
+ ESP_RETURN_ON_FALSE((color_size & (bus->ext_mem_align - 1)) == 0, ESP_ERR_INVALID_ARG, TAG, "color size not aligned");
+ cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA);
+ } else {
+ // check alignment
+ ESP_RETURN_ON_FALSE(((uint32_t)color & (bus->int_mem_align - 1)) == 0, ESP_ERR_INVALID_ARG, TAG, "color address not aligned");
+ ESP_RETURN_ON_FALSE((color_size & (bus->int_mem_align - 1)) == 0, ESP_ERR_INVALID_ARG, TAG, "color size not aligned");
+ cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
+ }
+ if (cache_line_size > 0) {
+ // flush data from cache to the physical memory
+ esp_cache_msync((void *)color, color_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
+ }
+
+ // in case bus_width=16 and cmd_bits=8, we still need 1 cmd_cycle
+ uint32_t cmd_cycles = i80_device->lcd_cmd_bits / bus->bus_width;
+ if (cmd_cycles * bus->bus_width < i80_device->lcd_cmd_bits) {
+ cmd_cycles++;
+ }
+ i80_lcd_prepare_cmd_buffer(bus, i80_device, &lcd_cmd);
+ if (i80_device->num_trans_inflight < i80_device->queue_size) {
+ trans_desc = &i80_device->trans_pool[i80_device->num_trans_inflight];
+ } else {
+ // transaction pool has used up, recycle one from done_queue
+ ESP_RETURN_ON_FALSE(xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE,
+ ESP_FAIL, TAG, "recycle inflight transactions failed");
+ i80_device->num_trans_inflight--;
+ }
+ trans_desc->i80_device = i80_device;
+ trans_desc->cmd_cycles = cmd_cycles;
+ trans_desc->cmd_value = lcd_cmd;
+ trans_desc->data = color;
+ trans_desc->data_length = color_size;
+ trans_desc->trans_done_cb = i80_device->on_color_trans_done;
+ trans_desc->user_ctx = i80_device->user_ctx;
+
+ // send transaction to trans_queue
+ xQueueSend(i80_device->trans_queue, &trans_desc, portMAX_DELAY);
+ i80_device->num_trans_inflight++;
+ // enable interrupt and go into isr handler, where we fetch the transactions from trans_queue and start it
+ // we will go into `i80_lcd_default_isr_handler` almost at once, because the "trans done" event is active at the moment
+ esp_intr_enable(bus->intr);
+ return ESP_OK;
+}
+
+static esp_err_t lcd_i80_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_clock_source_t clk_src)
+{
+ // get clock source frequency
+ uint32_t src_clk_hz = 0;
+ ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_hz),
+ TAG, "get clock source frequency failed");
+
+ ESP_RETURN_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)clk_src, true), TAG, "clock source enable failed");
+ LCD_CLOCK_SRC_ATOMIC() {
+ lcd_ll_select_clk_src(bus->hal.dev, clk_src);
+ // force to use integer division, as fractional division might lead to clock jitter
+ lcd_ll_set_group_clock_coeff(bus->hal.dev, LCD_PERIPH_CLOCK_PRE_SCALE, 0, 0);
+ }
+
+ // save the resolution of the i80 bus
+ bus->resolution_hz = src_clk_hz / LCD_PERIPH_CLOCK_PRE_SCALE;
+ // create pm lock based on different clock source
+#if CONFIG_PM_ENABLE
+ // clock sources like PLL and XTAL will be turned off in light sleep, so basically a NO_LIGHT_SLEEP lock is sufficient
+ esp_pm_lock_type_t lock_type = ESP_PM_NO_LIGHT_SLEEP;
+#if CONFIG_IDF_TARGET_ESP32P4
+ // use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS
+ lock_type = ESP_PM_CPU_FREQ_MAX;
+#endif
+ ESP_RETURN_ON_ERROR(esp_pm_lock_create(lock_type, 0, "i80_bus_lcd", &bus->pm_lock), TAG, "create pm lock failed");
+#endif
+ return ESP_OK;
+}
+
+static esp_err_t lcd_i80_init_dma_link(esp_lcd_i80_bus_handle_t bus, const esp_lcd_i80_bus_config_t *bus_config)
+{
+ esp_err_t ret = ESP_OK;
+ // alloc DMA channel and connect to LCD peripheral
+ gdma_channel_alloc_config_t dma_chan_config = {
+ .direction = GDMA_CHANNEL_DIRECTION_TX,
+ };
+ ret = LCD_GDMA_NEW_CHANNEL(&dma_chan_config, &bus->dma_chan);
+ ESP_RETURN_ON_ERROR(ret, TAG, "alloc DMA channel failed");
+ gdma_connect(bus->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0));
+ gdma_strategy_config_t strategy_config = {
+ .auto_update_desc = true,
+ .owner_check = true
+ };
+ gdma_apply_strategy(bus->dma_chan, &strategy_config);
+ // config DMA transfer parameters
+ gdma_transfer_config_t trans_cfg = {
+ .max_data_burst_size = bus_config->dma_burst_size ? bus_config->dma_burst_size : 32, // Enable DMA burst transfer for better performance
+ .access_ext_mem = true, // the LCD can carry pixel buffer from the external memory
+ };
+ ESP_RETURN_ON_ERROR(gdma_config_transfer(bus->dma_chan, &trans_cfg), TAG, "config DMA transfer failed");
+ gdma_get_alignment_constraints(bus->dma_chan, &bus->int_mem_align, &bus->ext_mem_align);
+
+ size_t buffer_alignment = MAX(bus->int_mem_align, bus->ext_mem_align);
+ size_t num_dma_nodes = esp_dma_calculate_node_count(bus->max_transfer_bytes, buffer_alignment, LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE);
+ // create DMA link list
+ gdma_link_list_config_t dma_link_config = {
+ .item_alignment = LCD_GDMA_DESCRIPTOR_ALIGN,
+ .num_items = num_dma_nodes,
+ .flags = {
+ .check_owner = true,
+ },
+ };
+ ESP_RETURN_ON_ERROR(gdma_new_link_list(&dma_link_config, &bus->dma_link), TAG, "create DMA link list failed");
+
+ return ESP_OK;
+}
+
+void *esp_lcd_i80_alloc_draw_buffer(esp_lcd_panel_io_handle_t io, size_t size, uint32_t caps)
+{
+ ESP_RETURN_ON_FALSE(io, NULL, TAG, "invalid argument");
+ lcd_panel_io_i80_t *i80_device = __containerof(io, lcd_panel_io_i80_t, base);
+ esp_lcd_i80_bus_t *bus = i80_device->bus;
+ void *buf = NULL;
+ // alloc from external memory
+ if (caps & MALLOC_CAP_SPIRAM) {
+ buf = heap_caps_aligned_calloc(bus->ext_mem_align, 1, size, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA);
+ } else {
+ buf = heap_caps_aligned_calloc(bus->int_mem_align, 1, size, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
+ }
+ return buf;
+}
+
+static esp_err_t lcd_i80_bus_configure_gpio(esp_lcd_i80_bus_handle_t bus, const esp_lcd_i80_bus_config_t *bus_config)
+{
+ int bus_id = bus->bus_id;
+ // check validation of GPIO number
+ bool valid_gpio = (bus_config->wr_gpio_num >= 0) && (bus_config->dc_gpio_num >= 0);
+ for (size_t i = 0; i < bus_config->bus_width; i++) {
+ valid_gpio = valid_gpio && (bus_config->data_gpio_nums[i] >= 0);
+ }
+ if (!valid_gpio) {
+ return ESP_ERR_INVALID_ARG;
+ }
+ // Set the number of output data lines
+ lcd_ll_set_data_wire_width(bus->hal.dev, bus_config->bus_width);
+ // connect peripheral signals via GPIO matrix
+ // data lines
+ for (size_t i = 0; i < bus_config->bus_width; i++) {
+ gpio_func_sel(bus_config->data_gpio_nums[i], PIN_FUNC_GPIO);
+ // the esp_rom_gpio_connect_out_signal function will also help enable the output path properly
+ esp_rom_gpio_connect_out_signal(bus_config->data_gpio_nums[i], lcd_periph_i80_signals.buses[bus_id].data_sigs[i], false, false);
+ }
+ // D/C signal
+ gpio_func_sel(bus_config->dc_gpio_num, PIN_FUNC_GPIO);
+ esp_rom_gpio_connect_out_signal(bus_config->dc_gpio_num, lcd_periph_i80_signals.buses[bus_id].dc_sig, false, false);
+ // WR signal (PCLK)
+ gpio_func_sel(bus_config->wr_gpio_num, PIN_FUNC_GPIO);
+ esp_rom_gpio_connect_out_signal(bus_config->wr_gpio_num, lcd_periph_i80_signals.buses[bus_id].wr_sig, false, false);
+ return ESP_OK;
+}
+
+static void lcd_periph_trigger_quick_trans_done_event(esp_lcd_i80_bus_handle_t bus)
+{
+ // trigger a quick interrupt event by a dummy transaction, wait the LCD interrupt line goes active
+ // next time when esp_intr_enable is invoked, we can go into interrupt handler immediately
+ // where we dispatch transactions for i80 devices
+ lcd_ll_set_phase_cycles(bus->hal.dev, 0, 1, 0);
+ static uint32_t fake_trigger = 0;
+ gdma_buffer_mount_config_t mount_config = {
+ .buffer = &fake_trigger,
+ .length = 4,
+ .flags = {
+ .mark_eof = true, // mark the "EOF" flag to trigger LCD EOF interrupt
+ .mark_final = true, // singly link list, mark final descriptor
+ }
+ };
+ gdma_link_mount_buffers(bus->dma_link, 0, &mount_config, 1, NULL);
+ gdma_start(bus->dma_chan, gdma_link_get_head_addr(bus->dma_link));
+ lcd_ll_start(bus->hal.dev);
+ while (!(lcd_ll_get_interrupt_status(bus->hal.dev) & LCD_LL_EVENT_TRANS_DONE)) {}
+}
+
+static void lcd_start_transaction(esp_lcd_i80_bus_t *bus, lcd_i80_trans_descriptor_t *trans_desc)
+{
+ // by default, the dummy phase is disabled because it's not common for most LCDs
+ uint32_t dummy_cycles = 0;
+ uint32_t cmd_cycles = trans_desc->cmd_value >= 0 ? trans_desc->cmd_cycles : 0;
+ // Number of data phase cycles are controlled by DMA buffer length, we only need to enable/disable the phase here
+ uint32_t data_cycles = trans_desc->data ? 1 : 0;
+ if (trans_desc->cmd_value >= 0) {
+ lcd_ll_set_command(bus->hal.dev, bus->bus_width, trans_desc->cmd_value);
+ }
+ lcd_ll_set_phase_cycles(bus->hal.dev, cmd_cycles, dummy_cycles, data_cycles);
+ lcd_ll_set_blank_cycles(bus->hal.dev, 1, 1);
+
+ // reset FIFO before starting a new transaction, in case there remains some dirty data in the FIFO because of the "fake trigger".
+ lcd_ll_fifo_reset(bus->hal.dev);
+
+ // always start GDMA, because the lcd will only start working after the dma retrieves the data
+ gdma_start(bus->dma_chan, gdma_link_get_head_addr(bus->dma_link));
+ // delay 4us 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(4);
+
+ lcd_ll_start(bus->hal.dev);
+}
+
+static void lcd_i80_switch_devices(lcd_panel_io_i80_t *cur_device, lcd_panel_io_i80_t *next_device)
+{
+ // we assume the next_device and cur_device are attached to the same bus
+ esp_lcd_i80_bus_t *bus = next_device->bus;
+ if (next_device != cur_device) {
+ // reconfigure PCLK for the new device
+ lcd_ll_set_pixel_clock_prescale(bus->hal.dev, next_device->clock_prescale);
+ lcd_ll_set_clock_idle_level(bus->hal.dev, !next_device->flags.pclk_idle_low);
+ lcd_ll_set_pixel_clock_edge(bus->hal.dev, next_device->flags.pclk_active_neg);
+ // configure DC line level for the new device
+ lcd_ll_set_dc_level(bus->hal.dev, next_device->dc_levels.dc_idle_level, next_device->dc_levels.dc_cmd_level,
+ next_device->dc_levels.dc_dummy_level, next_device->dc_levels.dc_data_level);
+ if (cur_device && cur_device->cs_gpio_num >= 0) {
+ gpio_output_disable(cur_device->cs_gpio_num);
+ }
+ if (next_device->cs_gpio_num >= 0) {
+ // connect CS signal to the new device
+ esp_rom_gpio_connect_out_signal(next_device->cs_gpio_num, lcd_periph_i80_signals.buses[bus->bus_id].cs_sig,
+ next_device->flags.cs_active_high, false);
+ }
+ }
+}
+
+IRAM_ATTR static void i80_lcd_default_isr_handler(void *args)
+{
+ esp_lcd_i80_bus_t *bus = (esp_lcd_i80_bus_t *)args;
+ lcd_i80_trans_descriptor_t *trans_desc = NULL;
+ lcd_panel_io_i80_t *cur_device = NULL;
+ lcd_panel_io_i80_t *next_device = NULL;
+ BaseType_t high_task_woken = pdFALSE;
+ bool need_yield = false;
+ uint32_t intr_status = lcd_ll_get_interrupt_status(bus->hal.dev);
+
+#if LCD_LL_EVENT_UNDERRUN
+ if (intr_status & LCD_LL_EVENT_UNDERRUN) {
+ lcd_ll_clear_interrupt_status(bus->hal.dev, LCD_LL_EVENT_UNDERRUN);
+ ESP_EARLY_LOGE(TAG, "LCD underrun");
+ }
+#endif
+
+ if (intr_status & LCD_LL_EVENT_TRANS_DONE) {
+ // disable interrupt temporarily, only re-enable when there be remained transaction in the queue
+ esp_intr_disable(bus->intr);
+ trans_desc = bus->cur_trans; // the finished transaction
+ cur_device = bus->cur_device;// the working device
+ // process finished transaction
+ if (trans_desc) {
+ assert(trans_desc->i80_device == cur_device && "transaction device mismatch");
+ // decrease pm lock reference count
+ if (bus->pm_lock) {
+ esp_pm_lock_release(bus->pm_lock);
+ }
+ // device callback
+ if (trans_desc->trans_done_cb) {
+ if (trans_desc->trans_done_cb(&cur_device->base, NULL, trans_desc->user_ctx)) {
+ need_yield = true;
+ }
+ }
+ // move transaction to done_queue
+ // there won't be case that will overflow the queue, so skip checking the return value
+ high_task_woken = pdFALSE;
+ xQueueSendFromISR(cur_device->done_queue, &trans_desc, &high_task_woken);
+ if (high_task_woken == pdTRUE) {
+ need_yield = true;
+ }
+ bus->cur_trans = NULL;
+ }
+ // fetch transactions from devices' trans_queue
+ // Note: the first registered device will have the highest priority to be scheduled
+ LIST_FOREACH(next_device, &bus->device_list, device_list_entry) {
+ high_task_woken = pdFALSE;
+ if (xQueueReceiveFromISR(next_device->trans_queue, &trans_desc, &high_task_woken) == pdTRUE) {
+ if (high_task_woken == pdTRUE) {
+ need_yield = true;
+ }
+ // sanity check
+ assert(trans_desc);
+ // only clear the interrupt status when we're sure there still remains transaction to handle
+ lcd_ll_clear_interrupt_status(bus->hal.dev, LCD_LL_EVENT_TRANS_DONE);
+ // switch devices if necessary
+ lcd_i80_switch_devices(cur_device, next_device);
+ // only reverse data bit/bytes for color data
+ lcd_ll_reverse_dma_data_bit_order(bus->hal.dev, next_device->flags.reverse_color_bits);
+ lcd_ll_enable_swizzle(bus->hal.dev, next_device->flags.swap_color_bytes);
+ bus->cur_trans = trans_desc;
+ bus->cur_device = next_device;
+ // mount data to DMA links
+ gdma_buffer_mount_config_t mount_config = {
+ .buffer = (void *)trans_desc->data,
+ .length = trans_desc->data_length,
+ .flags = {
+ .mark_eof = true,
+ .mark_final = GDMA_FINAL_LINK_TO_NULL, // singly link list, mark final descriptor
+ }
+ };
+ gdma_link_mount_buffers(bus->dma_link, 0, &mount_config, 1, NULL);
+ // enable interrupt again, because the new transaction can trigger new trans done event
+ esp_intr_enable(bus->intr);
+ // increase the pm lock reference count before starting a new transaction
+ if (bus->pm_lock) {
+ esp_pm_lock_acquire(bus->pm_lock);
+ }
+ lcd_start_transaction(bus, trans_desc);
+ break; // exit for-each loop
+ }
+ }
+ }
+ if (need_yield) {
+ portYIELD_FROM_ISR();
+ }
+}
### components/esp_lcd/include/esp_lcd_io_i2c.h
@@ -0,0 +1,122 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdbool.h>
+#include "esp_err.h"
+#include "esp_lcd_types.h"
+#include "driver/i2c_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef uint32_t esp_lcd_i2c_bus_handle_t; /*!< Type of LCD I2C bus handle */
+
+/**
+ * @brief Panel IO configuration structure, for I2C interface
+ *
+ */
+typedef struct {
+ uint32_t dev_addr; /*!< I2C device address */
+ esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; /*!< Callback invoked when color data transfer has finished */
+ void *user_ctx; /*!< User private data, passed directly to on_color_trans_done's user_ctx */
+ size_t control_phase_bytes; /*!< I2C LCD panel will encode control information (e.g. D/C selection) into control phase, in several bytes */
+ unsigned int dc_bit_offset; /*!< Offset of the D/C selection bit in control phase */
+ int lcd_cmd_bits; /*!< Bit-width of LCD command */
+ int lcd_param_bits; /*!< Bit-width of LCD parameter */
+ struct {
+ unsigned int dc_low_on_data: 1; /*!< If this flag is enabled, DC line = 0 means transfer data, DC line = 1 means transfer command; vice versa */
+ unsigned int disable_control_phase: 1; /*!< If this flag is enabled, the control phase isn't used */
+ } flags; /*!< Extra flags to fine-tune the I2C device */
+ uint32_t scl_speed_hz; /*!< I2C LCD SCL frequency (hz) */
+} esp_lcd_panel_io_i2c_config_t;
+
+/**
+ * @brief Create LCD panel IO handle, for I2C interface in legacy implementation
+ *
+ * @param[in] bus I2C bus handle, (in uint32_t)
+ * @param[in] io_config IO configuration, for I2C interface
+ * @param[out] ret_io Returned IO handle
+ *
+ * @note Please don't call this function in your project directly. Please call `esp_lcd_new_panel_to_i2c` instead.
+ *
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_ERR_NO_MEM if out of memory
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_new_panel_io_i2c_v1(uint32_t bus, const esp_lcd_panel_io_i2c_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io);
+
+/**
+ * @brief Create LCD panel IO handle, for I2C interface in new implementation
+ *
+ * @param[in] bus I2C bus handle, (in i2c_master_dev_handle_t)
+ * @param[in] io_config IO configuration, for I2C interface
+ * @param[out] ret_io Returned IO handle
+ *
+ * @note Please don't call this function in your project directly. Please call `esp_lcd_new_panel_to_i2c` instead.
+ *
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_ERR_NO_MEM if out of memory
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_new_panel_io_i2c_v2(i2c_master_bus_handle_t bus, const esp_lcd_panel_io_i2c_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef __cplusplus
+/**
+ * @brief Create LCD panel IO handle
+ *
+ * @param[in] bus I2C bus ID, indicates which I2C port to use
+ * @param[in] io_config IO configuration, for I2C interface
+ * @param[out] ret_io Returned IO handle
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_ERR_NO_MEM if out of memory
+ * - ESP_OK on success
+ */
+static inline esp_err_t esp_lcd_new_panel_io_i2c(uint32_t bus, const esp_lcd_panel_io_i2c_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
+{
+ return esp_lcd_new_panel_io_i2c_v1(bus, io_config, ret_io);
+}
+
+/**
+ * @brief Create LCD panel IO handle
+ *
+ * @param[in] bus I2C bus handle, returned from `i2c_new_master_bus`
+ * @param[in] io_config IO configuration, for I2C interface
+ * @param[out] ret_io Returned IO handle
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_ERR_NO_MEM if out of memory
+ * - ESP_OK on success
+ */
+static inline esp_err_t esp_lcd_new_panel_io_i2c(i2c_master_bus_handle_t bus, const esp_lcd_panel_io_i2c_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
+{
+ return esp_lcd_new_panel_io_i2c_v2(bus, io_config, ret_io);
+}
+#else
+/**
+ * @brief Create LCD panel IO handle
+ *
+ * @param[in] bus I2C bus handle
+ * @param[in] io_config IO configuration, for I2C interface
+ * @param[out] ret_io Returned IO handle
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_ERR_NO_MEM if out of memory
+ * - ESP_OK on success
+ */
+#define esp_lcd_new_panel_io_i2c(bus, io_config, ret_io) _Generic((bus), \
+ i2c_master_bus_handle_t : esp_lcd_new_panel_io_i2c_v2, \
+ default : esp_lcd_new_panel_io_i2c_v1) (bus, io_config, ret_io)
+
+#endif
### components/esp_lcd/include/esp_lcd_io_i80.h
@@ -0,0 +1,119 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdbool.h>
+#include "esp_err.h"
+#include "esp_lcd_types.h"
+#include "soc/soc_caps.h"
+
+#define ESP_LCD_I80_BUS_WIDTH_MAX 16 /*!< Maximum width of I80 bus */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct esp_lcd_i80_bus_t *esp_lcd_i80_bus_handle_t; /*!< Type of LCD intel 8080 bus handle */
+
+#if SOC_LCD_I80_SUPPORTED
+/**
+ * @brief LCD Intel 8080 bus configuration structure
+ */
+typedef struct {
+ int dc_gpio_num; /*!< GPIO used for D/C line */
+ int wr_gpio_num; /*!< GPIO used for WR line */
+ lcd_clock_source_t clk_src; /*!< Clock source for the I80 LCD peripheral */
+ int data_gpio_nums[ESP_LCD_I80_BUS_WIDTH_MAX]; /*!< GPIOs used for data lines */
+ size_t bus_width; /*!< Number of data lines, 8 or 16 */
+ size_t max_transfer_bytes; /*!< Maximum transfer size, this determines the length of internal DMA link */
+ union {
+ size_t psram_trans_align __attribute__((deprecated)); /*!< DMA transfer alignment for data allocated from PSRAM */
+ size_t dma_burst_size; /*!< DMA burst size, in bytes */
+ };
+ size_t sram_trans_align __attribute__((deprecated)); /*!< DMA transfer alignment for data allocated from SRAM */
+} esp_lcd_i80_bus_config_t;
+
+/**
+ * @brief Create Intel 8080 bus handle
+ *
+ * @param[in] bus_config Bus configuration
+ * @param[out] ret_bus Returned bus 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 bus is available
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lcd_i80_bus_handle_t *ret_bus);
+
+/**
+ * @brief Destroy Intel 8080 bus handle
+ *
+ * @param[in] bus Intel 8080 bus handle, created by `esp_lcd_new_i80_bus()`
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_ERR_INVALID_STATE if there still be some device attached to the bus
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_del_i80_bus(esp_lcd_i80_bus_handle_t bus);
+
+/**
+ * @brief Panel IO configuration structure, for intel 8080 interface
+ */
+typedef struct {
+ int cs_gpio_num; /*!< GPIO used for CS line, set to -1 will declaim exclusively use of I80 bus */
+ uint32_t pclk_hz; /*!< Frequency of pixel clock */
+ size_t trans_queue_depth; /*!< Transaction queue size, larger queue, higher throughput */
+ esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; /*!< Callback invoked when color data was transferred done */
+ void *user_ctx; /*!< User private data, passed directly to on_color_trans_done's user_ctx */
+ int lcd_cmd_bits; /*!< Bit-width of LCD command */
+ int lcd_param_bits; /*!< Bit-width of LCD parameter */
+ struct {
+ unsigned int dc_idle_level: 1; /*!< Level of DC line in IDLE phase */
+ unsigned int dc_cmd_level: 1; /*!< Level of DC line in CMD phase */
+ unsigned int dc_dummy_level: 1; /*!< Level of DC line in DUMMY phase */
+ unsigned int dc_data_level: 1; /*!< Level of DC line in DATA phase */
+ } dc_levels; /*!< Each i80 device might have its own D/C control logic */
+ struct {
+ unsigned int cs_active_high: 1; /*!< If set, a high level of CS line will select the device, otherwise, CS line is low level active */
+ unsigned int reverse_color_bits: 1; /*!< Reverse the data bits, D[N:0] -> D[0:N] */
+ unsigned int swap_color_bytes: 1; /*!< Swap adjacent two color bytes */
+ unsigned int pclk_active_neg: 1; /*!< The display will write data lines when there's a falling edge on WR signal (a.k.a the PCLK) */
+ unsigned int pclk_idle_low: 1; /*!< The WR signal (a.k.a the PCLK) stays at low level in IDLE phase */
+ } flags; /*!< Panel IO config flags */
+} esp_lcd_panel_io_i80_config_t;
+
+/**
+ * @brief Create LCD panel IO, for Intel 8080 interface
+ *
+ * @param[in] bus Intel 8080 bus handle, created by `esp_lcd_new_i80_bus()`
+ * @param[in] io_config IO configuration, for i80 interface
+ * @param[out] ret_io Returned panel IO handle
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_ERR_NOT_SUPPORTED if some configuration can't be satisfied, e.g. pixel clock out of the range
+ * - ESP_ERR_NO_MEM if out of memory
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_new_panel_io_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_panel_io_i80_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io);
+
+/**
+ * @brief Allocate a draw buffer that can be used by I80 interfaced LCD panel
+ *
+ * @note This function differs from the normal 'heap_caps_*' functions in that it can also automatically handle the alignment required by DMA burst, cache line size, etc.
+ *
+ * @param[in] io Panel IO handle, created by `esp_lcd_new_panel_io_i80()`
+ * @param[in] size Size of memory to be allocated
+ * @param[in] caps Bitwise OR of MALLOC_CAP_* flags indicating the type of memory desired for the allocation
+ * @return Pointer to a new buffer of size 'size' with capabilities 'caps', or NULL if allocation failed
+ */
+void *esp_lcd_i80_alloc_draw_buffer(esp_lcd_panel_io_handle_t io, size_t size, uint32_t caps);
+
+#endif // SOC_LCD_I80_SUPPORTED
+
+#ifdef __cplusplus
+}
+#endif
### components/esp_lcd/include/esp_lcd_io_parl.h
@@ -0,0 +1,75 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdbool.h>
+#include "esp_err.h"
+#include "esp_lcd_types.h"
+#include "soc/soc_caps.h"
+#include "driver/parlio_types.h"
+
+#define ESP_PARLIO_LCD_WIDTH_MAX 8 /*!< Maximum data width of parlio lcd interface */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if SOC_PARLIO_SUPPORTED
+/**
+ * @brief Parallel Panel IO configuration structure, for intel 8080 interface(8 data-lines) or SPI interface(1 data-lines)
+ */
+typedef struct {
+ int dc_gpio_num; /*!< GPIO used for D/C line */
+ int clk_gpio_num; /*!< GPIO used for CLK line */
+ int cs_gpio_num; /*!< GPIO used for CS line */
+ int data_gpio_nums[ESP_PARLIO_LCD_WIDTH_MAX]; /*!< GPIOs used for data lines */
+ size_t data_width; /*!< Number of data lines, 1(SPI) or 8(I80) */
+ uint32_t pclk_hz; /*!< Frequency of pixel clock */
+ parlio_clock_source_t clk_src; /*!< Clock source for the Parlio peripheral */
+ size_t max_transfer_bytes; /*!< Maximum transfer size, this determines the length of internal DMA link */
+ size_t dma_burst_size; /*!< DMA burst size, in bytes */
+ size_t trans_queue_depth; /*!< Transaction queue size, larger queue, higher throughput */
+ int lcd_cmd_bits; /*!< Bit-width of LCD command */
+ int lcd_param_bits; /*!< Bit-width of LCD parameter */
+ struct {
+ unsigned int dc_cmd_level: 1; /*!< Level of DC line in CMD phase */
+ unsigned int dc_data_level: 1; /*!< Level of DC line in DATA phase */
+ } dc_levels; /*!< Each LCD device might have its own D/C control logic */
+ struct {
+ unsigned int cs_active_high: 1; /*!< If set, a high level of CS line will select the device, otherwise, CS line is low level active */
+ } flags; /*!< Panel IO config flags */
+} esp_lcd_panel_io_parl_config_t;
+
+/**
+ * @brief Create LCD panel IO, for parlio interface
+ *
+ * @param[in] io_config IO configuration, for parlio interface
+ * @param[out] ret_io Returned panel IO handle
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_ERR_NOT_SUPPORTED if some configuration can't be satisfied, e.g. pixel clock out of the range
+ * - ESP_ERR_NO_MEM if out of memory
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_new_panel_io_parl(const esp_lcd_panel_io_parl_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io);
+
+/**
+ * @brief Allocate a draw buffer that can be used by parlio interface LCD panel
+ *
+ * @note This function differs from the normal 'heap_caps_*' functions in that it can also automatically handle the alignment required by DMA burst, cache line size, etc.
+ *
+ * @param[in] io Panel IO handle, created by `esp_lcd_new_panel_io_parl()`
+ * @param[in] size Size of memory to be allocated
+ * @param[in] caps Bitwise OR of MALLOC_CAP_* flags indicating the type of memory desired for the allocation
+ * @return Pointer to a new buffer of size 'size' with capabilities 'caps', or NULL if allocation failed
+ */
+void *esp_lcd_parlio_alloc_draw_buffer(esp_lcd_panel_io_handle_t io, size_t size, uint32_t caps);
+
+#endif // SOC_PARLIO_SUPPORTED
+
+#ifdef __cplusplus
+}
+#endif
### components/esp_lcd/include/esp_lcd_io_spi.h
@@ -0,0 +1,61 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdbool.h>
+#include "esp_err.h"
+#include "esp_lcd_types.h"
+#include "driver/spi_master.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef int esp_lcd_spi_bus_handle_t; /*!< Type of LCD SPI bus handle */
+
+/**
+ * @brief Panel IO configuration structure, for SPI interface
+ */
+typedef struct {
+ int cs_gpio_num; /*!< GPIO used for CS line */
+ int dc_gpio_num; /*!< GPIO used to select the D/C line, set this to -1 if the D/C line is not used */
+ int spi_mode; /*!< Traditional SPI mode (0~3) */
+ unsigned int pclk_hz; /*!< Frequency of pixel clock */
+ size_t trans_queue_depth; /*!< Size of internal transaction queue */
+ esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; /*!< Callback invoked when color data transfer has finished */
+ void *user_ctx; /*!< User private data, passed directly to on_color_trans_done's user_ctx */
+ int lcd_cmd_bits; /*!< Bit-width of LCD command */
+ int lcd_param_bits; /*!< Bit-width of LCD parameter */
+ uint8_t cs_ena_pretrans; /*!< Amount of SPI bit-cycles the cs should be activated before the transmission (0-16) */
+ uint8_t cs_ena_posttrans; /*!< Amount of SPI bit-cycles the cs should stay active after the transmission (0-16) */
+ struct {
+ unsigned int dc_high_on_cmd: 1; /*!< If enabled, DC level = 1 indicates command transfer */
+ unsigned int dc_low_on_data: 1; /*!< If enabled, DC level = 0 indicates color data transfer */
+ unsigned int dc_low_on_param: 1; /*!< If enabled, DC level = 0 indicates parameter transfer */
+ unsigned int octal_mode: 1; /*!< transmit with octal mode (8 data lines), this mode is used to simulate Intel 8080 timing */
+ unsigned int quad_mode: 1; /*!< transmit with quad mode (4 data lines), this mode is useful when transmitting LCD parameters (Only use one line for command) */
+ unsigned int sio_mode: 1; /*!< Read and write through a single data line (MOSI) */
+ unsigned int lsb_first: 1; /*!< transmit LSB bit first */
+ unsigned int cs_high_active: 1; /*!< CS line is high active */
+ } flags; /*!< Extra flags to fine-tune the SPI device */
+} esp_lcd_panel_io_spi_config_t;
+
+/**
+ * @brief Create LCD panel IO handle, for SPI interface
+ *
+ * @param[in] bus SPI bus handle
+ * @param[in] io_config IO configuration, for SPI interface
+ * @param[out] ret_io Returned IO handle
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_ERR_NO_MEM if out of memory
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_new_panel_io_spi(esp_lcd_spi_bus_handle_t bus, const esp_lcd_panel_io_spi_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io);
+
+#ifdef __cplusplus
+}
+#endif
### components/esp_lcd/include/esp_lcd_panel_commands.h
@@ -0,0 +1,56 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+/* Common LCD panel commands */
+#define LCD_CMD_NOP 0x00 // This command is empty command
+#define LCD_CMD_SWRESET 0x01 // Software reset registers (the built-in frame buffer is not affected)
+#define LCD_CMD_RDDID 0x04 // Read 24-bit display ID
+#define LCD_CMD_RDDST 0x09 // Read display status
+#define LCD_CMD_RDDPM 0x0A // Read display power mode
+#define LCD_CMD_RDD_MADCTL 0x0B // Read display MADCTL
+#define LCD_CMD_RDD_COLMOD 0x0C // Read display pixel format
+#define LCD_CMD_RDDIM 0x0D // Read display image mode
+#define LCD_CMD_RDDSM 0x0E // Read display signal mode
+#define LCD_CMD_RDDSR 0x0F // Read display self-diagnostic result
+#define LCD_CMD_SLPIN 0x10 // Go into sleep mode (DC/DC, oscillator, scanning stopped, but memory keeps content)
+#define LCD_CMD_SLPOUT 0x11 // Exit sleep mode
+#define LCD_CMD_PTLON 0x12 // Turns on partial display mode
+#define LCD_CMD_NORON 0x13 // Turns on normal display mode
+#define LCD_CMD_INVOFF 0x20 // Recover from display inversion mode
+#define LCD_CMD_INVON 0x21 // Go into display inversion mode
+#define LCD_CMD_GAMSET 0x26 // Select Gamma curve for current display
+#define LCD_CMD_DISPOFF 0x28 // Display off (disable frame buffer output)
+#define LCD_CMD_DISPON 0x29 // Display on (enable frame buffer output)
+#define LCD_CMD_CASET 0x2A // Set column address
+#define LCD_CMD_RASET 0x2B // Set row address
+#define LCD_CMD_RAMWR 0x2C // Write frame memory
+#define LCD_CMD_RAMRD 0x2E // Read frame memory
+#define LCD_CMD_PTLAR 0x30 // Define the partial area
+#define LCD_CMD_VSCRDEF 0x33 // Vertical scrolling definition
+#define LCD_CMD_TEOFF 0x34 // Turns off tearing effect
+#define LCD_CMD_TEON 0x35 // Turns on tearing effect
+#define LCD_CMD_MADCTL 0x36 // Memory data access control
+#define LCD_CMD_VSCSAD 0x37 // Vertical scroll start address
+#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_COLMOD 0x3A // Defines the format of RGB picture data
+#define LCD_CMD_RAMWRC 0x3C // Memory write continue
+#define LCD_CMD_RAMRDC 0x3E // Memory read continue
+#define LCD_CMD_STE 0x44 // Set tear scan line, tearing effect output signal when display module reaches line N
+#define LCD_CMD_GDCAN 0x45 // Get scan line
+#define LCD_CMD_WRDISBV 0x51 // Write display brightness
+#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
### components/esp_lcd/include/esp_lcd_panel_dev.h
@@ -0,0 +1,37 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "esp_err.h"
+#include "esp_lcd_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Configuration structure for panel device
+ */
+typedef struct {
+ int reset_gpio_num; /*!< GPIO used to reset the LCD panel, set to -1 if it's not used */
+ union {
+ esp_lcd_color_space_t color_space; /*!< @deprecated Set RGB color space, please use rgb_ele_order instead */
+ lcd_color_rgb_endian_t rgb_endian; /*!< @deprecated Set RGB data endian, please use rgb_ele_order instead */
+ lcd_rgb_element_order_t rgb_ele_order; /*!< Set RGB element order, RGB or BGR */
+ };
+ lcd_rgb_data_endian_t data_endian; /*!< Set the data endian for color data larger than 1 byte */
+ uint32_t bits_per_pixel; /*!< Color depth, in bpp */
+ struct {
+ uint32_t reset_active_high: 1; /*!< Setting this if the panel reset is high level active */
+ } flags; /*!< LCD panel config flags */
+ void *vendor_config; /*!< vendor specific configuration, optional, left as NULL if not used */
+} esp_lcd_panel_dev_config_t;
+
+#ifdef __cplusplus
+}
+#endif
### components/esp_lcd/include/esp_lcd_panel_io.h
@@ -0,0 +1,99 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdbool.h>
+#include "esp_err.h"
+#include "esp_lcd_types.h"
+#include "esp_lcd_io_i80.h"
+#include "esp_lcd_io_i2c.h"
+#include "esp_lcd_io_spi.h"
+#include "esp_lcd_io_parl.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Transmit LCD command and receive corresponding parameters
+ *
+ * @note Commands sent by this function are short, so they are sent using polling transactions.
+ * The function does not return before the command transfer is completed.
+ * If any queued transactions sent by `esp_lcd_panel_io_tx_color()` are still pending when this function is called,
+ * this function will wait until they are finished and the queue is empty before sending the command(s).
+ *
+ * @param[in] io LCD panel IO handle, which is created by other factory API like `esp_lcd_new_panel_io_spi()`
+ * @param[in] lcd_cmd The specific LCD command, set to -1 if no command needed
+ * @param[out] param Buffer for the command data
+ * @param[in] param_size Size of `param` buffer
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_ERR_NOT_SUPPORTED if read is not supported by transport
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_panel_io_rx_param(esp_lcd_panel_io_handle_t io, int lcd_cmd, void *param, size_t param_size);
+
+/**
+ * @brief Transmit LCD command and corresponding parameters
+ *
+ * @note Commands sent by this function are short, so they are sent using polling transactions.
+ * The function does not return before the command transfer is completed.
+ * If any queued transactions sent by `esp_lcd_panel_io_tx_color()` are still pending when this function is called,
+ * this function will wait until they are finished and the queue is empty before sending the command(s).
+ *
+ * @param[in] io LCD panel IO handle, which is created by other factory API like `esp_lcd_new_panel_io_spi()`
+ * @param[in] lcd_cmd The specific LCD command, set to -1 if no command needed
+ * @param[in] param Buffer that holds the command specific parameters, set to NULL if no parameter is needed for the command
+ * @param[in] param_size Size of `param` in memory, in bytes, set to zero if no parameter is needed for the command
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_panel_io_tx_param(esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *param, size_t param_size);
+
+/**
+ * @brief Transmit LCD RGB data
+ *
+ * @note This function will package the command and RGB data into a transaction, and push into a queue.
+ * The real transmission is performed in the background (DMA+interrupt).
+ * The caller should take care of the lifecycle of the `color` buffer.
+ * Recycling of color buffer should be done in the callback `on_color_trans_done()`.
+ *
+ * @param[in] io LCD panel IO handle, which is created by factory API like `esp_lcd_new_panel_io_spi()`
+ * @param[in] lcd_cmd The specific LCD command, set to -1 if no command needed
+ * @param[in] color Buffer that holds the RGB color data
+ * @param[in] color_size Size of `color` in memory, in bytes
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_panel_io_tx_color(esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *color, size_t color_size);
+
+/**
+ * @brief Destroy LCD panel IO handle (deinitialize panel and free all corresponding resource)
+ *
+ * @param[in] io LCD panel IO handle, which is created by factory API like `esp_lcd_new_panel_io_spi()`
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_panel_io_del(esp_lcd_panel_io_handle_t io);
+
+/**
+ * @brief Register LCD panel IO callbacks
+ *
+ * @param[in] io LCD panel IO handle, which is created by factory API like `esp_lcd_new_panel_io_spi()`
+ * @param[in] cbs structure with all LCD panel IO callbacks
+ * @param[in] user_ctx User private data, passed directly to callback's user_ctx
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_panel_io_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx);
+
+#ifdef __cplusplus
+}
+#endif
### components/esp_lcd/include/esp_lcd_panel_nt35510.h
@@ -0,0 +1,31 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdbool.h>
+#include "esp_err.h"
+#include "esp_lcd_panel_dev.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Create LCD panel for model NT35510
+ *
+ * @param[in] io LCD panel IO handle
+ * @param[in] panel_dev_config general panel device 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_OK on success
+ */
+esp_err_t esp_lcd_new_panel_nt35510(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel);
+
+#ifdef __cplusplus
+}
+#endif
### components/esp_lcd/include/esp_lcd_panel_ops.h
@@ -0,0 +1,160 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdbool.h>
+#include "esp_err.h"
+#include "esp_lcd_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Reset LCD panel
+ *
+ * @note Panel reset must be called before attempting to initialize the panel using `esp_lcd_panel_init()`.
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @return
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_panel_reset(esp_lcd_panel_handle_t panel);
+
+/**
+ * @brief Initialize LCD panel
+ *
+ * @note Before calling this function, make sure the LCD panel has finished the `reset` stage by `esp_lcd_panel_reset()`.
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @return
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_panel_init(esp_lcd_panel_handle_t panel);
+
+/**
+ * @brief Deinitialize the LCD panel
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @return
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_panel_del(esp_lcd_panel_handle_t panel);
+
+/**
+ * @brief Draw bitmap on LCD panel
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @param[in] x_start Start pixel index in the target frame buffer, on x-axis (x_start is included)
+ * @param[in] y_start Start pixel index in the target frame buffer, on y-axis (y_start is included)
+ * @param[in] x_end End pixel index in the target frame buffer, on x-axis (x_end is not included)
+ * @param[in] y_end End pixel index in the target frame buffer, on y-axis (y_end is not included)
+ * @param[in] color_data RGB color data that will be dumped to the specific window range
+ * @return
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_panel_draw_bitmap(esp_lcd_panel_handle_t panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
+
+/**
+ * @brief Mirror the LCD panel on specific axis
+ *
+ * @note Combined with `esp_lcd_panel_swap_xy()`, one can realize screen rotation
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @param[in] mirror_x Whether the panel will be mirrored about the x axis
+ * @param[in] mirror_y Whether the panel will be mirrored about the y axis
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel
+ */
+esp_err_t esp_lcd_panel_mirror(esp_lcd_panel_handle_t panel, bool mirror_x, bool mirror_y);
+
+/**
+ * @brief Swap/Exchange x and y axis
+ *
+ * @note Combined with `esp_lcd_panel_mirror()`, one can realize screen rotation
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @param[in] swap_axes Whether to swap the x and y axis
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel
+ */
+esp_err_t esp_lcd_panel_swap_xy(esp_lcd_panel_handle_t panel, bool swap_axes);
+
+/**
+ * @brief Set extra gap in x and y axis
+ *
+ * The gap is the space (in pixels) between the left/top sides of the LCD panel and the first row/column respectively of the actual contents displayed.
+ *
+ * @note Setting a gap is useful when positioning or centering a frame that is smaller than the LCD.
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @param[in] x_gap Extra gap on x axis, in pixels
+ * @param[in] y_gap Extra gap on y axis, in pixels
+ * @return
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_panel_set_gap(esp_lcd_panel_handle_t panel, int x_gap, int y_gap);
+
+/**
+ * @brief Invert the color (bit-wise invert the color data line)
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @param[in] invert_color_data Whether to invert the color data
+ * @return
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_panel_invert_color(esp_lcd_panel_handle_t panel, bool invert_color_data);
+
+/**
+ * @brief Turn on or off the display
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @param[in] on_off True to turns on display, False to turns off display
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel
+ */
+esp_err_t esp_lcd_panel_disp_on_off(esp_lcd_panel_handle_t panel, bool on_off);
+
+/**
+ * @brief Turn off the display
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @param[in] off Whether to turn off the screen
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel
+ */
+esp_err_t esp_lcd_panel_disp_off(esp_lcd_panel_handle_t panel, bool off)
+__attribute__((deprecated("use esp_lcd_panel_disp_on_off instead")));
+
+/**
+ * @brief Enter or exit sleep mode
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @param[in] sleep True to enter sleep mode, False to wake up
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel
+ */
+esp_err_t esp_lcd_panel_disp_sleep(esp_lcd_panel_handle_t panel, bool sleep);
+
+/**
+ * @brief Set the brightness of the display
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @param[in] brightness Brightness value (range depends on panel implementation, typically 0-255, 0-1023, or 0-100)
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel
+ */
+esp_err_t esp_lcd_panel_set_brightness(esp_lcd_panel_handle_t panel, int brightness);
+
+#ifdef __cplusplus
+}
+#endif
### components/esp_lcd/include/esp_lcd_panel_ssd1306.h
@@ -0,0 +1,61 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdbool.h>
+#include "esp_err.h"
+#include "esp_lcd_panel_dev.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief SSD1306 configuration structure
+ *
+ * To be used as esp_lcd_panel_dev_config_t.vendor_config.
+ * See esp_lcd_new_panel_ssd1306().
+ */
+typedef struct {
+ /**
+ * @brief Display's height in pixels (64(default) or 32)
+ */
+ uint8_t height;
+} esp_lcd_panel_ssd1306_config_t;
+
+/**
+ * @brief Create LCD panel for model SSD1306
+ *
+ * @param[in] io LCD panel IO handle
+ * @param[in] panel_dev_config general panel device 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_OK on success
+ *
+ * @note The default panel size is 128x64.
+ * @note Use esp_lcd_panel_ssd1306_config_t to set the correct size.
+ * Example usage:
+ * @code {c}
+ *
+ * esp_lcd_panel_ssd1306_config_t ssd1306_config = {
+ * .height = 32
+ * };
+ * esp_lcd_panel_dev_config_t panel_config = {
+ * <...>
+ * .vendor_config = &ssd1306_config
+ * };
+ *
+ * esp_lcd_panel_handle_t panel_handle = NULL;
+ * esp_lcd_new_panel_ssd1306(io_handle, &panel_config, &panel_handle);
+ * @endcode
+ */
+esp_err_t esp_lcd_new_panel_ssd1306(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel);
+
+#ifdef __cplusplus
+}
+#endif
### components/esp_lcd/include/esp_lcd_panel_st7789.h
@@ -0,0 +1,31 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdbool.h>
+#include "esp_err.h"
+#include "esp_lcd_panel_dev.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Create LCD panel for model ST7789
+ *
+ * @param[in] io LCD panel IO handle
+ * @param[in] panel_dev_config general panel device 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_OK on success
+ */
+esp_err_t esp_lcd_new_panel_st7789(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel);
+
+#ifdef __cplusplus
+}
+#endif
### components/esp_lcd/include/esp_lcd_panel_vendor.h
@@ -0,0 +1,12 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+
+#include "esp_lcd_panel_dev.h"
+#include "esp_lcd_panel_ssd1306.h"
+#include "esp_lcd_panel_st7789.h"
+#include "esp_lcd_panel_nt35510.h"
### components/esp_lcd/include/esp_lcd_types.h
@@ -0,0 +1,96 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdbool.h>
+#include "esp_assert.h"
+#include "hal/lcd_types.h"
+#include "hal/mipi_dsi_types.h"
+#include "hal/color_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Timing parameters for the video data transmission
+ */
+typedef struct {
+ uint32_t h_size; /*!< Horizontal resolution, i.e. the number of pixels in a line */
+ uint32_t v_size; /*!< Vertical resolution, i.e. the number of lines in the frame */
+ uint32_t hsync_pulse_width; /*!< Horizontal sync width, in pixel clock */
+ uint32_t hsync_back_porch; /*!< Horizontal back porch, number of pixel clock between hsync and start of line active data */
+ uint32_t hsync_front_porch; /*!< Horizontal front porch, number of pixel clock between the end of active data and the next hsync */
+ uint32_t vsync_pulse_width; /*!< Vertical sync width, in 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 */
+} esp_lcd_video_timing_t;
+
+typedef struct esp_lcd_panel_io_t *esp_lcd_panel_io_handle_t; /*!< Type of LCD panel IO handle */
+typedef struct esp_lcd_panel_t *esp_lcd_panel_handle_t; /*!< Type of LCD panel handle */
+
+/**
+ * @brief RGB element order
+ */
+typedef enum {
+ LCD_RGB_ELEMENT_ORDER_RGB = COLOR_RGB_ELEMENT_ORDER_RGB, /*!< RGB element order: RGB */
+ LCD_RGB_ELEMENT_ORDER_BGR = COLOR_RGB_ELEMENT_ORDER_BGR, /*!< RGB element order: BGR */
+} lcd_rgb_element_order_t;
+
+/** @cond */
+/// for backward compatible
+typedef lcd_rgb_element_order_t lcd_color_rgb_endian_t;
+#define LCD_RGB_ENDIAN_RGB (lcd_color_rgb_endian_t)LCD_RGB_ELEMENT_ORDER_RGB
+#define LCD_RGB_ENDIAN_BGR (lcd_color_rgb_endian_t)LCD_RGB_ELEMENT_ORDER_BGR
+
+typedef lcd_rgb_element_order_t esp_lcd_color_space_t;
+#define ESP_LCD_COLOR_SPACE_RGB (esp_lcd_color_space_t)LCD_RGB_ELEMENT_ORDER_RGB
+#define ESP_LCD_COLOR_SPACE_BGR (esp_lcd_color_space_t)LCD_RGB_ELEMENT_ORDER_BGR
+#define ESP_LCD_COLOR_SPACE_MONOCHROME (esp_lcd_color_space_t)2
+/** @endcond */
+
+/**
+ * @brief Type of LCD panel IO event data
+ */
+typedef struct {
+} esp_lcd_panel_io_event_data_t;
+
+/**
+ * @brief Declare the prototype of the function that will be invoked when panel IO finishes transferring color data
+ *
+ * @param[in] panel_io LCD panel IO handle, which is created by factory API like `esp_lcd_new_panel_io_spi()`
+ * @param[in] edata Panel IO event data, fed by driver
+ * @param[in] user_ctx User data, passed from `esp_lcd_panel_io_xxx_config_t`
+ * @return Whether a high priority task has been waken up by this function
+ */
+typedef bool (*esp_lcd_panel_io_color_trans_done_cb_t)(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx);
+
+/**
+ * @brief Type of LCD panel IO callbacks
+ */
+typedef struct {
+ esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; /*!< Callback invoked when color data transfer has finished */
+} esp_lcd_panel_io_callbacks_t;
+
+/**
+ * @brief Configuration of LCD color conversion
+ */
+typedef struct {
+ lcd_color_range_t in_color_range; /*!< Color range of the input color */
+ lcd_color_range_t out_color_range; /*!< Color range of the output color */
+ union {
+ struct {
+ lcd_yuv_conv_std_t conv_std; /*!< YUV conversion standard: BT601, BT709 */
+ struct {
+ lcd_yuv422_pack_order_t in_pack_order; /*!< YUV422 packing order of the input color */
+ } yuv422; /*!< YUV422 specific */
+ } yuv; /*!< YUV specific */
+ } spec; /*!< Extra configuration for specific color conversion */
+} esp_lcd_color_conv_config_t;
+
+#ifdef __cplusplus
+}
+#endif
### components/esp_lcd/interface/esp_lcd_panel_interface.h
@@ -0,0 +1,150 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdbool.h>
+#include "esp_err.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct esp_lcd_panel_t esp_lcd_panel_t; /*!< Type of LCD panel */
+
+/**
+ * @brief LCD panel interface
+ */
+struct esp_lcd_panel_t {
+ /**
+ * @brief Reset LCD panel
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @return
+ * - ESP_OK on success
+ */
+ esp_err_t (*reset)(esp_lcd_panel_t *panel);
+
+ /**
+ * @brief Initialize LCD panel
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @return
+ * - ESP_OK on success
+ */
+ esp_err_t (*init)(esp_lcd_panel_t *panel);
+
+ /**
+ * @brief Destroy LCD panel
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @return
+ * - ESP_OK on success
+ */
+ esp_err_t (*del)(esp_lcd_panel_t *panel);
+
+ /**
+ * @brief Draw bitmap on LCD panel
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @param[in] x_start Start pixel index in the target frame buffer, on x-axis (x_start is included)
+ * @param[in] y_start Start pixel index in the target frame buffer, on y-axis (y_start is included)
+ * @param[in] x_end End pixel index in the target frame buffer, on x-axis (x_end is not included)
+ * @param[in] y_end End pixel index in the target frame buffer, on y-axis (y_end is not included)
+ * @param[in] color_data RGB color data that will be dumped to the specific window range
+ * @return
+ * - ESP_OK on success
+ */
+ esp_err_t (*draw_bitmap)(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
+
+ /**
+ * @brief Mirror the LCD panel on specific axis
+ *
+ * @note Combine this function with `swap_xy`, one can realize screen rotatation
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @param[in] x_axis Whether the panel will be mirrored about the x_axis
+ * @param[in] y_axis Whether the panel will be mirrored about the y_axis
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel
+ */
+ esp_err_t (*mirror)(esp_lcd_panel_t *panel, bool x_axis, bool y_axis);
+
+ /**
+ * @brief Swap/Exchange x and y axis
+ *
+ * @note Combine this function with `mirror`, one can realize screen rotatation
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @param[in] swap_axes Whether to swap the x and y axis
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel
+ */
+ esp_err_t (*swap_xy)(esp_lcd_panel_t *panel, bool swap_axes);
+
+ /**
+ * @brief Set extra gap in x and y axis
+ *
+ * @note The gap is only used for calculating the real coordinates.
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @param[in] x_gap Extra gap on x axis, in pixels
+ * @param[in] y_gap Extra gap on y axis, in pixels
+ * @return
+ * - ESP_OK on success
+ */
+ esp_err_t (*set_gap)(esp_lcd_panel_t *panel, int x_gap, int y_gap);
+
+ /**
+ * @brief Invert the color (bit 1 -> 0 for color data line, and vice versa)
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @param[in] invert_color_data Whether to invert the color data
+ * @return
+ * - ESP_OK on success
+ */
+ esp_err_t (*invert_color)(esp_lcd_panel_t *panel, bool invert_color_data);
+
+ /**
+ * @brief Turn on or off the display
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @param[in] on_off True to turns on display, False to turns off display
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel
+ */
+ esp_err_t (*disp_on_off)(esp_lcd_panel_t *panel, bool on_off);
+
+ /**
+ * @brief Enter or exit sleep mode
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @param[in] sleep True to enter sleep mode, False to wake up
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel
+ */
+ esp_err_t (*disp_sleep)(esp_lcd_panel_t *panel, bool sleep);
+
+ /**
+ * @brief Set the brightness of the display
+ *
+ * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
+ * @param[in] brightness Brightness value (range depends on panel implementation, typically 0-255, 0-1023, or 0-100)
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel
+ */
+ esp_err_t (*set_brightness)(esp_lcd_panel_t *panel, int brightness);
+
+ void *user_data; /*!< User data, used to store externally customized data */
+};
+
+#ifdef __cplusplus
+}
+#endif
### components/esp_lcd/interface/esp_lcd_panel_io_interface.h
@@ -0,0 +1,93 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdbool.h>
+#include "esp_err.h"
+#include "esp_lcd_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct esp_lcd_panel_io_t esp_lcd_panel_io_t; /*!< Type of LCD panel IO */
+
+/**
+ * @brief LCD panel IO interface
+ */
+struct esp_lcd_panel_io_t {
+ /**
+ * @brief Transmit LCD command and receive corresponding parameters
+ *
+ * @note This is the panel-specific interface called by function `esp_lcd_panel_io_rx_param()`.
+ *
+ * @param[in] io LCD panel IO handle, which is created by other factory API like `esp_lcd_new_panel_io_spi()`
+ * @param[in] lcd_cmd The specific LCD command, set to -1 if no command needed
+ * @param[out] param Buffer for the command data
+ * @param[in] param_size Size of `param` buffer
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_ERR_NOT_SUPPORTED if read is not supported by transport
+ * - ESP_OK on success
+ */
+ esp_err_t (*rx_param)(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size);
+
+ /**
+ * @brief Transmit LCD command and corresponding parameters
+ *
+ * @note This is the panel-specific interface called by function `esp_lcd_panel_io_tx_param()`.
+ *
+ * @param[in] io LCD panel IO handle, which is created by other factory API like `esp_lcd_new_panel_io_spi()`
+ * @param[in] lcd_cmd The specific LCD command
+ * @param[in] param Buffer that holds the command specific parameters, set to NULL if no parameter is needed for the command
+ * @param[in] param_size Size of `param` in memory, in bytes, set to zero if no parameter is needed for the command
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_OK on success
+ */
+ esp_err_t (*tx_param)(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size);
+
+ /**
+ * @brief Transmit LCD RGB data
+ *
+ * @note This is the panel-specific interface called by function `esp_lcd_panel_io_tx_color()`.
+ *
+ * @param[in] io LCD panel IO handle, which is created by other factory API like `esp_lcd_new_panel_io_spi()`
+ * @param[in] lcd_cmd The specific LCD command
+ * @param[in] color Buffer that holds the RGB color data
+ * @param[in] color_size Size of `color` in memory, in bytes
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_OK on success
+ */
+ esp_err_t (*tx_color)(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size);
+
+ /**
+ * @brief Destroy LCD panel IO handle (deinitialize all and free resource)
+ *
+ * @param[in] io LCD panel IO handle, which is created by other factory API like `esp_lcd_new_panel_io_spi()`
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_OK on success
+ */
+ esp_err_t (*del)(esp_lcd_panel_io_t *io);
+
+ /**
+ * @brief Register LCD panel IO callbacks
+ *
+ * @param[in] io LCD panel IO handle, which is created by factory API like `esp_lcd_new_panel_io_spi()`
+ * @param[in] cbs structure with all LCD panel IO callbacks
+ * @param[in] user_ctx User private data, passed directly to callback's user_ctx
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_OK on success
+ */
+ esp_err_t (*register_event_callbacks)(esp_lcd_panel_io_t *io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx);
+};
+
+#ifdef __cplusplus
+}
+#endif
### components/esp_lcd/linker.lf
@@ -0,0 +1,31 @@
+[mapping:esp_lcd_dsi]
+archive: libesp_lcd.a
+entries:
+ if LCD_DSI_ISR_HANDLER_IN_IRAM = y:
+ esp_lcd_panel_dpi: mipi_dsi_dma_trans_done_cb (noflash)
+ esp_lcd_panel_dpi: mipi_dsi_bridge_isr_handler (noflash)
+
+[mapping:esp_lcd_dsi_dma]
+archive: libesp_hw_support.a
+entries:
+ if LCD_DSI_ISR_HANDLER_IN_IRAM = y:
+ # Control dw_gdma function placement granularly
+ dw_gdma: dw_gdma_link_list_get_item (noflash)
+ dw_gdma: dw_gdma_lli_set_block_markers (noflash)
+ dw_gdma: dw_gdma_channel_use_link_list (noflash)
+ dw_gdma: dw_gdma_channel_enable_ctrl (noflash)
+
+[mapping:esp_lcd_rgb_dma]
+archive: libesp_hw_support.a
+entries:
+ if LCD_RGB_ISR_IRAM_SAFE = y:
+ gdma: gdma_reset (noflash)
+ gdma: gdma_start (noflash)
+ gdma_link: gdma_link_get_head_addr (noflash)
+
+[mapping:esp_lcd_rgb_hal]
+archive: libhal.a
+entries:
+ if LCD_RGB_ISR_IRAM_SAFE = y:
+ lcd_hal: lcd_hal_cal_pclk_freq (noflash)
+ hal_utils: hal_utils_calc_clk_div_frac_fast (noflash)
### components/esp_lcd/parl/esp_lcd_panel_io_parl.c
@@ -0,0 +1,324 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Although we're manipulating Parlio peripheral, it has nothing to do with the Parlio.
+// In fact, we're simulating the Intel 8080 (8 data-width) or SPI(1 data-width) interface with Parlio peripheral.
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include "sdkconfig.h"
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+// The local log level must be defined before including esp_log.h
+// Set the maximum log level for this source file
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#endif
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/queue.h"
+#include "esp_attr.h"
+#include "esp_check.h"
+#include "esp_heap_caps.h"
+#include "soc/soc_caps.h"
+#include "soc/lcd_periph.h"
+#include "hal/gpio_hal.h"
+#include "driver/gpio.h"
+#include "driver/parlio_tx.h"
+#include "driver/parlio_types.h"
+#include "esp_private/gpio.h"
+#include "esp_private/parlio_tx_private.h"
+#include "esp_lcd_panel_io_interface.h"
+#include "esp_lcd_panel_io.h"
+#include "esp_lcd_common.h"
+
+static const char *TAG = "lcd_panel.io.parl";
+
+typedef struct lcd_panel_io_parlio_t lcd_panel_io_parlio_t;
+typedef struct parlio_trans_descriptor_t parlio_trans_descriptor_t;
+
+static esp_err_t panel_io_parl_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size);
+static esp_err_t panel_io_parl_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size);
+static esp_err_t panel_io_parl_del(esp_lcd_panel_io_t *io);
+static bool lcd_default_isr_callback(parlio_tx_unit_handle_t tx_unit, const parlio_tx_done_event_data_t *edata, void *user_ctx);
+static esp_err_t panel_io_parl_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx);
+
+struct parlio_trans_descriptor_t {
+ lcd_panel_io_parlio_t *parlio_device; // parlio device issuing this transaction
+ const void *data; // Data buffer
+ uint32_t data_length; // Data buffer size
+ struct {
+ unsigned int en_trans_done_cb: 1;
+ } flags;
+};
+
+struct lcd_panel_io_parlio_t {
+ esp_lcd_panel_io_t base; // Base class of generic lcd panel io
+ parlio_tx_unit_handle_t tx_unit; // Parlio TX unit
+ size_t data_width; // Number of data lines
+ int dc_gpio_num; // GPIO used for DC line
+ int cs_gpio_num; // GPIO used for CS line
+ int lcd_cmd_bits; // Bit width of LCD command
+ int lcd_param_bits; // Bit width of LCD parameter
+ void *user_ctx; // private data used when transfer color data
+ esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; // color data trans done callback
+ struct {
+ unsigned int dc_cmd_level: 1; // Level of DC line in CMD phase
+ unsigned int dc_data_level: 1; // Level of DC line in DATA phase
+ } dc_levels;
+ parlio_trans_descriptor_t trans_desc; // Transaction description
+};
+
+esp_err_t esp_lcd_new_panel_io_parl(const esp_lcd_panel_io_parl_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
+{
+ esp_err_t ret = ESP_OK;
+ lcd_panel_io_parlio_t *parlio_device = NULL;
+
+ ESP_GOTO_ON_FALSE(io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
+ // Only support 1-bit(SPI) or 8-bit(I80) data width
+#if SOC_PARLIO_SUPPORT_I80_LCD
+ ESP_GOTO_ON_FALSE(io_config->data_width == 1 || io_config->data_width == 8, ESP_ERR_INVALID_ARG, err,
+ TAG, "invalid bus width:%d", io_config->data_width);
+#else
+ ESP_GOTO_ON_FALSE(io_config->data_width == 1, ESP_ERR_INVALID_ARG, err,
+ TAG, "invalid bus width:%d", io_config->data_width);
+#endif
+
+ // allocate parlio device memory
+ parlio_device = heap_caps_calloc(1, sizeof(lcd_panel_io_parlio_t), MALLOC_CAP_DEFAULT);
+ ESP_GOTO_ON_FALSE(parlio_device, ESP_ERR_NO_MEM, err, TAG, "no mem for parlio device");
+
+ // initialize the parlio unit
+ parlio_tx_unit_handle_t tx_unit = NULL;
+ parlio_tx_unit_config_t pio_config = {
+ .clk_src = io_config->clk_src,
+ .data_width = io_config->data_width,
+ .clk_in_gpio_num = -1, // use internal clock source
+ .valid_gpio_num = io_config->cs_gpio_num,
+ .clk_out_gpio_num = io_config->clk_gpio_num,
+ .data_gpio_nums = {
+ io_config->data_gpio_nums[0],
+ io_config->data_gpio_nums[1],
+ io_config->data_gpio_nums[2],
+ io_config->data_gpio_nums[3],
+ io_config->data_gpio_nums[4],
+ io_config->data_gpio_nums[5],
+ io_config->data_gpio_nums[6],
+ io_config->data_gpio_nums[7],
+ },
+ .output_clk_freq_hz = io_config->pclk_hz,
+ .trans_queue_depth = io_config->trans_queue_depth ? io_config->trans_queue_depth : 4,
+ .max_transfer_size = io_config->max_transfer_bytes,
+ .sample_edge = PARLIO_SAMPLE_EDGE_POS,
+ .bit_pack_order = PARLIO_BIT_PACK_ORDER_MSB,
+ .dma_burst_size = io_config->dma_burst_size,
+ .flags.invert_valid_out = !io_config->flags.cs_active_high,
+ };
+ ESP_GOTO_ON_ERROR(parlio_new_tx_unit(&pio_config, &tx_unit), err, TAG, "config parlio tx unit failed");
+
+ parlio_tx_event_callbacks_t parlio_cbs = {
+ .on_trans_done = lcd_default_isr_callback,
+ };
+ ESP_GOTO_ON_ERROR(parlio_tx_unit_register_event_callbacks(tx_unit, &parlio_cbs, parlio_device), err, TAG, "register parlio tx callback failed");
+
+ // DC signal is controlled by software, set as general purpose IO
+ gpio_func_sel(io_config->dc_gpio_num, PIN_FUNC_GPIO);
+ gpio_output_enable(io_config->dc_gpio_num);
+
+ parlio_device->dc_gpio_num = io_config->dc_gpio_num;
+ parlio_device->tx_unit = tx_unit;
+ parlio_device->data_width = io_config->data_width;
+ parlio_device->lcd_cmd_bits = io_config->lcd_cmd_bits;
+ parlio_device->lcd_param_bits = io_config->lcd_param_bits;
+ parlio_device->dc_levels.dc_cmd_level = io_config->dc_levels.dc_cmd_level;
+ parlio_device->dc_levels.dc_data_level = io_config->dc_levels.dc_data_level;
+ parlio_device->cs_gpio_num = io_config->cs_gpio_num;
+ parlio_device->trans_desc.parlio_device = parlio_device;
+
+ // fill panel io function table
+ parlio_device->base.del = panel_io_parl_del;
+ parlio_device->base.tx_param = panel_io_parl_tx_param;
+ parlio_device->base.tx_color = panel_io_parl_tx_color;
+ parlio_device->base.register_event_callbacks = panel_io_parl_register_event_callbacks;
+
+ // enable parlio tx unit finally
+ ESP_GOTO_ON_ERROR(parlio_tx_unit_enable(tx_unit), err, TAG, "enable parlio tx unit failed");
+
+ *ret_io = &(parlio_device->base);
+ ESP_LOGD(TAG, "new parlio lcd panel io @%p", parlio_device);
+ return ESP_OK;
+
+err:
+ if (parlio_device) {
+ if (parlio_device->tx_unit) {
+ parlio_del_tx_unit(parlio_device->tx_unit);
+ }
+ free(parlio_device);
+ }
+ return ret;
+}
+
+void *esp_lcd_parlio_alloc_draw_buffer(esp_lcd_panel_io_handle_t io, size_t size, uint32_t caps)
+{
+ ESP_RETURN_ON_FALSE(io, NULL, TAG, "invalid argument");
+ lcd_panel_io_parlio_t *parlio_device = __containerof(io, lcd_panel_io_parlio_t, base);
+ size_t int_mem_align = 0;
+ size_t ext_mem_align = 0;
+ void *buf = NULL;
+ parlio_tx_get_alignment_constraints(parlio_device->tx_unit, &int_mem_align, &ext_mem_align);
+ // alloc from external memory
+ if (caps & MALLOC_CAP_SPIRAM) {
+ buf = heap_caps_aligned_calloc(ext_mem_align, 1, size, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA);
+ } else {
+ buf = heap_caps_aligned_calloc(int_mem_align, 1, size, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
+ }
+ return buf;
+}
+
+static esp_err_t panel_io_parl_del(esp_lcd_panel_io_t *io)
+{
+ lcd_panel_io_parlio_t *parlio_device = __containerof(io, lcd_panel_io_parlio_t, base);
+ // wait all pending transaction to finish
+ ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
+ ESP_LOGD(TAG, "del parlio lcd panel io @%p", parlio_device);
+
+ ESP_RETURN_ON_ERROR(parlio_tx_unit_disable(parlio_device->tx_unit), TAG, "disable parlio tx unit failed");
+ ESP_RETURN_ON_ERROR(parlio_del_tx_unit(parlio_device->tx_unit), TAG, "del parlio tx unit failed");
+
+ free(parlio_device);
+ return ESP_OK;
+}
+
+static esp_err_t panel_io_parl_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx)
+{
+ lcd_panel_io_parlio_t *parlio_device = __containerof(io, lcd_panel_io_parlio_t, base);
+ if (parlio_device->on_color_trans_done != NULL) {
+ ESP_LOGW(TAG, "Callback on_color_trans_done was already set and now it was overwritten!");
+ }
+ parlio_device->on_color_trans_done = cbs->on_color_trans_done;
+ parlio_device->user_ctx = user_ctx;
+ return ESP_OK;
+}
+
+static void parlio_lcd_prepare_cmd_buffer(parlio_trans_descriptor_t *trans_desc, const void *cmd)
+{
+ lcd_panel_io_parlio_t *parlio_device = trans_desc->parlio_device;
+ uint8_t *from = (uint8_t *)cmd;
+ // LCD is big-endian, e.g. to send command 0x1234, byte 0x12 should appear on the data bus first
+ // However, the Parlio peripheral will send 0x34 first, so we reversed the order below
+ if (parlio_device->data_width < parlio_device->lcd_cmd_bits) {
+ int start = 0;
+ int end = parlio_device->lcd_cmd_bits / 8 - 1;
+ lcd_com_reverse_buffer_bytes(from, start, end);
+ }
+ trans_desc->data = cmd;
+ trans_desc->data_length = MAX(parlio_device->lcd_cmd_bits, parlio_device->data_width) / 8;
+}
+
+static void parlio_lcd_prepare_param_buffer(parlio_trans_descriptor_t *trans_desc, const void *param, size_t param_num)
+{
+ lcd_panel_io_parlio_t *parlio_device = trans_desc->parlio_device;
+ uint8_t *from = (uint8_t *)param;
+ int param_size = parlio_device->lcd_param_bits / 8;
+ // LCD is big-endian, e.g. to send param 0x1234, byte 0x12 should appear on the data bus first
+ // However, the Parlio peripheral will send 0x34 first, so we reversed the order below
+ if (parlio_device->data_width < parlio_device->lcd_param_bits) {
+ for (size_t i = 0; i < param_num; i++) {
+ int start = i * param_size;
+ int end = start + param_size - 1;
+ lcd_com_reverse_buffer_bytes(from, start, end);
+ }
+ }
+ trans_desc->data = param;
+ trans_desc->data_length = param_num;
+}
+
+static void parlio_lcd_prepare_color_buffer(parlio_trans_descriptor_t *trans_desc, const void *color, size_t color_size)
+{
+ trans_desc->data = color;
+ trans_desc->data_length = color_size;
+}
+
+static esp_err_t panel_io_parl_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size)
+{
+ lcd_panel_io_parlio_t *parlio_device = __containerof(io, lcd_panel_io_parlio_t, base);
+ parlio_trans_descriptor_t *trans_desc = NULL;
+
+ // before issue a polling transaction, need to wait queued transactions finished
+ ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
+ trans_desc = &parlio_device->trans_desc;
+ trans_desc->flags.en_trans_done_cb = false;
+ parlio_lcd_prepare_cmd_buffer(trans_desc, &lcd_cmd);
+
+ gpio_set_level(parlio_device->dc_gpio_num, parlio_device->dc_levels.dc_cmd_level);
+ parlio_transmit_config_t transmit_config = {
+ .idle_value = 0x00,
+ };
+
+ ESP_RETURN_ON_ERROR(parlio_tx_unit_transmit(parlio_device->tx_unit, trans_desc->data, trans_desc->data_length * 8, &transmit_config), TAG, "error transmit");
+ if (param && param_size) {
+ parlio_lcd_prepare_param_buffer(trans_desc, param, param_size * 8 / parlio_device->lcd_param_bits);
+ // wait transmit done before changing DC level
+ ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
+ gpio_set_level(parlio_device->dc_gpio_num, parlio_device->dc_levels.dc_data_level);
+ ESP_RETURN_ON_ERROR(parlio_tx_unit_transmit(parlio_device->tx_unit, trans_desc->data, trans_desc->data_length * 8, &transmit_config), TAG, "error transmit");
+ }
+ // In case the lcd_cmd/param data is on the stack, wait transmit done to prevent it from being recycled
+ ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
+ return ESP_OK;
+}
+
+static esp_err_t panel_io_parl_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size)
+{
+ lcd_panel_io_parlio_t *parlio_device = __containerof(io, lcd_panel_io_parlio_t, base);
+ parlio_trans_descriptor_t *trans_desc = NULL;
+ trans_desc = &parlio_device->trans_desc;
+
+ if (lcd_cmd != -1) {
+ // wait transmit done to prevent skipping previous color transfer callback
+ ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
+ trans_desc->flags.en_trans_done_cb = false; // no callback for command transfer
+ parlio_lcd_prepare_cmd_buffer(trans_desc, &lcd_cmd);
+ gpio_set_level(parlio_device->dc_gpio_num, parlio_device->dc_levels.dc_cmd_level);
+ parlio_transmit_config_t transmit_config = {
+ .idle_value = 0x00,
+ };
+ ESP_RETURN_ON_ERROR(parlio_tx_unit_transmit(parlio_device->tx_unit, trans_desc->data, trans_desc->data_length * 8, &transmit_config), TAG, "error transmit");
+ // wait transmit done before changing DC level
+ ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
+ }
+
+ parlio_lcd_prepare_color_buffer(trans_desc, color, color_size);
+ trans_desc->flags.en_trans_done_cb = true;
+
+ gpio_set_level(parlio_device->dc_gpio_num, parlio_device->dc_levels.dc_data_level);
+ parlio_transmit_config_t transmit_config = {
+ .idle_value = 0x00,
+ };
+ ESP_RETURN_ON_ERROR(parlio_tx_unit_transmit(parlio_device->tx_unit, trans_desc->data, trans_desc->data_length * 8, &transmit_config), TAG, "error transmit");
+ return ESP_OK;
+}
+
+IRAM_ATTR static bool lcd_default_isr_callback(parlio_tx_unit_handle_t tx_unit, const parlio_tx_done_event_data_t *edata, void *user_ctx)
+{
+ lcd_panel_io_parlio_t *parlio_device = (lcd_panel_io_parlio_t *)user_ctx;
+ parlio_trans_descriptor_t *trans_desc = &parlio_device->trans_desc;
+ bool need_yield = false;
+
+ // device callback
+ if (trans_desc->flags.en_trans_done_cb) {
+ if (parlio_device->on_color_trans_done) {
+ if (parlio_device->on_color_trans_done(&parlio_device->base, NULL, parlio_device->user_ctx)) {
+ need_yield = true;
+ }
+ }
+ }
+
+ return need_yield;
+}
### 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);
### components/esp_lcd/priv_include/esp_lcd_common.h
@@ -0,0 +1,88 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stddef.h>
+#include "sdkconfig.h"
+#include "soc/soc_caps.h"
+#include "hal/dma_types.h"
+#include "esp_intr_alloc.h"
+#include "esp_heap_caps.h"
+#if SOC_LCDCAM_SUPPORTED
+#include "hal/lcd_hal.h"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// size of the internal buffer to transform the data into a proper format (e.g. data endian)
+#define LCD_I80_IO_FORMAT_BUF_SIZE 32
+
+#define LCD_I80_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED
+#define LCD_I80_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
+
+#define LCD_PERIPH_CLOCK_PRE_SCALE (2) // This is the minimum divider that can be applied to LCD peripheral
+
+#if SOC_PERIPH_CLK_CTRL_SHARED
+#define LCD_CLOCK_SRC_ATOMIC() PERIPH_RCC_ATOMIC()
+#else
+#define LCD_CLOCK_SRC_ATOMIC()
+#endif
+
+#define LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE 4095
+
+#if SOC_LCDCAM_SUPPORTED
+
+typedef enum {
+ LCD_COM_DEVICE_TYPE_I80,
+ LCD_COM_DEVICE_TYPE_RGB
+} lcd_com_device_type_t;
+
+/**
+ * @brief Register a LCD device to platform
+ *
+ * @param device_type Device type, refer to lcd_com_device_type_t
+ * @param device_obj Device object
+ * @return >=0: member_id, <0: no free lcd bus/panel slots
+ */
+int lcd_com_register_device(lcd_com_device_type_t device_type, void *device_obj);
+
+/**
+ * @brief Remove a device from platform
+ *
+ * @param device_type Device type, refer to lcd_com_device_type_t
+ * @param member_id member ID
+ */
+void lcd_com_remove_device(lcd_com_device_type_t device_type, int member_id);
+#endif // SOC_LCDCAM_SUPPORTED
+
+/**
+ * @brief Reverse the bytes in the buffer
+ *
+ * @note LCD is big-endian, e.g. to send command 0x1234, byte 0x12 should appear on the bus first
+ * However, the low level peripheral (like i80, i2s) will send 0x34 first.
+ * This helper function is used to reverse the bytes order
+ *
+ * @param buf buffer address
+ * @param start start index of the buffer
+ * @param end end index of the buffer
+ */
+static inline void lcd_com_reverse_buffer_bytes(uint8_t *buf, int start, int end)
+{
+ uint8_t temp = 0;
+ while (start < end) {
+ temp = buf[start];
+ buf[start] = buf[end];
+ buf[end] = temp;
+ start++;
+ end--;
+ }
+}
+
+#ifdef __cplusplus
+}
+#endif
### components/esp_lcd/rgb/esp_lcd_panel_rgb.c
@@ -0,0 +1,1236 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <string.h>
+#include "sdkconfig.h"
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+// The local log level must be defined before including esp_log.h
+// Set the maximum log level for this source file
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#endif
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_attr.h"
+#include "esp_check.h"
+#include "esp_pm.h"
+#include "esp_lcd_panel_interface.h"
+#include "esp_lcd_panel_rgb.h"
+#include "esp_lcd_panel_ops.h"
+#include "esp_rom_gpio.h"
+#include "soc/soc_caps.h"
+#include "esp_clk_tree.h"
+#include "hal/dma_types.h"
+#include "driver/gpio.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/esp_dma_utils.h"
+#include "esp_private/periph_ctrl.h"
+#include "esp_private/gpio.h"
+#include "esp_private/esp_gpio_reserve.h"
+#include "esp_psram.h"
+#include "esp_lcd_common.h"
+#include "esp_cache.h"
+#include "esp_memory_utils.h"
+#include "soc/lcd_periph.h"
+#include "soc/io_mux_reg.h"
+#include "hal/lcd_hal.h"
+#include "hal/lcd_ll.h"
+#include "hal/cache_hal.h"
+#include "hal/cache_ll.h"
+#include "rgb_lcd_rotation_sw.h"
+
+// hardware issue workaround
+#if CONFIG_IDF_TARGET_ESP32S3
+#define RGB_LCD_NEEDS_SEPARATE_RESTART_LINK 1
+#endif
+
+#if CONFIG_LCD_RGB_ISR_IRAM_SAFE
+#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
+#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
+
+#define RGB_LCD_PANEL_MAX_FB_NUM 3 // maximum supported frame buffer number
+#define RGB_LCD_PANEL_BOUNCE_BUF_NUM 2 // bounce buffer number
+
+static const char *TAG = "lcd_panel.rgb";
+
+typedef struct esp_rgb_panel_t esp_rgb_panel_t;
+
+static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel);
+static esp_err_t rgb_panel_reset(esp_lcd_panel_t *panel);
+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);
+static esp_err_t rgb_panel_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
+static esp_err_t rgb_panel_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
+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_disp_on_off(esp_lcd_panel_t *panel, bool off);
+static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *rgb_panel, lcd_clock_source_t clk_src);
+static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *rgb_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 *rgb_panel, const esp_lcd_rgb_panel_config_t *panel_config);
+static void lcd_rgb_panel_release_gpio(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);
+
+struct esp_rgb_panel_t {
+ esp_lcd_panel_t base; // Base class of generic lcd panel
+ int panel_id; // LCD panel ID
+ lcd_hal_context_t hal; // Hal layer object
+ size_t data_width; // Number of data lines
+ size_t fb_bits_per_pixel; // Frame buffer color depth, in bpp
+ size_t num_fbs; // Number of frame buffers
+ size_t output_bits_per_pixel; // Color depth seen from the output data line. Default to fb_bits_per_pixel, but can be changed by YUV-RGB conversion
+ size_t dma_burst_size; // DMA transfer burst size
+ 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
+ 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
+#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
+ gdma_link_list_handle_t dma_restart_link; // DMA link list for restarting the DMA
+#endif
+ 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 bb_fb_index; // Current frame buffer index which used by bounce buffer
+ size_t int_mem_align; // DMA buffer alignment for internal memory
+ size_t ext_mem_align; // DMA buffer alignment for external memory
+ 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, set to -1 if it's not used
+ int disp_gpio_num; // GPIO used for display control signal, set to -1 if it's not used
+ int data_gpio_nums[SOC_LCDCAM_RGB_DATA_WIDTH]; // GPIOs used for data lines, we keep these GPIOs for action like "invert_color"
+ uint64_t gpio_reserve_mask; // GPIOs reserved by this panel, used to revoke the GPIO reservation when the panel is deleted
+ 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)
+ int bounce_pos_px; // Position in whatever source material is used for the bounce buffer, in pixels
+ 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
+ esp_lcd_rgb_panel_draw_buf_complete_cb_t on_color_trans_done; // draw buffer completes
+ esp_lcd_rgb_panel_frame_buf_complete_cb_t on_frame_buf_complete; // callback used to notify when the bounce buffer finish copying the entire frame
+ 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
+ int x_gap; // Extra gap in x coordinate, it's used when calculate the flush window
+ 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)
+ int rotate_mask; // panel rotate_mask mask, Or'ed of `panel_rotate_mask_t`
+ struct {
+ 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 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 need_restart: 1; // Whether to restart the LCD controller and the DMA
+ 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;
+};
+
+static esp_err_t lcd_rgb_panel_alloc_frame_buffers(esp_rgb_panel_t *rgb_panel)
+{
+ bool fb_in_psram = rgb_panel->flags.fb_in_psram;
+
+ // 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 int_mem_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
+ uint32_t ext_mem_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA);
+
+ // alloc frame buffer
+ for (int i = 0; i < rgb_panel->num_fbs; i++) {
+ if (fb_in_psram) {
+ // the allocated buffer is also aligned to the cache line size
+ rgb_panel->fbs[i] = heap_caps_aligned_calloc(rgb_panel->ext_mem_align, 1, rgb_panel->fb_size,
+ MALLOC_CAP_SPIRAM | 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 = ext_mem_cache_line_size > 0;
+ } 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;
+ }
+ // flush data from cache to the physical memory
+ if (rgb_panel->flags.fb_behind_cache) {
+ esp_cache_msync(rgb_panel->fbs[i], rgb_panel->fb_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
+ }
+ }
+
+ // alloc bounce buffer
+ if (rgb_panel->bb_size) {
+ for (int i = 0; i < RGB_LCD_PANEL_BOUNCE_BUF_NUM; i++) {
+ // bounce buffer must be allocated from internal memory for performance
+ 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");
+ 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->bb_fb_index = 0;
+
+ return ESP_OK;
+}
+
+static esp_err_t lcd_rgb_panel_destroy(esp_rgb_panel_t *rgb_panel)
+{
+ // ensure the HW state machine is stopped
+ lcd_ll_stop(rgb_panel->hal.dev);
+ LCD_CLOCK_SRC_ATOMIC() {
+ lcd_ll_enable_clock(rgb_panel->hal.dev, false);
+ }
+ if (rgb_panel->panel_id >= 0) {
+ PERIPH_RCC_RELEASE_ATOMIC(lcd_periph_rgb_signals.panels[rgb_panel->panel_id].module, ref_count) {
+ if (ref_count == 0) {
+ lcd_ll_enable_bus_clock(rgb_panel->panel_id, false);
+ }
+ }
+ lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id);
+ }
+ // release the GPIOs
+ lcd_rgb_panel_release_gpio(rgb_panel);
+ if (rgb_panel->dma_chan) {
+ gdma_disconnect(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_LCD_NEEDS_SEPARATE_RESTART_LINK
+ if (rgb_panel->dma_restart_link) {
+ gdma_del_link_list(rgb_panel->dma_restart_link);
+ }
+#endif
+ 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)
+{
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+ esp_log_level_set(TAG, ESP_LOG_DEBUG);
+#endif
+ esp_err_t ret = ESP_OK;
+ esp_rgb_panel_t *rgb_panel = NULL;
+ 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;
+ 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);
+ 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_RETURN_ON_FALSE(!(rgb_panel_config->num_fbs > 0 && rgb_panel_config->num_fbs != 2 && rgb_panel_config->flags.double_fb),
+ ESP_ERR_INVALID_ARG, TAG, "num_fbs conflicts with double_fb");
+ ESP_RETURN_ON_FALSE(!(rgb_panel_config->num_fbs > 0 && rgb_panel_config->flags.no_fb),
+ ESP_ERR_INVALID_ARG, TAG, "num_fbs conflicts with no_fb");
+ ESP_RETURN_ON_FALSE(!(rgb_panel_config->flags.no_fb && rgb_panel_config->bounce_buffer_size_px == 0),
+ ESP_ERR_INVALID_ARG, TAG, "must set bounce buffer if there's no frame buffer");
+
+ // determine number of framebuffers
+ size_t num_fbs = 1;
+ if (rgb_panel_config->flags.no_fb) {
+ num_fbs = 0;
+ } else if (rgb_panel_config->flags.double_fb) {
+ num_fbs = 2;
+ } else if (rgb_panel_config->num_fbs > 0) {
+ num_fbs = rgb_panel_config->num_fbs;
+ }
+ ESP_RETURN_ON_FALSE(num_fbs <= RGB_LCD_PANEL_MAX_FB_NUM, ESP_ERR_INVALID_ARG, TAG, "too many frame buffers");
+
+ // bpp defaults to the number of data lines, but for serial RGB interface, they're not equal
+ // e.g. for serial RGB 8-bit interface, data lines are 8, whereas the bpp is 24 (RGB888)
+ size_t fb_bits_per_pixel = data_width;
+ if (rgb_panel_config->bits_per_pixel) { // override bpp if it's set
+ fb_bits_per_pixel = rgb_panel_config->bits_per_pixel;
+ }
+ // calculate buffer size
+ size_t fb_size = rgb_panel_config->timings.h_res * rgb_panel_config->timings.v_res * fb_bits_per_pixel / 8;
+ size_t bb_size = rgb_panel_config->bounce_buffer_size_px * fb_bits_per_pixel / 8;
+ size_t expect_bb_eof_count = 0;
+ if (bb_size) {
+ ESP_RETURN_ON_FALSE(fb_size % bb_size == 0, ESP_ERR_INVALID_ARG, TAG, "frame buffer size must be multiple of bounce buffer size");
+ expect_bb_eof_count = fb_size / bb_size;
+ }
+
+ // calculate the number of DMA descriptors
+ size_t num_dma_nodes = 0;
+ // allocate memory for rgb panel
+ rgb_panel = heap_caps_calloc(1, sizeof(esp_rgb_panel_t), LCD_RGB_MEM_ALLOC_CAPS);
+ 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_fbs = num_fbs;
+ rgb_panel->fb_size = fb_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;
+ // 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;
+
+ // enable APB to access LCD registers
+ PERIPH_RCC_ACQUIRE_ATOMIC(lcd_periph_rgb_signals.panels[panel_id].module, ref_count) {
+ if (ref_count == 0) {
+ lcd_ll_enable_bus_clock(panel_id, true);
+ lcd_ll_reset_register(panel_id);
+ }
+ }
+
+ // initialize HAL layer, so we can call LL APIs later
+ lcd_hal_init(&rgb_panel->hal, panel_id);
+ // enable clock
+ LCD_CLOCK_SRC_ATOMIC() {
+ lcd_ll_enable_clock(rgb_panel->hal.dev, true);
+ }
+ // set clock source
+ ret = lcd_rgb_panel_select_clock_src(rgb_panel, rgb_panel_config->clk_src);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "set source clock failed");
+ // reset peripheral and FIFO after we select a correct clock source
+ lcd_ll_fifo_reset(rgb_panel->hal.dev);
+ lcd_ll_reset(rgb_panel->hal.dev);
+ // install interrupt service, (LCD peripheral shares the interrupt source with Camera by different mask)
+ int isr_flags = LCD_RGB_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_LOWMED;
+ ret = esp_intr_alloc_intrstatus(lcd_periph_rgb_signals.panels[panel_id].irq_id, isr_flags,
+ (uint32_t)lcd_ll_get_interrupt_status_reg(rgb_panel->hal.dev),
+ LCD_LL_EVENT_RGB, rgb_lcd_default_isr_handler, rgb_panel, &rgb_panel->intr);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "install interrupt failed");
+ PERIPH_RCC_ATOMIC() {
+ lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_RGB, false); // disable all interrupts
+ }
+ lcd_ll_clear_interrupt_status(rgb_panel->hal.dev, UINT32_MAX); // clear pending interrupt
+
+ // install DMA service
+ rgb_panel->flags.stream_mode = !rgb_panel_config->flags.refresh_on_demand;
+ 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");
+ // allocate frame buffers + bounce buffers
+ ESP_GOTO_ON_ERROR(lcd_rgb_panel_alloc_frame_buffers(rgb_panel), err, TAG, "alloc frame buffers failed");
+ // initialize DMA descriptor link
+ ESP_GOTO_ON_ERROR(lcd_rgb_panel_init_trans_link(rgb_panel), err, TAG, "init DMA link failed");
+
+ // configure GPIO
+ ret = lcd_rgb_panel_configure_gpio(rgb_panel, rgb_panel_config);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "configure GPIO failed");
+ // fill other rgb panel runtime parameters
+ memcpy(rgb_panel->data_gpio_nums, rgb_panel_config->data_gpio_nums, sizeof(rgb_panel->data_gpio_nums));
+ rgb_panel->disp_gpio_num = rgb_panel_config->disp_gpio_num;
+ rgb_panel->de_gpio_num = rgb_panel_config->de_gpio_num;
+ rgb_panel->hsync_gpio_num = rgb_panel_config->hsync_gpio_num;
+ rgb_panel->vsync_gpio_num = rgb_panel_config->vsync_gpio_num;
+ rgb_panel->pclk_gpio_num = rgb_panel_config->pclk_gpio_num;
+ rgb_panel->timings = rgb_panel_config->timings;
+ rgb_panel->data_width = rgb_panel_config->data_width;
+ rgb_panel->output_bits_per_pixel = fb_bits_per_pixel; // by default, the output bpp is the same as the frame buffer bpp
+ rgb_panel->flags.disp_en_level = !rgb_panel_config->flags.disp_active_low;
+ rgb_panel->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
+ // fill function table
+ rgb_panel->base.del = rgb_panel_del;
+ rgb_panel->base.reset = rgb_panel_reset;
+ rgb_panel->base.init = rgb_panel_init;
+ rgb_panel->base.draw_bitmap = rgb_panel_draw_bitmap;
+ rgb_panel->base.disp_on_off = rgb_panel_disp_on_off;
+ rgb_panel->base.invert_color = rgb_panel_invert_color;
+ rgb_panel->base.mirror = rgb_panel_mirror;
+ rgb_panel->base.swap_xy = rgb_panel_swap_xy;
+ 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, num_fbs=%zu, fb_size=%zu, bb0 @%p, bb1 @%p, bb_size=%zu",
+ rgb_panel->panel_id, rgb_panel, rgb_panel->num_fbs, rgb_panel->fb_size,
+ rgb_panel->bounce_buffer[0], rgb_panel->bounce_buffer[1], rgb_panel->bb_size);
+ for (size_t i = 0; i < rgb_panel->num_fbs; i++) {
+ ESP_LOGD(TAG, "fb[%zu] @%p", i, rgb_panel->fbs[i]);
+ }
+ return ESP_OK;
+
+err:
+ if (rgb_panel) {
+ lcd_rgb_panel_destroy(rgb_panel);
+ }
+ return ret;
+}
+
+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");
+ esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
+#if CONFIG_LCD_RGB_ISR_IRAM_SAFE
+ if (callbacks->on_vsync) {
+ ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_vsync), ESP_ERR_INVALID_ARG, TAG, "on_vsync callback not in IRAM");
+ }
+ if (callbacks->on_color_trans_done) {
+ ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_color_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_color_trans_done callback not in IRAM");
+ }
+ if (callbacks->on_frame_buf_complete) {
+ ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_frame_buf_complete), ESP_ERR_INVALID_ARG, TAG, "on_frame_buf_complete callback not in IRAM");
+ }
+ if (callbacks->on_bounce_empty) {
+ ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_bounce_empty), ESP_ERR_INVALID_ARG, TAG, "on_bounce_empty callback not in IRAM");
+ }
+ if (user_ctx) {
+ ESP_RETURN_ON_FALSE(esp_ptr_internal(user_ctx), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
+ }
+#endif // CONFIG_LCD_RGB_ISR_IRAM_SAFE
+ rgb_panel->on_vsync = callbacks->on_vsync;
+ rgb_panel->on_color_trans_done = callbacks->on_color_trans_done;
+ rgb_panel->on_frame_buf_complete = callbacks->on_frame_buf_complete;
+ rgb_panel->on_bounce_empty = callbacks->on_bounce_empty;
+ rgb_panel->user_ctx = user_ctx;
+ 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 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;
+ portEXIT_CRITICAL(&rgb_panel->spinlock);
+ return ESP_OK;
+}
+
+esp_err_t esp_lcd_rgb_panel_restart(esp_lcd_panel_handle_t panel)
+{
+ 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);
+ ESP_RETURN_ON_FALSE(rgb_panel->flags.stream_mode, ESP_ERR_INVALID_STATE, TAG, "not in stream mode");
+
+ // the underlying restart job will be done in the `LCD_LL_EVENT_VSYNC_END` event handler
+ portENTER_CRITICAL(&rgb_panel->spinlock);
+ rgb_panel->flags.need_restart = true;
+ portEXIT_CRITICAL(&rgb_panel->spinlock);
+ return ESP_OK;
+}
+
+esp_err_t esp_lcd_rgb_panel_get_frame_buffer(esp_lcd_panel_handle_t panel, uint32_t fb_num, void **fb0, ...)
+{
+ 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);
+ ESP_RETURN_ON_FALSE(fb_num && fb_num <= rgb_panel->num_fbs, ESP_ERR_INVALID_ARG, TAG, "invalid frame buffer number");
+ void **fb_itor = fb0;
+ va_list args;
+ va_start(args, fb0);
+ for (int i = 0; i < fb_num; i++) {
+ if (fb_itor) {
+ *fb_itor = rgb_panel->fbs[i];
+ fb_itor = va_arg(args, void **);
+ }
+ }
+ va_end(args);
+ return ESP_OK;
+}
+
+esp_err_t esp_lcd_rgb_panel_refresh(esp_lcd_panel_handle_t panel)
+{
+ 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);
+ ESP_RETURN_ON_FALSE(!rgb_panel->flags.stream_mode, ESP_ERR_INVALID_STATE, TAG, "refresh on demand is not enabled");
+ lcd_rgb_panel_start_transmission(rgb_panel);
+ return ESP_OK;
+}
+
+esp_err_t esp_lcd_rgb_panel_set_yuv_conversion(esp_lcd_panel_handle_t panel, const esp_lcd_yuv_conv_config_t *config)
+{
+ 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);
+ lcd_hal_context_t *hal = &rgb_panel->hal;
+ bool en_conversion = config != NULL;
+
+ // bits per pixel for different YUV sample
+ const uint8_t bpp_yuv[] = {
+ [LCD_YUV_SAMPLE_422] = 16,
+ [LCD_YUV_SAMPLE_420] = 12,
+ [LCD_YUV_SAMPLE_411] = 12,
+ };
+
+ if (en_conversion) {
+ if (memcmp(&config->src, &config->dst, sizeof(config->src)) == 0) {
+ ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "conversion source and destination are the same");
+ }
+
+ if (config->src.color_space == LCD_COLOR_SPACE_YUV && config->dst.color_space == LCD_COLOR_SPACE_RGB) { // YUV->RGB
+ lcd_ll_set_convert_mode_yuv_to_rgb(hal->dev, config->src.yuv_sample);
+ rgb_panel->output_bits_per_pixel = rgb_panel->fb_bits_per_pixel;
+ } else if (config->src.color_space == LCD_COLOR_SPACE_RGB && config->dst.color_space == LCD_COLOR_SPACE_YUV) { // RGB->YUV
+ lcd_ll_set_convert_mode_rgb_to_yuv(hal->dev, config->dst.yuv_sample);
+ rgb_panel->output_bits_per_pixel = bpp_yuv[config->dst.yuv_sample];
+ } else if (config->src.color_space == LCD_COLOR_SPACE_YUV && config->dst.color_space == LCD_COLOR_SPACE_YUV) { // YUV->YUV
+ lcd_ll_set_convert_mode_yuv_to_yuv(hal->dev, config->src.yuv_sample, config->dst.yuv_sample);
+ rgb_panel->output_bits_per_pixel = bpp_yuv[config->dst.yuv_sample];
+ } else {
+ ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "unsupported conversion mode");
+ }
+
+ // set conversion standard
+ lcd_ll_set_yuv_convert_std(hal->dev, config->std);
+ // set conversion data width
+ lcd_ll_set_convert_data_width(hal->dev, rgb_panel->data_width);
+ // set color range
+ lcd_ll_set_input_color_range(hal->dev, config->src.color_range);
+ lcd_ll_set_output_color_range(hal->dev, config->dst.color_range);
+ } else {
+ // output bpp equals to frame buffer bpp
+ rgb_panel->output_bits_per_pixel = rgb_panel->fb_bits_per_pixel;
+ }
+
+ // enable or disable RGB-YUV conversion
+ lcd_ll_enable_rgb_yuv_convert(hal->dev, en_conversion);
+
+ return ESP_OK;
+}
+
+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;
+ ESP_RETURN_ON_ERROR(lcd_rgb_panel_destroy(rgb_panel), TAG, "destroy rgb panel(%d) failed", panel_id);
+ ESP_LOGD(TAG, "del rgb panel(%d)", panel_id);
+ return ESP_OK;
+}
+
+static esp_err_t rgb_panel_reset(esp_lcd_panel_t *panel)
+{
+ esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
+ lcd_ll_fifo_reset(rgb_panel->hal.dev);
+ lcd_ll_reset(rgb_panel->hal.dev);
+ return ESP_OK;
+}
+
+static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
+{
+ esp_err_t ret = ESP_OK;
+ esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
+ // set pixel clock frequency
+ hal_utils_clk_div_t lcd_clk_div = {};
+ rgb_panel->timings.pclk_hz = lcd_hal_cal_pclk_freq(&rgb_panel->hal, rgb_panel->src_clk_hz, rgb_panel->timings.pclk_hz, &lcd_clk_div);
+ LCD_CLOCK_SRC_ATOMIC() {
+ lcd_ll_set_group_clock_coeff(rgb_panel->hal.dev, lcd_clk_div.integer, lcd_clk_div.denominator, lcd_clk_div.numerator);
+ }
+ // pixel clock phase and polarity
+ lcd_ll_set_clock_idle_level(rgb_panel->hal.dev, rgb_panel->timings.flags.pclk_idle_high);
+ lcd_ll_set_pixel_clock_edge(rgb_panel->hal.dev, rgb_panel->timings.flags.pclk_active_neg);
+ // enable RGB mode and set data width
+ lcd_ll_enable_rgb_mode(rgb_panel->hal.dev, true);
+ lcd_ll_set_dma_read_stride(rgb_panel->hal.dev, rgb_panel->data_width);
+ // enable data phase only
+ lcd_ll_set_phase_cycles(rgb_panel->hal.dev, 0, 0, 1);
+ // number of data cycles is controlled by DMA buffer size
+ lcd_ll_enable_output_always_on(rgb_panel->hal.dev, true);
+ // configure HSYNC, VSYNC, DE signal idle state level
+ lcd_ll_set_idle_level(rgb_panel->hal.dev, !rgb_panel->timings.flags.hsync_idle_low,
+ !rgb_panel->timings.flags.vsync_idle_low, rgb_panel->timings.flags.de_idle_high);
+ // 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->output_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 beginning of line
+ lcd_ll_set_hsync_position(rgb_panel->hal.dev, 0);
+ // in stream mode, after finish one frame, the LCD controller will ask for data automatically from the DMA
+ // DMA should prepare the next frame data within porch region
+ lcd_ll_enable_auto_next_frame(rgb_panel->hal.dev, rgb_panel->flags.stream_mode);
+ PERIPH_RCC_ATOMIC() {
+ // trigger interrupt on the end of frame
+ lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_RGB, true);
+ }
+ // enable intr
+ esp_intr_enable(rgb_panel->intr);
+ // start transmission
+ if (rgb_panel->flags.stream_mode) {
+ lcd_rgb_panel_start_transmission(rgb_panel);
+ }
+ ESP_LOGD(TAG, "rgb panel(%d) start, pclk=%"PRIu32"Hz", rgb_panel->panel_id, rgb_panel->timings.pclk_hz);
+ return ret;
+}
+
+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);
+ ESP_RETURN_ON_FALSE(rgb_panel->num_fbs > 0, ESP_ERR_NOT_SUPPORTED, TAG, "no frame buffer installed");
+ esp_lcd_rgb_panel_draw_buf_complete_cb_t cb = rgb_panel->on_color_trans_done;
+
+ uint8_t *draw_buffer = (uint8_t *)color_data;
+ size_t fb_size = rgb_panel->fb_size;
+ int h_res = rgb_panel->timings.h_res;
+ int v_res = rgb_panel->timings.v_res;
+ int bytes_per_pixel = rgb_panel->fb_bits_per_pixel / 8;
+ uint32_t bytes_per_line = bytes_per_pixel * h_res;
+
+ // adjust the flush window by adding extra gap
+ x_start += rgb_panel->x_gap;
+ y_start += rgb_panel->y_gap;
+ x_end += rgb_panel->x_gap;
+ y_end += rgb_panel->y_gap;
+
+ // clip to boundaries
+ if (rgb_panel->rotate_mask & ROTATE_MASK_SWAP_XY) {
+ x_start = MAX(x_start, 0);
+ x_end = MIN(x_end, v_res);
+ y_start = MAX(y_start, 0);
+ y_end = MIN(y_end, h_res);
+ } else {
+ x_start = MAX(x_start, 0);
+ x_end = MIN(x_end, h_res);
+ y_start = MAX(y_start, 0);
+ y_end = MIN(y_end, v_res);
+ }
+
+ // check if we want to copy the draw buffer to the internal frame buffer
+ bool draw_buf_copy_to_fb = true;
+ uint8_t draw_buf_fb_index = 0;
+ for (int i = 0; i < rgb_panel->num_fbs; i++) {
+ if (draw_buffer >= rgb_panel->fbs[i] && draw_buffer < rgb_panel->fbs[i] + fb_size) {
+ draw_buf_fb_index = i;
+ draw_buf_copy_to_fb = false;
+ break;
+ }
+ }
+
+ if (draw_buf_copy_to_fb) {
+ // sync the draw buffer with the frame buffer by CPU copy
+ ESP_LOGV(TAG, "copy draw buffer to frame buffer by CPU");
+ uint8_t *fb = rgb_panel->fbs[rgb_panel->cur_fb_index];
+ size_t bytes_to_flush = v_res * bytes_per_line;
+ uint8_t *flush_ptr = fb;
+
+ const uint8_t *from = (const uint8_t *)color_data;
+ uint32_t copy_bytes_per_line = (x_end - x_start) * bytes_per_pixel;
+ size_t offset = y_start * copy_bytes_per_line + x_start * bytes_per_pixel;
+ uint8_t *to = fb;
+ if (1 == bytes_per_pixel) {
+ COPY_PIXEL_CODE_BLOCK(8)
+ } else if (2 == bytes_per_pixel) {
+ COPY_PIXEL_CODE_BLOCK(16)
+ } else if (3 == bytes_per_pixel) {
+ COPY_PIXEL_CODE_BLOCK(24)
+ }
+ // do memory sync only when the frame buffer is mounted to the DMA link list and behind the cache
+ if (!rgb_panel->bb_size && rgb_panel->flags.fb_behind_cache) {
+ esp_cache_msync(flush_ptr, bytes_to_flush, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
+ }
+ // after the draw buffer finished copying, notify the user to recycle the draw buffer
+ if (cb) {
+ cb(&rgb_panel->base, NULL, rgb_panel->user_ctx);
+ }
+ } else {
+ ESP_LOGV(TAG, "draw buffer is part of the frame buffer");
+ // the new frame buffer index is changed
+ rgb_panel->cur_fb_index = draw_buf_fb_index;
+ // when this function is called, the frame buffer already reflects the draw buffer changes
+ // if the frame buffer is also mounted to the DMA, we need to do the sync between them
+ if (!rgb_panel->bb_size && rgb_panel->flags.fb_behind_cache) {
+ uint8_t *cache_sync_start = rgb_panel->fbs[draw_buf_fb_index] + (y_start * h_res) * bytes_per_pixel;
+ size_t cache_sync_size = (y_end - y_start) * bytes_per_line;
+ esp_cache_msync(cache_sync_start, cache_sync_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
+ }
+ // after the draw buffer finished copying, notify the user to recycle the draw buffer
+ if (cb) {
+ cb(&rgb_panel->base, NULL, rgb_panel->user_ctx);
+ }
+ }
+
+ if (!rgb_panel->bb_size) {
+ if (rgb_panel->flags.stream_mode) {
+ for (int i = 0; i < rgb_panel->num_fbs; i++) {
+ // Note, because of DMA prefetch, there's possibility that the old frame buffer might be sent out again
+ // 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;
+}
+
+static esp_err_t rgb_panel_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
+{
+ esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
+ int panel_id = rgb_panel->panel_id;
+ // inverting the data line by GPIO matrix
+ for (int i = 0; i < rgb_panel->data_width; i++) {
+ if (rgb_panel->data_gpio_nums[i] >= 0) {
+ esp_rom_gpio_connect_out_signal(rgb_panel->data_gpio_nums[i], lcd_periph_rgb_signals.panels[panel_id].data_sigs[i],
+ invert_color_data, false);
+ }
+ }
+ return ESP_OK;
+}
+
+static esp_err_t rgb_panel_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
+{
+ esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
+ rgb_panel->rotate_mask &= ~(ROTATE_MASK_MIRROR_X | ROTATE_MASK_MIRROR_Y);
+ rgb_panel->rotate_mask |= (mirror_x << RGB_PANEL_MIRROR_X | mirror_y << RGB_PANEL_MIRROR_Y);
+ return ESP_OK;
+}
+
+static esp_err_t rgb_panel_swap_xy(esp_lcd_panel_t *panel, bool swap_axes)
+{
+ esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
+ rgb_panel->rotate_mask &= ~(ROTATE_MASK_SWAP_XY);
+ rgb_panel->rotate_mask |= swap_axes << RGB_PANEL_SWAP_XY;
+ return ESP_OK;
+}
+
+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->y_gap = y_gap;
+ return ESP_OK;
+}
+
+static esp_err_t rgb_panel_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
+{
+ esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
+ if (rgb_panel->disp_gpio_num < 0) {
+ return ESP_ERR_NOT_SUPPORTED;
+ }
+ if (!on_off) { // turn off screen
+ gpio_set_level(rgb_panel->disp_gpio_num, !rgb_panel->flags.disp_en_level);
+ } else { // turn on screen
+ gpio_set_level(rgb_panel->disp_gpio_num, rgb_panel->flags.disp_en_level);
+ }
+ return ESP_OK;
+}
+
+static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *rgb_panel, const esp_lcd_rgb_panel_config_t *panel_config)
+{
+ uint64_t gpio_reserve_mask = 0;
+ int panel_id = rgb_panel->panel_id;
+ // Set the number of output data lines
+ lcd_ll_set_data_wire_width(rgb_panel->hal.dev, panel_config->data_width);
+ // connect peripheral signals via GPIO matrix
+ for (size_t i = 0; i < panel_config->data_width; i++) {
+ if (panel_config->data_gpio_nums[i] >= 0) {
+ gpio_func_sel(panel_config->data_gpio_nums[i], PIN_FUNC_GPIO);
+ esp_rom_gpio_connect_out_signal(panel_config->data_gpio_nums[i],
+ lcd_periph_rgb_signals.panels[panel_id].data_sigs[i], false, false);
+ gpio_reserve_mask |= (1ULL << panel_config->data_gpio_nums[i]);
+ }
+ }
+ if (panel_config->hsync_gpio_num >= 0) {
+ gpio_func_sel(panel_config->hsync_gpio_num, PIN_FUNC_GPIO);
+ esp_rom_gpio_connect_out_signal(panel_config->hsync_gpio_num,
+ lcd_periph_rgb_signals.panels[panel_id].hsync_sig, false, false);
+ gpio_reserve_mask |= (1ULL << panel_config->hsync_gpio_num);
+ }
+ if (panel_config->vsync_gpio_num >= 0) {
+ gpio_func_sel(panel_config->vsync_gpio_num, PIN_FUNC_GPIO);
+ esp_rom_gpio_connect_out_signal(panel_config->vsync_gpio_num,
+ lcd_periph_rgb_signals.panels[panel_id].vsync_sig, false, false);
+ gpio_reserve_mask |= (1ULL << panel_config->vsync_gpio_num);
+ }
+ // PCLK may not be necessary in some cases (i.e. VGA output)
+ if (panel_config->pclk_gpio_num >= 0) {
+ gpio_func_sel(panel_config->pclk_gpio_num, PIN_FUNC_GPIO);
+ esp_rom_gpio_connect_out_signal(panel_config->pclk_gpio_num,
+ lcd_periph_rgb_signals.panels[panel_id].pclk_sig, false, false);
+ gpio_reserve_mask |= (1ULL << panel_config->pclk_gpio_num);
+ }
+ // DE signal might not be necessary for some RGB LCD
+ if (panel_config->de_gpio_num >= 0) {
+ gpio_func_sel(panel_config->de_gpio_num, PIN_FUNC_GPIO);
+ esp_rom_gpio_connect_out_signal(panel_config->de_gpio_num,
+ lcd_periph_rgb_signals.panels[panel_id].de_sig, false, false);
+ gpio_reserve_mask |= (1ULL << panel_config->de_gpio_num);
+ }
+ // disp enable GPIO is optional, it is a general purpose output GPIO
+ if (panel_config->disp_gpio_num >= 0) {
+ gpio_func_sel(panel_config->disp_gpio_num, PIN_FUNC_GPIO);
+ esp_rom_gpio_connect_out_signal(panel_config->disp_gpio_num,
+ lcd_periph_rgb_signals.panels[panel_id].disp_sig, false, false);
+ gpio_reserve_mask |= (1ULL << panel_config->disp_gpio_num);
+ }
+
+ // reserve the GPIOs
+ uint64_t busy_mask = esp_gpio_reserve(gpio_reserve_mask);
+ uint64_t conflict_mask = busy_mask & gpio_reserve_mask;
+ for (; conflict_mask > 0;) {
+ uint8_t pos = __builtin_ctzll(conflict_mask);
+ conflict_mask &= ~(1ULL << pos);
+ ESP_LOGW(TAG, "GPIO %d is not usable, maybe is reserved by others", pos);
+ }
+
+ rgb_panel->gpio_reserve_mask = gpio_reserve_mask;
+ return ESP_OK;
+}
+
+static void lcd_rgb_panel_release_gpio(esp_rgb_panel_t *rgb_panel)
+{
+ if (rgb_panel->gpio_reserve_mask) {
+ // disconnect the GPIOs from the LCD signals
+ for (size_t i = 0; i < rgb_panel->data_width; i++) {
+ if (rgb_panel->data_gpio_nums[i] >= 0) {
+ gpio_output_disable(rgb_panel->data_gpio_nums[i]);
+ }
+ }
+ if (rgb_panel->hsync_gpio_num >= 0) {
+ gpio_output_disable(rgb_panel->hsync_gpio_num);
+ }
+ if (rgb_panel->vsync_gpio_num >= 0) {
+ gpio_output_disable(rgb_panel->vsync_gpio_num);
+ }
+ // PCLK may not be necessary in some cases (i.e. VGA output)
+ if (rgb_panel->pclk_gpio_num >= 0) {
+ gpio_output_disable(rgb_panel->pclk_gpio_num);
+ }
+ // DE signal might not be necessary for some RGB LCD
+ if (rgb_panel->de_gpio_num >= 0) {
+ gpio_output_disable(rgb_panel->de_gpio_num);
+ }
+ // disp enable GPIO is optional, it is a general purpose output GPIO
+ if (rgb_panel->disp_gpio_num >= 0) {
+ gpio_output_disable(rgb_panel->disp_gpio_num);
+ }
+ // release the IOs so they can be used by others again
+ esp_gpio_revoke(rgb_panel->gpio_reserve_mask);
+ }
+}
+
+static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *rgb_panel, lcd_clock_source_t clk_src)
+{
+ // get clock source frequency
+ uint32_t src_clk_hz = 0;
+ ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_hz),
+ TAG, "get clock source frequency failed");
+ rgb_panel->src_clk_hz = src_clk_hz;
+ ESP_RETURN_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)clk_src, true), TAG, "clock source enable failed");
+ LCD_CLOCK_SRC_ATOMIC() {
+ lcd_ll_select_clk_src(rgb_panel->hal.dev, clk_src);
+ }
+
+ // create pm lock based on different clock source
+#if CONFIG_PM_ENABLE
+ // clock sources like PLL and XTAL will be turned off in light sleep, so basically a NO_LIGHT_SLEEP lock is sufficient
+ esp_pm_lock_type_t lock_type = ESP_PM_NO_LIGHT_SLEEP;
+#if CONFIG_IDF_TARGET_ESP32P4
+ // use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS
+ lock_type = ESP_PM_CPU_FREQ_MAX;
+#endif
+ ESP_RETURN_ON_ERROR(esp_pm_lock_create(lock_type, 0, "rgb_panel", &rgb_panel->pm_lock), TAG, "create pm lock failed");
+ // hold the lock during the whole lifecycle of RGB panel
+ esp_pm_lock_acquire(rgb_panel->pm_lock);
+ ESP_LOGD(TAG, "installed pm lock and hold the lock during the whole panel lifecycle");
+#endif
+
+ return ESP_OK;
+}
+
+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->fb_bits_per_pixel / 8;
+ if (unlikely(panel->num_fbs == 0)) {
+ // driver doesn't maintain a frame buffer, so ask the user to fill the bounce buffer
+ if (panel->on_bounce_empty) {
+ if (panel->on_bounce_empty(&panel->base, buffer, panel->bounce_pos_px, panel->bb_size, panel->user_ctx)) {
+ need_yield = true;
+ }
+ }
+ } else {
+ // copy partial frame buffer to the bounce buffer
+ // Note: if the frame buffer is behind a cache, and the cache is disabled, crash would happen here when auto write back happens
+ memcpy(buffer, &panel->fbs[panel->bb_fb_index][panel->bounce_pos_px * bytes_per_pixel], panel->bb_size);
+ }
+ // do memory sync if the bounce buffer is behind the cache
+ if (panel->flags.bb_behind_cache) {
+ esp_cache_msync(buffer, panel->bb_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
+ }
+
+ 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->bb_fb_index = panel->cur_fb_index;
+ esp_lcd_rgb_panel_frame_buf_complete_cb_t cb = panel->on_frame_buf_complete;
+ if (cb) {
+ if (cb(&panel->base, NULL, panel->user_ctx)) {
+ need_yield = true;
+ }
+ }
+ }
+
+ // Preload the next bit of buffer to the cache memory, this can improve the performance
+ if (panel->num_fbs > 0 && panel->flags.fb_behind_cache) {
+#if CONFIG_IDF_TARGET_ESP32S3
+ Cache_Start_DCache_Preload((uint32_t)&panel->fbs[panel->bb_fb_index][panel->bounce_pos_px * bytes_per_pixel],
+ panel->bb_size, 0);
+#elif CONFIG_IDF_TARGET_ESP32P4
+ Cache_Start_L2_Cache_Preload((uint32_t)&panel->fbs[panel->bb_fb_index][panel->bounce_pos_px * bytes_per_pixel],
+ panel->bb_size, 0);
+#else
+#error "Unsupported target"
+#endif
+ }
+ return need_yield;
+}
+
+static IRAM_ATTR bool lcd_rgb_panel_eof_handler(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
+{
+ bool need_yield = false;
+ esp_rgb_panel_t *rgb_panel = (esp_rgb_panel_t *)user_data;
+
+ if (rgb_panel->bb_size) {
+ // in bounce buffer mode, the DMA EOF means time to fill the finished bounce buffer
+ // Figure out which bounce buffer to write to
+ portENTER_CRITICAL_ISR(&rgb_panel->spinlock);
+ int bb = rgb_panel->bb_eof_count % RGB_LCD_PANEL_BOUNCE_BUF_NUM;
+ rgb_panel->bb_eof_count++;
+ portEXIT_CRITICAL_ISR(&rgb_panel->spinlock);
+ need_yield = lcd_rgb_panel_fill_bounce_buffer(rgb_panel, rgb_panel->bounce_buffer[bb]);
+ } else {
+ // if not bounce buffer, the DMA EOF event means the end of a frame has been sent out to the LCD controller
+ if (rgb_panel->on_frame_buf_complete) {
+ if (rgb_panel->on_frame_buf_complete(&rgb_panel->base, NULL, rgb_panel->user_ctx)) {
+ need_yield = true;
+ }
+ }
+ }
+ return need_yield;
+}
+
+static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *rgb_panel)
+{
+ // alloc DMA channel and connect to LCD peripheral
+ gdma_channel_alloc_config_t dma_chan_config = {
+ .direction = GDMA_CHANNEL_DIRECTION_TX,
+#if CONFIG_LCD_RGB_ISR_IRAM_SAFE
+ .flags.isr_cache_safe = true,
+#endif
+ };
+ ESP_RETURN_ON_ERROR(LCD_GDMA_NEW_CHANNEL(&dma_chan_config, &rgb_panel->dma_chan), TAG, "alloc DMA channel failed");
+ gdma_connect(rgb_panel->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0));
+
+ // configure DMA strategy
+ gdma_strategy_config_t dma_strategy = {
+ .eof_till_data_popped = false,
+ };
+ gdma_apply_strategy(rgb_panel->dma_chan, &dma_strategy);
+
+ // configure DMA transfer
+ gdma_transfer_config_t trans_cfg = {
+ .max_data_burst_size = rgb_panel->dma_burst_size,
+ .access_ext_mem = rgb_panel->flags.fb_in_psram,
+ };
+ 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);
+
+ // register DMA EOF callback
+ gdma_tx_event_callbacks_t cbs = {
+ .on_trans_eof = lcd_rgb_panel_eof_handler,
+ };
+ ESP_RETURN_ON_ERROR(gdma_register_tx_event_callbacks(rgb_panel->dma_chan, &cbs, rgb_panel), TAG, "register DMA EOF callback failed");
+
+ return ESP_OK;
+}
+
+#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
+// If we restart GDMA, 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 link (called restart link) to restart the transaction
+#define LCD_FIFO_PRESERVE_SIZE_PX (LCD_LL_FIFO_DEPTH + 1)
+#endif
+
+static esp_err_t lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *rgb_panel)
+{
+#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
+ // the restart link shares the same buffer with the frame/bounce buffer but start from a different offset
+ int restart_skip_bytes = LCD_FIFO_PRESERVE_SIZE_PX * (rgb_panel->fb_bits_per_pixel / 8);
+#endif
+ if (rgb_panel->bb_size) {
+ // DMA is used to convey the bounce buffer
+ size_t buffer_alignment = rgb_panel->int_mem_align;
+ size_t num_dma_nodes_per_bounce_buffer = esp_dma_calculate_node_count(rgb_panel->bb_size, buffer_alignment, LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE);
+ gdma_link_list_config_t link_cfg = {
+ .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].buffer_alignment = buffer_alignment;
+ 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");
+#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
+ // create restart link
+ gdma_link_list_config_t restart_link_cfg = {
+ .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,
+ .buffer_alignment = buffer_alignment,
+ .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");
+
+ // Magic here: we use the restart link to restart the bounce buffer link list, so concat them
+ gdma_link_concat(rgb_panel->dma_restart_link, 0, rgb_panel->dma_bb_link, 1);
+#endif
+ } else {
+ // DMA is used to convey the frame buffer
+ size_t buffer_alignment = rgb_panel->flags.fb_in_psram ? rgb_panel->ext_mem_align : rgb_panel->int_mem_align;
+ uint32_t num_dma_nodes = esp_dma_calculate_node_count(rgb_panel->fb_size, buffer_alignment, LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE);
+ gdma_link_list_config_t link_cfg = {
+ .item_alignment = LCD_GDMA_DESCRIPTOR_ALIGN,
+ .num_items = num_dma_nodes,
+ .flags = {
+ .check_owner = true,
+ },
+ };
+ gdma_buffer_mount_config_t mount_cfg = {
+ .length = rgb_panel->fb_size,
+ .flags = {
+ .mark_final = rgb_panel->flags.stream_mode ? GDMA_FINAL_LINK_TO_DEFAULT : GDMA_FINAL_LINK_TO_NULL,
+ .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];
+ mount_cfg.buffer_alignment = buffer_alignment;
+ 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");
+ }
+#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
+ // create restart link
+ gdma_link_list_config_t restart_link_cfg = {
+ .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,
+ .buffer_alignment = buffer_alignment,
+ .length = MIN(LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE, rgb_panel->fb_size) - restart_skip_bytes,
+ .flags.bypass_buffer_align_check = true, // the restart buffer may doesn't match the buffer alignment but it doesn't really matter in this case
+ };
+ 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);
+#endif
+ }
+
+ return ESP_OK;
+}
+
+// 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.
+static IRAM_ATTR void lcd_rgb_panel_try_restart_transmission(esp_rgb_panel_t *panel)
+{
+ int bb_size_px = panel->bb_size / (panel->fb_bits_per_pixel / 8);
+ bool do_restart = false;
+#if CONFIG_LCD_RGB_RESTART_IN_VSYNC
+ do_restart = true;
+#else
+ portENTER_CRITICAL_ISR(&panel->spinlock);
+ if (panel->flags.need_restart) {
+ panel->flags.need_restart = false;
+ do_restart = true;
+ }
+ if (panel->bb_eof_count < panel->expect_eof_count) {
+ do_restart = true;
+ }
+ panel->bb_eof_count = 0;
+ portEXIT_CRITICAL_ISR(&panel->spinlock);
+#endif // CONFIG_LCD_RGB_RESTART_IN_VSYNC
+
+ if (!do_restart) {
+ return;
+ }
+
+ if (panel->bb_size) {
+ // Catch de-synced frame buffer and reset if needed.
+ if (panel->bounce_pos_px > bb_size_px * 2) {
+ panel->bounce_pos_px = 0;
+ }
+ // Pre-fill bounce buffer 0, if the EOF ISR didn't do that already
+ if (panel->bounce_pos_px < bb_size_px) {
+ lcd_rgb_panel_fill_bounce_buffer(panel, panel->bounce_buffer[0]);
+ }
+ }
+
+ lcd_ll_fifo_reset(panel->hal.dev);
+ gdma_reset(panel->dma_chan);
+#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
+ // restart the DMA by a special DMA node
+ gdma_start(panel->dma_chan, gdma_link_get_head_addr(panel->dma_restart_link));
+#else
+ if (panel->bb_size) {
+ gdma_start(panel->dma_chan, gdma_link_get_head_addr(panel->dma_bb_link));
+ } else {
+ gdma_start(panel->dma_chan, gdma_link_get_head_addr(panel->dma_fb_links[panel->cur_fb_index]));
+ }
+#endif
+
+ if (panel->bb_size) {
+ // Fill 2nd bounce buffer while 1st is being sent out, if needed.
+ if (panel->bounce_pos_px < bb_size_px * 2) {
+ lcd_rgb_panel_fill_bounce_buffer(panel, panel->bounce_buffer[1]);
+ }
+ }
+
+}
+
+static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel)
+{
+ // reset FIFO of DMA and LCD, in case there remains old frame data
+ gdma_reset(rgb_panel->dma_chan);
+ lcd_ll_stop(rgb_panel->hal.dev);
+ lcd_ll_reset(rgb_panel->hal.dev);
+ lcd_ll_fifo_reset(rgb_panel->hal.dev);
+
+ // 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]);
+ }
+
+ // the start of DMA should be prior to the start of LCD engine
+ 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
+ // in fact, this is only needed when LCD pixel clock is set too high
+ esp_rom_delay_us(1);
+ // start LCD engine
+ lcd_ll_start(rgb_panel->hal.dev);
+}
+
+IRAM_ATTR static void lcd_rgb_panel_try_update_pclk(esp_rgb_panel_t *rgb_panel)
+{
+ hal_utils_clk_div_t lcd_clk_div = {};
+ 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, &lcd_clk_div);
+ LCD_CLOCK_SRC_ATOMIC() {
+ lcd_ll_set_group_clock_coeff(rgb_panel->hal.dev, lcd_clk_div.integer, lcd_clk_div.denominator, lcd_clk_div.numerator);
+ }
+ }
+ portEXIT_CRITICAL_ISR(&rgb_panel->spinlock);
+}
+
+IRAM_ATTR static void rgb_lcd_default_isr_handler(void *args)
+{
+ esp_rgb_panel_t *rgb_panel = (esp_rgb_panel_t *)args;
+ bool need_yield = false;
+
+ // clear the interrupt status
+ uint32_t intr_status = lcd_ll_get_interrupt_status(rgb_panel->hal.dev);
+ lcd_ll_clear_interrupt_status(rgb_panel->hal.dev, intr_status);
+
+#if LCD_LL_EVENT_UNDERRUN
+ if (intr_status & LCD_LL_EVENT_UNDERRUN) {
+ ESP_EARLY_LOGE(TAG, "LCD underrun");
+ }
+#endif
+
+ // VSYNC event happened
+ if (intr_status & LCD_LL_EVENT_VSYNC_END) {
+ // call user registered callback
+ 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) {
+ // check whether to restart the transmission
+ lcd_rgb_panel_try_restart_transmission(rgb_panel);
+ }
+
+ }
+ // yield if needed
+ if (need_yield) {
+ portYIELD_FROM_ISR();
+ }
+}
### components/esp_lcd/rgb/include/esp_lcd_panel_rgb.h
@@ -0,0 +1,301 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "esp_err.h"
+#include "esp_lcd_types.h"
+#include "soc/soc_caps.h"
+#include "hal/lcd_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if SOC_LCD_RGB_SUPPORTED
+/**
+ * @brief LCD RGB timing structure
+ * @verbatim
+ * Total Width
+ * <--------------------------------------------------->
+ * HSYNC width HBP Active Width HFP
+ * <---><--><--------------------------------------><--->
+ * ____ ____|_______________________________________|____|
+ * |___| | | |
+ * | | |
+ * __| | | |
+ * /|\ /|\ | | | |
+ * | VSYNC| | | | |
+ * |Width\|/ |__ | | |
+ * | /|\ | | | |
+ * | VBP | | | | |
+ * | \|/_____|_________|_______________________________________| |
+ * | /|\ | | / / / / / / / / / / / / / / / / / / / | |
+ * | | | |/ / / / / / / / / / / / / / / / / / / /| |
+ * Total | | | |/ / / / / / / / / / / / / / / / / / / /| |
+ * Height | | | |/ / / / / / / / / / / / / / / / / / / /| |
+ * |Active| | |/ / / / / / / / / / / / / / / / / / / /| |
+ * |Height| | |/ / / / / / Active Display Area / / / /| |
+ * | | | |/ / / / / / / / / / / / / / / / / / / /| |
+ * | | | |/ / / / / / / / / / / / / / / / / / / /| |
+ * | | | |/ / / / / / / / / / / / / / / / / / / /| |
+ * | | | |/ / / / / / / / / / / / / / / / / / / /| |
+ * | | | |/ / / / / / / / / / / / / / / / / / / /| |
+ * | \|/_____|_________|_______________________________________| |
+ * | /|\ | |
+ * | VFP | | |
+ * \|/ \|/_____|______________________________________________________|
+ * @endverbatim
+ */
+typedef struct {
+ 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 {
+ 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;
+
+/**
+ * @brief Type of RGB LCD panel event data
+ */
+typedef struct {
+} esp_lcd_rgb_panel_event_data_t;
+
+/**
+ * @brief A general function callback prototype for RGB panel driver
+ *
+ * @param[in] panel LCD panel handle, which is created by factory API like `esp_lcd_new_rgb_panel`
+ * @param[in] edata RGB panel event data, provided by driver
+ * @param[in] user_ctx User data, passed from `esp_lcd_rgb_panel_register_event_callbacks()`
+ * @return Whether a high priority task has been waken up by this function
+ */
+typedef bool (*esp_lcd_rgb_panel_general_cb_t)(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx);
+
+/**
+ * @brief Declare the prototype of the function that will be invoked when the user draw buffer is complete.
+ * The draw buffer can be recycled after this event.
+ */
+typedef esp_lcd_rgb_panel_general_cb_t esp_lcd_rgb_panel_draw_buf_complete_cb_t;
+
+/**
+ * @brief Declare the prototype of the function that will be invoked when a whole frame buffer is sent to the LCD DMA.
+ * The LCD hardware may still need some blank time to finish the refresh.
+ */
+typedef esp_lcd_rgb_panel_general_cb_t esp_lcd_rgb_panel_frame_buf_complete_cb_t;
+
+/**
+ * @brief Declare the prototype of the function that will be invoked when the LCD controller sends the VSYNC signal.
+ * It means, the LCD hardware should be ready, and after some blank time, the next frame will be flushed to the LCD controller.
+ */
+typedef esp_lcd_rgb_panel_general_cb_t esp_lcd_rgb_panel_vsync_cb_t;
+
+/**
+ * @brief Prototype for function to re-fill a bounce buffer, rather than copying from the frame buffer
+ *
+ * @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel`
+ * @param[in] bounce_buf Bounce buffer to write data into
+ * @param[in] pos_px How many pixels already were sent to the display in this frame, in other words,
+ * at what pixel the routine should start putting data into bounce_buf
+ * @param[in] len_bytes Length, in bytes, of the bounce buffer. Routine should fill this length fully.
+ * @param[in] user_ctx Opaque pointer that was passed from `esp_lcd_rgb_panel_register_event_callbacks()`
+ * @return Whether a high priority task has been waken up by this function
+ */
+typedef bool (*esp_lcd_rgb_panel_bounce_buf_fill_cb_t)(esp_lcd_panel_handle_t panel, void *bounce_buf, int pos_px, int len_bytes, void *user_ctx);
+
+/** @cond */
+/// for backward compatible
+typedef esp_lcd_rgb_panel_frame_buf_complete_cb_t esp_lcd_rgb_panel_bounce_buf_finish_cb_t __attribute__((deprecated("esp_lcd_rgb_panel_bounce_buf_finish_cb_t is deprecated, use esp_lcd_rgb_panel_frame_buf_complete_cb_t instead")));
+/** @endcond */
+
+/**
+ * @brief Group of supported RGB LCD panel callbacks
+ * @note The callbacks are all running under ISR environment
+ * @note When CONFIG_LCD_RGB_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM.
+ */
+typedef struct {
+ esp_lcd_rgb_panel_draw_buf_complete_cb_t on_color_trans_done; /*!< Invoked when user's color buffer copied to the internal frame buffer.
+ This is an indicator that the draw buffer can be recycled safely.
+ But doesn't mean the draw buffer finishes the refreshing to the screen. */
+ esp_lcd_rgb_panel_vsync_cb_t on_vsync; /*!< VSYNC event callback */
+ esp_lcd_rgb_panel_bounce_buf_fill_cb_t on_bounce_empty; /*!< Bounce buffer empty callback. */
+ union {
+ esp_lcd_rgb_panel_frame_buf_complete_cb_t on_bounce_frame_finish __attribute__((deprecated)); /*!< Bounce buffer finish callback. */
+ esp_lcd_rgb_panel_frame_buf_complete_cb_t on_frame_buf_complete; /*!< A whole frame buffer was just sent to the LCD DMA */
+ };
+} esp_lcd_rgb_panel_event_callbacks_t;
+
+/**
+ * @brief LCD RGB panel configuration structure
+ */
+typedef struct {
+ lcd_clock_source_t clk_src; /*!< Clock source for the RGB LCD peripheral */
+ esp_lcd_rgb_timing_t timings; /*!< RGB timing parameters, including the screen resolution */
+ size_t data_width; /*!< Number of data lines */
+ size_t bits_per_pixel; /*!< Frame buffer 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 num_fbs; /*!< Number of screen-sized frame buffers that allocated by the driver. By default (set to either 0 or 1) only one frame buffer will be used. Maximum number of buffers are 3 */
+ 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 __attribute__((deprecated)); /*!< Alignment of buffers (frame buffer or bounce buffer) that allocated in SRAM */
+ union {
+ size_t psram_trans_align __attribute__((deprecated)); /*!< Alignment of buffers (frame buffer) that allocated in PSRAM */
+ size_t dma_burst_size; /*!< DMA burst size, in bytes */
+ };
+ 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, set to -1 if it's not used */
+ int disp_gpio_num; /*!< GPIO used for display control signal, set to -1 if it's not used */
+ int data_gpio_nums[SOC_LCDCAM_RGB_DATA_WIDTH]; /*!< GPIOs used for data lines */
+ struct {
+ 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 in `esp_lcd_panel_draw_bitmap` and `esp_lcd_rgb_panel_refresh`. */
+ 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, same as num_fbs=2 */
+ 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[in] rgb_panel_config RGB panel configuration
+ * @param[out] ret_panel Returned LCD panel handle
+ * @return
+ * - 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);
+
+/**
+ * @brief Register LCD RGB panel event callbacks
+ *
+ * @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel`
+ * @param[in] callbacks Group of callback functions
+ * @param[in] user_ctx User data, which will be passed to the callback functions directly
+ * @return
+ * - ESP_OK: Set event callbacks successfully
+ * - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument
+ * - ESP_FAIL: Set event callbacks failed because of other error
+ */
+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);
+
+/**
+ * @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 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.
+ * Only in the next VSYNC event handler, will the driver attempt to update the PCLK frequency.
+ *
+ * @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 Restart the LCD transmission
+ *
+ * @note This function can be useful when the LCD controller is out of sync with the DMA because of insufficient bandwidth.
+ * To save the screen from a permanent shift, you can call this function to restart the LCD DMA.
+ * @note This function doesn't restart the DMA immediately but to set a flag internally.
+ * Only in the next VSYNC event handler, will the driver attempt to do the restart job.
+ * @note If CONFIG_LCD_RGB_RESTART_IN_VSYNC is enabled, you don't need to call this function manually,
+ * because the restart job will be done automatically in the VSYNC event handler.
+ *
+ * @param[in] panel panel LCD panel handle, returned from `esp_lcd_new_rgb_panel`
+ * @return
+ * - ESP_ERR_INVALID_ARG: Restart the LCD failed because of invalid argument
+ * - ESP_ERR_INVALID_STATE: Restart the LCD failed because the LCD diver is working in refresh-on-demand mode
+ * - ESP_OK: Restart the LCD successfully
+ */
+esp_err_t esp_lcd_rgb_panel_restart(esp_lcd_panel_handle_t panel);
+
+/**
+ * @brief Get the address of the frame buffer(s) that allocated by the driver
+ *
+ * @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel`
+ * @param[in] fb_num Number of frame buffer(s) to get. This value must be the same as the number of the following parameters.
+ * @param[out] fb0 Returned address of the frame buffer 0
+ * @param[out] ... List of other frame buffer addresses
+ * @return
+ * - ESP_ERR_INVALID_ARG: Get frame buffer address failed because of invalid argument
+ * - ESP_OK: Get frame buffer address successfully
+ */
+esp_err_t esp_lcd_rgb_panel_get_frame_buffer(esp_lcd_panel_handle_t panel, uint32_t fb_num, void **fb0, ...);
+
+/**
+ * @brief Manually trigger once transmission of the frame buffer to the LCD panel
+ *
+ * @note This function should only be called when the RGB panel is working under the `refresh_on_demand` mode.
+ *
+ * @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel`
+ * @return
+ * - ESP_ERR_INVALID_ARG: Start a refresh failed because of invalid argument
+ * - ESP_ERR_INVALID_STATE: Start a refresh failed because the LCD panel is not created with the `refresh_on_demand` flag enabled.
+ * - ESP_OK: Start a refresh successfully
+ */
+esp_err_t esp_lcd_rgb_panel_refresh(esp_lcd_panel_handle_t panel);
+
+/**
+ * @brief LCD color conversion profile
+ */
+typedef struct {
+ lcd_color_space_t color_space; /*!< Color space of the image */
+ lcd_color_range_t color_range; /*!< Color range of the image */
+ lcd_yuv_sample_t yuv_sample; /*!< YUV sample format of the image */
+} esp_lcd_color_conv_profile_t;
+
+/**
+ * @brief Configuration of YUG-RGB conversion
+ */
+typedef struct {
+ lcd_yuv_conv_std_t std; /*!< YUV conversion standard: BT601, BT709 */
+ esp_lcd_color_conv_profile_t src; /*!< Color conversion profile of the input image */
+ esp_lcd_color_conv_profile_t dst; /*!< Color conversion profile of the output image */
+} esp_lcd_yuv_conv_config_t;
+
+/**
+ * @brief Configure how to convert the color format between RGB and YUV
+ *
+ * @note Pass in `config` as NULL will disable the RGB-YUV converter.
+ * @note The hardware converter can only parse a "packed" storage format, while "planar" and "semi-planar" format is not supported.
+ *
+ * @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel`
+ * @param[in] config Configuration of RGB-YUV conversion
+ * @return
+ * - ESP_ERR_INVALID_ARG: Configure RGB-YUV conversion failed because of invalid argument
+ * - ESP_ERR_NOT_SUPPORTED: Configure RGB-YUV conversion failed because the conversion mode is not supported by the hardware
+ * - ESP_OK: Configure RGB-YUV conversion successfully
+ */
+esp_err_t esp_lcd_rgb_panel_set_yuv_conversion(esp_lcd_panel_handle_t panel, const esp_lcd_yuv_conv_config_t *config);
+
+#endif // SOC_LCD_RGB_SUPPORTED
+
+#ifdef __cplusplus
+}
+#endif
### components/esp_lcd/rgb/rgb_lcd_rotation_sw.h
@@ -0,0 +1,162 @@
+/*
+ * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+#include "esp_bit_defs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define RGB_PANEL_SWAP_XY 0
+#define RGB_PANEL_MIRROR_Y 1
+#define RGB_PANEL_MIRROR_X 2
+
+typedef enum {
+ ROTATE_MASK_SWAP_XY = BIT(RGB_PANEL_SWAP_XY),
+ ROTATE_MASK_MIRROR_Y = BIT(RGB_PANEL_MIRROR_Y),
+ ROTATE_MASK_MIRROR_X = BIT(RGB_PANEL_MIRROR_X),
+} panel_rotate_mask_t;
+
+__attribute__((always_inline))
+static inline void copy_pixel_8bpp(uint8_t *to, const uint8_t *from)
+{
+ *to++ = *from++;
+}
+
+__attribute__((always_inline))
+static inline void copy_pixel_16bpp(uint8_t *to, const uint8_t *from)
+{
+ *to++ = *from++;
+ *to++ = *from++;
+}
+
+__attribute__((always_inline))
+static inline void copy_pixel_24bpp(uint8_t *to, const uint8_t *from)
+{
+ *to++ = *from++;
+ *to++ = *from++;
+ *to++ = *from++;
+}
+
+#define COPY_PIXEL_CODE_BLOCK(_bpp) \
+ switch (rgb_panel->rotate_mask) \
+ { \
+ case 0: \
+ { \
+ uint8_t *to = fb + (y_start * h_res + 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; \
+ } \
+ bytes_to_flush = (y_end - y_start) * bytes_per_line; \
+ flush_ptr = fb + y_start * bytes_per_line; \
+ } \
+ break; \
+ case ROTATE_MASK_MIRROR_X: \
+ for (int y = y_start; y < y_end; y++) \
+ { \
+ uint32_t index = (y * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \
+ for (size_t x = x_start; x < x_end; x++) \
+ { \
+ copy_pixel_##_bpp##bpp(to + index, from); \
+ index -= bytes_per_pixel; \
+ from += bytes_per_pixel; \
+ } \
+ } \
+ bytes_to_flush = (y_end - y_start) * bytes_per_line; \
+ flush_ptr = fb + y_start * bytes_per_line; \
+ break; \
+ case ROTATE_MASK_MIRROR_Y: \
+ { \
+ uint8_t *to = fb + ((v_res - 1 - y_start) * h_res + 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; \
+ } \
+ bytes_to_flush = (y_end - y_start) * bytes_per_line; \
+ flush_ptr = fb + (v_res - y_end) * bytes_per_line; \
+ } \
+ break; \
+ case ROTATE_MASK_MIRROR_X | ROTATE_MASK_MIRROR_Y: \
+ for (int y = y_start; y < y_end; y++) \
+ { \
+ uint32_t index = ((v_res - 1 - y) * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \
+ for (size_t x = x_start; x < x_end; x++) \
+ { \
+ copy_pixel_##_bpp##bpp(to + index, from); \
+ index -= bytes_per_pixel; \
+ from += bytes_per_pixel; \
+ } \
+ } \
+ bytes_to_flush = (y_end - y_start) * bytes_per_line; \
+ flush_ptr = fb + (v_res - y_end) * bytes_per_line; \
+ break; \
+ case ROTATE_MASK_SWAP_XY: \
+ for (int y = y_start; y < y_end; y++) \
+ { \
+ for (int x = x_start; x < x_end; x++) \
+ { \
+ uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
+ uint32_t i = (x * h_res + y) * bytes_per_pixel; \
+ copy_pixel_##_bpp##bpp(to + i, from + j); \
+ } \
+ } \
+ bytes_to_flush = (x_end - x_start) * bytes_per_line; \
+ flush_ptr = fb + x_start * bytes_per_line; \
+ break; \
+ case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_X: \
+ for (int y = y_start; y < y_end; y++) \
+ { \
+ for (int x = x_start; x < x_end; x++) \
+ { \
+ uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
+ uint32_t i = (x * h_res + h_res - 1 - y) * bytes_per_pixel; \
+ copy_pixel_##_bpp##bpp(to + i, from + j); \
+ } \
+ } \
+ bytes_to_flush = (x_end - x_start) * bytes_per_line; \
+ flush_ptr = fb + x_start * bytes_per_line; \
+ break; \
+ case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_Y: \
+ for (int y = y_start; y < y_end; y++) \
+ { \
+ for (int x = x_start; x < x_end; x++) \
+ { \
+ uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
+ uint32_t i = ((v_res - 1 - x) * h_res + y) * bytes_per_pixel; \
+ copy_pixel_##_bpp##bpp(to + i, from + j); \
+ } \
+ } \
+ bytes_to_flush = (x_end - x_start) * bytes_per_line; \
+ flush_ptr = fb + (v_res - x_end) * bytes_per_line; \
+ break; \
+ case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_X | ROTATE_MASK_MIRROR_Y: \
+ for (int y = y_start; y < y_end; y++) \
+ { \
+ for (int x = x_start; x < x_end; x++) \
+ { \
+ uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
+ uint32_t i = ((v_res - 1 - x) * h_res + h_res - 1 - y) * bytes_per_pixel; \
+ copy_pixel_##_bpp##bpp(to + i, from + j); \
+ } \
+ } \
+ bytes_to_flush = (x_end - x_start) * bytes_per_line; \
+ flush_ptr = fb + (v_res - x_end) * bytes_per_line; \
+ break; \
+ default: \
+ break; \
+ }
+
+#ifdef __cplusplus
+}
+#endif
### components/esp_lcd/sdkconfig.rename
@@ -0,0 +1,4 @@
+# sdkconfig replacement configurations for deprecated options formatted as
+# CONFIG_DEPRECATED_OPTION CONFIG_NEW_OPTION
+
+CONFIG_LCD_DSI_ISR_IRAM_SAFE CONFIG_LCD_DSI_ISR_CACHE_SAFE
### components/esp_lcd/spi/esp_lcd_panel_io_spi.c
@@ -0,0 +1,436 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/cdefs.h>
+#include "sdkconfig.h"
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+// The local log level must be defined before including esp_log.h
+// Set the maximum log level for this source file
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#endif
+#include "esp_lcd_panel_io_interface.h"
+#include "esp_lcd_panel_io.h"
+#include "driver/spi_master.h"
+#include "driver/gpio.h"
+#include "esp_private/gpio.h"
+#include "hal/gpio_ll.h"
+#include "hal/gpio_hal.h"
+#include "esp_log.h"
+#include "esp_check.h"
+#include "esp_lcd_common.h"
+
+static const char *TAG = "lcd_panel.io.spi";
+
+static esp_err_t panel_io_spi_rx_param(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size);
+static esp_err_t panel_io_spi_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size);
+static esp_err_t panel_io_spi_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size);
+static esp_err_t panel_io_spi_del(esp_lcd_panel_io_t *io);
+static void lcd_spi_pre_trans_cb(spi_transaction_t *trans);
+static void lcd_spi_post_trans_color_cb(spi_transaction_t *trans);
+static esp_err_t panel_io_spi_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx);
+
+typedef struct {
+ spi_transaction_t base;
+ struct {
+ unsigned int dc_gpio_level: 1;
+ unsigned int en_trans_done_cb: 1;
+ } flags;
+} lcd_spi_trans_descriptor_t;
+
+typedef struct {
+ esp_lcd_panel_io_t base; // Base class of generic lcd panel io
+ spi_device_handle_t spi_dev; // SPI device handle
+ size_t spi_trans_max_bytes; // Maximum bytes that can be transmitted in one spi transaction
+ int dc_gpio_num; // D/C line GPIO number
+ esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; // User register's callback, invoked when color data trans done
+ void *user_ctx; // User's private data, passed directly to callback on_color_trans_done
+ size_t queue_size; // Size of transaction queue
+ size_t num_trans_inflight; // Number of transactions that are undergoing (the descriptor not recycled yet)
+ int lcd_cmd_bits; // Bit width of LCD command
+ int lcd_param_bits; // Bit width of LCD parameter
+ uint8_t cs_ena_pretrans; // Amount of SPI bit-cycles the cs should be activated before the transmission (0-16)
+ uint8_t cs_ena_posttrans; // Amount of SPI bit-cycles the cs should stay active after the transmission (0-16)
+ struct {
+ unsigned int dc_cmd_level: 1; // Indicates the level of DC line when transferring command
+ unsigned int dc_data_level: 1; // Indicates the level of DC line when transferring color data
+ unsigned int dc_param_level: 1; // Indicates the level of DC line when transferring parameters
+ unsigned int octal_mode: 1; // Indicates whether the transmitting is enabled with octal mode (8 data lines)
+ unsigned int quad_mode: 1; // Indicates whether the transmitting is enabled with quad mode (4 data lines)
+ } flags;
+ lcd_spi_trans_descriptor_t trans_pool[]; // Transaction pool
+} esp_lcd_panel_io_spi_t;
+
+esp_err_t esp_lcd_new_panel_io_spi(esp_lcd_spi_bus_handle_t bus, const esp_lcd_panel_io_spi_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
+{
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+ esp_log_level_set(TAG, ESP_LOG_DEBUG);
+#endif
+ esp_err_t ret = ESP_OK;
+ esp_lcd_panel_io_spi_t *spi_panel_io = NULL;
+ ESP_GOTO_ON_FALSE(bus && io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
+ spi_panel_io = calloc(1, sizeof(esp_lcd_panel_io_spi_t) + sizeof(lcd_spi_trans_descriptor_t) * io_config->trans_queue_depth);
+ ESP_GOTO_ON_FALSE(spi_panel_io, ESP_ERR_NO_MEM, err, TAG, "no mem for spi panel io");
+
+ spi_device_interface_config_t devcfg = {
+ .flags = SPI_DEVICE_HALFDUPLEX |
+ (io_config->flags.lsb_first ? SPI_DEVICE_TXBIT_LSBFIRST : 0) |
+ (io_config->flags.sio_mode ? SPI_DEVICE_3WIRE : 0) |
+ (io_config->flags.cs_high_active ? SPI_DEVICE_POSITIVE_CS : 0),
+ .clock_speed_hz = io_config->pclk_hz,
+ .mode = io_config->spi_mode,
+ .spics_io_num = io_config->cs_gpio_num,
+ .queue_size = io_config->trans_queue_depth,
+ .pre_cb = lcd_spi_pre_trans_cb, // pre-transaction callback, mainly control DC gpio level
+ .post_cb = lcd_spi_post_trans_color_cb, // post-transaction, where we invoke user registered "on_color_trans_done()"
+ .cs_ena_pretrans = io_config->cs_ena_pretrans,
+ .cs_ena_posttrans = io_config->cs_ena_posttrans,
+ };
+ ret = spi_bus_add_device((spi_host_device_t)bus, &devcfg, &spi_panel_io->spi_dev);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "adding spi device to bus failed");
+
+ // if the DC line is not encoded into any spi transaction phase or it's not controlled by SPI peripheral
+ if (io_config->dc_gpio_num >= 0) {
+ gpio_set_level(io_config->dc_gpio_num, 0);
+ gpio_func_sel(io_config->dc_gpio_num, PIN_FUNC_GPIO);
+ gpio_output_enable(io_config->dc_gpio_num);
+ }
+
+ spi_panel_io->flags.dc_cmd_level = io_config->flags.dc_high_on_cmd;
+ spi_panel_io->flags.dc_data_level = !io_config->flags.dc_low_on_data;
+ spi_panel_io->flags.dc_param_level = !io_config->flags.dc_low_on_param;
+ spi_panel_io->flags.octal_mode = io_config->flags.octal_mode;
+ spi_panel_io->flags.quad_mode = io_config->flags.quad_mode;
+ spi_panel_io->on_color_trans_done = io_config->on_color_trans_done;
+ spi_panel_io->user_ctx = io_config->user_ctx;
+ spi_panel_io->lcd_cmd_bits = io_config->lcd_cmd_bits;
+ spi_panel_io->lcd_param_bits = io_config->lcd_param_bits;
+ spi_panel_io->dc_gpio_num = io_config->dc_gpio_num;
+ spi_panel_io->queue_size = io_config->trans_queue_depth;
+ spi_panel_io->base.rx_param = panel_io_spi_rx_param;
+ spi_panel_io->base.tx_param = panel_io_spi_tx_param;
+ spi_panel_io->base.tx_color = panel_io_spi_tx_color;
+ spi_panel_io->base.del = panel_io_spi_del;
+ spi_panel_io->base.register_event_callbacks = panel_io_spi_register_event_callbacks;
+
+ size_t max_trans_bytes = 0;
+ ESP_GOTO_ON_ERROR(spi_bus_get_max_transaction_len((spi_host_device_t)bus, &max_trans_bytes), err, TAG, "get spi max transaction len failed");
+ spi_panel_io->spi_trans_max_bytes = max_trans_bytes;
+
+ *ret_io = &(spi_panel_io->base);
+ ESP_LOGD(TAG, "new spi lcd panel io @%p, max_trans_bytes: %d", spi_panel_io, (int)max_trans_bytes);
+
+ return ESP_OK;
+
+err:
+ if (spi_panel_io) {
+ if (io_config->dc_gpio_num >= 0) {
+ gpio_output_disable(io_config->dc_gpio_num);
+ }
+ free(spi_panel_io);
+ }
+ return ret;
+}
+
+static esp_err_t panel_io_spi_del(esp_lcd_panel_io_t *io)
+{
+ esp_err_t ret = ESP_OK;
+ spi_transaction_t *spi_trans = NULL;
+ esp_lcd_panel_io_spi_t *spi_panel_io = __containerof(io, esp_lcd_panel_io_spi_t, base);
+
+ // wait all pending transaction to finish
+ size_t num_trans_inflight = spi_panel_io->num_trans_inflight;
+ for (size_t i = 0; i < num_trans_inflight; i++) {
+ ret = spi_device_get_trans_result(spi_panel_io->spi_dev, &spi_trans, portMAX_DELAY);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "recycle spi transactions failed");
+ spi_panel_io->num_trans_inflight--;
+ }
+ spi_bus_remove_device(spi_panel_io->spi_dev);
+ if (spi_panel_io->dc_gpio_num >= 0) {
+ gpio_output_disable(spi_panel_io->dc_gpio_num);
+ }
+ ESP_LOGD(TAG, "del lcd panel io spi @%p", spi_panel_io);
+ free(spi_panel_io);
+
+err:
+ return ret;
+}
+
+static esp_err_t panel_io_spi_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx)
+{
+ esp_lcd_panel_io_spi_t *spi_panel_io = __containerof(io, esp_lcd_panel_io_spi_t, base);
+
+ if (spi_panel_io->on_color_trans_done != NULL) {
+ ESP_LOGW(TAG, "Callback on_color_trans_done was already set and now it was overwritten!");
+ }
+
+ spi_panel_io->on_color_trans_done = cbs->on_color_trans_done;
+ spi_panel_io->user_ctx = user_ctx;
+
+ return ESP_OK;
+}
+
+static void spi_lcd_prepare_cmd_buffer(esp_lcd_panel_io_spi_t *panel_io, const void *cmd)
+{
+ uint8_t *from = (uint8_t *)cmd;
+ // LCD is big-endian, e.g. to send command 0x1234, byte 0x12 should appear on the bus first
+ // However, the SPI peripheral will send 0x34 first, so we reversed the order below
+ if (panel_io->lcd_cmd_bits > 8) {
+ int start = 0;
+ int end = panel_io->lcd_cmd_bits / 8 - 1;
+ lcd_com_reverse_buffer_bytes(from, start, end);
+ }
+}
+
+static void spi_lcd_prepare_param_buffer(esp_lcd_panel_io_spi_t *panel_io, const void *param, size_t param_size)
+{
+ uint8_t *from = (uint8_t *)param;
+ int param_width = panel_io->lcd_param_bits / 8;
+ size_t param_num = param_size / param_width;
+ // LCD is big-endian, e.g. to send command 0x1234, byte 0x12 should appear on the bus first
+ // However, the SPI peripheral will send 0x34 first, so we reversed the order below
+ if (panel_io->lcd_param_bits > 8) {
+ for (size_t i = 0; i < param_num; i++) {
+ int start = i * param_width;
+ int end = start + param_width - 1;
+ lcd_com_reverse_buffer_bytes(from, start, end);
+ }
+ }
+}
+
+static esp_err_t panel_io_spi_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size)
+{
+ esp_err_t ret = ESP_OK;
+ spi_transaction_t *spi_trans = NULL;
+ lcd_spi_trans_descriptor_t *lcd_trans = NULL;
+ esp_lcd_panel_io_spi_t *spi_panel_io = __containerof(io, esp_lcd_panel_io_spi_t, base);
+ bool send_cmd = (lcd_cmd >= 0);
+
+ ESP_RETURN_ON_ERROR(spi_device_acquire_bus(spi_panel_io->spi_dev, portMAX_DELAY), TAG, "acquire spi bus failed");
+
+ // before issue a polling transaction, need to wait queued transactions finished
+ size_t num_trans_inflight = spi_panel_io->num_trans_inflight;
+ for (size_t i = 0; i < num_trans_inflight; i++) {
+ ret = spi_device_get_trans_result(spi_panel_io->spi_dev, &spi_trans, portMAX_DELAY);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "recycle spi transactions failed");
+ spi_panel_io->num_trans_inflight--;
+ }
+ lcd_trans = &spi_panel_io->trans_pool[0];
+ memset(lcd_trans, 0, sizeof(lcd_spi_trans_descriptor_t));
+
+ lcd_trans->base.user = spi_panel_io;
+ if (param && param_size) {
+ lcd_trans->base.flags |= SPI_TRANS_CS_KEEP_ACTIVE;
+ }
+ if (spi_panel_io->flags.octal_mode) {
+ // use 8 lines for transmitting command, address and data
+ lcd_trans->base.flags |= (SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR | SPI_TRANS_MODE_OCT);
+ }
+
+ if (send_cmd) {
+ spi_lcd_prepare_cmd_buffer(spi_panel_io, &lcd_cmd);
+ lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_cmd_level; // set D/C level in command phase
+ lcd_trans->base.length = spi_panel_io->lcd_cmd_bits;
+ lcd_trans->base.tx_buffer = &lcd_cmd;
+ // command is short, using polling mode
+ ret = spi_device_polling_transmit(spi_panel_io->spi_dev, &lcd_trans->base);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (polling) command failed");
+ }
+
+ if (param && param_size) {
+ spi_lcd_prepare_param_buffer(spi_panel_io, param, param_size);
+ lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_param_level; // set D/C level in param phase
+ lcd_trans->base.length = param_size * 8; // transaction length is in bits
+ lcd_trans->base.tx_buffer = param;
+ lcd_trans->base.flags &= ~SPI_TRANS_CS_KEEP_ACTIVE;
+ // parameter is usually short, using polling mode
+ ret = spi_device_polling_transmit(spi_panel_io->spi_dev, &lcd_trans->base);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (polling) param failed");
+ }
+
+err:
+ spi_device_release_bus(spi_panel_io->spi_dev);
+
+ return ret;
+}
+
+static esp_err_t panel_io_spi_rx_param(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size)
+{
+ esp_err_t ret = ESP_OK;
+ spi_transaction_t *spi_trans = NULL;
+ lcd_spi_trans_descriptor_t *lcd_trans = NULL;
+ esp_lcd_panel_io_spi_t *spi_panel_io = __containerof(io, esp_lcd_panel_io_spi_t, base);
+ bool send_cmd = (lcd_cmd >= 0);
+
+ ESP_RETURN_ON_ERROR(spi_device_acquire_bus(spi_panel_io->spi_dev, portMAX_DELAY), TAG, "acquire spi bus failed");
+
+ // before issue a polling transaction, need to wait queued transactions finished
+ size_t num_trans_inflight = spi_panel_io->num_trans_inflight;
+ for (size_t i = 0; i < num_trans_inflight; i++) {
+ ret = spi_device_get_trans_result(spi_panel_io->spi_dev, &spi_trans, portMAX_DELAY);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "recycle spi transactions failed");
+ spi_panel_io->num_trans_inflight--;
+ }
+ lcd_trans = &spi_panel_io->trans_pool[0];
+ memset(lcd_trans, 0, sizeof(lcd_spi_trans_descriptor_t));
+
+ lcd_trans->base.user = spi_panel_io;
+ lcd_trans->base.flags |= SPI_TRANS_CS_KEEP_ACTIVE;
+ if (spi_panel_io->flags.octal_mode) {
+ // use 8 lines for transmitting command, address and data
+ lcd_trans->base.flags |= (SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR | SPI_TRANS_MODE_OCT);
+ }
+
+ if (send_cmd) {
+ spi_lcd_prepare_cmd_buffer(spi_panel_io, &lcd_cmd);
+ lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_cmd_level; // set D/C level in command phase
+ lcd_trans->base.length = spi_panel_io->lcd_cmd_bits;
+ lcd_trans->base.tx_buffer = &lcd_cmd;
+ // command is short, using polling mode
+ ret = spi_device_polling_transmit(spi_panel_io->spi_dev, &lcd_trans->base);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (polling) command failed");
+ }
+
+ if (param && param_size) {
+ lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_param_level; // set D/C level in param phase
+ lcd_trans->base.length = 0;
+ lcd_trans->base.tx_buffer = NULL;
+ lcd_trans->base.rxlength = param_size * 8; // Read length in bits
+ lcd_trans->base.rx_buffer = param;
+ lcd_trans->base.flags &= ~SPI_TRANS_CS_KEEP_ACTIVE;
+ // parameter is usually short, using polling mode
+ ret = spi_device_polling_transmit(spi_panel_io->spi_dev, &lcd_trans->base);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (polling) param failed");
+ }
+
+err:
+ spi_device_release_bus(spi_panel_io->spi_dev);
+
+ return ret;
+}
+
+static esp_err_t panel_io_spi_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size)
+{
+ esp_err_t ret = ESP_OK;
+ spi_transaction_t *spi_trans = NULL;
+ lcd_spi_trans_descriptor_t *lcd_trans = NULL;
+ esp_lcd_panel_io_spi_t *spi_panel_io = __containerof(io, esp_lcd_panel_io_spi_t, base);
+
+ ESP_RETURN_ON_ERROR(spi_device_acquire_bus(spi_panel_io->spi_dev, portMAX_DELAY), TAG, "acquire spi bus failed");
+
+ bool send_cmd = (lcd_cmd >= 0);
+ if (send_cmd) {
+ // before issue a polling transaction, need to wait queued transactions finished
+ size_t num_trans_inflight = spi_panel_io->num_trans_inflight;
+ for (size_t i = 0; i < num_trans_inflight; i++) {
+ ret = spi_device_get_trans_result(spi_panel_io->spi_dev, &spi_trans, portMAX_DELAY);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "recycle spi transactions failed");
+ spi_panel_io->num_trans_inflight--;
+ }
+ lcd_trans = &spi_panel_io->trans_pool[0];
+ memset(lcd_trans, 0, sizeof(lcd_spi_trans_descriptor_t));
+
+ spi_lcd_prepare_cmd_buffer(spi_panel_io, &lcd_cmd);
+ lcd_trans->base.user = spi_panel_io;
+ lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_cmd_level; // set D/C level in command phase
+ lcd_trans->base.length = spi_panel_io->lcd_cmd_bits;
+ lcd_trans->base.tx_buffer = &lcd_cmd;
+ if (color && color_size) {
+ lcd_trans->base.flags |= SPI_TRANS_CS_KEEP_ACTIVE;
+ }
+ if (spi_panel_io->flags.octal_mode) {
+ // use 8 lines for transmitting command, address and data
+ lcd_trans->base.flags |= (SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR | SPI_TRANS_MODE_OCT);
+ }
+ // command is short, using polling mode
+ ret = spi_device_polling_transmit(spi_panel_io->spi_dev, &lcd_trans->base);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (polling) command failed");
+ }
+
+ // if the color buffer is big, we want to split it into chunks, and queue the chunks one by one
+ do {
+ size_t chunk_size = color_size;
+
+ if (spi_panel_io->num_trans_inflight < spi_panel_io->queue_size) {
+ // get the next available transaction
+ lcd_trans = &spi_panel_io->trans_pool[spi_panel_io->num_trans_inflight];
+ } else {
+ // transaction pool has used up, recycle one transaction
+ ret = spi_device_get_trans_result(spi_panel_io->spi_dev, &spi_trans, portMAX_DELAY);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "recycle spi transactions failed");
+ lcd_trans = __containerof(spi_trans, lcd_spi_trans_descriptor_t, base);
+ spi_panel_io->num_trans_inflight--;
+ }
+ memset(lcd_trans, 0, sizeof(lcd_spi_trans_descriptor_t));
+
+ // SPI per-transfer size has its limitation, if the color buffer is too big, we need to split it into multiple chunks
+ if (chunk_size > spi_panel_io->spi_trans_max_bytes) {
+ // cap the transfer size to the maximum supported by the bus
+ chunk_size = spi_panel_io->spi_trans_max_bytes;
+ lcd_trans->base.flags |= SPI_TRANS_CS_KEEP_ACTIVE;
+ } else {
+ // mark en_trans_done_cb only at the last round to avoid premature completion callback
+ lcd_trans->flags.en_trans_done_cb = 1;
+ lcd_trans->base.flags &= ~SPI_TRANS_CS_KEEP_ACTIVE;
+ }
+
+ lcd_trans->base.user = spi_panel_io;
+ lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_data_level; // set D/C level in data phase
+ lcd_trans->base.length = chunk_size * 8; // transaction length is in bits
+ lcd_trans->base.tx_buffer = color;
+ if (spi_panel_io->flags.octal_mode) {
+ // use 8 lines for transmitting command, address and data
+ lcd_trans->base.flags |= (SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR | SPI_TRANS_MODE_OCT);
+ } else if (spi_panel_io->flags.quad_mode) {
+ // use 4 lines only for transmitting data
+ lcd_trans->base.flags |= SPI_TRANS_MODE_QIO;
+ }
+
+ // color data is usually large, using queue+blocking mode
+ ret = spi_device_queue_trans(spi_panel_io->spi_dev, &lcd_trans->base, portMAX_DELAY);
+ ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (queue) color failed");
+ spi_panel_io->num_trans_inflight++;
+
+ // move on to the next chunk
+ color = (const uint8_t *)color + chunk_size;
+ color_size -= chunk_size;
+ } while (color_size > 0); // continue while we have remaining data to transmit
+
+err:
+ spi_device_release_bus(spi_panel_io->spi_dev);
+ return ret;
+}
+
+IRAM_ATTR static void lcd_spi_pre_trans_cb(spi_transaction_t *trans)
+{
+ esp_lcd_panel_io_spi_t *spi_panel_io = trans->user;
+ lcd_spi_trans_descriptor_t *lcd_trans = __containerof(trans, lcd_spi_trans_descriptor_t, base);
+ if (spi_panel_io->dc_gpio_num >= 0) { // set D/C line level if necessary
+ // use ll function to speed up
+ gpio_ll_set_level(&GPIO, spi_panel_io->dc_gpio_num, lcd_trans->flags.dc_gpio_level);
+
+ // ensure the D/C output is enabled
+ gpio_ll_output_enable(&GPIO, spi_panel_io->dc_gpio_num);
+ }
+}
+
+static void lcd_spi_post_trans_color_cb(spi_transaction_t *trans)
+{
+ esp_lcd_panel_io_spi_t *spi_panel_io = trans->user;
+ lcd_spi_trans_descriptor_t *lcd_trans = __containerof(trans, lcd_spi_trans_descriptor_t, base);
+
+ // disable the D/C output as we no longer need it
+ if (spi_panel_io->dc_gpio_num >= 0) {
+ gpio_ll_output_disable(&GPIO, spi_panel_io->dc_gpio_num);
+ }
+
+ if (lcd_trans->flags.en_trans_done_cb) {
+ if (spi_panel_io->on_color_trans_done) {
+ spi_panel_io->on_color_trans_done(&spi_panel_io->base, NULL, spi_panel_io->user_ctx);
+ }
+ }
+}
### 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
+ static 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,
+ };
+ dma2d_trans_conf.user_config = mcp;
+ ESP_RETURN_ON_ERROR(dma2d_enqueue(mcp->client, &dma2d_trans_conf, mcp->trans_desc), TAG, "DMA2D enqueue failed");
+ return ESP_OK;
+}
### components/esp_lcd/src/esp_lcd_common.c
@@ -0,0 +1,80 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "freertos/FreeRTOS.h"
+#include "soc/soc_caps.h"
+#include "esp_lcd_common.h"
+#if SOC_LCDCAM_SUPPORTED
+#include "hal/lcd_ll.h"
+#include "hal/lcd_hal.h"
+
+typedef struct esp_lcd_platform_t {
+ portMUX_TYPE spinlock; // spinlock used to protect platform level resources
+ union {
+ void *panels[SOC_LCDCAM_RGB_NUM_PANELS]; // array of RGB LCD panel instances
+ void *buses[SOC_LCDCAM_I80_NUM_BUSES]; // array of i80 bus instances
+ }; // LCD peripheral can only work under either RGB mode or intel 8080 mode
+} esp_lcd_platform_t;
+
+esp_lcd_platform_t s_lcd_platform = {
+ .spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED,
+ .buses = {} // initially the bus slots and panel slots are empty
+};
+
+int lcd_com_register_device(lcd_com_device_type_t device_type, void *device_obj)
+{
+ int member_id = -1;
+ switch (device_type) {
+ case LCD_COM_DEVICE_TYPE_I80:
+ // search for a bus slot then register to platform
+ for (int i = 0; (i < SOC_LCDCAM_I80_NUM_BUSES) && (member_id == -1); i++) {
+ portENTER_CRITICAL(&s_lcd_platform.spinlock);
+ if (!s_lcd_platform.buses[i]) {
+ s_lcd_platform.buses[i] = device_obj;
+ member_id = i;
+ }
+ portEXIT_CRITICAL(&s_lcd_platform.spinlock);
+ }
+ break;
+ case LCD_COM_DEVICE_TYPE_RGB:
+ // search for a panel slot then register to platform
+ for (int i = 0; (i < SOC_LCDCAM_RGB_NUM_PANELS) && (member_id == -1); i++) {
+ portENTER_CRITICAL(&s_lcd_platform.spinlock);
+ if (!s_lcd_platform.panels[i]) {
+ s_lcd_platform.panels[i] = device_obj;
+ member_id = i;
+ }
+ portEXIT_CRITICAL(&s_lcd_platform.spinlock);
+ }
+ break;
+ default:
+ break;
+ }
+ return member_id;
+}
+
+void lcd_com_remove_device(lcd_com_device_type_t device_type, int member_id)
+{
+ switch (device_type) {
+ case LCD_COM_DEVICE_TYPE_I80:
+ portENTER_CRITICAL(&s_lcd_platform.spinlock);
+ if (s_lcd_platform.buses[member_id]) {
+ s_lcd_platform.buses[member_id] = NULL;
+ }
+ portEXIT_CRITICAL(&s_lcd_platform.spinlock);
+ break;
+ case LCD_COM_DEVICE_TYPE_RGB:
+ portENTER_CRITICAL(&s_lcd_platform.spinlock);
+ if (s_lcd_platform.panels[member_id]) {
+ s_lcd_platform.panels[member_id] = NULL;
+ }
+ portEXIT_CRITICAL(&s_lcd_platform.spinlock);
+ break;
+ default:
+ break;
+ }
+}
+#endif // SOC_LCDCAM_SUPPORTED
### components/esp_lcd/src/esp_lcd_panel_io.c
@@ -0,0 +1,43 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "esp_check.h"
+#include "esp_lcd_panel_io.h"
+#include "esp_lcd_panel_io_interface.h"
+
+static const char *TAG = "lcd_panel.io";
+
+esp_err_t esp_lcd_panel_io_rx_param(esp_lcd_panel_io_handle_t io, int lcd_cmd, void *param, size_t param_size)
+{
+ ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_ARG, TAG, "invalid panel io handle");
+ ESP_RETURN_ON_FALSE(io->rx_param, ESP_ERR_NOT_SUPPORTED, TAG, "rx_param is not supported yet");
+ return io->rx_param(io, lcd_cmd, param, param_size);
+}
+
+esp_err_t esp_lcd_panel_io_tx_param(esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *param, size_t param_size)
+{
+ ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_ARG, TAG, "invalid panel io handle");
+ return io->tx_param(io, lcd_cmd, param, param_size);
+}
+
+esp_err_t esp_lcd_panel_io_tx_color(esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *color, size_t color_size)
+{
+ ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_ARG, TAG, "invalid panel io handle");
+ return io->tx_color(io, lcd_cmd, color, color_size);
+}
+
+esp_err_t esp_lcd_panel_io_del(esp_lcd_panel_io_handle_t io)
+{
+ ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_ARG, TAG, "invalid panel io handle");
+ return io->del(io);
+}
+
+esp_err_t esp_lcd_panel_io_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx)
+{
+ ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_ARG, TAG, "invalid panel io handle");
+ ESP_RETURN_ON_FALSE(cbs, ESP_ERR_INVALID_ARG, TAG, "invalid callbacks structure");
+ return io->register_event_callbacks(io, cbs, user_ctx);
+}
### components/esp_lcd/src/esp_lcd_panel_nt35510.c
@@ -0,0 +1,322 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdlib.h>
+#include <sys/cdefs.h>
+#include "sdkconfig.h"
+
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+// The local log level must be defined before including esp_log.h
+// Set the maximum log level for this source file
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#endif
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_lcd_panel_interface.h"
+#include "esp_lcd_panel_io.h"
+#include "esp_lcd_panel_vendor.h"
+#include "esp_lcd_panel_ops.h"
+#include "esp_lcd_panel_commands.h"
+#include "driver/gpio.h"
+#include "esp_log.h"
+#include "esp_check.h"
+#include "esp_compiler.h"
+
+static const char *TAG = "lcd_panel.nt35510";
+
+static esp_err_t panel_nt35510_del(esp_lcd_panel_t *panel);
+static esp_err_t panel_nt35510_reset(esp_lcd_panel_t *panel);
+static esp_err_t panel_nt35510_init(esp_lcd_panel_t *panel);
+static esp_err_t panel_nt35510_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end,
+ const void *color_data);
+static esp_err_t panel_nt35510_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
+static esp_err_t panel_nt35510_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
+static esp_err_t panel_nt35510_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
+static esp_err_t panel_nt35510_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
+static esp_err_t panel_nt35510_disp_on_off(esp_lcd_panel_t *panel, bool off);
+static esp_err_t panel_nt35510_sleep(esp_lcd_panel_t *panel, bool sleep);
+
+typedef struct {
+ esp_lcd_panel_t base;
+ esp_lcd_panel_io_handle_t io;
+ int reset_gpio_num;
+ bool reset_level;
+ int x_gap;
+ int y_gap;
+ uint8_t fb_bits_per_pixel;
+ uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register
+ uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register
+} nt35510_panel_t;
+
+esp_err_t
+esp_lcd_new_panel_nt35510(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
+ esp_lcd_panel_handle_t *ret_panel)
+{
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+ esp_log_level_set(TAG, ESP_LOG_DEBUG);
+#endif
+ esp_err_t ret = ESP_OK;
+ nt35510_panel_t *nt35510 = NULL;
+ ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
+ // leak detection of nt35510 because saving nt35510->base address
+ ESP_COMPILER_DIAGNOSTIC_PUSH_IGNORE("-Wanalyzer-malloc-leak")
+ nt35510 = calloc(1, sizeof(nt35510_panel_t));
+ ESP_GOTO_ON_FALSE(nt35510, ESP_ERR_NO_MEM, err, TAG, "no mem for nt35510 panel");
+
+ if (panel_dev_config->reset_gpio_num >= 0) {
+ gpio_config_t io_conf = {
+ .mode = GPIO_MODE_OUTPUT,
+ .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num,
+ };
+ ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
+ }
+
+ switch (panel_dev_config->rgb_ele_order) {
+ case LCD_RGB_ELEMENT_ORDER_RGB:
+ nt35510->madctl_val = 0;
+ break;
+ case LCD_RGB_ELEMENT_ORDER_BGR:
+ nt35510->madctl_val |= LCD_CMD_BGR_BIT;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported RGB element order");
+ break;
+ }
+
+ uint8_t fb_bits_per_pixel = 0;
+ switch (panel_dev_config->bits_per_pixel) {
+ case 16: // RGB565
+ nt35510->colmod_val = 0x55;
+ fb_bits_per_pixel = 16;
+ break;
+ case 18: // RGB666
+ nt35510->colmod_val = 0x66;
+ // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel
+ fb_bits_per_pixel = 24;
+ break;
+ case 24: // RGB888
+ nt35510->colmod_val = 0x77;
+ fb_bits_per_pixel = 24;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
+ break;
+ }
+
+ nt35510->io = io;
+ nt35510->fb_bits_per_pixel = fb_bits_per_pixel;
+ nt35510->reset_gpio_num = panel_dev_config->reset_gpio_num;
+ nt35510->reset_level = panel_dev_config->flags.reset_active_high;
+ nt35510->base.del = panel_nt35510_del;
+ nt35510->base.reset = panel_nt35510_reset;
+ nt35510->base.init = panel_nt35510_init;
+ nt35510->base.draw_bitmap = panel_nt35510_draw_bitmap;
+ nt35510->base.invert_color = panel_nt35510_invert_color;
+ nt35510->base.set_gap = panel_nt35510_set_gap;
+ nt35510->base.mirror = panel_nt35510_mirror;
+ nt35510->base.swap_xy = panel_nt35510_swap_xy;
+ nt35510->base.disp_on_off = panel_nt35510_disp_on_off;
+ nt35510->base.disp_sleep = panel_nt35510_sleep;
+ *ret_panel = &(nt35510->base);
+ ESP_LOGD(TAG, "new nt35510 panel @%p", nt35510);
+
+ return ESP_OK;
+
+err:
+ if (nt35510) {
+ if (panel_dev_config->reset_gpio_num >= 0) {
+ gpio_reset_pin(panel_dev_config->reset_gpio_num);
+ }
+ free(nt35510);
+ }
+ return ret;
+ ESP_COMPILER_DIAGNOSTIC_POP("-Wanalyzer-malloc-leak")
+}
+
+static esp_err_t panel_nt35510_del(esp_lcd_panel_t *panel)
+{
+ nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base);
+
+ if (nt35510->reset_gpio_num >= 0) {
+ gpio_reset_pin(nt35510->reset_gpio_num);
+ }
+ ESP_LOGD(TAG, "del nt35510 panel @%p", nt35510);
+ free(nt35510);
+ return ESP_OK;
+}
+
+static esp_err_t panel_nt35510_reset(esp_lcd_panel_t *panel)
+{
+ nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base);
+ esp_lcd_panel_io_handle_t io = nt35510->io;
+
+ // perform hardware reset
+ if (nt35510->reset_gpio_num >= 0) {
+ gpio_set_level(nt35510->reset_gpio_num, nt35510->reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ gpio_set_level(nt35510->reset_gpio_num, !nt35510->reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ } else {
+ // perform software reset
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET << 8, NULL, 0), TAG,
+ "io tx param failed");
+ vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5m before sending new command
+ }
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_nt35510_init(esp_lcd_panel_t *panel)
+{
+ nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base);
+ esp_lcd_panel_io_handle_t io = nt35510->io;
+ // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT << 8, NULL, 0), TAG,
+ "io tx param failed");;
+ vTaskDelay(pdMS_TO_TICKS(100));
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL << 8, (uint16_t[]) {
+ nt35510->madctl_val,
+ }, 2), TAG, "io tx param failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD << 8, (uint16_t[]) {
+ nt35510->colmod_val,
+ }, 2), TAG, "io tx param failed");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_nt35510_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end,
+ const void *color_data)
+{
+ nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base);
+ esp_lcd_panel_io_handle_t io = nt35510->io;
+
+ x_start += nt35510->x_gap;
+ x_end += nt35510->x_gap;
+ y_start += nt35510->y_gap;
+ y_end += nt35510->y_gap;
+
+ // define an area of frame memory where MCU can access
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, (LCD_CMD_CASET << 8) + 0, (uint16_t[]) {
+ (x_start >> 8) & 0xFF,
+ }, 2), TAG, "io tx param failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, (LCD_CMD_CASET << 8) + 1, (uint16_t[]) {
+ x_start & 0xFF,
+ }, 2), TAG, "io tx param failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, (LCD_CMD_CASET << 8) + 2, (uint16_t[]) {
+ ((x_end - 1) >> 8) & 0xFF,
+ }, 2), TAG, "io tx param failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, (LCD_CMD_CASET << 8) + 3, (uint16_t[]) {
+ (x_end - 1) & 0xFF,
+ }, 2), TAG, "io tx param failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, (LCD_CMD_RASET << 8) + 0, (uint16_t[]) {
+ (y_start >> 8) & 0xFF,
+ }, 2), TAG, "io tx param failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, (LCD_CMD_RASET << 8) + 1, (uint16_t[]) {
+ y_start & 0xFF,
+ }, 2), TAG, "io tx param failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, (LCD_CMD_RASET << 8) + 2, (uint16_t[]) {
+ ((y_end - 1) >> 8) & 0xFF,
+ }, 2), TAG, "io tx param failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, (LCD_CMD_RASET << 8) + 3, (uint16_t[]) {
+ (y_end - 1) & 0xFF,
+ }, 2), TAG, "io tx param failed");
+ // transfer frame buffer
+ size_t len = (x_end - x_start) * (y_end - y_start) * nt35510->fb_bits_per_pixel / 8;
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR << 8, color_data, len), TAG, "io tx color failed");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_nt35510_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
+{
+ nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base);
+ esp_lcd_panel_io_handle_t io = nt35510->io;
+ int command = 0;
+ if (invert_color_data) {
+ command = LCD_CMD_INVON;
+ } else {
+ command = LCD_CMD_INVOFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command << 8, NULL, 0), TAG,
+ "io tx param failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_nt35510_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
+{
+ nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base);
+ esp_lcd_panel_io_handle_t io = nt35510->io;
+ if (mirror_x) {
+ nt35510->madctl_val |= LCD_CMD_MX_BIT;
+ } else {
+ nt35510->madctl_val &= ~LCD_CMD_MX_BIT;
+ }
+ if (mirror_y) {
+ nt35510->madctl_val |= LCD_CMD_MY_BIT;
+ } else {
+ nt35510->madctl_val &= ~LCD_CMD_MY_BIT;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL << 8, (uint16_t[]) {
+ nt35510->madctl_val
+ }, 2), TAG, "io tx param failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_nt35510_swap_xy(esp_lcd_panel_t *panel, bool swap_axes)
+{
+ nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base);
+ esp_lcd_panel_io_handle_t io = nt35510->io;
+ if (swap_axes) {
+ nt35510->madctl_val |= LCD_CMD_MV_BIT;
+ } else {
+ nt35510->madctl_val &= ~LCD_CMD_MV_BIT;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL << 8, (uint16_t[]) {
+ nt35510->madctl_val
+ }, 2), TAG, "io tx param failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_nt35510_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
+{
+ nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base);
+ nt35510->x_gap = x_gap;
+ nt35510->y_gap = y_gap;
+ return ESP_OK;
+}
+
+static esp_err_t panel_nt35510_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
+{
+ nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base);
+ esp_lcd_panel_io_handle_t io = nt35510->io;
+ int command = 0;
+ if (on_off) {
+ command = LCD_CMD_DISPON;
+ } else {
+ command = LCD_CMD_DISPOFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command << 8, NULL, 0), TAG,
+ "io tx param failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_nt35510_sleep(esp_lcd_panel_t *panel, bool sleep)
+{
+ nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base);
+ esp_lcd_panel_io_handle_t io = nt35510->io;
+ int command = 0;
+ if (sleep) {
+ command = LCD_CMD_SLPIN;
+ } else {
+ command = LCD_CMD_SLPOUT;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG,
+ "io tx param failed");
+ vTaskDelay(pdMS_TO_TICKS(100));
+
+ return ESP_OK;
+}
### components/esp_lcd/src/esp_lcd_panel_ops.c
@@ -0,0 +1,92 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "esp_check.h"
+#include "esp_lcd_panel_ops.h"
+#include "esp_lcd_panel_interface.h"
+
+static const char *TAG = "lcd_panel";
+
+esp_err_t esp_lcd_panel_reset(esp_lcd_panel_handle_t panel)
+{
+ ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
+ ESP_RETURN_ON_FALSE(panel->reset, ESP_ERR_NOT_SUPPORTED, TAG, "reset is not supported by this panel");
+ return panel->reset(panel);
+}
+
+esp_err_t esp_lcd_panel_init(esp_lcd_panel_handle_t panel)
+{
+ ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
+ return panel->init(panel);
+}
+
+esp_err_t esp_lcd_panel_del(esp_lcd_panel_handle_t panel)
+{
+ ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
+ return panel->del(panel);
+}
+
+esp_err_t esp_lcd_panel_draw_bitmap(esp_lcd_panel_handle_t panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
+{
+ ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
+ ESP_RETURN_ON_FALSE((x_start < x_end) && (y_start < y_end), ESP_ERR_INVALID_ARG, TAG, "start position must be smaller than end position");
+ ESP_RETURN_ON_FALSE(panel->draw_bitmap, ESP_ERR_NOT_SUPPORTED, TAG, "draw_bitmap is not supported by this panel");
+ return panel->draw_bitmap(panel, x_start, y_start, x_end, y_end, color_data);
+}
+
+esp_err_t esp_lcd_panel_mirror(esp_lcd_panel_handle_t panel, bool mirror_x, bool mirror_y)
+{
+ ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
+ ESP_RETURN_ON_FALSE(panel->mirror, ESP_ERR_NOT_SUPPORTED, TAG, "mirror is not supported by this panel");
+ return panel->mirror(panel, mirror_x, mirror_y);
+}
+
+esp_err_t esp_lcd_panel_swap_xy(esp_lcd_panel_handle_t panel, bool swap_axes)
+{
+ ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
+ ESP_RETURN_ON_FALSE(panel->swap_xy, ESP_ERR_NOT_SUPPORTED, TAG, "swap_xy is not supported by this panel");
+ return panel->swap_xy(panel, swap_axes);
+}
+
+esp_err_t esp_lcd_panel_set_gap(esp_lcd_panel_handle_t panel, int x_gap, int y_gap)
+{
+ ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
+ ESP_RETURN_ON_FALSE(panel->set_gap, ESP_ERR_NOT_SUPPORTED, TAG, "set_gap is not supported by this panel");
+ return panel->set_gap(panel, x_gap, y_gap);
+}
+
+esp_err_t esp_lcd_panel_invert_color(esp_lcd_panel_handle_t panel, bool invert_color_data)
+{
+ ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
+ ESP_RETURN_ON_FALSE(panel->invert_color, ESP_ERR_NOT_SUPPORTED, TAG, "invert_color is not supported by this panel");
+ return panel->invert_color(panel, invert_color_data);
+}
+
+esp_err_t esp_lcd_panel_disp_on_off(esp_lcd_panel_handle_t panel, bool on_off)
+{
+ ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
+ ESP_RETURN_ON_FALSE(panel->disp_on_off, ESP_ERR_NOT_SUPPORTED, TAG, "disp_on_off is not supported by this panel");
+ return panel->disp_on_off(panel, on_off);
+}
+
+esp_err_t esp_lcd_panel_disp_off(esp_lcd_panel_handle_t panel, bool off)
+{
+ return esp_lcd_panel_disp_on_off(panel, !off);
+}
+
+esp_err_t esp_lcd_panel_disp_sleep(esp_lcd_panel_handle_t panel, bool sleep)
+{
+ ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
+ ESP_RETURN_ON_FALSE(panel->disp_sleep, ESP_ERR_NOT_SUPPORTED, TAG, "sleep is not supported by this panel");
+ return panel->disp_sleep(panel, sleep);
+}
+
+esp_err_t esp_lcd_panel_set_brightness(esp_lcd_panel_handle_t panel, int brightness)
+{
+ ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
+ ESP_RETURN_ON_FALSE(panel->set_brightness, ESP_ERR_NOT_SUPPORTED, TAG, "set_brightness is not supported by this panel");
+ return panel->set_brightness(panel, brightness);
+}
### components/esp_lcd/src/esp_lcd_panel_ssd1306.c
@@ -0,0 +1,280 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <sys/cdefs.h>
+#include "sdkconfig.h"
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+// The local log level must be defined before including esp_log.h
+// Set the maximum log level for this source file
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#endif
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_lcd_panel_interface.h"
+#include "esp_lcd_panel_io.h"
+#include "esp_lcd_panel_ssd1306.h"
+#include "esp_lcd_panel_ops.h"
+#include "driver/gpio.h"
+#include "esp_log.h"
+#include "esp_check.h"
+#include "esp_compiler.h"
+
+static const char *TAG = "lcd_panel.ssd1306";
+
+// SSD1306 commands
+#define SSD1306_CMD_SET_MEMORY_ADDR_MODE 0x20
+#define SSD1306_CMD_SET_COLUMN_RANGE 0x21
+#define SSD1306_CMD_SET_PAGE_RANGE 0x22
+#define SSD1306_CMD_SET_CHARGE_PUMP 0x8D
+#define SSD1306_CMD_MIRROR_X_OFF 0xA0
+#define SSD1306_CMD_MIRROR_X_ON 0xA1
+#define SSD1306_CMD_INVERT_OFF 0xA6
+#define SSD1306_CMD_INVERT_ON 0xA7
+#define SSD1306_CMD_SET_MULTIPLEX 0xA8
+#define SSD1306_CMD_DISP_OFF 0xAE
+#define SSD1306_CMD_DISP_ON 0xAF
+#define SSD1306_CMD_MIRROR_Y_OFF 0xC0
+#define SSD1306_CMD_MIRROR_Y_ON 0xC8
+#define SSD1306_CMD_SET_COMPINS 0xDA
+
+static esp_err_t panel_ssd1306_del(esp_lcd_panel_t *panel);
+static esp_err_t panel_ssd1306_reset(esp_lcd_panel_t *panel);
+static esp_err_t panel_ssd1306_init(esp_lcd_panel_t *panel);
+static esp_err_t panel_ssd1306_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
+static esp_err_t panel_ssd1306_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
+static esp_err_t panel_ssd1306_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
+static esp_err_t panel_ssd1306_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
+static esp_err_t panel_ssd1306_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
+static esp_err_t panel_ssd1306_disp_on_off(esp_lcd_panel_t *panel, bool off);
+
+typedef struct {
+ esp_lcd_panel_t base;
+ esp_lcd_panel_io_handle_t io;
+ uint8_t height;
+ int reset_gpio_num;
+ int x_gap;
+ int y_gap;
+ unsigned int bits_per_pixel;
+ bool reset_level;
+ bool swap_axes;
+} ssd1306_panel_t;
+
+esp_err_t esp_lcd_new_panel_ssd1306(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel)
+{
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+ esp_log_level_set(TAG, ESP_LOG_DEBUG);
+#endif
+ esp_err_t ret = ESP_OK;
+ ssd1306_panel_t *ssd1306 = NULL;
+ ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
+ ESP_GOTO_ON_FALSE(panel_dev_config->bits_per_pixel == 1, ESP_ERR_INVALID_ARG, err, TAG, "bpp must be 1");
+ esp_lcd_panel_ssd1306_config_t *ssd1306_spec_config = (esp_lcd_panel_ssd1306_config_t *)panel_dev_config->vendor_config;
+ // leak detection of ssd1306 because saving ssd1306->base address
+ ESP_COMPILER_DIAGNOSTIC_PUSH_IGNORE("-Wanalyzer-malloc-leak")
+ ssd1306 = calloc(1, sizeof(ssd1306_panel_t));
+ ESP_GOTO_ON_FALSE(ssd1306, ESP_ERR_NO_MEM, err, TAG, "no mem for ssd1306 panel");
+
+ if (panel_dev_config->reset_gpio_num >= 0) {
+ gpio_config_t io_conf = {
+ .mode = GPIO_MODE_OUTPUT,
+ .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num,
+ };
+ ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
+ }
+
+ ssd1306->io = io;
+ ssd1306->bits_per_pixel = panel_dev_config->bits_per_pixel;
+ ssd1306->reset_gpio_num = panel_dev_config->reset_gpio_num;
+ ssd1306->reset_level = panel_dev_config->flags.reset_active_high;
+ ssd1306->height = ssd1306_spec_config ? ssd1306_spec_config->height : 64;
+ ssd1306->base.del = panel_ssd1306_del;
+ ssd1306->base.reset = panel_ssd1306_reset;
+ ssd1306->base.init = panel_ssd1306_init;
+ ssd1306->base.draw_bitmap = panel_ssd1306_draw_bitmap;
+ ssd1306->base.invert_color = panel_ssd1306_invert_color;
+ ssd1306->base.set_gap = panel_ssd1306_set_gap;
+ ssd1306->base.mirror = panel_ssd1306_mirror;
+ ssd1306->base.swap_xy = panel_ssd1306_swap_xy;
+ ssd1306->base.disp_on_off = panel_ssd1306_disp_on_off;
+ *ret_panel = &(ssd1306->base);
+ ESP_LOGD(TAG, "new ssd1306 panel @%p", ssd1306);
+
+ return ESP_OK;
+
+err:
+ if (ssd1306) {
+ if (panel_dev_config->reset_gpio_num >= 0) {
+ gpio_reset_pin(panel_dev_config->reset_gpio_num);
+ }
+ free(ssd1306);
+ }
+ return ret;
+ ESP_COMPILER_DIAGNOSTIC_POP("-Wanalyzer-malloc-leak")
+}
+
+static esp_err_t panel_ssd1306_del(esp_lcd_panel_t *panel)
+{
+ ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base);
+ if (ssd1306->reset_gpio_num >= 0) {
+ gpio_reset_pin(ssd1306->reset_gpio_num);
+ }
+ ESP_LOGD(TAG, "del ssd1306 panel @%p", ssd1306);
+ free(ssd1306);
+ return ESP_OK;
+}
+
+static esp_err_t panel_ssd1306_reset(esp_lcd_panel_t *panel)
+{
+ ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base);
+
+ // perform hardware reset
+ if (ssd1306->reset_gpio_num >= 0) {
+ gpio_set_level(ssd1306->reset_gpio_num, ssd1306->reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ gpio_set_level(ssd1306->reset_gpio_num, !ssd1306->reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ }
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_ssd1306_init(esp_lcd_panel_t *panel)
+{
+ ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base);
+ esp_lcd_panel_io_handle_t io = ssd1306->io;
+
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SSD1306_CMD_SET_MULTIPLEX, (uint8_t[]) {
+ ssd1306->height - 1 // set multiplex ratio
+ }, 1), TAG, "io tx param SSD1306_CMD_SET_MULTIPLEX failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SSD1306_CMD_SET_COMPINS, (uint8_t[1]) {
+ ssd1306->height == 64 ? 0x12 : 0x02 // set COM pins hardware configuration
+ }, 1), TAG, "io tx param SSD1306_CMD_SET_COMPINS failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SSD1306_CMD_DISP_OFF, NULL, 0), TAG,
+ "io tx param SSD1306_CMD_DISP_OFF failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SSD1306_CMD_SET_MEMORY_ADDR_MODE, (uint8_t[]) {
+ 0x00 // horizontal addressing mode
+ }, 1), TAG, "io tx param SSD1306_CMD_SET_MEMORY_ADDR_MODE failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SSD1306_CMD_SET_CHARGE_PUMP, (uint8_t[]) {
+ 0x14 // enable charge pump
+ }, 1), TAG, "io tx param SSD1306_CMD_SET_CHARGE_PUMP failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SSD1306_CMD_MIRROR_X_OFF, NULL, 0), TAG,
+ "io tx param SSD1306_CMD_MIRROR_X_OFF failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SSD1306_CMD_MIRROR_Y_OFF, NULL, 0), TAG,
+ "io tx param SSD1306_CMD_MIRROR_Y_OFF failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_ssd1306_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
+{
+ ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base);
+ esp_lcd_panel_io_handle_t io = ssd1306->io;
+
+ // adding extra gap
+ x_start += ssd1306->x_gap;
+ x_end += ssd1306->x_gap;
+ y_start += ssd1306->y_gap;
+ y_end += ssd1306->y_gap;
+
+ if (ssd1306->swap_axes) {
+ int x = x_start;
+ x_start = y_start;
+ y_start = x;
+ x = x_end;
+ x_end = y_end;
+ y_end = x;
+ }
+
+ // one page contains 8 rows (COMs)
+ uint8_t page_start = y_start / 8;
+ uint8_t page_end = (y_end - 1) / 8;
+ // define an area of frame memory where MCU can access
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SSD1306_CMD_SET_COLUMN_RANGE, (uint8_t[]) {
+ (x_start & 0x7F),
+ ((x_end - 1) & 0x7F),
+ }, 2), TAG, "io tx param SSD1306_CMD_SET_COLUMN_RANGE failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SSD1306_CMD_SET_PAGE_RANGE, (uint8_t[]) {
+ (page_start & 0x07),
+ (page_end & 0x07),
+ }, 2), TAG, "io tx param SSD1306_CMD_SET_PAGE_RANGE failed");
+ // transfer frame buffer
+ size_t len = (y_end - y_start) * (x_end - x_start) * ssd1306->bits_per_pixel / 8;
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, -1, color_data, len), TAG, "io tx color failed");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_ssd1306_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
+{
+ ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base);
+ esp_lcd_panel_io_handle_t io = ssd1306->io;
+ int command = 0;
+ if (invert_color_data) {
+ command = SSD1306_CMD_INVERT_ON;
+ } else {
+ command = SSD1306_CMD_INVERT_OFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG,
+ "io tx param SSD1306_CMD_INVERT_ON/OFF failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_ssd1306_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
+{
+ ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base);
+ esp_lcd_panel_io_handle_t io = ssd1306->io;
+
+ int command = 0;
+ if (mirror_x) {
+ command = SSD1306_CMD_MIRROR_X_ON;
+ } else {
+ command = SSD1306_CMD_MIRROR_X_OFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG,
+ "io tx param SSD1306_CMD_MIRROR_X_ON/OFF failed");
+ if (mirror_y) {
+ command = SSD1306_CMD_MIRROR_Y_ON;
+ } else {
+ command = SSD1306_CMD_MIRROR_Y_OFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG,
+ "io tx param SSD1306_CMD_MIRROR_Y_ON/OFF failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_ssd1306_swap_xy(esp_lcd_panel_t *panel, bool swap_axes)
+{
+ ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base);
+ ssd1306->swap_axes = swap_axes;
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_ssd1306_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
+{
+ ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base);
+ ssd1306->x_gap = x_gap;
+ ssd1306->y_gap = y_gap;
+ return ESP_OK;
+}
+
+static esp_err_t panel_ssd1306_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
+{
+ ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base);
+ esp_lcd_panel_io_handle_t io = ssd1306->io;
+ int command = 0;
+ if (on_off) {
+ command = SSD1306_CMD_DISP_ON;
+ } else {
+ command = SSD1306_CMD_DISP_OFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG,
+ "io tx param SSD1306_CMD_DISP_ON/OFF failed");
+ // SEG/COM will be ON/OFF after 100ms after sending DISP_ON/OFF command
+ vTaskDelay(pdMS_TO_TICKS(100));
+ return ESP_OK;
+}
### components/esp_lcd/src/esp_lcd_panel_st7789.c
@@ -0,0 +1,334 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdlib.h>
+#include <sys/cdefs.h>
+#include "sdkconfig.h"
+
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+// The local log level must be defined before including esp_log.h
+// Set the maximum log level for this source file
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#endif
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_lcd_panel_interface.h"
+#include "esp_lcd_panel_io.h"
+#include "esp_lcd_panel_vendor.h"
+#include "esp_lcd_panel_ops.h"
+#include "esp_lcd_panel_commands.h"
+#include "driver/gpio.h"
+#include "esp_log.h"
+#include "esp_check.h"
+#include "esp_compiler.h"
+
+#define ST7789_CMD_RAMCTRL 0xb0
+#define ST7789_DATA_LITTLE_ENDIAN_BIT (1 << 3)
+
+static const char *TAG = "lcd_panel.st7789";
+
+static esp_err_t panel_st7789_del(esp_lcd_panel_t *panel);
+static esp_err_t panel_st7789_reset(esp_lcd_panel_t *panel);
+static esp_err_t panel_st7789_init(esp_lcd_panel_t *panel);
+static esp_err_t panel_st7789_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end,
+ const void *color_data);
+static esp_err_t panel_st7789_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
+static esp_err_t panel_st7789_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
+static esp_err_t panel_st7789_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
+static esp_err_t panel_st7789_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
+static esp_err_t panel_st7789_set_brightness(esp_lcd_panel_t *panel, int brightness);
+static esp_err_t panel_st7789_disp_on_off(esp_lcd_panel_t *panel, bool off);
+static esp_err_t panel_st7789_sleep(esp_lcd_panel_t *panel, bool sleep);
+
+typedef struct {
+ esp_lcd_panel_t base;
+ esp_lcd_panel_io_handle_t io;
+ int reset_gpio_num;
+ bool reset_level;
+ int x_gap;
+ int y_gap;
+ uint8_t fb_bits_per_pixel;
+ uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register
+ uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register
+ uint8_t ramctl_val_1;
+ uint8_t ramctl_val_2;
+} st7789_panel_t;
+
+esp_err_t
+esp_lcd_new_panel_st7789(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
+ esp_lcd_panel_handle_t *ret_panel)
+{
+#if CONFIG_LCD_ENABLE_DEBUG_LOG
+ esp_log_level_set(TAG, ESP_LOG_DEBUG);
+#endif
+ esp_err_t ret = ESP_OK;
+ st7789_panel_t *st7789 = NULL;
+ ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
+ // leak detection of st7789 because saving st7789->base address
+ ESP_COMPILER_DIAGNOSTIC_PUSH_IGNORE("-Wanalyzer-malloc-leak")
+ st7789 = calloc(1, sizeof(st7789_panel_t));
+ ESP_GOTO_ON_FALSE(st7789, ESP_ERR_NO_MEM, err, TAG, "no mem for st7789 panel");
+
+ if (panel_dev_config->reset_gpio_num >= 0) {
+ gpio_config_t io_conf = {
+ .mode = GPIO_MODE_OUTPUT,
+ .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num,
+ };
+ ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
+ }
+
+ switch (panel_dev_config->rgb_ele_order) {
+ case LCD_RGB_ELEMENT_ORDER_RGB:
+ st7789->madctl_val = 0;
+ break;
+ case LCD_RGB_ELEMENT_ORDER_BGR:
+ st7789->madctl_val |= LCD_CMD_BGR_BIT;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported RGB element order");
+ break;
+ }
+
+ uint8_t fb_bits_per_pixel = 0;
+ switch (panel_dev_config->bits_per_pixel) {
+ case 16: // RGB565
+ st7789->colmod_val = 0x55;
+ fb_bits_per_pixel = 16;
+ break;
+ case 18: // RGB666
+ st7789->colmod_val = 0x66;
+ // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel
+ fb_bits_per_pixel = 24;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
+ break;
+ }
+
+ st7789->ramctl_val_1 = 0x00;
+ st7789->ramctl_val_2 = 0xf0; // Use big endian by default
+ if ((panel_dev_config->data_endian) == LCD_RGB_DATA_ENDIAN_LITTLE) {
+ // Use little endian
+ st7789->ramctl_val_2 |= ST7789_DATA_LITTLE_ENDIAN_BIT;
+ }
+
+ st7789->io = io;
+ st7789->fb_bits_per_pixel = fb_bits_per_pixel;
+ st7789->reset_gpio_num = panel_dev_config->reset_gpio_num;
+ st7789->reset_level = panel_dev_config->flags.reset_active_high;
+ st7789->base.del = panel_st7789_del;
+ st7789->base.reset = panel_st7789_reset;
+ st7789->base.init = panel_st7789_init;
+ st7789->base.draw_bitmap = panel_st7789_draw_bitmap;
+ st7789->base.invert_color = panel_st7789_invert_color;
+ st7789->base.set_gap = panel_st7789_set_gap;
+ st7789->base.set_brightness = panel_st7789_set_brightness;
+ st7789->base.mirror = panel_st7789_mirror;
+ st7789->base.swap_xy = panel_st7789_swap_xy;
+ st7789->base.disp_on_off = panel_st7789_disp_on_off;
+ st7789->base.disp_sleep = panel_st7789_sleep;
+ *ret_panel = &(st7789->base);
+ ESP_LOGD(TAG, "new st7789 panel @%p", st7789);
+
+ return ESP_OK;
+
+err:
+ if (st7789) {
+ if (panel_dev_config->reset_gpio_num >= 0) {
+ gpio_reset_pin(panel_dev_config->reset_gpio_num);
+ }
+ free(st7789);
+ }
+ return ret;
+ ESP_COMPILER_DIAGNOSTIC_POP("-Wanalyzer-malloc-leak")
+}
+
+static esp_err_t panel_st7789_del(esp_lcd_panel_t *panel)
+{
+ st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
+
+ if (st7789->reset_gpio_num >= 0) {
+ gpio_reset_pin(st7789->reset_gpio_num);
+ }
+ ESP_LOGD(TAG, "del st7789 panel @%p", st7789);
+ free(st7789);
+ return ESP_OK;
+}
+
+static esp_err_t panel_st7789_reset(esp_lcd_panel_t *panel)
+{
+ st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
+ esp_lcd_panel_io_handle_t io = st7789->io;
+
+ // perform hardware reset
+ if (st7789->reset_gpio_num >= 0) {
+ gpio_set_level(st7789->reset_gpio_num, st7789->reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ gpio_set_level(st7789->reset_gpio_num, !st7789->reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ } else { // perform software reset
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG,
+ "io tx param failed");
+ vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5m before sending new command
+ }
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_st7789_init(esp_lcd_panel_t *panel)
+{
+ st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
+ esp_lcd_panel_io_handle_t io = st7789->io;
+ // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG,
+ "io tx param failed");
+ vTaskDelay(pdMS_TO_TICKS(100));
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
+ st7789->madctl_val,
+ }, 1), TAG, "io tx param failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]) {
+ st7789->colmod_val,
+ }, 1), TAG, "io tx param failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7789_CMD_RAMCTRL, (uint8_t[]) {
+ st7789->ramctl_val_1, st7789->ramctl_val_2
+ }, 2), TAG, "io tx param failed");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_st7789_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end,
+ const void *color_data)
+{
+ st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
+ esp_lcd_panel_io_handle_t io = st7789->io;
+
+ x_start += st7789->x_gap;
+ x_end += st7789->x_gap;
+ y_start += st7789->y_gap;
+ y_end += st7789->y_gap;
+
+ // define an area of frame memory where MCU can access
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]) {
+ (x_start >> 8) & 0xFF,
+ x_start & 0xFF,
+ ((x_end - 1) >> 8) & 0xFF,
+ (x_end - 1) & 0xFF,
+ }, 4), TAG, "io tx param failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]) {
+ (y_start >> 8) & 0xFF,
+ y_start & 0xFF,
+ ((y_end - 1) >> 8) & 0xFF,
+ (y_end - 1) & 0xFF,
+ }, 4), TAG, "io tx param failed");
+ // transfer frame buffer
+ size_t len = (x_end - x_start) * (y_end - y_start) * st7789->fb_bits_per_pixel / 8;
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len), TAG, "io tx color failed");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_st7789_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
+{
+ st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
+ esp_lcd_panel_io_handle_t io = st7789->io;
+ int command = 0;
+ if (invert_color_data) {
+ command = LCD_CMD_INVON;
+ } else {
+ command = LCD_CMD_INVOFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG,
+ "io tx param failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_st7789_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
+{
+ st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
+ esp_lcd_panel_io_handle_t io = st7789->io;
+ if (mirror_x) {
+ st7789->madctl_val |= LCD_CMD_MX_BIT;
+ } else {
+ st7789->madctl_val &= ~LCD_CMD_MX_BIT;
+ }
+ if (mirror_y) {
+ st7789->madctl_val |= LCD_CMD_MY_BIT;
+ } else {
+ st7789->madctl_val &= ~LCD_CMD_MY_BIT;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
+ st7789->madctl_val
+ }, 1), TAG, "io tx param failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_st7789_swap_xy(esp_lcd_panel_t *panel, bool swap_axes)
+{
+ st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
+ esp_lcd_panel_io_handle_t io = st7789->io;
+ if (swap_axes) {
+ st7789->madctl_val |= LCD_CMD_MV_BIT;
+ } else {
+ st7789->madctl_val &= ~LCD_CMD_MV_BIT;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
+ st7789->madctl_val
+ }, 1), TAG, "io tx param failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_st7789_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
+{
+ st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
+ st7789->x_gap = x_gap;
+ st7789->y_gap = y_gap;
+ return ESP_OK;
+}
+
+static esp_err_t panel_st7789_set_brightness(esp_lcd_panel_t *panel, int brightness)
+{
+ st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
+ esp_lcd_panel_io_handle_t io = st7789->io;
+ ESP_RETURN_ON_FALSE(brightness >= 0 && brightness <= 0xFF, ESP_ERR_INVALID_ARG, TAG,
+ "brightness out of range");
+ uint8_t brightness_value = (uint8_t)brightness;
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_WRDISBV, &brightness_value, 1), TAG,
+ "io tx param failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_st7789_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
+{
+ st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
+ esp_lcd_panel_io_handle_t io = st7789->io;
+ int command = 0;
+ if (on_off) {
+ command = LCD_CMD_DISPON;
+ } else {
+ command = LCD_CMD_DISPOFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG,
+ "io tx param failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_st7789_sleep(esp_lcd_panel_t *panel, bool sleep)
+{
+ st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
+ esp_lcd_panel_io_handle_t io = st7789->io;
+ int command = 0;
+ if (sleep) {
+ command = LCD_CMD_SLPIN;
+ } else {
+ command = LCD_CMD_SLPOUT;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG,
+ "io tx param failed");
+ vTaskDelay(pdMS_TO_TICKS(100));
+
+ return ESP_OK;
+}Why this scored 17/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.