/***************************************************
  This is a library for the GC9A01 IPS SPI display.

  Originally written by Limor Fried/Ladyada for 
  Adafruit Industries.

  Modified by Ananev Ilia
 ****************************************************/

#include "Arduino_GC9A01.h"
#include <limits.h>
#include "pins_arduino.h"
#include "wiring_private.h"
#include <SPI.h>

static const uint8_t PROGMEM
  cmd_240x240[] = {                 		// Initialization commands for 7789 screens
        50,                      
        0xEF,0,
        0xEB,1,0x14,
        0xFE,0,
        0xEF,0,
        0xEB,1,0x14,
        0x84,1,0x40,
        0x85,1,0xFF,
        0x86,1,0xFF,
        0x87,1,0xFF,
        0x88,1,0x0A,
        0x89,1,0x21,
        0x8A,1,0x00,
        0x8B,1,0x80,
        0x8C,1,0x01,
        0x8D,1,0x01,
        0x8E,1,0xFF,
        0x8F,1,0xFF,
        0xB6,2,0x00,0x20,
        0x36,1,0x08,
        0x3A,1,0x05,
        0x90,4,0x08,0x08,0x08,0x08,
        0xBD,1,0x06,
        0xBC,1,0x00,
        0xFF,3,0x60,0x01,0x04,
        0xC3,1,0x13,
        0xC4,1,0x13,
        0xC9,1,0x22,
        0xBE,1,0x11,
        0xE1,2,0x10,0x0E,
        0xDF,3,0x21,0x0C,0x02,
        0xF0,6,0x45,0x09,0x08,0x08,0x26,0x2A,
        0xF1,6,0x43,0x70,0x72,0x36,0x37,0x6F,
        0xF2,6,0x45,0x09,0x08,0x08,0x26,0x2A,
        0xF3,6,0x43,0x70,0x72,0x36,0x37,0x6F,
        0xED,2,0x1B,0x0B,
        0xAE,1,0x77,
        0xCD,1,0x63,
        0x70,9,0x07,0x07,0x04,0x0E,0x0F,0x09,0x07,0x08,0x03,
        0xE8,1,0x34,
        0x62,12,0x18,0x0D,0x71,0xED,0x70,0x70,0x18,0x0F,0x71,0xEF,0x70,0x70,
        0x63,12,0x18,0x11,0x71,0xF1,0x70,0x70,0x18,0x13,0x71,0xF3,0x70,0x70,
        0x64,7,0x28,0x29,0xF1,0x01,0xF1,0x00,0x07,
        0x66,10,0x3C,0x00,0xCD,0x67,0x45,0x45,0x10,0x00,0x00,0x00,
        0x67,10,0x00,0x3C,0x00,0x00,0x00,0x01,0x54,0x10,0x32,0x98,
        0x74,7,0x10,0x85,0x80,0x00,0x00,0x4E,0x00,
        0x98,2,0x3E,0x07,
        0x35,0,
        0x21,0,
        0x11,ST_CMD_DELAY,120,
        0x29,ST_CMD_DELAY,20
        };

inline uint16_t swapcolor(uint16_t x) { 
  return (x << 11) | (x & 0x07E0) | (x >> 11);
}

#if defined (SPI_HAS_TRANSACTION)
  static SPISettings mySPISettings;
#elif defined (__AVR__) || defined(CORE_TEENSY)
  static uint8_t SPCRbackup;
  static uint8_t mySPCR;
#endif


#if defined (SPI_HAS_TRANSACTION)
#define SPI_BEGIN_TRANSACTION()    if (_hwSPI)    SPI.beginTransaction(mySPISettings)
#define SPI_END_TRANSACTION()      if (_hwSPI)    SPI.endTransaction()
#else
#define SPI_BEGIN_TRANSACTION()    (void)
#define SPI_END_TRANSACTION()      (void)
#endif

// Constructor when using software SPI.  All output pins are configurable.
Arduino_GC9A01::Arduino_GC9A01(int8_t dc, int8_t rst, int8_t sid, int8_t sclk, int8_t cs) 
  : Adafruit_GFX(GC9A01_TFTWIDTH, GC9A01_TFTHEIGHT)
{
  _cs   = cs;
  _dc   = dc;
  _sid  = sid;
  _sclk = sclk;
  _rst  = rst;
  _hwSPI = false;
  if(dc == -1) _SPI9bit = true;
  else _SPI9bit = false;  
}

// Constructor when using hardware SPI.  Faster, but must use SPI pins
// specific to each board type (e.g. 11,13 for Uno, 51,52 for Mega, etc.)
Arduino_GC9A01::Arduino_GC9A01(int8_t dc, int8_t rst, int8_t cs) 
  : Adafruit_GFX(GC9A01_TFTWIDTH, GC9A01_TFTHEIGHT) {
  _cs   = cs;
  _dc   = dc;
  _rst  = rst;
  _hwSPI = true;
  _SPI9bit = false;
  _sid  = _sclk = -1;
}

