/*--------------------------------------------------------------------------
eeprom.c

Version:
12/2004 Ver 0.3 - Corrected Update_Record and Read_Recort_Data_Structure
                  for record sizes > 0xFF bytes.
10/2004 Ver 0.2 - Corrected Eeprom_Sector_Swap to properly handle smaller
                  sized records.
11/2002 Ver 0.1 - Initial Version

Description:
Device driver for EEPROM emulation using boot (secondary) flash.

*****************************************************************************
Important Note:
XDATA must be initialized to 0 in the startup file for this driver to work
properly.  This will be investigated and corrected in future releases.
*****************************************************************************


Copyright (c) 2005 STMicroelectronics, Inc.

This example demo code is provided as is and has no warranty,
implied or otherwise.  You are free to use/modify any of the provided
code at your own risk in your applications with the expressed limitation
of liability (see below) so long as your product using the code contains
at least one uPSD products (device).

LIMITATION OF LIABILITY:   NEITHER STMicroelectronics NOR ITS VENDORS OR 
AGENTS SHALL BE LIABLE FOR ANY LOSS OF PROFITS, LOSS OF USE, LOSS OF DATA,
INTERRUPTION OF BUSINESS, NOR FOR INDIRECT, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES OF ANY KIND WHETHER UNDER THIS AGREEMENT OR
OTHERWISE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
--------------------------------------------------------------------------*/


#include "eeprom.h"
#include "upsd3300.h"			// special function register declarations for UPSD


/***** EEPROM_Format *****/
// Formats sectors 0 and 1 to accept record data
// Accepts maximum number of records allowed.
// Returns 0 on success. If error, returns 1.
// ********** WARNING ********** // 
// This function erases any existing data in both sectors
// ********** WARNING ********** // 
BYTE EEPROM_Format(WORD max_records)
{
	xdata struct record_entry xdata record;

	// Verify data will fit into half of sector
	if  ( (sizeof(record) * max_records)  > (SECTOR_SIZE/2) )
		return ILLEGAL_RECORD_NUMBER;

	// Format sector 0
	if ( E_andF_Sector(SECTOR_0, max_records) ) return FORMAT_FAILED;
	// Erase sector 1
	if ( Eeprom_Sector_Erase(SECTOR_1) ) return SECTOR_ERASE_ERROR;

	return 0;
}

