/**
 *  \file  am335x_indcomm_spi_flash.c
 *
 *  \brief This file includes driver API to access SPI FLASH 
 *  c file.
 *
 */

/*
 * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
 */
 
/* ========================================================================== */
/*                             Include Files                                  */
/* ========================================================================== */ 
#include "osdrv_spiflash.h"
#include "osdrv_mcspi.h"
#include "plat_mcspi.h"
#include "plat_utils.h"

/* ========================================================================== */
/*                           Macros & Typedefs                                */
/* ========================================================================== */
/** \brief SPI Flash intialization status macros. */
#define SPI_FALSH_NOT_INITALIZED    0
#define SPI_FLASH_INITALIZED        1

/* ========================================================================== */
/*                         Structures and Enums                               */
/* ========================================================================== */
/**
 *  \brief None
 *         
 */


/* ========================================================================== */
/*                 Internal Function Declarations                             */
/* ========================================================================== */
/**
 * \brief   This routine reads the data from a page .It takes pagenum as a 
 * parameter along with that buffer where read data is stored and length of the buffer. 
 *
 * \param  src     [IN]     source from where data has to be read
 * \param  dst     [IN]     Buffer where read data has to be stored
 * \param  length  [IN]     Length of the buffer   
 * \param  spiAddr [IN]     McSPI Base address
 *
 * \return  
 *      SUCCESS for success 
 *      FAIL for error
 **/
void _SPIFlashReadPage( unsigned int src, unsigned int dst,
                    unsigned int length, int spiId );
/**
 * \brief   This verifes that the SPI flash is erased from a
 *          goven offset. 
 *
 * \param  offset     [IN]    SPI Flash memory offset 
 * \param  spiAddr    [IN]    McSPI Base address
 *
 * \return  
 *      SUCCESS for success 
 *      FAIL for error
 **/                       
int _SPIVerifyErasedSector(unsigned int offset,int spiId);

/** **************************************************************************
 * \brief    This routine erases the contents from SPI Flash from a gven offset 
 *           to the given length.
 *
 * This routine reads the data from a page .It takes pagenum as a parameter 
 * along with that buffer where read data is stored and length of the buffer. 
 * 
 * \param  mode    [IN]     Bulk erase or Sector erase - Currently bulk erase is not supported
 * \param  offset  [IN]     byte offset at which erase whould start
 * \param  length  [IN]     total number oy bytes to be erased
 * \param  spiAddr [IN]     spi address      
 * 
 * \return
 *
 *      SUCCESS for success 
 *      FAIL for error
 *
 */
int _SPIFlashEraseSector( unsigned int mode, unsigned int offset,
                    unsigned int length, int spiId );
/** **************************************************************************
 * \brief  Routine to write a page of Spiflash 
 *
 * This routine write a page of data to Spiflash device.This routine writes
 * the data byte by byte till the page size every time it checks whether page  
 * has exceed the pagesize .It takes buffer to be written as a parameter along
 * with that pagenumber from where data to be read and length of the buffer.
 *
 *
 * \param  src     [IN]     Buffer to be written
 * \param  dst     [IN]     pagenumber where data to be written
 * \param  length  [IN]     Length of the buffer
 *
 * \return 
 *      None
 */ 
void _SPIFlashWritePage( unsigned int src, unsigned int dst, 
                            unsigned int length, int spiId );

                            
/** **************************************************************************
 * \brief Routine to read SPI flash status register
 *
 * This routine sends the read status command to spiflash and reads the status
 * and returns the same.
 *
 * \param 
 * \None
 *
 * \return 
 *      Read status value
 * 
 */
static unsigned char _SPIFlashStatus( int spiId);

                            
/* ========================================================================== */
/*                            Global Variables                                */
/* ========================================================================== */
/** \brief */
static unsigned char spiFlashTxBuf[SPIFLASH_PAGESIZE + 5];
/** \brief */
static unsigned char spiFlashRxBuf[SPIFLASH_PAGESIZE + 5];
/** \brief */
static unsigned char statusbuf[8];

/** \brief */
volatile unsigned char spiFlashInitStatus = SPI_FALSH_NOT_INITALIZED;
/** \brief */
volatile int flash_spi_instance = -1;
/** \brief */
volatile int flash_spi_chnNum = -1;
/* ========================================================================== */
/*                          Function Definitions                              */
/* ========================================================================== */