inline void Arduino_GC9A01::spiwrite(uint8_t c) 
{
  if (_hwSPI) 
  {
#if defined (SPI_HAS_TRANSACTION)
      SPI.transfer(c);
#elif defined (__AVR__) || defined(CORE_TEENSY)
      SPCRbackup = SPCR;
      SPCR = mySPCR;
      SPI.transfer(c);
      SPCR = SPCRbackup;
#elif defined (__arm__)
      SPI.setClockDivider(21); //4MHz
      SPI.setDataMode(SPI_MODE2);
      SPI.transfer(c);
#endif
  }
  else 
  {
	  if(_SPI9bit)
	  {
		//9s bit send first
#if defined(USE_FAST_IO)
		*clkport &= ~clkpinmask;
		if(_DCbit) *dataport |=  datapinmask;
		else        *dataport &= ~datapinmask;
		*clkport |=  clkpinmask;
#else
		digitalWrite(_sclk, LOW);
		if(_DCbit) digitalWrite(_sid, HIGH);
		else        digitalWrite(_sid, LOW);
		digitalWrite(_sclk, HIGH);
#endif
		
		
		// Fast SPI bitbang swiped from LPD8806 library
		for(uint8_t bit = 0x80; bit; bit >>= 1) {
#if defined(USE_FAST_IO)
		*clkport &= ~clkpinmask;
		if(c & bit) *dataport |=  datapinmask;
		else        *dataport &= ~datapinmask;
		*clkport |=  clkpinmask;
#else
		digitalWrite(_sclk, LOW);
		if(c & bit) digitalWrite(_sid, HIGH);
		else        digitalWrite(_sid, LOW);
		digitalWrite(_sclk, HIGH);
#endif
		  
	  }
	  }
	  else
	  {
		  // Fast SPI bitbang swiped from LPD8806 library
		for(uint8_t bit = 0x80; bit; bit >>= 1) {
#if defined(USE_FAST_IO)
		*clkport &= ~clkpinmask;
		if(c & bit) *dataport |=  datapinmask;
		else        *dataport &= ~datapinmask;
		*clkport |=  clkpinmask;
#else
		digitalWrite(_sclk, LOW);
		if(c & bit) digitalWrite(_sid, HIGH);
		else        digitalWrite(_sid, LOW);
		digitalWrite(_sclk, HIGH);
#endif
	  }
	}
  }
}

void Arduino_GC9A01::writecommand(uint8_t c) {

  DC_LOW();
  CS_LOW();
  SPI_BEGIN_TRANSACTION();
  spiwrite(c);

  CS_HIGH();
  SPI_END_TRANSACTION();
}

void Arduino_GC9A01::writedata(uint8_t c) {
  SPI_BEGIN_TRANSACTION();
  DC_HIGH();
  CS_LOW();
    
  spiwrite(c);

  CS_HIGH();
  SPI_END_TRANSACTION();
}

void Arduino_GC9A01::writedata16(uint16_t c) {
  SPI_BEGIN_TRANSACTION();
  DC_HIGH();
  CS_LOW();

  spiwrite(c>>8);
  spiwrite(c&0xFF);

  CS_HIGH();
  SPI_END_TRANSACTION();
}

// Companion code to the above tables.  Reads and issues
// a series of LCD commands stored in PROGMEM byte array.
void Arduino_GC9A01::displayInit(const uint8_t *addr) {

  uint8_t  numCommands, numArgs;
  uint16_t ms;
  //<-----------------------------------------------------------------------------------------
  DC_HIGH();
  #if defined(USE_FAST_IO)
      *clkport |=  clkpinmask;
  #else
      digitalWrite(_sclk, HIGH);
  #endif
  //<-----------------------------------------------------------------------------------------

  numCommands = pgm_read_byte(addr++);   // Number of commands to follow
  while(numCommands--) {                 // For each command...
    
    writecommand(pgm_read_byte(addr++)); //   Read, issue command
    numArgs  = pgm_read_byte(addr++);    //   Number of args to follow
    ms       = numArgs & ST_CMD_DELAY;   //   If hibit set, delay follows args
    numArgs &= ~ST_CMD_DELAY;            //   Mask out delay bit
    while(numArgs--) {                   //   For each argument...
      writedata(pgm_read_byte(addr++));  //     Read, issue argument
    }

    if(ms) {
      ms = pgm_read_byte(addr++); // Read post-command delay time (ms)
      if(ms == 255) ms = 500;     // If 255, delay for 500 ms
      delay(ms);
    }
  }
}