/***** Eeprom_Init *****/
// Verifies database integrity after power loss.
// Attempts to recover data corruption after power loss.
// Re-formats database if corrupted. 
BYTE Eeprom_Init(void)
{
	xdata struct sector_header xdata sector_header_0;
	xdata struct sector_header xdata sector_header_1;
	xdata struct record_entry xdata record;
	WORD i, j;
	BYTE *ptr;
	WORD xdata max_rec;
	WORD xdata last_address;
	WORD xdata base_address;
	WORD xdata new_address;
	BYTE xdata valid_sector;

	// Get both sector headers
	ptr = (BYTE*) (&sector_header_0);
	base_address = SECTOR_0_BASE_ADDRESS;
	for ( i=0; i < sizeof(sector_header_0); i++ )
	{
		ptr[i] = Boot_Flash_Read( base_address++ );
	}
	ptr = (BYTE*) (&sector_header_1);
	base_address = SECTOR_1_BASE_ADDRESS;
	for ( i=0; i < sizeof(sector_header_1); i++ )
	{
		ptr[i] = Boot_Flash_Read( base_address++ );
	}

	// Check for corrupted sectors
	// This would occur if a sector erase was interrupted by a power loss
	// In this case, the sector must be re-erased
	if ( ~(sector_header_0.sector ^ sector_header_0.sector_checksum) )
	{
		if ( Eeprom_Sector_Erase(SECTOR_0) ) return SECTOR_ERASE_ERROR;
		sector_header_0.sector_status = ERASED;
	}
	if ( ~(sector_header_1.sector ^ sector_header_1.sector_checksum) )
	{
		if ( Eeprom_Sector_Erase(SECTOR_1) ) return SECTOR_ERASE_ERROR;
		sector_header_1.sector_status = ERASED;
	}

	// Get maximum number of records from header
	// If unable, return error
	if ( sector_header_0.max_records != 0xFFFF )
		max_rec = sector_header_0.max_records;
	else if ( sector_header_1.max_records != 0xFFFF )
		max_rec = sector_header_1.max_records;
	else
		return ILLEGAL_RECORD_NUMBER;

	// Check for invalid header states and repair
	switch(sector_header_0.sector_status)
	{
    case ERASED:
		if( sector_header_1.sector_status == VALID__SECTOR )  // sector 1 is valid
		{		    
          	if ( Eeprom_Sector_Erase(SECTOR_0) ) return SECTOR_ERASE_ERROR;			
		}     
		else  // invalid state re-format database
		{
          	if ( Eeprom_Sector_Erase(SECTOR_1) ) return SECTOR_ERASE_ERROR;			
			if ( E_andF_Sector(SECTOR_0, max_rec) ) return FORMAT_FAILED;
			return INVALID_SECTOR_STATE;
     	}
		break;
	case RECEIVE_DATA:	
       	if ( sector_header_1.sector_status == VALID__SECTOR ) // use sector 1
       	{
          	if ( Eeprom_Sector_Erase(SECTOR_0) ) return SECTOR_ERASE_ERROR;			
      	}
		else if(sector_header_1.sector_status == TRANSFER_COMPLETE) //use sector 0
		{
          	if ( Eeprom_Sector_Erase(SECTOR_1) ) return SECTOR_ERASE_ERROR;	
			// update sector 0 header to valid data 
			sector_header_0.sector_status = VALID__SECTOR;
			ptr = (BYTE*) (&sector_header_0);
			base_address = SECTOR_0_BASE_ADDRESS;
			for ( i=0; i < sizeof(sector_header_0); i++ )
			{
				if ( Boot_Flash_Write( base_address++, ptr[i] ) ) return FLASH_WRITE_ERROR;
			}
		}
		else  // invalid state erase both sectors
		{
          	if ( Eeprom_Sector_Erase(SECTOR_1) ) return SECTOR_ERASE_ERROR;			
			if ( E_andF_Sector(SECTOR_0, max_rec) ) return FORMAT_FAILED;
			return INVALID_SECTOR_STATE;
     	}
		break;
	case VALID__SECTOR:	
		if ( sector_header_1.sector_status == VALID__SECTOR ) // invalid state erase both sectors
		{		    
          	if ( Eeprom_Sector_Erase(SECTOR_1) ) return SECTOR_ERASE_ERROR;			
			if ( E_andF_Sector(SECTOR_0, max_rec) ) return FORMAT_FAILED;
			return INVALID_SECTOR_STATE;
       	}
		else // sector 0 is valid sector, erase sector 1
       	{
          	if ( Eeprom_Sector_Erase(SECTOR_1) ) return SECTOR_ERASE_ERROR;			
		}
		break;
   	case TRANSFER_COMPLETE:
		if ( sector_header_1.sector_status == VALID__SECTOR ) // erase sector 0
		{		    
          	if ( Eeprom_Sector_Erase(SECTOR_0) ) return SECTOR_ERASE_ERROR;			
		}				
		else if (sector_header_1.sector_status == RECEIVE_DATA) // erase sector 0, use sector 1
		{
          	if ( Eeprom_Sector_Erase(SECTOR_0) ) return SECTOR_ERASE_ERROR;	
			// mark sector 1 as valid sector 
			sector_header_1.sector_status = VALID__SECTOR;
			ptr = (BYTE*) (&sector_header_1);
			base_address = SECTOR_1_BASE_ADDRESS;
			for ( i=0; i < sizeof(sector_header_1); i++ )
			{
				if ( Boot_Flash_Write( base_address++, ptr[i] ) ) return FLASH_WRITE_ERROR;
			}
		}
		else // invalid state, format both sectors
		{
          	if ( Eeprom_Sector_Erase(SECTOR_1) ) return SECTOR_ERASE_ERROR;			
			if ( E_andF_Sector(SECTOR_0, max_rec) ) return FORMAT_FAILED;
			return INVALID_SECTOR_STATE;
 		}
		break;
	default:  // any other state, erase both sectors
 	
          	if ( Eeprom_Sector_Erase(SECTOR_1) ) return SECTOR_ERASE_ERROR;			
			if ( E_andF_Sector(SECTOR_0, max_rec) ) return FORMAT_FAILED;
			return INVALID_SECTOR_STATE;
		break;
	}

	// Check for corrupted data 
	// This would happen if a data write/update was interrupted by a power loss
	for ( i=0; i<max_rec; i++ )
	{
		// get address of last entry of each record
		last_address = Read_Record_Data_Structure( i, (BYTE*) (&record) );
		// repair record entry if necessary
		if ( record.status == UPDATE_DATA )
  		{
			// get sector	
			valid_sector = Find_Active_Sector(F_WRITE);
			if ( valid_sector == SECTOR_ID_ERROR ) return SECTOR_ID_ERROR;
			// get base address of sector
			base_address = 	SECTOR_0_BASE_ADDRESS + ((WORD)valid_sector * SECTOR_SIZE);
 	  		// get address for repaired entry
	   		new_address = Find_Next_Address();
			if( new_address == SECTOR_FULL ) return SECTOR_FULL; // abort if sector is full
			// set status and pointer to next data
			record.status = VALID_DATA;
			record.last_record_update = 0xFFFF;
			// write new record with old data
			ptr = (BYTE*) (&record);
			for ( j=0; j<sizeof(record); j++ )
			{
				 if ( Boot_Flash_Write(new_address++, ptr[j]) ) return FLASH_WRITE_ERROR;
			}
			// fix status and pointer of old record
			record.status = SUPERSEDED;
			record.last_record_update = new_address - sizeof(record);
			for ( j=0; j < sizeof(record); j++ )
			{
				if ( Boot_Flash_Write( last_address++, ptr[j] ) ) return FLASH_WRITE_ERROR;
			}
		}
	}

	return 0;
}

/***** Update_Record *****/
// Update record in EEPROM.
// Accepts record id and new record data.
// Swaps to empty sector if current sector is full
BYTE Update_Record(WORD id, BYTE xdata *buf)
{
    WORD i; 
    BYTE xdata status;
	BYTE xdata bufover[EEPROM_RECORD_SIZE];

	status = Write_Record(id, buf);
   
	// when the sector is full, save the new record to ram and transfer to a blank sector	
	if( status == SECTOR_FULL ) 
	{
		// store new data in temporary buffer
		for ( i=0; i < sizeof(bufover); i++ )
		{
			bufover[i] = buf[i];
		}
		// perform sector swap
		status = Eeprom_Sector_Swap(id, &bufover);
	}
	
	return status;	
}

/***** Read_Record *****/
// Reads a data element from EEPROM.
// Accepts record id number.
// Returns record data byte. If error, returns error code.
BYTE Read_Record(WORD id_number, BYTE* buffer)
{
	BYTE xdata valid_sector = 0xFF;
	xdata struct sector_header xdata header;
	xdata struct record_entry xdata record;
	WORD i;
	BYTE *ptr;
	BYTE xdata *data_buf;
	WORD xdata address;
	WORD xdata base_address;
	WORD xdata last_address;

	// get active sector
	valid_sector = Find_Active_Sector(F_READ);
	if ( valid_sector == SECTOR_ID_ERROR ) return SECTOR_ID_ERROR;

	// get pointer to data
	data_buf = buffer;

	// calculate base address of data
	base_address = 	SECTOR_0_BASE_ADDRESS + ((WORD)valid_sector * SECTOR_SIZE) + 
					(WORD)sizeof(header) + ( id_number * (WORD)sizeof(record) );

	// get base record
	ptr = (BYTE*) (&record);
	address = base_address;
	for ( i=0; i<sizeof(record); i++ )
	{
		ptr[i] = Boot_Flash_Read( address++ );
	}

	// get last record
	if ( record.last_record_update != 0xFFFF )
	{
		address = base_address;
		do	
		{
			ptr = (BYTE*) (&record);
			last_address = address;
			for ( i=0; i<sizeof(record); i++ )
			{
				ptr[i] = Boot_Flash_Read( address++ );
			}
			address = record.last_record_update;
		} while ( record.last_record_update != 0xFFFF );
	 }
	 else
	 {
	 	last_address = base_address;
	 }

	 if( record.status == UNINITIALIZED ) return UNINITIALIZED;

	// Set data buffer
	for ( i=0; i<EEPROM_RECORD_SIZE; i++ )
	{
		data_buf[i] = record.record_data[i];
	}

	return 0;
}

