blob: b6b5b66ca3d70c459a01c6db0ee11c592be0b1b6 [file] [log] [blame]
/* ----------------------------------------------------------------------------
* SAM Software Package License
* ----------------------------------------------------------------------------
* Copyright (c) 2012, Atmel Corporation
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are 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 << 2) | 3; /* 32 pages */
} else if (dwPageNum == 16) {
wPage &= ~(16u - 1u);
dwFarg = (wPage << 2) | 2; /* 16 pages */
} else if (dwPageNum == 8) {
wPage &= ~(8u - 1u);
dwFarg = (wPage << 2) | 1; /* 8 pages */
} else {
wPage &= ~(4u - 1u);
dwFarg = (wPage << 2) | 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()
}
/* 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;
}