/**
 *	@file 		flash.c
 *	@ingroup 	FLASH
 *	
 *	@brief 		Intel Strata (+ compatible) Flash Driver
 *
 *  @attention  If data caching is enabled. Be sure to use a cplbtab
 *              which has disabled caching for async bank 0 and 1!
 *		
 *	BLT_DISCLAIMER
 *	
 *	@author 	Roland Oberhammer, 
 *	
 *	@cond svn
 *	
 *	Information of last commit
*   $Rev::               $:  Revision of last commit
*   $Author::            $:  Author of last commit
*   $Date::              $:  Date of last commit
 *	
 *	@endcond
 **/
 
/** @defgroup FLASH
 *  @ingroup driverapi
 *  Flash Memory Controller
 */
 
//////////////////////////////////////////////////////////////
/*
#ifdef _USE_DATA_CACHING_
#include <cplbtab.h> 
#endif
*/
 
#include <math.h>
#include <stdio.h>
#include <environment.h>
#include "flash.h"


#ifndef FLASH_INTEL_STRATA
#error "Flash type not supported!"
#endif

#if ((FLASH_BANK_SIZE * FLASH_NOF_BANKS) != FLASH_SIZE)
#error "Illegal bank- flash- size combination!"
#endif

T_FLASH_SPEC g_tFlash;	///< Global for management purposes!

#ifdef _USE_VDK_

#include <VDK.h>
#include <services/services.h>

char Flash_LockAccess (VDK_ThreadID *ThreadId) {
	if (g_tFlash.bLocked) {
		*ThreadId = g_tFlash.tThreadId;
		return -1;
	} else {
		g_tFlash.tThreadId = VDK_GetThreadID();
		g_tFlash.bLocked = true;
		return 0;
	}
}

char Flash_UnlockAccess (void) {
	g_tFlash.bLocked = false;
	return 0;	
}

#endif

static void Flash_BankSelectReset (void) {
	unsigned char i = 0;
	for (i=0; i<g_tFlash.cNofBankSelect; i++) {
		gpio_clear (g_tFlash.nBankSelect[i]);
	}
	g_tFlash.cCurrentBank = 0;
}

void Flash_BankSelectSetup (void) {
	unsigned char i = 0;
	for (i=0; i<g_tFlash.cNofBankSelect; i++) {
		gpio_becomeOutput (g_tFlash.nBankSelect[i]);
		gpio_clear (g_tFlash.nBankSelect[i]);
	}
	g_tFlash.cCurrentBank = 0;
}





// has to be called, prior to use the flash
/**
 *	@public
 *	@brief		Initialises the Flash driver (Sets some global configuration variables for management purposes).
 *
 *	@attention 	This function must be called prior to flash use.
 *
 *	Sets some global configuration variables for managment purposes.
 *	Has to called prior to use the flash.
 **/
void Flash_Setup (void) {
	g_tFlash.cNofBanks = FLASH_NOF_BANKS;
	g_tFlash.nBankSize = FLASH_BANK_SIZE;
	g_tFlash.cNofBankSelect = (unsigned char)(log ( (double)g_tFlash.cNofBanks) / log (2.0));
	g_tFlash.nBankSelect[0] = FLASH_BANK_SELECT0;
	g_tFlash.nBankSelect[1] = FLASH_BANK_SELECT1;
	g_tFlash.nBankSelect[2] = FLASH_BANK_SELECT2;
	g_tFlash.nBankSelect[3] = FLASH_BANK_SELECT3;
	g_tFlash.nBankSelect[4] = FLASH_BANK_SELECT4;
	g_tFlash.nBankSelect[5] = FLASH_BANK_SELECT5;
#ifdef _USE_VDK_
	g_tFlash.bLocked = false;
#endif
	Flash_BankSelectSetup ();
	
	g_tFlash.bMultiThreading = true;
}

void Flash_SelectBank (unsigned char pa_cBank) {
	if (g_tFlash.cCurrentBank != pa_cBank) {
		// bank must be changed
		if (pa_cBank < g_tFlash.cNofBanks) {
			// valid bank selected
			register unsigned char i;
			register unsigned char cBankMask = 0x01;
			for (i=0; i<g_tFlash.cNofBankSelect; i++) {
				if (pa_cBank & cBankMask) {
					gpio_set (g_tFlash.nBankSelect[i]);
				} else {
					gpio_clear (g_tFlash.nBankSelect[i]);		
				}
				cBankMask <<= 1;
			}
			g_tFlash.cCurrentBank = pa_cBank;
			// wait for stabilizing gpio pins
			//Sleep (1);	// not really necessary
			asm("ssync;");
		}
	}
}