/***** Write_Record *****/
// Write or update record in EEPROM.
// Accepts record id and new record data.
// If error, returns error code.
BYTE Write_Record(WORD id, BYTE xdata *buffer)
{
	xdata struct sector_header xdata header;
	xdata struct record_entry xdata record;
	WORD i;
	BYTE xdata valid_sector;
	WORD xdata last_address;
	WORD xdata base_address;
	WORD xdata new_address;
	WORD address;
	BYTE *ptr;
	BYTE xdata *data_buf;

	// get active sector
	valid_sector = Find_Active_Sector(F_WRITE);
	if ( valid_sector == SECTOR_ID_ERROR ) return SECTOR_ID_ERROR;

	// get pointer to data
	data_buf = buffer;

	// calculate base address of data
	base_address = 	SECTOR_0_BASE_ADDRESS + ((WORD)valid_sector * SECTOR_SIZE) + 
					(WORD)sizeof(header) + ( id * (WORD)sizeof(record) );

	// get base record
	ptr = (BYTE*) (&record);
	address = base_address;
	for ( i=0; i<sizeof(record); i++ )
	{
		ptr[i] = Boot_Flash_Read( address++ );
	}

	// write data if record not initialized
	if ( record.status == UNINITIALIZED )
	{
		record.status = VALID_DATA;
		record.last_record_update = 0xFFFF;
		for ( i=0; i < EEPROM_RECORD_SIZE; i++ )
		{
			record.record_data[i] = data_buf[i];	// set data byte
		}

		// write record to flash
		ptr = (BYTE*) (&record);
		address = base_address;
		for ( i=0; i < sizeof(record); i++ )
		{
			if ( Boot_Flash_Write( address++, ptr[i] ) ) return FLASH_WRITE_ERROR;
		}

		return 0;
	}
	
	// find last entry if record initialized	
	address = base_address;
	do
	{
		ptr = (BYTE*) (&record);
		for ( i=0; i<sizeof(record); i++ )
		{
			ptr[i] = Boot_Flash_Read( address++ );
		}
		if ( record.status == SUPERSEDED )
		{
			address = record.last_record_update;
		}
		
	} while ( record.status == SUPERSEDED );

	last_address = address - (WORD)sizeof(record);

	// get next available address
	new_address = Find_Next_Address();
	if( new_address == SECTOR_FULL ) return SECTOR_FULL; // abort if sector is full

	// set old record to update in progress
	address = last_address;
	for ( i=0; i<sizeof(record); i++ )
	{
		ptr[i] = Boot_Flash_Read( address++ );
	}
	record.status = UPDATE_DATA;
	address = last_address;
	for ( i=0; i < sizeof(record); i++ )
	{
		if ( Boot_Flash_Write( address++, ptr[i] ) ) return FLASH_WRITE_ERROR;
	}

	// write data to new address
	address = new_address;
	record.status = VALID_DATA;
	record.last_record_update = 0xFFFF;
	for ( i=0; i < EEPROM_RECORD_SIZE; i++ )
	{
		record.record_data[i] = data_buf[i];	// set data byte
	}
	address = new_address;
	for ( i=0; i < sizeof(record); i++ )
	{
		if ( Boot_Flash_Write( address++, ptr[i] ) ) return FLASH_WRITE_ERROR;
	}

	// update old record to superseded
	address = last_address;
	for ( i=0; i<sizeof(record); i++ )
	{
		ptr[i] = Boot_Flash_Read( address++ );
	}
	record.status = SUPERSEDED;
	record.last_record_update = new_address;
	address = last_address;
	for ( i=0; i < sizeof(record); i++ )
	{
		if ( Boot_Flash_Write( address++, ptr[i] ) ) return FLASH_WRITE_ERROR;
	}

	return 0;
}

