// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2021-2022, NVIDIA CORPORATION. All rights reserved
 *
 * The driver handles Error's from Control Backbone(CBB) version 2.0.
 * generated due to illegal accesses. The driver prints debug information
 * about failed transaction on receiving interrupt from Error Notifier.
 * Error types supported by CBB2.0 are:
 *   UNSUPPORTED_ERR, PWRDOWN_ERR, TIMEOUT_ERR, FIREWALL_ERR, DECODE_ERR,
 *   SLAVE_ERR
 */

#include <linux/acpi.h>
#include <linux/clk.h>
#include <linux/cpufeature.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <soc/tegra/fuse.h>
#include <soc/tegra/tegra-cbb.h>

#define FABRIC_EN_CFG_INTERRUPT_ENABLE_0_0	0x0
#define FABRIC_EN_CFG_STATUS_0_0		0x40
#define FABRIC_EN_CFG_ADDR_INDEX_0_0		0x60
#define FABRIC_EN_CFG_ADDR_LOW_0		0x80
#define FABRIC_EN_CFG_ADDR_HI_0			0x84

#define FABRIC_MN_MASTER_ERR_EN_0		0x200
#define FABRIC_MN_MASTER_ERR_FORCE_0		0x204
#define FABRIC_MN_MASTER_ERR_STATUS_0		0x208
#define FABRIC_MN_MASTER_ERR_OVERFLOW_STATUS_0	0x20c

#define FABRIC_MN_MASTER_LOG_ERR_STATUS_0	0x300
#define FABRIC_MN_MASTER_LOG_ADDR_LOW_0		0x304
#define FABRIC_MN_MASTER_LOG_ADDR_HIGH_0	0x308
#define FABRIC_MN_MASTER_LOG_ATTRIBUTES0_0	0x30c
#define FABRIC_MN_MASTER_LOG_ATTRIBUTES1_0	0x310
#define FABRIC_MN_MASTER_LOG_ATTRIBUTES2_0	0x314
#define FABRIC_MN_MASTER_LOG_USER_BITS0_0	0x318

#define AXI_SLV_TIMEOUT_STATUS_0_0		0x8
#define APB_BLOCK_TMO_STATUS_0			0xc00
#define APB_BLOCK_NUM_TMO_OFFSET		0x20

#define FAB_EM_EL_MSTRID		GENMASK(29, 24)
#define FAB_EM_EL_VQC			GENMASK(17, 16)
#define FAB_EM_EL_GRPSEC		GENMASK(14, 8)
#define FAB_EM_EL_FALCONSEC		GENMASK(1, 0)

#define FAB_EM_EL_FABID			GENMASK(20, 16)
#define FAB_EM_EL_SLAVEID		GENMASK(7, 0)

#define FAB_EM_EL_ACCESSID		GENMASK(7, 0)

#define FAB_EM_EL_AXCACHE		GENMASK(27, 24)
#define FAB_EM_EL_AXPROT		GENMASK(22, 20)
#define FAB_EM_EL_BURSTLENGTH		GENMASK(19, 12)
#define FAB_EM_EL_BURSTTYPE		GENMASK(9, 8)
#define FAB_EM_EL_BEATSIZE		GENMASK(6, 4)
#define FAB_EM_EL_ACCESSTYPE		GENMASK(0, 0)

#define USRBITS_MSTR_ID			GENMASK(29, 24)

#define REQ_SOCKET_ID			GENMASK(27, 24)

#define CCPLEX_MSTRID			0x1
#define FIREWALL_APERTURE_SZ		0x10000
/* Write firewall check enable */
#define WEN				0x20000

enum tegra234_cbb_fabric_ids {
	CBB_FAB_ID,
	SCE_FAB_ID,
	RCE_FAB_ID,
	DCE_FAB_ID,
	AON_FAB_ID,
	PSC_FAB_ID,
	BPMP_FAB_ID,
	FSI_FAB_ID,
	MAX_FAB_ID,
};

struct tegra234_slave_lookup {
	const char *name;
	unsigned int offset;
};

struct tegra234_cbb_fabric {
	const char *name;
	phys_addr_t off_mask_erd;
	phys_addr_t firewall_base;
	unsigned int firewall_ctl;
	unsigned int firewall_wr_ctl;
	const char * const *master_id;
	unsigned int notifier_offset;
	const struct tegra_cbb_error *errors;
	const int max_errors;
	const struct tegra234_slave_lookup *slave_map;
	const int max_slaves;
};

struct tegra234_cbb {
	struct tegra_cbb base;

	const struct tegra234_cbb_fabric *fabric;
	struct resource *res;
	void __iomem *regs;

	int num_intr;
	int sec_irq;

	/* record */
	void __iomem *mon;
	unsigned int type;
	u32 mask;
	u64 access;
	u32 mn_attr0;
	u32 mn_attr1;
	u32 mn_attr2;
	u32 mn_user_bits;
};

static inline struct tegra234_cbb *to_tegra234_cbb(struct tegra_cbb *cbb)
{
	return container_of(cbb, struct tegra234_cbb, base);
}

static LIST_HEAD(cbb_list);
static DEFINE_SPINLOCK(cbb_lock);

static bool
tegra234_cbb_write_access_allowed(struct platform_device *pdev, struct tegra234_cbb *cbb)
{
	u32 val;

	if (!cbb->fabric->firewall_base ||
	    !cbb->fabric->firewall_ctl ||
	    !cbb->fabric->firewall_wr_ctl) {
		dev_info(&pdev->dev, "SoC data missing for firewall\n");
		return false;
	}

	if ((cbb->fabric->firewall_ctl > FIREWALL_APERTURE_SZ) ||
	    (cbb->fabric->firewall_wr_ctl > FIREWALL_APERTURE_SZ)) {
		dev_err(&pdev->dev, "wrong firewall offset value\n");
		return false;
	}

	val = readl(cbb->regs + cbb->fabric->firewall_base + cbb->fabric->firewall_ctl);
	/*
	 * If the firewall check feature for allowing or blocking the
	 * write accesses through the firewall of a fabric is disabled
	 * then CCPLEX can write to the registers of that fabric.
	 */
	if (!(val & WEN))
		return true;

	/*
	 * If the firewall check is enabled then check whether CCPLEX
	 * has write access to the fabric's error notifier registers
	 */
	val = readl(cbb->regs + cbb->fabric->firewall_base + cbb->fabric->firewall_wr_ctl);
	if (val & (BIT(CCPLEX_MSTRID)))
		return true;

	return false;
}

