/*
 * KDP ST7789 Panel driver
 *
 * Copyright (C) 2019 Kneron, Inc. All rights reserved.
 *
 */

#include "kdrv_gdma.h"
#include "kdrv_display.h"
#include "kdrv_lcm.h"
#include "kdrv_pwm.h"
#include "kl520_include.h"
#include "kdev_status.h"


osMutexId_t mutex_st7789 = NULL;
osMutexId_t mutex_snapshot = NULL;

#define TE_ENABLE
#define PORTRAIT_DMA_ENABLE

#define FRAME_RATE_60HZ 0
#define FRAME_RATE_39HZ 1
#define FRAME_RATE_CTRL FRAME_RATE_60HZ

#define RAM_ENDIAN_MSB  0
#define RAM_ENDIAN_LSB  1
#define RAM_ENDIAN_TYPE RAM_ENDIAN_MSB


/* 
This command is used to transfer data from MCU to frame memory.
-When this command is accepted, the column register and the page register are reset to the start column/start
page positions.
-The start column/start page positions are different in accordance with MADCTL setting.
-Sending any other command can stop frame write.
*/
#define PANEL_REG_RAMWR             0x2C //Memory write
#define PANEL_REG_WRMEMC            0x3C //Write memory continue

//#define ST7789_LANDSCAPE
#define ST7789_PORTRAIT

#ifdef ST7789_PORTRAIT
    //#define	ST7789_PORTRAIT_PARTIAL
    //#define ZOOM_IN_RATIO				3/2
#endif

#ifdef ST7789_LANDSCAPE
#define ST7789_PANEL_WIDTH          QVGA_LANDSCAPE_WIDTH
#define ST7789_PANEL_HEIGHT         QVGA_LANDSCAPE_HEIGHT
#else
#define ST7789_PANEL_WIDTH          QVGA_PORTRAIT_WIDTH
#define ST7789_PANEL_HEIGHT         QVGA_PORTRAIT_HEIGHT
#endif
#define ST7789_PANEL_TOTAL_PIXEL    ST7789_PANEL_WIDTH * ST7789_PANEL_HEIGHT
#define OPEN_DOWN_SCALING
#define BYTES_PER_PIXEL				2

#ifdef PORTRAIT_DMA_ENABLE
#undef TE_ENABLE
#undef FRAME_RATE_CTRL
#undef RAM_ENDIAN_TYPE

#define FRAME_RATE_CTRL FRAME_RATE_39HZ
#define RAM_ENDIAN_TYPE RAM_ENDIAN_LSB
#define TE_ENABLE

#endif

