/* ----------------------------------------------------------------------------
 *         ATMEL Microcontroller Software Support
 * ----------------------------------------------------------------------------
 * Copyright (c) 2009, 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.
 * ----------------------------------------------------------------------------
 */
/**
  *  \file
  *
  *  Implementation of norflash driver for AMD type.
  */

/*----------------------------------------------------------------------------
 *        Headers
 *----------------------------------------------------------------------------*/
#include "memories.h"

#include <string.h>

/*----------------------------------------------------------------------------
 *        Local definitions
 *----------------------------------------------------------------------------*/

/** Command for vendor command set CMD_SET_AMD. */
#define AMD_CMD_IDOUT         0x00F0
#define AMD_CMD_CFI           0x0098
#define AMD_CMD_IDIN          0x0090
#define AMD_CMD_UNLOCK_1      0x00AA
#define AMD_CMD_UNLOCK_2      0x0055
#define AMD_CMD_ERASE_SETUP   0x0080
#define AMD_CMD_ERASE_RESUME  0x0030
#define AMD_CMD_ERASE_CHIP    0x0010
#define AMD_CMD_ERASE_SECTOR  0x0030
#define AMD_CMD_PROGRAM       0x00A0
#define AMD_CMD_UNLOCK_BYPASS 0x0020
#define AMD_CMD_SECTOR_UNLOCK 0x0070

/** Command offset for vendor command set CMD_SET_AMD */
#define AMD_OFFSET_UNLOCK_1   0x05555
#define AMD_OFFSET_UNLOCK_2   0x0AAAA
/** Query command address. */
#define FLASH_ADDRESS_CFI     0x0055

/** AMD norflash device Identifier infomation address offset. */
#define AMD_MANU_ID           0x00
#define AMD_DEVIDE_ID         0x01

/** Data polling mask for vendor command set CMD_SET_AMD */
#define AMD_POLLING_DQ7       0x80
#define AMD_POLLING_DQ6       0x60
#define AMD_POLLING_DQ5       0x20
#define AMD_POLLING_DQ3       0x08

/*----------------------------------------------------------------------------
 *        Local functions
 *----------------------------------------------------------------------------*/

/**
 * \brief It implements a RESET command.
 *
 * \param pNorFlashInfo  Pointer to an NorFlashInfo instance.
 * \param address  Dummy data for AMD.
 */
static void amd_Reset( NorFlashInfo *pNorFlashInfo, uint32_t address )
{
    uint8_t busWidth ;
    
    /* Stop warning */
    address = address;

    busWidth = NorFlash_GetDataBusWidth( pNorFlashInfo ) ;

    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_1), AMD_CMD_UNLOCK_1 ) ;

    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_2), AMD_CMD_UNLOCK_2 ) ;

    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_1), AMD_CMD_IDOUT ) ;
}


/**
 * \brief Read specified manufactory id or device id.
 *
 * \param pNorFlashInfo  Pointer to an NorFlashInfo instance.
 * \param index_ 0: manufactorid 1: device id.
 */
static uint32_t amd_ReadIdentification( NorFlashInfo *pNorFlashInfo, uint8_t index_ )
{
    uint32_t id;
    uint8_t busWidth;
    uint32_t address;

    busWidth = NorFlash_GetDataBusWidth( pNorFlashInfo ) ;

    /* The amd_Read identification command sequence is initiated by first */
    /* writing two unlock cycles. */
    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_1), AMD_CMD_UNLOCK_1 ) ;
    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_2), AMD_CMD_UNLOCK_2 ) ;

    /* Followed by a third write cycle that contains the autoselect command. */
    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_1), AMD_CMD_IDIN);

    /* The device then enters the autoselect mode. It may read at any address any */
    /* number of times without initiating another autoselect command sequence. */
    address = NorFlash_GetByteAddressInChip(pNorFlashInfo, index_);
    ReadRawData( busWidth, address, (uint8_t*)&id ) ;

    /* The system must write the exit command to return to the read mode */
    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_1), AMD_CMD_UNLOCK_1 ) ;
    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_2), AMD_CMD_UNLOCK_2 ) ;
    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_1), AMD_CMD_IDOUT ) ;

    return id;
}

