/**********************************************************************************
 *
 * @file    eeprom.c
 * @brief   eeprom emulation file
 *
 * @date    1 Dec. 2022
 * @author  AE Team
 * @note
 *          Change Logs:
 *          Date            Author          Notes
 *          1 Dec. 2022     Lisq            the first version
 *
 * Copyright (C) Shanghai Eastsoft Microelectronics Co. Ltd. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the License); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an AS IS BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **********************************************************************************
 */

/** @addtogroup EEPROM_Emulation
  * @{
  */

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Public Variables ----------------------------------------------------------*/
/* Private Typedef -----------------------------------------------------------*/
/* Private Define ------------------------------------------------------------*/
/* Private Macros ------------------------------------------------------------*/
/* Private Variables ---------------------------------------------------------*/

/* Global variable used to store variable value in read sequence */
uint32_t g_data_var = 0U;

/* Private Constants  ---------------------------------------------------------*/
/* Private Function  ----------------------------------------------------------*/
/* Private function prototypes ----------------------------------------------- */

static uint32_t ee_format(void);
static uint32_t ee_find_valid_page(uint8_t operation);
static uint32_t ee_verify_pagefull_write_variable(uint32_t virt_address, uint32_t data);
static uint32_t ee_page_transfer(uint32_t virt_address, uint32_t data);

/**
  * @brief  Restore the pages to a known good state in case of page's status
  *   corruption after a power loss.
  * @param  None.
  * @retval - RESET: on write Flash error
  *         - SET: on success
  */
