/* ---------------------------------------------------------------------------- */
/*                  Atmel Microcontroller Software Support                      */
/*                       SAM Software Package License                           */
/* ---------------------------------------------------------------------------- */
/* Copyright (c) 2015, Atmel Corporation                                        */
/*                                                                              */
/* All rights reserved.                                                         */
/*                                                                              */
/* Redistribution and use in source and binary forms, with or without           */
/* modification, are permitted provided that the following condition is met:    */
/*                                                                              */
/* - Redistributions of source code must retain the above copyright notice,     */
/* this list of conditions and the disclaimer below.                            */
/*                                                                              */
/* Atmel's name may not be used to endorse or promote products derived from     */
/* this software without specific prior written permission.                     */
/*                                                                              */
/* DISCLAIMER:  THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR   */
/* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE   */
/* DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,      */
/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT */
/* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,  */
/* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF    */
/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING         */
/* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, */
/* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                           */
/* ---------------------------------------------------------------------------- */

/** \addtogroup flashd_module Flash Memory Interface
 * The flash driver manages the programming, erasing, locking and unlocking
 * sequences with dedicated commands.
 *
 * To implement flash programming operation, the user has to follow these few
 * steps :
 * <ul>
 * <li>Configure flash wait states to initializes the flash. </li>
 * <li>Checks whether a region to be programmed is locked. </li>
 * <li>Unlocks the user region to be programmed if the region have locked
 * before.</li>
 * <li>Erases the user page before program (optional).</li>
 * <li>Writes the user page from the page buffer.</li>
 * <li>Locks the region of programmed area if any.</li>
 * </ul>
 *
 * Writing 8-bit and 16-bit data is not allowed and may lead to unpredictable
 * data corruption.
 * A check of this validity and padding for 32-bit alignment should be done in
 * write algorithm.
 * Lock/unlock range associated with the user address range is automatically
 * translated.
 *
 * This security bit can be enabled through the command "Set General Purpose
 * NVM Bit 0".
 *
 * A 128-bit factory programmed unique ID could be read to serve several
 * purposes.
 *
 * The driver accesses the flash memory by calling the lowlevel module provided
 * in \ref efc_module.
 * For more accurate information, please look at the EEFC section of the
 * Datasheet.
 *
 * Related files :\n
 * \ref flashd.c\n
 * \ref flashd.h.\n
 * \ref efc.c\n
 * \ref efc.h.\n
 */
/*@{*/
/*@}*/


/**
 * \file
 *
 * The flash driver provides the unified interface for flash program operations.
 *
 */

/*----------------------------------------------------------------------------
 *        Headers
 *----------------------------------------------------------------------------*/
#include "chip.h"

#include <string.h>
#include <assert.h>

/*----------------------------------------------------------------------------
 *        Definitions
 *----------------------------------------------------------------------------*/

#define GPNVM_NUM_MAX    9

/*----------------------------------------------------------------------------
 *        Local variables
 *----------------------------------------------------------------------------*/

static uint32_t _pdwPageBuffer[IFLASH_PAGE_SIZE / sizeof(uint32_t)];
static uint32_t _dwUseIAP = 1; /* Use IAP interface by default. */


/*----------------------------------------------------------------------------
 *        Local functions
 *----------------------------------------------------------------------------*/


/**
 * \brief Computes the lock range associated with the given address range.
 *
 * \param dwStart  Start address of lock range.
 * \param dwEnd  End address of lock range.
 * \param pdwActualStart  Actual start address of lock range.
 * \param pdwActualEnd  Actual end address of lock range.
 */
