/* ----------------------------------------------------------------------------
 *         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 at25d_module AT25 driver
 * \ingroup lib_spiflash
 * 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 <libspiflash.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))
    {
    }
}

/**
 * \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);
    AT25D_EnableWrite(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 Enter 4-bytes address mode
 *
 * \para pAt25  Pointer to an AT25 driver instance.
 */
void AT25D_Enter4ByteMode(At25 *pAt25)
{
    unsigned char error;

    assert(pAt25);
    AT25D_EnableWrite(pAt25);
    /* Issue a write enable command */
    error = AT25_SendCommand(pAt25, AT25_ENTER_4ADDR_MODE, 1, 0, 0, 0, 0, 0);
    assert(!error);

    /* Wait for transfer to finish */
    AT25D_Wait(pAt25);
    pAt25->fourbytemode = 1;
}

/**
 * \brief Exit 4-bytes address mode
 *
 * \para pAt25  Pointer to an AT25 driver instance.
 */
void AT25D_Exit4ByteMode(At25 *pAt25)
{
    unsigned char error;

    assert(pAt25);

    /* Issue a write enable command */
    error = AT25_SendCommand(pAt25, AT25_EXIT_4ADDR_MODE, 1, 0, 0, 0, 0, 0);
    assert(!error);

    /* Wait for transfer to finish */
    AT25D_Wait(pAt25);
    pAt25->fourbytemode = 0;
}
/**
 * \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;
}