uint32_t ee_init(void)
{
    uint32_t page_status0 = 6U, page_status1 = 6U;
    uint32_t var_idx = 0U;
    uint32_t eeprom_status = 0U, read_status = 0U;
    int32_t x = -1;
    uint32_t flash_status;
    uint8_t addr_index;

    /* Get Page0 status */
    page_status0 = (*(__IO uint32_t *)PAGE0_BASE_ADDRESS);
    /* Get Page1 status */
    page_status1 = (*(__IO uint32_t *)PAGE1_BASE_ADDRESS);

    /* Check for invalid header states and repair if necessary */
    switch (page_status0)
    {
        case ERASED:
            if (page_status1 == VALID_PAGE) /* Page0 erased, Page1 valid */
            {
                /* Erase Page0 */
                for (addr_index = 0; addr_index < MCU_PAGE_NUM; addr_index++)
                {
                    IAP_FLASH_Unlock();
                    flash_status = IAP_Code_PageErase((PAGE0_BASE_ADDRESS + addr_index * MCU_PAGE_SIZE), ~(PAGE0_BASE_ADDRESS + addr_index * MCU_PAGE_SIZE));
                    IAP_FLASH_Lock();

                    /* If erase operation was failed, a Flash error code is returned */
                    if (flash_status != SET)
                        return flash_status;
                }
            }
            else if (page_status1 == RECEIVE_DATA) /* Page0 erased, Page1 receive */
            {
                /* Erase Page0 */
                for (addr_index = 0; addr_index < MCU_PAGE_NUM; addr_index++)
                {
                    IAP_FLASH_Unlock();
                    flash_status = IAP_Code_PageErase((PAGE0_BASE_ADDRESS + addr_index * MCU_PAGE_SIZE), ~(PAGE0_BASE_ADDRESS + addr_index * MCU_PAGE_SIZE));
                    IAP_FLASH_Lock();

                    /* If erase operation was failed, a Flash error code is returned */
                    if (flash_status != SET)
                        return flash_status;
                }

                /* Mark Page1 as valid */
                IAP_FLASH_Unlock();
                flash_status = IAP_Code_WordProgram(PAGE1_BASE_ADDRESS, ~PAGE1_BASE_ADDRESS, VALID_PAGE);
                IAP_FLASH_Lock();

                /* If program operation was failed, a Flash error code is returned */
                if (flash_status != SET)
                    return flash_status;
            }
            else /* First EEPROM access (Page0&1 are erased) or invalid state -> format EEPROM */
            {
                /* Erase both Page0 and Page1 and set Page0 as valid page */
                flash_status = ee_format();

                /* If erase/program operation was failed, a Flash error code is returned */
                if (flash_status != SET)
                    return flash_status;
            }

            break;

        case RECEIVE_DATA:
            if (page_status1 == VALID_PAGE) /* Page0 receive, Page1 valid */
            {
                /* Transfer data from Page1 to Page0 */
                for (var_idx = 0; var_idx < NB_OF_VAR; var_idx++)
                {
                    if ((*(__IO uint32_t *)(PAGE0_BASE_ADDRESS + 12)) == g_virt_add_var_tab[var_idx])
                        x = var_idx;

                    if (var_idx != x)
                    {
                        /* Read the last variables' updates */
                        read_status = ee_read_variable(g_virt_add_var_tab[var_idx], &g_data_var);

                        /* In case variable corresponding to the virtual address was found */
                        if (read_status != 0x1)
                        {
                            /* Transfer the variable to the Page0 */
                            eeprom_status = ee_verify_pagefull_write_variable(g_virt_add_var_tab[var_idx], g_data_var);

                            /* If program operation was failed, a Flash error code is returned */
                            if (eeprom_status != SET)
                                return eeprom_status;
                        }
                    }
                }

                /* Mark Page0 as valid */
                IAP_FLASH_Unlock();
                flash_status = IAP_Code_WordProgram(PAGE0_BASE_ADDRESS, ~PAGE0_BASE_ADDRESS, VALID_PAGE);
                IAP_FLASH_Lock();

                /* If program operation was failed, a Flash error code is returned */
                if (flash_status != SET)
                    return flash_status;

                /* Erase Page1 */
                for (addr_index = 0; addr_index < MCU_PAGE_NUM; addr_index++)
                {
                    IAP_FLASH_Unlock();
                    flash_status = IAP_Code_PageErase((PAGE1_BASE_ADDRESS + addr_index * MCU_PAGE_SIZE), ~(PAGE1_BASE_ADDRESS + addr_index * MCU_PAGE_SIZE));
                    IAP_FLASH_Lock();

                    /* If erase operation was failed, a Flash error code is returned */
                    if (flash_status != SET)
                        return flash_status;
                }
            }
            else if (page_status1 == ERASED) /* Page0 receive, Page1 erased */
            {
                /* Erase Page1 */
                for (addr_index = 0; addr_index < MCU_PAGE_NUM; addr_index++)
                {
                    IAP_FLASH_Unlock();
                    flash_status = IAP_Code_PageErase((PAGE1_BASE_ADDRESS + addr_index * MCU_PAGE_SIZE), ~(PAGE1_BASE_ADDRESS + addr_index * MCU_PAGE_SIZE));
                    IAP_FLASH_Lock();

                    /* If erase operation was failed, a Flash error code is returned */
                    if (flash_status != SET)
                        return flash_status;
                }

                /* Mark Page0 as valid */
                IAP_FLASH_Unlock();
                flash_status = IAP_Code_WordProgram(PAGE0_BASE_ADDRESS, ~PAGE0_BASE_ADDRESS, VALID_PAGE);
                IAP_FLASH_Lock();

                /* If program operation was failed, a Flash error code is returned */
                if (flash_status != SET)
                    return flash_status;
            }
            else /* Invalid state -> format eeprom */
            {
                /* Erase both Page0 and Page1 and set Page0 as valid page */
                flash_status = ee_format();

                /* If erase/program operation was failed, a Flash error code is returned */
                if (flash_status != SET)
                    return flash_status;
            }

            break;

        case VALID_PAGE:
            if (page_status1 == VALID_PAGE) /* Invalid state -> format eeprom */
            {
                /* Erase both Page0 and Page1 and set Page0 as valid page */
                flash_status = ee_format();

                /* If erase/program operation was failed, a Flash error code is returned */
                if (flash_status != SET)
                    return flash_status;
            }
            else if (page_status1 == ERASED) /* Page0 valid, Page1 erased */
            {
                /* Erase Page1 */
                for (addr_index = 0; addr_index < MCU_PAGE_NUM; addr_index++)
                {
                    IAP_FLASH_Unlock();
                    flash_status = IAP_Code_PageErase((PAGE1_BASE_ADDRESS + addr_index * MCU_PAGE_SIZE), ~(PAGE1_BASE_ADDRESS + addr_index * MCU_PAGE_SIZE));
                    IAP_FLASH_Lock();

                    /* If erase operation was failed, a Flash error code is returned */
                    if (flash_status != SET)
                        return flash_status;
                }
            }
            else /* Page0 valid, Page1 receive */
            {
                /* Transfer data from Page0 to Page1 */
                for (var_idx = 0; var_idx < NB_OF_VAR; var_idx++)
                {
                    if ((*(__IO uint32_t *)(PAGE1_BASE_ADDRESS + 12)) == g_virt_add_var_tab[var_idx])
                        x = var_idx;

                    if (var_idx != x)
                    {
                        /* Read the last variables' updates */
                        read_status = ee_read_variable(g_virt_add_var_tab[var_idx], &g_data_var);

                        /* In case variable corresponding to the virtual address was found */
                        if (read_status != 0x1)
                        {
                            /* Transfer the variable to the Page1 */
                            eeprom_status = ee_verify_pagefull_write_variable(g_virt_add_var_tab[var_idx], g_data_var);

                            /* If program operation was failed, a Flash error code is returned */
                            if (eeprom_status != SET)
                                return eeprom_status;
                        }
                    }
                }

                /* Mark Page1 as valid */
                IAP_FLASH_Unlock();
                flash_status = IAP_Code_WordProgram(PAGE1_BASE_ADDRESS, ~PAGE1_BASE_ADDRESS, VALID_PAGE);
                IAP_FLASH_Lock();

                /* If program operation was failed, a Flash error code is returned */
                if (flash_status != SET)
                    return flash_status;

                /* Erase Page0 */
                for (addr_index = 0; addr_index < MCU_PAGE_NUM; addr_index++)
                {
                    IAP_FLASH_Unlock();
                    flash_status = IAP_Code_PageErase((PAGE0_BASE_ADDRESS + addr_index * MCU_PAGE_SIZE), ~(PAGE0_BASE_ADDRESS + addr_index * MCU_PAGE_SIZE));
                    IAP_FLASH_Lock();

                    /* If erase operation was failed, a Flash error code is returned */
                    if (flash_status != SET)
                        return flash_status;
                }
            }

            break;

        default:  /* Any other state -> format eeprom */
            /* Erase both Page0 and Page1 and set Page0 as valid page */
            flash_status = ee_format();

            /* If erase/program operation was failed, a Flash error code is returned */
            if (flash_status != SET)
                return flash_status;

            break;
    }

    return SET;
}

