//*****************************************************************************
//
// eeprom.c - Driver for programming the on-chip EEPROM.
//
// Copyright (c) 2010-2012 Texas Instruments Incorporated.  All rights reserved.
// Software License Agreement
// 
//   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 following disclaimer.
// 
//   Redistributions in binary form must reproduce the above copyright
//   notice, this list of conditions and the following disclaimer in the
//   documentation and/or other materials provided with the  
//   distribution.
// 
//   Neither the name of Texas Instruments Incorporated nor the names of
//   its contributors may be used to endorse or promote products derived
//   from this software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS 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.
// 
// This is part of revision 9453 of the Stellaris Peripheral Driver Library.
//
//*****************************************************************************

#include "inc/hw_eeprom.h"
#include "inc/hw_flash.h"
#include "inc/hw_ints.h"
#include "inc/hw_sysctl.h"
#include "inc/hw_types.h"
#include "driverlib/debug.h"
#include "driverlib/flash.h"
#include "driverlib/interrupt.h"
#include "driverlib/sysctl.h"
#include "driverlib/eeprom.h"

//*****************************************************************************
//
//! \addtogroup eeprom_api
//! @{
//
//*****************************************************************************

//*****************************************************************************
//
// Useful macros to extract the number of EEPROM blocks available on the target
// device and the total EEPROM storage in bytes from the EESIZE register.
//
//*****************************************************************************
#define BLOCKS_FROM_EESIZE(x) (((x) & EEPROM_EESIZE_BLKCNT_M) >>               \
                              EEPROM_EESIZE_BLKCNT_S)
#define SIZE_FROM_EESIZE(x)   ((((x) & EEPROM_EESIZE_WORDCNT_M) >>             \
                              EEPROM_EESIZE_WORDCNT_S) * 4)

//*****************************************************************************
//
// Useful macro to extract the offset from a linear address.
//
//*****************************************************************************
#define OFFSET_FROM_ADDR(x) (((x) >> 2) & 0x0F)

//*****************************************************************************
//
// The key value required to initiate a mass erase.
//
//*****************************************************************************
#define EEPROM_MASS_ERASE_KEY ((unsigned long)0xE37B << EEPROM_EEDBGME_KEY_S)

//*****************************************************************************
//
// This function implements a workaround for a bug in Blizzard rev A silicon.
// It ensures that only the 1KB flash sector containing a given EEPROM address
// is erased if an erase/copy operation is required as a result of a following
// EEPROM write.
//
//*****************************************************************************
static void
EEPROMSetSectorMask(unsigned long ulAddress)
{
    unsigned long ulMask;

    //
    // Determine which page contains the passed EEPROM address.  The 2KB EEPROM
    // is implemented in 16KB of flash with each 1KB sector of flash holding
    // values for 32 consecutive EEPROM words (or 128 bytes).
    //
    ulMask = ~(1 << (ulAddress >> 7));

    SysCtlDelay(10);
    HWREG(0x400FD0FC) = 3;
    SysCtlDelay(10);
    HWREG(0x400AE2C0) = ulMask;
    SysCtlDelay(10);
    HWREG(0x400FD0FC) = 0;
    SysCtlDelay(10);
}

//*****************************************************************************
//
// Clear the FSM sector erase mask to ensure that any following main array
// flash erase operations operate as expected.
//
//*****************************************************************************
static void
EEPROMClearSectorMask(void)
{
    SysCtlDelay(10);
    HWREG(0x400FD0FC) = 3;
    SysCtlDelay(10);
    HWREG(0x400AE2C0) = 0;
    SysCtlDelay(10);
    HWREG(0x400FD0FC) = 0;
    SysCtlDelay(10);
}

//*****************************************************************************
//
// Block until the EEPROM peripheral is not busy.
//
//*****************************************************************************
static void
EEPROMWaitForDone(void)
{
    //
    // Is the EEPROM still busy?
    //
    while(HWREG(EEPROM_EEDONE) & EEPROM_EEDONE_WORKING)
    {
        //
        // Spin while EEPROM is busy.
        //
    }
}