/*
 *  This function intializes MCSPI channel, and SPI Flash.
 *  Return :  0 if success.
 *            < 0 if failure
 */
 /**
 * \brief   This function intializes MCSPI channel, and SPI Flash.
 *
 * \param   
 *
 * \return  0 if success, < 0 otherwise
 *
 **/
int SpiFlashInit(int spiId,unsigned int chNum)
{
    int retVal;
    unsigned int spi_address = 0;
    
    if(spiId > 1)
        return -1;
    
    retVal = McSPIPinMuxConfig(spiId);
    
    if(0 != retVal)
    	return -1;    
        
    /* Initialize the SPI interface */

    spi_address = McSPIInit(spiId, SPI_FLASH_CD, SPI_FLASH_DIR,MCSPI_SINGLE_CH,chNum);
    if(!spi_address)
    	return -1;
        
    flash_spi_chnNum = chNum;
    flash_spi_instance = spiId;

        
    retVal = SPIFlashReadID(spiId);
    if(retVal)
    {
        return -1;
    }    
    spiFlashInitStatus = SPI_FLASH_INITALIZED;        
    return 0;
}

int SpiFlashRead(unsigned int offset,unsigned int noOfbytes,unsigned char* rxBuff)
{
	int   no_of_pages;  
	short   i, itr =0  ;
	unsigned char*  p8;
    unsigned int bytes_to_copy = 0;
	
    if(SPI_FALSH_NOT_INITALIZED == spiFlashInitStatus)
        return -1;
    
	no_of_pages = noOfbytes/SPIFLASH_PAGESIZE + ((noOfbytes % SPIFLASH_PAGESIZE) ? 1 : 0);
	

	i = (offset/SPIFLASH_PAGESIZE);
	p8 = (unsigned char*)rxBuff;
	while(itr < no_of_pages) 
    {
		bytes_to_copy = noOfbytes - (itr * SPIFLASH_PAGESIZE);
		if(bytes_to_copy > 256)
		{
			bytes_to_copy = 256;
		}

        _SPIFlashReadPage((i * SPIFLASH_PAGESIZE), ( unsigned int )p8, 
                      bytes_to_copy, flash_spi_instance );
		p8 += bytes_to_copy;
		i++;
        itr++;
	}
    return 0;
	
}

int SpiFlashWrite(unsigned int offset,unsigned int noOfbytes,unsigned char* txBuff,unsigned char erase_flag)
{
	int   no_of_pages;  
	short   i, itr =0  ,itr2=0;
    unsigned char tx[256];
	unsigned char*  p8;
    unsigned int bytes_to_copy = 0;
	
    if(SPI_FALSH_NOT_INITALIZED == spiFlashInitStatus)
        return -1;
        
    if((erase_flag) && (-1 == SpiFlashErase(offset,noOfbytes)))
    	return -1;

	no_of_pages = noOfbytes/SPIFLASH_PAGESIZE + ((noOfbytes % SPIFLASH_PAGESIZE) ? 1 : 0);

	i = (offset/SPIFLASH_PAGESIZE);
	
	while(itr < no_of_pages) {
		p8 = (unsigned char*) tx;

		bytes_to_copy = noOfbytes - (itr * SPIFLASH_PAGESIZE);
		if(bytes_to_copy > 256)
		{
			bytes_to_copy = 256;
		}
        
        #if 1
        for(itr2 = 0 ; itr2 < bytes_to_copy; itr2++)
        {
            p8[itr2] = txBuff[(itr * SPIFLASH_PAGESIZE)+itr2];
        }   
        #else
            memcpy(p8,&txBuff[itr * SPIFLASH_PAGESIZE],SPIFLASH_PAGESIZE);
        #endif        
		

        _SPIFlashWritePage((unsigned int)p8 ,i *SPIFLASH_PAGESIZE,
                		bytes_to_copy, flash_spi_instance);

		i++;
        itr++;
	}	
    return 0;
	
}

int SpiFlashErase(unsigned int offset,unsigned int noOfbytes)
{
	int   no_of_sectors;  
	
    if(SPI_FALSH_NOT_INITALIZED == spiFlashInitStatus)
        return -1;    

	no_of_sectors = (noOfbytes/SPIFLASH_SECTORSIZE + ((noOfbytes % SPIFLASH_SECTORSIZE) ? 1 : 0));

    _SPIFlashEraseSector( 0, 
                (offset/SPIFLASH_SECTORSIZE)*SPIFLASH_SECTORSIZE,
                no_of_sectors*SPIFLASH_SECTORSIZE,
                flash_spi_instance);
    return 0;
}

