/*
 * Copyright 2010 Allied Telesis Labs
 *
 * MPC8536 eSPI interface
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <common.h>
#include <malloc.h>
#include <spi.h>
#include <asm/immap_85xx.h>

#define ESPI_EV_DON		(1 << 14)
#define ESPI_EV_RXT		(1 << 13)
#define ESPI_EV_RXF		(1 << 12)
#define ESPI_EV_TXT		(1 << 11)
#define ESPI_EV_RNE		(1 << 9)
#define ESPI_EV_TNF		(1 << 8)

#define ESPI_MODE_EN		(1 << 31)
#define ESPI_MODE_LOOP		(1 << 30)
#define ESPI_MODE_TXTHR(x)	((x) << 8)
#define ESPI_MODE_RXTHR(x)	((x) << 0)

#define ESPI_COM_CS(x)		((x) << 30)
#define ESPI_COM_TRANLEN(x)	((x) << 0)

#define ESPI_CSMODE_CI		(1 << 31)
#define ESPI_CSMODE_CP		(1 << 30)
#define ESPI_CSMODE_REV		(1 << 29)
#define ESPI_CSMODE_DIV16	(1 << 28)
#define ESPI_CSMODE_PM(x)	((x) << 24)
#define ESPI_CSMODE_ODD		(1 << 23)
#define ESPI_CSMODE_POL		(1 << 20)
#define ESPI_CSMODE_LEN(x)	((x) << 16)
#define ESPI_CSMODE_CSBEF(x)	((x) << 12)
#define ESPI_CSMODE_CSAFT(x)	((x) << 8)
#define ESPI_CSMODE_CSCG(x)	((x) << 3)

#define BRG_CLK_IN_HZ 400000000

static volatile ccsr_espi_t *espi = (void *)(CONFIG_SYS_MPC85xx_ESPI_ADDR);

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)
{
	u32 csmode;
	u32 div_ratio;

	/* Can't change clock rate while enabled */
	espi->mode &= ~ESPI_MODE_EN;

	csmode = espi->csmode[slave->cs];
	csmode &= ~(ESPI_CSMODE_DIV16 | ESPI_CSMODE_PM(0xF) | ESPI_CSMODE_ODD);

	div_ratio = div_rounded_up(BRG_CLK_IN_HZ / 4, freq_hz);
	if (div_ratio > 16) {
		csmode |= ESPI_CSMODE_DIV16;
		div_ratio = div_rounded_up(BRG_CLK_IN_HZ / 64, freq_hz);
	}
	if (div_ratio > 16) {
		div_ratio = 16;
	}
	csmode |= ESPI_CSMODE_PM(div_ratio-1);

	if (csmode & ESPI_CSMODE_DIV16) {
		div_ratio *= 16;
	}
	div_ratio *= 4;
	if(actual_freq)
		*actual_freq =
			(BRG_CLK_IN_HZ + div_ratio / 2) / div_ratio;

	espi->csmode[slave->cs] = csmode;
	espi->mode |= ESPI_MODE_EN;
}

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

	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);

	espi->mode = 0;
	espi->event = 0xffffffff;	/* Clear all eSPI events */
	espi->mask = 0x00000000;	/* Mask  all eSPI interrupts */

	espi->csmode[slave->cs] =
		(ESPI_CSMODE_CI | ESPI_CSMODE_CP |
		 ESPI_CSMODE_REV | ESPI_CSMODE_LEN(8-1) |
		 ESPI_CSMODE_CSBEF(1) | ESPI_CSMODE_CSAFT(1) | ESPI_CSMODE_CSCG(1));

	espi->mode = ESPI_MODE_RXTHR(3) | ESPI_MODE_TXTHR(4) | ESPI_MODE_EN;

	spi_setclock(slave, max_hz, NULL);

	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 && cs < 4));
}
#endif

void spi_cs_activate(struct spi_slave *slave)
{
	u32 csmode = espi->csmode[slave->cs];
	csmode |= ESPI_CSMODE_POL;
	espi->csmode[slave->cs] = csmode;
}

void spi_cs_deactivate(struct spi_slave *slave)
{
	u32 csmode = espi->csmode[slave->cs];
	csmode &= ~ESPI_CSMODE_POL;
	espi->csmode[slave->cs] = csmode;
}

int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,
		void *din, unsigned long flags)
{
	u32 event;
	int count = bitlen / 8;

	debug("spi_xfer: slave %u:%u dout %p din %p bitlen %u count %d\n",
	      slave->bus, slave->cs, dout, din, bitlen, count);

	if (flags & SPI_XFER_BEGIN)
		spi_cs_activate(slave);

	/* Clear all eSPI events */
	espi->event = 0xffffffff;

	/* Start a command */
	espi->com = (count - 1) | ESPI_COM_CS(slave->cs);

	/* handle data in 32-bit chunks */
	for (;count >= 4; count -= 4) {
		/* Wait until can transmit */
		while (((event = espi->event) & ESPI_EV_TXT) == 0) {
			continue;
		}
		if (dout) {
			espi->tx = *((u32 *)dout);
			dout += 4;
		} else {
			espi->tx = 0xffffffff;
		}
		espi->event = ESPI_EV_TXT;

		/* Wait until four bytes in our receive fifo */
		while (((event = espi->event) & ESPI_EV_RXT) == 0) {
			continue;
		}
		if (din) {
			*((u32 *)din) = espi->rx;
			din += 4;
		} else {
			u32 dummy __attribute__((unused)) = espi->rx;
		}
		espi->event = ESPI_EV_RXT;
	}

	for(; count; count--) {
		/* Wait until can transmit */
		while (((event = espi->event) & ESPI_EV_TXT) == 0) {
			continue;
		}

		if (dout) {
			*(u8 *)(&espi->tx) = *(u8 *)dout++;
		} else {
			*(u8 *)(&espi->tx) = 0xff;
		}
		espi->event = ESPI_EV_TXT;

		/* Wait until one byte in our receive fifo */
		while (((event = espi->event) & ESPI_EV_RNE) == 0) {
			continue;
		}

		if (din) {
			*(u8 *)din++ = *(volatile u8 *)(&espi->rx);
		} else {
			u8 dummy __attribute__((unused)) = *(volatile u8 *)(&espi->rx);
		}
		espi->event = ESPI_EV_RNE;
	}

	/* Ensure the operation is complete */
	while (((event = espi->event) & ESPI_EV_DON) == 0) {
		continue;
	}
	if (flags & SPI_XFER_END)
		spi_cs_deactivate(slave);

	return 0;
}

u8 espi_select(u8 select)
{
	u32 csmode = espi->csmode[CONFIG_ESPI_CS];
	if (select) {
		csmode |= ESPI_CSMODE_POL;
	} else {
		csmode &= ~ESPI_CSMODE_POL;
	}
	espi->csmode[CONFIG_ESPI_CS] = csmode;

	return 1;
}