//*****************************************************************************
//
//! Performs any necessary recovery in case of power failures during write.
//!
//! This function must be called after SysCtlPeripheralEnable() and before
//! the EEPROM is accessed to check for errors resulting from power failure
//! during a previous write operation.  The function detects these errors
//! and performs as much recovery as possible before returning information to
//! the caller on whether or not a previous data write was lost and must
//! be retried.
//!
//! In cases where \b EEPROM_INIT_RETRY is returned, the application is
//! responsible for determining which data write may have been lost and
//! rewriting this data.  If \b EEPROM_INIT_ERROR is returned, the EEPROM was
//! unable to recover its state.  This condition may or may not be resolved on
//! future resets depending upon the cause of the fault. For example, if the
//! supply voltage is unstable, retrying the operation once the voltage is
//! stabilized may clear the error.
//!
//! Failure to call this function after a reset may lead to permanent data loss
//! if the EEPROM is later written!
//!
//! \return Returns \b EEPROM_INIT_OK if no errors were detected,
//! \b EEPROM_INIT_RETRY if a previous write operation may have been
//! interrupted by a power or reset event or \b EEPROM_INIT_ERROR if the EEPROM
//! peripheral cannot currently recover from an interrupted write or erase
//! operation.
//
//*****************************************************************************
unsigned long
EEPROMInit(void)
{
    unsigned long ulStatus;

    //
    // Insert a small delay (6 cycles + call overhead) to guard against the
    // possibility that this function is called immediately after the EEPROM
    // peripheral is enabled.  Without this delay, there is a slight chance
    // that the first EEPROM register read will fault if you are using a
    // compiler with a ridiculously good optimizer!
    //
    SysCtlDelay(2);

    //
    // Make sure the EEPROM has finished its reset processing.
    //
    EEPROMWaitForDone();

    //
    // Read the EESUPP register to see if any errors have been reported.
    //
    ulStatus = HWREG(EEPROM_EESUPP);

    //
    // Did an error of some sort occur during a previous attempt to write to
    // the EEPROM?
    //
    if(ulStatus & (EEPROM_EESUPP_PRETRY | EEPROM_EESUPP_ERETRY))
    {
        //
        // Perform a second reset to allow the EEPROM a chance to correct
        // the errors.
        //
        SysCtlPeripheralReset(SYSCTL_PERIPH_EEPROM0);

        //
        // Wait for the EEPROM to complete it's reset processing once again.
        //
        SysCtlDelay(2);
        EEPROMWaitForDone();

        //
        // Read EESUPP once again to determine if the error conditions are
        // cleared.
        //
        ulStatus = HWREG(EEPROM_EESUPP);
        if(ulStatus & (EEPROM_EESUPP_PRETRY | EEPROM_EESUPP_ERETRY))
        {
            return(EEPROM_INIT_ERROR);
        }
        else
        {
            return(EEPROM_INIT_RETRY);
        }
    }

    //
    // The EEPROM does not indicate that any error occurred.
    //
    return(EEPROM_INIT_OK);
}

//*****************************************************************************
//
//! Determines the size of the EEPROM.
//!
//! This function returns the size of the EEPROM in bytes.
//!
//! \return Returns the total number of bytes in the EEPROM.
//
//*****************************************************************************
unsigned long
EEPROMSizeGet(void)
{
    //
    // Return the size of the EEPROM in bytes.
    //
    return(SIZE_FROM_EESIZE(HWREG(EEPROM_EESIZE)));
}

//*****************************************************************************
//
//! Determines the number of blocks in the EEPROM.
//!
//! This function may be called to determine the number of blocks in the
//! EEPROM.  Each block is the same size and the number of bytes of storage
//! contained in a block may be determined by dividing the size of the device,
//! obtained via a call to the EEPROMSizeGet() function, by the number of
//! blocks returned by this function.
//!
//! \return Returns the total number of bytes in the device EEPROM.
//
//*****************************************************************************
unsigned long
EEPROMBlockCountGet(void)
{
    //
    // Extract the number of blocks and return it to the caller.
    //
    return(BLOCKS_FROM_EESIZE(HWREG(EEPROM_EESIZE)));
}

