// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2019 Intel Corporation <www.intel.com>
 *
 */
#include <dm.h>
#include <hang.h>
#include <wait_bit.h>

#include <asm/io.h>
#include <linux/bitops.h>

/* Directory */
#define DIRUSFER		0x80010
#define DIRUCASER0		0x80040
#define DIRUSFMCR		0x80080
#define DIRUSFMAR		0x80084

#define DIRUSFMCR_SFID_SHIFT	16

/* Coherent cache agent interface */
#define CAIUIDR			0x00ffc

#define CAIUIDR_CA_GET(v)	(((v) & 0x00008000) >> 15)
#define CAIUIDR_TYPE_GET(v)	(((v) & 0x000f0000) >> 16)
#define CAIUIDR_TYPE_ACE_CAI_DVM_SUPPORT	0
#define CAIUIDR_TYPE_ACELITE_CAI_DVM_SUPPORT	1

/* Coherent subsystem */
#define CSADSER0		0xff040
#define CSUIDR			0xffff8
#define CSIDR			0xffffc

#define CSUIDR_NUMCAIUS_GET(v)	(((v) & 0x0000007f) >> 0)
#define CSUIDR_NUMDIRUS_GET(v)	(((v) & 0x003f0000) >> 16)
#define CSUIDR_NUMCMIUS_GET(v)	(((v) & 0x3f000000) >> 24)

#define CSIDR_NUMSFS_GET(v)	(((v) & 0x007c0000) >> 18)

#define DIR_REG_SZ		0x1000
#define CAIU_REG_SZ		0x1000

#define CCU_DIR_REG_ADDR(base, reg, dir)	\
		((base) + (reg) + ((dir) * DIR_REG_SZ))

/* OCRAM firewall register */
#define OCRAM_FW_01			0x100204
#define OCRAM_SECURE_REGIONS		4

#define OCRAM_PRIVILEGED_MASK		BIT(29)
#define OCRAM_SECURE_MASK		BIT(30)

static void ncore_ccu_init_dirs(void __iomem *base)
{
	ulong i, f;
	int ret;
	u32 num_of_dirs;
	u32 num_of_snoop_filters;
	u32 reg;

	num_of_dirs = CSUIDR_NUMDIRUS_GET(readl(base + CSUIDR));
	num_of_snoop_filters =
		CSIDR_NUMSFS_GET(readl(base + CSIDR)) + 1;

	/* Initialize each snoop filter in each directory */
	for (f = 0; f < num_of_snoop_filters; f++) {
		reg = f << DIRUSFMCR_SFID_SHIFT;
		for (i = 0; i < num_of_dirs; i++) {
			/* Initialize all entries */
			writel(reg, CCU_DIR_REG_ADDR(base, DIRUSFMCR, i));

			/* Poll snoop filter maintenance operation active
			 * bit become 0.
			 */
			ret = wait_for_bit_le32((const void *)
						CCU_DIR_REG_ADDR(base,
								 DIRUSFMAR, i),
						BIT(0), false, 1000, false);
			if (ret) {
				puts("CCU: Directory initialization failed!\n");
				hang();
			}

			/* Enable snoop filter, a bit per snoop filter */
			setbits_le32((ulong)CCU_DIR_REG_ADDR(base, DIRUSFER, i),
				     BIT(f));
		}
	}
}

static void ncore_ccu_init_coh_agent(void __iomem *base)
{
	u32 num_of_coh_agent_intf;
	u32 num_of_dirs;
	u32 reg;
	u32 type;
	u32 i, dir;

	num_of_coh_agent_intf =
		CSUIDR_NUMCAIUS_GET(readl(base + CSUIDR));
	num_of_dirs = CSUIDR_NUMDIRUS_GET(readl(base + CSUIDR));

	for (i = 0; i < num_of_coh_agent_intf; i++) {
		reg = readl(base + CAIUIDR + (i * CAIU_REG_SZ));
		if (CAIUIDR_CA_GET(reg)) {
			/* Caching agent bit is enabled, enable caching agent
			 * snoop in each directory
			 */
			for (dir = 0; dir < num_of_dirs; dir++) {
				setbits_le32((ulong)
					     CCU_DIR_REG_ADDR(base, DIRUCASER0,
							      dir),
					     BIT(i));
			}
		}

		type = CAIUIDR_TYPE_GET(reg);
		if (type == CAIUIDR_TYPE_ACE_CAI_DVM_SUPPORT ||
		    type == CAIUIDR_TYPE_ACELITE_CAI_DVM_SUPPORT) {
			/* DVM support is enabled, enable ACE DVM snoop*/
			setbits_le32((ulong)(base + CSADSER0),
				     BIT(i));
		}
	}
}

static void ocram_bypass_firewall(void __iomem *base)
{
	int i;

	for (i = 0; i < OCRAM_SECURE_REGIONS; i++) {
		clrbits_le32(base + OCRAM_FW_01 + (i * sizeof(u32)),
			     OCRAM_PRIVILEGED_MASK | OCRAM_SECURE_MASK);
	}
}

static int ncore_ccu_probe(struct udevice *dev)
{
	void __iomem *base;
	fdt_addr_t addr;

	addr = dev_read_addr(dev);
	if (addr == FDT_ADDR_T_NONE)
		return -EINVAL;

	base = (void __iomem *)addr;

	ncore_ccu_init_dirs(base);
	ncore_ccu_init_coh_agent(base);
	ocram_bypass_firewall(base);

	return 0;
}

static const struct udevice_id ncore_ccu_ids[] = {
	{ .compatible = "arteris,ncore-ccu" },
	{}
};

U_BOOT_DRIVER(ncore_ccu) = {
	.name   = "ncore_ccu",
	.id     = UCLASS_CACHE,
	.of_match = ncore_ccu_ids,
	.probe	= ncore_ccu_probe,
	.flags  = DM_FLAG_PRE_RELOC,
};