/***** Boot_Flash_Write *****/
// Writes data byte to secondary flash.
// Accepts address in flash; data (byte).
// Returns 0 for successful write. If error, returns 1. 
BYTE Boot_Flash_Write(WORD address, BYTE data_byte)
{
	BYTE xdata readback;
	BYTE xdata done;
	BYTE xdata error;
	BYTE xdata err;
	BYTE xdata poll;
	BYTE xdata erase_flag_0 = 0;
	BYTE xdata erase_flag_1 = 0;
	static BYTE reentry_flag;

	// check for re-entrant call to this function
	if ( reentry_flag ) return FLASH_ACCESS_ERROR;

	reentry_flag = 1;

	// un-comment for re-entrancy test
	//EA = 1;		/* enable interrupts */

	done = FALSE;
   	err = FALSE;

	// suspend any erase in progress		
	if ( Eeprom_Sector_Erase_Status(SECTOR_0) )
    {
		if ( Eeprom_Sector_Erase_Suspend(SECTOR_0) ) return SECTOR_ERASE_ERROR;
		erase_flag_0 = 1;
	}
	if ( Eeprom_Sector_Erase_Status(SECTOR_1) )
    {
		if ( Eeprom_Sector_Erase_Suspend(SECTOR_1) ) return SECTOR_ERASE_ERROR;
		erase_flag_1 = 1;
	}

	// disable interrupts during flash write sequence
	EA = 0;

	// write data byte
	*((char xdata *) (SECTOR_0_BASE_ADDRESS + 0x0555)) = 0xAA;
	*((char xdata *) (SECTOR_0_BASE_ADDRESS + 0x0AAA)) = 0x55;
	*((char xdata *) (SECTOR_0_BASE_ADDRESS + 0x0555)) = 0xA0;
	*((char xdata *) address) = data_byte;

	// enable interrupts following write
	EA = 1;

	// now use dat polling method to verify successful write
	do
    {  
   		poll = *((char xdata *) address);	// Read the location that was just programmed
		error = poll & NVM_ERROR;		// save timeout error bit at DQ5
		poll = poll & NVM_DATA_POLL;	// get DQ7 of poll byte read from flash  

		if ((data_byte & NVM_DATA_POLL) == poll)	// compare DQ7 
			done = TRUE;		// dat byte programmed into flash OK, indicate successful exit criteria
		else if (error == NVM_ERROR)	// check for timeout error   
			err = TRUE;					// indicate timeout error occurred

    } while((done == FALSE) && (err == FALSE));

	// make sure timeout error and dat poll didn't occur simultaneously
	if (err == TRUE)                
	{
   		poll = *((char xdata *) address);	// Read location in flash again
		poll = poll & NVM_DATA_POLL;   				// get DQ7 of poll byte read from flash  

		if ((data_byte & NVM_DATA_POLL) == poll)	// compare DQ7 
			done = TRUE;	// dat byte programmed into flash OK at the same time timout 
							//error occured, indicate successful exit criteria

		*((char xdata *) (SECTOR_0_BASE_ADDRESS + 0x0555)) = 0xF0;
		//*((char xdata *) 0x0555) = 0xF0;	// reset the flash array (short reset instruction) 
	}	        							// now delay 3 msec per dat sheet

	// verify successful write by reading back data and comparing with original	
	readback = *((char xdata *) address);	// readback data from flash

	// resume any suspended erase
	if ( erase_flag_0 )
	{
		if ( Eeprom_Sector_Erase_Resume(SECTOR_0) ) return SECTOR_ERASE_ERROR;
	}
	if ( erase_flag_1 )
	{
		if ( Eeprom_Sector_Erase_Resume(SECTOR_1) ) return SECTOR_ERASE_ERROR;
	}

	reentry_flag = 0;

	return !(readback == data_byte);	// if valid return success
}

/***** Read_Record_Data_Structure *****/
// Reads an entire record structure from memory.
// Accepts record id number.
// Returns address of last record. If error, returns error message.
WORD Read_Record_Data_Structure(WORD id_number, BYTE* buffer)
{
	BYTE xdata valid_sector = 0xFF;
	xdata struct sector_header xdata header;
	xdata struct record_entry xdata record;
	WORD i;
	BYTE *ptr;
	BYTE xdata *data_buf;
	WORD address;
	WORD xdata base_address;
	WORD xdata last_address;

	// get active sector
	valid_sector = Find_Active_Sector(F_READ);
	if ( valid_sector == SECTOR_ID_ERROR ) return SECTOR_ID_ERROR;

	// get pointer to data
	data_buf = buffer;

	// calculate base address of data
	base_address = 	SECTOR_0_BASE_ADDRESS + ((WORD)valid_sector * SECTOR_SIZE) + 
					(WORD)sizeof(header) + ( id_number * (WORD)sizeof(record) );

	// get base record
	ptr = (BYTE*) (&record);
	address = base_address;
	for ( i=0; i<sizeof(record); i++ )
	{
		ptr[i] = Boot_Flash_Read( address++ );
	}

	// get last record
	if ( record.last_record_update != 0xFFFF )
	{
		address = base_address;
		do	
		{
			ptr = (BYTE*) (&record);
			last_address = address;
			for ( i=0; i<sizeof(record); i++ )
			{
				ptr[i] = Boot_Flash_Read( address++ );
			}
			address = record.last_record_update;
		} while ( record.last_record_update != 0xFFFF );
	 }
	 else
	 {
	 	last_address = base_address;
	 }

	// Get last record
	for (i=0; i<sizeof(record); i++)
	{
		data_buf[i] = Boot_Flash_Read(last_address++);
	}

	// return address of last record
	return last_address - sizeof(record);
}

/***** Boot_Flash_Read *****/
// Reads data from secondary flash.
// Accepts address.
// Returns data at address. 
BYTE Boot_Flash_Read(WORD address)
{	
	BYTE xdata erase_flag_0 = 0;
	BYTE xdata erase_flag_1 = 0;
	BYTE data_byte;
	static BYTE xdata reentry_flag;

	// check for re-entrant call to this function
	if ( reentry_flag ) return FLASH_ACCESS_ERROR;

	reentry_flag = 1;

	// un-comment for re-entrancy test
	//EA = 1;		/* enable interrupts */

	// suspend any erase in progress		
	if ( Eeprom_Sector_Erase_Status(SECTOR_0) )
    {
		if ( Eeprom_Sector_Erase_Suspend(SECTOR_0) ) return SECTOR_ERASE_ERROR;
		erase_flag_0 = 1;
	}
	if ( Eeprom_Sector_Erase_Status(SECTOR_1) )
    {
		if ( Eeprom_Sector_Erase_Suspend(SECTOR_1) ) return SECTOR_ERASE_ERROR;
		erase_flag_1 = 1;
	}

	// read data byte from flash
	data_byte = *((char xdata *) address);

	// resume any suspended erase
	if ( erase_flag_0 )
	{
		if ( Eeprom_Sector_Erase_Resume(SECTOR_0) ) return SECTOR_ERASE_ERROR;
	}
	if ( erase_flag_1 )
	{
		if ( Eeprom_Sector_Erase_Resume(SECTOR_1) ) return SECTOR_ERASE_ERROR;
	}

	reentry_flag = 0;

	return data_byte;
}

