/* ----------------------------------------------------------------------------
 *         SAM Software Package License
 * ----------------------------------------------------------------------------
 * Copyright (c) 2013, 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 S25FL1 driver
 * \ingroup lib_spiflash
 * The S25FL1 serial dataflash driver is based on the corresponding S25FL1 SPI
 * driver.
 * A S25FL1 instance has to be initialized using the Dataflash level function
 * S25FL1D_Configure(). S25FL1 Dataflash can be automatically detected using
 * the S25FL1D_FindDevice() function. Then S25FL1 dataflash operations such as
 * read, write and erase DF can be launched using S25FL1D_SendCommand function
 * with corresponding S25FL1 command set.
 *
 * \section Usage
 * <ul>
 * <li> Reads a serial flash device ID using S25FL1D_ReadJedecId().</li>
 * <li> Reads data from the S25fl1 at the specified address using S25FL1D_Read().
 * </li>
 * <li> Writes data on the S25fl1 at the specified address using S25FL1D_Write().
 * </li>
 * <li> Erases all chip using S25FL1D_EraseBlock().</li>
 * <li> Erases a specified block using S25FL1D_EraseBlock().</li>
 * <li> Poll until the S25fl1 has completed of corresponding operations using
 * S25FL1D_IsBusy().</li>
 * <li> Retrieves and returns the S25fl1 current using S25FL1D_ReadStatus().</li>
 * </ul>
 *
 * Related files :\n
 * \ref at25d.c\n
 * \ref at25d.h.\n
 */
/*@{*/
/*@}*/

/**
 * \file
 *
 * Implementation for the S25FL1 Serialflash driver.
 *
 */

/*----------------------------------------------------------------------------
 *        Headers
 *----------------------------------------------------------------------------*/
#include <board.h>
#include <assert.h>
#include "stdlib.h"
#include "string.h"


/*----------------------------------------------------------------------------
 *        Variable
 *----------------------------------------------------------------------------*/
static QspiInstFrame_t *pDev, *pMem;

static QspiDma_t qspiDma;
static sXdmad qspiDmaInst;


/*----------------------------------------------------------------------------
 *        Definition
 *----------------------------------------------------------------------------*/
#define READ_DEV        0
#define WRITE_DEV       1 

#define PAGE_SIZE       256
/**
 * \brief XDMA handler.
 */
void XDMAC_Handler(void)
{
	XDMAD_Handler(&qspiDmaInst);
}

/**
 * \brief Start S25FL1D Send command with/without data write/read.
 * \param Instr Instruct 
 * \param pTxData point to Tx buffer address
 * \param pRxData point to Rx buffer address
 * \param ReadWrite Command/Write/Read access
 * \param Size buffer size in byte
 * \returns 0
 */
static uint8_t S25FL1D_SendCommand(uint8_t Instr, uint32_t *pTxData,
		uint32_t *pRxData, Access_t ReadWrite, uint32_t size)
{
	qspiDma.Qspid.qspiCommand.Instruction = Instr;
	
	if(qspiDma.Qspid.qspiMode) {
		pDev->InstFrame.bm.bInstEn = 1;
		qspiDma.Qspid.pQspiFrame =  pDev;
		qspiDma.Qspid.qspiBuffer.pDataTx = pTxData;
		qspiDma.Qspid.qspiBuffer.pDataRx = pRxData;

		// to prevent unaligned access
		if( (size % sizeof(uint32_t)) && size > 1) {
			size += (sizeof(uint32_t) - (size % sizeof(uint32_t)));
		}

	if(ReadWrite == CmdAccess) {
		pDev->InstFrame.bm.bXfrType 
			= (QSPI_IFR_TFRTYP_TRSFR_READ >> QSPI_IFR_TFRTYP_Pos);
		pDev->InstFrame.bm.bDataEn = 0;
		
		QSPI_SendCommand(&qspiDma.Qspid, 0);
		
	} else if (ReadWrite == WriteAccess) {
		pDev->InstFrame.bm.bDataEn = 1;
		pDev->InstFrame.bm.bXfrType 
			= (QSPI_IFR_TFRTYP_TRSFR_WRITE >> QSPI_IFR_TFRTYP_Pos);
		qspiDma.Qspid.qspiBuffer.TxDataSize  = size;
		QSPI_SendCommandWithData(&qspiDma.Qspid, 0);
		
	} else {
			pDev->InstFrame.bm.bXfrType 
				= (QSPI_IFR_TFRTYP_TRSFR_READ >> QSPI_IFR_TFRTYP_Pos);
			pDev->InstFrame.bm.bDataEn = 1;
			qspiDma.Qspid.qspiBuffer.RxDataSize  = size;
			QSPI_ReadCommand(&qspiDma.Qspid, 0);
		}
	} else {
		if((ReadWrite == CmdAccess) || (ReadWrite == WriteAccess)) {
			qspiDma.Qspid.qspiBuffer.pDataTx = malloc(size+1);
			qspiDma.Qspid.qspiBuffer.pDataTx[0] 
				= qspiDma.Qspid.qspiCommand.Instruction;
			if(size) {
				memcpy(&qspiDma.Qspid.qspiBuffer.pDataTx[1], pTxData, size);
			}
			qspiDma.Qspid.qspiBuffer.TxDataSize  = size+1;

			QSPI_MultiWriteSPI(&qspiDma.Qspid, 
				(uint16_t const*)qspiDma.Qspid.qspiBuffer.pDataTx, 
				qspiDma.Qspid.qspiBuffer.TxDataSize); 

			free(qspiDma.Qspid.qspiBuffer.pDataTx);
		} else if (ReadWrite == ReadAccess) { 
			qspiDma.Qspid.qspiBuffer.pDataRx = pRxData;
			QSPI_SingleWriteSPI(&qspiDma.Qspid,
					(uint16_t const*)&qspiDma.Qspid.qspiCommand.Instruction);
			QSPI_MultiReadSPI(&qspiDma.Qspid, 
					(uint16_t *)qspiDma.Qspid.qspiBuffer.pDataRx, size);
		} else {
			TRACE_ERROR("%s Wrong access parameter \n\r", __FUNCTION__);
		} 
		QSPI_EndTransfer(qspiDma.Qspid.pQspiHw);
	}
	return 0;
}