inline unsigned char Flash_GetBank (unsigned long nOffset) {
	return (nOffset / g_tFlash.nBankSize);
}

inline unsigned long Flash_GetAddress (unsigned char cBank, unsigned long nOffset) {
	return (nOffset - cBank * g_tFlash.nBankSize);	
}


/**
    @brief Disable Multithreading
**/
int Flash_DisableMultiThreading(void) {
    g_tFlash.bMultiThreading = false;
    
    return 0;
}

int Flash_EnableMultiThreading(void) {
    g_tFlash.bMultiThreading = true;    
    
    return 0;
}

/**
 *	@public	
 *	@brief		Erases the Specified Sector
 *
 *	@param 		startAddress 	start address of the flash device
 *	@param 		sectorAddress	address of the sector to be erased
 *
 *	@return 	Contents of the flash status register (on success 0x0080 for Intel Strata Compatible Devices)
 *	
 **/
unsigned short Flash_EraseSector(unsigned long startAddress, unsigned long sectorAddress) {
	if (g_tFlash.cNofBanks > 1) {
		// we have more than one bank, so check witch bank to select
		// and correct the offset
		unsigned char cBank = Flash_GetBank (sectorAddress);
		Flash_SelectBank (cBank);
		sectorAddress = Flash_GetAddress (cBank, sectorAddress);
	}
	
	volatile unsigned short *memIndex = (unsigned short *)(startAddress + sectorAddress);
/*	
#ifdef _USE_DATA_CACHING_		
	int nOldVal = __cplb_ctrl;
	dcache_invalidate_both(); 
	disable_data_cache();
#endif	
*/
	*memIndex = 0x0050;
	asm("ssync;");
	*memIndex = 0x0020;
	asm("ssync;");
	*memIndex = 0x00d0;
	asm("ssync;");

	unsigned short status;
	unsigned long nTimeout = 0;
	do {
		status = *memIndex;
		asm("ssync;");
	#ifdef _USE_VDK_
	    if (g_tFlash.bMultiThreading) {
	        VDK_Yield();
	    }
	#endif    
		nTimeout ++;
	}
	while (((status & 0x0080) == 0x00) && (nTimeout < FLASH_ERASE_TIMEOUT));
		
	if (nTimeout >= FLASH_ERASE_TIMEOUT) {
		status = 0xff00;
	}
	
	nTimeout = FLASH_ERASE_TIMEOUT;
	do {
		*memIndex = 0xff; 		//back to "read array mode"
		asm("ssync;");
	#ifdef _USE_VDK_
	     if (g_tFlash.bMultiThreading) {
	        VDK_Yield();
	    }
	#endif    
		nTimeout--;
	}
	while ((*memIndex != 0xffff) && nTimeout);
	
	if (!nTimeout) {
		status = 0xff00;
	}
/*	
#ifdef _USE_DATA_CACHING_		
	enable_data_cache(nOldVal);
#endif	
*/	
	return (status);
}


/**
 *	@public
 *	@brief		Erases the Entire Flash memory!
 *
 *	@return 	Contents of the flash status register (on success 0x0080 for Intel Strata Compatible Devices)
 *	
 **/
unsigned short Flash_EraseChip(void) {
	
	volatile unsigned long sectorAddress;
	unsigned short status;
	
	sectorAddress = 0;
	Flash_Reset ();
	do {
		status = Flash_EraseSector(FLASH_START_ADDRESS, sectorAddress);
		#if FLASH_DEBUG_LEVEL > 1
			printf("  Status of block %xh: %xh\n", sectorAddress, status);
		#endif
		sectorAddress += FLASH_SECTOR_SIZE;
	}
	while ((sectorAddress <= FLASH_LAST_SECTOR) && (status != 0xff00));

	return (status);
}


// Programmingsequence for intel flash devices.
// Offset: Wordaddress, relativ to startAddress
// Return values:
// 0x0080:	everything ok.
// 0xff00:	programming sequence timeout error.
// 0xee00:	value not programmed correctly.


/**
 *	@public
 *	@brief 		Programs a Word at the specified address. (startAddress + wordAddress)
 *
 *	@param 		startAddress 	Start address of the flash device
 *	@param 		wordAddress		Address of the word to be programmed
 *	@param 		data			Value to be written
 *
 *	@return 	0x0080 All data written sucessfully, 0xff00 Programming sequence timeout error, 0xee00 Value not programmed correctly.
 *
 **/
