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

#include <linux/completion.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>

#define SNAFCFR 0x00
#define   SNAFCFR_DMA_IE BIT(20)
#define SNAFCCR 0x04
#define SNAFWCMR 0x08
#define SNAFRCMR 0x0c
#define SNAFRDR 0x10
#define SNAFWDR 0x14
#define SNAFDTR 0x18
#define SNAFDRSAR 0x1c
#define SNAFDIR 0x20
#define   SNAFDIR_DMA_IP BIT(0)
#define SNAFDLR 0x24
#define SNAFSR 0x40
#define  SNAFSR_NFCOS BIT(3)
#define  SNAFSR_NFDRS BIT(2)
#define  SNAFSR_NFDWS BIT(1)

#define CMR_LEN(len) ((len) - 1)
#define CMR_WID(width) (((width) >> 1) << 28)

struct rtl_snand {
	struct device *dev;
	struct regmap *regmap;
	struct completion comp;
};

static irqreturn_t rtl_snand_irq(int irq, void *data)
{
	struct rtl_snand *snand = data;
	u32 val = 0;

	regmap_read(snand->regmap, SNAFSR, &val);
	if (val & (SNAFSR_NFCOS | SNAFSR_NFDRS | SNAFSR_NFDWS))
		return IRQ_NONE;

	regmap_write(snand->regmap, SNAFDIR, SNAFDIR_DMA_IP);
	complete(&snand->comp);

	return IRQ_HANDLED;
}

static bool rtl_snand_supports_op(struct spi_mem *mem,
				  const struct spi_mem_op *op)
{
	if (!spi_mem_default_supports_op(mem, op))
		return false;
	if (op->cmd.nbytes != 1 || op->cmd.buswidth != 1)
		return false;
	return true;
}

static void rtl_snand_set_cs(struct rtl_snand *snand, int cs, bool active)
{
	u32 val;

	if (active)
		val = ~(1 << (4 * cs));
	else
		val = ~0;

	regmap_write(snand->regmap, SNAFCCR, val);
}

static int rtl_snand_wait_ready(struct rtl_snand *snand)
{
	u32 val;

	return regmap_read_poll_timeout(snand->regmap, SNAFSR, val, !(val & SNAFSR_NFCOS),
					0, 2 * USEC_PER_MSEC);
}

static int rtl_snand_xfer_head(struct rtl_snand *snand, int cs, const struct spi_mem_op *op)
{
	int ret;
	u32 val, len = 0;

	rtl_snand_set_cs(snand, cs, true);

	val = op->cmd.opcode << 24;
	len = 1;
	if (op->addr.nbytes && op->addr.buswidth == 1) {
		val |= op->addr.val << ((3 - op->addr.nbytes) * 8);
		len += op->addr.nbytes;
	}

	ret = rtl_snand_wait_ready(snand);
	if (ret)
		return ret;

	ret = regmap_write(snand->regmap, SNAFWCMR, CMR_LEN(len));
	if (ret)
		return ret;

	ret = regmap_write(snand->regmap, SNAFWDR, val);
	if (ret)
		return ret;

	ret = rtl_snand_wait_ready(snand);
	if (ret)
		return ret;

	if (op->addr.buswidth > 1) {
		val = op->addr.val << ((3 - op->addr.nbytes) * 8);
		len = op->addr.nbytes;

		ret = regmap_write(snand->regmap, SNAFWCMR,
				   CMR_WID(op->addr.buswidth) | CMR_LEN(len));
		if (ret)
			return ret;

		ret = regmap_write(snand->regmap, SNAFWDR, val);
		if (ret)
			return ret;

		ret = rtl_snand_wait_ready(snand);
		if (ret)
			return ret;
	}

	if (op->dummy.nbytes) {
		val = 0;

		ret = regmap_write(snand->regmap, SNAFWCMR,
				   CMR_WID(op->dummy.buswidth) | CMR_LEN(op->dummy.nbytes));
		if (ret)
			return ret;

		ret = regmap_write(snand->regmap, SNAFWDR, val);
		if (ret)
			return ret;

		ret = rtl_snand_wait_ready(snand);
		if (ret)
			return ret;
	}

	return 0;
}