//*****************************************************************************
//
//! Reads data from the EEPROM.
//!
//! \param pulData is a pointer to storage for the data read from the EEPROM.
//! This pointer must point to at least \e ulCount bytes of available memory.
//! \param ulAddress is the byte address within the EEPROM from which data is
//! to be read.  This value must be a multiple of 4.
//! \param ulCount is the number of bytes of data to read from the EEPROM.
//! This value must be a multiple of 4.
//!
//! This function may be called to read a number of words of data from a
//! word-aligned address within the EEPROM.  Data read is copied into the
//! buffer pointed to by the \e pulData parameter.
//!
//! \return None.
//
//*****************************************************************************
void
EEPROMRead(unsigned long *pulData, unsigned long ulAddress,
           unsigned long ulCount)
{
    //
    // Check parameters in a debug build.
    //
    ASSERT(pulData);
    ASSERT(ulAddress < SIZE_FROM_EESIZE(HWREG(EEPROM_EESIZE)));
    ASSERT((ulAddress + ulCount) <= SIZE_FROM_EESIZE(HWREG(EEPROM_EESIZE)));
    ASSERT((ulAddress & 3) == 0);
    ASSERT((ulCount & 3) == 0);

    //
    // Set the block and offset appropriately to read the first word.
    //
    HWREG(EEPROM_EEBLOCK) = EEPROMBlockFromAddr(ulAddress);
    HWREG(EEPROM_EEOFFSET) = OFFSET_FROM_ADDR(ulAddress);

    //
    // Convert the byte count to a word count.
    //
    ulCount /= 4;

    //
    // Read each word in turn.
    //
    while(ulCount)
    {
        //
        // Read the next word through the autoincrementing register.
        //
        *pulData = HWREG(EEPROM_EERDWRINC);

        //
        // Move on to the next word.
        //
        pulData++;
        ulCount--;

        //
        // Do we need to move to the next block?  This is the case if the
        // offset register has just wrapped back to 0.
        //
        if(HWREG(EEPROM_EEOFFSET) == 0)
        {
            HWREG(EEPROM_EEBLOCK) += 1;
        }
    }
}

//*****************************************************************************
//
//! Writes data to the EEPROM.
//!
//! \param pulData points to the first word of data to write to the EEPROM.
//! \param ulAddress defines the byte address within the EEPROM that the data
//! is to be written to.  This value must be a multiple of 4.
//! \param ulCount defines the number of bytes of data that is to be written.
//! This value must be a multiple of 4.
//!
//! This function may be called to write data into the EEPROM at a given
//! word-aligned address.  The call is synchronous and returns only after
//! all data has been written or an error occurs.
//!
//! \return Returns 0 on success or non-zero values on failure.  Failure codes
//! are logical OR combinations of \b EEPROM_RC_INVPL, \b EEPROM_RC_WRBUSY,
//! \b EEPROM_RC_NOPERM, \b EEPROM_RC_WKCOPY, \b EEPROM_RC_WKERASE, and
//! \b EEPROM_RC_WORKING.
//
//*****************************************************************************
unsigned long
EEPROMProgram(unsigned long *pulData, unsigned long ulAddress,
              unsigned long ulCount)
{
    unsigned long ulStatus;

    //
    // Check parameters in a debug build.
    //
    ASSERT(pulData);
    ASSERT(ulAddress < SIZE_FROM_EESIZE(HWREG(EEPROM_EESIZE)));
    ASSERT((ulAddress + ulCount) <= SIZE_FROM_EESIZE(HWREG(EEPROM_EESIZE)));
    ASSERT((ulAddress & 3) == 0);
    ASSERT((ulCount & 3) == 0);

    //
    // Make sure the EEPROM is idle before we start.
    //
    do
    {
        //
        // Read the status.
        //
        ulStatus = HWREG(EEPROM_EEDONE);
    }
    while(ulStatus & EEPROM_EEDONE_WORKING);

    //
    // Set the block and offset appropriately to program the first word.
    //
    HWREG(EEPROM_EEBLOCK) = EEPROMBlockFromAddr(ulAddress);
    HWREG(EEPROM_EEOFFSET) = OFFSET_FROM_ADDR(ulAddress);

    //
    // Convert the byte count to a word count.
    //
    ulCount /= 4;

    //
    // Write each word in turn.
    //
    while(ulCount)
    {
        //
        // This is a workaround for a silicon problem on Blizzard rev A.  We
        // need to do this before every word write to ensure that we don't
        // have problems in multi-word writes that span multiple flash sectors.
        //
        if(CLASS_IS_BLIZZARD && REVISION_IS_A0)
        {
            EEPROMSetSectorMask(ulAddress);
            ulAddress += 4;
        }

        //
        // Write the next word through the autoincrementing register.
        //
        HWREG(EEPROM_EERDWRINC) = *pulData;

        //
        // Wait for the write to complete.
        //
        do
        {
            //
            // Read the status.
            //
            ulStatus = HWREG(EEPROM_EEDONE);
        }
        while(ulStatus & EEPROM_EEDONE_WORKING);

        //
        // Make sure we completed the write without errors.  Note that we
        // must check this per-word because write permission can be set per
        // block resulting in only a section of the write not being performed.
        //
        if(ulStatus & (EEPROM_EEDONE_NOPERM | EEPROM_EEDONE_INVPL))
        {
            //
            // An error was reported that would prevent the values from
            // being written correctly.
            //
            if(CLASS_IS_BLIZZARD && REVISION_IS_A0)
            {
                EEPROMClearSectorMask();
            }
            return(ulStatus);
        }

        //
        // Move on to the next word.
        //
        pulData++;
        ulCount--;

        //
        // Do we need to move to the next block?  This is the case if the
        // offset register has just wrapped back to 0.
        //
        if(HWREG(EEPROM_EEOFFSET) == 0)
        {
            HWREG(EEPROM_EEBLOCK) += 1;
        }
    }

    //
    // Clear the sector protection bits to prevent possible problems when
    // programming the main flash array later.
    //
    if(CLASS_IS_BLIZZARD && REVISION_IS_A0)
    {
        EEPROMClearSectorMask();
    }

    //
    // Return the current status to the caller.
    //
    return(HWREG(EEPROM_EEDONE));
}