static void tegra234_cbb_fault_enable(struct tegra_cbb *cbb)
{
	struct tegra234_cbb *priv = to_tegra234_cbb(cbb);
	void __iomem *addr;

	addr = priv->regs + priv->fabric->notifier_offset;
	writel(0x1ff, addr + FABRIC_EN_CFG_INTERRUPT_ENABLE_0_0);
	dsb(sy);
}

static void tegra234_cbb_error_clear(struct tegra_cbb *cbb)
{
	struct tegra234_cbb *priv = to_tegra234_cbb(cbb);

	writel(0x3f, priv->mon + FABRIC_MN_MASTER_ERR_STATUS_0);
	dsb(sy);
}

static u32 tegra234_cbb_get_status(struct tegra_cbb *cbb)
{
	struct tegra234_cbb *priv = to_tegra234_cbb(cbb);
	void __iomem *addr;
	u32 value;

	addr = priv->regs + priv->fabric->notifier_offset;
	value = readl(addr + FABRIC_EN_CFG_STATUS_0_0);
	dsb(sy);

	return value;
}

static void tegra234_cbb_mask_serror(struct tegra234_cbb *cbb)
{
	writel(0x1, cbb->regs + cbb->fabric->off_mask_erd);
	dsb(sy);
}

static u32 tegra234_cbb_get_tmo_slv(void __iomem *addr)
{
	u32 timeout;

	timeout = readl(addr);
	return timeout;
}

static void tegra234_cbb_tmo_slv(struct seq_file *file, const char *slave, void __iomem *addr,
				 u32 status)
{
	tegra_cbb_print_err(file, "\t  %s : %#x\n", slave, status);
}

static void tegra234_cbb_lookup_apbslv(struct seq_file *file, const char *slave,
				       void __iomem *base)
{
	unsigned int block = 0;
	void __iomem *addr;
	char name[64];
	u32 status;

	status = tegra234_cbb_get_tmo_slv(base);
	if (status)
		tegra_cbb_print_err(file, "\t  %s_BLOCK_TMO_STATUS : %#x\n", slave, status);

	while (status) {
		if (status & BIT(0)) {
			u32 timeout, clients, client = 0;

			addr = base + APB_BLOCK_NUM_TMO_OFFSET + (block * 4);
			timeout = tegra234_cbb_get_tmo_slv(addr);
			clients = timeout;

			while (timeout) {
				if (timeout & BIT(0)) {
					if (clients != 0xffffffff)
						clients &= BIT(client);

					sprintf(name, "%s_BLOCK%d_TMO", slave, block);

					tegra234_cbb_tmo_slv(file, name, addr, clients);
				}

				timeout >>= 1;
				client++;
			}
		}

		status >>= 1;
		block++;
	}
}

static void tegra234_lookup_slave_timeout(struct seq_file *file, struct tegra234_cbb *cbb,
					  u8 slave_id, u8 fab_id)
{
	const struct tegra234_slave_lookup *map = cbb->fabric->slave_map;
	void __iomem *addr;

	/*
	 * 1) Get slave node name and address mapping using slave_id.
	 * 2) Check if the timed out slave node is APB or AXI.
	 * 3) If AXI, then print timeout register and reset axi slave
	 *    using <FABRIC>_SN_<>_SLV_TIMEOUT_STATUS_0_0 register.
	 * 4) If APB, then perform an additional lookup to find the client
	 *    which timed out.
	 *	a) Get block number from the index of set bit in
	 *	   <FABRIC>_SN_AXI2APB_<>_BLOCK_TMO_STATUS_0 register.
	 *	b) Get address of register repective to block number i.e.
	 *	   <FABRIC>_SN_AXI2APB_<>_BLOCK<index-set-bit>_TMO_0.
	 *	c) Read the register in above step to get client_id which
	 *	   timed out as per the set bits.
	 *      d) Reset the timedout client and print details.
	 *	e) Goto step-a till all bits are set.
	 */

	addr = cbb->regs + map[slave_id].offset;

	if (strstr(map[slave_id].name, "AXI2APB")) {
		addr += APB_BLOCK_TMO_STATUS_0;

		tegra234_cbb_lookup_apbslv(file, map[slave_id].name, addr);
	} else {
		char name[64];
		u32 status;

		addr += AXI_SLV_TIMEOUT_STATUS_0_0;

		status = tegra234_cbb_get_tmo_slv(addr);
		if (status) {
			sprintf(name, "%s_SLV_TIMEOUT_STATUS", map[slave_id].name);
			tegra234_cbb_tmo_slv(file, name, addr, status);
		}
	}
}

static void tegra234_cbb_print_error(struct seq_file *file, struct tegra234_cbb *cbb, u32 status,
				     u32 overflow)
{
	unsigned int type = 0;

	if (status & (status - 1))
		tegra_cbb_print_err(file, "\t  Multiple type of errors reported\n");

	while (status) {
		if (type >= cbb->fabric->max_errors) {
			tegra_cbb_print_err(file, "\t  Wrong type index:%u, status:%u\n",
					    type, status);
			return;
		}

		if (status & 0x1)
			tegra_cbb_print_err(file, "\t  Error Code\t\t: %s\n",
					    cbb->fabric->errors[type].code);

		status >>= 1;
		type++;
	}

	type = 0;

	while (overflow) {
		if (type >= cbb->fabric->max_errors) {
			tegra_cbb_print_err(file, "\t  Wrong type index:%u, overflow:%u\n",
					    type, overflow);
			return;
		}

		if (overflow & 0x1)
			tegra_cbb_print_err(file, "\t  Overflow\t\t: Multiple %s\n",
					    cbb->fabric->errors[type].code);

		overflow >>= 1;
		type++;
	}
}

