/*
 * Copyright (c) 2022, Shenzhen CVA Innovation CO.,LTD
 * All rights reserved.
 *
 * Shenzhen CVA Innovation CO.,LTD (CVA chip) is supplying this file for use
 * exclusively with CVA's microcontroller products. This file can be freely
 * distributed within development tools that are supporting such microcontroller
 * products.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS".  NO WARRANTIES, WHETHER EXPRESS, IMPLIED
 * OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE.
 * CVA SHALL NOT, IN ANY CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL,
 * OR CONSEQUENTIAL DAMAGES, FOR ANY REASON WHATSOEVER.
 */

/*******************************************************************************
 * the includes
 ******************************************************************************/
#include <stddef.h>
#include "fls.h"
#include "mcu.h"
#include "bootloader_cfg.h"

#if BOOTLOADER_CFG_FLS_CODE_GENERATE_EN != 0u 

#if BOOTLOADER_CFG_FLS_COPY_AUTO_EN == 1u 
#define PRAGMA_SECTION              ".customSection"
#else
#define PRAGMA_SECTION              ".ram_flash_driver"
#endif

/*******************************************************************************
 * the defines
 ******************************************************************************/

#pragma location = PRAGMA_SECTION
static Fls_StdRtnType
    Fls_CommandSequence(FtfcRegType* flashRegPtr);

#pragma location = PRAGMA_SECTION
static Fls_StdRtnType
    Fls_CommandSequence(FtfcRegType* flashRegPtr);

#pragma location = PRAGMA_SECTION
void Fls_Init(uint32_t flashDrvInstance);

#pragma location = PRAGMA_SECTION
Fls_StdRtnType
    Fls_Erase(uint32_t targetAddress, uint32_t sectorNum);

#pragma location = PRAGMA_SECTION
Fls_StdRtnType
    Fls_Write(uint32_t targetAddress, const uint8_t* sourceAddressPtr, uint32_t bytesNum);

/*******************************************************************************
 * the typedefs
 ******************************************************************************/

/*******************************************************************************
 * the globals
 ******************************************************************************/

#if BOOTLOADER_CFG_FLS_COPY_AUTO_EN == 0u
#pragma location = ".ram_flash_driver_map"
__root uint32_t fls_DrvMapTbl[] = {
    0, /* Store flash instance address */
    (uint32_t)(&Fls_Init),
    (uint32_t)(&Fls_Erase),
    (uint32_t)(&Fls_Write)};

#endif

/*******************************************************************************
 * the functions
 ******************************************************************************/
#pragma location = PRAGMA_SECTION
static void
    Fls_ClearCommandState(FtfcRegType* obj)
{
    FtfcReg_SetFstatProtViolF(obj, 1);
    FtfcReg_SetFstatAccErrF(obj, 1);
    FtfcReg_SetFstatRdColF(obj, 1);
    FtfcReg_SetFstatFtfcSecF(obj, 1);
    FtfcReg_SetFstatFtfcDedF(obj, 1);
    FtfcReg_SetFstatAhbSecF(obj, 1);
    FtfcReg_SetFstatAhbDedF(obj, 1);
}

#pragma location = PRAGMA_SECTION
static Fls_StdRtnType
    Fls_CommandSequence(FtfcRegType* obj)
{
    Fls_StdRtnType ret = FLS_STD_RETURN_OK;

    FtfcReg_SetFstatCcif(obj, 1);

    while(0U == FtfcReg_GetFstatCcif(obj))
    {
    }

    if(FtfcReg_GetFstatAccErrF(obj) || FtfcReg_GetFstatProtViolF(obj) || FtfcReg_GetFstatMgStat(obj))
    {
        ret = FLS_STD_RETURN_ERASE_NG;
    }

    return ret;
}