//*****************************************************************************
//
//! Writes a word to the EEPROM.
//!
//! \param ulData is the word to write to the EEPROM.
//! \param ulAddress defines the byte address within the EEPROM to which the
//! data is to be written.  This value must be a multiple of 4.
//!
//! This function is intended to allow EEPROM programming under interrupt
//! control. It may be called to start the process of writing a single word of
//! data into the EEPROM at a given word-aligned address.  The call is
//! asynchronous and returna immediately without waiting for the write to
//! complete.  Completion of the operation is signaled by means of an
//! interrupt from the EEPROM module.  The EEPROM peripheral shares a single
//! interrupt vector with the flash memory subsystem, \e INT_FLASH.
//!
//! \return Returns status and error information in the form of a logical OR
//! combinations of \b EEPROM_RC_INVPL, \b EEPROM_RC_WRBUSY,
//! \b EEPROM_RC_NOPERM, \b EEPROM_RC_WKCOPY, \b EEPROM_RC_WKERASE and
//! \b EEPROM_RC_WORKING.  Flags \b EEPROM_RC_WKCOPY, \b EEPROM_RC_WKERASE, and
//! \b EEPROM_RC_WORKING are expected in normal operation and do not indicate
//! an error.
//
//*****************************************************************************
unsigned long
EEPROMProgramNonBlocking(unsigned long ulData, unsigned long ulAddress)
{
    //
    // Check parameters in a debug build.
    //
    ASSERT(ulAddress < SIZE_FROM_EESIZE(HWREG(EEPROM_EESIZE)));
    ASSERT((ulAddress & 3) == 0);

    //
    // This is a workaround for a silicon problem on Blizzard rev A.
    //
    if(CLASS_IS_BLIZZARD && REVISION_IS_A0)
    {
        EEPROMSetSectorMask(ulAddress);
    }

    //
    // Set the block and offset appropriately to program the desired word.
    //
    HWREG(EEPROM_EEBLOCK) = EEPROMBlockFromAddr(ulAddress);
    HWREG(EEPROM_EEOFFSET) = OFFSET_FROM_ADDR(ulAddress);

    //
    // Write the new word using the auto-incrementing register just in case
    // the caller wants to write follow-on words using direct register access
    //
    HWREG(EEPROM_EERDWRINC) = ulData;

    //
    // Return the current status to the caller.
    //
    return(HWREG(EEPROM_EEDONE));
}

//*****************************************************************************
//
//! Erases the EEPROM and returns it to the factory default condition.
//!
//! This function completely erases the EEPROM and removes any and
//! all access protection on its blocks, leaving the device in the factory
//! default condition.  After this operation, all EEPROM words contain the
//! value 0xFFFFFFFF and all blocks are accessible for both read and write
//! operations in all CPU modes.  No passwords are active.
//!
//! The function is synchronous and does not return until the erase operation
//! has completed.
//!
//! \return Returns 0 on success or non-zero values on failure.  Failure codes
//! are logical OR combinations of \b EEPROM_RC_INVPL, \b EEPROM_RC_WRBUSY,
//! \b EEPROM_RC_NOPERM, \b EEPROM_RC_WKCOPY, \b EEPROM_RC_WKERASE, and
//! \b EEPROM_RC_WORKING.
//
//*****************************************************************************
unsigned long
EEPROMMassErase(void)
{
    //
    // This is a workaround for a silicon problem on Blizzard rev A.
    //
    if(CLASS_IS_BLIZZARD && REVISION_IS_A0)
    {
        EEPROMClearSectorMask();
    }

    //
    // Start the mass erase processing
    //
    HWREG(EEPROM_EEDBGME) = EEPROM_MASS_ERASE_KEY | EEPROM_EEDBGME_ME;

    //
    // Wait for completion.
    //
    EEPROMWaitForDone();

    //
    // Reset the peripheral.  This is required so that all protection
    // mechanisms and passwords are reset now that the EEPROM data has been
    // scrubbed.
    //
    SysCtlPeripheralReset(SYSCTL_PERIPH_EEPROM0);

    //
    // Wait for completion again.
    //
    SysCtlDelay(2);
    EEPROMWaitForDone();

    //
    // Pass any error codes back to the caller.
    //
    return(HWREG(EEPROM_EEDONE));
}

