Blob Blame History Raw
From: Sumit Gupta <sumitg@nvidia.com>
Date: Thu, 12 May 2022 01:46:50 +0530
Subject: soc/tegra: cbb: Add driver for Tegra234 CBB 2.0
Git-commit: fc2f151d23145f68a6748c4684a81b70823a489d
Patch-mainline: v6.1-rc1
References: jsc#PED-1763

Adding driver to handle errors from CBB version 2.0 which is used in
Tegra234 SoC. The driver prints debug information about failed
transaction on receiving interrupt from the error notifier. The error
notifier collates the interrupts from various error monitor blocks and
presents a single interrupt to the SoC interrupt controller.

For timeout errors, the driver also does the lookup to find timed out
clients and prints their client ID. Drivers for hardware that needs to
be reset on timeout will have to call BPMP from the client IP's driver.
BPMP firmware will also clear the timeout bit after resetting the IP
so that next transactions are send to them after reset.

Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
Signed-off-by: Thierry Reding <treding@nvidia.com>
Signed-off-by: Matthias Brugger <mbrugger@suse.com>
---
 drivers/soc/tegra/Kconfig            |   2 +-
 drivers/soc/tegra/cbb/Makefile       |   1 +
 drivers/soc/tegra/cbb/tegra234-cbb.c | 846 +++++++++++++++++++++++++++
 3 files changed, 848 insertions(+), 1 deletion(-)
 create mode 100644 drivers/soc/tegra/cbb/tegra234-cbb.c

diff --git a/drivers/soc/tegra/Kconfig b/drivers/soc/tegra/Kconfig
index 65283a93e78f..bd360488cd82 100644
--- a/drivers/soc/tegra/Kconfig
+++ b/drivers/soc/tegra/Kconfig
@@ -165,7 +165,7 @@ config SOC_TEGRA30_VOLTAGE_COUPLER
 
 config SOC_TEGRA_CBB
 	tristate "Tegra driver to handle error from CBB"
-	depends on ARCH_TEGRA_194_SOC
+	depends on ARCH_TEGRA_194_SOC || ARCH_TEGRA_234_SOC
 	default y
 	help
 	  Support for handling error from Tegra Control Backbone(CBB).
diff --git a/drivers/soc/tegra/cbb/Makefile b/drivers/soc/tegra/cbb/Makefile
index 711b75610703..e3ac6cdddf5c 100644
--- a/drivers/soc/tegra/cbb/Makefile
+++ b/drivers/soc/tegra/cbb/Makefile
@@ -5,4 +5,5 @@
 ifdef CONFIG_SOC_TEGRA_CBB
 obj-y += tegra-cbb.o
 obj-$(CONFIG_ARCH_TEGRA_194_SOC) += tegra194-cbb.o
+obj-$(CONFIG_ARCH_TEGRA_234_SOC) += tegra234-cbb.o
 endif
diff --git a/drivers/soc/tegra/cbb/tegra234-cbb.c b/drivers/soc/tegra/cbb/tegra234-cbb.c
new file mode 100644
index 000000000000..c437457aa349
--- /dev/null
+++ b/drivers/soc/tegra/cbb/tegra234-cbb.c
@@ -0,0 +1,846 @@
+// 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/clk.h>
+#include <linux/cpufeature.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/of_irq.h>
+#include <linux/of_address.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/version.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)
+
+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;
+	bool erd_mask_inband_err;
+	const char * const *master_id;
+	unsigned int notifier_offset;
+	const struct tegra_cbb_error *errors;
+	const struct tegra234_slave_lookup *slave_map;
+};
+
+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 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 (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 (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, slave_id, fab_id, burst_type;
+	char fabric_name[20];
+
+	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);
+
+	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");
+	tegra_cbb_print_err(file, "\t  Error Code\t\t: %s\n",
+			    cbb->fabric->errors[cbb->type].code);
+
+	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);
+
+	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 (!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@%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;
+
+			mstr_id =  FIELD_GET(USRBITS_MSTR_ID, priv->mn_user_bits);
+
+			/*
+			 * If illegal request is from CCPLEX(id:0x1) master then call BUG() to
+			 * crash system.
+			 */
+			if ((mstr_id == 0x1) && priv->fabric->off_mask_erd)
+				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,
+	.errors = tegra234_cbb_errors,
+	.notifier_offset = 0x17000,
+};
+
+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,
+	.errors = tegra234_cbb_errors,
+	.notifier_offset = 0x19000,
+};
+
+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,
+	.errors = tegra234_cbb_errors,
+	.notifier_offset = 0x60000,
+	.off_mask_erd = 0x3a004
+};
+
+static const struct tegra234_slave_lookup tegra234_dce_slave_map[] = {
+	{ "AXI2APB", 0x00000 },
+	{ "AST0",    0x15000 },
+	{ "AST1",    0x16000 },
+	{ "CPU",     0x18000 },
+};
+
+static const struct tegra234_cbb_fabric tegra234_dce_fabric = {
+	.name = "dce-fabric",
+	.master_id = tegra234_master_id,
+	.slave_map = tegra234_dce_slave_map,
+	.errors = tegra234_cbb_errors,
+	.notifier_offset = 0x19000,
+};
+
+static const struct tegra234_slave_lookup tegra234_rce_slave_map[] = {
+	{ "AXI2APB", 0x00000 },
+	{ "AST0",    0x15000 },
+	{ "AST1",    0x16000 },
+	{ "CPU",     0x18000 },
+};
+
+static const struct tegra234_cbb_fabric tegra234_rce_fabric = {
+	.name = "rce-fabric",
+	.master_id = tegra234_master_id,
+	.slave_map = tegra234_rce_slave_map,
+	.errors = tegra234_cbb_errors,
+	.notifier_offset = 0x19000,
+};
+
+static const struct tegra234_slave_lookup tegra234_sce_slave_map[] = {
+	{ "AXI2APB", 0x00000 },
+	{ "AST0",    0x15000 },
+	{ "AST1",    0x16000 },
+	{ "CBB",     0x17000 },
+	{ "CPU",     0x18000 },
+};
+
+static const struct tegra234_cbb_fabric tegra234_sce_fabric = {
+	.name = "sce-fabric",
+	.master_id = tegra234_master_id,
+	.slave_map = tegra234_sce_slave_map,
+	.errors = tegra234_cbb_errors,
+	.notifier_offset = 0x19000,
+};
+
+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);
+
+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;
+
+	fabric = of_device_get_match_data(&pdev->dev);
+
+	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);
+
+	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 tegra234_cbb_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+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,
+	.remove = tegra234_cbb_remove,
+	.driver = {
+		.name = "tegra234-cbb",
+		.of_match_table = tegra234_cbb_dt_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");
+MODULE_LICENSE("GPL");
-- 
2.38.0