kdev_status_t kdev_panel_initialize(kdrv_display_t *display_drv)
{
    if(display_drv == NULL)
        return KDEV_STATUS_ERROR;

    if(mutex_st7789 == NULL)
        mutex_st7789 = osMutexNew(NULL);
    
    if(mutex_snapshot == NULL)
        mutex_snapshot = osMutexNew(NULL);
    
    uint32_t base = display_drv->base;
//    u16 hor_no_in = display_drv->vi_params.input_xres;
//    u16 ver_no_in = display_drv->vi_params.input_yres;
//    u16 hor_no_out = ST7789_PANEL_WIDTH;//MZT_PANEL_WIDTH;
//    u16 ver_no_out = ST7789_PANEL_HEIGHT;//MZT_PANEL_HEIGHT;
//    DSG("[%s] hor_no_in=%u ver_no_in=%u hor_no_out=%u ver_no_out=%u",
//    __func__, hor_no_in, ver_no_in, hor_no_out, ver_no_out);

    kdrv_lcm_write_cmd(base, 0x11); //Exit Sleep
    //kdrv_delay_us(100);
    kdrv_pwmtimer_delay_ms(10);

    //-----------Display and color format setting-------------//
    // Column Address Set
    kdrv_lcm_write_cmd(base, 0x2A); 
    // XS
    kdrv_lcm_write_data(base, 0x00);
    kdrv_lcm_write_data(base, 0x00);
    // XE
#ifdef ST7789_LANDSCAPE
    kdrv_lcm_write_data(base, 0x01);
    kdrv_lcm_write_data(base, 0x3F);
#else
    kdrv_lcm_write_data(base, 0x00);
    kdrv_lcm_write_data(base, 0xEF);
#endif

    // Row Address Set
    kdrv_lcm_write_cmd(base, 0x2B);
#ifdef ST7789_LANDSCAPE
    // YS
    kdrv_lcm_write_data(base, 0x00);
    kdrv_lcm_write_data(base, 0x00);
    // YE
    kdrv_lcm_write_data(base, 0x00);
    kdrv_lcm_write_data(base, 0xEF);
#else
    #ifdef ST7789_PORTRAIT_PARTIAL
      // YS
    kdrv_lcm_write_data(base, 0x00);
    kdrv_lcm_write_data(base, 0x40);
    // YE
    kdrv_lcm_write_data(base, 0x01);
    kdrv_lcm_write_data(base, 0x00);
    #else
      // YS
    kdrv_lcm_write_data(base, 0x00);
    kdrv_lcm_write_data(base, 0x00);
    // YE
    kdrv_lcm_write_data(base, 0x01);
    kdrv_lcm_write_data(base, 0x3F);
    #endif
#endif
    
#ifdef ST7789_PORTRAIT_PARTIAL
    kdrv_lcm_write_cmd(base, 0x13);
    kdrv_lcm_write_data(base, 0x00);
    kdrv_lcm_write_cmd(base, 0x12);
    kdrv_lcm_write_data(base, 0xFF);
    kdrv_lcm_write_cmd(base, 0x30);
    kdrv_lcm_write_data(base, 0x00);
    kdrv_lcm_write_data(base, 0x40);
    kdrv_lcm_write_data(base, 0x01);
    kdrv_lcm_write_data(base, 0x00);
#endif

#ifdef TE_ENABLE
    kdrv_lcm_write_cmd(base, 0x35); // Enable Tearing effect
    kdrv_lcm_write_data(base, 0x00);
#endif

    kdrv_lcm_write_cmd(base, 0x36); // Memory Access Control
#ifdef ST7789_LANDSCAPE
    // xy_exchange / y_inverse
    //(Rotate positive 90 degrees)
    //MY = 1, MX = 0, MV = 1, ML = 0
    kdrv_lcm_write_data(base, 0xA0); 
#else
    //MY = 1, MX = 1, MV = 0, ML = 0 
    //0820,Tei/Jeff
    {
       //ops->write_data(base, 0x40); //image from camera
        kdrv_lcm_write_data(base, 0x00); //image from flash
    }
#endif

    // Interface Pixel Format
    kdrv_lcm_write_cmd(base, 0x3a);//DPI=101  16bit RGB
    kdrv_lcm_write_data(base, 0x55);
    //ops->write_data(base, 0x05);
    //-------------ST7789V Porch setting-----------//
    kdrv_lcm_write_cmd(base, 0xb2);
    kdrv_lcm_write_data(base, 0x0C); // Back porch (normal)
    kdrv_lcm_write_data(base, 0x0C); // Front porch (normal)
    kdrv_lcm_write_data(base, 0x00); // 
    kdrv_lcm_write_data(base, 0X33); // Back porch (idle) + Front porch (idle)
    kdrv_lcm_write_data(base, 0X33); // Back porch (partial) + Front porch (partial)
    
    // Gate Control. 13.26, -10.43
    kdrv_lcm_write_cmd(base, 0xb7);
    kdrv_lcm_write_data(base, 0x35);//(0x71);
    
    //Setting limitation: VCOMS+VCOMS offset+VDV=0.1V~1.675V.
    // VCOMS Setting. used for feed through voltage compensation
    // 1.175V
    kdrv_lcm_write_cmd(base, 0xBB);
    kdrv_lcm_write_data(base, 0x2b);//(0x25);
    
    // LCM Control ???
    kdrv_lcm_write_cmd(base, 0xC0); 
    kdrv_lcm_write_data(base, 0x2c); 
    
    // VDV and VRH command enable
    kdrv_lcm_write_cmd(base, 0xC2); 
    kdrv_lcm_write_data(base, 0x01);
    
    // VRH Set
    kdrv_lcm_write_cmd(base, 0xC3); 
    kdrv_lcm_write_data(base, 0x11);//(0X14);//
    
    // VDVS Set
    kdrv_lcm_write_cmd(base, 0xC4); 
    kdrv_lcm_write_data(base, 0x20); // 0
    
    // Frame rate control in normal mode
#if FRAME_RATE_CTRL == FRAME_RATE_60HZ
    kdrv_lcm_write_cmd(base, 0xC6); 
    kdrv_lcm_write_data(base, 0x0F);
#elif FRAME_RATE_CTRL == FRAME_RATE_39HZ
    kdrv_lcm_write_cmd(base, 0xC6); 
    kdrv_lcm_write_data(base, 0x1F);
#endif

    // Power Control 1
    kdrv_lcm_write_cmd(base, 0xd0); 
    kdrv_lcm_write_data(base, 0xa4);
    kdrv_lcm_write_data(base, 0xa1); // AVDD : 6.8V, AVCL : -4.6V, VDDS : 2.3 V

    //---------------ST7789V gamma setting-------------//
    { // Positive Voltage gamma control
        kdrv_lcm_write_cmd(base, 0xE0); 
        kdrv_lcm_write_data(base, 0xd0);
        kdrv_lcm_write_data(base, 0x08);
        kdrv_lcm_write_data(base, 0x0e);
        kdrv_lcm_write_data(base, 0x0a);
        kdrv_lcm_write_data(base, 0x0a);
        kdrv_lcm_write_data(base, 0x06);
        kdrv_lcm_write_data(base, 0x38);
        kdrv_lcm_write_data(base, 0x44);
        kdrv_lcm_write_data(base, 0x50);
        kdrv_lcm_write_data(base, 0x29);
        kdrv_lcm_write_data(base, 0x15);
        kdrv_lcm_write_data(base, 0x16);
        kdrv_lcm_write_data(base, 0x33);
        kdrv_lcm_write_data(base, 0x36);
    }
    { // Negative Boltage gamma control
        kdrv_lcm_write_cmd(base, 0XE1); 
        kdrv_lcm_write_data(base, 0xd0);
        kdrv_lcm_write_data(base, 0x07);
        kdrv_lcm_write_data(base, 0x0d);
        kdrv_lcm_write_data(base, 0x09);
        kdrv_lcm_write_data(base, 0x08);
        kdrv_lcm_write_data(base, 0x06);
        kdrv_lcm_write_data(base, 0x33);
        kdrv_lcm_write_data(base, 0x33);
        kdrv_lcm_write_data(base, 0x4d);
        kdrv_lcm_write_data(base, 0x28); 
        kdrv_lcm_write_data(base, 0x16);
        kdrv_lcm_write_data(base, 0x15);
        kdrv_lcm_write_data(base, 0x33);
        kdrv_lcm_write_data(base, 0x35);   
    }

    kdrv_lcm_write_cmd(base, 0x21); // Display Inversion On ??
    
    // RAM Control
#if RAM_ENDIAN_TYPE == RAM_ENDIAN_MSB
    kdrv_lcm_write_cmd(base, 0xB0);
    kdrv_lcm_write_data(base, 0x00); // Ram access from MCU interface
    kdrv_lcm_write_data(base, 0xd0); // Normal Endian (MSB first), 18 bit bus width
#elif RAM_ENDIAN_TYPE == RAM_ENDIAN_LSB
    kdrv_lcm_write_cmd(base, 0xB0);
    kdrv_lcm_write_data(base, 0x00); // Ram access from MCU interface
    kdrv_lcm_write_data(base, 0xd8); // Normal Endian (MSB first), 18 bit bus width    
#endif
    
    kdrv_lcm_write_cmd(base, 0x29); //Display on
    //ops->write_cmd(base, 0x28); //Display off

    //_st7789_240x320_clear(display_drv, YELLOW);//WHITE);

    return KDEV_STATUS_OK;
}

