/*
 * Copyright (c) 2016, Freescale Semiconductor, Inc.
 * Copyright (c) 2017, NXP
 * Copyright (c) 2021, ATL Electronics.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT cypress_psoc6_spi

#define LOG_LEVEL CONFIG_SPI_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(spi_psoc6);

#include <errno.h>
#include <zephyr/drivers/spi.h>
#include <soc.h>

#include "spi_context.h"

#include "cy_syslib.h"
#include "cy_sysclk.h"
#include "cy_scb_spi.h"
#include "cy_sysint.h"

#define SPI_CHIP_SELECT_COUNT		4
#define SPI_MAX_DATA_WIDTH		16
#define SPI_PSOC6_CLK_DIV_NUMBER	1

struct spi_psoc6_config {
	CySCB_Type *base;
	uint32_t periph_id;
	void (*irq_config_func)(const struct device *dev);
	uint32_t num_pins;
	struct soc_gpio_pin pins[];
};

struct spi_psoc6_transfer {
	uint8_t *txData;
	uint8_t *rxData;
	size_t dataSize;
};

struct spi_psoc6_data {
	struct spi_context ctx;
	struct cy_stc_scb_spi_config cfg;
	struct spi_psoc6_transfer xfer;
};

static void spi_psoc6_transfer_next_packet(const struct device *dev)
{
	const struct spi_psoc6_config *config = dev->config;
	struct spi_psoc6_data *data = dev->data;
	struct spi_context *ctx = &data->ctx;
	struct spi_psoc6_transfer *xfer = &data->xfer;
	uint32_t count;

	LOG_DBG("TX L: %d, RX L: %d", ctx->tx_len, ctx->rx_len);

	if ((ctx->tx_len == 0U) && (ctx->rx_len == 0U)) {
		/* nothing left to rx or tx, we're done! */
		xfer->dataSize = 0U;

		spi_context_cs_control(ctx, false);
		spi_context_complete(ctx, dev, 0U);
		return;
	}

	if (ctx->tx_len == 0U) {
		/* rx only, nothing to tx */
		xfer->txData = NULL;
		xfer->rxData = ctx->rx_buf;
		xfer->dataSize = ctx->rx_len;
	} else if (ctx->rx_len == 0U) {
		/* tx only, nothing to rx */
		xfer->txData = (uint8_t *) ctx->tx_buf;
		xfer->rxData = NULL;
		xfer->dataSize = ctx->tx_len;
	} else if (ctx->tx_len == ctx->rx_len) {
		/* rx and tx are the same length */
		xfer->txData = (uint8_t *) ctx->tx_buf;
		xfer->rxData = ctx->rx_buf;
		xfer->dataSize = ctx->tx_len;
	} else if (ctx->tx_len > ctx->rx_len) {
		/* Break up the tx into multiple transfers so we don't have to
		 * rx into a longer intermediate buffer. Leave chip select
		 * active between transfers.
		 */
		xfer->txData = (uint8_t *) ctx->tx_buf;
		xfer->rxData = ctx->rx_buf;
		xfer->dataSize = ctx->rx_len;
	} else {
		/* Break up the rx into multiple transfers so we don't have to
		 * tx from a longer intermediate buffer. Leave chip select
		 * active between transfers.
		 */
		xfer->txData = (uint8_t *) ctx->tx_buf;
		xfer->rxData = ctx->rx_buf;
		xfer->dataSize = ctx->tx_len;
	}

	if (xfer->txData != NULL) {
		if (Cy_SCB_SPI_WriteArray(config->base, xfer->txData,
					  xfer->dataSize) != xfer->dataSize) {
			goto err;
		}
	} else {
		/* Need fill TX fifo with garbage to perform read.
		 * This keeps logic simple and saves stack.
		 * Use 0 as dummy data.
		 */
		for (count = 0U; count < xfer->dataSize; count++) {
			if (Cy_SCB_SPI_Write(config->base, 0U) == 0U) {
				goto err;
			}
		}
	}

	LOG_DBG("TRX L: %d", xfer->dataSize);

	return;
err:
	/* no FIFO available to run the transfer */
	xfer->dataSize = 0U;

	spi_context_cs_control(ctx, false);
	spi_context_complete(ctx, dev, -ENOMEM);
}

