]> rtime.felk.cvut.cz Git - hercules2020/nv-tegra/linux-4.4.git/commitdiff
platform: tegra: add hsp driver
authorSivaram Nair <sivaramn@nvidia.com>
Mon, 5 Dec 2016 19:39:41 +0000 (11:39 -0800)
committermobile promotions <svcmobile_promotions@nvidia.com>
Wed, 28 Dec 2016 05:34:32 +0000 (21:34 -0800)
Copied from linux-t18x repo

Bug 200257382

Change-Id: I0746282a8c73350bb31a0a035e28de466c21e9ac
Signed-off-by: Sivaram Nair <sivaramn@nvidia.com>
Reviewed-on: http://git-master/r/1266822
Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com>
Tested-by: mobile promotions <svcmobile_promotions@nvidia.com>
Documentation/devicetree/bindings/platform/tegra/tegra-hsp.txt [new file with mode: 0644]
drivers/platform/tegra/Kconfig
drivers/platform/tegra/Makefile
drivers/platform/tegra/tegra-hsp.c [new file with mode: 0644]
drivers/platform/tegra/tegra186-hsp.c [new file with mode: 0644]
include/linux/tegra-hsp.h [new file with mode: 0644]

diff --git a/Documentation/devicetree/bindings/platform/tegra/tegra-hsp.txt b/Documentation/devicetree/bindings/platform/tegra/tegra-hsp.txt
new file mode 100644 (file)
index 0000000..37633ce
--- /dev/null
@@ -0,0 +1,27 @@
+* NVIDIA Tegra Hardware Synchronization Primitives (HSP) bindings
+
+Tegra HSP unit is designed to support synchronization among processors on the
+SoC for various tasks where shared resources need to be accessed exclusively
+or inter-processor communications support.
+
+Required properties:
+
+- compatible: should be "nvidia,tegra186-hsp"
+
+- reg: base address and length of the HSP aperture
+
+- interrupts: interrupts corresponding to interrupt-names
+
+- interrupt-names: where applicable:
+  - "doorbell":        CPU doorbell interrupt
+  - "sharedN": shared interrupt number N (N in 0..7)
+  - "emptyN":  mailbox number N empty interrupt (N in 0..7)
+  - "fullN":   mailbox number N full interrupt (N in 0..7)
+
+- nvidia,doorbell: doorbell identifier of the local doorbell
+
+- nvidia,mbox-ie: if present, enable and disable all mbox interrupts
+  using mbox-specific registers
+
+Symbolic doorbell constants are defined in:
+  <dt-bindings/platform/t186/tegra-hsp.h>
index 1e91d6d0f4a4fa0b2285d8a4f383bac8bb43145c..570a25bb95ec01294b1d18e6947125464b3c414f 100644 (file)
@@ -230,6 +230,11 @@ config TEGRA_FIRMWARES_INVENTORY
        help
          Register version readers for firmwares
 
+config TEGRA_HSP
+       bool "Enable Tegra Hardware Synchronization Primitives driver"
+       depends on ARCH_TEGRA_18x_SOC
+       default y
+
 trysource "../t18x/drivers/platform/tegra/Kconfig"
 trysource "../t18x/drivers/platform/tegra/Kconfig.t18x"
 trysource "drivers/platform/tegra/Kconfig.t18x"
index bdac934640d15df12520aba77b5360a0118d858f..833fc670b6580187669e857f5d0a39093eb9e528 100644 (file)
@@ -105,3 +105,5 @@ obj-$(CONFIG_TEGRA_FIRMWARES_CLASS) += firmwares.o
 obj-$(CONFIG_TEGRA_FIRMWARES_INVENTORY)        += firmwares-all.o
 
 obj-$(CONFIG_ARCH_TEGRA_18x_SOC)       += tegra_chipid.o