/**
 * \brief It implement a program word command. Returns 0 if the operation was
 * successful; otherwise returns an error code.
 *
 * \param pNorFlashInfo  Pointer to an NorFlashInfo instance.
 * \param address Start address offset to be wrote.
 * \param data word to be written.
 */
static uint8_t amd_Program( NorFlashInfo *pNorFlashInfo, uint32_t address, uint32_t data )
{
    uint32_t pollingData;
    uint32_t busAddress;
    uint8_t done = 0;
    uint8_t busWidth;

    busWidth = NorFlash_GetDataBusWidth(pNorFlashInfo);
    /* The program command sequence is initiated by writing two unlock write cycles. */
    WriteCommand(busWidth,
                 NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_1),
                 AMD_CMD_UNLOCK_1);
    WriteCommand(busWidth,
                 NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_2),
                 AMD_CMD_UNLOCK_2);
    /* Followed by the program set-up command. */
    WriteCommand(busWidth,
                 NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_1),
                 AMD_CMD_PROGRAM);

    /* The program address and data are written next, */
    /* which in turn initiate the Embedded Program algorithm. */
    busAddress = NorFlash_GetAddressInChip(pNorFlashInfo, address);
    WriteRawData(busWidth, busAddress, (uint8_t*)&data);

    /* Data polling */
    do
    {
        ReadRawData(busWidth, busAddress, (uint8_t *)&pollingData);
        /* Check if the chip program algorithm is completed. */
        if ((pollingData & AMD_POLLING_DQ7) == (data & AMD_POLLING_DQ7))
        {
            /* Program operation successful. Device in read mode. */
            done = 1;
        }
        else
        {
            /* check if chip Program algrithm exceeded timing limits */
            if (pollingData & AMD_POLLING_DQ5 )
            {
                /* I/O should be rechecked. */
                ReadRawData(busWidth, busAddress, (uint8_t *)&pollingData);

                if ((pollingData & AMD_POLLING_DQ7) == (data & AMD_POLLING_DQ7))
                {
                    /* Program operation successful. Device in read mode. */
                    done = 1;
                }
                else
                {
                    /* Program operation not successful, write reset command. */
                    amd_Reset(pNorFlashInfo, 0);

                    return NorCommon_ERROR_CANNOTWRITE;
                }
            }
        }
    } while (!done);
    return 0;
}

/*----------------------------------------------------------------------------
 *        Exported functions
 *----------------------------------------------------------------------------*/

/**
 * \brief It implements a RESET command.
 *
 * \param pNorFlashInfo  Pointer to an NorFlashInfo instance.
 * \param address  Dummy data for AMD.
 */
void AMD_Reset( NorFlashInfo *pNorFlashInfo, uint32_t address )
{
    amd_Reset( pNorFlashInfo, address ) ;
}

/**
 * \brief The Read Device Identifier command instructs the device to output
 * manufacturer code.
 *
 * \param pNorFlashInfo  Pointer to an NorFlashInfo instance.
 */
uint32_t AMD_ReadManufactoryId( NorFlashInfo *pNorFlashInfo )
{
    return amd_ReadIdentification(pNorFlashInfo, AMD_MANU_ID);
}

/**
 * \brief The Read Device Identifier command instructs the device to output device id.
 *
 * \param pNorFlashInfo  Pointer to an NorFlashInfo instance.
 */
uint32_t AMD_ReadDeviceID( NorFlashInfo *pNorFlashInfo )
{
    return amd_ReadIdentification(pNorFlashInfo, AMD_DEVIDE_ID);
}

