/*
 * Copyright (c) 2022 Microchip Technology Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT microchip_mpfs_spi

#include <zephyr/device.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/sys/sys_io.h>
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
#include <zephyr/irq.h>

LOG_MODULE_REGISTER(mss_spi, CONFIG_SPI_LOG_LEVEL);

#include "spi_context.h"

/* MSS SPI Register offsets */
#define MSS_SPI_REG_CONTROL     (0x00)
#define MSS_SPI_REG_TXRXDF_SIZE (0x04)
#define MSS_SPI_REG_STATUS      (0x08)
#define MSS_SPI_REG_INT_CLEAR   (0x0c)
#define MSS_SPI_REG_RX_DATA     (0x10)
#define MSS_SPI_REG_TX_DATA     (0x14)
#define MSS_SPI_REG_CLK_GEN     (0x18)
#define MSS_SPI_REG_SS          (0x1c)
#define MSS_SPI_REG_MIS         (0x20)
#define MSS_SPI_REG_RIS         (0x24)
#define MSS_SPI_REG_CONTROL2    (0x28)
#define MSS_SPI_REG_COMMAND     (0x2c)
#define MSS_SPI_REG_PKTSIZE     (0x30)
#define MSS_SPI_REG_CMD_SIZE    (0x34)
#define MSS_SPI_REG_HWSTATUS    (0x38)
#define MSS_SPI_REG_FRAMESUP    (0x50)

/* SPICR bit definitions */
#define MSS_SPI_CONTROL_ENABLE       BIT(0)
#define MSS_SPI_CONTROL_MASTER       BIT(1)
#define MSS_SPI_CONTROL_PROTO_MSK    BIT(2)
#define MSS_SPI_CONTROL_PROTO_MOTO   (0 << 2)
#define MSS_SPI_CONTROL_RX_DATA_INT  BIT(4)
#define MSS_SPI_CONTROL_TX_DATA_INT  BIT(5)
#define MSS_SPI_CONTROL_RX_OVER_INT  BIT(6)
#define MSS_SPI_CONTROL_TX_UNDER_INT BIT(7)
#define MSS_SPI_CONTROL_CNT_MSK      (0xffff << 8)
#define MSS_SPI_CONTROL_CNT_SHF      (8)
#define MSS_SPI_CONTROL_SPO          BIT(24)
#define MSS_SPI_CONTROL_SPH          BIT(25)
#define MSS_SPI_CONTROL_SPS          BIT(26)
#define MSS_SPI_CONTROL_FRAMEURUN    BIT(27)
#define MSS_SPI_CONTROL_CLKMODE      BIT(28)
#define MSS_SPI_CONTROL_BIGFIFO      BIT(29)
#define MSS_SPI_CONTROL_OENOFF       BIT(30)
#define MSS_SPI_CONTROL_RESET        BIT(31)

/* SPIFRAMESIZE bit definitions */
#define MSS_SPI_FRAMESIZE_DEFAULT (8)

/* SPISS bit definitions */
#define MSS_SPI_SSEL_MASK (0xff)
#define MSS_SPI_DIRECT    (0x100)
#define MSS_SPI_SSELOUT   (0x200)
#define MSS_SPI_MIN_SLAVE (0)
#define MSS_SPI_MAX_SLAVE (7)

/* SPIST bit definitions */
#define MSS_SPI_STATUS_ACTIVE                 BIT(14)
#define MSS_SPI_STATUS_SSEL                   BIT(13)
#define MSS_SPI_STATUS_FRAMESTART             BIT(12)
#define MSS_SPI_STATUS_TXFIFO_EMPTY_NEXT_READ BIT(11)
#define MSS_SPI_STATUS_TXFIFO_EMPTY           BIT(10)
#define MSS_SPI_STATUS_TXFIFO_FULL_NEXT_WRITE BIT(9)
#define MSS_SPI_STATUS_TXFIFO_FULL            BIT(8)
#define MSS_SPI_STATUS_RXFIFO_EMPTY_NEXT_READ BIT(7)
#define MSS_SPI_STATUS_RXFIFO_EMPTY           BIT(6)
#define MSS_SPI_STATUS_RXFIFO_FULL_NEXT_WRITE BIT(5)
#define MSS_SPI_STATUS_RXFIFO_FULL            BIT(4)
#define MSS_SPI_STATUS_TX_UNDERRUN            BIT(3)
#define MSS_SPI_STATUS_RX_OVERFLOW            BIT(2)
#define MSS_SPI_STATUS_RXDAT_RCED             BIT(1)
#define MSS_SPI_STATUS_TXDAT_SENT             BIT(0)

