/***********************************************************************
 * DISCLAIMER:                                                         *
 * The software supplied by Renesas Technology America Inc. is         *
 * intended and supplied for use on Renesas Technology products.       *
 * This software is owned by Renesas Technology America, Inc. or       *
 * Renesas Technology Corporation and is protected under applicable    *
 * copyright laws. All rights are reserved.                            *
 *                                                                     *
 * 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. RENESAS TECHNOLOGY AMERICA, INC. AND        *
 * AND RENESAS TECHNOLOGY CORPORATION RESERVE THE RIGHT, WITHOUT       *
 * NOTICE, TO MAKE CHANGES TO THIS SOFTWARE. NEITHER RENESAS           *
 * TECHNOLOGY AMERICA, INC. NOR RENESAS TECHNOLOGY CORPORATION SHALL,  *
 * IN ANY CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR         *
 * CONSEQUENTIAL DAMAGES FOR ANY REASON WHATSOEVER ARISING OUT OF THE  *
 * USE OR APPLICATION OF THIS SOFTWARE.                                *
 ***********************************************************************/

/*****************************************************************************
*
*   File Name: VirEE_Byte.c
*
*   Content:   Provides variable record length Virtual EEPROM Functions
*
*   Copyright (c) 2004 Renesas Technology America , Inc.
*   All rights reserved
*=============================================================================
*   $Revision: 1.2 $ $Date: 2004-01-19 15:31:06-05 $
*===========================================================================*/

#include "flash_api.h"      /* uses EW1 for actual flash erase/writes */
#include "virtEE_Variable.h"    /* Application interface */

#define NV_BLK_STATUS (BLOCK_SIZE-1)

#define BLOCK_FULL (0xAA55)

#pragma ASM
 .section NV_block_1,DATA
 .org ADDR_BLOCK_LO
_nv_block_lo:
 .blkw BLOCK_SIZE
 .section NV_block_2,DATA
 .org ADDR_BLOCK_HI
_nv_block_hi:
 .blkw BLOCK_SIZE
#pragma ENDASM

extern NV_FAR unsigned int nv_block_hi[BLOCK_SIZE];
extern NV_FAR unsigned int nv_block_lo[BLOCK_SIZE];

static unsigned int NV_FAR *pBlk[2] = {nv_block_lo, nv_block_hi};
static unsigned char api_block[2] = {API_BLOCK_LO,API_BLOCK_HI};

typedef struct
{
  unsigned char record_label; /* arbitrary value 0x00 to 0xFE to distinguish between different record types */
  unsigned char data_size;    /* number of data WORDS (16-bit values) -1 associated with this record type. 0x00=1 Word size... 0xFF=256 Word size */
  unsigned int data;          /* first word of data */
} NV_header_type;

typedef union
{
  NV_header_type NV_FAR *p;
  unsigned int NV_FAR *pWord;
} pNV_header_type;

static NV_header_type NV_FAR * seek_record(unsigned char block, unsigned char record_label);
static unsigned char defrag(void);
static unsigned char blank_check(unsigned char block);

/*****************************************************************************
Name:       NV_Read
Parameters:   NV_type *pNV: structure indicating record type
Returns:      0=OK, 1=record not found, 2=invalid state NG...pNV->pData data location if OK
Description:  locates data stored in NV
*****************************************************************************/
unsigned char NV_Read(NV_type *pNV)
{
  pNV_header_type tmp[2];
  unsigned char select;
  unsigned char status;

  /* ensure defrag not in process */
  if (0 != (status = defrag()))
    return(status);

  /* check range */
  if ((0xFF != pNV->record_label) && (pNV->record_label > MAX_RECORD_LABEL))
    return(1);

  /* seek the requested record in both blocks */
  tmp[0].p = seek_record(0, pNV->record_label);
  tmp[1].p = seek_record(1, pNV->record_label);

  /* if both records are valid, there's a problem */
  if ((0 != tmp[0].p) && (0 != tmp[1].p))
  {
    if (0xFF != pNV->record_label)
      return(2);                /* there are two valid pointers...this is a problem (unless we're seeking terminal record) */
    if (pBlk[1] == tmp[1].pWord)
      select = 0;
    else if (pBlk[0] == tmp[0].pWord)
      select = 1;
    else
      return(2);                /* neither terminal record is at beginning of block...this is an invalid state */
  }
  else if (0 != tmp[0].p)
    select = 0;                 /* only pointer 0 is valid */
  else if (0 != tmp[1].p)
    select = 1;                 /* only pointer 1 is vaid */
  else
    return(1);                  /* no valid data has been found */

  /* transfer data structure information to requester */
  pNV->data_size = tmp[select].p->data_size;
  pNV->pData = &tmp[select].p->data;
  return(0);
}

