/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * Copyright 2016-2017 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

#include "board.h"
#include "pin_mux.h"
#include "fsl_debug_console.h"


#include "fsl_iocon.h"
#include "fsl_usart.h"

#include "fsl_sd.h"
#include "fsl_sd_disk.h"
#include "diskio.h"
#include "ff.h"

#include "cJSON.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/

#define APP_RAM_APP_BASEADDR 0x20000000
#define APP_SDCARD_READ_BUFF_LEN 512

#define APP_FILEPATHS_NUM_MAX 10
#define APP_FILEPATHS_LEN_MAX 64 /* the length of the string for filepath. */
char app_firmware_filepaths[APP_FILEPATHS_NUM_MAX][APP_FILEPATHS_LEN_MAX] = {0};
uint32_t app_firmware_num = 0u;
char app_filepath_ext[APP_FILEPATHS_LEN_MAX+3];


#define APP_CONF_JSON_BUFF_LEN_MAX  1024
char app_conf_jsonstr[APP_CONF_JSON_BUFF_LEN_MAX];

/*******************************************************************************
 * Prototypes
 ******************************************************************************/
static status_t sdcardWaitCardInsert(void);
void SysTick_Deinit(void);

uint32_t app_load_application_from_sdcard(char * filepath, uint32_t baseaddr);
void     app_execute_ram_firmware(void * addr);
int32_t  app_load_application_filepaths_from_sdcard(char * conffilepath);
void     app_display_application_filepaths(void);


/*******************************************************************************
 * Variables
 ******************************************************************************/
static FATFS app_fs; /* File system object */

/*! @brief SDMMC host detect card configuration */
static const sdmmchost_detect_card_t s_sdCardDetect = {
#ifndef BOARD_SD_DETECT_TYPE
    .cdType = kSDMMCHOST_DetectCardByGpioCD,
#else
    .cdType = BOARD_SD_DETECT_TYPE,
#endif
    .cdTimeOut_ms = (~0U),
};

/*! @brief SDMMC card power control configuration */
#if defined DEMO_SDCARD_POWER_CTRL_FUNCTION_EXIST
static const sdmmchost_pwr_card_t s_sdCardPwrCtrl = {
    .powerOn          = BOARD_PowerOnSDCARD,
    .powerOnDelay_ms  = 500U,
    .powerOff         = BOARD_PowerOffSDCARD,
    .powerOffDelay_ms = 0U,
};
#endif

/*! @brief SDMMC card power control configuration */
#if defined DEMO_SDCARD_SWITCH_VOLTAGE_FUNCTION_EXIST
static const sdmmchost_card_switch_voltage_func_t s_sdCardVoltageSwitch = {
    .cardSignalLine1V8 = BOARD_USDHC_Switch_VoltageTo1V8,
    .cardSignalLine3V3 = BOARD_USDHC_Switch_VoltageTo3V3,
};
#endif
/*******************************************************************************
 * Code
 ******************************************************************************/
/* This function is used to init the SDIF unused data pin, DATA4 - DATA7, these pin should be configured
 * ,otherswise the SDIF will not work, please check the corresponding errata.
 */
void Board_InitSdifUnusedDataPin(void)
{
    IOCON_PinMuxSet(IOCON, 4, 29,
                    (IOCON_FUNC2 | IOCON_PIO_SLEW_MASK | IOCON_DIGITAL_EN | IOCON_MODE_PULLUP)); /* sd data[4] */
    IOCON_PinMuxSet(IOCON, 4, 30,
                    (IOCON_FUNC2 | IOCON_PIO_SLEW_MASK | IOCON_DIGITAL_EN | IOCON_MODE_PULLUP)); /* sd data[5] */
    IOCON_PinMuxSet(IOCON, 4, 31,
                    (IOCON_FUNC2 | IOCON_PIO_SLEW_MASK | IOCON_DIGITAL_EN | IOCON_MODE_PULLUP)); /* sd data[6] */
    IOCON_PinMuxSet(IOCON, 5, 0,
                    (IOCON_FUNC2 | IOCON_PIO_SLEW_MASK | IOCON_DIGITAL_EN | IOCON_MODE_PULLUP)); /* sd data[7] */
}

/*!
 * @brief Main function
 */