/**
  * @brief  Returns the last stored variable data, if found, which correspond to
  *   the passed virtual address
  * @param  virt_address: Variable virtual address
  * @param  data: Global variable contains the read variable value
  * @retval Success or error status:
  *           - 0: if variable was found
  *           - 1: if the variable was not found
  *           - NO_VALID_PAGE: if no valid page was found.
  */
uint32_t ee_read_variable(uint32_t virt_address, uint32_t *data)
{
    uint32_t valid_page = PAGE0;
    uint32_t address_value = 0x5555U, read_status = 1U;
    uint32_t address = EEPROM_START_ADDRESS, page_startaddress = EEPROM_START_ADDRESS;

    /* Get active Page for read operation */
    valid_page = ee_find_valid_page(READ_FROM_VALID_PAGE);

    /* Check if there is no valid page */
    if (valid_page == NO_VALID_PAGE)
        return  NO_VALID_PAGE;

    /* Get the valid Page start address */
    page_startaddress = (uint32_t)(EEPROM_START_ADDRESS + (uint32_t)(valid_page * PAGE_SIZE));

    /* Get the valid Page end address */
    address = (uint32_t)((EEPROM_START_ADDRESS - 4) + (uint32_t)((1 + valid_page) * PAGE_SIZE));

    /* Check each active page address starting from end */
    while (address >= (page_startaddress + 4))
    {
        /* Get the current location content to be compared with virtual address */
        address_value = (*(__IO uint32_t *)address) >> 16;

        /* Compare the read address with the virtual address */
        if (address_value == virt_address)
        {
            /* Get content of address-4 which is variable value */
            *data = (*(__IO uint32_t *)address) & 0xFFFF;

            /* In case variable value is read, reset read_status flag */
            read_status = 0;

            break;
        }
        else
        {
            /* Next address location */
            address = address - 4;
        }
    }

    /* Return read_status value: (0: variable exist, 1: variable doesn't exist) */
    return read_status;
}

