/****************************************************************************
 *  SPI flash driver for M25P64
 ****************************************************************************/
#include <common.h>
#include <linux/ctype.h>
#include <asm/io.h>

#if defined(CONFIG_SPI)

/* Application definitions */

#define	NUM_SECTORS	128	/* number of sectors */
#define SECTOR_SIZE	0x10000
#define NOP_NUM		1000

#define COMMON_SPI_SETTINGS (SPE|MSTR|CPHA|CPOL) /* Settings to the SPI_CTL */
#define TIMOD01 (0x01)	/* stes the SPI to work with core instructions */

/* Flash commands */
#define	SPI_WREN	(0x06)	/*Set Write Enable Latch */
#define	SPI_WRDI	(0x04)	/*Reset Write Enable Latch */
#define	SPI_RDSR	(0x05)	/*Read Status Register */
#define	SPI_WRSR	(0x01)	/*Write Status Register */
#define	SPI_READ	(0x03)	/*Read data from memory */
#define	SPI_FAST_READ	(0x0B)	/*Read data from memory */
#define	SPI_PP		(0x02)	/*Program Data into memory */
#define	SPI_SE		(0xD8)	/*Erase one sector in memory */
#define	SPI_BE		(0xC7)	/*Erase all memory */
#define	WIP		(0x1)	/*Check the write in progress bit of the SPI status register */
#define	WEL		(0x2)	/*Check the write enable bit of the SPI status register */

#define	TIMEOUT		350000000

typedef enum {
	NO_ERR,
	POLL_TIMEOUT,
	INVALID_SECTOR,
	INVALID_BLOCK,
} ERROR_CODE;

void spi_init_f(void);
void spi_init_r(void);
ssize_t spi_read(uchar *, int, uchar *, int);
ssize_t spi_write(uchar *, int, uchar *, int);

char ReadStatusRegister(void);
void Wait_For_SPIF(void);
void SetupSPI(const int spi_setting);
void SPI_OFF(void);
void SendSingleCommand(const int iCommand);

ERROR_CODE GetSectorNumber(unsigned long ulOffset, int *pnSector);
ERROR_CODE EraseBlock(int nBlock);
ERROR_CODE ReadData(unsigned long ulStart, long lCount, int *pnData);
ERROR_CODE WriteData(unsigned long ulStart, long lCount, int *pnData);
ERROR_CODE Wait_For_Status(char Statusbit);
ERROR_CODE Wait_For_WEL(void);

/*
 * Function:    spi_init_f
 * Description: Init SPI-Controller (ROM part)
 * return:      ---
 */
void spi_init_f(void)
{
}

/*
 * Function:    spi_init_r
 * Description: Init SPI-Controller (RAM part) -
 *		 The malloc engine is ready and we can move our buffers to
 *		 normal RAM
 *  return:      ---
 */
void spi_init_r(void)
{
	return;
}

/*
 * Function:    spi_write
 */
ssize_t spi_write(uchar * addr, int alen, uchar * buffer, int len)
{
	unsigned long offset;
	int start_block, end_block;
	int start_byte, end_byte;
	ERROR_CODE result = NO_ERR;
	uchar temp[SECTOR_SIZE];
	int i, num;

	offset = addr[0] << 16 | addr[1] << 8 | addr[2];
	/* Get the start block number */
	result = GetSectorNumber(offset, &start_block);
	if (result == INVALID_SECTOR) {
		printf("Invalid sector! ");
		return 0;
	}
	/* Get the end block number */
	result = GetSectorNumber(offset + len - 1, &end_block);
	if (result == INVALID_SECTOR) {
		printf("Invalid sector! ");
		return 0;
	}

	for (num = start_block; num <= end_block; num++) {
		ReadData(num * SECTOR_SIZE, SECTOR_SIZE, (int *)temp);
		start_byte = num * SECTOR_SIZE;
		end_byte = (num + 1) * SECTOR_SIZE - 1;
		if (start_byte < offset)
			start_byte = offset;
		if (end_byte > (offset + len))
			end_byte = (offset + len - 1);
		for (i = start_byte; i <= end_byte; i++)
			temp[i - num * SECTOR_SIZE] = buffer[i - offset];
		EraseBlock(num);
		result = WriteData(num * SECTOR_SIZE, SECTOR_SIZE, (int *)temp);
		if (result != NO_ERR)
			return 0;
		printf(".");
	}
	return len;
}