/**
 * \brief Erases the specified block of the device. Returns 0 if the operation was
 * successful; otherwise returns an error code.
 *
 * \param pNorFlashInfo  Pointer to an NorFlashInfo instance.
 * \param address Address offset to be erase.
 */
uint8_t AMD_EraseSector( NorFlashInfo *pNorFlashInfo, uint32_t address )
{
    uint32_t pollingData;
    uint32_t busAddress;
    uint8_t busWidth;
    uint8_t done = 0;

    busWidth = NorFlash_GetDataBusWidth(pNorFlashInfo);

    /*Programming is a six-bus-cycle operation. */
    /* The erase command sequence is initiated by writing two unlock write cycles. */
    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_1), AMD_CMD_UNLOCK_1);
    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_2), AMD_CMD_UNLOCK_2);

    /* Followed by the program set-up command. */
    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_1), AMD_CMD_ERASE_SETUP);

    /* Two additional unlock cycles are written. */
    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_1), AMD_CMD_UNLOCK_1);
    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_2), AMD_CMD_UNLOCK_2);

    /* Followed by the address of the sector to be erased, and the sector erase command. */
    busAddress = NorFlash_GetAddressInChip(pNorFlashInfo,address);
    WriteCommand(busWidth, busAddress, AMD_CMD_ERASE_SECTOR);

    /* Data polling */
    do
    {
        ReadRawData(busWidth, busAddress, (uint8_t *)&pollingData);
        /* Check if the chip erase algorithm is completed. */
        if ((pollingData & AMD_POLLING_DQ7) == AMD_POLLING_DQ7 )
        {
            /* Erase operation successful. Device in read mode. */
            done = 1;
        }
        else
        {
            /* check if sector earse algrithm exceeded timing limits */
            if (pollingData & AMD_POLLING_DQ5 )
            {
                /* I/O should be rechecked. */
                ReadRawData(busWidth, busAddress, (uint8_t *)&pollingData);
                if ((pollingData & AMD_POLLING_DQ7) == AMD_POLLING_DQ7 )
                {
                    /* Erase operation successful. Device in read mode. */
                    done = 1;
                }
                else
                {
                    /* Erase operation not successful, write reset command. */
                    amd_Reset(pNorFlashInfo, 0);
                    return NorCommon_ERROR_CANNOTERASE;
                }
            }
        }
    } while (!done);

    return 0;
}

/**
 * \brief Erases all the block of the device. Returns 0 if the operation was successful;
 * otherwise returns an error code.
 *
 * \param pNorFlashInfo  Pointer to an NorFlashInfo instance.
 */
uint8_t AMD_EraseChip( NorFlashInfo *pNorFlashInfo )
{
    uint32_t pollingData;
    uint8_t busWidth;
    uint32_t address;
    uint8_t done = 0;

    busWidth = NorFlash_GetDataBusWidth(pNorFlashInfo);

    /*Programming is a six-bus-cycle operation. */
    /* The erase command sequence is initiated by writing two unlock write cycles. */
    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_1), AMD_CMD_UNLOCK_1 ) ;
    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_2), AMD_CMD_UNLOCK_2 ) ;

    /* Followed by the program set-up command. */
    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_1), AMD_CMD_ERASE_SETUP);

    /* Two additional unlock cycles are written. */
    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_1), AMD_CMD_UNLOCK_1);
    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_2), AMD_CMD_UNLOCK_2 ) ;

    /* Then followed by the chip erase command. */
    WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_1), AMD_CMD_ERASE_CHIP ) ;

    address = NorFlash_GetByteAddressInChip(pNorFlashInfo, 0);

    /* Data polling */
    do
    {
        ReadRawData(busWidth , address, (uint8_t*)&pollingData);
        /* Check if the chip erase algorithm is completed. */
        if ((pollingData & AMD_POLLING_DQ7) == AMD_POLLING_DQ7 )
        {
            /* Erase operation successful. Device in read mode. */
            done = 1;
        }
        else
        {
            /* When the time-out period is complete, DQ3 switches from a ¡°0¡± to a ¡°1.¡± */
            if (pollingData & AMD_POLLING_DQ3 )
            {
                return NorCommon_ERROR_CANNOTERASE;
            }

            /* check if chip earse algrithm exceeded timing limits */
            if (pollingData & AMD_POLLING_DQ5 )
            {

                /* I/O should be rechecked. */
                ReadRawData(busWidth , address, (uint8_t*)&pollingData);
                if ((pollingData & AMD_POLLING_DQ7) == AMD_POLLING_DQ7 )
                {
                    /* Erase operation successful. Device in read mode. */
                    done = 1;
                }
                else
                {
                    /* Erase operation not successful, write reset command. */
                    amd_Reset(pNorFlashInfo, 0);
                    return NorCommon_ERROR_CANNOTERASE;
                }
            }
        }
    } while (!done);

    return 0;
}

