/**************************************************************************//**
 * @file     main.c
 * @brief    Demo how to use USB Device & MSC driver to implement MSC Device with
 *           - NAND
 *           - NAND & SD
 *           - SD Port 0 
 *           - SD Port 0 & Port 1
 *           - Ram Disk
 *           - SPI
 *
 * 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 "N9H26.h"

//#define DETECT_USBD_PLUG
//#define PLUG_DEVICE_TEST
//#define NON_BLOCK_MODE

INT SpiFlashOpen(UINT32 FATOffset);

#ifdef __TWO_NAND__
UINT32 u32NAND_EXPORT = (MSC_NAND_CS0 | MSC_NAND_CS1);
#else
UINT32 u32NAND_EXPORT = MSC_NAND_CS0;
#endif
#ifdef __TWO_SD__
UINT32 u32SD_EXPORT = (MSC_SD_MP_PORT0 | MSC_SD_MP_PORT1);
#else
UINT32 u32SD_EXPORT = MSC_SD_PORT0;    //Card Detect only work when  u32SD_EXPORT = MSC_SD_PORT0
#endif

#ifndef DETECT_USBD_PLUG
BOOL bPlugStauts = FALSE;
BOOL bHostPlugStauts = FALSE;
#endif

#ifdef PLUG_DEVICE_TEST
UINT32 u32TimerChannel = 0;        
BOOL bTimeOut = FALSE;
/*  */
void Timer0_Callback(void)
{
    bTimeOut = TRUE;
    sysClearTimerEvent(TIMER0, u32TimerChannel);
}
#endif

