/*
 * 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)

/*******************************************************************************
 * the defines
 ******************************************************************************/
#if(BOOTLOADER_CFG_FLS_COPY_AUTO_EN == 1u)
#pragma location = ".textrw"
#else
#pragma location = ".ram_flash_driver"
#endif
static void
    Fls_ClearCommandState(FlashDrvType *obj);

#if(BOOTLOADER_CFG_FLS_COPY_AUTO_EN == 1u)
#pragma location = ".textrw"
#else
#pragma location = ".ram_flash_driver"
#endif
static Fls_StdRtnType
    Fls_CommandSequence(FlashDrvType *obj);

#if(BOOTLOADER_CFG_FLS_COPY_AUTO_EN == 1u)
#pragma location = ".textrw"
#else
#pragma location = ".ram_flash_driver"
#endif
static Fls_StdRtnType
    Fls_CommandSequence(FlashDrvType *obj);

#if(BOOTLOADER_CFG_FLS_COPY_AUTO_EN == 1u)
#pragma location = ".textrw"
#else
#pragma location = ".ram_flash_driver"
#endif
void Fls_Init(FlashDrvType *obj);

#if(BOOTLOADER_CFG_FLS_COPY_AUTO_EN == 1u)
#pragma location = ".textrw"
#else
#pragma location = ".ram_flash_driver"
#endif
Fls_StdRtnType
    Fls_Erase(uint32_t targetAddress, uint32_t sectorNum);

#if(BOOTLOADER_CFG_FLS_COPY_AUTO_EN == 1u)
#pragma location = ".textrw"
#else
#pragma location = ".ram_flash_driver"
#endif
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
 ******************************************************************************/
#if(BOOTLOADER_CFG_FLS_COPY_AUTO_EN == 1u)
#pragma location = ".textrw"
#else
#pragma location = ".ram_flash_driver"
#endif
static void
    Fls_ClearCommandState(FlashDrvType *obj)
{
    FtfcReg_SetFstatRspErrF(obj->reg, 1);
    FtfcReg_SetFstatProtViolF(obj->reg, 1);
    FtfcReg_SetFstatAccErrF(obj->reg, 1);
    FtfcReg_SetFstatRdColF(obj->reg, 1);
    FtfcReg_SetFstatSecF(obj->reg, 1);
    FtfcReg_SetFstatDedF(obj->reg, 1);
}

#if(BOOTLOADER_CFG_FLS_COPY_AUTO_EN == 1u)
#pragma location = ".textrw"
#else
#pragma location = ".ram_flash_driver"
#endif
static Fls_StdRtnType
    Fls_CommandSequence(FlashDrvType *obj)
{
    Fls_StdRtnType ret = FLS_STD_RETURN_OK;

    FtfcReg_SetFstatCcif(obj->reg, 1);

    while(0U == FtfcReg_GetFstatCcif(obj->reg))
    {
    }

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

    return ret;
}

#if(BOOTLOADER_CFG_FLS_COPY_AUTO_EN == 1u)
#pragma location = ".textrw"
#else
#pragma location = ".ram_flash_driver"
#endif
static Fls_StdRtnType
    Fls_SetFlexRam(FlashDrvType *obj, bool eeramEn)
{
    Fls_StdRtnType ret = FLS_STD_RETURN_OK;

    bool gctrg = false;

    if(FtfcReg_GetFstatEeeRamRdy(obj->reg) == 0 && eeramEn == true && Scg0Reg_GetTrimAutoGcEn(obj->scg0Reg) == 1)
    {
        gctrg = true;
    }

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

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

        if((!eeramEn) && (FtfcReg_GetFstatRamRdy(obj->reg) == 0u))
        {
            ret = FLS_STD_RETURN_PGM_NG;
        }

        if((ret == FLS_STD_RETURN_OK) && (gctrg == true))
        {
            FtfcReg_SetFstatEeeTlov(obj->reg, 1);
        }
    }

    return ret;
}

/*! \brief Initializes the Flash driver module
 *
 * This function initializes Flash driver by setting the register
 * instance to it.
 *
 * \param[in] flashDrvPtr : pointer to Flash driver instance
 * \return void
 */
