Repository: lvgl/lvgl
Pull request #7815: feat(drivers): add ft81x framebuffer driver
Author: login: liamHowatt
Created: 2025-02-20T23:05:51Z | Merged: 2025-04-28T19:25:56Z

Framebuffer driver for EVE ft81x/bt81x smart display controller.

Working example: https://github.com/liamHowatt/esp_idf_dev/blob/ft81x-eve/main/main.c

Only tested on Newhaven 800x480 5.0" with ESP32 Devkit V1.

Only supports single-buffered partial.

### Notes
- Update the [Documentation](https://github.com/lvgl/lvgl/tree/master/docs) if needed.
- Add [Examples](https://github.com/lvgl/lvgl/tree/master/examples) if relevant.
- Add [Tests](https://github.com/lvgl/lvgl/blob/master/tests/README.md) if applicable.
- If you added new options to `lv_conf_template.h` run [lv_conf_internal_gen.py](https://github.com/lvgl/lvgl/blob/master/scripts/lv_conf_internal_gen.py) and update [Kconfig](https://github.com/lvgl/lvgl/blob/master/Kconfig).
- Run `scripts/code-format.py` (`astyle v3.4.12` needs to installed by running `cd scripts; ./install_astyle.sh`) and follow the [Code Conventions](https://docs.lvgl.io/master/CODING_STYLE.html).
- Mark the Pull request as [Draft](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request) while you are working on the first version, and mark is as _Ready_ when it's ready for review.
- When changes were requested, [re-request review](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review) to notify the maintainers.
- Help us to review this Pull Request! Anyone can [approve or request changes](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/approving-a-pull-request-with-required-reviews).



Diff:
diff --git a/Kconfig b/Kconfig
index 8688396f1f3a..33d07af44c5c 100644
--- a/Kconfig
+++ b/Kconfig
@@ -2018,6 +2018,10 @@ menu "LVGL configuration"
 			default n
 			depends on LV_USE_ST_LTDC && !LV_USE_DRAW_DMA2D
 
+		config LV_USE_FT81X
+			bool "Use ft81x EVE driver"
+			default n
+
 		config LV_USE_WINDOWS
 			bool "Use LVGL Windows backend"
 			depends on LV_OS_WINDOWS
diff --git a/docs/details/integration/driver/display/ft81x.rst b/docs/details/integration/driver/display/ft81x.rst
new file mode 100644
index 000000000000..eff21f9f9bb0
--- /dev/null
+++ b/docs/details/integration/driver/display/ft81x.rst
@@ -0,0 +1,114 @@
+=====
+FT81x
+=====
+
+A minimal framebuffer driver for EVE FT81x smart display controllers. Works for BT81x too.
+
+Single-buffered partial render mode supported for now. It may not support DSPI or QSPI currently.
+
+Tested at 32 MHz on ft812 and at 23 MHz on bt817.
+
+Usage
+*****
+
+.. code-block:: c
+
+    #define FB_SIZE             800 * 2 * 50
+    #define MAX_TRANSFER_SIZE   FB_SIZE
+
+    static void spi_cb(lv_display_t * disp, lv_ft81x_spi_operation operation, void * data, uint32_t length)
+    {
+        spi_device_handle_t spi = lv_ft81x_get_user_data(disp);
+        switch(operation) {
+            case LV_FT81X_SPI_OPERATION_CS_ASSERT:
+                gpio_set_level(CS_PIN, 0);
+                break;
+            case LV_FT81X_SPI_OPERATION_CS_DEASSERT:
+                gpio_set_level(CS_PIN, 1);
+                esp_rom_delay_us(10); /* tiny delay in case a CS_ASSERT immediately follows */
+                break;
+            case LV_FT81X_SPI_OPERATION_SEND: {
+                spi_transaction_t trans = {0};
+                while(length) {
+                    uint32_t sz = length < MAX_TRANSFER_SIZE ? length : MAX_TRANSFER_SIZE;
+                    trans.length = sz * 8;
+                    trans.rxlength = 0;
+                    trans.tx_buffer = data;
+                    spi_device_polling_transmit(spi, &trans);
+                    length -= sz;
+                    data += sz;
+                }
+                break;
+            }
+            case LV_FT81X_SPI_OPERATION_RECEIVE: {
+                spi_transaction_t trans = {0};
+                trans.length = length * 8;
+                trans.rxlength = length * 8;
+                trans.rx_buffer = data;
+                spi_device_polling_transmit(spi, &trans);
+                break;
+            }
+        }
+    }
+
+    int main() {
+        /* ... */
+
+        // reset the ft81x
+        gpio_set_level(PD_PIN, 0);
+        vTaskDelay(6 / portTICK_PERIOD_MS);
+        gpio_set_level(PD_PIN, 1);
+        vTaskDelay(21 / portTICK_PERIOD_MS);
+
+        /* ... */
+
+        // taken from https://github.com/lvgl/lvgl_esp32_drivers/blob/9fed1cc47b5a45fec6bae08b55d2147d3b50260c/lvgl_tft/EVE_config.h
+        // NHD-5.0-800480FT-CxXx-xxx 800x480 5.0" Newhaven, resistive or capacitive, FT81x
+        // EVE_NHD_50
+        #define EVE_VSYNC0	(0L)
+        #define EVE_VSYNC1	(3L)
+        #define EVE_VOFFSET	(32L)
+        #define EVE_VCYCLE	(525L)
+        #define EVE_HSYNC0	(0L)
+        #define EVE_HSYNC1	(48L)
+        #define EVE_HOFFSET	(88L)
+        #define EVE_HCYCLE 	(928L)
+        #define EVE_PCLKPOL	(0L)
+        #define EVE_SWIZZLE	(0L)
+        #define EVE_PCLK	(2L)
+        #define EVE_CSPREAD	(1L)
+
+        lv_ft81x_parameters_t params = {
+            .hor_res = 800,
+            .ver_res = 480,
+
+            .hcycle = EVE_HCYCLE,
+            .hoffset = EVE_HOFFSET,
+            .hsync0 = EVE_HSYNC0,
+            .hsync1 = EVE_HSYNC1,
+            .vcycle = EVE_VCYCLE,
+            .voffset = EVE_VOFFSET,
+            .vsync0 = EVE_VSYNC0,
+            .vsync1 = EVE_VSYNC1,
+            .swizzle = EVE_SWIZZLE,
+            .pclkpol = EVE_PCLKPOL,
+            .cspread = EVE_CSPREAD,
+            .pclk = EVE_PCLK,
+
+            .has_crystal = true,
+            .is_bt81x = false
+        };
+
+        static uint8_t fb[FB_SIZE] __attribute__((aligned(4)));
+        lv_display_t * disp = lv_ft81x_create(&params, fb, FB_SIZE, spi_cb, spi);
+
+        /* ... */
+    }
+
+
+Troubleshooting
+***************
+
+If the backlight does not come on (or is too bright or dim),
+try changing the value of ``PWM_DUTY_BACKLIGHT_ON``
+in ``lv_ft81x.c``, which can vary by board.
diff --git a/lv_conf_template.h b/lv_conf_template.h
index b6e11dfc2e85..0346e508e6d2 100644
--- a/lv_conf_template.h
+++ b/lv_conf_template.h
@@ -1254,6 +1254,7 @@
 #define LV_USE_ST7789        0
 #define LV_USE_ST7796        0
 #define LV_USE_ILI9341       0
+#define LV_USE_FT81X         0
 
 #if (LV_USE_ST7735 | LV_USE_ST7789 | LV_USE_ST7796 | LV_USE_ILI9341)
     #define LV_USE_GENERIC_MIPI 1
diff --git a/src/drivers/display/ft81x/lv_ft81x.c b/src/drivers/display/ft81x/lv_ft81x.c
new file mode 100644
index 000000000000..2013d4a1c8b3
--- /dev/null
+++ b/src/drivers/display/ft81x/lv_ft81x.c
@@ -0,0 +1,399 @@
+/**
+ * @file lv_ft81x.c
+ *
+ */
+
+/*********************
+ *      INCLUDES
+ *********************/
+
+#include "lv_ft81x.h"
+#if LV_USE_FT81X
+
+#include "lv_ft81x_defines.h"
+
+#include "../../../stdlib/lv_mem.h"
+#include "../../../stdlib/lv_sprintf.h"
+#include "../../../stdlib/lv_string.h"
+#include "../../../misc/lv_types.h"
+#include "../../../misc/lv_utils.h"
+
+/*********************
+ *      DEFINES
+ *********************/
+
+/* Increase as functionality is added if needed. */
+#define LV_FT81X_CMD_BUF_SIZE 63
+
+/* The PWM value that corresponds to the backlight being "on".
+   0x80 was found to work on at least two boards but should
+   be changed as needed. */
+#define PWM_DUTY_BACKLIGHT_ON 0x80
+
+/**********************
+ *      TYPEDEFS
+ **********************/
+
+typedef struct {
+    lv_ft81x_spi_cb_t spi_cb;
+    void * user_data;
+    uint16_t cmd_offset;
+} lv_ft81x_driver_data_t;
+
+typedef struct {
+    uint32_t buf_len;
+    uint8_t buf[LV_FT81X_CMD_BUF_SIZE];
+} lv_ft81x_cmd_list_t;
+
+/**********************
+ *  STATIC PROTOTYPES
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params);
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map);
+static void delete_cb(lv_event_t * e);
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter);
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address);
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address);
+/* static uint32_t lv_ft81x_read_32(lv_display_t * disp, uint32_t address); */
+static void lv_ft81x_write_8(lv_display_t * disp, uint32_t address, uint8_t val);
+static void lv_ft81x_write_16(lv_display_t * disp, uint32_t address, uint16_t val);
+static void lv_ft81x_write_32(lv_display_t * disp, uint32_t address, uint32_t val);
+static void lv_ft81x_cmd_list_init(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_cmd_list_add_16(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint16_t value);
+static void lv_ft81x_cmd_list_add_32(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint32_t value);
+static void lv_ft81x_cmd_list_send(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_encode_read_address(void * dst_4_bytes, uint32_t address);
+static void lv_ft81x_encode_write_address(void * dst_3_bytes, uint32_t address);
+
+/**********************
+ *  STATIC VARIABLES
+ **********************/
+
+/**********************
+ *      MACROS
+ **********************/
+
+#if LV_BIG_ENDIAN_SYSTEM
+    #define BE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define BE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_32(x) lv_swap_bytes_32(x)
+    #define LE_TO_OR_FROM_NATIVE_16(x) lv_swap_bytes_16(x)
+#else
+    #define BE_TO_OR_FROM_NATIVE_32(x) lv_swap_bytes_32(x)
+    #define BE_TO_OR_FROM_NATIVE_16(x) lv_swap_bytes_16(x)
+    #define LE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+#endif
+
+/**********************
+ *   GLOBAL FUNCTIONS
+ **********************/
+
+lv_display_t * lv_ft81x_create(const lv_ft81x_parameters_t * params, void * partial_buf, uint32_t buf_size,
+                               lv_ft81x_spi_cb_t spi_cb, void * user_data)
+{
+    LV_ASSERT_NULL(spi_cb);
+
+    lv_display_t * disp = lv_display_create(params->hor_res, params->ver_res);
+
+    lv_ft81x_driver_data_t * drv = lv_malloc_zeroed(sizeof(lv_ft81x_driver_data_t));
+    LV_ASSERT_MALLOC(drv);
+    drv->spi_cb = spi_cb;
+    drv->user_data = user_data;
+    drv->cmd_offset = 0;
+    lv_display_set_driver_data(disp, drv);
+    lv_display_set_flush_cb(disp, flush_cb);
+    lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
+    lv_display_set_buffers(disp, partial_buf, NULL, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
+    lv_display_add_event_cb(disp, delete_cb, LV_EVENT_DELETE, NULL);
+
+    lv_result_t init_res = initialize(disp, params);
+    if(init_res != LV_RESULT_OK) {
+        lv_display_delete(disp);
+        return NULL;
+    }
+
+    return disp;
+}
+
+void * lv_ft81x_get_user_data(lv_display_t * disp)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    return drv->user_data;
+}
+
+/**********************
+ *   STATIC FUNCTIONS
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params)
+{
+    lv_ft81x_cmd(disp, params->has_crystal ? EVE_CLKEXT : EVE_CLKINT, 0);
+    if(params->is_bt81x) lv_ft81x_cmd(disp, EVE_CLKSEL, 0x46);
+    lv_ft81x_cmd(disp, EVE_ACTIVE, 0);
+
+    /* at least 40 ms is needed for EVE to become ready. */
+    lv_delay_ms(40);
+
+    uint32_t start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_ID) != 0x7c) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("failed to read 0x7C from the ID register 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_CPURESET) & 0x03) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("CPURESET register coprocessor and touch engines not in \"working status\" 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    if(params->is_bt81x) lv_ft81x_write_32(disp, REG_FREQUENCY, 72000000);
+
+    lv_ft81x_write_8(disp, REG_PWM_DUTY, PWM_DUTY_BACKLIGHT_ON);
+
+    lv_ft81x_write_16(disp, REG_HSIZE,   params->hor_res);   /* active display width */
+    lv_ft81x_write_16(disp, REG_HCYCLE,  params->hcycle);  /* total number of clocks per line, incl front/back porch */
+    lv_ft81x_write_16(disp, REG_HOFFSET, params->hoffset); /* start of active line */
+    lv_ft81x_write_16(disp, REG_HSYNC0,  params->hsync0);  /* start of horizontal sync pulse */
+    lv_ft81x_write_16(disp, REG_HSYNC1,  params->hsync1);  /* end of horizontal sync pulse */
+    lv_ft81x_write_16(disp, REG_VSIZE,   params->ver_res);   /* active display height */
+    lv_ft81x_write_16(disp, REG_VCYCLE,  params->vcycle);  /* total number of lines per screen, including pre/post */
+    lv_ft81x_write_16(disp, REG_VOFFSET, params->voffset); /* start of active screen */
+    lv_ft81x_write_16(disp, REG_VSYNC0,  params->vsync0);  /* start of vertical sync pulse */
+    lv_ft81x_write_16(disp, REG_VSYNC1,  params->vsync1);  /* end of vertical sync pulse */
+    lv_ft81x_write_8(disp, REG_SWIZZLE,  params->swizzle); /* FT8xx output to LCD - pin order */
+    lv_ft81x_write_8(disp, REG_PCLK_POL, params->pclkpol); /* LCD data is clocked in on this PCLK edge */
+    lv_ft81x_write_8(disp, REG_CSPREAD,
+                     params->cspread); /* helps with noise, when set to 1 fewer signals are changed simultaneously, reset-default: 1 */
+
+    /* write a basic display-list to get things started */
+    lv_ft81x_write_32(disp, EVE_RAM_DL, DL_CLEAR_RGB);
+    lv_ft81x_write_32(disp, EVE_RAM_DL + 4, (DL_CLEAR | CLR_COL | CLR_STN | CLR_TAG));
+    lv_ft81x_write_32(disp, EVE_RAM_DL + 8, DL_DISPLAY);    /* end of display list */
+    lv_ft81x_write_32(disp, REG_DLSWAP, EVE_DLSWAP_FRAME);
+
+    /* nothing is being displayed yet... the pixel clock is still 0x00 */
+    lv_ft81x_write_8(disp, REG_GPIO,
+                     0x80); /* enable the DISP signal to the LCD panel, it is set to output in REG_GPIO_DIR by default */
+    lv_ft81x_write_8(disp, REG_PCLK, params->pclk); /* now start clocking data to the LCD panel */
+
+    LV_ASSERT(lv_ft81x_read_16(disp, REG_CMD_READ) != 0xfff);
+
+    LV_ASSERT(0 == lv_ft81x_read_16(disp, REG_CMD_WRITE));
+
+
+    lv_ft81x_cmd_list_t cmd_list;
+    lv_ft81x_cmd_list_init(disp, &cmd_list);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_MEMSET);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0); /* address */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0x00); /* val */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 2 * params->hor_res * params->ver_res); /* count */
+    lv_ft81x_cmd_list_send(disp, &cmd_list);
+
+    lv_ft81x_cmd_list_init(disp, &cmd_list);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_DLSTART);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_CLEAR_RGB | 0); /* clear to black */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_CLEAR | CLR_COL | CLR_STN | CLR_TAG);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(0));
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(20));
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_SETBITMAP);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0); /* address */
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, EVE_RGB565);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, params->hor_res);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, params->ver_res);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, 0);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_BEGIN | EVE_BITMAPS);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, VERTEX2F(0, 0));
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_END);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(0));
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_DISPLAY);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_SWAP);
+
+    lv_ft81x_cmd_list_send(disp, &cmd_list);
+
+
+    return LV_RESULT_OK;
+}
+
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+
+    if(drv->spi_cb == NULL) {
+        LV_LOG_ERROR("The SPI callback is NULL.");
+        return;
+    }
+
+    int32_t hor_res = lv_display_get_horizontal_resolution(disp);
+    uint32_t disp_row_bytes = hor_res * 2;
+
+    uint32_t address = disp_row_bytes * area->y1 + area->x1 * 2;
+    uint8_t encoded_address[3];
+
+    if(lv_area_get_width(area) == hor_res) {
+        lv_ft81x_encode_write_address(encoded_address, address);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, encoded_address, sizeof(encoded_address));
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, px_map, lv_area_get_size(area) * 2);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    }
+    else {
+        uint32_t area_height = lv_area_get_height(area);
+        uint32_t area_row_bytes = lv_area_get_width(area) * 2;
+        for(uint32_t i = 0; i < area_height; i++) {
+            lv_ft81x_encode_write_address(encoded_address, address);
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, encoded_address, sizeof(encoded_address));
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, px_map, area_row_bytes);
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+            address += disp_row_bytes;
+            px_map += area_row_bytes;
+        }
+    }
+
+    lv_display_flush_ready(disp);
+}
+
+static void delete_cb(lv_event_t * e)
+{
+    lv_display_t * disp = lv_event_get_target(e);
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    lv_free(drv);
+}
+
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    uint8_t data[3] = {command, parameter, 0x00};
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, data, sizeof(data));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+}
+
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    lv_ft81x_encode_read_address(&address, address);
+    uint8_t val;
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, &address, sizeof(address));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_RECEIVE, &val, sizeof(val));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    return val;
+}
+
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    lv_ft81x_encode_read_address(&address, address);
+    uint16_t val;
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, &address, sizeof(address));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_RECEIVE, &val, sizeof(val));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    val = LE_TO_OR_FROM_NATIVE_16(val);
+    return val;
+}
+
+static void lv_ft81x_write_8(lv_display_t * disp, uint32_t address, uint8_t val)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    uint8_t data[4];
+    lv_ft81x_encode_write_address(data, address);
+    data[3] = val;
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, data, sizeof(data));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+}
+
+static void lv_ft81x_write_16(lv_display_t * disp, uint32_t address, uint16_t val)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    val = LE_TO_OR_FROM_NATIVE_16(val);
+    uint8_t data[5];
+    lv_ft81x_encode_write_address(data, address);
+    lv_memcpy(data + 3, &val, 2);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, data, sizeof(data));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+}
+
+static void lv_ft81x_write_32(lv_display_t * disp, uint32_t address, uint32_t val)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    val = LE_TO_OR_FROM_NATIVE_32(val);
+    uint8_t data[7];
+    lv_ft81x_encode_write_address(data, address);
+    lv_memcpy(data + 3, &val, 4);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, data, sizeof(data));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+}
+
+static void lv_ft81x_cmd_list_init(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    LV_ASSERT_MSG(LV_FT81X_CMD_BUF_SIZE >= 3, "increase LV_FT81X_CMD_BUF_SIZE as needed");
+    lv_ft81x_encode_write_address(cmd_list->buf, EVE_RAM_CMD + drv->cmd_offset);
+    cmd_list->buf_len = 3;
+}
+
+static void lv_ft81x_cmd_list_add_16(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint16_t value)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    uint8_t * buf_dst = &cmd_list->buf[cmd_list->buf_len];
+    cmd_list->buf_len += 2;
+    LV_ASSERT_MSG(cmd_list->buf_len <= LV_FT81X_CMD_BUF_SIZE, "increase LV_FT81X_CMD_BUF_SIZE as needed");
+    value = LE_TO_OR_FROM_NATIVE_16(value);
+    lv_memcpy(buf_dst, &value, 2);
+    drv->cmd_offset = (drv->cmd_offset + 2) & 0xfff; /* circular */
+}
+
+static void lv_ft81x_cmd_list_add_32(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint32_t value)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    uint8_t * buf_dst = &cmd_list->buf[cmd_list->buf_len];
+    cmd_list->buf_len += 4;
+    LV_ASSERT_MSG(cmd_list->buf_len <= LV_FT81X_CMD_BUF_SIZE, "increase LV_FT81X_CMD_BUF_SIZE as needed");
+    value = LE_TO_OR_FROM_NATIVE_32(value);
+    lv_memcpy(buf_dst, &value, 4);
+    drv->cmd_offset = (drv->cmd_offset + 4) & 0xfff; /* circular */
+}
+
+static void lv_ft81x_cmd_list_send(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, cmd_list->buf, cmd_list->buf_len);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    lv_ft81x_write_16(disp, REG_CMD_WRITE, drv->cmd_offset);
+}
+
+static void lv_ft81x_encode_read_address(void * dst_4_bytes, uint32_t address)
+{
+    address = BE_TO_OR_FROM_NATIVE_32(address << 8);
+    lv_memcpy(dst_4_bytes, &address, 4);
+}
+
+static void lv_ft81x_encode_write_address(void * dst_3_bytes, uint32_t address)
+{
+    address = BE_TO_OR_FROM_NATIVE_32((address | 0x800000) << 8);
+    lv_memcpy(dst_3_bytes, &address, 3);
+}
+
+#endif /*LV_USE_FT81X*/
diff --git a/src/drivers/display/ft81x/lv_ft81x.h b/src/drivers/display/ft81x/lv_ft81x.h
new file mode 100644
index 000000000000..0995d4a47de0
--- /dev/null
+++ b/src/drivers/display/ft81x/lv_ft81x.h
@@ -0,0 +1,93 @@
+/**
+ * @file lv_ft81x.h
+ *
+ */
+
+#ifndef LV_FT81X_H
+#define LV_FT81X_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ *      INCLUDES
+ *********************/
+
+#include "../../../lv_conf_internal.h"
+#if LV_USE_FT81X
+
+#include "../../../display/lv_display.h"
+
+/*********************
+ *      DEFINES
+ *********************/
+
+/**********************
+ *      TYPEDEFS
+ **********************/
+
+typedef struct {
+    uint16_t hor_res;
+    uint16_t ver_res;
+
+    uint16_t hcycle;
+    uint16_t hoffset;
+    uint16_t hsync0;
+    uint16_t hsync1;
+    uint16_t vcycle;
+    uint16_t voffset;
+    uint16_t vsync0;
+    uint16_t vsync1;
+    uint8_t swizzle;
+    uint8_t pclkpol;
+    uint8_t cspread;
+    uint8_t pclk;
+
+    bool has_crystal;
+    bool is_bt81x;
+} lv_ft81x_parameters_t;
+
+typedef enum {
+    LV_FT81X_SPI_OPERATION_CS_ASSERT,
+    LV_FT81X_SPI_OPERATION_CS_DEASSERT,
+    LV_FT81X_SPI_OPERATION_SEND,
+    LV_FT81X_SPI_OPERATION_RECEIVE
+} lv_ft81x_spi_operation;
+
+typedef void (*lv_ft81x_spi_cb_t)(lv_display_t * disp, lv_ft81x_spi_operation operation, void * data, uint32_t length);
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a framebuffer-based ft81x driver display.
+ * @param params      pointer to a struct of display panel properties. does not need to be static.
+ * @param partial_buf a single partial buffer
+ * @param buf_size    size of the partial buffer
+ * @param spi_cb      a callback called by the driver to perform SPI operations
+ * @param user_data   use `lv_ft81x_get_user_data` to get this pointer inside the SPI callback
+ * @return pointer to the display
+ */
+lv_display_t * lv_ft81x_create(const lv_ft81x_parameters_t * params, void * partial_buf, uint32_t buf_size,
+                               lv_ft81x_spi_cb_t spi_cb, void * user_data);
+
+/**
+ * Get the `user_data` parameter that was passed to `lv_ft81x_create`. Useful in the SPI callback.
+ * @param disp      pointer to the ft81x display
+ * @return          the `user_data` pointer
+ */
+void * lv_ft81x_get_user_data(lv_display_t * disp);
+
+/**********************
+ *      MACROS
+ **********************/
+
+#endif /*LV_USE_FT81X*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_FT81X_H*/
diff --git a/src/drivers/display/ft81x/lv_ft81x_defines.h b/src/drivers/display/ft81x/lv_ft81x_defines.h
new file mode 100644
index 000000000000..ae9a51109285
--- /dev/null
+++ b/src/drivers/display/ft81x/lv_ft81x_defines.h
@@ -0,0 +1,846 @@
+/**
+ * @file lv_ft81x_defines.h
+ *
+ * Copied from https://github.com/lvgl/lvgl_esp32_drivers/blob/9fed1cc47b5a45fec6bae08b55d2147d3b50260c/lvgl_tft/EVE.h
+ */
+
+#ifndef LV_FT81X_DEFINES_H
+#define LV_FT81X_DEFINES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ *      INCLUDES
+ *********************/
+
+/*********************
+ *      DEFINES
+ *********************/
+
+/* ft81x and ft80x each define exclusive
+ * definitions. Define ft81x for now.
+ *
+ * bt81x is a superset of ft81x and ft80x.
+ * Don't define it since its special
+ * definitions are not required to use it.
+ */
+#define FT81X_ENABLE
+/* #define BT81X_ENABLE */
+
+
+#define DL_CLEAR        0x26000000UL /* requires OR'd arguments */
+#define DL_CLEAR_RGB    0x02000000UL /* requires OR'd arguments */
+#define DL_COLOR_RGB    0x04000000UL /* requires OR'd arguments */
+#define DL_POINT_SIZE   0x0D000000UL /* requires OR'd arguments */
+#define DL_END          0x21000000UL
+#define DL_BEGIN        0x1F000000UL /* requires OR'd arguments */
+#define DL_DISPLAY      0x00000000UL
+
+#define CLR_COL              0x4
+#define CLR_STN              0x2
+#define CLR_TAG              0x1
+
+/* SPI SIO/DIO/QIO tranfer widths */
+#define SPI_WIDTH_SIO       0x0
+#define SPI_WIDTH_DIO       0x1
+#define SPI_WIDTH_QIO       0x2
+
+
+/* Host commands */
+#define EVE_ACTIVE  0x00  /* place FT8xx in active state */
+#define EVE_STANDBY 0x41  /* place FT8xx in Standby (clk running) */
+#define EVE_SLEEP   0x42  /* place FT8xx in Sleep (clk off) */
+#define EVE_PWRDOWN 0x50  /* place FT8xx in Power Down (core off) */
+#define EVE_CLKEXT  0x44  /* select external clock source */
+#define EVE_CLKINT  0x48  /* select internal clock source */
+#define EVE_CORERST 0x68  /* reset core - all registers default and processors reset */
+#define EVE_CLK48M  0x62  /* select 48MHz PLL output */
+#define EVE_CLK36M  0x61  /* select 36MHz PLL output */
+
+
+/* defines used for graphics commands */
+#define EVE_NEVER                0UL
+#define EVE_LESS                 1UL
+#define EVE_LEQUAL               2UL
+#define EVE_GREATER              3UL
+#define EVE_GEQUAL               4UL
+#define EVE_EQUAL                5UL
+#define EVE_NOTEQUAL             6UL
+#define EVE_ALWAYS               7UL
+
+
+/* Bitmap formats */
+#define EVE_ARGB1555             0UL
+#define EVE_L1                   1UL
+#define EVE_L4                   2UL
+#define EVE_L8                   3UL
+#define EVE_RGB332               4UL
+#define EVE_ARGB2                5UL
+#define EVE_ARGB4                6UL
+#define EVE_RGB565               7UL
+#define EVE_PALETTED             8UL
+#define EVE_TEXT8X8              9UL
+#define EVE_TEXTVGA              10UL
+#define EVE_BARGRAPH             11UL
+
+
+/* Bitmap filter types */
+#define EVE_NEAREST              0UL
+#define EVE_BILINEAR             1UL
+
+
+/* Bitmap wrap types */
+#define EVE_BORDER               0UL
+#define EVE_REPEAT               1UL
+
+
+/* Stencil defines */
+#define EVE_KEEP                 1UL
+#define EVE_REPLACE              2UL
+#define EVE_INCR                 3UL
+#define EVE_DECR                 4UL
+#define EVE_INVERT               5UL
+
+
+/* Graphics display list swap defines */
+#define EVE_DLSWAP_DONE          0UL
+#define EVE_DLSWAP_LINE          1UL
+#define EVE_DLSWAP_FRAME         2UL
+
+
+/* Interrupt bits */
+#define EVE_INT_SWAP             0x01
+#define EVE_INT_TOUCH            0x02
+#define EVE_INT_TAG              0x04
+#define EVE_INT_SOUND            0x08
+#define EVE_INT_PLAYBACK         0x10
+#define EVE_INT_CMDEMPTY         0x20
+#define EVE_INT_CMDFLAG          0x40
+#define EVE_INT_CONVCOMPLETE     0x80
+
+
+/* Touch mode */
+#define EVE_TMODE_OFF           0
+#define EVE_TMODE_ONESHOT       1
+#define EVE_TMODE_FRAME         2
+#define EVE_TMODE_CONTINUOUS    3
+
+
+/* Alpha blending */
+#define EVE_ZERO                 0UL
+#define EVE_ONE                  1UL
+#define EVE_SRC_ALPHA            2UL
+#define EVE_DST_ALPHA            3UL
+#define EVE_ONE_MINUS_SRC_ALPHA  4UL
+#define EVE_ONE_MINUS_DST_ALPHA  5UL
+
+
+/* Graphics primitives */
+#define EVE_BITMAPS              1UL
+#define EVE_POINTS               2UL
+#define EVE_LINES                3UL
+#define EVE_LINE_STRIP           4UL
+#define EVE_EDGE_STRIP_R         5UL
+#define EVE_EDGE_STRIP_L         6UL
+#define EVE_EDGE_STRIP_A         7UL
+#define EVE_EDGE_STRIP_B         8UL
+#define EVE_RECTS                9UL
+
+
+/* Widget command */
+#define EVE_OPT_MONO             1
+#define EVE_OPT_NODL             2
+#define EVE_OPT_FLAT             256
+#define EVE_OPT_CENTERX          512
+#define EVE_OPT_CENTERY          1024
+#define EVE_OPT_CENTER           (EVE_OPT_CENTERX | EVE_OPT_CENTERY)
+#define EVE_OPT_NOBACK           4096
+#define EVE_OPT_NOTICKS          8192
+#define EVE_OPT_NOHM             16384
+#define EVE_OPT_NOPOINTER        16384
+#define EVE_OPT_NOSECS           32768
+#define EVE_OPT_NOHANDS          49152
+#define EVE_OPT_RIGHTX           2048
+#define EVE_OPT_SIGNED           256
+
+
+/* Defines related to inbuilt font */
+#define EVE_NUMCHAR_PERFONT         (128L)  /* number of font characters per bitmap handle */
+#define EVE_FONT_TABLE_SIZE         (148L)  /* size of the font table - utilized for loopup by the graphics engine */
+#define EVE_FONT_TABLE_POINTER  (0xFFFFCUL) /* pointer to the inbuilt font tables starting from bitmap handle 16 */
+
+
+/* Audio sample type defines */
+#define EVE_LINEAR_SAMPLES       0UL    /* 8bit signed samples */
+#define EVE_ULAW_SAMPLES         1UL    /* 8bit ulaw samples */
+#define EVE_ADPCM_SAMPLES        2UL    /* 4bit ima adpcm samples */
+
+
+/* Synthesized sound */
+#define EVE_SILENCE              0x00
+#define EVE_SQUAREWAVE           0x01
+#define EVE_SINEWAVE             0x02
+#define EVE_SAWTOOTH             0x03
+#define EVE_TRIANGLE             0x04
+#define EVE_BEEPING              0x05
+#define EVE_ALARM                0x06
+#define EVE_WARBLE               0x07
+#define EVE_CAROUSEL             0x08
+#define EVE_PIPS(n)              (0x0F + (n))
+#define EVE_HARP                 0x40
+#define EVE_XYLOPHONE            0x41
+#define EVE_TUBA                 0x42
+#define EVE_GLOCKENSPIEL         0x43
+#define EVE_ORGAN                0x44
+#define EVE_TRUMPET              0x45
+#define EVE_PIANO                0x46
+#define EVE_CHIMES               0x47
+#define EVE_MUSICBOX             0x48
+#define EVE_BELL                 0x49
+#define EVE_CLICK                0x50
+#define EVE_SWITCH               0x51
+#define EVE_COWBELL              0x52
+#define EVE_NOTCH                0x53
+#define EVE_HIHAT                0x54
+#define EVE_KICKDRUM             0x55
+#define EVE_POP                  0x56
+#define EVE_CLACK                0x57
+#define EVE_CHACK                0x58
+#define EVE_MUTE                 0x60
+#define EVE_UNMUTE               0x61
+
+
+/* Synthesized sound frequencies, midi note */
+#define EVE_MIDI_A0   21
+#define EVE_MIDI_A_0  22
+#define EVE_MIDI_B0   23
+#define EVE_MIDI_C1   24
+#define EVE_MIDI_C_1  25
+#define EVE_MIDI_D1   26
+#define EVE_MIDI_D_1  27
+#define EVE_MIDI_E1   28
+#define EVE_MIDI_F1   29
+#define EVE_MIDI_F_1  30
+#define EVE_MIDI_G1   31
+#define EVE_MIDI_G_1  32
+#define EVE_MIDI_A1   33
+#define EVE_MIDI_A_1  34
+#define EVE_MIDI_B1   35
+#define EVE_MIDI_C2   36
+#define EVE_MIDI_C_2  37
+#define EVE_MIDI_D2   38
+#define EVE_MIDI_D_2  39
+#define EVE_MIDI_E2   40
+#define EVE_MIDI_F2   41
+#define EVE_MIDI_F_2  42
+#define EVE_MIDI_G2   43
+#define EVE_MIDI_G_2  44
+#define EVE_MIDI_A2   45
+#define EVE_MIDI_A_2  46
+#define EVE_MIDI_B2   47
+#define EVE_MIDI_C3   48
+#define EVE_MIDI_C_3  49
+#define EVE_MIDI_D3   50
+#define EVE_MIDI_D_3  51
+#define EVE_MIDI_E3   52
+#define EVE_MIDI_F3   53
+#define EVE_MIDI_F_3  54
+#define EVE_MIDI_G3   55
+#define EVE_MIDI_G_3  56
+#define EVE_MIDI_A3   57
+#define EVE_MIDI_A_3  58
+#define EVE_MIDI_B3   59
+#define EVE_MIDI_C4   60
+#define EVE_MIDI_C_4  61
+#define EVE_MIDI_D4   62
+#define EVE_MIDI_D_4  63
+#define EVE_MIDI_E4   64
+#define EVE_MIDI_F4   65
+#define EVE_MIDI_F_4  66
+#define EVE_MIDI_G4   67
+#define EVE_MIDI_G_4  68
+#define EVE_MIDI_A4   69
+#define EVE_MIDI_A_4  70
+#define EVE_MIDI_B4   71
+#define EVE_MIDI_C5   72
+#define EVE_MIDI_C_5  73
+#define EVE_MIDI_D5   74
+#define EVE_MIDI_D_5  75
+#define EVE_MIDI_E5   76
+#define EVE_MIDI_F5   77
+#define EVE_MIDI_F_5  78
+#define EVE_MIDI_G5   79
+#define EVE_MIDI_G_5  80
+#define EVE_MIDI_A5   81
+#define EVE_MIDI_A_5  82
+#define EVE_MIDI_B5   83
+#define EVE_MIDI_C6   84
+#define EVE_MIDI_C_6  85
+#define EVE_MIDI_D6   86
+#define EVE_MIDI_D_6  87
+#define EVE_MIDI_E6   88
+#define EVE_MIDI_F6   89
+#define EVE_MIDI_F_6  90
+#define EVE_MIDI_G6   91
+#define EVE_MIDI_G_6  92
+#define EVE_MIDI_A6   93
+#define EVE_MIDI_A_6  94
+#define EVE_MIDI_B6   95
+#define EVE_MIDI_C7   96
+#define EVE_MIDI_C_7  97
+#define EVE_MIDI_D7   98
+#define EVE_MIDI_D_7  99
+#define EVE_MIDI_E7   100
+#define EVE_MIDI_F7   101
+#define EVE_MIDI_F_7  102
+#define EVE_MIDI_G7   103
+#define EVE_MIDI_G_7  104
+#define EVE_MIDI_A7   105
+#define EVE_MIDI_A_7  106
+#define EVE_MIDI_B7   107
+#define EVE_MIDI_C8   108
+
+
+/* GPIO bits */
+#define EVE_GPIO0   0
+#define EVE_GPIO1   1   /* default gpio pin for audio shutdown, 1 - enable, 0 - disable */
+#define EVE_GPIO7   7   /* default gpio pin for display enable, 1 - enable, 0 - disable */
+
+
+/* Display rotation */
+#define EVE_DISPLAY_0       0   /* 0 degrees rotation */
+#define EVE_DISPLAY_180     1   /* 180 degrees rotation */
+
+
+/* commands common to EVE/EVE2/EVE3 */
+#define CMD_APPEND              0xFFFFFF1E
+#define CMD_BGCOLOR             0xFFFFFF09
+#define CMD_BUTTON              0xFFFFFF0D
+#define CMD_CALIBRATE           0xFFFFFF15
+#define CMD_CLOCK               0xFFFFFF14
+#define CMD_COLDSTART           0xFFFFFF32
+#define CMD_DIAL                0xFFFFFF2D
+#define CMD_DLSTART             0xFFFFFF00
+#define CMD_FGCOLOR             0xFFFFFF0A
+#define CMD_GAUGE               0xFFFFFF13
+#define CMD_GETMATRIX           0xFFFFFF33
+#define CMD_GETPROPS            0xFFFFFF25
+#define CMD_GETPTR              0xFFFFFF23
+#define CMD_GRADCOLOR           0xFFFFFF34
+#define CMD_GRADIENT            0xFFFFFF0B
+#define CMD_INFLATE             0xFFFFFF22
+#define CMD_INTERRUPT           0xFFFFFF02
+#define CMD_KEYS                0xFFFFFF0E
+#define CMD_LOADIDENTITY        0xFFFFFF26
+#define CMD_LOADIMAGE           0xFFFFFF24
+#define CMD_LOGO                0xFFFFFF31
+#define CMD_MEMCPY              0xFFFFFF1D
+#define CMD_MEMCRC              0xFFFFFF18
+#define CMD_MEMSET              0xFFFFFF1B
+#define CMD_MEMWRITE            0xFFFFFF1A
+#define CMD_MEMZERO             0xFFFFFF1C
+#define CMD_NUMBER              0xFFFFFF2E
+#define CMD_PROGRESS            0xFFFFFF0F
+#define CMD_REGREAD             0xFFFFFF19
+#define CMD_ROTATE              0xFFFFFF29
+#define CMD_SCALE               0xFFFFFF28
+#define CMD_SCREENSAVER         0xFFFFFF2F
+#define CMD_SCROLLBAR           0xFFFFFF11
+#define CMD_SETFONT             0xFFFFFF2B
+#define CMD_SETMATRIX           0xFFFFFF2A
+#define CMD_SKETCH              0xFFFFFF30
+#define CMD_SLIDER              0xFFFFFF10
+#define CMD_SNAPSHOT            0xFFFFFF1F
+#define CMD_SPINNER             0xFFFFFF16
+#define CMD_STOP                0xFFFFFF17
+#define CMD_SWAP                0xFFFFFF01
+#define CMD_TEXT                0xFFFFFF0C
+#define CMD_TOGGLE              0xFFFFFF12
+#define CMD_TRACK               0xFFFFFF2C
+#define CMD_TRANSLATE           0xFFFFFF27
+
+
+/* the following are undocumented commands that therefore should not be used */
+#if 0
+#define CMD_CRC                 0xFFFFFF03
+#define CMD_HAMMERAUX           0xFFFFFF04
+#define CMD_MARCH               0xFFFFFF05
+#define CMD_IDCT                0xFFFFFF06
+#define CMD_EXECUTE             0xFFFFFF07
+#define CMD_GETPOINT            0xFFFFFF08
+#define CMD_TOUCH_TRANSFORM     0xFFFFFF20
+#endif
+
+
+/* FT8xx graphics engine specific macros useful for static display list generation */
+#define ALPHA_FUNC(func,ref) ((9UL<<24)|(((func)&7UL)<<8)|(((ref)&255UL)<<0))
+#define BEGIN(prim) ((31UL<<24)|(((prim)&15UL)<<0))
+#define BITMAP_HANDLE(handle) ((5UL<<24)|(((handle)&31UL)<<0))
+#define BITMAP_LAYOUT(format,linestride,height) ((7UL<<24)|(((format)&31UL)<<19)|(((linestride)&1023UL)<<9)|(((height)&511UL)<<0))
+#define BITMAP_SIZE(filter,wrapx,wrapy,width,height) ((8UL<<24)|(((filter)&1UL)<<20)|(((wrapx)&1UL)<<19)|(((wrapy)&1UL)<<18)|(((width)&511UL)<<9)|(((height)&511UL)<<0))
+#define BITMAP_TRANSFORM_A(a) ((21UL<<24)|(((a)&131071UL)<<0))
+#define BITMAP_TRANSFORM_B(b) ((22UL<<24)|(((b)&131071UL)<<0))
+#define BITMAP_TRANSFORM_C(c) ((23UL<<24)|(((c)&16777215UL)<<0))
+#define BITMAP_TRANSFORM_D(d) ((24UL<<24)|(((d)&131071UL)<<0))
+#define BITMAP_TRANSFORM_E(e) ((25UL<<24)|(((e)&131071UL)<<0))
+#define BITMAP_TRANSFORM_F(f) ((26UL<<24)|(((f)&16777215UL)<<0))
+#define BLEND_FUNC(src,dst) ((11UL<<24)|(((src)&7UL)<<3)|(((dst)&7UL)<<0))
+#define CALL(dest) ((29UL<<24)|(((dest)&65535UL)<<0))
+#define CELL(cell) ((6UL<<24)|(((cell)&127UL)<<0))
+#define CLEAR(c,s,t) ((38UL<<24)|(((c)&1UL)<<2)|(((s)&1UL)<<1)|(((t)&1UL)<<0))
+#define CLEAR_COLOR_A(alpha) ((15UL<<24)|(((alpha)&255UL)<<0))
+#define CLEAR_COLOR_RGB(red,green,blue) ((2UL<<24)|(((red)&255UL)<<16)|(((green)&255UL)<<8)|(((blue)&255UL)<<0))
+#define CLEAR_STENCIL(s) ((17UL<<24)|(((s)&255UL)<<0))
+#define CLEAR_TAG(s) ((18UL<<24)|(((s)&255UL)<<0))
+#define COLOR_A(alpha) ((16UL<<24)|(((alpha)&255UL)<<0))
+#define COLOR_MASK(r,g,b,a) ((32UL<<24)|(((r)&1UL)<<3)|(((g)&1UL)<<2)|(((b)&1UL)<<1)|(((a)&1UL)<<0))
+#define COLOR_RGB(red,green,blue) ((4UL<<24)|(((red)&255UL)<<16)|(((green)&255UL)<<8)|(((blue)&255UL)<<0))
+/* #define DISPLAY() ((0UL<<24)) */
+#define END() ((33UL<<24))
+#define JUMP(dest) ((30UL<<24)|(((dest)&65535UL)<<0))
+#define LINE_WIDTH(width) ((14UL<<24)|(((width)&4095UL)<<0))
+#define MACRO(m) ((37UL<<24)|(((m)&1UL)<<0))
+#define POINT_SIZE(size) ((13UL<<24)|(((size)&8191UL)<<0))
+#define RESTORE_CONTEXT() ((35UL<<24))
+#define RETURN() ((36UL<<24))
+#define SAVE_CONTEXT() ((34UL<<24))
+#define STENCIL_FUNC(func,ref,mask) ((10UL<<24)|(((func)&7UL)<<16)|(((ref)&255UL)<<8)|(((mask)&255UL)<<0))
+#define STENCIL_MASK(mask) ((19UL<<24)|(((mask)&255UL)<<0))
+#define STENCIL_OP(sfail,spass) ((12UL<<24)|(((sfail)&7UL)<<3)|(((spass)&7UL)<<0))
+#define TAG(s) ((3UL<<24)|(((s)&255UL)<<0))
+#define TAG_MASK(mask) ((20UL<<24)|(((mask)&1UL)<<0))
+#define VERTEX2F(x,y) ((1UL<<30)|(((x)&32767UL)<<15)|(((y)&32767UL)<<0))
+#define VERTEX2II(x,y,handle,cell) ((2UL<<30)|(((x)&511UL)<<21)|(((y)&511UL)<<12)|(((handle)&31UL)<<7)|(((cell)&127UL)<<0))
+
+
+/* ----------------- BT81x exclusive definitions -----------------*/
+#if defined (BT81X_ENABLE)
+
+#define EVE_GLFORMAT 31UL   /* used with BITMAP_LAYOUT to indicate bitmap-format is specified by BITMAP_EXT_FORMAT */
+
+#define DL_BITMAP_EXT_FORMAT    0x2E000000 /* requires OR'd arguments */
+
+/* extended Bitmap formats */
+#define EVE_COMPRESSED_RGBA_ASTC_4x4_KHR   37808UL
+#define EVE_COMPRESSED_RGBA_ASTC_5x4_KHR   37809UL
+#define EVE_COMPRESSED_RGBA_ASTC_5x5_KHR   37810UL
+#define EVE_COMPRESSED_RGBA_ASTC_6x5_KHR   37811UL
+#define EVE_COMPRESSED_RGBA_ASTC_6x6_KHR   37812UL
+#define EVE_COMPRESSED_RGBA_ASTC_8x5_KHR   37813UL
+#define EVE_COMPRESSED_RGBA_ASTC_8x6_KHR   37814UL
+#define EVE_COMPRESSED_RGBA_ASTC_8x8_KHR   37815UL
+#define EVE_COMPRESSED_RGBA_ASTC_10x5_KHR  37816UL
+#define EVE_COMPRESSED_RGBA_ASTC_10x6_KHR  37817UL
+#define EVE_COMPRESSED_RGBA_ASTC_10x8_KHR  37818UL
+#define EVE_COMPRESSED_RGBA_ASTC_10x10_KHR 37819UL
+#define EVE_COMPRESSED_RGBA_ASTC_12x10_KHR 37820UL
+#define EVE_COMPRESSED_RGBA_ASTC_12x12_KHR 37821UL
+
+
+#define EVE_RAM_ERR_REPORT      0x309800UL /* max 128 bytes null terminated string */
+#define EVE_RAM_FLASH           0x800000UL
+#define EVE_RAM_FLASH_POSTBLOB  0x801000UL
+
+#define EVE_OPT_FLASH  64UL
+#define EVE_OPT_FORMAT 4096UL
+#define EVE_OPT_FILL   8192UL
+
+
+/* additional commands for BT81x */
+#define CMD_BITMAP_TRANSFORM 0xFFFFFF21
+#define CMD_SYNC             0xFFFFFF42     /* does not need a dedicated function, just use EVE_cmd_dl(CMD_SYNC) */
+#define CMD_FLASHERASE       0xFFFFFF44     /* does not need a dedicated function, just use EVE_cmd_dl(CMD_FLASHERASE) */
+#define CMD_FLASHWRITE       0xFFFFFF45
+#define CMD_FLASHREAD        0xFFFFFF46
+#define CMD_FLASHUPDATE      0xFFFFFF47
+#define CMD_FLASHDETACH      0xFFFFFF48     /* does not need a dedicated function, just use EVE_cmd_dl(CMD_FLASHDETACH) */
+#define CMD_FLASHATTACH      0xFFFFFF49     /* does not need a dedicated function, just use EVE_cmd_dl(CMD_FLASHATTACH) */
+#define CMD_FLASHFAST        0xFFFFFF4A
+#define CMD_FLASHSPIDESEL    0xFFFFFF4B     /* does not need a dedicated function, just use EVE_cmd_dl(CMD_FLASHSPIDESEL) */
+#define CMD_FLASHSPITX       0xFFFFFF4C
+#define CMD_FLASHSPIRX       0xFFFFFF4D
+#define CMD_FLASHSOURCE      0xFFFFFF4E
+#define CMD_CLEARCACHE       0xFFFFFF4F     /* does not need a dedicated function, just use EVE_cmd_dl(CMD_CLEARCACHE) */
+#define CMD_INFLATE2         0xFFFFFF50
+#define CMD_ROTATEAROUND     0xFFFFFF51
+#define CMD_RESETFONTS       0xFFFFFF52     /* does not need a dedicated function, just use EVE_cmd_dl(CMD_RESETFONTS) */
+#define CMD_ANIMSTART        0xFFFFFF53
+#define CMD_ANIMSTOP         0xFFFFFF54
+#define CMD_ANIMXY           0xFFFFFF55
+#define CMD_ANIMDRAW         0xFFFFFF56
+#define CMD_GRADIENTA        0xFFFFFF57
+#define CMD_FILLWIDTH        0xFFFFFF58
+#define CMD_APPENDF          0xFFFFFF59
+#define CMD_ANIMFRAME        0xFFFFFF5A
+#define CMD_VIDEOSTARTF      0xFFFFFF5F     /* does not need a dedicated function, just use EVE_cmd_dl(CMD_VIDEOSTARTF) */
+
+#if 0
+/* some undocumented commands for BT81x */
+#define CMD_NOP              0xFFFFFF5B
+#define CMD_SHA1             0xFFFFFF5C
+#define CMD_HMAC             0xFFFFFF5D
+#define CMD_LAST_            0xFFFFFF5E
+
+#endif
+
+
+/* additional registers for BT81x */
+#define REG_ADAPTIVE_FRAMERATE 0x30257cUL
+#define REG_PLAYBACK_PAUSE     0x3025ecUL
+#define REG_FLASH_STATUS       0x3025f0UL
+#define REG_FLASH_SIZE         0x309024UL
+#define REG_PLAY_CONTROL       0x30914eUL
+#define REG_COPRO_PATCH_DTR    0x309162UL
+
+
+/* BT81x graphics engine specific macros */
+#define BITMAP_EXT_FORMAT(format) ((46UL<<24)|(((format)&65535UL)<<0))
+#define BITMAP_SWIZZLE(r,g,b,a) ((47UL<<24)|(((r)&7UL)<<9)|(((g)&7UL)<<6)|(((b)&7UL)<<3)|(((a)&7UL)<<0))
+#define BITMAP_SOURCE2(flash_or_ram, addr) ((1UL<<24)|((flash_or_ram) << 23)|(((addr)&8388607UL)<<0))
+#define INT_FRR() ((48UL<<24))
+
+#undef BITMAP_TRANSFORM_A
+#undef BITMAP_TRANSFORM_B
+#undef BITMAP_TRANSFORM_D
+#undef BITMAP_TRANSFORM_E
+
+#define BITMAP_TRANSFORM_A_EXT(p,v) ((21UL<<24)|(((p)&1UL)<<17)|(((v)&131071UL)<<0))
+#define BITMAP_TRANSFORM_B_EXT(p,v) ((22UL<<24)|(((p)&1UL)<<17)|(((v)&131071UL)<<0))
+#define BITMAP_TRANSFORM_D_EXT(p,v) ((24UL<<24)|(((p)&1UL)<<17)|(((v)&131071UL)<<0))
+#define BITMAP_TRANSFORM_E_EXT(p,v) ((25UL<<24)|(((p)&1UL)<<17)|(((v)&131071UL)<<0))
+
+#define BITMAP_TRANSFORM_A(a) BITMAP_TRANSFORM_A_EXT(0,a)
+#define BITMAP_TRANSFORM_B(b) BITMAP_TRANSFORM_B_EXT(0,b)
+#define BITMAP_TRANSFORM_D(d) BITMAP_TRANSFORM_D_EXT(0,d)
+#define BITMAP_TRANSFORM_E(e) BITMAP_TRANSFORM_E_EXT(0,e)
+
+#endif
+
+/* ----------------- FT81x / BT81x exclusive definitions -----------------*/
+#if defined (FT81X_ENABLE)
+
+
+/* Host commands */
+#define EVE_CLKSEL          0x61 /* configure system clock */
+#define EVE_RST_PULSE       0x68 /* reset core - all registers default and processors reset */
+#define EVE_PINDRIVE        0x70 /* setup drive strength for various pins */
+#define EVE_PIN_PD_STATE    0x71 /* setup how pins behave during power down */
+
+
+/* Memory definitions */
+#define EVE_RAM_G           0x000000UL
+#define EVE_ROM_CHIPID      0x0C0000UL
+#define EVE_ROM_FONT        0x1E0000UL
+#define EVE_ROM_FONT_ADDR   0x2FFFFCUL
+#define EVE_RAM_DL          0x300000UL
+#define EVE_RAM_REG         0x302000UL
+#define EVE_RAM_CMD         0x308000UL
+
+
+/* Memory buffer sizes */
+#define EVE_RAM_G_SIZE      1024*1024L
+#define EVE_CMDFIFO_SIZE    4*1024L
+#define EVE_RAM_DL_SIZE     8*1024L
+
+
+/* various additional defines for FT81x */
+#define EVE_ADC_DIFFERENTIAL     1UL
+#define EVE_ADC_SINGLE_ENDED     0UL
+
+#define EVE_INT_G8               18UL
+#define EVE_INT_L8C              12UL
+#define EVE_INT_VGA              13UL
+
+#define EVE_OPT_MEDIAFIFO        16UL
+#define EVE_OPT_FULLSCREEN       8UL
+#define EVE_OPT_NOTEAR           4UL
+#define EVE_OPT_SOUND            32UL
+
+#define EVE_PALETTED565          14UL
+#define EVE_PALETTED4444         15UL
+#define EVE_PALETTED8            16UL
+#define EVE_L2                   17UL
+
+
+/* additional commands for FT81x */
+#define CMD_MEDIAFIFO           0xFFFFFF39
+#define CMD_PLAYVIDEO           0xFFFFFF3A
+#define CMD_ROMFONT             0xFFFFFF3F
+#define CMD_SETBASE             0xFFFFFF38
+#define CMD_SETBITMAP           0xFFFFFF43
+#define CMD_SETFONT2            0xFFFFFF3B
+#define CMD_SETROTATE           0xFFFFFF36
+#define CMD_SETSCRATCH          0xFFFFFF3C
+#define CMD_SNAPSHOT2           0xFFFFFF37
+#define CMD_VIDEOFRAME          0xFFFFFF41
+#define CMD_VIDEOSTART          0xFFFFFF40
+
+
+/* the following are undocumented commands that therefore should not be used */
+#if 0
+#define CMD_CSKETCH             0xFFFFFF35
+#define CMD_INT_RAMSHARED       0xFFFFFF3D
+#define CMD_INT_SWLOADIMAGE     0xFFFFFF3E
+#endif
+
+
+/* Register definitions */
+#define REG_ANA_COMP         0x302184UL /* only listed in datasheet */
+#define REG_BIST_EN          0x302174UL /* only listed in datasheet */
+#define REG_CLOCK            0x302008UL
+#define REG_CMDB_SPACE       0x302574UL
+#define REG_CMDB_WRITE       0x302578UL
+#define REG_CMD_DL           0x302100UL
+#define REG_CMD_READ         0x3020f8UL
+#define REG_CMD_WRITE        0x3020fcUL
+#define REG_CPURESET         0x302020UL
+#define REG_CSPREAD          0x302068UL
+#define REG_CTOUCH_EXTENDED  0x302108UL
+#define REG_CTOUCH_TOUCH0_XY 0x302124UL /* only listed in datasheet */
+#define REG_CTOUCH_TOUCH4_X  0x30216cUL
+#define REG_CTOUCH_TOUCH4_Y  0x302120UL
+#define REG_CTOUCH_TOUCH1_XY 0x30211cUL
+#define REG_CTOUCH_TOUCH2_XY 0x30218cUL
+#define REG_CTOUCH_TOUCH3_XY 0x302190UL
+#define REG_TOUCH_CONFIG     0x302168UL
+#define REG_DATESTAMP        0x302564UL /* only listed in datasheet */
+#define REG_DITHER           0x302060UL
+#define REG_DLSWAP           0x302054UL
+#define REG_FRAMES           0x302004UL
+#define REG_FREQUENCY        0x30200cUL
+#define REG_GPIO             0x302094UL
+#define REG_GPIOX            0x30209cUL
+#define REG_GPIOX_DIR        0x302098UL
+#define REG_GPIO_DIR         0x302090UL
+#define REG_HCYCLE           0x30202cUL
+#define REG_HOFFSET          0x302030UL
+#define REG_HSIZE            0x302034UL
+#define REG_HSYNC0           0x302038UL
+#define REG_HSYNC1           0x30203cUL
+#define REG_ID               0x302000UL
+#define REG_INT_EN           0x3020acUL
+#define REG_INT_FLAGS        0x3020a8UL
+#define REG_INT_MASK         0x3020b0UL
+#define REG_MACRO_0          0x3020d8UL
+#define REG_MACRO_1          0x3020dcUL
+#define REG_MEDIAFIFO_READ   0x309014UL /* only listed in programmers guide */
+#define REG_MEDIAFIFO_WRITE  0x309018UL /* only listed in programmers guide */
+#define REG_OUTBITS          0x30205cUL
+#define REG_PCLK             0x302070UL
+#define REG_PCLK_POL         0x30206cUL
+#define REG_PLAY             0x30208cUL
+#define REG_PLAYBACK_FORMAT  0x3020c4UL
+#define REG_PLAYBACK_FREQ    0x3020c0UL
+#define REG_PLAYBACK_LENGTH  0x3020b8UL
+#define REG_PLAYBACK_LOOP    0x3020c8UL
+#define REG_PLAYBACK_PLAY    0x3020ccUL
+#define REG_PLAYBACK_READPTR 0x3020bcUL
+#define REG_PLAYBACK_START   0x3020b4UL
+#define REG_PWM_DUTY         0x3020d4UL
+#define REG_PWM_HZ           0x3020d0UL
+#define REG_RENDERMODE       0x302010UL /* only listed in datasheet */
+#define REG_ROTATE           0x302058UL
+#define REG_SNAPFORMAT       0x30201cUL /* only listed in datasheet */
+#define REG_SNAPSHOT         0x302018UL /* only listed in datasheet */
+#define REG_SNAPY            0x302014UL /* only listed in datasheet */
+#define REG_SOUND            0x302088UL
+#define REG_SPI_WIDTH        0x302188UL /* listed with false offset in programmers guide V1.1 */
+#define REG_SWIZZLE          0x302064UL
+#define REG_TAG              0x30207cUL
+#define REG_TAG_X            0x302074UL
+#define REG_TAG_Y            0x302078UL
+#define REG_TAP_CRC          0x302024UL /* only listed in datasheet */
+#define REG_TAP_MASK         0x302028UL /* only listed in datasheet */
+#define REG_TOUCH_ADC_MODE   0x302108UL
+#define REG_TOUCH_CHARGE     0x30210cUL
+#define REG_TOUCH_DIRECT_XY  0x30218cUL
+#define REG_TOUCH_DIRECT_Z1Z2 0x302190UL
+#define REG_TOUCH_MODE       0x302104UL
+#define REG_TOUCH_OVERSAMPLE 0x302114UL
+#define REG_TOUCH_RAW_XY     0x30211cUL
+#define REG_TOUCH_RZ         0x302120UL
+#define REG_TOUCH_RZTHRESH   0x302118UL
+#define REG_TOUCH_SCREEN_XY  0x302124UL
+#define REG_TOUCH_SETTLE     0x302110UL
+#define REG_TOUCH_TAG        0x30212cUL
+#define REG_TOUCH_TAG1       0x302134UL /* only listed in datasheet */
+#define REG_TOUCH_TAG1_XY    0x302130UL /* only listed in datasheet */
+#define REG_TOUCH_TAG2       0x30213cUL /* only listed in datasheet */
+#define REG_TOUCH_TAG2_XY    0x302138UL /* only listed in datasheet */
+#define REG_TOUCH_TAG3       0x302144UL /* only listed in datasheet */
+#define REG_TOUCH_TAG3_XY    0x302140UL /* only listed in datasheet */
+#define REG_TOUCH_TAG4       0x30214cUL /* only listed in datasheet */
+#define REG_TOUCH_TAG4_XY    0x302148UL /* only listed in datasheet */
+#define REG_TOUCH_TAG_XY     0x302128UL
+#define REG_TOUCH_TRANSFORM_A 0x302150UL
+#define REG_TOUCH_TRANSFORM_B 0x302154UL
+#define REG_TOUCH_TRANSFORM_C 0x302158UL
+#define REG_TOUCH_TRANSFORM_D 0x30215cUL
+#define REG_TOUCH_TRANSFORM_E 0x302160UL
+#define REG_TOUCH_TRANSFORM_F 0x302164UL
+#define REG_TRACKER          0x309000UL /* only listed in programmers guide */
+#define REG_TRACKER_1        0x309004UL /* only listed in programmers guide */
+#define REG_TRACKER_2        0x309008UL /* only listed in programmers guide */
+#define REG_TRACKER_3        0x30900cUL /* only listed in programmers guide */
+#define REG_TRACKER_4        0x309010UL /* only listed in programmers guide */
+#define REG_TRIM             0x302180UL
+#define REG_VCYCLE           0x302040UL
+#define REG_VOFFSET          0x302044UL
+#define REG_VOL_PB           0x302080UL
+#define REG_VOL_SOUND        0x302084UL
+#define REG_VSIZE            0x302048UL
+#define REG_VSYNC0           0x30204cUL
+#define REG_VSYNC1           0x302050UL
+
+#if 0
+#define REG_BUSYBITS         0x3020e8UL /* only listed as "reserved" in datasheet */
+#define REG_CRC              0x302178UL /* only listed as "reserved" in datasheet */
+#define REG_SPI_EARLY_TX     0x30217cUL /* only listed as "reserved" in datasheet */
+#define REG_ROMSUB_SEL       0x3020f0UL /* only listed as "reserved" in datasheet */
+#define REG_TOUCH_FAULT      0x302170UL /* only listed as "reserved" in datasheet */
+#endif
+
+
+/* FT81x graphics engine specific macros useful for static display list generation */
+
+/* beware, these are different to FTDIs implementation as these take the original values as parameters and not only the upper bits */
+#define BITMAP_LAYOUT_H(linestride,height) ((40UL<<24)|((((linestride&0xC00)>>10)&3UL)<<2)|((((height&0x600)>>9)&3UL)<<0))
+#define BITMAP_SIZE_H(width,height) ((41UL<<24)|((((width&0x600)>>9)&3UL)<<2)|((((height&0x600)>>9)&3UL)<<0))
+
+#define BITMAP_SOURCE(addr) ((1UL<<24)|(((addr)&4194303UL)<<0))
+//#define NOP() ((45UL<<24))
+#define PALETTE_SOURCE(addr) ((42UL<<24)|(((addr)&4194303UL)<<0))
+#define SCISSOR_SIZE(width,height) ((28UL<<24)|(((width)&4095UL)<<12)|(((height)&4095UL)<<0))
+#define SCISSOR_XY(x,y) ((27UL<<24)|(((x)&2047UL)<<11)|(((y)&2047UL)<<0))
+#define VERTEX_FORMAT(frac) ((39UL<<24)|(((frac)&7UL)<<0))
+#define VERTEX_TRANSLATE_X(x) ((43UL<<24)|(((x)&131071UL)<<0))
+#define VERTEX_TRANSLATE_Y(y) ((44UL<<24)|(((y)&131071UL)<<0))
+
+
+
+/* ----------------- FT80x exclusive definitions -----------------*/
+#else
+
+/* Memory definitions */
+#define EVE_RAM_G           0x000000UL
+#define EVE_ROM_CHIPID      0x0C0000UL
+#define EVE_ROM_FONT        0x0BB23CUL
+#define EVE_ROM_FONT_ADDR   0x0FFFFCUL
+#define EVE_RAM_DL          0x100000UL
+#define EVE_RAM_PAL         0x102000UL
+#define EVE_RAM_CMD         0x108000UL
+#define EVE_RAM_SCREENSHOT  0x1C2000UL
+
+
+/* Memory buffer sizes */
+#define EVE_RAM_G_SIZE      256*1024L
+#define EVE_CMDFIFO_SIZE    4*1024L
+#define EVE_RAM_DL_SIZE     8*1024L
+#define EVE_RAM_PAL_SIZE    1*1024L
+
+
+/* Register definitions */
+#define REG_ID                  0x102400UL
+#define REG_FRAMES              0x102404UL
+#define REG_CLOCK               0x102408UL
+#define REG_FREQUENCY           0x10240CUL
+#define REG_SCREENSHOT_EN       0x102410UL
+#define REG_SCREENSHOT_Y        0x102414UL
+#define REG_SCREENSHOT_START    0x102418UL
+#define REG_CPURESET            0x10241CUL
+#define REG_TAP_CRC             0x102420UL
+#define REG_TAP_MASK            0x102424UL
+#define REG_HCYCLE              0x102428UL
+#define REG_HOFFSET             0x10242CUL
+#define REG_HSIZE               0x102430UL
+#define REG_HSYNC0              0x102434UL
+#define REG_HSYNC1              0x102438UL
+#define REG_VCYCLE              0x10243CUL
+#define REG_VOFFSET             0x102440UL
+#define REG_VSIZE               0x102444UL
+#define REG_VSYNC0              0x102448UL
+#define REG_VSYNC1              0x10244CUL
+#define REG_DLSWAP              0x102450UL
+#define REG_ROTATE              0x102454UL
+#define REG_OUTBITS             0x102458UL
+#define REG_DITHER              0x10245CUL
+#define REG_SWIZZLE             0x102460UL
+#define REG_CSPREAD             0x102464UL
+#define REG_PCLK_POL            0x102468UL
+#define REG_PCLK                0x10246CUL
+#define REG_TAG_X               0x102470UL
+#define REG_TAG_Y               0x102474UL
+#define REG_TAG                 0x102478UL
+#define REG_VOL_PB              0x10247CUL
+#define REG_VOL_SOUND           0x102480UL
+#define REG_SOUND               0x102484UL
+#define REG_PLAY                0x102488UL
+#define REG_GPIO_DIR            0x10248CUL
+#define REG_GPIO                0x102490UL
+#define REG_INT_FLAGS           0x102498UL
+#define REG_INT_EN              0x10249CUL
+#define REG_INT_MASK            0x1024A0UL
+#define REG_PLAYBACK_START      0x1024A4UL
+#define REG_PLAYBACK_LENGTH     0x1024A8UL
+#define REG_PLAYBACK_READPTR    0x1024ACUL
+#define REG_PLAYBACK_FREQ       0x1024B0UL
+#define REG_PLAYBACK_FORMAT     0x1024B4UL
+#define REG_PLAYBACK_LOOP       0x1024B8UL
+#define REG_PLAYBACK_PLAY       0x1024BCUL
+#define REG_PWM_HZ              0x1024C0UL
+#define REG_PWM_DUTY            0x1024C4UL
+#define REG_MACRO_0             0x1024C8UL
+#define REG_MACRO_1             0x1024CCUL
+#define REG_SCREENSHOT_BUSY     0x1024D8UL
+#define REG_CMD_READ            0x1024E4UL
+#define REG_CMD_WRITE           0x1024E8UL
+#define REG_CMD_DL              0x1024ECUL
+#define REG_TOUCH_MODE          0x1024F0UL
+#define REG_TOUCH_ADC_MODE      0x1024F4UL
+#define REG_TOUCH_CHARGE        0x1024F8UL
+#define REG_TOUCH_SETTLE        0x1024FCUL
+#define REG_TOUCH_OVERSAMPLE    0x102500UL
+#define REG_TOUCH_RZTHRESH      0x102504UL
+#define REG_TOUCH_RAW_XY        0x102508UL
+#define REG_TOUCH_RZ            0x10250CUL
+#define REG_TOUCH_SCREEN_XY     0x102510UL
+#define REG_TOUCH_TAG_XY        0x102514UL
+#define REG_TOUCH_TAG           0x102518UL
+#define REG_TOUCH_TRANSFORM_A   0x10251CUL
+#define REG_TOUCH_TRANSFORM_B   0x102520UL
+#define REG_TOUCH_TRANSFORM_C   0x102524UL
+#define REG_TOUCH_TRANSFORM_D   0x102528UL
+#define REG_TOUCH_TRANSFORM_E   0x10252CUL
+#define REG_TOUCH_TRANSFORM_F   0x102530UL
+#define REG_SCREENSHOT_READ     0x102554UL
+#define REG_TRIM                0x10256CUL
+#define REG_TOUCH_DIRECT_XY     0x102574UL
+#define REG_TOUCH_DIRECT_Z1Z2   0x102578UL
+#define REG_TRACKER             0x109000UL
+
+/* FT80x graphics engine specific macros useful for static display list generation */
+#define BITMAP_SOURCE(addr) ((1UL<<24)|(((addr)&1048575UL)<<0))
+#define SCISSOR_SIZE(width,height) ((28UL<<24)|(((width)&1023UL)<<10)|(((height)&1023UL)<<0))
+#define SCISSOR_XY(x,y) ((27UL<<24)|(((x)&511UL)<<9)|(((y)&511UL)<<0))
+
+#endif
+
+/**********************
+ *      TYPEDEFS
+ **********************/
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**********************
+ *      MACROS
+ **********************/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_FT81X_DEFINES_H*/
diff --git a/src/drivers/lv_drivers.h b/src/drivers/lv_drivers.h
index 77cd816e05f9..0850108132f3 100644
--- a/src/drivers/lv_drivers.h
+++ b/src/drivers/lv_drivers.h
@@ -33,6 +33,7 @@ extern "C" {
 
 #include "display/renesas_glcdc/lv_renesas_glcdc.h"
 #include "display/st_ltdc/lv_st_ltdc.h"
+#include "display/ft81x/lv_ft81x.h"
 
 #include "nuttx/lv_nuttx_entry.h"
 #include "nuttx/lv_nuttx_fbdev.h"
diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h
index c50731bd8d3f..4ef3f56f3e6c 100644
--- a/src/lv_conf_internal.h
+++ b/src/lv_conf_internal.h
@@ -4049,6 +4049,13 @@
         #define LV_USE_ILI9341       0
     #endif
 #endif
+#ifndef LV_USE_FT81X
+    #ifdef CONFIG_LV_USE_FT81X
+        #define LV_USE_FT81X CONFIG_LV_USE_FT81X
+    #else
+        #define LV_USE_FT81X         0
+    #endif
+#endif
 
 #if (LV_USE_ST7735 | LV_USE_ST7789 | LV_USE_ST7796 | LV_USE_ILI9341)
     #ifndef LV_USE_GENERIC_MIPI
diff --git a/src/misc/lv_utils.h b/src/misc/lv_utils.h
index 7a8d61014cab..9f30251a6f3b 100644
--- a/src/misc/lv_utils.h
+++ b/src/misc/lv_utils.h
@@ -57,6 +57,29 @@ void * lv_utils_bsearch(const void * key, const void * base, size_t n, size_t si
  */
 lv_result_t lv_draw_buf_save_to_file(const lv_draw_buf_t * draw_buf, const char * path);
 
+/**
+ * Reverse the order of the bytes in a 32-bit value.
+ * @param x     a 32-bit value.
+ * @return      the value `x` with reversed byte-order.
+ */
+static inline uint32_t lv_swap_bytes_32(uint32_t x)
+{
+    return (x << 24)
+           | ((x & 0x0000ff00U) <<  8)
+           | ((x & 0x00ff0000U) >>  8)
+           | (x >> 24);
+}
+
+/**
+ * Reverse the order of the bytes in a 16-bit value.
+ * @param x     a 16-bit value.
+ * @return      the value `x` with reversed byte-order.
+ */
+static inline uint16_t lv_swap_bytes_16(uint16_t x)
+{
+    return (x << 8) | (x >> 8);
+}
+
 /**********************
  *      MACROS
  **********************/
diff --git a/tests/src/lv_test_conf_full.h b/tests/src/lv_test_conf_full.h
index 0419ff1f93a8..5202c7aa3f15 100644
--- a/tests/src/lv_test_conf_full.h
+++ b/tests/src/lv_test_conf_full.h
@@ -154,6 +154,7 @@
 #define LV_USE_ST7735       1
 #define LV_USE_ST7789       1
 #define LV_USE_ST7796       1
+#define LV_USE_FT81X        1
 
 #ifndef LV_USE_LIBINPUT
     #define LV_USE_LIBINPUT     1


Reviews:
user: login: kisvegabor
body: Nice! How was the performance on a 800x480 display with ESP32-S3?
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-21T05:24:47Z
user: login: uLipe
body: Great driver! I just added a couple of nits.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-21T17:28:27Z
user: login: PGNetHun
body: Although it does not follow "MIPI" generic API approach (where command control and communication implementation is separated, like [lv_ili9341.c](https://github.com/lvgl/lvgl/blob/master/src/drivers/display/ili9341/lv_ili9341.c)), but actual PR is OK, at least there will be a working ft81x display driver in repo, and then later (if needed) it can be refactored to above mentioned generic API approach.
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-18T00:03:19Z
user: login: PGNetHun
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-18T00:05:52Z
user: login: liamHowatt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T08:53:03Z
user: login: liamHowatt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T08:53:09Z
user: login: liamHowatt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T08:53:18Z
user: login: liamHowatt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T08:53:27Z
user: login: kisvegabor
body: Thanks! I think it's great for the first iterataion! 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-25T09:09:37Z
user: login: uLipe
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-26T02:07:00Z
user: login: PGNetHun
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-28T14:41:55Z

Review comments:
diff_hunk: @@ -0,0 +1,435 @@
+/**
+ * @file lv_ft81x.c
+ *
+ */
+
+/*********************
+ *      INCLUDES
+ *********************/
+
+#include "lv_ft81x.h"
+#if LV_USE_FT81X
+
+#include "lv_ft81x_defines.h"
+
+#include "../../../stdlib/lv_mem.h"
+#include "../../../stdlib/lv_sprintf.h"
+#include "../../../stdlib/lv_string.h"
+#include "../../../misc/lv_types.h"
+
+/*********************
+ *      DEFINES
+ *********************/
+
+/* Increase as functionality is added if needed. */
+#define LV_FT81X_CMD_BUF_SIZE 63
+
+/**********************
+ *      TYPEDEFS
+ **********************/
+
+typedef struct {
+    lv_ft81x_spi_cb_t spi_cb;
+    void * user_data;
+    uint16_t cmd_offset;
+} lv_ft81x_driver_data_t;
+
+typedef struct {
+    uint32_t buf_len;
+    uint8_t buf[LV_FT81X_CMD_BUF_SIZE];
+} lv_ft81x_cmd_list_t;
+
+/**********************
+ *  STATIC PROTOTYPES
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params);
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map);
+static void delete_cb(lv_event_t * e);
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter);
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address);
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address);
+/* static uint32_t lv_ft81x_read_32(lv_display_t * disp, uint32_t address); */
+static void lv_ft81x_write_8(lv_display_t * disp, uint32_t address, uint8_t val);
+static void lv_ft81x_write_16(lv_display_t * disp, uint32_t address, uint16_t val);
+static void lv_ft81x_write_32(lv_display_t * disp, uint32_t address, uint32_t val);
+static void lv_ft81x_cmd_list_init(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_cmd_list_add_16(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint16_t value);
+static void lv_ft81x_cmd_list_add_32(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint32_t value);
+static void lv_ft81x_cmd_list_send(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_encode_read_address(void * dst_4_bytes, uint32_t address);
+static void lv_ft81x_encode_write_address(void * dst_3_bytes, uint32_t address);
+
+static inline uint32_t swap_bytes_32(uint32_t x);
+static inline uint16_t swap_bytes_16(uint16_t x);
+
+/**********************
+ *  STATIC VARIABLES
+ **********************/
+
+/**********************
+ *      MACROS
+ **********************/
+
+#if LV_BIG_ENDIAN_SYSTEM
+    #define BE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define BE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define LE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+#else
+    #define BE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define BE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+    #define LE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+#endif
+
+/**********************
+ *   GLOBAL FUNCTIONS
+ **********************/
+
+lv_display_t * lv_ft81x_create(const lv_ft81x_parameters_t * params, void * partial_buf, uint32_t buf_size,
+                               lv_ft81x_spi_cb_t spi_cb, void * user_data)
+{
+    lv_display_t * disp = lv_display_create(params->hor_res, params->ver_res);
+
+    lv_ft81x_driver_data_t * drv = lv_malloc_zeroed(sizeof(lv_ft81x_driver_data_t));
+    LV_ASSERT_MALLOC(drv);
+    drv->spi_cb = spi_cb;
+    drv->user_data = user_data;
+    drv->cmd_offset = 0;
+    lv_display_set_driver_data(disp, drv);
+    lv_display_set_flush_cb(disp, flush_cb);
+    lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
+    lv_display_set_buffers(disp, partial_buf, NULL, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
+    lv_display_add_event_cb(disp, delete_cb, LV_EVENT_DELETE, NULL);
+
+    lv_result_t init_res = initialize(disp, params);
+    if(init_res != LV_RESULT_OK) {
+        lv_display_delete(disp);
+        return NULL;
+    }
+
+    return disp;
+}
+
+void * lv_ft81x_get_user_data(lv_display_t * disp)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    return drv->user_data;
+}
+
+/**********************
+ *   STATIC FUNCTIONS
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params)
+{
+    lv_ft81x_cmd(disp, params->has_crystal ? EVE_CLKEXT : EVE_CLKINT, 0);
+    if(params->is_bt81x) lv_ft81x_cmd(disp, EVE_CLKSEL, 0x46);
+    lv_ft81x_cmd(disp, EVE_ACTIVE, 0);
+
+    lv_delay_ms(40);
path: src/drivers/display/ft81x/lv_ft81x.c
user: login: uLipe
body: Nit: I think the reason of these delays should be documented
created_at: 2025-02-21T17:21:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,435 @@
+/**
+ * @file lv_ft81x.c
+ *
+ */
+
+/*********************
+ *      INCLUDES
+ *********************/
+
+#include "lv_ft81x.h"
+#if LV_USE_FT81X
+
+#include "lv_ft81x_defines.h"
+
+#include "../../../stdlib/lv_mem.h"
+#include "../../../stdlib/lv_sprintf.h"
+#include "../../../stdlib/lv_string.h"
+#include "../../../misc/lv_types.h"
+
+/*********************
+ *      DEFINES
+ *********************/
+
+/* Increase as functionality is added if needed. */
+#define LV_FT81X_CMD_BUF_SIZE 63
+
+/**********************
+ *      TYPEDEFS
+ **********************/
+
+typedef struct {
+    lv_ft81x_spi_cb_t spi_cb;
+    void * user_data;
+    uint16_t cmd_offset;
+} lv_ft81x_driver_data_t;
+
+typedef struct {
+    uint32_t buf_len;
+    uint8_t buf[LV_FT81X_CMD_BUF_SIZE];
+} lv_ft81x_cmd_list_t;
+
+/**********************
+ *  STATIC PROTOTYPES
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params);
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map);
+static void delete_cb(lv_event_t * e);
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter);
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address);
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address);
+/* static uint32_t lv_ft81x_read_32(lv_display_t * disp, uint32_t address); */
+static void lv_ft81x_write_8(lv_display_t * disp, uint32_t address, uint8_t val);
+static void lv_ft81x_write_16(lv_display_t * disp, uint32_t address, uint16_t val);
+static void lv_ft81x_write_32(lv_display_t * disp, uint32_t address, uint32_t val);
+static void lv_ft81x_cmd_list_init(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_cmd_list_add_16(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint16_t value);
+static void lv_ft81x_cmd_list_add_32(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint32_t value);
+static void lv_ft81x_cmd_list_send(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_encode_read_address(void * dst_4_bytes, uint32_t address);
+static void lv_ft81x_encode_write_address(void * dst_3_bytes, uint32_t address);
+
+static inline uint32_t swap_bytes_32(uint32_t x);
+static inline uint16_t swap_bytes_16(uint16_t x);
+
+/**********************
+ *  STATIC VARIABLES
+ **********************/
+
+/**********************
+ *      MACROS
+ **********************/
+
+#if LV_BIG_ENDIAN_SYSTEM
+    #define BE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define BE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define LE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+#else
+    #define BE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define BE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+    #define LE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+#endif
+
+/**********************
+ *   GLOBAL FUNCTIONS
+ **********************/
+
+lv_display_t * lv_ft81x_create(const lv_ft81x_parameters_t * params, void * partial_buf, uint32_t buf_size,
+                               lv_ft81x_spi_cb_t spi_cb, void * user_data)
+{
+    lv_display_t * disp = lv_display_create(params->hor_res, params->ver_res);
+
+    lv_ft81x_driver_data_t * drv = lv_malloc_zeroed(sizeof(lv_ft81x_driver_data_t));
+    LV_ASSERT_MALLOC(drv);
+    drv->spi_cb = spi_cb;
+    drv->user_data = user_data;
+    drv->cmd_offset = 0;
+    lv_display_set_driver_data(disp, drv);
+    lv_display_set_flush_cb(disp, flush_cb);
+    lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
+    lv_display_set_buffers(disp, partial_buf, NULL, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
+    lv_display_add_event_cb(disp, delete_cb, LV_EVENT_DELETE, NULL);
+
+    lv_result_t init_res = initialize(disp, params);
+    if(init_res != LV_RESULT_OK) {
+        lv_display_delete(disp);
+        return NULL;
+    }
+
+    return disp;
+}
+
+void * lv_ft81x_get_user_data(lv_display_t * disp)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    return drv->user_data;
+}
+
+/**********************
+ *   STATIC FUNCTIONS
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params)
+{
+    lv_ft81x_cmd(disp, params->has_crystal ? EVE_CLKEXT : EVE_CLKINT, 0);
+    if(params->is_bt81x) lv_ft81x_cmd(disp, EVE_CLKSEL, 0x46);
+    lv_ft81x_cmd(disp, EVE_ACTIVE, 0);
+
+    lv_delay_ms(40);
+
+    uint32_t start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_ID) != 0x7c) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("failed to read 0x7C from the ID register 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_CPURESET) & 0x03) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("CPURESET register coprocessor and touch engines not in \"working status\" 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    if(params->is_bt81x) lv_ft81x_write_32(disp, REG_FREQUENCY, 72000000);
path: src/drivers/display/ft81x/lv_ft81x.c
user: login: uLipe
body: I think the value for this register could be exposed as a lv_conf option, is there any reason to hardcode it?
created_at: 2025-02-21T17:22:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,435 @@
+/**
+ * @file lv_ft81x.c
+ *
+ */
+
+/*********************
+ *      INCLUDES
+ *********************/
+
+#include "lv_ft81x.h"
+#if LV_USE_FT81X
+
+#include "lv_ft81x_defines.h"
+
+#include "../../../stdlib/lv_mem.h"
+#include "../../../stdlib/lv_sprintf.h"
+#include "../../../stdlib/lv_string.h"
+#include "../../../misc/lv_types.h"
+
+/*********************
+ *      DEFINES
+ *********************/
+
+/* Increase as functionality is added if needed. */
+#define LV_FT81X_CMD_BUF_SIZE 63
+
+/**********************
+ *      TYPEDEFS
+ **********************/
+
+typedef struct {
+    lv_ft81x_spi_cb_t spi_cb;
+    void * user_data;
+    uint16_t cmd_offset;
+} lv_ft81x_driver_data_t;
+
+typedef struct {
+    uint32_t buf_len;
+    uint8_t buf[LV_FT81X_CMD_BUF_SIZE];
+} lv_ft81x_cmd_list_t;
+
+/**********************
+ *  STATIC PROTOTYPES
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params);
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map);
+static void delete_cb(lv_event_t * e);
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter);
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address);
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address);
+/* static uint32_t lv_ft81x_read_32(lv_display_t * disp, uint32_t address); */
+static void lv_ft81x_write_8(lv_display_t * disp, uint32_t address, uint8_t val);
+static void lv_ft81x_write_16(lv_display_t * disp, uint32_t address, uint16_t val);
+static void lv_ft81x_write_32(lv_display_t * disp, uint32_t address, uint32_t val);
+static void lv_ft81x_cmd_list_init(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_cmd_list_add_16(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint16_t value);
+static void lv_ft81x_cmd_list_add_32(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint32_t value);
+static void lv_ft81x_cmd_list_send(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_encode_read_address(void * dst_4_bytes, uint32_t address);
+static void lv_ft81x_encode_write_address(void * dst_3_bytes, uint32_t address);
+
+static inline uint32_t swap_bytes_32(uint32_t x);
+static inline uint16_t swap_bytes_16(uint16_t x);
+
+/**********************
+ *  STATIC VARIABLES
+ **********************/
+
+/**********************
+ *      MACROS
+ **********************/
+
+#if LV_BIG_ENDIAN_SYSTEM
+    #define BE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define BE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define LE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+#else
+    #define BE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define BE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+    #define LE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+#endif
+
+/**********************
+ *   GLOBAL FUNCTIONS
+ **********************/
+
+lv_display_t * lv_ft81x_create(const lv_ft81x_parameters_t * params, void * partial_buf, uint32_t buf_size,
+                               lv_ft81x_spi_cb_t spi_cb, void * user_data)
+{
+    lv_display_t * disp = lv_display_create(params->hor_res, params->ver_res);
+
+    lv_ft81x_driver_data_t * drv = lv_malloc_zeroed(sizeof(lv_ft81x_driver_data_t));
+    LV_ASSERT_MALLOC(drv);
+    drv->spi_cb = spi_cb;
+    drv->user_data = user_data;
+    drv->cmd_offset = 0;
+    lv_display_set_driver_data(disp, drv);
+    lv_display_set_flush_cb(disp, flush_cb);
+    lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
+    lv_display_set_buffers(disp, partial_buf, NULL, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
+    lv_display_add_event_cb(disp, delete_cb, LV_EVENT_DELETE, NULL);
+
+    lv_result_t init_res = initialize(disp, params);
+    if(init_res != LV_RESULT_OK) {
+        lv_display_delete(disp);
+        return NULL;
+    }
+
+    return disp;
+}
+
+void * lv_ft81x_get_user_data(lv_display_t * disp)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    return drv->user_data;
+}
+
+/**********************
+ *   STATIC FUNCTIONS
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params)
+{
+    lv_ft81x_cmd(disp, params->has_crystal ? EVE_CLKEXT : EVE_CLKINT, 0);
+    if(params->is_bt81x) lv_ft81x_cmd(disp, EVE_CLKSEL, 0x46);
+    lv_ft81x_cmd(disp, EVE_ACTIVE, 0);
+
+    lv_delay_ms(40);
+
+    uint32_t start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_ID) != 0x7c) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("failed to read 0x7C from the ID register 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_CPURESET) & 0x03) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("CPURESET register coprocessor and touch engines not in \"working status\" 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    if(params->is_bt81x) lv_ft81x_write_32(disp, REG_FREQUENCY, 72000000);
+
+    lv_ft81x_write_8(disp, REG_PWM_DUTY, 0x80);
path: src/drivers/display/ft81x/lv_ft81x.c
user: login: uLipe
body: Nit: Magic register values should be documented
created_at: 2025-02-21T17:23:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,435 @@
+/**
+ * @file lv_ft81x.c
+ *
+ */
+
+/*********************
+ *      INCLUDES
+ *********************/
+
+#include "lv_ft81x.h"
+#if LV_USE_FT81X
+
+#include "lv_ft81x_defines.h"
+
+#include "../../../stdlib/lv_mem.h"
+#include "../../../stdlib/lv_sprintf.h"
+#include "../../../stdlib/lv_string.h"
+#include "../../../misc/lv_types.h"
+
+/*********************
+ *      DEFINES
+ *********************/
+
+/* Increase as functionality is added if needed. */
+#define LV_FT81X_CMD_BUF_SIZE 63
+
+/**********************
+ *      TYPEDEFS
+ **********************/
+
+typedef struct {
+    lv_ft81x_spi_cb_t spi_cb;
+    void * user_data;
+    uint16_t cmd_offset;
+} lv_ft81x_driver_data_t;
+
+typedef struct {
+    uint32_t buf_len;
+    uint8_t buf[LV_FT81X_CMD_BUF_SIZE];
+} lv_ft81x_cmd_list_t;
+
+/**********************
+ *  STATIC PROTOTYPES
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params);
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map);
+static void delete_cb(lv_event_t * e);
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter);
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address);
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address);
+/* static uint32_t lv_ft81x_read_32(lv_display_t * disp, uint32_t address); */
+static void lv_ft81x_write_8(lv_display_t * disp, uint32_t address, uint8_t val);
+static void lv_ft81x_write_16(lv_display_t * disp, uint32_t address, uint16_t val);
+static void lv_ft81x_write_32(lv_display_t * disp, uint32_t address, uint32_t val);
+static void lv_ft81x_cmd_list_init(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_cmd_list_add_16(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint16_t value);
+static void lv_ft81x_cmd_list_add_32(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint32_t value);
+static void lv_ft81x_cmd_list_send(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_encode_read_address(void * dst_4_bytes, uint32_t address);
+static void lv_ft81x_encode_write_address(void * dst_3_bytes, uint32_t address);
+
+static inline uint32_t swap_bytes_32(uint32_t x);
+static inline uint16_t swap_bytes_16(uint16_t x);
+
+/**********************
+ *  STATIC VARIABLES
+ **********************/
+
+/**********************
+ *      MACROS
+ **********************/
+
+#if LV_BIG_ENDIAN_SYSTEM
+    #define BE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define BE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define LE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+#else
+    #define BE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define BE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+    #define LE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+#endif
+
+/**********************
+ *   GLOBAL FUNCTIONS
+ **********************/
+
+lv_display_t * lv_ft81x_create(const lv_ft81x_parameters_t * params, void * partial_buf, uint32_t buf_size,
+                               lv_ft81x_spi_cb_t spi_cb, void * user_data)
+{
+    lv_display_t * disp = lv_display_create(params->hor_res, params->ver_res);
+
+    lv_ft81x_driver_data_t * drv = lv_malloc_zeroed(sizeof(lv_ft81x_driver_data_t));
+    LV_ASSERT_MALLOC(drv);
+    drv->spi_cb = spi_cb;
+    drv->user_data = user_data;
+    drv->cmd_offset = 0;
+    lv_display_set_driver_data(disp, drv);
+    lv_display_set_flush_cb(disp, flush_cb);
+    lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
+    lv_display_set_buffers(disp, partial_buf, NULL, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
+    lv_display_add_event_cb(disp, delete_cb, LV_EVENT_DELETE, NULL);
+
+    lv_result_t init_res = initialize(disp, params);
+    if(init_res != LV_RESULT_OK) {
+        lv_display_delete(disp);
+        return NULL;
+    }
+
+    return disp;
+}
+
+void * lv_ft81x_get_user_data(lv_display_t * disp)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    return drv->user_data;
+}
+
+/**********************
+ *   STATIC FUNCTIONS
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params)
+{
+    lv_ft81x_cmd(disp, params->has_crystal ? EVE_CLKEXT : EVE_CLKINT, 0);
+    if(params->is_bt81x) lv_ft81x_cmd(disp, EVE_CLKSEL, 0x46);
+    lv_ft81x_cmd(disp, EVE_ACTIVE, 0);
+
+    lv_delay_ms(40);
+
+    uint32_t start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_ID) != 0x7c) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("failed to read 0x7C from the ID register 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_CPURESET) & 0x03) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("CPURESET register coprocessor and touch engines not in \"working status\" 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    if(params->is_bt81x) lv_ft81x_write_32(disp, REG_FREQUENCY, 72000000);
+
+    lv_ft81x_write_8(disp, REG_PWM_DUTY, 0x80);
path: src/drivers/display/ft81x/lv_ft81x.c
user: login: uLipe
body: Except for cases there is no documentation for that XD
created_at: 2025-02-21T17:23:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,435 @@
+/**
+ * @file lv_ft81x.c
+ *
+ */
+
+/*********************
+ *      INCLUDES
+ *********************/
+
+#include "lv_ft81x.h"
+#if LV_USE_FT81X
+
+#include "lv_ft81x_defines.h"
+
+#include "../../../stdlib/lv_mem.h"
+#include "../../../stdlib/lv_sprintf.h"
+#include "../../../stdlib/lv_string.h"
+#include "../../../misc/lv_types.h"
+
+/*********************
+ *      DEFINES
+ *********************/
+
+/* Increase as functionality is added if needed. */
+#define LV_FT81X_CMD_BUF_SIZE 63
+
+/**********************
+ *      TYPEDEFS
+ **********************/
+
+typedef struct {
+    lv_ft81x_spi_cb_t spi_cb;
+    void * user_data;
+    uint16_t cmd_offset;
+} lv_ft81x_driver_data_t;
+
+typedef struct {
+    uint32_t buf_len;
+    uint8_t buf[LV_FT81X_CMD_BUF_SIZE];
+} lv_ft81x_cmd_list_t;
+
+/**********************
+ *  STATIC PROTOTYPES
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params);
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map);
+static void delete_cb(lv_event_t * e);
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter);
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address);
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address);
+/* static uint32_t lv_ft81x_read_32(lv_display_t * disp, uint32_t address); */
+static void lv_ft81x_write_8(lv_display_t * disp, uint32_t address, uint8_t val);
+static void lv_ft81x_write_16(lv_display_t * disp, uint32_t address, uint16_t val);
+static void lv_ft81x_write_32(lv_display_t * disp, uint32_t address, uint32_t val);
+static void lv_ft81x_cmd_list_init(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_cmd_list_add_16(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint16_t value);
+static void lv_ft81x_cmd_list_add_32(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint32_t value);
+static void lv_ft81x_cmd_list_send(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_encode_read_address(void * dst_4_bytes, uint32_t address);
+static void lv_ft81x_encode_write_address(void * dst_3_bytes, uint32_t address);
+
+static inline uint32_t swap_bytes_32(uint32_t x);
+static inline uint16_t swap_bytes_16(uint16_t x);
+
+/**********************
+ *  STATIC VARIABLES
+ **********************/
+
+/**********************
+ *      MACROS
+ **********************/
+
+#if LV_BIG_ENDIAN_SYSTEM
+    #define BE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define BE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define LE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+#else
+    #define BE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define BE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+    #define LE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+#endif
+
+/**********************
+ *   GLOBAL FUNCTIONS
+ **********************/
+
+lv_display_t * lv_ft81x_create(const lv_ft81x_parameters_t * params, void * partial_buf, uint32_t buf_size,
+                               lv_ft81x_spi_cb_t spi_cb, void * user_data)
+{
+    lv_display_t * disp = lv_display_create(params->hor_res, params->ver_res);
+
+    lv_ft81x_driver_data_t * drv = lv_malloc_zeroed(sizeof(lv_ft81x_driver_data_t));
+    LV_ASSERT_MALLOC(drv);
+    drv->spi_cb = spi_cb;
+    drv->user_data = user_data;
+    drv->cmd_offset = 0;
+    lv_display_set_driver_data(disp, drv);
+    lv_display_set_flush_cb(disp, flush_cb);
+    lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
+    lv_display_set_buffers(disp, partial_buf, NULL, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
+    lv_display_add_event_cb(disp, delete_cb, LV_EVENT_DELETE, NULL);
+
+    lv_result_t init_res = initialize(disp, params);
+    if(init_res != LV_RESULT_OK) {
+        lv_display_delete(disp);
+        return NULL;
+    }
+
+    return disp;
+}
+
+void * lv_ft81x_get_user_data(lv_display_t * disp)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    return drv->user_data;
+}
+
+/**********************
+ *   STATIC FUNCTIONS
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params)
+{
+    lv_ft81x_cmd(disp, params->has_crystal ? EVE_CLKEXT : EVE_CLKINT, 0);
+    if(params->is_bt81x) lv_ft81x_cmd(disp, EVE_CLKSEL, 0x46);
+    lv_ft81x_cmd(disp, EVE_ACTIVE, 0);
+
+    lv_delay_ms(40);
+
+    uint32_t start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_ID) != 0x7c) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("failed to read 0x7C from the ID register 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_CPURESET) & 0x03) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("CPURESET register coprocessor and touch engines not in \"working status\" 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    if(params->is_bt81x) lv_ft81x_write_32(disp, REG_FREQUENCY, 72000000);
+
+    lv_ft81x_write_8(disp, REG_PWM_DUTY, 0x80);
+
+    lv_ft81x_write_16(disp, REG_HSIZE,   params->hor_res);   /* active display width */
+    lv_ft81x_write_16(disp, REG_HCYCLE,  params->hcycle);  /* total number of clocks per line, incl front/back porch */
+    lv_ft81x_write_16(disp, REG_HOFFSET, params->hoffset); /* start of active line */
+    lv_ft81x_write_16(disp, REG_HSYNC0,  params->hsync0);  /* start of horizontal sync pulse */
+    lv_ft81x_write_16(disp, REG_HSYNC1,  params->hsync1);  /* end of horizontal sync pulse */
+    lv_ft81x_write_16(disp, REG_VSIZE,   params->ver_res);   /* active display height */
+    lv_ft81x_write_16(disp, REG_VCYCLE,  params->vcycle);  /* total number of lines per screen, including pre/post */
+    lv_ft81x_write_16(disp, REG_VOFFSET, params->voffset); /* start of active screen */
+    lv_ft81x_write_16(disp, REG_VSYNC0,  params->vsync0);  /* start of vertical sync pulse */
+    lv_ft81x_write_16(disp, REG_VSYNC1,  params->vsync1);  /* end of vertical sync pulse */
+    lv_ft81x_write_8(disp, REG_SWIZZLE,  params->swizzle); /* FT8xx output to LCD - pin order */
+    lv_ft81x_write_8(disp, REG_PCLK_POL, params->pclkpol); /* LCD data is clocked in on this PCLK edge */
+    lv_ft81x_write_8(disp, REG_CSPREAD,
+                     params->cspread); /* helps with noise, when set to 1 fewer signals are changed simultaneously, reset-default: 1 */
+
+    /* write a basic display-list to get things started */
+    lv_ft81x_write_32(disp, EVE_RAM_DL, DL_CLEAR_RGB);
+    lv_ft81x_write_32(disp, EVE_RAM_DL + 4, (DL_CLEAR | CLR_COL | CLR_STN | CLR_TAG));
+    lv_ft81x_write_32(disp, EVE_RAM_DL + 8, DL_DISPLAY);    /* end of display list */
+    lv_ft81x_write_32(disp, REG_DLSWAP, EVE_DLSWAP_FRAME);
+
+    /* nothing is being displayed yet... the pixel clock is still 0x00 */
+    lv_ft81x_write_8(disp, REG_GPIO,
+                     0x80); /* enable the DISP signal to the LCD panel, it is set to output in REG_GPIO_DIR by default */
+    lv_ft81x_write_8(disp, REG_PCLK, params->pclk); /* now start clocking data to the LCD panel */
+
+    LV_ASSERT(lv_ft81x_read_16(disp, REG_CMD_READ) != 0xfff);
+
+    LV_ASSERT(0 == lv_ft81x_read_16(disp, REG_CMD_WRITE));
+
+
+    lv_ft81x_cmd_list_t cmd_list;
+    lv_ft81x_cmd_list_init(disp, &cmd_list);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_MEMSET);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0); /* address */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0x00); /* val */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 2 * params->hor_res * params->ver_res); /* count */
+    lv_ft81x_cmd_list_send(disp, &cmd_list);
+
+    lv_ft81x_cmd_list_init(disp, &cmd_list);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_DLSTART);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_CLEAR_RGB | 0); /* clear to black */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_CLEAR | CLR_COL | CLR_STN | CLR_TAG);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(0));
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(20));
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_SETBITMAP);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0); /* address */
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, EVE_RGB565);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, params->hor_res);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, params->ver_res);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, 0);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_BEGIN | EVE_BITMAPS);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, VERTEX2F(0, 0));
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_END);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(0));
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_DISPLAY);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_SWAP);
+
+    lv_ft81x_cmd_list_send(disp, &cmd_list);
+
+
+    return LV_RESULT_OK;
+}
+
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+
+    int32_t hor_res = lv_display_get_horizontal_resolution(disp);
+    uint32_t disp_row_bytes = hor_res * 2;
+
+    uint32_t address = disp_row_bytes * area->y1 + area->x1 * 2;
+    uint8_t encoded_address[3];
+
+    if(lv_area_get_width(area) == hor_res) {
+        lv_ft81x_encode_write_address(encoded_address, address);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
path: src/drivers/display/ft81x/lv_ft81x.c
user: login: uLipe
body: I know that the flush callback should be performant, but I think we should  validate if a callback is set before executing it and put a LOG_ERR to make easier to debug silent  corrupted drivers.
created_at: 2025-02-21T17:25:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,435 @@
+/**
+ * @file lv_ft81x.c
+ *
+ */
+
+/*********************
+ *      INCLUDES
+ *********************/
+
+#include "lv_ft81x.h"
+#if LV_USE_FT81X
+
+#include "lv_ft81x_defines.h"
+
+#include "../../../stdlib/lv_mem.h"
+#include "../../../stdlib/lv_sprintf.h"
+#include "../../../stdlib/lv_string.h"
+#include "../../../misc/lv_types.h"
+
+/*********************
+ *      DEFINES
+ *********************/
+
+/* Increase as functionality is added if needed. */
+#define LV_FT81X_CMD_BUF_SIZE 63
+
+/**********************
+ *      TYPEDEFS
+ **********************/
+
+typedef struct {
+    lv_ft81x_spi_cb_t spi_cb;
+    void * user_data;
+    uint16_t cmd_offset;
+} lv_ft81x_driver_data_t;
+
+typedef struct {
+    uint32_t buf_len;
+    uint8_t buf[LV_FT81X_CMD_BUF_SIZE];
+} lv_ft81x_cmd_list_t;
+
+/**********************
+ *  STATIC PROTOTYPES
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params);
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map);
+static void delete_cb(lv_event_t * e);
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter);
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address);
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address);
+/* static uint32_t lv_ft81x_read_32(lv_display_t * disp, uint32_t address); */
+static void lv_ft81x_write_8(lv_display_t * disp, uint32_t address, uint8_t val);
+static void lv_ft81x_write_16(lv_display_t * disp, uint32_t address, uint16_t val);
+static void lv_ft81x_write_32(lv_display_t * disp, uint32_t address, uint32_t val);
+static void lv_ft81x_cmd_list_init(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_cmd_list_add_16(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint16_t value);
+static void lv_ft81x_cmd_list_add_32(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint32_t value);
+static void lv_ft81x_cmd_list_send(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_encode_read_address(void * dst_4_bytes, uint32_t address);
+static void lv_ft81x_encode_write_address(void * dst_3_bytes, uint32_t address);
+
+static inline uint32_t swap_bytes_32(uint32_t x);
+static inline uint16_t swap_bytes_16(uint16_t x);
+
+/**********************
+ *  STATIC VARIABLES
+ **********************/
+
+/**********************
+ *      MACROS
+ **********************/
+
+#if LV_BIG_ENDIAN_SYSTEM
+    #define BE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define BE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define LE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+#else
+    #define BE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define BE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+    #define LE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+#endif
+
+/**********************
+ *   GLOBAL FUNCTIONS
+ **********************/
+
+lv_display_t * lv_ft81x_create(const lv_ft81x_parameters_t * params, void * partial_buf, uint32_t buf_size,
+                               lv_ft81x_spi_cb_t spi_cb, void * user_data)
+{
+    lv_display_t * disp = lv_display_create(params->hor_res, params->ver_res);
+
+    lv_ft81x_driver_data_t * drv = lv_malloc_zeroed(sizeof(lv_ft81x_driver_data_t));
+    LV_ASSERT_MALLOC(drv);
+    drv->spi_cb = spi_cb;
+    drv->user_data = user_data;
+    drv->cmd_offset = 0;
+    lv_display_set_driver_data(disp, drv);
+    lv_display_set_flush_cb(disp, flush_cb);
+    lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
+    lv_display_set_buffers(disp, partial_buf, NULL, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
+    lv_display_add_event_cb(disp, delete_cb, LV_EVENT_DELETE, NULL);
+
+    lv_result_t init_res = initialize(disp, params);
+    if(init_res != LV_RESULT_OK) {
+        lv_display_delete(disp);
+        return NULL;
+    }
+
+    return disp;
+}
+
+void * lv_ft81x_get_user_data(lv_display_t * disp)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    return drv->user_data;
+}
+
+/**********************
+ *   STATIC FUNCTIONS
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params)
+{
+    lv_ft81x_cmd(disp, params->has_crystal ? EVE_CLKEXT : EVE_CLKINT, 0);
+    if(params->is_bt81x) lv_ft81x_cmd(disp, EVE_CLKSEL, 0x46);
+    lv_ft81x_cmd(disp, EVE_ACTIVE, 0);
+
+    lv_delay_ms(40);
+
+    uint32_t start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_ID) != 0x7c) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("failed to read 0x7C from the ID register 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_CPURESET) & 0x03) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("CPURESET register coprocessor and touch engines not in \"working status\" 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    if(params->is_bt81x) lv_ft81x_write_32(disp, REG_FREQUENCY, 72000000);
+
+    lv_ft81x_write_8(disp, REG_PWM_DUTY, 0x80);
+
+    lv_ft81x_write_16(disp, REG_HSIZE,   params->hor_res);   /* active display width */
+    lv_ft81x_write_16(disp, REG_HCYCLE,  params->hcycle);  /* total number of clocks per line, incl front/back porch */
+    lv_ft81x_write_16(disp, REG_HOFFSET, params->hoffset); /* start of active line */
+    lv_ft81x_write_16(disp, REG_HSYNC0,  params->hsync0);  /* start of horizontal sync pulse */
+    lv_ft81x_write_16(disp, REG_HSYNC1,  params->hsync1);  /* end of horizontal sync pulse */
+    lv_ft81x_write_16(disp, REG_VSIZE,   params->ver_res);   /* active display height */
+    lv_ft81x_write_16(disp, REG_VCYCLE,  params->vcycle);  /* total number of lines per screen, including pre/post */
+    lv_ft81x_write_16(disp, REG_VOFFSET, params->voffset); /* start of active screen */
+    lv_ft81x_write_16(disp, REG_VSYNC0,  params->vsync0);  /* start of vertical sync pulse */
+    lv_ft81x_write_16(disp, REG_VSYNC1,  params->vsync1);  /* end of vertical sync pulse */
+    lv_ft81x_write_8(disp, REG_SWIZZLE,  params->swizzle); /* FT8xx output to LCD - pin order */
+    lv_ft81x_write_8(disp, REG_PCLK_POL, params->pclkpol); /* LCD data is clocked in on this PCLK edge */
+    lv_ft81x_write_8(disp, REG_CSPREAD,
+                     params->cspread); /* helps with noise, when set to 1 fewer signals are changed simultaneously, reset-default: 1 */
+
+    /* write a basic display-list to get things started */
+    lv_ft81x_write_32(disp, EVE_RAM_DL, DL_CLEAR_RGB);
+    lv_ft81x_write_32(disp, EVE_RAM_DL + 4, (DL_CLEAR | CLR_COL | CLR_STN | CLR_TAG));
+    lv_ft81x_write_32(disp, EVE_RAM_DL + 8, DL_DISPLAY);    /* end of display list */
+    lv_ft81x_write_32(disp, REG_DLSWAP, EVE_DLSWAP_FRAME);
+
+    /* nothing is being displayed yet... the pixel clock is still 0x00 */
+    lv_ft81x_write_8(disp, REG_GPIO,
+                     0x80); /* enable the DISP signal to the LCD panel, it is set to output in REG_GPIO_DIR by default */
+    lv_ft81x_write_8(disp, REG_PCLK, params->pclk); /* now start clocking data to the LCD panel */
+
+    LV_ASSERT(lv_ft81x_read_16(disp, REG_CMD_READ) != 0xfff);
+
+    LV_ASSERT(0 == lv_ft81x_read_16(disp, REG_CMD_WRITE));
+
+
+    lv_ft81x_cmd_list_t cmd_list;
+    lv_ft81x_cmd_list_init(disp, &cmd_list);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_MEMSET);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0); /* address */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0x00); /* val */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 2 * params->hor_res * params->ver_res); /* count */
+    lv_ft81x_cmd_list_send(disp, &cmd_list);
+
+    lv_ft81x_cmd_list_init(disp, &cmd_list);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_DLSTART);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_CLEAR_RGB | 0); /* clear to black */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_CLEAR | CLR_COL | CLR_STN | CLR_TAG);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(0));
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(20));
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_SETBITMAP);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0); /* address */
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, EVE_RGB565);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, params->hor_res);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, params->ver_res);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, 0);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_BEGIN | EVE_BITMAPS);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, VERTEX2F(0, 0));
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_END);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(0));
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_DISPLAY);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_SWAP);
+
+    lv_ft81x_cmd_list_send(disp, &cmd_list);
+
+
+    return LV_RESULT_OK;
+}
+
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+
+    int32_t hor_res = lv_display_get_horizontal_resolution(disp);
+    uint32_t disp_row_bytes = hor_res * 2;
+
+    uint32_t address = disp_row_bytes * area->y1 + area->x1 * 2;
+    uint8_t encoded_address[3];
+
+    if(lv_area_get_width(area) == hor_res) {
+        lv_ft81x_encode_write_address(encoded_address, address);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, encoded_address, sizeof(encoded_address));
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, px_map, lv_area_get_size(area) * 2);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    }
+    else {
+        uint32_t area_height = lv_area_get_height(area);
+        uint32_t area_row_bytes = lv_area_get_width(area) * 2;
+        for(uint32_t i = 0; i < area_height; i++) {
+            lv_ft81x_encode_write_address(encoded_address, address);
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, encoded_address, sizeof(encoded_address));
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, px_map, area_row_bytes);
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+            address += disp_row_bytes;
+            px_map += area_row_bytes;
+        }
+    }
+
+    lv_display_flush_ready(disp);
+}
+
+static void delete_cb(lv_event_t * e)
+{
+    lv_display_t * disp = lv_event_get_target(e);
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    lv_free(drv);
+}
+
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    uint8_t data[3] = {command, parameter, 0x00};
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, data, sizeof(data));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+}
+
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    lv_ft81x_encode_read_address(&address, address);
+    uint8_t val;
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, &address, sizeof(address));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_RECEIVE, &val, sizeof(val));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    return val;
+}
+
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    lv_ft81x_encode_read_address(&address, address);
+    uint16_t val;
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, &address, sizeof(address));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_RECEIVE, &val, sizeof(val));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    val = LE_TO_OR_FROM_NATIVE_16(val);
+    return val;
+}
+
+/*  static uint32_t lv_ft81x_read_32(lv_display_t * disp, uint32_t address)
path: src/drivers/display/ft81x/lv_ft81x.c
user: login: uLipe
body: Will this function be used when this PR gets merged? If not I suggest to remove it
created_at: 2025-02-21T17:26:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,435 @@
+/**
+ * @file lv_ft81x.c
+ *
+ */
+
+/*********************
+ *      INCLUDES
+ *********************/
+
+#include "lv_ft81x.h"
+#if LV_USE_FT81X
+
+#include "lv_ft81x_defines.h"
+
+#include "../../../stdlib/lv_mem.h"
+#include "../../../stdlib/lv_sprintf.h"
+#include "../../../stdlib/lv_string.h"
+#include "../../../misc/lv_types.h"
+
+/*********************
+ *      DEFINES
+ *********************/
+
+/* Increase as functionality is added if needed. */
+#define LV_FT81X_CMD_BUF_SIZE 63
+
+/**********************
+ *      TYPEDEFS
+ **********************/
+
+typedef struct {
+    lv_ft81x_spi_cb_t spi_cb;
+    void * user_data;
+    uint16_t cmd_offset;
+} lv_ft81x_driver_data_t;
+
+typedef struct {
+    uint32_t buf_len;
+    uint8_t buf[LV_FT81X_CMD_BUF_SIZE];
+} lv_ft81x_cmd_list_t;
+
+/**********************
+ *  STATIC PROTOTYPES
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params);
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map);
+static void delete_cb(lv_event_t * e);
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter);
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address);
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address);
+/* static uint32_t lv_ft81x_read_32(lv_display_t * disp, uint32_t address); */
+static void lv_ft81x_write_8(lv_display_t * disp, uint32_t address, uint8_t val);
+static void lv_ft81x_write_16(lv_display_t * disp, uint32_t address, uint16_t val);
+static void lv_ft81x_write_32(lv_display_t * disp, uint32_t address, uint32_t val);
+static void lv_ft81x_cmd_list_init(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_cmd_list_add_16(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint16_t value);
+static void lv_ft81x_cmd_list_add_32(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint32_t value);
+static void lv_ft81x_cmd_list_send(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_encode_read_address(void * dst_4_bytes, uint32_t address);
+static void lv_ft81x_encode_write_address(void * dst_3_bytes, uint32_t address);
+
+static inline uint32_t swap_bytes_32(uint32_t x);
+static inline uint16_t swap_bytes_16(uint16_t x);
+
+/**********************
+ *  STATIC VARIABLES
+ **********************/
+
+/**********************
+ *      MACROS
+ **********************/
+
+#if LV_BIG_ENDIAN_SYSTEM
+    #define BE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define BE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define LE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+#else
+    #define BE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define BE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+    #define LE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+#endif
+
+/**********************
+ *   GLOBAL FUNCTIONS
+ **********************/
+
+lv_display_t * lv_ft81x_create(const lv_ft81x_parameters_t * params, void * partial_buf, uint32_t buf_size,
+                               lv_ft81x_spi_cb_t spi_cb, void * user_data)
+{
+    lv_display_t * disp = lv_display_create(params->hor_res, params->ver_res);
+
+    lv_ft81x_driver_data_t * drv = lv_malloc_zeroed(sizeof(lv_ft81x_driver_data_t));
+    LV_ASSERT_MALLOC(drv);
+    drv->spi_cb = spi_cb;
+    drv->user_data = user_data;
+    drv->cmd_offset = 0;
+    lv_display_set_driver_data(disp, drv);
+    lv_display_set_flush_cb(disp, flush_cb);
+    lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
+    lv_display_set_buffers(disp, partial_buf, NULL, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
+    lv_display_add_event_cb(disp, delete_cb, LV_EVENT_DELETE, NULL);
+
+    lv_result_t init_res = initialize(disp, params);
+    if(init_res != LV_RESULT_OK) {
+        lv_display_delete(disp);
+        return NULL;
+    }
+
+    return disp;
+}
+
+void * lv_ft81x_get_user_data(lv_display_t * disp)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    return drv->user_data;
+}
+
+/**********************
+ *   STATIC FUNCTIONS
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params)
+{
+    lv_ft81x_cmd(disp, params->has_crystal ? EVE_CLKEXT : EVE_CLKINT, 0);
+    if(params->is_bt81x) lv_ft81x_cmd(disp, EVE_CLKSEL, 0x46);
+    lv_ft81x_cmd(disp, EVE_ACTIVE, 0);
+
+    lv_delay_ms(40);
+
+    uint32_t start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_ID) != 0x7c) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("failed to read 0x7C from the ID register 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_CPURESET) & 0x03) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("CPURESET register coprocessor and touch engines not in \"working status\" 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    if(params->is_bt81x) lv_ft81x_write_32(disp, REG_FREQUENCY, 72000000);
+
+    lv_ft81x_write_8(disp, REG_PWM_DUTY, 0x80);
+
+    lv_ft81x_write_16(disp, REG_HSIZE,   params->hor_res);   /* active display width */
+    lv_ft81x_write_16(disp, REG_HCYCLE,  params->hcycle);  /* total number of clocks per line, incl front/back porch */
+    lv_ft81x_write_16(disp, REG_HOFFSET, params->hoffset); /* start of active line */
+    lv_ft81x_write_16(disp, REG_HSYNC0,  params->hsync0);  /* start of horizontal sync pulse */
+    lv_ft81x_write_16(disp, REG_HSYNC1,  params->hsync1);  /* end of horizontal sync pulse */
+    lv_ft81x_write_16(disp, REG_VSIZE,   params->ver_res);   /* active display height */
+    lv_ft81x_write_16(disp, REG_VCYCLE,  params->vcycle);  /* total number of lines per screen, including pre/post */
+    lv_ft81x_write_16(disp, REG_VOFFSET, params->voffset); /* start of active screen */
+    lv_ft81x_write_16(disp, REG_VSYNC0,  params->vsync0);  /* start of vertical sync pulse */
+    lv_ft81x_write_16(disp, REG_VSYNC1,  params->vsync1);  /* end of vertical sync pulse */
+    lv_ft81x_write_8(disp, REG_SWIZZLE,  params->swizzle); /* FT8xx output to LCD - pin order */
+    lv_ft81x_write_8(disp, REG_PCLK_POL, params->pclkpol); /* LCD data is clocked in on this PCLK edge */
+    lv_ft81x_write_8(disp, REG_CSPREAD,
+                     params->cspread); /* helps with noise, when set to 1 fewer signals are changed simultaneously, reset-default: 1 */
+
+    /* write a basic display-list to get things started */
+    lv_ft81x_write_32(disp, EVE_RAM_DL, DL_CLEAR_RGB);
+    lv_ft81x_write_32(disp, EVE_RAM_DL + 4, (DL_CLEAR | CLR_COL | CLR_STN | CLR_TAG));
+    lv_ft81x_write_32(disp, EVE_RAM_DL + 8, DL_DISPLAY);    /* end of display list */
+    lv_ft81x_write_32(disp, REG_DLSWAP, EVE_DLSWAP_FRAME);
+
+    /* nothing is being displayed yet... the pixel clock is still 0x00 */
+    lv_ft81x_write_8(disp, REG_GPIO,
+                     0x80); /* enable the DISP signal to the LCD panel, it is set to output in REG_GPIO_DIR by default */
+    lv_ft81x_write_8(disp, REG_PCLK, params->pclk); /* now start clocking data to the LCD panel */
+
+    LV_ASSERT(lv_ft81x_read_16(disp, REG_CMD_READ) != 0xfff);
+
+    LV_ASSERT(0 == lv_ft81x_read_16(disp, REG_CMD_WRITE));
+
+
+    lv_ft81x_cmd_list_t cmd_list;
+    lv_ft81x_cmd_list_init(disp, &cmd_list);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_MEMSET);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0); /* address */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0x00); /* val */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 2 * params->hor_res * params->ver_res); /* count */
+    lv_ft81x_cmd_list_send(disp, &cmd_list);
+
+    lv_ft81x_cmd_list_init(disp, &cmd_list);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_DLSTART);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_CLEAR_RGB | 0); /* clear to black */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_CLEAR | CLR_COL | CLR_STN | CLR_TAG);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(0));
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(20));
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_SETBITMAP);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0); /* address */
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, EVE_RGB565);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, params->hor_res);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, params->ver_res);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, 0);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_BEGIN | EVE_BITMAPS);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, VERTEX2F(0, 0));
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_END);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(0));
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_DISPLAY);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_SWAP);
+
+    lv_ft81x_cmd_list_send(disp, &cmd_list);
+
+
+    return LV_RESULT_OK;
+}
+
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+
+    int32_t hor_res = lv_display_get_horizontal_resolution(disp);
+    uint32_t disp_row_bytes = hor_res * 2;
+
+    uint32_t address = disp_row_bytes * area->y1 + area->x1 * 2;
+    uint8_t encoded_address[3];
+
+    if(lv_area_get_width(area) == hor_res) {
+        lv_ft81x_encode_write_address(encoded_address, address);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, encoded_address, sizeof(encoded_address));
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, px_map, lv_area_get_size(area) * 2);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    }
+    else {
+        uint32_t area_height = lv_area_get_height(area);
+        uint32_t area_row_bytes = lv_area_get_width(area) * 2;
+        for(uint32_t i = 0; i < area_height; i++) {
+            lv_ft81x_encode_write_address(encoded_address, address);
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, encoded_address, sizeof(encoded_address));
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, px_map, area_row_bytes);
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+            address += disp_row_bytes;
+            px_map += area_row_bytes;
+        }
+    }
+
+    lv_display_flush_ready(disp);
+}
+
+static void delete_cb(lv_event_t * e)
+{
+    lv_display_t * disp = lv_event_get_target(e);
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    lv_free(drv);
+}
+
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    uint8_t data[3] = {command, parameter, 0x00};
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, data, sizeof(data));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+}
+
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    lv_ft81x_encode_read_address(&address, address);
+    uint8_t val;
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, &address, sizeof(address));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_RECEIVE, &val, sizeof(val));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    return val;
+}
+
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    lv_ft81x_encode_read_address(&address, address);
+    uint16_t val;
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, &address, sizeof(address));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_RECEIVE, &val, sizeof(val));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    val = LE_TO_OR_FROM_NATIVE_16(val);
+    return val;
+}
+
+/*  static uint32_t lv_ft81x_read_32(lv_display_t * disp, uint32_t address)
+    {
+        lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+        lv_ft81x_encode_read_address(&address, address);
+        uint32_t val;
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, &address, sizeof(address));
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_RECEIVE, &val, sizeof(val));
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+        val = LE_TO_OR_FROM_NATIVE_32(val);
+        return val;
+    } */
+
+static void lv_ft81x_write_8(lv_display_t * disp, uint32_t address, uint8_t val)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    uint8_t data[4];
+    lv_ft81x_encode_write_address(data, address);
+    data[3] = val;
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, data, sizeof(data));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+}
+
+static void lv_ft81x_write_16(lv_display_t * disp, uint32_t address, uint16_t val)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    val = LE_TO_OR_FROM_NATIVE_16(val);
+    uint8_t data[5];
+    lv_ft81x_encode_write_address(data, address);
+    lv_memcpy(data + 3, &val, 2);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, data, sizeof(data));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+}
+
+static void lv_ft81x_write_32(lv_display_t * disp, uint32_t address, uint32_t val)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    val = LE_TO_OR_FROM_NATIVE_32(val);
+    uint8_t data[7];
+    lv_ft81x_encode_write_address(data, address);
+    lv_memcpy(data + 3, &val, 4);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, data, sizeof(data));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+}
+
+static void lv_ft81x_cmd_list_init(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    LV_ASSERT_MSG(LV_FT81X_CMD_BUF_SIZE >= 3, "increase LV_FT81X_CMD_BUF_SIZE as needed");
+    lv_ft81x_encode_write_address(cmd_list->buf, EVE_RAM_CMD + drv->cmd_offset);
+    cmd_list->buf_len = 3;
+}
+
+static void lv_ft81x_cmd_list_add_16(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint16_t value)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    uint8_t * buf_dst = &cmd_list->buf[cmd_list->buf_len];
+    cmd_list->buf_len += 2;
+    LV_ASSERT_MSG(cmd_list->buf_len <= LV_FT81X_CMD_BUF_SIZE, "increase LV_FT81X_CMD_BUF_SIZE as needed");
+    value = LE_TO_OR_FROM_NATIVE_16(value);
+    lv_memcpy(buf_dst, &value, 2);
+    drv->cmd_offset = (drv->cmd_offset + 2) & 0xfff; /* circular */
+}
+
+static void lv_ft81x_cmd_list_add_32(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint32_t value)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    uint8_t * buf_dst = &cmd_list->buf[cmd_list->buf_len];
+    cmd_list->buf_len += 4;
+    LV_ASSERT_MSG(cmd_list->buf_len <= LV_FT81X_CMD_BUF_SIZE, "increase LV_FT81X_CMD_BUF_SIZE as needed");
+    value = LE_TO_OR_FROM_NATIVE_32(value);
+    lv_memcpy(buf_dst, &value, 4);
+    drv->cmd_offset = (drv->cmd_offset + 4) & 0xfff; /* circular */
+}
+
+static void lv_ft81x_cmd_list_send(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, cmd_list->buf, cmd_list->buf_len);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    lv_ft81x_write_16(disp, REG_CMD_WRITE, drv->cmd_offset);
+}
+
+static void lv_ft81x_encode_read_address(void * dst_4_bytes, uint32_t address)
+{
+    address = BE_TO_OR_FROM_NATIVE_32(address << 8);
+    lv_memcpy(dst_4_bytes, &address, 4);
+}
+
+static void lv_ft81x_encode_write_address(void * dst_3_bytes, uint32_t address)
+{
+    address = BE_TO_OR_FROM_NATIVE_32((address | 0x800000) << 8);
+    lv_memcpy(dst_3_bytes, &address, 3);
+}
+
+static inline uint32_t swap_bytes_32(uint32_t x)
path: src/drivers/display/ft81x/lv_ft81x.c
user: login: uLipe
body: These functions are somewhat generic, why not move them to  utils/misc section and make them visible to everyone.
created_at: 2025-02-21T17:27:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -145,6 +145,8 @@
 #define LV_USE_ST7789       1
 #define LV_USE_ST7796       1
 
