#include <stdint.h>
/*
Minimal helper library for the 320x240 TFT LCD, with ILI9341 controller chip
Required configuration, example:
#define DISPLAY_SPI spi0

#define PIN_TFT_BACKLIGHT 1
#define PIN_TFT_SCK 2
#define PIN_TFT_MOSI 3
#define PIN_TFT_CS 5 // optional
#define PIN_TFT_DC 6
#define PIN_TFT_RESET 7

*/
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t


#define DC_C sio_hw->gpio_clr = (1ul << PIN_TFT_DC)
#define DC_D sio_hw->gpio_set = (1ul << PIN_TFT_DC)
#define WAIT_FOR_BUSY while (spi_get_hw(DISPLAY_SPI)->sr & SPI_SSPSR_BSY_BITS){};
#define tft_Write_8N(B) while (!spi_is_writable(DISPLAY_SPI)){}; spi_get_hw(DISPLAY_SPI)->dr = (uint8_t)(B)

#define COLOR_BLACK 0x0000       //   0,   0,   0
#define COLOR_NAVY 0x000F        //   0,   0, 123
#define COLOR_DARKGREEN 0x03E0   //   0, 125,   0
#define COLOR_DARKCYAN 0x03EF    //   0, 125, 123
#define COLOR_MAROON 0x7800      // 123,   0,   0
#define COLOR_PURPLE 0x780F      // 123,   0, 123
#define COLOR_OLIVE 0x7BE0       // 123, 125,   0
#define COLOR_LIGHTGREY 0xC618   // 198, 195, 198
#define COLOR_DARKGREY 0x7BEF    // 123, 125, 123
#define COLOR_BLUE 0x001F        //   0,   0, 255
#define COLOR_GREEN 0x07E0       //   0, 255,   0
#define COLOR_CYAN 0x07FF        //   0, 255, 255
#define COLOR_RED 0xF800         // 255,   0,   0
#define COLOR_MAGENTA 0xF81F     // 255,   0, 255
#define COLOR_YELLOW 0xFFE0      // 255, 255,   0
#define COLOR_WHITE 0xFFFF       // 255, 255, 255
#define COLOR_ORANGE 0xFD20      // 255, 165,   0
#define COLOR_GREENYELLOW 0xAFE5 // 173, 255,  41
#define COLOR_PINK 0xFC18        // 255, 130, 198

// #define ILI9341_TFTWIDTH 240  // ILI9341 max TFT width
// #define ILI9341_TFTHEIGHT 320 // ILI9341 max TFT height
// with swapped vertical/horizontal memory access, making it landscape mode
#define ILI9341_TFTWIDTH 320
#define ILI9341_TFTHEIGHT 240

#define ILI9341_NOP 0x00     // No-op register
#define ILI9341_SWRESET 0x01 // Software reset register
#define ILI9341_RDDID 0x04   // Read display identification information
#define ILI9341_RDDST 0x09   // Read Display Status

#define ILI9341_SLPIN 0x10  // Enter Sleep Mode
#define ILI9341_SLPOUT 0x11 // Sleep Out
#define ILI9341_PTLON 0x12  // Partial Mode ON
#define ILI9341_NORON 0x13  // Normal Display Mode ON

#define ILI9341_RDMODE 0x0A     // Read Display Power Mode
#define ILI9341_RDMADCTL 0x0B   // Read Display MADCTL
#define ILI9341_RDPIXFMT 0x0C   // Read Display Pixel Format
#define ILI9341_RDIMGFMT 0x0D   // Read Display Image Format
#define ILI9341_RDSELFDIAG 0x0F // Read Display Self-Diagnostic Result

#define ILI9341_INVOFF 0x20   // Display Inversion OFF
#define ILI9341_INVON 0x21    // Display Inversion ON
#define ILI9341_GAMMASET 0x26 // Gamma Set
#define ILI9341_DISPOFF 0x28  // Display OFF
#define ILI9341_DISPON 0x29   // Display ON