/***** Eeprom_Sector_Erase *****/
// Erases one boot flash sector.
// Accepts sector number (0-3).
// Returns 0 for successful erasure. If error, returns 1. 
BYTE Eeprom_Sector_Erase(BYTE sector)
{
	BYTE xdata done;
 	BYTE xdata poll;
 	BYTE xdata error;
 	BYTE xdata err;

	done = FALSE;
   	err = FALSE;

	switch (sector)
	{
	case SECTOR_0:
		*(BOOT_FLASH_SECTOR_0_X555) = 0xAA;		// unlock main flash, write 0xAA to addess 0xX555
		*(BOOT_FLASH_SECTOR_0_XAAA) = 0x55;		// unlock main flash, write 0x55 to addess 0xXAAA
		*(BOOT_FLASH_SECTOR_0_X555) = 0x80;		// write 0x80 command to erase entire chip
		*(BOOT_FLASH_SECTOR_0_X555) = 0xAA;     // continue unlock sequence
		*(BOOT_FLASH_SECTOR_0_XAAA) = 0x55;		// continue unlock sequence
		*((volatile unsigned char xdata *)SECTOR_0_BASE_ADDRESS) = 0x30;// erase sector
		do		  			// now use dat polling method to verify successful erase
    	{  
			poll = *((volatile unsigned char xdata *)SECTOR_0_BASE_ADDRESS); 	// read flash status from any address
			error = poll & NVM_ERROR;	// save timeout error bit at DQ5
			poll = poll & NVM_DATA_POLL;	// look at D7   
			if ( poll == NVM_DATA_POLL )	// compare DQ7 
				done = TRUE;		// bulk erase OK,
			else if (error == NVM_ERROR)	// check for timeout error   
				err = TRUE;		// indicate timeout error occurred
    	} while( (done == FALSE) && (err == FALSE) ); 
		if ( err == TRUE )			// make sure timeout error and dat poll didn't occur simultaneously
    	{
			poll = *((volatile unsigned char xdata *)SECTOR_0_BASE_ADDRESS); 	// Read flash status again
			poll = poll & NVM_DATA_POLL;	// get DQ7 of poll byte read from flash  
			if (poll == NVM_DATA_POLL)		// compare DQ7 
				done = TRUE;				// the flash erased OK at the same time timout error occured
			*(BOOT_FLASH_SECTOR_0_X555) = 0xF0;  // reset the flash array (short reset instruction) 
    	}
		break;
	case SECTOR_1:
		*(BOOT_FLASH_SECTOR_1_X555) = 0xAA;		// unlock main flash, write 0xAA to addess 0xX555
		*(BOOT_FLASH_SECTOR_1_XAAA) = 0x55;		// unlock main flash, write 0x55 to addess 0xXAAA
		*(BOOT_FLASH_SECTOR_1_X555) = 0x80;		// write 0x80 command to erase entire chip
		*(BOOT_FLASH_SECTOR_1_X555) = 0xAA;     // continue unlock sequence
		*(BOOT_FLASH_SECTOR_1_XAAA) = 0x55;		// continue unlock sequence
		*((volatile unsigned char xdata *)SECTOR_1_BASE_ADDRESS) = 0x30;// erase sector
		do		  			// now use dat polling method to verify successful erase
    	{  
			poll = *((volatile unsigned char xdata *)SECTOR_1_BASE_ADDRESS); 	// read flash status from any address
			error = poll & NVM_ERROR;	// save timeout error bit at DQ5
			poll = poll & NVM_DATA_POLL;	// look at D7   
			if ( poll == NVM_DATA_POLL )	// compare DQ7 
				done = TRUE;		// bulk erase OK,
			else if (error == NVM_ERROR)	// check for timeout error   
				err = TRUE;		// indicate timeout error occurred
    	} while( (done == FALSE) && (err == FALSE) ); 
		if ( err == TRUE )			// make sure timeout error and dat poll didn't occur simultaneously
    	{
			poll = *((volatile unsigned char xdata *)SECTOR_1_BASE_ADDRESS); 	// Read flash status again
			poll = poll & NVM_DATA_POLL;	// get DQ7 of poll byte read from flash  
			if (poll == NVM_DATA_POLL)		// compare DQ7 
				done = TRUE;				// the flash erased OK at the same time timout error occured
			*(BOOT_FLASH_SECTOR_0_X555) = 0xF0;  // reset the flash array (short reset instruction) 
    	}
		break;
	case SECTOR_2:
		*(BOOT_FLASH_SECTOR_2_X555) = 0xAA;		// unlock main flash, write 0xAA to addess 0xX555
		*(BOOT_FLASH_SECTOR_2_XAAA) = 0x55;		// unlock main flash, write 0x55 to addess 0xXAAA
		*(BOOT_FLASH_SECTOR_2_X555) = 0x80;		// write 0x80 command to erase entire chip
		*(BOOT_FLASH_SECTOR_2_X555) = 0xAA;     // continue unlock sequence
		*(BOOT_FLASH_SECTOR_2_XAAA) = 0x55;		// continue unlock sequence
		*((volatile unsigned char xdata *)SECTOR_2_BASE_ADDRESS) = 0x30;// erase sector
		do		  			// now use dat polling method to verify successful erase
    	{  
			poll = *((volatile unsigned char xdata *)SECTOR_2_BASE_ADDRESS); 	// read flash status from any address
			error = poll & NVM_ERROR;	// save timeout error bit at DQ5
			poll = poll & NVM_DATA_POLL;	// look at D7   
			if ( poll == NVM_DATA_POLL )	// compare DQ7 
				done = TRUE;		// bulk erase OK,
			else if (error == NVM_ERROR)	// check for timeout error   
				err = TRUE;		// indicate timeout error occurred
    	} while( (done == FALSE) && (err == FALSE) ); 
		if ( err == TRUE )			// make sure timeout error and dat poll didn't occur simultaneously
    	{
			poll = *((volatile unsigned char xdata *)SECTOR_2_BASE_ADDRESS); 	// Read flash status again
			poll = poll & NVM_DATA_POLL;	// get DQ7 of poll byte read from flash  
			if (poll == NVM_DATA_POLL)		// compare DQ7 
				done = TRUE;				// the flash erased OK at the same time timout error occured
			*(BOOT_FLASH_SECTOR_0_X555) = 0xF0;  // reset the flash array (short reset instruction) 
    	}
		break;
	case SECTOR_3:
		*(BOOT_FLASH_SECTOR_3_X555) = 0xAA;		// unlock main flash, write 0xAA to addess 0xX555
		*(BOOT_FLASH_SECTOR_3_XAAA) = 0x55;		// unlock main flash, write 0x55 to addess 0xXAAA
		*(BOOT_FLASH_SECTOR_3_X555) = 0x80;		// write 0x80 command to erase entire chip
		*(BOOT_FLASH_SECTOR_3_X555) = 0xAA;     // continue unlock sequence
		*(BOOT_FLASH_SECTOR_3_XAAA) = 0x55;		// continue unlock sequence
		*((volatile unsigned char xdata *)SECTOR_3_BASE_ADDRESS) = 0x30;// erase sector
		do		  			// now use dat polling method to verify successful erase
    	{  
			poll = *((volatile unsigned char xdata *)SECTOR_3_BASE_ADDRESS); 	// read flash status from any address
			error = poll & NVM_ERROR;	// save timeout error bit at DQ5
			poll = poll & NVM_DATA_POLL;	// look at D7   
			if ( poll == NVM_DATA_POLL )	// compare DQ7 
				done = TRUE;		// bulk erase OK,
			else if (error == NVM_ERROR)	// check for timeout error   
				err = TRUE;		// indicate timeout error occurred
    	} while( (done == FALSE) && (err == FALSE) ); 
		if ( err == TRUE )			// make sure timeout error and dat poll didn't occur simultaneously
    	{
			poll = *((volatile unsigned char xdata *)SECTOR_3_BASE_ADDRESS); 	// Read flash status again
			poll = poll & NVM_DATA_POLL;	// get DQ7 of poll byte read from flash  
			if (poll == NVM_DATA_POLL)		// compare DQ7 
				done = TRUE;				// the flash erased OK at the same time timout error occured
			*(BOOT_FLASH_SECTOR_0_X555) = 0xF0;  // reset the flash array (short reset instruction) 
    	}
		break;
	default:
		return 1;
	}

   	return !(done);	
}

