/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2011 Samsung Electronics
 * Copyright 2013 Google Inc.
 *
 * 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; version 2 of the License.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <console/console.h>
#include <arch/io.h>
#include <stdlib.h>
#include <assert.h>
#include <spi_flash.h>

#include "cpu.h"
#include "spi.h"

#define EXYNOS_SPI_MAX_TRANSFER_BYTES (65535)

#if defined(CONFIG_DEBUG_SPI) && CONFIG_DEBUG_SPI
# define DEBUG_SPI(x,...)	printk(BIOS_DEBUG, "EXYNOS_SPI: " x)
#else
# define DEBUG_SPI(x,...)
#endif

struct exynos_spi_slave {
	struct spi_slave slave;
	struct exynos_spi *regs;
	unsigned int fifo_size;
	uint8_t half_duplex;
	uint8_t frame_header;  /* header byte to detect in half-duplex mode. */
};

/* TODO(hungte) Move the SPI param list to per-board configuration, probably
 * Kconfig or mainboard.c */
static struct exynos_spi_slave exynos_spi_slaves[3] = {
	// SPI 0
	{
		.slave = { .bus = 0, },
		.regs = (void *)EXYNOS5_SPI0_BASE,
	},
	// SPI 1
	{
		.slave = { .bus = 1, .rw = SPI_READ_FLAG, },
		.regs = (void *)EXYNOS5_SPI1_BASE,
		.fifo_size = 64,
		.half_duplex = 0,
	},
	// SPI 2
	{
		.slave = { .bus = 2,
			   .rw = SPI_READ_FLAG | SPI_WRITE_FLAG, },
		.regs = (void *)EXYNOS5_SPI2_BASE,
		.fifo_size = 64,
		.half_duplex = 1,
		.frame_header = 0xec,
	},
};

static inline struct exynos_spi_slave *to_exynos_spi(struct spi_slave *slave)
{
	return container_of(slave, struct exynos_spi_slave, slave);
}

void spi_init(void)
{
	printk(BIOS_INFO, "Exynos SPI driver initiated.\n");
}

struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs)
{
	ASSERT(bus >= 0 && bus < 3);
	return &(exynos_spi_slaves[bus].slave);
}

int spi_cs_is_valid(unsigned int bus, unsigned int cs)
{
	return bus > 0 && bus < 3;
}

void spi_cs_activate(struct spi_slave *slave)
{
	struct exynos_spi *regs = to_exynos_spi(slave)->regs;
	// TODO(hungte) Add some delay if too many transactions happen at once.
	clrbits_le32(&regs->cs_reg, SPI_SLAVE_SIG_INACT);
}

void spi_cs_deactivate(struct spi_slave *slave)
{
	struct exynos_spi *regs = to_exynos_spi(slave)->regs;
	setbits_le32(&regs->cs_reg, SPI_SLAVE_SIG_INACT);
}

static inline void exynos_spi_soft_reset(struct exynos_spi *regs)
{
	/* The soft reset clears only FIFO and status register.
	 * All special function registers are not changed. */
	setbits_le32(&regs->ch_cfg, SPI_CH_RST);
	clrbits_le32(&regs->ch_cfg, SPI_CH_RST);
}

static inline void exynos_spi_flush_fifo(struct exynos_spi *regs)
{
	/*
	 * Flush spi tx, rx fifos and reset the SPI controller
	 * and clear rx/tx channel
	 */
	clrbits_le32(&regs->ch_cfg, SPI_RX_CH_ON | SPI_TX_CH_ON);
	clrbits_le32(&regs->ch_cfg, SPI_CH_HS_EN);
	exynos_spi_soft_reset(regs);
	setbits_le32(&regs->ch_cfg, SPI_RX_CH_ON | SPI_TX_CH_ON);
}

static void exynos_spi_request_bytes(struct exynos_spi *regs, int count,
				     int width)
{
	uint32_t mode_word = SPI_MODE_CH_WIDTH_WORD | SPI_MODE_BUS_WIDTH_WORD,
		 swap_word = (SPI_TX_SWAP_EN | SPI_RX_SWAP_EN |
			      SPI_TX_BYTE_SWAP | SPI_RX_BYTE_SWAP |
			      SPI_TX_HWORD_SWAP | SPI_RX_HWORD_SWAP);

	/* For word address we need to swap bytes */
	if (width == sizeof(uint32_t)) {
		setbits_le32(&regs->mode_cfg, mode_word);
		setbits_le32(&regs->swap_cfg, swap_word);
		count /= width;
	} else {
		/* Select byte access and clear the swap configuration */
		clrbits_le32(&regs->mode_cfg, mode_word);
		writel(0, &regs->swap_cfg);
	}

	exynos_spi_soft_reset(regs);

	if (count) {
		ASSERT(count < (1 << 16));
		writel(count | SPI_PACKET_CNT_EN, &regs->pkt_cnt);
	} else {
		writel(0, &regs->pkt_cnt);
	}
}

