/*****************************************************************************************
 *
 * spi.c: Matthew Millman
 *
 * MPC83XX SPI interface
 *
 */

#include <common.h>
#include <malloc.h>
#include <spi.h>
#include <asm/immap_83xx.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 MPC83xx_SPI_SPMODE_CI        0x20000000	/* Clock invert */
#define MPC83xx_SPI_SPMODE_CP        0x10000000	/* Clock phase */
#define MPC83xx_SPI_SPMODE_LEN_8     0x00700000	/* 8 bits */
#define MPC83xx_SPI_SPMODE_REV       0x04000000	/* Reverse data (MSB first) */
#define MPC83xx_SPI_SPMODE_M         0x02000000	/* Master mode */
#define MPC83xx_SPI_SPMODE_EN        0x01000000	/* Enable SPI */
#define MPC83xx_SPI_SPMODE_DIV16     0x08000000	/* Divide clock by 16 */
#define MPC83xx_SPI_SPMODE_MOD_MASK  0x000F0000	/* Prescale modulus */

#define MPC83xx_SPI_SPCOM_LST        0x00400000	/* Last byte */

#define MPC83xx_SPI_SPIE_LT          0x00004000	/* Last byte sent */
#define MPC83xx_SPI_SPIE_OV          0x00001000	/* Overrun */
#define MPC83xx_SPI_SPIE_NE          0x00000200	/* Not empty */
#define MPC83xx_SPI_SPIE_NF          0x00000100	/* Not full */

#define BRG_CLK_IN_HZ 266000000


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 spmode;
    u32 actual_freq_hz;
    u8 div16;
    u8 sel;
    u32 div_ratio;
    u32 mod;
    volatile immap_t *im;

    im = (immap_t *) CONFIG_SYS_IMMR;

    sel = *(u8 *) (CONFIG_SYS_CPLD_BASE + 3);
    *(u8 *) (CONFIG_SYS_CPLD_BASE + 3) |= 0x20; // always deselect when setting clock

    actual_freq_hz = 0;

    DPRINTF("spi: spi_setclock(): freq_hz=%u\n", freq_hz);

    spmode = im->spi.mode;

    udelay(1000);

    im->spi.mode = spmode & ~(MPC83xx_SPI_SPMODE_EN|MPC83xx_SPI_SPMODE_MOD_MASK|
                              MPC83xx_SPI_SPMODE_DIV16);

    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 &=
	    ~(MPC83xx_SPI_SPMODE_MOD_MASK | MPC83xx_SPI_SPMODE_DIV16);

	if (div16) {
	    spmode |= MPC83xx_SPI_SPMODE_DIV16;
	}

	spmode |= mod << 16;

	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%08X\n", div16,
            actual_freq_hz, spmode);

    } else {
	DPRINTF
	    ("mpc83xx/spi: spi_setclock(): can't generate desired clock freq %d\n",
	     freq_hz);
    }

    im->spi.mode = spmode;

    udelay(1000);

    *(u8 *) (CONFIG_SYS_CPLD_BASE + 3) = sel; // restore original select setting

    DPRINTF("&SPMODE=0x%08X\n", &(im->spi.mode));
    DPRINTF("&SPIE=0x%08X\n", &(im->spi.event));
    DPRINTF("&SPIM=0x%08X\n", &(im->spi.mask));
    DPRINTF("&SPCOM=0x%08X\n", &(im->spi.com));
    DPRINTF("&SPITD=0x%08X\n", &(im->spi.tx));
    DPRINTF("&SPIRD=0x%08X\n", &(im->spi.rx));

    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 immap_t *im;
    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);

    im = (immap_t *) CONFIG_SYS_IMMR;

    /* 2 */

    im->spi.event = 0xFFFFFFFF;

    /* 3 */

    im->spi.mode =
	MPC83xx_SPI_SPMODE_M | MPC83xx_SPI_SPMODE_LEN_8 |
	MPC83xx_SPI_SPMODE_REV | MPC83xx_SPI_SPMODE_CI |
	MPC83xx_SPI_SPMODE_CP | MPC83xx_SPI_SPMODE_EN;

    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)
{
	return;
}

void spi_cs_deactivate(struct spi_slave *slave)
{
	return;
}

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

    im = (immap_t *) CONFIG_SYS_IMMR;

    /*
     * This 'deficit' counter was used durning the debugging of this driver
     * to ensure it never transmitted more than it had received.
     * It isn't really needed any more but there's no harm in leaving it here.
     */

    /* Discard anything in the buffer */
    temp = im->spi.rx;

    /* Read event for the first time */
    event = im->spi.event;

    while (count--) {
	/* spin until TX is done */

	while ((event & MPC83xx_SPI_SPIE_NF) == 0) {
	    event = im->spi.event;
	}

	if (!dout)
	    im->spi.tx = (u32) (0xFF);
	else
	    im->spi.tx = (u32) (*(u8 *)dout++);

	im->spi.event = MPC83xx_SPI_SPIE_NF;

	/* Wait for byte to be ready for reading */

	while (((event = im->spi.event) & MPC83xx_SPI_SPIE_NE) == 0) {
	    continue;
	}

	if (!din)
	    temp = im->spi.rx;
	else
	    *(u8 *)din++ = (u8) (im->spi.rx);

	if (event & MPC83xx_SPI_SPIE_OV) {
	    DPRINTF("SPI Overrun\n");
	}

	im->spi.event = event;
    }

    return 0;
}

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