/**
 * \brief Start S25FL1D Memory access with/without data write/read.
 * \param Instr Instruct 
 * \param pTxData point to Tx buffer address
 * \param pRxData point to Rx buffer address
 * \param ReadWrite Command/Write/Read access
 * \param Size buffer size in byte
 * \returns 0
 */
static uint8_t S25FL1D_MemoryAccess(uint8_t Instr, uint32_t Addr, 
				uint32_t *pTxData, uint32_t *pRxData, Access_t ReadWrite, 
				uint32_t size, uint8_t Secure)
{
	uint8_t SpiBuffer[4];
	qspiDma.Qspid.qspiCommand.Instruction = Instr;
	
	if(qspiDma.Qspid.qspiMode) {
		qspiDma.Qspid.qspiBuffer.pDataTx = pTxData;
		qspiDma.Qspid.qspiBuffer.pDataRx = pRxData;
		pMem->Addr=Addr;
		pMem->InstFrame.bm.bInstEn = 1;
		pMem->InstFrame.bm.bDataEn = 1;
		pMem->InstFrame.bm.bAddrEn = 1;
		qspiDma.Qspid.pQspiFrame =  pMem;
		if (ReadWrite == WriteAccess) {
		pMem->InstFrame.bm.bXfrType 
			= (QSPI_IFR_TFRTYP_TRSFR_WRITE_MEMORY >> QSPI_IFR_TFRTYP_Pos);
		qspiDma.Qspid.qspiBuffer.TxDataSize = size;
		} else {
		pMem->InstFrame.bm.bXfrType 
			= (QSPI_IFR_TFRTYP_TRSFR_READ_MEMORY >> QSPI_IFR_TFRTYP_Pos);
		qspiDma.Qspid.qspiBuffer.RxDataSize = size;
		}
		QSPI_EnableMemAccess(&qspiDma.Qspid, 0, Secure);
#ifdef USE_QSPI_DMA
		QSPID_ReadWriteQSPI(&qspiDma, ReadWrite);
#else
		QSPI_ReadWriteMem(&qspiDma.Qspid, ReadWrite);
#endif
	} else {
		qspiDma.Qspid.qspiBuffer.pDataTx = malloc((size+4));
		SpiBuffer[0] = Instr;
		SpiBuffer[1] = (uint8_t)(Addr >> 16);
		SpiBuffer[2] = (uint8_t)(Addr >> 8);
		SpiBuffer[3] = (uint8_t)(Addr); 
		memcpy(qspiDma.Qspid.qspiBuffer.pDataTx, SpiBuffer, 4);
        if(pTxData !=NULL) {
          memcpy((qspiDma.Qspid.qspiBuffer.pDataTx+1), pTxData, size);
        }
		
		if (ReadWrite == WriteAccess) {
		qspiDma.Qspid.qspiBuffer.TxDataSize  = size+4;
#ifdef USE_QSPI_DMA
		qspiDma.Qspid.qspiBuffer.RxDataSize  = size+4;
		qspiDma.Qspid.qspiBuffer.pDataRx = qspiDma.Qspid.qspiBuffer.pDataTx;
		QSPID_ReadWriteSPI(&qspiDma, ReadWrite);
#else        
		
		QSPI_MultiWriteSPI(&qspiDma.Qspid, 
						(uint16_t *)qspiDma.Qspid.qspiBuffer.pDataTx,
						qspiDma.Qspid.qspiBuffer.TxDataSize);
#endif
		} else {
#ifdef USE_QSPI_DMA
		qspiDma.Qspid.qspiBuffer.pDataRx = pRxData;
		/* instr(1) + addrs(3) + dummy read(1)*/
		qspiDma.Qspid.qspiBuffer.RxDataSize  = size+6;
		qspiDma.Qspid.qspiBuffer.TxDataSize  = size+6;
		QSPID_ReadWriteSPI(&qspiDma, ReadWrite);
		while(qspiDma.progress);
		/*qspiDma.Qspid.qspiBuffer.pDataRx = pRxData;
		qspiDma.Qspid.qspiBuffer.RxDataSize  = size;
		qspiDma.Qspid.qspiBuffer.TxDataSize  = size;
		QSPID_ReadWriteSPI(&qspiDma, ReadWrite);*/
#else
		qspiDma.Qspid.qspiBuffer.pDataRx = pRxData;
		qspiDma.Qspid.qspiBuffer.RxDataSize  = size;
		qspiDma.Qspid.qspiBuffer.TxDataSize  = 4;
		QSPI_MultiWriteSPI(&qspiDma.Qspid, 
						(uint16_t *)qspiDma.Qspid.qspiBuffer.pDataTx,
						qspiDma.Qspid.qspiBuffer.TxDataSize);
		QSPI_MultiReadSPI(&qspiDma.Qspid, 
						(uint16_t *)qspiDma.Qspid.qspiBuffer.pDataRx,
						size);
        QSPI_EndTransfer(qspiDma.Qspid.pQspiHw);
#endif
		}
		free(qspiDma.Qspid.qspiBuffer.pDataTx);
		qspiDma.Qspid.qspiBuffer.pDataTx= NULL;
	}
	return 0;
}

