// SPDX-License-Identifier:     GPL-2.0+
/*
 * Copyright (C) 2019 Rockchip Electronics Co., Ltd
 */
#include <common.h>
#include <asm/io.h>
#include <clk.h>
#include <dm.h>
#include <linux/bitops.h>
#include <misc.h>
#include <irq-generic.h>
#include <reset.h>

DECLARE_GLOBAL_DATA_PTR;

#define DECOM_CTRL		0x0
#define DECOM_ENR		0x4
#define DECOM_RADDR		0x8
#define DECOM_WADDR		0xc
#define DECOM_UDDSL		0x10
#define DECOM_UDDSH		0x14
#define DECOM_TXTHR		0x18
#define DECOM_RXTHR		0x1c
#define DECOM_SLEN		0x20
#define DECOM_STAT		0x24
#define DECOM_ISR		0x28
#define DECOM_IEN		0x2c
#define DECOM_AXI_STAT		0x30
#define DECOM_TSIZEL		0x34
#define DECOM_TSIZEH		0x38
#define DECOM_MGNUM		0x3c
#define DECOM_FRAME		0x40
#define DECOM_DICTID		0x44
#define DECOM_CSL		0x48
#define DECOM_CSH		0x4c
#define DECOM_LMTSL             0x50
#define DECOM_LMTSH             0x54

#define LZ4_HEAD_CSUM_CHECK_EN	BIT(1)
#define LZ4_BLOCK_CSUM_CHECK_EN	BIT(2)
#define LZ4_CONT_CSUM_CHECK_EN	BIT(3)

#define DSOLIEN			BIT(19)
#define ZDICTEIEN		BIT(18)
#define GCMEIEN			BIT(17)
#define GIDEIEN			BIT(16)
#define CCCEIEN			BIT(15)
#define BCCEIEN			BIT(14)
#define HCCEIEN			BIT(13)
#define CSEIEN			BIT(12)
#define DICTEIEN		BIT(11)
#define VNEIEN			BIT(10)
#define WNEIEN			BIT(9)
#define RDCEIEN			BIT(8)
#define WRCEIEN			BIT(7)
#define DISEIEN			BIT(6)
#define LENEIEN			BIT(5)
#define LITEIEN			BIT(4)
#define SQMEIEN			BIT(3)
#define SLCIEN			BIT(2)
#define HDEIEN			BIT(1)
#define DSIEN			BIT(0)

#define DECOM_STOP		BIT(0)
#define DECOM_COMPLETE		BIT(0)
#define DECOM_GZIP_MODE		BIT(4)
#define DECOM_ZLIB_MODE		BIT(5)
#define DECOM_DEFLATE_MODE	BIT(0)
#define DECOM_AXI_IDLE		BIT(4)
#define DECOM_LZ4_MODE		0

#define DECOM_ENABLE		0x1
#define DECOM_DISABLE		0x0

#define DECOM_IRQ		0xffff /* fixme */

#define DECOM_INT_MASK \
	(DSOLIEN | ZDICTEIEN | GCMEIEN | GIDEIEN | \
	CCCEIEN | BCCEIEN | HCCEIEN | CSEIEN | \
	DICTEIEN | VNEIEN | WNEIEN | RDCEIEN | WRCEIEN | \
	DISEIEN | LENEIEN | LITEIEN | SQMEIEN | SLCIEN | \
	HDEIEN | DSIEN)

#define DCLK_DECOM		400 * 1000 * 1000

struct rockchip_decom_priv {
	struct reset_ctl rst;
	void __iomem *base;
	bool idle_check_once;
	bool done;
	struct clk dclk;
	int cached; /* 1: access the data through dcache; 0: no dcache */
};

static int rockchip_decom_start(struct udevice *dev, void *buf)
{
	struct rockchip_decom_priv *priv = dev_get_priv(dev);
	struct decom_param *param = (struct decom_param *)buf;
	unsigned int limit_lo = param->size_dst & 0xffffffff;
	unsigned int limit_hi = param->size_dst >> 32;

#if CONFIG_IS_ENABLED(DM_RESET)
	reset_assert(&priv->rst);
	udelay(10);
	reset_deassert(&priv->rst);
#endif
	/*
	 * Purpose:
	 *    src: clean dcache to get the real compressed data from ddr.
	 *    dst: invalidate dcache.
	 *
	 * flush_dcache_all() operating on set/way is faster than
	 * flush_cache() and invalidate_dcache_range() operating
	 * on virtual address.
	 */
	if (!priv->cached)
		flush_dcache_all();

	priv->done = false;

	if (param->mode == DECOM_LZ4)
		writel(LZ4_CONT_CSUM_CHECK_EN |
		       LZ4_HEAD_CSUM_CHECK_EN |
		       LZ4_BLOCK_CSUM_CHECK_EN |
		       DECOM_LZ4_MODE,
		       priv->base + DECOM_CTRL);
	else if (param->mode == DECOM_GZIP)
		writel(DECOM_DEFLATE_MODE | DECOM_GZIP_MODE,
		       priv->base + DECOM_CTRL);
	else if (param->mode == DECOM_ZLIB)
		writel(DECOM_DEFLATE_MODE | DECOM_ZLIB_MODE,
		       priv->base + DECOM_CTRL);

	writel(param->addr_src, priv->base + DECOM_RADDR);
	writel(param->addr_dst, priv->base + DECOM_WADDR);

	writel(limit_lo, priv->base + DECOM_LMTSL);
	writel(limit_hi, priv->base + DECOM_LMTSH);

	writel(DECOM_ENABLE, priv->base + DECOM_ENR);

	priv->idle_check_once = true;

	return 0;
}