static void spi_psoc6_isr(const struct device *dev)
{
	const struct spi_psoc6_config *config = dev->config;
	struct spi_psoc6_data *data = dev->data;

	Cy_SCB_ClearMasterInterrupt(config->base,
				    CY_SCB_MASTER_INTR_SPI_DONE);

	/* extract data from RX FIFO */
	if (data->xfer.rxData != NULL) {
		Cy_SCB_SPI_ReadArray(config->base,
				     data->xfer.rxData,
				     data->xfer.dataSize);
	} else {
		Cy_SCB_ClearRxFifo(config->base);
	}

	/* Set next data block */
	spi_context_update_tx(&data->ctx, 1, data->xfer.dataSize);
	spi_context_update_rx(&data->ctx, 1, data->xfer.dataSize);

	/* Start next block
	 * Since 1 byte at TX FIFO will start transfer data, let's try
	 * minimize ISR recursion disabling all interrupt sources when add
	 * data on TX FIFO
	 */
	Cy_SCB_SetMasterInterruptMask(config->base, 0U);

	spi_psoc6_transfer_next_packet(dev);

	if (data->xfer.dataSize > 0U) {
		Cy_SCB_SetMasterInterruptMask(config->base,
					      CY_SCB_MASTER_INTR_SPI_DONE);
	}
}

static uint32_t spi_psoc6_get_freqdiv(uint32_t frequency)
{
	uint32_t oversample;
	uint32_t bus_freq = 100000000UL;
	/*
	 * TODO: Get PerBusSpeed when clocks are available to PSoC-6.
	 * Currently the bus freq is fixed to 50Mhz and max SPI clk can be
	 * 12.5MHz.
	 */

	for (oversample = 4; oversample < 16; oversample++) {
		if ((bus_freq / oversample) <= frequency) {
			break;
		}
	}

	/* Oversample [4, 16] */
	return oversample;
}

static void spi_psoc6_master_get_defaults(struct cy_stc_scb_spi_config *cfg)
{
	cfg->spiMode = CY_SCB_SPI_MASTER;
	cfg->subMode = CY_SCB_SPI_MOTOROLA;
	cfg->sclkMode = 0U;
	cfg->oversample = 0U;
	cfg->rxDataWidth = 0U;
	cfg->txDataWidth = 0U;
	cfg->enableMsbFirst = false;
	cfg->enableFreeRunSclk = false;
	cfg->enableInputFilter = false;
	cfg->enableMisoLateSample = false;
	cfg->enableTransferSeperation = false;
	cfg->ssPolarity = 0U;
	cfg->enableWakeFromSleep = false;
	cfg->rxFifoTriggerLevel = 0U;
	cfg->rxFifoIntEnableMask = 0U;
	cfg->txFifoTriggerLevel = 0U;
	cfg->txFifoIntEnableMask = 0U;
	cfg->masterSlaveIntEnableMask = 0U;
}

static int spi_psoc6_configure(const struct device *dev,
			       const struct spi_config *spi_cfg)
{
	struct spi_psoc6_data *data = dev->data;
	uint32_t word_size;

	if (spi_context_configured(&data->ctx, spi_cfg)) {
		/* This configuration is already in use */
		return 0;
	}

	if (spi_cfg->operation & SPI_HALF_DUPLEX) {
		LOG_ERR("Half-duplex not supported");
		return -ENOTSUP;
	}

	word_size = SPI_WORD_SIZE_GET(spi_cfg->operation);
	if (word_size > SPI_MAX_DATA_WIDTH) {
		LOG_ERR("Word size %d is greater than %d",
			word_size, SPI_MAX_DATA_WIDTH);
		return -EINVAL;
	}

	if (SPI_OP_MODE_GET(spi_cfg->operation) == SPI_OP_MODE_MASTER) {
		spi_psoc6_master_get_defaults(&data->cfg);

		if (spi_cfg->slave > SPI_CHIP_SELECT_COUNT) {
			LOG_ERR("Slave %d is greater than %d",
				spi_cfg->slave, SPI_CHIP_SELECT_COUNT);
			return -EINVAL;
		}

		data->cfg.rxDataWidth = data->cfg.txDataWidth = word_size;

		if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPHA) {
			if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPOL) {
				data->cfg.sclkMode = CY_SCB_SPI_CPHA1_CPOL1;
			} else {
				data->cfg.sclkMode = CY_SCB_SPI_CPHA1_CPOL0;
			}
		} else {
			if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPOL) {
				data->cfg.sclkMode = CY_SCB_SPI_CPHA0_CPOL1;
			} else {
				data->cfg.sclkMode = CY_SCB_SPI_CPHA0_CPOL0;
			}
		}

		data->cfg.enableMsbFirst = !!!(spi_cfg->operation &
					       SPI_TRANSFER_LSB);
		data->cfg.oversample = spi_psoc6_get_freqdiv(spi_cfg->frequency);

		data->ctx.config = spi_cfg;
	} else {
		/* Slave mode is not implemented yet. */
		return -ENOTSUP;
	}

	return 0;
}

