From 23071e932adce4d5f7a708b7c252e5758faaf36d Mon Sep 17 00:00:00 2001
From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
Date: Wed, 24 Jan 2024 07:40:16 +1100
Subject: [PATCH] Add support for Pico-ResTouch-LCD-3.5 to ili9xxx driver
 (#6129)

* Working version of Waveshare 3.5 Res Touch driver.

* Default color order BGR
---
 esphome/components/ili9xxx/display.py         |  1 +
 .../components/ili9xxx/ili9xxx_display.cpp    | 83 ++++++++-----------
 esphome/components/ili9xxx/ili9xxx_display.h  | 52 ++++++++++--
 esphome/components/ili9xxx/ili9xxx_init.h     | 28 +++----
 4 files changed, 94 insertions(+), 70 deletions(-)

diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py
index b3fe8b2b41..0bd810ea16 100644
--- a/esphome/components/ili9xxx/display.py
+++ b/esphome/components/ili9xxx/display.py
@@ -66,6 +66,7 @@ MODELS = {
     "ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay),
     "S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay),
     "S3BOX_LITE": ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay),
+    "WAVESHARE_RES_3_5": ili9xxx_ns.class_("WAVESHARERES35", ILI9XXXDisplay),
 }
 
 COLOR_ORDERS = {
diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp
index ab577b3875..e3f2c94880 100644
--- a/esphome/components/ili9xxx/ili9xxx_display.cpp
+++ b/esphome/components/ili9xxx/ili9xxx_display.cpp
@@ -7,7 +7,6 @@
 namespace esphome {
 namespace ili9xxx {
 
-static const char *const TAG = "ili9xxx";
 static const uint16_t SPI_SETUP_US = 100;         // estimated fixed overhead in microseconds for an SPI write
 static const uint16_t SPI_MAX_BLOCK_SIZE = 4092;  // Max size of continuous SPI transfer
 
@@ -17,13 +16,7 @@ static inline void put16_be(uint8_t *buf, uint16_t value) {
   buf[1] = value;
 }
 
-void ILI9XXXDisplay::setup() {
-  ESP_LOGD(TAG, "Setting up ILI9xxx");
-
-  this->setup_pins_();
-  this->init_lcd_();
-
-  this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF);
+void ILI9XXXDisplay::set_madctl() {
   // custom x/y transform and color order
   uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB;
   if (this->swap_xy_)
@@ -32,8 +25,19 @@ void ILI9XXXDisplay::setup() {
     mad |= MADCTL_MX;
   if (this->mirror_y_)
     mad |= MADCTL_MY;
-  this->send_command(ILI9XXX_MADCTL, &mad, 1);
+  this->command(ILI9XXX_MADCTL);
+  this->data(mad);
+  esph_log_d(TAG, "Wrote MADCTL 0x%02X", mad);
+}
 
+void ILI9XXXDisplay::setup() {
+  ESP_LOGD(TAG, "Setting up ILI9xxx");
+
+  this->setup_pins_();
+  this->init_lcd_();
+
+  this->set_madctl();
+  this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF);
   this->x_low_ = this->width_;
   this->y_low_ = this->height_;
   this->x_high_ = 0;
@@ -89,6 +93,7 @@ void ILI9XXXDisplay::dump_config() {
   LOG_PIN("  CS Pin: ", this->cs_);
   LOG_PIN("  DC Pin: ", this->dc_pin_);
   LOG_PIN("  Busy Pin: ", this->busy_pin_);
+  ESP_LOGCONFIG(TAG, "  Color order: %s", this->color_order_ == display::COLOR_ORDER_BGR ? "BGR" : "RGB");
   ESP_LOGCONFIG(TAG, "  Swap_xy: %s", YESNO(this->swap_xy_));
   ESP_LOGCONFIG(TAG, "  Mirror_x: %s", YESNO(this->mirror_x_));
   ESP_LOGCONFIG(TAG, "  Mirror_y: %s", YESNO(this->mirror_y_));
@@ -196,7 +201,6 @@ void ILI9XXXDisplay::display_() {
   uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE];
   // check if something was displayed
   if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) {
-    ESP_LOGV(TAG, "Nothing to display");
     return;
   }
 
@@ -211,14 +215,13 @@ void ILI9XXXDisplay::display_() {
   size_t mw_time = (w * h * 16) / mhz + w * h * 2 / ILI9XXX_TRANSFER_BUFFER_SIZE * SPI_SETUP_US;
   ESP_LOGV(TAG,
            "Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, "
-           "height:%d, mode=%d, 18bit=%d, sw_time=%dus, mw_time=%dus)",
+           "height:%zu, mode=%d, 18bit=%d, sw_time=%zuus, mw_time=%zuus)",
            this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, this->buffer_color_mode_,
            this->is_18bitdisplay_, sw_time, mw_time);
   auto now = millis();
-  this->enable();
   if (this->buffer_color_mode_ == BITS_16 && !this->is_18bitdisplay_ && sw_time < mw_time) {
     // 16 bit mode maps directly to display format
-    ESP_LOGV(TAG, "Doing single write of %d bytes", this->width_ * h * 2);
+    ESP_LOGV(TAG, "Doing single write of %zu bytes", this->width_ * h * 2);
     set_addr_window_(0, this->y_low_, this->width_ - 1, this->y_high_);
     this->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2);
   } else {
@@ -267,7 +270,7 @@ void ILI9XXXDisplay::display_() {
       this->write_array(transfer_buffer, idx);
     }
   }
-  this->disable();
+  this->end_data_();
   ESP_LOGV(TAG, "Data write took %dms", (unsigned) (millis() - now));
   // invalidate watermarks
   this->x_low_ = this->width_;
@@ -290,7 +293,6 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons
     return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
                                             x_pad);
   }
-  this->enable();
   this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1);
   // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
   if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
@@ -302,7 +304,7 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons
       this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2);
     }
   }