+#define LV_USE_FT81X        1
path: tests/src/lv_test_conf_full.h
user: login: PGNetHun
body: Move 1 line above, do not keep empty line in list
created_at: 2025-03-17T23:46:59Z
author_association: COLLABORATOR
diff_hunk: @@ -1224,6 +1224,8 @@
     #define LV_ST_LTDC_USE_DMA2D_FLUSH 0
 #endif
 
+#define LV_USE_FT81X      0
path: lv_conf_template.h
user: login: PGNetHun
body: Please move below `LV_USE_ILI9341` (line 1213), because this FT81x is also an SPI display.
created_at: 2025-03-17T23:52:05Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,435 @@
+/**
+ * @file lv_ft81x.c
+ *
+ */
+
+/*********************
+ *      INCLUDES
+ *********************/
+
+#include "lv_ft81x.h"
+#if LV_USE_FT81X
+
+#include "lv_ft81x_defines.h"
+
+#include "../../../stdlib/lv_mem.h"
+#include "../../../stdlib/lv_sprintf.h"
+#include "../../../stdlib/lv_string.h"
+#include "../../../misc/lv_types.h"
+
+/*********************
+ *      DEFINES
+ *********************/
+
+/* Increase as functionality is added if needed. */
+#define LV_FT81X_CMD_BUF_SIZE 63
+
+/**********************
+ *      TYPEDEFS
+ **********************/
+
+typedef struct {
+    lv_ft81x_spi_cb_t spi_cb;
+    void * user_data;
+    uint16_t cmd_offset;
+} lv_ft81x_driver_data_t;
+
+typedef struct {
+    uint32_t buf_len;
+    uint8_t buf[LV_FT81X_CMD_BUF_SIZE];
+} lv_ft81x_cmd_list_t;
+
+/**********************
+ *  STATIC PROTOTYPES
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params);
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map);
+static void delete_cb(lv_event_t * e);
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter);
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address);
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address);
+/* static uint32_t lv_ft81x_read_32(lv_display_t * disp, uint32_t address); */
+static void lv_ft81x_write_8(lv_display_t * disp, uint32_t address, uint8_t val);
+static void lv_ft81x_write_16(lv_display_t * disp, uint32_t address, uint16_t val);
+static void lv_ft81x_write_32(lv_display_t * disp, uint32_t address, uint32_t val);
+static void lv_ft81x_cmd_list_init(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_cmd_list_add_16(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint16_t value);
+static void lv_ft81x_cmd_list_add_32(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint32_t value);
+static void lv_ft81x_cmd_list_send(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_encode_read_address(void * dst_4_bytes, uint32_t address);
+static void lv_ft81x_encode_write_address(void * dst_3_bytes, uint32_t address);
+
+static inline uint32_t swap_bytes_32(uint32_t x);
+static inline uint16_t swap_bytes_16(uint16_t x);
+
+/**********************
+ *  STATIC VARIABLES
+ **********************/
+
+/**********************
+ *      MACROS
+ **********************/
+
+#if LV_BIG_ENDIAN_SYSTEM
+    #define BE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define BE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define LE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+#else
+    #define BE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define BE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+    #define LE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+#endif
+
+/**********************
+ *   GLOBAL FUNCTIONS
+ **********************/
+
+lv_display_t * lv_ft81x_create(const lv_ft81x_parameters_t * params, void * partial_buf, uint32_t buf_size,
+                               lv_ft81x_spi_cb_t spi_cb, void * user_data)
+{
+    lv_display_t * disp = lv_display_create(params->hor_res, params->ver_res);
+
+    lv_ft81x_driver_data_t * drv = lv_malloc_zeroed(sizeof(lv_ft81x_driver_data_t));
+    LV_ASSERT_MALLOC(drv);
+    drv->spi_cb = spi_cb;
+    drv->user_data = user_data;
+    drv->cmd_offset = 0;
+    lv_display_set_driver_data(disp, drv);
+    lv_display_set_flush_cb(disp, flush_cb);
+    lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
+    lv_display_set_buffers(disp, partial_buf, NULL, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
+    lv_display_add_event_cb(disp, delete_cb, LV_EVENT_DELETE, NULL);
+
+    lv_result_t init_res = initialize(disp, params);
+    if(init_res != LV_RESULT_OK) {
+        lv_display_delete(disp);
+        return NULL;
+    }
+
+    return disp;
+}
+
+void * lv_ft81x_get_user_data(lv_display_t * disp)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    return drv->user_data;
+}
+
+/**********************
+ *   STATIC FUNCTIONS
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params)
+{
+    lv_ft81x_cmd(disp, params->has_crystal ? EVE_CLKEXT : EVE_CLKINT, 0);
+    if(params->is_bt81x) lv_ft81x_cmd(disp, EVE_CLKSEL, 0x46);
+    lv_ft81x_cmd(disp, EVE_ACTIVE, 0);
+
+    lv_delay_ms(40);
+
+    uint32_t start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_ID) != 0x7c) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("failed to read 0x7C from the ID register 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_CPURESET) & 0x03) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("CPURESET register coprocessor and touch engines not in \"working status\" 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    if(params->is_bt81x) lv_ft81x_write_32(disp, REG_FREQUENCY, 72000000);
+
+    lv_ft81x_write_8(disp, REG_PWM_DUTY, 0x80);
+
+    lv_ft81x_write_16(disp, REG_HSIZE,   params->hor_res);   /* active display width */
+    lv_ft81x_write_16(disp, REG_HCYCLE,  params->hcycle);  /* total number of clocks per line, incl front/back porch */
+    lv_ft81x_write_16(disp, REG_HOFFSET, params->hoffset); /* start of active line */
+    lv_ft81x_write_16(disp, REG_HSYNC0,  params->hsync0);  /* start of horizontal sync pulse */
+    lv_ft81x_write_16(disp, REG_HSYNC1,  params->hsync1);  /* end of horizontal sync pulse */
+    lv_ft81x_write_16(disp, REG_VSIZE,   params->ver_res);   /* active display height */
+    lv_ft81x_write_16(disp, REG_VCYCLE,  params->vcycle);  /* total number of lines per screen, including pre/post */
+    lv_ft81x_write_16(disp, REG_VOFFSET, params->voffset); /* start of active screen */
+    lv_ft81x_write_16(disp, REG_VSYNC0,  params->vsync0);  /* start of vertical sync pulse */
+    lv_ft81x_write_16(disp, REG_VSYNC1,  params->vsync1);  /* end of vertical sync pulse */
+    lv_ft81x_write_8(disp, REG_SWIZZLE,  params->swizzle); /* FT8xx output to LCD - pin order */
+    lv_ft81x_write_8(disp, REG_PCLK_POL, params->pclkpol); /* LCD data is clocked in on this PCLK edge */
+    lv_ft81x_write_8(disp, REG_CSPREAD,
+                     params->cspread); /* helps with noise, when set to 1 fewer signals are changed simultaneously, reset-default: 1 */
+
+    /* write a basic display-list to get things started */
+    lv_ft81x_write_32(disp, EVE_RAM_DL, DL_CLEAR_RGB);
+    lv_ft81x_write_32(disp, EVE_RAM_DL + 4, (DL_CLEAR | CLR_COL | CLR_STN | CLR_TAG));
+    lv_ft81x_write_32(disp, EVE_RAM_DL + 8, DL_DISPLAY);    /* end of display list */
+    lv_ft81x_write_32(disp, REG_DLSWAP, EVE_DLSWAP_FRAME);
+
+    /* nothing is being displayed yet... the pixel clock is still 0x00 */
+    lv_ft81x_write_8(disp, REG_GPIO,
+                     0x80); /* enable the DISP signal to the LCD panel, it is set to output in REG_GPIO_DIR by default */
+    lv_ft81x_write_8(disp, REG_PCLK, params->pclk); /* now start clocking data to the LCD panel */
+
+    LV_ASSERT(lv_ft81x_read_16(disp, REG_CMD_READ) != 0xfff);
+
+    LV_ASSERT(0 == lv_ft81x_read_16(disp, REG_CMD_WRITE));
+
+
+    lv_ft81x_cmd_list_t cmd_list;
+    lv_ft81x_cmd_list_init(disp, &cmd_list);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_MEMSET);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0); /* address */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0x00); /* val */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 2 * params->hor_res * params->ver_res); /* count */
+    lv_ft81x_cmd_list_send(disp, &cmd_list);
+
+    lv_ft81x_cmd_list_init(disp, &cmd_list);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_DLSTART);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_CLEAR_RGB | 0); /* clear to black */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_CLEAR | CLR_COL | CLR_STN | CLR_TAG);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(0));
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(20));
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_SETBITMAP);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0); /* address */
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, EVE_RGB565);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, params->hor_res);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, params->ver_res);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, 0);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_BEGIN | EVE_BITMAPS);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, VERTEX2F(0, 0));
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_END);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(0));
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_DISPLAY);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_SWAP);
+
+    lv_ft81x_cmd_list_send(disp, &cmd_list);
+
+
+    return LV_RESULT_OK;
+}
+
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+
+    int32_t hor_res = lv_display_get_horizontal_resolution(disp);
+    uint32_t disp_row_bytes = hor_res * 2;
+
+    uint32_t address = disp_row_bytes * area->y1 + area->x1 * 2;
+    uint8_t encoded_address[3];
+
+    if(lv_area_get_width(area) == hor_res) {
+        lv_ft81x_encode_write_address(encoded_address, address);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, encoded_address, sizeof(encoded_address));
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, px_map, lv_area_get_size(area) * 2);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    }
+    else {
+        uint32_t area_height = lv_area_get_height(area);
+        uint32_t area_row_bytes = lv_area_get_width(area) * 2;
+        for(uint32_t i = 0; i < area_height; i++) {
+            lv_ft81x_encode_write_address(encoded_address, address);
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, encoded_address, sizeof(encoded_address));
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, px_map, area_row_bytes);
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+            address += disp_row_bytes;
+            px_map += area_row_bytes;
+        }
+    }
+
+    lv_display_flush_ready(disp);
+}
+
+static void delete_cb(lv_event_t * e)
+{
+    lv_display_t * disp = lv_event_get_target(e);
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    lv_free(drv);
+}
+
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    uint8_t data[3] = {command, parameter, 0x00};
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, data, sizeof(data));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+}
+
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    lv_ft81x_encode_read_address(&address, address);
+    uint8_t val;
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, &address, sizeof(address));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_RECEIVE, &val, sizeof(val));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    return val;
+}
+
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    lv_ft81x_encode_read_address(&address, address);
+    uint16_t val;
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, &address, sizeof(address));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_RECEIVE, &val, sizeof(val));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    val = LE_TO_OR_FROM_NATIVE_16(val);
+    return val;
+}
+
+/*  static uint32_t lv_ft81x_read_32(lv_display_t * disp, uint32_t address)
+    {
+        lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+        lv_ft81x_encode_read_address(&address, address);
+        uint32_t val;
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, &address, sizeof(address));
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_RECEIVE, &val, sizeof(val));
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+        val = LE_TO_OR_FROM_NATIVE_32(val);
+        return val;
+    } */
+
+static void lv_ft81x_write_8(lv_display_t * disp, uint32_t address, uint8_t val)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    uint8_t data[4];
+    lv_ft81x_encode_write_address(data, address);
+    data[3] = val;
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, data, sizeof(data));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+}
+
+static void lv_ft81x_write_16(lv_display_t * disp, uint32_t address, uint16_t val)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    val = LE_TO_OR_FROM_NATIVE_16(val);
+    uint8_t data[5];
+    lv_ft81x_encode_write_address(data, address);
+    lv_memcpy(data + 3, &val, 2);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, data, sizeof(data));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+}
+
+static void lv_ft81x_write_32(lv_display_t * disp, uint32_t address, uint32_t val)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    val = LE_TO_OR_FROM_NATIVE_32(val);
+    uint8_t data[7];
+    lv_ft81x_encode_write_address(data, address);
+    lv_memcpy(data + 3, &val, 4);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, data, sizeof(data));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+}
+
+static void lv_ft81x_cmd_list_init(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    LV_ASSERT_MSG(LV_FT81X_CMD_BUF_SIZE >= 3, "increase LV_FT81X_CMD_BUF_SIZE as needed");
+    lv_ft81x_encode_write_address(cmd_list->buf, EVE_RAM_CMD + drv->cmd_offset);
+    cmd_list->buf_len = 3;
+}
+
+static void lv_ft81x_cmd_list_add_16(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint16_t value)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    uint8_t * buf_dst = &cmd_list->buf[cmd_list->buf_len];
+    cmd_list->buf_len += 2;
+    LV_ASSERT_MSG(cmd_list->buf_len <= LV_FT81X_CMD_BUF_SIZE, "increase LV_FT81X_CMD_BUF_SIZE as needed");
+    value = LE_TO_OR_FROM_NATIVE_16(value);
+    lv_memcpy(buf_dst, &value, 2);
+    drv->cmd_offset = (drv->cmd_offset + 2) & 0xfff; /* circular */
+}
+
+static void lv_ft81x_cmd_list_add_32(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint32_t value)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    uint8_t * buf_dst = &cmd_list->buf[cmd_list->buf_len];
+    cmd_list->buf_len += 4;
+    LV_ASSERT_MSG(cmd_list->buf_len <= LV_FT81X_CMD_BUF_SIZE, "increase LV_FT81X_CMD_BUF_SIZE as needed");
+    value = LE_TO_OR_FROM_NATIVE_32(value);
+    lv_memcpy(buf_dst, &value, 4);
+    drv->cmd_offset = (drv->cmd_offset + 4) & 0xfff; /* circular */
+}
+
+static void lv_ft81x_cmd_list_send(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, cmd_list->buf, cmd_list->buf_len);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    lv_ft81x_write_16(disp, REG_CMD_WRITE, drv->cmd_offset);
+}
+
+static void lv_ft81x_encode_read_address(void * dst_4_bytes, uint32_t address)
+{
+    address = BE_TO_OR_FROM_NATIVE_32(address << 8);
+    lv_memcpy(dst_4_bytes, &address, 4);
+}
+
+static void lv_ft81x_encode_write_address(void * dst_3_bytes, uint32_t address)
+{
+    address = BE_TO_OR_FROM_NATIVE_32((address | 0x800000) << 8);
+    lv_memcpy(dst_3_bytes, &address, 3);
+}
+
+static inline uint32_t swap_bytes_32(uint32_t x)
path: src/drivers/display/ft81x/lv_ft81x.c
user: login: PGNetHun
body: Or check whether there are already swap bytes (inline) functions.
created_at: 2025-03-18T00:05:52Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,435 @@
+/**
+ * @file lv_ft81x.c
+ *
+ */
+
+/*********************
+ *      INCLUDES
+ *********************/
+
+#include "lv_ft81x.h"
+#if LV_USE_FT81X
+
+#include "lv_ft81x_defines.h"
+
+#include "../../../stdlib/lv_mem.h"
+#include "../../../stdlib/lv_sprintf.h"
+#include "../../../stdlib/lv_string.h"
+#include "../../../misc/lv_types.h"
+
+/*********************
+ *      DEFINES
+ *********************/
+
+/* Increase as functionality is added if needed. */
+#define LV_FT81X_CMD_BUF_SIZE 63
+
+/**********************
+ *      TYPEDEFS
+ **********************/
+
+typedef struct {
+    lv_ft81x_spi_cb_t spi_cb;
+    void * user_data;
+    uint16_t cmd_offset;
+} lv_ft81x_driver_data_t;
+
+typedef struct {
+    uint32_t buf_len;
+    uint8_t buf[LV_FT81X_CMD_BUF_SIZE];
+} lv_ft81x_cmd_list_t;
+
+/**********************
+ *  STATIC PROTOTYPES
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params);
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map);
+static void delete_cb(lv_event_t * e);
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter);
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address);
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address);
+/* static uint32_t lv_ft81x_read_32(lv_display_t * disp, uint32_t address); */
+static void lv_ft81x_write_8(lv_display_t * disp, uint32_t address, uint8_t val);
+static void lv_ft81x_write_16(lv_display_t * disp, uint32_t address, uint16_t val);
+static void lv_ft81x_write_32(lv_display_t * disp, uint32_t address, uint32_t val);
+static void lv_ft81x_cmd_list_init(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_cmd_list_add_16(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint16_t value);
+static void lv_ft81x_cmd_list_add_32(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint32_t value);
+static void lv_ft81x_cmd_list_send(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_encode_read_address(void * dst_4_bytes, uint32_t address);
+static void lv_ft81x_encode_write_address(void * dst_3_bytes, uint32_t address);
+
+static inline uint32_t swap_bytes_32(uint32_t x);
+static inline uint16_t swap_bytes_16(uint16_t x);
+
+/**********************
+ *  STATIC VARIABLES
+ **********************/
+
+/**********************
+ *      MACROS
+ **********************/
+
+#if LV_BIG_ENDIAN_SYSTEM
+    #define BE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define BE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define LE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+#else
+    #define BE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define BE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+    #define LE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+#endif
+
+/**********************
+ *   GLOBAL FUNCTIONS
+ **********************/
+
+lv_display_t * lv_ft81x_create(const lv_ft81x_parameters_t * params, void * partial_buf, uint32_t buf_size,
+                               lv_ft81x_spi_cb_t spi_cb, void * user_data)
+{
+    lv_display_t * disp = lv_display_create(params->hor_res, params->ver_res);
+
+    lv_ft81x_driver_data_t * drv = lv_malloc_zeroed(sizeof(lv_ft81x_driver_data_t));
+    LV_ASSERT_MALLOC(drv);
+    drv->spi_cb = spi_cb;
+    drv->user_data = user_data;
+    drv->cmd_offset = 0;
+    lv_display_set_driver_data(disp, drv);
+    lv_display_set_flush_cb(disp, flush_cb);
+    lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
+    lv_display_set_buffers(disp, partial_buf, NULL, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
+    lv_display_add_event_cb(disp, delete_cb, LV_EVENT_DELETE, NULL);
+
+    lv_result_t init_res = initialize(disp, params);
+    if(init_res != LV_RESULT_OK) {
+        lv_display_delete(disp);
+        return NULL;
+    }
+
+    return disp;
+}
+
+void * lv_ft81x_get_user_data(lv_display_t * disp)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    return drv->user_data;
+}
+
+/**********************
+ *   STATIC FUNCTIONS
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params)
+{
+    lv_ft81x_cmd(disp, params->has_crystal ? EVE_CLKEXT : EVE_CLKINT, 0);
+    if(params->is_bt81x) lv_ft81x_cmd(disp, EVE_CLKSEL, 0x46);
+    lv_ft81x_cmd(disp, EVE_ACTIVE, 0);
+
+    lv_delay_ms(40);
path: src/drivers/display/ft81x/lv_ft81x.c
user: login: liamHowatt
body: Added.

For reference, here is where the 40 ms came from.

https://github.com/lvgl/lvgl_esp32_drivers/blob/26fe6e7703162ac9b0ee82405e858dc37b885d1f/lvgl_tft/EVE_commands.c#L866-L874
created_at: 2025-04-25T08:53:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,435 @@
+/**
+ * @file lv_ft81x.c
+ *
+ */
+
+/*********************
+ *      INCLUDES
+ *********************/
+
+#include "lv_ft81x.h"
+#if LV_USE_FT81X
+
+#include "lv_ft81x_defines.h"
+
+#include "../../../stdlib/lv_mem.h"
+#include "../../../stdlib/lv_sprintf.h"
+#include "../../../stdlib/lv_string.h"
+#include "../../../misc/lv_types.h"
+
+/*********************
+ *      DEFINES
+ *********************/
+
+/* Increase as functionality is added if needed. */
+#define LV_FT81X_CMD_BUF_SIZE 63
+
+/**********************
+ *      TYPEDEFS
+ **********************/
+
+typedef struct {
+    lv_ft81x_spi_cb_t spi_cb;
+    void * user_data;
+    uint16_t cmd_offset;
+} lv_ft81x_driver_data_t;
+
+typedef struct {
+    uint32_t buf_len;
+    uint8_t buf[LV_FT81X_CMD_BUF_SIZE];
+} lv_ft81x_cmd_list_t;
+
+/**********************
+ *  STATIC PROTOTYPES
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params);
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map);
+static void delete_cb(lv_event_t * e);
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter);
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address);
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address);
+/* static uint32_t lv_ft81x_read_32(lv_display_t * disp, uint32_t address); */
+static void lv_ft81x_write_8(lv_display_t * disp, uint32_t address, uint8_t val);
+static void lv_ft81x_write_16(lv_display_t * disp, uint32_t address, uint16_t val);
+static void lv_ft81x_write_32(lv_display_t * disp, uint32_t address, uint32_t val);
+static void lv_ft81x_cmd_list_init(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_cmd_list_add_16(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint16_t value);
+static void lv_ft81x_cmd_list_add_32(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint32_t value);
+static void lv_ft81x_cmd_list_send(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_encode_read_address(void * dst_4_bytes, uint32_t address);
+static void lv_ft81x_encode_write_address(void * dst_3_bytes, uint32_t address);
+
+static inline uint32_t swap_bytes_32(uint32_t x);
+static inline uint16_t swap_bytes_16(uint16_t x);
+
+/**********************
+ *  STATIC VARIABLES
+ **********************/
+
+/**********************
+ *      MACROS
+ **********************/
+
+#if LV_BIG_ENDIAN_SYSTEM
+    #define BE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define BE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define LE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+#else
+    #define BE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define BE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+    #define LE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+#endif
+
+/**********************
+ *   GLOBAL FUNCTIONS
+ **********************/
+
+lv_display_t * lv_ft81x_create(const lv_ft81x_parameters_t * params, void * partial_buf, uint32_t buf_size,
+                               lv_ft81x_spi_cb_t spi_cb, void * user_data)
+{
+    lv_display_t * disp = lv_display_create(params->hor_res, params->ver_res);
+
+    lv_ft81x_driver_data_t * drv = lv_malloc_zeroed(sizeof(lv_ft81x_driver_data_t));
+    LV_ASSERT_MALLOC(drv);
+    drv->spi_cb = spi_cb;
+    drv->user_data = user_data;
+    drv->cmd_offset = 0;
+    lv_display_set_driver_data(disp, drv);
+    lv_display_set_flush_cb(disp, flush_cb);
+    lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
+    lv_display_set_buffers(disp, partial_buf, NULL, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
+    lv_display_add_event_cb(disp, delete_cb, LV_EVENT_DELETE, NULL);
+
+    lv_result_t init_res = initialize(disp, params);
+    if(init_res != LV_RESULT_OK) {
+        lv_display_delete(disp);
+        return NULL;
+    }
+
+    return disp;
+}
+
+void * lv_ft81x_get_user_data(lv_display_t * disp)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    return drv->user_data;
+}
+
+/**********************
+ *   STATIC FUNCTIONS
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params)
+{
+    lv_ft81x_cmd(disp, params->has_crystal ? EVE_CLKEXT : EVE_CLKINT, 0);
+    if(params->is_bt81x) lv_ft81x_cmd(disp, EVE_CLKSEL, 0x46);
+    lv_ft81x_cmd(disp, EVE_ACTIVE, 0);
+
+    lv_delay_ms(40);
+
+    uint32_t start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_ID) != 0x7c) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("failed to read 0x7C from the ID register 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_CPURESET) & 0x03) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("CPURESET register coprocessor and touch engines not in \"working status\" 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    if(params->is_bt81x) lv_ft81x_write_32(disp, REG_FREQUENCY, 72000000);
path: src/drivers/display/ft81x/lv_ft81x.c
user: login: liamHowatt
body: It's specific to the BT81x. I'm not sure how it would behave with a different value.
created_at: 2025-04-25T08:53:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,435 @@
+/**
+ * @file lv_ft81x.c
+ *
+ */
+
+/*********************
+ *      INCLUDES
+ *********************/
+
+#include "lv_ft81x.h"
+#if LV_USE_FT81X
+
+#include "lv_ft81x_defines.h"
+
+#include "../../../stdlib/lv_mem.h"
+#include "../../../stdlib/lv_sprintf.h"
+#include "../../../stdlib/lv_string.h"
+#include "../../../misc/lv_types.h"
+
+/*********************
+ *      DEFINES
+ *********************/
+
+/* Increase as functionality is added if needed. */
+#define LV_FT81X_CMD_BUF_SIZE 63
+
+/**********************
+ *      TYPEDEFS
+ **********************/
+
+typedef struct {
+    lv_ft81x_spi_cb_t spi_cb;
+    void * user_data;
+    uint16_t cmd_offset;
+} lv_ft81x_driver_data_t;
+
+typedef struct {
+    uint32_t buf_len;
+    uint8_t buf[LV_FT81X_CMD_BUF_SIZE];
+} lv_ft81x_cmd_list_t;
+
+/**********************
+ *  STATIC PROTOTYPES
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params);
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map);
+static void delete_cb(lv_event_t * e);
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter);
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address);
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address);
+/* static uint32_t lv_ft81x_read_32(lv_display_t * disp, uint32_t address); */
+static void lv_ft81x_write_8(lv_display_t * disp, uint32_t address, uint8_t val);
+static void lv_ft81x_write_16(lv_display_t * disp, uint32_t address, uint16_t val);
+static void lv_ft81x_write_32(lv_display_t * disp, uint32_t address, uint32_t val);
+static void lv_ft81x_cmd_list_init(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_cmd_list_add_16(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint16_t value);
+static void lv_ft81x_cmd_list_add_32(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint32_t value);
+static void lv_ft81x_cmd_list_send(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_encode_read_address(void * dst_4_bytes, uint32_t address);
+static void lv_ft81x_encode_write_address(void * dst_3_bytes, uint32_t address);
+
+static inline uint32_t swap_bytes_32(uint32_t x);
+static inline uint16_t swap_bytes_16(uint16_t x);
+
+/**********************
+ *  STATIC VARIABLES
+ **********************/
+
+/**********************
+ *      MACROS
+ **********************/
+
+#if LV_BIG_ENDIAN_SYSTEM
+    #define BE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define BE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define LE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+#else
+    #define BE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define BE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+    #define LE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+#endif
+
+/**********************
+ *   GLOBAL FUNCTIONS
+ **********************/
+
+lv_display_t * lv_ft81x_create(const lv_ft81x_parameters_t * params, void * partial_buf, uint32_t buf_size,
+                               lv_ft81x_spi_cb_t spi_cb, void * user_data)
+{
+    lv_display_t * disp = lv_display_create(params->hor_res, params->ver_res);
+
+    lv_ft81x_driver_data_t * drv = lv_malloc_zeroed(sizeof(lv_ft81x_driver_data_t));
+    LV_ASSERT_MALLOC(drv);
+    drv->spi_cb = spi_cb;
+    drv->user_data = user_data;
+    drv->cmd_offset = 0;
+    lv_display_set_driver_data(disp, drv);
+    lv_display_set_flush_cb(disp, flush_cb);
+    lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
+    lv_display_set_buffers(disp, partial_buf, NULL, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
+    lv_display_add_event_cb(disp, delete_cb, LV_EVENT_DELETE, NULL);
+
+    lv_result_t init_res = initialize(disp, params);
+    if(init_res != LV_RESULT_OK) {
+        lv_display_delete(disp);
+        return NULL;
+    }
+
+    return disp;
+}
+
+void * lv_ft81x_get_user_data(lv_display_t * disp)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    return drv->user_data;
+}
+
+/**********************
+ *   STATIC FUNCTIONS
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params)
+{
+    lv_ft81x_cmd(disp, params->has_crystal ? EVE_CLKEXT : EVE_CLKINT, 0);
+    if(params->is_bt81x) lv_ft81x_cmd(disp, EVE_CLKSEL, 0x46);
+    lv_ft81x_cmd(disp, EVE_ACTIVE, 0);
+
+    lv_delay_ms(40);
+
+    uint32_t start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_ID) != 0x7c) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("failed to read 0x7C from the ID register 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_CPURESET) & 0x03) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("CPURESET register coprocessor and touch engines not in \"working status\" 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    if(params->is_bt81x) lv_ft81x_write_32(disp, REG_FREQUENCY, 72000000);
+
+    lv_ft81x_write_8(disp, REG_PWM_DUTY, 0x80);
path: src/drivers/display/ft81x/lv_ft81x.c
user: login: liamHowatt
body: Documented.

You got me. This value really was experimental. I know it works on at least two boards, but I had to find the value by accident.
created_at: 2025-04-25T08:53:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,435 @@
+/**
+ * @file lv_ft81x.c
+ *
+ */
+
+/*********************
+ *      INCLUDES
+ *********************/
+
+#include "lv_ft81x.h"
+#if LV_USE_FT81X
+
+#include "lv_ft81x_defines.h"
+
+#include "../../../stdlib/lv_mem.h"
+#include "../../../stdlib/lv_sprintf.h"
+#include "../../../stdlib/lv_string.h"
+#include "../../../misc/lv_types.h"
+
+/*********************
+ *      DEFINES
+ *********************/
+
+/* Increase as functionality is added if needed. */
+#define LV_FT81X_CMD_BUF_SIZE 63
+
+/**********************
+ *      TYPEDEFS
+ **********************/
+
+typedef struct {
+    lv_ft81x_spi_cb_t spi_cb;
+    void * user_data;
+    uint16_t cmd_offset;
+} lv_ft81x_driver_data_t;
+
+typedef struct {
+    uint32_t buf_len;
+    uint8_t buf[LV_FT81X_CMD_BUF_SIZE];
+} lv_ft81x_cmd_list_t;
+
+/**********************
+ *  STATIC PROTOTYPES
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params);
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map);
+static void delete_cb(lv_event_t * e);
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter);
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address);
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address);
+/* static uint32_t lv_ft81x_read_32(lv_display_t * disp, uint32_t address); */
+static void lv_ft81x_write_8(lv_display_t * disp, uint32_t address, uint8_t val);
+static void lv_ft81x_write_16(lv_display_t * disp, uint32_t address, uint16_t val);
+static void lv_ft81x_write_32(lv_display_t * disp, uint32_t address, uint32_t val);
+static void lv_ft81x_cmd_list_init(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_cmd_list_add_16(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint16_t value);
+static void lv_ft81x_cmd_list_add_32(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint32_t value);
+static void lv_ft81x_cmd_list_send(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list);
+static void lv_ft81x_encode_read_address(void * dst_4_bytes, uint32_t address);
+static void lv_ft81x_encode_write_address(void * dst_3_bytes, uint32_t address);
+
+static inline uint32_t swap_bytes_32(uint32_t x);
+static inline uint16_t swap_bytes_16(uint16_t x);
+
+/**********************
+ *  STATIC VARIABLES
+ **********************/
+
+/**********************
+ *      MACROS
+ **********************/
+
+#if LV_BIG_ENDIAN_SYSTEM
+    #define BE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define BE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define LE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+#else
+    #define BE_TO_OR_FROM_NATIVE_32(x) swap_bytes_32(x)
+    #define BE_TO_OR_FROM_NATIVE_16(x) swap_bytes_16(x)
+    #define LE_TO_OR_FROM_NATIVE_32(x) ((uint32_t)(x))
+    #define LE_TO_OR_FROM_NATIVE_16(x) ((uint16_t)(x))
+#endif
+
+/**********************
+ *   GLOBAL FUNCTIONS
+ **********************/
+
+lv_display_t * lv_ft81x_create(const lv_ft81x_parameters_t * params, void * partial_buf, uint32_t buf_size,
+                               lv_ft81x_spi_cb_t spi_cb, void * user_data)
+{
+    lv_display_t * disp = lv_display_create(params->hor_res, params->ver_res);
+
+    lv_ft81x_driver_data_t * drv = lv_malloc_zeroed(sizeof(lv_ft81x_driver_data_t));
+    LV_ASSERT_MALLOC(drv);
+    drv->spi_cb = spi_cb;
+    drv->user_data = user_data;
+    drv->cmd_offset = 0;
+    lv_display_set_driver_data(disp, drv);
+    lv_display_set_flush_cb(disp, flush_cb);
+    lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
+    lv_display_set_buffers(disp, partial_buf, NULL, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
+    lv_display_add_event_cb(disp, delete_cb, LV_EVENT_DELETE, NULL);
+
+    lv_result_t init_res = initialize(disp, params);
+    if(init_res != LV_RESULT_OK) {
+        lv_display_delete(disp);
+        return NULL;
+    }
+
+    return disp;
+}
+
+void * lv_ft81x_get_user_data(lv_display_t * disp)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    return drv->user_data;
+}
+
+/**********************
+ *   STATIC FUNCTIONS
+ **********************/
+
+static lv_result_t initialize(lv_display_t * disp, const lv_ft81x_parameters_t * params)
+{
+    lv_ft81x_cmd(disp, params->has_crystal ? EVE_CLKEXT : EVE_CLKINT, 0);
+    if(params->is_bt81x) lv_ft81x_cmd(disp, EVE_CLKSEL, 0x46);
+    lv_ft81x_cmd(disp, EVE_ACTIVE, 0);
+
+    lv_delay_ms(40);
+
+    uint32_t start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_ID) != 0x7c) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("failed to read 0x7C from the ID register 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    start_millis = lv_tick_get();
+    while(lv_ft81x_read_8(disp, REG_CPURESET) & 0x03) {
+        if(lv_tick_get() - start_millis > 1000) {
+            LV_LOG_ERROR("CPURESET register coprocessor and touch engines not in \"working status\" 1000 ms after activation.");
+            return LV_RESULT_INVALID;
+        }
+        lv_delay_ms(1);
+    };
+
+    if(params->is_bt81x) lv_ft81x_write_32(disp, REG_FREQUENCY, 72000000);
+
+    lv_ft81x_write_8(disp, REG_PWM_DUTY, 0x80);
+
+    lv_ft81x_write_16(disp, REG_HSIZE,   params->hor_res);   /* active display width */
+    lv_ft81x_write_16(disp, REG_HCYCLE,  params->hcycle);  /* total number of clocks per line, incl front/back porch */
+    lv_ft81x_write_16(disp, REG_HOFFSET, params->hoffset); /* start of active line */
+    lv_ft81x_write_16(disp, REG_HSYNC0,  params->hsync0);  /* start of horizontal sync pulse */
+    lv_ft81x_write_16(disp, REG_HSYNC1,  params->hsync1);  /* end of horizontal sync pulse */
+    lv_ft81x_write_16(disp, REG_VSIZE,   params->ver_res);   /* active display height */
+    lv_ft81x_write_16(disp, REG_VCYCLE,  params->vcycle);  /* total number of lines per screen, including pre/post */
+    lv_ft81x_write_16(disp, REG_VOFFSET, params->voffset); /* start of active screen */
+    lv_ft81x_write_16(disp, REG_VSYNC0,  params->vsync0);  /* start of vertical sync pulse */
+    lv_ft81x_write_16(disp, REG_VSYNC1,  params->vsync1);  /* end of vertical sync pulse */
+    lv_ft81x_write_8(disp, REG_SWIZZLE,  params->swizzle); /* FT8xx output to LCD - pin order */
+    lv_ft81x_write_8(disp, REG_PCLK_POL, params->pclkpol); /* LCD data is clocked in on this PCLK edge */
+    lv_ft81x_write_8(disp, REG_CSPREAD,
+                     params->cspread); /* helps with noise, when set to 1 fewer signals are changed simultaneously, reset-default: 1 */
+
+    /* write a basic display-list to get things started */
+    lv_ft81x_write_32(disp, EVE_RAM_DL, DL_CLEAR_RGB);
+    lv_ft81x_write_32(disp, EVE_RAM_DL + 4, (DL_CLEAR | CLR_COL | CLR_STN | CLR_TAG));
+    lv_ft81x_write_32(disp, EVE_RAM_DL + 8, DL_DISPLAY);    /* end of display list */
+    lv_ft81x_write_32(disp, REG_DLSWAP, EVE_DLSWAP_FRAME);
+
+    /* nothing is being displayed yet... the pixel clock is still 0x00 */
+    lv_ft81x_write_8(disp, REG_GPIO,
+                     0x80); /* enable the DISP signal to the LCD panel, it is set to output in REG_GPIO_DIR by default */
+    lv_ft81x_write_8(disp, REG_PCLK, params->pclk); /* now start clocking data to the LCD panel */
+
+    LV_ASSERT(lv_ft81x_read_16(disp, REG_CMD_READ) != 0xfff);
+
+    LV_ASSERT(0 == lv_ft81x_read_16(disp, REG_CMD_WRITE));
+
+
+    lv_ft81x_cmd_list_t cmd_list;
+    lv_ft81x_cmd_list_init(disp, &cmd_list);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_MEMSET);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0); /* address */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0x00); /* val */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 2 * params->hor_res * params->ver_res); /* count */
+    lv_ft81x_cmd_list_send(disp, &cmd_list);
+
+    lv_ft81x_cmd_list_init(disp, &cmd_list);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_DLSTART);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_CLEAR_RGB | 0); /* clear to black */
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_CLEAR | CLR_COL | CLR_STN | CLR_TAG);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(0));
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(20));
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_SETBITMAP);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, 0); /* address */
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, EVE_RGB565);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, params->hor_res);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, params->ver_res);
+    lv_ft81x_cmd_list_add_16(disp, &cmd_list, 0);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_BEGIN | EVE_BITMAPS);
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, VERTEX2F(0, 0));
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_END);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, TAG(0));
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, DL_DISPLAY);
+
+    lv_ft81x_cmd_list_add_32(disp, &cmd_list, CMD_SWAP);
+
+    lv_ft81x_cmd_list_send(disp, &cmd_list);
+
+
+    return LV_RESULT_OK;
+}
+
+static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+
+    int32_t hor_res = lv_display_get_horizontal_resolution(disp);
+    uint32_t disp_row_bytes = hor_res * 2;
+
+    uint32_t address = disp_row_bytes * area->y1 + area->x1 * 2;
+    uint8_t encoded_address[3];
+
+    if(lv_area_get_width(area) == hor_res) {
+        lv_ft81x_encode_write_address(encoded_address, address);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, encoded_address, sizeof(encoded_address));
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, px_map, lv_area_get_size(area) * 2);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    }
+    else {
+        uint32_t area_height = lv_area_get_height(area);
+        uint32_t area_row_bytes = lv_area_get_width(area) * 2;
+        for(uint32_t i = 0; i < area_height; i++) {
+            lv_ft81x_encode_write_address(encoded_address, address);
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, encoded_address, sizeof(encoded_address));
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, px_map, area_row_bytes);
+            drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+            address += disp_row_bytes;
+            px_map += area_row_bytes;
+        }
+    }
+
+    lv_display_flush_ready(disp);
+}
+
+static void delete_cb(lv_event_t * e)
+{
+    lv_display_t * disp = lv_event_get_target(e);
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    lv_free(drv);
+}
+
+static void lv_ft81x_cmd(lv_display_t * disp, uint8_t command, uint8_t parameter)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    uint8_t data[3] = {command, parameter, 0x00};
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, data, sizeof(data));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+}
+
+static uint8_t lv_ft81x_read_8(lv_display_t * disp, uint32_t address)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    lv_ft81x_encode_read_address(&address, address);
+    uint8_t val;
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, &address, sizeof(address));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_RECEIVE, &val, sizeof(val));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    return val;
+}
+
+static uint16_t lv_ft81x_read_16(lv_display_t * disp, uint32_t address)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    lv_ft81x_encode_read_address(&address, address);
+    uint16_t val;
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, &address, sizeof(address));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_RECEIVE, &val, sizeof(val));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    val = LE_TO_OR_FROM_NATIVE_16(val);
+    return val;
+}
+
+/*  static uint32_t lv_ft81x_read_32(lv_display_t * disp, uint32_t address)
+    {
+        lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+        lv_ft81x_encode_read_address(&address, address);
+        uint32_t val;
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, &address, sizeof(address));
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_RECEIVE, &val, sizeof(val));
+        drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+        val = LE_TO_OR_FROM_NATIVE_32(val);
+        return val;
+    } */
+
+static void lv_ft81x_write_8(lv_display_t * disp, uint32_t address, uint8_t val)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    uint8_t data[4];
+    lv_ft81x_encode_write_address(data, address);
+    data[3] = val;
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, data, sizeof(data));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+}
+
+static void lv_ft81x_write_16(lv_display_t * disp, uint32_t address, uint16_t val)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    val = LE_TO_OR_FROM_NATIVE_16(val);
+    uint8_t data[5];
+    lv_ft81x_encode_write_address(data, address);
+    lv_memcpy(data + 3, &val, 2);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, data, sizeof(data));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+}
+
+static void lv_ft81x_write_32(lv_display_t * disp, uint32_t address, uint32_t val)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    val = LE_TO_OR_FROM_NATIVE_32(val);
+    uint8_t data[7];
+    lv_ft81x_encode_write_address(data, address);
+    lv_memcpy(data + 3, &val, 4);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, data, sizeof(data));
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+}
+
+static void lv_ft81x_cmd_list_init(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    LV_ASSERT_MSG(LV_FT81X_CMD_BUF_SIZE >= 3, "increase LV_FT81X_CMD_BUF_SIZE as needed");
+    lv_ft81x_encode_write_address(cmd_list->buf, EVE_RAM_CMD + drv->cmd_offset);
+    cmd_list->buf_len = 3;
+}
+
+static void lv_ft81x_cmd_list_add_16(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint16_t value)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    uint8_t * buf_dst = &cmd_list->buf[cmd_list->buf_len];
+    cmd_list->buf_len += 2;
+    LV_ASSERT_MSG(cmd_list->buf_len <= LV_FT81X_CMD_BUF_SIZE, "increase LV_FT81X_CMD_BUF_SIZE as needed");
+    value = LE_TO_OR_FROM_NATIVE_16(value);
+    lv_memcpy(buf_dst, &value, 2);
+    drv->cmd_offset = (drv->cmd_offset + 2) & 0xfff; /* circular */
+}
+
+static void lv_ft81x_cmd_list_add_32(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list, uint32_t value)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    uint8_t * buf_dst = &cmd_list->buf[cmd_list->buf_len];
+    cmd_list->buf_len += 4;
+    LV_ASSERT_MSG(cmd_list->buf_len <= LV_FT81X_CMD_BUF_SIZE, "increase LV_FT81X_CMD_BUF_SIZE as needed");
+    value = LE_TO_OR_FROM_NATIVE_32(value);
+    lv_memcpy(buf_dst, &value, 4);
+    drv->cmd_offset = (drv->cmd_offset + 4) & 0xfff; /* circular */
+}
+
+static void lv_ft81x_cmd_list_send(lv_display_t * disp, lv_ft81x_cmd_list_t * cmd_list)
+{
+    lv_ft81x_driver_data_t * drv = lv_display_get_driver_data(disp);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_ASSERT, NULL, 0);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_SEND, cmd_list->buf, cmd_list->buf_len);
+    drv->spi_cb(disp, LV_FT81X_SPI_OPERATION_CS_DEASSERT, NULL, 0);
+    lv_ft81x_write_16(disp, REG_CMD_WRITE, drv->cmd_offset);
+}
+
+static void lv_ft81x_encode_read_address(void * dst_4_bytes, uint32_t address)
+{
+    address = BE_TO_OR_FROM_NATIVE_32(address << 8);
+    lv_memcpy(dst_4_bytes, &address, 4);
+}
+
+static void lv_ft81x_encode_write_address(void * dst_3_bytes, uint32_t address)
+{
+    address = BE_TO_OR_FROM_NATIVE_32((address | 0x800000) << 8);
+    lv_memcpy(dst_3_bytes, &address, 3);
+}
+
+static inline uint32_t swap_bytes_32(uint32_t x)
path: src/drivers/display/ft81x/lv_ft81x.c
user: login: liamHowatt
body: Done.

