/****************
 * commands.cpp *
 ****************/

/****************************************************************************
 *   Written By Mark Pelletier  2017 - Aleph Objects, Inc.                  *
 *   Written By Marcio Teixeira 2018 - Aleph Objects, Inc.                  *
 *                                                                          *
 *   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.                           *
 *                                                                          *
 *   To view a copy of the GNU General Public License, go to the following  *
 *   location: <https://www.gnu.org/licenses/>.                             *
 ****************************************************************************/

#include "ftdi_basic.h"

#ifdef FTDI_BASIC

#define MULTIPLE_OF_4(val) ((((val)+3)>>2)<<2)

using namespace FTDI;
using namespace FTDI::SPI;

void CLCD::enable() {
  mem_write_8(REG::PCLK, Pclk);
}

void CLCD::disable() {
  mem_write_8(REG::PCLK, 0x00);
}

void CLCD::set_brightness(uint8_t brightness) {
  mem_write_8(REG::PWM_DUTY, min(128,brightness));
}

uint8_t CLCD::get_brightness() {
  return mem_read_8(REG::PWM_DUTY);
}

void CLCD::turn_on_backlight() {
  mem_write_8(REG::PWM_DUTY, 128);
}

void CLCD::FontMetrics::load(const uint8_t font) {
  static_assert(sizeof(FontMetrics) == 148, "Sizeof font metrics is incorrect");
  uint32_t rom_fontroot = mem_read_32(MAP::ROM_FONT_ADDR);
  mem_read_bulk(rom_fontroot + 148 * (font - 16), (uint8_t*) this, 148);
}

uint16_t CLCD::FontMetrics::get_text_width(const char *str, size_t n) const {
  uint16_t width = 0;
  const uint8_t *p = (const uint8_t *) str;
  for (;;) {
    const uint8_t val = *p++; n--;
    if (!val || n == 0) break;
    width += val < 128 ? char_widths[val] : 0;
  }
  return width;
}

uint16_t CLCD::FontMetrics::get_text_width(FSTR_P str, size_t n) const {
  uint16_t width = 0;
  const uint8_t *p = (const uint8_t *) str;
  for (;;) {
    const uint8_t val = pgm_read_byte(p++); n--;
    if (!val || n == 0) break;
    width += val < 128 ? char_widths[val] : 0;
  }
  return width;
}

/************************** HOST COMMAND FUNCTION *********************************/

void CLCD::host_cmd(unsigned char host_command, unsigned char byte2) {  // Sends 24-Bit Host Command to LCD
  if (host_command != FTDI::ACTIVE) {
    host_command |= 0x40;
  }
  spi_ftdi_select();
  spi_send(host_command);
  spi_send(byte2);
  spi_send(0x00);
  spi_ftdi_deselect();
}

/************************** MEMORY READ FUNCTIONS *********************************/

void CLCD::spi_read_addr(uint32_t reg_address) {
  spi_send((reg_address >> 16) & 0x3F);  // Address [21:16]
  spi_send((reg_address >> 8 ) & 0xFF);  // Address [15:8]
  spi_send((reg_address >> 0)  & 0xFF);  // Address [7:0]
  spi_send(0x00);                        // Dummy Byte
}

// Write 4-Byte Address, Read Multiple Bytes
void CLCD::mem_read_bulk(uint32_t reg_address, uint8_t *data, uint16_t len) {
  spi_ftdi_select();
  spi_read_addr(reg_address);
  spi_read_bulk (data, len);
  spi_ftdi_deselect();
}

// Write 4-Byte Address, Read 1-Byte Data
uint8_t CLCD::mem_read_8(uint32_t reg_address) {
  spi_ftdi_select();
  spi_read_addr(reg_address);
  uint8_t r_data = spi_read_8();
  spi_ftdi_deselect();
  return r_data;
}

// Write 4-Byte Address, Read 2-Bytes Data
uint16_t CLCD::mem_read_16(uint32_t reg_address) {
  using namespace SPI::least_significant_byte_first;
  spi_ftdi_select();
  spi_read_addr(reg_address);
  uint16_t r_data = spi_read_16();
  spi_ftdi_deselect();
  return r_data;
}

// Write 4-Byte Address, Read 4-Bytes Data
uint32_t CLCD::mem_read_32(uint32_t reg_address) {
  using namespace SPI::least_significant_byte_first;
  spi_ftdi_select();
  spi_read_addr(reg_address);
  uint32_t r_data = spi_read_32();
  spi_ftdi_deselect();
  return r_data;
}

/************************** MEMORY WRITE FUNCTIONS *********************************/

// Generic operations for transforming a byte, for use with _mem_write_bulk:
static inline uint8_t reverse_byte(uint8_t a) {
  return ((a & 0x1)  << 7) | ((a & 0x2)  << 5) |
         ((a & 0x4)  << 3) | ((a & 0x8)  << 1) |
         ((a & 0x10) >> 1) | ((a & 0x20) >> 3) |
         ((a & 0x40) >> 5) | ((a & 0x80) >> 7);
}
static inline uint8_t xbm_write(const uint8_t *p) {return reverse_byte(pgm_read_byte(p));}