static void print_errlog_err(struct seq_file *file, struct tegra234_cbb *cbb)
{
	u8 cache_type, prot_type, burst_length, mstr_id, grpsec, vqc, falconsec, beat_size;
	u8 access_type, access_id, requester_socket_id, local_socket_id, slave_id, fab_id;
	char fabric_name[20];
	bool is_numa = false;
	u8 burst_type;

	if (num_possible_nodes() > 1)
		is_numa = true;

	mstr_id = FIELD_GET(FAB_EM_EL_MSTRID, cbb->mn_user_bits);
	vqc = FIELD_GET(FAB_EM_EL_VQC, cbb->mn_user_bits);
	grpsec = FIELD_GET(FAB_EM_EL_GRPSEC, cbb->mn_user_bits);
	falconsec = FIELD_GET(FAB_EM_EL_FALCONSEC, cbb->mn_user_bits);

	/*
	 * For SOC with multiple NUMA nodes, print cross socket access
	 * errors only if initiator/master_id is CCPLEX, CPMU or GPU.
	 */
	if (is_numa) {
		local_socket_id = numa_node_id();
		requester_socket_id = FIELD_GET(REQ_SOCKET_ID, cbb->mn_attr2);

		if (requester_socket_id != local_socket_id) {
			if ((mstr_id != 0x1) && (mstr_id != 0x2) && (mstr_id != 0xB))
				return;
		}
	}

	fab_id = FIELD_GET(FAB_EM_EL_FABID, cbb->mn_attr2);
	slave_id = FIELD_GET(FAB_EM_EL_SLAVEID, cbb->mn_attr2);

	access_id = FIELD_GET(FAB_EM_EL_ACCESSID, cbb->mn_attr1);

	cache_type = FIELD_GET(FAB_EM_EL_AXCACHE, cbb->mn_attr0);
	prot_type = FIELD_GET(FAB_EM_EL_AXPROT, cbb->mn_attr0);
	burst_length = FIELD_GET(FAB_EM_EL_BURSTLENGTH, cbb->mn_attr0);
	burst_type = FIELD_GET(FAB_EM_EL_BURSTTYPE, cbb->mn_attr0);
	beat_size = FIELD_GET(FAB_EM_EL_BEATSIZE, cbb->mn_attr0);
	access_type = FIELD_GET(FAB_EM_EL_ACCESSTYPE, cbb->mn_attr0);

	tegra_cbb_print_err(file, "\n");
	if (cbb->type < cbb->fabric->max_errors)
		tegra_cbb_print_err(file, "\t  Error Code\t\t: %s\n",
				    cbb->fabric->errors[cbb->type].code);
	else
		tegra_cbb_print_err(file, "\t  Wrong type index:%u\n", cbb->type);

	tegra_cbb_print_err(file, "\t  MASTER_ID\t\t: %s\n", cbb->fabric->master_id[mstr_id]);
	tegra_cbb_print_err(file, "\t  Address\t\t: %#llx\n", cbb->access);

	tegra_cbb_print_cache(file, cache_type);
	tegra_cbb_print_prot(file, prot_type);

	tegra_cbb_print_err(file, "\t  Access_Type\t\t: %s", (access_type) ? "Write\n" : "Read\n");
	tegra_cbb_print_err(file, "\t  Access_ID\t\t: %#x", access_id);

	if (fab_id == PSC_FAB_ID)
		strcpy(fabric_name, "psc-fabric");
	else if (fab_id == FSI_FAB_ID)
		strcpy(fabric_name, "fsi-fabric");
	else
		strcpy(fabric_name, cbb->fabric->name);

	if (is_numa) {
		tegra_cbb_print_err(file, "\t  Requester_Socket_Id\t: %#x\n",
				    requester_socket_id);
		tegra_cbb_print_err(file, "\t  Local_Socket_Id\t: %#x\n",
				    local_socket_id);
		tegra_cbb_print_err(file, "\t  No. of NUMA_NODES\t: %#x\n",
				    num_possible_nodes());
	}

	tegra_cbb_print_err(file, "\t  Fabric\t\t: %s\n", fabric_name);
	tegra_cbb_print_err(file, "\t  Slave_Id\t\t: %#x\n", slave_id);
	tegra_cbb_print_err(file, "\t  Burst_length\t\t: %#x\n", burst_length);
	tegra_cbb_print_err(file, "\t  Burst_type\t\t: %#x\n", burst_type);
	tegra_cbb_print_err(file, "\t  Beat_size\t\t: %#x\n", beat_size);
	tegra_cbb_print_err(file, "\t  VQC\t\t\t: %#x\n", vqc);
	tegra_cbb_print_err(file, "\t  GRPSEC\t\t: %#x\n", grpsec);
	tegra_cbb_print_err(file, "\t  FALCONSEC\t\t: %#x\n", falconsec);

	if ((fab_id == PSC_FAB_ID) || (fab_id == FSI_FAB_ID))
		return;

	if (slave_id >= cbb->fabric->max_slaves) {
		tegra_cbb_print_err(file, "\t  Invalid slave_id:%d\n", slave_id);
		return;
	}

	if (!strcmp(cbb->fabric->errors[cbb->type].code, "TIMEOUT_ERR")) {
		tegra234_lookup_slave_timeout(file, cbb, slave_id, fab_id);
		return;
	}

	tegra_cbb_print_err(file, "\t  Slave\t\t\t: %s\n", cbb->fabric->slave_map[slave_id].name);
}