/* SPIINT register defines */
#define MSS_SPI_INT_TXDONE       BIT(0)
#define MSS_SPI_INT_RXRDY        BIT(1)
#define MSS_SPI_INT_RX_CH_OVRFLW BIT(2)
#define MSS_SPI_INT_TX_CH_UNDRUN BIT(3)
#define MSS_SPI_INT_CMD          BIT(4)
#define MSS_SPI_INT_SSEND        BIT(5)

/* SPICOMMAND bit definitions */
#define MSS_SPI_COMMAND_FIFO_MASK (0xC)

/* SPIFRAMESUP bit definitions */
#define MSS_SPI_FRAMESUP_UP_BYTES_MSK (0xFFFF << 16)
#define MSS_SPI_FRAMESUP_LO_BYTES_MSK (0xFFFF << 0)

struct mss_spi_config {
	mm_reg_t base;
	uint8_t clk_gen;
	int clock_freq;
};

struct mss_spi_transfer {
	uint32_t rx_len;
	uint32_t control;
};

struct mss_spi_data {
	struct spi_context ctx;
	struct mss_spi_transfer xfer;
};

static inline uint32_t mss_spi_read(const struct mss_spi_config *cfg, mm_reg_t offset)
{
	return sys_read32(cfg->base + offset);
}

static inline void mss_spi_write(const struct mss_spi_config *cfg, mm_reg_t offset, uint32_t val)
{
	sys_write32(val, cfg->base + offset);
}

static inline void mss_spi_hw_tfsz_set(const struct mss_spi_config *cfg, int len)
{
	uint32_t control;

	mss_spi_write(cfg, MSS_SPI_REG_FRAMESUP, (len & MSS_SPI_FRAMESUP_UP_BYTES_MSK));
	control = mss_spi_read(cfg, MSS_SPI_REG_CONTROL);
	control &= ~MSS_SPI_CONTROL_CNT_MSK;
	control |= ((len & MSS_SPI_FRAMESUP_LO_BYTES_MSK) << MSS_SPI_CONTROL_CNT_SHF);
	mss_spi_write(cfg, MSS_SPI_REG_CONTROL, control);
}

static inline void mss_spi_enable_controller(const struct mss_spi_config *cfg)
{
	uint32_t control;

	control = mss_spi_read(cfg, MSS_SPI_REG_CONTROL);
	control |= MSS_SPI_CONTROL_ENABLE;
	mss_spi_write(cfg, MSS_SPI_REG_CONTROL, control);
}

static inline void mss_spi_disable_controller(const struct mss_spi_config *cfg)
{
	uint32_t control;

	control = mss_spi_read(cfg, MSS_SPI_REG_CONTROL);
	control &= ~MSS_SPI_CONTROL_ENABLE;
	mss_spi_write(cfg, MSS_SPI_REG_CONTROL, control);
}

static void mss_spi_enable_ints(const struct mss_spi_config *cfg)
{
	uint32_t control;
	uint32_t mask = MSS_SPI_CONTROL_RX_DATA_INT | MSS_SPI_CONTROL_TX_DATA_INT |
			MSS_SPI_CONTROL_RX_OVER_INT | MSS_SPI_CONTROL_TX_UNDER_INT;

	control = mss_spi_read(cfg, MSS_SPI_REG_CONTROL);
	control |= mask;
	mss_spi_write(cfg, MSS_SPI_REG_CONTROL, control);
}