//*****************************************************************************
//
//! Returns the current protection level for an EEPROM block.
//!
//! \param ulBlock is the block number for which the protection level is to be
//! queried.
//!
//! This function returns the current protection settings for a given
//! EEPROM block.  If block 0 is currently locked, it must be unlocked prior
//! to calling this function to query the protection setting for other blocks.
//!
//! \return Returns one of \b EEPROM_PROT_RW_LRO_URW, \b EEPROM_PROT_NA_LNA_URW
//! or \b EEPROM_PROT_RO_LNA_URO optionally OR-ed with
//! \b EEPROM_PROT_SUPERVISOR_ONLY.
//
//*****************************************************************************
unsigned long
EEPROMBlockProtectGet(unsigned long ulBlock)
{
    //
    // Parameter validity check.
    //
    ASSERT(ulBlock < BLOCKS_FROM_EESIZE(HWREG(EEPROM_EESIZE)));

    //
    // Set the current block.
    //
    HWREG(EEPROM_EEBLOCK) = ulBlock;

    //
    // Return the protection flags for this block.
    //
    return(HWREG(EEPROM_EEPROT));
}

//*****************************************************************************
//
//! Set the current protection options for an EEPROM block.
//!
//! \param ulBlock is the block number for which the protection options are to
//! be set.
//! \param ulProtect consists of one of the values \b EEPROM_PROT_RW_LRO_URW,
//! \b EEPROM_PROT_NA_LNA_URW or \b EEPROM_PROT_RO_LNA_URO optionally ORed with
//! \b EEPROM_PROT_SUPERVISOR_ONLY.
//!
//! This function sets the protection settings for a given EEPROM block
//! assuming no protection settings have previously been written.  Note that
//! protection settings applied to block 0 have special meaning and control
//! access to the EEPROM peripheral as a whole.  Protection settings applied to
//! blocks numbered 1 and above are layered above any protection set on block 0
//! such that the effective protection on each block is the logical OR of the
//! protection flags set for block 0 and for the target block.  This protocol
//! allows global protection options to be set for the whole device via block
//! 0 and more restrictive protection settings to be set on a block-by-block
//! basis.
//!
//! The protection flags indicate access permissions as follow:
//!
//! \b EEPROM_PROT_SUPERVISOR_ONLY restricts access to the block to threads
//! running in supervisor mode.  If clear, both user and supervisor threads
//! can access the block.
//!
//! \b EEPROM_PROT_RW_LRO_URW provides read/write access to the block if no
//! password is set or if a password is set and the block is unlocked.  If the
//! block is locked, only read access is permitted.
//!
//! \b EEPROM_PROT_NA_LNA_URW provides neither read nor write access unless
//! a password is set and the block is unlocked.  If the block is unlocked,
//! both read and write access are permitted.
//!
//! \b EEPROM_PROT_RO_LNA_URO provides read access to the block if no password
//! is set or if a password is set and the block is unlocked.  If the block is
//! password protected and locked, neither read nor write access is permitted.
//!
//! \return Returns a logical OR combination of \b EEPROM_RC_INVPL,
//! \b EEPROM_RC_WRBUSY, \b EEPROM_RC_NOPERM, \b EEPROM_RC_WKCOPY,
//! \b EEPROM_RC_WKERASE, and \b EEPROM_RC_WORKING to indicate status and error
//! conditions.
//
//*****************************************************************************
unsigned long
EEPROMBlockProtectSet(unsigned long ulBlock, unsigned long ulProtect)
{
    //
    // Parameter validity check.
    //
    ASSERT(ulBlock < BLOCKS_FROM_EESIZE(HWREG(EEPROM_EESIZE)));

    //
    // Set the current block.
    //
    HWREG(EEPROM_EEBLOCK) = ulBlock;

    //
    // Set the protection options for this block.
    //
    HWREG(EEPROM_EEPROT) = ulProtect;

    //
    // Wait for the write to complete.
    //
    while(HWREG(EEPROM_EEDONE) & EEPROM_EEDONE_WORKING)
    {
        //
        // Still working.
        //
    }

    //
    // Pass any error codes back to the caller.
    //
    return(HWREG(EEPROM_EEDONE));
}