static void ComputeLockRange(uint32_t dwStart, uint32_t dwEnd,
							  uint32_t *pdwActualStart, uint32_t *pdwActualEnd)
{
	Efc *pStartEfc;
	Efc *pEndEfc;
	uint16_t wStartPage;
	uint16_t wEndPage;
	uint16_t wNumPagesInRegion;
	uint16_t wActualStartPage;
	uint16_t wActualEndPage;

	/* Convert start and end address in page numbers */
	EFC_TranslateAddress(&pStartEfc, dwStart, &wStartPage, 0);
	EFC_TranslateAddress(&pEndEfc, dwEnd, &wEndPage, 0);

	/* Find out the first page of the first region to lock */
	wNumPagesInRegion = IFLASH_LOCK_REGION_SIZE / IFLASH_PAGE_SIZE;
	wActualStartPage = wStartPage - (wStartPage % wNumPagesInRegion);
	wActualEndPage = wEndPage;

	if ((wEndPage % wNumPagesInRegion) != 0)
		wActualEndPage += wNumPagesInRegion - (wEndPage % wNumPagesInRegion);

	/* Store actual page numbers */
	EFC_ComputeAddress(pStartEfc, wActualStartPage, 0, pdwActualStart);
	EFC_ComputeAddress(pEndEfc, wActualEndPage, 0, pdwActualEnd);
	TRACE_DEBUG("Actual lock range is 0x%06X - 0x%06X\n\r",
				 (unsigned int)*pdwActualStart, (unsigned int)*pdwActualEnd);
}


/*----------------------------------------------------------------------------
 *        Exported functions
 *----------------------------------------------------------------------------*/

/**
 * \brief Initializes the flash driver.
 *
 * \param dwMCk     Master clock frequency in Hz.
 * \param dwUseIAP  0: use EEFC controller interface, 1: use IAP interface.
 *                  dwUseIAP should be set to 1 when running out of flash.
 */

extern void FLASHD_Initialize(uint32_t dwMCk, uint32_t dwUseIAP)
{
	dwMCk = dwMCk; /* avoid warnings */

	EFC_DisableFrdyIt(EFC);
	_dwUseIAP = dwUseIAP;
}

/**
 * \brief Erases the entire flash.
 *
 * \param dwAddress  Flash start address.
 * \return 0 if successful; otherwise returns an error code.
 */
extern uint32_t FLASHD_Erase(uint32_t dwAddress)
{
	Efc *pEfc;
	uint16_t wPage;
	uint16_t wOffset;
	uint32_t dwError;

	assert((dwAddress >= IFLASH_ADDR)
			|| (dwAddress <= (IFLASH_ADDR + IFLASH_SIZE)));

	/* Translate write address */
	EFC_TranslateAddress(&pEfc, dwAddress, &wPage, &wOffset);
	dwError = EFC_PerformCommand(pEfc, EFC_FCMD_EA, 0, _dwUseIAP);

	return dwError;
}

/**
 * \brief Erases flash by sector.
 *
 * \param dwAddress  Start address of be erased sector.
 *
 * \return 0 if successful; otherwise returns an error code.
 */
extern uint32_t FLASHD_EraseSector(uint32_t dwAddress)
{
	Efc *pEfc;
	uint16_t wPage;
	uint16_t wOffset;
	uint32_t dwError;

	assert((dwAddress >= IFLASH_ADDR)
			|| (dwAddress <= (IFLASH_ADDR + IFLASH_SIZE)));

	/* Translate write address */
	EFC_TranslateAddress(&pEfc, dwAddress, &wPage, &wOffset);
	dwError = EFC_PerformCommand(pEfc, EFC_FCMD_ES, wPage, _dwUseIAP);

	return dwError;
}

/**
 * \brief Erases flash by pages.
 *
 * \param dwAddress  Start address of be erased pages.
 * \param dwPageNum  Number of pages to be erased with EPA command (4, 8, 16, 32)
 *
 * \return 0 if successful; otherwise returns an error code.
 */
extern uint32_t FLASHD_ErasePages(uint32_t dwAddress, uint32_t dwPageNum)
{
	Efc *pEfc;
	uint16_t wPage;
	uint16_t wOffset;
	uint32_t dwError;
	static uint32_t dwFarg;

	assert((dwAddress >= IFLASH_ADDR)
			|| (dwAddress <= (IFLASH_ADDR + IFLASH_SIZE)));

	/* Translate write address */
	EFC_TranslateAddress(&pEfc, dwAddress, &wPage, &wOffset);

	/* Get FARG field for EPA command:
	 * The first page to be erased is specified in the FARG[15:2] field of
	 * the MC_FCR register. The first page number must be modulo 4, 8,16 or 32
	 * according to the number of pages to erase at the same time.
	 *
	 * The 2 lowest bits of the FARG field define the number of pages to
	 * be erased (FARG[1:0]).
	 */
	if (dwPageNum == 32) {
		wPage &= ~(32u - 1u);
		dwFarg = (wPage) | 3; /* 32 pages */
	} else if (dwPageNum == 16) {
		wPage &= ~(16u - 1u);
		dwFarg = (wPage) | 2; /* 16 pages */
	} else if (dwPageNum == 8) {
		wPage &= ~(8u - 1u);
		dwFarg = (wPage) | 1; /* 8 pages */
	} else {
		wPage &= ~(4u - 1u);
		dwFarg = (wPage) | 0; /* 4 pages */
	}

	dwError = EFC_PerformCommand(pEfc, EFC_FCMD_EPA, dwFarg, _dwUseIAP);

	return dwError;
}


