From 7013815ea783fa8b5e6ef5d3c8a1563f095bfa64 Mon Sep 17 00:00:00 2001 From: morris Date: Wed, 20 Dec 2023 13:28:00 +0800 Subject: [PATCH] feat(dsi): add mipi dsi driver to esp_lcd --- components/esp_lcd/CMakeLists.txt | 5 + components/esp_lcd/README.md | 20 ++ components/esp_lcd/dsi/esp_lcd_mipi_dsi_bus.c | 130 +++++++ components/esp_lcd/dsi/esp_lcd_panel_dpi.c | 335 ++++++++++++++++++ components/esp_lcd/dsi/esp_lcd_panel_io_dbi.c | 95 +++++ .../esp_lcd/dsi/include/esp_lcd_mipi_dsi.h | 118 ++++++ components/esp_lcd/dsi/mipi_dsi_priv.h | 38 ++ components/esp_lcd/include/esp_lcd_types.h | 17 +- .../esp32p4/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32p4/include/soc/soc_caps.h | 2 +- 10 files changed, 762 insertions(+), 2 deletions(-) create mode 100644 components/esp_lcd/dsi/esp_lcd_mipi_dsi_bus.c create mode 100644 components/esp_lcd/dsi/esp_lcd_panel_dpi.c create mode 100644 components/esp_lcd/dsi/esp_lcd_panel_io_dbi.c create mode 100644 components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h create mode 100644 components/esp_lcd/dsi/mipi_dsi_priv.h diff --git a/components/esp_lcd/CMakeLists.txt b/components/esp_lcd/CMakeLists.txt index bffe85979f..6010623ef4 100644 --- a/components/esp_lcd/CMakeLists.txt +++ b/components/esp_lcd/CMakeLists.txt @@ -31,6 +31,11 @@ if(CONFIG_SOC_LCDCAM_SUPPORTED) list(APPEND srcs "i80/esp_lcd_panel_io_i80.c" "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" diff --git a/components/esp_lcd/README.md b/components/esp_lcd/README.md index add8ed69b7..bd3e933ea6 100644 --- a/components/esp_lcd/README.md +++ b/components/esp_lcd/README.md @@ -60,6 +60,26 @@ classDiagram -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 diff --git a/components/esp_lcd/dsi/esp_lcd_mipi_dsi_bus.c b/components/esp_lcd/dsi/esp_lcd_mipi_dsi_bus.c new file mode 100644 index 0000000000..676702d401 --- /dev/null +++ b/components/esp_lcd/dsi/esp_lcd_mipi_dsi_bus.c @@ -0,0 +1,130 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "soc/soc_caps.h" +#include "esp_check.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_clk_tree.h" +#include "mipi_dsi_priv.h" + +static const char *TAG = "lcd.dsi.bus"; + +#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 10 + +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->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 %"PRIu32, 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 + DSI_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_clock_source_t phy_clk_src = bus_config->phy_clk_src; + if (phy_clk_src == 0) { + phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT; + } + // enable the clock source for DSI PHY + DSI_CLOCK_SRC_ATOMIC() { + // set clock source for DSI PHY + mipi_dsi_ll_set_phy_clock_source(bus_id, phy_clk_src); + // the configuration clock is used for all modes except the shutdown mode + mipi_dsi_ll_enable_phy_config_clock(bus_id, true); + // enable the clock for generating the serial clock + mipi_dsi_ll_enable_phy_reference_clock(bus_id, true); + } + + // 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 = bus_config->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_stoped(hal->host)) { + 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 + mipi_dsi_host_ll_enable_tx_eotp(hal->host, true, true); + + // 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, bus_config->lane_bit_rate_mbps / 8 / 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, bus_config->lane_bit_rate_mbps / 8 / 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 + DSI_CLOCK_SRC_ATOMIC() { + mipi_dsi_ll_enable_phy_reference_clock(bus_id, false); + mipi_dsi_ll_enable_phy_config_clock(bus_id, false); + } + // disable the APB clock for accessing the DSI peripheral registers + DSI_RCC_ATOMIC() { + mipi_dsi_ll_enable_bus_clock(bus_id, false); + } + free(bus); + return ESP_OK; +} diff --git a/components/esp_lcd/dsi/esp_lcd_panel_dpi.c b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c new file mode 100644 index 0000000000..418b7d58ba --- /dev/null +++ b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c @@ -0,0 +1,335 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "soc/soc_caps.h" +#include "esp_check.h" +#include "esp_lcd_panel_interface.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_clk_tree.h" +#include "esp_cache.h" +#include "mipi_dsi_priv.h" +#include "esp_private/dw_gdma.h" +#include "hal/cache_hal.h" +#include "hal/cache_ll.h" + +static const char *TAG = "lcd.dsi.dpi"; + +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 + void *frame_buffer; // Frame buffer + uint32_t h_pixels; // Horizontal pixels + uint32_t v_pixels; // Vertical pixels + size_t frame_buffer_size; // Frame buffer size + size_t bytes_per_pixel; // Bytes per pixel + dw_gdma_channel_handle_t dma_chan; // DMA channel + dw_gdma_link_list_handle_t link_list; // DMA link list +}; + +static bool dma_list_invalid_block_cb(dw_gdma_channel_handle_t chan, const dw_gdma_break_event_data_t *event_data, void *user_data) +{ + dw_gdma_lli_handle_t lli = event_data->invalid_lli; + dw_gdma_block_markers_t markers = { + .is_valid = true, // mark the block as valid so that the DMA can continue the transfer + }; + dw_gdma_lli_set_block_markers(lli, markers); + // after the item is marked as valid again, tell the DMA to continue the transfer + dw_gdma_channel_continue(chan); + return false; +} + +static esp_err_t dpi_panel_create_dma_link(esp_lcd_dpi_panel_t *dpi_panel) +{ + esp_err_t ret = ESP_OK; + 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_DST, // the DSI bridge as the DMA flow controller + .chan_priority = 1, + }; + ESP_GOTO_ON_ERROR(dw_gdma_new_channel(&dma_alloc_config, &dma_chan), err, TAG, "create DMA channel failed"); + + // create a linked list for the DMA channel + dw_gdma_link_list_config_t link_list_config = { + .num_items = 1, // Assume one link item can carry the whole image + .link_type = DW_GDMA_LINKED_LIST_TYPE_CIRCULAR, + }; + ESP_GOTO_ON_ERROR(dw_gdma_new_link_list(&link_list_config, &link_list), err, TAG, "create DMA link list failed"); + + // register DMA ISR callbacks + dw_gdma_event_callbacks_t dsi_dma_cbs = { + .on_invalid_block = dma_list_invalid_block_cb, + }; + ESP_GOTO_ON_ERROR(dw_gdma_channel_register_event_callbacks(dma_chan, &dsi_dma_cbs, NULL), err, TAG, "register DMA callbacks failed"); + + dpi_panel->dma_chan = dma_chan; + dpi_panel->link_list = link_list; + return ESP_OK; +err: + if (dma_chan) { + dw_gdma_del_channel(dma_chan); + } + if (link_list) { + dw_gdma_del_link_list(link_list); + } + return ret; +} + +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; + void *frame_buffer = NULL; + esp_lcd_dpi_panel_t *dpi_panel = 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, ESP_ERR_INVALID_ARG, TAG, "invalid DPI clock frequency %"PRIu32, panel_config->dpi_clock_freq_mhz); + 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->bus = bus; + + // allocate frame buffer from PSRAM + size_t bytes_per_pixel = 0; + switch (panel_config->pixel_format) { + case LCD_COLOR_PIXEL_FORMAT_RGB565: + bytes_per_pixel = 2; + break; + case LCD_COLOR_PIXEL_FORMAT_RGB666: + bytes_per_pixel = 3; + break; + case LCD_COLOR_PIXEL_FORMAT_RGB888: + bytes_per_pixel = 3; + break; + } + uint32_t cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA); + // DMA doesn't have requirement on the buffer alignment, but the cache does + uint32_t alignment = cache_line_size; + size_t frame_buffer_size = panel_config->video_timing.h_size * panel_config->video_timing.v_size * bytes_per_pixel; + frame_buffer = heap_caps_aligned_calloc(alignment, 1, frame_buffer_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + ESP_GOTO_ON_FALSE(frame_buffer, ESP_ERR_NO_MEM, err, TAG, "no memory for frame buffer"); + dpi_panel->frame_buffer = frame_buffer; + dpi_panel->frame_buffer_size = frame_buffer_size; + dpi_panel->bytes_per_pixel = bytes_per_pixel; + dpi_panel->h_pixels = panel_config->video_timing.h_size; + dpi_panel->v_pixels = panel_config->video_timing.v_size; + // preset the frame buffer with black color + ESP_GOTO_ON_ERROR(esp_cache_msync(frame_buffer, frame_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M), err, TAG, "cache write back failed"); + + // 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 + uint32_t dpi_div = mipi_dsi_hal_host_dpi_calculate_divider(hal, dpi_clk_src_freq_hz / 1000 / 1000, panel_config->dpi_clock_freq_mhz); + // set the clock source, set the divider, and enable the dpi clock + DSI_CLOCK_SRC_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); + } + + // 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_hal_host_dpi_set_color_coding(hal, panel_config->pixel_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); + // 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); + // after sending a frame, the DSI device should return an ack + mipi_dsi_host_ll_dpi_enable_frame_ack(hal->host, true); + // commands are transmitted in low-power mode + mipi_dsi_host_ll_dpi_enable_lp_command(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 * 16); + mipi_dsi_brg_ll_set_underrun_discard_count(hal->bridge, panel_config->video_timing.h_size); + // let the DSI bridge as the DMA flow controller + mipi_dsi_brg_ll_set_flow_controller(hal->bridge, MIPI_DSI_LL_FLOW_CONTROLLER_BRIDGE); + 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, "new dpi panel @%p, fb@%p", dpi_panel, dpi_panel->frame_buffer); + return ESP_OK; +err: + if (dpi_panel) { + dpi_panel_del(&dpi_panel->base); + } + if (frame_buffer) { + free(frame_buffer); + } + 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 + DSI_CLOCK_SRC_ATOMIC() { + mipi_dsi_ll_enable_dpi_clock(bus_id, false); + } + // disable the DSI bridge + mipi_dsi_brg_ll_enable(hal->bridge, false); + if (dpi_panel->dma_chan) { + dw_gdma_del_channel(dpi_panel->dma_chan); + } + if (dpi_panel->frame_buffer) { + free(dpi_panel->frame_buffer); + } + if (dpi_panel->link_list) { + dw_gdma_del_link_list(dpi_panel->link_list); + } + free(dpi_panel); + 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 = dpi_panel->link_list; + + ESP_RETURN_ON_ERROR(dw_gdma_channel_use_link_list(dma_chan, link_list), TAG, "use DMA link list failed"); + dw_gdma_block_transfer_config_t dma_transfer_config = { + .src = { + .addr = (uint32_t)(dpi_panel->frame_buffer), + .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_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->frame_buffer_size * 8 / 64, + }; + 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, + }; + dw_gdma_lli_set_block_markers(dw_gdma_link_list_get_item(link_list, 0), markers); + 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); + + 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 *frame_buffer = dpi_panel->frame_buffer; + size_t frame_buffer_size = dpi_panel->frame_buffer_size; + + // TODO: memory copy by 2D-DMA + size_t bytes_per_pixel = dpi_panel->bytes_per_pixel; + const uint8_t *from = (const uint8_t *)color_data; + uint8_t *to = frame_buffer + (y_start * dpi_panel->h_pixels + x_start) * bytes_per_pixel; + uint32_t copy_bytes_per_line = (x_end - x_start) * bytes_per_pixel; + uint32_t bytes_per_line = bytes_per_pixel * dpi_panel->h_pixels; + 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; + } + ESP_RETURN_ON_ERROR(esp_cache_msync(frame_buffer, frame_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M), TAG, "cache write back failed"); + + return ESP_OK; +} + +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) { + // reenable 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; +} diff --git a/components/esp_lcd/dsi/esp_lcd_panel_io_dbi.c b/components/esp_lcd/dsi/esp_lcd_panel_io_dbi.c new file mode 100644 index 0000000000..369776958d --- /dev/null +++ b/components/esp_lcd/dsi/esp_lcd_panel_io_dbi.c @@ -0,0 +1,95 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "soc/soc_caps.h" +#include "esp_check.h" +#include "esp_lcd_panel_io_interface.h" +#include "esp_lcd_mipi_dsi.h" +#include "mipi_dsi_priv.h" + +static const char *TAG = "lcd.dsi.dbi"; + +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; +} diff --git a/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h b/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h new file mode 100644 index 0000000000..9edaa138af --- /dev/null +++ b/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h @@ -0,0 +1,118 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include +#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 */ + mipi_dsi_phy_clock_source_t phy_clk_src; /*!< MIPI DSI PHY clock source */ + uint32_t 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 */ + uint32_t dpi_clock_freq_mhz; /*!< DPI clock frequency in MHz */ + lcd_color_rgb_pixel_format_t pixel_format; /*!< Pixel format */ + esp_lcd_video_timing_t video_timing; /*!< Video timing */ +} 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_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 Set pre-defined pattern to the screen for testing or debugging purpose + * + * @param[in] dbi_panel MIPI DBI panel handle, returned from `esp_lcd_new_panel_dpi` + * @param[in] 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 dbi_panel, mipi_dsi_pattern_type_t pattern); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_lcd/dsi/mipi_dsi_priv.h b/components/esp_lcd/dsi/mipi_dsi_priv.h new file mode 100644 index 0000000000..f16e0d3fb7 --- /dev/null +++ b/components/esp_lcd/dsi/mipi_dsi_priv.h @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "hal/mipi_dsi_hal.h" +#include "hal/mipi_dsi_ll.h" +#include "esp_heap_caps.h" +#include "esp_private/periph_ctrl.h" + +#if SOC_PERIPH_CLK_CTRL_SHARED +#define DSI_CLOCK_SRC_ATOMIC() PERIPH_RCC_ATOMIC() +#else +#define DSI_CLOCK_SRC_ATOMIC() +#endif + +#if !SOC_RCC_IS_INDEPENDENT +#define DSI_RCC_ATOMIC() PERIPH_RCC_ATOMIC() +#else +#define DSI_RCC_ATOMIC() +#endif + +#define DSI_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct esp_lcd_dsi_bus_t { + int bus_id; + mipi_dsi_hal_context_t hal; +} esp_lcd_dsi_bus_t; + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_lcd/include/esp_lcd_types.h b/components/esp_lcd/include/esp_lcd_types.h index 6c6856d838..951a4b8243 100644 --- a/components/esp_lcd/include/esp_lcd_types.h +++ b/components/esp_lcd/include/esp_lcd_types.h @@ -5,13 +5,28 @@ */ #pragma once -#include "hal/lcd_types.h" #include "esp_assert.h" +#include "hal/lcd_types.h" +#include "hal/mipi_dsi_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 */ diff --git a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in index 528f21af38..b2fb0c346f 100644 --- a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in @@ -35,6 +35,10 @@ config SOC_PCNT_SUPPORTED bool default y +config SOC_MIPI_DSI_SUPPORTED + bool + default y + config SOC_MCPWM_SUPPORTED bool default y diff --git a/components/soc/esp32p4/include/soc/soc_caps.h b/components/soc/esp32p4/include/soc/soc_caps.h index 34d7ead08a..0358f666a3 100644 --- a/components/soc/esp32p4/include/soc/soc_caps.h +++ b/components/soc/esp32p4/include/soc/soc_caps.h @@ -28,7 +28,7 @@ #define SOC_GPTIMER_SUPPORTED 1 #define SOC_PCNT_SUPPORTED 1 // #define SOC_LCDCAM_SUPPORTED 1 // TODO: IDF-7465 -// #define SOC_MIPI_DSI_SUPPORTED 1 // TODO: IDF-7085 +#define SOC_MIPI_DSI_SUPPORTED 1 #define SOC_MCPWM_SUPPORTED 1 #define SOC_TWAI_SUPPORTED 1 #define SOC_ETM_SUPPORTED 1