/* ----------------------------------------------------------------------------
 *         ATMEL Microcontroller Software Support
 * ----------------------------------------------------------------------------
 * Copyright (c) 2008, 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.
 * ----------------------------------------------------------------------------
 */

//------------------------------------------------------------------------------
//      Includes
//------------------------------------------------------------------------------

#include "memories.h"
#include <assert.h>

//------------------------------------------------------------------------------
//      Internal Functions
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
//! \brief  Reads a specified amount of data from a flash memory
//! \param  media    Pointer to a Media instance
//! \param  address  Address of the data to read
//! \param  data     Pointer to the buffer in which to store the retrieved
//!                   data
//! \param  length   Length of the buffer
//! \param  callback Optional pointer to a callback function to invoke when
//!                   the operation is finished
//! \param  argument Optional pointer to an argument for the callback
//! \return Operation result code
//------------------------------------------------------------------------------
static unsigned char FLA_Read(Media      *media,
                               uint32_t address,
                               void         *data,
                               uint32_t length,
                               MediaCallback   callback,
                               void         *argument)
{
    unsigned char *source = (unsigned char *) address;
    unsigned char *dest = (unsigned char *) data;

    // Check that the media is ready
    if (media->state != MED_STATE_READY) {

        TRACE_INFO("Media busy\n\r");
        return MED_STATUS_BUSY;
    }

    // Check that the data to read is not too big
    if ((length + address) > media->size) {

        TRACE_WARNING("FLA_Read: Data too big\n\r");
        return MED_STATUS_ERROR;
    }

    // Enter Busy state
    media->state = MED_STATE_BUSY;

    // Read data
    while (length > 0) {

        *dest = *source;

        dest++;
        source++;
        length--;
    }

    // Leave the Busy state
    media->state = MED_STATE_READY;

    // Invoke callback
    if (callback != 0) {

        callback(argument, MED_STATUS_SUCCESS, 0, 0);
    }

    return MED_STATUS_SUCCESS;
}

//------------------------------------------------------------------------------
//! \brief  Writes data on a flash media
//! \param  media    Pointer to a Media instance
//! \param  address  Address at which to write
//! \param  data     Pointer to the data to write
//! \param  length   Size of the data buffer
//! \param  callback Optional pointer to a callback function to invoke when
//!                   the write operation terminates
//! \param  argument Optional argument for the callback function
//! \return Operation result code
//! \see    Media
//! \see    Callback_f
//------------------------------------------------------------------------------
static unsigned char FLA_Write(Media      *media,
                                uint32_t address,
                                void         *data,
                                uint32_t length,
                                MediaCallback   callback,
                                void         *argument)
{
    unsigned char error;

    // Check that the media if ready
    if (media->state != MED_STATE_READY) {

        TRACE_WARNING("FLA_Write: Media is busy\n\r");
        return MED_STATUS_BUSY;
    }

    // Check that address is dword-aligned
    if (address%4 != 0)
    {
        TRACE_DEBUG("address = 0x%X\n\r", address);
        TRACE_WARNING("FLA_Write: Address must be dword-aligned\n\r");
        return MED_STATUS_ERROR;
    }

    // Check that length is a multiple of 4
    if (length%4 != 0) {

        TRACE_WARNING("FLA_Write: Data length must be a multiple of 4 bytes\n\r");
        return MED_STATUS_ERROR;
    }

    // Check that the data to write is not too big
    if ((length + address) > media->size) {

        TRACE_WARNING("FLA_Write: Data too big\n\r");
        return MED_STATUS_ERROR;
    }

    // Put the media in Busy state
    media->state = MED_STATE_BUSY;

    // Initialize the transfer descriptor
    media->transfer.data = data;
    media->transfer.address = address;
    media->transfer.length = length;
    media->transfer.callback = callback;
    media->transfer.argument = argument;

    // Start the write operation
    error = FLASHD_Write( address, data, length);
    assert( !error ) ; /* "-F- Error when trying to write page (0x%02X)\n\r", error */
#ifdef NDEBUG
    error = error; /* stop warnings */
#endif

    // End of transfer
    // Put the media in Ready state
    media->state = MED_STATE_READY;

    // Invoke the callback if it exists
    if (media->transfer.callback != 0) {

        media->transfer.callback(media->transfer.argument, 0, 0, 0);
    }

    return MED_STATUS_SUCCESS;
}

//------------------------------------------------------------------------------
//! \brief Lock all the regions in the given address range. The actual unlock
//!        range is reported through two output parameters.
//! \param media Pointer to Media instance.
//! \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.
//------------------------------------------------------------------------------
static unsigned char FLA_Lock( Media* media, uint32_t start, uint32_t end, uint32_t *pActualStart, uint32_t *pActualEnd )
{
    /* Stop warning */
    media = media;

    if ( FLASHD_Lock( start, end, pActualStart, pActualEnd ) )
    {
        return MED_STATUS_ERROR ;
    }

    return MED_STATUS_SUCCESS ;
}

//------------------------------------------------------------------------------
//! \brief Unlock all the regions in the given address range. The actual unlock
//!        range is reported through two output parameters.
//! \param media Pointer to Media instance.
//! \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.
//------------------------------------------------------------------------------
static unsigned char FLA_Unlock( Media* media, uint32_t start, uint32_t end, uint32_t *pActualStart, uint32_t *pActualEnd )
{
    /* Stop warning */
    media = media;

    if ( FLASHD_Unlock( start, end, pActualStart, pActualEnd ) )
    {
        return MED_STATUS_ERROR ;
    }

    return MED_STATUS_SUCCESS ;
}

//------------------------------------------------------------------------------
//      Exported Functions
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//! \brief  Initializes a Media instance and the associated physical interface
//! \param  media Pointer to the Media instance to initialize
//! \param  efc   Pointer to AT91S_EFC interface.
//! \see    Media
//------------------------------------------------------------------------------
void FLA_Initialize( Media *media, Efc *efc )
{
    TRACE_INFO("Flash init\n\r");

    // Initialize media fields
    media->write = FLA_Write;
    media->read = FLA_Read;
    media->lock = FLA_Lock;
    media->unlock = FLA_Unlock;
    media->flush = 0;
    media->handler = 0;

    media->blockSize = 1;
    media->baseAddress = 0; // Address based on whole memory space
    media->size = IFLASH_SIZE;
    media->interface = efc;

    media->mappedRD  = 0;
    media->mappedWR  = 0;
    media->protected = 0;
    media->removable = 0;
    media->state = MED_STATE_READY;

    media->transfer.data = 0;
    media->transfer.address = 0;
    media->transfer.length = 0;
    media->transfer.callback = 0;
    media->transfer.argument = 0;

    // Initialize low-level interface
    // Configure Flash Mode register
    efc->EEFC_FMR |= (BOARD_MCK / 666666) << 16;
}