//*****************************************************************************
//
//! Sets the password used to protect an EEPROM block.
//!
//! \param ulBlock is the EEPROM block number for which the password is to be
//! set.
//! \param pulPassword points to an array of unsigned long values comprising
//! the password to set.  Each element may be any 32-bit value other than
//! 0xFFFFFFFF. This array must contain the number of elements given by the
//! \b ulCount parameter.
//! \param ulCount provides the number of unsigned longs in the \b ulPassword.
//! Valid values are 1, 2 and 3.
//!
//! This function allows the password used to unlock an EEPROM block to be
//! set.  Valid passwords may be either 32, 64 or 96 bits comprising words
//! with any value other than 0xFFFFFFFF. The password may only be set once.
//! Any further attempts to set the password result in an error.  Once the
//! password is set, the block remains unlocked until EEPROMBlockLock() is
//! called for that block or block 0, or a reset occurs.
//!
//! If a password is set on block 0, this affects locking of the peripheral as
//! a whole. When block 0 is locked, all other EEPROM blocks are inaccessible
//! until block 0 is unlocked.  Once block 0 is unlocked, other blocks
//! become accessible according to any passwords set on those blocks and the
//! protection set for that block via a call to EEPROMBlockProtectSet().
//!
//! \return Returns a logical OR combination of \b EEPROM_RC_INVPL,
//! \b EEPROM_RC_WRBUSY, \b EEPROM_RC_NOPERM, \b EEPROM_RC_WKCOPY,
//! \b EEPROM_RC_WKERASE, and \b EEPROM_RC_WORKING to indicate status and error
//! conditions.
//
//*****************************************************************************
unsigned long
EEPROMBlockPasswordSet(unsigned long ulBlock, unsigned long *pulPassword,
                       unsigned long ulCount)
{
    unsigned long ulReg;

    //
    // Check parameters in a debug build.
    //
    ASSERT(pulPassword);
    ASSERT(ulBlock < BLOCKS_FROM_EESIZE(HWREG(EEPROM_EESIZE)));
    ASSERT(ulCount <= 3);

    //
    // Set the block number whose password we are about to write.
    //
    HWREG(EEPROM_EEBLOCK) = ulBlock;

    //
    // Start with the first password word.
    //
    ulReg = EEPROM_EEPASS0;

    //
    // Write the password.
    //
    while(ulCount)
    {
        //
        // Start the process of writing the password.
        //
        HWREG(ulReg) = *pulPassword;

        //
        // Update values in preparation for writing the next word.
        //
        pulPassword++;
        ulReg += 4;
        ulCount--;

        //
        // Wait for the last word write to complete or an error to be reported.
        //
        while(HWREG(EEPROM_EEDONE) & EEPROM_EEDONE_WORKING)
        {
            //
            // Still working.
            //
        }
    }

    //
    // Return the final write status.
    //
    return(HWREG(EEPROM_EEDONE));
}

//*****************************************************************************
//
//! Locks a password-protected EEPROM block.
//!
//! \param ulBlock is the EEPROM block number which is to be locked.
//!
//! This function locks an EEPROM block that has previously been protected by
//! writing a password.  Access to the block once it is locked is determined
//! by the protection settings applied via a previous call to the
//! EEPROMBlockProtectSet() function.  If no password has previously been set
//! for the block, this function has no effect.
//!
//! Locking block 0 has the effect of making all other blocks in the EEPROM
//! inaccessible.
//!
//! \return Returns the lock state for the block on exit, 1 if unlocked (as
//! would be the case if no password was set) or 0 if locked.
//!
//*****************************************************************************
unsigned long
EEPROMBlockLock(unsigned long ulBlock)
{
    //
    // Check parameters in a debug build.
    //
    ASSERT(ulBlock < BLOCKS_FROM_EESIZE(HWREG(EEPROM_EESIZE)));

    //
    // Select the block we are going to lock.
    //
    HWREG(EEPROM_EEBLOCK) = ulBlock;

    //
    // Lock the block.
    //
    HWREG(EEPROM_EEUNLOCK) = 0xFFFFFFFF;

    //
    // Return the current lock state.
    //
    return(HWREG(EEPROM_EEUNLOCK));
}