int SPIFlashGetInstance()
{
    if(spiFlashInitStatus == SPI_FALSH_NOT_INITALIZED)
        return -1;
    return flash_spi_instance;
}

int SPIFlashReadID ()
{
    int retVal = SUCCESS;
    statusbuf[0] = SPIFLASH_CMD_READID;
    statusbuf[1] = 0x00;
    statusbuf[2] = 0x00;
    statusbuf[3] = 0x00;
    statusbuf[4] = 0x00;
    statusbuf[5] = 0x00;

    McSPIRead(flash_spi_instance, statusbuf, MFID_DEVID_LEN, SPI_FLASH_CS);
    /* The Manufacturrerer ID and the Device ID is read into offset 5 & 6 */
    if (SPIFLASH_MANUF_ID != statusbuf[4])
    {
        retVal = FAIL;
        #ifdef DEBUG_CONSOLE_PRINT
        printf ("Unknown manufacturer ID read: 0x%x.\r\n",statusbuf[4]);
        #endif
    } 
    else
    {
        #ifdef DEBUG_CONSOLE_PRINT
        printf ("Manufacturer ID read is 0x%x.\r\n", statusbuf[4]);
        printf ("Device ID read is 0x%x.\r\n", statusbuf[5]);
        #endif
    }
	
    return (retVal);
}

/* -------------------------------------------------------------------------- */
/*                 Internal Function Definitions                              */
/* -------------------------------------------------------------------------- */

void _SPIFlashReadPage( unsigned int src, unsigned int dst,
                        unsigned int length, int spiId )
{
    int i;
    unsigned char *psrc, *pdst;

    // Setup command
    spiFlashRxBuf[0] = SPIFLASH_CMD_READ;
    spiFlashRxBuf[1] = ( src >> SHIFT_WORD );
    spiFlashRxBuf[2] = ( src >> SHIFT_BYTE );
    spiFlashRxBuf[3] = ( src >> 0 );

    // Execute spiflash read cycle
    McSPIRead(spiId, spiFlashRxBuf, length + 5, SPI_FLASH_CS);
    // Copy returned data
    pdst = ( unsigned char * )dst;
    psrc = spiFlashRxBuf + 4;

    for ( i = 0 ; i < length ; i++ )
        *pdst++ = *psrc++;
}

int _SPIVerifyErasedSector(unsigned int offset,int spiId)
{
	unsigned int itr = 0,itr2 = 0;
	unsigned char t_buff[256]; 
	for(itr = offset ; itr < (offset+SPIFLASH_SECTORSIZE) ; )
	{
		_SPIFlashReadPage(itr ,
				( unsigned int )t_buff, 
				SPIFLASH_PAGESIZE, spiId );
				
		for(itr2 = 0 ; itr2 < SPIFLASH_PAGESIZE ; itr2++)
		{
			if(0xFF != t_buff[itr2])
			{
                #ifdef DEBUG_CONSOLE_PRINT
				printf(" Erase failed at offset %d ,page %d, byte :%d \n",itr,(itr/SPIFLASH_PAGESIZE),itr2);
                #endif
				break;
			}	
						
		}				
				
		itr += SPIFLASH_PAGESIZE;	
					
	}			
	return 0;	
}

