diff --git a/cpu/esp32/Kconfig.common b/cpu/esp32/Kconfig.common index 35c9ed6016..9551dc5f4d 100644 --- a/cpu/esp32/Kconfig.common +++ b/cpu/esp32/Kconfig.common @@ -35,6 +35,7 @@ rsource "bootloader/Kconfig" rsource "esp-ble-nimble/Kconfig" rsource "esp-idf/Kconfig" rsource "esp-idf-api/Kconfig" +rsource "esp-lcd/Kconfig" rsource "periph/Kconfig" endif diff --git a/cpu/esp32/Makefile b/cpu/esp32/Makefile index 91a69869e9..cc18edea8c 100644 --- a/cpu/esp32/Makefile +++ b/cpu/esp32/Makefile @@ -29,6 +29,10 @@ ifneq (, $(filter esp_freertos, $(USEMODULE))) DIRS += freertos endif +ifneq (, $(filter esp_lcd, $(USEMODULE))) + DIRS += esp-lcd +endif + ifneq (, $(filter stdio_usb_serial_jtag, $(USEMODULE))) DIRS += stdio_usb_serial_jtag endif diff --git a/cpu/esp32/Makefile.dep b/cpu/esp32/Makefile.dep index 09521bd845..ca2556901e 100644 --- a/cpu/esp32/Makefile.dep +++ b/cpu/esp32/Makefile.dep @@ -128,6 +128,12 @@ ifneq (,$(filter esp_idf_heap,$(USEMODULE))) USEPKG += tlsf endif +ifneq (,$(filter lcd_parallel_ll_mcu,$(USEMODULE))) + USEMODULE += esp_lcd + USEMODULE += esp_idf_lcd + USEMODULE += esp_idf_heap +endif + ifneq (,$(filter mtd periph_flashpage,$(USEMODULE))) USEMODULE += esp_idf_spi_flash endif diff --git a/cpu/esp32/Makefile.include b/cpu/esp32/Makefile.include index a0a62d24fc..3b1bed8517 100644 --- a/cpu/esp32/Makefile.include +++ b/cpu/esp32/Makefile.include @@ -113,6 +113,7 @@ PSEUDOMODULES += esp_hw_counter PSEUDOMODULES += esp_idf_gpio_hal PSEUDOMODULES += esp_i2c_hw PSEUDOMODULES += esp_jtag +PSEUDOMODULES += esp_lcd_gpio PSEUDOMODULES += esp_rtc_timer_32k PSEUDOMODULES += esp_spi_ram PSEUDOMODULES += esp_spi_oct @@ -167,6 +168,10 @@ ifneq (,$(filter esp_spi_ram,$(USEMODULE))) INCLUDES += -I$(ESP32_SDK_DIR)/components/esp_hw_support/include/soc/$(CPU_FAM) endif +ifneq (,$(filter esp_idf_lcd,$(USEMODULE))) + INCLUDES += -I$(ESP32_SDK_DIR)/components/esp_lcd/include +endif + ifneq (,$(filter esp_idf_spi_flash,$(USEMODULE))) INCLUDES += -I$(ESP32_SDK_DIR)/components/spi_flash/include endif diff --git a/cpu/esp32/esp-idf/lcd/Kconfig b/cpu/esp32/esp-idf/lcd/Kconfig new file mode 100644 index 0000000000..8a8156adb7 --- /dev/null +++ b/cpu/esp32/esp-idf/lcd/Kconfig @@ -0,0 +1,16 @@ +# Copyright (c) 2022 Gunar Schorcht +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# + +config MODULE_ESP_IDF_LCD + bool + depends on TEST_KCONFIG + depends on MODULE_ESP_IDF && HAS_ESP_LCD + + default y if MODULE_LCD_PARALLEL_LL_MCU + + help + ESP-IDF code for peripheral GPIO. diff --git a/cpu/esp32/esp-idf/lcd/Makefile b/cpu/esp32/esp-idf/lcd/Makefile new file mode 100644 index 0000000000..7a97024cbe --- /dev/null +++ b/cpu/esp32/esp-idf/lcd/Makefile @@ -0,0 +1,36 @@ +MODULE = esp_idf_lcd + +# source files to be compiled for this module +ESP32_SDK_SRC = \ + components/esp_lcd/src/esp_lcd_common.c \ + components/esp_lcd/src/esp_lcd_panel_io.c \ + components/esp_pm/pm_locks.c \ + components/soc/$(CPU_FAM)/lcd_periph.c \ + # + +ifeq (esp32s3,$(CPU_FAM)) + ESP32_SDK_SRC += \ + components/driver/gdma.c \ + components/esp_lcd/src/esp_lcd_panel_io_i80.c \ + components/hal/gdma_hal.c \ + components/hal/lcd_hal.c \ + components/soc/$(CPU_FAM)/gdma_periph.c \ + # +else ifneq (,$(filter esp32 esp32s2,$(CPU_FAM))) + ESP32_SDK_SRC = \ + components/driver/i2s.c \ + components/esp_lcd/src/esp_lcd_panel_io_i2s.c \ + components/hal/i2s_hal.c \ + # +endif + +# additional include pathes required by this module +INCLUDES += -I$(ESP32_SDK_DIR)/components/esp_lcd/include +INCLUDES += -I$(ESP32_SDK_DIR)/components/esp_lcd/interface + +include $(RIOTBASE)/Makefile.base + +ESP32_SDK_BIN = $(BINDIR)/$(MODULE) + +include ../esp_idf.mk +include ../esp_idf_cflags.mk diff --git a/cpu/esp32/esp-lcd/Kconfig b/cpu/esp32/esp-lcd/Kconfig new file mode 100644 index 0000000000..0f534c6f1e --- /dev/null +++ b/cpu/esp32/esp-lcd/Kconfig @@ -0,0 +1,54 @@ +# Copyright (c) 2023 Gunar Schorcht +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# + +menuconfig MODULE_ESP_LCD + bool "Enable LCD low-level parallel interface driver" + depends on MODULE_LCD + default y if HAVE_LCD_PARALLEL_LL_MCU + help + Enabe the MCU-driven low-level MCU 8080 8-/16-bit parallel interface + driver. + +if MODULE_ESP_LCD + +config MODULE_ESP_LCD_GPIO + bool "GPIO-driven low-level parallel interface driver" + depends on !CPU_FAM_ESP32 && !CPU_FAM_ESP32S2 && !CPU_FAM_ESP32S3 + default y + help + The ESP32x SoC variant used does not have a peripheral for the parallel + low-level interface. However, it can be emulated with special low-level + GPIO operations. It is faster than the GPIO-driven 8-/16-bit parallel + interface implemented in the LCD driver, but requires 4 kByte RAM for + 8-bit data bus width and 8 kByte RAM for 16-bit data bus width. + +config LCD_WRITE_CLOCK_MHZ + int "LCD write clock rate in MHz" + range 1 80 + depends on CPU_FAM_ESP32 || CPU_FAM_ESP32S2 || CPU_FAM_ESP32S3 + default 10 if CPU_FAM_ESP32 + default 40 if CPU_FAM_ESP32S2 + default 20 if CPU_FAM_ESP32S3 + help + Defines the clock rate that is used for the LCD write signal. It + depends on used ESP32x SoC variant and used display interface. + +config LCD_DATA_BUF_SIZE + int "LCD data buffer size in byte" + depends on CPU_FAM_ESP32 || CPU_FAM_ESP32S2 || CPU_FAM_ESP32S3 + default 512 + help + Defines the size of the buffers used to write data to the LCD + screen. Since double buffering is used, there are two buffers + of this size. One buffer is used first by the LCD driver to + write the data that needs to be transferred to the LCD, and + one buffer from which the DMA then transfers the data to the + LCD peripherals. This allows data to be written before the + DMA transfer is complete. The larger the buffers, the better + the performance, but the higher the memory requirements. + +endif diff --git a/cpu/esp32/esp-lcd/Makefile b/cpu/esp32/esp-lcd/Makefile new file mode 100644 index 0000000000..0281ca80a6 --- /dev/null +++ b/cpu/esp32/esp-lcd/Makefile @@ -0,0 +1,9 @@ +MODULE = esp_lcd + +ifneq (,$(filter esp32 esp32s2 esp32s3,$(CPU_FAM))) + SRC = esp_lcd_mcu.c +else + SRC = esp_lcd_gpio.c +endif + +include $(RIOTBASE)/Makefile.base diff --git a/cpu/esp32/esp-lcd/esp_lcd_gpio.c b/cpu/esp32/esp-lcd/esp_lcd_gpio.c new file mode 100644 index 0000000000..12f2dcd51b --- /dev/null +++ b/cpu/esp32/esp-lcd/esp_lcd_gpio.c @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2023 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_esp32 + * @{ + * + * @file + * @brief GPIO-driven low-Level parallel interface implementation for LCDs + * + * @author Gunar Schorcht + * @} + */ + +#include + +#include "periph/gpio.h" +#include "lcd.h" +#include "ztimer.h" + +#if MODULE_ESP_LCD_GPIO + +#include "soc/gpio_reg.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +typedef struct { + uint32_t set_mask_0; /* port 0 set mask */ + uint32_t set_mask_1; /* port 0 set mask */ + uint32_t clr_mask_0; /* port 1 clear mask */ + uint32_t clr_mask_1; /* port 1 clear mask */ +} _pin_mask_t; + +static _pin_mask_t _low_byte_masks[256] = {}; + +#if IS_USED(MODULE_LCD_PARALLEL_16BIT) +static _pin_mask_t _high_byte_masks[256] = {}; +#endif + +/* + * Following functions are not implemented by intention to let the + * GPIO-driven low-level implementation handle the configuration + * of the GPIOs. The function `_lcd_ll_mcu_set_data_dir` is used to + * initialize the GPIO masks when the clear masks are completely 0. + */ +#if 0 + +static void _lcd_ll_mcu_init(lcd_t *dev) +{ + (void)dev; +} + +static void _lcd_ll_mcu_cmd_start(lcd_t *dev, uint8_t cmd, bool cont) +{ + (void)dev; + (void)cmd; + (void)cont; +} + +#endif + +static void _lcd_ll_mcu_set_data_dir(lcd_t *dev, bool output) +{ + DEBUG("[lcd_ll_mcu] %s %u\n", __func__, output); + + /* sanity check to ensure that data pins can be handled as array */ + assert((&dev->params->d7_pin - &dev->params->d0_pin) == 7); +#if IS_USED(MODULE_LCD_PARALLEL_16BIT) + assert((&dev->params->d15_pin - &dev->params->d8_pin) == 7); +#endif + + if ((_low_byte_masks[0].clr_mask_0 == 0) && + (_low_byte_masks[0].clr_mask_1 == 0)) { + /* initialize the mask array if it is not yet initialized */ + const gpio_t *pins = &dev->params->d0_pin; + + for (unsigned data = 0; data < 256; data++) { + for (unsigned i = 0; i < 8; i++) { + if (data & (1 << i)) { + /* set mask */ + if (pins[i] < 32) { + _low_byte_masks[data].set_mask_0 |= 1 << pins[i]; + } + else { + _low_byte_masks[data].set_mask_1 |= 1 << (pins[i] - 32); + } + } + else { + /* clear mask */ + if (pins[i] < 32) { + _low_byte_masks[data].clr_mask_0 |= 1 << pins[i]; + } + else { + _low_byte_masks[data].clr_mask_1 |= 1 << (pins[i] - 32); + } + } + } + } +#if IS_USED(MODULE_LCD_PARALLEL_16BIT) + pins = &dev->params->d8_pin; + + for (unsigned data = 0; data < 256; data++) { + for (unsigned i = 0; i < 8; i++) { + if (data & (1 << i)) { + /* set mask */ + if (pins[i] < 32) { + _high_byte_masks[data].set_mask_0 |= 1 << pins[i]; + } + else { + _high_byte_masks[data].set_mask_1 |= 1 << (pins[i] - 32); + } + } + else { + /* clear mask */ + if (pins[i] < 32) { + _high_byte_masks[data].clr_mask_0 |= 1 << pins[i]; + } + else { + _high_byte_masks[data].clr_mask_1 |= 1 << (pins[i] - 32); + } + } + } + } +#endif + } + + gpio_init(dev->params->d0_pin, output ? GPIO_OUT : GPIO_IN); + gpio_init(dev->params->d1_pin, output ? GPIO_OUT : GPIO_IN); + gpio_init(dev->params->d2_pin, output ? GPIO_OUT : GPIO_IN); + gpio_init(dev->params->d3_pin, output ? GPIO_OUT : GPIO_IN); + gpio_init(dev->params->d4_pin, output ? GPIO_OUT : GPIO_IN); + gpio_init(dev->params->d5_pin, output ? GPIO_OUT : GPIO_IN); + gpio_init(dev->params->d6_pin, output ? GPIO_OUT : GPIO_IN); + gpio_init(dev->params->d7_pin, output ? GPIO_OUT : GPIO_IN); +#if IS_USED(MODULE_LCD_PARALLEL_16BIT) + gpio_init(dev->params->d8_pin, output ? GPIO_OUT : GPIO_IN); + gpio_init(dev->params->d9_pin, output ? GPIO_OUT : GPIO_IN); + gpio_init(dev->params->d10_pin, output ? GPIO_OUT : GPIO_IN); + gpio_init(dev->params->d11_pin, output ? GPIO_OUT : GPIO_IN); + gpio_init(dev->params->d12_pin, output ? GPIO_OUT : GPIO_IN); + gpio_init(dev->params->d13_pin, output ? GPIO_OUT : GPIO_IN); + gpio_init(dev->params->d14_pin, output ? GPIO_OUT : GPIO_IN); + gpio_init(dev->params->d15_pin, output ? GPIO_OUT : GPIO_IN); +#endif /* IS_USED(MODULE_LCD_PARALLEL_16BIT) */ +} + +static void _lcd_ll_mcu_write_data(lcd_t *dev, bool cont, + uint16_t data, unsigned pin_num) +{ + if (gpio_is_valid(dev->params->cs_pin)) { + gpio_clear(dev->params->cs_pin); + } + + gpio_clear(dev->params->wrx_pin); + + uint8_t _byte = data & 0xff; + + uint32_t set_mask_0 = _low_byte_masks[_byte].set_mask_0; + uint32_t clr_mask_0 = _low_byte_masks[_byte].clr_mask_0; + uint32_t set_mask_1 = _low_byte_masks[_byte].set_mask_1; + uint32_t clr_mask_1 = _low_byte_masks[_byte].clr_mask_1; + +#if IS_USED(MODULE_LCD_PARALLEL_16BIT) + _byte = data >> 8; + + set_mask_0 |= _high_byte_masks[_byte].set_mask_0; + clr_mask_0 |= _high_byte_masks[_byte].clr_mask_0; + set_mask_1 |= _high_byte_masks[_byte].set_mask_1; + clr_mask_1 |= _high_byte_masks[_byte].clr_mask_1; +#endif + + *((uint32_t *)GPIO_OUT_W1TS_REG) = set_mask_0; + *((uint32_t *)GPIO_OUT_W1TC_REG) = clr_mask_0; + *((uint32_t *)GPIO_OUT1_W1TS_REG) = set_mask_1; + *((uint32_t *)GPIO_OUT1_W1TC_REG) = clr_mask_1; + + gpio_set(dev->params->wrx_pin); + + if (gpio_is_valid(dev->params->cs_pin) && !cont) { + gpio_set(dev->params->cs_pin); + } +} + +static uint16_t _lcd_ll_mcu_read_data(lcd_t *dev, bool cont, unsigned pin_num) +{ + const gpio_t *pins = &dev->params->d0_pin; + + if (gpio_is_valid(dev->params->cs_pin)) { + gpio_clear(dev->params->cs_pin); + } + + gpio_clear(dev->params->rdx_pin); + + uint32_t in_0 = *((uint32_t *)GPIO_IN_REG); + uint32_t in_1 = *((uint32_t *)GPIO_IN1_REG); + + gpio_set(dev->params->rdx_pin); + + if (gpio_is_valid(dev->params->cs_pin) && !cont) { + gpio_set(dev->params->cs_pin); + }; + + uint16_t in = 0; + + for (unsigned i = 0; i < pin_num; i++) { + if (pins[i] < 32) { + in |= in_0 & (1 << pins[i]) ? 1 : 0; + } + else { + in |= in_1 & (1 << (pins[i] - 32)) ? 1 : 0; + } + } + + return in; +} + +static void _lcd_ll_mcu_write_byte(lcd_t *dev, bool cont, uint8_t out) +{ + DEBUG("[lcd_ll_mcu] write byte: %02x\n", out); + + _lcd_ll_mcu_write_data(dev, cont, out, 8); +} + +static uint8_t _lcd_ll_mcu_read_byte(lcd_t *dev, bool cont) +{ + return _lcd_ll_mcu_read_data(dev, cont, 8); +} + +#if IS_USED(MODULE_LCD_PARALLEL_16BIT) + +static void _lcd_ll_mcu_write_word(lcd_t *dev, bool cont, uint16_t out) +{ + DEBUG("[lcd_ll_mcu] write word: %04x\n", out); + + _lcd_ll_mcu_write_data(dev, cont, out, 16); +} + +static uint16_t _lcd_ll_mcu_read_word(lcd_t *dev, bool cont) +{ + return _lcd_ll_mcu_read_data(dev, cont, 16); +} + +#endif /* IS_USED(MODULE_LCD_PARALLEL_16BIT) */ + +const lcd_ll_par_driver_t lcd_ll_par_driver = { + .init = lcd_ll_par_gpio_init, /* GPIO-driven `init` is used */ + .set_data_dir = _lcd_ll_mcu_set_data_dir, + .cmd_start = lcd_ll_par_gpio_cmd_start, /* GPIO-driven `cmd_start` is used */ + .write_byte = _lcd_ll_mcu_write_byte, + .read_byte = _lcd_ll_mcu_read_byte, +#if IS_USED(MODULE_LCD_PARALLEL_16BIT) + .write_word = _lcd_ll_mcu_write_word, + .read_word = _lcd_ll_mcu_read_word, +#endif +}; + +#else /* MODULE_ESP_LCD_GPIO */ + +/* the GPIO-driven low-level interface is not used */ +const lcd_ll_par_driver_t lcd_ll_par_driver = { +}; + +#endif /* MODULE_ESP_LCD_GPIO */ diff --git a/cpu/esp32/esp-lcd/esp_lcd_mcu.c b/cpu/esp32/esp-lcd/esp_lcd_mcu.c new file mode 100644 index 0000000000..0439bf4030 --- /dev/null +++ b/cpu/esp32/esp-lcd/esp_lcd_mcu.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2023 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_esp32 + * @{ + * + * @file + * @brief Peripheral low-Level parallel interface implementation for LCDs + * + * @author Gunar Schorcht + * @} + */ + +#include +#include + +#include "lcd.h" +#include "lcd_internal.h" +#include "log.h" +#include "macros/units.h" +#include "periph/gpio.h" +#include "ztimer.h" + +#include "esp_lcd_panel_io.h" +#include "soc/gpio_reg.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#if !defined(CPU_FAM_ESP32) && !defined(CPU_FAM_ESP32S2) && !defined(CPU_FAM_ESP32S3) +#error "ESP32x SoC family not supported" +#endif + +#ifndef CONFIG_LCD_WRITE_CLOCK_MHZ +#if CONFIG_LCD_I80_COLOR_IN_PSRAM +/* PCLK has to be low enough for SPI RAM */ +#define CONFIG_LCD_WRITE_CLOCK_MHZ 2 +#else + +#if defined(CPU_FAM_ESP32S3) +#define CONFIG_LCD_WRITE_CLOCK_MHZ 20 +#elif defined(CPU_FAM_ESP32S2) +#define CONFIG_LCD_WRITE_CLOCK_MHZ 40 +#else /* ESP32 */ +#define CONFIG_LCD_WRITE_CLOCK_MHZ 10 +#endif + +#endif /* CONFIG_LCD_I80_COLOR_IN_PSRAM */ +#endif /* CONFIG_LCD_WRITE_CLOCK_MHZ */ + +static_assert(CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE >= 32, + "CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE mus be at least 32"); + +/* ESP32x SoCs support only one LCD peripheral so we can use single instances + * of the following variables */ + +int _cmd = -1; /* means no command needed in ESP-IDF */ + +size_t _idx_bytes = 0; +uint8_t _data_bytes[CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE]; +uint8_t _trans_bytes[CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE]; + +esp_lcd_i80_bus_handle_t _i80_bus_handle = NULL; +esp_lcd_panel_io_handle_t _i80_io_handle = NULL; + +/* indicates that a transfer of the data buffer is still in progress and must + * not be overwritten */ +static bool _dma_transfer_in_progress = false; + +static bool _dma_transfer_done(esp_lcd_panel_io_handle_t io_handle, + esp_lcd_panel_io_event_data_t *io_event_data, + void *user_ctx) +{ + (void)io_handle; + (void)io_event_data; + (void)user_ctx; + + _dma_transfer_in_progress = false; + + return false; +} + +static void _lcd_ll_mcu_init(lcd_t *dev) +{ + esp_lcd_i80_bus_config_t i80_bus_config = { + .dc_gpio_num = dev->params->dcx_pin, + .wr_gpio_num = dev->params->wrx_pin, + .clk_src = LCD_CLK_SRC_PLL160M, +#if IS_USED(MODULE_LCD_PARALLEL_16BIT) + .data_gpio_nums = { + dev->params->d0_pin, + dev->params->d1_pin, + dev->params->d2_pin, + dev->params->d3_pin, + dev->params->d4_pin, + dev->params->d5_pin, + dev->params->d6_pin, + dev->params->d7_pin, + dev->params->d8_pin, + dev->params->d9_pin, + dev->params->d10_pin, + dev->params->d11_pin, + dev->params->d12_pin, + dev->params->d13_pin, + dev->params->d14_pin, + dev->params->d15_pin, + }, + .bus_width = 16, +#else + .data_gpio_nums = { + dev->params->d0_pin, + dev->params->d1_pin, + dev->params->d2_pin, + dev->params->d3_pin, + dev->params->d4_pin, + dev->params->d5_pin, + dev->params->d6_pin, + dev->params->d7_pin, + }, + .bus_width = 8, +#endif + .max_transfer_bytes = dev->params->rgb_channels * 40 * sizeof(uint16_t), + }; + + esp_lcd_panel_io_i80_config_t i80_io_config = { + .cs_gpio_num = gpio_is_valid(dev->params->cs_pin) ? (int)dev->params->cs_pin + : -1, + .pclk_hz = MHZ(CONFIG_LCD_WRITE_CLOCK_MHZ), + .trans_queue_depth = 10, + .dc_levels = { + .dc_idle_level = 0, + .dc_cmd_level = 0, + .dc_dummy_level = 1, + .dc_data_level = 1, + }, + .on_color_trans_done = _dma_transfer_done, + .user_ctx = NULL, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + }; + + esp_lcd_new_i80_bus(&i80_bus_config, &_i80_bus_handle); + esp_lcd_new_panel_io_i80(_i80_bus_handle, &i80_io_config, &_i80_io_handle); + + if (gpio_is_valid(dev->params->rdx_pin)) { + gpio_init(dev->params->rdx_pin, GPIO_IN_PU); + gpio_set(dev->params->rdx_pin); + } + + if (gpio_is_valid(dev->params->cs_pin)) { + gpio_init(dev->params->cs_pin, GPIO_OUT); + gpio_clear(dev->params->cs_pin); + } +} + +static void _lcd_ll_mcu_set_data_dir(lcd_t *dev, bool output) +{ + (void)dev; + (void)output; + LOG_ERROR("[lcd_ll_mcu] set dir: %d is not supported\n", output); + /* not supported yet */ +} + +static void _lcd_ll_mcu_cmd_start(lcd_t *dev, uint8_t cmd, bool cont) +{ + DEBUG("[lcd_ll_mcu] write cmd: %02x\n", cmd); + + if (!cont) { + /* cmd without parameters */ + esp_lcd_panel_io_tx_param(_i80_io_handle, cmd, NULL, 0); + _cmd = -1; + } + else { + /* cmd with parameters */ + _cmd = cmd; + } +} + +static void _lcd_ll_mcu_transfer(lcd_t *dev, bool cont) +{ + if (!cont) { + /* if no further data follow, send the command with the data in the buffer */ + esp_lcd_panel_io_tx_param(_i80_io_handle, _cmd, _data_bytes, _idx_bytes); + _cmd = -1; + _idx_bytes = 0; + } + else if (_idx_bytes == CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE) { + /* spin lock as long as a DMA data transfer is still in progress */ + while (_dma_transfer_in_progress) {} + + /* copy data buffer to the DMA transfer buffer */ + memcpy(_trans_bytes, _data_bytes, _idx_bytes); + /* start DMA data transfer */ + _dma_transfer_in_progress = true; + esp_lcd_panel_io_tx_color(_i80_io_handle, _cmd, _data_bytes, _idx_bytes); + + /* It should only be possible to follow more data than + * CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE with the RAMWR command. + * Transferring more data to continue the operation with cmd=-1 does + * not seem to work. Therefore a RAMWRC generated in this + * case for further data */ + _cmd = (_cmd == LCD_CMD_RAMWR) ? LCD_CMD_RAMWRC : _cmd; + _idx_bytes = 0; + } +} + +static void _lcd_ll_mcu_write_byte(lcd_t *dev, bool cont, uint8_t out) +{ + DEBUG("[lcd_ll_mcu] write byte: %02x\n", out); + + _data_bytes[_idx_bytes++] = out; + /* transfer the data if necessary */ + _lcd_ll_mcu_transfer(dev, cont); +} + +static uint8_t _lcd_ll_mcu_read_byte(lcd_t *dev, bool cont) +{ + LOG_ERROR("[lcd_ll_mcu] read from LCD is not supported\n"); + return 0; +} + +#if IS_USED(MODULE_LCD_PARALLEL_16BIT) + +static void _lcd_ll_mcu_write_word(lcd_t *dev, bool cont, uint16_t out) +{ + DEBUG("[lcd_ll_mcu] write word: %04x\n", out); + + /* out is given in BE order */ + _data_bytes[_idx_bytes++] = out >> 8; + _data_bytes[_idx_bytes++] = out & 0xff; + /* transfer the data if necessary */ + _lcd_ll_mcu_transfer(dev, cont); +} + +static uint16_t _lcd_ll_mcu_read_word(lcd_t *dev, bool cont) +{ + LOG_ERROR("[lcd_ll_mcu] read from LCD is not supported\n"); + /* not supported yet */ + return 0; +} + +#endif /* IS_USED(MODULE_LCD_PARALLEL_16BIT) */ + +const lcd_ll_par_driver_t lcd_ll_par_driver = { + .init = _lcd_ll_mcu_init, + .set_data_dir = _lcd_ll_mcu_set_data_dir, + .cmd_start = _lcd_ll_mcu_cmd_start, + .write_byte = _lcd_ll_mcu_write_byte, + .read_byte = _lcd_ll_mcu_read_byte, +#if IS_USED(MODULE_LCD_PARALLEL_16BIT) + .write_word = _lcd_ll_mcu_write_word, + .read_word = _lcd_ll_mcu_read_word, +#endif +}; diff --git a/cpu/esp32/include/irq_arch.h b/cpu/esp32/include/irq_arch.h index a321c44d22..da7c759a7b 100644 --- a/cpu/esp32/include/irq_arch.h +++ b/cpu/esp32/include/irq_arch.h @@ -47,6 +47,7 @@ extern "C" { #define CPU_INUM_WDT 13 /**< Level interrupt with low priority 1 */ #define CPU_INUM_SOFTWARE 17 /**< Level interrupt with low priority 1 */ #define CPU_INUM_ETH 18 /**< Level interrupt with low priority 1 */ +#define CPU_INUM_LCD 18 /**< Level interrupt with low priority 1 */ #define CPU_INUM_TIMER 19 /**< Level interrupt with medium priority 2 */ #define CPU_INUM_FRC2 20 /**< Level interrupt with medium priority 2 */ #define CPU_INUM_SYSTIMER 20 /**< Level interrupt with medium priority 2 */ diff --git a/cpu/esp32/include/sdkconfig.h b/cpu/esp32/include/sdkconfig.h index ae581d4a25..489f279684 100644 --- a/cpu/esp32/include/sdkconfig.h +++ b/cpu/esp32/include/sdkconfig.h @@ -233,6 +233,17 @@ #endif /* !CONFIG_ESP_FLASHPAGE_CAPACITY */ +/** + * LCD driver configuration + */ +#if MODULE_ESP_IDF_LCD +#ifndef CONFIG_LCD_DATA_BUF_SIZE +#define CONFIG_LCD_DATA_BUF_SIZE 512 +#endif + +#define CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE CONFIG_LCD_DATA_BUF_SIZE +#endif + #endif /* DOXYGEN */ /** diff --git a/cpu/esp32/irq_arch.c b/cpu/esp32/irq_arch.c index 37f96cb191..7510df5052 100644 --- a/cpu/esp32/irq_arch.c +++ b/cpu/esp32/irq_arch.c @@ -87,10 +87,19 @@ static const struct intr_handle_data_t _irq_data_table[] = { { ETS_USB_SERIAL_JTAG_INTR_SOURCE, CPU_INUM_SERIAL_JTAG, 1 }, #endif { ETS_RMT_INTR_SOURCE, CPU_INUM_RMT, 1 }, +#if defined(CPU_FAM_ESP32) || defined(CPU_FAM_ESP32S2) + { ETS_I2S0_INTR_SOURCE, CPU_INUM_LCD, 1 }, +#elif defined(CPU_FAM_ESP32S3) + { ETS_LCD_CAM_INTR_SOURCE, CPU_INUM_LCD, 1 }, +#endif }; #define IRQ_DATA_TABLE_SIZE ARRAY_SIZE(_irq_data_table) +#if defined(CPU_FAM_ESP32) && MODULE_ESP_LCD && MODULE_ESP_ETH +#error "esp_eth and esp_lcd can't be used at the same time because of an interrupt conflict" +#endif + void esp_irq_init(void) { #ifdef SOC_CPU_HAS_FLEXIBLE_INTC @@ -172,6 +181,17 @@ esp_err_t esp_intr_alloc(int source, int flags, intr_handler_t handler, return ESP_OK; } +esp_err_t esp_intr_alloc_intrstatus(int source, int flags, + uint32_t reg, uint32_t mask, + intr_handler_t handler, + void *arg, intr_handle_t *ret_handle) +{ + /* TODO status register and status mask handling for shared interrupts */ + (void)reg; + (void)mask; + return esp_intr_alloc(source, flags, handler, arg, ret_handle); +} + esp_err_t esp_intr_free(intr_handle_t handle) { return esp_intr_disable(handle);