// EEPROM.c

// EEPROM emulator functions

#include "defs.h"
#include "EEPROM.h"

typedef enum { 
  FLASH_ERROR,
  FLASH_COMPLETE,
  PAGE0,
  PAGE1,
  ERASED,
  RECEIVE_DATA,
  VALID_PAGE,
  NO_VALID_PAGE,
  PAGE_FULL,
  VARIABLE_EXIST,
  VARIABLE_DOESNOT_EXIST
} FLASH_Status;

// Valid pages in read and write defines
#define READ_FROM_VALID_PAGE    0x00
#define WRITE_IN_VALID_PAGE     0x01

static FLASH_Status FLASH_Save(u16 Address, u16 Data) 
{
  u8 status;

  FEEMOD = 0x8;				// enable erase/write command

  FEEADR = Address;
  FEEDAT = Data;
  FEECON = 0x02;  				// single Write command
  status = FEESTA & 0x03;
  while (!(status)) status = FEESTA & 0x03;
  if ((status & 0x02)==0x02) return FLASH_ERROR;

  FEEMOD = 0;				// disable erase/write command

  if ((status & 0x02)==0x02) return FLASH_ERROR;

  return FLASH_COMPLETE;
}

static u16 FLASH_Load(u16 Address)
{
  u8 status;

  FEEADR = Address;
  FEECON = 0x01;				// single read command

  status = FEESTA & 0x03;
  while (!(status)) status = FEESTA & 0x03;
//	if ((status&0x02)==0x02) EEPROMerr = 1;

  return (FEEDAT);		
} 


static FLASH_Status FLASH_ErasePage(u16 Page_Address)
{
  u8 status;

  FEEMOD = 0x8;				// enable erase/write command

  FEEADR = Page_Address;
  FEECON = 0x05;				// erase page command
  status = FEESTA & 0x03;
  while (!(status)) status = FEESTA & 0x03;

  FEEMOD = 0;				// disable erase/write command

  if ((status & 0x02)==0x02) return FLASH_ERROR;
  return FLASH_COMPLETE;
}


Bool EE_Erased(void)
{
   return ((FLASH_Load(PAGE0_BASE_ADDRESS) == 0xFFFF) &&
           (FLASH_Load(PAGE0_BASE_ADDRESS + 2) == 0xFFFF) &&
           (FLASH_Load(PAGE1_BASE_ADDRESS) == 0xFFFF) &&
           (FLASH_Load(PAGE1_BASE_ADDRESS + 2) == 0xFFFF));
}


/* Global variable used to store variable value in read sequence */
u16 DataVar = 0 ;

/* Private functions ---------------------------------------------------------*/
static FLASH_Status EE_FindValidPage(u8 operation);
static FLASH_Status EE_VerifyPageFullWriteVariable(u16 VirtAddress, u16 Data);
static FLASH_Status EE_PageTransfer(u16 VirtAddress, u16 Data);
static FLASH_Status EE_ReadVariable(u16 VirtAddress, u16* Read_data);

/*******************************************************************************
* Function Name  : EE_Format
* Description    : Erases PAGE0 and PAGE1 and writes VALID_PAGE header to PAGE0
* Return         : Status of the last operation (Flash write or erase) done during
*                  EEPROM formating
*******************************************************************************/
Bool EE_Format(void)
{
  FLASH_Status FlashStatus;

  FlashStatus = FLASH_ErasePage(PAGE0_BASE_ADDRESS);
  if (FlashStatus == FLASH_COMPLETE) {
    // Set Page0 as valid page: Write VALID_PAGE at Page0 base address
    FlashStatus = FLASH_Save(PAGE0_BASE_ADDRESS, VALID_PAGE);
    if (FlashStatus == FLASH_COMPLETE)
      FlashStatus = FLASH_ErasePage(PAGE1_BASE_ADDRESS);
  }

  return (FlashStatus == FLASH_COMPLETE);
}