/**
 * \brief Reads and returns the status register of the serial flash.
 *
 * \param pS25fl1  Pointer to an S25FL1 driver instance.
 */
static uint32_t S25FL1D_ReadStatus(void)
{
	uint32_t status, ReadStatus;

	S25FL1D_SendCommand(READ_STATUS_1, 0, &ReadStatus, ReadAccess, 1);
	status = ReadStatus;
	
	S25FL1D_SendCommand(READ_STATUS_2, 0, &ReadStatus, ReadAccess, 1);
	status |= ((ReadStatus << 8) & 0xFF00);
	
	S25FL1D_SendCommand(READ_STATUS_3, 0, &ReadStatus, ReadAccess, 1);
	status |= ((ReadStatus << 16) & 0xFF0000);
	return status;
}

/**
 * \brief Reads and returns the status register of the serial flash.
 *
 * \param pS25fl1  Pointer to an S25FL1 driver instance.
 */
static uint8_t S25FL1D_ReadStatus1(void)
{
	uint8_t status;
	S25FL1D_SendCommand(READ_STATUS_1, 0, (uint32_t *)&status, ReadAccess, 1);
	return status;
}

/**
 * \brief Reads and returns the status register of the serial flash.
 *
 * \param pS25fl1  Pointer to an S25FL1 driver instance.
 */
static uint8_t S25FL1D_ReadStatus2(void)
{
	uint8_t status;
	S25FL1D_SendCommand(READ_STATUS_2, 0, (uint32_t *)&status, ReadAccess, 1);
	return status;
}

/**
 * \brief Reads and returns the status register of the serial flash.
 *
 * \param pS25fl1  Pointer to an S25FL1 driver instance.
 */
static uint8_t S25FL1D_ReadStatus3(void)
{
	uint8_t status;
	S25FL1D_SendCommand(READ_STATUS_3, 0, (uint32_t *)&status, ReadAccess, 1);
	return status;
}
/**
 * \brief Wait for transfer to finish calling the SPI driver ISR.
 * (interrupts are disabled)
 *
 * \param pS25fl1  Pointer to an S25FL1 driver instance.
 */
static void S25FL1D_IsBusy(void)
{
#ifdef USE_QSPI_DMA  
	while(QSPID_IsBusy(&qspiDma.progress) ) {
		Wait(1);
	}
#endif
	while(S25FL1D_ReadStatus1() & STATUS_RDYBSY);
}

static void S25FL1D_EnableWrite(void)
{
	uint8_t status = 0;


	while(!(status & STATUS_WEL)) {
		S25FL1D_SendCommand(WRITE_ENABLE, 0, 0, CmdAccess, 0);
		status = S25FL1D_ReadStatus1();
	}
}


static void S25FL1D_DisableWrite(void)
{
	uint8_t status;

	status = S25FL1D_ReadStatus1();

	while( (status & STATUS_WEL) != 0) {
		S25FL1D_SendCommand(WRITE_DISABLE, 0, 0, CmdAccess, 0);
		status = S25FL1D_ReadStatus1();
	}
}
/**
 * \brief Writes the given value in the status register of the serial flash
 * device.
 *
 * \param pS25fl1  Pointer to an S25FL1 driver instance.
 * \param status  Status to write.
 */
static void S25FL1D_WriteStatus( uint8_t *pStatus)
{
	S25FL1D_EnableWrite();

	S25FL1D_SendCommand(WRITE_STATUS, (uint32_t *)pStatus, 0, WriteAccess, 3);
	
	S25FL1D_DisableWrite();
}

/**
 * \brief Writes the given value in the status register of the serial flash
 * device.
 *
 * \param pS25fl1  Pointer to an S25FL1 driver instance.
 * \param status  Status to write.
 */
static void S25FL1D_WriteVolatileStatus( uint8_t *pStatus)
{
	uint32_t DataWr = 0;
	DataWr = *pStatus;
	
	S25FL1D_SendCommand(0x50, 0, 0 , CmdAccess, 0);
	
	S25FL1D_SendCommand(WRITE_STATUS,&DataWr, 0 , WriteAccess, 3);
	S25FL1D_DisableWrite();
}