//*****************************************************************************
//
//! Unlocks a password-protected EEPROM block.
//!
//! \param ulBlock is the EEPROM block number which is to be unlocked.
//! \param pulPassword points to an array of unsigned long values containing
//! the password for the blockt.  Each element must match the password
//! originally set via a call to EEPROMBlockPasswordSet().
//! \param ulCount provides the number of unsigned longs in the \b pulPassword
//! array and must match the value originally passed to
//! EEPROMBlockPasswordSet().  Valid values are 1, 2 and 3.
//!
//! This function unlocks an EEPROM block that has previously been protected by
//! writing a password.  Access to the block once it is unlocked is determined
//! by the protection settings applied via a previous call to the
//! EEPROMBlockProtectSet() function.
//!
//! To successfully unlock an EEPROM block, the password provided must match
//! the password provided on the original call to EEPROMBlockPasswordSet().  If
//! an incorrect password is provided, the block remains locked.
//!
//! Unlocking block 0 has the effect of making all other blocks in the device
//! accessible according to their own access protection settings.  When block
//! 0 is locked, all other EEPROM blocks are inaccessible.
//!
//! \return Returns the lock state for the block on exit, 1 if unlocked or 0 if
//! locked.
//!
//*****************************************************************************
unsigned long
EEPROMBlockUnlock(unsigned long ulBlock, unsigned long *pulPassword,
                  unsigned long ulCount)
{
    //
    // Check parameters in a debug build.
    //
    ASSERT(pulPassword);
    ASSERT(ulBlock < BLOCKS_FROM_EESIZE(HWREG(EEPROM_EESIZE)));
    ASSERT(ulCount <= 3);

    //
    // Set the block that we are trying to unlock.
    //
    HWREG(EEPROM_EEBLOCK) = ulBlock;

    //
    // Write the unlock register with 0xFFFFFFFF to reset the unlock
    // sequence just in case a short password was previously used to try to
    // unlock the block.
    //
    HWREG(EEPROM_EEUNLOCK) = 0xFFFFFFFF;

    //
    // We need to write the password words in the opposite order when unlocking
    // compared to locking so start at the end of the array.
    //
    pulPassword += (ulCount - 1);

    //
    // Write the supplied password to unlock the block.
    //
    while(ulCount)
    {
        HWREG(EEPROM_EEUNLOCK) = *pulPassword--;
        ulCount--;
    }

    //
    // Let the caller know if their password worked.
    //
    return(HWREG(EEPROM_EEUNLOCK));
}

//*****************************************************************************
//
//! Hides an EEPROM block until the next reset.
//!
//! \param ulBlock is the EEPROM block number which is to be hidden.
//!
//! This function hides an EEPROM block other than block 0.  Once hidden, a
//! block is completely inaccessible until the next reset.  This mechanism
//! allows initialization code to have access to data which is to be hidden
//! from the rest of the application.  Unlike applications using passwords, an
//! application making using of block hiding need not contain any embedded
//! passwords which could be found through disassembly.
//!
//! \return None.
//!
//*****************************************************************************
void
EEPROMBlockHide(unsigned long ulBlock)
{
    //
    // Check parameters in a debug build.
    //
    ASSERT(!ulBlock);
    ASSERT(ulBlock < BLOCKS_FROM_EESIZE(HWREG(EEPROM_EESIZE)));

    //
    // Hide the requested block.
    //
    HWREG(EEPROM_EEHIDE) = (1 << ulBlock);
}

//*****************************************************************************
//
//! Enables the EEPROM interrupt.
//!
//! \param ulIntFlags indicates which EEPROM interrupt source to enable.  This
//! must be \b EEPROM_INT_PROGRAM currently.
//!
//! This function enables the EEPROM interrupt.  When enabled, an interrupt
//! is generated when any EEPROM write or erase operation completes.  The
//! EEPROM peripheral shares a single interrupt vector with the flash memory
//! subsystem, \b INT_FLASH.  This function is provided as a convenience but
//! the EEPROM interrupt can also be enabled using a call to FlashIntEnable()
//! passing FLASH_INT_EEPROM in the \b ulIntFlags parameter.
//!
//! \return None.
//!
//*****************************************************************************
void
EEPROMIntEnable(unsigned long ulIntFlags)
{
    //
    // Look for valid interrupt sources.
    //
    ASSERT(ulIntFlags == EEPROM_INT_PROGRAM);

    //
    // Enable interrupts from the EEPROM module.
    //
    HWREG(EEPROM_EEINT) |= EEPROM_EEINT_INT;

    //
    // Enable the EEPROM interrupt in the flash controller module.
    //
    HWREG(FLASH_FCIM) |= FLASH_FCRIS_ERIS;
}

