diff --git a/Devices/es3c35p/CMakeLists.txt b/Devices/es3c35p/CMakeLists.txt new file mode 100644 index 00000000..7ad1ad01 --- /dev/null +++ b/Devices/es3c35p/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register( + SRCS "source/module.cpp" + REQUIRES TactilityKernel +) diff --git a/Devices/es3c35p/device.properties b/Devices/es3c35p/device.properties new file mode 100644 index 00000000..5ca2c5b7 --- /dev/null +++ b/Devices/es3c35p/device.properties @@ -0,0 +1,23 @@ +general.vendor=LCDWIKI/Hosyond +general.name=ES3C35P + +apps.launcherAppId=Launcher + +hardware.target=ESP32S3 +hardware.flashSize=16MB +hardware.spiRam=true +hardware.spiRamMode=OCT +hardware.spiRamSpeed=80M +hardware.esptoolFlashFreq=80M +hardware.tinyUsb=true +hardware.bluetooth=true + +display.size=3.5" +display.shape=rectangle +display.dpi=165 + +lvgl.colorDepth=16 + +storage.userDataLocation=SD + +dependencies.useDeprecatedHal=false diff --git a/Devices/es3c35p/devicetree.yaml b/Devices/es3c35p/devicetree.yaml new file mode 100644 index 00000000..f7e2389b --- /dev/null +++ b/Devices/es3c35p/devicetree.yaml @@ -0,0 +1,6 @@ +dependencies: + - Platforms/platform-esp32 + - Drivers/st77922-module + - Drivers/es8311-module + - Drivers/audio-stream-module +dts: es3c35p.dts diff --git a/Devices/es3c35p/es3c35p.dts b/Devices/es3c35p/es3c35p.dts new file mode 100644 index 00000000..5f808e2b --- /dev/null +++ b/Devices/es3c35p/es3c35p.dts @@ -0,0 +1,143 @@ +/dts-v1/; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/ { + compatible = "root"; + model = "LCDWIKI/Hosyond ES3C35P"; + + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + + ble0 { + compatible = "espressif,esp32-ble"; + status = "disabled"; + }; + + gpio0 { + compatible = "espressif,esp32-gpio"; + gpio-count = <49>; + }; + + /* FM8002E speaker amplifier enable, active-low on GPIO1. */ + amp_enable { + compatible = "gpio-hog"; + pin = <&gpio0 1 GPIO_FLAG_NONE>; + mode = ; + }; + + adc0 { + compatible = "espressif,esp32-adc-oneshot"; + unit-id = ; + clk-src = ; + channels = ; + }; + + /* BAT+ through a 200K/200K divider into GPIO8 / ADC1_CH7. */ + battery-sense { + compatible = "battery-sense"; + io-channel = <&adc0 0>; + reference-voltage-mv = <3300>; + multiplier = <2000>; + }; + + i2s0 { + compatible = "espressif,esp32-i2s"; + port = ; + pin-bclk = <&gpio0 18 GPIO_FLAG_NONE>; + pin-ws = <&gpio0 21 GPIO_FLAG_NONE>; + pin-data-out = <&gpio0 15 GPIO_FLAG_NONE>; + pin-data-in = <&gpio0 16 GPIO_FLAG_NONE>; + pin-mclk = <&gpio0 17 GPIO_FLAG_NONE>; + }; + + i2c0 { + compatible = "espressif,esp32-i2c"; + port = ; + clock-frequency = <400000>; + pin-sda = <&gpio0 38 GPIO_FLAG_NONE>; + pin-scl = <&gpio0 39 GPIO_FLAG_NONE>; + + touch@55 { + compatible = "sitronix,st77922-touch"; + reg = <0x55>; + x-max = <320>; + y-max = <480>; + pin-reset = <&gpio0 48 GPIO_FLAG_NONE>; + pin-interrupt = <&gpio0 47 GPIO_FLAG_NONE>; + }; + + es8311: es8311@18 { + compatible = "everest,es8311"; + reg = <0x18>; + i2s = <&i2s0>; + }; + }; + + display_backlight_pwm { + compatible = "espressif,esp32-pwm-ledc"; + pin = <&gpio0 41 GPIO_FLAG_NONE>; + period-ns = <200000>; + ledc-timer = <0>; + ledc-channel = <0>; + }; + + display_backlight { + compatible = "pwm-backlight"; + status = "disabled"; + pwm = <&display_backlight_pwm>; + }; + + spi0 { + compatible = "espressif,esp32-spi"; + host = ; + pin-sclk = <&gpio0 12 GPIO_FLAG_NONE>; + pin-mosi = <&gpio0 11 GPIO_FLAG_NONE>; + pin-miso = <&gpio0 13 GPIO_FLAG_NONE>; + pin-wp = <&gpio0 14 GPIO_FLAG_NONE>; + pin-hd = <&gpio0 9 GPIO_FLAG_NONE>; + cs-gpios = <&gpio0 10 GPIO_FLAG_NONE>; + /* Accommodate the driver's 1/10-frame DMA staging transfers, matching + * the vendor LVGL port's full-frame refresh path. */ + max-transfer-size = <65536>; + + display@0 { + compatible = "sitronix,st77922"; + horizontal-resolution = <320>; + vertical-resolution = <480>; + /* 80 MHz works in the vendor demo, but produces visible QSPI corruption on + * some modules/cables. The component's 40 MHz default is reliably clean. */ + pixel-clock-hz = <40000000>; + backlight = <&display_backlight>; + }; + }; + + sdmmc0 { + compatible = "espressif,esp32-sdmmc"; + pin-clk = <&gpio0 5 GPIO_FLAG_NONE>; + pin-cmd = <&gpio0 4 GPIO_FLAG_NONE>; + pin-d0 = <&gpio0 6 GPIO_FLAG_NONE>; + pin-d1 = <&gpio0 7 GPIO_FLAG_NONE>; + pin-d2 = <&gpio0 2 GPIO_FLAG_NONE>; + pin-d3 = <&gpio0 3 GPIO_FLAG_NONE>; + slot = ; + bus-width = <4>; + }; +}; diff --git a/Devices/es3c35p/source/module.cpp b/Devices/es3c35p/source/module.cpp new file mode 100644 index 00000000..c4652ced --- /dev/null +++ b/Devices/es3c35p/source/module.cpp @@ -0,0 +1,9 @@ +#include + +extern "C" { + +Module es3c35p_module = { + .name = "es3c35p" +}; + +} diff --git a/Drivers/st77922-module/CMakeLists.txt b/Drivers/st77922-module/CMakeLists.txt new file mode 100644 index 00000000..6d6944eb --- /dev/null +++ b/Drivers/st77922-module/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +file(GLOB_RECURSE SOURCE_FILES "source/*.c*") + +tactility_add_module(st77922-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel platform-esp32 esp_lcd_st77922 driver +) diff --git a/Drivers/st77922-module/bindings/sitronix,st77922-touch.yaml b/Drivers/st77922-module/bindings/sitronix,st77922-touch.yaml new file mode 100644 index 00000000..cadd3337 --- /dev/null +++ b/Drivers/st77922-module/bindings/sitronix,st77922-touch.yaml @@ -0,0 +1,37 @@ +description: Touch interface integrated in the Sitronix ST77922 TDDI + +include: ["i2c-device.yaml"] + +compatible: "sitronix,st77922-touch" + +bus: i2c + +properties: + x-max: + type: int + required: true + description: Maximum X coordinate + y-max: + type: int + required: true + description: Maximum Y coordinate + swap-xy: + type: boolean + default: false + description: Swap the X and Y axes + mirror-x: + type: boolean + default: false + description: Mirror the X axis + mirror-y: + type: boolean + default: false + description: Mirror the Y axis + pin-reset: + type: phandles + default: GPIO_PIN_SPEC_NONE + description: Reset GPIO pin + pin-interrupt: + type: phandles + default: GPIO_PIN_SPEC_NONE + description: Interrupt GPIO pin diff --git a/Drivers/st77922-module/bindings/sitronix,st77922.yaml b/Drivers/st77922-module/bindings/sitronix,st77922.yaml new file mode 100644 index 00000000..681ddead --- /dev/null +++ b/Drivers/st77922-module/bindings/sitronix,st77922.yaml @@ -0,0 +1,47 @@ +description: Sitronix ST77922 QSPI display panel + +compatible: "sitronix,st77922" + +bus: spi + +properties: + horizontal-resolution: + type: int + required: true + description: Horizontal resolution in pixels + vertical-resolution: + type: int + required: true + description: Vertical resolution in pixels + mirror-x: + type: boolean + default: false + description: Mirror the X axis + mirror-y: + type: boolean + default: false + description: Mirror the Y axis + invert-color: + type: boolean + default: false + description: Invert the panel's color output + bgr-order: + type: boolean + default: false + description: Use BGR element order instead of RGB + bits-per-pixel: + type: int + default: 16 + description: Color depth in bits per pixel + pixel-clock-hz: + type: int + default: 80000000 + description: QSPI pixel clock frequency in Hz + transaction-queue-depth: + type: int + default: 10 + description: Size of the internal SPI transaction queue + backlight: + type: phandle + default: "NULL" + description: Optional reference to this display's backlight device diff --git a/Drivers/st77922-module/devicetree.yaml b/Drivers/st77922-module/devicetree.yaml new file mode 100644 index 00000000..a07d6f33 --- /dev/null +++ b/Drivers/st77922-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/st77922-module/include/bindings/st77922.h b/Drivers/st77922-module/include/bindings/st77922.h new file mode 100644 index 00000000..47762b4c --- /dev/null +++ b/Drivers/st77922-module/include/bindings/st77922.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(st77922, struct St77922Config) diff --git a/Drivers/st77922-module/include/bindings/st77922_touch.h b/Drivers/st77922-module/include/bindings/st77922_touch.h new file mode 100644 index 00000000..0c038a12 --- /dev/null +++ b/Drivers/st77922-module/include/bindings/st77922_touch.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(st77922_touch, struct St77922TouchConfig) diff --git a/Drivers/st77922-module/include/drivers/st77922.h b/Drivers/st77922-module/include/drivers/st77922.h new file mode 100644 index 00000000..2cb119d4 --- /dev/null +++ b/Drivers/st77922-module/include/drivers/st77922.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#include + +struct St77922Config { + uint16_t horizontal_resolution; + uint16_t vertical_resolution; + bool mirror_x; + bool mirror_y; + bool invert_color; + bool bgr_order; + uint32_t bits_per_pixel; + uint32_t pixel_clock_hz; + uint8_t transaction_queue_depth; + struct Device* backlight; +}; diff --git a/Drivers/st77922-module/include/drivers/st77922_touch.h b/Drivers/st77922-module/include/drivers/st77922_touch.h new file mode 100644 index 00000000..b4bc5779 --- /dev/null +++ b/Drivers/st77922-module/include/drivers/st77922_touch.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#include + +struct St77922TouchConfig { + uint8_t address; + uint16_t x_max; + uint16_t y_max; + bool swap_xy; + bool mirror_x; + bool mirror_y; + struct GpioPinSpec pin_reset; + struct GpioPinSpec pin_interrupt; +}; diff --git a/Drivers/st77922-module/include/st77922_module.h b/Drivers/st77922-module/include/st77922_module.h new file mode 100644 index 00000000..73ef420f --- /dev/null +++ b/Drivers/st77922-module/include/st77922_module.h @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +extern Module st77922_module; diff --git a/Drivers/st77922-module/source/module.cpp b/Drivers/st77922-module/source/module.cpp new file mode 100644 index 00000000..30051012 --- /dev/null +++ b/Drivers/st77922-module/source/module.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +extern "C" { + +extern Driver st77922_driver; +extern Driver st77922_touch_driver; + +static Driver* const st77922_drivers[] = { + &st77922_driver, + &st77922_touch_driver, + nullptr +}; + +Module st77922_module = { + .name = "st77922", + .drivers = st77922_drivers +}; + +} // extern "C" diff --git a/Drivers/st77922-module/source/st77922.cpp b/Drivers/st77922-module/source/st77922.cpp new file mode 100644 index 00000000..f640bb9f --- /dev/null +++ b/Drivers/st77922-module/source/st77922.cpp @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include "st77922_init.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "ST77922" +#define GET_CONFIG(device) (static_cast((device)->config)) + +struct St77922Internal { + esp_lcd_panel_io_handle_t io_handle; + esp_lcd_panel_handle_t panel_handle; + SemaphoreHandle_t draw_done; + uint8_t* transfer_buffer; + size_t transfer_buffer_size; +}; + +static bool IRAM_ATTR transfer_done(esp_lcd_panel_io_handle_t, esp_lcd_panel_io_event_data_t*, void* context) { + auto* internal = static_cast(context); + BaseType_t task_woken = pdFALSE; + xSemaphoreGiveFromISR(internal->draw_done, &task_woken); + return task_woken == pdTRUE; +} + +static error_t start(Device* device) { + auto* parent = device_get_parent(device); + check(device_get_type(parent) == &SPI_CONTROLLER_TYPE); + const auto* spi = static_cast(parent->config); + const auto* config = GET_CONFIG(device); + + GpioPinSpec cs; + if (esp32_spi_get_cs_pin(device, &cs) != ERROR_NONE) { + return ERROR_RESOURCE; + } + + auto* internal = static_cast(calloc(1, sizeof(St77922Internal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + internal->draw_done = xSemaphoreCreateBinary(); + if (internal->draw_done == nullptr) { + free(internal); + return ERROR_OUT_OF_MEMORY; + } + // The vendor port renders a complete frame in PSRAM, then copies it through a + // 1/10-frame DMA buffer. Besides making full-frame refresh possible, this keeps + // the panel's GRAM synchronized when animated objects invalidate old and new + // positions in separate LVGL regions. + const size_t bytes_per_pixel = config->bits_per_pixel / 8; + const size_t rows_per_transfer = config->vertical_resolution > 10 + ? config->vertical_resolution / 10 : config->vertical_resolution; + internal->transfer_buffer_size = + static_cast(config->horizontal_resolution) * rows_per_transfer * bytes_per_pixel; + if (spi->max_transfer_size > 0 + && internal->transfer_buffer_size > static_cast(spi->max_transfer_size)) { + internal->transfer_buffer_size = static_cast(spi->max_transfer_size); + } + internal->transfer_buffer = static_cast(heap_caps_malloc( + internal->transfer_buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)); + if (internal->transfer_buffer == nullptr) { + vSemaphoreDelete(internal->draw_done); + free(internal); + return ERROR_OUT_OF_MEMORY; + } + + esp_lcd_panel_io_spi_config_t io_config = { + .cs_gpio_num = static_cast(cs.pin), + .dc_gpio_num = -1, + .spi_mode = 0, + .pclk_hz = config->pixel_clock_hz, + .trans_queue_depth = config->transaction_queue_depth, + .on_color_trans_done = transfer_done, + .user_ctx = internal, + .lcd_cmd_bits = 32, + .lcd_param_bits = 8, + .cs_ena_pretrans = 0, + .cs_ena_posttrans = 0, + .flags = { + .dc_high_on_cmd = 0, + .dc_low_on_data = 0, + .dc_low_on_param = 0, + .octal_mode = 0, + .quad_mode = 1, + .sio_mode = 0, + .lsb_first = 0, + .cs_high_active = 0, + }, + }; + + esp_err_t result = esp_lcd_new_panel_io_spi( + static_cast(spi->host), &io_config, &internal->io_handle); + if (result != ESP_OK) { + LOG_E(TAG, "Failed to create panel IO: %s", esp_err_to_name(result)); + heap_caps_free(internal->transfer_buffer); + vSemaphoreDelete(internal->draw_done); + free(internal); + return ERROR_RESOURCE; + } + + size_t init_count = 0; + st77922_vendor_config_t vendor = { + .init_cmds = st77922_board_init_commands(&init_count), + .init_cmds_size = static_cast(init_count), + .flags = { .use_qspi_interface = 1 }, + }; + esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = -1, + .rgb_ele_order = config->bgr_order ? LCD_RGB_ELEMENT_ORDER_BGR : LCD_RGB_ELEMENT_ORDER_RGB, + .data_endian = LCD_RGB_DATA_ENDIAN_LITTLE, + .bits_per_pixel = config->bits_per_pixel, + .flags = { .reset_active_high = false }, + .vendor_config = &vendor, + }; + result = esp_lcd_new_panel_st77922(internal->io_handle, &panel_config, &internal->panel_handle); + bool ok = result == ESP_OK; + ok = ok && esp_lcd_panel_reset(internal->panel_handle) == ESP_OK; + ok = ok && esp_lcd_panel_init(internal->panel_handle) == ESP_OK; + ok = ok && ((!config->mirror_x && !config->mirror_y) + || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK); + ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK); + ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK; + if (!ok) { + LOG_E(TAG, "Failed to bring up panel: %s", esp_err_to_name(result)); + if (internal->panel_handle != nullptr) esp_lcd_panel_del(internal->panel_handle); + esp_lcd_panel_io_del(internal->io_handle); + heap_caps_free(internal->transfer_buffer); + vSemaphoreDelete(internal->draw_done); + free(internal); + return ERROR_RESOURCE; + } + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK + || esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) { + return ERROR_RESOURCE; + } + heap_caps_free(internal->transfer_buffer); + vSemaphoreDelete(internal->draw_done); + free(internal); + device_set_driver_data(device, nullptr); + return ERROR_NONE; +} + +static error_t reset(Device* device) { + auto* data = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_reset(data->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} +static error_t init(Device* device) { + auto* data = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_init(data->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} +static error_t draw_bitmap(Device* device, int32_t xs, int32_t ys, int32_t xe, int32_t ye, const void* pixels) { + auto* data = static_cast(device_get_driver_data(device)); + const auto* config = GET_CONFIG(device); + const size_t row_bytes = static_cast(xe - xs) * config->bits_per_pixel / 8; + if (row_bytes == 0 || row_bytes > data->transfer_buffer_size) { + return ERROR_OUT_OF_RANGE; + } + + const size_t rows_per_transfer = data->transfer_buffer_size / row_bytes; + const auto* source = static_cast(pixels); + int32_t chunk_y = ys; + while (chunk_y < ye) { + const size_t remaining_rows = static_cast(ye - chunk_y); + const size_t chunk_rows = remaining_rows < rows_per_transfer ? remaining_rows : rows_per_transfer; + const size_t chunk_bytes = chunk_rows * row_bytes; + memcpy(data->transfer_buffer, source, chunk_bytes); + + xSemaphoreTake(data->draw_done, 0); + if (esp_lcd_panel_draw_bitmap( + data->panel_handle, xs, chunk_y, xe, chunk_y + static_cast(chunk_rows), + data->transfer_buffer) != ESP_OK) { + LOG_E(TAG, "Failed to queue color transfer at y=%ld", static_cast(chunk_y)); + return ERROR_RESOURCE; + } + if (xSemaphoreTake(data->draw_done, pdMS_TO_TICKS(1000)) != pdTRUE) { + LOG_E(TAG, "Timed out waiting for color transfer at y=%ld", static_cast(chunk_y)); + return ERROR_TIMEOUT; + } + + source += chunk_bytes; + chunk_y += static_cast(chunk_rows); + } + return ERROR_NONE; +} +static error_t mirror(Device* device, bool x, bool y) { + auto* data = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_mirror(data->panel_handle, x, y) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} +static bool get_mirror_x(Device* device) { return GET_CONFIG(device)->mirror_x; } +static bool get_mirror_y(Device* device) { return GET_CONFIG(device)->mirror_y; } +static error_t invert(Device* device, bool value) { + auto* data = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_invert_color(data->panel_handle, value) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} +static error_t on_off(Device* device, bool value) { + auto* data = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_disp_on_off(data->panel_handle, value) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} +static error_t sleep(Device* device, bool value) { + auto* data = static_cast(device_get_driver_data(device)); + return esp_lcd_panel_disp_sleep(data->panel_handle, value) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} +static DisplayColorFormat color_format(Device*) { return DISPLAY_COLOR_FORMAT_RGB565_SWAPPED; } +static uint16_t resolution_x(Device* device) { return GET_CONFIG(device)->horizontal_resolution; } +static uint16_t resolution_y(Device* device) { return GET_CONFIG(device)->vertical_resolution; } +static void frame_buffer(Device*, uint8_t, void** output) { *output = nullptr; } +static uint8_t frame_buffer_count(Device*) { return 0; } +static error_t backlight(Device* device, Device** output) { + *output = GET_CONFIG(device)->backlight; + return *output == nullptr ? ERROR_NOT_SUPPORTED : ERROR_NONE; +} + +static const DisplayApi display_api = { + .capabilities = DISPLAY_CAPABILITY_CAP_MIRROR | DISPLAY_CAPABILITY_INVERT_COLOR | + DISPLAY_CAPABILITY_ON_OFF | DISPLAY_CAPABILITY_SLEEP | DISPLAY_CAPABILITY_BACKLIGHT | + DISPLAY_CAPABILITY_REQUIRES_FULL_FRAME, + .reset = reset, + .init = init, + .draw_bitmap = draw_bitmap, + .mirror = mirror, + .swap_xy = nullptr, + .get_swap_xy = nullptr, + .get_mirror_x = get_mirror_x, + .get_mirror_y = get_mirror_y, + .set_gap = nullptr, + .get_gap_x = nullptr, + .get_gap_y = nullptr, + .invert_color = invert, + .disp_on_off = on_off, + .disp_sleep = sleep, + .get_color_format = color_format, + .get_resolution_x = resolution_x, + .get_resolution_y = resolution_y, + .get_frame_buffer = frame_buffer, + .get_frame_buffer_count = frame_buffer_count, + .get_backlight = backlight, + .has_capability = nullptr, +}; + +Driver st77922_driver = { + .name = "st77922", + .compatible = (const char*[]) { "sitronix,st77922", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &display_api, + .device_type = &DISPLAY_TYPE, + .owner = &st77922_module, + .internal = nullptr +}; diff --git a/Drivers/st77922-module/source/st77922_init.c b/Drivers/st77922-module/source/st77922_init.c new file mode 100644 index 00000000..6c68a0ef --- /dev/null +++ b/Drivers/st77922-module/source/st77922_init.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "st77922_init.h" + +// Initialization sequence supplied with the LCDWIKI/Hosyond ES3C35P vendor ESP-IDF demo. +static const st77922_lcd_init_cmd_t init_commands[] = { + {0xF1, (uint8_t []){0x00}, 1, 0}, + {0x60, (uint8_t []){0x00, 0x00, 0x00}, 3, 0}, + {0x65, (uint8_t []){0x80}, 1, 0}, + {0x79, (uint8_t []){0x06}, 1, 0}, + {0x7B, (uint8_t []){0x00, 0x08, 0x08}, 3, 0}, + {0x80, (uint8_t []){0x55, 0x62, 0x2F, 0x17, 0xF0, 0x52, 0x70, 0xD2, 0x52, 0x62, 0xEA}, 11, 0}, + {0x81, (uint8_t []){0x26, 0x52, 0x72, 0x27}, 4, 0}, + {0x84, (uint8_t []){0x92, 0x25}, 2, 0}, + {0x87, (uint8_t []){0x10, 0x10, 0x58, 0x00, 0x02, 0x3A}, 6, 0}, + {0x88, (uint8_t []){0x00, 0x00, 0x2C, 0x10, 0x04, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x06}, 15, 0}, + {0x89, (uint8_t []){0x00, 0x00, 0x00}, 3, 0}, + {0x8A, (uint8_t []){0x13, 0x00, 0x2C, 0x00, 0x00, 0x2C, 0x10, 0x10, 0x00, 0x3E, 0x19}, 11, 0}, + {0x8B, (uint8_t []){0x15, 0xB1, 0xB1, 0x44, 0x96, 0x2C, 0x10, 0x97, 0x8E}, 9, 0}, + {0x8C, (uint8_t []){0x1D, 0xB1, 0xB1, 0x44, 0x96, 0x2C, 0x10, 0x50, 0x0F, 0x01, 0xC5, 0x12, 0x09}, 13, 0}, + {0x8D, (uint8_t []){0x0C}, 1, 0}, + {0x8E, (uint8_t []){0x33, 0x01, 0x0C, 0x13, 0x01, 0x01}, 6, 0}, + {0xB3, (uint8_t []){0x00, 0x30}, 2, 0}, + {0xF1, (uint8_t []){0x00}, 1, 0}, + {0x71, (uint8_t []){0xD0}, 1, 0}, + {0x66, (uint8_t []){0x02, 0x3F}, 2, 0}, + {0xBE, (uint8_t []){0x26, 0x00, 0x9D}, 3, 0}, + {0x70, (uint8_t []){0x01, 0xA0, 0x11, 0x40, 0xE0, 0x00, 0x11, 0x69, 0x11, 0x00, 0x00, 0x1A}, 12, 0}, + {0x90, (uint8_t []){0x04, 0x04, 0x55, 0x74, 0x00, 0x40, 0x43, 0x27, 0x27}, 9, 0}, + {0x91, (uint8_t []){0x04, 0x04, 0x55, 0x75, 0x00, 0x40, 0x42, 0x27, 0x27}, 9, 0}, + {0x92, (uint8_t []){0x04, 0x44, 0x55, 0xC0, 0x06, 0x00, 0x07, 0x05, 0x90, 0x27}, 10, 0}, + {0x93, (uint8_t []){0x04, 0x43, 0x11, 0x00, 0x00, 0x00, 0x00, 0x05, 0x90, 0x27}, 10, 0}, + {0x94, (uint8_t []){0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 6, 0}, + {0x95, (uint8_t []){0x96, 0x16, 0x00, 0x00, 0xFF}, 5, 0}, + {0x96, (uint8_t []){0x44, 0x53, 0x03, 0x12, 0x23, 0x24, 0x06, 0x05, 0x94, 0x27, 0x00, 0x44}, 12, 0}, + {0x97, (uint8_t []){0x44, 0x53, 0x47, 0x56, 0x20, 0x20, 0x02, 0x01, 0x94, 0x27, 0x00, 0x44}, 12, 0}, + {0xBA, (uint8_t []){0x55, 0x94, 0x2D, 0x94, 0x27}, 5, 0}, + {0x9A, (uint8_t []){0x40, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00}, 7, 0}, + {0x9B, (uint8_t []){0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00}, 7, 0}, + {0x9C, (uint8_t []){0x5C, 0x12, 0x00, 0x00, 0x10, 0x12, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00}, 13, 0}, + {0x9D, (uint8_t []){0x8A, 0x51, 0x00, 0x00, 0x00, 0x80, 0x1E, 0x01}, 8, 0}, + {0x9E, (uint8_t []){0x51, 0x00, 0x00, 0x00, 0x80, 0x1E, 0x01}, 7, 0}, + {0xB4, (uint8_t []){0x1D, 0x1C, 0x1E, 0x0B, 0x14, 0x02, 0x13, 0x09, 0x1E, 0x00, 0x1E, 0x10}, 12, 0}, + {0xB5, (uint8_t []){0x1D, 0x1C, 0x1E, 0x0A, 0x15, 0x03, 0x11, 0x08, 0x1E, 0x01, 0x1E, 0x12}, 12, 0}, + {0xB6, (uint8_t []){0x77, 0x77, 0x00, 0x0A, 0xFF, 0x0A, 0xFF}, 7, 0}, + {0x86, (uint8_t []){0xCD, 0x04, 0xB1, 0x02, 0x58, 0x12, 0x58, 0x0C, 0x13, 0x01, 0xA5, 0x00, 0xA5, 0xA5}, 14, 0}, + {0xB7, (uint8_t []){0x07, 0x0A, 0x0E, 0x06, 0x05, 0x03, 0x2B, 0x03, 0x03, 0x42, 0x07, 0x10, 0x10, 0x2E, 0x3F, 0x0D}, 16, 0}, + {0xB8, (uint8_t []){0x07, 0x0A, 0x0D, 0x05, 0x05, 0x02, 0x2B, 0x02, 0x03, 0x42, 0x06, 0x10, 0x0F, 0x2E, 0x3F, 0x0D}, 16, 0}, + {0xB9, (uint8_t []){0x23, 0x23}, 2, 0}, + {0xBF, (uint8_t []){0x10, 0x14, 0x14, 0x0B, 0x0B, 0x0B}, 6, 0}, + {0xF2, (uint8_t []){0x00}, 1, 0}, + {0x73, (uint8_t []){0x04, 0xDA, 0x12, 0x54, 0x47}, 5, 0}, + {0x77, (uint8_t []){0x6B, 0x5B, 0xFD, 0xC3, 0xC5}, 5, 0}, + {0x7A, (uint8_t []){0x15, 0x27}, 2, 0}, + {0x7B, (uint8_t []){0x04, 0x57}, 2, 0}, + {0x7E, (uint8_t []){0x01, 0x0E}, 2, 0}, + {0xBF, (uint8_t []){0x36}, 1, 0}, + {0xE3, (uint8_t []){0x40, 0x40}, 2, 0}, + {0xF0, (uint8_t []){0x00}, 1, 0}, + {0xD0, (uint8_t []){0x00}, 1, 0}, + {0x2A, (uint8_t []){0x00, 0x00, 0x01, 0x3F}, 4, 0}, + {0x2B, (uint8_t []){0x00, 0x00, 0x01, 0xDF}, 4, 0}, + {0x21, NULL, 0, 0}, + {0x11, NULL, 0, 120}, + {0x29, NULL, 0, 0}, + {0x2C, NULL, 0, 0}, + {0x3A, (uint8_t []){0x01}, 1, 0}, + {0x36, (uint8_t []){0x00}, 1, 0}, + {0x35, (uint8_t []){0x01}, 1, 20}, +}; + +const st77922_lcd_init_cmd_t* st77922_board_init_commands(size_t* count) { + *count = sizeof(init_commands) / sizeof(init_commands[0]); + return init_commands; +} diff --git a/Drivers/st77922-module/source/st77922_init.h b/Drivers/st77922-module/source/st77922_init.h new file mode 100644 index 00000000..a80e7def --- /dev/null +++ b/Drivers/st77922-module/source/st77922_init.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +const st77922_lcd_init_cmd_t* st77922_board_init_commands(size_t* count); + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/st77922-module/source/st77922_touch.cpp b/Drivers/st77922-module/source/st77922_touch.cpp new file mode 100644 index 00000000..920712e4 --- /dev/null +++ b/Drivers/st77922-module/source/st77922_touch.cpp @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define TAG "ST77922 touch" +#define GET_CONFIG(device) (static_cast((device)->config)) + +static constexpr uint16_t REG_MAX_TOUCHES = 0x0009; +static constexpr uint16_t REG_TOUCH_INFO = 0x0010; +static constexpr uint16_t REG_TOUCH_POINT0 = 0x0014; +static constexpr uint8_t MAX_POINTS = 10; +static constexpr TickType_t TIMEOUT = pdMS_TO_TICKS(1000); + +struct TouchPoint { + uint16_t x; + uint16_t y; +}; + +struct St77922TouchInternal { + Device* i2c; + GpioDescriptor* reset; + uint8_t supported_points; + uint8_t point_count; + TouchPoint points[MAX_POINTS]; + bool swap_xy; + bool mirror_x; + bool mirror_y; +}; + +static error_t read_register(Device* i2c, uint16_t reg, uint8_t* data, size_t size, TickType_t timeout) { + const uint8_t address[] = { + static_cast(reg >> 8), + static_cast(reg & 0xFF), + }; + return i2c_controller_write_read(i2c, 0x55, address, sizeof(address), data, size, timeout); +} + +static error_t start(Device* device) { + auto* parent = device_get_parent(device); + check(device_get_type(parent) == &I2C_CONTROLLER_TYPE); + const auto* config = GET_CONFIG(device); + + auto* internal = static_cast(calloc(1, sizeof(St77922TouchInternal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + internal->i2c = parent; + internal->supported_points = 1; + internal->swap_xy = config->swap_xy; + internal->mirror_x = config->mirror_x; + internal->mirror_y = config->mirror_y; + + if (config->pin_reset.gpio_controller != nullptr) { + internal->reset = gpio_descriptor_acquire(config->pin_reset.gpio_controller, + config->pin_reset.pin, GPIO_FLAG_DIRECTION_OUTPUT | GPIO_FLAG_ACTIVE_LOW, GPIO_OWNER_GPIO); + if (internal->reset == nullptr) { + free(internal); + return ERROR_RESOURCE; + } + if (gpio_descriptor_set_level(internal->reset, true) != ERROR_NONE) { + gpio_descriptor_release(internal->reset); + free(internal); + return ERROR_RESOURCE; + } + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_descriptor_set_level(internal->reset, false); + vTaskDelay(pdMS_TO_TICKS(100)); + } + + uint8_t supported = 0; + if (read_register(parent, REG_MAX_TOUCHES, &supported, 1, TIMEOUT) == ERROR_NONE + && supported > 0 && supported <= MAX_POINTS) { + internal->supported_points = supported; + } + LOG_I(TAG, "Controller reports %u touch points", internal->supported_points); + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + if (internal->reset != nullptr) { + gpio_descriptor_release(internal->reset); + } + free(internal); + device_set_driver_data(device, nullptr); + return ERROR_NONE; +} + +static error_t enter_sleep(Device*) { return ERROR_NOT_SUPPORTED; } +static error_t exit_sleep(Device*) { return ERROR_NOT_SUPPORTED; } + +static error_t read_data(Device* device, TickType_t timeout) { + auto* internal = static_cast(device_get_driver_data(device)); + uint8_t touch_info = 0; + if (read_register(internal->i2c, REG_TOUCH_INFO, &touch_info, 1, timeout) != ERROR_NONE) { + internal->point_count = 0; + return ERROR_RESOURCE; + } + if ((touch_info & 0x08) == 0) { + internal->point_count = 0; + return ERROR_NONE; + } + + uint8_t data[7 * MAX_POINTS] = {}; + const size_t read_size = 7 * internal->supported_points; + if (read_register(internal->i2c, REG_TOUCH_POINT0, data, read_size, timeout) != ERROR_NONE) { + internal->point_count = 0; + return ERROR_RESOURCE; + } + + internal->point_count = 0; + for (uint8_t index = 0; index < internal->supported_points; index++) { + const uint8_t offset = index * 7; + if ((data[offset] & 0x80) == 0) { + continue; + } + internal->points[internal->point_count++] = { + .x = static_cast(((data[offset] & 0x3F) << 8) | data[offset + 1]), + .y = static_cast(((data[offset + 2] & 0x3F) << 8) | data[offset + 3]), + }; + } + return ERROR_NONE; +} + +static bool get_touched_points(Device* device, uint16_t* x, uint16_t* y, uint16_t* strength, + uint8_t* count, uint8_t maximum) { + auto* internal = static_cast(device_get_driver_data(device)); + const auto* config = GET_CONFIG(device); + *count = std::min(internal->point_count, maximum); + for (uint8_t index = 0; index < *count; index++) { + uint16_t point_x = internal->points[index].x; + uint16_t point_y = internal->points[index].y; + if (internal->swap_xy) { + std::swap(point_x, point_y); + } + const uint16_t max_x = internal->swap_xy ? config->y_max : config->x_max; + const uint16_t max_y = internal->swap_xy ? config->x_max : config->y_max; + x[index] = internal->mirror_x ? max_x - point_x : point_x; + y[index] = internal->mirror_y ? max_y - point_y : point_y; + if (strength != nullptr) { + strength[index] = 0; + } + } + return *count > 0; +} + +static error_t set_swap_xy(Device* device, bool value) { + static_cast(device_get_driver_data(device))->swap_xy = value; + return ERROR_NONE; +} +static error_t get_swap_xy(Device* device, bool* value) { + *value = static_cast(device_get_driver_data(device))->swap_xy; + return ERROR_NONE; +} +static error_t set_mirror_x(Device* device, bool value) { + static_cast(device_get_driver_data(device))->mirror_x = value; + return ERROR_NONE; +} +static error_t get_mirror_x(Device* device, bool* value) { + *value = static_cast(device_get_driver_data(device))->mirror_x; + return ERROR_NONE; +} +static error_t set_mirror_y(Device* device, bool value) { + static_cast(device_get_driver_data(device))->mirror_y = value; + return ERROR_NONE; +} +static error_t get_mirror_y(Device* device, bool* value) { + *value = static_cast(device_get_driver_data(device))->mirror_y; + return ERROR_NONE; +} + +static const PointerApi pointer_api = { + .enter_sleep = enter_sleep, + .exit_sleep = exit_sleep, + .read_data = read_data, + .get_touched_points = get_touched_points, + .set_swap_xy = set_swap_xy, + .get_swap_xy = get_swap_xy, + .set_mirror_x = set_mirror_x, + .get_mirror_x = get_mirror_x, + .set_mirror_y = set_mirror_y, + .get_mirror_y = get_mirror_y, +}; + +Driver st77922_touch_driver = { + .name = "st77922-touch", + .compatible = (const char*[]) { "sitronix,st77922-touch", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &pointer_api, + .device_type = &POINTER_TYPE, + .owner = &st77922_module, + .internal = nullptr +}; diff --git a/Firmware/idf_component.yml b/Firmware/idf_component.yml index 67cbc771..3b2206a0 100644 --- a/Firmware/idf_component.yml +++ b/Firmware/idf_component.yml @@ -42,6 +42,10 @@ dependencies: version: "1.3.4" rules: - if: "target in [esp32, esp32s3]" + espressif/esp_lcd_st77922: + version: "1.0.3" + rules: + - if: "target in [esp32s3]" espressif/esp_lcd_gc9a01: "2.0.3" espressif/esp_lcd_jd9165: version: "1.0.3" @@ -85,4 +89,3 @@ dependencies: rules: - if: "target in [esp32s3, esp32p4]" idf: '5.5.2' -