#include "ili9320.h"

#include "../pins.h"
#include "../functions.h"

#include "gc320x240.h"

#define ILI9320_WIDTH           240
#define ILI9320_HEIGHT          320
#define ILI9320_PIXELS          (ILI9320_WIDTH * ILI9320_HEIGHT)

static uint8_t bus_requested = 0;

static GlcdDriver glcd = {
    .clear = ili9320Clear,
    .drawPixel = ili9320DrawPixel,
    .drawRectangle = ili9320DrawRectangle,
    .drawFontChar = ili9320DrawFontChar,
};

static inline void ili9320SendData(uint16_t data) __attribute__((always_inline));
static inline void ili9320SendData(uint16_t data)
{
    uint8_t dataH = data >> 8;
    uint8_t dataL = data & 0xFF;

    ILI9320_DHI_Port->BSRR = 0x00FF0000 | dataH;    // If port bits 7..0 are used
    CLR(ILI9320_WR);                                // Strob MSB
    SET(ILI9320_WR);
    ILI9320_DHI_Port->BSRR = 0x00FF0000 | dataL;    // If port bits 7..0 are used
    CLR(ILI9320_WR);                                // Strob LSB
    SET(ILI9320_WR);

    // If input IRQ requested bus status, switch temporarly to input mode and read bus
    if (bus_requested) {
        ILI9320_DHI_Port->BSRR = 0x000000FF;        // Set 1 on all data lines
        ILI9320_DHI_Port->CRL = 0x88888888;         // SET CNF=10, MODE=00 - Input pullup
        // Small delay to stabilize data before reading
        volatile uint8_t delay = 2;
        while (--delay);
        glcd.bus = ILI9320_DHI_Port->IDR & 0x00FF;   // Read 8-bit bus
        ILI9320_DHI_Port->CRL = 0x33333333;         // Set CNF=00, MODE=11 - Output push-pull 50 MHz
        bus_requested = 0;
    }
}

static inline void ili9320SelectReg(uint16_t reg) __attribute__((always_inline));
static inline void ili9320SelectReg(uint16_t reg)
{
    CLR(ILI9320_RS);
    ili9320SendData(reg);
    SET(ILI9320_RS);
}

static void ili9320WriteReg(uint16_t reg, uint16_t data)
{
    ili9320SelectReg(reg);
    ili9320SendData(data);
}

static inline void ili9320InitSeq(void)
{
    // Wait for reset
    _delay_ms(50);

    CLR(ILI9320_CS);

    ili9320WriteReg(0x0000, 0x0001);   // Start Oscillation
    _delay_ms(10);
    ili9320WriteReg(0x0007, 0x0000);   // Display control1
    ili9320WriteReg(0x0013, 0x0000);   // Power control4 setting
    ili9320WriteReg(0x0011, 0x2604);   // Power control2 setting
    ili9320WriteReg(0x0014, 0x0015);   // Power control5 setting
    ili9320WriteReg(0x0010, 0x3C00);   // Power control1 setting
    ili9320WriteReg(0x0013, 0x0040);   // Power control4 setting
    _delay_ms(10);
    ili9320WriteReg(0x0013, 0x0060);   // Power control4 setting
    _delay_ms(50);
    ili9320WriteReg(0x0013, 0x0070);   // Power control4 setting
    _delay_ms(40);

    ili9320WriteReg(0x0001, 0x0127);   // Driver output setting (240x320 mode, GS=0, SS=1)
    ili9320WriteReg(0x0002, 0x0700);   // LCD driving waveform setting
    ili9320WriteReg(0x0003, 0x1020);   // Entry mode setting (TRI=0, DFM=0, BGR=1, ID1:ID0 =11)
    ili9320WriteReg(0x0007, 0x0000);   // Display control1
    ili9320WriteReg(0x0008, 0x0202);   // Display control2
    ili9320WriteReg(0x000B, 0x0200);   // Frame cycle setting
    ili9320WriteReg(0x000C, 0x0000);   // External display interface setting
    ili9320WriteReg(0x00015,0x0000);

    //gamma setting
    ili9320WriteReg(0x0030, 0x0000);   // Gamma control1
    ili9320WriteReg(0x0031, 0x0606);   // Gamma control2
    ili9320WriteReg(0x0032, 0x0006);   // Gamma control3
    ili9320WriteReg(0x0033, 0x0403);   // Gamma control4
    ili9320WriteReg(0x0034, 0x0107);   // Gamma control5
    ili9320WriteReg(0x0035, 0x0101);   // Gamma control6
    ili9320WriteReg(0x0036, 0x0707);   // Gamma control7
    ili9320WriteReg(0x0037, 0x0304);   // Gamma control8
    ili9320WriteReg(0x0038, 0x0A00);   // Gamma control9
    ili9320WriteReg(0x0039, 0x0706);   // Gamma control10

    ili9320WriteReg(0x0040, 0x0000);   // Gate scan position (start G1 scan)
    ili9320WriteReg(0x0041, 0x0000);   // Vertical scroll setting
    ili9320WriteReg(0x0042, 0x013F);   // Screen end position (320-1)
    ili9320WriteReg(0x0043, 0x0000);   // Screen start position (0)
    ili9320WriteReg(0x0044, 0x0000);   // 2th Screen end position
    ili9320WriteReg(0x0045, 0x0000);   // 2th Screen start position
    ili9320WriteReg(0x0046, 0xEF00);   // window addr set for x0,x1 (0, 239)
    ili9320WriteReg(0x0047, 0x013F);   // window addr set for y0    (319)
    ili9320WriteReg(0x0048, 0x0000);   // window addr set for y1    (0)
    ili9320WriteReg(0x0007, 0x0011);   // Display control1
    _delay_ms(40);
    ili9320WriteReg(0x0007, 0x0017);   // Display control1

    SET(ILI9320_CS);
}

