/* ----------------------------------------------------------------------------
* ATMEL Microcontroller Software Support
* ----------------------------------------------------------------------------
* Copyright (c) 2009, 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 programing operation, the user has to follow these few steps :
*
* - Configue flash wait states to initializes the flash.
* - Checks whether a region to be programmed is locked.
* - Unlocks the user region to be programmed if the region have locked before.
* - Erases the user page before program (optional).
* - Writes the user page from the page buffer.
* - Locks the region of programmed area if any.
*
*
* 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 "flashd.h"
#include
#include
#include
#include
#include
#include
/*----------------------------------------------------------------------------
* Local functions
*----------------------------------------------------------------------------*/
/**
* \brief Computes the lock range associated with the given address range.
*
* \param start Start address of lock range.
* \param end End address of lock range.
* \param pActualStart Actual start address of lock range.
* \param pActualEnd Actual end address of lock range.
*/
static void ComputeLockRange(
uint32_t start,
uint32_t end,
uint32_t *pActualStart,
uint32_t *pActualEnd)
{
Efc *pStartEfc, *pEndEfc;
uint16_t startPage, endPage;
uint16_t numPagesInRegion;
uint16_t actualStartPage, actualEndPage;
// Convert start and end address in page numbers
EFC_TranslateAddress(&pStartEfc, start, &startPage, 0);
EFC_TranslateAddress(&pEndEfc, end, &endPage, 0);
// Find out the first page of the first region to lock
numPagesInRegion = AT91C_IFLASH_LOCK_REGION_SIZE / AT91C_IFLASH_PAGE_SIZE;
actualStartPage = startPage - (startPage % numPagesInRegion);
actualEndPage = endPage;
if ((endPage % numPagesInRegion) != 0) {
actualEndPage += numPagesInRegion - (endPage % numPagesInRegion);
}
// Store actual page numbers
EFC_ComputeAddress(pStartEfc, actualStartPage, 0, pActualStart);
EFC_ComputeAddress(pEndEfc, actualEndPage, 0, pActualEnd);
TRACE_DEBUG("Actual lock range is 0x%06X - 0x%06X\n\r", *pActualStart, *pActualEnd);
}
/*----------------------------------------------------------------------------
* Exported functions
*----------------------------------------------------------------------------*/
/**
* \brief Initializes the flash driver.
*
* \param mck Master clock frequency in Hz.
*/
void FLASHD_Initialize(uint32_t mck)
{
EFC_DisableFrdyIt(EFC);
if ((mck/1000000) >= 64) {
EFC_SetWaitState(EFC, 2);
}
else if ((mck/1000000) >= 50) {
EFC_SetWaitState(EFC, 1);
}
else {
EFC_SetWaitState(EFC, 0);
}
}
/**
* \brief Erases the entire flash.
*
* \param address Flash start address.
* \return 0 if successful; otherwise returns an error code.
*/
uint8_t FLASHD_Erase(uint32_t address)
{
Efc *pEfc;
uint16_t page;
uint16_t offset;
uint8_t error;
SANITY_CHECK((address >=AT91C_IFLASH) || (address <= (AT91C_IFLASH + AT91C_IFLASH_SIZE)));
// Translate write address
EFC_TranslateAddress(&pEfc, address, &page, &offset);
error = EFC_PerformCommand(pEfc, EFC_FCMD_EA, 0);
return error;
}
static uint8_t pPageBuffer[AT91C_IFLASH_PAGE_SIZE];
/**
* \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.
*/
uint8_t FLASHD_Write(
uint32_t address,
const void *pBuffer,
uint32_t size)
{
Efc *pEfc;
uint16_t page;
uint16_t offset;
uint32_t writeSize;
uint32_t pageAddress;
uint16_t padding;
uint8_t error;
uint32_t sizeTmp;
uint32_t *pAlignedDestination;
uint32_t *pAlignedSource;
SANITY_CHECK(pBuffer);
SANITY_CHECK(address >=AT91C_IFLASH);
SANITY_CHECK((address + size) <= (AT91C_IFLASH + AT91C_IFLASH_SIZE));
// Translate write address
EFC_TranslateAddress(&pEfc, address, &page, &offset);
// Write all pages
while (size > 0) {
// Copy data in temporary buffer to avoid alignment problems
writeSize = min(AT91C_IFLASH_PAGE_SIZE - offset, size);
EFC_ComputeAddress(pEfc, page, 0, &pageAddress);
padding = AT91C_IFLASH_PAGE_SIZE - offset - writeSize;
// Pre-buffer data
memcpy(pPageBuffer, (void *) pageAddress, offset);
// Buffer data
memcpy(pPageBuffer + offset, pBuffer, writeSize);
// Post-buffer data
memcpy(pPageBuffer + 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;
pAlignedSource = (uint32_t*)pPageBuffer;
sizeTmp = AT91C_IFLASH_PAGE_SIZE;
while (sizeTmp >= 4) {
*pAlignedDestination++ = *pAlignedSource++;
sizeTmp -= 4;
}
// Send writing command
error = EFC_PerformCommand(pEfc, EFC_FCMD_EWP, page);
if (error) {
return error;
}
// Progression
address += AT91C_IFLASH_PAGE_SIZE;
pBuffer = (void *) ((uint32_t) pBuffer + writeSize);
size -= 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 address 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.
*/
uint8_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;
uint8_t error;
uint16_t numPagesInRegion = AT91C_IFLASH_LOCK_REGION_SIZE / AT91C_IFLASH_PAGE_SIZE;
// Compute actual lock range and store it
ComputeLockRange(start, end, &actualStart, &actualEnd);
if (pActualStart) {
*pActualStart = actualStart;
}
if (pActualEnd) {
*pActualEnd = actualEnd;
}
// Compute page numbers
EFC_TranslateAddress(&pEfc, actualStart, &startPage, 0);
EFC_TranslateAddress(0, actualEnd, &endPage, 0);
// Lock all pages
while (startPage < endPage) {
error = EFC_PerformCommand(pEfc, EFC_FCMD_SLB, startPage);
if (error) {
return error;
}
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 address 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.
*/
uint8_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;
uint8_t error;
uint16_t numPagesInRegion = AT91C_IFLASH_LOCK_REGION_SIZE / AT91C_IFLASH_PAGE_SIZE;
// Compute actual unlock range and store it
ComputeLockRange(start, end, &actualStart, &actualEnd);
if (pActualStart) {
*pActualStart = actualStart;
}
if (pActualEnd) {
*pActualEnd = actualEnd;
}
// Compute page numbers
EFC_TranslateAddress(&pEfc, actualStart, &startPage, 0);
EFC_TranslateAddress(0, actualEnd, &endPage, 0);
// Unlock all pages
while (startPage < endPage) {
error = EFC_PerformCommand(pEfc, EFC_FCMD_CLB, startPage);
if (error) {
return error;
}
startPage += numPagesInRegion;
}
return 0;
}
/**
* \brief Returns the number of locked regions inside the given address range.
*
* \param address Start address of range
* \param end End address of range.
*/
uint8_t FLASHD_IsLocked(uint32_t start, uint32_t end)
{
Efc *pEfc;
uint16_t startPage, endPage;
uint8_t startRegion, endRegion;
uint32_t numPagesInRegion;
uint32_t status;
uint8_t error;
uint32_t numLockedRegions = 0;
SANITY_CHECK(end >= start);
SANITY_CHECK((start >=AT91C_IFLASH) && (end <= AT91C_IFLASH + AT91C_IFLASH_SIZE));
// Compute page numbers
EFC_TranslateAddress(&pEfc, start, &startPage, 0);
EFC_TranslateAddress(0, end, &endPage, 0);
// Compute region numbers
numPagesInRegion = AT91C_IFLASH_LOCK_REGION_SIZE / AT91C_IFLASH_PAGE_SIZE;
startRegion = startPage / numPagesInRegion;
endRegion = endPage / numPagesInRegion;
if ((endPage % numPagesInRegion) != 0) {
endRegion++;
}
// Retrieve lock status
error = EFC_PerformCommand(pEfc, EFC_FCMD_GLB, 0);
ASSERT(!error, "-F- Error while trying to fetch lock bits status (0x%02X)\n\r", error);
status = EFC_GetResult(pEfc);
// Check status of each involved region
while (startRegion < endRegion) {
if ((status & (1 << startRegion)) != 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.
*/
uint8_t FLASHD_IsGPNVMSet(uint8_t gpnvm)
{
uint8_t error;
uint32_t status;
SANITY_CHECK(gpnvm < 2);
// Get GPNVMs status
error = EFC_PerformCommand(EFC, EFC_FCMD_GFB, 0);
ASSERT(!error, "-F- Error while trying to fetch GPNVMs status (0x%02X)\n\r", error);
status = EFC_GetResult(EFC);
// Check if GPNVM is set
if ((status & (1 << gpnvm)) != 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.
*/
uint8_t FLASHD_SetGPNVM(uint8_t gpnvm)
{
SANITY_CHECK(gpnvm < 2);
if (!FLASHD_IsGPNVMSet(gpnvm)) {
return EFC_PerformCommand(EFC, EFC_FCMD_SFB, gpnvm);
}
else {
return 0;
}
}
/**
* \brief Clears the selected GPNVM bit.
*
* \param gpnvm GPNVM bit index.
* \returns 0 if successful; otherwise returns an error code.
*/
uint8_t FLASHD_ClearGPNVM(uint8_t gpnvm)
{
SANITY_CHECK(gpnvm < 2);
if (FLASHD_IsGPNVMSet(gpnvm)) {
return EFC_PerformCommand(EFC, EFC_FCMD_CFB, gpnvm);
}
else {
return 0;
}
}
/**
* \brief Read the unique ID.
*
* \param uniqueID pointer on a 4bytes char containing the unique ID value.
* \returns 0 if successful; otherwise returns an error code.
*/
uint8_t FLASHD_ReadUniqueID (uint32_t * uniqueID)
{
uint8_t error;
SANITY_CHECK(uniqueID != NULL);
uniqueID[0] = 0;
uniqueID[1] = 0;
uniqueID[2] = 0;
uniqueID[3] = 0;
EFC_StartCommand(EFC, EFC_FCMD_STUI, 0);
uniqueID[0] = *(uint32_t *)AT91C_IFLASH;
uniqueID[1] = *(uint32_t *)(AT91C_IFLASH + 4);
uniqueID[2] = *(uint32_t *)(AT91C_IFLASH + 8);
uniqueID[3] = *(uint32_t *)(AT91C_IFLASH + 12);
error = EFC_PerformCommand(EFC, EFC_FCMD_SPUI, 0);
if (error) return error;
return 0;
}