static int print_errmonX_info(struct seq_file *file, struct tegra234_cbb *cbb)
{
	u32 overflow, status, error;

	status = readl(cbb->mon + FABRIC_MN_MASTER_ERR_STATUS_0);
	if (!status) {
		pr_err("Error Notifier received a spurious notification\n");
		return -ENODATA;
	}

	if (status == 0xffffffff) {
		pr_err("CBB registers returning all 1's which is invalid\n");
		return -EINVAL;
	}

	overflow = readl(cbb->mon + FABRIC_MN_MASTER_ERR_OVERFLOW_STATUS_0);

	tegra234_cbb_print_error(file, cbb, status, overflow);

	error = readl(cbb->mon + FABRIC_MN_MASTER_LOG_ERR_STATUS_0);
	if (!error) {
		pr_info("Error Monitor doesn't have Error Logger\n");
		return -EINVAL;
	}

	cbb->type = 0;

	while (error) {
		if (error & BIT(0)) {
			u32 hi, lo;

			hi = readl(cbb->mon + FABRIC_MN_MASTER_LOG_ADDR_HIGH_0);
			lo = readl(cbb->mon + FABRIC_MN_MASTER_LOG_ADDR_LOW_0);

			cbb->access = (u64)hi << 32 | lo;

			cbb->mn_attr0 = readl(cbb->mon + FABRIC_MN_MASTER_LOG_ATTRIBUTES0_0);
			cbb->mn_attr1 = readl(cbb->mon + FABRIC_MN_MASTER_LOG_ATTRIBUTES1_0);
			cbb->mn_attr2 = readl(cbb->mon + FABRIC_MN_MASTER_LOG_ATTRIBUTES2_0);
			cbb->mn_user_bits = readl(cbb->mon + FABRIC_MN_MASTER_LOG_USER_BITS0_0);

			print_errlog_err(file, cbb);
		}

		cbb->type++;
		error >>= 1;
	}

	return 0;
}

static int print_err_notifier(struct seq_file *file, struct tegra234_cbb *cbb, u32 status)
{
	unsigned int index = 0;
	int err;

	pr_crit("**************************************\n");
	pr_crit("CPU:%d, Error:%s, Errmon:%d\n", smp_processor_id(),
		cbb->fabric->name, status);

	while (status) {
		if (status & BIT(0)) {
			unsigned int notifier = cbb->fabric->notifier_offset;
			u32 hi, lo, mask = BIT(index);
			phys_addr_t addr;
			u64 offset;

			writel(mask, cbb->regs + notifier + FABRIC_EN_CFG_ADDR_INDEX_0_0);
			hi = readl(cbb->regs + notifier + FABRIC_EN_CFG_ADDR_HI_0);
			lo = readl(cbb->regs + notifier + FABRIC_EN_CFG_ADDR_LOW_0);

			addr = (u64)hi << 32 | lo;

			offset = addr - cbb->res->start;
			cbb->mon = cbb->regs + offset;
			cbb->mask = BIT(index);

			err = print_errmonX_info(file, cbb);
			tegra234_cbb_error_clear(&cbb->base);
			if (err)
				return err;
		}

		status >>= 1;
		index++;
	}

	tegra_cbb_print_err(file, "\t**************************************\n");
	return 0;
}

#ifdef CONFIG_DEBUG_FS
static DEFINE_MUTEX(cbb_debugfs_mutex);

static int tegra234_cbb_debugfs_show(struct tegra_cbb *cbb, struct seq_file *file, void *data)
{
	int err = 0;

	mutex_lock(&cbb_debugfs_mutex);

	list_for_each_entry(cbb, &cbb_list, node) {
		struct tegra234_cbb *priv = to_tegra234_cbb(cbb);
		u32 status;

		status = tegra_cbb_get_status(&priv->base);
		if (status) {
			err = print_err_notifier(file, priv, status);
			if (err)
				break;
		}
	}

	mutex_unlock(&cbb_debugfs_mutex);
	return err;
}
#endif

/*
 * Handler for CBB errors
 */
static irqreturn_t tegra234_cbb_isr(int irq, void *data)
{
	bool is_inband_err = false;
	struct tegra_cbb *cbb;
	unsigned long flags;
	u8 mstr_id;
	int err;

	spin_lock_irqsave(&cbb_lock, flags);

	list_for_each_entry(cbb, &cbb_list, node) {
		struct tegra234_cbb *priv = to_tegra234_cbb(cbb);
		u32 status = tegra_cbb_get_status(cbb);

		if (status && (irq == priv->sec_irq)) {
			tegra_cbb_print_err(NULL, "CPU:%d, Error: %s@0x%llx, irq=%d\n",
					    smp_processor_id(), priv->fabric->name,
					    priv->res->start, irq);

			err = print_err_notifier(NULL, priv, status);
			if (err)
				goto unlock;

			/*
			 * If illegal request is from CCPLEX(id:0x1) master then call WARN()
			 */
			if (priv->fabric->off_mask_erd) {
				mstr_id =  FIELD_GET(USRBITS_MSTR_ID, priv->mn_user_bits);
				if (mstr_id == CCPLEX_MSTRID)
					is_inband_err = 1;
			}
		}
	}

unlock:
	spin_unlock_irqrestore(&cbb_lock, flags);
	WARN_ON(is_inband_err);
	return IRQ_HANDLED;
}

/*
 * Register handler for CBB_SECURE interrupt for reporting errors
 */
static int tegra234_cbb_interrupt_enable(struct tegra_cbb *cbb)
{
	struct tegra234_cbb *priv = to_tegra234_cbb(cbb);

	if (priv->sec_irq) {
		int err = devm_request_irq(cbb->dev, priv->sec_irq, tegra234_cbb_isr, 0,
					   dev_name(cbb->dev), priv);
		if (err) {
			dev_err(cbb->dev, "failed to register interrupt %u: %d\n", priv->sec_irq,
				err);
			return err;
		}
	}

	return 0;
}