static uint8_t S25FL1D_CheckProtectedAddr(uint8_t Status1, uint32_t Addr)
{
	const uint32_t AddrJump 
			= (Status1 & SEC_PROTECT_Msk) ? 0x001000UL : 0x010000UL;
	static uint8_t Protected = 0;
	
	const uint8_t blockBits = ((Status1 & BLOCK_PROTECT_Msk) >> 2);

	switch(blockBits) {
	case 1:
	if (Status1 & TOP_BTM_PROTECT_Msk) {
		if( ( Addr > 0x000000) && ( Addr < (0x000000 + AddrJump - 1) ) ) {
		Protected = 1;
		}
	} else {
		if( ( Addr > (0x1FFFFF - AddrJump + 1) ) && ( Addr < 0x1FFFFF  ) ) {
		Protected = 1;
		}
	}
	break;
	
	case 2:
	if (Status1 & TOP_BTM_PROTECT_Msk) {
		if( ( Addr > 0x000000) && ( Addr < (0x000000 + (2* AddrJump)- 1 )) ) {
		Protected = 1;
		}
	} else {
		if( ( Addr > (0x1FFFFF - ( 2*AddrJump ) + 1) ) && ( Addr < 0x1FFFFF  ) ){
		Protected = 1;
		}
	}
	break;
	case 3:
	if (Status1 & TOP_BTM_PROTECT_Msk) {
		if( ( Addr > 0x000000) && ( Addr < (0x000000 + (4 * AddrJump) - 1) )) {
		Protected = 1;
		}
	} else {
		if( ( Addr > (0x1FFFFF - ( 4*AddrJump ) + 1) ) && ( Addr < 0x1FFFFF  )) {
		Protected = 1;
		}
	}
	break;
	
	case 4:
	if (Status1 & TOP_BTM_PROTECT_Msk) {
		if( ( Addr > 0x000000) && ( Addr < (0x000000 + (8 * AddrJump) - 1) )) {
		Protected = 1;
		}
	} else {
		if( ( Addr > (0x1FFFFF - ( 8*AddrJump ) + 1) ) && ( Addr < 0x1FFFFF  )) {
		Protected = 1;
		}
	}
	break; 
	case 5:
	if( !(Status1 & SEC_PROTECT_Msk) ) {
		if (Status1 & TOP_BTM_PROTECT_Msk) {
			if( ( Addr > 0x000000) && ( Addr < (0x000000 + (16 * AddrJump) - 1) )) {
				Protected = 1;
			}
		} else {
			if( ( Addr > (0x1FFFFF - ( 16*AddrJump ) + 1) ) && ( Addr < 0x1FFFFF )) {
				Protected = 1;
			}
		}
	}
	break;
	
	case 6:
	
	if( !(Status1 & SEC_PROTECT_Msk) ) {
	
		if (Status1 & TOP_BTM_PROTECT_Msk) {
		if( ( Addr > 0x000000) && ( Addr < (0x000000 + (32 * AddrJump) - 1) )) {
			Protected = 1;
		}
		}
	}
	break;
	}

	return Protected;
}

/*----------------------------------------------------------------------------
 *         Global functions
 *----------------------------------------------------------------------------*/
void S25FL1D_InitFlashInterface(uint8_t Mode)
{   
	if(Mode) {
		QSPID_Configure(&qspiDma, QspiMemMode,
						QSPI_MR_CSMODE_LASTXFER, &qspiDmaInst);
		qspiDma.Qspid.qspiMode = (QspiMode_t)QSPI_MR_SMM_MEMORY;
		
		pDev = (QspiInstFrame_t *)malloc (sizeof(QspiInstFrame_t));
		memset(pDev, 0, sizeof(QspiInstFrame_t));
		pDev->InstFrame.bm.bwidth = QSPI_IFR_WIDTH_SINGLE_BIT_SPI;


		pMem = (QspiInstFrame_t *)malloc (sizeof(QspiInstFrame_t));
		memset(pMem, 0, sizeof(QspiInstFrame_t));
		pMem->InstFrame.bm.bwidth = QSPI_IFR_WIDTH_SINGLE_BIT_SPI;
	} else {
		QSPID_Configure(&qspiDma,
						SpiMode,(QSPI_MR_CSMODE_LASTXFER | QSPI_MR_DLYCS (20)),
						&qspiDmaInst);
		qspiDma.Qspid.qspiMode = (QspiMode_t)QSPI_MR_SMM_SPI;
	}
	
	QSPI_ConfigureClock(QSPI, ClockMode_00, QSPI_SCR_SCBR(1));
	
	QSPI_Enable(QSPI);
	
}

/**
 * \brief Reads and returns the serial flash device ID.
 *
 * \param pS25fl1  Pointer to an S25FL1 driver instance.
 */
uint32_t S25FL1D_ReadJedecId(void)
{
	static uint32_t pId;
	S25FL1D_SendCommand(READ_JEDEC_ID, 0, &pId, ReadAccess, 3);

	return pId;
}