/**
 * \brief Writes a data buffer in the internal flash
 *
 * \note This function works in polling mode, and thus only returns when the
 * data has been effectively written.
 * \param address  Write address.
 * \param pBuffer  Data buffer.
 * \param size  Size of data buffer in bytes.
 * \return 0 if successful, otherwise returns an error code.
 */
extern uint32_t FLASHD_Write(uint32_t dwAddress,
							  const void *pvBuffer, uint32_t dwSize)
{
	Efc *pEfc;
	uint16_t page;
	uint16_t offset;
	uint32_t writeSize;
	uint32_t pageAddress;
	uint16_t padding;
	uint32_t dwError;
	uint32_t dwIdx;
	uint32_t *pAlignedDestination;
	uint8_t  *pucPageBuffer = (uint8_t *)_pdwPageBuffer;

	assert(pvBuffer);
	assert(dwAddress >= IFLASH_ADDR);
	assert((dwAddress + dwSize) <= (IFLASH_ADDR + IFLASH_SIZE));

	/* Translate write address */
	EFC_TranslateAddress(&pEfc, dwAddress, &page, &offset);

	/* Write all pages */
	while (dwSize > 0) {
		/* Copy data in temporary buffer to avoid alignment problems */
		writeSize = min((uint32_t)IFLASH_PAGE_SIZE - offset, dwSize);
		EFC_ComputeAddress(pEfc, page, 0, &pageAddress);
		padding = IFLASH_PAGE_SIZE - offset - writeSize;

		/* Pre-buffer data */
		memcpy(pucPageBuffer, (void *) pageAddress, offset);

		/* Buffer data */
		memcpy(pucPageBuffer + offset, pvBuffer, writeSize);

		/* Post-buffer data */
		memcpy(pucPageBuffer + offset + writeSize,
				(void *) (pageAddress + offset + writeSize), padding);

		/* Write page
		 * Writing 8-bit and 16-bit data is not allowed and may
		    lead to unpredictable data corruption
		 */
		pAlignedDestination = (uint32_t *)pageAddress;

		for (dwIdx = 0; dwIdx < (IFLASH_PAGE_SIZE / sizeof(uint32_t)); ++ dwIdx) {
			*pAlignedDestination++ = _pdwPageBuffer[dwIdx];
			memory_barrier()
		}

		/* Cache coherence operation before flash write*/
		SCB_CleanDCache_by_Addr((uint32_t *)pageAddress, IFLASH_PAGE_SIZE);

		/* Note: It is not possible to use Erase and write Command (EWP) on all Flash
		(this command is available on the First 2 Small Sector, 16K Bytes).
		For the next block, Erase them first then use Write page command. */

		/* Send writing command */
		dwError = EFC_PerformCommand(pEfc, EFC_FCMD_WP, page, _dwUseIAP);

		if (dwError)
			return dwError;

		/* Progression */
		pvBuffer = (void *)((uint32_t) pvBuffer + writeSize);
		dwSize -= writeSize;
		page++;
		offset = 0;
	}

	return 0;
}

/**
 * \brief Locks all the regions in the given address range. The actual lock
 * range is reported through two output parameters.
 *
 * \param start  Start address of lock range.
 * \param end    End address of lock range.
 * \param pActualStart  Start address of the actual lock range (optional).
 * \param pActualEnd  End address of the actual lock range (optional).
 * \return 0 if successful, otherwise returns an error code.
 */
