/******************************************************************************
 *
 * Filename: spi_flash.c
 *
 * Instantiation of SPI flash control routines supporting AT45DB161B
 *
 * Revision information:
 *
 * 17JAN2005	kb_admin	initial creation
 *				adapted from external sources
 *				tested for basic operation only!!!
 *
 * BEGIN_KBDD_BLOCK
 * No warranty, expressed or implied, is included with this software.  It is
 * provided "AS IS" and no warranty of any kind including statutory or aspects
 * relating to merchantability or fitness for any purpose is provided.  All
 * intellectual property rights of others is maintained with the respective
 * owners.  This software is not copyrighted and is intended for reference
 * only.
 * END_BLOCK
 *
 * $FreeBSD: release/9.2.0/sys/boot/arm/at91/libat91/spi_flash.c 174877 2007-12-23 14:46:30Z ticso $
 *****************************************************************************/

#include "at91rm9200.h"
#include "spi_flash.h"
#include "lib.h"

/*********************** PRIVATE FUNCTIONS/DATA ******************************/


static spiCommand_t	spi_command;
static char		tx_commandBuffer[8], rx_commandBuffer[8];

/*
 * .KB_C_FN_DEFINITION_START
 * void SendCommand(spiCommand_t *pCommand)
 *  Private function sends 8-bit value to the device and returns the 8-bit
 * value in response.
 * .KB_C_FN_DEFINITION_END
 */
static void
SendCommand(spiCommand_t *pCommand)
{
	AT91PS_SPI	pSPI = AT91C_BASE_SPI;

	pSPI->SPI_PTCR = AT91C_PDC_TXTDIS | AT91C_PDC_RXTDIS;

	pSPI->SPI_RPR = (unsigned)pCommand->rx_cmd;
	pSPI->SPI_RCR = pCommand->rx_cmd_size;
	pSPI->SPI_TPR = (unsigned)pCommand->tx_cmd;
	pSPI->SPI_TCR = pCommand->tx_cmd_size;

	pSPI->SPI_TNPR = (unsigned)pCommand->tx_data;
	pSPI->SPI_TNCR = pCommand->tx_data_size;
	pSPI->SPI_RNPR = (unsigned)pCommand->rx_data;
	pSPI->SPI_RNCR = pCommand->rx_data_size;

	pSPI->SPI_PTCR = AT91C_PDC_TXTEN | AT91C_PDC_RXTEN;

	// wait for completion
	while (!(pSPI->SPI_SR & AT91C_SPI_SPENDRX))
		Delay(700);
}


/*
 * .KB_C_FN_DEFINITION_START
 * char GetFlashStatus(void)
 *  Private function to return device status.
 * .KB_C_FN_DEFINITION_END
 */
static char
GetFlashStatus(void)
{
	p_memset((char *)&spi_command, 0, sizeof(spi_command));
	p_memset(tx_commandBuffer, 0, 8);
	tx_commandBuffer[0] = STATUS_REGISTER_READ;
	p_memset(rx_commandBuffer, 0, 8);
	spi_command.tx_cmd = tx_commandBuffer;
	spi_command.rx_cmd = rx_commandBuffer;
	spi_command.rx_cmd_size = 2;
	spi_command.tx_cmd_size = 2;
	SendCommand(&spi_command);
	return (rx_commandBuffer[1]);
}

/*
 * .KB_C_FN_DEFINITION_START
 * void WaitForDeviceReady(void)
 *  Private function to poll until the device is ready for next operation.
 * .KB_C_FN_DEFINITION_END
 */
static void
WaitForDeviceReady(void)
{
	while (!(GetFlashStatus() & 0x80)) ;
}

/*************************** GLOBAL FUNCTIONS ********************************/


/*
 * .KB_C_FN_DEFINITION_START
 * void SPI_ReadFlash(unsigned flash_addr, unsigned dest_addr, unsigned size)
 *  Global function to read the SPI flash device using the continuous read
 * array command.
 * .KB_C_FN_DEFINITION_END
 */
void
SPI_ReadFlash(unsigned flash_addr, char *dest_addr, unsigned size)
{
	unsigned	pageAddress, byteAddress;

	// determine page address
	pageAddress = flash_addr / FLASH_PAGE_SIZE;

	// determine byte address
	byteAddress = flash_addr % FLASH_PAGE_SIZE;

	p_memset(tx_commandBuffer, 0, 8);
#ifdef BOOT_BWCT
	tx_commandBuffer[0] = 0xd2;
	tx_commandBuffer[1] = ((pageAddress >> 6) & 0xFF);
	tx_commandBuffer[2] = ((pageAddress << 2) & 0xFC) |
				((byteAddress >> 8) & 0x3);
	tx_commandBuffer[3] = byteAddress & 0xFF;
	spi_command.tx_cmd = tx_commandBuffer;
	spi_command.tx_cmd_size = 8;
	spi_command.tx_data_size = size;
	spi_command.tx_data = dest_addr;

	p_memset(rx_commandBuffer, 0, 8);
	spi_command.rx_cmd = rx_commandBuffer;
	spi_command.rx_cmd_size = 8;
	spi_command.rx_data_size = size;
	spi_command.rx_data = dest_addr;
#else
	tx_commandBuffer[0] = CONTINUOUS_ARRAY_READ_HF;
	tx_commandBuffer[1] = ((pageAddress >> 5) & 0xFF);
	tx_commandBuffer[2] = ((pageAddress << 3) & 0xF8) |
				((byteAddress >> 8) & 0x7);
	tx_commandBuffer[3] = byteAddress & 0xFF;
	spi_command.tx_cmd = tx_commandBuffer;
	spi_command.tx_cmd_size = 5;
	spi_command.tx_data_size = size;
	spi_command.tx_data = dest_addr;

	p_memset(rx_commandBuffer, 0, 8);
	spi_command.rx_cmd = rx_commandBuffer;
	spi_command.rx_cmd_size = 5;
	spi_command.rx_data_size = size;
	spi_command.rx_data = dest_addr;
#endif

	SendCommand(&spi_command);
}