/***** Eeprom_Sector_Erase_Start*****/
// Start Erases one boot flash sector.
// Accepts sector number (0-3).
// Returns 0 for successful erasure. If error, returns 1. 
BYTE Eeprom_Sector_Erase_Start(BYTE sector)
{
	switch (sector)
	{
	case SECTOR_0:
		*(BOOT_FLASH_SECTOR_0_X555) = 0xAA;		// unlock main flash, write 0xAA to addess 0xX555
		*(BOOT_FLASH_SECTOR_0_XAAA) = 0x55;		// unlock main flash, write 0x55 to addess 0xXAAA
		*(BOOT_FLASH_SECTOR_0_X555) = 0x80;		// write 0x80 command to erase entire chip
		*(BOOT_FLASH_SECTOR_0_X555) = 0xAA;     // continue unlock sequence
		*(BOOT_FLASH_SECTOR_0_XAAA) = 0x55;		// continue unlock sequence
		*((volatile unsigned char xdata *)SECTOR_0_BASE_ADDRESS) = 0x30;// erase sector
		break;
	case SECTOR_1:
		*(BOOT_FLASH_SECTOR_1_X555) = 0xAA;		// unlock main flash, write 0xAA to addess 0xX555
		*(BOOT_FLASH_SECTOR_1_XAAA) = 0x55;		// unlock main flash, write 0x55 to addess 0xXAAA
		*(BOOT_FLASH_SECTOR_1_X555) = 0x80;		// write 0x80 command to erase entire chip
		*(BOOT_FLASH_SECTOR_1_X555) = 0xAA;     // continue unlock sequence
		*(BOOT_FLASH_SECTOR_1_XAAA) = 0x55;		// continue unlock sequence
		*((volatile unsigned char xdata *)SECTOR_1_BASE_ADDRESS) = 0x30;// erase sector
		break;
	case SECTOR_2:
		*(BOOT_FLASH_SECTOR_2_X555) = 0xAA;		// unlock main flash, write 0xAA to addess 0xX555
		*(BOOT_FLASH_SECTOR_2_XAAA) = 0x55;		// unlock main flash, write 0x55 to addess 0xXAAA
		*(BOOT_FLASH_SECTOR_2_X555) = 0x80;		// write 0x80 command to erase entire chip
		*(BOOT_FLASH_SECTOR_2_X555) = 0xAA;     // continue unlock sequence
		*(BOOT_FLASH_SECTOR_2_XAAA) = 0x55;		// continue unlock sequence
		*((volatile unsigned char xdata *)SECTOR_2_BASE_ADDRESS) = 0x30;// erase sector
		break;
	case SECTOR_3:
		*(BOOT_FLASH_SECTOR_3_X555) = 0xAA;		// unlock main flash, write 0xAA to addess 0xX555
		*(BOOT_FLASH_SECTOR_3_XAAA) = 0x55;		// unlock main flash, write 0x55 to addess 0xXAAA
		*(BOOT_FLASH_SECTOR_3_X555) = 0x80;		// write 0x80 command to erase entire chip
		*(BOOT_FLASH_SECTOR_3_X555) = 0xAA;     // continue unlock sequence
		*(BOOT_FLASH_SECTOR_3_XAAA) = 0x55;		// continue unlock sequence
		*((volatile unsigned char xdata *)SECTOR_3_BASE_ADDRESS) = 0x30;// erase sector
		break;
	default:
		return 1;

	}
   	return 0;
}