static int rockchip_decom_stop(struct udevice *dev)
{
	struct rockchip_decom_priv *priv = dev_get_priv(dev);

	writel(DECOM_DISABLE, priv->base + DECOM_ENR);

	return 0;
}

/* Caller must call this function to check if decompress done */
static int rockchip_decom_done_poll(struct udevice *dev)
{
	struct rockchip_decom_priv *priv = dev_get_priv(dev);

	/*
	 * Test the decom is idle first time.
	 */
	if (!priv->idle_check_once)
		return !(readl(priv->base + DECOM_AXI_STAT) & DECOM_AXI_IDLE);

	return !(readl(priv->base + DECOM_STAT) & DECOM_COMPLETE);
}

static int rockchip_decom_capability(u32 *buf)
{
	*buf = DECOM_GZIP | DECOM_LZ4;

	return 0;
}

static int rockchip_decom_data_size(struct udevice *dev, u64 *buf)
{
	struct rockchip_decom_priv *priv = dev_get_priv(dev);
	struct decom_param *param = (struct decom_param *)buf;
	u32 sizel, sizeh;

	sizel = readl(priv->base + DECOM_TSIZEL);
	sizeh = readl(priv->base + DECOM_TSIZEH);
	param->size_dst = sizel | ((u64)sizeh << 32);

	return 0;
}

/* Caller must fill in param @buf which represent struct decom_param */
static int rockchip_decom_ioctl(struct udevice *dev, unsigned long request,
				void *buf)
{
	int ret = -EINVAL;

	switch (request) {
	case IOCTL_REQ_START:
		ret = rockchip_decom_start(dev, buf);
		break;
	case IOCTL_REQ_POLL:
		ret = rockchip_decom_done_poll(dev);
		break;
	case IOCTL_REQ_STOP:
		ret = rockchip_decom_stop(dev);
		break;
	case IOCTL_REQ_CAPABILITY:
		ret = rockchip_decom_capability(buf);
		break;
	case IOCTL_REQ_DATA_SIZE:
		ret = rockchip_decom_data_size(dev, buf);
		break;
	default:
		printf("Unsupported ioctl: %ld\n", (ulong)request);
		break;
	}

	return ret;
}

static const struct misc_ops rockchip_decom_ops = {
	.ioctl = rockchip_decom_ioctl,
};

static int rockchip_decom_ofdata_to_platdata(struct udevice *dev)
{
	struct rockchip_decom_priv *priv = dev_get_priv(dev);

	priv->base = dev_read_addr_ptr(dev);
	if (!priv->base)
		return -ENOENT;

	priv->cached = dev_read_u32_default(dev, "data-cached", 0);

	return 0;
}

static int rockchip_decom_probe(struct udevice *dev)
{
	struct rockchip_decom_priv *priv = dev_get_priv(dev);
	int ret;

#if CONFIG_IS_ENABLED(DM_RESET)
	ret = reset_get_by_name(dev, "dresetn", &priv->rst);
	if (ret) {
		debug("reset_get_by_name() failed: %d\n", ret);
		return ret;
	}
#endif

	ret = clk_get_by_index(dev, 1, &priv->dclk);
	if (ret < 0)
		return ret;

	ret = clk_set_rate(&priv->dclk, DCLK_DECOM);
	if (ret < 0)
		return ret;

	return 0;
}

static const struct udevice_id rockchip_decom_ids[] = {
	{ .compatible = "rockchip,hw-decompress" },
	{}
};

U_BOOT_DRIVER(rockchip_hw_decompress) = {
	.name = "rockchip_hw_decompress",
	.id = UCLASS_MISC,
	.of_match = rockchip_decom_ids,
	.probe = rockchip_decom_probe,
	.ofdata_to_platdata = rockchip_decom_ofdata_to_platdata,
	.priv_auto_alloc_size = sizeof(struct rockchip_decom_priv),
	.ops = &rockchip_decom_ops,
};