extern uint32_t FLASHD_Lock(uint32_t start, uint32_t end,
							 uint32_t *pActualStart, uint32_t *pActualEnd)
{
	Efc *pEfc;
	uint32_t actualStart, actualEnd;
	uint16_t startPage, endPage;
	uint32_t dwError;
	uint16_t numPagesInRegion = IFLASH_LOCK_REGION_SIZE / IFLASH_PAGE_SIZE;

	/* Compute actual lock range and store it */
	ComputeLockRange(start, end, &actualStart, &actualEnd);

	if (pActualStart != NULL)
		*pActualStart = actualStart;

	if (pActualEnd != NULL)
		*pActualEnd = actualEnd;

	/* Compute page numbers */
	EFC_TranslateAddress(&pEfc, actualStart, &startPage, 0);
	EFC_TranslateAddress(0, actualEnd, &endPage, 0);

	/* Lock all pages */
	while (startPage < endPage) {
		dwError = EFC_PerformCommand(pEfc, EFC_FCMD_SLB, startPage, _dwUseIAP);

		if (dwError)
			return dwError;

		startPage += numPagesInRegion;
	}

	return 0;
}

/**
 * \brief Unlocks all the regions in the given address range. The actual unlock
 * range is reported through two output parameters.
 * \param start  Start address of unlock range.
 * \param end  End address of unlock range.
 * \param pActualStart  Start address of the actual unlock range (optional).
 * \param pActualEnd  End address of the actual unlock range (optional).
 * \return 0 if successful, otherwise returns an error code.
 */
extern uint32_t FLASHD_Unlock(uint32_t start, uint32_t end,
							   uint32_t *pActualStart, uint32_t *pActualEnd)
{
	Efc *pEfc;
	uint32_t actualStart, actualEnd;
	uint16_t startPage, endPage;
	uint32_t dwError;
	uint16_t numPagesInRegion = IFLASH_LOCK_REGION_SIZE / IFLASH_PAGE_SIZE;

	/* Compute actual unlock range and store it */
	ComputeLockRange(start, end, &actualStart, &actualEnd);

	if (pActualStart != NULL)
		*pActualStart = actualStart;

	if (pActualEnd != NULL)
		*pActualEnd = actualEnd;

	/* Compute page numbers */
	EFC_TranslateAddress(&pEfc, actualStart, &startPage, 0);
	EFC_TranslateAddress(0, actualEnd, &endPage, 0);

	/* Unlock all pages */
	while (startPage < endPage) {
		dwError = EFC_PerformCommand(pEfc, EFC_FCMD_CLB, startPage, _dwUseIAP);

		if (dwError)
			return dwError;

		startPage += numPagesInRegion;
	}

	return 0;
}

/**
 * \brief Returns the number of locked regions inside the given address range.
 *
 * \param start  Start address of range
 * \param end    End address of range.
 */
extern uint32_t FLASHD_IsLocked(uint32_t start, uint32_t end)
{
	uint32_t i, j;
	Efc *pEfc;
	uint16_t startPage, endPage;
	uint8_t startRegion, endRegion;
	uint32_t numPagesInRegion;
	uint32_t status[IFLASH_NB_OF_LOCK_BITS / 32u];
	uint32_t numLockedRegions = 0;

	assert(end >= start);
	assert((start >= IFLASH_ADDR) && (end <= IFLASH_ADDR + IFLASH_SIZE));

	/* Compute page numbers */
	EFC_TranslateAddress(&pEfc, start, &startPage, 0);
	EFC_TranslateAddress(0, end, &endPage, 0);

	/* Compute region numbers */
	numPagesInRegion = IFLASH_LOCK_REGION_SIZE / IFLASH_PAGE_SIZE;
	startRegion = startPage / numPagesInRegion;
	endRegion = endPage / numPagesInRegion;

	if ((endPage % numPagesInRegion) != 0)
		endRegion++;

	/* Retrieve lock status */
	EFC_PerformCommand(pEfc, EFC_FCMD_GLB, 0, _dwUseIAP);

	for (i = 0; i < (IFLASH_NB_OF_LOCK_BITS / 32u); i++)
		status[i] = EFC_GetResult(pEfc);

	/* Check status of each involved region */
	while (startRegion < endRegion) {
		i = startRegion / 32u;
		j = startRegion % 32u;

		if ((status[i] & (1 << j)) != 0)
			numLockedRegions++;

		startRegion++;
	}

	return numLockedRegions;
}