static int spi_rx_tx(struct spi_slave *slave, uint8_t *rxp, int rx_bytes,
		     const uint8_t *txp, int tx_bytes)
{
	struct exynos_spi_slave *espi = to_exynos_spi(slave);
	struct exynos_spi *regs = espi->regs;

	int step;
	int todo = MAX(rx_bytes, tx_bytes);
	int wait_for_frame_header = espi->half_duplex;

	ASSERT(todo < EXYNOS_SPI_MAX_TRANSFER_BYTES);

	/* Select transfer mode. */
	if (espi->half_duplex) {
		step = 1;
	} else if ((rx_bytes | tx_bytes | (uintptr_t)rxp |(uintptr_t)txp) & 3) {
		printk(BIOS_CRIT, "%s: WARNING: tranfer mode decreased to 1B\n",
		       __func__);
		step = 1;
	} else {
		step = sizeof(uint32_t);
	}

	exynos_spi_request_bytes(regs, espi->half_duplex ? 0 : todo, step);

	/* Note: Some device, like ChromeOS EC, tries to work in half-duplex
	 * mode and sends a large amount of data (larger than FIFO size).
	 * Printing lots of debug messages or doing extra delay in the loop
	 * below may cause rx buffer to overflow and getting unexpected data
	 * error.
	 */
	while (rx_bytes || tx_bytes) {
		int temp;
		uint32_t spi_sts = readl(&regs->spi_sts);
		int rx_lvl = (spi_sts >> SPI_RX_LVL_OFFSET) & SPI_FIFO_LVL_MASK,
		    tx_lvl = (spi_sts >> SPI_TX_LVL_OFFSET) & SPI_FIFO_LVL_MASK;
		int min_tx = ((tx_bytes || !espi->half_duplex) ?
			      (espi->fifo_size / 2) : 1);

		// TODO(hungte) Abort if timeout happens in half-duplex mode.

		/*
		 * Don't completely fill the txfifo, since we don't want our
		 * rxfifo to overflow, and it may already contain data.
		 */
		while (tx_lvl < min_tx) {
			if (tx_bytes) {
				if (step == sizeof(uint32_t)) {
					temp = *((uint32_t *)txp);
					txp += sizeof(uint32_t);
				} else {
					temp = *txp++;
				}
				tx_bytes -= step;
			} else {
				temp = -1;
			}
			writel(temp, &regs->tx_data);
			tx_lvl += step;
		}

		while ((rx_lvl >= step) && rx_bytes) {
			temp = readl(&regs->rx_data);
			rx_lvl -= step;
			if (wait_for_frame_header) {
				if ((temp & 0xff) == espi->frame_header) {
					wait_for_frame_header = 0;
				}
				break;  /* Restart the outer loop. */
			}
			if (step == sizeof(uint32_t)) {
				*((uint32_t *)rxp) = temp;
				rxp += sizeof(uint32_t);
			} else {
				*rxp++ = temp;
			}
			rx_bytes -= step;
		}
	}
	return 0;
}

int spi_claim_bus(struct spi_slave *slave)
{
	struct exynos_spi_slave *espi = to_exynos_spi(slave);
	struct exynos_spi *regs = espi->regs;

	exynos_spi_flush_fifo(regs);

	// Select Active High Clock, Format A (SCP 30.2.1.8).
	clrbits_le32(&regs->ch_cfg, SPI_CH_CPOL_L | SPI_CH_CPHA_B);

	// Set FeedBack Clock Selection.
	writel(SPI_FB_DELAY_180, &regs->fb_clk);

	// HIGH speed is required for Tx/Rx to work in 50MHz (SCP 30.2.1.6).
	if (espi->half_duplex) {
		clrbits_le32(&regs->ch_cfg, SPI_CH_HS_EN);
		printk(BIOS_DEBUG, "%s: LOW speed.\n", __func__);
	} else {
		setbits_le32(&regs->ch_cfg, SPI_CH_HS_EN);
		printk(BIOS_DEBUG, "%s: HIGH speed.\n", __func__);
	}
	return 0;
}

