/**************************************************************************//**
 * @file     main.c
 * @brief    SpiLoader source file
 *
 * SPDX-License-Identifier: Apache-2.0
 * @copyright (C) 2020 Nuvoton Technology Corp. All rights reserved.
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "N9H20.h"
#include "SpiLoader.h"
#include "tag.h"

#define E_CLKSKEW  0x00888800

#define __No_RTC__
#define __UPLL_192__

//#define _S605_

#ifdef _S605_
    #ifdef __Security__
        #define DATE_CODE   "20181206 with Security for S605"
    #else
        #define DATE_CODE   "20181206 for S605"
    #endif
#else
    #ifdef __Security__
        #define DATE_CODE   "20181206 with Security"
    #else
        #define DATE_CODE   "20181206"
    #endif
#endif

#define __DAC_ON__

#if defined (__GNUC__)
UINT8  image_buffer[512] __attribute__((aligned(32)));
#else
UINT8 __align(32) image_buffer[512];
#endif

unsigned char *imagebuf;
unsigned int *pImageList;

extern void lcmFill2Dark(unsigned char*);
//unsigned char kbuf[CP_SIZE]; /* save first 16k of buffer. Copy to 0 after vector table is no longer needed */
extern void AudioChanelControl(void);
extern void backLightEnable(void);
int kfd, mfd;
#ifdef __Security__
#define KEY_INDEX 1
void RPMC_CreateRootKey(unsigned char *u8uid, unsigned int id_len, unsigned char *rootkey);
#endif

UINT32 u32TimerChannel = 0;
void Timer0_300msCallback(void)
{
#ifdef __BACKLIGHT__
    backLightEnable();
#endif
    sysClearTimerEvent(TIMER0, u32TimerChannel);
}

#define E_CLKSKEW   0x00888800
#define __DDR_6__

void init(void)
{
    WB_UART_T  uart;
    UINT32  u32ExtFreq;
    UINT32 u32Cke = inp32(REG_AHBCLK);

    /* Reset SIC engine to fix USB update kernel and mvoie file */
    outp32(REG_AHBCLK, u32Cke  | (SIC_CKE | NAND_CKE | SD_CKE)); 
    outp32(REG_AHBIPRST, inp32(REG_AHBIPRST )|SICRST ); 
    outp32(REG_AHBIPRST, 0); 
    outp32(REG_APBIPRST, TMR0RST | TMR1RST); 
    outp32(REG_APBIPRST, 0); 
    outp32(REG_AHBCLK,u32Cke);
    sysEnableCache(CACHE_WRITE_BACK);

    u32ExtFreq = sysGetExternalClock();     /* KHz unit */
    outp32(REG_DQSODS, 0x1010);
    outp32(REG_CKDQSDS, E_CLKSKEW);
    if(u32ExtFreq==12000)
    {
        outp32(REG_SDREF, 0x805A);
    }
    else
    {
        outp32(REG_SDREF, 0x80C0);
    }
#ifdef __UPLL_192__
    if((inp32(REG_CHIPCFG) & SDRAMSEL) == 0x20)     /* Power On Setting SDRAM type is DDR2 */
    {
        outp32(REG_SDMR, 0x432);
        outp32(REG_DQSODS, 0x00001010);
        outp32(REG_MISCPCR,0x00000001);             /* Driving strength */
        outp32(REG_SDTIME, 0x21667525);
    }
    else if((inp32(REG_CHIPCFG) & SDRAMSEL) == 0x30)     /* Power On Setting SDRAM type is DDR */
    {
    #ifdef __DDR_75__
        outp32(REG_SDTIME, 0x098E7549); /* DDR Speed grade-75 */
    #endif
    #ifdef __DDR_6__
        outp32(REG_SDTIME, 0x094E7425); /* DDR Speed grade-6 */
    #endif
    #ifdef __DDR_5__
        outp32(REG_SDTIME, 0x094E6425); /* DDR Speed grade-5 */
    #endif
        outp32(REG_SDMR, 0x22);         /* Cas Latency = 2 */
    }

    sysSetSystemClock(eSYS_UPLL,    //E_SYS_SRC_CLK eSrcClk,
                    192000,         //UINT32 u32PllKHz,
                    192000,         //UINT32 u32SysKHz,
                    192000,         //UINT32 u32CpuKHz,
                    192000/2,       //UINT32 u32HclkKHz,
                    192000/4);      //UINT32 u32ApbKHz
#endif

    /* enable UART */
    sysUartPort(1);
    uart.uiFreq = u32ExtFreq*1000;    /* Hz unit */
    uart.uiBaudrate = 115200;
    uart.uiDataBits = WB_DATA_BITS_8;
    uart.uiStopBits = WB_STOP_BITS_1;
    uart.uiParity = WB_PARITY_NONE;
    uart.uiRxTriggerLevel = LEVEL_1_BYTE;
    sysInitializeUART(&uart);
    sysprintf("SPI Loader start (%s).\n", DATE_CODE);
    sysSetLocalInterrupt(ENABLE_IRQ);
    sysFlushCache(I_D_CACHE);
}