// Initialization code common to all GC9A01 displays
void Arduino_GC9A01::commonInit(const uint8_t *cmdList) {
  _ystart = _xstart = 0;
  _colstart  = _rowstart = 0; // May be overridden in init func

  pinMode(_dc, OUTPUT);
  if(_cs) {
	  pinMode(_cs, OUTPUT);
  }

#if defined(USE_FAST_IO)
  dcport    = portOutputRegister(digitalPinToPort(_dc));
  dcpinmask = digitalPinToBitMask(_dc);
  if(_cs) {
	csport    = portOutputRegister(digitalPinToPort(_cs));
	cspinmask = digitalPinToBitMask(_cs);
  }
  
#endif

  if(_hwSPI) { // Using hardware SPI
#if defined (SPI_HAS_TRANSACTION)
    SPI.begin();
    mySPISettings = SPISettings(4000000, MSBFIRST, SPI_MODE0);
#elif defined (__AVR__) || defined(CORE_TEENSY)
    SPCRbackup = SPCR;
    SPI.begin();
    SPI.setClockDivider(SPI_CLOCK_DIV4);
    SPI.setDataMode(SPI_MODE2);
    mySPCR = SPCR; // save our preferred state
    SPCR = SPCRbackup;  // then restore
#elif defined (__SAM3X8E__)
    SPI.begin();
    SPI.setClockDivider(21); //4MHz
    SPI.setDataMode(SPI_MODE2);
#endif
  } else {
    pinMode(_sclk, OUTPUT);
    pinMode(_sid , OUTPUT);
    digitalWrite(_sclk, LOW);
    digitalWrite(_sid, LOW);

#if defined(USE_FAST_IO)
    clkport     = portOutputRegister(digitalPinToPort(_sclk));
    dataport    = portOutputRegister(digitalPinToPort(_sid));
    clkpinmask  = digitalPinToBitMask(_sclk);
    datapinmask = digitalPinToBitMask(_sid);
#endif
  }

  // toggle RST low to reset; CS low so it'll listen to us
  CS_LOW();
  if (_rst != -1) {
    pinMode(_rst, OUTPUT);
    digitalWrite(_rst, LOW);
    delay(100);
    digitalWrite(_rst, HIGH);
    delay(100);
  }

  if(cmdList) 
    displayInit(cmdList);
}

void Arduino_GC9A01::setRotation(uint8_t m) {

  writecommand(GC9A01_MADCTL);
  rotation = m % 4; // can't be higher than 3
  switch (rotation) {
   case 0:
     writedata(GC9A01_MADCTL_MX | GC9A01_MADCTL_MY | GC9A01_MADCTL_RGB);

     _xstart = _colstart;
     _ystart = _rowstart;
     break;
   case 1:
     writedata(GC9A01_MADCTL_MY | GC9A01_MADCTL_MV | GC9A01_MADCTL_RGB);

     _ystart = _colstart;
     _xstart = _rowstart;
     break;
  case 2:
     writedata(GC9A01_MADCTL_RGB);
 
     _xstart = _colstart;
     _ystart = _rowstart;
     break;

   case 3:
     writedata(GC9A01_MADCTL_MX | GC9A01_MADCTL_MV | GC9A01_MADCTL_RGB);

     _ystart = _colstart;
     _xstart = _rowstart;
     break;
  }
}

void Arduino_GC9A01::setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1,
 uint16_t y1) {

  //uint16_t x_start = x0 + _xstart, x_end = x1 + _xstart;
  //uint16_t y_start = y0 + _ystart, y_end = y1 + _ystart;
  

  writecommand(GC9A01_CASET); // Column addr set
  //writedata(x0 >> 8);
  //writedata(x0 & 0xFF);     // XSTART 
  //writedata(x1 >> 8);
  //writedata(x1 & 0xFF);     // XEND
  writedata16(x0);     // XSTART
  writedata16(x1);     // XEND

  writecommand(GC9A01_RASET); // Row addr set
  //writedata(y0 >> 8);
  //writedata(y0 & 0xFF);     // YSTART
  //writedata(y1 >> 8);
  //writedata(y1 & 0xFF);     // YEND
  writedata16(y0);     // YSTART
  writedata16(y1);     // YEND

  writecommand(GC9A01_RAMWR); // write to RAM
}

void Arduino_GC9A01::pushColor(uint16_t color) {
  SPI_BEGIN_TRANSACTION();
  DC_HIGH();
  CS_LOW();

  spiwrite(color >> 8);
  spiwrite(color);

  CS_HIGH();
  SPI_END_TRANSACTION();
}

void Arduino_GC9A01::drawPixel(int16_t x, int16_t y, uint16_t color) {
  if((x < 0) ||(x >= _width) || (y < 0) || (y >= _height)) return;
  setAddrWindow(x,y,x,y);

  writedata16(color);
}