+obj-$(CONFIG_TEGRA_HSP)                        += tegra-hsp.o
+obj-$(CONFIG_TEGRA_HSP)                        += tegra186-hsp.o
diff --git a/drivers/platform/tegra/tegra-hsp.c b/drivers/platform/tegra/tegra-hsp.c
new file mode 100644 (file)
index 0000000..a2be484
--- /dev/null
@@ -0,0 +1,533 @@
+/*
+ * Copyright (c) 2014-2016 NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/debugfs.h>
+
+#include <linux/tegra-hsp.h>
+
+#define HSP_DIMENSIONING               0x380
+
+#define HSP_DB_REG_TRIGGER             0x0
+#define HSP_DB_REG_ENABLE              0x4
+#define HSP_DB_REG_RAW                 0x8
+#define HSP_DB_REG_PENDING             0xc
+
+enum tegra_hsp_init_status {
+       HSP_INIT_PENDING,
+       HSP_INIT_FAILED,
+       HSP_INIT_OKAY,
+};
+
+struct hsp_top {
+       int status;
+       void __iomem *base;
+};
+
+static DEFINE_MUTEX(hsp_top_lock);
+
+struct db_handler_info {
+       db_handler_t    handler;
+       void                    *data;
+};
+
+static struct hsp_top hsp_top = { .status = HSP_INIT_PENDING };
+static void __iomem *db_bases[HSP_NR_DBS];
+
+static DEFINE_MUTEX(db_handlers_lock);
+static int db_irq;
+static struct db_handler_info db_handlers[HSP_LAST_MASTER + 1];
+
+static const char * const master_names[] = {
+       [HSP_MASTER_SECURE_CCPLEX] = "SECURE_CCPLEX",
+       [HSP_MASTER_SECURE_DPMU] = "SECURE_DPMU",
+       [HSP_MASTER_SECURE_BPMP] = "SECURE_BPMP",
+       [HSP_MASTER_SECURE_SPE] = "SECURE_SPE",
+       [HSP_MASTER_SECURE_SCE] = "SECURE_SCE",
+       [HSP_MASTER_CCPLEX] = "CCPLEX",
+       [HSP_MASTER_DPMU] = "DPMU",
+       [HSP_MASTER_BPMP] = "BPMP",
+       [HSP_MASTER_SPE] = "SPE",
+       [HSP_MASTER_SCE] = "SCE",
+       [HSP_MASTER_APE] = "APE",
+};
+
+static const char * const db_names[] = {
+       [HSP_DB_DPMU] = "DPMU",
+       [HSP_DB_CCPLEX] = "CCPLEX",
+       [HSP_DB_CCPLEX_TZ] = "CCPLEX_TZ",
+       [HSP_DB_BPMP] = "BPMP",
+       [HSP_DB_SPE] = "SPE",
+       [HSP_DB_SCE] = "SCE",
+       [HSP_DB_APE] = "APE",
+};
+
+static inline int is_master_valid(int master)
+{
+       return master_names[master] != NULL;
+}
+
+static inline int next_valid_master(int m)
+{
+       for (m++; m <= HSP_LAST_MASTER && !is_master_valid(m); m++)
+               ;
+       return m;
+}
+
+#define for_each_valid_master(m) \
+       for (m = HSP_FIRST_MASTER; \
+                m <= HSP_LAST_MASTER; \
+                m = next_valid_master(m))
+
+#define hsp_ready() (hsp_top.status == HSP_INIT_OKAY)
+
+static inline u32 hsp_readl(void __iomem *base, int reg)
+{
+       return readl(base + reg);
+}
+
+static inline void hsp_writel(void __iomem *base, int reg, u32 val)
+{
+       writel(val, base + reg);
+       (void)readl(base + reg);
+}
+
+static irqreturn_t dbell_irq(int irq, void *data)
+{
+       ulong reg;
+       int master;
+       struct db_handler_info *info;
+
+       reg = (ulong)hsp_readl(db_bases[HSP_DB_CCPLEX], HSP_DB_REG_PENDING);
+       hsp_writel(db_bases[HSP_DB_CCPLEX], HSP_DB_REG_PENDING, reg);
+
+       for_each_set_bit(master, &reg, HSP_LAST_MASTER + 1) {
+               info = &db_handlers[master];
+               if (unlikely(!is_master_valid(master))) {
+                       pr_warn("invalid master from HW.\n");
+                       continue;
+               }
+               if (info->handler)
+                       info->handler(info->data);
+       }
+
+       return IRQ_HANDLED;
+}
+
+static int tegra_hsp_db_set_master(enum tegra_hsp_master master, bool enabled)
+{
+       u32 reg;
+
+       if (!hsp_ready() || !is_master_valid(master))
+               return -EINVAL;
+
+       mutex_lock(&hsp_top_lock);
+       reg = hsp_readl(db_bases[HSP_DB_CCPLEX], HSP_DB_REG_ENABLE);
+       if (enabled)
+               reg |= BIT(master);
+       else
+               reg &= ~BIT(master);
+       hsp_writel(db_bases[HSP_DB_CCPLEX], HSP_DB_REG_ENABLE, reg);
+       mutex_unlock(&hsp_top_lock);
+       return 0;
+}
+
+/**
+ * tegra_hsp_db_enable_master: allow <master> to ring CCPLEX
+ * @master:     HSP master
+ *
+ * Returns 0 if successful.
+ */
+int tegra_hsp_db_enable_master(enum tegra_hsp_master master)
+{
+       return tegra_hsp_db_set_master(master, true);
+}
+EXPORT_SYMBOL(tegra_hsp_db_enable_master);
+
+/**
+ * tegra_hsp_db_disable_master: disallow <master> from ringing CCPLEX
+ * @master:    HSP master
+ *
+ * Returns 0 if successful.
+ */
+int tegra_hsp_db_disable_master(enum tegra_hsp_master master)
+{
+       return tegra_hsp_db_set_master(master, false);
+}
+EXPORT_SYMBOL(tegra_hsp_db_disable_master);
+
+/**
+ * tegra_hsp_db_ring: ring the <dbell>
+ * @dbell:      HSP dbell to be rung
+ *
+ * Returns 0 if successful.
+ */
+int tegra_hsp_db_ring(enum tegra_hsp_doorbell dbell)
+{
+       if (!hsp_ready() || dbell >= HSP_NR_DBS)
+               return -EINVAL;
+       hsp_writel(db_bases[dbell], HSP_DB_REG_TRIGGER, 1);
+       return 0;
+}
+EXPORT_SYMBOL(tegra_hsp_db_ring);
+
+/**
+ * tegra_hsp_db_can_ring: check if CCPLEX can ring the <dbell>
+ * @dbell:      HSP dbell to be checked
+ *
+ * Returns 1 if CCPLEX can ring <dbell> otherwise 0.
+ */
+int tegra_hsp_db_can_ring(enum tegra_hsp_doorbell dbell)
+{
+       int reg;
+       if (!hsp_ready() || dbell >= HSP_NR_DBS)
+               return 0;
+       reg = hsp_readl(db_bases[dbell], HSP_DB_REG_ENABLE);
+       return !!(reg & BIT(HSP_MASTER_CCPLEX));
+}
+EXPORT_SYMBOL(tegra_hsp_db_can_ring);
+
+/**
+ * tegra_hsp_db_add_handler: register an CCPLEX doorbell IRQ handler
+ * @ master:   master id
+ * @ handler:  IRQ handler
+ * @ data:             custom data
+ *
+ * Returns 0 if successful.
+ */
+int tegra_hsp_db_add_handler(int master, db_handler_t handler, void *data)
+{
+       if (!handler || !is_master_valid(master))
+               return -EINVAL;
+
+       if (unlikely(db_irq <= 0))
+               return -ENODEV;
+
+       mutex_lock(&db_handlers_lock);
+       if (likely(db_handlers[master].handler != NULL)) {
+               mutex_unlock(&db_handlers_lock);
+               return -EBUSY;
+       }
+
+       disable_irq(db_irq);
+       db_handlers[master].handler = handler;
+       db_handlers[master].data = data;
+       enable_irq(db_irq);
+       mutex_unlock(&db_handlers_lock);
+
+       return 0;
+}
+EXPORT_SYMBOL(tegra_hsp_db_add_handler);
+
+/**
+ * tegra_hsp_db_del_handler: unregister an CCPLEX doorbell IRQ handler
+ * @handler:   IRQ handler
+ *
+ * Returns 0 if successful.
+ */
+int tegra_hsp_db_del_handler(int master)
+{
+       if (!is_master_valid(master))
+               return -EINVAL;
+
+       if (unlikely(db_irq <= 0))
+               return -ENODEV;
+
+       mutex_lock(&db_handlers_lock);
+       WARN_ON(db_handlers[master].handler == NULL);
+       disable_irq(db_irq);
+       db_handlers[master].handler = NULL;
+       db_handlers[master].data = NULL;
+       enable_irq(db_irq);
+       mutex_unlock(&db_handlers_lock);
+
+       return 0;
+}
+EXPORT_SYMBOL(tegra_hsp_db_del_handler);
+
+#ifdef CONFIG_DEBUG_FS
+
+static int hsp_dbg_enable_master_show(void *data, u64 *val)
+{
+       *val = hsp_readl(db_bases[HSP_DB_CCPLEX], HSP_DB_REG_ENABLE);
+       return 0;
+}
+
+static int hsp_dbg_enable_master_store(void *data, u64 val)
+{
+       if (!is_master_valid((u32)val))
+               return -EINVAL;
+       return tegra_hsp_db_enable_master((enum tegra_hsp_master)val);
+}
+
+static int hsp_dbg_ring_store(void *data, u64 val)
+{
+       if (val >= HSP_NR_DBS)
+               return -EINVAL;
+       return tegra_hsp_db_ring((enum tegra_hsp_doorbell)val);
+}
+
+static int hsp_dbg_can_ring_show(void *data, u64 *val)
+{
+       enum tegra_hsp_doorbell db;
+       *val = 0ull;
+       for (db = HSP_FIRST_DB; db <= HSP_LAST_DB; db++)
+               if (tegra_hsp_db_can_ring(db))
+                       *val |= BIT(db);
+       return 0;
+}
+
+/* By convention, CPU shouldn't touch other processors' DBs.
+ * So this interface is created for debugging purpose.
+ */
+static int hsp_dbg_can_ring_store(void *data, u64 val)
+{
+       int reg;
+       enum tegra_hsp_doorbell dbell = (int)val;
+
+       if (!hsp_ready() || dbell >= HSP_NR_DBS)
+               return -EINVAL;
+
+       mutex_lock(&hsp_top_lock);
+       reg = hsp_readl(db_bases[dbell], HSP_DB_REG_ENABLE);
+       reg |= BIT(HSP_MASTER_CCPLEX);
+       hsp_writel(db_bases[dbell], HSP_DB_REG_ENABLE, reg);
+       mutex_unlock(&hsp_top_lock);
+       return 0;
+}
+
+static int hsp_dbg_pending_show(void *data, u64 *val)
+{
+       *val = hsp_readl(db_bases[HSP_DB_CCPLEX], HSP_DB_REG_PENDING);
+       return 0;
+}
+
+static int hsp_dbg_pending_store(void *data, u64 val)
+{
+       hsp_writel(db_bases[HSP_DB_CCPLEX], HSP_DB_REG_PENDING, (u32)val);
+       return 0;
+}
+
+static int hsp_dbg_raw_show(void *data, u64 *val)
+{
+       *val = hsp_readl(db_bases[HSP_DB_CCPLEX], HSP_DB_REG_RAW);
+       return 0;
+}
+
+static int hsp_dbg_raw_store(void *data, u64 val)
+{
+       hsp_writel(db_bases[HSP_DB_CCPLEX], HSP_DB_REG_RAW, (u32)val);
+       return 0;
+}
+
+static u32 ccplex_intr_count;
+
+static void hsp_dbg_db_handler(void *data)
+{
+       ccplex_intr_count++;
+}
+
+static int hsp_dbg_intr_count_show(void *data, u64 *val)
+{
+       *val = ccplex_intr_count;
+       return 0;
+}
+
+static int hsp_dbg_intr_count_store(void *data, u64 val)
+{
+       ccplex_intr_count = val;
+       return 0;
+}
+
+static int hsp_dbg_doorbells_show(struct seq_file *s, void *data)
+{
+       int db;
+       seq_printf(s, "%-20s%-10s%-10s\n", "name", "id", "offset");
+       seq_printf(s, "--------------------------------------------------\n");
+       for (db = HSP_FIRST_DB; db <= HSP_LAST_DB; db++)
+               seq_printf(s, "%-20s%-10d%-10lx\n", db_names[db], db,
+                       (uintptr_t)(db_bases[db] - hsp_top.base));
+       return 0;
+}
+
+static int hsp_dbg_masters_show(struct seq_file *s, void *data)
+{
+       int m;
+       seq_printf(s, "%-20s%-10s\n", "name", "id");
+       seq_printf(s, "----------------------------------------\n");
+       for_each_valid_master(m)
+               seq_printf(s, "%-20s%-10d\n", master_names[m], m);
+       return 0;
+}
+
+static int hsp_dbg_handlers_show(struct seq_file *s, void *data)
+{
+       int m;
+       seq_printf(s, "%-20s%-30s\n", "master", "handler");
+       seq_printf(s, "--------------------------------------------------\n");
+       for_each_valid_master(m)
+               seq_printf(s, "%-20s%-30pS\n", master_names[m],
+                       db_handlers[m].handler);
+       return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(enable_master_fops,
+       hsp_dbg_enable_master_show, hsp_dbg_enable_master_store, "%llx\n");
+DEFINE_SIMPLE_ATTRIBUTE(ring_fops,
+       NULL, hsp_dbg_ring_store, "%lld\n");
+DEFINE_SIMPLE_ATTRIBUTE(can_ring_fops,
+       hsp_dbg_can_ring_show, hsp_dbg_can_ring_store, "%llx\n");
+DEFINE_SIMPLE_ATTRIBUTE(pending_fops,
+       hsp_dbg_pending_show, hsp_dbg_pending_store, "%llx\n");
+DEFINE_SIMPLE_ATTRIBUTE(raw_fops,
+       hsp_dbg_raw_show, hsp_dbg_raw_store, "%llx\n");
+DEFINE_SIMPLE_ATTRIBUTE(intr_count_fops,
+       hsp_dbg_intr_count_show, hsp_dbg_intr_count_store, "%lld\n");
+
+#define DEFINE_DBG_OPEN(name) \
+static int hsp_dbg_##name##_open(struct inode *inode, struct file *file) \
+{ \
+       return single_open(file, hsp_dbg_##name##_show, inode->i_private); \
+} \
+static const struct file_operations name##_fops = { \
+       .open = hsp_dbg_##name##_open, \
+       .read = seq_read, \
+       .llseek = seq_lseek, \
+       .release = single_release, \
+};
+
+DEFINE_DBG_OPEN(doorbells);
+DEFINE_DBG_OPEN(masters);
+DEFINE_DBG_OPEN(handlers);
+
+struct debugfs_entry {
+       const char *name;
+       const struct file_operations *fops;
+       mode_t mode;
+};
+
+static struct debugfs_entry hsp_dbg_attrs[] = {
+       { "enable_master", &enable_master_fops, S_IRUGO | S_IWUSR },
+       { "ring", &ring_fops, S_IWUSR },
+       { "can_ring", &can_ring_fops, S_IRUGO | S_IWUSR },
+       { "pending", &pending_fops, S_IRUGO | S_IWUSR },
+       { "raw", &raw_fops, S_IRUGO | S_IWUSR },
+       { "doorbells", &doorbells_fops, S_IRUGO },
+       { "masters", &masters_fops, S_IRUGO },
+       { "handlers", &handlers_fops, S_IRUGO },
+       { "intr_count", &intr_count_fops, S_IRUGO },
+       { NULL, NULL, 0 }
+};
+
+static struct dentry *hsp_debugfs_root;
+
+static int debugfs_init(void)
+{
+       struct dentry *dent;
+       struct debugfs_entry *fent;
+
+       if (!hsp_ready())
+               return 0;
+
+       hsp_debugfs_root = debugfs_create_dir("tegra_hsp", NULL);
+       if (IS_ERR_OR_NULL(hsp_debugfs_root))
+               return -EFAULT;
+
+       fent = hsp_dbg_attrs;
+       while (fent->name) {
+               dent = debugfs_create_file(fent->name, fent->mode,
+                       hsp_debugfs_root, NULL, fent->fops);
+               if (IS_ERR_OR_NULL(dent))
+                       goto abort;
+               fent++;
+       }
+
+       tegra_hsp_db_add_handler(HSP_MASTER_CCPLEX, hsp_dbg_db_handler, NULL);
+
+       return 0;
+
+abort:
+       debugfs_remove_recursive(hsp_debugfs_root);
+       return -EFAULT;
+}
+late_initcall(debugfs_init);
+#endif
+
+#define NV(prop) "nvidia," prop
+
+int tegra_hsp_init(void)
+{
+       int i;
+       int irq;
+       struct device_node *np = NULL;
+       void __iomem *base;
+       int ret = -EINVAL;
+       u32 reg;
+
+       if (hsp_ready())
+               return 0;
+
+       /* Look for TOP0 HSP (the one with the doorbells) */
+       do {
+               np = of_find_compatible_node(np, NULL, NV("tegra186-hsp"));
+               if (np == NULL) {
+                       WARN_ON(1);
+                       pr_err("tegra-hsp: NV data required.\n");
+                       return -ENODEV;
+               }
+
+               irq = of_irq_get_byname(np, "doorbell");
+       } while (irq <= 0);
+
+       base = of_iomap(np, 0);
+       if (base == NULL) {
+               pr_err("tegra-hsp: failed to map HSP IO space.\n");
+               goto out;
+       }
+
+       reg = readl(base + HSP_DIMENSIONING);
+       hsp_top.base = base;
+
+       base += 0x10000; /* skip common regs */
+       base += (reg & 0x000f) << 15; /* skip shared mailboxes */
+       base += ((reg & 0x00f0) >> 4) << 16; /* skip shared semaphores */
+       base += ((reg & 0x0f00) >> 8) << 16; /* skip arbitrated semaphores */
+
+       for (i = HSP_FIRST_DB; i <= HSP_LAST_DB; i++) {
+               db_bases[i] = base;
+               base += 0x100;
+               pr_debug("tegra-hsp: db[%d]: %p\n", i, db_bases[i]);
+       }
+
+       ret = request_irq(irq, dbell_irq, IRQF_NO_SUSPEND, "hsp", NULL);
+       if (ret) {
+               pr_err("tegra-hsp: request_irq() failed (%d)\n", ret);
+               goto out;
+       }
+
+       db_irq = irq;
+       hsp_top.status = HSP_INIT_OKAY;
+       ret = 0;
+out:
+       of_node_put(np);
+       return ret;
+}
diff --git a/drivers/platform/tegra/tegra186-hsp.c b/drivers/platform/tegra/tegra186-hsp.c
new file mode 100644 (file)
index 0000000..4438be7
--- /dev/null
@@ -0,0 +1,598 @@
+/*
+ * Copyright (c) 2016 NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/rculist.h>
+
+#include <linux/tegra-hsp.h>
+
+#define NV(p) "nvidia," #p
+
+struct tegra_hsp {
+       void __iomem *base;
+       struct reset_control *reset;
+       struct mutex lock;
+       u8 n_sm;
+       u8 n_as;
+       u8 n_ss;
+       u8 n_db;
+       u8 n_si;
+       bool mbox_ie;
+};
+
+struct tegra_hsp_irq {
+       int irq;
+       u8 si_index;
+       u8 ie_shift;
+       u8 index;
+       u8 per_sm_ie;
+};
+
+struct tegra_hsp_sm_pair {
+       tegra_hsp_sm_full_fn notify_full;
+       struct tegra_hsp_irq full;
+       tegra_hsp_sm_empty_fn notify_empty;
+       struct tegra_hsp_irq empty;
+       struct device dev;
+};
+
+#define TEGRA_HSP_IE(si)               (0x100 + (4 * (si)))
+#define TEGRA_HSP_IE_SM_EMPTY(sm)      (0x1u << (sm))
+#define TEGRA_HSP_IE_SM_FULL(sm)       (0x100u << (sm))
+#define TEGRA_HSP_IE_DB(db)            (0x10000u << (db))
+#define TEGRA_HSP_IE_AS(as)            (0x1000000u << (as))
+#define TEGRA_HSP_DIMENSIONING         0x380
+#define TEGRA_HSP_SM(sm)               (0x10000 + (0x8000 * (sm)))
+#define TEGRA_HSP_SM_FULL              0x80000000u
+
+#define TEGRA_HSP_SM_IE_FULL           0x4u
+#define TEGRA_HSP_SM_IE_EMPTY          0x8u
+
+static void __iomem *tegra_hsp_reg(struct device *dev, u32 offset)
+{
+       struct tegra_hsp *hsp = dev_get_drvdata(dev);
+
+       return hsp->base + offset;
+}
+
+static void __iomem *tegra_hsp_ie_reg(struct device *dev, u8 si)
+{
+       return tegra_hsp_reg(dev, TEGRA_HSP_IE(si));
+}
+
+static void tegra_hsp_irq_suspend(struct device *dev, struct tegra_hsp_irq *hi)
+{
+       if (hi->si_index != 0xff) {
+               struct tegra_hsp *hsp = dev_get_drvdata(dev);
+               void __iomem *reg = tegra_hsp_ie_reg(dev, hi->si_index);
+
+               mutex_lock(&hsp->lock);
+               writel(readl(reg) & ~(1u << hi->ie_shift), reg);
+               mutex_unlock(&hsp->lock);
+       }
+}
+
+static void tegra_hsp_irq_resume(struct device *dev, struct tegra_hsp_irq *hi)
+{
+       if (hi->si_index != 0xff) {
+               struct tegra_hsp *hsp = dev_get_drvdata(dev);
+               void __iomem *reg = tegra_hsp_ie_reg(dev, hi->si_index);
+
+               mutex_lock(&hsp->lock);
+               writel(readl(reg) | (1u << hi->ie_shift), reg);
+               mutex_unlock(&hsp->lock);
+       }
+}
+
+static void __iomem *tegra_hsp_sm_reg(struct device *dev, u32 sm)
+{
+       return tegra_hsp_reg(dev, TEGRA_HSP_SM(sm));
+}
+
+static void tegra_hsp_enable_per_sm_irq(struct device *dev,
+                                       const struct tegra_hsp_irq *hi,
+                                       int irq)
+{
+       if (hi->per_sm_ie != 0) {
+               void __iomem *reg = tegra_hsp_sm_reg(dev, hi->index);
+
+               /* Disable empty if enable full, disable full if enable empty */
+               writel(0, reg + TEGRA_HSP_SM_IE_EMPTY + TEGRA_HSP_SM_IE_FULL
+                       - hi->per_sm_ie);
+               writel(1, reg + hi->per_sm_ie);
+       } else if (!(irq < 0)) {
+               enable_irq(irq);
+       }
+}
+
+static void tegra_hsp_disable_per_sm_irq(struct device *dev,
+                                       const struct tegra_hsp_irq *hi)
+{
+       if (hi->per_sm_ie != 0)
+               writel(0, tegra_hsp_sm_reg(dev, hi->index) + hi->per_sm_ie);
+       else
+               disable_irq_nosync(hi->irq);
+}
+
+static irqreturn_t tegra_hsp_full_isr(int irq, void *data)
+{
+       struct tegra_hsp_irq *hi = data;
+       struct tegra_hsp_sm_pair *pair =
+               container_of(hi, struct tegra_hsp_sm_pair, full);
+       struct device *dev = pair->dev.parent;
+       void __iomem *reg = tegra_hsp_sm_reg(dev, hi->index);
+       u32 value = readl(reg);
+
+       if (!(value & TEGRA_HSP_SM_FULL))
+               return IRQ_NONE;
+
+       if (pair->notify_full != NULL) {
+               void *data = dev_get_drvdata(&pair->dev);
+
+               value = pair->notify_full(data, value & ~TEGRA_HSP_SM_FULL);
+       } else
+               value = 0;
+
+       /* Write new value to empty the mailbox and clear the interrupt */
+       writel(value & ~TEGRA_HSP_SM_FULL, reg);
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t tegra_hsp_empty_isr(int irq, void *data)
+{
+       struct tegra_hsp_irq *hi = data;
+       struct tegra_hsp_sm_pair *pair =
+               container_of(hi, struct tegra_hsp_sm_pair, empty);
+       struct device *dev = pair->dev.parent;
+       void __iomem *reg = tegra_hsp_sm_reg(dev, hi->index);
+       u32 value = readl(reg);
+
+       if (value & TEGRA_HSP_SM_FULL)
+               return IRQ_NONE;
+
+       tegra_hsp_disable_per_sm_irq(dev, &pair->empty);
+
+       pair->notify_empty(dev_get_drvdata(&pair->dev), value);
+       return IRQ_HANDLED;
+}
+
+static int tegra_hsp_get_shared_irq(struct device *dev, irq_handler_t handler,
+                                       unsigned long flags,
+                                       bool empty,
+                                       struct tegra_hsp_irq *hi)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct tegra_hsp *hsp = dev_get_drvdata(dev);
+       u8 ie_shift;
+       int ret = -ENODEV;
+       unsigned i;
+
+       if (empty)
+               ie_shift = hi->index;
+       else
+               ie_shift = hi->index + 8;
+
+       flags |= IRQF_PROBE_SHARED;
+
+       for (i = 0; i < hsp->n_si; i++) {
+               char irqname[8];
+
+               sprintf(irqname, "shared%X", i);
+               hi->irq = platform_get_irq_byname(pdev, irqname);
+               if (hi->irq < 0)
+                       continue;
+
+               hi->si_index = i;
+               hi->ie_shift = ie_shift;
+
+               ret = request_threaded_irq(hi->irq, NULL, handler, flags,
+                                               dev_name(dev), hi);
+               if (ret)
+                       continue;
+
+               dev_dbg(&pdev->dev, "using shared IRQ %u (%d)\n", i, hi->irq);
+
+               tegra_hsp_enable_per_sm_irq(dev, hi, -EPERM);
+
+               /* Update interrupt masks (for shared interrupts only) */
+               tegra_hsp_irq_resume(dev, hi);
+
+               return 0;
+       }
+
+       if (ret != -EPROBE_DEFER)
+               dev_err(dev, "cannot get shared IRQ: %d\n", ret);
+       return ret;
+}
+
+static int tegra_hsp_get_sm_irq(struct device *dev, bool empty,
+                               struct tegra_hsp_irq *hi)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       irq_handler_t handler = empty ? tegra_hsp_empty_isr
+                                       : tegra_hsp_full_isr;
+       unsigned long flags = IRQF_ONESHOT;
+       char name[7];
+
+       if (!empty || hi->per_sm_ie != 0)
+               flags |= IRQF_SHARED;
+
+       /* Look for dedicated internal IRQ */
+       sprintf(name, empty ? "empty%X" : "full%X", hi->index);
+       hi->irq = platform_get_irq_byname(pdev, name);
+       if (!(hi->irq < 0)) {
+               hi->si_index = 0xff;
+
+               if (request_threaded_irq(hi->irq, NULL, handler, flags,
+                                               dev_name(dev), hi) == 0) {
+                       tegra_hsp_enable_per_sm_irq(dev, hi, -EPERM);
+                       return 0;
+               }
+       }
+
+       /* Look for a free shared IRQ */
+       return tegra_hsp_get_shared_irq(dev, handler, flags, empty, hi);
+}
+
+static void tegra_hsp_irq_free(struct device *dev, struct tegra_hsp_irq *hi)
+{
+       tegra_hsp_irq_suspend(dev, hi);
+       free_irq(hi->irq, hi);
+}
+
+static int tegra_hsp_sm_suspend(struct device *dev)
+{
+       struct tegra_hsp_sm_pair *pair =
+               container_of(dev, struct tegra_hsp_sm_pair, dev);
+
+       tegra_hsp_irq_suspend(dev->parent, &pair->full);
+       if (pair->notify_empty != NULL)
+               tegra_hsp_irq_suspend(dev->parent, &pair->empty);
+       return 0;
+}
+
+static int tegra_hsp_sm_resume(struct device *dev)
+{
+       struct tegra_hsp_sm_pair *pair =
+               container_of(dev, struct tegra_hsp_sm_pair, dev);
+
+       if (pair->notify_empty != NULL)
+               tegra_hsp_irq_resume(dev->parent, &pair->empty);
+       tegra_hsp_irq_resume(dev->parent, &pair->full);
+       return 0;
+}
+
+static const struct dev_pm_ops tegra_hsp_sm_pm_ops = {
+       .suspend_noirq  = tegra_hsp_sm_suspend,
+       .resume_noirq   = tegra_hsp_sm_resume,
+};
+
+static const struct device_type tegra_hsp_sm_dev_type = {
+       .name   = "tegra-hsp-shared-mailbox-pair",
+       .pm     = &tegra_hsp_sm_pm_ops,
+};
+
+static void tegra_hsp_sm_dev_release(struct device *dev)
+{
+       struct tegra_hsp_sm_pair *pair =
+               container_of(dev, struct tegra_hsp_sm_pair, dev);
+
+       kfree(pair);
+}
+
+static struct tegra_hsp_sm_pair *tegra_hsp_sm_pair_request(
+       struct device *dev, u32 index,
+       tegra_hsp_sm_full_fn full, tegra_hsp_sm_empty_fn empty, void *data)
+{
+       struct tegra_hsp *hsp = dev_get_drvdata(dev);
+       struct tegra_hsp_sm_pair *pair;
+       int err;
+
+       if (hsp == NULL)
+               return ERR_PTR(-EPROBE_DEFER);
+       if (index >= hsp->n_sm)
+               return ERR_PTR(-ENODEV);
+
+       pair = kzalloc(sizeof(*pair), GFP_KERNEL);
+       if (unlikely(pair == NULL))
+               return ERR_PTR(-ENOMEM);
+
+       pair->notify_full = full;
+       pair->notify_empty = empty;
+       pair->full.index = index;
+       pair->full.per_sm_ie = hsp->mbox_ie ? TEGRA_HSP_SM_IE_FULL : 0;
+       pair->empty.index = index ^ 1;
+       pair->empty.per_sm_ie = hsp->mbox_ie ? TEGRA_HSP_SM_IE_EMPTY : 0;
+       pair->dev.parent = dev;
+       pair->dev.type = &tegra_hsp_sm_dev_type;
+       pair->dev.release = tegra_hsp_sm_dev_release;
+       dev_set_name(&pair->dev, "%s:sm%u", dev_name(dev), index);
+       dev_set_drvdata(&pair->dev, data);
+
+       err = device_register(&pair->dev);
+       if (err) {
+               put_device(&pair->dev);
+               return ERR_PTR(err);
+       }
+
+       /* Get empty interrupt if necessary */
+       if (pair->notify_empty != NULL) {
+               err = tegra_hsp_get_sm_irq(dev, true, &pair->empty);
+               if (err)
+                       goto error;
+       }
+
+       /* Get full interrupt */
+       err = tegra_hsp_get_sm_irq(dev, false, &pair->full);
+       if (err) {
+               if (pair->notify_empty != NULL)
+                       tegra_hsp_irq_free(dev, &pair->empty);
+               goto error;
+       }
+
+       return pair;
+
+error:
+       put_device(&pair->dev);
+       return ERR_PTR(err);
+}
+
+/**
+ * of_tegra_sm_pair_request - request a Tegra HSP shared mailbox pair from DT.
+ *
+ * @np: device node
+ * @index: mailbox pair entry offset in the DT property
+ *
+ * Looks up a shared mailbox pair in device tree by index. The device node
+ * needs a nvidia,hsp-shared-mailbox property, containing pairs of
+ * OF phandle and mailbox number. The OF phandle points to the Tegra HSP
+ * platform device. The mailbox number refers to the consumer side mailbox.
+ * The producer side mailbox is the other one in the same (even-odd) pair.
+ */
+struct tegra_hsp_sm_pair *of_tegra_hsp_sm_pair_request(
+       const struct device_node *np, u32 index,
+       tegra_hsp_sm_full_fn full, tegra_hsp_sm_empty_fn empty, void *data)
+{
+       struct platform_device *pdev;
+       struct tegra_hsp_sm_pair *pair;
+       struct of_phandle_args smspec;
+       int err;
+
+       err = of_parse_phandle_with_fixed_args(np, NV(hsp-shared-mailbox), 1,
+                                               index, &smspec);
+       if (err)
+               return ERR_PTR(err);
+
+       pdev = of_find_device_by_node(smspec.np);
+       index = smspec.args[0];
+       of_node_put(smspec.np);
+
+       if (pdev == NULL)
+               return ERR_PTR(-EPROBE_DEFER);
+
+       pair = tegra_hsp_sm_pair_request(&pdev->dev, index, full, empty, data);
+       platform_device_put(pdev);
+       return pair;
+}
+EXPORT_SYMBOL(of_tegra_hsp_sm_pair_request);
+
+/**
+ * of_tegra_sm_pair_by_name - request a Tegra HSP shared mailbox pair from DT.
+ *
+ * @np: device node
+ * @name: mailbox pair entry name
+ *
+ * Looks up a shared mailbox pair in device tree by name. The device node needs
+ * nvidia,hsp-shared-mailbox and nvidia-hsp-shared-mailbox-names properties.
+ */
+struct tegra_hsp_sm_pair *of_tegra_hsp_sm_pair_by_name(
+       const struct device_node *np, char const *name,
+       tegra_hsp_sm_full_fn full, tegra_hsp_sm_empty_fn empty, void *data)
+{
+       int index;
+
+       /* The of_property_match_string() prototype will be fixed in _next */
+       /* Until then, we have to cast to non-const */
+       /* If match fails, index will be -1 and parse_phandles fails */
+       index = of_property_match_string((struct device_node *)np,
+                       NV(hsp-shared-mailbox-names), name);
+
+       return of_tegra_hsp_sm_pair_request(np, index, full, empty, data);
+}
+EXPORT_SYMBOL(of_tegra_hsp_sm_pair_by_name);
+
+/**
+ * tegra_hsp_sm_pair_free - free a Tegra HSP shared mailbox pair.
+ */
+void tegra_hsp_sm_pair_free(struct tegra_hsp_sm_pair *pair)
+{
+       struct device *dev;
+       struct tegra_hsp *hsp;
+
+       if (IS_ERR_OR_NULL(pair))
+               return;
+
+       dev = pair->dev.parent;
+       hsp = dev_get_drvdata(dev);
+
+       /* Make sure that the structure is no longer referenced.
+        * This also implies that callbacks are no longer pending. */
+       tegra_hsp_irq_free(dev, &pair->full);
+       if (pair->notify_empty != NULL)
+               tegra_hsp_irq_free(dev, &pair->empty);
+
+       device_unregister(&pair->dev);
+}
+EXPORT_SYMBOL(tegra_hsp_sm_pair_free);
+
+/**
+ * tegra_hsp_sm_pair_write - fill a Tegra HSP shared mailbox
+ *
+ * @pair: shared mailbox pair
+ * @value: value to fill mailbox with (only 31-bits low order bits are used)
+ *
+ * This writes a value to the producer side mailbox of a mailbox pair.
+ * The mailbox must be empty (especially if notify_empty callback is non-nul).
+ */
+void tegra_hsp_sm_pair_write(const struct tegra_hsp_sm_pair *pair,
+                               u32 value)
+{
+       struct device *dev = pair->dev.parent;
+       void __iomem *reg = tegra_hsp_sm_reg(dev, pair->empty.index);
+
+       /* Ensure any pending empty ISR invocation has disabled the IRQ */
+       if (pair->notify_empty != NULL) {
+               might_sleep();
+               synchronize_irq(pair->empty.irq);
+       }
+
+       writel(TEGRA_HSP_SM_FULL | value, reg);
+
+       if (pair->notify_empty != NULL)
+               tegra_hsp_enable_per_sm_irq(dev, &pair->empty, pair->empty.irq);
+}
+EXPORT_SYMBOL(tegra_hsp_sm_pair_write);
+
+bool tegra_hsp_sm_pair_is_empty(const struct tegra_hsp_sm_pair *pair)
+{
+       struct device *dev = pair->dev.parent;
+       u32 cvalue, pvalue;
+
+       /* Ensure any pending full ISR invocation has emptied the mailbox */
+       might_sleep();
+       synchronize_irq(pair->full.irq);
+
+       pvalue = readl(tegra_hsp_sm_reg(dev, pair->empty.index));
+       cvalue = readl(tegra_hsp_sm_reg(dev, pair->full.index));
+       return ((pvalue|cvalue) & TEGRA_HSP_SM_FULL) == 0;
+}
+EXPORT_SYMBOL(tegra_hsp_sm_pair_is_empty);
+
+static int tegra_hsp_suspend(struct device *dev)
+{
+       struct tegra_hsp *hsp = dev_get_drvdata(dev);
+       int ret = 0;
+
+       if (!IS_ERR(hsp->reset))
+               ret = reset_control_assert(hsp->reset);
+
+       return ret;
+}
+
+static int tegra_hsp_resume(struct device *dev)
+{
+       struct tegra_hsp *hsp = dev_get_drvdata(dev);
+       int ret = 0;
+
+       if (!IS_ERR(hsp->reset))
+               ret = reset_control_deassert(hsp->reset);
+
+       return ret;
+}
+
+static const struct dev_pm_ops tegra_hsp_pm_ops = {
+       .suspend_noirq  = tegra_hsp_suspend,
+       .resume_noirq   = tegra_hsp_resume,
+};
+
+static const struct of_device_id tegra_hsp_of_match[] = {
+       { .compatible = NV(tegra186-hsp), },
+       { },
+};
+
+static int tegra_hsp_probe(struct platform_device *pdev)
+{
+       struct device_node *np = pdev->dev.of_node;
+       struct resource *r;
+       struct tegra_hsp *hsp;
+       u32 reg;
+
+       if (np == NULL)
+               return -ENXIO;
+
+       hsp = devm_kzalloc(&pdev->dev, sizeof(*hsp), GFP_KERNEL);
+       if (unlikely(hsp == NULL))
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, hsp);
+       mutex_init(&hsp->lock);
+
+       r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (r == NULL)
+               return -EINVAL;
+
+       if (resource_size(r) < 0x10000) {
+               dev_err(&pdev->dev, "memory range too short\n");
+               return -EINVAL;
+       }
+
+       hsp->base = devm_ioremap(&pdev->dev, r->start, resource_size(r));
+       if (hsp->base == NULL)
+               return -ENOMEM;
+
+       /* devm_reset_control_get() fails indistinctly with -EPROBE_DEFER */
+       hsp->reset = of_reset_control_get(pdev->dev.of_node, "hsp");
+       if (hsp->reset == ERR_PTR(-EPROBE_DEFER))
+               return -EPROBE_DEFER;
+
+       reg = readl(tegra_hsp_reg(&pdev->dev, TEGRA_HSP_DIMENSIONING));
+       hsp->n_sm = reg & 0xf;
+       hsp->n_ss = (reg >> 4) & 0xf;
+       hsp->n_as = (reg >> 8) & 0xf;
+       hsp->n_db = (reg >> 12) & 0xf;
+       hsp->n_si = (reg >> 16) & 0xf;
+       hsp->mbox_ie = of_property_read_bool(pdev->dev.of_node, NV(mbox-ie));
+
+       if ((resource_size(r) >> 16) < (1 + (hsp->n_sm / 2) + hsp->n_ss +
+                                       hsp->n_as + (hsp->n_db > 0))) {
+               dev_err(&pdev->dev, "memory range too short\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static __exit int tegra_hsp_remove(struct platform_device *pdev)
+{
+       struct tegra_hsp *hsp = platform_get_drvdata(pdev);
+
+       reset_control_put(hsp->reset);
+       return 0;
+}
+
+static struct platform_driver tegra_hsp_driver = {
+       .probe  = tegra_hsp_probe,
+       .remove = __exit_p(tegra_hsp_remove),
+       .driver = {
+               .name   = "tegra186-hsp",
+               .owner  = THIS_MODULE,
+               .suppress_bind_attrs = true,
+               .of_match_table = of_match_ptr(tegra_hsp_of_match),
+               .pm     = &tegra_hsp_pm_ops,
+       },
+};
+module_platform_driver(tegra_hsp_driver);
+MODULE_AUTHOR("Remi Denis-Courmont <remid@nvidia.com>");
+MODULE_DESCRIPTION("NVIDIA Tegra 186 HSP driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/tegra-hsp.h b/include/linux/tegra-hsp.h
new file mode 100644 (file)
index 0000000..86ed010
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2014-2016 NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LINUX_TEGRA_HSP_H
+#define _LINUX_TEGRA_HSP_H
+
+#include <linux/types.h>
+
+enum tegra_hsp_master {
+       HSP_FIRST_MASTER = 1,
+
+       /* secure */
+       HSP_MASTER_SECURE_CCPLEX = HSP_FIRST_MASTER,
+       HSP_MASTER_SECURE_DPMU,
+       HSP_MASTER_SECURE_BPMP,
+       HSP_MASTER_SECURE_SPE,
+       HSP_MASTER_SECURE_SCE,
+       HSP_MASTER_SECURE_DMA,
+       HSP_MASTER_SECURE_TSECA,
+       HSP_MASTER_SECURE_TSECB,
+       HSP_MASTER_SECURE_JTAGM,
+       HSP_MASTER_SECURE_CSITE,
+       HSP_MASTER_SECURE_APE,
+
+       /* non-secure */
+       HSP_MASTER_CCPLEX = HSP_FIRST_MASTER + 16,
+       HSP_MASTER_DPMU,
+       HSP_MASTER_BPMP,
+       HSP_MASTER_SPE,
+       HSP_MASTER_SCE,
+       HSP_MASTER_DMA,
+       HSP_MASTER_TSECA,
+       HSP_MASTER_TSECB,
+       HSP_MASTER_JTAGM,
+       HSP_MASTER_CSITE,
+       HSP_MASTER_APE,
+
+       HSP_LAST_MASTER = HSP_MASTER_APE,
+};
+
+enum tegra_hsp_doorbell {
+       HSP_FIRST_DB = 0,
+       HSP_DB_DPMU = HSP_FIRST_DB,
+       HSP_DB_CCPLEX,
+       HSP_DB_CCPLEX_TZ,
+       HSP_DB_BPMP,
+       HSP_DB_SPE,
+       HSP_DB_SCE,
+       HSP_DB_APE,
+       HSP_LAST_DB = HSP_DB_APE,
+       HSP_NR_DBS,
+};
+
+typedef void (*db_handler_t)(void *data);
+
+int tegra_hsp_init(void);
+
+int tegra_hsp_db_enable_master(enum tegra_hsp_master master);
+
+int tegra_hsp_db_disable_master(enum tegra_hsp_master master);
+
+int tegra_hsp_db_ring(enum tegra_hsp_doorbell dbell);
+
+int tegra_hsp_db_can_ring(enum tegra_hsp_doorbell dbell);
+
+int tegra_hsp_db_add_handler(int master, db_handler_t handler, void *data);
+
+int tegra_hsp_db_del_handler(int master);
+
+#define tegra_hsp_find_master(mask, master)    ((mask) & (1 << (master)))
+
+struct tegra_hsp_sm_pair;
+
+typedef u32 (*tegra_hsp_sm_full_fn)(void *, u32);
+typedef void (*tegra_hsp_sm_empty_fn)(void *, u32);
+
+struct tegra_hsp_sm_pair *of_tegra_hsp_sm_pair_request(
+       const struct device_node *np, u32 index,
+       tegra_hsp_sm_full_fn, tegra_hsp_sm_empty_fn, void *);
+struct tegra_hsp_sm_pair *of_tegra_hsp_sm_pair_by_name(
+       const struct device_node *np, char const *name,
+       tegra_hsp_sm_full_fn, tegra_hsp_sm_empty_fn, void *);
+void tegra_hsp_sm_pair_free(struct tegra_hsp_sm_pair *);
+void tegra_hsp_sm_pair_write(const struct tegra_hsp_sm_pair *, u32 value);
+bool tegra_hsp_sm_pair_is_empty(const struct tegra_hsp_sm_pair *);
+
+#endif