/**
 * \brief Enables critical writes operation on a serial flash device, such as 
 * sector protection, status register, etc.
 *
 * \para pS25fl1  Pointer to an S25FL1 driver instance.
 */
void S25FL1D_QuadMode(uint8_t Enable)
{

	uint8_t status[3];
	
	status[0] = S25FL1D_ReadStatus1();
	status[1] = S25FL1D_ReadStatus2();
	status[2] = S25FL1D_ReadStatus3();

	if(Enable) {
		while(!(status[1] & STATUS_QUAD_ENABLE)) {
			status[1] |= STATUS_QUAD_ENABLE ;
			S25FL1D_WriteStatus(status);
			status[1] = S25FL1D_ReadStatus2();
			Wait(50);
		}
	} else {
		while((status[1] & STATUS_QUAD_ENABLE)) {
			status[1] &= (~STATUS_QUAD_ENABLE);
			S25FL1D_WriteStatus(status);
			status[1] = S25FL1D_ReadStatus2();
			Wait(50);
		}
	}
}


/**
 * \brief Enables critical writes operation on a serial flash device, such as 
 * sector protection, status register, etc.
 *
 * \para pS25fl1  Pointer to an S25FL1 driver instance.
 */
void S25FL1D_EnableWrap(uint8_t ByetAlign)
{

	uint8_t status[3];

	status[0] = S25FL1D_ReadStatus1();
	status[1] = S25FL1D_ReadStatus2();
	status[2] = S25FL1D_ReadStatus3();

	status[2] |= (ByetAlign << 5);

	pDev->InstFrame.bm.bDummyCycles = 24;
	S25FL1D_SendCommand(WRAP_ENABLE,(uint32_t *)&status[2], 0,  WriteAccess, 1);

	S25FL1D_WriteVolatileStatus(status);
	status[2] = S25FL1D_ReadStatus3();
	Wait(50);
}


/**
 * \brief Enables critical writes operation on a serial flash device, such as
 * sector protection, status register, etc.
 *
 * \para pS25fl1  Pointer to an S25FL1 driver instance.
 */
void S25FL1D_SetReadLatencyControl(uint8_t Latency)
{

	uint8_t status[3];

	status[0] = S25FL1D_ReadStatus();
	status[1] = S25FL1D_ReadStatus2();
	status[2] = S25FL1D_ReadStatus3();

	status[2] |= Latency;

	qspiDma.Qspid.qspiBuffer.pDataTx = (uint32_t *)&status[2];
	while(  (status[2] & STATUS_LATENCY_CTRL) != Latency) {
		S25FL1D_WriteVolatileStatus(status);
		status[2] = S25FL1D_ReadStatus3();
		Wait(50);
	}
}
void S25FL1D_SoftReset(void)
{
	S25FL1D_SendCommand(SOFT_RESET_ENABLE,0, 0,  CmdAccess, 0);
	S25FL1D_SendCommand(SOFT_RESET, 0, 0, CmdAccess, 0);
}

/**
 * \brief Unprotected the contents of the serial flash device.
 *
 * \param pS25fl1  Pointer to an S25FL1 driver instance.
 *
 * \return 0 if the device has been unprotected; otherwise returns
 * S25FL1D_ERROR_PROTECTED.
 */
unsigned char S25FL1D_Unprotect(void)
{
	unsigned char status[3];
	/* Get the status register value to check the current protection */
	status[0]= S25FL1D_ReadStatus();
	status[1]= S25FL1D_ReadStatus2();
	status[2]= S25FL1D_ReadStatus3();
	if ((status[0] & STATUS_SWP) == STATUS_SWP_PROTNONE) {

		/* Protection already disabled */
		return 0;
	}

	status[0] &= (!STATUS_SWP);
	/* Check if sector protection registers are locked */
	if ((status[0] & STATUS_SPRL) == STATUS_SPRL_LOCKED) {
		status[0] &= (!STATUS_SPRL);
		/* Unprotected sector protection registers by writing the status reg. */
		S25FL1D_WriteStatus(status);
	}
	S25FL1D_WriteStatus(status);

	/* Check the new status */
	status[0] = S25FL1D_ReadStatus();
	if ((status[0] & (STATUS_SPRL | STATUS_SWP)) != 0) {
		return ERROR_PROTECTED;
	} else {

		return 0;
	}
}

/**
 * \brief Unprotected the contents of the serial flash device.
 *
 * \param pS25fl1  Pointer to an S25FL1 driver instance.
 *
 * \return 0 if the device has been unprotected; otherwise returns
 * S25FL1D_ERROR_PROTECTED.
 */
unsigned char S25FL1D_Protect(uint32_t StartAddr, uint32_t Size)
{
	unsigned char status[3];
	/* Get the status register value to check the current protection */
	status[0]= S25FL1D_ReadStatus1();
	status[1]= S25FL1D_ReadStatus2();
	status[2]= S25FL1D_ReadStatus3();

	status[0] &= (!STATUS_SWP);
	/* Check if sector protection registers are locked */
	if ((status[0] & STATUS_SPRL) == STATUS_SPRL_LOCKED) {
		status[0] &= (!STATUS_SPRL);
		/* Unprotected sector protection registers by writing the status reg. */
		S25FL1D_WriteStatus(status);
	}
	S25FL1D_WriteStatus(status);

	/* Check the new status */
	status[0] = S25FL1D_ReadStatus();
	if ((status[0] & (STATUS_SPRL | STATUS_SWP)) != 0) {
		return ERROR_PROTECTED;
	} else {
		return 0;
	}
}