static void tegra234_cbb_error_enable(struct tegra_cbb *cbb)
{
	tegra_cbb_fault_enable(cbb);
}

static const struct tegra_cbb_ops tegra234_cbb_ops = {
	.get_status = tegra234_cbb_get_status,
	.error_clear = tegra234_cbb_error_clear,
	.fault_enable = tegra234_cbb_fault_enable,
	.error_enable = tegra234_cbb_error_enable,
	.interrupt_enable = tegra234_cbb_interrupt_enable,
#ifdef CONFIG_DEBUG_FS
	.debugfs_show = tegra234_cbb_debugfs_show,
#endif
};

static const char * const tegra234_master_id[] = {
	[0x00] = "TZ",
	[0x01] = "CCPLEX",
	[0x02] = "CCPMU",
	[0x03] = "BPMP_FW",
	[0x04] = "AON",
	[0x05] = "SCE",
	[0x06] = "GPCDMA_P",
	[0x07] = "TSECA_NONSECURE",
	[0x08] = "TSECA_LIGHTSECURE",
	[0x09] = "TSECA_HEAVYSECURE",
	[0x0a] = "CORESIGHT",
	[0x0b] = "APE",
	[0x0c] = "PEATRANS",
	[0x0d] = "JTAGM_DFT",
	[0x0e] = "RCE",
	[0x0f] = "DCE",
	[0x10] = "PSC_FW_USER",
	[0x11] = "PSC_FW_SUPERVISOR",
	[0x12] = "PSC_FW_MACHINE",
	[0x13] = "PSC_BOOT",
	[0x14] = "BPMP_BOOT",
	[0x15] = "NVDEC_NONSECURE",
	[0x16] = "NVDEC_LIGHTSECURE",
	[0x17] = "NVDEC_HEAVYSECURE",
	[0x18] = "CBB_INTERNAL",
	[0x19] = "RSVD"
};

static const struct tegra_cbb_error tegra234_cbb_errors[] = {
	{
		.code = "SLAVE_ERR",
		.desc = "Slave being accessed responded with an error"
	}, {
		.code = "DECODE_ERR",
		.desc = "Attempt to access an address hole"
	}, {
		.code = "FIREWALL_ERR",
		.desc = "Attempt to access a region which is firewall protected"
	}, {
		.code = "TIMEOUT_ERR",
		.desc = "No response returned by slave"
	}, {
		.code = "PWRDOWN_ERR",
		.desc = "Attempt to access a portion of fabric that is powered down"
	}, {
		.code = "UNSUPPORTED_ERR",
		.desc = "Attempt to access a slave through an unsupported access"
	}
};

static const struct tegra234_slave_lookup tegra234_aon_slave_map[] = {
	{ "AXI2APB", 0x00000 },
	{ "AST",     0x14000 },
	{ "CBB",     0x15000 },
	{ "CPU",     0x16000 },
};

static const struct tegra234_cbb_fabric tegra234_aon_fabric = {
	.name = "aon-fabric",
	.master_id = tegra234_master_id,
	.slave_map = tegra234_aon_slave_map,
	.max_slaves = ARRAY_SIZE(tegra234_aon_slave_map),
	.errors = tegra234_cbb_errors,
	.max_errors = ARRAY_SIZE(tegra234_cbb_errors),
	.notifier_offset = 0x17000,
	.firewall_base = 0x30000,
	.firewall_ctl = 0x8d0,
	.firewall_wr_ctl = 0x8c8,
};

static const struct tegra234_slave_lookup tegra234_bpmp_slave_map[] = {
	{ "AXI2APB", 0x00000 },
	{ "AST0",    0x15000 },
	{ "AST1",    0x16000 },
	{ "CBB",     0x17000 },
	{ "CPU",     0x18000 },
};

static const struct tegra234_cbb_fabric tegra234_bpmp_fabric = {
	.name = "bpmp-fabric",
	.master_id = tegra234_master_id,
	.slave_map = tegra234_bpmp_slave_map,
	.max_slaves = ARRAY_SIZE(tegra234_bpmp_slave_map),
	.errors = tegra234_cbb_errors,
	.max_errors = ARRAY_SIZE(tegra234_cbb_errors),
	.notifier_offset = 0x19000,
	.firewall_base = 0x30000,
	.firewall_ctl = 0x8f0,
	.firewall_wr_ctl = 0x8e8,
};

