/*
 * FM3 MFS SPI drievr
 * Copyright (C) 2014 Yoshinori Sato <ysato@users.sourceforge.jp>
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

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

#define MFS_CH (CONFIG_FM3_SPI_CH)
#define MFS_BASE (0x40038000 + MFS_CH * 0x100)
#define SMR (MFS_BASE + 0x00)
#define SCR (MFS_BASE + 0x01)
#define ESCR (MFS_BASE + 0x04)
#define SSR (MFS_BASE + 0x05)
#define TDR (MFS_BASE + 0x08)
#define RDR (MFS_BASE + 0x08)
#define BGR (MFS_BASE + 0x0C)
#if MFS_CH >= 4
#define FCR0 (MFS_BASE + 0x14)
#define FCR1 (MFS_BASE + 0x15)
#define FBYTE1 (MFS_BASE + 0x18)
#define FBYTE2 (MFS_BASE + 0x19)
#endif

struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs,
		unsigned int max_hz, unsigned int mode)
{
	struct spi_slave *ss;
	unsigned short bgr;

	ss = spi_alloc_slave_base(bus, cs);
	if (!ss)
		return NULL;

	bgr = CONFIG_APB2_CLK_FREQ / max_hz;
	if (bgr < 3)
		bgr = 3;

	writeb(0x80, SCR);
	writeb((mode&SPI_CPOL)?0x47:0x4f, SMR);
	writeb(0x00, ESCR);
#if defined(FBYTE2)
	writeb(0x01, FBYTE2);
	writew(0x000f, FCR0);
#endif
	writew(bgr, BGR);
	writeb((mode&SPI_CPHA)?0x03:0x23, SCR);
	return ss;
}

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

int  spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,
		void *din, unsigned long flags)
{
	int i;
	unsigned char ssr;
	unsigned char *txd = (unsigned char *)dout;
	unsigned char *rxd = (unsigned char *)din;
	unsigned short rdr;
	int ret = 0;

	for(i = 0; i < (bitlen / 8); i++) {
		if(txd) {
			writew(*txd, TDR);
			txd++;
		} else
			writew(0xff, TDR);
		while(!((ssr = readb(SSR)) & 0x04));
		if (ssr & 0x08) {
			writeb(0x80, SSR);
#if defined(FCR0)
			writew(0x000f, FCR0);
#endif
			ret = 1;
			break;
		}
		rdr = readw(RDR);
		if (rxd)
			*rxd++ = rdr;
	}
	return ret;
}

void spi_set_speed(struct spi_slave *spi, uint speed)
{
	unsigned short bgr;
	bgr = CONFIG_APB2_CLK_FREQ / speed;
	if (bgr < 3)
		bgr = 3;
	writew(bgr, BGR);
}