#pragma location = PRAGMA_SECTION
static Fls_StdRtnType
    Fls_SetFlexRam(FtfcRegType* obj, bool eeramEn)
{
    Fls_StdRtnType ret = FLS_STD_RETURN_OK;

    if(0U == FtfcReg_GetFstatCcif(obj))
    {
        ret = FLS_STD_RETURN_BUSY;
    }
    else
    {
        Fls_ClearCommandState(obj);
        FtfcReg_SetFccob(obj, 0, FLASHDRV_OPCMD_SETFRAM);
        FtfcReg_SetFccob(obj, 1, eeramEn ? 1 : 0);
        ret = Fls_CommandSequence(obj);

        if(eeramEn && (FtfcReg_GetFstatEeeRamRdy(obj) == 0))
        {
            ret = FLS_STD_RETURN_PGM_NG;
        }

        if(!eeramEn && !FtfcReg_GetFstatRamRdy(obj))
        {
            ret = FLS_STD_RETURN_PGM_NG;
        }
    }

    return ret;
}

/*! \brief Initializes the Flash driver module
 *
 * This function initializes Flash driver by setting the register
 * instance to it.
 *
 * \param[in] flashDrvInstance : pointer to Flash driver instance
 * \return void
 */
#pragma location = PRAGMA_SECTION
void Fls_Init(uint32_t flashDrvInstance)
{
    FlashDrvType *flashDrvPtr = (FlashDrvType *)flashDrvInstance;
    /* Update the flash instance */
    *((uint32_t *)BOOTLOADER_CFG_FLS_DRV_RAM_START_ADDR) = (uint32_t)flashDrvPtr->reg;
    /* disable cache,because cache-enable impact the flash memory data checking on debug side */
    *((volatile uint32_t*)0xE0082000u) = 0x0;
    flashDrvPtr->callback = NULL;
}

/*! \brief Erased the pflash
 *
 * This function erase the length located in the pflash
 *
 * \param[in] targetAddress : pointer to pFlash address
 * \param[in] sectorNum : how many sector to be erased
 * \return Fls_StdRtnType
 */
#pragma location = PRAGMA_SECTION
Fls_StdRtnType
    Fls_Erase(uint32_t targetAddress, uint32_t sectorNum)
{
    Fls_StdRtnType  ret         = FLS_STD_RETURN_OK;
    FtfcRegType*    flashRegPtr;

    flashRegPtr = (FtfcRegType*)((*(uint32_t*)BOOTLOADER_CFG_FLS_DRV_RAM_START_ADDR));
    /* Check the current requested address is aligned with 1024byte or not */
    if((targetAddress & FLS_ERASE_SECTION_ADDR_ALIGNED_MASK) != 0u)
    {
        ret = FLS_STD_RETURN_ADDR_INVALID;
    }
    else
    {
        uint32_t i = 0;

        /* Check how many sections to be erased */
        for(i = 0; i < sectorNum; i++)
        {
            if(FtfcReg_GetFstatCcif(flashRegPtr) == 0u)
            {
                ret = FLS_STD_RETURN_BUSY;
                break;
            }
            else
            {
                Fls_ClearCommandState(flashRegPtr);

                FtfcReg_SetFccob(flashRegPtr, 0, FLASHDRV_OPCMD_SCTERS);
                FtfcReg_SetFccob(flashRegPtr, 1, FCCOBADDRLOW((targetAddress + (i << 11))));
                FtfcReg_SetFccob(flashRegPtr, 2, FCCOBADDRMDLOW((targetAddress + (i << 11))));
                FtfcReg_SetFccob(flashRegPtr, 3, FCCOBADDRMDHIG((targetAddress + (i << 11))));
                FtfcReg_SetFccob(flashRegPtr, 4, FCCOBADDRHIG((targetAddress + (i << 11))));
                FtfcReg_SetFccob(flashRegPtr, 5, FLASHDRV_SCTERS_RETRY);

                ret = Fls_CommandSequence(flashRegPtr);
            }

            if(ret != FLS_STD_RETURN_OK)
            {
                break;
            }
        }
    }

    return ret;
}

/*! \brief Write datas to pflash
 *
 * This function wrtie the length bytes to pflash
 *
 * \param[in] targetAddress : the target address to begin to write in pflash
 * \param[in] sourceAddressPtr : pointer to write data buffer
 * \param[in] bytesNum : how many bytes to be written to pflash
 * \return Fls_StdRtnType
 */
