/*****************************************************************************************
 *
 * spi.c: Matthew Millman
 *
 * MPC85XX CPM SPI interface, high volume/high throughput driver intended for use with 
 * SD/MMC Cards, but probably could be used for other things.
 *
 * Parts of this code were borrowed from cpu/mpc8260/spi.c
 */

#include <common.h>
#include <malloc.h>
#include <spi.h>
#include <asm/mpc_spi.h>
#include <asm/cpm_85xx.h>

#define EPRINTF(...) printf(__VA_ARGS__)
#define DPRINTF(...) //printf(__VA_ARGS__)

#define SPI_MAX_COUNT 512

#define SPI_EMASK		0x37	/* Event Mask				*/
#define SPI_STR			0x80	/* SPCOM: Start transmit		*/
#define SPI_TXB			0x02	/* Tx Buffer Empty			*/

#define MPC85xx_SPI_SPMODE_CI        0x2000	/*Clock invert*/
#define MPC85xx_SPI_SPMODE_CP        0x1000	/*Clock phase*/
#define MPC85xx_SPI_SPMODE_LEN_8     0x0070	/* 8 bits*/
#define MPC85xx_SPI_SPMODE_REV       0x0400	/*Reverse data (MSB first)*/
#define MPC85xx_SPI_SPMODE_M         0x0200	/*Master mode*/
#define MPC85xx_SPI_SPMODE_EN        0x0100	/*Enable SPI*/
#define MPC85xx_SPI_SPMODE_DIV16     0x0800	/*Divide clock by 16*/
#define MPC85xx_SPI_SPMODE_MOD_MASK  0x000F	/*Prescale modulus*/

#define BRG_CLK_IN_HZ 166000000

static mpc_spi_driver_t mpc85xx_spi_driver;
static mpc_spi_driver_t *spi_driver = &mpc85xx_spi_driver;

static u32 div_rounded_up(u32 numerator, u32 denominator)
{
	return (numerator + (denominator - 1)) / denominator;
}

/* spi_setclock()
 *
 * Set the frequency of the SPI bus clock.
 * This function sets the clock frequency to the highest value it can that 
 * does not exceed the desired frequency, and returns the actual frequency 
 * that it sets.
 */
 
void spi_setclock(struct spi_slave *slave, u32 freq_hz,
                  u32 *actual_freq)
{
	u16		spmode;
	u32		actual_freq_hz;
	u8		div16;
	u32		div_ratio;
	u32		mod;
	volatile ccsr_cpm_t *im_cpm = (ccsr_cpm_t *)CONFIG_SYS_MPC85xx_CPM_ADDR;

	actual_freq_hz = 0;

	DPRINTF("spi: spi_setclock(): freq_hz=%u\n", freq_hz);
	
	spmode = im_cpm->im_cpm_spi.spmode;

	im_cpm->im_cpm_spi.spmode &= ~MPC85xx_SPI_SPMODE_EN;

	div16  = 0;
	div_ratio = div_rounded_up(BRG_CLK_IN_HZ, freq_hz);
	
	if (div_ratio > 64)
	{
		div16 = 1;
		div_ratio = div_rounded_up(BRG_CLK_IN_HZ / 16, freq_hz);
	}

	if (div_ratio <= 64)
	{
		mod = (div_ratio - 1) / 4;

		mod &= 0xF;

		spmode &= ~(MPC85xx_SPI_SPMODE_MOD_MASK | MPC85xx_SPI_SPMODE_DIV16);
		
		if (div16)
		{
			spmode |= MPC85xx_SPI_SPMODE_DIV16;	
		}

		spmode |= mod;				

		actual_freq_hz = div16 ? BRG_CLK_IN_HZ / 16 : BRG_CLK_IN_HZ;
		actual_freq_hz /= (4 * (mod + 1));

		DPRINTF("div16=%u, actual_freq_hz=%u, spmode=0x%04X\n", div16, actual_freq_hz, spmode);

		spi_driver->ns_per_byte = actual_freq_hz == 0 ? 0 :
				(8 * 1000000) / (actual_freq_hz/1000);
	}
	else
	{
		DPRINTF("mpc85xx/spi: spi_setclock(): can't generate desired clock freq %d\n", freq_hz);
	}

	im_cpm->im_cpm_spi.spmode = spmode;

	DPRINTF("&spmode=0x%08X\n", &im_cpm->im_cpm_spi.spmode);
	
	if (actual_freq) {
		*actual_freq = actual_freq_hz;
	}
}

struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs,
		unsigned int max_hz, unsigned int mode)
{

	volatile spi_t *spi;
	volatile cbd_t *tbdf, *rbdf;
	struct spi_slave *slave;
	u16 txbuf_dp;
	u16 rxbuf_dp;
	
	volatile ccsr_cpm_t *im_cpm = (ccsr_cpm_t *)CONFIG_SYS_MPC85xx_CPM_ADDR;
	volatile ccsr_cpm_cp_t *cp = (ccsr_cpm_cp_t *)&im_cpm->im_cpm_cp;
	
	if (!spi_cs_is_valid(bus, cs))
		return NULL;

	slave = malloc(sizeof(struct spi_slave));
	if (!slave)
		return NULL;

	slave->bus = bus;
	slave->cs = cs;

	debug("spi_setup_slave: new slave %d:%d\n", slave->bus, slave->cs);

	DPRINTF("spi_base: 0x%08X, *spi_base: 0x%08X\n", &im_cpm->im_dprambase[PROFF_SPI_BASE],
	       *(ushort *) (im_cpm->im_dprambase[PROFF_SPI_BASE]));
	
	/* 1 */

	/* --------------------------------------------
	* GPIO or per. Function
	* PPARD[16] = 1 [0x00008000] (SPIMISO)
	* PPARD[17] = 1 [0x00004000] (SPIMOSI)
	* PPARD[18] = 1 [0x00002000] (SPICLK)
	* PPARD[19] = 0 [0x00001000] -> GPIO: (CS for SD Card)
	* -------------------------------------------- */
	im_cpm->im_cpm_iop.ppard |=  0x0000E000;	/* set  bits	*/
	im_cpm->im_cpm_iop.ppard &= ~0x00001000;	/* reset bit	*/

	/* ----------------------------------------------
	* In/Out or per. Function 0/1
	* PDIRD[16] = 0 [0x00008000] -> PERI1: SPIMISO
	* PDIRD[17] = 0 [0x00004000] -> PERI1: SPIMOSI
	* PDIRD[18] = 0 [0x00002000] -> PERI1: SPICLK
	* PDIRD[19] = 1 [0x00001000] -> GPIO OUT: CS for SD Card
	* ---------------------------------------------- */
	im_cpm->im_cpm_iop.pdird &= ~0x0000E000;
	im_cpm->im_cpm_iop.pdird |= 0x00001000;


	im_cpm->im_cpm_iop.psord |= 0x0000E000;
	
	/* 3 */
	spi_driver->proff_spi = m8560_cpm_dpalloc(sizeof(spi_t), 8);
	
	*(ushort *)(&im_cpm->im_dprambase[PROFF_SPI_BASE]) = spi_driver->proff_spi;
		
	spi  = (spi_t *)&im_cpm->im_dprambase[spi_driver->proff_spi];
	
	DPRINTF("spi_pram: 0x%08X\n", spi);
	
	/* 4 */

	/* Set up the SPI parameters in the parameter ram */
	spi->spi_rbase = m8560_cpm_dpalloc (sizeof(cbd_t), 8);
	spi->spi_tbase = m8560_cpm_dpalloc (sizeof(cbd_t), 8);

	/* 5 */
	
	spi->spi_tfcr = 0x30;
	spi->spi_rfcr = 0x30;
	
	/* 6 */
	
	spi->spi_mrblr = SPI_MAX_COUNT;
	
	/* 7 */
	
	txbuf_dp = m8560_cpm_dpalloc (SPI_MAX_COUNT, 8);
	spi_driver->txbuf = (uchar *) &im_cpm->im_dprambase[txbuf_dp];
	
	tbdf = (cbd_t *) & im_cpm->im_dprambase[spi->spi_tbase];
	tbdf->cbd_sc = 0xB800;
	tbdf->cbd_datlen = 0x0000;
	tbdf->cbd_bufaddr = (ulong) spi_driver->txbuf;
	
	/* 8 */
	
	rxbuf_dp = m8560_cpm_dpalloc (SPI_MAX_COUNT, 8);
	spi_driver->rxbuf = (uchar *) &im_cpm->im_dprambase[rxbuf_dp];
	
	rbdf = (cbd_t *) & im_cpm->im_dprambase[spi->spi_rbase];	
	rbdf->cbd_sc = 0xB000;
	rbdf->cbd_datlen = 0x0000;
	rbdf->cbd_bufaddr = (ulong) spi_driver->rxbuf;
	
	/* 9 */
	
	while (cp->cpcr & CPM_CR_FLG)
	;
	
	cp->cpcr = mk_cr_cmd(CPM_CR_SPI_PAGE, CPM_CR_SPI_SBLOCK,
			     0, CPM_CR_INIT_TRX) | CPM_CR_FLG;
	while (cp->cpcr & CPM_CR_FLG)
		;

	/* 10 + 11 */
	im_cpm->im_cpm_spi.spie = SPI_EMASK;		/* Clear all SPI events	*/
	im_cpm->im_cpm_spi.spim = 0x00;			/* Mask  all SPI events */
	
	im_cpm->im_cpm_spi.spmode = MPC85xx_SPI_SPMODE_M  | MPC85xx_SPI_SPMODE_LEN_8 | MPC85xx_SPI_SPMODE_REV | 
			MPC85xx_SPI_SPMODE_CI | MPC85xx_SPI_SPMODE_CP | MPC85xx_SPI_SPMODE_EN;
	
	spi_driver->ns_per_byte = 0;

	return slave;
}