static const struct tegra234_slave_lookup tegra234_cbb_slave_map[] = {
	{ "AON",        0x40000 },
	{ "BPMP",       0x41000 },
	{ "CBB",        0x42000 },
	{ "HOST1X",     0x43000 },
	{ "STM",        0x44000 },
	{ "FSI",        0x45000 },
	{ "PSC",        0x46000 },
	{ "PCIE_C1",    0x47000 },
	{ "PCIE_C2",    0x48000 },
	{ "PCIE_C3",    0x49000 },
	{ "PCIE_C0",    0x4a000 },
	{ "PCIE_C4",    0x4b000 },
	{ "GPU",        0x4c000 },
	{ "SMMU0",      0x4d000 },
	{ "SMMU1",      0x4e000 },
	{ "SMMU2",      0x4f000 },
	{ "SMMU3",      0x50000 },
	{ "SMMU4",      0x51000 },
	{ "PCIE_C10",   0x52000 },
	{ "PCIE_C7",    0x53000 },
	{ "PCIE_C8",    0x54000 },
	{ "PCIE_C9",    0x55000 },
	{ "PCIE_C5",    0x56000 },
	{ "PCIE_C6",    0x57000 },
	{ "DCE",        0x58000 },
	{ "RCE",        0x59000 },
	{ "SCE",        0x5a000 },
	{ "AXI2APB_1",  0x70000 },
	{ "AXI2APB_10", 0x71000 },
	{ "AXI2APB_11", 0x72000 },
	{ "AXI2APB_12", 0x73000 },
	{ "AXI2APB_13", 0x74000 },
	{ "AXI2APB_14", 0x75000 },
	{ "AXI2APB_15", 0x76000 },
	{ "AXI2APB_16", 0x77000 },
	{ "AXI2APB_17", 0x78000 },
	{ "AXI2APB_18", 0x79000 },
	{ "AXI2APB_19", 0x7a000 },
	{ "AXI2APB_2",  0x7b000 },
	{ "AXI2APB_20", 0x7c000 },
	{ "AXI2APB_21", 0x7d000 },
	{ "AXI2APB_22", 0x7e000 },
	{ "AXI2APB_23", 0x7f000 },
	{ "AXI2APB_25", 0x80000 },
	{ "AXI2APB_26", 0x81000 },
	{ "AXI2APB_27", 0x82000 },
	{ "AXI2APB_28", 0x83000 },
	{ "AXI2APB_29", 0x84000 },
	{ "AXI2APB_30", 0x85000 },
	{ "AXI2APB_31", 0x86000 },
	{ "AXI2APB_32", 0x87000 },
	{ "AXI2APB_33", 0x88000 },
	{ "AXI2APB_34", 0x89000 },
	{ "AXI2APB_35", 0x92000 },
	{ "AXI2APB_4",  0x8b000 },
	{ "AXI2APB_5",  0x8c000 },
	{ "AXI2APB_6",  0x8d000 },
	{ "AXI2APB_7",  0x8e000 },
	{ "AXI2APB_8",  0x8f000 },
	{ "AXI2APB_9",  0x90000 },
	{ "AXI2APB_3",  0x91000 },
};

static const struct tegra234_cbb_fabric tegra234_cbb_fabric = {
	.name = "cbb-fabric",
	.master_id = tegra234_master_id,
	.slave_map = tegra234_cbb_slave_map,
	.max_slaves = ARRAY_SIZE(tegra234_cbb_slave_map),
	.errors = tegra234_cbb_errors,
	.max_errors = ARRAY_SIZE(tegra234_cbb_errors),
	.notifier_offset = 0x60000,
	.off_mask_erd = 0x3a004,
	.firewall_base = 0x10000,
	.firewall_ctl = 0x23f0,
	.firewall_wr_ctl = 0x23e8,
};

static const struct tegra234_slave_lookup tegra234_common_slave_map[] = {
	{ "AXI2APB", 0x00000 },
	{ "AST0",    0x15000 },
	{ "AST1",    0x16000 },
	{ "CBB",     0x17000 },
	{ "RSVD",    0x00000 },
	{ "CPU",     0x18000 },
};

static const struct tegra234_cbb_fabric tegra234_dce_fabric = {
	.name = "dce-fabric",
	.master_id = tegra234_master_id,
	.slave_map = tegra234_common_slave_map,
	.max_slaves = ARRAY_SIZE(tegra234_common_slave_map),
	.errors = tegra234_cbb_errors,
	.max_errors = ARRAY_SIZE(tegra234_cbb_errors),
	.notifier_offset = 0x19000,
	.firewall_base = 0x30000,
	.firewall_ctl = 0x290,
	.firewall_wr_ctl = 0x288,
};

static const struct tegra234_cbb_fabric tegra234_rce_fabric = {
	.name = "rce-fabric",
	.master_id = tegra234_master_id,
	.slave_map = tegra234_common_slave_map,
	.max_slaves = ARRAY_SIZE(tegra234_common_slave_map),
	.errors = tegra234_cbb_errors,
	.max_errors = ARRAY_SIZE(tegra234_cbb_errors),
	.notifier_offset = 0x19000,
	.firewall_base = 0x30000,
	.firewall_ctl = 0x290,
	.firewall_wr_ctl = 0x288,
};

static const struct tegra234_cbb_fabric tegra234_sce_fabric = {
	.name = "sce-fabric",
	.master_id = tegra234_master_id,
	.slave_map = tegra234_common_slave_map,
	.max_slaves = ARRAY_SIZE(tegra234_common_slave_map),
	.errors = tegra234_cbb_errors,
	.max_errors = ARRAY_SIZE(tegra234_cbb_errors),
	.notifier_offset = 0x19000,
	.firewall_base = 0x30000,
	.firewall_ctl = 0x290,
	.firewall_wr_ctl = 0x288,
};

static const char * const tegra241_master_id[] = {
	[0x0] = "TZ",
	[0x1] = "CCPLEX",
	[0x2] = "CCPMU",
	[0x3] = "BPMP_FW",
	[0x4] = "PSC_FW_USER",
	[0x5] = "PSC_FW_SUPERVISOR",
	[0x6] = "PSC_FW_MACHINE",
	[0x7] = "PSC_BOOT",
	[0x8] = "BPMP_BOOT",
	[0x9] = "JTAGM_DFT",
	[0xa] = "CORESIGHT",
	[0xb] = "GPU",
	[0xc] = "PEATRANS",
	[0xd ... 0x3f] = "RSVD"
};

/*
 * Possible causes for Slave and Timeout errors.
 * SLAVE_ERR:
 * Slave being accessed responded with an error. Slave could return
 * an error for various cases :
 *   Unsupported access, clamp setting when power gated, register
 *   level firewall(SCR), address hole within the slave, etc
 *
 * TIMEOUT_ERR:
 * No response returned by slave. Can be due to slave being clock
 * gated, under reset, powered down or slave inability to respond
 * for an internal slave issue
 */
