/* SPDX-License-Identifier: GPL-2.0-only */

/*
 * Derived from Cavium's BSD-3 Clause OCTEONTX-SDK-6.2.0.
 */

#include <device/mmio.h>
#include <assert.h>
#include <console/console.h>
#include <endian.h>
#include <soc/addressmap.h>
#include <soc/spi.h>
#include <soc/clock.h>
#include <spi-generic.h>
#include <spi_flash.h>
#include <timer.h>

union cavium_spi_cfg {
	u64 u;
	struct {
		u64 enable	: 1;
		u64 idlelow	: 1;
		u64 clk_cont	: 1;
		u64 wireor	: 1;
		u64 lsbfirst	: 1;
		u64		: 2;
		u64 cshi	: 1;
		u64 idleclks	: 2;
		u64 tristate	: 1;
		u64 cslate	: 1;
		u64 csena	: 4; /* Must be one */
		u64 clkdiv	: 13;
		u64		: 35;
	} s;
};

union cavium_spi_sts {
	u64 u;
	struct {
		u64 busy	: 1;
		u64 mpi_intr	: 1;
		u64		: 6;
		u64 rxnum	: 5;
		u64		: 51;
	} s;
};

union cavium_spi_tx {
	u64 u;
	struct {
		u64 totnum	: 5;
		u64		: 3;
		u64 txnum	: 5;
		u64		: 3;
		u64 leavecs	: 1;
		u64		: 3;
		u64 csid	: 2;
		u64		: 42;
	} s;
};

struct cavium_spi {
	union cavium_spi_cfg cfg;
	union cavium_spi_sts sts;
	union cavium_spi_tx tx;
	u64	rsvd1;
	u64	sts_w1s;
	u64	rsvd2;
	u64	int_ena_w1c;
	u64	int_ena_w1s;
	u64	wide_dat;
	u8	rsvd4[0x38];
	u64	dat[8];
};

check_member(cavium_spi, cfg, 0);
check_member(cavium_spi, sts, 0x8);
check_member(cavium_spi, tx, 0x10);
check_member(cavium_spi, dat[7], 0xb8);

struct cavium_spi_slave {
	struct cavium_spi *regs;
	int cs;
};

#define SPI_TIMEOUT_US	5000

static struct cavium_spi_slave cavium_spi_slaves[] = {
	{
	 .regs = (struct cavium_spi *)MPI_PF_BAR0,
	 .cs = 0,
	},
};

static struct cavium_spi_slave *to_cavium_spi(const struct spi_slave *slave)
{
	assert(slave->bus < ARRAY_SIZE(cavium_spi_slaves));
	return &cavium_spi_slaves[slave->bus];
}

/**
 * Enable the SPI controller. Pins are driven.
 *
 * @param bus                 The SPI bus to operate on
 */
void spi_enable(const size_t bus)
{
	union cavium_spi_cfg cfg;

	assert(bus < ARRAY_SIZE(cavium_spi_slaves));
	if (bus >= ARRAY_SIZE(cavium_spi_slaves))
		return;

	struct cavium_spi *regs = cavium_spi_slaves[bus].regs;

	cfg.u = read64(&regs->cfg);
	cfg.s.csena = 0xf;
	cfg.s.enable = 1;
	write64(&regs->cfg, cfg.u);
}

/**
 * Disable the SPI controller. Pins are tristated.
 *
 * @param bus                 The SPI bus to operate on
 */
void spi_disable(const size_t bus)
{
	union cavium_spi_cfg cfg;

	assert(bus < ARRAY_SIZE(cavium_spi_slaves));
	if (bus >= ARRAY_SIZE(cavium_spi_slaves))
		return;

	struct cavium_spi *regs = cavium_spi_slaves[bus].regs;

	cfg.u = read64(&regs->cfg);
	cfg.s.csena = 0xf;
	cfg.s.enable = 0;
	write64(&regs->cfg, cfg.u);
}

/**
 * Set SPI Chip select line and level if asserted.
 *
 * @param bus                 The SPI bus to operate on
 * @param chip_select         The chip select pin to use (0 - 3)
 * @param assert_is_low       CS pin state is low when asserted
 */