/**
  * @brief  Writes/upadtes variable data in EEPROM.
  * @param  virt_address: Variable virtual address
  * @param  data: 32 bit data to be written
  * @retval Success or error status:
  *           - SET: on success
  *           - PAGE_FULL: if valid page is full
  *           - NO_VALID_PAGE: if no valid page was found
  *           - RESET: on write Flash error
  */
uint32_t ee_write_variable(uint32_t virt_address, uint32_t data)
{
    uint32_t status = 0U;

    /* Write the variable virtual address and value in the EEPROM */
    status = ee_verify_pagefull_write_variable(virt_address, data);

    /* In case the EEPROM active page is full */
    if (status == PAGE_FULL)
        status = ee_page_transfer(virt_address, data);  /* Perform Page transfer */

    /* Return last operation status */
    return status;
}

/**
  * @brief  Erases PAGE and PAGE1 and writes VALID_PAGE header to PAGE
  * @param  None
  * @retval Status of the last operation (Flash write or erase) done during
  *         EEPROM formating
  */
static uint32_t ee_format(void)
{
    uint32_t flash_status = SET;
    uint8_t addr_index;

    /* Erase Page0 */
    for (addr_index = 0; addr_index < MCU_PAGE_NUM; addr_index++)
    {
        IAP_FLASH_Unlock();
        flash_status = IAP_Code_PageErase((PAGE0_BASE_ADDRESS + addr_index * MCU_PAGE_SIZE), ~(PAGE0_BASE_ADDRESS + addr_index * MCU_PAGE_SIZE));
        IAP_FLASH_Lock();

        /* If erase operation was failed, a Flash error code is returned */
        if (flash_status != SET)
            return flash_status;
    }

    /* Set Page0 as valid page: Write VALID_PAGE at Page0 base address */
    IAP_FLASH_Unlock();
    flash_status = IAP_Code_WordProgram(PAGE0_BASE_ADDRESS, ~PAGE0_BASE_ADDRESS, VALID_PAGE);
    IAP_FLASH_Lock();

    /* If program operation was failed, a Flash error code is returned */
    if (flash_status != SET)
        return flash_status;

    /* Erase Page1 */
    for (addr_index = 0; addr_index < MCU_PAGE_NUM; addr_index++)
    {
        IAP_FLASH_Unlock();
        flash_status = IAP_Code_PageErase((PAGE1_BASE_ADDRESS + addr_index * MCU_PAGE_SIZE), ~(PAGE1_BASE_ADDRESS + addr_index * MCU_PAGE_SIZE));
        IAP_FLASH_Lock();

        /* If erase operation was failed, a Flash error code is returned */
        if (flash_status != SET)
            return flash_status;
    }

    /* Return Page1 erase operation status */
    return flash_status;
}

/**
  * @brief  Find valid Page for write or read operation
  * @param  operation: operation to achieve on the valid page.
  *   This parameter can be one of the following values:
  *     @arg READ_FROM_VALID_PAGE: read operation from valid page
  *     @arg WRITE_IN_VALID_PAGE: write operation from valid page
  * @retval Valid page number (PAGE or PAGE1) or NO_VALID_PAGE in case
  *   of no valid page was found
  */
