diff --git a/components/driver/gdma.c b/components/driver/gdma.c index 8e2e9fd027..974bb0c48c 100644 --- a/components/driver/gdma.c +++ b/components/driver/gdma.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -26,19 +26,17 @@ static const char *TAG = "gdma"; -#if CONFIG_GDMA_ISR_IRAM_SAFE -#define GDMA_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED) +#if CONFIG_GDMA_ISR_IRAM_SAFE || CONFIG_GDMA_CTRL_FUNC_IN_IRAM #define GDMA_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) #else -#define GDMA_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED #define GDMA_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT -#endif // CONFIG_GDMA_ISR_IRAM_SAFE +#endif -#if CONFIG_GDMA_CTRL_FUNC_IN_IRAM -#define GDMA_CTRL_FUNC_ATTR IRAM_ATTR +#if CONFIG_GDMA_ISR_IRAM_SAFE +#define GDMA_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED) #else -#define GDMA_CTRL_FUNC_ATTR -#endif // CONFIG_GDMA_CTRL_FUNC_IN_IRAM +#define GDMA_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED +#endif #define GDMA_INVALID_PERIPH_TRIG (0x3F) #define SEARCH_REQUEST_RX_CHANNEL (1 << 0) @@ -109,11 +107,9 @@ struct gdma_rx_channel_t { }; static gdma_group_t *gdma_acquire_group_handle(int group_id); -static void gdma_release_group_handle(gdma_group_t *group); static gdma_pair_t *gdma_acquire_pair_handle(gdma_group_t *group, int pair_id); +static void gdma_release_group_handle(gdma_group_t *group); static void gdma_release_pair_handle(gdma_pair_t *pair); -static void gdma_uninstall_group(gdma_group_t *group); -static void gdma_uninstall_pair(gdma_pair_t *pair); static esp_err_t gdma_del_tx_channel(gdma_channel_t *dma_channel); static esp_err_t gdma_del_rx_channel(gdma_channel_t *dma_channel); static esp_err_t gdma_install_rx_interrupt(gdma_rx_channel_t *rx_chan); @@ -161,27 +157,28 @@ esp_err_t gdma_new_channel(const gdma_channel_alloc_config_t *config, gdma_chann for (int i = 0; i < SOC_GDMA_GROUPS && search_code; i++) { // loop to search group group = gdma_acquire_group_handle(i); - for (int j = 0; j < SOC_GDMA_PAIRS_PER_GROUP && search_code && group; j++) { // loop to search pair + ESP_GOTO_ON_FALSE(group, ESP_ERR_NO_MEM, err, TAG, "no mem for group(%d)", i); + for (int j = 0; j < SOC_GDMA_PAIRS_PER_GROUP && search_code; j++) { // loop to search pair pair = gdma_acquire_pair_handle(group, j); - if (pair) { - portENTER_CRITICAL(&pair->spinlock); - if (!(search_code & pair->occupy_code)) { // pair has suitable position for acquired channel(s) - pair->occupy_code |= search_code; - search_code = 0; // exit search loop - } - portEXIT_CRITICAL(&pair->spinlock); - if (!search_code) { - portENTER_CRITICAL(&group->spinlock); - group->pair_ref_counts[j]++; // channel obtains a reference to pair - portEXIT_CRITICAL(&group->spinlock); - } + ESP_GOTO_ON_FALSE(pair, ESP_ERR_NO_MEM, err, TAG, "no mem for pair(%d,%d)", i, j); + portENTER_CRITICAL(&pair->spinlock); + if (!(search_code & pair->occupy_code)) { // pair has suitable position for acquired channel(s) + pair->occupy_code |= search_code; + search_code = 0; // exit search loop + } + portEXIT_CRITICAL(&pair->spinlock); + if (search_code) { + gdma_release_pair_handle(pair); + pair = NULL; } - gdma_release_pair_handle(pair); } // loop used to search pair - gdma_release_group_handle(group); + if (search_code) { + gdma_release_group_handle(group); + group = NULL; + } } // loop used to search group ESP_GOTO_ON_FALSE(search_code == 0, ESP_ERR_NOT_FOUND, err, TAG, "no free gdma channel, search code=%d", search_code); - + assert(pair && group); // pair and group handle shouldn't be NULL search_done: // register TX channel if (alloc_tx_channel) { @@ -214,6 +211,12 @@ err: if (alloc_rx_channel) { free(alloc_rx_channel); } + if (pair) { + gdma_release_pair_handle(pair); + } + if (group) { + gdma_release_group_handle(group); + } return ret; } @@ -386,9 +389,7 @@ esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_ ESP_GOTO_ON_FALSE(esp_ptr_in_iram(cbs->on_trans_eof), ESP_ERR_INVALID_ARG, err, TAG, "on_trans_eof not in IRAM"); } if (user_data) { - ESP_GOTO_ON_FALSE(esp_ptr_in_dram(user_data) || - esp_ptr_in_diram_dram(user_data) || - esp_ptr_in_rtc_dram_fast(user_data), ESP_ERR_INVALID_ARG, err, TAG, "user context not in DRAM"); + ESP_GOTO_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, err, TAG, "user context not in internal RAM"); } #endif // CONFIG_GDMA_ISR_IRAM_SAFE @@ -424,9 +425,7 @@ esp_err_t gdma_register_rx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_ ESP_GOTO_ON_FALSE(esp_ptr_in_iram(cbs->on_recv_eof), ESP_ERR_INVALID_ARG, err, TAG, "on_recv_eof not in IRAM"); } if (user_data) { - ESP_GOTO_ON_FALSE(esp_ptr_in_dram(user_data) || - esp_ptr_in_diram_dram(user_data) || - esp_ptr_in_rtc_dram_fast(user_data), ESP_ERR_INVALID_ARG, err, TAG, "user context not in DRAM"); + ESP_GOTO_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, err, TAG, "user context not in internal RAM"); } #endif // CONFIG_GDMA_ISR_IRAM_SAFE @@ -447,7 +446,7 @@ err: return ret; } -GDMA_CTRL_FUNC_ATTR esp_err_t gdma_start(gdma_channel_handle_t dma_chan, intptr_t desc_base_addr) +esp_err_t gdma_start(gdma_channel_handle_t dma_chan, intptr_t desc_base_addr) { esp_err_t ret = ESP_OK; gdma_pair_t *pair = NULL; @@ -468,7 +467,7 @@ err: return ret; } -GDMA_CTRL_FUNC_ATTR esp_err_t gdma_stop(gdma_channel_handle_t dma_chan) +esp_err_t gdma_stop(gdma_channel_handle_t dma_chan) { esp_err_t ret = ESP_OK; gdma_pair_t *pair = NULL; @@ -487,7 +486,7 @@ err: return ret; } -GDMA_CTRL_FUNC_ATTR esp_err_t gdma_append(gdma_channel_handle_t dma_chan) +esp_err_t gdma_append(gdma_channel_handle_t dma_chan) { esp_err_t ret = ESP_OK; gdma_pair_t *pair = NULL; @@ -506,7 +505,7 @@ err: return ret; } -GDMA_CTRL_FUNC_ATTR esp_err_t gdma_reset(gdma_channel_handle_t dma_chan) +esp_err_t gdma_reset(gdma_channel_handle_t dma_chan) { esp_err_t ret = ESP_OK; gdma_pair_t *pair = NULL; @@ -525,7 +524,7 @@ err: return ret; } -static void gdma_uninstall_group(gdma_group_t *group) +static void gdma_release_group_handle(gdma_group_t *group) { int group_id = group->group_id; bool do_deinitialize = false; @@ -581,14 +580,7 @@ out: return group; } -static void gdma_release_group_handle(gdma_group_t *group) -{ - if (group) { - gdma_uninstall_group(group); - } -} - -static void gdma_uninstall_pair(gdma_pair_t *pair) +static void gdma_release_pair_handle(gdma_pair_t *pair) { gdma_group_t *group = pair->group; int pair_id = pair->pair_id; @@ -606,8 +598,7 @@ static void gdma_uninstall_pair(gdma_pair_t *pair) if (do_deinitialize) { free(pair); ESP_LOGD(TAG, "del pair (%d,%d)", group->group_id, pair_id); - - gdma_uninstall_group(group); + gdma_release_group_handle(group); } } @@ -646,17 +637,12 @@ out: return pair; } -static void gdma_release_pair_handle(gdma_pair_t *pair) -{ - if (pair) { - gdma_uninstall_pair(pair); - } -} - static esp_err_t gdma_del_tx_channel(gdma_channel_t *dma_channel) { gdma_pair_t *pair = dma_channel->pair; gdma_group_t *group = pair->group; + int pair_id = pair->pair_id; + int group_id = group->group_id; gdma_tx_channel_t *tx_chan = __containerof(dma_channel, gdma_tx_channel_t, base); portENTER_CRITICAL(&pair->spinlock); pair->tx_chan = NULL; @@ -666,15 +652,16 @@ static esp_err_t gdma_del_tx_channel(gdma_channel_t *dma_channel) if (dma_channel->intr) { esp_intr_free(dma_channel->intr); portENTER_CRITICAL(&pair->spinlock); - gdma_ll_tx_enable_interrupt(group->hal.dev, pair->pair_id, UINT32_MAX, false); // disable all interupt events - gdma_ll_tx_clear_interrupt_status(group->hal.dev, pair->pair_id, UINT32_MAX); // clear all pending events + gdma_ll_tx_enable_interrupt(group->hal.dev, pair_id, UINT32_MAX, false); // disable all interupt events + gdma_ll_tx_clear_interrupt_status(group->hal.dev, pair_id, UINT32_MAX); // clear all pending events portEXIT_CRITICAL(&pair->spinlock); - ESP_LOGD(TAG, "uninstall interrupt service for tx channel (%d,%d)", group->group_id, pair->pair_id); + ESP_LOGD(TAG, "uninstall interrupt service for tx channel (%d,%d)", group_id, pair_id); } - ESP_LOGD(TAG, "del tx channel (%d,%d)", group->group_id, pair->pair_id); free(tx_chan); - gdma_uninstall_pair(pair); + ESP_LOGD(TAG, "del tx channel (%d,%d)", group_id, pair_id); + // channel has a reference on pair, release it now + gdma_release_pair_handle(pair); return ESP_OK; } @@ -682,6 +669,8 @@ static esp_err_t gdma_del_rx_channel(gdma_channel_t *dma_channel) { gdma_pair_t *pair = dma_channel->pair; gdma_group_t *group = pair->group; + int pair_id = pair->pair_id; + int group_id = group->group_id; gdma_rx_channel_t *rx_chan = __containerof(dma_channel, gdma_rx_channel_t, base); portENTER_CRITICAL(&pair->spinlock); pair->rx_chan = NULL; @@ -691,15 +680,15 @@ static esp_err_t gdma_del_rx_channel(gdma_channel_t *dma_channel) if (dma_channel->intr) { esp_intr_free(dma_channel->intr); portENTER_CRITICAL(&pair->spinlock); - gdma_ll_rx_enable_interrupt(group->hal.dev, pair->pair_id, UINT32_MAX, false); // disable all interupt events - gdma_ll_rx_clear_interrupt_status(group->hal.dev, pair->pair_id, UINT32_MAX); // clear all pending events + gdma_ll_rx_enable_interrupt(group->hal.dev, pair_id, UINT32_MAX, false); // disable all interupt events + gdma_ll_rx_clear_interrupt_status(group->hal.dev, pair_id, UINT32_MAX); // clear all pending events portEXIT_CRITICAL(&pair->spinlock); - ESP_LOGD(TAG, "uninstall interrupt service for rx channel (%d,%d)", group->group_id, pair->pair_id); + ESP_LOGD(TAG, "uninstall interrupt service for rx channel (%d,%d)", group_id, pair_id); } - ESP_LOGD(TAG, "del rx channel (%d,%d)", group->group_id, pair->pair_id); free(rx_chan); - gdma_uninstall_pair(pair); + ESP_LOGD(TAG, "del rx channel (%d,%d)", group_id, pair_id); + gdma_release_pair_handle(pair); return ESP_OK; } diff --git a/components/driver/linker.lf b/components/driver/linker.lf index f260c79cf7..c01f1574b6 100644 --- a/components/driver/linker.lf +++ b/components/driver/linker.lf @@ -1,4 +1,3 @@ - [mapping:driver] archive: libdriver.a entries: @@ -7,3 +6,8 @@ entries: # placed in flash. if TWAI_ISR_IN_IRAM = y && (TWAI_ERRATA_FIX_RX_FRAME_INVALID = y || TWAI_ERRATA_FIX_RX_FIFO_CORRUPT = y): periph_ctrl: periph_module_reset (noflash) + if GDMA_CTRL_FUNC_IN_IRAM = y: + gdma: gdma_start (noflash) + gdma: gdma_stop (noflash) + gdma: gdma_append (noflash) + gdma: gdma_reset (noflash) diff --git a/components/esp_lcd/CMakeLists.txt b/components/esp_lcd/CMakeLists.txt index ffc0cd5119..fd008fae51 100644 --- a/components/esp_lcd/CMakeLists.txt +++ b/components/esp_lcd/CMakeLists.txt @@ -14,4 +14,5 @@ set(priv_requires "driver") idf_component_register(SRCS ${srcs} INCLUDE_DIRS ${includes} - PRIV_REQUIRES ${priv_requires}) + PRIV_REQUIRES ${priv_requires} + LDFRAGMENTS linker.lf) diff --git a/components/esp_lcd/Kconfig b/components/esp_lcd/Kconfig index 02a47a09c4..6c48962aff 100644 --- a/components/esp_lcd/Kconfig +++ b/components/esp_lcd/Kconfig @@ -6,5 +6,16 @@ menu "LCD and Touch Panel" help LCD driver allocates an internal buffer to transform the data into a proper format, because of the endian order mismatch. This option is to set the size of the buffer, in bytes. + + config LCD_RGB_ISR_IRAM_SAFE + depends on IDF_TARGET_ESP32S3 + bool "RGB LCD ISR IRAM-Safe" + default n + select GDMA_CTRL_FUNC_IN_IRAM # need to restart GDMA in the LCD ISR + 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. endmenu endmenu diff --git a/components/esp_lcd/include/esp_lcd_panel_io.h b/components/esp_lcd/include/esp_lcd_panel_io.h index 330f4d9a16..75daae34df 100644 --- a/components/esp_lcd/include/esp_lcd_panel_io.h +++ b/components/esp_lcd/include/esp_lcd_panel_io.h @@ -151,6 +151,8 @@ typedef struct { int data_gpio_nums[SOC_LCD_I80_BUS_WIDTH]; /*!< 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 */ + size_t psram_trans_align; /*!< DMA transfer alignment for data allocated from PSRAM */ + size_t sram_trans_align; /*!< DMA transfer alignment for data allocated from SRAM */ } esp_lcd_i80_bus_config_t; /** diff --git a/components/esp_lcd/include/esp_lcd_panel_rgb.h b/components/esp_lcd/include/esp_lcd_panel_rgb.h index 6f06deb816..95dfb6ba4f 100644 --- a/components/esp_lcd/include/esp_lcd_panel_rgb.h +++ b/components/esp_lcd/include/esp_lcd_panel_rgb.h @@ -59,7 +59,7 @@ typedef struct { unsigned int hsync_front_porch; /*!< Horizontal front porch, number of PCLK between the end of active data and the next hsync */ unsigned int vsync_pulse_width; /*!< Vertical sync width, unit: number of lines */ unsigned int vsync_back_porch; /*!< Vertical back porch, number of invalid lines between vsync and start of frame */ - unsigned int vsync_front_porch; /*!< Vertical front porch, number of invalid lines between then end of frame and the next vsync */ + unsigned int vsync_front_porch; /*!< Vertical front porch, number of invalid lines between the end of frame and the next vsync */ struct { unsigned int hsync_idle_low: 1; /*!< The hsync signal is low in IDLE state */ unsigned int vsync_idle_low: 1; /*!< The vsync signal is low in IDLE state */ diff --git a/components/esp_lcd/linker.lf b/components/esp_lcd/linker.lf new file mode 100644 index 0000000000..51f703659e --- /dev/null +++ b/components/esp_lcd/linker.lf @@ -0,0 +1,6 @@ +[mapping:esp_lcd] +archive: libesp_lcd.a +entries: + if LCD_RGB_ISR_IRAM_SAFE = y: + esp_lcd_common: lcd_com_mount_dma_data (noflash) + esp_lcd_rgb_panel: lcd_rgb_panel_start_transmission (noflash) diff --git a/components/esp_lcd/src/esp_lcd_common.h b/components/esp_lcd/src/esp_lcd_common.h index a6e97ba70b..f036bc5fa8 100644 --- a/components/esp_lcd/src/esp_lcd_common.h +++ b/components/esp_lcd/src/esp_lcd_common.h @@ -1,13 +1,16 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include +#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 @@ -16,6 +19,9 @@ extern "C" { #endif +#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_LCDCAM_SUPPORTED diff --git a/components/esp_lcd/src/esp_lcd_panel_io_i2s.c b/components/esp_lcd/src/esp_lcd_panel_io_i2s.c index dcd150e1d7..c89aafad17 100644 --- a/components/esp_lcd/src/esp_lcd_panel_io_i2s.c +++ b/components/esp_lcd/src/esp_lcd_panel_io_i2s.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -54,7 +54,7 @@ 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 IRAM_ATTR void lcd_default_isr_handler(void *args); +static void lcd_default_isr_handler(void *args); struct esp_lcd_i80_bus_t { int bus_id; // Bus ID, index from 0 @@ -143,9 +143,16 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc bus->format_buffer = heap_caps_calloc(1, CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE, MALLOC_CAP_INTERNAL | 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"); - // I2S0 has the LCD mode, but the LCD mode can't work with other modes at the same time, we need to register the driver object to the I2S platform - ESP_GOTO_ON_ERROR(i2s_priv_register_object(bus, 0), err, TAG, "register to I2S platform failed"); - bus->bus_id = 0; + // 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_priv_register_object(bus, i) == 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 @@ -157,7 +164,7 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc 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 = ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED; + int isr_flags = LCD_I80_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED; ret = esp_intr_alloc_intrstatus(lcd_periph_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, lcd_default_isr_handler, bus, &bus->intr); @@ -251,7 +258,7 @@ esp_err_t esp_lcd_new_panel_io_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_p 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 %u", io_config->pclk_hz); - i80_device = calloc(1, sizeof(lcd_panel_io_i80_t) + io_config->trans_queue_depth * sizeof(lcd_i80_trans_descriptor_t)); + 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 *)); @@ -310,9 +317,12 @@ 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 < i80_device->num_trans_inflight; i++) { - xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY); + 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); @@ -453,12 +463,13 @@ static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, cons lcd_i80_trans_descriptor_t *trans_desc = NULL; assert(param_size <= (bus->num_dma_nodes * DMA_DESCRIPTOR_BUFFER_MAX_SIZE) && "parameter bytes too long, enlarge max_transfer_bytes"); assert(param_size <= CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE && "format buffer too small, increase CONFIG_LCD_PANEL_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 < next_device->num_trans_inflight; i++) { - xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY); + 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--; } - next_device->num_trans_inflight = 0; i2s_ll_clear_intr_status(bus->hal.dev, I2S_LL_EVENT_TX_EOF); // switch devices if necessary @@ -517,12 +528,13 @@ static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons lcd_panel_io_i80_t *cur_device = bus->cur_device; lcd_i80_trans_descriptor_t *trans_desc = NULL; assert(color_size <= (bus->num_dma_nodes * DMA_DESCRIPTOR_BUFFER_MAX_SIZE) && "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 < next_device->num_trans_inflight; i++) { - xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY); + 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--; } - next_device->num_trans_inflight = 0; i2s_ll_clear_intr_status(bus->hal.dev, I2S_LL_EVENT_TX_EOF); // switch devices if necessary @@ -717,6 +729,8 @@ static IRAM_ATTR void lcd_default_isr_handler(void *args) 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 diff --git a/components/esp_lcd/src/esp_lcd_panel_io_i80.c b/components/esp_lcd/src/esp_lcd_panel_io_i80.c index 85f1fef228..fdc168242e 100644 --- a/components/esp_lcd/src/esp_lcd_panel_io_i80.c +++ b/components/esp_lcd/src/esp_lcd_panel_io_i80.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -16,13 +16,13 @@ #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_rom_gpio.h" #include "soc/soc_caps.h" +#include "soc/rtc.h" // for `rtc_clk_xtal_freq_get()` +#include "soc/soc_memory_types.h" #include "hal/dma_types.h" #include "hal/gpio_hal.h" #include "esp_private/gdma.h" @@ -40,6 +40,9 @@ 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; +// This function is located in ROM (also see esp_rom/${target}/ld/${target}.rom.ld) +extern int Cache_WriteBack_Addr(uint32_t addr, uint32_t size); + 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); @@ -49,7 +52,7 @@ static esp_err_t lcd_i80_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_c 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 IRAM_ATTR void lcd_default_isr_handler(void *args); +static void lcd_default_isr_handler(void *args); struct esp_lcd_i80_bus_t { int bus_id; // Bus ID, index from 0 @@ -62,6 +65,8 @@ struct esp_lcd_i80_bus_t { uint8_t *format_buffer; // The driver allocates an internal buffer for DMA to do data format transformer size_t resolution_hz; // LCD_CLK resolution, determined by selected clock source gdma_channel_handle_t dma_chan; // DMA channel handle + size_t psram_trans_align; // DMA transfer alignment for data allocated from PSRAM + size_t sram_trans_align; // DMA transfer alignment for data allocated from SRAM 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 @@ -119,11 +124,11 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc ESP_GOTO_ON_FALSE(bus_config && ret_bus, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); size_t num_dma_nodes = bus_config->max_transfer_bytes / DMA_DESCRIPTOR_BUFFER_MAX_SIZE + 1; // DMA descriptors must be placed in internal SRAM - bus = heap_caps_calloc(1, sizeof(esp_lcd_i80_bus_t) + num_dma_nodes * sizeof(dma_descriptor_t), MALLOC_CAP_DMA); + bus = heap_caps_calloc(1, sizeof(esp_lcd_i80_bus_t) + num_dma_nodes * sizeof(dma_descriptor_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); ESP_GOTO_ON_FALSE(bus, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 bus"); bus->num_dma_nodes = num_dma_nodes; bus->bus_id = -1; - bus->format_buffer = heap_caps_calloc(1, CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE, MALLOC_CAP_DMA); + bus->format_buffer = heap_caps_calloc(1, CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); ESP_GOTO_ON_FALSE(bus->format_buffer, ESP_ERR_NO_MEM, err, TAG, "no mem for format buffer"); // register to platform int bus_id = lcd_com_register_device(LCD_COM_DEVICE_TYPE_I80, bus); @@ -142,7 +147,7 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc ESP_GOTO_ON_ERROR(ret, err, TAG, "select periph clock %d failed", bus_config->clk_src); // install interrupt service, (LCD peripheral shares the same interrupt source with Camera peripheral with different mask) // interrupt is disabled by default - int isr_flags = ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED; + int isr_flags = LCD_I80_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED; ret = esp_intr_alloc_intrstatus(lcd_periph_signals.buses[bus_id].irq_id, isr_flags, (uint32_t)lcd_ll_get_interrupt_status_reg(bus->hal.dev), LCD_LL_EVENT_TRANS_DONE, lcd_default_isr_handler, bus, &bus->intr); @@ -150,12 +155,14 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc lcd_ll_enable_interrupt(bus->hal.dev, LCD_LL_EVENT_TRANS_DONE, false); // disable all interrupts lcd_ll_clear_interrupt_status(bus->hal.dev, UINT32_MAX); // clear pending interrupt // install DMA service + bus->psram_trans_align = bus_config->psram_trans_align; + bus->sram_trans_align = bus_config->sram_trans_align; ret = lcd_i80_init_dma_link(bus); ESP_GOTO_ON_ERROR(ret, err, TAG, "install DMA failed"); // enable 8080 mode and set bus width lcd_ll_enable_rgb_mode(bus->hal.dev, false); lcd_ll_set_data_width(bus->hal.dev, bus_config->bus_width); - bus->bus_width = lcd_ll_get_data_width(bus->hal.dev); + bus->bus_width = bus_config->bus_width; // number of data cycles is controlled by DMA buffer size lcd_ll_enable_output_always_on(bus->hal.dev, true); // enable trans done interrupt @@ -237,7 +244,7 @@ esp_err_t esp_lcd_new_panel_io_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_p uint32_t pclk_prescale = bus->resolution_hz / io_config->pclk_hz; ESP_GOTO_ON_FALSE(pclk_prescale > 0 && pclk_prescale <= LCD_LL_CLOCK_PRESCALE_MAX, ESP_ERR_NOT_SUPPORTED, err, TAG, "prescaler can't satisfy PCLK clock %u", io_config->pclk_hz); - i80_device = calloc(1, sizeof(lcd_panel_io_i80_t) + io_config->trans_queue_depth * sizeof(lcd_i80_trans_descriptor_t)); + 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 *)); @@ -302,8 +309,11 @@ static esp_err_t panel_io_i80_del(esp_lcd_panel_io_t *io) esp_lcd_i80_bus_t *bus = i80_device->bus; lcd_i80_trans_descriptor_t *trans_desc = NULL; // wait all pending transaction to finish - for (size_t i = 0; i < i80_device->num_trans_inflight; i++) { - xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY); + 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); @@ -370,19 +380,20 @@ static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, cons 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 - for (size_t i = 0; i < next_device->num_trans_inflight; i++) { - xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY); + 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--; } - next_device->num_trans_inflight = 0; 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_data_byte_order(bus->hal.dev, false); - lcd_ll_reverse_data_bit_order(bus->hal.dev, false); - lcd_ll_reverse_data_8bits_order(bus->hal.dev, next_device->lcd_param_bits > bus->bus_width); + lcd_ll_reverse_bit_order(bus->hal.dev, false); + lcd_ll_swap_byte_order(bus->hal.dev, bus->bus_width, next_device->lcd_param_bits > bus->bus_width); bus->cur_trans = NULL; bus->cur_device = next_device; // package a transaction @@ -425,7 +436,8 @@ static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons trans_desc = &i80_device->trans_pool[i80_device->num_trans_inflight]; } else { // transaction pool has used up, recycle one from done_queue - xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY); + 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; @@ -435,6 +447,12 @@ static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons 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; + + if (esp_ptr_external_ram(color)) { + // flush framebuffer from cache to the physical PSRAM + Cache_WriteBack_Addr((uint32_t)color, color_size); + } + // send transaction to trans_queue xQueueSend(i80_device->trans_queue, &trans_desc, portMAX_DELAY); i80_device->num_trans_inflight++; @@ -447,7 +465,8 @@ static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons static esp_err_t lcd_i80_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_clock_source_t clk_src) { esp_err_t ret = ESP_OK; - lcd_ll_set_group_clock_src(bus->hal.dev, clk_src, LCD_PERIPH_CLOCK_PRE_SCALE, 1, 0); + // force to use integer division, as fractional division might lead to clock jitter + lcd_ll_set_group_clock_src(bus->hal.dev, clk_src, LCD_PERIPH_CLOCK_PRE_SCALE, 0, 0); switch (clk_src) { case LCD_CLK_SRC_PLL160M: bus->resolution_hz = 160000000 / LCD_PERIPH_CLOCK_PRE_SCALE; @@ -488,6 +507,12 @@ static esp_err_t lcd_i80_init_dma_link(esp_lcd_i80_bus_handle_t bus) .owner_check = true }; gdma_apply_strategy(bus->dma_chan, &strategy_config); + // set DMA transfer ability + gdma_transfer_ability_t ability = { + .psram_trans_align = bus->psram_trans_align, + .sram_trans_align = bus->sram_trans_align, + }; + gdma_set_transfer_ability(bus->dma_chan, &ability); return ESP_OK; err: if (bus->dma_chan) { @@ -540,6 +565,9 @@ static void lcd_start_transaction(esp_lcd_i80_bus_t *bus, lcd_i80_trans_descript lcd_ll_set_command(bus->hal.dev, bus->bus_width, trans_desc->cmd_value); if (trans_desc->data) { // some specific LCD commands can have no parameters gdma_start(bus->dma_chan, (intptr_t)(bus->dma_nodes)); + // 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); } lcd_ll_start(bus->hal.dev); } @@ -612,14 +640,15 @@ IRAM_ATTR static void lcd_default_isr_handler(void *args) 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, intr_status); // switch devices if necessary lcd_i80_switch_devices(cur_device, next_device); // only reverse data bit/bytes for color data - lcd_ll_reverse_data_bit_order(bus->hal.dev, next_device->flags.reverse_color_bits); - lcd_ll_reverse_data_byte_order(bus->hal.dev, next_device->flags.swap_color_bytes); - lcd_ll_reverse_data_8bits_order(bus->hal.dev, false); + lcd_ll_reverse_bit_order(bus->hal.dev, next_device->flags.reverse_color_bits); + lcd_ll_swap_byte_order(bus->hal.dev, bus->bus_width, next_device->flags.swap_color_bytes); bus->cur_trans = trans_desc; bus->cur_device = next_device; // mount data to DMA links diff --git a/components/esp_lcd/src/esp_lcd_panel_io_spi.c b/components/esp_lcd/src/esp_lcd_panel_io_spi.c index 95791d8654..64f8db0730 100644 --- a/components/esp_lcd/src/esp_lcd_panel_io_spi.c +++ b/components/esp_lcd/src/esp_lcd_panel_io_spi.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -11,12 +11,15 @@ #include #include "esp_lcd_panel_io_interface.h" #include "esp_lcd_panel_io.h" +#include "hal/spi_ll.h" #include "driver/spi_master.h" #include "driver/gpio.h" #include "esp_log.h" #include "esp_check.h" #include "esp_lcd_common.h" +#define LCD_SPI_MAX_DATA_SIZE (SPI_LL_DATA_MAX_BIT_LEN / 8) + static const char *TAG = "lcd_panel.io.spi"; 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); @@ -29,7 +32,7 @@ typedef struct { spi_transaction_t base; struct { unsigned int dc_gpio_level: 1; - unsigned int trans_is_color: 1; + unsigned int en_trans_done_cb: 1; } flags; } lcd_spi_trans_descriptor_t; @@ -239,18 +242,53 @@ static esp_err_t panel_io_spi_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons 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"); - // sending LCD color data - lcd_trans->flags.trans_is_color = 1; - lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_data_level; // set D/C line to data mode - lcd_trans->base.length = color_size * 8; // transaction length is in bits - lcd_trans->base.tx_buffer = color; - if (spi_panel_io->flags.dc_as_cmd_phase) { // encoding DC value to SPI command phase when necessary - lcd_trans->base.cmd = spi_panel_io->flags.dc_data_level; - } - // 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++; + // split to chunks if required: + // the SPI bus has a maximum transaction size determined by SPI_USR_MOSI_DBITLEN's bit width + 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 trunks + if (chunk_size > LCD_SPI_MAX_DATA_SIZE) { + // cap the transfer size to the maximum supported by the bus + chunk_size = LCD_SPI_MAX_DATA_SIZE; + } 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.user = spi_panel_io; + lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_data_level; // set D/C line to data mode + lcd_trans->base.length = chunk_size * 8; // transaction length is in bits + lcd_trans->base.tx_buffer = color; + if (spi_panel_io->flags.dc_as_cmd_phase) { // encoding DC value to SPI command phase when necessary + lcd_trans->base.cmd = spi_panel_io->flags.dc_data_level; + } + 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); + } + + // 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: return ret; @@ -269,7 +307,7 @@ 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); - if (lcd_trans->flags.trans_is_color) { + 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); } diff --git a/components/esp_lcd/src/esp_lcd_rgb_panel.c b/components/esp_lcd/src/esp_lcd_rgb_panel.c index 60299aeb6a..b577f7a5a5 100644 --- a/components/esp_lcd/src/esp_lcd_rgb_panel.c +++ b/components/esp_lcd/src/esp_lcd_rgb_panel.c @@ -16,8 +16,6 @@ #include "freertos/semphr.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_interface.h" #include "esp_lcd_panel_rgb.h" @@ -39,6 +37,12 @@ #include "hal/lcd_hal.h" #include "hal/lcd_ll.h" +#if CONFIG_LCD_RGB_ISR_IRAM_SAFE +#define LCD_RGB_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED) +#else +#define LCD_RGB_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED +#endif + static const char *TAG = "lcd_panel.rgb"; typedef struct esp_rgb_panel_t esp_rgb_panel_t; @@ -58,7 +62,8 @@ static esp_err_t rgb_panel_disp_off(esp_lcd_panel_t *panel, bool off); static esp_err_t lcd_rgb_panel_select_periph_clock(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src); static esp_err_t lcd_rgb_panel_create_trans_link(esp_rgb_panel_t *panel); static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *panel, const esp_lcd_rgb_panel_config_t *panel_config); -static IRAM_ATTR void lcd_default_isr_handler(void *args); +static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel); +static void lcd_default_isr_handler(void *args); struct esp_rgb_panel_t { esp_lcd_panel_t base; // Base class of generic lcd panel @@ -77,9 +82,6 @@ struct esp_rgb_panel_t { size_t resolution_hz; // Peripheral clock resolution esp_lcd_rgb_timing_t timings; // RGB timing parameters (e.g. pclk, sync pulse, porch width) gdma_channel_handle_t dma_chan; // DMA channel handle - int new_frame_id; // ID for new frame, we use ID to identify whether the frame content has been updated - int cur_frame_id; // ID for current transferring frame - SemaphoreHandle_t done_sem; // Binary semaphore, indicating if the new frame has been flushed to LCD esp_lcd_rgb_panel_frame_trans_done_cb_t on_frame_trans_done; // Callback, invoked after frame trans done 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 @@ -87,7 +89,6 @@ struct esp_rgb_panel_t { struct { unsigned int disp_en_level: 1; // The level which can turn on the screen by `disp_gpio_num` unsigned int stream_mode: 1; // If set, the LCD transfers data continuously, otherwise, it stops refreshing the LCD when transaction done - unsigned int new_frame: 1; // Whether the frame we're going to flush is a new one unsigned int fb_in_psram: 1; // Whether the frame buffer is in PSRAM } flags; dma_descriptor_t dma_nodes[]; // DMA descriptor pool of size `num_dma_nodes` @@ -100,6 +101,16 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf ESP_GOTO_ON_FALSE(rgb_panel_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid parameter"); ESP_GOTO_ON_FALSE(rgb_panel_config->data_width == 16, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported data width %d", rgb_panel_config->data_width); + +#if CONFIG_LCD_RGB_ISR_IRAM_SAFE + if (rgb_panel_config->on_frame_trans_done) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(rgb_panel_config->on_frame_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_frame_trans_done callback not in IRAM"); + } + if (rgb_panel_config->user_ctx) { + ESP_RETURN_ON_FALSE(esp_ptr_internal(rgb_panel_config->user_ctx), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); + } +#endif + // calculate the number of DMA descriptors size_t fb_size = rgb_panel_config->timings.h_res * rgb_panel_config->timings.v_res * rgb_panel_config->data_width / 8; size_t num_dma_nodes = fb_size / DMA_DESCRIPTOR_BUFFER_MAX_SIZE; @@ -107,7 +118,7 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf num_dma_nodes++; } // DMA descriptors must be placed in internal SRAM (requested by DMA) - rgb_panel = heap_caps_calloc(1, sizeof(esp_rgb_panel_t) + num_dma_nodes * sizeof(dma_descriptor_t), MALLOC_CAP_DMA); + rgb_panel = heap_caps_calloc(1, sizeof(esp_rgb_panel_t) + num_dma_nodes * sizeof(dma_descriptor_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); ESP_GOTO_ON_FALSE(rgb_panel, ESP_ERR_NO_MEM, err, TAG, "no mem for rgb panel"); rgb_panel->num_dma_nodes = num_dma_nodes; rgb_panel->panel_id = -1; @@ -117,6 +128,7 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf rgb_panel->panel_id = panel_id; // enable APB to access LCD registers periph_module_enable(lcd_periph_signals.panels[panel_id].module); + periph_module_reset(lcd_periph_signals.panels[panel_id].module); // alloc frame buffer bool alloc_from_psram = false; // fb_in_psram is only an option, if there's no PSRAM on board, we still alloc from SRAM @@ -140,17 +152,13 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf rgb_panel->sram_trans_align = sram_trans_align; rgb_panel->fb_size = fb_size; rgb_panel->flags.fb_in_psram = alloc_from_psram; - // semaphore indicates new frame trans done - rgb_panel->done_sem = xSemaphoreCreateBinary(); - ESP_GOTO_ON_FALSE(rgb_panel->done_sem, ESP_ERR_NO_MEM, err, TAG, "create done sem failed"); - xSemaphoreGive(rgb_panel->done_sem); // initialize the semaphore count to 1 // initialize HAL layer, so we can call LL APIs later lcd_hal_init(&rgb_panel->hal, panel_id); // set peripheral clock resolution ret = lcd_rgb_panel_select_periph_clock(rgb_panel, rgb_panel_config->clk_src); ESP_GOTO_ON_ERROR(ret, err, TAG, "select periph clock failed"); // install interrupt service, (LCD peripheral shares the interrupt source with Camera by different mask) - int isr_flags = ESP_INTR_FLAG_SHARED; + int isr_flags = LCD_RGB_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED; ret = esp_intr_alloc_intrstatus(lcd_periph_signals.panels[panel_id].irq_id, isr_flags, (uint32_t)lcd_ll_get_interrupt_status_reg(rgb_panel->hal.dev), LCD_LL_EVENT_VSYNC_END, lcd_default_isr_handler, rgb_panel, &rgb_panel->intr); @@ -184,7 +192,7 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf 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, fb_size=%zu", rgb_panel->panel_id, rgb_panel, rgb_panel->fb_size); + ESP_LOGD(TAG, "new rgb panel(%d) @%p, fb @%p, size=%zu", rgb_panel->panel_id, rgb_panel, rgb_panel->fb, rgb_panel->fb_size); return ESP_OK; err: @@ -196,9 +204,6 @@ err: if (rgb_panel->fb) { free(rgb_panel->fb); } - if (rgb_panel->done_sem) { - vSemaphoreDelete(rgb_panel->done_sem); - } if (rgb_panel->dma_chan) { gdma_disconnect(rgb_panel->dma_chan); gdma_del_channel(rgb_panel->dma_chan); @@ -218,14 +223,12 @@ err: 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); - xSemaphoreTake(rgb_panel->done_sem, portMAX_DELAY); // wait for last flush done int panel_id = rgb_panel->panel_id; gdma_disconnect(rgb_panel->dma_chan); gdma_del_channel(rgb_panel->dma_chan); esp_intr_free(rgb_panel->intr); periph_module_disable(lcd_periph_signals.panels[panel_id].module); lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id); - vSemaphoreDelete(rgb_panel->done_sem); free(rgb_panel->fb); if (rgb_panel->pm_lock) { esp_pm_lock_release(rgb_panel->pm_lock); @@ -280,10 +283,16 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel) lcd_ll_enable_output_hsync_in_porch_region(rgb_panel->hal.dev, true); // generate the hsync at the very begining of line lcd_ll_set_hsync_position(rgb_panel->hal.dev, 0); - // starting sending next frame automatically - lcd_ll_enable_auto_next_frame(rgb_panel->hal.dev, rgb_panel->flags.stream_mode); + // restart flush by hardware has some limitation, instead, the driver will restart the flush in the VSYNC end interrupt by software + lcd_ll_enable_auto_next_frame(rgb_panel->hal.dev, false); // trigger interrupt on the end of frame lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_VSYNC_END, 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=%uHz", rgb_panel->panel_id, rgb_panel->timings.pclk_hz); err: return ret; @@ -303,7 +312,7 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int x_end = MIN(x_end, rgb_panel->timings.h_res); y_start = MIN(y_start, rgb_panel->timings.v_res); y_end = MIN(y_end, rgb_panel->timings.v_res); - xSemaphoreTake(rgb_panel->done_sem, portMAX_DELAY); // wait for last transaction done + // convert the frame buffer to 3D array int bytes_per_pixel = rgb_panel->data_width / 8; int pixels_per_line = rgb_panel->timings.h_res; @@ -321,20 +330,12 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int // CPU writes data to PSRAM through DCache, data in PSRAM might not get updated, so write back Cache_WriteBack_Addr((uint32_t)&to[y_start][0][0], (y_end - y_start) * rgb_panel->timings.h_res * bytes_per_pixel); } - // we don't care the exact frame ID, as long as it's different from the previous one - rgb_panel->new_frame_id++; + + // restart the new transmission if (!rgb_panel->flags.stream_mode) { - // in one-off mode, the "new frame" flag is controlled by this API - rgb_panel->cur_frame_id = rgb_panel->new_frame_id; - rgb_panel->flags.new_frame = 1; - // reset FIFO of DMA and LCD, incase there remains old frame data - gdma_reset(rgb_panel->dma_chan); - lcd_ll_stop(rgb_panel->hal.dev); - lcd_ll_fifo_reset(rgb_panel->hal.dev); - gdma_start(rgb_panel->dma_chan, (intptr_t)rgb_panel->dma_nodes); + lcd_rgb_panel_start_transmission(rgb_panel); } - // start LCD engine - lcd_ll_start(rgb_panel->hal.dev); + return ESP_OK; } @@ -439,7 +440,8 @@ static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *panel, const esp_ static esp_err_t lcd_rgb_panel_select_periph_clock(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src) { esp_err_t ret = ESP_OK; - lcd_ll_set_group_clock_src(panel->hal.dev, clk_src, LCD_PERIPH_CLOCK_PRE_SCALE, 1, 0); + // force to use integer division, as fractional division might lead to clock jitter + lcd_ll_set_group_clock_src(panel->hal.dev, clk_src, LCD_PERIPH_CLOCK_PRE_SCALE, 0, 0); switch (clk_src) { case LCD_CLK_SRC_PLL160M: panel->resolution_hz = 160000000 / LCD_PERIPH_CLOCK_PRE_SCALE; @@ -469,12 +471,8 @@ static esp_err_t lcd_rgb_panel_create_trans_link(esp_rgb_panel_t *panel) panel->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU; panel->dma_nodes[i].next = &panel->dma_nodes[i + 1]; } - // fix the last DMA descriptor according to whether the LCD works in stream mode - if (panel->flags.stream_mode) { - panel->dma_nodes[panel->num_dma_nodes - 1].next = &panel->dma_nodes[0]; // chain into a circle - } else { - panel->dma_nodes[panel->num_dma_nodes - 1].next = NULL; // one-off DMA chain - } + // one-off DMA chain + panel->dma_nodes[panel->num_dma_nodes - 1].next = NULL; // mount the frame buffer to the DMA descriptors lcd_com_mount_dma_data(panel->dma_nodes, panel->fb, panel->fb_size); // alloc DMA channel and connect to LCD peripheral @@ -496,32 +494,37 @@ err: return ret; } +static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel) +{ + // reset FIFO of DMA and LCD, incase there remains old frame data + gdma_reset(rgb_panel->dma_chan); + lcd_ll_stop(rgb_panel->hal.dev); + lcd_ll_fifo_reset(rgb_panel->hal.dev); + gdma_start(rgb_panel->dma_chan, (intptr_t)rgb_panel->dma_nodes); + // 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_default_isr_handler(void *args) { - esp_rgb_panel_t *panel = (esp_rgb_panel_t *)args; + esp_rgb_panel_t *rgb_panel = (esp_rgb_panel_t *)args; bool need_yield = false; - BaseType_t high_task_woken = pdFALSE; - uint32_t intr_status = lcd_ll_get_interrupt_status(panel->hal.dev); - lcd_ll_clear_interrupt_status(panel->hal.dev, intr_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 (intr_status & LCD_LL_EVENT_VSYNC_END) { - if (panel->flags.new_frame) { // the finished one is a new frame - if (panel->on_frame_trans_done) { - if (panel->on_frame_trans_done(&panel->base, NULL, panel->user_ctx)) { - need_yield = true; - } - } - xSemaphoreGiveFromISR(panel->done_sem, &high_task_woken); - if (high_task_woken == pdTRUE) { + // call user registered callback + if (rgb_panel->on_frame_trans_done) { + if (rgb_panel->on_frame_trans_done(&rgb_panel->base, NULL, rgb_panel->user_ctx)) { need_yield = true; } } - // in stream mode, the "new frame" flag is controlled by comparing "new frame id" and "cur frame id" - if (panel->flags.stream_mode) { - // new_frame_id is only modified in `rgb_panel_draw_bitmap()`, fetch first and use below to avoid inconsistent - int new_frame_id = panel->new_frame_id; - panel->flags.new_frame = (panel->cur_frame_id != new_frame_id); - panel->cur_frame_id = new_frame_id; + // to restart the transmission + if (rgb_panel->flags.stream_mode) { + lcd_rgb_panel_start_transmission(rgb_panel); } } if (need_yield) { diff --git a/components/esp_lcd/test/test_i2c_lcd_panel.c b/components/esp_lcd/test/test_i2c_lcd_panel.c index 3705c8a31e..a62af2666d 100644 --- a/components/esp_lcd/test/test_i2c_lcd_panel.c +++ b/components/esp_lcd/test/test_i2c_lcd_panel.c @@ -71,59 +71,3 @@ TEST_CASE("lcd panel with i2c interface (ssd1306)", "[lcd]") TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); TEST_ESP_OK(i2c_driver_delete(TEST_I2C_HOST_ID)); } - -// The following test shows a porting example of LVGL GUI library -// To run the LVGL tests, you need to clone the LVGL library into components directory firstly -#if CONFIG_LV_USE_USER_DATA -#include "test_lvgl_port.h" -#if CONFIG_LV_COLOR_DEPTH_1 -static bool notify_lvgl_ready_to_flush(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) -{ - lv_disp_t *disp = *(lv_disp_t **)user_ctx; - lv_disp_flush_ready(&disp->driver); - return false; -} - -TEST_CASE("lvgl gui with i2c interface (ssd1306)", "[lcd][lvgl][ignore]") -{ - // initialize LVGL graphics library - lv_disp_t *disp = NULL; - lv_init(); - - i2c_config_t conf = { - .mode = I2C_MODE_MASTER, - .sda_io_num = TEST_I2C_SDA_GPIO, - .scl_io_num = TEST_I2C_SCL_GPIO, - .sda_pullup_en = GPIO_PULLUP_ENABLE, - .scl_pullup_en = GPIO_PULLUP_ENABLE, - .master.clk_speed = TEST_LCD_PIXEL_CLOCK_HZ, - }; - TEST_ESP_OK(i2c_param_config(TEST_I2C_HOST_ID, &conf)); - TEST_ESP_OK(i2c_driver_install(TEST_I2C_HOST_ID, I2C_MODE_MASTER, 0, 0, 0)); - - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = TEST_I2C_DEV_ADDR, - .control_phase_bytes = 1, // According to SSD1306 datasheet - .dc_bit_offset = 6, // According to SSD1306 datasheet - .lcd_cmd_bits = 8, // According to SSD1306 datasheet - .lcd_param_bits = 8, // According to SSD1306 datasheet - .on_color_trans_done = notify_lvgl_ready_to_flush, - .user_ctx = &disp, - }; - TEST_ESP_OK(esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)TEST_I2C_HOST_ID, &io_config, &io_handle)); - - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_panel_dev_config_t panel_config = { - .bits_per_pixel = 1, - .color_space = ESP_LCD_COLOR_SPACE_MONOCHROME, - .reset_gpio_num = -1, - }; - TEST_ESP_OK(esp_lcd_new_panel_ssd1306(io_handle, &panel_config, &panel_handle)); - TEST_ESP_OK(esp_lcd_panel_reset(panel_handle)); - TEST_ESP_OK(esp_lcd_panel_init(panel_handle)); - - test_lvgl_task_loop(panel_handle, TEST_LCD_H_RES, TEST_LCD_V_RES, &disp); -} -#endif // CONFIG_LV_COLOR_DEPTH_1 -#endif // CONFIG_LV_USE_USER_DATA diff --git a/components/esp_lcd/test/test_i80_lcd_panel.c b/components/esp_lcd/test/test_i80_lcd_panel.c index 738184bb40..8d6f7b8567 100644 --- a/components/esp_lcd/test/test_i80_lcd_panel.c +++ b/components/esp_lcd/test/test_i80_lcd_panel.c @@ -408,157 +408,6 @@ TEST_CASE("lcd panel with i80 interface (st7789, 8bits)", "[lcd]") #undef TEST_IMG_SIZE } -// The following test shows a porting example of LVGL GUI library -// To run the LVGL tests, you need to clone the LVGL library into components directory firstly -#if CONFIG_LV_USE_USER_DATA -#include "test_lvgl_port.h" - -static bool notify_lvgl_ready_to_flush(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) -{ - lv_disp_t *disp = *(lv_disp_t **)user_ctx; - lv_disp_flush_ready(&disp->driver); - return false; -} - -TEST_CASE("lvgl gui with i80 interface (st7789, 8bits)", "[lcd][lvgl][ignore]") -{ - // initialize LVGL graphics library - lv_disp_t *disp = NULL; - lv_init(); - - gpio_config_t bk_gpio_config = { - .mode = GPIO_MODE_OUTPUT, - .pin_bit_mask = 1ULL << TEST_LCD_BK_LIGHT_GPIO - }; - TEST_ESP_OK(gpio_config(&bk_gpio_config)); - - esp_lcd_i80_bus_handle_t i80_bus = NULL; - esp_lcd_i80_bus_config_t bus_config = { - .dc_gpio_num = TEST_LCD_DC_GPIO, - .wr_gpio_num = TEST_LCD_PCLK_GPIO, - .data_gpio_nums = { - TEST_LCD_DATA0_GPIO, - TEST_LCD_DATA1_GPIO, - TEST_LCD_DATA2_GPIO, - TEST_LCD_DATA3_GPIO, - TEST_LCD_DATA4_GPIO, - TEST_LCD_DATA5_GPIO, - TEST_LCD_DATA6_GPIO, - TEST_LCD_DATA7_GPIO, - }, - .bus_width = 8, - .max_transfer_bytes = TEST_LCD_H_RES * 40 * sizeof(uint16_t) - }; - TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_io_i80_config_t io_config = { - .cs_gpio_num = TEST_LCD_CS_GPIO, - .pclk_hz = 10000000, // 10MHz - .trans_queue_depth = 10, - .dc_levels = { - .dc_idle_level = 0, - .dc_cmd_level = 0, - .dc_dummy_level = 0, - .dc_data_level = 1, - }, - .flags = { - .swap_color_bytes = 1, - }, - .on_color_trans_done = notify_lvgl_ready_to_flush, - .user_ctx = &disp, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - }; - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle)); - - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = TEST_LCD_RST_GPIO, - .color_space = ESP_LCD_COLOR_SPACE_RGB, - .bits_per_pixel = 16, - }; - TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle)); - - // turn off backlight - gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 0); - esp_lcd_panel_reset(panel_handle); - esp_lcd_panel_init(panel_handle); - esp_lcd_panel_invert_color(panel_handle, true); - // the gap is LCD panel specific, even panels with the same driver IC, can have different gap value - esp_lcd_panel_set_gap(panel_handle, 0, 20); - // turn on backlight - gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 1); - - test_lvgl_task_loop(panel_handle, TEST_LCD_H_RES, TEST_LCD_V_RES, &disp); -} - -#define TEST_NT35510_DATA_WIDTH (8) // change this to 16 when NT35510 is configured to 16bit in length -TEST_CASE("lvgl gui with i80 interface (nt35510, 8/16bits)", "[lcd][lvgl][ignore]") -{ - // initialize LVGL graphics library - lv_disp_t *disp = NULL; - lv_init(); - - esp_lcd_i80_bus_handle_t i80_bus = NULL; - esp_lcd_i80_bus_config_t bus_config = { - .dc_gpio_num = TEST_LCD_DC_GPIO, - .wr_gpio_num = TEST_LCD_PCLK_GPIO, - .data_gpio_nums = { - TEST_LCD_DATA0_GPIO, - TEST_LCD_DATA1_GPIO, - TEST_LCD_DATA2_GPIO, - TEST_LCD_DATA3_GPIO, - TEST_LCD_DATA4_GPIO, - TEST_LCD_DATA5_GPIO, - TEST_LCD_DATA6_GPIO, - TEST_LCD_DATA7_GPIO, - TEST_LCD_DATA8_GPIO, - TEST_LCD_DATA9_GPIO, - TEST_LCD_DATA10_GPIO, - TEST_LCD_DATA11_GPIO, - TEST_LCD_DATA12_GPIO, - TEST_LCD_DATA13_GPIO, - TEST_LCD_DATA14_GPIO, - TEST_LCD_DATA15_GPIO, - }, - .bus_width = TEST_NT35510_DATA_WIDTH, - .max_transfer_bytes = TEST_LCD_H_RES * 40 * sizeof(uint16_t) - }; - TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_io_i80_config_t io_config = { - .cs_gpio_num = TEST_LCD_CS_GPIO, - .pclk_hz = 10000000, // 10MHz - .trans_queue_depth = 4, - .dc_levels = { - .dc_idle_level = 0, - .dc_cmd_level = 0, - .dc_dummy_level = 0, - .dc_data_level = 1, - }, - .on_color_trans_done = notify_lvgl_ready_to_flush, - .user_ctx = &disp, - .lcd_cmd_bits = 16, - .lcd_param_bits = 16, - }; - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle)); - - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = -1, - .color_space = ESP_LCD_COLOR_SPACE_RGB, - .bits_per_pixel = 16, - }; - TEST_ESP_OK(esp_lcd_new_panel_nt35510(io_handle, &panel_config, &panel_handle)); - - esp_lcd_panel_reset(panel_handle); - esp_lcd_panel_init(panel_handle); - esp_lcd_panel_swap_xy(panel_handle, true); - esp_lcd_panel_mirror(panel_handle, true, false); - - test_lvgl_task_loop(panel_handle, TEST_LCD_H_RES, TEST_LCD_V_RES, &disp); -} -#endif // CONFIG_LV_USE_USER_DATA #endif // SOC_LCD_I80_SUPPORTED #if SOC_I2S_LCD_I80_VARIANT diff --git a/components/esp_lcd/test/test_lvgl_port.h b/components/esp_lcd/test/test_lvgl_port.h deleted file mode 100644 index 872644b7e8..0000000000 --- a/components/esp_lcd/test/test_lvgl_port.h +++ /dev/null @@ -1,4 +0,0 @@ -#include "esp_lcd_panel_ops.h" -#include "lvgl.h" - -void test_lvgl_task_loop(esp_lcd_panel_handle_t panel_handle, int h_res, int v_res, lv_disp_t **disp); diff --git a/components/esp_lcd/test/test_lvgl_port_v7.c b/components/esp_lcd/test/test_lvgl_port_v7.c deleted file mode 100644 index a81f118c01..0000000000 --- a/components/esp_lcd/test/test_lvgl_port_v7.c +++ /dev/null @@ -1,113 +0,0 @@ -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "unity.h" -#include "test_utils.h" -#include "esp_freertos_hooks.h" -#include "soc/soc_caps.h" -#if CONFIG_LV_USE_USER_DATA -#include "test_lvgl_port.h" - -static void my_lvgl_flush(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) -{ - esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data; - - int offsetx1 = area->x1; - int offsetx2 = area->x2; - int offsety1 = area->y1; - int offsety2 = area->y2; - - esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map); -} - -#if CONFIG_LV_COLOR_DEPTH_1 -static void my_lvgl_set_px_cb(lv_disp_drv_t *disp_drv, uint8_t *buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, - lv_color_t color, lv_opa_t opa) -{ - uint16_t byte_index = x + (( y >> 3 ) * buf_w); - uint8_t bit_index = y & 0x7; - - if ((color.full == 0) && (LV_OPA_TRANSP != opa)) { - buf[byte_index] |= (1 << bit_index); - } else { - buf[byte_index] &= ~(1 << bit_index); - } -} - -static void my_lvgl_rounder(lv_disp_drv_t *disp_drv, lv_area_t *area) -{ - area->y1 = (area->y1 & (~0x7)); - area->y2 = (area->y2 & (~0x7)) + 7; -} -#endif - -static void increase_lvgl_tick(void) -{ - lv_tick_inc(portTICK_PERIOD_MS); -} - -static void create_demo_application(lv_disp_t *disp) -{ - // Get the current screen - lv_obj_t *scr = lv_disp_get_scr_act(disp); - // Create a Label on the currently active screen - lv_obj_t *label = lv_label_create(scr, NULL); - // Modify the Label's text - lv_label_set_text(label, "Hello World"); - // Align the Label to the center - lv_obj_align(label, NULL, LV_ALIGN_IN_TOP_MID, 0, 0); - -#if !CONFIG_LV_COLOR_DEPTH_1 - // new screen_spinner - lv_obj_t *screen_spinner = lv_spinner_create(scr, NULL); - lv_obj_align(screen_spinner, label, LV_ALIGN_OUT_BOTTOM_MID, 15, 20); - lv_obj_set_size(screen_spinner, 100, 100); - lv_spinner_set_arc_length(screen_spinner, 60); - lv_spinner_set_spin_time(screen_spinner, 1000); - lv_spinner_set_type(screen_spinner, LV_SPINNER_TYPE_SPINNING_ARC); - lv_spinner_set_dir(screen_spinner, LV_SPINNER_DIR_FORWARD); - - lv_obj_t *bar = lv_bar_create(scr, NULL); - lv_obj_set_size(bar, 100, 20); - lv_obj_align(bar, screen_spinner, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); - lv_bar_set_anim_time(bar, 2000); - lv_bar_set_value(bar, 100, LV_ANIM_ON); -#endif -} - -void test_lvgl_task_loop(esp_lcd_panel_handle_t panel_handle, int h_res, int v_res, lv_disp_t **disp) -{ - static lv_disp_buf_t disp_buf; - // alloc frame buffer used by LVGL - lv_color_t *buf1 = heap_caps_malloc(h_res * 20 * sizeof(lv_color_t), MALLOC_CAP_DMA); - TEST_ASSERT_NOT_NULL(buf1); - lv_color_t *buf2 = heap_caps_malloc(h_res * 20 * sizeof(lv_color_t), MALLOC_CAP_DMA); - TEST_ASSERT_NOT_NULL(buf2); - lv_disp_buf_init(&disp_buf, buf1, buf2, h_res * 20); - // register display driver - lv_disp_drv_t disp_drv; - lv_disp_drv_init(&disp_drv); - disp_drv.hor_res = h_res; - disp_drv.ver_res = v_res; - disp_drv.flush_cb = my_lvgl_flush; -#if CONFIG_LV_COLOR_DEPTH_1 - disp_drv.rounder_cb = my_lvgl_rounder; - disp_drv.set_px_cb = my_lvgl_set_px_cb; -#endif - - disp_drv.buffer = &disp_buf; - disp_drv.user_data = panel_handle; // LV_USE_USER_DATA is disabled by default, need to enable it in menuconfig - *disp = lv_disp_drv_register(&disp_drv); - - // Tick interface for LVGL - esp_register_freertos_tick_hook(increase_lvgl_tick); - - // create a demo UI on that screen - create_demo_application(*disp); - - while (1) { - vTaskDelay(pdMS_TO_TICKS(10)); - lv_task_handler(); // The task running lv_task_handler should have lower priority than that running `lv_tick_inc` - } -} - -#endif // CONFIG_LV_USE_USER_DATA diff --git a/components/esp_lcd/test/test_rgb_panel.c b/components/esp_lcd/test/test_rgb_panel.c index a8852bc6dc..ff4da2a671 100644 --- a/components/esp_lcd/test/test_rgb_panel.c +++ b/components/esp_lcd/test/test_rgb_panel.c @@ -5,6 +5,7 @@ #include "esp_lcd_panel_rgb.h" #include "esp_lcd_panel_ops.h" #include "soc/soc_caps.h" +#include "esp_attr.h" #define TEST_LCD_H_RES (480) #define TEST_LCD_V_RES (272) @@ -29,20 +30,23 @@ #define TEST_LCD_DATA13_GPIO (16) // R2 #define TEST_LCD_DATA14_GPIO (17) // R3 #define TEST_LCD_DATA15_GPIO (18) // R4 -#define TEST_LCD_DISP_EN_GPIO (39) +#define TEST_LCD_DISP_EN_GPIO (-1) + +#define TEST_LCD_PIXEL_CLOCK_HZ (10 * 1000 * 1000) #if SOC_LCD_RGB_SUPPORTED // RGB driver consumes a huge memory to save frame buffer, only test it with PSRAM enabled #if CONFIG_SPIRAM_USE_MALLOC -TEST_CASE("lcd rgb lcd panel", "[lcd]") -{ -#define TEST_IMG_SIZE (100 * 100 * sizeof(uint16_t)) - uint8_t *img = malloc(TEST_IMG_SIZE); - TEST_ASSERT_NOT_NULL(img); +#define TEST_IMG_SIZE (100 * 100 * sizeof(uint16_t)) + +static esp_lcd_panel_handle_t test_rgb_panel_initialization(bool stream_mode, esp_lcd_rgb_panel_frame_trans_done_cb_t cb, void *user_data) +{ esp_lcd_panel_handle_t panel_handle = NULL; esp_lcd_rgb_panel_config_t panel_config = { .data_width = 16, + .psram_trans_align = 64, + .clk_src = LCD_CLK_SRC_PLL160M, .disp_gpio_num = TEST_LCD_DISP_EN_GPIO, .pclk_gpio_num = TEST_LCD_PCLK_GPIO, .vsync_gpio_num = TEST_LCD_VSYNC_GPIO, @@ -67,104 +71,80 @@ TEST_CASE("lcd rgb lcd panel", "[lcd]") TEST_LCD_DATA15_GPIO, }, .timings = { - .pclk_hz = 12000000, + .pclk_hz = TEST_LCD_PIXEL_CLOCK_HZ, .h_res = TEST_LCD_H_RES, .v_res = TEST_LCD_V_RES, - .hsync_back_porch = 43, - .hsync_front_porch = 2, - .hsync_pulse_width = 1, - .vsync_back_porch = 12, - .vsync_front_porch = 1, + .hsync_back_porch = 68, + .hsync_front_porch = 20, + .hsync_pulse_width = 5, + .vsync_back_porch = 18, + .vsync_front_porch = 4, .vsync_pulse_width = 1, }, - .flags.fb_in_psram = 1, + .on_frame_trans_done = cb, + .user_ctx = user_data, + .flags.fb_in_psram = 1, // allocate frame buffer in PSRAM + .flags.relax_on_idle = !stream_mode, }; - // Test stream mode and one-off mode - for (int i = 0; i < 2; i++) { - panel_config.flags.relax_on_idle = i; - TEST_ESP_OK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle)); - TEST_ESP_OK(esp_lcd_panel_reset(panel_handle)); - TEST_ESP_OK(esp_lcd_panel_init(panel_handle)); - for (int i = 0; i < 200; i++) { - uint8_t color_byte = esp_random() & 0xFF; - int x_start = esp_random() % (TEST_LCD_H_RES - 100); - int y_start = esp_random() % (TEST_LCD_V_RES - 100); - memset(img, color_byte, TEST_IMG_SIZE); - esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img); - } - - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - } - free(img); -#undef TEST_IMG_SIZE -} - -// The following test shows a porting example of LVGL GUI library -// To run the LVGL tests, you need to clone the LVGL library into components directory firstly -#if CONFIG_LV_USE_USER_DATA -#include "test_lvgl_port.h" - -static bool notify_lvgl_ready_to_flush(esp_lcd_panel_handle_t panel, esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx) -{ - lv_disp_t *disp = *(lv_disp_t **)user_ctx; - lv_disp_flush_ready(&disp->driver); - return false; -} - -TEST_CASE("lvgl gui with rgb interface", "[lcd][lvgl][ignore]") -{ - // initialize LVGL graphics library - lv_disp_t *disp = NULL; - lv_init(); - - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_rgb_panel_config_t panel_config = { - .data_width = 16, - .disp_gpio_num = -1, - .pclk_gpio_num = TEST_LCD_PCLK_GPIO, - .vsync_gpio_num = TEST_LCD_VSYNC_GPIO, - .hsync_gpio_num = TEST_LCD_HSYNC_GPIO, - .de_gpio_num = TEST_LCD_DE_GPIO, - .data_gpio_nums = { - TEST_LCD_DATA0_GPIO, - TEST_LCD_DATA1_GPIO, - TEST_LCD_DATA2_GPIO, - TEST_LCD_DATA3_GPIO, - TEST_LCD_DATA4_GPIO, - TEST_LCD_DATA5_GPIO, - TEST_LCD_DATA6_GPIO, - TEST_LCD_DATA7_GPIO, - TEST_LCD_DATA8_GPIO, - TEST_LCD_DATA9_GPIO, - TEST_LCD_DATA10_GPIO, - TEST_LCD_DATA11_GPIO, - TEST_LCD_DATA12_GPIO, - TEST_LCD_DATA13_GPIO, - TEST_LCD_DATA14_GPIO, - TEST_LCD_DATA15_GPIO, - }, - .timings = { - .pclk_hz = 6000000, - .h_res = TEST_LCD_H_RES, - .v_res = TEST_LCD_V_RES, - .hsync_back_porch = 43, - .hsync_front_porch = 2, - .hsync_pulse_width = 1, - .vsync_back_porch = 12, - .vsync_front_porch = 1, - .vsync_pulse_width = 1, - }, - .flags.fb_in_psram = 1, - .on_frame_trans_done = notify_lvgl_ready_to_flush, - .user_ctx = &disp, - }; TEST_ESP_OK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle)); TEST_ESP_OK(esp_lcd_panel_reset(panel_handle)); TEST_ESP_OK(esp_lcd_panel_init(panel_handle)); - test_lvgl_task_loop(panel_handle, TEST_LCD_H_RES, TEST_LCD_V_RES, &disp); + return panel_handle; } -#endif // CONFIG_LV_USE_USER_DATA + +TEST_CASE("lcd_rgb_panel_stream_mode", "[lcd]") +{ + uint8_t *img = malloc(TEST_IMG_SIZE); + TEST_ASSERT_NOT_NULL(img); + + printf("initialize RGB panel with stream mode\r\n"); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(true, NULL, NULL); + printf("flush random color block\r\n"); + for (int i = 0; i < 200; i++) { + uint8_t color_byte = esp_random() & 0xFF; + int x_start = esp_random() % (TEST_LCD_H_RES - 100); + int y_start = esp_random() % (TEST_LCD_V_RES - 100); + memset(img, color_byte, TEST_IMG_SIZE); + esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img); + } + printf("delete RGB panel\r\n"); + TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); + free(img); +} + +static IRAM_ATTR bool test_rgb_panel_trans_done(esp_lcd_panel_handle_t panel, esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx) +{ + TaskHandle_t task_to_notify = (TaskHandle_t)user_ctx; + BaseType_t high_task_wakeup; + vTaskNotifyGiveFromISR(task_to_notify, &high_task_wakeup); + return high_task_wakeup == pdTRUE; +} + +TEST_CASE("lcd_rgb_panel_one_shot_mode", "[lcd]") +{ + uint8_t *img = malloc(TEST_IMG_SIZE); + TEST_ASSERT_NOT_NULL(img); + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + + printf("initialize RGB panel with ont-shot mode\r\n"); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(false, test_rgb_panel_trans_done, cur_task); + printf("flush random color block\r\n"); + for (int i = 0; i < 200; i++) { + uint8_t color_byte = esp_random() & 0xFF; + int x_start = esp_random() % (TEST_LCD_H_RES - 100); + int y_start = esp_random() % (TEST_LCD_V_RES - 100); + memset(img, color_byte, TEST_IMG_SIZE); + esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img); + // wait for flush done + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + } + + printf("delete RGB panel\r\n"); + TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); + free(img); +} + #endif // CONFIG_SPIRAM_USE_MALLOC #endif // SOC_LCD_RGB_SUPPORTED diff --git a/components/esp_lcd/test/test_spi_lcd_panel.c b/components/esp_lcd/test/test_spi_lcd_panel.c index 4a92833e69..f042ae60a8 100644 --- a/components/esp_lcd/test/test_spi_lcd_panel.c +++ b/components/esp_lcd/test/test_spi_lcd_panel.c @@ -61,9 +61,10 @@ static void lcd_initialize_spi(esp_lcd_panel_io_handle_t *io_handle, esp_lcd_pan TEST_ESP_OK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)TEST_SPI_HOST_ID, &io_config, io_handle)); } +#define TEST_IMG_SIZE (200 * 200 * sizeof(uint16_t)) + static void lcd_panel_test(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t panel_handle) { -#define TEST_IMG_SIZE (100 * 100 * sizeof(uint16_t)) uint8_t *img = heap_caps_malloc(TEST_IMG_SIZE, MALLOC_CAP_DMA); TEST_ASSERT_NOT_NULL(img); @@ -79,10 +80,10 @@ static void lcd_panel_test(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_ha for (int i = 0; i < 200; i++) { uint8_t color_byte = esp_random() & 0xFF; - int x_start = esp_random() % (TEST_LCD_H_RES - 100); - int y_start = esp_random() % (TEST_LCD_V_RES - 100); + int x_start = esp_random() % (TEST_LCD_H_RES - 200); + int y_start = esp_random() % (TEST_LCD_V_RES - 200); memset(img, color_byte, TEST_IMG_SIZE); - esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img); + esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 200, y_start + 200, img); } // turn off screen esp_lcd_panel_disp_off(panel_handle, true); @@ -91,7 +92,6 @@ static void lcd_panel_test(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_ha TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST_ID)); TEST_ESP_OK(gpio_reset_pin(TEST_LCD_BK_LIGHT_GPIO)); free(img); -#undef TEST_IMG_SIZE } TEST_CASE("lcd panel spi io test", "[lcd]") @@ -179,81 +179,3 @@ TEST_CASE("lcd panel with 1-line spi interface (st7789)", "[lcd]") TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle)); lcd_panel_test(io_handle, panel_handle); } - -// The following test shows a porting example of LVGL GUI library -// To run the LVGL tests, you need to clone the LVGL library into components directory firstly -#if CONFIG_LV_USE_USER_DATA -#include "test_lvgl_port.h" - -static bool notify_lvgl_ready_to_flush(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) -{ - lv_disp_t *disp = *(lv_disp_t **)user_ctx; - lv_disp_flush_ready(&disp->driver); - return false; -} - -static void lvgl_gui_test(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t panel_handle, lv_disp_t **disp) -{ - // initialize LVGL graphics library - lv_init(); - // turn off backlight - gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 0); - esp_lcd_panel_reset(panel_handle); - esp_lcd_panel_init(panel_handle); - esp_lcd_panel_invert_color(panel_handle, true); - // the gap is LCD panel specific, even panels with the same driver IC, can have different gap value - esp_lcd_panel_set_gap(panel_handle, 0, 20); - // turn on backlight - gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 1); - - test_lvgl_task_loop(panel_handle, TEST_LCD_H_RES, TEST_LCD_V_RES, disp); -} - -#if SOC_SPI_SUPPORT_OCT -TEST_CASE("lvgl gui with 8-line spi interface (st7789)", "[lcd][lvgl][ignore]") -{ - lv_disp_t *disp = NULL; - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_handle_t panel_handle = NULL; - lcd_initialize_spi(&io_handle, notify_lvgl_ready_to_flush, &disp, 8, 8, true); - esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = TEST_LCD_RST_GPIO, - .color_space = ESP_LCD_COLOR_SPACE_RGB, - .bits_per_pixel = 16, - }; - TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle)); - lvgl_gui_test(io_handle, panel_handle, &disp); -} - -TEST_CASE("lvgl gui with 8-line spi interface (nt35510)", "[lcd][lvgl][ignore]") -{ - lv_disp_t *disp = NULL; - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_handle_t panel_handle = NULL; - lcd_initialize_spi(&io_handle, notify_lvgl_ready_to_flush, &disp, 16, 16, true); - esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = TEST_LCD_RST_GPIO, - .color_space = ESP_LCD_COLOR_SPACE_RGB, - .bits_per_pixel = 16, - }; - TEST_ESP_OK(esp_lcd_new_panel_nt35510(io_handle, &panel_config, &panel_handle)); - lvgl_gui_test(io_handle, panel_handle, &disp); -} -#endif // SOC_SPI_SUPPORT_OCT - -TEST_CASE("lvgl gui with 1-line spi interface (st7789)", "[lcd][lvgl][ignore]") -{ - lv_disp_t *disp = NULL; - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_handle_t panel_handle = NULL; - lcd_initialize_spi(&io_handle, notify_lvgl_ready_to_flush, &disp, 8, 8, false); - esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = TEST_LCD_RST_GPIO, - .color_space = ESP_LCD_COLOR_SPACE_RGB, - .bits_per_pixel = 16, - }; - TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle)); - lvgl_gui_test(io_handle, panel_handle, &disp); -} - -#endif // CONFIG_LV_USE_USER_DATA diff --git a/components/hal/esp32/include/hal/spi_ll.h b/components/hal/esp32/include/hal/spi_ll.h index 89b4dedae3..63efb384eb 100644 --- a/components/hal/esp32/include/hal/spi_ll.h +++ b/components/hal/esp32/include/hal/spi_ll.h @@ -1,16 +1,8 @@ -// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ /******************************************************************************* * NOTICE @@ -49,6 +41,8 @@ extern "C" { #define SPI_LL_PERIPH_CLK_FREQ (80 * 1000000) #define SPI_LL_GET_HW(ID) ((ID)==0? &SPI1:((ID)==1? &SPI2 : &SPI3)) +#define SPI_LL_DATA_MAX_BIT_LEN (1 << 24) + /** * The data structure holding calculated clock configuration. Since the * calculation needs long time, it should be calculated during initialization and diff --git a/components/hal/esp32c3/include/hal/spi_ll.h b/components/hal/esp32c3/include/hal/spi_ll.h index 5aace3ffff..01070e918f 100644 --- a/components/hal/esp32c3/include/hal/spi_ll.h +++ b/components/hal/esp32c3/include/hal/spi_ll.h @@ -40,6 +40,8 @@ extern "C" { #define SPI_LL_PERIPH_CLK_FREQ (80 * 1000000) #define SPI_LL_GET_HW(ID) ((ID)==0? ({abort();NULL;}):&GPSPI2) +#define SPI_LL_DATA_MAX_BIT_LEN (1 << 18) + /** * The data structure holding calculated clock configuration. Since the * calculation needs long time, it should be calculated during initialization and diff --git a/components/hal/esp32h2/include/hal/spi_ll.h b/components/hal/esp32h2/include/hal/spi_ll.h index 764f3fd872..43893938fa 100644 --- a/components/hal/esp32h2/include/hal/spi_ll.h +++ b/components/hal/esp32h2/include/hal/spi_ll.h @@ -1,16 +1,8 @@ -// Copyright 2020 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ /******************************************************************************* * NOTICE @@ -48,6 +40,8 @@ extern "C" { #define SPI_LL_PERIPH_CLK_FREQ (80 * 1000000) #define SPI_LL_GET_HW(ID) ((ID)==0? ({abort();NULL;}):&GPSPI2) +#define SPI_LL_DATA_MAX_BIT_LEN (1 << 18) + /** * The data structure holding calculated clock configuration. Since the * calculation needs long time, it should be calculated during initialization and diff --git a/components/hal/esp32s2/include/hal/spi_ll.h b/components/hal/esp32s2/include/hal/spi_ll.h index c6dac13154..b975206d0f 100644 --- a/components/hal/esp32s2/include/hal/spi_ll.h +++ b/components/hal/esp32s2/include/hal/spi_ll.h @@ -43,6 +43,8 @@ extern "C" { #define SPI_LL_PERIPH_CLK_FREQ (80 * 1000000) #define SPI_LL_GET_HW(ID) ((ID)==0? ({abort();NULL;}):((ID)==1? &GPSPI2 : &GPSPI3)) +#define SPI_LL_DATA_MAX_BIT_LEN (1 << 23) + /** * The data structure holding calculated clock configuration. Since the * calculation needs long time, it should be calculated during initialization and diff --git a/components/hal/esp32s3/include/hal/lcd_ll.h b/components/hal/esp32s3/include/hal/lcd_ll.h index 8a5e0534e6..495d08b905 100644 --- a/components/hal/esp32s3/include/hal/lcd_ll.h +++ b/components/hal/esp32s3/include/hal/lcd_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -34,7 +34,11 @@ static inline void lcd_ll_enable_clock(lcd_cam_dev_t *dev, bool en) static inline void lcd_ll_set_group_clock_src(lcd_cam_dev_t *dev, lcd_clock_source_t src, int div_num, int div_a, int div_b) { // lcd_clk = module_clock_src / (div_num + div_b / div_a) - HAL_ASSERT(div_num >= 2); + HAL_ASSERT(div_num >= 2 && div_num <= 256); + // dic_num == 0 means 256 divider in hardware + if (div_num >= 256) { + div_num = 0; + } HAL_FORCE_MODIFY_U32_REG_FIELD(dev->lcd_clock, lcd_clkm_div_num, div_num); dev->lcd_clock.lcd_clkm_div_a = div_a; dev->lcd_clock.lcd_clkm_div_b = div_b; @@ -42,10 +46,15 @@ static inline void lcd_ll_set_group_clock_src(lcd_cam_dev_t *dev, lcd_clock_sour case LCD_CLK_SRC_PLL160M: dev->lcd_clock.lcd_clk_sel = 3; break; + case LCD_CLK_SRC_PLL240M: + dev->lcd_clock.lcd_clk_sel = 2; + break; case LCD_CLK_SRC_XTAL: dev->lcd_clock.lcd_clk_sel = 1; break; default: + // disble LCD clock source + dev->lcd_clock.lcd_clk_sel = 0; HAL_ASSERT(false && "unsupported clock source"); break; } @@ -60,7 +69,6 @@ static inline void lcd_ll_set_clock_idle_level(lcd_cam_dev_t *dev, bool level) __attribute__((always_inline)) static inline void lcd_ll_set_pixel_clock_edge(lcd_cam_dev_t *dev, bool active_on_neg) { - dev->lcd_clock.lcd_clk_equ_sysclk = 0; // if we want to pixel_clk == lcd_clk, just make clkcnt = 0 dev->lcd_clock.lcd_ck_out_edge = active_on_neg; } @@ -68,7 +76,15 @@ __attribute__((always_inline)) static inline void lcd_ll_set_pixel_clock_prescale(lcd_cam_dev_t *dev, uint32_t prescale) { // Formula: pixel_clk = lcd_clk / (1 + clkcnt_n) - dev->lcd_clock.lcd_clkcnt_n = prescale - 1; + // clkcnt_n can't be zero + uint32_t scale = 1; + if (prescale == 1) { + dev->lcd_clock.lcd_clk_equ_sysclk = 1; + } else { + dev->lcd_clock.lcd_clk_equ_sysclk = 0; + scale = prescale - 1; + } + dev->lcd_clock.lcd_clkcnt_n = scale; } static inline void lcd_ll_enable_rgb_yuv_convert(lcd_cam_dev_t *dev, bool en) @@ -97,14 +113,10 @@ static inline void lcd_ll_set_blank_cycles(lcd_cam_dev_t *dev, uint32_t fk_cycle static inline void lcd_ll_set_data_width(lcd_cam_dev_t *dev, uint32_t width) { + HAL_ASSERT(width == 8 || width == 16); dev->lcd_user.lcd_2byte_en = (width == 16); } -static inline uint32_t lcd_ll_get_data_width(lcd_cam_dev_t *dev) -{ - return dev->lcd_user.lcd_2byte_en ? 16 : 8; -} - static inline void lcd_ll_enable_output_always_on(lcd_cam_dev_t *dev, bool en) { dev->lcd_user.lcd_always_out_en = en; @@ -117,6 +129,7 @@ static inline void lcd_ll_start(lcd_cam_dev_t *dev) dev->lcd_user.lcd_start = 1; } +__attribute__((always_inline)) static inline void lcd_ll_stop(lcd_cam_dev_t *dev) { dev->lcd_user.lcd_start = 0; @@ -125,33 +138,35 @@ static inline void lcd_ll_stop(lcd_cam_dev_t *dev) static inline void lcd_ll_reset(lcd_cam_dev_t *dev) { - dev->lcd_user.lcd_reset = 1; - dev->lcd_user.lcd_reset = 0; + dev->lcd_user.lcd_reset = 1; // self clear } __attribute__((always_inline)) -static inline void lcd_ll_reverse_data_bit_order(lcd_cam_dev_t *dev, bool en) +static inline void lcd_ll_reverse_bit_order(lcd_cam_dev_t *dev, bool en) { // whether to change LCD_DATA_out[N:0] to LCD_DATA_out[0:N] dev->lcd_user.lcd_bit_order = en; } __attribute__((always_inline)) -static inline void lcd_ll_reverse_data_byte_order(lcd_cam_dev_t *dev, bool en) +static inline void lcd_ll_swap_byte_order(lcd_cam_dev_t *dev, uint32_t width, bool en) { - dev->lcd_user.lcd_byte_order = en; + HAL_ASSERT(width == 8 || width == 16); + if (width == 8) { + // {B0}{B1}{B2}{B3} => {B1}{B0}{B3}{B2} + dev->lcd_user.lcd_8bits_order = en; + dev->lcd_user.lcd_byte_order = 0; + } else if (width == 16) { + // {B1,B0},{B3,B2} => {B0,B1}{B2,B3} + dev->lcd_user.lcd_byte_order = en; + dev->lcd_user.lcd_8bits_order = 0; + } } __attribute__((always_inline)) -static inline void lcd_ll_reverse_data_8bits_order(lcd_cam_dev_t *dev, bool en) -{ - dev->lcd_user.lcd_8bits_order = en; -} - static inline void lcd_ll_fifo_reset(lcd_cam_dev_t *dev) { - dev->lcd_misc.lcd_afifo_reset = 1; - dev->lcd_misc.lcd_afifo_reset = 0; + dev->lcd_misc.lcd_afifo_reset = 1; // self clear } __attribute__((always_inline)) @@ -171,6 +186,7 @@ static inline void lcd_ll_set_dc_delay_ticks(lcd_cam_dev_t *dev, uint32_t delay) __attribute__((always_inline)) static inline void lcd_ll_set_command(lcd_cam_dev_t *dev, uint32_t data_width, uint32_t command) { + HAL_ASSERT(data_width == 8 || data_width == 16); // if command phase has two cycles, in the first cycle, command[15:0] is sent out via lcd_data_out[15:0] // in the second cycle, command[31:16] is sent out via lcd_data_out[15:0] if (data_width == 8) { diff --git a/components/hal/esp32s3/include/hal/spi_ll.h b/components/hal/esp32s3/include/hal/spi_ll.h index a223b97600..c7dd96fa8c 100644 --- a/components/hal/esp32s3/include/hal/spi_ll.h +++ b/components/hal/esp32s3/include/hal/spi_ll.h @@ -42,6 +42,8 @@ extern "C" { #define SPI_LL_PERIPH_CLK_FREQ (80 * 1000000) #define SPI_LL_GET_HW(ID) ((ID)==0? ({abort();NULL;}):((ID)==1? &GPSPI2 : &GPSPI3)) +#define SPI_LL_DATA_MAX_BIT_LEN (1 << 18) + /** * The data structure holding calculated clock configuration. Since the * calculation needs long time, it should be calculated during initialization and diff --git a/components/hal/include/hal/lcd_types.h b/components/hal/include/hal/lcd_types.h index 1a62d8f8eb..13810a79ac 100644 --- a/components/hal/include/hal/lcd_types.h +++ b/components/hal/include/hal/lcd_types.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -19,6 +19,8 @@ extern "C" { * +=====================+=========================+============================+ * | LCD_CLK_SRC_PLL160M | High resolution | ESP_PM_APB_FREQ_MAX lock | * +---------------------+-------------------------+----------------------------+ + * | LCD_CLK_SRC_PLL240M | High resolution | ESP_PM_APB_FREQ_MAX lock | + * +---------------------+-------------------------+----------------------------+ * | LCD_CLK_SRC_APLL | Configurable resolution | ESP_PM_NO_LIGHT_SLEEP lock | * +---------------------+-------------------------+----------------------------+ * | LCD_CLK_SRC_XTAL | Medium resolution | No PM lock | @@ -27,6 +29,7 @@ extern "C" { */ typedef enum { LCD_CLK_SRC_PLL160M, /*!< Select PLL160M as the source clock */ + LCD_CLK_SRC_PLL240M, /*!< Select PLL240M as the source clock */ LCD_CLK_SRC_APLL, /*!< Select APLL as the source clock */ LCD_CLK_SRC_XTAL, /*!< Select XTAL as the source clock */ } lcd_clock_source_t; diff --git a/components/soc/esp32/include/soc/soc_caps.h b/components/soc/esp32/include/soc/soc_caps.h index 3449129ad0..97650f57b6 100644 --- a/components/soc/esp32/include/soc/soc_caps.h +++ b/components/soc/esp32/include/soc/soc_caps.h @@ -163,7 +163,7 @@ /*-------------------------- LCD CAPS ----------------------------------------*/ /* Notes: On esp32, LCD intel 8080 timing is generated by I2S peripheral */ #define SOC_LCD_I80_SUPPORTED (1) /*!< Intel 8080 LCD is supported */ -#define SOC_LCD_I80_BUSES (1) /*!< Only I2S0 has LCD mode */ +#define SOC_LCD_I80_BUSES (2) /*!< Both I2S0/1 have LCD mode */ #define SOC_LCD_I80_BUS_WIDTH (24) /*!< Intel 8080 bus width */ /*-------------------------- LEDC CAPS ---------------------------------------*/ diff --git a/components/soc/esp32/lcd_periph.c b/components/soc/esp32/lcd_periph.c index a3bc25a335..148a1527aa 100644 --- a/components/soc/esp32/lcd_periph.c +++ b/components/soc/esp32/lcd_periph.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -40,6 +40,37 @@ const lcd_signal_conn_t lcd_periph_signals = { I2S0O_DATA_OUT23_IDX, }, .wr_sig = I2S0O_WS_OUT_IDX, + }, + [1] = { + .module = PERIPH_I2S1_MODULE, + .irq_id = ETS_I2S1_INTR_SOURCE, + .data_sigs = { + I2S1O_DATA_OUT0_IDX, + I2S1O_DATA_OUT1_IDX, + I2S1O_DATA_OUT2_IDX, + I2S1O_DATA_OUT3_IDX, + I2S1O_DATA_OUT4_IDX, + I2S1O_DATA_OUT5_IDX, + I2S1O_DATA_OUT6_IDX, + I2S1O_DATA_OUT7_IDX, + I2S1O_DATA_OUT8_IDX, + I2S1O_DATA_OUT9_IDX, + I2S1O_DATA_OUT10_IDX, + I2S1O_DATA_OUT11_IDX, + I2S1O_DATA_OUT12_IDX, + I2S1O_DATA_OUT13_IDX, + I2S1O_DATA_OUT14_IDX, + I2S1O_DATA_OUT15_IDX, + I2S1O_DATA_OUT16_IDX, + I2S1O_DATA_OUT17_IDX, + I2S1O_DATA_OUT18_IDX, + I2S1O_DATA_OUT19_IDX, + I2S1O_DATA_OUT20_IDX, + I2S1O_DATA_OUT21_IDX, + I2S1O_DATA_OUT22_IDX, + I2S1O_DATA_OUT23_IDX, + }, + .wr_sig = I2S1O_WS_OUT_IDX, } } };