I find these sort of things to be subjective. Everyone has their own idea of how these should be done, which is why I favor local implementations.

> Or check whether there are already swap bytes (inline) functions.

Feel free to open a PR for it, following this one.
created_at: 2025-04-25T08:53:27Z
author_association: CONTRIBUTOR

PR comments:
user: login: lvgl-bot
created_at: 2025-03-13T02:04:44Z
body: We need some feedback on this pull request.

Now we mark this as "Abandoned" because there was no activity here for 14 days.

Remove the "Abandoned" label or comment else this will be closed in 7 days.
author_association: NONE
user: login: kisvegabor
created_at: 2025-03-17T07:41:01Z
body: not stale @liamHowatt 
author_association: MEMBER
user: login: lvgl-bot
created_at: 2025-04-05T02:04:51Z
body: We need some feedback on this pull request.

Now we mark this as "Abandoned" because there was no activity here for 14 days.

Remove the "Abandoned" label or comment else this will be closed in 7 days.
author_association: NONE
user: login: kisvegabor
created_at: 2025-04-07T18:33:39Z
body: Not stale
author_association: MEMBER
user: login: lvgl-bot
created_at: 2025-04-24T02:09:04Z
body: We need some feedback on this pull request.

Now we mark this as "Abandoned" because there was no activity here for 14 days.