static void rtl_snand_xfer_tail(struct rtl_snand *snand, int cs)
{
	rtl_snand_set_cs(snand, cs, false);
}

static int rtl_snand_xfer(struct rtl_snand *snand, int cs, const struct spi_mem_op *op)
{
	unsigned int pos, nbytes;
	int ret;
	u32 val, len = 0;

	ret = rtl_snand_xfer_head(snand, cs, op);
	if (ret)
		goto out_deselect;

	if (op->data.dir == SPI_MEM_DATA_IN) {
		pos = 0;
		len = op->data.nbytes;

		while (pos < len) {
			nbytes = len - pos;
			if (nbytes > 4)
				nbytes = 4;

			ret = rtl_snand_wait_ready(snand);
			if (ret)
				goto out_deselect;

			ret = regmap_write(snand->regmap, SNAFRCMR,
					   CMR_WID(op->data.buswidth) | CMR_LEN(nbytes));
			if (ret)
				goto out_deselect;

			ret = rtl_snand_wait_ready(snand);
			if (ret)
				goto out_deselect;

			ret = regmap_read(snand->regmap, SNAFRDR, &val);
			if (ret)
				goto out_deselect;

			memcpy(op->data.buf.in + pos, &val, nbytes);

			pos += nbytes;
		}
	} else if (op->data.dir == SPI_MEM_DATA_OUT) {
		pos = 0;
		len = op->data.nbytes;

		while (pos < len) {
			nbytes = len - pos;
			if (nbytes > 4)
				nbytes = 4;

			memcpy(&val, op->data.buf.out + pos, nbytes);

			pos += nbytes;

			ret = regmap_write(snand->regmap, SNAFWCMR, CMR_LEN(nbytes));
			if (ret)
				goto out_deselect;

			ret = regmap_write(snand->regmap, SNAFWDR, val);
			if (ret)
				goto out_deselect;

			ret = rtl_snand_wait_ready(snand);
			if (ret)
				goto out_deselect;
		}
	}

out_deselect:
	rtl_snand_xfer_tail(snand, cs);

	if (ret)
		dev_err(snand->dev, "transfer failed %d\n", ret);

	return ret;
}

static int rtl_snand_dma_xfer(struct rtl_snand *snand, int cs, const struct spi_mem_op *op)
{
	unsigned int pos, nbytes;
	int ret;
	dma_addr_t buf_dma;
	enum dma_data_direction dir;
	u32 trig, len, maxlen;

	ret = rtl_snand_xfer_head(snand, cs, op);
	if (ret)
		goto out_deselect;

	if (op->data.dir == SPI_MEM_DATA_IN) {
		maxlen = 2080;
		dir = DMA_FROM_DEVICE;
		trig = 0;
	} else if (op->data.dir == SPI_MEM_DATA_OUT) {
		maxlen = 520;
		dir = DMA_TO_DEVICE;
		trig = 1;
	} else {
		ret = -EOPNOTSUPP;
		goto out_deselect;
	}

	buf_dma = dma_map_single(snand->dev, op->data.buf.in, op->data.nbytes, dir);
	ret = dma_mapping_error(snand->dev, buf_dma);
	if (ret)
		goto out_deselect;

	ret = regmap_write(snand->regmap, SNAFDIR, SNAFDIR_DMA_IP);
	if (ret)
		goto out_unmap;

	ret = regmap_update_bits(snand->regmap, SNAFCFR, SNAFCFR_DMA_IE, SNAFCFR_DMA_IE);
	if (ret)
		goto out_unmap;

	pos = 0;
	len = op->data.nbytes;

	while (pos < len) {
		nbytes = len - pos;
		if (nbytes > maxlen)
			nbytes = maxlen;

		reinit_completion(&snand->comp);

		ret = regmap_write(snand->regmap, SNAFDRSAR, buf_dma + pos);
		if (ret)
			goto out_disable_int;

		pos += nbytes;

		ret = regmap_write(snand->regmap, SNAFDLR,
				CMR_WID(op->data.buswidth) | nbytes);
		if (ret)
			goto out_disable_int;

		ret = regmap_write(snand->regmap, SNAFDTR, trig);
		if (ret)
			goto out_disable_int;

		if (!wait_for_completion_timeout(&snand->comp, usecs_to_jiffies(20000)))
			ret = -ETIMEDOUT;

		if (ret)
			goto out_disable_int;
	}

out_disable_int:
	regmap_update_bits(snand->regmap, SNAFCFR, SNAFCFR_DMA_IE, 0);
out_unmap:
	dma_unmap_single(snand->dev, buf_dma, op->data.nbytes, dir);
out_deselect:
	rtl_snand_xfer_tail(snand, cs);

	if (ret)
		dev_err(snand->dev, "transfer failed %d\n", ret);

	return ret;
}