/**
 * \brief Erases all the content of the memory chip.
 *
 * \param pS25fl1  Pointer to an S25FL1 driver instance.
 *
 * \return 0 if the device has been unprotected; otherwise returns
 * ERROR_PROTECTED.
 */
unsigned char S25FL1D_EraseChip(void)
{
	char wait_ch[4] = {'\\','|','/','-' };
	uint8_t i=0;
	uint8_t Status = STATUS_RDYBSY;
	uint8_t ChipStatus= S25FL1D_ReadStatus1();
	
	if(ChipStatus & CHIP_PROTECT_Msk) {
		TRACE_ERROR("Chip is Protected \n\r");
		TRACE_INFO("Flash Status Register 1 is %x", ChipStatus);
		return 1;
	} else {
		S25FL1D_EnableWrite();
		S25FL1D_SendCommand(CHIP_ERASE_2, 0, 0, CmdAccess, 0);

		while(Status & STATUS_RDYBSY) {
			 
			Wait(200);
			printf("Erasing flash memory %c\r", wait_ch[i]);
			i++;
			Status = S25FL1D_ReadStatus1();
			memory_barrier();
			i = i % 4;
		}
		printf("\rErasing flash memory done..... 100%%\n\r");
		return 0;
	}
}

/**
 *\brief  Erases the specified block of the serial firmware dataflash.
 *
 * \param pS25fl1  Pointer to an S25FL1 driver instance.
 * \param address  Address of the block to erase.
 *
 * \return 0 if successful; otherwise returns ERROR_PROTECTED if the
 * device is protected or ERROR_BUSY if it is busy executing a command.
 */
unsigned char S25FL1D_EraseSector(unsigned int address)
{
	uint8_t status;
	uint8_t Secure = 0;
	/* Check that the flash is ready and unprotected */
	status = S25FL1D_ReadStatus1();
	if ((status & STATUS_RDYBSY) != STATUS_RDYBSY_READY) {
		TRACE_ERROR("%s : Flash busy\n\r", __FUNCTION__);
		return ERROR_BUSY;
	} else if (status & BLOCK_PROTECT_Msk) {
		if(S25FL1D_CheckProtectedAddr(status, address)) {
			TRACE_ERROR("%s : Flash Addrs is protected\n\r", __FUNCTION__);
			return ERROR_PROTECTED;
		}
	}

	/* Enable critical write operation */
	S25FL1D_EnableWrite();

	if(qspiDma.Qspid.qspiMode) {
		pDev->Addr = address;
		pDev->InstFrame.bm.bAddrEn = 1;
		/* Start the block erase command */
		S25FL1D_SendCommand(BLOCK_ERASE_4K, 0, 0, CmdAccess, 0);
	} else {
		/* Start the block erase command */
		S25FL1D_MemoryAccess(BLOCK_ERASE_4K, address, 0, 0, WriteAccess,
						0, Secure);
	}
	/* Wait for transfer to finish */
	S25FL1D_IsBusy();
	return 0;
}

/**
 *\brief  Erases the specified 64KB block of the serial firmware dataflash.
 *
 * \param pS25fl1  Pointer to an S25FL1 driver instance.
 * \param address  Address of the block to erase.
 *
 * \return 0 if successful; otherwise returns ERROR_PROTECTED if the
 * device is protected or ERROR_BUSY if it is busy executing a command.
 */