void CLCD::spi_write_addr(uint32_t reg_address) {
  spi_send((reg_address >> 16) | 0x80);  // Address [21:16]
  spi_send((reg_address >> 8 ) & 0xFF);  // Address [15:8]
  spi_send((reg_address >> 0)  & 0xFF);  // Address [7:0]
}

// Write 3-Byte Address, Multiple Bytes, plus padding bytes, from RAM
void CLCD::mem_write_bulk(uint32_t reg_address, const void *data, uint16_t len, uint8_t padding) {
  spi_ftdi_select();
  spi_write_addr(reg_address);
  spi_write_bulk<ram_write>(data, len, padding);
  spi_ftdi_deselect();
}

// Write 3-Byte Address, Multiple Bytes, plus padding bytes, from PROGMEM
void CLCD::mem_write_bulk(uint32_t reg_address, FSTR_P str, uint16_t len, uint8_t padding) {
  spi_ftdi_select();
  spi_write_addr(reg_address);
  spi_write_bulk<pgm_write>(str, len, padding);
  spi_ftdi_deselect();
}

 // Write 3-Byte Address, Multiple Bytes, plus padding bytes, from PROGMEM
void CLCD::mem_write_pgm(uint32_t reg_address, const void *data, uint16_t len, uint8_t padding) {
  spi_ftdi_select();
  spi_write_addr(reg_address);
  spi_write_bulk<pgm_write>(data, len, padding);
  spi_ftdi_deselect();
}

// Write 3-Byte Address, Multiple Bytes, plus padding bytes, from PROGMEM, reversing bytes (suitable for loading XBM images)
void CLCD::mem_write_xbm(uint32_t reg_address, FSTR_P data, uint16_t len, uint8_t padding) {
  spi_ftdi_select();
  spi_write_addr(reg_address);
  spi_write_bulk<xbm_write>(data, len, padding);
  spi_ftdi_deselect();
}

// Write 3-Byte Address, Write 1-Byte Data
void CLCD::mem_write_8(uint32_t reg_address, uint8_t data) {
  spi_ftdi_select();
  spi_write_addr(reg_address);
  spi_write_8(data);
  spi_ftdi_deselect();
}

// Write 3-Byte Address, Write 2-Bytes Data
void CLCD::mem_write_16(uint32_t reg_address, uint16_t data) {
  using namespace SPI::least_significant_byte_first;
  spi_ftdi_select();
  spi_write_addr(reg_address);
  spi_write_16(data);
  spi_ftdi_deselect();
}

// Write 3-Byte Address, Write 4-Bytes Data
void CLCD::mem_write_32(uint32_t reg_address, uint32_t data) {
  using namespace SPI::least_significant_byte_first;
  spi_ftdi_select();
  spi_write_addr(reg_address);
  spi_write_32(data);
  spi_ftdi_deselect();
}

// Fill area of len size with repeated data bytes
void CLCD::mem_write_fill(uint32_t reg_address, uint8_t data, uint16_t len) {
  spi_ftdi_select();
  spi_write_addr(reg_address);
  while (len--) spi_write_8(data);
  spi_ftdi_deselect();
}

/******************* FT800/810 Co-processor Commands *********************************/

#if FTDI_API_LEVEL == 800
uint32_t CLCD::CommandFifo::command_write_ptr = 0xFFFFFFFFul;
#endif

void CLCD::CommandFifo::cmd(uint32_t cmd32) {
  write((void*)&cmd32, sizeof(uint32_t));
}

void CLCD::CommandFifo::cmd(void *data, uint16_t len) {
  write(data, len);
}

void CLCD::CommandFifo::bgcolor(uint32_t rgb) {
  cmd(CMD_BGCOLOR);
  cmd(rgb);
}

void CLCD::CommandFifo::fgcolor(uint32_t rgb) {
  cmd(CMD_FGCOLOR);
  cmd(rgb);
}

void CLCD::CommandFifo::gradcolor(uint32_t rgb) {
  cmd(CMD_GRADCOLOR);
  cmd(rgb);
}

// This sends the a text command to the command preprocessor, must be followed by str()
void CLCD::CommandFifo::button(int16_t x, int16_t y, int16_t w, int16_t h, int16_t font,  uint16_t option) {
  struct {
    int32_t type = CMD_BUTTON;
    int16_t x;
    int16_t y;
    int16_t w;
    int16_t h;
    int16_t font;
    uint16_t option;
  } cmd_data;

  cmd_data.x      = x;
  cmd_data.y      = y;
  cmd_data.w      = w;
  cmd_data.h      = h;
  cmd_data.font   = font;
  cmd_data.option = option;

  cmd( &cmd_data, sizeof(cmd_data) );
}

// This sends the a text command to the command preprocessor, must be followed by str()
void CLCD::CommandFifo::text(int16_t x, int16_t y, int16_t font,  uint16_t options) {
  struct {
    int32_t type = CMD_TEXT;
    int16_t x;
    int16_t y;
    int16_t font;
    uint16_t options;
    } cmd_data;

  cmd_data.x       = x;
  cmd_data.y       = y;
  cmd_data.font    = font;
  cmd_data.options = options;

  cmd( &cmd_data, sizeof(cmd_data) );
}