/**
 * \brief Check if the given GPNVM bit is set or not.
 *
 * \param gpnvm  GPNVM bit index.
 * \returns 1 if the given GPNVM bit is currently set; otherwise returns 0.
 */
extern uint32_t FLASHD_IsGPNVMSet(uint8_t ucGPNVM)
{
	uint32_t dwStatus;

	assert(ucGPNVM < GPNVM_NUM_MAX);

	/* Get GPNVMs status */
	EFC_PerformCommand(EFC, EFC_FCMD_GFB, 0, _dwUseIAP);
	dwStatus = EFC_GetResult(EFC);

	/* Check if GPNVM is set */
	if ((dwStatus & (1 << ucGPNVM)) != 0)
		return 1;
	else
		return 0;
}

/**
 * \brief Sets the selected GPNVM bit.
 *
 * \param gpnvm  GPNVM bit index.
 * \returns 0 if successful; otherwise returns an error code.
 */
extern uint32_t FLASHD_SetGPNVM(uint8_t ucGPNVM)
{
	assert(ucGPNVM < GPNVM_NUM_MAX);

	if (!FLASHD_IsGPNVMSet(ucGPNVM))
		return EFC_PerformCommand(EFC, EFC_FCMD_SFB, ucGPNVM, _dwUseIAP);
	else
		return 0;
}

/**
 * \brief Clears the selected GPNVM bit.
 *
 * \param gpnvm  GPNVM bit index.
 * \returns 0 if successful; otherwise returns an error code.
 */
extern uint32_t FLASHD_ClearGPNVM(uint8_t ucGPNVM)
{
	assert(ucGPNVM < GPNVM_NUM_MAX);

	if (FLASHD_IsGPNVMSet(ucGPNVM))
		return EFC_PerformCommand(EFC, EFC_FCMD_CFB, ucGPNVM, _dwUseIAP);
	else
		return 0;
}

/**
 * \brief Read the unique ID.
 *
 * \param pdwUniqueID pointer on a 4bytes char containing the unique ID value.
 * \returns 0 if successful; otherwise returns an error code.
 */
#ifdef __ICCARM__
	extern __ramfunc uint32_t FLASHD_ReadUniqueID(uint32_t *pdwUniqueID)
#else
	__attribute__ ((section (".ramfunc")))
	uint32_t FLASHD_ReadUniqueID(uint32_t *pdwUniqueID)
#endif
{
	uint32_t status;

	if (pdwUniqueID == NULL)
		return 1;

	pdwUniqueID[0] = 0;
	pdwUniqueID[1] = 0;
	pdwUniqueID[2] = 0;
	pdwUniqueID[3] = 0;

	/* Send the Start Read unique Identifier command (STUI) by writing the Flash
	   Command Register with the STUI command.*/
	EFC->EEFC_FCR = EEFC_FCR_FKEY_PASSWD | EFC_FCMD_STUI;

	/* When the Unique Identifier is ready to be read, the FRDY bit in the Flash
	   Programming Status Register (EEFC_FSR) falls. */
	do {
		status = EFC->EEFC_FSR;
	} while ((status & EEFC_FSR_FRDY) == EEFC_FSR_FRDY);

	/* The Unique Identifier is located in the first 128 bits of the Flash
	   memory mapping. So, at the address 0x400000-0x40000F. */
	pdwUniqueID[0] = *(uint32_t *)IFLASH_ADDR;
	pdwUniqueID[1] = *(uint32_t *)(IFLASH_ADDR + 4);
	pdwUniqueID[2] = *(uint32_t *)(IFLASH_ADDR + 8);
	pdwUniqueID[3] = *(uint32_t *)(IFLASH_ADDR + 12);

	/* To stop the Unique Identifier mode, the user needs to send the Stop Read
	   unique Identifier command (SPUI) by writing the Flash Command Register
	   with the SPUI command. */
	EFC->EEFC_FCR = EEFC_FCR_FKEY_PASSWD | EFC_FCMD_SPUI;

	/* When the Stop read Unique Unique Identifier command (SPUI) has been
	performed, the FRDY bit in the Flash Programming Status Register (EEFC_FSR)
	rises. */
	do {
		status = EFC->EEFC_FSR;
	} while ((status & EEFC_FSR_FRDY) != EEFC_FSR_FRDY);

	return 0;
}