#if(BOOTLOADER_CFG_FLS_COPY_AUTO_EN == 1u)
#pragma location = ".textrw"
#else
#pragma location = ".ram_flash_driver"
#endif
void Fls_Init(FlashDrvType * obj)
{
    /* Update the flash instance */
    *((uint32_t *)BOOTLOADER_CFG_FLS_DRV_RAM_START_ADDR) = (uint32_t)obj;
   
    Fls_ClearCommandState(obj);
    obj->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
 */
#if(BOOTLOADER_CFG_FLS_COPY_AUTO_EN == 1u)
#pragma location = ".textrw"
#else
#pragma location = ".ram_flash_driver"
#endif
Fls_StdRtnType
    Fls_Erase(uint32_t targetAddress, uint32_t sectorNum)
{
    Fls_StdRtnType  ret         = FLS_STD_RETURN_OK;
    FlashDrvType  *flashRegPtr;

    flashRegPtr = (FlashDrvType *)((*(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->reg) == 0u)
            {
                ret = FLS_STD_RETURN_BUSY;
                break;
            }
            else
            {
                Fls_ClearCommandState(flashRegPtr);

                FtfcReg_SetFccob(flashRegPtr->reg, 0, FLASHDRV_OPCMD_SCTERS);
                FtfcReg_SetFccob(flashRegPtr->reg, 1, FCCOBADDRLOW((targetAddress + (i * FLS_ERASE_SECTION_SIZE))));
                FtfcReg_SetFccob(flashRegPtr->reg, 2, FCCOBADDRMDLOW((targetAddress + (i * FLS_ERASE_SECTION_SIZE))));
                FtfcReg_SetFccob(flashRegPtr->reg, 3, FCCOBADDRMDHIG((targetAddress + (i * FLS_ERASE_SECTION_SIZE))));
                FtfcReg_SetFccob(flashRegPtr->reg, 4, FCCOBADDRHIG((targetAddress + (i * FLS_ERASE_SECTION_SIZE))));
                FtfcReg_SetFccob(flashRegPtr->reg, 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
 */
#if(BOOTLOADER_CFG_FLS_COPY_AUTO_EN == 1u)
#pragma location = ".textrw"
#else
#pragma location = ".ram_flash_driver"
#endif
Fls_StdRtnType
    Fls_Write(uint32_t targetAddress, const uint8_t* sourceAddressPtr, uint32_t bytesNum)
{
    Fls_StdRtnType ret = FLS_STD_RETURN_OK;
    FlashDrvType  *flashRegPtr;

    flashRegPtr = (FlashDrvType *)((*(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->reg) == 1)
        {
            eeramrdy = true;
        }

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

        offset   = (targetAddress & (FLS_PGM_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->reg))
        {
            ret = FLS_STD_RETURN_BUSY;
        }
        else
        {
            Fls_ClearCommandState(flashRegPtr);

            FtfcReg_SetFccob(flashRegPtr->reg, 0, FLASHDRV_OPCMD_SCTPGM);
            FtfcReg_SetFccob(flashRegPtr->reg, 1, FCCOBADDRLOW(targetAddress));
            FtfcReg_SetFccob(flashRegPtr->reg, 2, FCCOBADDRMDLOW(targetAddress));
            FtfcReg_SetFccob(flashRegPtr->reg, 3, FCCOBADDRMDHIG(targetAddress));
            FtfcReg_SetFccob(flashRegPtr->reg, 4, FCCOBADDRHIG(targetAddress));
            FtfcReg_SetFccob(flashRegPtr->reg, 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 Check the flash is write or not been write
 *
 * This function Check the flash is write or not been write
 *
 * \param[in] memAddress : the target address to begin to read in pflash
 * \return Fls_StdRtnType
 */
Fls_StdRtnType Fls_CheckWriteState(uint32_t memAddress)
{
    Fls_StdRtnType ret = FLS_STD_RETURN_OK;
    FlashDrvType  *obj;

    if((memAddress & FLS_PGM_SECTION_ADDR_ALIGNED_MASK) != 0u)
    {
        ret = FLS_STD_RETURN_ADDR_INVALID;
    }
    else
    {
        obj = (FlashDrvType *)((*(uint32_t *)BOOTLOADER_CFG_FLS_DRV_RAM_START_ADDR));

        if(0U == FtfcReg_GetFstatCcif(obj->reg))
        {
            ret = FLS_STD_RETURN_BUSY;
        }
        else
        {
            Fls_ClearCommandState(obj);
            FtfcReg_SetFccob(obj->reg, 0, FLASHDRV_OPCMD_SCTRD1S);
            FtfcReg_SetFccob(obj->reg, 1, FCCOBADDRLOW(memAddress));
            FtfcReg_SetFccob(obj->reg, 2, FCCOBADDRMDLOW(memAddress));
            FtfcReg_SetFccob(obj->reg, 3, FCCOBADDRMDHIG(memAddress));
            FtfcReg_SetFccob(obj->reg, 4, FCCOBADDRHIG(memAddress));
            FtfcReg_SetFccob(obj->reg, 5, (8u / FLASHDRV_PHRASE_SIZE) - 1);

            ret = Fls_CommandSequence(obj);
            /* Return success: means the flash is virgin, or the flash is write */
            if(ret == FLS_STD_RETURN_OK)
            {
                ret = FLS_STD_RETURN_VIRGIN;
            }
            else
            {
                ret = FLS_STD_RETURN_OK;
            }
        }
    }

    return ret;
}

#else

static void Fls_ClearCommandState(FlashDrvType *obj)
{
    FtfcReg_SetFstatRspErrF(obj->reg, 1);
    FtfcReg_SetFstatProtViolF(obj->reg, 1);
    FtfcReg_SetFstatAccErrF(obj->reg, 1);
    FtfcReg_SetFstatRdColF(obj->reg, 1);
    FtfcReg_SetFstatSecF(obj->reg, 1);
    FtfcReg_SetFstatDedF(obj->reg, 1);
}

static Fls_StdRtnType Fls_CommandSequence(FlashDrvType *obj)
{
    Fls_StdRtnType ret = FLS_STD_RETURN_OK;

    FtfcReg_SetFstatCcif(obj->reg, 1);

    while(0U == FtfcReg_GetFstatCcif(obj->reg))
    {
    }

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

    return ret;
}


/*! \brief Check the flash is write or not been write
 *
 * This function Check the flash is write or not been write
 *
 * \param[in] memAddress : the target address to begin to read in pflash
 * \return Fls_StdRtnType
 */
Fls_StdRtnType Fls_CheckWriteState(uint32_t memAddress)
{
    Fls_StdRtnType ret = FLS_STD_RETURN_OK;
    FlashDrvType  *obj;

    if((memAddress & FLS_PGM_SECTION_ADDR_ALIGNED_MASK) != 0u)
    {
        ret = FLS_STD_RETURN_ADDR_INVALID;
    }
    else
    {
        obj = (FlashDrvType *)((*(uint32_t *)BOOTLOADER_CFG_FLS_DRV_RAM_START_ADDR));

        if(0U == FtfcReg_GetFstatCcif(obj->reg))
        {
            ret = FLS_STD_RETURN_BUSY;
        }
        else
        {
            Fls_ClearCommandState(obj);
            FtfcReg_SetFccob(obj->reg, 0, FLASHDRV_OPCMD_SCTRD1S);
            FtfcReg_SetFccob(obj->reg, 1, FCCOBADDRLOW(memAddress));
            FtfcReg_SetFccob(obj->reg, 2, FCCOBADDRMDLOW(memAddress));
            FtfcReg_SetFccob(obj->reg, 3, FCCOBADDRMDHIG(memAddress));
            FtfcReg_SetFccob(obj->reg, 4, FCCOBADDRHIG(memAddress));
            FtfcReg_SetFccob(obj->reg, 5, (8u / FLASHDRV_PHRASE_SIZE) - 1);

            ret = Fls_CommandSequence(obj);
            /* Return success: means the flash is virgin, or the flash is write */
            if(ret == FLS_STD_RETURN_OK)
            {
                ret = FLS_STD_RETURN_VIRGIN;
            }
            else
            {
                ret = FLS_STD_RETURN_OK;
            }
        }
    }
    
    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
