/*
 * Copyright (c) 2023 Rivos Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(spi_opentitan);

#include "spi_context.h"

#include <zephyr/device.h>
#include <zephyr/drivers/spi.h>
#include <soc.h>
#include <stdbool.h>

/* Register offsets within the SPI host register space. */
#define SPI_HOST_INTR_STATE_REG_OFFSET 0x00
#define SPI_HOST_INTR_ENABLE_REG_OFFSET 0x04
#define SPI_HOST_INTR_TEST_REG_OFFSET 0x08
#define SPI_HOST_ALERT_TEST_REG_OFFSET 0x0c
#define SPI_HOST_CONTROL_REG_OFFSET 0x10
#define SPI_HOST_STATUS_REG_OFFSET 0x14
#define SPI_HOST_CONFIGOPTS_REG_OFFSET 0x18
#define SPI_HOST_CSID_REG_OFFSET 0x1c
#define SPI_HOST_COMMAND_REG_OFFSET 0x20
#define SPI_HOST_RXDATA_REG_OFFSET 0x24
#define SPI_HOST_TXDATA_REG_OFFSET 0x28
#define SPI_HOST_ERROR_ENABLE_REG_OFFSET 0x2c
#define SPI_HOST_ERROR_STATUS_REG_OFFSET 0x30
#define SPI_HOST_EVENT_ENABLE_REG_OFFSET 0x34

/* Control register fields. */
#define SPI_HOST_CONTROL_OUTPUT_EN_BIT BIT(29)
#define SPI_HOST_CONTROL_SW_RST_BIT BIT(30)
#define SPI_HOST_CONTROL_SPIEN_BIT BIT(31)

/* Status register fields. */
#define SPI_HOST_STATUS_TXQD_MASK GENMASK(7, 0)
#define SPI_HOST_STATUS_RXQD_MASK GENMASK(15, 8)
#define SPI_HOST_STATUS_BYTEORDER_BIT BIT(22)
#define SPI_HOST_STATUS_RXEMPTY_BIT BIT(24)
#define SPI_HOST_STATUS_ACTIVE_BIT BIT(30)
#define SPI_HOST_STATUS_READY_BIT BIT(31)

/* Command register fields. */
#define SPI_HOST_COMMAND_LEN_MASK GENMASK(8, 0)
/* "Chip select active after transaction" */
#define SPI_HOST_COMMAND_CSAAT_BIT BIT(9)
#define SPI_HOST_COMMAND_SPEED_MASK GENMASK(11, 10)
#define SPI_HOST_COMMAND_SPEED_STANDARD (0 << 10)
#define SPI_HOST_COMMAND_SPEED_DUAL (1 << 10)
#define SPI_HOST_COMMAND_SPEED_QUAD (2 << 10)
#define SPI_HOST_COMMAND_DIRECTION_MASK GENMASK(13, 12)
#define SPI_HOST_COMMAND_DIRECTION_RX (0x1 << 12)
#define SPI_HOST_COMMAND_DIRECTION_TX (0x2 << 12)
#define SPI_HOST_COMMAND_DIRECTION_BOTH (0x3 << 12)

/* Configopts register fields. */
#define SPI_HOST_CONFIGOPTS_CPHA0_BIT BIT(30)
#define SPI_HOST_CONFIGOPTS_CPOL0_BIT BIT(31)

#define DT_DRV_COMPAT lowrisc_opentitan_spi

struct spi_opentitan_data {
	struct spi_context ctx;
};

struct spi_opentitan_cfg {
	uint32_t base;
	uint32_t f_input;
};