#ifdef _S605_   /* support S605 chip */
/*----------------------------------------------------------------------------
  Initail GPIO pins GPE10 and GPE11 to output mode for S605 module.
  ----------------------------------------------------------------------------*/
void S605_init_gpio()
{
    sysprintf("Initial GPIO pins for S605 module.\n");

    /* initial GPE10 to output mode and pull low it. */
    outpw(REG_GPEFUN, inpw(REG_GPEFUN) & (~MF_GPE10));      /* set GPE10 as GPIO pin */
    outpw(REG_GPIOE_PUEN, inpw(REG_GPIOE_PUEN) | BIT10);    /* set GPE10 internal resistor to pull up */
    outpw(REG_GPIOE_DOUT, inpw(REG_GPIOE_DOUT) & (~BIT10)); /* output 0 to GPE10 */
    outpw(REG_GPIOE_OMD,  inpw(REG_GPIOE_OMD)  | BIT10);    /* set GPE10 to OUTPUT mode */

    /* initial GPE11 to output mode and pull low it. */
    outpw(REG_GPEFUN, inpw(REG_GPEFUN) & (~MF_GPE11));      /* set GPE11 as GPIO pin */
    outpw(REG_GPIOE_PUEN, inpw(REG_GPIOE_PUEN) | BIT11);    /* set GPE11 internal resistor to pull up */
    outpw(REG_GPIOE_DOUT, inpw(REG_GPIOE_DOUT) & (~BIT11)); /* output 0 to GPE11 */
    outpw(REG_GPIOE_OMD,  inpw(REG_GPIOE_OMD)  | BIT11);    /* set GPE11 to OUTPUT mode */
}


/*----------------------------------------------------------------------------
 * Control GPE10 and GPE11 to power on S605 module.
 *      When power on, pull low both RST (GPE10 pin) and PMU (GPE11 pin);
 *      And then, pull high PMU;
 *      Wait 3ms at least, pull high RST;
 *      Keep status 12ms at least;
 *----------------------------------------------------------------------------*/
void S605_power_on()
{
    UINT32 u32Delay;

    sysprintf("Power on S605 module.\n");
    outpw(REG_GPIOE_DOUT, inpw(REG_GPIOE_DOUT) & (~BIT10)); /* pull low RST */
    outpw(REG_GPIOE_DOUT, inpw(REG_GPIOE_DOUT) & (~BIT11)); /* pull low PMU */
    u32Delay = 20000;
    while(u32Delay--);  /* delay 10000 loop ~= 5ms */

    outpw(REG_GPIOE_DOUT, inpw(REG_GPIOE_DOUT) | BIT11);    /* pull high PMU */
    u32Delay = 20000;
    while(u32Delay--);  /* delay 10000 loop ~= 5ms */

    outpw(REG_GPIOE_DOUT, inpw(REG_GPIOE_DOUT) | BIT10);    /* pull hiigh RST */
    u32Delay = 40000;
    while(u32Delay--);  /* delay 10000 loop ~= 5ms */
}
#endif


/*
    The following codes are added to support Linux tag list [2007/03/21]
    currently, only compressed romfs's size and physical address are supported!
*/
int  TAG_create(unsigned int addr, unsigned int size)
{
    static struct tag *tlist;

    tlist = (struct tag *) 0x100; /* will destroy BIB_ShowInfo() */

    //sysprintf("tlist->hdr.tag = ATAG_CORE;\n");
    tlist->hdr.tag = ATAG_CORE;
    tlist->hdr.size = tag_size (tag_core);
    tlist = tag_next (tlist);

    tlist->hdr.tag = ATAG_INITRD2;
    tlist->hdr.size = tag_size (tag_initrd);
    tlist->u.initrd.start = addr;  /* omfs starting address */
    tlist->u.initrd.size = size;   /* romfs size */
    tlist = tag_next (tlist);

//     tag-list node
//     tlist->hdr.tag = ATAG_MACADDR;
//     tlist->hdr.size = tag_size (tag_macaddr);
//     memcpy(&tlist->u.macaddr.mac[0], &_HostMAC[0], 6);
    /*    
    uprintf("===>%02x %02x %02x %02x %02x %02x\n", tlist->u.macaddr.mac[0],
                                                   tlist->u.macaddr.mac[1],
                                                   tlist->u.macaddr.mac[2],
                                                   tlist->u.macaddr.mac[3],
                                                   tlist->u.macaddr.mac[4],
                                                   tlist->u.macaddr.mac[5],
                                                   tlist->u.macaddr.mac[6]);
    */                                                   
    tlist = tag_next (tlist);
    tlist->hdr.tag = ATAG_NONE;
    tlist->hdr.size = 0;
    return 0;
}

