//*****************************************************************************
//
// flash_pb.c - Flash parameter block functions.
//
// Copyright (c) 2008-2012 Texas Instruments Incorporated.  All rights reserved.
// Software License Agreement
// 
// Texas Instruments (TI) is supplying this software for use solely and
// exclusively on TI's microcontroller products. The software is owned by
// TI and/or its suppliers, and is protected under applicable copyright
// laws. You may not combine this software with "viral" open-source
// software in order to form a larger program.
// 
// THIS SOFTWARE IS PROVIDED "AS IS" AND WITH ALL FAULTS.
// NO WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT
// NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. TI SHALL NOT, UNDER ANY
// CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
// DAMAGES, FOR ANY REASON WHATSOEVER.
// 
// This is part of revision 9453 of the Stellaris Firmware Development Package.
//
//*****************************************************************************

#include "inc/hw_flash.h"
#include "inc/hw_types.h"
#include "driverlib/debug.h"
#include "driverlib/flash.h"
#include "driverlib/sysctl.h"
#include "utils/flash_pb.h"

//*****************************************************************************
//
//! \addtogroup flash_pb_api
//! @{
//
//*****************************************************************************

//*****************************************************************************
//
// The address of the beginning of the flash used for storing parameter blocks;
// this must be the start of an erase block in the flash.
//
//*****************************************************************************
static unsigned char *g_pucFlashPBStart;

//*****************************************************************************
//
// The address of the end of the flash used for storing parameter blocks; this
// must be the start of an erase block in the flash, or the first location
// after the end of the flash array if the last erase block is used for storing
// parameters.
//
//*****************************************************************************
static unsigned char *g_pucFlashPBEnd;

//*****************************************************************************
//
// The size of the parameter block when stored in flash; this must be a power
// of two less than or equal to 1024.
//
//*****************************************************************************
static unsigned long g_ulFlashPBSize;

//*****************************************************************************
//
// The address of the most recent parameter block in flash.
//
//*****************************************************************************
static unsigned char *g_pucFlashPBCurrent;

//*****************************************************************************
//
//! Determines if the parameter block at the given address is valid.
//!
//! \param pucOffset is the address of the parameter block to check.
//!
//! This function will compute the checksum of a parameter block in flash to
//! determine if it is valid.
//!
//! \return Returns one if the parameter block is valid and zero if it is not.
//
//*****************************************************************************
static unsigned long
FlashPBIsValid(unsigned char *pucOffset)
{
    unsigned long ulIdx, ulSum;

    //
    // Check the arguments.
    //
    ASSERT(pucOffset != (void *)0);

    //
    // Loop through the bytes in the block, computing the checksum.
    //
    for(ulIdx = 0, ulSum = 0; ulIdx < g_ulFlashPBSize; ulIdx++)
    {
        ulSum += pucOffset[ulIdx];
    }

    //
    // The checksum should be zero, so return a failure if it is not.
    //
    if((ulSum & 255) != 0)
    {
        return(0);
    }

    //
    // If the sum is equal to the size * 255, then the block is all ones and
    // should not be considered valid.
    //
    if((g_ulFlashPBSize * 255) == ulSum)
    {
        return(0);
    }

    //
    // This is a valid parameter block.
    //
    return(1);
}

//*****************************************************************************
//
//! Gets the address of the most recent parameter block.
//!
//! This function returns the address of the most recent parameter block that
//! is stored in flash.
//!
//! \return Returns the address of the most recent parameter block, or NULL if
//! there are no valid parameter blocks in flash.
//
//*****************************************************************************
unsigned char *
FlashPBGet(void)
{
    //
    // See if there is a valid parameter block.
    //
    if(g_pucFlashPBCurrent)
    {
        //
        // Return the address of the most recent parameter block.
        //
        return(g_pucFlashPBCurrent);
    }

    //
    // There are no valid parameter blocks in flash, so return NULL.
    //
    return(0);
}

