/*
 * Marvell MVEBU SoC SPI controller
 *  compatible with Dove, Kirkwood, MV78x00, Armada 370/XP
 *
 * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
 *
 * 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.
 */

#include <common.h>
#include <driver.h>
#include <errno.h>
#include <init.h>
#include <io.h>
#include <malloc.h>
#include <spi/spi.h>
#include <linux/clk.h>
#include <linux/err.h>

#define SPI_IF_CTRL		0x00
#define  IF_CS_NUM(x)		((x) << 2)
#define  IF_CS_NUM_MASK		IF_CS_NUM(7)
#define  IF_READ_READY		BIT(1)
#define  IF_CS_ENABLE		BIT(0)
#define SPI_IF_CONFIG		0x04
#define  IF_CLK_DIV(x)		((x) << 11)
#define  IF_CLK_DIV_MASK	(0x7 << 11)
#define  IF_FAST_READ		BIT(10)
#define  IF_ADDRESS_LEN_4BYTE	(3 << 8)
#define  IF_ADDRESS_LEN_3BYTE	(2 << 8)
#define  IF_ADDRESS_LEN_2BYTE	(1 << 8)
#define  IF_ADDRESS_LEN_1BYTE	(0 << 8)
#define  IF_CLK_PRESCALE_POW8	BIT(7)
#define  IF_CLK_PRESCALE_POW4	BIT(6)
#define  IF_TRANSFER_2BYTE	BIT(5)
#define  IF_CLK_PRESCALE_POW2	BIT(4)
#define  IF_CLK_PRESCALE(x)	((x) & 0x0f)
#define  IF_CLK_PRE_PRESCALE(x)	(((((x) & 0xc) << 1) | ((x) & 0x1)) << 4)
#define  IF_CLK_PRESCALE_MASK	(IF_CLK_PRESCALE(7) | IF_CLK_PRE_PRESCALE(7))
#define SPI_DATA_OUT		0x08
#define SPI_DATA_IN		0x0c
#define SPI_INT_CAUSE		0x10
#define SPI_INT_MASK		0x14
#define  INT_READ_READY		BIT(0)

#define SPI_SPI_MAX_CS	8

struct mvebu_spi {
	struct spi_master master;
	void __iomem *base;
	struct clk *clk;
	bool data16;
	int (*set_baudrate)(struct mvebu_spi *p, u32 speed);
};

#define priv_from_spi_device(s)	\
	container_of(s->master, struct mvebu_spi, master);

static inline int mvebu_spi_set_cs(struct mvebu_spi *p, u8 cs, u8 mode, bool en)
{
	u32 val;

	/*
	 * Only Armada 370/XP support up to 8 CS signals, for the
	 * others this register bits are read-only
	 */
	if (cs > SPI_SPI_MAX_CS)
		return -EINVAL;

	if (mode & SPI_CS_HIGH)
		en = !en;

	val = IF_CS_NUM(cs);
	if (en)
		val |= IF_CS_ENABLE;

	writel(val, p->base + SPI_IF_CTRL);

	return 0;
}

static int mvebu_spi_set_transfer_size(struct mvebu_spi *p, int size)
{
	u32 val;

	if (size != 8 && size != 16)
		return -EINVAL;

	p->data16 = (size == 16);

	val = readl(p->base + SPI_IF_CONFIG) & ~IF_TRANSFER_2BYTE;
	if (p->data16)
		val |= IF_TRANSFER_2BYTE;
	writel(val, p->base + SPI_IF_CONFIG);

	return 0;
}

static int mvebu_spi_set_baudrate(struct mvebu_spi *p, u32 speed)
{
	u32 pscl, val;

	/* standard prescaler values: 1,2,4,6,...,30 */
	pscl = DIV_ROUND_UP(clk_get_rate(p->clk), speed);
	pscl = roundup(pscl, 2);

	dev_dbg(p->master.dev, "%s: clk = %lu, speed = %u, pscl = %d\n",
		__func__, clk_get_rate(p->clk), speed, pscl);

	if (pscl > 30)
		return -EINVAL;

	val = readl(p->base + SPI_IF_CONFIG) & ~(IF_CLK_PRESCALE_MASK);
	val |= IF_CLK_PRESCALE_POW2 | IF_CLK_PRESCALE(pscl/2);
	writel(val, p->base + SPI_IF_CONFIG);

	return 0;
}