/*
 * Function: spi_read
 */
ssize_t spi_read(uchar * addr, int alen, uchar * buffer, int len)
{
	unsigned long offset;
	offset = addr[0] << 16 | addr[1] << 8 | addr[2];
	ReadData(offset, len, (int *)buffer);
	return len;
}

void SendSingleCommand(const int iCommand)
{
	unsigned short dummy;

	/* turns on the SPI in single write mode */
	SetupSPI((COMMON_SPI_SETTINGS | TIMOD01));

	/* sends the actual command to the SPI TX register */
	*pSPI_TDBR = iCommand;
	sync();

	/* The SPI status register will be polled to check the SPIF bit */
	Wait_For_SPIF();

	dummy = *pSPI_RDBR;

	/* The SPI will be turned off */
	SPI_OFF();

}

void SetupSPI(const int spi_setting)
{

	if (icache_status() || dcache_status())
		udelay(CONFIG_CCLK_HZ / 50000000);
	/*sets up the PF10 to be the slave select of the SPI */
	*pPORTF_FER |= (PF10 | PF11 | PF12 | PF13);
	*pSPI_FLG = 0xFF02;
	*pSPI_BAUD = CONFIG_SPI_BAUD;
	*pSPI_CTL = spi_setting;
	sync();

	*pSPI_FLG = 0xFD02;
	sync();
}

void SPI_OFF(void)
{

	*pSPI_CTL = 0x0400;	/* disable SPI */
	*pSPI_FLG = 0;
	*pSPI_BAUD = 0;
	sync();
	udelay(CONFIG_CCLK_HZ / 50000000);

}

void Wait_For_SPIF(void)
{
	unsigned short dummyread;
	while ((*pSPI_STAT & TXS)) ;
	while (!(*pSPI_STAT & SPIF)) ;
	while (!(*pSPI_STAT & RXS)) ;
	/* Read dummy to empty the receive register */
	dummyread = *pSPI_RDBR;
}

ERROR_CODE Wait_For_WEL(void)
{
	int i;
	char status_register = 0;
	ERROR_CODE ErrorCode = NO_ERR;

	for (i = 0; i < TIMEOUT; i++) {
		status_register = ReadStatusRegister();
		if ((status_register & WEL)) {
			ErrorCode = NO_ERR;
			break;
		}
		ErrorCode = POLL_TIMEOUT;	/* Time out error */
	};

	return ErrorCode;
}

ERROR_CODE Wait_For_Status(char Statusbit)
{
	int i;
	char status_register = 0xFF;
	ERROR_CODE ErrorCode = NO_ERR;

	for (i = 0; i < TIMEOUT; i++) {
		status_register = ReadStatusRegister();
		if (!(status_register & Statusbit)) {
			ErrorCode = NO_ERR;
			break;
		}
		ErrorCode = POLL_TIMEOUT;	/* Time out error */
	};

	return ErrorCode;
}

char ReadStatusRegister(void)
{
	char status_register = 0;

	SetupSPI((COMMON_SPI_SETTINGS | TIMOD01));	/* Turn on the SPI */

	*pSPI_TDBR = SPI_RDSR;	/* send instruction to read status register */
	sync();
	Wait_For_SPIF();	/*wait until the instruction has been sent */
	*pSPI_TDBR = 0;		/*send dummy to receive the status register */
	sync();
	Wait_For_SPIF();	/*wait until the data has been sent */
	status_register = *pSPI_RDBR;	/*read the status register */

	SPI_OFF();		/* Turn off the SPI */

	return status_register;
}

ERROR_CODE GetSectorNumber(unsigned long ulOffset, int *pnSector)
{
	int nSector = 0;
	ERROR_CODE ErrorCode = NO_ERR;

	if (ulOffset > (NUM_SECTORS * 0x10000 - 1)) {
		ErrorCode = INVALID_SECTOR;
		return ErrorCode;
	}

	nSector = (int)ulOffset / 0x10000;
	*pnSector = nSector;

	return ErrorCode;
}