static void spi_psoc6_transceive_sync_loop(const struct device *dev)
{
	const struct spi_psoc6_config *config = dev->config;
	struct spi_psoc6_data *data = dev->data;

	while (data->xfer.dataSize > 0U) {
		while (!Cy_SCB_IsTxComplete(config->base)) {
			;
		}

		if (data->xfer.rxData != NULL) {
			Cy_SCB_SPI_ReadArray(config->base,
					     data->xfer.rxData,
					     data->xfer.dataSize);
		} else {
			Cy_SCB_ClearRxFifo(config->base);
		}

		spi_context_update_tx(&data->ctx, 1, data->xfer.dataSize);
		spi_context_update_rx(&data->ctx, 1, data->xfer.dataSize);

		spi_psoc6_transfer_next_packet(dev);
	}
}

static int spi_psoc6_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 asynchronous,
				spi_callback_t cb,
				void *userdata)
{
	const struct spi_psoc6_config *config = dev->config;
	struct spi_psoc6_data *data = dev->data;
	int ret;

	spi_context_lock(&data->ctx, asynchronous, cb, userdata, spi_cfg);

	LOG_DBG("\n\n");

	ret = spi_psoc6_configure(dev, spi_cfg);
	if (ret) {
		goto out;
	}

	Cy_SCB_SPI_Init(config->base, &data->cfg, NULL);
	Cy_SCB_SPI_SetActiveSlaveSelect(config->base, spi_cfg->slave);
	Cy_SCB_SPI_Enable(config->base);

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

	spi_context_cs_control(&data->ctx, true);

	spi_psoc6_transfer_next_packet(dev);

	if (asynchronous) {
		Cy_SCB_SetMasterInterruptMask(config->base,
					      CY_SCB_MASTER_INTR_SPI_DONE);
	} else {
		spi_psoc6_transceive_sync_loop(dev);
	}

	ret = spi_context_wait_for_completion(&data->ctx);

	Cy_SCB_SPI_Disable(config->base, NULL);

out:
	spi_context_release(&data->ctx, ret);

	return ret;
}

static int spi_psoc6_transceive_sync(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 spi_psoc6_transceive(dev, spi_cfg, tx_bufs,
				    rx_bufs, false, NULL, NULL);
}

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

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

	spi_context_unlock_unconditionally(&data->ctx);

	return 0;
}

static int spi_psoc6_init(const struct device *dev)
{
	int err;
	const struct spi_psoc6_config *config = dev->config;
	struct spi_psoc6_data *data = dev->data;

	soc_gpio_list_configure(config->pins, config->num_pins);

	Cy_SysClk_PeriphAssignDivider(config->periph_id,
				      CY_SYSCLK_DIV_8_BIT,
				      SPI_PSOC6_CLK_DIV_NUMBER);
	Cy_SysClk_PeriphSetDivider(CY_SYSCLK_DIV_8_BIT,
				   SPI_PSOC6_CLK_DIV_NUMBER, 0U);
	Cy_SysClk_PeriphEnableDivider(CY_SYSCLK_DIV_8_BIT,
				      SPI_PSOC6_CLK_DIV_NUMBER);

#ifdef CONFIG_SPI_ASYNC
	config->irq_config_func(dev);
#endif

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

	return spi_psoc6_release(dev, NULL);
}

static const struct spi_driver_api spi_psoc6_driver_api = {
	.transceive = spi_psoc6_transceive_sync,
#ifdef CONFIG_SPI_ASYNC
	.transceive_async = spi_psoc6_transceive_async,
#endif
	.release = spi_psoc6_release,
};

#define SPI_PSOC6_DEVICE_INIT(n)					\
	static void spi_psoc6_spi##n##_irq_cfg(const struct device *port); \
	static const struct spi_psoc6_config spi_psoc6_config_##n = {	\
		.base = (CySCB_Type *)DT_INST_REG_ADDR(n),		\
		.periph_id = DT_INST_PROP(n, peripheral_id),		\
		.num_pins = CY_PSOC6_DT_INST_NUM_PINS(n),		\
		.pins = CY_PSOC6_DT_INST_PINS(n),			\
		.irq_config_func = spi_psoc6_spi##n##_irq_cfg,		\
	};								\
	static struct spi_psoc6_data spi_psoc6_dev_data_##n = {		\
		SPI_CONTEXT_INIT_LOCK(spi_psoc6_dev_data_##n, ctx),	\
		SPI_CONTEXT_INIT_SYNC(spi_psoc6_dev_data_##n, ctx),	\
		SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(n), ctx)	\
	};								\
	DEVICE_DT_INST_DEFINE(n, &spi_psoc6_init, NULL,			\
			      &spi_psoc6_dev_data_##n,			\
			      &spi_psoc6_config_##n, POST_KERNEL,	\
			      CONFIG_SPI_INIT_PRIORITY,			\
			      &spi_psoc6_driver_api);			\
	static void spi_psoc6_spi##n##_irq_cfg(const struct device *port) \
	{								\
		CY_PSOC6_DT_INST_NVIC_INSTALL(n,			\
					      spi_psoc6_isr);		\
	};

DT_INST_FOREACH_STATUS_OKAY(SPI_PSOC6_DEVICE_INIT)