/***** Eeprom_Sector_Erase_Suspend*****/
// Susupend Erases one boot flash sector.
// Accepts sector number (0-3).
// Returns 0 for successful erasure. If error, returns 1. 
BYTE Eeprom_Sector_Erase_Suspend(BYTE sector)
{
	switch (sector)
	{
	case SECTOR_0:
		*((volatile unsigned char xdata *)SECTOR_0_BASE_ADDRESS) = 0xB0;// Suspend erase sector
		break;
	case SECTOR_1:
		*((volatile unsigned char xdata *)SECTOR_1_BASE_ADDRESS) = 0xB0;// Suspend erase sector
		break;
	case SECTOR_2:
		*((volatile unsigned char xdata *)SECTOR_2_BASE_ADDRESS) = 0xB0;// Suspend erase sector
		break;
	case SECTOR_3:
		*((volatile unsigned char xdata *)SECTOR_3_BASE_ADDRESS) = 0xB0;// Suspend erase sector
		break;
	default:
		return 1;
	}


   	return 0;
}

/***** Eeprom_Sector_Erase_Resume*****/
// Resume Erases one boot flash sector.
// Accepts sector number (0-3).
// Returns 0 for successful erasure. If error, returns 1. 
BYTE Eeprom_Sector_Erase_Resume(BYTE sector)
{
	switch (sector)
	{
	case SECTOR_0:
		*((volatile unsigned char xdata *)SECTOR_0_BASE_ADDRESS) = 0x30;// Resume erase sector
		break;
	case SECTOR_1:
		*((volatile unsigned char xdata *)SECTOR_1_BASE_ADDRESS) = 0x30;// Resume erase sector
		break;
	case SECTOR_2:
		*((volatile unsigned char xdata *)SECTOR_2_BASE_ADDRESS) = 0x30;// Resume erase sector
		break;
	case SECTOR_3:
		*((volatile unsigned char xdata *)SECTOR_3_BASE_ADDRESS) = 0x30;// Resume erase sector
		break;
	default:
		return 1;
	}

   	return 0;
}

/***** Eeprom_Sector_Erase_Status *****/
// Gets status of sector erase.
// Accepts sector number (0-3).
// Returns 0 if erase cycle finished. Returns 1 if erase in progress. 
BYTE Eeprom_Sector_Erase_Status(BYTE sector)
{
	BYTE poll;

	switch (sector)
	{
	case SECTOR_0:
		poll = *((volatile unsigned char xdata *)SECTOR_0_BASE_ADDRESS);
		return !( poll & 0x80); 
	case SECTOR_1:
		poll = *((volatile unsigned char xdata *)SECTOR_1_BASE_ADDRESS);
		return !( poll & 0x80); 
	case SECTOR_2:
		poll = *((volatile unsigned char xdata *)SECTOR_2_BASE_ADDRESS);
		return !( poll & 0x80); 
	case SECTOR_3:
		poll = *((volatile unsigned char xdata *)SECTOR_3_BASE_ADDRESS);
		return !( poll & 0x80); 
	default:
		return 1;
	}
   	return 0;
}

/***** Find_Next_Address *****/
// Finds next available address for data record.
// Returns next available address. 
WORD Find_Next_Address(void)
{
	BYTE xdata valid_sector;
	xdata struct sector_header xdata header;
	xdata struct record_entry xdata record;
	WORD address;
	BYTE *ptr;
	WORD i;

	// Find valid sector
	valid_sector = Find_Active_Sector(F_WRITE);
	if ( valid_sector == SECTOR_ID_ERROR ) return SECTOR_ID_ERROR;

	// Get sector header
	ptr = (BYTE*) (&header);
	address = SECTOR_0_BASE_ADDRESS + ((WORD)valid_sector * SECTOR_SIZE);	
	for ( i=0; i<(WORD)sizeof(header); i++ )
	{
		ptr[i] = Boot_Flash_Read( address++ );
	}

	// Calculate address
	address = SECTOR_0_BASE_ADDRESS + ((WORD)valid_sector * SECTOR_SIZE) + 	// sector base address
	(WORD)sizeof(header) + 													// sector header
	(header.max_records * (WORD)sizeof(record));							// first instance of each record
	ptr = (BYTE*) (&record);
	for ( i=0; i < SECTOR_SIZE; i += sizeof(record) )
	{
		if ( Boot_Flash_Read( address ) == UNINITIALIZED ) return address;
		address += sizeof(record);
		if ( address >= (SECTOR_0_BASE_ADDRESS + ((WORD)valid_sector * SECTOR_SIZE) 
			+ SECTOR_SIZE - (WORD)sizeof(record)) ) return SECTOR_FULL;
	}
	return ADDRESS_ERROR;
}


/***** Find_Active_Sector *****/
// Finds active sector.
// Returns sector number. 
// If error, returns error code. 
BYTE Find_Active_Sector(BYTE io)
{
	xdata struct sector_header xdata header0;
	xdata struct sector_header xdata header1;
	WORD address;
	BYTE *ptr;
	BYTE i;

	// Check sector 0 for status	
	ptr = (BYTE*) (&header0);
	address = SECTOR_0_BASE_ADDRESS;
	for ( i=0; i < sizeof(header0); i++ )
	{
		ptr[i] = Boot_Flash_Read( address++ );
	}
	// Check sector 1 for status	
	ptr = (BYTE*) (&header1);
	address = SECTOR_1_BASE_ADDRESS;
	for ( i=0; i < sizeof(header1); i++ )
	{
		ptr[i] = Boot_Flash_Read( address++ ); 
	}	

	switch (io)
	{
	case F_WRITE: // write
		if( header0.sector_status == VALID__SECTOR )
		{
			if( header1.sector_status == RECEIVE_DATA )
			{
				return (SECTOR_1);
			} 
			else
			{
				return (SECTOR_0); 
			}
		}
		else if( header1.sector_status == VALID__SECTOR )			 
		{
			if( header0.sector_status == RECEIVE_DATA )
			{
				return (SECTOR_0);
			}
			else
			{
				return (SECTOR_1); 
			}
		}
	case F_READ:  // read  
		if( header0.sector_status == VALID__SECTOR )
		{	    
			return (SECTOR_0); 
		}
		else if( header1.sector_status == VALID__SECTOR )           
		{
			return (SECTOR_1); 
		}			 
	default: 
	        return (SECTOR_0); 		
	}
	return 1;
}