static const struct tegra_cbb_error tegra241_cbb_errors[] = {
	{
		.code = "SLAVE_ERR",
		.desc = "Slave being accessed responded with an error."
	}, {
		.code = "DECODE_ERR",
		.desc = "Attempt to access an address hole or Reserved region of memory."
	}, {
		.code = "FIREWALL_ERR",
		.desc = "Attempt to access a region which is firewalled."
	}, {
		.code = "TIMEOUT_ERR",
		.desc = "No response returned by slave."
	}, {
		.code = "PWRDOWN_ERR",
		.desc = "Attempt to access a portion of the fabric that is powered down."
	}, {
		.code = "UNSUPPORTED_ERR",
		.desc = "Attempt to access a slave through an unsupported access."
	}, {
		.code = "POISON_ERR",
		.desc = "Slave responds with poison error to indicate error in data."
	}, {
		.code = "RSVD"
	}, {
		.code = "RSVD"
	}, {
		.code = "RSVD"
	}, {
		.code = "RSVD"
	}, {
		.code = "RSVD"
	}, {
		.code = "RSVD"
	}, {
		.code = "RSVD"
	}, {
		.code = "RSVD"
	}, {
		.code = "RSVD"
	}, {
		.code = "NO_SUCH_ADDRESS_ERR",
		.desc = "The address belongs to the pri_target range but there is no register "
			"implemented at the address."
	}, {
		.code = "TASK_ERR",
		.desc = "Attempt to update a PRI task when the current task has still not "
			"completed."
	}, {
		.code = "EXTERNAL_ERR",
		.desc = "Indicates that an external PRI register access met with an error due to "
			"any issue in the unit."
	}, {
		.code = "INDEX_ERR",
		.desc = "Applicable to PRI index aperture pair, when the programmed index is "
			"outside the range defined in the manual."
	}, {
		.code = "RESET_ERR",
		.desc = "Target in Reset Error: Attempt to access a SubPri or external PRI "
			"register but they are in reset."
	}, {
		.code = "REGISTER_RST_ERR",
		.desc = "Attempt to access a PRI register but the register is partial or "
			"completely in reset."
	}, {
		.code = "POWER_GATED_ERR",
		.desc = "Returned by external PRI client when the external access goes to a power "
			"gated domain."
	}, {
		.code = "SUBPRI_FS_ERR",
		.desc = "Subpri is floorswept: Attempt to access a subpri through the main pri "
			"target but subPri logic is floorswept."
	}, {
		.code = "SUBPRI_CLK_OFF_ERR",
		.desc = "Subpri clock is off: Attempt to access a subpri through the main pri "
			"target but subPris clock is gated/off."
	},
};

static const struct tegra234_slave_lookup tegra241_cbb_slave_map[] = {
	{ "RSVD",       0x00000 },
	{ "PCIE_C8",    0x51000 },
	{ "PCIE_C9",    0x52000 },
	{ "RSVD",       0x00000 },
	{ "RSVD",       0x00000 },
	{ "RSVD",       0x00000 },
	{ "RSVD",       0x00000 },
	{ "RSVD",       0x00000 },
	{ "RSVD",       0x00000 },
	{ "RSVD",       0x00000 },
	{ "RSVD",       0x00000 },
	{ "AON",        0x5b000 },
	{ "BPMP",       0x5c000 },
	{ "RSVD",       0x00000 },
	{ "RSVD",       0x00000 },
	{ "PSC",        0x5d000 },
	{ "STM",        0x5e000 },
	{ "AXI2APB_1",  0x70000 },
	{ "AXI2APB_10", 0x71000 },
	{ "AXI2APB_11", 0x72000 },
	{ "AXI2APB_12", 0x73000 },
	{ "AXI2APB_13", 0x74000 },
	{ "AXI2APB_14", 0x75000 },
	{ "AXI2APB_15", 0x76000 },
	{ "AXI2APB_16", 0x77000 },
	{ "AXI2APB_17", 0x78000 },
	{ "AXI2APB_18", 0x79000 },
	{ "AXI2APB_19", 0x7a000 },
	{ "AXI2APB_2",  0x7b000 },
	{ "AXI2APB_20", 0x7c000 },
	{ "AXI2APB_4",  0x87000 },
	{ "AXI2APB_5",  0x88000 },
	{ "AXI2APB_6",  0x89000 },
	{ "AXI2APB_7",  0x8a000 },
	{ "AXI2APB_8",  0x8b000 },
	{ "AXI2APB_9",  0x8c000 },
	{ "AXI2APB_3",  0x8d000 },
	{ "AXI2APB_21", 0x7d000 },
	{ "AXI2APB_22", 0x7e000 },
	{ "AXI2APB_23", 0x7f000 },
	{ "AXI2APB_24", 0x80000 },
	{ "AXI2APB_25", 0x81000 },
	{ "AXI2APB_26", 0x82000 },
	{ "AXI2APB_27", 0x83000 },
	{ "AXI2APB_28", 0x84000 },
	{ "PCIE_C4",    0x53000 },
	{ "PCIE_C5",    0x54000 },
	{ "PCIE_C6",    0x55000 },
	{ "PCIE_C7",    0x56000 },
	{ "PCIE_C2",    0x57000 },
	{ "PCIE_C3",    0x58000 },
	{ "PCIE_C0",    0x59000 },
	{ "PCIE_C1",    0x5a000 },
	{ "CCPLEX",     0x50000 },
	{ "AXI2APB_29", 0x85000 },
	{ "AXI2APB_30", 0x86000 },
	{ "CBB_CENTRAL", 0x00000 },
	{ "AXI2APB_31", 0x8E000 },
	{ "AXI2APB_32", 0x8F000 },
};

static const struct tegra234_cbb_fabric tegra241_cbb_fabric = {
	.name = "cbb-fabric",
	.master_id = tegra241_master_id,
	.slave_map = tegra241_cbb_slave_map,
	.max_slaves = ARRAY_SIZE(tegra241_cbb_slave_map),
	.errors = tegra241_cbb_errors,
	.max_errors = ARRAY_SIZE(tegra241_cbb_errors),
	.notifier_offset = 0x60000,
	.off_mask_erd = 0x40004,
	.firewall_base = 0x20000,
	.firewall_ctl = 0x2370,
	.firewall_wr_ctl = 0x2368,
};