static int spi_config(const struct device *dev, uint32_t frequency,
		uint16_t operation)
{
	const struct spi_opentitan_cfg *cfg = dev->config;

	uint32_t reg;

	if (operation & SPI_HALF_DUPLEX) {
		return -ENOTSUP;
	}

	if (SPI_OP_MODE_GET(operation) != SPI_OP_MODE_MASTER) {
		return -ENOTSUP;
	}

	if (operation & SPI_MODE_LOOP) {
		return -ENOTSUP;
	}

	if (SPI_WORD_SIZE_GET(operation) != 8) {
		return -ENOTSUP;
	}

	if (IS_ENABLED(CONFIG_SPI_EXTENDED_MODES) &&
		(operation & SPI_LINES_MASK) != SPI_LINES_SINGLE) {
		return -ENOTSUP;
	}

	/* Most significant bit always transferred first. */
	if (operation & SPI_TRANSFER_LSB) {
		return -ENOTSUP;
	}

	/* Set the SPI frequency, polarity, and clock phase in CONFIGOPTS register.
	 * Applied divider (divides f_in / 2) is CLKDIV register (16 bit) + 1.
	 */
	reg = cfg->f_input / 2 / frequency;
	if (reg > 0xffffu) {
		reg = 0xffffu;
	} else if (reg > 0) {
		reg--;
	}
	/* Setup phase */
	if (operation & SPI_MODE_CPHA) {
		reg |= SPI_HOST_CONFIGOPTS_CPHA0_BIT;
	}
	/* Setup polarity. */
	if (operation & SPI_MODE_CPOL) {
		reg |= SPI_HOST_CONFIGOPTS_CPOL0_BIT;
	}
	sys_write32(reg, cfg->base + SPI_HOST_CONFIGOPTS_REG_OFFSET);

	return 0;
}

static bool spi_opentitan_rx_available(const struct spi_opentitan_cfg *cfg)
{
	/* Rx bytes are available if Tx FIFO is non-empty. */
	return !(sys_read32(cfg->base + SPI_HOST_STATUS_REG_OFFSET) & SPI_HOST_STATUS_RXEMPTY_BIT);
}

static void spi_opentitan_xfer(const struct device *dev, const bool gpio_cs_control)
{
	const struct spi_opentitan_cfg *cfg = dev->config;
	struct spi_opentitan_data *data = dev->data;
	struct spi_context *ctx = &data->ctx;

	while (spi_context_tx_on(ctx) || spi_context_rx_on(ctx)) {
		const size_t segment_len = MAX(ctx->tx_len, ctx->rx_len);
		uint32_t host_command_reg;

		/* Setup transaction duplex. */
		if (!spi_context_tx_on(ctx)) {
			host_command_reg = SPI_HOST_COMMAND_DIRECTION_RX;
		} else if (!spi_context_rx_on(ctx)) {
			host_command_reg = SPI_HOST_COMMAND_DIRECTION_TX;
		} else {
			host_command_reg = SPI_HOST_COMMAND_DIRECTION_BOTH;
		}

		size_t tx_bytes_to_queue = spi_context_tx_buf_on(ctx) ? ctx->tx_len : 0;

		/* First place Tx bytes in FIFO, packed four to a word. */
		while (tx_bytes_to_queue > 0) {
			uint32_t fifo_word = 0;

			for (int byte = 0; byte < 4; ++byte) {
				if (tx_bytes_to_queue == 0) {
					break;
				}
				fifo_word |= *ctx->tx_buf << (8 * byte);
				spi_context_update_tx(ctx, 1, 1);
				tx_bytes_to_queue--;
			}
			sys_write32(fifo_word, cfg->base + SPI_HOST_TXDATA_REG_OFFSET);
		}

		/* Keep CS asserted if another Tx segment remains or if two more Rx
		 * segments remain (because we will handle one Rx segment after the
		 * forthcoming transaction).
		 */
		if (ctx->tx_count > 0 || ctx->rx_count > 1)  {
			host_command_reg |= SPI_HOST_COMMAND_CSAAT_BIT;
		}
		/* Segment length field holds COMMAND.LEN + 1. */
		host_command_reg |= segment_len - 1;

		/* Issue transaction. */
		sys_write32(host_command_reg, cfg->base + SPI_HOST_COMMAND_REG_OFFSET);

		size_t rx_bytes_to_read = spi_context_rx_buf_on(ctx) ? ctx->rx_len : 0;

		/* Read from Rx FIFO as required. */
		while (rx_bytes_to_read > 0) {
			while (!spi_opentitan_rx_available(cfg)) {
				;
			}
			uint32_t rx_word = sys_read32(cfg->base +
				SPI_HOST_RXDATA_REG_OFFSET);
			for (int byte = 0; byte < 4; ++byte) {
				if (rx_bytes_to_read == 0) {
					break;
				}
				*ctx->rx_buf = (rx_word >> (8 * byte)) & 0xff;
				spi_context_update_rx(ctx, 1, 1);
				rx_bytes_to_read--;
			}
		}
	}

	/* Deassert the CS line if required. */
	if (gpio_cs_control) {
		spi_context_cs_control(ctx, false);
	}

	spi_context_complete(ctx, dev, 0);
}