int spi_xfer(struct spi_slave *slave, const void *dout, unsigned int out_bytes,
	     void *din, unsigned int in_bytes)
{
	uint8_t *out_ptr = (uint8_t *)dout, *in_ptr = (uint8_t *)din;
	int offset, todo, len;
	int ret = 0;

	len = MAX(out_bytes, in_bytes);

	/*
	 * Exynos SPI limits each transfer to (2^16-1=65535) bytes. To keep
	 * things simple (especially for word-width transfer mode), allow a
	 * maximum of (2^16-4=65532) bytes. We could allow more in word mode,
	 * but the performance difference is small.
	 */
	spi_cs_activate(slave);
	for (offset = 0; !ret && (offset < len); offset += todo) {
		todo = min(len - offset, (1 << 16) - 4);
		ret = spi_rx_tx(slave, in_ptr, MIN(in_bytes, todo), out_ptr,
				MIN(out_bytes, todo));
		// Adjust remaining bytes and pointers.
		if (in_bytes >= todo) {
			in_bytes -= todo;
			in_ptr += todo;
		} else {
			in_bytes = 0;
			in_ptr = NULL;
		}
		if (out_bytes >= todo) {
			out_bytes -= todo;
			out_ptr += todo;
		} else {
			out_bytes = 0;
			out_ptr = NULL;
		}
	}
	spi_cs_deactivate(slave);

	return ret;
}

static int exynos_spi_read(struct spi_slave *slave, void *dest, uint32_t len,
			   uint32_t off)
{
	struct exynos_spi *regs = to_exynos_spi(slave)->regs;
	int rv;

	// TODO(hungte) Merge the "read address" command into spi_xfer calls
	// (full-duplex mode).

	spi_cs_activate(slave);

	// Specify read address (in word-width mode).
	ASSERT(off < (1 << 24));
	exynos_spi_request_bytes(regs, sizeof(off), sizeof(off));
	writel(htonl((SF_READ_DATA_CMD << 24) | off), &regs->tx_data);
	while (!(readl(&regs->spi_sts) & SPI_ST_TX_DONE)) {
		/* Wait for TX done */
	}

	// Now, safe to transfer.
	rv = spi_xfer(slave, NULL, 0, dest, len * 8);
	spi_cs_deactivate(slave);

	return (rv == 0) ? len : -1;
}

void spi_release_bus(struct spi_slave *slave)
{
	struct exynos_spi *regs = to_exynos_spi(slave)->regs;
	/* Reset swap mode to make sure no one relying on default values (Ex,
	 * payload or kernel) will go wrong. */
	clrbits_le32(&regs->mode_cfg, (SPI_MODE_CH_WIDTH_WORD |
				       SPI_MODE_BUS_WIDTH_WORD));
	writel(0, &regs->swap_cfg);
	exynos_spi_flush_fifo(regs);
}

// SPI as CBFS media.
struct exynos_spi_media {
	struct spi_slave *slave;
	struct cbfs_simple_buffer buffer;
};

static int exynos_spi_cbfs_open(struct cbfs_media *media)
{
	struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context;
	DEBUG_SPI("exynos_spi_cbfs_open\n");
	return spi_claim_bus(spi->slave);
}

static int exynos_spi_cbfs_close(struct cbfs_media *media)
{
	struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context;
	DEBUG_SPI("exynos_spi_cbfs_close\n");
	spi_release_bus(spi->slave);
	return 0;
}

static size_t exynos_spi_cbfs_read(struct cbfs_media *media, void *dest,
				   size_t offset, size_t count)
{
	struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context;
	int bytes;
	DEBUG_SPI("exynos_spi_cbfs_read(%u)\n", count);
	bytes = exynos_spi_read(spi->slave, dest, count, offset);
	return bytes;
}

static void *exynos_spi_cbfs_map(struct cbfs_media *media, size_t offset,
				 size_t count)
{
	struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context;
	DEBUG_SPI("exynos_spi_cbfs_map\n");
	// exynos: spi_rx_tx may work in 4 byte-width-transmission mode and
	// requires buffer memory address to be aligned.
	if (count % 4)
		count += 4 - (count % 4);
	return cbfs_simple_buffer_map(&spi->buffer, media, offset, count);
}

static void *exynos_spi_cbfs_unmap(struct cbfs_media *media,
				   const void *address)
{
	struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context;
	DEBUG_SPI("exynos_spi_cbfs_unmap\n");
	return cbfs_simple_buffer_unmap(&spi->buffer, address);
}

int initialize_exynos_spi_cbfs_media(struct cbfs_media *media,
				     void *buffer_address,
				     size_t buffer_size)
{
	// TODO Replace static variable to support multiple streams.
	static struct exynos_spi_media context;
	static struct exynos_spi_slave *eslave = &exynos_spi_slaves[1];
	DEBUG_SPI("initialize_exynos_spi_cbfs_media\n");

	context.slave = &eslave->slave;
	context.buffer.allocated = context.buffer.last_allocate = 0;
	context.buffer.buffer = buffer_address;
	context.buffer.size = buffer_size;
	media->context = (void*)&context;
	media->open = exynos_spi_cbfs_open;
	media->close = exynos_spi_cbfs_close;
	media->read = exynos_spi_cbfs_read;
	media->map = exynos_spi_cbfs_map;
	media->unmap = exynos_spi_cbfs_unmap;

	return 0;
}