#define VERIFY_SECTOR_ERASE
int _SPIFlashEraseSector( unsigned int mode, unsigned int offset,
                          unsigned int length, int spiId )
{
	unsigned short quitLoop = 0;
	unsigned int itr;
	
    #ifdef DEBUG_CONSOLE_PRINT
	printf("Flash erase offset = %d , length = %d \n",offset, length);
    #endif
	if (offset % SPIFLASH_SECTORSIZE || length % SPIFLASH_SECTORSIZE) {
        #ifdef DEBUG_CONSOLE_PRINT
		printf("SF: Erase offset/length not multiple of sector size\n");
        #endif
		return -1;
	}
	length /= SPIFLASH_SECTORSIZE;
    /* Issue WPEN */
	for (itr = 0; itr < length; itr++)
	{
		//printf("Erasing sector @ %d \n",offset);
		if((_SPIFlashStatus( spiId )& FLASH_WEL_STATUS) != FLASH_WEL_STATUS)
		{
			spiFlashTxBuf[0] = SPIFLASH_CMD_WREN;
			McSPIWrite(spiId, spiFlashTxBuf, SPI_WPEN_LEN, SPI_FLASH_CS);
			while(( _SPIFlashStatus( spiId ) & FLASH_WEL_STATUS ) != FLASH_WEL_STATUS )
				{
				quitLoop = quitLoop + 1;
				if(quitLoop > 200)
					break;
				}
		}

		/* Issue Erase */
		spiFlashTxBuf[0] = SPIFLASH_CMD_SCTR_ERASE;//SPIFLASH_CMD_ERASE;
		spiFlashTxBuf[1] = ( unsigned char )( offset >> SHIFT_WORD );
		spiFlashTxBuf[2] = ( unsigned char )( offset >> SHIFT_BYTE );
		spiFlashTxBuf[3] = ( unsigned char )( offset );
		McSPIWrite(spiId, spiFlashTxBuf, SPI_ERASE_LEN, SPI_FLASH_CS);

		while( ( _SPIFlashStatus( spiId) & FLASH_STATUS ) );
		
	#ifdef VERIFY_SECTOR_ERASE
		_SPIVerifyErasedSector(offset,spiId);
	#endif				
		
		offset += SPIFLASH_SECTORSIZE;
	}		
	return 0;
}


void _SPIFlashWritePage( unsigned int src, unsigned int dst, 
                         unsigned int length, int spiId )
{
    int i;
    int bytesLeft;
    int bytesToProgram;
    unsigned char *psrc;
    unsigned short quitLoop;
    volatile int loop;

    /* Establish source */
    psrc = ( unsigned char * )src;
    bytesLeft = length;

    while ( bytesLeft > 0 )
    {
    	bytesToProgram = bytesLeft;

        /* Most to program is SPIFLASH_CMD_BLOCKSIZE */
        if ( bytesToProgram > SPIFLASH_PAGESIZE )
             bytesToProgram = SPIFLASH_PAGESIZE;

        /* Make sure you don't run off the end of a block */
        if ( ( dst & SPIFLASH_PAGEMASK ) != ( ( dst + bytesToProgram ) 
            & SPIFLASH_PAGEMASK ) )

            bytesToProgram -= ( dst + bytesToProgram ) - 
                ( ( dst + bytesToProgram ) & SPIFLASH_PAGEMASK );
        quitLoop = 0;
        /* Issue WPEN */

        /* Issue WPEN */
        quitLoop = 0;
        if((_SPIFlashStatus( spiId)& FLASH_WEL_STATUS) != FLASH_WEL_STATUS)
        {
            spiFlashTxBuf[0] = SPIFLASH_CMD_WREN;
            McSPIWrite(spiId, spiFlashTxBuf, SPI_WPEN_LEN, SPI_FLASH_CS);

            while( ( _SPIFlashStatus( spiId) & FLASH_WEL_STATUS ) != FLASH_WEL_STATUS)
            {
             quitLoop = quitLoop + 1;
             if(quitLoop > 200)
                 break;
            }
        }

        /* Create command block for program operation */
        spiFlashTxBuf[0] = SPIFLASH_CMD_WRITE;
        spiFlashTxBuf[1] = ( unsigned char )( dst >> SHIFT_WORD );
        spiFlashTxBuf[2] = ( unsigned char )( dst >> SHIFT_BYTE );
        spiFlashTxBuf[3] = ( unsigned char )( dst );
    	for(loop = 0; loop < 1000; loop++);

        for ( i = 0 ; i < bytesToProgram ; i++ )
            spiFlashTxBuf[4+i] = *psrc++;

        /* Execute write command */
	    McSPIWrite(spiId, spiFlashTxBuf, bytesToProgram + 4, SPI_FLASH_CS);
        /* Wait while busy */
        while( ( _SPIFlashStatus( spiId) & FLASH_STATUS ) );

        /* Get ready for next iteration */
        bytesLeft -= bytesToProgram;
        dst += bytesToProgram;
    }
}

static unsigned char _SPIFlashStatus( int spiId)
{
    /* Issue read status command */
    statusbuf[0] = SPIFLASH_CMD_RDSR;
    statusbuf[1] = 0;
#ifdef AM335X_DRIVER     
   	DELAYMilliSeconds(10);
#else
    DELAYMilliSeconds(20);
#endif    
    McSPIRead(spiId, statusbuf, SPI_STATUS_LEN, SPI_FLASH_CS);
    return statusbuf[1];
}