volatile int tag_flag = 0, tagaddr,tagsize;
#ifndef __No_LCM__
static UINT32 bIsInitVpost=FALSE;
LCDFORMATEX lcdInfo;

void initVPostShowLogo(void)
{
    if(bIsInitVpost==FALSE)
    {
        bIsInitVpost = TRUE;
        //lcdInfo.ucVASrcFormat = DRVVPOST_FRAME_YCBYCR;
        lcdInfo.ucVASrcFormat = DRVVPOST_FRAME_RGB565;
        lcdInfo.nScreenWidth = PANEL_WIDTH;
        lcdInfo.nScreenHeight = PANEL_HEIGHT;
        vpostLCMInit(&lcdInfo, (UINT32*)FB_ADDR);
        //backLightEnable();
    }
}
#endif
void spuDacOnLoader(UINT8 level)
{
    outp32(REG_AHBCLK, inp32(REG_AHBCLK) &(~( ADO_CKE | SPU_CKE | HCLK4_CKE)));  /* disable SPU engine clock */
    outp32(REG_AHBCLK, inp32(REG_AHBCLK) | ADO_CKE | SPU_CKE | HCLK4_CKE);       /* enable SPU engine clock */

    outp32(REG_SPU_CTRL, inp32(REG_SPU_CTRL) & ~SPURST);

    outp32(REG_SPU_CTRL, inp32(REG_SPU_CTRL) | SPURST);
    outp32(REG_SPU_CTRL, inp32(REG_SPU_CTRL) & ~SPURST);

    outpw(REG_SPU_DAC_PAR, inpw(REG_SPU_DAC_PAR) | 0x30);       /* disable */
    if(level == 3)
        outpw(REG_SPU_DAC_PAR, inpw(REG_SPU_DAC_PAR) & ~0x30);  /* delay time, p0=3s */
    else if(level == 1)
        outpw(REG_SPU_DAC_PAR, inpw(REG_SPU_DAC_PAR) & ~0x20);  /* delay time, p0=0.5-1s */
    else if(level == 2)
        outpw(REG_SPU_DAC_PAR, inpw(REG_SPU_DAC_PAR) & ~0x10);  /* delay time, p0=2s */
    else
    {
        outpw(REG_SPU_DAC_VOL, inpw(REG_SPU_DAC_VOL) & ~0x00FF0000);  /* P7 */
        outpw(REG_SPU_DAC_PAR, inpw(REG_SPU_DAC_PAR) | 0x30);   /* disable */
        return;
    }
#if 0
    if(level == 3)    /* modify this delay time to meet the product request */
        _sysDelay(300);
    else if(level == 1)
        _sysDelay(70);
    else if(level == 2)
        _sysDelay(200);
#endif
}