/*****************************************************************************
Name:       NV_Write
Parameters:   NV_type *pNV: structure indicating source information
Returns:      0=OK, 1=NG
Description:  Stores data structure in flash memory.
*****************************************************************************/
unsigned char NV_Write(NV_type *pNV)
{
  char swap = 0;
  unsigned int block_full = BLOCK_FULL;
  pNV_header_type pHdr;
  unsigned char block;
  NV_type tmp;
  unsigned char status;

  /* ensure defrag not in process */
  if (0 != (status = defrag()))
    return(status);

  /* check range */
  if (pNV->record_label > MAX_RECORD_LABEL)
    return(1);

  /* check if data structure to write is same as already in memory.... */
  tmp = *pNV;

  status = NV_Read(&tmp);
  switch (status)
  {
    case 0:
    {
      /* record found */
      /* compare existing data structure to old version */
      unsigned int index;
      char same = 1;

      if (tmp.data_size != pNV->data_size)
        same = 0; /* sizes are different */
      else
      {
        for (index = 0; index <= (unsigned int)tmp.data_size; index++)     /* <= because data_size is size-1 */
          if (tmp.pData[index] != pNV->pData[index])
          {
            same = 0;
            break;          /* stop testing  */
          }
      }

      if (same == 1)
        return(0);

      break;
    }

    case 1:
    {
      /* record not found */
      break;
    }

    default:
    {
      /* invalid state */
      return(status);
    }
  }

  tmp.record_label = 0xFF; /* seek termination record */
  if (0 != (status = NV_Read(&tmp)))
    return(status);

  /* back out the header address based on the data address (-1 because first data word is in header) */
  pHdr.pWord = tmp.pData-((sizeof(NV_header_type)/2)-1);

  if (pHdr.pWord < pBlk[1])
    block = 0;
  else
    block = 1;

  /* determine if data will fit in remaining block area */
  if ((&tmp.pData[pNV->data_size] - pBlk[block]) >= (NV_BLK_STATUS-1))   /* ensure there will be enough room for record, final termination record and status */
  {
    swap = 1;    /* data won't fit */
  }
  else
  {
    /* record will fit, ensure record area is blank */
    unsigned int index;
    for (index = 0; index < (pNV->data_size+(sizeof(NV_header_type)/2)); index++)
      if (0xFFFF != pHdr.pWord[index])
        swap = 1;
  }

  if (swap == 1)
  {
    /* mark the block as full...this will start defrag process */
    if (0 != (status = FlashWrite((FLASH_PTR_TYPE)&pBlk[block][NV_BLK_STATUS],(BUF_PTR_TYPE)&block_full, 2)))
      return(status);

    /* set header pointer to opposite block (start location) */
    block = (block + 1) & 0x01;
    pHdr.pWord = pBlk[block];

    /* ensure we're ok to use it */
    if (0 != (status = blank_check(block)))
      return(status);
  }

  /* write the data structure to memory */
  if (0 != (status = FlashWrite((FLASH_PTR_TYPE)&pHdr.p->data, (BUF_PTR_TYPE)pNV->pData, ((unsigned int)pNV->data_size+1)*2)))
    return(status);

  /* write header type/size to memory now that data is written */
  if (0 != (status = FlashWrite( (FLASH_PTR_TYPE)&pHdr.p->record_label, (BUF_PTR_TYPE)(unsigned int *)pNV, 2)))
    return(status);

  /* if we're swapping, this will transfer old blocks...if not, it will just return */
  return(defrag());
}

/*****************************************************************************
Name:       seek_record
Parameters:   block : block to search.
              record_label: type of record to search for.
Returns:       NV_header_type *: pointer to active record of requested type...0 if no header located
Description:   helper function for NV_Read to locate most valid record in block
*****************************************************************************/
static NV_header_type NV_FAR * seek_record(unsigned char block, unsigned char record_label)
{
  NV_header_type NV_FAR *pRecord = 0;
  pNV_header_type pHdr;
  pHdr.pWord = pBlk[block];

  if ((0xFFFF != pBlk[block][NV_BLK_STATUS]) && (BLOCK_FULL != pBlk[block][NV_BLK_STATUS]))
    return(pRecord);                        /* invalid block status */

  while ((pHdr.pWord  - pBlk[block]) < NV_BLK_STATUS)       /* use difference math to accomodate 16-bit range */
  {
      wdtr  = 0;
    /* ensure some level of data integrity */
    if (pHdr.p->record_label != 0xFF)
      if ((&(&pHdr.p->data)[pHdr.p->data_size] - pBlk[block]) >= NV_BLK_STATUS)    /* use difference math to accomodate 16-bit range */
        return(pRecord);                    /* non-termination record not wholly within block */

    if (pHdr.p->record_label == record_label)
      pRecord = pHdr.p;                     /* target record found */

    if (pHdr.p->record_label == 0xFF)       /* termination record found */
      break;

    pHdr.pWord += pHdr.p->data_size + (sizeof(NV_header_type)/2); /* increment header pointer...no need to add 1 to size because first word is allocated in header */
  }

  return(pRecord);
}