/**
 * \brief Unlock the specified block of the device. 
 *
 * \param pNorFlashInfo  Pointer to an NorFlashInfo instance.
 * \param address Address offset to be unlock.
 */
void AMD_UnlockSector( NorFlashInfo *pNorFlashInfo, uint32_t address )
{
	uint32_t busAddress;
	uint8_t busWidth;
	
	busWidth = NorFlash_GetDataBusWidth(pNorFlashInfo);
	busAddress = NorFlash_GetAddressInChip(pNorFlashInfo,address);
       WriteCommand( busWidth, NorFlash_GetByteAddressInChip(pNorFlashInfo, AMD_OFFSET_UNLOCK_1), AMD_CMD_UNLOCK_1);
       WriteCommand( busWidth,busAddress, AMD_CMD_SECTOR_UNLOCK); 
}

/**
 * \brief Sends data to the NorFlashInfo chip from the provided buffer.
 *
 * \param pNorFlashInfo  Pointer to an NorFlashInfo instance.
 * \param address Start address offset to be wrote.
 * \param buffer Buffer where the data is stored.
 * \param size Number of bytes that will be written.
 */
uint8_t AMD_Write_Data( NorFlashInfo *pNorFlashInfo, uint32_t address, uint8_t *buffer, uint32_t size )
{
    uint32_t i;
    uint8_t busWidth;
    busWidth = pNorFlashInfo->deviceChipWidth;

    if (busWidth == FLASH_CHIP_WIDTH_8BITS )
    {
        for(i=0; i < size; i++)
        {
            if(amd_Program(pNorFlashInfo, address, buffer[i]))
            {
                return NorCommon_ERROR_CANNOTWRITE;
            }
            address ++;
        }
    }
    else
    {
        if( busWidth == FLASH_CHIP_WIDTH_16BITS )
        {
            uint16_t *buffer16 = (uint16_t *) buffer;

            size = (size + 1) >> 1;

            for(i=0; i < size; i++)
            {
                if(amd_Program(pNorFlashInfo, address, buffer16[i]))
                {
                    return NorCommon_ERROR_CANNOTWRITE;
                }
                address+= 2;
            }
        }
        else
        {
            if(busWidth == FLASH_CHIP_WIDTH_32BITS )
            {
                uint32_t *buffer32 = (uint32_t *) buffer;

                size = (size + 3) >> 2;

                for(i=0; i < size; i++)
                {
                    if(amd_Program(pNorFlashInfo, address, buffer32[i]))
                    {
                        return NorCommon_ERROR_CANNOTWRITE;
                    }
                    address+= 4;
                }
            }
        }
    }

    return 0;
}

const NorFlashOperations amdOperations =
{
   AMD_Reset,
   AMD_Write_Data,
   AMD_ReadManufactoryId,
   AMD_ReadDeviceID,
   AMD_EraseChip,
   AMD_EraseSector,
   AMD_UnlockSector
} ;