#if defined(CONFIG_ARCH_ARMADA_370) || defined(CONFIG_ARCH_ARMADA_XP)
static int armada_370_xp_spi_set_baudrate(struct mvebu_spi *p, u32 speed)
{
	u32 pscl, pdiv, rate, val;

	/* prescaler values: 1,2,3,...,15 */
	pscl = DIV_ROUND_UP(clk_get_rate(p->clk), speed);

	/* additional prescaler divider: 1, 2, 4, 8, 16, 32, 64, 128 */
	pdiv = 0; rate = pscl;
	while (rate > 15 && pdiv <= 7) {
		rate /= 2;
		pdiv++;
	}

	dev_dbg(p->master.dev, "%s: clk = %lu, speed = %u, pscl = %d, pdiv = %d\n",
		__func__, clk_get_rate(p->clk), speed, pscl, pdiv);

	if (rate > 15 || pdiv > 7)
		return -EINVAL;

	val = readl(p->base + SPI_IF_CONFIG) & ~(IF_CLK_PRESCALE_MASK);
	val |= IF_CLK_PRE_PRESCALE(pdiv) | IF_CLK_PRESCALE(pscl);
	writel(val, p->base + SPI_IF_CONFIG);

	return 0;
}
#endif

#if defined(CONFIG_ARCH_DOVE)
static int dove_spi_set_baudrate(struct mvebu_spi *p, u32 speed)
{
	u32 pscl, sdiv, rate, val;

	/* prescaler values: 1,2,3,...,15 and 1,2,4,6,...,30 */
	pscl = DIV_ROUND_UP(clk_get_rate(p->clk), speed);
	if (pscl > 15)
		pscl = roundup(pscl, 2);

	/* additional sclk divider: 1, 2, 4, 8, 16 */
	sdiv = 0; rate = pscl;
	while (rate > 30 && sdiv <= 4) {
		rate /= 2;
		sdiv++;
	}

	dev_dbg(p->master.dev, "%s: clk = %lu, speed = %u, pscl = %d, sdiv = %d\n",
		__func__, clk_get_rate(p->clk), speed, pscl, sdiv);

	if (rate > 30 || sdiv > 4)
		return -EINVAL;

	val = readl(p->base + SPI_IF_CONFIG) &
		~(IF_CLK_DIV_MASK | IF_CLK_PRESCALE_MASK);

	val |= IF_CLK_DIV(sdiv);
	if (pscl > 15)
		val |= IF_CLK_PRESCALE_POW2 | IF_CLK_PRESCALE(pscl/2);
	else
		val |= IF_CLK_PRESCALE(pscl);
	writel(val, p->base + SPI_IF_CONFIG);

	return 0;
}
#endif

static int mvebu_spi_set_mode(struct mvebu_spi *p, u8 mode)
{
	/*
	 * From public datasheets of Orion SoCs, it is unclear
	 * if the SPI controller supports setting CPOL/CPHA.
	 * Dove has an SCK_INV but as with the other SoCs, it
	 * is tagged with "Must be 1".
	 *
	 * For now, we just bail out if device requests any
	 * other mode than SPI_MODE0.
	 */

	if ((mode & (SPI_CPOL|SPI_CPHA)) == SPI_MODE_0)
		return 0;

	pr_err("%s: unsupported SPI mode %02x\n", __func__, mode);

	return -EINVAL;
}

static int mvebu_spi_setup(struct spi_device *spi)
{
	int ret;
	struct mvebu_spi *priv = priv_from_spi_device(spi);

	dev_dbg(&spi->dev, "%s: mode %02x, bits_per_word = %d, speed = %d\n",
		__func__, spi->mode, spi->bits_per_word, spi->max_speed_hz);

	ret = mvebu_spi_set_cs(priv, spi->chip_select, spi->mode, false);
	if (ret)
		return ret;
	ret = mvebu_spi_set_mode(priv, spi->mode);
	if (ret)
		return ret;
	ret = mvebu_spi_set_transfer_size(priv, spi->bits_per_word);
	if (ret)
		return ret;

	return priv->set_baudrate(priv, spi->max_speed_hz);
}

