// 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 };