--- /dev/null
+* 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>
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"
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
--- /dev/null
+/*
+ * 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, ®, 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;
+}
--- /dev/null
+/*
+ * 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");
--- /dev/null
+/*
+ * 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