#include "uDisplay_I80_panel.h" #if (SOC_LCD_I80_SUPPORTED && SOC_LCDCAM_I80_NUM_BUSES && !SOC_PARLIO_GROUPS ) #ifdef UDSP_DEBUG extern void AddLog(uint32_t loglevel, const char *formatP, ...); #define LOG_LEVEL_DEBUG 3 #endif // Pin control helpers static inline volatile uint32_t* get_gpio_hi_reg(int_fast8_t pin) { return (pin & 32) ? &GPIO.out1_w1ts.val : &GPIO.out_w1ts; } static inline volatile uint32_t* get_gpio_lo_reg(int_fast8_t pin) { return (pin & 32) ? &GPIO.out1_w1tc.val : &GPIO.out_w1tc; } static inline void gpio_hi(int_fast8_t pin) { if (pin >= 0) *get_gpio_hi_reg(pin) = 1 << (pin & 31); } static inline void gpio_lo(int_fast8_t pin) { if (pin >= 0) *get_gpio_lo_reg(pin) = 1 << (pin & 31); } I80Panel::I80Panel(const I80PanelConfig& config) : cfg(config), _width(config.width), _height(config.height), _rotation(0), _i80_bus(nullptr), _dev(nullptr), _dmadesc(nullptr), _DMA_Enabled(false), _dma_chan(nullptr), _dmadesc_size(0), _addr_x0(0), _addr_y0(0), _addr_x1(0), _addr_y1(0) { framebuffer = nullptr; // Initialize pins manually FIRST (matching old code order) if (cfg.cs_pin >= 0) { pinMode(cfg.cs_pin, OUTPUT); digitalWrite(cfg.cs_pin, HIGH); } pinMode(cfg.dc_pin, OUTPUT); digitalWrite(cfg.dc_pin, HIGH); pinMode(cfg.wr_pin, OUTPUT); digitalWrite(cfg.wr_pin, HIGH); if (cfg.rd_pin >= 0) { pinMode(cfg.rd_pin, OUTPUT); digitalWrite(cfg.rd_pin, HIGH); } for (int i = 0; i < 8; i++) { pinMode(cfg.data_pins_low[i], OUTPUT); } if (cfg.bus_width == 16) { for (int i = 0; i < 8; i++) { pinMode(cfg.data_pins_high[i], OUTPUT); } } // Now create I80 bus config esp_lcd_i80_bus_config_t bus_config = { .dc_gpio_num = cfg.dc_pin, .wr_gpio_num = cfg.wr_pin, .clk_src = LCD_CLK_SRC_DEFAULT, .bus_width = cfg.bus_width, .max_transfer_bytes = (size_t)cfg.width * cfg.height * 2 }; // Set data pins if (cfg.bus_width == 8) { for (int i = 0; i < 8; i++) { bus_config.data_gpio_nums[i] = cfg.data_pins_low[i]; } } else { for (int i = 0; i < 8; i++) { bus_config.data_gpio_nums[i] = cfg.data_pins_low[i]; bus_config.data_gpio_nums[i + 8] = cfg.data_pins_high[i]; } } // Create I80 bus (this will take over GPIO matrix for DC, WR, and data pins) esp_lcd_new_i80_bus(&bus_config, &_i80_bus); // Calculate clock using original algorithm uint32_t div_a, div_b, div_n, clkcnt; calcClockDiv(&div_a, &div_b, &div_n, &clkcnt, 240*1000*1000, cfg.clock_speed_hz); lcd_cam_lcd_clock_reg_t lcd_clock; lcd_clock.lcd_clkcnt_n = std::max((uint32_t)1u, clkcnt - 1); lcd_clock.lcd_clk_equ_sysclk = (clkcnt == 1); lcd_clock.lcd_ck_idle_edge = true; lcd_clock.lcd_ck_out_edge = false; lcd_clock.lcd_clkm_div_num = div_n; lcd_clock.lcd_clkm_div_b = div_b; lcd_clock.lcd_clkm_div_a = div_a; lcd_clock.lcd_clk_sel = 2; // 240MHz lcd_clock.clk_en = true; _clock_reg_value = lcd_clock.val; _alloc_dmadesc(1); _dev = &LCD_CAM; // EXECUTE INITIALIZATION COMMANDS (from original uDisplay code) if (cfg.init_commands && cfg.init_commands_count > 0) { uint16_t index = 0; pb_beginTransaction(); while (index < cfg.init_commands_count) { cs_control(false); uint8_t cmd = cfg.init_commands[index++]; pb_writeCommand(cmd, 8); if (index < cfg.init_commands_count) { uint8_t args = cfg.init_commands[index++]; uint8_t arg_count = args & 0x1f; #ifdef UDSP_DEBUG AddLog(LOG_LEVEL_DEBUG, "UDisplay: cmd, args %02x, %d", cmd, arg_count); #endif for (uint32_t cnt = 0; cnt < arg_count && index < cfg.init_commands_count; cnt++) { uint8_t arg_data = cfg.init_commands[index++]; #ifdef UDSP_DEBUG AddLog(LOG_LEVEL_DEBUG, "%02x ", arg_data); #endif pb_writeData(arg_data, 8); } cs_control(true); // Handle delay after command if (args & 0x80) { uint32_t delay_ms = 0; switch (args & 0xE0) { case 0x80: delay_ms = 150; break; case 0xA0: delay_ms = 10; break; case 0xE0: delay_ms = 500; break; } if (delay_ms > 0) { #ifdef UDSP_DEBUG AddLog(LOG_LEVEL_DEBUG, "UDisplay: delay %d ms", delay_ms); #endif delay(delay_ms); } } } else { cs_control(true); } } pb_endTransaction(); } } I80Panel::~I80Panel() { deInitDMA(); if (_dmadesc) { heap_caps_free(_dmadesc); _dmadesc = nullptr; } if (_i80_bus) { esp_lcd_del_i80_bus(_i80_bus); } } // DMA Implementation bool I80Panel::initDMA() { if (_DMA_Enabled) return true; gdma_channel_alloc_config_t dma_chan_config = { .direction = GDMA_CHANNEL_DIRECTION_TX }; if (gdma_new_channel(&dma_chan_config, &_dma_chan) == ESP_OK) { gdma_connect(_dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0)); _alloc_dmadesc(16); _DMA_Enabled = true; return true; } return false; } void I80Panel::deInitDMA() { if (_dma_chan) { gdma_disconnect(_dma_chan); gdma_del_channel(_dma_chan); _dma_chan = nullptr; } _DMA_Enabled = false; } bool I80Panel::dmaBusy() { if (!_DMA_Enabled) return false; return (_dev->lcd_user.val & LCD_CAM_LCD_START); } void I80Panel::dmaWait() { if (!_DMA_Enabled) return; while (dmaBusy()) { delay(1); } } // Graphics implementation bool I80Panel::drawPixel(int16_t x, int16_t y, uint16_t color) { if ((x < 0) || (x >= _width) || (y < 0) || (y >= _height)) return true; pb_beginTransaction(); cs_control(false); setAddrWindow_int(x, y, 1, 1); writeColor(color); cs_control(true); pb_endTransaction(); return true; } bool I80Panel::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { if((x >= _width) || (y >= _height)) return true; if((x + w - 1) >= _width) w = _width - x; if((y + h - 1) >= _height) h = _height - y; pb_beginTransaction(); cs_control(false); setAddrWindow_int(x, y, w, h); for (int16_t yp = h; yp > 0; yp--) { for (int16_t xp = w; xp > 0; xp--) { writeColor(color); } } cs_control(true); pb_endTransaction(); return true; } bool I80Panel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) { if((x >= _width) || (y >= _height)) return true; if((x + w - 1) >= _width) w = _width - x; pb_beginTransaction(); cs_control(false); setAddrWindow_int(x, y, w, 1); while (w--) { writeColor(color); } cs_control(true); pb_endTransaction(); return true; } bool I80Panel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) { if ((x >= _width) || (y >= _height)) return true; if ((y + h - 1) >= _height) h = _height - y; pb_beginTransaction(); cs_control(false); setAddrWindow_int(x, y, 1, h); while (h--) { writeColor(color); } cs_control(true); pb_endTransaction(); return true; } bool I80Panel::pushColors(uint16_t *data, uint16_t len, bool first) { // Match old code: just push pixels, no transaction management // Transaction is managed by setAddrWindow() pb_pushPixels(data, len, true, false); // swap_bytes=true to match old driver return true; } bool I80Panel::setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) { // Match old code behavior exactly if (!x0 && !y0 && !x1 && !y1) { // End transaction signal cs_control(true); pb_endTransaction(); } else { // Begin transaction and send address window commands pb_beginTransaction(); cs_control(false); setAddrWindow_int(x0, y0, x1 - x0, y1 - y0); // Leave transaction open for pushColors } return true; } bool I80Panel::displayOnff(int8_t on) { return false; // Let uDisplay handle display commands } bool I80Panel::invertDisplay(bool invert) { return false; // Let uDisplay handle inversion commands } bool I80Panel::setRotation(uint8_t rotation) { _rotation = rotation & 3; // Calculate new dimensions based on rotation FIRST uint16_t new_width, new_height; switch (_rotation) { case 0: case 2: new_width = cfg.width; new_height = cfg.height; break; case 1: case 3: new_width = cfg.height; new_height = cfg.width; break; } // Send MADCTL rotation command to display pb_beginTransaction(); cs_control(false); // Send rotation command (matches old code behavior) pb_writeCommand(cfg.cmd_madctl, 8); if (!cfg.allcmd_mode) { pb_writeData(cfg.rot_cmd[_rotation], 8); } else { pb_writeCommand(cfg.rot_cmd[_rotation], 8); } // For sa_mode == 8, also send startline command if (cfg.sa_mode == 8 && !cfg.allcmd_mode) { pb_writeCommand(cfg.cmd_startline, 8); pb_writeData((_rotation < 2) ? new_height : 0, 8); } cs_control(true); pb_endTransaction(); // Update dimensions _width = new_width; _height = new_height; return true; } bool I80Panel::updateFrame() { return true; // I80 updates are immediate } uint32_t I80Panel::getSimpleResistiveTouch(uint32_t threshold) { uint32_t aval = 0; uint16_t xp, yp; if (pb_busy()) return 0; // Disable GPIO matrix routing to use pins as GPIOs _pb_init_pin(true); // Temporarily reconfigure I80 pins as GPIOs for analog touch gpio_matrix_out(cfg.dc_pin, 0x100, 0, 0); pinMode(cfg.data_pins_low[0], INPUT_PULLUP); pinMode(cfg.dc_pin, INPUT_PULLUP); pinMode(cfg.cs_pin, OUTPUT); pinMode(cfg.data_pins_low[1], OUTPUT); digitalWrite(cfg.cs_pin, HIGH); digitalWrite(cfg.data_pins_low[1], LOW); xp = 4096 - analogRead(cfg.data_pins_low[0]); pinMode(cfg.cs_pin, INPUT_PULLUP); pinMode(cfg.data_pins_low[1], INPUT_PULLUP); pinMode(cfg.data_pins_low[0], OUTPUT); pinMode(cfg.dc_pin, OUTPUT); digitalWrite(cfg.data_pins_low[0], HIGH); digitalWrite(cfg.dc_pin, LOW); yp = 4096 - analogRead(cfg.data_pins_low[1]); aval = (xp << 16) | yp; // Restore pins to I80 function pinMode(cfg.dc_pin, OUTPUT); pinMode(cfg.cs_pin, OUTPUT); pinMode(cfg.data_pins_low[0], OUTPUT); pinMode(cfg.data_pins_low[1], OUTPUT); digitalWrite(cfg.dc_pin, HIGH); digitalWrite(cfg.cs_pin, HIGH); // Re-enable GPIO matrix routing for I80 _pb_init_pin(false); gpio_matrix_out(cfg.dc_pin, LCD_DC_IDX, 0, 0); return aval; } // Color mode helper void I80Panel::writeColor(uint16_t color) { if (cfg.color_mode == 18) { uint8_t r = (color & 0xF800) >> 11; uint8_t g = (color & 0x07E0) >> 5; uint8_t b = color & 0x001F; r = (r * 255) / 31; g = (g * 255) / 63; b = (b * 255) / 31; pb_writeData(r, 8); pb_writeData(g, 8); pb_writeData(b, 8); } else { pb_writeData(color, 16); } } void I80Panel::setAddrWindow_int(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { // Apply rotation-specific offsets (matches old code logic) x += cfg.x_addr_offset[_rotation]; y += cfg.y_addr_offset[_rotation]; uint16_t x2 = x + w - 1; uint16_t y2 = y + h - 1; if (cfg.sa_mode != 8) { // Normal mode: send 32-bit packed coordinates uint32_t xa = ((uint32_t)x << 16) | x2; uint32_t ya = ((uint32_t)y << 16) | y2; pb_writeCommand(cfg.cmd_set_addr_x, 8); pb_writeData(xa, 32); pb_writeCommand(cfg.cmd_set_addr_y, 8); pb_writeData(ya, 32); if (cfg.cmd_write_ram != 0xff) { pb_writeCommand(cfg.cmd_write_ram, 8); } } else { // Special mode 8: swap coordinates if rotation is odd if (_rotation & 1) { uint16_t tmp; tmp = x; x = y; y = tmp; tmp = x2; x2 = y2; y2 = tmp; } pb_writeCommand(cfg.cmd_set_addr_x, 8); if (cfg.allcmd_mode) { pb_writeCommand(x, 8); pb_writeCommand(x2, 8); } else { pb_writeData(x, 8); pb_writeData(x2, 8); } pb_writeCommand(cfg.cmd_set_addr_y, 8); if (cfg.allcmd_mode) { pb_writeCommand(y, 8); pb_writeCommand(y2, 8); } else { pb_writeData(y, 8); pb_writeData(y2, 8); } if (cfg.cmd_write_ram != 0xff) { pb_writeCommand(cfg.cmd_write_ram, 8); } } // Store for push operations _addr_x0 = x; _addr_y0 = y; _addr_x1 = x2; _addr_y1 = y2; } // Low-level I80 implementation void I80Panel::calcClockDiv(uint32_t* div_a, uint32_t* div_b, uint32_t* div_n, uint32_t* clkcnt, uint32_t baseClock, uint32_t targetFreq) { uint32_t diff = INT32_MAX; *div_n = 256; *div_a = 63; *div_b = 62; *clkcnt = 64; uint32_t start_cnt = std::min(64u, (baseClock / (targetFreq * 2) + 1)); uint32_t end_cnt = std::max(2u, baseClock / 256u / targetFreq); if (start_cnt <= 2) { end_cnt = 1; } for (uint32_t cnt = start_cnt; diff && cnt >= end_cnt; --cnt) { float fdiv = (float)baseClock / cnt / targetFreq; uint32_t n = std::max(2u, (uint32_t)fdiv); fdiv -= n; for (uint32_t a = 63; diff && a > 0; --a) { uint32_t b = roundf(fdiv * a); if (a == b && n == 256) break; uint32_t freq = baseClock / ((n * cnt) + (float)(b * cnt) / (float)a); uint32_t d = abs((int)targetFreq - (int)freq); if (diff <= d) continue; diff = d; *clkcnt = cnt; *div_n = n; *div_b = b; *div_a = a; if (b == 0 || a == b) break; } } if (*div_a == *div_b) { *div_b = 0; *div_n += 1; } } void I80Panel::_alloc_dmadesc(size_t len) { if (_dmadesc) heap_caps_free(_dmadesc); _dmadesc_size = len; _dmadesc = (lldesc_t*)heap_caps_malloc(sizeof(lldesc_t) * len, MALLOC_CAP_DMA); } void I80Panel::_setup_dma_desc_links(const uint8_t *data, int32_t len) { // ORIGINAL CODE: This function was empty in the original implementation // DMA descriptor setup is incomplete - transfers larger than pre-allocated // descriptor count will be silently truncated, causing corrupted data // This matches the original uDisplay behavior but should be fixed eventually static constexpr size_t MAX_DMA_LEN = (4096-4); // TODO: Implement proper DMA descriptor chain setup // Currently, if len > MAX_DMA_LEN * _dmadesc_size, data will be truncated // without any error detection or recovery } void I80Panel::pb_beginTransaction(void) { auto dev = _dev; dev->lcd_clock.val = _clock_reg_value; dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE; dev->lcd_user.val = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M; } void I80Panel::pb_endTransaction(void) { auto dev = _dev; while (dev->lcd_user.val & LCD_CAM_LCD_START) {} } void I80Panel::pb_wait(void) { auto dev = _dev; while (dev->lcd_user.val & LCD_CAM_LCD_START) {} } bool I80Panel::pb_busy(void) { auto dev = _dev; return (dev->lcd_user.val & LCD_CAM_LCD_START); } void I80Panel::_pb_init_pin(bool read) { if (read) { if (cfg.bus_width == 8) { for (size_t i = 0; i < 8; ++i) { gpio_ll_output_disable(&GPIO, (gpio_num_t)cfg.data_pins_low[i]); } } else { for (size_t i = 0; i < 8; ++i) { gpio_ll_output_disable(&GPIO, (gpio_num_t)cfg.data_pins_low[i]); } for (size_t i = 0; i < 8; ++i) { gpio_ll_output_disable(&GPIO, (gpio_num_t)cfg.data_pins_high[i]); } } } else { auto idx_base = LCD_DATA_OUT0_IDX; if (cfg.bus_width == 8) { for (size_t i = 0; i < 8; ++i) { gpio_matrix_out(cfg.data_pins_low[i], idx_base + i, 0, 0); } } else { for (size_t i = 0; i < 8; ++i) { gpio_matrix_out(cfg.data_pins_low[i], idx_base + i, 0, 0); } for (size_t i = 0; i < 8; ++i) { gpio_matrix_out(cfg.data_pins_high[i], idx_base + 8 + i, 0, 0); } } } } bool I80Panel::pb_writeCommand(uint32_t data, uint_fast8_t bit_length) { auto dev = _dev; auto reg_lcd_user = &(dev->lcd_user.val); dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE | LCD_CAM_LCD_CD_CMD_SET; if (cfg.bus_width == 8) { auto bytes = bit_length >> 3; do { dev->lcd_cmd_val.lcd_cmd_value = data; data >>= 8; while (*reg_lcd_user & LCD_CAM_LCD_START) {} *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; } while (--bytes); return true; } else { dev->lcd_cmd_val.val = data; while (*reg_lcd_user & LCD_CAM_LCD_START) {} *reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; return true; } } void I80Panel::pb_writeData(uint32_t data, uint_fast8_t bit_length) { auto dev = _dev; auto reg_lcd_user = &(dev->lcd_user.val); dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE; auto bytes = bit_length >> 3; if (cfg.bus_width == 8) { uint8_t shift = (bytes - 1) * 8; for (uint32_t cnt = 0; cnt < bytes; cnt++) { dev->lcd_cmd_val.lcd_cmd_value = (data >> shift) & 0xff; shift -= 8; while (*reg_lcd_user & LCD_CAM_LCD_START) {} *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; } return; } else { if (bytes == 1 || bytes == 4) { uint8_t shift = (bytes - 1) * 8; for (uint32_t cnt = 0; cnt < bytes; cnt++) { dev->lcd_cmd_val.lcd_cmd_value = (data >> shift) & 0xff; shift -= 8; while (*reg_lcd_user & LCD_CAM_LCD_START) {} *reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; } return; } dev->lcd_cmd_val.val = data; while (*reg_lcd_user & LCD_CAM_LCD_START) {} *reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; return; } } void I80Panel::pb_writeBytes(const uint8_t* data, uint32_t length, bool use_dma) { // original code commented out /* uint32_t freq = spi_speed * 1000000; uint32_t slow = (freq< 4000000) ? 2 : (freq < 8000000) ? 1 : 0; auto dev = _dev; do { auto reg_lcd_user = &(dev->lcd_user.val); dev->lcd_misc.lcd_cd_cmd_set = 0; dev->lcd_cmd_val.lcd_cmd_value = data[0] | data[1] << 16; uint32_t cmd_val = data[2] | data[3] << 16; while (*reg_lcd_user & LCD_CAM_LCD_START) {} *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_CMD_2_CYCLE_EN | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START; if (use_dma) { if (slow) { ets_delay_us(slow); } _setup_dma_desc_links(&data[4], length - 4); gdma_start(_dma_chan, (intptr_t)(_dmadesc)); length = 0; } else { size_t len = length; if (len > CACHE_SIZE) { len = (((len - 1) % CACHE_SIZE) + 4) & ~3u; } memcpy(_cache_flip, &data[4], (len-4+3)&~3); _setup_dma_desc_links((const uint8_t*)_cache_flip, len-4); gdma_start(_dma_chan, (intptr_t)(_dmadesc)); length -= len; data += len; _cache_flip = _cache[(_cache_flip == _cache[0])]; } dev->lcd_cmd_val.lcd_cmd_value = cmd_val; dev->lcd_misc.lcd_cd_data_set = 0; *reg_lcd_user = LCD_CAM_LCD_ALWAYS_OUT_EN | LCD_CAM_LCD_DOUT | LCD_CAM_LCD_CMD | LCD_CAM_LCD_CMD_2_CYCLE_EN | LCD_CAM_LCD_UPDATE_REG; while (*reg_lcd_user & LCD_CAM_LCD_START) {} *reg_lcd_user = LCD_CAM_LCD_ALWAYS_OUT_EN | LCD_CAM_LCD_DOUT | LCD_CAM_LCD_CMD | LCD_CAM_LCD_CMD_2_CYCLE_EN | LCD_CAM_LCD_START; } while (length); */ } // FIXED: Byte swap logic was backwards in 8-bit mode void I80Panel::pb_pushPixels(uint16_t* data, uint32_t length, bool swap_bytes, bool use_dma) { auto dev = _dev; auto reg_lcd_user = &(dev->lcd_user.val); dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE; if (cfg.bus_width == 8) { if (swap_bytes) { for (uint32_t cnt = 0; cnt < length; cnt++) { dev->lcd_cmd_val.lcd_cmd_value = *data >> 8; // High byte first while (*reg_lcd_user & LCD_CAM_LCD_START) {} *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; dev->lcd_cmd_val.lcd_cmd_value = *data; // Low byte second while (*reg_lcd_user & LCD_CAM_LCD_START) {} *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; data++; } } else { for (uint32_t cnt = 0; cnt < length; cnt++) { dev->lcd_cmd_val.lcd_cmd_value = *data; // Low byte first while (*reg_lcd_user & LCD_CAM_LCD_START) {} *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; dev->lcd_cmd_val.lcd_cmd_value = *data >> 8; // High byte second while (*reg_lcd_user & LCD_CAM_LCD_START) {} *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; data++; } } } else { if (swap_bytes) { uint16_t iob; for (uint32_t cnt = 0; cnt < length; cnt++) { iob = *data++; iob = (iob << 8) | (iob >> 8); dev->lcd_cmd_val.lcd_cmd_value = iob; while (*reg_lcd_user & LCD_CAM_LCD_START) {} *reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; } } else { for (uint32_t cnt = 0; cnt < length; cnt++) { dev->lcd_cmd_val.lcd_cmd_value = *data++; while (*reg_lcd_user & LCD_CAM_LCD_START) {} *reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; } } } } void I80Panel::cs_control(bool level) { auto pin = cfg.cs_pin; if (pin < 0) return; if (level) { gpio_hi(pin); } else { gpio_lo(pin); } } #endif // SOC_LCD_I80_SUPPORTED && SOC_LCDCAM_I80_NUM_BUSES