int main(void)
{
    unsigned int startBlock;
    unsigned int fileLen;
    unsigned int executeAddr;
    unsigned int start_addr, size;

#ifdef __Security__
    UINT8  u8UID[8];
    unsigned char ROOTKey[32];      /* Rootkey array */
    unsigned char HMACKey[32];      /* HMACkey array */
    unsigned char HMACMessage[4];   /* HMAC message data, use for update HMAC key */
    unsigned char Input_tag[12];    /* Input tag data for request content */
    unsigned char RPMCStatus;
#endif
    int count, i, j;
    void  (*fw_func)(void);

    outp32(0xFF000000, 0);

    if(sysGetChipVersion() == 'G')
        outp32(REG_CLKDIV4, inp32(REG_CLKDIV4)| 0x100);

    spuDacOnLoader(2);

    outp32(REG_APBCLK, inp32(REG_APBCLK) | RTC_CKE);

#ifdef __No_RTC__
    sysprintf("* Not Config RTC\n");
    outp32(REG_APBCLK, inp32(REG_APBCLK) & ~RTC_CKE);
#else
    if(inp32(INIR) & 0x1)
    {
        sysprintf("* Enable HW Power Off\n");

        count = 0;

        outp32(REG_APBCLK, inp32(REG_APBCLK) | RTC_CKE);

        outp32(AER,0x0000a965);

        while(1)
        {
            if((inp32(AER) & 0x10000) == 0x10000)
                break;
            if(count > 1000000)
            {
                sysprintf("Write RTC Fail!!\n");
                break;
            }
            count++;
        }
        outp32(PWRON, 0x60005);   /* Press Power Key during 6 sec to Power off (0x'6'0005) */
        outp32(RIIR,0x4);
    }
    else
    {
        if((inp32(INIR) & 0x1) == 0)
        sysprintf("RTC is in-active!!\n");
    }
 
#endif
    init();

#ifdef _S605_   /* support S605 chip */
    S605_init_gpio();
    S605_power_on();
#endif

#ifndef __No_LCM__
    initVPostShowLogo();
#endif
    imagebuf = (UINT8 *)((UINT32)image_buffer | 0x80000000);
    pImageList=((unsigned int *)(((unsigned int)image_buffer)|0x80000000));

    /* Initial DMAC and NAND interface */
    SPI_OpenSPI();
#ifdef __Security__
    if ((RPMC_ReadUID(u8UID)) == -1)
    {
        sysprintf("read id error !!\n");
        return -1;
    }

    sysprintf("SPI flash uid [0x%02X%02X%02X%02X%02X%02X%02X%02X]\n",u8UID[0], u8UID[1],u8UID[2], u8UID[3],u8UID[4], u8UID[5],u8UID[6], u8UID[7]);
  
    /* first stage, initial rootkey */
    RPMC_CreateRootKey((unsigned char *)u8UID,8, ROOTKey);  /* caculate ROOTKey with UID & ROOTKeyTag by SHA256 */

    /* Second stage, update HMACKey after ever power on. without update HMACkey, Gneiss would not function*/
    HMACMessage[0] = rand()%0x100;        /* Get random data for HMAC message, it can also be serial number, RTC information and so on. */
    HMACMessage[1] = rand()%0x100;
    HMACMessage[2] = rand()%0x100;
    HMACMessage[3] = rand()%0x100;

    /* 
        Update HMAC key and get new HMACKey. 
        HMACKey is generated by SW using Rootkey and HMACMessage.
        RPMC would also generate the same HMACKey by HW 
     */
    RPMCStatus = RPMC_UpHMACkey(KEY_INDEX, ROOTKey, HMACMessage, HMACKey);
    if(RPMCStatus == 0x80)
    {
        /* update HMACkey success */
        sysprintf("RPMC_UpHMACkey Success - 0x%02X!!\n",RPMCStatus );
    }
    else
    {
        /* write HMACkey fail, check datasheet for the error bit */
        sysprintf("RPMC_UpHMACkey Fail - 0x%02X!!\n",RPMCStatus );
    }

    /* 
        Third stage, increase RPMC counter
        input tag is send in to RPMC, it could be time stamp, serial number and so on
    */
    for(i= 0; i<12;i++)
        Input_tag[i] = u8UID[i%8];

    RPMCStatus = RPMC_IncCounter(KEY_INDEX, HMACKey, Input_tag);
    if(RPMCStatus == 0x80)
    {
        /* increase counter success */
        sysprintf("RPMC_IncCounter Success - 0x%02X!!\n",RPMCStatus );
    }
    else
    {
        /* increase counter fail, check datasheet for the error bit */
        sysprintf("RPMC_IncCounter Fail - 0x%02X!!\n",RPMCStatus );
        while(1);
    }

    if(RPMC_Challenge(KEY_INDEX, HMACKey, Input_tag)!=0)
    {
        sysprintf("RPMC_Challenge Fail!!\n" );
        /* return signature miss-match */
        while(1);
    }
    else
        sysprintf("RPMC_Challenge Pass!!\n" );
#endif
    memset(imagebuf, 0, 32);
    sysprintf("Load Image ");
    /* read image information */
    SPIReadFast(0, 63*1024, 512, (UINT32*)imagebuf);  /* offset, len, address */

    if (((*(pImageList+0)) == 0xAA554257) && ((*(pImageList+3)) == 0x63594257))
    {
        count = *(pImageList+1);

        pImageList=((unsigned int*)(((unsigned int)image_buffer)|0x80000000));
        startBlock = fileLen = executeAddr = 0;

        /* load logo first */
        pImageList = pImageList+4;
        for (i=0; i<count; i++)
        {
            if (((*(pImageList) >> 16) & 0xffff) == 4)    /* logo */
            {
                startBlock = *(pImageList + 1) & 0xffff;
                executeAddr = *(pImageList + 2);
                fileLen = *(pImageList + 3);
                SPIReadFast(0, startBlock * 0x10000, fileLen, (UINT32*)executeAddr);
                break;
            }
            /* pointer to next image */
            pImageList = pImageList+12;
        }

        pImageList=((unsigned int*)(((unsigned int)image_buffer)|0x80000000));
        startBlock = fileLen = executeAddr = 0;

        /* load romfs file */
        pImageList = pImageList+4;
        for (i=0; i<count; i++)
        {
            if (((*(pImageList) >> 16) & 0xffff) == 2)    /* RomFS */
            {
                startBlock = *(pImageList + 1) & 0xffff;
                executeAddr = *(pImageList + 2);
                fileLen = *(pImageList + 3);
                SPIReadFast(0, startBlock * 0x10000, fileLen, (UINT32*)executeAddr);
                tag_flag = 1;
                tagaddr = executeAddr;
                tagsize = fileLen;
                break;
            }
            /* pointer to next image */
            pImageList = pImageList+12;
        }

        pImageList=((unsigned int*)(((unsigned int)image_buffer)|0x80000000));
        startBlock = fileLen = executeAddr = 0;

        /* load execution file */
        pImageList = pImageList+4;
        for (i=0; i<count; i++)
        {
            if (((*(pImageList) >> 16) & 0xffff) == 1)    /* execute */
            {
                startBlock = *(pImageList + 1) & 0xffff;
                executeAddr = *(pImageList + 2);
                fileLen = *(pImageList + 3);

                sysSetGlobalInterrupt(DISABLE_ALL_INTERRUPTS);
                sysSetLocalInterrupt(DISABLE_FIQ_IRQ);
#ifdef __DAC_ON__

                outpw(REG_SPU_DAC_VOL, inpw(REG_SPU_DAC_VOL) & ~0x0800000); /* P7 */

                start_addr = startBlock * 0x10000;

                size = (fileLen >> 10) << 8;

                for(j=0;j<4;j++)
                {
                    SPIReadFast(0, start_addr + size * j, size, (UINT32*) (executeAddr + size * j));

                    switch(j)
                    {
                        case 0:;
                            outpw(REG_SPU_DAC_VOL, inpw(REG_SPU_DAC_VOL) & ~0x0400000);  /* P6 */
                            break;
                        case 1:
                            outpw(REG_SPU_DAC_VOL, inpw(REG_SPU_DAC_VOL) & ~0x01e0000);  /* P1-4 */
                            break;
                        case 2:
                            outpw(REG_SPU_DAC_VOL, inpw(REG_SPU_DAC_VOL) & ~0x0200000);  /* P5 */
                            break;
                    }
                }
                fileLen = fileLen - size * 4;
                if(fileLen)
                    SPIReadFast(0, start_addr + size * 4, fileLen, (UINT32*) (executeAddr + size * 4));

                outpw(REG_SPU_DAC_VOL, inpw(REG_SPU_DAC_VOL) & ~0x00010000);  /* P0 */

                outpw(REG_SPU_DAC_VOL, inpw(REG_SPU_DAC_VOL) | 0x00001F1F);

                outp32(REG_AHBCLK, inp32(REG_AHBCLK) | ADO_CKE | SPU_CKE | HCLK4_CKE);    /* enable SPU engine clock */
                /* Initial SPU in advance for linux set volume issue */
                spuOpen(eDRVSPU_FREQ_8000);
#else
                SPIReadFast(0, startBlock * 0x10000, fileLen, (UINT32*)executeAddr);
#endif
                sysSetGlobalInterrupt(DISABLE_ALL_INTERRUPTS);
                sysSetLocalInterrupt(DISABLE_FIQ_IRQ);
                /* Invalid and disable cache */
                sysDisableCache();
                sysInvalidCache();

                if(tag_flag)
                {
                    sysprintf("Create Tag - Address 0x%08X, Size 0x%08X\n",tagaddr,tagsize );
                    TAG_create(tagaddr,tagsize);
                }

                /* JUMP to kernel */
                sysprintf("Jump to kernel\n\n\n");

                //lcmFill2Dark((char *)(FB_ADDR | 0x80000000));
                outp32(REG_AHBIPRST, JPGRST | SICRST |UDCRST | EDMARST);
                outp32(REG_AHBIPRST, 0);
                outp32(REG_APBIPRST, UART1RST | UART0RST | TMR1RST | TMR0RST );
                outp32(REG_APBIPRST, 0);
                sysFlushCache(I_D_CACHE);
                fw_func = (void(*)(void))(executeAddr);
                fw_func();
                break;
            }
            /* pointer to next image */
            pImageList = pImageList+12;
        }
    }

    return(0); /* avoid compilation warning */
}