-  this->disable();
+  this->end_data_();
 }
 
 // should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color
@@ -328,20 +330,6 @@ void ILI9XXXDisplay::send_command(uint8_t command_byte, const uint8_t *data_byte
   this->end_data_();
 }
 
-uint8_t ILI9XXXDisplay::read_command(uint8_t command_byte, uint8_t index) {
-  uint8_t data = 0x10 + index;
-  this->send_command(0xD9, &data, 1);  // Set Index Register
-  uint8_t result;
-  this->start_command_();
-  this->write_byte(command_byte);
-  this->start_data_();
-  do {
-    result = this->read_byte();
-  } while (index--);
-  this->end_data_();
-  return result;
-}
-
 void ILI9XXXDisplay::start_command_() {
   this->dc_pin_->digital_write(false);
   this->enable();
@@ -357,9 +345,9 @@ void ILI9XXXDisplay::end_data_() { this->disable(); }
 void ILI9XXXDisplay::reset_() {
   if (this->reset_pin_ != nullptr) {
     this->reset_pin_->digital_write(false);
-    delay(10);
+    delay(20);
     this->reset_pin_->digital_write(true);
-    delay(10);
+    delay(20);
   }
 }
 
@@ -369,7 +357,7 @@ void ILI9XXXDisplay::init_lcd_() {
   while ((cmd = *addr++) > 0) {
     x = *addr++;
     num_args = x & 0x7F;
-    send_command(cmd, addr, num_args);
+    this->send_command(cmd, addr, num_args);
     addr += num_args;
     if (x & 0x80)
       delay(150);  // NOLINT
@@ -377,24 +365,19 @@ void ILI9XXXDisplay::init_lcd_() {
 }
 
 // Tell the display controller where we want to draw pixels.
-// when called, the SPI should have already been enabled, only the D/C pin will be toggled here.
 void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
-  uint8_t buf[4];
-  this->dc_pin_->digital_write(false);
-  this->write_byte(ILI9XXX_CASET);  // Column address set
-  put16_be(buf, x1 + this->offset_x_);
-  put16_be(buf + 2, x2 + this->offset_x_);
-  this->dc_pin_->digital_write(true);
-  this->write_array(buf, sizeof buf);
-  this->dc_pin_->digital_write(false);
-  this->write_byte(ILI9XXX_PASET);  // Row address set
-  put16_be(buf, y1 + this->offset_y_);
-  put16_be(buf + 2, y2 + this->offset_y_);
-  this->dc_pin_->digital_write(true);
-  this->write_array(buf, sizeof buf);
-  this->dc_pin_->digital_write(false);
-  this->write_byte(ILI9XXX_RAMWR);  // Write to RAM
-  this->dc_pin_->digital_write(true);
+  this->command(ILI9XXX_CASET);
+  this->data(x1 >> 8);
+  this->data(x1 & 0xFF);
+  this->data(x2 >> 8);
+  this->data(x2 & 0xFF);
+  this->command(ILI9XXX_PASET);  // Page address set
+  this->data(y1 >> 8);
+  this->data(y1 & 0xFF);
+  this->data(y2 >> 8);
+  this->data(y2 & 0xFF);
+  this->command(ILI9XXX_RAMWR);  // Write to RAM
+  this->start_data_();
 }
 
 void ILI9XXXDisplay::invert_colors(bool invert) {
diff --git a/esphome/components/ili9xxx/ili9xxx_display.h b/esphome/components/ili9xxx/ili9xxx_display.h
index 590be3e364..7b92bd2336 100644
--- a/esphome/components/ili9xxx/ili9xxx_display.h
+++ b/esphome/components/ili9xxx/ili9xxx_display.h
@@ -8,6 +8,7 @@
 namespace esphome {
 namespace ili9xxx {
 
+static const char *const TAG = "ili9xxx";
 const size_t ILI9XXX_TRANSFER_BUFFER_SIZE = 126;  // ensure this is divisible by 6
 
 enum ILI9XXXColorMode {
@@ -32,6 +33,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
     while ((cmd = *addr++) != 0) {
       num_args = *addr++ & 0x7F;
       bits = *addr;
+      esph_log_d(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, bits);
       switch (cmd) {
         case ILI9XXX_MADCTL: {
           this->swap_xy_ = (bits & MADCTL_MV) != 0;
@@ -68,10 +70,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
     this->offset_y_ = offset_y;
   }
   void invert_colors(bool invert);
-  void command(uint8_t value);
-  void data(uint8_t value);
+  virtual void command(uint8_t value);
+  virtual void data(uint8_t value);
   void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes);
-  uint8_t read_command(uint8_t command_byte, uint8_t index);
   void set_color_order(display::ColorOrder color_order) { this->color_order_ = color_order; }
   void set_swap_xy(bool swap_xy) { this->swap_xy_ = swap_xy; }
   void set_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; }
@@ -92,6 +93,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
   void draw_absolute_pixel_internal(int x, int y, Color color) override;
   void setup_pins_();
 
+  virtual void set_madctl();
   void display_();
   void init_lcd_();
   void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2);
@@ -127,7 +129,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
   bool need_update_ = false;
   bool is_18bitdisplay_ = false;
   bool pre_invertcolors_ = false;
-  display::ColorOrder color_order_{};
+  display::ColorOrder color_order_{display::COLOR_ORDER_BGR};
   bool swap_xy_{};
   bool mirror_x_{};
   bool mirror_y_{};
@@ -181,10 +183,48 @@ class ILI9XXXILI9486 : public ILI9XXXDisplay {
   ILI9XXXILI9486() : ILI9XXXDisplay(INITCMD_ILI9486, 480, 320, false) {}
 };
 
-//-----------   ILI9XXX_35_TFT rotated display --------------
 class ILI9XXXILI9488 : public ILI9XXXDisplay {
  public:
-  ILI9XXXILI9488() : ILI9XXXDisplay(INITCMD_ILI9488, 480, 320, true) {}
+  ILI9XXXILI9488(const uint8_t *seq = INITCMD_ILI9488) : ILI9XXXDisplay(seq, 480, 320, true) {}
+
+ protected:
+  void set_madctl() override {
+    uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB;
+    uint8_t dfun = 0x22;
+    this->width_ = 320;
+    this->height_ = 480;
+    if (!(this->swap_xy_ || this->mirror_x_ || this->mirror_y_)) {
+      // no transforms
+    } else if (this->mirror_y_ && this->mirror_x_) {
+      // rotate 180
+      dfun = 0x42;
+    } else if (this->swap_xy_) {
+      this->width_ = 480;
+      this->height_ = 320;
+      mad |= 0x20;
+      if (this->mirror_x_) {
+        dfun = 0x02;
+      } else {
+        dfun = 0x62;
+      }
+    }
+    this->command(ILI9XXX_DFUNCTR);
+    this->data(0);
+    this->data(dfun);
+    this->command(ILI9XXX_MADCTL);
+    this->data(mad);
+  }
+};
+//-----------   Waveshare 3.5 Res Touch - ILI9488 interfaced via 16 bit shift register to parallel */
+class WAVESHARERES35 : public ILI9XXXILI9488 {
+ public:
+  WAVESHARERES35() : ILI9XXXILI9488(INITCMD_WAVESHARE_RES_3_5) {}
+  void data(uint8_t value) override {
+    this->start_data_();
+    this->write_byte(0);
+    this->write_byte(value);
+    this->end_data_();
+  }
 };
 
 //-----------   ILI9XXX_35_TFT origin colors rotated display --------------
diff --git a/esphome/components/ili9xxx/ili9xxx_init.h b/esphome/components/ili9xxx/ili9xxx_init.h
index a74824052f..fe3f168c32 100644
--- a/esphome/components/ili9xxx/ili9xxx_init.h
+++ b/esphome/components/ili9xxx/ili9xxx_init.h
@@ -141,7 +141,8 @@ static const uint8_t PROGMEM INITCMD_ILI9486[] = {
   0x00                                   // End of list
 };
 
-static const uint8_t PROGMEM INITCMD_ILI9488[] = {
+
+static const uint8_t INITCMD_ILI9488[] = {
   ILI9XXX_GMCTRP1,15, 0x0f, 0x24, 0x1c, 0x0a, 0x0f, 0x08, 0x43, 0x88, 0x32, 0x0f, 0x10, 0x06, 0x0f, 0x07, 0x00,
   ILI9XXX_GMCTRN1,15, 0x0F, 0x38, 0x30, 0x09, 0x0f, 0x0f, 0x4e, 0x77, 0x3c, 0x07, 0x10, 0x05, 0x23, 0x1b, 0x00,
 
@@ -153,28 +154,27 @@ static const uint8_t PROGMEM INITCMD_ILI9488[] = {
   ILI9XXX_FRMCTR1, 1, 0xA0,  // Frame rate = 60Hz
   ILI9XXX_INVCTR,  1, 0x02,  // Display Inversion Control = 2dot
 
-  ILI9XXX_DFUNCTR, 2, 0x02, 0x02, // Nomal scan
-
   0xE9, 1, 0x00,   // Set Image Functio. Disable 24 bit data
 
   ILI9XXX_ADJCTL3, 4, 0xA9, 0x51, 0x2C, 0x82,  // Adjust Control 3
-
-  ILI9XXX_MADCTL,  1, 0x28,
-  //ILI9XXX_PIXFMT,  1, 0x55,  // Interface Pixel Format = 16bit
   ILI9XXX_PIXFMT, 1, 0x66,   //ILI9488 only supports 18-bit pixel format in 4/3 wire SPI mode
-
-
-
-  // 5 frames
-  //ILI9XXX_ETMOD,   1, 0xC6,  //
-
-
   ILI9XXX_SLPOUT,  0x80,    // Exit sleep mode
-  //ILI9XXX_INVON  , 0,
   ILI9XXX_DISPON,  0x80,    // Set display on
   0x00 // end
 };
 
+static const uint8_t INITCMD_WAVESHARE_RES_3_5[] = {
+    ILI9XXX_PWCTR3, 1, 0x33,
+    ILI9XXX_VMCTR1, 3, 0x00, 0x1e, 0x80,
+    ILI9XXX_FRMCTR1, 1, 0xA0,
+    ILI9XXX_GMCTRP1, 15, 0x0, 0x13, 0x18, 0x04, 0x0F, 0x06, 0x3a, 0x56, 0x4d, 0x03, 0x0a, 0x06, 0x30, 0x3e, 0x0f,
+    ILI9XXX_GMCTRN1, 15, 0x0, 0x13, 0x18, 0x01, 0x11, 0x06, 0x38, 0x34, 0x4d, 0x06, 0x0d, 0x0b, 0x31, 0x37, 0x0f,
+    ILI9XXX_PIXFMT, 1, 0x55,
+    ILI9XXX_SLPOUT, 0x80,   // slpout, delay
+    ILI9XXX_DISPON, 0,
+    0x00                                   // End of list
+};
+
 static const uint8_t PROGMEM INITCMD_ILI9488_A[] = {
   ILI9XXX_GMCTRP1,15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F,
   ILI9XXX_GMCTRN1,15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F,