unsigned char S25FL1D_Erase64KBlock( unsigned int address)
{
	unsigned char status;

	/* Check that the flash is ready and unprotected */
	status = S25FL1D_ReadStatus();
	if ((status & STATUS_RDYBSY) != STATUS_RDYBSY_READY) {
		TRACE_ERROR("S25FL1D_EraseBlock : Flash busy\n\r");
		return ERROR_BUSY;
	}
	else if ((status & STATUS_SWP) != STATUS_SWP_PROTNONE) {
		TRACE_ERROR("EraseBlock : Flash protected\n\r");
		return ERROR_PROTECTED;
	}

	/* Enable critical write operation */
	S25FL1D_EnableWrite();

	if(qspiDma.Qspid.qspiMode) {
		pDev->Addr = address;
		pDev->InstFrame.bm.bAddrEn = 1;
		/* Start the block erase command */
		S25FL1D_SendCommand(BLOCK_ERASE_64K, 0, 0, CmdAccess, 0);
	} else {
#ifdef USE_QSPI_DMA
		if(QSPID_EnableSpiChannel(&qspiDma) == QSPID_ERROR_LOCK)
			return 1;
#endif
		/* Start the block erase command */
		S25FL1D_MemoryAccess(BLOCK_ERASE_64K, address, 0, 0, WriteAccess, 0, 0);
#ifdef USE_QSPI_DMA
		QSPID_DisableSpiChannel(&qspiDma);
#endif
	}
	
	/* Wait for transfer to finish */
	S25FL1D_IsBusy();
	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 pS25fl1  Pointer to an S25FL1 driver instance.
 * \param pData  Data buffer.
 * \param size  Number of bytes in buffer.
 * \param address  Write address.
 *
 * \return 0 if successful; otherwise, returns ERROR_PROGRAM is there has
 * been an error during the data programming.
 */
unsigned char S25FL1D_Write(
		uint32_t *pData,
		uint32_t size,
		uint32_t address,
		uint8_t Secure)
{
	unsigned int i = 0;
	
	uint32_t  NumberOfWrites = (size >> 8); //  ( (Size / pagezize)  )
	uint32_t Addrs = address;
  
#ifdef USE_QSPI_DMA    
	if(qspiDma.Qspid.qspiMode) {
		if(QSPID_EnableQspiTxChannel(&qspiDma) == QSPID_ERROR_LOCK)
		return 1;
	} else {
		if(QSPID_EnableSpiChannel(&qspiDma) == QSPID_ERROR_LOCK)
		return 1;
	}
#endif
	// if less than page size
	if(NumberOfWrites == 0) {
		S25FL1D_EnableWrite();
		S25FL1D_MemoryAccess(BYTE_PAGE_PROGRAM , Addrs, pData, 0, 
						WriteAccess, size, Secure);
	// multiple page
	} else {
		for(i=0; i< NumberOfWrites; i++) {
			S25FL1D_EnableWrite();
			S25FL1D_MemoryAccess(BYTE_PAGE_PROGRAM , Addrs, pData, 0,
							WriteAccess, PAGE_SIZE, Secure);
			S25FL1D_IsBusy();
			pData += (PAGE_SIZE >> 2);
			Addrs += PAGE_SIZE;
		}
		if(size % PAGE_SIZE ) {
			S25FL1D_EnableWrite();
			S25FL1D_MemoryAccess(BYTE_PAGE_PROGRAM , Addrs, pData, 0, 
							WriteAccess, (size - (NumberOfWrites * PAGE_SIZE)),
							Secure);
			S25FL1D_IsBusy();
		}
	}
#ifdef USE_QSPI_DMA
	  
		if(qspiDma.Qspid.qspiMode) {
		QSPID_DisableQspiTxChannel(&qspiDma);
		} else {
		QSPID_DisableSpiChannel(&qspiDma);
		}
	  
#endif
	S25FL1D_DisableWrite();
	return 0;
}

/**
 * \brief Reads data from the specified address on the serial flash.
 *
 * \param pS25fl1  Pointer to an S25FL1 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 S25FL1D_Read(
		uint32_t *pData,
		uint32_t size,
		uint32_t address)
{    
	uint8_t Secure = 0;
	
#ifdef USE_QSPI_DMA
	if(qspiDma.Qspid.qspiMode) {
		if(QSPID_EnableQspiRxChannel(&qspiDma) == QSPID_ERROR_LOCK)
		return 1;
	} else {
		if(QSPID_EnableSpiChannel(&qspiDma) == QSPID_ERROR_LOCK)
		return 1;
	}
#endif
		
		S25FL1D_MemoryAccess(READ_ARRAY , address, 0, pData, 
						ReadAccess, size , Secure);
    
#ifdef USE_QSPI_DMA
		
		if(qspiDma.Qspid.qspiMode) {
			QSPID_DisableQspiRxChannel(&qspiDma);
		} else {
			QSPID_DisableSpiChannel(&qspiDma);
		}
#endif
		return 0;
	
}

/**
 * \brief Reads data from the specified address on the serial flash.
 *
 * \param pS25fl1  Pointer to an S25FL1 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 S25FL1D_ReadDual(
		uint32_t *pData,
		uint32_t size,
		uint32_t address)
{
  
	uint8_t Secure = 0;
#ifdef USE_QSPI_DMA  
	if(qspiDma.Qspid.qspiMode) {
		if(QSPID_EnableQspiRxChannel(&qspiDma) == QSPID_ERROR_LOCK)
		return 1;
	} else {
		if(QSPID_EnableSpiChannel(&qspiDma) == QSPID_ERROR_LOCK)
		return 1;
	}
#endif
		pMem->InstFrame.bm.bDummyCycles = 8;
		pMem->InstFrame.bm.bwidth = QSPI_IFR_WIDTH_DUAL_OUTPUT;
	  
		S25FL1D_MemoryAccess(READ_ARRAY_DUAL , address, 0, pData, 
						ReadAccess, size, Secure);
	  
#ifdef USE_QSPI_DMA
	while(QSPID_IsBusy(&qspiDma.progress) ) {
		Wait(1);
	}
	if(qspiDma.Qspid.qspiMode) {
		QSPID_DisableQspiRxChannel(&qspiDma);
	} else {
		QSPID_DisableSpiChannel(&qspiDma);
	}
#endif
		return 0;
}

/**
 * \brief Reads data from the specified address on the serial flash.
 *
 * \param pS25fl1  Pointer to an S25FL1 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 S25FL1D_ReadQuad(
		uint32_t *pData,
		uint32_t size,
		uint32_t address)
{
	uint8_t Secure = 0;
#ifdef USE_QSPI_DMA
	if(qspiDma.Qspid.qspiMode) {
		if(QSPID_EnableQspiRxChannel(&qspiDma) == QSPID_ERROR_LOCK)
		return 1;
	} else {
		if(QSPID_EnableSpiChannel(&qspiDma) == QSPID_ERROR_LOCK)
		return 1;
	}
#endif
	pMem->InstFrame.bm.bDummyCycles = 8;
	pMem->InstFrame.bm.bwidth = QSPI_IFR_WIDTH_QUAD_OUTPUT;
	S25FL1D_MemoryAccess(READ_ARRAY_QUAD,	address, 0, pData,
					ReadAccess, size, Secure);
	
#ifdef USE_QSPI_DMA
	while(QSPID_IsBusy(&qspiDma.progress) ) {
		Wait(1);
	}
	if(qspiDma.Qspid.qspiMode) {
		QSPID_DisableQspiRxChannel(&qspiDma);
	} else {
		QSPID_DisableSpiChannel(&qspiDma);
	}
#endif
	return 0;
}

/**
 * \brief Reads data from the specified address on the serial flash.
 *
 * \param pS25fl1  Pointer to an S25FL1 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 S25FL1D_ReadDualIO(
		uint32_t *pData,
		uint32_t size,
		uint32_t address,
		uint8_t ContMode,
		uint8_t Secure)
{
#ifdef USE_QSPI_DMA  
	if(qspiDma.Qspid.qspiMode) {
		if(QSPID_EnableQspiRxChannel(&qspiDma) == QSPID_ERROR_LOCK)
		return 1;
	} else {
		if(QSPID_EnableSpiChannel(&qspiDma) == QSPID_ERROR_LOCK)
		return 1;
	}
#endif
	pMem->InstFrame.bm.bDummyCycles = 4;
	if(ContMode) {
		pMem->InstFrame.bm.bOptLen
			= (QSPI_IFR_OPTL_OPTION_4BIT >> QSPI_IFR_OPTL_Pos);
		qspiDma.Qspid.qspiCommand.Option= 0x2;
		pMem->InstFrame.bm.bContinuesRead = ContMode;
		pMem->InstFrame.bm.bDummyCycles = 3;
	}
	pMem->InstFrame.bm.bwidth = QSPI_IFR_WIDTH_DUAL_IO;
	S25FL1D_MemoryAccess(READ_ARRAY_DUAL_IO , address, 0,
					pData, ReadAccess, size, Secure);
	pMem->InstFrame.bm.bOptEn = 0;
	pMem->InstFrame.bm.bContinuesRead  = 0;
#ifdef USE_QSPI_DMA
	while(QSPID_IsBusy(&qspiDma.progress) ) {
		Wait(1);
	}
	if(qspiDma.Qspid.qspiMode) {
		QSPID_DisableQspiRxChannel(&qspiDma);
	} else {
		QSPID_DisableSpiChannel(&qspiDma);
	}
#endif
	return 0;
}

/**
 * \brief Reads data from the specified address on the serial flash.
 *
 * \param pS25fl1  Pointer to an S25FL1 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 S25FL1D_ReadQuadIO(
		uint32_t *pData,
		uint32_t size,
		uint32_t address,
		uint8_t ContMode,
		uint8_t Secure)
{
#ifdef USE_QSPI_DMA  
	if(qspiDma.Qspid.qspiMode) {
		if(QSPID_EnableQspiRxChannel(&qspiDma) == QSPID_ERROR_LOCK)
		return 1;
	} else {
	if(QSPID_EnableSpiChannel(&qspiDma) == QSPID_ERROR_LOCK)
		return 1;
	}
#endif
	pMem->InstFrame.bm.bDummyCycles = 6;
	if(ContMode) {
		pMem->InstFrame.bm.bOptLen
			= (QSPI_IFR_OPTL_OPTION_4BIT >> QSPI_IFR_OPTL_Pos);
		qspiDma.Qspid.qspiCommand.Option= 0x2;
		pMem->InstFrame.bm.bContinuesRead = ContMode;
		pMem->InstFrame.bm.bDummyCycles = 5;
		pMem->InstFrame.bm.bOptEn = 1;
	}

	pMem->InstFrame.bm.bwidth = QSPI_IFR_WIDTH_QUAD_IO;
	S25FL1D_MemoryAccess(READ_ARRAY_QUAD_IO , address, 0,
						pData, ReadAccess, size, Secure);
	pMem->InstFrame.bm.bOptEn = 0;
	pMem->InstFrame.bm.bContinuesRead = 0;
#ifdef USE_QSPI_DMA
	while(QSPID_IsBusy(&qspiDma.progress) ) {
		Wait(1);
	}
	if(qspiDma.Qspid.qspiMode) {
		QSPID_DisableQspiRxChannel(&qspiDma);
	} else {
		QSPID_DisableSpiChannel(&qspiDma);
	}
#endif
	return 0;
}