int main(void)
{
    const TCHAR driverNumberBuffer[3U] = {SDDISK + '0', ':', '/'};
    strcpy(app_filepath_ext,driverNumberBuffer);

    CLOCK_EnableClock(kCLOCK_InputMux);
    CLOCK_AttachClk(BOARD_DEBUG_UART_CLK_ATTACH);    /* attach 12 MHz clock to FLEXCOMM0 (debug console) */
    CLOCK_AttachClk(BOARD_SDIF_CLK_ATTACH);/* attach main clock to SDIF */

    BOARD_InitPins();
    /* This function is used to cover the IP bug which the DATA4-7 pin should be configured, otherwise the SDIF will not
     * work */
    Board_InitSdifUnusedDataPin();
    BOARD_BootClockPLL180M();
    /* need call this function to clear the halt bit in clock divider register */
    CLOCK_SetClkDiv(kCLOCK_DivSdioClk, (uint32_t)(SystemCoreClock / FSL_FEATURE_SDIF_MAX_SOURCE_CLOCK + 1U), true);
    BOARD_InitDebugConsole();

    PRINTF("\r\nsdcard based 2nd bootloader example. build at %s, on %s.\r\n", __TIME__, __DATE__);
    PRINTF("\r\nplease insert a sdcard into board... ");

    if (sdcardWaitCardInsert() != kStatus_Success)
    {
        PRINTF("sdcard detect failed.\r\n");
        while (1);
    }

    if (f_mount(&app_fs, driverNumberBuffer, 0U))
    {
        PRINTF("mount volume failed.\r\n");
        while (1);
    }


    strcpy(app_filepath_ext+2, "/conf.json");
    int exe_fw_idx = app_load_application_filepaths_from_sdcard(app_filepath_ext);
    if (exe_fw_idx < 0)
    {
        PRINTF("app_load_application_filepaths_from_sdcard() failed.\r\n");
        while (1);
    }

    app_display_application_filepaths();

    /* dialog of selecting firmware. */
    PRINTF("please select the index of firmware to execute: ");
    while (1)
    {
        uint8_t fw_idx = GETCHAR();
        if ( (fw_idx >= '0') && (fw_idx <= '9') )
        {
            exe_fw_idx = fw_idx - '0';
            break;
        }
        else
        {
            PRINTF("please input 0- 9\r\n");
        }
    }

    PRINTF("\r\n%s loading ... ", app_firmware_filepaths[exe_fw_idx]);

    strcpy(app_filepath_ext+2, app_firmware_filepaths[exe_fw_idx]);
    if (0u != app_load_application_from_sdcard(app_filepath_ext,  APP_RAM_APP_BASEADDR ) )
    {
        PRINTF("firmware load failed.\r\n");
        while (1);
    }

    PRINTF("firmware loaded done.\r\n");

    /* de-init the used devices. */
    SDIF_Deinit(SDIF);
    SysTick_Deinit();
    USART_Deinit(USART0);

    app_execute_ram_firmware((void *)APP_RAM_APP_BASEADDR);

    while (1)
    {
    }
}

static status_t sdcardWaitCardInsert(void)
{
    /* Save host information. */
    g_sd.host.base           = SD_HOST_BASEADDR;
    g_sd.host.sourceClock_Hz = SD_HOST_CLK_FREQ;
    /* card detect type */
    g_sd.usrParam.cd = &s_sdCardDetect;
#if defined DEMO_SDCARD_POWER_CTRL_FUNCTION_EXIST
    g_sd.usrParam.pwr = &s_sdCardPwrCtrl;
#endif
#if defined DEMO_SDCARD_SWITCH_VOLTAGE_FUNCTION_EXIST
    g_sd.usrParam.cardVoltage = &s_sdCardVoltageSwitch;
#endif
    /* SD host init function */
    if (SD_HostInit(&g_sd) != kStatus_Success)
    {
        PRINTF("SD host init fail\r\n");
        return kStatus_Fail;
    }
    /* power off card */
    SD_PowerOffCard(g_sd.host.base, g_sd.usrParam.pwr);
    /* wait card insert */
    if (SD_WaitCardDetectStatus(SD_HOST_BASEADDR, &s_sdCardDetect, true) == kStatus_Success)
    {
        PRINTF("Card inserted.\r\n");
        /* power on the card */
        SD_PowerOnCard(g_sd.host.base, g_sd.usrParam.pwr);
    }
    else
    {
        PRINTF("Card detect fail.\r\n");
        return kStatus_Fail;
    }

    return kStatus_Success;
}

