text: /**
   * Marlin 3D Printer Firmware
   * Copyright (c) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
   *
   * Based on Sprinter and grbl.
   * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   *
   */
  
  /**
   * Based on u8g_dev_ssd1306_128x64.c
   *
   * Universal 8bit Graphics Library
   *
   * Copyright (c) 2015, olikraus@gmail.com
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without modification,
   * are permitted provided that the following conditions are met:
   *
   *  * Redistributions of source code must retain the above copyright notice, this list
   *    of conditions and the following disclaimer.
   *
   *  * Redistributions in binary form must reproduce the above copyright notice, this
   *    list of conditions and the following disclaimer in the documentation and/or other
   *    materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
   * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  /**
   * These routines are meant for two wire I2C interfaces.
   *
   * Three and four wire I2C interfaces have an A0 line.  That line is
   * used to switch between command and data modes.
   *
   * The two wire LCDs use an instruction byte to signal if data or
   * command info is to follow.  The command stream needs the instruction
   * byte between eack command byte.  The data stream needs one at the
   * beginning.
   */
  
  #include "../../inc/MarlinConfigPre.h"
  
  #if HAS_GRAPHICAL_LCD
  
  #include <U8glib.h>
  #include "HAL_LCD_com_defines.h"
  
  #define WIDTH 128
  #define HEIGHT 64
  #define PAGE_HEIGHT 8
  
  uint8_t u8g_WriteEscSeqP_2_wire(u8g_t *u8g, u8g_dev_t *dev, const uint8_t *esc_seq);
  
  // The sh1106 is compatible to the ssd1306, but is 132x64. 128x64 display area is centered within
  // the 132x64.
  
  static const uint8_t u8g_dev_sh1106_128x64_data_start_2_wire[] PROGMEM = {
    0x010,          // set upper 4 bit of the col adr to 0
    0x002,          // set lower 4 bit of the col adr to 2 (centered display with ssd1306)
    U8G_ESC_END     // end of sequence
  };
  
  #define SH1106_PAGE_ADR(N)       (0x20), (N)
  #define SH1106_COLUMN_RANGE(N)   (0x21), (((N) >> 8) & 0xFF), ((N) & 0xFF)
  #define SH1106_PAGE_RANGE(N,O)   (0x22), (N), (O)
  #define SH1106_SCROLL(N)         ((N) ? 0x2F : 0x2E)
  #define SH1106_START_LINE(N)     (0x40 | (N))
  #define SH1106_CONTRAST(N)       (0x81), (N)
  #define SH1106_CHARGE_PUMP(N)    (0x8D), ((N) ? 0x14 : 0x10)
  #define SH1106_ADC_REVERSE(N)    ((N) ? 0xA1 : 0xA0)
  #define SH1106_ALL_PIX(N)        ((N) ? 0xA5 : 0xA4)
  #define SH1106_INVERTED(N)       ((N) ? 0xA7 : 0xA6)
  #define SH1106_MUX_RATIO(N)      (0xA8), (N)
  #define SH1106_ON(N)             ((N) ? 0xAF : 0xAE)
  #define SH1106_OUT_MODE(N)       ((N) ? 0xC8 : 0xC0)
  #define SH1106_DISP_OFFS(N)      (0xD3), (N)
  #define SH1106_OSC_FREQ(R,F)     (0xD5), ((F) << 4 | (R))
  #define SH1106_CHARGE_PER(P,D)   (0xD9), ((D) << 4 | (P))
  #define SH1106_COM_CONFIG(N)     (0xDA), ((N) ? 0x12 : 0x02)
  #define SH1106_VCOM_DESEL(N)     (0xDB), (N)
  #define SH1106_NOOP()            (0xE3)
  
  static const uint8_t u8g_dev_sh1106_128x64_init_seq_2_wire[] PROGMEM = {
    U8G_ESC_ADR(0),               // Initiate command mode
    SH1106_ON(0),                 // Display off, sleep mode
    SH1106_MUX_RATIO(0x3F),       // Mux ratio
    SH1106_DISP_OFFS(0),          // Display offset
    SH1106_START_LINE(0),         // Start line
    SH1106_ADC_REVERSE(1),        // Segment remap A0/A1
    SH1106_OUT_MODE(1),           // C0: scan dir normal, C8: reverse
    SH1106_COM_CONFIG(1),         // Com pin HW config, sequential com pin config (bit 4), disable left/right remap (bit 5)
    SH1106_CONTRAST(0xCF),        // [2] set contrast control
    SH1106_PAGE_ADR(0x02),        // 2012-05-27: page addressing mode
    SH1106_COLUMN_RANGE(0x281),   // Set column range from 0 through 131
    SH1106_PAGE_RANGE(0, 7),      // Set page range from 0 through 7
    SH1106_CHARGE_PER(0x1, 0xF),  // [2] pre-charge period 0x22/F1
    SH1106_VCOM_DESEL(0x40),      // Vcomh deselect level
    SH1106_ALL_PIX(0),            // Output ram to display
    SH1106_INVERTED(0),           // Normal display mode
    SH1106_OSC_FREQ(0, 8),        // Clock divide ratio (0:1) and oscillator frequency (8)
    SH1106_CHARGE_PUMP(1),        // [2] charge pump setting (P62): 0x14 enable, 0x10 disable
    SH1106_SCROLL(0),             // 2012-05-27: Deactivate scroll
    SH1106_ON(1),                 // Display on
    U8G_ESC_END                   // End of sequence
  };
  
  uint8_t u8g_dev_sh1106_128x64_2x_2_wire_fn(u8g_t *u8g, u8g_dev_t *dev, uint8_t msg, void *arg) {
    switch (msg) {
      case U8G_DEV_MSG_INIT:
        u8g_InitCom(u8g, dev, U8G_SPI_CLK_CYCLE_300NS);
        u8g_WriteEscSeqP_2_wire(u8g, dev, u8g_dev_sh1106_128x64_init_seq_2_wire);
        break;
      case U8G_DEV_MSG_STOP:
        break;
      case U8G_DEV_MSG_PAGE_NEXT: {
          u8g_pb_t *pb = (u8g_pb_t *)(dev->dev_mem);
          u8g_SetAddress(u8g, dev, 0);           // instruction mode
          u8g_WriteEscSeqP_2_wire(u8g, dev, u8g_dev_sh1106_128x64_data_start_2_wire);
          u8g_WriteByte(u8g, dev, 0x0B0 | (pb->p.page*2)); // select current page
          u8g_SetAddress(u8g, dev, 1);           // data mode
          u8g_WriteSequence(u8g, dev, pb->width, (uint8_t *) pb->buf);
          u8g_SetChipSelect(u8g, dev, 0);
          u8g_SetAddress(u8g, dev, 0);           // instruction mode
          u8g_WriteEscSeqP_2_wire(u8g, dev, u8g_dev_sh1106_128x64_data_start_2_wire);
          u8g_WriteByte(u8g, dev, 0x0B0 | (pb->p.page*2+1)); // select current page
          u8g_SetAddress(u8g, dev, 1);           // data mode
          u8g_WriteSequence(u8g, dev, pb->width, (uint8_t *)(pb->buf)+pb->width);
          u8g_SetChipSelect(u8g, dev, 0);
        }
        break;
      case U8G_DEV_MSG_SLEEP_ON:
        return 1;
      case U8G_DEV_MSG_SLEEP_OFF:
        return 1;
    }
    return u8g_dev_pb16v1_base_fn(u8g, dev, msg, arg);
  }
  
  uint8_t u8g_dev_sh1106_128x64_2x_i2c_2_wire_buf[WIDTH*2] U8G_NOCOMMON ;
  u8g_pb_t u8g_dev_sh1106_128x64_2x_i2c_2_wire_pb = { {16, HEIGHT, 0, 0, 0},  WIDTH, u8g_dev_sh1106_128x64_2x_i2c_2_wire_buf};
  u8g_dev_t u8g_dev_sh1106_128x64_2x_i2c_2_wire = { u8g_dev_sh1106_128x64_2x_2_wire_fn, &u8g_dev_sh1106_128x64_2x_i2c_2_wire_pb, U8G_COM_SSD_I2C_HAL };
  
  /////////////////////////////////////////////////////////////////////////////////////////////
  
  static const uint8_t u8g_dev_ssd1306_128x64_data_start_2_wire[] PROGMEM = {
    0x010,          // set upper 4 bit of the col adr to 0
    0x000,          // set lower 4 bit of the col adr to 0
    U8G_ESC_END     // end of sequence
  };
  
  static const uint8_t u8g_dev_ssd1306_128x64_init_seq_2_wire[] PROGMEM = {
    U8G_ESC_ADR(0), // initiate command mode
    0x0AE,          // display off, sleep mode
    0x0A8, 0x03F,   // mux ratio
    0x0D3, 0x00,    // display offset
    0x040,          // start line
    0x0A1,          // segment remap a0/a1
    0x0C8,          // c0: scan dir normal, c8: reverse
    0x0DA, 0x012,   // com pin HW config, sequential com pin config (bit 4), disable left/right remap (bit 5)
    0x081, 0x0CF,   // [2] set contrast control
    0x020, 0x002,   // 2012-05-27: page addressing mode
    0x21, 0, 0x7F,  // set column range from 0 through 127
    0x22, 0, 7,     // set page range from 0 through 7
    0x0D9, 0x0F1,   // [2] pre-charge period 0x022/f1
    0x0DB, 0x040,   // vcomh deselect level
    0x0A4,          // output ram to display
    0x0A6,          // none inverted normal display mode
    0x0D5, 0x080,   // clock divide ratio (0x00=1) and oscillator frequency (0x8)
    0x08D, 0x014,   // [2] charge pump setting (p62): 0x014 enable, 0x010 disable
    0x02E,          // 2012-05-27: Deactivate scroll
    0x0AF,          // display on
    U8G_ESC_END     // end of sequence
  };
  
  uint8_t u8g_dev_ssd1306_128x64_2x_2_wire_fn(u8g_t *u8g, u8g_dev_t *dev, uint8_t msg, void *arg) {
    switch (msg) {
      case U8G_DEV_MSG_INIT:
        u8g_InitCom(u8g, dev, U8G_SPI_CLK_CYCLE_300NS);
        u8g_WriteEscSeqP_2_wire(u8g, dev, u8g_dev_ssd1306_128x64_init_seq_2_wire);
        break;
      case U8G_DEV_MSG_STOP:
        break;
      case U8G_DEV_MSG_PAGE_NEXT: {
          u8g_pb_t *pb = (u8g_pb_t *)(dev->dev_mem);
          u8g_SetAddress(u8g, dev, 0);           // instruction mode
          u8g_WriteEscSeqP_2_wire(u8g, dev, u8g_dev_ssd1306_128x64_data_start_2_wire);
          u8g_WriteByte(u8g, dev, 0x0B0 | (pb->p.page*2)); // select current page
          u8g_SetAddress(u8g, dev, 1);           // data mode
          u8g_WriteSequence(u8g, dev, pb->width, (uint8_t *) pb->buf);
          u8g_SetChipSelect(u8g, dev, 0);
          u8g_SetAddress(u8g, dev, 0);           // instruction mode
          u8g_WriteEscSeqP_2_wire(u8g, dev, u8g_dev_ssd1306_128x64_data_start_2_wire);
          u8g_WriteByte(u8g, dev, 0x0B0 | (pb->p.page*2+1)); // select current page
          u8g_SetAddress(u8g, dev, 1);           // data mode
          u8g_WriteSequence(u8g, dev, pb->width, (uint8_t *)(pb->buf)+pb->width);
          u8g_SetChipSelect(u8g, dev, 0);
        }
        break;
      case U8G_DEV_MSG_SLEEP_ON:
        return 1;
      case U8G_DEV_MSG_SLEEP_OFF:
        return 1;
    }
    return u8g_dev_pb16v1_base_fn(u8g, dev, msg, arg);
  }
  
  
  uint8_t u8g_dev_ssd1306_128x64_2x_i2c_2_wire_buf[WIDTH*2] U8G_NOCOMMON ;
  u8g_pb_t u8g_dev_ssd1306_128x64_2x_i2c_2_wire_pb = { {16, HEIGHT, 0, 0, 0},  WIDTH, u8g_dev_ssd1306_128x64_2x_i2c_2_wire_buf};
  u8g_dev_t u8g_dev_ssd1306_128x64_2x_i2c_2_wire = { u8g_dev_ssd1306_128x64_2x_2_wire_fn, &u8g_dev_ssd1306_128x64_2x_i2c_2_wire_pb, U8G_COM_SSD_I2C_HAL };
  
  
  /////////////////////////////////////////////////////////////////////////////////////////////
  
  // This routine adds the instruction byte in between the command bytes.  This makes the init
  // sequences a lot easier to read.
  
  #define I2C_CMD_MODE    0x080
  
  uint8_t u8g_WriteEscSeqP_2_wire(u8g_t *u8g, u8g_dev_t *dev, const uint8_t *esc_seq) {
    uint8_t is_escape = 0;
    uint8_t value;
    for (;;) {
      value = u8g_pgm_read(esc_seq);
      if (is_escape == 0) {
        if (value != 255) {
          if (u8g_WriteByte(u8g, dev, value) == 0 )
            return 0;
          if (u8g_WriteByte(u8g, dev, I2C_CMD_MODE) == 0 )
            return 0;
        }
        else {
          is_escape = 1;
        }
      }
      else {
        if (value == 255) {
          if (u8g_WriteByte(u8g, dev, value) == 0 )
            return 0;
          if (u8g_WriteByte(u8g, dev, I2C_CMD_MODE) == 0 )
            return 0;
        }
        else if (value == 254) {
          break;
        }
        else if (value >= 0x0F0) {
          /* not yet used, do nothing */
        }
        else if (value >= 0xE0 ) {
          u8g_SetAddress(u8g, dev, value & 0x0F);
        }
        else if (value >= 0xD0) {
          u8g_SetChipSelect(u8g, dev, value & 0x0F);
        }
        else if (value >= 0xC0) {
          u8g_SetResetLow(u8g, dev);
          value &= 0x0F;
          value <<= 4;
          value+=2;
          u8g_Delay(value);
          u8g_SetResetHigh(u8g, dev);
          u8g_Delay(value);
        }
        else if (value >= 0xBE) {                       /* not yet implemented */
          /* u8g_SetVCC(u8g, dev, value & 0x01); */
        }
        else if (value <= 127) {
          u8g_Delay(value);
        }
        is_escape = 0;
      }
      esc_seq++;
    }
    return 1;
  }
  
  #endif // HAS_GRAPHICAL_LCD
  