ERROR_CODE EraseBlock(int nBlock)
{
	unsigned long ulSectorOff = 0x0, ShiftValue;
	ERROR_CODE ErrorCode = NO_ERR;

	/* if the block is invalid just return */
	if ((nBlock < 0) || (nBlock > NUM_SECTORS)) {
		ErrorCode = INVALID_BLOCK;
		return ErrorCode;
	}
	/* figure out the offset of the block in flash */
	if ((nBlock >= 0) && (nBlock < NUM_SECTORS)) {
		ulSectorOff = (nBlock * SECTOR_SIZE);

	} else {
		ErrorCode = INVALID_BLOCK;
		return ErrorCode;
	}

	/* A write enable instruction must previously have been executed */
	SendSingleCommand(SPI_WREN);

	/* The status register will be polled to check the write enable latch "WREN" */
	ErrorCode = Wait_For_WEL();

	if (POLL_TIMEOUT == ErrorCode) {
		printf("SPI Erase block error\n");
		return ErrorCode;
	} else

	/* Turn on the SPI to send single commands */
	SetupSPI((COMMON_SPI_SETTINGS | TIMOD01));

	/*
	 * Send the erase block command to the flash followed by the 24 address
	 * to point to the start of a sector
	 */
	*pSPI_TDBR = SPI_SE;
	sync();
	Wait_For_SPIF();
	/* Send the highest byte of the 24 bit address at first */
	ShiftValue = (ulSectorOff >> 16);
	*pSPI_TDBR = ShiftValue;
	sync();
	/* Wait until the instruction has been sent */
	Wait_For_SPIF();
	/* Send the middle byte of the 24 bit address  at second */
	ShiftValue = (ulSectorOff >> 8);
	*pSPI_TDBR = ShiftValue;
	sync();
	/* Wait until the instruction has been sent */
	Wait_For_SPIF();
	/* Send the lowest byte of the 24 bit address finally */
	*pSPI_TDBR = ulSectorOff;
	sync();
	/* Wait until the instruction has been sent */
	Wait_For_SPIF();

	/* Turns off the SPI */
	SPI_OFF();

	/* Poll the status register to check the Write in Progress bit */
	/* Sector erase takes time */
	ErrorCode = Wait_For_Status(WIP);

	/* block erase should be complete */
	return ErrorCode;
}

/*
 * ERROR_CODE ReadData()
 * Read a value from flash for verify purpose
 * Inputs:	unsigned long ulStart - holds the SPI start address
 *			int pnData - pointer to store value read from flash
 *			long lCount - number of elements to read
 */
ERROR_CODE ReadData(unsigned long ulStart, long lCount, int *pnData)
{
	unsigned long ShiftValue;
	char *cnData;
	int i;

	/* Pointer cast to be able to increment byte wise */

	cnData = (char *)pnData;
	/* Start SPI interface */
	SetupSPI((COMMON_SPI_SETTINGS | TIMOD01));

#ifdef CONFIG_SPI_FLASH_FAST_READ
	/* Send the read command to SPI device */
	*pSPI_TDBR = SPI_FAST_READ;
#else
	/* Send the read command to SPI device */
	*pSPI_TDBR = SPI_READ;
#endif
	sync();
	/* Wait until the instruction has been sent */
	Wait_For_SPIF();
	/* Send the highest byte of the 24 bit address at first */
	ShiftValue = (ulStart >> 16);
	/* Send the byte to the SPI device */
	*pSPI_TDBR = ShiftValue;
	sync();
	/* Wait until the instruction has been sent */
	Wait_For_SPIF();
	/* Send the middle byte of the 24 bit address  at second */
	ShiftValue = (ulStart >> 8);
	/* Send the byte to the SPI device */
	*pSPI_TDBR = ShiftValue;
	sync();
	/* Wait until the instruction has been sent */
	Wait_For_SPIF();
	/* Send the lowest byte of the 24 bit address finally */
	*pSPI_TDBR = ulStart;
	sync();
	/* Wait until the instruction has been sent */
	Wait_For_SPIF();

#ifdef CONFIG_SPI_FLASH_FAST_READ
	/* Send dummy for FAST_READ */
	*pSPI_TDBR = 0;
	sync();
	/* Wait until the instruction has been sent */
	Wait_For_SPIF();
#endif

	/* After the SPI device address has been placed on the MOSI pin the data can be */
	/* received on the MISO pin. */
	for (i = 0; i < lCount; i++) {
		*pSPI_TDBR = 0;
		sync();
		while (!(*pSPI_STAT & RXS)) ;
		*cnData++ = *pSPI_RDBR;

		if ((i >= SECTOR_SIZE) && (i % SECTOR_SIZE == 0))
			printf(".");
	}

	/* Turn off the SPI */
	SPI_OFF();

	return NO_ERR;
}

