soc/tegra: cbb: Add driver for Tegra234 CBB 2.0
authorSumit Gupta <sumitg@nvidia.com>
Wed, 11 May 2022 20:16:50 +0000 (01:46 +0530)
committerThierry Reding <treding@nvidia.com>
Thu, 15 Sep 2022 10:41:36 +0000 (12:41 +0200)
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>
drivers/soc/tegra/Kconfig
drivers/soc/tegra/cbb/Makefile
drivers/soc/tegra/cbb/tegra234-cbb.c [new file with mode: 0644]

index 65283a9..bd36048 100644 (file)
@@ -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).
index 711b756..e3ac6cd 100644 (file)
@@ -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 (file)
index 0000000..c437457
--- /dev/null
@@ -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");