static void mss_spi_disable_ints(const struct mss_spi_config *cfg)
{
	uint32_t control;
	uint32_t mask = MSS_SPI_CONTROL_RX_DATA_INT | MSS_SPI_CONTROL_TX_DATA_INT |
			MSS_SPI_CONTROL_RX_OVER_INT | MSS_SPI_CONTROL_TX_UNDER_INT;

	mask = ~mask;
	control = mss_spi_read(cfg, MSS_SPI_REG_CONTROL);
	control &= ~mask;
	mss_spi_write(cfg, MSS_SPI_REG_CONTROL, control);
}

static inline void mss_spi_readwr_fifo(const struct device *dev)
{
	const struct mss_spi_config *cfg = dev->config;
	struct mss_spi_data *data = dev->data;
	struct spi_context *ctx = &data->ctx;
	struct mss_spi_transfer *xfer = &data->xfer;
	uint32_t rx_raw = 0, rd_byte_size, tr_len;
	uint32_t data8, transfer_idx = 0;
	int count;

	tr_len = spi_context_longest_current_buf(ctx);
	count = spi_context_total_tx_len(ctx);
	if (ctx->rx_buf) {
		rd_byte_size = count - tr_len;
	} else {
		rd_byte_size = 0;
	}
	mss_spi_hw_tfsz_set(cfg, count);

	mss_spi_enable_ints(cfg);
	spi_context_update_rx(ctx, 1, xfer->rx_len);
	while (transfer_idx < count) {
		if (!(mss_spi_read(cfg, MSS_SPI_REG_STATUS) & MSS_SPI_STATUS_RXFIFO_EMPTY)) {
			rx_raw = mss_spi_read(cfg, MSS_SPI_REG_RX_DATA);
			if (transfer_idx >= tr_len) {
				if (spi_context_rx_buf_on(ctx)) {
					UNALIGNED_PUT(rx_raw, (uint8_t *)ctx->rx_buf);
					spi_context_update_rx(ctx, 1, 1);
				}
			}
			++transfer_idx;
		}

		if (!(mss_spi_read(cfg, MSS_SPI_REG_STATUS) & MSS_SPI_STATUS_TXFIFO_FULL)) {
			if (spi_context_tx_buf_on(ctx)) {
				data8 = ctx->tx_buf[0];
				mss_spi_write(cfg, MSS_SPI_REG_TX_DATA, data8);
				spi_context_update_tx(ctx, 1, 1);
			} else {
				mss_spi_write(cfg, MSS_SPI_REG_TX_DATA, 0x0);
			}
		}
	}
}

static inline int mss_spi_select_slave(const struct mss_spi_config *cfg, int cs)
{
	uint32_t slave;
	uint32_t reg = mss_spi_read(cfg, MSS_SPI_REG_SS);

	slave = (cs >= MSS_SPI_MIN_SLAVE && cs <= MSS_SPI_MAX_SLAVE) ? (1 << cs) : 0;
	reg &= ~MSS_SPI_SSEL_MASK;
	reg |= slave;

	mss_spi_write(cfg, MSS_SPI_REG_SS, reg);

	return 0;
}

static inline void mss_spi_activate_cs(struct mss_spi_config *cfg)
{
	uint32_t reg = mss_spi_read(cfg, MSS_SPI_REG_SS);

	reg |= MSS_SPI_SSELOUT;
	mss_spi_write(cfg, MSS_SPI_REG_SS, reg);
}

static inline void mss_spi_deactivate_cs(const struct mss_spi_config *cfg)
{
	uint32_t reg = mss_spi_read(cfg, MSS_SPI_REG_SS);

	reg &= ~MSS_SPI_SSELOUT;
	mss_spi_write(cfg, MSS_SPI_REG_SS, reg);
}

static inline int mss_spi_clk_gen_set(const struct mss_spi_config *cfg,
				      const struct spi_config *spi_cfg)
{
	uint32_t idx, clkrate, val = 0, speed;

	if (spi_cfg->frequency > cfg->clock_freq) {
		speed = cfg->clock_freq / 2;
	}

	for (idx = 1; idx < 16; idx++) {
		clkrate = cfg->clock_freq / (2 * idx);
		if (clkrate <= spi_cfg->frequency) {
			val = idx;
			break;
		}
	}

	mss_spi_write(cfg, MSS_SPI_REG_CLK_GEN, val);

	return 0;
}