unsigned short Flash_ProgramWord(unsigned long startAddress, unsigned long wordAddress, unsigned short data) {
/*
#ifdef _USE_DATA_CACHING_		
	int nOldVal = __cplb_ctrl;
	dcache_invalidate_both(); 
	disable_data_cache();
#endif
*/
	if (g_tFlash.cNofBanks > 1) {
		// we have more than one bank, so check witch bank to select
		// and correct the offset
		unsigned char cBank = Flash_GetBank (wordAddress);
		Flash_SelectBank (cBank);
		wordAddress = Flash_GetAddress (cBank, wordAddress);
	}
	
	volatile unsigned short *memIndex;
	unsigned long nTimeout;
	unsigned short status;
	
	memIndex = (unsigned short *)(startAddress + wordAddress);

	*memIndex = 0x00ff;
	asm("ssync;");
	*memIndex = 0x0050; 	//reset statusregister
	asm("ssync;");
	*memIndex = 0x0040;
	asm("ssync;");
	*memIndex = data;
	asm("ssync;");
	
	nTimeout=0;
	do {
		status = *memIndex;
		asm("ssync;");
	#ifdef _USE_VDK_	
		 if (g_tFlash.bMultiThreading) {
	        VDK_Yield();
	    }
	#endif	
		nTimeout++;
	}
	while (((status & 0x0080) == 0x00) && (nTimeout < FLASH_PROGRAM_TIMEOUT));
	if (nTimeout >= FLASH_PROGRAM_TIMEOUT) {
		status = 0xff00;
	}
	
	nTimeout = FLASH_PROGRAM_TIMEOUT;
	unsigned short nValue;
	if (status != 0xff00) {
		do {
			*memIndex = 0xffff; 		//back to "read array mode".
			asm("ssync;");
			nValue = *memIndex;
			asm("ssync;");
		#ifdef _USE_VDK_
			 if (g_tFlash.bMultiThreading) {
	        VDK_Yield();
	    }
		#endif
            nTimeout --;
		} while (nTimeout && (nValue != data));
		if (nTimeout == 0) {
			status = 0xee00;		
		}
	}
/*	
#ifdef _USE_DATA_CACHING_		
	enable_data_cache(nOldVal);
#endif	
*/	
	return status;	
}






/**
 *	@public
 *	@brief		Reads the Flash Device identifier, and Manufacture Codes
 *
 *	@param 		cDevCode	Holds the device code after the function call
 *	@param 		cManuCode	Holds the manufacturer code after the function call	
 *
 **/
void Flash_ReadIdentifier (unsigned short *cDevCode, unsigned short *cManuCode) {
	volatile unsigned short *nFlashAddr;

	Flash_BankSelectReset ();
	nFlashAddr = (unsigned short *)FLASH_START_ADDRESS;
	
	*nFlashAddr = 0x0090;	//read identifier command.
	asm("ssync;");
/*
#ifdef _USE_DATA_CACHING_		
		flush_data_cache(); 
#endif
*/		
	*cManuCode = *nFlashAddr;
	asm("ssync;");
	nFlashAddr++;
/*	
#ifdef _USE_DATA_CACHING_		
		flush_data_cache(); 
#endif	
*/
	*cDevCode  = *nFlashAddr;
	asm("ssync;");
	
	*nFlashAddr = 0x00ff;	//return to read array mode.
	asm("ssync;");
}


/**
 *	@public
 *	@brief		Reads the Flash Device identifier, and Manufacture Codes
 *
 *	@param 		cDevCode	Holds the device code after the function call
 *	@param 		cManuCode	Holds the manufacturer code after the function call	
 *
 **/
void Flash_Read64BitUniqueFactoryID(unsigned long long *pa_unID) {
	volatile unsigned short *nFlashAddr;
	
	Flash_BankSelectReset ();
	nFlashAddr = (unsigned short *)FLASH_START_ADDRESS;
	
	*nFlashAddr = 0x0090;	//read identifier command.
	asm("ssync;");
/*
#ifdef _USE_DATA_CACHING_		
		flush_data_cache(); 
#endif
*/		
    nFlashAddr += 0x81;
	*pa_unID = *nFlashAddr;
	asm("ssync;");
	
	nFlashAddr++;
	*pa_unID += ((unsigned long long)*nFlashAddr) << 16;
	asm("ssync;");

	nFlashAddr++;
	*pa_unID += ((unsigned long long)*nFlashAddr) << 32;
	asm("ssync;");

	nFlashAddr++;
	*pa_unID += ((unsigned long long)*nFlashAddr) << 48;
	asm("ssync;");
		
	
/*	
#ifdef _USE_DATA_CACHING_		
		flush_data_cache(); 
#endif	
*/
	
	*nFlashAddr = 0x00ff;	//return to read array mode.
	asm("ssync;");
	
}