static bool rtl_snand_dma_op(const struct spi_mem_op *op)
{
	switch (op->data.dir) {
	case SPI_MEM_DATA_IN:
	case SPI_MEM_DATA_OUT:
		return op->data.nbytes > 32;
	default:
		return false;
	}
}

static int rtl_snand_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
{
	struct rtl_snand *snand = spi_controller_get_devdata(mem->spi->controller);
	int cs = spi_get_chipselect(mem->spi, 0);

	dev_dbg(snand->dev, "cs %d op cmd %02x %d:%d, dummy %d:%d, addr %08llx@%d:%d, data %d:%d\n",
		cs, op->cmd.opcode,
		op->cmd.buswidth, op->cmd.nbytes, op->dummy.buswidth,
		op->dummy.nbytes, op->addr.val, op->addr.buswidth,
		op->addr.nbytes, op->data.buswidth, op->data.nbytes);

	if (rtl_snand_dma_op(op))
		return rtl_snand_dma_xfer(snand, cs, op);
	else
		return rtl_snand_xfer(snand, cs, op);
}

static const struct spi_controller_mem_ops rtl_snand_mem_ops = {
	.supports_op = rtl_snand_supports_op,
	.exec_op = rtl_snand_exec_op,
};

static const struct of_device_id rtl_snand_match[] = {
	{ .compatible = "realtek,rtl9301-snand" },
	{ .compatible = "realtek,rtl9302b-snand" },
	{ .compatible = "realtek,rtl9302c-snand" },
	{ .compatible = "realtek,rtl9303-snand" },
	{},
};
MODULE_DEVICE_TABLE(of, rtl_snand_match);

static int rtl_snand_probe(struct platform_device *pdev)
{
	struct rtl_snand *snand;
	struct device *dev = &pdev->dev;
	struct spi_controller *ctrl;
	void __iomem *base;
	const struct regmap_config rc = {
		.reg_bits	= 32,
		.val_bits	= 32,
		.reg_stride	= 4,
	};
	int irq, ret;

	ctrl = devm_spi_alloc_host(dev, sizeof(*snand));
	if (!ctrl)
		return -ENOMEM;

	snand = spi_controller_get_devdata(ctrl);
	snand->dev = dev;

	base = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(base))
		return PTR_ERR(base);

	snand->regmap = devm_regmap_init_mmio(dev, base, &rc);
	if (IS_ERR(snand->regmap))
		return PTR_ERR(snand->regmap);

	init_completion(&snand->comp);

	irq = platform_get_irq(pdev, 0);
	if (irq < 0)
		return irq;

	ret = dma_set_mask(snand->dev, DMA_BIT_MASK(32));
	if (ret)
		return dev_err_probe(dev, ret, "failed to set DMA mask\n");

	ret = devm_request_irq(dev, irq, rtl_snand_irq, 0, "rtl-snand", snand);
	if (ret)
		return dev_err_probe(dev, ret, "failed to request irq\n");

	ctrl->num_chipselect = 2;
	ctrl->mem_ops = &rtl_snand_mem_ops;
	ctrl->bits_per_word_mask = SPI_BPW_MASK(8);
	ctrl->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;

	return devm_spi_register_controller(dev, ctrl);
}

static struct platform_driver rtl_snand_driver = {
	.driver = {
		.name = "realtek-rtl-snand",
		.of_match_table = rtl_snand_match,
	},
	.probe = rtl_snand_probe,
};
module_platform_driver(rtl_snand_driver);

MODULE_DESCRIPTION("Realtek SPI-NAND Flash Controller Driver");
MODULE_LICENSE("GPL");