static inline int mss_spi_hw_mode_set(const struct mss_spi_config *cfg, unsigned int mode)
{
	uint32_t control = mss_spi_read(cfg, MSS_SPI_REG_CONTROL);

	/* set the mode */
	if (mode & SPI_MODE_CPHA) {
		control |= MSS_SPI_CONTROL_SPH;
	} else {
		control &= ~MSS_SPI_CONTROL_SPH;
	}

	if (mode & SPI_MODE_CPOL) {
		control |= MSS_SPI_CONTROL_SPO;
	} else {
		control &= ~MSS_SPI_CONTROL_SPO;
	}

	mss_spi_write(cfg, MSS_SPI_REG_CONTROL, control);

	return 0;
}

static void mss_spi_interrupt(const struct device *dev)
{
	const struct mss_spi_config *cfg = dev->config;
	struct mss_spi_data *data = dev->data;
	struct spi_context *ctx = &data->ctx;
	int intfield = mss_spi_read(cfg, MSS_SPI_REG_MIS) & 0xf;

	if (intfield == 0) {
		return;
	}

	mss_spi_write(cfg, MSS_SPI_REG_INT_CLEAR, intfield);
	spi_context_complete(ctx, dev, 0);
}

static int mss_spi_release(const struct device *dev, const struct spi_config *config)
{
	const struct mss_spi_config *cfg = dev->config;
	struct mss_spi_data *data = dev->data;

	mss_spi_disable_ints(cfg);

	/* release kernel resources */
	spi_context_unlock_unconditionally(&data->ctx);
	mss_spi_disable_controller(cfg);

	return 0;
}

static int mss_spi_configure(const struct device *dev, const struct spi_config *spi_cfg)
{
	const struct mss_spi_config *cfg = dev->config;
	struct mss_spi_data *data = dev->data;
	struct spi_context *ctx = &data->ctx;
	struct mss_spi_transfer *xfer = &data->xfer;
	uint32_t control;

	if (spi_cfg->operation & (SPI_TRANSFER_LSB | SPI_OP_MODE_SLAVE | SPI_MODE_LOOP)) {
		LOG_WRN("not supported operation\n\r");
		return -ENOTSUP;
	}

	if (SPI_WORD_SIZE_GET(spi_cfg->operation) != MSS_SPI_FRAMESIZE_DEFAULT) {
		return -ENOTSUP;
	}

	ctx->config = spi_cfg;
	mss_spi_select_slave(cfg, spi_cfg->slave);
	control = mss_spi_read(cfg, MSS_SPI_REG_CONTROL);

	/*
	 * Fill up the default values
	 * Slave select behaviour set
	 * Fifo depth greater than 4 frames
	 * Methodology to calculate SPI Clock:
	 *	0:	SPICLK = 1 / (2 CLK_GEN + 1) , CLK_GEN is from 0 to 15
	 *	1:	SPICLK = 1 / (2 * (CLK_GEN + 1)) , CLK_GEN is from 0 to 255
	 */

	mss_spi_write(cfg, MSS_SPI_REG_CONTROL, xfer->control);

	if (mss_spi_clk_gen_set(cfg, spi_cfg)) {
		LOG_ERR("can't set clk divider\n");
		return -EINVAL;
	}

	mss_spi_hw_mode_set(cfg, spi_cfg->operation);
	mss_spi_write(cfg, MSS_SPI_REG_TXRXDF_SIZE, MSS_SPI_FRAMESIZE_DEFAULT);
	mss_spi_enable_controller(cfg);
	mss_spi_write(cfg, MSS_SPI_REG_COMMAND, MSS_SPI_COMMAND_FIFO_MASK);

	return 0;
}