kdev_status_t kdev_panel_clear(kdrv_display_t *display_drv, u32 color)
{
    int i;

    if(display_drv == NULL)
        return KDEV_STATUS_ERROR;

    kdrv_lcm_write_cmd(display_drv->base, PANEL_REG_WRMEMC);
    for(i = 0; i < ST7789_PANEL_TOTAL_PIXEL; ++i)
    {
        kdrv_lcm_write_data(display_drv->base, (u8)((((color & 0xFF00) >>8 ) & 0xFF)));
        kdrv_lcm_write_data(display_drv->base, (u8)(color & 0x00FF));
    }
    return KDEV_STATUS_OK;
}

uint16_t kdev_panel_read_display_id(kdrv_display_t *display_drv)
{
    uint32_t base;
    uint16_t id = 0;

    if(display_drv == NULL)
        return 0xFFFF;

    base = display_drv->base;
    
    kdrv_lcm_write_cmd(base, 0x04);
    id = kdrv_lcm_read_data(base);  //dummy read 
    id = kdrv_lcm_read_data(base); 
    id = ((kdrv_lcm_read_data(base) & 0xBF) << 8);
    id |= kdrv_lcm_read_data(base);
    return id;
    
}

kdev_status_t kdev_panel_refresh(kdrv_display_t* display_drv)
{
    uint32_t base;
    uint32_t readAddr = kdrv_lcm_get_db_frame();
    uint16_t lcm_w = display_drv->vi_params.input_xres;
    
#ifdef ST7789_PORTRAIT_PARTIAL
    uint16_t lcm_h = 192;//ST7789_PANEL_HEIGHT;
#else
    uint16_t lcm_h = display_drv->vi_params.input_yres;
#endif

    if(display_drv == NULL)
        return KDEV_STATUS_ERROR;

   base = display_drv->base;
#if 0//ndef ST7789_PORTRAIT_PARTIAL
    //home point : (0, 0)
    kdrv_display_write_cmd(base, 0x2A);
    kdrv_display_write_data(base, 0x00);
    kdrv_display_write_data(base, 0x00);
    kdrv_display_write_data(base, 0x00);
    kdrv_display_write_data(base, 0xEF);
    kdrv_display_write_cmd(base, 0x2B);
    kdrv_display_write_data(base, 0x00);
    kdrv_display_write_data(base, 0x00);
    kdrv_display_write_data(base, 0x01);
    kdrv_display_write_data(base, 0x3F);
#endif
    osThreadId_t tid = osThreadGetId();
    osPriority_t save_pri = osThreadGetPriority(tid);
    
    kdrv_lcm_write_cmd(base, PANEL_REG_RAMWR);
    
    //kdp520_gpio_setedgemode( 1 << GPIO_16, 0);
    
    //osThreadSetPriority(tid, save_pri+1);
    //TODO: (save_pri+1) is dangerous, below workaround is just to remove warning,
    //      programmer must fix this.
    osThreadSetPriority(tid, (osPriority_t)((uint32_t)save_pri+1));
    
    //kdp520_gpio_enableint( GPIO_16, GPIO_EDGE, false);
    //kdp520_wait_gpio_int(1<<GPIO_16);    
    //kdp520_gpio_disableint(GPIO_16);

    static bool dma_configured = false;
    static kdrv_gdma_handle_t dma_handle;

    if(!dma_configured)
    {
        kdrv_gdma_acquire_handle(&dma_handle);

        gdma_setting_t dma_setting;
        dma_setting.dst_width = GDMA_TXFER_WIDTH_8_BITS;
        dma_setting.src_width = GDMA_TXFER_WIDTH_32_BITS;
        dma_setting.burst_size = GDMA_BURST_SIZE_32;
        dma_setting.dst_addr_ctrl = GDMA_FIXED_ADDRESS;
        dma_setting.src_addr_ctrl = GDMA_INCREMENT_ADDRESS;
        dma_setting.dma_mode = GDMA_NORMAL_MODE;
        dma_setting.dma_dst_req = 0;
        dma_setting.dma_src_req = 0;
        kdrv_gdma_configure_setting(dma_handle, &dma_setting);

        dma_configured = true;
    }
    kdrv_gdma_transfer(dma_handle, (uint32_t)0xC340020c, (uint32_t)(readAddr), lcm_w*lcm_h*2);
    osThreadSetPriority(tid, save_pri);

    return KDEV_STATUS_OK;
}