static int spi_opentitan_init(const struct device *dev)
{
	const struct spi_opentitan_cfg *cfg = dev->config;
	struct spi_opentitan_data *data = dev->data;
	int err;

	/* Place SPI host peripheral in reset and wait for reset to complete. */
	sys_write32(SPI_HOST_CONTROL_SW_RST_BIT,
		cfg->base + SPI_HOST_CONTROL_REG_OFFSET);
	while (sys_read32(cfg->base + SPI_HOST_STATUS_REG_OFFSET)
			& (SPI_HOST_STATUS_ACTIVE_BIT | SPI_HOST_STATUS_TXQD_MASK |
				SPI_HOST_STATUS_RXQD_MASK)) {
		;
	}
	/* Clear reset and enable SPI host peripheral. */
	sys_write32(SPI_HOST_CONTROL_OUTPUT_EN_BIT | SPI_HOST_CONTROL_SPIEN_BIT,
		cfg->base + SPI_HOST_CONTROL_REG_OFFSET);

	err = spi_context_cs_configure_all(&data->ctx);
	if (err < 0) {
		return err;
	}

	/* Make sure the context is unlocked */
	spi_context_unlock_unconditionally(&data->ctx);
	return 0;
}

static int spi_opentitan_transceive(const struct device *dev,
			  const struct spi_config *config,
			  const struct spi_buf_set *tx_bufs,
			  const struct spi_buf_set *rx_bufs)
{
	int rc = 0;
	bool gpio_cs_control = false;
	struct spi_opentitan_data *data = dev->data;

	/* Lock the SPI Context */
	spi_context_lock(&data->ctx, false, NULL, NULL, config);

	/* Configure the SPI bus */
	data->ctx.config = config;
	rc = spi_config(dev, config->frequency, config->operation);
	if (rc < 0) {
		spi_context_release(&data->ctx, rc);
		return rc;
	}

	spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, 1);

	/* Assert the CS line. HW will always assert the CS pin identified by CSID
	 * (default CSID: 0), so GPIO CS control will work in addition to HW
	 * asserted (and presumably ignored) CS.
	 */
	if (config->cs) {
		gpio_cs_control = true;
		spi_context_cs_control(&data->ctx, true);
	}

	/* Perform transfer */
	spi_opentitan_xfer(dev, gpio_cs_control);

	rc = spi_context_wait_for_completion(&data->ctx);

	spi_context_release(&data->ctx, rc);

	return rc;
}

#ifdef CONFIG_SPI_ASYNC
static int spi_opentitan_transceive_async(const struct device *dev,
					const struct spi_config *spi_cfg,
					const struct spi_buf_set *tx_bufs,
					const struct spi_buf_set *rx_bufs,
					spi_callback_t cb,
					void *userdata)
{
	return -ENOTSUP;
}
#endif

static int spi_opentitan_release(const struct device *dev,
			   const struct spi_config *config)
{
	struct spi_opentitan_data *data = dev->data;

	spi_context_unlock_unconditionally(&data->ctx);
	return 0;
}

/* Device Instantiation */

static const struct spi_driver_api spi_opentitan_api = {
	.transceive = spi_opentitan_transceive,
#ifdef CONFIG_SPI_ASYNC
	.transceive_async = spi_opentitan_transceive_async,
#endif
	.release = spi_opentitan_release,
};

#define SPI_INIT(n) \
	static struct spi_opentitan_data spi_opentitan_data_##n = { \
		SPI_CONTEXT_INIT_LOCK(spi_opentitan_data_##n, ctx), \
		SPI_CONTEXT_INIT_SYNC(spi_opentitan_data_##n, ctx), \
		SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(n), ctx)	\
	}; \
	static struct spi_opentitan_cfg spi_opentitan_cfg_##n = { \
		.base = DT_INST_REG_ADDR(n), \
		.f_input = DT_INST_PROP(n, clock_frequency), \
	}; \
	DEVICE_DT_INST_DEFINE(n, \
			spi_opentitan_init, \
			NULL, \
			&spi_opentitan_data_##n, \
			&spi_opentitan_cfg_##n, \
			POST_KERNEL, \
			CONFIG_SPI_INIT_PRIORITY, \
			&spi_opentitan_api);

DT_INST_FOREACH_STATUS_OKAY(SPI_INIT)