ERROR_CODE WriteFlash(unsigned long ulStartAddr, long lTransferCount,
		      int *iDataSource, long *lWriteCount)
{

	unsigned long ulWAddr;
	long lWTransferCount = 0;
	int i;
	char iData;
	char *temp = (char *)iDataSource;
	ERROR_CODE ErrorCode = NO_ERR;

	/* First, a Write Enable Command must be sent to the SPI. */
	SendSingleCommand(SPI_WREN);

	/*
	 * Second, the SPI Status Register will be tested whether the
	 * Write Enable Bit has been set
	 */
	ErrorCode = Wait_For_WEL();
	if (POLL_TIMEOUT == ErrorCode) {
		printf("SPI Write Time Out\n");
		return ErrorCode;
	} else
		/* Third, the 24 bit address will be shifted out
		 * the SPI MOSI bytewise.
		 * Turns the SPI on
		 */
		SetupSPI((COMMON_SPI_SETTINGS | TIMOD01));
	*pSPI_TDBR = SPI_PP;
	sync();
	/*wait until the instruction has been sent */
	Wait_For_SPIF();
	ulWAddr = (ulStartAddr >> 16);
	*pSPI_TDBR = ulWAddr;
	sync();
	/*wait until the instruction has been sent */
	Wait_For_SPIF();
	ulWAddr = (ulStartAddr >> 8);
	*pSPI_TDBR = ulWAddr;
	sync();
	/*wait until the instruction has been sent */
	Wait_For_SPIF();
	ulWAddr = ulStartAddr;
	*pSPI_TDBR = ulWAddr;
	sync();
	/*wait until the instruction has been sent */
	Wait_For_SPIF();
	/*
	 * Fourth, maximum number of 256 bytes will be taken from the Buffer
	 * and sent to the SPI device.
	 */
	for (i = 0; (i < lTransferCount) && (i < 256); i++, lWTransferCount++) {
		iData = *temp;
		*pSPI_TDBR = iData;
		sync();
		/*wait until the instruction has been sent */
		Wait_For_SPIF();
		temp++;
	}

	/* Turns the SPI off */
	SPI_OFF();

	/*
	 * Sixth, the SPI Write in Progress Bit must be toggled to ensure the
	 * programming is done before start of next transfer
	 */
	ErrorCode = Wait_For_Status(WIP);

	if (POLL_TIMEOUT == ErrorCode) {
		printf("SPI Program Time out!\n");
		return ErrorCode;
	} else

		*lWriteCount = lWTransferCount;

	return ErrorCode;
}

ERROR_CODE WriteData(unsigned long ulStart, long lCount, int *pnData)
{

	unsigned long ulWStart = ulStart;
	long lWCount = lCount, lWriteCount;
	long *pnWriteCount = &lWriteCount;

	ERROR_CODE ErrorCode = NO_ERR;

	while (lWCount != 0) {
		ErrorCode = WriteFlash(ulWStart, lWCount, pnData, pnWriteCount);

		/*
		 * After each function call of WriteFlash the counter
		 * must be adjusted
		 */
		lWCount -= *pnWriteCount;

		/* Also, both address pointers must be recalculated. */
		ulWStart += *pnWriteCount;
		pnData += *pnWriteCount / 4;
	}

	/* return the appropriate error code */
	return ErrorCode;
}

#endif				/* CONFIG_SPI */