/*****************************************************************************
Name:       blank_check
Parameters:   block : block to check
Returns:       0=OK, 1=NG
Description:   check a block for blank, erase if not.
*****************************************************************************/
static unsigned char blank_check(unsigned char block)
{
  unsigned int index;
  char erase = 0;

  wdtr  = 0;

  /* check status of new block and erase as necessary */
  for (index = 0; index < BLOCK_SIZE; index++)
    if(pBlk[block][index] != 0xFFFF)
    {
      erase = 1;
      break;
    }

  if (erase == 1)
    return(FlashErase(api_block[block]));            /* erase the indicated block */

  return(0);
}

/*****************************************************************************
Name:       defrag
Parameters:   none
Returns:       0:OK, 1:NG
Description:   When a block is full (or other condition problem)...this code will
               copy data records from the old block to the new block.
               The process is started when a block is marked as full (BLOCK_FULL)
               during a NV_Write command. Whenever defrag is called with a block
               marked full, active data records are transferred to the opposite
               block. Because this process can be interrupted by a reset, it is
               called prior to any NV_Read or NV_Write command to ensure it completes
               prior to further block modification. Once all records are transferred
               to the new block, the old block is erased.
*****************************************************************************/
static unsigned char defrag_2(void);
static unsigned char defrag(void)
{
  unsigned char status;
  if (0xFF == (status = defrag_2()))
    return(defrag_2());         /* second run required because we had to erase a corrupted destination block */
  return(status);
}

static unsigned char defrag_2(void)
{
  pNV_header_type pOld, pNew;
  unsigned char record_label, new_record_label = 0xFF;
  unsigned char status;
  unsigned char old_block, new_block;

  /* determine source and destination locations of defrag */
  if (BLOCK_FULL == pBlk[0][NV_BLK_STATUS])
  {
    old_block = 0;
    pNew.pWord = pBlk[1];
    new_block = 1;
  }
  else if (BLOCK_FULL == pBlk[1][NV_BLK_STATUS])
  {
    old_block = 1;
    pNew.pWord = pBlk[0];
    new_block = 0;
  }
  else
  {
    /* no defrag needed */
    return(0);
  }

  /* we may be picking up in the middle of this process... */
  /* first record data may not match because it was newly written */
  if (pNew.p->record_label != 0xFF)
  {
    new_record_label = pNew.p->record_label;
    pNew.pWord += pNew.p->data_size + (sizeof(NV_header_type)/2);
  }

  /* collect all most-recent records from old block and transfer to new block
     (except for the new record)...which has already been written. */
  for (record_label = 0; record_label <= MAX_RECORD_LABEL; record_label++)
  {
      wdtr  = 0;

    /* don't search for record that caused the swap/defrag...already written */
    if (record_label != new_record_label)
    {
      if (0 != (pOld.p = seek_record(old_block, record_label)))
      {
        /* found a record */
        unsigned int index;
        /* save size prior to messing with pointer */
        unsigned int size = pOld.p->data_size + (sizeof(NV_header_type)/2);

        for (index = 0; index < size; index++, pOld.pWord++, pNew.pWord++)
        {
          /* carefully transfer record to new block...may already be in process */
          if (*pOld.pWord != *pNew.pWord)
          {
            /* data doesn't match...hopefully blank so we can program */
            if (*pNew.pWord == 0xFFFF)
            {
              /* write this word of the record to new location */
              if (0 != (status = FlashWrite((FLASH_PTR_TYPE)pNew.pWord, (BUF_PTR_TYPE)pOld.pWord, 2))  )
                return(status);
            }
            else
            {
              /* data doesn't match and not blank...only thing we can do is erase the new block and recover data active in old block */
              if (0 != (status = blank_check(new_block)))
                return(status);
              return(0xFF);
            } /* if */
          } /* if */
        } /* for */
      } /* if */
    } /* if */
  } /* for */

  return(blank_check(old_block));          /* erase the old block */
}