static inline int mvebu_spi_wait_for_read_ready(struct mvebu_spi *p)
{
	int timeout = 100;
	while ((readl(p->base + SPI_IF_CTRL) & IF_READ_READY) == 0 &&
		timeout--)
		udelay(1);
	if (timeout < 0)
		return -EIO;
	return 0;
}

static int mvebu_spi_do_transfer(struct spi_device *spi,
				 struct spi_transfer *t)
{
	const u8 *txdata = t->tx_buf;
	u8 *rxdata = t->rx_buf;
	int ret = 0, n, inc;
	struct mvebu_spi *priv = priv_from_spi_device(spi);

	if (t->bits_per_word)
		ret = mvebu_spi_set_transfer_size(priv, spi->bits_per_word);
	if (ret)
		return ret;

	if (t->speed_hz)
		ret = priv->set_baudrate(priv, t->speed_hz);
	if (ret)
		return ret;

	inc = (priv->data16) ? 2 : 1;
	for (n = 0; n < t->len; n += inc) {
		u32 data = 0;

		if (txdata)
			data = *txdata++;
		if (txdata && priv->data16)
			data |= (*txdata++ << 8);

		writel(data, priv->base + SPI_DATA_OUT);

		ret = mvebu_spi_wait_for_read_ready(priv);
		if (ret) {
			dev_err(&spi->dev, "timeout reading from device %s\n",
				dev_name(&spi->dev));
			return ret;
		}

		data = readl(priv->base + SPI_DATA_IN);

		if (rxdata)
			*rxdata++ = (data & 0xff);
		if (rxdata && priv->data16)
			*rxdata++ = (data >> 8) & 0xff;
	}

	return 0;
}

static int mvebu_spi_transfer(struct spi_device *spi, struct spi_message *msg)
{
	struct spi_transfer *t;
	int ret;
	struct mvebu_spi *priv = priv_from_spi_device(spi);

	ret = mvebu_spi_set_cs(priv, spi->chip_select, spi->mode, true);
	if (ret)
		return ret;

	msg->actual_length = 0;

	list_for_each_entry(t, &msg->transfers, transfer_list) {
		ret = mvebu_spi_do_transfer(spi, t);
		if (ret)
			break;
		msg->actual_length += t->len;
	}

	ret = mvebu_spi_set_cs(priv, spi->chip_select, spi->mode, false);
	if (ret)
		return ret;

	return ret;
}

static struct of_device_id mvebu_spi_dt_ids[] = {
	{ .compatible = "marvell,orion-spi",
	  .data = (unsigned long)&mvebu_spi_set_baudrate },
#if defined(CONFIG_ARCH_ARMADA_370) || defined(CONFIG_ARCH_ARMADA_XP)
	{ .compatible = "marvell,armada-370-xp-spi",
	  .data = (unsigned long)&armada_370_xp_spi_set_baudrate },
#endif
#if defined(CONFIG_ARCH_DOVE)
	{ .compatible = "marvell,dove-spi",
	  .data = (unsigned long)&dove_spi_set_baudrate },
#endif
	{ }
};

static int mvebu_spi_probe(struct device_d *dev)
{
	struct spi_master *master;
	struct mvebu_spi *priv;
	const struct of_device_id *match;
	int ret = 0;

	match = of_match_node(mvebu_spi_dt_ids, dev->device_node);
	if (!match)
		return -EINVAL;

	priv = xzalloc(sizeof(*priv));
	priv->base = dev_request_mem_region(dev, 0);
	if (!priv->base) {
		ret = -EINVAL;
		goto err_free;
	}
	priv->set_baudrate = (void *)match->data;
	priv->clk = clk_get(dev, NULL);
	if (IS_ERR(priv->clk)) {
		ret = PTR_ERR(priv->clk);
		goto err_free;
	}

	master = &priv->master;
	master->dev = dev;
	master->bus_num = dev->id;
	master->setup = mvebu_spi_setup;
	master->transfer = mvebu_spi_transfer;
	master->num_chipselect = 1;

	ret = spi_register_master(master);
	if (!ret)
		return 0;

err_free:
	free(priv);

	return ret;
}

static struct driver_d mvebu_spi_driver = {
	.name  = "mvebu-spi",
	.probe = mvebu_spi_probe,
	.of_compatible = DRV_OF_COMPAT(mvebu_spi_dt_ids),
};
device_platform_driver(mvebu_spi_driver);