/**
 *	@public
 *	@brief		Unlocks a specified sector of the flash
 *
 *	@param 		pa_nSectorAddr		Sector address to be unlocked
 *
 *	@return		Contents of the flash status register (on success 0x0080 for Intel Strata Compatible Devices)
 *
 **/
unsigned short Flash_UnlockBlock (unsigned long pa_nSectorAddr) {
	if (g_tFlash.cNofBanks > 1) {
		// we have more than one bank, so check witch bank to select
		// and correct the offset
		unsigned char cBank = Flash_GetBank (pa_nSectorAddr);
		Flash_SelectBank (cBank);
		pa_nSectorAddr = Flash_GetAddress (cBank, pa_nSectorAddr);
	}
	
	volatile unsigned short *nFlashAddr;
	unsigned short nStatus = 0;

	nFlashAddr = (unsigned short *)(FLASH_START_ADDRESS + pa_nSectorAddr);

	*nFlashAddr = 0x00ff;	
	asm("ssync;");
	*nFlashAddr = 0x0050; 	//reset statusregister
	asm("ssync;");
	*nFlashAddr = 0x0060;	
	asm("ssync;");
	*nFlashAddr = 0x00d0;	
	asm("ssync;");
	
/*
#ifdef _USE_DATA_CACHING_		
		flush_data_cache(); 
#endif	
*/
	unsigned long nTimeout = FLASH_UNLOCK_TIMEOUT;
	do {
		nStatus = *nFlashAddr;
	#ifdef _USE_VDK_
	     if (g_tFlash.bMultiThreading) {
	        VDK_Yield();
	    }
	#endif    	
		nTimeout --;
	} while (((nStatus & 0x0080) != 0x0080) && nTimeout);

	*nFlashAddr = 0x00ff;		//return to read array mode.	
	
	if (!nTimeout) {
		nStatus = 0xff00;
	}
		
	return (nStatus);
}




/**
 *	@public
 *	@brief		Locks a specified sector of the Flash
 *
 *	@param 		pa_nSectorAddr	Address of sector to be locked
 *
 *	@return		Contents of the flash status register (on success 0x0080 for Intel Strata Compatible Devices)
 *	
 **/
unsigned short Flash_LockBlock (unsigned long pa_nSectorAddr) {
	if (g_tFlash.cNofBanks > 1) {
		// we have more than one bank, so check witch bank to select
		// and correct the offset
		unsigned char cBank = Flash_GetBank (pa_nSectorAddr);
		Flash_SelectBank (cBank);
		pa_nSectorAddr = Flash_GetAddress (cBank, pa_nSectorAddr);
	}
		
	volatile unsigned short *nFlashAddr;
	unsigned short nStatus = 0x0080;
	//unsigned long nTimeout = 0;

	nFlashAddr = (unsigned short *)(FLASH_START_ADDRESS + pa_nSectorAddr);

	//nTimeout=0;
	asm("ssync;");
	*nFlashAddr = 0x0060;	
	asm("ssync;");
	*nFlashAddr = 0x0001;	
	asm("ssync;");
	*nFlashAddr = 0x00ff;		//return to read array mode.
	asm("ssync;");
/*
#ifdef _USE_DATA_CACHING_		
		flush_data_cache(); 
#endif
*/	
	return (nStatus);
}



/**
 *	@public
 *	@brief		Locks all sectors of the Flash
 *
 *	@return		Contents of the flash status register (on success 0x0080 for Intel Strata Compatible Devices)
 *	
 **/
unsigned short Flash_LockAll (void) {
	volatile unsigned long sectorAddress = 0;
	unsigned short status = 0;
	
	Flash_Reset ();
	do {
		status = Flash_LockBlock (sectorAddress);
		sectorAddress += FLASH_SECTOR_SIZE;
	}
	while ((sectorAddress <= FLASH_LAST_SECTOR) && (status != 0xff00));

	return status;
}



/**
 *	@public
 *	@brief		Unlocks all sectors of the Flash
 *
 *	@return		Contents of the flash status register (on success 0x0080 for Intel Strata Compatible Devices)
 *	
 **/