/* Plug detection for mscdMassEvent callback function (Retrun vale - TRUE:Run MSC;FALSE:Exit MSC) */
BOOL PlugDetection(void)
{
#ifdef DETECT_USBD_PLUG        /* Check plug status */
    return udcIsAttached();
#else    
    #ifdef PLUG_DEVICE_TEST    /* Check plug into Host or adaptor */
    if(udcIsAttached())
    {
        if(bPlugStauts != udcIsAttached())
        {
            bPlugStauts = TRUE;
            bHostPlugStauts = FALSE;
            sysprintf("<Plug>");
        }
        
        if(bHostPlugStauts != udcIsAttachedToHost())
        {
            bHostPlugStauts = udcIsAttachedToHost();
            if(bHostPlugStauts)
            {
                bTimeOut = TRUE;
                sysClearTimerEvent(TIMER0, u32TimerChannel);
                sysprintf("<Host>\n");
            }
        }
        if(bTimeOut)
        {
            if(bHostPlugStauts)
                return TRUE;
            else
            {
                sysprintf("<Adaptor>\n");    
                return FALSE;
            }
        
        return TRUE;
    }
    else
        return FALSE;
    #else                    /* Do not check plug status */
        return TRUE;
    #endif    
#endif
}

#ifndef __RAM_DISK_ONLY__
#ifdef __NAND__
NDISK_T MassNDisk;

NDRV_T _nandDiskDriver0 = 
{
    nandInit0,
    nandpread0,
    nandpwrite0,
    nand_is_page_dirty0,
    nand_is_valid_block0,
    nand_ioctl,
    nand_block_erase0,
    nand_chip_erase0,
    0
};
#endif

#endif
INT main(void)
{
#ifdef __NAND__
    UINT32 block_size, free_size, disk_size, u32TotalSize;
    #ifdef __TWO_NAND__
    PDISK_T  *pDiskList, *ptPDiskPtr;
    #else
    UINT32 u32NANDsize1;
    #endif
#endif    
#if !defined(__RAM_DISK_ONLY__) && !defined (__SPI_ONLY__)
    UINT32 u32SicRef;
    INT32 status0 = 0,status1 = 0,status2 = 0;
#endif

    UINT32 u32ExtFreq;
    WB_UART_T uart;

    sysDisableCache();
    sysFlushCache(I_D_CACHE);

    /* Enable USB */
    udcOpen();

    sysUartPort(1);
    u32ExtFreq = sysGetExternalClock();        /* Hz unit */
    uart.uiFreq = u32ExtFreq;
    uart.uiBaudrate = 115200;
    uart.uiDataBits = WB_DATA_BITS_8;
    uart.uart_no=WB_UART_1;
    uart.uiStopBits = WB_STOP_BITS_1;
    uart.uiParity = WB_PARITY_NONE;
    uart.uiRxTriggerLevel = LEVEL_1_BYTE;
    sysInitializeUART(&uart);
    sysEnableCache(CACHE_WRITE_BACK);
    sysSetTimerReferenceClock (TIMER0, u32ExtFreq);
    sysStartTimer(TIMER0, 100, PERIODIC_MODE);


    /**********************************************************************************************
     * Clock Constraints:
     * (a) If Memory Clock > System Clock, the source clock of Memory and System can come from
     *     different clock source. Suggestion MPLL for Memory Clock, UPLL for System Clock
     * (b) For Memory Clock = System Clock, the source clock of Memory and System must come from
     *     same clock source
     *********************************************************************************************/
#if 0
    /**********************************************************************************************
     * Slower down system and memory clock procedures:
     * If current working clock fast than desired working clock, Please follow the procedure below
     * 1. Change System Clock first
     * 2. Then change Memory Clock
     *
     * Following example shows the Memory Clock = System Clock case. User can specify different
     * Memory Clock and System Clock depends on DRAM bandwidth or power consumption requirement.
     *********************************************************************************************/
    sysSetSystemClock(eSYS_EXT, 12000000, 12000000);
    sysSetDramClock(eSYS_EXT, 12000000, 12000000);
#else
    /**********************************************************************************************
     * Speed up system and memory clock procedures:
     * If current working clock slower than desired working clock, Please follow the procedure below
     * 1. Change Memory Clock first
     * 2. Then change System Clock
     *
     * Following example shows to speed up clock case. User can specify different
     * Memory Clock and System Clock depends on DRAM bandwidth or power consumption requirement.
     *********************************************************************************************/
    sysSetDramClock(eSYS_MPLL, 360000000, 360000000);
    sysSetSystemClock(eSYS_UPLL,            //E_SYS_SRC_CLK eSrcClk,
                      240000000,            //UINT32 u32PllKHz,
                      240000000);           //UINT32 u32SysKHz,
    sysSetCPUClock(240000000/2);
#endif

    sysprintf("<MSC>\n");
#if !defined(__RAM_DISK_ONLY__) && !defined (__SPI_ONLY__)

    u32SicRef = sysGetPLLOutputHz(eSYS_UPLL, sysGetExternalClock());

    sicIoctl(SIC_SET_CLOCK, u32SicRef / 1000, 0, 0);  /* clock from PLL */

    sicOpen();
    /* initial nuvoton file system */
    fsInitFileSystem();

    #ifdef __NAND__
    sysprintf("<NAND>\n");

    fsAssignDriveNumber('C', DISK_TYPE_SMART_MEDIA, 0, 1);     /* NAND 0, 2 partitions */
    fsAssignDriveNumber('D', DISK_TYPE_SMART_MEDIA, 0, 2);     /* NAND 0, 2 partitions */

    if(GNAND_InitNAND(&_nandDiskDriver0, &MassNDisk, TRUE) < 0) 
    {
        sysprintf("GNAND_InitNAND error\n");
        return 0;
    }

    if(GNAND_MountNandDisk(&MassNDisk) < 0) 
    {
        sysprintf("GNAND_MountNandDisk error\n");
        return 0;
    }

    fsSetVolumeLabel('C', "NAND1-1\n", strlen("NAND1-1"));
    fsSetVolumeLabel('D', "NAND1-2\n", strlen("NAND1-2"));

    u32TotalSize = MassNDisk.nZone* MassNDisk.nLBPerZone*MassNDisk.nPagePerBlock*MassNDisk.nPageSize;

    if(u32TotalSize > 32 * 0x100000)
        u32NANDsize1 = 32 * 1024;
    else
        u32NANDsize1 = u32TotalSize / 1024 / 5;
            
    /* Format NAND if necessery */
    if ((fsDiskFreeSpace('C', &block_size, &free_size, &disk_size) < 0) ||
        (fsDiskFreeSpace('D', &block_size, &free_size, &disk_size) < 0))
    {   
        if (fsTwoPartAndFormatAll((PDISK_T *)MassNDisk.pDisk, u32NANDsize1, (u32TotalSize - u32NANDsize1)) < 0) 
        {
            sysprintf("Format failed\n");
            fsSetVolumeLabel('C', "NAND1-1\n", strlen("NAND1-1"));
            fsSetVolumeLabel('D', "NAND1-2\n", strlen("NAND1-2"));    
            return 0;
        }
    }
    #endif    /* #ifdef __NAND__ */

    #ifdef __SD__
    sysprintf("<SD>\n");
    if(u32SD_EXPORT & MSC_SD_PORT0)
    {
        sicIoctl(SIC_SET_CARD_DETECT, TRUE, 0, 0);  /* MUST call sicIoctl() BEFORE sicSdOpen0() */
        status0 = sicSdOpen0();
        if(status0 < 0)
            sicSdClose0();
        sysprintf("SD0 %d\n",status0);
    }
    if(u32SD_EXPORT & MSC_SD_PORT1)
    {
        status1 = sicSdOpen1();
        if(status1 < 0)
            sicSdClose1();
        sysprintf("SD1 %d\n",status1);
    }
    if(u32SD_EXPORT & MSC_SD_PORT2)
    {
        status2 = sicSdOpen2();
        if(status2 < 0)
            sicSdClose2();
        sysprintf("SD2 %d\n",status2);
    }
    #endif
#else
#ifdef __RAM_DISK_ONLY__
    sysprintf("MSC Ram Disk Test\n");
#endif
#ifdef __SPI_ONLY__
    sysprintf("MSC Spi Disk Test\n");
#endif
#endif    /* #ifndef __RAM_DISK_ONLY__  */

    mscdInit();
#ifdef __SPI_ONLY__
    {
        UINT32 block_size, free_size, disk_size, reserved_size;

        fsInitFileSystem();
        fsAssignDriveNumber('C', DISK_TYPE_SD_MMC, 0, 1);
        
        reserved_size = 64*1024;                /* SPI reserved size before FAT = 64KB */
        SpiFlashOpen(reserved_size);

        if (fsDiskFreeSpace('C', &block_size, &free_size, &disk_size) < 0)  
        {
            UINT32 u32BlockSize, u32FreeSize, u32DiskSize;
            PDISK_T  *pDiskList;
            fsSetReservedArea(reserved_size/512);
            pDiskList = fsGetFullDiskInfomation();
            fsFormatFlashMemoryCard(pDiskList);
            fsReleaseDiskInformation(pDiskList);
            fsDiskFreeSpace('C', &u32BlockSize, &u32FreeSize, &u32DiskSize);
            sysprintf("block_size = %d\n", u32BlockSize);
            sysprintf("free_size = %d\n", u32FreeSize);
            sysprintf("disk_size = %d\n", u32DiskSize);
        }
    }
    mscdFlashInitExtend(NULL,NULL,NULL, 0,0,0,0);
#elif defined (__RAM_DISK_ONLY__)
    mscdFlashInitExtend(NULL,NULL,NULL, 0,0,0,MSC_RAMDISK_8M);
#else
    #ifdef __SD__
    mscdSdEnable(u32SD_EXPORT);
    #endif
    #ifdef __SD_ONLY__
    mscdFlashInitExtend(NULL,NULL,NULL, status0,status1,status2,0);
    #else
        #ifdef __NAND__
    mscdNandEnable(u32NAND_EXPORT);
            #ifdef __TWO_NAND__
    mscdFlashInitExtend(&MassNDisk,NULL,&MassNDisk2,status0,status1,status2,0);
            #else
    mscdFlashInitExtend(&MassNDisk,NULL,NULL,status0,status1,status2,0);
            #endif
        #endif
    #endif
#endif
    udcInit();
#ifdef PLUG_DEVICE_TEST
    while(1)
    {
        bTimeOut = FALSE;
        bPlugStauts = FALSE;
        bHostPlugStauts = FALSE;
        
        sysStartTimer(TIMER0, 
                        100, 
                        PERIODIC_MODE);
                        
        u32TimerChannel = sysSetTimerEvent(TIMER0,150,(PVOID)Timer0_Callback);
        
        mscdMassEvent(PlugDetection);
        
        if(bPlugStauts != udcIsAttached() && udcIsAttached() == FALSE)
            sysprintf("<Unplug>\n");

        while(udcIsAttached());
    }
#else
#ifdef NON_BLOCK_MODE
    mscdBlcokModeEnable(FALSE);    /* Non-Block mode */
    while(1)
    {
        if(!PlugDetection())
            break;
        mscdMassEvent(NULL);
    }
#else
    mscdMassEvent(PlugDetection);  /* Default : Block mode */
#endif
#endif
    mscdDeinit();
    udcDeinit();
    udcClose();
    sysprintf("Sample code End\n");
}