#pragma location = PRAGMA_SECTION
Fls_StdRtnType
    Fls_Write(uint32_t targetAddress, const uint8_t* sourceAddressPtr, uint32_t bytesNum)
{
    Fls_StdRtnType ret = FLS_STD_RETURN_OK;
    FtfcRegType*   flashRegPtr;

    flashRegPtr = (FtfcRegType*)((*(uint32_t*)BOOTLOADER_CFG_FLS_DRV_RAM_START_ADDR));

    /* Check the current requested address is aligned with 8byte or not */
    if((targetAddress & FLS_PGM_SECTION_ADDR_ALIGNED_MASK) != 0u)
    {
        ret = FLS_STD_RETURN_ADDR_INVALID;
    }
    else if((bytesNum & FLS_PGM_SECTION_ADDR_ALIGNED_MASK) != 0u)
    {
        ret = FLS_STD_RETURN_LEN_INVALID;
    }
    else
    {
        bool     eeramrdy = false;
        uint32_t i = 0, offset = 0;
        uint8_t* pfastram;

        if(FtfcReg_GetFstatEeeRamRdy(flashRegPtr) == 1)
        {
            eeramrdy = true;
        }

        if(eeramrdy)
        {
            Fls_SetFlexRam(flashRegPtr, false);
        }

        offset   = (targetAddress & (FLS_ERASE_SECTION_SIZE - 1u));
        pfastram = (uint8_t*)(FLS_FASTRAM_ADDR + offset);
        for(i = 0; i < bytesNum; i++)
        {
            *pfastram = sourceAddressPtr[i];
            pfastram++;
        }

        if(0U == FtfcReg_GetFstatCcif(flashRegPtr))
        {
            ret = FLS_STD_RETURN_BUSY;
        }
        else
        {
            Fls_ClearCommandState(flashRegPtr);

            FtfcReg_SetFccob(flashRegPtr, 0, FLASHDRV_OPCMD_SCTPGM);
            FtfcReg_SetFccob(flashRegPtr, 1, FCCOBADDRLOW(targetAddress));
            FtfcReg_SetFccob(flashRegPtr, 2, FCCOBADDRMDLOW(targetAddress));
            FtfcReg_SetFccob(flashRegPtr, 3, FCCOBADDRMDHIG(targetAddress));
            FtfcReg_SetFccob(flashRegPtr, 4, FCCOBADDRHIG(targetAddress));
            FtfcReg_SetFccob(flashRegPtr, 5, (uint8_t)((bytesNum / FLS_PGM_PHRASE_BYTE_SIZE) - 1));

            ret = Fls_CommandSequence(flashRegPtr);
        }

        if(eeramrdy)
        {
            Fls_SetFlexRam(flashRegPtr, true);
        }
    }

    return ret;
}

/*! \brief Read out data from pflash
 *
 * This function read out the length bytes data from pflash
 *
 * \param[in] readAddress : the target address to begin to read in pflash
 * \param[in] readBufferPtr : pointer to store read out datas
 * \param[in] bytesNum : how many bytes to be readout from pflash
 * \return Fls_StdRtnType
 */
Fls_StdRtnType Fls_Read(uint32_t readAddress, uint8_t* readBufferPtr, uint32_t bytesNum)
{
    Fls_StdRtnType ret = FLS_STD_RETURN_OK;
    uint32_t       i   = 0;
    uint8_t*       targetReadAddrPtr;

    targetReadAddrPtr = (uint8_t*)readAddress;

    for(i = 0; i < bytesNum; i++)
    {
        readBufferPtr[i] = targetReadAddrPtr[i];
    }

    return ret;
}

/*! \brief Compare written datas in pflash
 *
 * This function compare the written length datas
 *
 * \param[in] sourceAddressPtr : pointer to  stored in localed buffer
 * \param[in] targetAddressPtr : pointer to stored in pflash
 * \param[in] bytesNum : how many bytes to be compared from pflash
 * \return Fls_StdRtnType
 */
Fls_StdRtnType Fls_Compare(const uint8_t* sourceAddressPtr, const uint8_t* targetAddressPtr, uint32_t bytesNum)
{
    Fls_StdRtnType ret = FLS_STD_RETURN_OK;
    uint32_t       i   = 0;

    for(i = 0; i < bytesNum; i++)
    {
        if((*(sourceAddressPtr + i)) != (*(targetAddressPtr + i)))
        {
            ret = FLS_STD_RETURN_DATA_INVALID;
            break;
        }
    }

    return ret;
}

#endif