/***** Eeprom_Sector_Swap *****/
// Transfers data from full sector to empty one.
// Returns new sector number. 
 BYTE Eeprom_Sector_Swap(WORD inn, BYTE xdata *buf)
{
	xdata struct sector_header xdata sector_header_old;
	xdata struct sector_header xdata sector_header_new; 
	xdata struct record_entry xdata buffer; 		   	
	BYTE *ptr; 
	WORD xdata id;
	BYTE *ptr_old;
	BYTE *ptr_new;
	WORD address;
	BYTE xdata status;
	BYTE i;
	BYTE xdata valid_sector;
	WORD xdata new_sector; // place holder for the sector we are moving to
	WORD xdata old_sector; // place holder for the sector we are moving from
        
	// get active sector  (This is the sector we are moving from)
	valid_sector = Find_Active_Sector(F_READ);
		if ( valid_sector == SECTOR_ID_ERROR ) return SECTOR_ID_ERROR;

	if(valid_sector == SECTOR_1)
	{
		new_sector = SECTOR_0_BASE_ADDRESS;  // move data to this sector
		old_sector = SECTOR_1_BASE_ADDRESS;  // move data from this sector
		sector_header_new.sector = SECTOR_0;
	}
	else
	{
		new_sector = SECTOR_1_BASE_ADDRESS; // move data to this sector
		old_sector = SECTOR_0_BASE_ADDRESS; // move data from this sector
		sector_header_new.sector = SECTOR_1;
	}
		
	// Get old sector header
	ptr_old = (BYTE*) (&sector_header_old);
	address = old_sector;
	for ( i=0; i < sizeof(sector_header_old); i++ )
	{		
        ptr_old[i] = Boot_Flash_Read(address++);
	}

	// Verify new sector is erased
	if ( Eeprom_Sector_Erase(sector_header_new.sector) ) return SECTOR_ERASE_ERROR;

	sector_header_new.sector_checksum = ~(sector_header_new.sector);
	sector_header_new.max_records = sector_header_old.max_records;
	sector_header_new.rec_length = (WORD)EEPROM_RECORD_SIZE;
	sector_header_new.sector_status = RECEIVE_DATA;

	// mark receiving sector (new sector)      
	// mark new sector to receive 
	address = new_sector; 
	ptr_new = (BYTE*) (&sector_header_new);
	for ( i=0; i < sizeof(sector_header_new); i++ )
	{  
		if ( Boot_Flash_Write( address++, ptr_new[i] ) ) return FLASH_WRITE_ERROR;      
	}
                    
	for (id=0; id < sector_header_old.max_records; id++)
	{                       
		if(id == inn) 
		{ 
			ptr = buf; // add the new record as the data is swapped into the new sector 
			status = 0;
		}
		else
		{
			ptr = (BYTE*) (&buffer);
			status = Read_Record(id, ptr);
		}
		if( status != UNINITIALIZED ) Write_Record( id, ptr );			            
	}
      
	// mark good and bad sectors

	// mark old sector TRANSFER_COMPLETE BEFORE marking new sector ACTIVE 
	sector_header_old.sector_status =  TRANSFER_COMPLETE;                      
	address = old_sector; 
	for ( i=0; i < sizeof(sector_header_old); i++ )
	{  
		if ( Boot_Flash_Write( address++, ptr_old[i] ) ) return FLASH_WRITE_ERROR;      
	}

	// mark new sector ACTIVE AFTER marking old TRANSFER_COMPLETE   
	sector_header_new.sector_status =   VALID__SECTOR;      
	address = new_sector; 
	for ( i=0; i < sizeof(sector_header_new); i++ )
	{  
		if ( Boot_Flash_Write( address++, ptr_new[i] ) ) return FLASH_WRITE_ERROR;      
	}
	
	// Start erase of old sector
	if ( Eeprom_Sector_Erase_Start(valid_sector) ) return SECTOR_ERASE_ERROR;
			
	return 0;
}

/***** ERASE and FORMAT SECTOR *****/
// Erases and formats a sector.
// Accepts sector number, type of operation and max records allowed.
// Legal sectors are:
// 	SECTOR_0 for address 0x8000
//	SECTOR_1 for address 0xA000
// If error, returns error code.
BYTE E_andF_Sector(BYTE sector, WORD max_rec)
{                                          
	xdata struct sector_header xdata header;           
	BYTE i;
	WORD address;
	BYTE *ptr;

	header.sector_status = VALID__SECTOR;
	header.sector = sector;
	header.sector_checksum = ~(sector);
	header.max_records = max_rec;
	header.rec_length = (WORD)EEPROM_RECORD_SIZE;

	if(sector == SECTOR_0) address = SECTOR_0_BASE_ADDRESS;
	if(sector == SECTOR_1) address = SECTOR_1_BASE_ADDRESS;

	if ( Eeprom_Sector_Erase(sector) ) return SECTOR_ERASE_ERROR;

	ptr = (BYTE*) (&header);

	// write sector header		
	for ( i=0; i < sizeof( header ); i++ ) 
	{
		if ( Boot_Flash_Write( address++, ptr[i] ) ) return FLASH_WRITE_ERROR;
	}

	return 0;
}

/***** Get_Sector_Status *****/
// Returns sector status.
// If error, returns INVALID_STATUS. 
BYTE Get_Sector_Status(BYTE sector)
{
	xdata struct sector_header xdata header;
	WORD address;
	BYTE *ptr;
	BYTE i;

	// Get sector header	
	ptr = (BYTE*) (&header);
	address = SECTOR_0_BASE_ADDRESS + (sector * SECTOR_SIZE);
	for ( i=0; i < sizeof(header); i++ )
	{
		ptr[i] = Boot_Flash_Read( address++ );
	}
	
	switch (header.sector_status)
	{
		case ERASED:
			return ERASED;
		case RECEIVE_DATA:
			return RECEIVE_DATA;
		case VALID__SECTOR:
			return VALID__SECTOR;
		case TRANSFER_COMPLETE:
			return TRANSFER_COMPLETE;
		default:
			return INVALID_STATUS;			
	}
}