void spi_set_cs(const size_t bus,
		const size_t chip_select,
		const size_t assert_is_low)
{
	union cavium_spi_cfg cfg;

	assert(bus < ARRAY_SIZE(cavium_spi_slaves));
	if (bus >= ARRAY_SIZE(cavium_spi_slaves))
		return;

	cavium_spi_slaves[bus].cs = chip_select & 0x3;
	struct cavium_spi *regs = cavium_spi_slaves[bus].regs;

	cfg.u = read64(&regs->cfg);
	cfg.s.csena = 0xf;
	cfg.s.cshi = !assert_is_low;
	write64(&regs->cfg, cfg.u);

	//FIXME: CS2/3: Change pin mux here
}

/**
 * Set SPI clock frequency.
 *
 * @param bus                 The SPI bus to operate on
 * @param speed_hz            The SPI frequency in Hz
 * @param idle_low            The SPI clock idles low
 * @param idle_cycles         Number of CLK cycles between two commands (0 - 3)

 */
void spi_set_clock(const size_t bus,
		   const size_t speed_hz,
		   const size_t idle_low,
		   const size_t idle_cycles)
{
	union cavium_spi_cfg cfg;

	assert(bus < ARRAY_SIZE(cavium_spi_slaves));
	if (bus >= ARRAY_SIZE(cavium_spi_slaves))
		return;

	struct cavium_spi *regs = cavium_spi_slaves[bus].regs;
	const uint64_t sclk = thunderx_get_io_clock();

	cfg.u = read64(&regs->cfg);
	cfg.s.csena = 0xf;
	cfg.s.clk_cont = 0;
	cfg.s.idlelow = !!idle_low;
	cfg.s.idleclks = idle_cycles & 0x3;
	cfg.s.clkdiv = MIN(sclk / (2ULL * speed_hz), 0x1fff);
	write64(&regs->cfg, cfg.u);

	printk(BIOS_DEBUG, "SPI: set clock to %lld kHz\n",
	       (sclk / (2ULL * cfg.s.clkdiv)) >> 10);
}

/**
 * Get current SPI clock frequency in Hz.
 *
 * @param bus                 The SPI bus to operate on
 */
uint64_t spi_get_clock(const size_t bus)
{
	union cavium_spi_cfg cfg;

	assert(bus < ARRAY_SIZE(cavium_spi_slaves));
	if (bus >= ARRAY_SIZE(cavium_spi_slaves))
		return 0;

	struct cavium_spi *regs = cavium_spi_slaves[bus].regs;
	const uint64_t sclk = thunderx_get_io_clock();

	cfg.u = read64(&regs->cfg);

	return (sclk / (2ULL * cfg.s.clkdiv));
}

/**
 * Set SPI LSB/MSB first.
 *
 * @param bus                 The SPI bus to operate on
 * @param lsb_first           The SPI operates LSB first
 *
 */
void spi_set_lsbmsb(const size_t bus, const size_t lsb_first)
{
	union cavium_spi_cfg cfg;

	assert(bus < ARRAY_SIZE(cavium_spi_slaves));
	if (bus >= ARRAY_SIZE(cavium_spi_slaves))
		return;

	struct cavium_spi *regs = cavium_spi_slaves[bus].regs;

	cfg.u = read64(&regs->cfg);
	cfg.s.csena = 0xf;
	cfg.s.lsbfirst = !!lsb_first;
	write64(&regs->cfg, cfg.u);
}

/**
 * Init SPI with custom parameters and enable SPI controller.
 *
 * @param bus                 The SPI bus to operate on
 * @param speed_hz            The SPI frequency in Hz
 * @param idle_low            The SPI clock idles low
 * @param idle_cycles         Number of CLK cycles between two commands (0 - 3)
 * @param lsb_first           The SPI operates LSB first
 * @param chip_select         The chip select pin to use (0 - 3)
 * @param assert_is_low       CS pin state is low when asserted
 */
void spi_init_custom(const size_t bus,
		     const size_t speed_hz,
		     const size_t idle_low,
		     const size_t idle_cycles,
		     const size_t lsb_first,
		     const size_t chip_select,
		     const size_t assert_is_low)
{
	spi_disable(bus);
	spi_set_clock(bus, speed_hz, idle_low, idle_cycles);
	spi_set_lsbmsb(bus, lsb_first);
	spi_set_cs(bus, chip_select, assert_is_low);
	spi_enable(bus);
}

/**
 * Init all SPI controllers with default values and enable all SPI controller.
 *
 */
