blob: cfdd40d302431d31b1a9ccf41a9bf9e7e3d7742e [file] [log] [blame]
/* ----------------------------------------------------------------------------
* ATMEL Microcontroller Software Support
* ----------------------------------------------------------------------------
* Copyright (c) 2010, 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 external_component External Component
*
* \addtogroup at25d_module AT25 driver
* \ingroup external_component
* The AT25 serial dataflash driver is based on the corresponding AT25 SPI driver.
* A AT25 instance has to be initialized using the Dataflash levle function
* AT25_Configure(). AT25 Dataflash can be automatically detected using
* the AT25_FindDevice() function. Then AT25 dataflash operations such as
* read, write and erase DF can be launched using AT25_SendCommand function
* with corresponding AT25 command set.
*
* \section Usage
* <ul>
* <li> Reads a serial flash device ID using AT25D_ReadJedecId().</li>
* <li> Reads data from the At25 at the specified address using AT25D_Read().</li>
* <li> Writes data on the At25 at the specified address using AT25D_Write().</li>
* <li> Erases all chip using AT25D_EraseBlock().</li>
* <li> Erases a specified block using AT25D_EraseBlock().</li>
* <li> Poll until the At25 has completed of corresponding operations using
* AT25D_WaitReady().</li>
* <li> Retrieves and returns the At25 current using AT25D_ReadStatus().</li>
* </ul>
*
* Related files :\n
* \ref at25d.c\n
* \ref at25d.h.\n
*/
/*@{*/
/*@}*/
/**
* \file
*
* Implementation for the AT25 Serialflash driver.
*
*/
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#include <board.h>
#include <assert.h>
/*----------------------------------------------------------------------------
* Local functions
*----------------------------------------------------------------------------*/
/**
* \brief Wait for transfer to finish calling the SPI driver ISR. (interrupts are disabled)
*
* \param pAt25 Pointer to an AT25 driver instance.
*/
static void AT25D_Wait(At25 *pAt25)
{
/* Wait for transfer to finish */
while (AT25_IsBusy(pAt25))
SPID_Handler(pAt25->pSpid);
}
/**
* \brief Reads and returns the status register of the serial flash.
*
* \param pAt25 Pointer to an AT25 driver instance.
*/
static unsigned char AT25D_ReadStatus(At25 *pAt25)
{
unsigned char error, status;
assert(pAt25);
/* Issue a status read command */
error = AT25_SendCommand(pAt25, AT25_READ_STATUS, 1, &status, 1, 0, 0, 0);
assert(!error);
/* Wait for transfer to finish */
AT25D_Wait(pAt25);
return status;
}
/**
* \brief Writes the given value in the status register of the serial flash device.
*
* \param pAt25 Pointer to an AT25 driver instance.
* \param status Status to write.
*/
static void AT25D_WriteStatus(At25 *pAt25, unsigned char status)
{
unsigned char error;
assert(pAt25);
/* Issue a write status command */
error = AT25_SendCommand(pAt25, AT25_WRITE_STATUS, 1, &status, 1, 0, 0, 0);
assert(!error);
/* Wait for transfer to finish */
AT25D_Wait(pAt25);
}
/*----------------------------------------------------------------------------
* Global functions
*----------------------------------------------------------------------------*/
/**
* \brief Waits for the serial flash device to become ready to accept new commands.
*
* \param pAt25 Pointer to an AT25 driver instance.
*/
void AT25D_WaitReady(At25 *pAt25)
{
unsigned char ready = 0;
assert(pAt25);
/* Read status register and check busy bit */
while (!ready) {
ready = ((AT25D_ReadStatus(pAt25) & AT25_STATUS_RDYBSY) == AT25_STATUS_RDYBSY_READY);
}
}
/**
* \brief Reads and returns the serial flash device ID.
*
* \param pAt25 Pointer to an AT25 driver instance.
*/
unsigned int AT25D_ReadJedecId(At25 *pAt25)
{
unsigned char error;
unsigned int id = 0;
assert(pAt25);
/* Issue a read ID command */
error = AT25_SendCommand(pAt25, AT25_READ_JEDEC_ID, 1,
(unsigned char *) &id, 3, 0, 0, 0);
assert(!error);
/* Wait for transfer to finish */
AT25D_Wait(pAt25);
return id;
}
/**
* \brief Enables critical writes operation on a serial flash device, such as sector
* protection, status register, etc.
*
* \para pAt25 Pointer to an AT25 driver instance.
*/
void AT25D_EnableWrite(At25 *pAt25)
{
unsigned char error;
assert(pAt25);
/* Issue a write enable command */
error = AT25_SendCommand(pAt25, AT25_WRITE_ENABLE, 1, 0, 0, 0, 0, 0);
assert(!error);
/* Wait for transfer to finish */
AT25D_Wait(pAt25);
}
/**
* \brief Disables write operation on a serial flash device.
*
* \para pAt25 Pointer to an AT25 driver instance.
*/
void AT25D_DisableWrite(At25 *pAt25)
{
unsigned char error;
assert(pAt25);
/* Issue a write enable command */
error = AT25_SendCommand(pAt25, AT25_WRITE_DISABLE, 1, 0, 0, 0, 0, 0);
assert(!error);
/* Wait for transfer to finish */
AT25D_Wait(pAt25);
}
/**
* \brief Unprotects the contents of the serial flash device.
*
* \param pAt25 Pointer to an AT25 driver instance.
*
* \return 0 if the device has been unprotected; otherwise returns
* AT25_ERROR_PROTECTED.
*/
unsigned char AT25D_Unprotect(At25 *pAt25)
{
unsigned char status;
assert(pAt25);
/* Get the status register value to check the current protection */
status = AT25D_ReadStatus(pAt25);
if ((status & AT25_STATUS_SWP) == AT25_STATUS_SWP_PROTNONE) {
/* Protection already disabled */
return 0;
}
/* Check if sector protection registers are locked */
if ((status & AT25_STATUS_SPRL) == AT25_STATUS_SPRL_LOCKED) {
/* Unprotect sector protection registers by writing the status reg. */
AT25D_EnableWrite(pAt25);
AT25D_WriteStatus(pAt25, 0);
}
/* Perform a global unprotect command */
AT25D_EnableWrite(pAt25);
AT25D_WriteStatus(pAt25, 0);
/* Check the new status */
status = AT25D_ReadStatus(pAt25);
if ((status & (AT25_STATUS_SPRL | AT25_STATUS_SWP)) != 0) {
return AT25_ERROR_PROTECTED;
}
else {
return 0;
}
}
/**
* \brief Erases all the content of the memory chip.
*
* \param pAt25 Pointer to an AT25 driver instance.
*
* \return 0 if the device has been unprotected; otherwise returns
* AT25_ERROR_PROTECTED.
*/
unsigned char AT25D_EraseChip(At25 *pAt25)
{
unsigned char status;
unsigned char error;
assert(pAt25);
/* Check that the flash is unprotected */
status = AT25D_ReadStatus(pAt25);
if ((status & AT25_STATUS_SWP) != AT25_STATUS_SWP_PROTNONE) {
return AT25_ERROR_PROTECTED;
}
/* Enable critical write operation */
AT25D_EnableWrite(pAt25);
/* Erase the chip */
error = AT25_SendCommand(pAt25, AT25_CHIP_ERASE_2, 1, 0, 0, 0, 0, 0);
assert(!error);
/* Wait for transfer to finish */
AT25D_Wait(pAt25);
/* Poll the Serial flash status register until the operation is achieved */
AT25D_WaitReady(pAt25);
return 0;
}
/**
*\brief Erases the specified block of the serial firmware dataflash.
*
* \param pAt25 Pointer to an AT25 driver instance.
* \param address Address of the block to erase.
*
* \return 0 if successful; otherwise returns AT25_ERROR_PROTECTED if the
* device is protected or AT25_ERROR_BUSY if it is busy executing a command.
*/
unsigned char AT25D_EraseBlock(At25 *pAt25, unsigned int address)
{
unsigned char status;
unsigned char error;
assert(pAt25);
/* Check that the flash is ready and unprotected */
status = AT25D_ReadStatus(pAt25);
if ((status & AT25_STATUS_RDYBSY) != AT25_STATUS_RDYBSY_READY) {
TRACE_ERROR("AT25D_EraseBlock : Flash busy\n\r");
return AT25_ERROR_BUSY;
}
else if ((status & AT25_STATUS_SWP) != AT25_STATUS_SWP_PROTNONE) {
TRACE_ERROR("AT25D_EraseBlock : Flash protected\n\r");
return AT25_ERROR_PROTECTED;
}
/* Enable critical write operation */
AT25D_EnableWrite(pAt25);
/* Start the block erase command */
error = AT25_SendCommand(pAt25, AT25_BlockEraseCmd(pAt25), 4, 0, 0, address, 0, 0);
assert(!error);
/* Wait for transfer to finish */
AT25D_Wait(pAt25);
/* Poll the Serial flash status register until the operation is achieved */
AT25D_WaitReady(pAt25);
return 0;
}
/**
*\brief Erases the specified 64KB block of the serial firmware dataflash.
*
* \param pAt25 Pointer to an AT25 driver instance.
* \param address Address of the block to erase.
*
* \return 0 if successful; otherwise returns AT25_ERROR_PROTECTED if the
* device is protected or AT25_ERROR_BUSY if it is busy executing a command.
*/
unsigned char AT25D_Erase64KBlock(At25 *pAt25, unsigned int address)
{
unsigned char status;
unsigned char error;
assert(pAt25);
/* Check that the flash is ready and unprotected */
status = AT25D_ReadStatus(pAt25);
if ((status & AT25_STATUS_RDYBSY) != AT25_STATUS_RDYBSY_READY) {
TRACE_ERROR("AT25D_EraseBlock : Flash busy\n\r");
return AT25_ERROR_BUSY;
}
else if ((status & AT25_STATUS_SWP) != AT25_STATUS_SWP_PROTNONE) {
TRACE_ERROR("AT25D_EraseBlock : Flash protected\n\r");
return AT25_ERROR_PROTECTED;
}
/* Enable critical write operation */
AT25D_EnableWrite(pAt25);
/* Start the block erase command */
error = AT25_SendCommand(pAt25, AT25_BLOCK_ERASE_64K, 4, 0, 0, address, 0, 0);
assert(!error);
/* Wait for transfer to finish */
AT25D_Wait(pAt25);
/* Poll the Serial flash status register until the operation is achieved */
AT25D_WaitReady(pAt25);
return 0;
}
/**
* \brief Writes data at the specified address on the serial firmware dataflash. The
* page(s) to program must have been erased prior to writing. This function
* handles page boundary crossing automatically.
*
* \param pAt25 Pointer to an AT25 driver instance.
* \param pData Data buffer.
* \param size Number of bytes in buffer.
* \param address Write address.
*
* \return 0 if successful; otherwise, returns AT25_ERROR_PROGRAM is there has
* been an error during the data programming.
*/
unsigned char AT25D_Write(
At25 *pAt25,
unsigned char *pData,
unsigned int size,
unsigned int address)
{
unsigned int pageSize;
unsigned int writeSize;
unsigned char error;
unsigned char status;
unsigned int i = 0;
assert(pAt25);
assert(pData);
/* Retrieve device page size */
pageSize = AT25_PageSize(pAt25);
/* Program one page after the other */
while (size > 0) {
/* Compute number of bytes to program in page */
writeSize = min(size, pageSize - (address % pageSize));
/* Enable critical write operation */
AT25D_EnableWrite(pAt25);
/* Program page */
if (AT25_ManId(pAt25) == SST_SPI_FLASH) {
error = AT25_SendCommand(pAt25, AT25_SEQUENTIAL_PROGRAM_1, 4,
pData, 2, address, 0, 0);
assert(!error);
/* Wait for transfer to finish */
AT25D_Wait(pAt25);
/* Poll the Serial flash status register until the operation is achieved */
AT25D_WaitReady(pAt25);
for (i = 2; i < pageSize; i += 2) {
error = AT25_SendCommand(pAt25, AT25_SEQUENTIAL_PROGRAM_1, 1,
pData + i, 2, 0, 0, 0);
assert(!error);
/* Wait for transfer to finish */
AT25D_Wait(pAt25);
/* Poll the Serial flash status register until the operation is achieved */
AT25D_WaitReady(pAt25);
}
}
else {
error = AT25_SendCommand(pAt25, AT25_BYTE_PAGE_PROGRAM, 4,
pData, writeSize, address, 0, 0);
assert(!error);
/* Wait for transfer to finish */
AT25D_Wait(pAt25);
/* Poll the Serial flash status register until the operation is achieved */
AT25D_WaitReady(pAt25);
}
/* Make sure that write was without error */
status = AT25D_ReadStatus(pAt25);
if ((status & AT25_STATUS_EPE) == AT25_STATUS_EPE_ERROR) {
return AT25_ERROR_PROGRAM;
}
pData += writeSize;
size -= writeSize;
address += writeSize;
}
/* Enable critical write operation */
AT25D_DisableWrite(pAt25);
return 0;
}
/**
* \brief Reads data from the specified address on the serial flash.
*
* \param pAt25 Pointer to an AT25 driver instance.
* \param pData Data buffer.
* \param size Number of bytes to read.
* \param address Read address.
*
* \return 0 if successful; otherwise, fail.
*/
unsigned char AT25D_Read(
At25 *pAt25,
unsigned char *pData,
unsigned int size,
unsigned int address)
{
unsigned char error;
/* Start a read operation */
error = AT25_SendCommand(pAt25, AT25_READ_ARRAY_LF, 4, pData, size, address, 0, 0);
assert(!error);
/* Wait for transfer to finish */
AT25D_Wait(pAt25);
return error;
}