#define ILI9341_CASET 0x2A // Column Address Set
#define ILI9341_PASET 0x2B // Page Address Set
#define ILI9341_RAMWR 0x2C // Memory Write
#define ILI9341_RAMRD 0x2E // Memory Read

#define ILI9341_PTLAR 0x30    // Partial Area
#define ILI9341_VSCRDEF 0x33  // Vertical Scrolling Definition
#define ILI9341_MADCTL 0x36   // Memory Access Control
#define ILI9341_VSCRSADD 0x37 // Vertical Scrolling Start Address
#define ILI9341_PIXFMT 0x3A   // COLMOD: Pixel Format Set

#define ILI9341_FRMCTR1 0xB1 // Frame Rate Control (In Normal Mode/Full Colors)
#define ILI9341_FRMCTR2 0xB2 // Frame Rate Control (In Idle Mode/8 colors)
#define ILI9341_FRMCTR3 0xB3 // Frame Rate control (In Partial Mode/Full Colors)
#define ILI9341_INVCTR 0xB4  // Display Inversion Control
#define ILI9341_DFUNCTR 0xB6 // Display Function Control

#define ILI9341_PWCTR1 0xC0 // Power Control 1
#define ILI9341_PWCTR2 0xC1 // Power Control 2
#define ILI9341_PWCTR3 0xC2 // Power Control 3
#define ILI9341_PWCTR4 0xC3 // Power Control 4
#define ILI9341_PWCTR5 0xC4 // Power Control 5
#define ILI9341_VMCTR1 0xC5 // VCOM Control 1
#define ILI9341_VMCTR2 0xC7 // VCOM Control 2

#define ILI9341_RDID1 0xDA // Read ID 1
#define ILI9341_RDID2 0xDB // Read ID 2
#define ILI9341_RDID3 0xDC // Read ID 3
#define ILI9341_RDID4 0xDD // Read ID 4

#define ILI9341_GMCTRP1 0xE0 // Positive Gamma Correction
#define ILI9341_GMCTRN1 0xE1 // Negative Gamma Correction

static const uint8_t initcmd[] = {
	0xEF, 3, 0x03, 0x80, 0x02, // undocumented mystery command
	0xCF, 3, 0x00, 0xC1, 0x30,             // power control B
	0xED, 4, 0x64, 0x03, 0x12, 0x81,       // power on sequence control
	0xE8, 3, 0x85, 0x00, 0x78,             // driver timing control A
	0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02, // power control A
	0xF7, 1, 0x20,                         // pump ratio control
	0xEA, 2, 0x00, 0x00,                   // driver timing control B
	ILI9341_PWCTR1  , 1, 0x23,             // Power control VRH[5:0]
	ILI9341_PWCTR2  , 1, 0x10,             // Power control SAP[2:0];BT[3:0]
	ILI9341_VMCTR1  , 2, 0x3e, 0x28,       // VCOM control 1
	ILI9341_VMCTR2  , 1, 0x86,             // VCOM control 2
	ILI9341_MADCTL  , 1, 0x48,             // Memory Access Control, sets access order
	// ILI9341_VSCRSADD, 1, 0x00,             // Vertical scroll zero
	ILI9341_PIXFMT  , 1, 0x55,             // pixel format, 16bpp (RGB565)
	// 0xF6, 3, 0x01, 0x00, 0x02, // enable RGB mode
	ILI9341_FRMCTR1 , 2, 0x00, 0x13,       // frame rate control, division ratio 2^0, 79hz(why??)
	ILI9341_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control,
	0xF2, 1, 0x00,                         // 3Gamma Function Disable
	ILI9341_GAMMASET , 1, 0x01,             // Gamma curve selected
	ILI9341_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
		0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00,
	ILI9341_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
		0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F,
	ILI9341_SLPOUT  , 0x80,                // Exit Sleep
	ILI9341_DISPON  , 0,                // Display on
	0x00                                   // End of list
};