/*
 * .KB_C_FN_DEFINITION_START
 * void SPI_WriteFlash(unsigned flash_addr, unsigned src_addr, unsigned size)
 *  Global function to program the SPI flash device.  Notice the warning
 * provided in lower-level functions regarding corruption of data in non-
 * page aligned write operations.
 * .KB_C_FN_DEFINITION_END
 */
void
SPI_WriteFlash(unsigned flash_addr, char *src_addr, unsigned size)
{
	unsigned	pageAddress, byteAddress;

	// determine page address
	pageAddress = flash_addr / FLASH_PAGE_SIZE;

	// determine byte address
	byteAddress = flash_addr % FLASH_PAGE_SIZE;

	p_memset(tx_commandBuffer, 0, 8);
#ifdef BOOT_BWCT
	tx_commandBuffer[0] = 0x82;
	tx_commandBuffer[1] = ((pageAddress >> 6) & 0xFF);
	tx_commandBuffer[2] = ((pageAddress << 2) & 0xFC) |
				((byteAddress >> 8) & 0x3);
	tx_commandBuffer[3] = (byteAddress & 0xFF);
#else
	tx_commandBuffer[0] = PROGRAM_THROUGH_BUFFER;
	tx_commandBuffer[1] = ((pageAddress >> 5) & 0xFF);
	tx_commandBuffer[2] = ((pageAddress << 3) & 0xF8) |
				((byteAddress >> 8) & 0x7);
	tx_commandBuffer[3] = (byteAddress & 0xFF);
#endif

	p_memset(rx_commandBuffer, 0, 8);

	spi_command.tx_cmd = tx_commandBuffer;
	spi_command.rx_cmd = rx_commandBuffer;
	spi_command.rx_cmd_size = 4;
	spi_command.tx_cmd_size = 4;

	spi_command.tx_data_size = size;
	spi_command.tx_data = src_addr;
	spi_command.rx_data_size = size;
	spi_command.rx_data = src_addr;

	SendCommand(&spi_command);

	WaitForDeviceReady();
}

/*
 * .KB_C_FN_DEFINITION_START
 * void SPI_InitFlash(void)
 *  Global function to initialize the SPI flash device/accessor functions.
 * .KB_C_FN_DEFINITION_END
 */
void
SPI_InitFlash(void)
{
	AT91PS_PIO	pPio;
	AT91PS_SPI	pSPI = AT91C_BASE_SPI;
	unsigned	value;

	// enable CS0, CLK, MOSI, MISO
	pPio = (AT91PS_PIO)AT91C_BASE_PIOA;
	pPio->PIO_ASR = AT91C_PA3_NPCS0 | AT91C_PA1_MOSI | AT91C_PA0_MISO |
	    AT91C_PA2_SPCK;
	pPio->PIO_PDR = AT91C_PA3_NPCS0 | AT91C_PA1_MOSI | AT91C_PA0_MISO |
	    AT91C_PA2_SPCK;

	// enable clocks to SPI
	AT91C_BASE_PMC->PMC_PCER = 1u << AT91C_ID_SPI;

	// reset the SPI
	pSPI->SPI_CR = AT91C_SPI_SWRST;

	pSPI->SPI_MR = (0xf << 24) | AT91C_SPI_MSTR | AT91C_SPI_MODFDIS |
	    (0xE << 16);

	pSPI->SPI_CSR[0] = AT91C_SPI_CPOL | (4 << 16) | (2 << 8);
	pSPI->SPI_CR = AT91C_SPI_SPIEN;

	pSPI->SPI_PTCR = AT91C_PDC_TXTDIS;
	pSPI->SPI_PTCR = AT91C_PDC_RXTDIS;
	pSPI->SPI_RNPR = 0;
	pSPI->SPI_RNCR = 0;
	pSPI->SPI_TNPR = 0;
	pSPI->SPI_TNCR = 0;
	pSPI->SPI_RPR = 0;
	pSPI->SPI_RCR = 0;
	pSPI->SPI_TPR = 0;
	pSPI->SPI_TCR = 0;
	pSPI->SPI_PTCR = AT91C_PDC_RXTEN;
	pSPI->SPI_PTCR = AT91C_PDC_TXTEN;

	value = pSPI->SPI_RDR;
	value = pSPI->SPI_SR;

	value = GetFlashStatus() & 0xFC;
#ifdef BOOT_BWCT
	if (value != 0xB4 && value != 0xAC)
		printf(" Bad SPI status: 0x%x\n", value);
#else
	if (value != 0xBC)
		printf(" Bad SPI status: 0x%x\n", value);
#endif
}