static int mss_spi_transceive(const struct device *dev, const struct spi_config *spi_cfg,
			      const struct spi_buf_set *tx_bufs, const struct spi_buf_set *rx_bufs,
			      bool async, spi_callback_t cb, void *userdata)
{

	const struct mss_spi_config *config = dev->config;
	struct mss_spi_data *data = dev->data;
	struct spi_context *ctx = &data->ctx;
	struct mss_spi_transfer *xfer = &data->xfer;
	int ret = 0;

	spi_context_lock(ctx, async, cb, userdata, spi_cfg);

	ret = mss_spi_configure(dev, spi_cfg);
	if (ret) {
		LOG_ERR("Fail to configure\n\r");
		goto out;
	}

	spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, 1);
	xfer->rx_len = ctx->rx_len;
	mss_spi_readwr_fifo(dev);
	ret = spi_context_wait_for_completion(ctx);
out:
	spi_context_release(ctx, ret);
	mss_spi_disable_ints(config);
	mss_spi_disable_controller(config);

	return ret;
}

static int mss_spi_transceive_blocking(const struct device *dev, const struct spi_config *spi_cfg,
				       const struct spi_buf_set *tx_bufs,
				       const struct spi_buf_set *rx_bufs)
{

	return mss_spi_transceive(dev, spi_cfg, tx_bufs, rx_bufs, false, NULL, NULL);
}

#ifdef CONFIG_SPI_ASYNC
static int mss_spi_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 mss_spi_transceive(dev, spi_cfg, tx_bufs, rx_bufs, true, cb, userdata);
}
#endif /* CONFIG_SPI_ASYNC */

static int mss_spi_init(const struct device *dev)
{
	const struct mss_spi_config *cfg = dev->config;
	struct mss_spi_data *data = dev->data;
	struct mss_spi_transfer *xfer = &data->xfer;
	int ret = 0;
	uint32_t control = 0;

	/* Remove SPI from Reset  */
	control = mss_spi_read(cfg, MSS_SPI_REG_CONTROL);
	control &= ~MSS_SPI_CONTROL_RESET;
	mss_spi_write(cfg, MSS_SPI_REG_CONTROL, control);

	/* Set master mode */
	mss_spi_disable_controller(cfg);
	xfer->control = (MSS_SPI_CONTROL_SPS | MSS_SPI_CONTROL_BIGFIFO | MSS_SPI_CONTROL_MASTER |
			 MSS_SPI_CONTROL_CLKMODE);

	spi_context_unlock_unconditionally(&data->ctx);

	return ret;
}

#define MICROCHIP_SPI_PM_OPS (NULL)

static const struct spi_driver_api mss_spi_driver_api = {
	.transceive = mss_spi_transceive_blocking,
#ifdef CONFIG_SPI_ASYNC
	.transceive_async = mss_spi_transceive_async,
#endif /* CONFIG_SPI_ASYNC */
	.release = mss_spi_release,
};

#define MSS_SPI_INIT(n)                                                                            \
	static int mss_spi_init_##n(const struct device *dev)                                      \
	{                                                                                          \
		mss_spi_init(dev);                                                                 \
                                                                                                   \
		IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), mss_spi_interrupt,          \
			    DEVICE_DT_INST_GET(n), 0);                                             \
                                                                                                   \
		irq_enable(DT_INST_IRQN(n));                                                       \
                                                                                                   \
		return 0;                                                                          \
	}                                                                                          \
                                                                                                   \
	static const struct mss_spi_config mss_spi_config_##n = {                                  \
		.base = DT_INST_REG_ADDR(n),                                                       \
		.clock_freq = DT_INST_PROP(n, clock_frequency),                                    \
	};                                                                                         \
                                                                                                   \
	static struct mss_spi_data mss_spi_data_##n = {                                            \
		SPI_CONTEXT_INIT_LOCK(mss_spi_data_##n, ctx),                                      \
		SPI_CONTEXT_INIT_SYNC(mss_spi_data_##n, ctx),                                      \
	};                                                                                         \
                                                                                                   \
	DEVICE_DT_INST_DEFINE(n, mss_spi_init_##n, NULL, &mss_spi_data_##n, &mss_spi_config_##n,   \
			      POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,                     \
			      &mss_spi_driver_api);

DT_INST_FOREACH_STATUS_OKAY(MSS_SPI_INIT)