void tft_send_command(u8 cmd, const u8 *args, size_t arg_count) {
	#ifdef PIN_TFT_CS
		gpio_put(PIN_TFT_CS, 0);
	#endif
	DC_C;
	spi_write_blocking(DISPLAY_SPI, &cmd, 1);
	if (arg_count) {
		DC_D;
		spi_write_blocking(DISPLAY_SPI, args, arg_count);
	}
	#ifdef PIN_TFT_CS
		gpio_put(PIN_TFT_CS, 1);
	#endif
}

void tft_init_pins() {
	gpio_init(PIN_TFT_BACKLIGHT);
	gpio_set_dir(PIN_TFT_BACKLIGHT, GPIO_OUT);

	gpio_set_dir(PIN_TFT_SCK, GPIO_OUT);
	gpio_set_function(PIN_TFT_SCK, GPIO_FUNC_SPI);

	gpio_set_dir(PIN_TFT_MOSI, GPIO_OUT);
	gpio_set_function(PIN_TFT_MOSI, GPIO_FUNC_SPI);

	gpio_set_function(PIN_TFT_DC, GPIO_FUNC_SIO);
	gpio_set_dir(PIN_TFT_DC, GPIO_OUT);
	gpio_put(PIN_TFT_DC, 1);

	#ifdef PIN_TFT_CS
		gpio_init(PIN_TFT_CS);
		gpio_set_dir(PIN_TFT_CS, GPIO_OUT);
	#endif

	gpio_set_function(PIN_TFT_RESET, GPIO_FUNC_SIO);
	gpio_set_dir(PIN_TFT_RESET, GPIO_OUT);
	gpio_put(PIN_TFT_RESET, 1);
}

void tft_init_display(uint baudrate) {
	tft_init_pins();
	spi_init(DISPLAY_SPI, baudrate);

	// reset display
	gpio_put(PIN_TFT_RESET, 1);
	sleep_ms(5);
	gpio_put(PIN_TFT_RESET, 0);
	sleep_ms(20);
	gpio_put(PIN_TFT_RESET, 1);
	sleep_ms(150);

	// init display
	const u8 *addr = initcmd;
	while (1) {
		u8 cmd = *(addr++);
		if (cmd == 0) break;
		u8 x = *(addr++);
		u8 arg_count = x & 0x7f;
		tft_send_command(cmd, addr, arg_count);
		addr += arg_count;
		if (x & 0x80)
			sleep_ms(120);
	}
	sleep_ms(150);
	gpio_put(PIN_TFT_BACKLIGHT, 1);
}

void tft_set_pixel(int x, int y, u32 color) {
	tft_send_command(ILI9341_CASET, (u8[]){x >> 8, x, x >> 8, x}, 4);
	tft_send_command(ILI9341_PASET, (u8[]){y >> 8, y, y >> 8, y}, 4);
	DC_C;
	spi_get_hw(DISPLAY_SPI)->dr = ILI9341_RAMWR;
	WAIT_FOR_BUSY;
	DC_D;
	tft_Write_8N((u8)(color >> 8));
	tft_Write_8N((u8)(color & 0xff));
	WAIT_FOR_BUSY;
}

void tft_fill(u16 color) {
	tft_send_command(ILI9341_CASET, (u8[]){0, 0, ILI9341_TFTWIDTH >> 8, (u8)ILI9341_TFTWIDTH}, 4);
	tft_send_command(ILI9341_PASET, (u8[]){0, 0, ILI9341_TFTHEIGHT >> 8, (u8)ILI9341_TFTHEIGHT}, 4);
	DC_C;
	spi_get_hw(DISPLAY_SPI)->dr = ILI9341_RAMWR;
	WAIT_FOR_BUSY;
	DC_D;

	u8 upper = color >> 8;
	u8 lower = color;
	u32 pixel_count = ILI9341_TFTWIDTH * ILI9341_TFTHEIGHT;

	while (pixel_count--) {
		tft_Write_8N(upper);
		tft_Write_8N(lower);
	}
	WAIT_FOR_BUSY;
}