static uint32_t ee_find_valid_page(uint8_t operation)
{
    uint32_t page_status0 = 6U, page_status1 = 6U;

    /* Get Page0 actual status */
    page_status0 = (*(__IO uint32_t *)PAGE0_BASE_ADDRESS);

    /* Get Page1 actual status */
    page_status1 = (*(__IO uint32_t *)PAGE1_BASE_ADDRESS);

    /* Write or read operation */
    switch (operation)
    {
        case WRITE_IN_VALID_PAGE:   /* ---- Write operation ---- */
            if (page_status1 == VALID_PAGE)
            {
                /* Page0 receiving data */
                if (page_status0 == RECEIVE_DATA)
                    return PAGE0;         /* Page0 valid */
                else
                    return PAGE1;         /* Page1 valid */
            }
            else if (page_status0 == VALID_PAGE)
            {
                /* Page1 receiving data */
                if (page_status1 == RECEIVE_DATA)
                    return PAGE1;         /* Page1 valid */
                else
                    return PAGE0;         /* Page0 valid */
            }
            else
            {
                return NO_VALID_PAGE;   /* No valid Page */
            }

        case READ_FROM_VALID_PAGE:  /* ---- Read operation ---- */
            if (page_status0 == VALID_PAGE)
                return PAGE0;           /* Page0 valid */
            else if (page_status1 == VALID_PAGE)
                return PAGE1;           /* Page1 valid */
            else
                return NO_VALID_PAGE ;  /* No valid Page */

        default:
            return PAGE0;             /* Page0 valid */
    }
}

/**
  * @brief  Verify if active page is full and Writes variable in EEPROM.
  * @param  virt_address: 32 bit virtual address of the variable
  * @param  data: 32 bit data to be written as variable value
  * @retval Success or error status:
  *           - SET: on success
  *           - PAGE_FULL: if valid page is full
  *           - NO_VALID_PAGE: if no valid page was found
  *           - RESET: on write Flash error
  */
static uint32_t ee_verify_pagefull_write_variable(uint32_t virt_address, uint32_t data)
{
    uint32_t flash_status = SET;
    uint32_t valid_page = PAGE0;
    uint32_t address = EEPROM_START_ADDRESS;
    uint32_t page_endaddress = EEPROM_START_ADDRESS + PAGE_SIZE;

    /* Get valid Page for write operation */
    valid_page = ee_find_valid_page(WRITE_IN_VALID_PAGE);

    /* Check if there is no valid page */
    if (valid_page == NO_VALID_PAGE)
        return NO_VALID_PAGE;

    /* Get the valid Page start address */
    address = (uint32_t)(EEPROM_START_ADDRESS + (uint32_t)(valid_page * PAGE_SIZE));

    /* Get the valid Page end address */
    page_endaddress = (uint32_t)((EEPROM_START_ADDRESS - 4) + (uint32_t)((1 + valid_page) * PAGE_SIZE));

    /* Check each active page address starting from begining */
    while (address <= page_endaddress)
    {
        /* Verify if address and address+4 contents are 0xFFFFFFFF */
        if ((*(__IO uint32_t *)address) == 0xFFFFFFFF)
        {
            /* Set variable virtual address and data */
            IAP_FLASH_Unlock();
            flash_status = IAP_Code_WordProgram(address, ~address, (virt_address << 16) | data);
            IAP_FLASH_Lock();

            /* Return program operation status */
            return flash_status;
        }
        else
        {
            /* Next address location */
            address = address + 4;
        }
    }

    /* Return PAGE_FULL in case the valid page is full */
    return PAGE_FULL;
}