void Arduino_GC9A01::drawFastVLine(int16_t x, int16_t y, int16_t h,
 uint16_t color) {

  // Rudimentary clipping
  if((x >= _width) || (y >= _height)) return;
  if((y+h-1) >= _height) h = _height-y;
  setAddrWindow(x, y, x, y+h-1);

  uint8_t hi = color >> 8, lo = color;
    
  SPI_BEGIN_TRANSACTION();
  DC_HIGH();
  CS_LOW();

  while (h--) {
    spiwrite(hi);
    spiwrite(lo);
  }

  CS_HIGH();
  SPI_END_TRANSACTION();
}

void Arduino_GC9A01::drawFastHLine(int16_t x, int16_t y, int16_t w,
  uint16_t color) {

  // Rudimentary clipping
  if((x >= _width) || (y >= _height)) return;
  if((x+w-1) >= _width)  w = _width-x;
  setAddrWindow(x, y, x+w-1, y);

  uint8_t hi = color >> 8, lo = color;

  SPI_BEGIN_TRANSACTION();
  DC_HIGH();
  CS_LOW();

  while (w--) {
    spiwrite(hi);
    spiwrite(lo);
  }

  CS_HIGH();
  SPI_END_TRANSACTION();
}

//void Arduino_GC9A01::fillScreen(uint16_t color) {
//  fillRect(0, 0,  _width, _height, color);
//}

// fill a rectangle
void Arduino_GC9A01::fillRect(int16_t x, int16_t y, int16_t w, int16_t h,
  uint16_t color) {

  // rudimentary clipping (drawChar w/big text requires this)
  if((x >= _width) || (y >= _height)) return;
  if((x + w - 1) >= _width)  w = _width  - x;
  if((y + h - 1) >= _height) h = _height - y;

  setAddrWindow(x, y, x+w-1, y+h-1);

  uint8_t hi = color >> 8, lo = color;
    
  SPI_BEGIN_TRANSACTION();

  DC_HIGH();
  CS_LOW();
  for(y=h; y>0; y--) {
    for(x=w; x>0; x--) {
      spiwrite(hi);
      spiwrite(lo);
    }
  }
  CS_HIGH();
  SPI_END_TRANSACTION();
}

// Pass 8-bit (each) R,G,B, get back 16-bit packed color
uint16_t Arduino_GC9A01::Color565(uint8_t r, uint8_t g, uint8_t b) {
  return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}

void Arduino_GC9A01::invertDisplay(boolean i) {
  writecommand(i ? GC9A01_INVON : GC9A01_INVOFF);
}

/******** low level bit twiddling **********/

inline void Arduino_GC9A01::CS_HIGH(void) {
	if(_cs) {
		#if defined(USE_FAST_IO)
			*csport |= cspinmask;
		#else
		digitalWrite(_cs, HIGH);
		#endif
	}
}

inline void Arduino_GC9A01::CS_LOW(void) {
	if(_cs) {
		#if defined(USE_FAST_IO)
		*csport &= ~cspinmask;
		#else
		digitalWrite(_cs, LOW);
		#endif
	}
}

inline void Arduino_GC9A01::DC_HIGH(void) {
	_DCbit = true;
#if defined(USE_FAST_IO)
  *dcport |= dcpinmask;
#else
  digitalWrite(_dc, HIGH);
#endif
}

inline void Arduino_GC9A01::DC_LOW(void) {
	_DCbit = false;
#if defined(USE_FAST_IO)
  *dcport &= ~dcpinmask;
#else
  digitalWrite(_dc, LOW);
#endif
}

void Arduino_GC9A01::test() {
   uint16_t x_start=100;
   uint16_t x_end=100;
   uint16_t y_start=100;
   uint16_t y_end=100;
   uint16_t color=GC9A01_BLACK;
   
  writecommand(GC9A01_CASET); // Column addr set
  writedata(x_start >> 8);
  writedata(x_start & 0xFF);     // XSTART 
  writedata(x_end >> 8);
  writedata(x_end & 0xFF);     // XEND

  writecommand(GC9A01_RASET); // Row addr set
  writedata(y_start >> 8);
  writedata(y_start & 0xFF);     // YSTART
  writedata(y_end >> 8);
  writedata(y_end & 0xFF);     // YEND

  writecommand(GC9A01_RAMWR); // write to RAM

  SPI_BEGIN_TRANSACTION();
  DC_HIGH();
  CS_LOW();

  spiwrite(byte(color >> 8));
  spiwrite(byte(color&0xFF));

  CS_HIGH();
  SPI_END_TRANSACTION();
}
void Arduino_GC9A01::init(uint16_t width, uint16_t height) {
  commonInit(NULL);
  _colstart = GC9A01_240x240_XSTART;
  _rowstart = GC9A01_240x240_YSTART;
  _height = 240;
  _width = 240;

  displayInit(cmd_240x240);

  //setRotation(2);
}
