/***************************************************************************
 *   Copyright (C) 2024 - 2025 by Silvano Seva IU2KWO                      *
 *                                                                         *
 *   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/>   *
 ***************************************************************************/

#include <peripherals/gpio.h>
#include <interfaces/display.h>
#include <interfaces/delays.h>
#include <interfaces/platform.h>
#include <backlight.h>
#include <hwconfig.h>
#include <string.h>

enum ST775RCmd
{
    ST775R_CMD_NOP       = 0x00,
    ST775R_CMD_SWRESET   = 0x01,
    ST775R_CMD_RDID      = 0x04,
    ST775R_CMD_RDDST     = 0x09,
    ST775R_CMD_RDDPM     = 0x0A,
    ST775R_CMD_RDDMADCTL = 0x0B,
    ST775R_CMD_RDDCOLMOD = 0x0C,
    ST775R_CMD_RDDIM     = 0x0D,
    ST775R_CMD_RDDSM     = 0x0E,
    ST775R_CMD_SLPIN     = 0x10,
    ST775R_CMD_SLPOUT    = 0x11,
    ST775R_CMD_PTLON     = 0x12,
    ST775R_CMD_NORON     = 0x13,
    ST775R_CMD_INVOFF    = 0x20,
    ST775R_CMD_INVON     = 0x21,
    ST775R_CMD_GAMSET    = 0x26,
    ST775R_CMD_DISPOFF   = 0x28,
    ST775R_CMD_DISPON    = 0x29,
    ST775R_CMD_CASET     = 0x2A,
    ST775R_CMD_RASET     = 0x2B,
    ST775R_CMD_RAMWR     = 0x2C,
    ST775R_CMD_RGBSET    = 0x2D,
    ST775R_CMD_RAMRD     = 0x2E,
    ST775R_CMD_PTLAR     = 0x30,
    ST775R_CMD_TEOFF     = 0x34,
    ST775R_CMD_TEON      = 0x35,
    ST775R_CMD_MADCTL    = 0x36,
    ST775R_CMD_IDMOFF    = 0x38,
    ST775R_CMD_IDMON     = 0x39,
    ST775R_CMD_COLMOD    = 0x3A,
    ST775R_CMD_RDID1     = 0xDA,
    ST775R_CMD_RDID2     = 0xDB,
    ST775R_CMD_RDID3     = 0xDC
};