/**
  * @brief  Transfers last updated variables data from the full Page to
  *   an empty one.
  * @param  virt_address: 32 bit virtual address of the variable
  * @param  data: 32 bit data to be written as variable value
  * @retval Success or error status:
  *           - SET: on success
  *           - PAGE_FULL: if valid page is full
  *           - NO_VALID_PAGE: if no valid page was found
  *           - RESET: on write Flash error
  */
static uint32_t ee_page_transfer(uint32_t virt_address, uint32_t data)
{
    uint32_t flash_status = SET;
    uint32_t new_pageaddress = EEPROM_START_ADDRESS;
    uint32_t old_pageaddress;
    uint32_t valid_page = PAGE0, var_idx = 0U;
    uint32_t eeprom_status = 0U, read_status = 0U;
    uint8_t addr_index;

    /* Get active Page for read operation */
    valid_page = ee_find_valid_page(READ_FROM_VALID_PAGE);

    if (valid_page == PAGE1)       /* Page1 valid */
    {
        /* New page address where variable will be moved to */
        new_pageaddress = PAGE0_BASE_ADDRESS;

        /* Old page ID where variable will be taken from */
        old_pageaddress = PAGE1_BASE_ADDRESS;
    }
    else if (valid_page == PAGE0)  /* Page0 valid */
    {
        /* New page address  where variable will be moved to */
        new_pageaddress = PAGE1_BASE_ADDRESS;

        /* Old page ID where variable will be taken from */
        old_pageaddress = PAGE0_BASE_ADDRESS;
    }
    else
    {
        return NO_VALID_PAGE;       /* No valid Page */
    }

    /* Set the new Page status to RECEIVE_DATA status */
    IAP_FLASH_Unlock();
    flash_status = IAP_Code_WordProgram(new_pageaddress, ~new_pageaddress, RECEIVE_DATA);
    IAP_FLASH_Lock();

    /* If program operation was failed, a Flash error code is returned */
    if (flash_status != SET)
        return flash_status;

    /* Write the variable passed as parameter in the new active page */
    eeprom_status = ee_verify_pagefull_write_variable(virt_address, data);

    /* If program operation was failed, a Flash error code is returned */
    if (eeprom_status != SET)
        return eeprom_status;

    /* Transfer process: transfer variables from old to the new active page */
    for (var_idx = 0; var_idx < NB_OF_VAR; var_idx++)
    {
        if (g_virt_add_var_tab[var_idx] != virt_address)  /* Check each variable except the one passed as parameter */
        {
            /* Read the other last variable updates */
            read_status = ee_read_variable(g_virt_add_var_tab[var_idx], &g_data_var);

            /* In case variable corresponding to the virtual address was found */
            if (read_status != 0x1)
            {
                /* Transfer the variable to the new active page */
                eeprom_status = ee_verify_pagefull_write_variable(g_virt_add_var_tab[var_idx], g_data_var);

                /* If program operation was failed, a Flash error code is returned */
                if (eeprom_status != SET)
                    return eeprom_status;
            }
        }
    }

    /* Erase the old Page: Set old Page status to ERASED status */
    for (addr_index = 0; addr_index < MCU_PAGE_NUM; addr_index++)
    {
        IAP_FLASH_Unlock();
        flash_status = IAP_Code_PageErase((old_pageaddress + addr_index * MCU_PAGE_SIZE), ~(old_pageaddress + addr_index * MCU_PAGE_SIZE));
        IAP_FLASH_Lock();

        /* If erase operation was failed, a Flash error code is returned */
        if (flash_status != SET)
            return flash_status;
    }

    /* Set new Page status to VALID_PAGE status */
    IAP_FLASH_Unlock();
    flash_status = IAP_Code_WordProgram(new_pageaddress, ~new_pageaddress, VALID_PAGE);
    IAP_FLASH_Lock();

    /* If program operation was failed, a Flash error code is returned */
    if (flash_status != SET)
        return flash_status;

    /* Return last operation flash status */
    return flash_status;
}

/**
  * @}
  */

/************* (C) COPYRIGHT Eastsoft Microelectronics *****END OF FILE****/