void spi_init(void)
{
	for (size_t i = 0; i < ARRAY_SIZE(cavium_spi_slaves); i++) {
		spi_disable(i);
		spi_set_clock(i, 12500000, 0, 0);
		spi_set_lsbmsb(i, 0);
		spi_set_cs(i, 0, 1);
		spi_enable(i);
	}
}

static int cavium_spi_wait(struct cavium_spi *regs)
{
	struct stopwatch sw;
	union cavium_spi_sts sts;

	stopwatch_init_usecs_expire(&sw, SPI_TIMEOUT_US);
	do {
		sts.u = read64(&regs->sts);
		if (!sts.s.busy)
			return 0;
	} while (!stopwatch_expired(&sw));
	printk(BIOS_DEBUG, "SPI: Timed out after %uus\n", SPI_TIMEOUT_US);
	return -1;
}

static int do_xfer(const struct spi_slave *slave, struct spi_op *vector,
		int leavecs)
{
	struct cavium_spi *regs = to_cavium_spi(slave)->regs;
	uint8_t *out_buf = (uint8_t *)vector->dout;
	size_t bytesout = vector->bytesout;
	uint8_t *in_buf = (uint8_t *)vector->din;
	size_t bytesin = vector->bytesin;
	union cavium_spi_sts sts;
	union cavium_spi_tx tx;

	/**
	 * The CN81xx SPI controller is half-duplex and has 8 data registers.
	 * If >8 bytes remain in the transfer then we must set LEAVECS = 1 so
	 * that the /CS remains asserted. Once <=8 bytes remain we must set
	 * LEAVECS = 0 so that /CS is de-asserted, thus completing the transfer.
	 */
	while (bytesout) {
		size_t out_now = MIN(bytesout, 8);
		unsigned int i;

		for (i = 0; i < out_now; i++)
			write64(&regs->dat[i], out_buf[i] & 0xff);

		tx.u = 0;
		tx.s.csid = to_cavium_spi(slave)->cs;
		if (leavecs || ((bytesout > 8) || bytesin))
			tx.s.leavecs = 1;
		/* number of bytes to transmit goes in both TXNUM and TOTNUM */
		tx.s.totnum = out_now;
		tx.s.txnum = out_now;
		write64(&regs->tx, tx.u);

		/* check status */
		if (cavium_spi_wait(regs) < 0)
			return -1;

		bytesout -= out_now;
		out_buf += out_now;
	}

	while (bytesin) {
		size_t in_now = MIN(bytesin, 8);
		unsigned int i;

		tx.u = 0;
		tx.s.csid = to_cavium_spi(slave)->cs;
		if (leavecs || (bytesin > 8))
			tx.s.leavecs = 1;
		tx.s.totnum = in_now;
		write64(&regs->tx, tx.u);

		/* check status */
		if (cavium_spi_wait(regs) < 0)
			return -1;

		sts.u = read64(&regs->sts);
		if (sts.s.rxnum != in_now) {
			printk(BIOS_ERR,
			       "SPI: Incorrect number of bytes received: %u.\n",
			       sts.s.rxnum);
			return -1;
		}

		for (i = 0; i < in_now; i++) {
			*in_buf = (uint8_t)((read64(&regs->dat[i]) & 0xff));
			in_buf++;
		}
		bytesin -= in_now;
	}

	return 0;
}

static int spi_ctrlr_xfer_vector(const struct spi_slave *slave,
		struct spi_op vectors[], size_t count)
{
	int i;

	for (i = 0; i < count; i++) {
		if (do_xfer(slave, &vectors[i], count - 1 == i ? 0 : 1)) {
			printk(BIOS_ERR,
			       "SPI: Failed to transfer %zu vectors.\n", count);
			return -1;
		}
	}

	return 0;
}
static const struct spi_ctrlr spi_ctrlr = {

	.xfer_vector = spi_ctrlr_xfer_vector,
	.max_xfer_size = SPI_CTRLR_DEFAULT_MAX_XFER_SIZE,
};

const struct spi_ctrlr_buses spi_ctrlr_bus_map[] = {
	{
		.ctrlr = &spi_ctrlr,
		.bus_start = 0,
		.bus_end = ARRAY_SIZE(cavium_spi_slaves) - 1,
	},
};
const size_t spi_ctrlr_bus_map_count = ARRAY_SIZE(spi_ctrlr_bus_map);