Remove the "Abandoned" label or comment else this will be closed in 7 days.
author_association: NONE
user: login: kisvegabor
created_at: 2025-04-24T16:37:11Z
body: @liamHowatt is it ready to merge?
author_association: MEMBER
user: login: uLipe
created_at: 2025-04-24T21:14:05Z
body: If this is required for the next release, @liamHowatt you can treat all of my comments as non-blocking, so feel free to resolve it.
author_association: CONTRIBUTOR
user: login: liamHowatt
created_at: 2025-04-25T08:53:46Z
body: Sorry for the unexplained silence. I wanted to validate on BT817 -- and I have now, and it works. I have applied all feedback @uLipe @PGNetHun

> If this is required for the next release, @liamHowatt you can treat all of my comments as non-blocking, so feel free to resolve it.

Nope, rigorous review is preferred.

> @liamHowatt is it ready to merge?

Yes, after review.

> Nice! How was the performance on a 800x480 display with ESP32-S3?

Very poor. 1-2 FPS with `lv_demo_widgets_start_slideshow()`. I see that  An improvement could come from non-blocking DMA flushing. My only hesitation is it can only work for `lv_area_get_width(area) == hor_res`. See the `flush_cb` in `lv_ft81x.c`. I can imagine a non-breaking addition to this driver later which allows parallel flushing with notification of completion.

It sounds like @PGNetHun has an idea of how the API could be improved in general to match other MIPI-like drivers.
author_association: CONTRIBUTOR
user: login: liamHowatt
created_at: 2025-04-28T14:11:17Z
body: @PGNetHun merging is blocked by your requested changes. Please review if you see this.
author_association: CONTRIBUTOR
user: login: PGNetHun
created_at: 2025-04-28T14:42:57Z
body: > @PGNetHun merging is blocked by your requested changes. Please review if you see this.

Hello!
Sorry. 
I reviewed again, approved.

I agree with @kisvegabor , if this works, then merge to main, and later performance can be improved or driver refactored.
author_association: COLLABORATOR
user: login: kisvegabor
created_at: 2025-04-28T19:25:52Z
body: Thanks for all the reviews! 
author_association: MEMBER