static const struct tegra234_slave_lookup tegra241_bpmp_slave_map[] = {
	{ "RSVD",    0x00000 },
	{ "RSVD",    0x00000 },
	{ "RSVD",    0x00000 },
	{ "CBB",     0x15000 },
	{ "CPU",     0x16000 },
	{ "AXI2APB", 0x00000 },
	{ "DBB0",    0x17000 },
	{ "DBB1",    0x18000 },
};

static const struct tegra234_cbb_fabric tegra241_bpmp_fabric = {
	.name = "bpmp-fabric",
	.master_id = tegra241_master_id,
	.slave_map = tegra241_bpmp_slave_map,
	.max_slaves = ARRAY_SIZE(tegra241_bpmp_slave_map),
	.errors = tegra241_cbb_errors,
	.max_errors = ARRAY_SIZE(tegra241_cbb_errors),
	.notifier_offset = 0x19000,
	.firewall_base = 0x30000,
	.firewall_ctl = 0x8f0,
	.firewall_wr_ctl = 0x8e8,
};

static const struct of_device_id tegra234_cbb_dt_ids[] = {
	{ .compatible = "nvidia,tegra234-cbb-fabric", .data = &tegra234_cbb_fabric },
	{ .compatible = "nvidia,tegra234-aon-fabric", .data = &tegra234_aon_fabric },
	{ .compatible = "nvidia,tegra234-bpmp-fabric", .data = &tegra234_bpmp_fabric },
	{ .compatible = "nvidia,tegra234-dce-fabric", .data = &tegra234_dce_fabric },
	{ .compatible = "nvidia,tegra234-rce-fabric", .data = &tegra234_rce_fabric },
	{ .compatible = "nvidia,tegra234-sce-fabric", .data = &tegra234_sce_fabric },
	{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, tegra234_cbb_dt_ids);

struct tegra234_cbb_acpi_uid {
	const char *hid;
	const char *uid;
	const struct tegra234_cbb_fabric *fabric;
};

static const struct tegra234_cbb_acpi_uid tegra234_cbb_acpi_uids[] = {
	{ "NVDA1070", "1", &tegra241_cbb_fabric },
	{ "NVDA1070", "2", &tegra241_bpmp_fabric },
	{ },
};

static const struct
tegra234_cbb_fabric *tegra234_cbb_acpi_get_fabric(struct acpi_device *adev)
{
	const struct tegra234_cbb_acpi_uid *entry;

	for (entry = tegra234_cbb_acpi_uids; entry->hid; entry++) {
		if (acpi_dev_hid_uid_match(adev, entry->hid, entry->uid))
			return entry->fabric;
	}

	return NULL;
}

static const struct acpi_device_id tegra241_cbb_acpi_ids[] = {
	{ "NVDA1070" },
	{ },
};
MODULE_DEVICE_TABLE(acpi, tegra241_cbb_acpi_ids);

static int tegra234_cbb_probe(struct platform_device *pdev)
{
	const struct tegra234_cbb_fabric *fabric;
	struct tegra234_cbb *cbb;
	unsigned long flags = 0;
	int err;

	if (pdev->dev.of_node) {
		fabric = of_device_get_match_data(&pdev->dev);
	} else {
		struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
		if (!device)
			return -ENODEV;

		fabric = tegra234_cbb_acpi_get_fabric(device);
		if (!fabric) {
			dev_err(&pdev->dev, "no device match found\n");
			return -ENODEV;
		}
	}

	cbb = devm_kzalloc(&pdev->dev, sizeof(*cbb), GFP_KERNEL);
	if (!cbb)
		return -ENOMEM;

	INIT_LIST_HEAD(&cbb->base.node);
	cbb->base.ops = &tegra234_cbb_ops;
	cbb->base.dev = &pdev->dev;
	cbb->fabric = fabric;

	cbb->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &cbb->res);
	if (IS_ERR(cbb->regs))
		return PTR_ERR(cbb->regs);

	err = tegra_cbb_get_irq(pdev, NULL, &cbb->sec_irq);
	if (err)
		return err;

	platform_set_drvdata(pdev, cbb);

	/*
	 * Don't enable error reporting for a Fabric if write to it's registers
	 * is blocked by CBB firewall.
	 */
	if (!tegra234_cbb_write_access_allowed(pdev, cbb)) {
		dev_info(&pdev->dev, "error reporting not enabled due to firewall\n");
		return 0;
	}

	spin_lock_irqsave(&cbb_lock, flags);
	list_add(&cbb->base.node, &cbb_list);
	spin_unlock_irqrestore(&cbb_lock, flags);

	/* set ERD bit to mask SError and generate interrupt to report error */
	if (cbb->fabric->off_mask_erd)
		tegra234_cbb_mask_serror(cbb);

	return tegra_cbb_register(&cbb->base);
}

static int __maybe_unused tegra234_cbb_resume_noirq(struct device *dev)
{
	struct tegra234_cbb *cbb = dev_get_drvdata(dev);

	tegra234_cbb_error_enable(&cbb->base);

	dev_dbg(dev, "%s resumed\n", cbb->fabric->name);

	return 0;
}

static const struct dev_pm_ops tegra234_cbb_pm = {
	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, tegra234_cbb_resume_noirq)
};

static struct platform_driver tegra234_cbb_driver = {
	.probe = tegra234_cbb_probe,
	.driver = {
		.name = "tegra234-cbb",
		.of_match_table = tegra234_cbb_dt_ids,
		.acpi_match_table = tegra241_cbb_acpi_ids,
		.pm = &tegra234_cbb_pm,
	},
};

static int __init tegra234_cbb_init(void)
{
	return platform_driver_register(&tegra234_cbb_driver);
}
pure_initcall(tegra234_cbb_init);

static void __exit tegra234_cbb_exit(void)
{
	platform_driver_unregister(&tegra234_cbb_driver);
}
module_exit(tegra234_cbb_exit);

MODULE_DESCRIPTION("Control Backbone 2.0 error handling driver for Tegra234");