//*****************************************************************************
//
//! Disables the EEPROM interrupt.
//!
//! \param ulIntFlags indicates which EEPROM interrupt source to disable.  This
//! must be \b EEPROM_INT_PROGRAM currently.
//!
//! This function disables the EEPROM interrupt and prevents calls to the
//! interrupt vector when any EEPROM write or erase operation completes.  The
//! EEPROM peripheral shares a single interrupt vector with the flash memory
//! subsystem, \e INT_FLASH.    This function is provided as a convenience but
//! the EEPROM interrupt can also be disabled using a call to FlashIntDisable()
//! passing FLASH_INT_EEPROM in the \b ulIntFlags parameter.
//!
//! \return None.
//!
//*****************************************************************************
void
EEPROMIntDisable(unsigned long ulIntFlags)
{
    //
    // Look for valid interrupt sources.
    //
    ASSERT(ulIntFlags == EEPROM_INT_PROGRAM);

    //
    // Disable the EEPROM interrupt in the flash controller module.
    //
    HWREG(FLASH_FCIM) &= ~FLASH_FCIM_EMASK;

    //
    // Disable interrupts from the EEPROM module.
    //
    HWREG(EEPROM_EEINT) &= ~EEPROM_EEINT_INT;
}

//*****************************************************************************
//
//! Reports the state of the EEPROM interrupt.
//!
//! \param bMasked determines whether the masked or unmasked state of the
//! interrupt is to be returned. If bMasked is \e true, the masked state is
//! returned, otherwise the unmasked state is returned.
//!
//! This function allows an application to query the state of the EEPROM
//! interrupt.  If active, the interrupt may be cleared by calling
//! EEPROMIntClear().
//!
//! \return Returns \b EEPROM_INT_PROGRAM if an interrupt is being signaled or
//! 0 otherwise.
//
//*****************************************************************************
unsigned long
EEPROMIntStatus(tBoolean bMasked)
{
    if(bMasked)
    {
        //
        // If asked for the masked interrupt status, we check to see if the
        // relevant interrupt is pending in the flash controller then return
        // the appropriate EEPROM flag if it is.
        //
        return((HWREG(FLASH_FCMISC) & FLASH_FCMISC_EMISC) ?
                EEPROM_INT_PROGRAM : 0);
    }
    else
    {
        //
        // If asked for the unmasked interrupt status, infer that an interrupt
        // is pending if the WORKING bit of the EEDONE register is clear.  The
        // actual interrupt fires on the high to low transition of this bit
        // but we don't have access to an unmasked interrupt status for the
        // EEPROM because it's handled via the flash controller so we have to
        // make do with this instead.
        //
        return((HWREG(EEPROM_EEDONE) & EEPROM_EEDONE_WORKING) ?
               0 : EEPROM_INT_PROGRAM);
    }
}

//*****************************************************************************
//
//! Clears the EEPROM interrupt.
//!
//! \param ulIntFlags indicates which interrupt sources to clear.  Currently,
//! the only valid value is \b EEPROM_INT_PROGRAM.
//!
//! This function allows an application to clear the EEPROM interrupt.
//!
//! \note Because there is a write buffer in the Cortex-M processor, it may
//! take several clock cycles before the interrupt source is actually cleared.
//! Therefore, it is recommended that the interrupt source be cleared early in
//! the interrupt handler (as opposed to the very last action) to avoid
//! returning from the interrupt handler before the interrupt source is
//! actually cleared.  Failure to do so may result in the interrupt handler
//! being immediately reentered (because the interrupt controller still sees
//! the interrupt source asserted).
//!
//! \return None.
//!
//*****************************************************************************
void
EEPROMIntClear(unsigned long ulIntFlags)
{
    //
    // Clear the flash interrupt.
    //
    HWREG(FLASH_FCMISC) = FLASH_FCMISC_EMISC;

    //
    // Clear the sector protection bits to prevent possible problems when
    // programming the main flash array later.
    //
    if(CLASS_IS_BLIZZARD && REVISION_IS_A0)
    {
        EEPROMClearSectorMask();
    }
}

//*****************************************************************************
//
//! Returns status on the last EEPROM program or erase operation.
//!
//! This function returns the current status of the last program or erase
//! operation performed by the EEPROM.  It is intended to provide error
//! information to applications programming or setting EEPROM protection
//! options under interrupt control.
//!
//! \return Returns 0 if the last program or erase operation completed without
//! any errors.  If an operation is ongoing or an error occurred, the return
//! value is a logical OR combination of \b EEPROM_RC_INVPL,
//! \b EEPROM_RC_WRBUSY, \b EEPROM_RC_NOPERM, \b EEPROM_RC_WKCOPY,
//! \b EEPROM_RC_WKERASE, and \b EEPROM_RC_WORKING.
//!
//*****************************************************************************
unsigned long
EEPROMStatusGet(void)
{
    return(HWREG(EEPROM_EEDONE));
}

//*****************************************************************************
//
// Close the Doxygen group.
//! @}
//
//*****************************************************************************