//FIL app_file;   /* File object */
uint8_t app_sdcard_read_buff[APP_SDCARD_READ_BUFF_LEN];
uint32_t app_load_application_from_sdcard(char * filepath, uint32_t baseaddr)
{
    FRESULT error;
    uint32_t ff_br;
    FIL ff_obj;

    error = f_open(&ff_obj, _T(filepath), FA_READ);
    if (error)
    {
        if (error == FR_EXIST)
        {
            PRINTF("File exists.\r\n");
        }
        else
        {
            PRINTF("Open file failed.\r\n");
            return -1;
        }
    }

    /* read file and copy to ram. */
    uint8_t *addr = (uint8_t *)baseaddr;
    do
    {
        f_read(&ff_obj, app_sdcard_read_buff, APP_SDCARD_READ_BUFF_LEN, &ff_br);

        for (uint32_t i = 0u; i < ff_br; i++)
        {
            *addr = app_sdcard_read_buff[i];
            addr++;
        }

    } while (ff_br == APP_SDCARD_READ_BUFF_LEN);

    f_close(&ff_obj);

    return 0;
}

/* execute the firmware exists in sramx. */
typedef void(*func_0_t)(void);
void app_execute_ram_firmware(void * addr)
{
    uint32_t * vectorTable = (uint32_t *)addr;
    uint32_t sp_base = vectorTable[0];
    func_0_t pc_func = (func_0_t)(vectorTable[1]);

    //PRINTF("1.\r\n");
    //PRINTF("sp: 0x%X\r\n", sp_base);
    //PRINTF("pc: 0x%X\r\n", pc_func);

    /* set new msp and psp. */
    __set_MSP(sp_base);

    //PRINTF("2.\r\n");

    __set_PSP(sp_base);

    //PRINTF("3.\r\n");

#if __VTOR_PRESENT == 1
    SCB->VTOR = addr;
#endif

    //PRINTF("4.\r\n");
    /* jump to application. */
    pc_func();

    /* the code should never reach here. */
    while (1)
    {}
}

/* The SDCard component is using the systick to implement the timeout service. */
void SysTick_Deinit(void)
{
    SysTick->CTRL = 0u;
}

int32_t app_load_application_filepaths_from_sdcard(char * conffilepath) /* filepath of the conf.json file. */
{
    FRESULT error;
    uint32_t ff_br;
    FIL ff_obj;
    uint32_t ret;

    error = f_open(&ff_obj, _T(conffilepath), FA_READ);
    if (error)
    {
        if (error == FR_EXIST)
        {
            PRINTF("File exists.\r\n");
        }
        else
        {
            PRINTF("Open file failed.\r\n");
            return -1;
        }
    }

    /* read file and copy to ram. */
    f_read(&ff_obj, app_conf_jsonstr, APP_CONF_JSON_BUFF_LEN_MAX, &ff_br);
    f_close(&ff_obj);

    if (ff_br == APP_CONF_JSON_BUFF_LEN_MAX)
    {
        return -1; /* the buffer is not big enough to keep all the content. */
    }

    /* parse the items from string. */
    cJSON *cjson_root = cJSON_Parse(app_conf_jsonstr);

    /* get the root node. */
    if (cjson_root == NULL)
    {
        const char *error_ptr = cJSON_GetErrorPtr();
        if (error_ptr != NULL)
        {
            PRINTF("Error before: %s\n", error_ptr);
        }
        cJSON_Delete(cjson_root);
        return -2; /* can not  */
    }

    /* read the default firmware's index. */
    cJSON * default_key = cJSON_GetObjectItemCaseSensitive(cjson_root, "default");
    cJSON * index_key = cJSON_GetObjectItemCaseSensitive(default_key, "idx");
    cJSON * num_key = cJSON_GetObjectItemCaseSensitive(default_key, "num");
    ret = (int)index_key->valueint;
    app_firmware_num = (int)num_key->valueint;

    /* read firmware list. */
    cJSON *filepaths = cJSON_GetObjectItemCaseSensitive(cjson_root, "filepaths");
    cJSON *filepath = NULL;

    cJSON_ArrayForEach(filepath, filepaths)
    {
        cJSON *path = cJSON_GetObjectItemCaseSensitive(filepath, "path");
        cJSON *idx = cJSON_GetObjectItemCaseSensitive(filepath, "idx");

        strcpy(app_firmware_filepaths[idx->valueint], path->valuestring);
    }
    PRINTF("\r\n");

    cJSON_Delete(cjson_root);

    return ret;
}

void app_display_application_filepaths(void)
{
  PRINTF("there are %d firmwares detected inside the sdcard:\r\n", app_firmware_num);

    for (uint32_t i = 0u; i < app_firmware_num; i++)
    {
      PRINTF("%2d : %s\r\n", i,  app_firmware_filepaths[i]);
    }
    PRINTF("\r\n");
}


/* EOF. */