unsigned short Flash_UnlockAll (void) {
	volatile unsigned long sectorAddress = 0;
	unsigned short status = 0;
	
	Flash_Reset ();
	do {
		status = Flash_UnlockBlock (sectorAddress);
		sectorAddress += FLASH_SECTOR_SIZE;
	}
	while ((sectorAddress <= FLASH_LAST_SECTOR) && (status != 0xff00));

	return status;
}





/**
 *	@public
 *	@brief		Resets the Flash's State Machine to default values.
 *
 *	@return 	Returns 0 on sucess!
 *
 **/
 
 /* TODO: It seems the return value here is unecessary, or maybe we could handle it better! */
unsigned short Flash_Reset (void) {
	// reset the bank selectors
	Flash_BankSelectReset ();
	// return to read array mode
	*(unsigned short *)FLASH_START_ADDRESS = 0xffff;
	asm("ssync;");
	
	return 0;
}





/**
 *	@public
 *	@brief		Reads a word from the specified address
 *
 *	@param 		pa_nOffset 	Address offset to the Word
 *
 *	@return 	Contents of the flash status register (on success 0x0080 for Intel Strata Compatible Devices)
 *	
 **/
unsigned short Flash_ReadWord (unsigned long pa_nOffset) {
/*
#ifdef _USE_DATA_CACHING_		
	int nOldVal = __cplb_ctrl;
	dcache_invalidate_both(); 
	disable_data_cache();
#endif
*/	
	if (g_tFlash.cNofBanks > 1) {
		// we have more than one bank, so check which bank to select
		// and correct the offset
		unsigned char cBank = Flash_GetBank (pa_nOffset);
		Flash_SelectBank (cBank);
		pa_nOffset = Flash_GetAddress (cBank, pa_nOffset);
	}
	
	volatile unsigned short *nFlashAddr;
	unsigned short nValue;
	
	nFlashAddr = (unsigned short *)(FLASH_START_ADDRESS + pa_nOffset);

	// put the value at the location passed in
	nValue = (*nFlashAddr);
	asm ("ssync;");
/*	
#ifdef _USE_DATA_CACHING_		
	enable_data_cache(nOldVal);
#endif	
*/
	return (nValue);
}





/**
 *	@public
 *	@brief		Gets the sector number of the specified Address offset.
 *
 *	@param 		ulOffset		Address offset
 *
 *	@return 	Sector number containing the memory location specified by the Address offset.
 *	
 **/
int Flash_GetSectorNumber( unsigned long ulOffset) {
    /*
	if (g_tFlash.cNofBanks > 1) {
		// we have more than one bank, so check witch bank to select
		// and correct the offset
		unsigned char cBank = Flash_GetBank (ulOffset);
		Flash_SelectBank (cBank);
		ulOffset = Flash_GetAddress (cBank, ulOffset);
	}	
	*/
	return ((int)(ulOffset / FLASH_SECTOR_SIZE));		//calculates the sectornumber.
}






/**
 *	@public
 *	@brief		Checks if the Flash at the specified address is Empty.
 *
 *	@attention	Empty means a value of 0xffff (Erased Value!)
 *
 *	@param 		pa_nStartAddress	Start address of the check
 *	@param		pa_nEndAddress		End address of the check
 *
 *	@return 	true if empty, otherwise false
 **/
bool Flash_CheckIfEmpty (unsigned long pa_nStartAddress, unsigned long pa_nEndAddress) {
	unsigned long nWordIndex;

	// calculate relative offset to FLASH_START_ADDRESS
	pa_nStartAddress -= FLASH_START_ADDRESS;
	for (nWordIndex=pa_nStartAddress; nWordIndex < pa_nEndAddress/*FLASH_SIZE*/; nWordIndex+=2) {
		if (Flash_ReadWord(nWordIndex) != 0xffff) {
			return false;
		}
	}
		
	return true;
}






/**
 *	@public
 *	@brief		Get the size of the sector.
 *
 *	@param 		pa_unSectorAddr	Sector of interrest
 *
 *	@return 	the size of the given sector
 **/
unsigned long Flash_GetSectorSize(unsigned long pa_unSectorAddr) {
	return FLASH_SECTOR_SIZE;
}






/**
 *	@public
 *	@brief		Get the sector address.
 *
 *	@param 		pa_unSectorNumber	Sector number of interrest
 *
 *	@return 	the address of the given sector
 **/
unsigned long Flash_GetSectorAddress(unsigned long pa_unSectorNumber) {
	return (pa_unSectorNumber * FLASH_SECTOR_SIZE);
}