/*******************************************************************************
* Function Name  : EE_FindValidPage
* Description    : Find valid Page for write or read operation
* Input          : - Operation: operation to achieve on the valid page:
*                      - READ_FROM_VALID_PAGE: read operation from valid page
*                      - WRITE_IN_VALID_PAGE: write operation from valid page
* Output         : None
* Return         : Valid page number (PAGE0 or PAGE1) or NO_VALID_PAGE in case
*                  of no valid page was found
*******************************************************************************/
FLASH_Status EE_FindValidPage(u8 Operation)
{
  FLASH_Status PageStatus0, PageStatus1;

  PageStatus0 = FLASH_Load(PAGE0_BASE_ADDRESS);
  PageStatus1 = FLASH_Load(PAGE1_BASE_ADDRESS);

  // Write or read operation
  switch (Operation) {
    case WRITE_IN_VALID_PAGE:   // ---- Write operation ----
      if (PageStatus1 == VALID_PAGE) {
        // Page0 receiving data
        if (PageStatus0 == RECEIVE_DATA) {
          return PAGE0;   // Page0 valid
        }
        else {
          return PAGE1;   // Page1 valid
        }
      }
      else if (PageStatus0 == VALID_PAGE) {
        // Page1 receiving data
        if (PageStatus1 == 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 (PageStatus0 == VALID_PAGE) {
        return PAGE0;           // Page0 valid
      }
      else if (PageStatus1 == VALID_PAGE) {
        return PAGE1;           // Page1 valid
      }
      else {
        return NO_VALID_PAGE ;  // No valid Page
      }

    default:
      return PAGE0;       // Page0 valid
  }
}

/*******************************************************************************
* Function Name  : EE_VerifyPageFullWriteVariable
* Description    : Verify if active page is full and Writes variable in EEPROM.
* Input          : - VirtAddress: 16 bit virtual address of the variable
*                  - Data: 16 bit data to be written as variable value
* Output         : None
* Return         : - Success or error status: 
*                      - FLASH_COMPLETE: on success
*                      - PAGE_FULL: if valid page is full
*                      - NO_VALID_PAGE: if no valid page was found
*                      - Flash error code: on write Flash error
*******************************************************************************/
FLASH_Status EE_VerifyPageFullWriteVariable(u16 VirtAddress, u16 Data)
{
  FLASH_Status FlashStatus;
  FLASH_Status ValidPage;
  u16 Address, PageEndAddress;

  // Get valid Page for write operation
  ValidPage = EE_FindValidPage(WRITE_IN_VALID_PAGE);
  if (ValidPage == NO_VALID_PAGE)
    return  NO_VALID_PAGE;

  // Get the valid Page start Address
  Address = (EEPROM_START_ADDRESS + (ValidPage * PAGE_SIZE) + 4);

  // Get the valid Page end Address
  PageEndAddress = (EEPROM_START_ADDRESS + ((1 + ValidPage) * PAGE_SIZE) - 2);
  // Check each active page address starting from begining
  while (Address < PageEndAddress) {
    // Verify if Address and Address+2 are free
    if ((FLASH_Load(Address) == 0xFFFF) && (FLASH_Load(Address + 2) == 0xFFFF)) {
      FlashStatus = FLASH_Save(Address, Data);
      if (FlashStatus == FLASH_COMPLETE)
        FlashStatus = FLASH_Save(Address + 2, VirtAddress);
      return FlashStatus;
    }
    else {
      // Next address location
      Address = Address + 4;
    }
  }

  return PAGE_FULL;
}

/*******************************************************************************
* Function Name  : EE_ReadVariable
* Description    : Returns the last stored variable data, if found, which 
*                  correspond to the passed virtual address
* Input          : - VirtAddress: Variable virtual address
*                  - Read_data: Global variable contains the read variable value
* Output         : None
* Return         : - 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.
*******************************************************************************/
FLASH_Status EE_ReadVariable(u16 VirtAddress, u16* ReadData)
{
  FLASH_Status ValidPage;
  u16 AddressValue;
  u16 Address, PageStartAddress;

  // Get active Page for read operation
  ValidPage = EE_FindValidPage(READ_FROM_VALID_PAGE);

  if (ValidPage == NO_VALID_PAGE)
    return  NO_VALID_PAGE;

  // Get the valid Page start Address
  PageStartAddress = (EEPROM_START_ADDRESS + (ValidPage * PAGE_SIZE) + 4);
  // Get the valid Page end Address
  Address = (EEPROM_START_ADDRESS + ((1 + ValidPage) * PAGE_SIZE) - 2);

  // Check each active page address starting from end
  while (Address >= (PageStartAddress + 2)) {
    // Get the current location content to be compared with virtual address
    AddressValue = FLASH_Load(Address);
    // Compare the read address with the virtual address
    if (AddressValue == VirtAddress) {
      // Get content of Address-2 which is variable value
      *ReadData = FLASH_Load(Address - 2);
      return VARIABLE_EXIST;
    }
    else {
      // Next address location
      Address = Address - 4;
    }
  }

  return VARIABLE_DOESNOT_EXIST;
}

Bool EE_Read(u16 VirtAddress, u16* Read_data)
{
  u16 *DataPtr = Read_data;
  
  return (EE_ReadVariable(VirtAddress, DataPtr) == VARIABLE_EXIST);

}

/*******************************************************************************
* Function Name  : EE_PageTransfer
* Description    : Transfers last updated variable data from the full Page to 
*                  an empty one.
* Input          : - VirtAddress: 16 bit virtual address of the variable
*                  - Data: 16 bit data to be written as variable value
* Output         : None
* Return         : - Success or error status: 
*                      - FLASH_COMPLETE: on success,
*                      - PAGE_FULL: if valid page is full
*                      - NO_VALID_PAGE: if no valid page was found
*                      - Flash error code: on write Flash error
*******************************************************************************/
FLASH_Status EE_PageTransfer(u16 VirtAddress, u16 Data)
{
  FLASH_Status FlashStatus;
  u16 NewPageAddress, OldPageAddress;
  FLASH_Status ValidPage;
  u16 VarIdx;
  FLASH_Status EepromStatus, ReadStatus;

  // Get active Page for read operation
  ValidPage = EE_FindValidPage(READ_FROM_VALID_PAGE);

  if (ValidPage == PAGE1) {
    NewPageAddress = PAGE0_BASE_ADDRESS;
    OldPageAddress = PAGE1_BASE_ADDRESS;
  }
  else if (ValidPage == PAGE0) {
    NewPageAddress = PAGE1_BASE_ADDRESS;
    OldPageAddress = PAGE0_BASE_ADDRESS;
  }
  else
    return NO_VALID_PAGE;

  // Set the new Page status to RECEIVE_DATA status
  FlashStatus = FLASH_Save(NewPageAddress, RECEIVE_DATA);
  if (FlashStatus != FLASH_COMPLETE)
    return FlashStatus;

  // Write the variable passed as parameter in the new active page
  EepromStatus = EE_VerifyPageFullWriteVariable(VirtAddress, Data);
  if (EepromStatus != FLASH_COMPLETE)
    return EepromStatus;

  // Transfer process: transfer variables from old to the new active page
  for (VarIdx = 0; VarIdx < NumbOfVar; VarIdx++) {
    // Check each variable except the one passed as parameter
    if (VarIdx != VirtAddress) { 
      // Read the other last variable updates
      ReadStatus = EE_ReadVariable(VarIdx, &DataVar);
      // In case variable corresponding to the virtual address was found
      if (ReadStatus == VARIABLE_EXIST) {
        // Transfer the variable to the new active page
        EepromStatus = EE_VerifyPageFullWriteVariable(VarIdx, DataVar);
        // If program operation was failed, a Flash error code is returned
        if (EepromStatus != FLASH_COMPLETE) {
          return EepromStatus;
        }
      }
    }
  }

  // Erase the old Page: Set old Page status to ERASED status
  FlashStatus = FLASH_ErasePage(OldPageAddress);
  if (FlashStatus == FLASH_COMPLETE)
    FlashStatus = FLASH_Save(NewPageAddress, VALID_PAGE);

  return FlashStatus;
}

/*******************************************************************************
* Function Name  : EE_WriteVariable
* Description    : Writes/upadtes variable data in EEPROM.
* Input          : - VirtAddress: Variable virtual address
*                  - Data: 16 bit data to be written
* Output         : None
* Return         : - Success or error status: 
*                      - FLASH_COMPLETE: on success,
*                      - PAGE_FULL: if valid page is full
*                      - NO_VALID_PAGE: if no valid page was found
*                      - Flash error code: on write Flash error
*******************************************************************************/
Bool EE_Write(u16 VirtAddress, u16 Data)
{
  FLASH_Status Status;

  // Write the variable virtual address and value in the EEPROM
  Status = EE_VerifyPageFullWriteVariable(VirtAddress, Data);
  // In case the EEPROM active page is full
  if (Status == PAGE_FULL)
    Status = EE_PageTransfer(VirtAddress, Data);

  return (Status == FLASH_COMPLETE);
}