//*****************************************************************************
//
//! Writes a new parameter block to flash.
//!
//! \param pucBuffer is the address of the parameter block to be written to
//! flash.
//!
//! This function will write a parameter block to flash.  Saving the new
//! parameter blocks involves three steps:
//!
//! - Setting the sequence number such that it is one greater than the sequence
//!   number of the latest parameter block in flash.
//! - Computing the checksum of the parameter block.
//! - Writing the parameter block into the storage immediately following the
//!   latest parameter block in flash; if that storage is at the start of an
//!   erase block, that block is erased first.
//!
//! By this process, there is always a valid parameter block in flash.  If
//! power is lost while writing a new parameter block, the checksum will not
//! match and the partially written parameter block will be ignored.  This is
//! what makes this fault-tolerant.
//!
//! Another benefit of this scheme is that it provides wear leveling on the
//! flash.  Since multiple parameter blocks fit into each erase block of flash,
//! and multiple erase blocks are used for parameter block storage, it takes
//! quite a few parameter block saves before flash is re-written.
//!
//! \return None.
//
//*****************************************************************************
void
FlashPBSave(unsigned char *pucBuffer)
{
    unsigned char *pucNew;
    unsigned long ulIdx, ulSum;

    //
    // Check the arguments.
    //
    ASSERT(pucBuffer != (void *)0);

    //
    // See if there is a valid parameter block in flash.
    //
    if(g_pucFlashPBCurrent)
    {
        //
        // Set the sequence number to one greater than the most recent
        // parameter block.
        //
        pucBuffer[0] = g_pucFlashPBCurrent[0] + 1;

        //
        // Try to write the new parameter block immediately after the most
        // recent parameter block.
        //
        pucNew = g_pucFlashPBCurrent + g_ulFlashPBSize;
        if(pucNew == g_pucFlashPBEnd)
        {
            pucNew = g_pucFlashPBStart;
        }
    }
    else
    {
        //
        // There is not a valid parameter block in flash, so set the sequence
        // number of this parameter block to zero.
        //
        pucBuffer[0] = 0;

        //
        // Try to write the new parameter block at the beginning of the flash
        // space for parameter blocks.
        //
        pucNew = g_pucFlashPBStart;
    }

    //
    // Compute the checksum of the parameter block to be written.
    //
    for(ulIdx = 0, ulSum = 0; ulIdx < g_ulFlashPBSize; ulIdx++)
    {
        ulSum -= pucBuffer[ulIdx];
    }

    //
    // Store the checksum into the parameter block.
    //
    pucBuffer[1] += ulSum;

    //
    // Look for a location to store this parameter block.  This infinite loop
    // will be explicitly broken out of when a valid location is found.
    //
    while(1)
    {
        //
        // See if this location is at the start of an erase block.
        //
        if(((unsigned long)pucNew & 1023) == 0)
        {
            //
            // Erase this block of the flash.  This does not assume that the
            // erase succeeded in case this block of the flash has become bad
            // through too much use.  Given the extremely low frequency that
            // the parameter blocks are written, this will likely never fail.
            // But, that assumption is not made in order to be safe.
            //
            FlashErase((unsigned long)pucNew);
        }

        //
        // Loop through this portion of flash to see if is all ones (i.e. it
        // is an erased portion of flash).
        //
        for(ulIdx = 0; ulIdx < g_ulFlashPBSize; ulIdx++)
        {
            if(pucNew[ulIdx] != 0xff)
            {
                break;
            }
        }

        //
        // If all bytes in this portion of flash are ones, then break out of
        // the loop since this is a good location for storing the parameter
        // block.
        //
        if(ulIdx == g_ulFlashPBSize)
        {
            break;
        }

        //
        // Increment to the next parameter block location.
        //
        pucNew += g_ulFlashPBSize;
        if(pucNew == g_pucFlashPBEnd)
        {
            pucNew = g_pucFlashPBStart;
        }

        //
        // If every possible location has been checked and none are valid, then
        // it will not be possible to write this parameter block.  Simply
        // return without writing it.
        //
        if((g_pucFlashPBCurrent && (pucNew == g_pucFlashPBCurrent)) ||
           (!g_pucFlashPBCurrent && (pucNew == g_pucFlashPBStart)))
        {
            return;
        }
    }

    //
    // Write this parameter block to flash.
    //
    FlashProgram((unsigned long *)pucBuffer, (unsigned long)pucNew,
                 g_ulFlashPBSize);

    //
    // Compare the parameter block data to the data that should now be in
    // flash.  Return if any of the data does not compare, leaving the previous
    // parameter block in flash as the most recent (since the current parameter
    // block failed to properly program).
    //
    for(ulIdx = 0; ulIdx < g_ulFlashPBSize; ulIdx++)
    {
        if(pucNew[ulIdx] != pucBuffer[ulIdx])
        {
            return;
        }
    }

    //
    // The new parameter block becomes the most recent parameter block.
    //
    g_pucFlashPBCurrent = pucNew;
}