static inline void ili9320SetWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) __attribute__((always_inline));
static inline void ili9320SetWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
    ili9320WriteReg(0x0046, ((ILI9320_WIDTH - y - 1) <<8) + (ILI9320_WIDTH - y - h));
    ili9320WriteReg(0x0048, x);
    ili9320WriteReg(0x0047, x + w - 1);

    ili9320WriteReg(0x0020, ILI9320_WIDTH - y - 1);
    ili9320WriteReg(0x0021, x);
}

void ili9320Init(GlcdDriver **driver)
{
    *driver = &glcd;
    gc320x240Init(*driver);

    SET(ILI9320_LED);
    SET(ILI9320_RD);
    SET(ILI9320_WR);
    SET(ILI9320_RS);
    SET(ILI9320_CS);

    CLR(ILI9320_RESET);
    _delay_ms(1);
    SET(ILI9320_RESET);

    ili9320InitSeq();
}

void ili9320Clear(void)
{
    ili9320DrawRectangle(0, 0, glcd.canvas->width, glcd.canvas->height, LCD_COLOR_BLACK);
}

void ili9320BusIRQ(void)
{
    bus_requested = 1;
}

void ili9320Sleep(void)
{
    CLR(ILI9320_CS);

    ili9320WriteReg(0x0007, 0x0000); // Display OFF
    // Power Off Sequence
    ili9320WriteReg(0x0010, 0x0000); // SAP, BT[3:0], AP, DSTB, SLP, STB
    ili9320WriteReg(0x0011, 0x0000); // DC1[2:0], DC0[2:0], VC[2:0]
    ili9320WriteReg(0x0013, 0x0000); // VREG1OUT voltage

    ili9320WriteReg(0x0014, 0x0000); // VDV[4:0] for VCOM amplitude
    _delay_ms(200);
    ili9320WriteReg(0x0010, 0x0002); // SAP, BT[3:0], AP, DSTB, SLP, STB

    SET(ILI9320_CS);
}

void ili9320Wakeup(void)
{
    CLR(ILI9320_CS);

    // Power On Sequence
    ili9320WriteReg(0x0010, 0x0000); // SAP, BT[3:0], AP, DSTB, SLP, STB
    ili9320WriteReg(0x0011, 0x0000); // DC1[2:0], DC0[2:0], VC[2:0]
    ili9320WriteReg(0x0013, 0x0000); // VREG1OUT voltage
    ili9320WriteReg(0x0014, 0x0000); // VDV[4:0] for VCOM amplitude
    _delay_ms(200);
    ili9320WriteReg(0x0007, 0x0000);   // Display control1
    ili9320WriteReg(0x0013, 0x0000);   // Power control4 setting
    ili9320WriteReg(0x0011, 0x2604);   // Power control2 setting
    ili9320WriteReg(0x0014, 0x0015);   // Power control5 setting
    ili9320WriteReg(0x0010, 0x3C00);   // Power control1 setting
    ili9320WriteReg(0x0013, 0x0040);   // Power control4 setting
    _delay_ms(10);
    ili9320WriteReg(0x0013, 0x0060);   // Power control4 setting
    _delay_ms(50);
    ili9320WriteReg(0x0013, 0x0070);   // Power control4 setting
    _delay_ms(40);

    ili9320WriteReg(0x0007, 0x0173); // 262K color and display ON

    SET(ILI9320_CS);
}

void ili9320DrawPixel(int16_t x, int16_t y, uint16_t color)
{
    CLR(ILI9320_CS);

    ili9320SetWindow(x, y, 1, 1);

    ili9320SelectReg(0x0022);
    ili9320SendData(color);

    SET(ILI9320_CS);
}

void ili9320DrawRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color)
{
    CLR(ILI9320_CS);

    ili9320SetWindow(x, y, w, h);

    ili9320SelectReg(0x0022);
    for (uint32_t i = 0; i < w * h; i++)
        ili9320SendData(color);

    SET(ILI9320_CS);
}

void ili9320DrawFontChar(CharParam *param)
{
    uint8_t w = param->width;
    uint8_t h = glcd.font.data[FONT_HEIGHT];
    uint16_t x0 = glcd.canvas->x;
    uint16_t y0 = glcd.canvas->y;
    uint16_t color = glcd.font.color;
    uint16_t bgColor = glcd.canvas->color;
    uint8_t mult = glcd.font.mult;

    CLR(ILI9320_CS);

    ili9320SetWindow(x0, y0, mult * w, mult * h * 8);

    ili9320SelectReg(0x0022);
    for (uint16_t i = 0; i < w; i++) {
        for (uint8_t mx = 0; mx < mult; mx++) {
            for (uint16_t j = 0; j < h; j++) {
                uint8_t data = param->data[w * j + i];
                for (uint8_t bit = 0; bit < 8; bit++) {
                    for (uint8_t my = 0; my < mult; my++)
                        ili9320SendData(data & 0x01 ? color : bgColor);
                    data >>= 1;
                }
            }
        }
    }

    SET(ILI9320_CS);
}