void spi_free_slave(struct spi_slave *slave)
{
	free(slave);
}

int spi_claim_bus(struct spi_slave *slave)
{
	return 0;
}

void spi_release_bus(struct spi_slave *slave)
{
}

#ifndef CONFIG_SPI_CS_IS_VALID
/*
 * you can define this function board specific
 * define above CONFIG in board specific config file and
 * provide the function in board specific src file
 */
int spi_cs_is_valid(unsigned int bus, unsigned int cs)
{
	return (bus == 0 && cs == 0);
}
#endif

void spi_cs_activate(struct spi_slave *slave) {}

void spi_cs_deactivate(struct spi_slave *slave) {}

/* spi_xfer()
 *
 * Read/Write
 */
int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,
		void *din, unsigned long flags)
{
	volatile cbd_t *tbdf, *rbdf;
	volatile spi_t *spi;
	volatile ccsr_cpm_t *im_cpm = (ccsr_cpm_t *)CONFIG_SYS_MPC85xx_CPM_ADDR;
	const uchar *txbuffer = dout;
	uchar *rxbuffer = din;
	int len = bitlen / 8;
	
	spi  = (spi_t *)&im_cpm->im_dprambase[spi_driver->proff_spi];
	tbdf = (cbd_t *)&im_cpm->im_dprambase[spi->spi_tbase];
	rbdf = (cbd_t *)&im_cpm->im_dprambase[spi->spi_rbase];
	
	if (len > SPI_MAX_COUNT)
	{
		DPRINTF("spi_xfer: block too large!\n");
		return 0;
	}
	
	if (txbuffer)
		memcpy(spi_driver->txbuf, txbuffer, len);
	else
		memset(spi_driver->txbuf, 0xFF, len);
	
	tbdf->cbd_bufaddr = (ulong)spi_driver->txbuf;
	tbdf->cbd_sc  = BD_SC_READY | BD_SC_LAST | BD_SC_WRAP;
	tbdf->cbd_datlen = len;	
	
	rbdf->cbd_bufaddr = (ulong)spi_driver->rxbuf;
	rbdf->cbd_sc = BD_SC_EMPTY | BD_SC_WRAP;
	rbdf->cbd_datlen = 0;	 /* rx length has no significance */
	
	im_cpm->im_cpm_spi.spie = SPI_EMASK;		/* Clear all SPI events	*/
	im_cpm->im_cpm_spi.spcom |= SPI_STR;		/* Start transmit (but actually receive) */
	
	while ((tbdf->cbd_sc & BD_SC_READY) != 0)
		;
	
	udelay((spi_driver->ns_per_byte / (1000 / 3)) + 1 );	/*Three byte times plus 1us*/
	
	if (rxbuffer)
		memcpy(rxbuffer, spi_driver->rxbuf, len);
	
	return 0;
}

void spi_init_f (void) {}
void spi_init_r (void) {}