//*****************************************************************************
//
//! Initializes the flash parameter block.
//!
//! \param ulStart is the address of the flash memory to be used for storing
//! flash parameter blocks; this must be the start of an erase block in the
//! flash.
//! \param ulEnd is the address of the end of flash memory to be used for
//! storing flash parameter blocks; this must be the start of an erase block in
//! the flash (the first block that is NOT part of the flash memory to be
//! used), or the address of the first word after the flash array if the last
//! block of flash is to be used.
//! \param ulSize is the size of the parameter block when stored in flash;
//! this must be a power of two less than or equal to the flash erase block
//! size (typically 1024).
//!
//! This function initializes a fault-tolerant, persistent storage mechanism
//! for a parameter block for an application.  The last several erase blocks
//! of flash (as specified by \e ulStart and \e ulEnd are used for the
//! storage; more than one erase block is required in order to be
//! fault-tolerant.
//!
//! A parameter block is an array of bytes that contain the persistent
//! parameters for the application.  The only special requirement for the
//! parameter block is that the first byte is a sequence number (explained
//! in FlashPBSave()) and the second byte is a checksum used to validate the
//! correctness of the data (the checksum byte is the byte such that the sum of
//! all bytes in the parameter block is zero).
//!
//! The portion of flash for parameter block storage is split into N
//! equal-sized regions, where each region is the size of a parameter block
//! (\e ulSize).  Each region is scanned to find the most recent valid
//! parameter block.  The region that has a valid checksum and has the highest
//! sequence number (with special consideration given to wrapping back to zero)
//! is considered to be the current parameter block.
//!
//! In order to make this efficient and effective, three conditions must be
//! met.  The first is \e ulStart and \e ulEnd must be specified such that at
//! least two erase blocks of flash are dedicated to parameter block storage.
//! If not, fault tolerance can not be guaranteed since an erase of a single
//! block will leave a window where there are no valid parameter blocks in
//! flash.  The second condition is that the size (\e ulSize) of the parameter
//! block must be an integral divisor of the size of an erase block of flash.
//! If not, a parameter block will end up spanning between two erase blocks of
//! flash, making it more difficult to manage.  The final condition is that the
//! size of the flash dedicated to parameter blocks (\e ulEnd - \e ulStart)
//! divided by the parameter block size (\e ulSize) must be less than or equal
//! to 128.  If not, it will not be possible in all cases to determine which
//! parameter block is the most recent (specifically when dealing with the
//! sequence number wrapping back to zero).
//!
//! When the microcontroller is initially programmed, the flash blocks used for
//! parameter block storage are left in an erased state.
//!
//! This function must be called before any other flash parameter block
//! functions are called.
//!
//! \return None.
//
//*****************************************************************************
void
FlashPBInit(unsigned long ulStart, unsigned long ulEnd, unsigned long ulSize)
{
    unsigned char *pucOffset, *pucCurrent;
    unsigned char ucOne, ucTwo;

    //
    // Check the arguments.
    //
    ASSERT((ulStart % FLASH_ERASE_SIZE) == 0);
    ASSERT((ulEnd % FLASH_ERASE_SIZE) == 0);
    ASSERT((FLASH_ERASE_SIZE % ulSize) == 0);

    //
    // Set the number of clocks per microsecond to enable the flash controller
    // to properly program the flash.
    //
    FlashUsecSet(SysCtlClockGet() / 1000000);

    //
    // Save the characteristics of the flash memory to be used for storing
    // parameter blocks.
    //
    g_pucFlashPBStart = (unsigned char *)ulStart;
    g_pucFlashPBEnd = (unsigned char *)ulEnd;
    g_ulFlashPBSize = ulSize;

    //
    // Loop through the portion of flash memory used for storing parameter
    // blocks.
    //
    for(pucOffset = g_pucFlashPBStart, pucCurrent = 0;
        pucOffset < g_pucFlashPBEnd; pucOffset += g_ulFlashPBSize)
    {
        //
        // See if this is a valid parameter block (i.e. the checksum is
        // correct).
        //
        if(FlashPBIsValid(pucOffset))
        {
            //
            // See if a valid parameter block has been previously found.
            //
            if(pucCurrent != 0)
            {
                //
                // Get the sequence numbers for the current and new parameter
                // blocks.
                //
                ucOne = pucCurrent[0];
                ucTwo = pucOffset[0];

                //
                // See if the sequence number for the new parameter block is
                // greater than the current block.  The comparison isn't
                // straightforward since the one byte sequence number will wrap
                // after 256 parameter blocks.
                //
                if(((ucOne > ucTwo) && ((ucOne - ucTwo) < 128)) ||
                   ((ucTwo > ucOne) && ((ucTwo - ucOne) > 128)))
                {
                    //
                    // The new parameter block is older than the current
                    // parameter block, so skip the new parameter block and
                    // keep searching.
                    //
                    continue;
                }
            }

            //
            // The new parameter block is more recent than the current one, so
            // make it the new current parameter block.
            //
            pucCurrent = pucOffset;
        }
    }

    //
    // Save the address of the most recent parameter block found.  If no valid
    // parameter blocks were found, this will be a NULL pointer.
    //
    g_pucFlashPBCurrent = pucCurrent;
}

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