// This sends the a toggle command to the command preprocessor, must be followed by str()
void CLCD::CommandFifo::toggle(int16_t x, int16_t y, int16_t w, int16_t font, uint16_t options, bool state) {
  struct {
    int32_t type = CMD_TOGGLE;
    int16_t x;
    int16_t y;
    int16_t w;
    int16_t font;
    uint16_t options;
    uint16_t state;
  } cmd_data;

  cmd_data.x       = x;
  cmd_data.y       = y;
  cmd_data.w       = w;
  cmd_data.font    = font;
  cmd_data.options = options;
  cmd_data.state   = state ? 65535 : 0;

  cmd( &cmd_data, sizeof(cmd_data) );
}

// This sends the a keys command to the command preprocessor, must be followed by str()
void CLCD::CommandFifo::keys(int16_t x, int16_t y, int16_t w, int16_t h, int16_t font, uint16_t options) {
  struct {
    int32_t type = CMD_KEYS;
    int16_t x;
    int16_t y;
    int16_t w;
    int16_t h;
    int16_t font;
    uint16_t options;
  } cmd_data;

  cmd_data.x       = x;
  cmd_data.y       = y;
  cmd_data.w       = w;
  cmd_data.h       = h;
  cmd_data.font    = font;
  cmd_data.options = options;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::clock(int16_t x, int16_t y, int16_t r, uint16_t options, int16_t h, int16_t m, int16_t s, int16_t ms)
{
  struct {
    int32_t type = CMD_CLOCK;
    int16_t x;
    int16_t y;
    int16_t r;
    uint16_t options;
    int16_t h;
    int16_t m;
    int16_t s;
    int16_t ms;
  } cmd_data;

  cmd_data.x       = x;
  cmd_data.y       = y;
  cmd_data.r       = r;
  cmd_data.options = options;
  cmd_data.h       = h;
  cmd_data.m       = m;
  cmd_data.s       = s;
  cmd_data.ms      = ms;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::gauge(int16_t x, int16_t y, int16_t r, uint16_t options, uint16_t major, uint16_t minor, uint16_t val, uint16_t range)
{
  struct {
    int32_t  type = CMD_GAUGE;
    int16_t  x;
    int16_t  y;
    int16_t  r;
    uint16_t options;
    uint16_t major;
    uint16_t minor;
    uint16_t val;
    uint16_t range;
  } cmd_data;

  cmd_data.x       = x;
  cmd_data.y       = y;
  cmd_data.r       = r;
  cmd_data.options = options;
  cmd_data.major   = major;
  cmd_data.minor   = minor;
  cmd_data.val     = val;
  cmd_data.range   = range;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::dial(int16_t x, int16_t y, int16_t r, uint16_t options, uint16_t val)
{
  struct {
    int32_t  type = CMD_DIAL;
    int16_t  x;
    int16_t  y;
    int16_t  r;
    uint16_t options;
    uint16_t val;
  } cmd_data;

  cmd_data.x       = x;
  cmd_data.y       = y;
  cmd_data.r       = r;
  cmd_data.options = options;
  cmd_data.val     = val;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::scrollbar(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t options, uint16_t val, uint16_t size, uint16_t range) {
  struct {
    int32_t  type = CMD_SCROLLBAR;
    int16_t  x;
    int16_t  y;
    int16_t  w;
    uint16_t h;
    uint16_t options;
    uint16_t val;
    uint16_t size;
    uint16_t range;
  } cmd_data;

  cmd_data.x       = x;
  cmd_data.y       = y;
  cmd_data.w       = w;
  cmd_data.h       = h;
  cmd_data.options = options;
  cmd_data.val     = val;
  cmd_data.size    = size;
  cmd_data.range   = range;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::progress(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t options,  uint16_t val, uint16_t range) {
  struct {
    int32_t  type = CMD_PROGRESS;
    int16_t  x;
    int16_t  y;
    int16_t  w;
    int16_t  h;
    uint16_t options;
    uint16_t val;
    uint16_t range;
  } cmd_data;

  cmd_data.x       = x;
  cmd_data.y       = y;
  cmd_data.w       = w;
  cmd_data.h       = h;
  cmd_data.options = options;
  cmd_data.val     = val;
  cmd_data.range   = range;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::slider(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t options, uint16_t val, uint16_t range) {
  struct {
    int32_t type = CMD_SLIDER;
    int16_t x;
    int16_t y;
    int16_t w;
    int16_t h;
    uint16_t options;
    uint16_t val;
    uint16_t range;
  } cmd_data;

  cmd_data.x       = x;
  cmd_data.y       = y;
  cmd_data.w       = w;
  cmd_data.h       = h;
  cmd_data.options = options;
  cmd_data.val     = val;
  cmd_data.range   = range;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::gradient(int16_t x0, int16_t y0, uint32_t rgb0, int16_t x1, int16_t y1, uint32_t rgb1) {
  struct {
    int32_t type = CMD_GRADIENT;
    int16_t x0;
    int16_t y0;
    uint32_t rgb0;
    int16_t x1;
    int16_t y1;
    uint32_t rgb1;
  } cmd_data;

  cmd_data.x0      = x0;
  cmd_data.y0      = y0;
  cmd_data.rgb0    = rgb0;
  cmd_data.x1      = x1;
  cmd_data.y1      = y1;
  cmd_data.rgb1    = rgb1;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::number(int16_t x, int16_t y, int16_t font, uint16_t options, int32_t n) {
  struct {
    int32_t  type = CMD_NUMBER;
    int16_t  x;
    int16_t  y;
    int16_t  font;
    uint16_t options;
    int16_t n;
  } cmd_data;

  cmd_data.x       = x;
  cmd_data.y       = y;
  cmd_data.font    = font;
  cmd_data.options = options;
  cmd_data.n       = n;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::memzero(uint32_t ptr, uint32_t size) {
  struct {
    uint32_t  type = CMD_MEMZERO;
    uint32_t  ptr;
    uint32_t  size;
  } cmd_data;

  cmd_data.ptr    = ptr;
  cmd_data.size   = size;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::memset(uint32_t ptr, uint32_t val, uint32_t size) {
  struct {
    uint32_t  type = CMD_MEMSET;
    uint32_t  ptr;
    uint32_t  val;
    uint32_t  size;
  } cmd_data;

  cmd_data.ptr    = ptr;
  cmd_data.val    = val;
  cmd_data.size   = size;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::memcpy(uint32_t dst, uint32_t src, uint32_t size) {
  struct {
    uint32_t  type = CMD_MEMCPY;
    uint32_t  dst;
    uint32_t  src;
    uint32_t  size;
  } cmd_data;

  cmd_data.dst    = dst;
  cmd_data.src    = src;
  cmd_data.size   = size;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::memcrc(uint32_t ptr, uint32_t num, uint32_t result) {
  struct {
    uint32_t  type = CMD_MEMCRC;
    uint32_t  ptr;
    uint32_t  num;
    uint32_t  result;
  } cmd_data;

  cmd_data.ptr    = ptr;
  cmd_data.num    = num;
  cmd_data.result   = result;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::memwrite(uint32_t ptr, uint32_t value) {
  struct {
    uint32_t  type = CMD_MEMWRITE;
    uint32_t  ptr;
    uint32_t  num;
    uint32_t  value;
  } cmd_data;

  cmd_data.ptr    = ptr;
  cmd_data.num    = 4;
  cmd_data.value  = value;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::append(uint32_t ptr, uint32_t size) {
  struct {
    uint32_t  type = CMD_APPEND;
    uint32_t  ptr;
    uint32_t  size;
  } cmd_data;

  cmd_data.ptr    = ptr;
  cmd_data.size   = size;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::inflate(uint32_t ptr) {
  struct {
    uint32_t  type = CMD_INFLATE;
    uint32_t  ptr;
  } cmd_data;

  cmd_data.ptr    = ptr;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::getptr(uint32_t result) {
  struct {
    uint32_t  type = CMD_GETPTR;
    uint32_t  result;
  } cmd_data;

  cmd_data.result    = result;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::track(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t tag) {
  struct {
    uint32_t type = CMD_TRACK;
    int16_t  x;
    int16_t  y;
    int16_t  w;
    int16_t  h;
    int16_t  tag;
  } cmd_data;

  cmd_data.x       = x;
  cmd_data.y       = y;
  cmd_data.w       = w;
  cmd_data.h       = h;
  cmd_data.tag     = tag;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::sketch(int16_t x, int16_t y, uint16_t w, uint16_t h, uint32_t ptr, uint16_t format) {
  struct {
    uint32_t type = CMD_SKETCH;
    int16_t   x;
    int16_t   y;
    uint16_t  w;
    uint16_t  h;
    uint32_t  ptr;
    uint16_t  format;
  } cmd_data;

  cmd_data.x       = x;
  cmd_data.y       = y;
  cmd_data.w       = w;
  cmd_data.h       = h;
  cmd_data.ptr     = ptr;
  cmd_data.format  = format;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::snapshot(uint32_t ptr) {
  struct {
    uint32_t type = CMD_SNAPSHOT;
    uint32_t  ptr;
  } cmd_data;

  cmd_data.ptr     = ptr;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::spinner(int16_t x, int16_t y, uint16_t style, uint16_t scale) {
  struct {
    uint32_t type = CMD_SPINNER;
    uint16_t x;
    uint16_t y;
    uint16_t style;
    uint16_t scale;
  } cmd_data;

  cmd_data.x     = x;
  cmd_data.y     = y;
  cmd_data.style = style;
  cmd_data.scale = scale;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::loadimage(uint32_t ptr, uint32_t options) {
  struct {
    uint32_t type = CMD_LOADIMAGE;
    uint32_t ptr;
    uint32_t options;
  } cmd_data;

  cmd_data.ptr     = ptr;
  cmd_data.options = options;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::getprops(uint32_t ptr, uint32_t width, uint32_t height) {
  struct {
    uint32_t  type = CMD_GETPROPS;
    uint32_t  ptr;
    uint32_t  width;
    uint32_t  height;
  } cmd_data;

  cmd_data.ptr    = ptr;
  cmd_data.width  = width;
  cmd_data.height = height;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::scale(int32_t sx, int32_t sy) {
  struct {
    uint32_t type = CMD_SCALE;
    int32_t  sx;
    int32_t  sy;
  } cmd_data;

  cmd_data.sx       = sx;
  cmd_data.sy       = sy;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::rotate(int32_t a) {
  struct {
    uint32_t type = CMD_ROTATE;
    int32_t  a;
  } cmd_data;

  cmd_data.a       = a;

  cmd( &cmd_data, sizeof(cmd_data) );
}

void CLCD::CommandFifo::translate(int32_t tx, int32_t ty) {
  struct {
    uint32_t type = CMD_TRANSLATE;
    int32_t  tx;
    int32_t  ty;
  } cmd_data;

  cmd_data.tx       = tx;
  cmd_data.ty       = ty;

  cmd( &cmd_data, sizeof(cmd_data) );
}

#if FTDI_API_LEVEL >= 810
void CLCD::CommandFifo::setbase(uint8_t base) {
  struct {
    int32_t  type = CMD_SETBASE;
    uint32_t base;
  } cmd_data;

  cmd_data.base     = base;

  cmd( &cmd_data, sizeof(cmd_data) );
}
#endif

#if FTDI_API_LEVEL >= 810
void CLCD::CommandFifo::setbitmap(uint32_t addr, uint16_t fmt, uint16_t w, uint16_t h) {
  struct {
    uint32_t type = CMD_SETBITMAP;
    uint32_t addr;
    uint16_t fmt;
    uint16_t w;
    uint16_t h;
    uint16_t dummy;
  } cmd_data;

  cmd_data.addr    = addr;
  cmd_data.fmt     = fmt;
  cmd_data.w       = w;
  cmd_data.h       = h;
  cmd_data.dummy   = 0;

  cmd( &cmd_data, sizeof(cmd_data) );
}
#endif

#if FTDI_API_LEVEL >= 810
void CLCD::CommandFifo::snapshot2(uint32_t format, uint32_t ptr, int16_t x, int16_t y, uint16_t w, uint16_t h) {
  struct {
    uint32_t type = CMD_SNAPSHOT2;
    uint32_t  format;
    uint32_t  ptr;
    int16_t   x;
    int16_t   y;
    uint16_t  w;
    uint16_t  h;
  } cmd_data;

  cmd_data.format  = format;
  cmd_data.ptr     = ptr;
  cmd_data.x       = x;
  cmd_data.y       = y;
  cmd_data.w       = w;
  cmd_data.h       = h;

  cmd( &cmd_data, sizeof(cmd_data) );
}
#endif

#if FTDI_API_LEVEL >= 810
void CLCD::CommandFifo::mediafifo(uint32_t ptr, uint32_t size) {
  struct {
    uint32_t type = CMD_MEDIAFIFO;
    uint32_t ptr;
    uint32_t size;
  } cmd_data;

  cmd_data.ptr  = ptr;
  cmd_data.size = size;

  cmd( &cmd_data, sizeof(cmd_data) );
}
#endif

#if FTDI_API_LEVEL >= 810
void CLCD::CommandFifo::videostart() {
  cmd( CMD_VIDEOSTART );
}
#endif

#if FTDI_API_LEVEL >= 810
void CLCD::CommandFifo::videoframe(uint32_t dst, uint32_t ptr) {
  struct {
    uint32_t type = CMD_VIDEOFRAME;
    uint32_t dst;
    uint32_t ptr;
  } cmd_data;

  cmd_data.dst = dst;
  cmd_data.ptr = ptr;

  cmd( &cmd_data, sizeof(cmd_data) );
}
#endif

#if FTDI_API_LEVEL >= 810
void CLCD::CommandFifo::playvideo(uint32_t options) {
  struct {
    uint32_t type = CMD_PLAYVIDEO;
    uint32_t options;
  } cmd_data;

  cmd_data.options = options;

  cmd( &cmd_data, sizeof(cmd_data) );
}
#endif

#if FTDI_API_LEVEL >= 810
void CLCD::CommandFifo::setrotate(uint8_t rotation) {
  struct {
    uint32_t  type = CMD_SETROTATE;
    uint32_t  rotation;
  } cmd_data;

  cmd_data.rotation = rotation;

  cmd( &cmd_data, sizeof(cmd_data) );
}
#endif

#if FTDI_API_LEVEL >= 810
void CLCD::CommandFifo::romfont(uint8_t font, uint8_t romslot) {
  struct {
    uint32_t  type = CMD_ROMFONT;
    uint32_t  font;
    uint32_t  romslot;
  } cmd_data;

  cmd_data.font    = font;
  cmd_data.romslot = romslot;

  cmd( &cmd_data, sizeof(cmd_data) );
}
#endif

/**************************** FT800/810 Co-Processor Command FIFO ****************************/

bool CLCD::CommandFifo::is_processing() {
  return (mem_read_32(REG::CMD_READ) & 0x0FFF) != (mem_read_32(REG::CMD_WRITE) & 0x0FFF);
}

bool CLCD::CommandFifo::has_fault() {
  uint16_t Cmd_Read_Reg  = mem_read_32(REG::CMD_READ) & 0x0FFF;
  return Cmd_Read_Reg == 0x0FFF;
}

#if FTDI_API_LEVEL == 800

void CLCD::CommandFifo::start() {
  if (command_write_ptr == 0xFFFFFFFFul) {
    command_write_ptr = mem_read_32(REG::CMD_WRITE) & 0x0FFF;
  }
}

void CLCD::CommandFifo::execute() {
  if (command_write_ptr != 0xFFFFFFFFul) {
    mem_write_32(REG::CMD_WRITE, command_write_ptr);
  }
}

void CLCD::CommandFifo::reset() {
  safe_delay(100);
  mem_write_32(REG::CPURESET,  0x00000001);
  mem_write_32(REG::CMD_WRITE, 0x00000000);
  mem_write_32(REG::CMD_READ,  0x00000000);
  mem_write_32(REG::CPURESET,  0x00000000);
  safe_delay(300);
  command_write_ptr = 0xFFFFFFFFul;
};

template <class T> bool CLCD::CommandFifo::_write_unaligned(T data, uint16_t len) {
  const char *ptr = (const char*)data;
  uint32_t bytes_tail, bytes_head;
  uint32_t command_read_ptr;

  #if ENABLED(TOUCH_UI_DEBUG)
    if (command_write_ptr == 0xFFFFFFFFul)
      SERIAL_ECHO_MSG("Attempt to write to FIFO before CommandFifo::Cmd_Start().");
  #endif

  /* Wait until there is enough space in the circular buffer for the transfer */
  do {
    command_read_ptr = mem_read_32(REG::CMD_READ) & 0x0FFF;
    if (command_read_ptr <= command_write_ptr) {
      bytes_tail = 4096U - command_write_ptr;
      bytes_head = command_read_ptr;
    }
    else {
      bytes_tail = command_read_ptr - command_write_ptr;
      bytes_head = 0;
    }
    // Check for faults which can lock up the command processor
    if (has_fault()) {
      #if ENABLED(TOUCH_UI_DEBUG)
        SERIAL_ECHOLNPGM("Fault waiting for space in the command processor");
      #endif
      return false;
    }
  } while ((bytes_tail + bytes_head) < len);

  /* Write as many bytes as possible following REG::CMD_WRITE */
  uint16_t bytes_to_write = min(len, bytes_tail);
  mem_write_bulk (MAP::RAM_CMD + command_write_ptr, T(ptr), bytes_to_write);
  command_write_ptr += bytes_to_write;
  ptr  += bytes_to_write;
  len  -= bytes_to_write;

  if (len > 0) {
    /* Write remaining bytes at start of circular buffer */
    mem_write_bulk (MAP::RAM_CMD, T(ptr), len);
    command_write_ptr = len;
  }

  if (command_write_ptr == 4096U) {
    command_write_ptr = 0;
  }
  return true;
}

// Writes len bytes into the FIFO, if len is not
// divisible by four, zero bytes will be written
// to align to the boundary.

template <class T> bool CLCD::CommandFifo::write(T data, uint16_t len) {
  const uint8_t padding = MULTIPLE_OF_4(len) - len;
  const uint8_t pad_bytes[] = { 0, 0, 0, 0 };
  return _write_unaligned(data,      len) &&
         _write_unaligned(pad_bytes, padding);
}

#else // FTDI_API_LEVEL != 800 ...

void CLCD::CommandFifo::start() {
}

void CLCD::CommandFifo::execute() {
}

void CLCD::CommandFifo::reset() {
  #if ENABLED(TOUCH_UI_DEBUG)
    SERIAL_ECHOLNPGM("Resetting command processor");
  #endif
  safe_delay(100);
  mem_write_32(REG::CPURESET,  0x00000001);
  mem_write_32(REG::CMD_WRITE, 0x00000000);
  mem_write_32(REG::CMD_READ,  0x00000000);
  mem_write_32(REG::CPURESET,  0x00000000);
  safe_delay(300);
};

// Writes len bytes into the FIFO, if len is not
// divisible by four, zero bytes will be written
// to align to the boundary.

template <class T> bool CLCD::CommandFifo::write(T data, uint16_t len) {
  const uint8_t padding = MULTIPLE_OF_4(len) - len;

  if (has_fault()) {
    #if ENABLED(TOUCH_UI_DEBUG)
      SERIAL_ECHOLNPGM("Faulted... ignoring write.");
    #endif
    return false;
  }
  // The FT810 provides a special register that can be used
  // for writing data without us having to do our own FIFO
  // management.
  uint16_t Command_Space = mem_read_32(REG::CMDB_SPACE) & 0x0FFF;
  if (Command_Space < (len + padding)) {
    #if ENABLED(TOUCH_UI_DEBUG)
      SERIAL_ECHO_MSG("Waiting for ", len + padding, " bytes in command queue, now free: ", Command_Space);
    #endif
    do {
      Command_Space = mem_read_32(REG::CMDB_SPACE) & 0x0FFF;
      if (has_fault()) {
        #if ENABLED(TOUCH_UI_DEBUG)
          SERIAL_ECHOLNPGM("... fault");
        #endif
        return false;
      }
    } while (Command_Space < len + padding);
    #if ENABLED(TOUCH_UI_DEBUG)
      SERIAL_ECHOLNPGM("... done");
    #endif
  }
  mem_write_bulk(REG::CMDB_WRITE, data, len, padding);
  return true;
}

#endif // ... FTDI_API_LEVEL != 800

template bool CLCD::CommandFifo::write(const void*, uint16_t);
template bool CLCD::CommandFifo::write(FSTR_P, uint16_t);

// CO_PROCESSOR COMMANDS

void CLCD::CommandFifo::str(const char * data, size_t maxlen) {
  // Write the string without the terminating '\0'
  const size_t len = strnlen(data, maxlen);
  write(data, len);

  // If padding was added by the previous write, then
  // the string is terminated. Otherwise write four
  // more zeros.
  const uint8_t padding = MULTIPLE_OF_4(len) - len;
  if (padding == 0) {
    const uint8_t pad_bytes[] = {0, 0, 0, 0};
    write(pad_bytes, 4);
  }
}

void CLCD::CommandFifo::str(const char * data) {
  write(data, strlen(data)+1);
}

void CLCD::CommandFifo::str(FSTR_P data) {
  write(data, strlen_P((const char*)data)+1);
}

/******************* LCD INITIALIZATION ************************/

void CLCD::init() {
  spi_init();                                  // Set Up I/O Lines for SPI and FT800/810 Control
  ftdi_reset();                                // Power down/up the FT8xx with the appropriate delays

  host_cmd(Use_Crystal ? CLKEXT : CLKINT, 0);
  host_cmd(FTDI::ACTIVE, 0);                        // Activate the System Clock

  delay(40); // FTDI/BRT recommendation: no SPI traffic during startup. EVE needs at the very least 45ms to start, so leave her alone for a little while.

  /* read the device-id until it returns 0x7C or times out, should take less than 150ms */
  uint8_t counter;
  for (counter = 0; counter < 250; counter++) {
   uint8_t device_id = mem_read_8(REG::ID);            // Read Device ID, Should Be 0x7C;
   if (device_id == 0x7C) {
     if (ENABLED(TOUCH_UI_DEBUG)) SERIAL_ECHO_MSG("FTDI chip initialized ");
     break;
   }
   else
     delay(1);

    if (TERN0(TOUCH_UI_DEBUG, counter > 248))
      SERIAL_ECHO_MSG("Timeout waiting for device ID, should be 124, got ", device_id);
  }

  /* Ensure all units are in working condition, usually the touch-controller needs a little more time */
  for (counter = 0; counter < 100; counter++) {
    uint8_t reset_status = mem_read_8(REG::CPURESET) & 0x03;
    if (reset_status == 0x00) {
      if (ENABLED(TOUCH_UI_DEBUG)) SERIAL_ECHO_MSG("FTDI chip all units running ");
      break;
    }
    else
      delay(1);

    if (TERN0(TOUCH_UI_DEBUG, counter > 98))
      SERIAL_ECHO_MSG("Timeout waiting for reset status. Should be 0x00, got ", reset_status);
  }

  #if ENABLED(USE_GT911)   /* switch BT815 to use Goodix GT911 touch controller */
    mem_write_32(REG::TOUCH_CONFIG, 0x000005D1);
  #endif

  #if ENABLED(PATCH_GT911) /* patch FT813 use Goodix GT911 touch controller */
    mem_write_pgm(REG::CMDB_WRITE, GT911_data, sizeof(GT911_data)); /* write binary blob to command-fifo */
    delay(10);
    mem_write_8(REG::TOUCH_OVERSAMPLE, 0x0F);  /* setup oversample to 0x0f as "hidden" in binary-blob for AN_336 */
    mem_write_16(REG::TOUCH_CONFIG, 0x05d0); /* write magic cookie as requested by AN_336 */

    /* specific to the EVE2 modules from Matrix-Orbital we have to use GPIO3 to reset GT911 */
    mem_write_16(REG::GPIOX_DIR,0x8008); /* Reset-Value is 0x8000, adding 0x08 sets GPIO3 to output, default-value for REG_GPIOX is 0x8000 -> Low output on GPIO3 */
    delay(1); /* wait more than 100µs */
    mem_write_8(REG::CPURESET, 0x00); /* clear all resets */
    delay(56); /* wait more than 55ms */
    mem_write_16(REG::GPIOX_DIR,0x8000); /* setting GPIO3 back to input */
  #endif

  mem_write_8(REG::PWM_DUTY, 0);   // turn off Backlight, Frequency already is set to 250Hz default

  /* Configure the FT8xx Registers */
  mem_write_16(REG::HCYCLE,  FTDI::Hcycle);
  mem_write_16(REG::HOFFSET, FTDI::Hoffset);
  mem_write_16(REG::HSYNC0,  FTDI::Hsync0);
  mem_write_16(REG::HSYNC1,  FTDI::Hsync1);
  mem_write_16(REG::VCYCLE,  FTDI::Vcycle);
  mem_write_16(REG::VOFFSET, FTDI::Voffset);
  mem_write_16(REG::VSYNC0,  FTDI::Vsync0);
  mem_write_16(REG::VSYNC1,  FTDI::Vsync1);
  mem_write_16(REG::HSIZE,   FTDI::Hsize);
  mem_write_16(REG::VSIZE,   FTDI::Vsize);
  mem_write_8(REG::SWIZZLE,  FTDI::Swizzle);
  mem_write_8(REG::PCLK_POL, FTDI::Pclkpol);
  mem_write_8(REG::CSPREAD,  FTDI::CSpread);

  /* write a basic display-list to get things started */
  mem_write_32(MAP::RAM_DL,      DL::CLEAR_COLOR_RGB);
  mem_write_32(MAP::RAM_DL + 4, (DL::CLEAR | 0x07)); /* clear color, stencil and tag buffer */
  mem_write_32(MAP::RAM_DL + 8,  DL::DL_DISPLAY);    /* end of display list */

  mem_write_8(REG::DLSWAP, 0x02); // activate display list, Bad Magic Cookie 2 = switch to new list after current frame is scanned out

  //mem_write_8(REG::TOUCH_MODE, 0x03);      // Configure the Touch Screen, Bad Magic Cookie, 3 = CONTINUOUS = Reset Default
  //mem_write_8(REG::TOUCH_ADC_MODE, 0x01);  // Bad Magic Cookie, 1 = single touch = Reset Default
  //mem_write_8(REG::TOUCH_OVERSAMPLE, 0x0F); // Reset Default = 7 - why 15?
  mem_write_16(REG::TOUCH_RZTHRESH, touch_threshold); /* setup touch sensitivity */
  mem_write_8(REG::VOL_SOUND, 0x00);       // Turn Synthesizer Volume Off

  /* turn on the display by setting DISP high */
  /* turn on the Audio Amplifier by setting GPIO_1 high for the select few modules supporting this */
  /* no need to use GPIOX here since DISP/GPIO_0 and GPIO_1 are on REG::GPIO for FT81x as well */
  if (GPIO_1_Audio_Shutdown) {
    mem_write_8(REG::GPIO_DIR,   GPIO_DISP  | GPIO_GP1);
    mem_write_8(REG::GPIO,       GPIO_DISP  | GPIO_GP1);
  }
  else if (GPIO_0_Audio_Enable) {
    mem_write_8(REG::GPIO_DIR,   GPIO_DISP  | GPIO_GP0);
    mem_write_8(REG::GPIO,       GPIO_DISP  | GPIO_GP0);
  }
  else
    mem_write_8(REG::GPIO, GPIO_DISP); /* REG::GPIO_DIR is set to output for GPIO_DISP by default */

  mem_write_8(REG::PCLK, Pclk); // Turns on Clock by setting PCLK Register to the value necessary for the module

  mem_write_16(REG::PWM_HZ, 0x00FA);

  // Turning off dithering seems to help prevent horizontal line artifacts on certain colors
  mem_write_8(REG::DITHER,  0);

  default_touch_transform();
  default_display_orientation();
}

void CLCD::default_touch_transform() {
  // Set Initial Values for Touch Transform Registers
  mem_write_32(REG::ROTATE, 0);
  mem_write_32(REG::TOUCH_TRANSFORM_A, FTDI::default_transform_a);
  mem_write_32(REG::TOUCH_TRANSFORM_B, FTDI::default_transform_b);
  mem_write_32(REG::TOUCH_TRANSFORM_C, FTDI::default_transform_c);
  mem_write_32(REG::TOUCH_TRANSFORM_D, FTDI::default_transform_d);
  mem_write_32(REG::TOUCH_TRANSFORM_E, FTDI::default_transform_e);
  mem_write_32(REG::TOUCH_TRANSFORM_F, FTDI::default_transform_f);
}

void CLCD::default_display_orientation() {
  #if FTDI_API_LEVEL >= 810
    // Set the initial display orientation. On the FT810, we use the command
    // processor to do this since it will also update the transform matrices.
    CommandFifo cmd;
    cmd.setrotate(
        ENABLED(TOUCH_UI_MIRRORED) * 4
      + ENABLED(TOUCH_UI_PORTRAIT) * 2
      + ENABLED(TOUCH_UI_INVERTED) * 1
    );
    cmd.execute();
  #elif EITHER(TOUCH_UI_PORTRAIT, TOUCH_UI_MIRRORED)
    #error "PORTRAIT or MIRRORED orientation not supported on the FT800."
  #elif ENABLED(TOUCH_UI_INVERTED)
    mem_write_32(REG::ROTATE, 1);
  #endif
}

#endif // FTDI_BASIC