static inline void sendCmd(uint8_t cmd)
{
    // Set D/C low (command mode), clear WR and data lines
    GPIOD->BSRR = 0x30FF0000;
#ifdef PLATFORM_CS7000P
    asm volatile("           mov   r1, #65    \n"
#else
    asm volatile("           mov   r1, #21    \n"
#endif
                 "___loop_d: cmp   r1, #0     \n"
                 "           itt   ne         \n"
                 "           subne r1, r1, #1 \n"
                 "           bne   ___loop_d  \n":::"r1");
    GPIOD->BSRR = cmd;

    asm volatile("            mov   r1, #5     \n"
                 "___loop_1:  cmp   r1, #0     \n"
                 "            itt   ne         \n"
                 "            subne r1, r1, #1 \n"
                 "            bne   ___loop_1  \n":::"r1");
    GPIOD->BSRR = (1 << 13);
}

static inline void sendData(uint8_t val)
{
    // Set D/C high (data mode), clear WR and data lines
    GPIOD->BSRR = 0x20FF1000;
#ifdef PLATFORM_CS7000P
    asm volatile("           mov   r1, #65    \n"
#else
    asm volatile("           mov   r1, #21    \n"
#endif
                 "___loop_e: cmp   r1, #0     \n"
                 "           itt   ne         \n"
                 "           subne r1, r1, #1 \n"
                 "           bne   ___loop_e  \n":::"r1");
    GPIOD->BSRR = val;

    asm volatile("            mov   r1, #5     \n"
                 "___loop_2:  cmp   r1, #0     \n"
                 "            itt   ne         \n"
                 "            subne r1, r1, #1 \n"
                 "            bne   ___loop_2  \n":::"r1");
    GPIOD->BSRR = (1 << 13);
}

void display_init()
{
    backlight_init();

    /* Set up gpios */
    gpio_setMode(LCD_D0,  OUTPUT);
    gpio_setMode(LCD_D1,  OUTPUT);
    gpio_setMode(LCD_D2,  OUTPUT);
    gpio_setMode(LCD_D3,  OUTPUT);
    gpio_setMode(LCD_D4,  OUTPUT);
    gpio_setMode(LCD_D5,  OUTPUT);
    gpio_setMode(LCD_D6,  OUTPUT);
    gpio_setMode(LCD_D7,  OUTPUT);
    gpio_setMode(LCD_WR,  OUTPUT);
    gpio_setMode(LCD_RD,  OUTPUT);
    gpio_setMode(LCD_DC,  OUTPUT);
    gpio_setMode(LCD_RST, OUTPUT);
    gpio_setMode(LCD_CS,  OUTPUT);

    gpio_setOutputSpeed(LCD_D0,  HIGH);
    gpio_setOutputSpeed(LCD_D1,  HIGH);
    gpio_setOutputSpeed(LCD_D2,  HIGH);
    gpio_setOutputSpeed(LCD_D3,  HIGH);
    gpio_setOutputSpeed(LCD_D4,  HIGH);
    gpio_setOutputSpeed(LCD_D5,  HIGH);
    gpio_setOutputSpeed(LCD_D6,  HIGH);
    gpio_setOutputSpeed(LCD_D7,  HIGH);
    gpio_setOutputSpeed(LCD_WR,  HIGH);
    gpio_setOutputSpeed(LCD_RD,  HIGH);
    gpio_setOutputSpeed(LCD_DC,  HIGH);
    gpio_setOutputSpeed(LCD_RST, HIGH);
    gpio_setOutputSpeed(LCD_CS,  HIGH);

    gpio_clearPin(LCD_RST); /* Put LCD in reset mode       */
    gpio_setPin(LCD_CS);    /* CS idle state is high level */
    gpio_setPin(LCD_DC);    /* Idle state for DC line      */
    gpio_setPin(LCD_WR);    /* Idle state for WR line      */
    gpio_setPin(LCD_RD);    /* Idle state for RD line      */
    gpio_setPin(LCD_RST);   /* Exit from reset             */

    delayMs(10);

    gpio_clearPin(LCD_CS);

    sendCmd(ST775R_CMD_SWRESET);
    sendCmd(0xb1);    /* Undocumented command */
    sendData(5);
    sendData(8);
    sendData(5);
    sendCmd(0xb2);    /* Undocumented command */
    sendData(5);
    sendData(8);
    sendData(5);
    sendCmd(0xb3);    /* Undocumented command */
    sendData(5);
    sendData(8);
    sendData(5);
    sendData(5);
    sendData(8);
    sendData(5);
    sendCmd(0xb4);    /* Undocumented command */
    sendData(0);
    sendCmd(0xb6);    /* Undocumented command */
    sendData(0xb4);
    sendData(0xf0);
    sendCmd(0xc0);    /* Undocumented command */
    sendData(0xa2);
    sendData(2);
    /* sendData(0x85); TODO: see original fw  */
    sendData(0x84);
    sendCmd(0xc1);    /* Undocumented command */
    sendData(5);
    sendCmd(0xc2);    /* Undocumented command */
    sendData(10);
    sendData(0);
    sendCmd(0xc3);    /* Undocumented command */
    sendData(0x8a);
    sendData(0x2a);
    sendCmd(0xc4);    /* Undocumented command */
    sendData(0x8a);
    sendData(0xee);
    sendCmd(0xc5);    /* Undocumented command */
    sendData(0xe);
    sendCmd(ST775R_CMD_MADCTL);
    sendData(0xB0);
    sendCmd(0xe0);    /* Undocumented command */
    sendData(5);
    /* sendData(0x28); TODO: see original fw  */
    /* sendData(0x28); TODO: see original fw  */
    sendData(0x16);
    sendData(0xf);
    sendData(0x18);
    sendData(0x2f);
    sendData(0x28);
    sendData(0x20);
    sendData(0x22);
    sendData(0x1f);
    sendData(0x1b);
    sendData(0x23);
    sendData(0x37);
    sendData(0);
    sendData(7);
    sendData(2);
    sendData(0x10);
    sendCmd(0xe1);    /* Undocumented command */
    sendData(7);
    /* sendData(0x28); TODO: see original fw  */
    /* sendData(0x28); TODO: see original fw  */
    sendData(0x1b);
    sendData(0xf);
    sendData(0x17);
    sendData(0x33);
    sendData(0x2c);
    sendData(0x29);
    sendData(0x2e);
    sendData(0x30);
    sendData(0x30);
    sendData(0x39);
    sendData(0x3f);
    sendData(0);
    sendData(7);
    sendData(3);
    sendData(0x10);
    sendCmd(0xf0);    /* Undocumented command */
    sendData(1);
    sendCmd(0xf6);    /* Undocumented command */
    sendData(0);
    sendCmd(ST775R_CMD_COLMOD);
    sendData(0x05);   /* 16bpp - RGB 565      */
    sendCmd(ST775R_CMD_RASET);
    sendData(0);
    sendData(0);
    sendData(0);
    sendData(0x7f);   /* 128 rows             */
    sendCmd(ST775R_CMD_CASET);
    sendData(0);
    sendData(0);
    sendData(0);
    sendData(0x9f);   /* 160 columns          */

    /* Exit from sleep */
    sendCmd(ST775R_CMD_SLPOUT);
    delayMs(120);

    /* Enable display */
    sendCmd(ST775R_CMD_DISPON);

    gpio_setPin(LCD_CS);
}

void display_terminate()
{
}

void display_renderRows(uint8_t startRow, uint8_t endRow, void *fb)
{
    /*
     * Put screen data lines back to output mode, since they are in common with
     * keyboard buttons and the keyboard driver sets them as inputs.
     */
    gpio_setMode(LCD_D0, OUTPUT);
    gpio_setMode(LCD_D1, OUTPUT);
    gpio_setMode(LCD_D2, OUTPUT);
    gpio_setMode(LCD_D3, OUTPUT);
    gpio_setMode(LCD_D4, OUTPUT);
    gpio_setMode(LCD_D5, OUTPUT);
    gpio_setMode(LCD_D6, OUTPUT);
    gpio_setMode(LCD_D7, OUTPUT);

    /*
     * Select the display, configure start and end rows in display driver and
     * write to display memory.
     */
    gpio_clearPin(LCD_CS);

    sendCmd(ST775R_CMD_RASET);
    sendData(0x00);
    sendData(startRow);
    sendData(0x00);
    sendData(endRow);
    sendCmd(ST775R_CMD_RAMWR);

    for(uint8_t y = startRow; y < endRow; y++)
    {
        for(uint8_t x = 0; x < CONFIG_SCREEN_WIDTH; x++)
        {
            size_t pos = x + y * CONFIG_SCREEN_WIDTH;
            uint16_t pixel = ((uint16_t *) fb)[pos];
            sendData((pixel >> 8) & 0xff);
            sendData(pixel & 0xff);
        }
    }

    gpio_setPin(LCD_CS);
}

void display_render(void *fb)
{
    display_renderRows(0, CONFIG_SCREEN_HEIGHT, fb);
}

void display_setContrast(uint8_t contrast)
{
    /* This controller does not support contrast regulation */
    (void) contrast;
}
