[PATCH v1 2/4] mailbox: Add iProc mailbox controller driver

Jonathan Richardson jonathan.richardson at broadcom.com
Tue Oct 18 12:00:36 PDT 2016


The Broadcom iProc mailbox controller handles all communication with a
Cortex-M0 MCU processor that provides support for power, clock, and
reset management.

Tested-by: Jonathan Richardson <jonathan.richardson at broadcom.com>
Reviewed-by: Jonathan Richardson <jonathan.richardson at broadcom.com>
Reviewed-by: Vikram Prakash <vikram.prakash at broadcom.com>
Reviewed-by: Shreesha Rajashekar <shreesha.rajashekar at broadcom.com>
Reviewed-by: Ray Jui <ray.jui at broadcom.com>
Reviewed-by: Scott Branden <scott.branden at broadcom.com>
Signed-off-by: Jonathan Richardson <jonathan.richardson at broadcom.com>
---
 drivers/mailbox/Kconfig             |  10 +
 drivers/mailbox/Makefile            |   2 +
 drivers/mailbox/bcm-iproc-mailbox.c | 422 ++++++++++++++++++++++++++++++++++++
 include/linux/bcm_iproc_mailbox.h   |  32 +++
 4 files changed, 466 insertions(+)
 create mode 100644 drivers/mailbox/bcm-iproc-mailbox.c
 create mode 100644 include/linux/bcm_iproc_mailbox.h

diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index 11eebfe..284916d 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -143,4 +143,14 @@ config BCM_PDC_MBOX
 	  Mailbox implementation for the Broadcom PDC ring manager,
 	  which provides access to various offload engines on Broadcom
 	  SoCs. Say Y here if you want to use the Broadcom PDC.
+
+config BCM_IPROC_MBOX
+	bool "Broadcom iProc Mailbox"
+	depends on ARCH_BCM_IPROC || COMPILE_TEST
+	default ARCH_BCM_IPROC
+	help
+	  Broadcom iProc architected SoC's have an always on Cortex-M0 MCU processor
+	  that handles support for power, clock, and reset management. The iProc
+	  mailbox controller handles all communication with this processor.
+
 endif
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index ace6fed..f96eab6 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -29,3 +29,5 @@ obj-$(CONFIG_XGENE_SLIMPRO_MBOX) += mailbox-xgene-slimpro.o
 obj-$(CONFIG_HI6220_MBOX)	+= hi6220-mailbox.o
 
 obj-$(CONFIG_BCM_PDC_MBOX)	+= bcm-pdc-mailbox.o
+
+obj-$(CONFIG_BCM_IPROC_MBOX)	+= bcm-iproc-mailbox.o
diff --git a/drivers/mailbox/bcm-iproc-mailbox.c b/drivers/mailbox/bcm-iproc-mailbox.c
new file mode 100644
index 0000000..4e5c97c
--- /dev/null
+++ b/drivers/mailbox/bcm-iproc-mailbox.c
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2016 Broadcom.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/of_device.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/mailbox_controller.h>
+#include <linux/mailbox_client.h>
+#include <linux/bcm_iproc_mailbox.h>
+#include <linux/delay.h>
+
+#define IPROC_CRMU_MAILBOX0_OFFSET       0x0
+#define IPROC_CRMU_MAILBOX1_OFFSET       0x4
+
+#define CRMU_IPROC_MAILBOX0_OFFSET       0x8
+#define CRMU_IPROC_MAILBOX1_OFFSET       0xc
+
+#define IPROC_INTR_STATUS                0x34
+#define IPROC_MAILBOX_INTR_SHIFT         0
+#define IPROC_MAILBOX_INTR_MASK          0x1
+
+#define IPROC_INTR_CLEAR                 0x3c
+#define IPROC_MAILBOX_INTR_CLR_SHIFT     0
+
+#define M0_IPC_CMD_DONE_MASK             0x80000000
+#define M0_IPC_CMD_REPLY_MASK            0x3fff0000
+#define M0_IPC_CMD_REPLY_SHIFT           16
+
+/* Domains that interrupts get forwarded to. */
+enum mbox_domain {
+	AON_GPIO_DOMAIN = 0,
+};
+
+enum iproc_m0_cmd {
+	/*
+	 * Enable/disable GPIO event forwarding from M0 to A9
+	 * Param - 1 to enable, 0 to disable
+	 * Response - return code
+	 */
+	M0_IPC_M0_CMD_AON_GPIO_FORWARDING_ENABLE = 0xe,
+
+	/*
+	 * AON GPIO interrupt ("forwarded" to IPROC)
+	 * Param - AON GPIO mask
+	 */
+	M0_IPC_HOST_CMD_AON_GPIO_EVENT = 0x102,
+};
+
+struct iproc_mbox {
+	struct device         *dev;
+	void __iomem          *base;
+	spinlock_t            lock;
+	struct irq_domain     *irq_domain;
+	struct mbox_controller controller;
+	u32                   num_chans;
+	int                   mbox_irq;
+};
+
+static struct lock_class_key mbox_lock_class;
+
+static void iproc_mbox_irq_handler(struct irq_desc *desc)
+{
+	struct iproc_mbox *mbox = irq_desc_get_handler_data(desc);
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	unsigned long status;
+	u32 cmd, param;
+	int virq;
+
+	chained_irq_enter(chip, desc);
+
+	/* Determine type of interrupt. */
+	status = readl(mbox->base + IPROC_INTR_STATUS);
+	status = (status >> IPROC_MAILBOX_INTR_SHIFT) &
+		IPROC_MAILBOX_INTR_MASK;
+
+	/* Process mailbox interrupts. */
+	if (status) {
+		writel(1 << IPROC_MAILBOX_INTR_CLR_SHIFT,
+			mbox->base + IPROC_INTR_CLEAR);
+
+		cmd = readl(mbox->base + CRMU_IPROC_MAILBOX0_OFFSET);
+		param = readl(mbox->base + CRMU_IPROC_MAILBOX1_OFFSET);
+
+		dev_dbg(mbox->dev,
+			"Received message from M0: cmd 0x%x param 0x%x\n",
+			cmd, param);
+
+		/* Process AON GPIO interrupt - forward to GPIO handler. */
+		if (cmd == M0_IPC_HOST_CMD_AON_GPIO_EVENT) {
+			virq = irq_find_mapping(mbox->irq_domain,
+				AON_GPIO_DOMAIN);
+			generic_handle_irq(virq);
+		}
+	}
+
+	chained_irq_exit(chip, desc);
+}
+
+static int iproc_mbox_send_data_m0_imp(struct iproc_mbox *mbox,
+	struct iproc_mbox_msg *msg, int max_retries, int poll_period_us)
+{
+	unsigned long flags;
+	u32 val;
+	int err = 0;
+	int retries;
+
+	spin_lock_irqsave(&mbox->lock, flags);
+
+	dev_dbg(mbox->dev, "Send msg to M0: cmd=0x%x, param=0x%x, wait_ack=%d\n",
+		msg->cmd, msg->param, msg->wait_ack);
+
+	writel(msg->cmd, mbox->base + IPROC_CRMU_MAILBOX0_OFFSET);
+	writel(msg->param, mbox->base + IPROC_CRMU_MAILBOX1_OFFSET);
+
+	if (msg->wait_ack) {
+		err = msg->reply_code = -ETIMEDOUT;
+		for (retries = 0; retries < max_retries; retries++) {
+			val = readl(mbox->base + IPROC_CRMU_MAILBOX0_OFFSET);
+			if (val & M0_IPC_CMD_DONE_MASK) {
+				/*
+				 * M0 replied - save reply code and
+				 * clear error.
+				 */
+				msg->reply_code = (val &
+					M0_IPC_CMD_REPLY_MASK) >>
+					M0_IPC_CMD_REPLY_SHIFT;
+				err = 0;
+				break;
+			}
+			udelay(poll_period_us);
+		}
+	}
+
+	spin_unlock_irqrestore(&mbox->lock, flags);
+
+	return err;
+}
+
+static void iproc_mbox_aon_gpio_forwarding_enable(struct iproc_mbox *mbox,
+	bool en)
+{
+	struct iproc_mbox_msg msg;
+	const int max_retries = 5;
+	const int poll_period_us = 200;
+
+	msg.cmd = M0_IPC_M0_CMD_AON_GPIO_FORWARDING_ENABLE;
+	msg.param = en ? 1 : 0;
+	msg.wait_ack = true;
+
+	iproc_mbox_send_data_m0_imp(mbox, &msg, max_retries, poll_period_us);
+}
+
+static void iproc_mbox_irq_unmask(struct irq_data *d)
+{
+	struct iproc_mbox *iproc_mbox = irq_data_get_irq_chip_data(d);
+
+	iproc_mbox_aon_gpio_forwarding_enable(iproc_mbox, true);
+}
+
+static void iproc_mbox_irq_mask(struct irq_data *d)
+{
+	/* Do nothing - Mask callback is not required, since upon GPIO event,
+	 * M0 disables GPIO forwarding to A9. Hence, GPIO forwarding is already
+	 * disabled  when in mbox irq handler, and no other mbox events from M0
+	 * to A9 are expected until GPIO forwarding is enabled following
+	 * iproc_mbox_irq_unmask()
+	 */
+}
+
+static struct irq_chip iproc_mbox_irq_chip = {
+	.name = "bcm-iproc-mbox",
+	.irq_mask = iproc_mbox_irq_mask,
+	.irq_unmask = iproc_mbox_irq_unmask,
+};
+
+static int iproc_mbox_irq_map(struct irq_domain *d, unsigned int irq,
+	irq_hw_number_t hwirq)
+{
+	int ret;
+
+	ret = irq_set_chip_data(irq, d->host_data);
+	if (ret < 0)
+		return ret;
+	irq_set_lockdep_class(irq, &mbox_lock_class);
+	irq_set_chip_and_handler(irq, &iproc_mbox_irq_chip,
+		handle_simple_irq);
+
+	return 0;
+}
+
+static void iproc_mbox_irq_unmap(struct irq_domain *d, unsigned int irq)
+{
+	irq_set_chip_and_handler(irq, NULL, NULL);
+	irq_set_chip_data(irq, NULL);
+}
+
+static struct irq_domain_ops iproc_mbox_irq_ops = {
+	.map = iproc_mbox_irq_map,
+	.unmap = iproc_mbox_irq_unmap,
+	.xlate = irq_domain_xlate_onecell,
+};
+
+static const struct of_device_id iproc_mbox_of_match[] = {
+	{ .compatible = "brcm,iproc-mailbox" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, iproc_mbox_of_match);
+
+/*
+ * Sends a message to M0. The mailbox framework prevents multiple accesses to
+ * the same channel but there is only one h/w "channel". This driver allows
+ * multiple clients to create channels to the controller but must serialize
+ * access to the mailbox registers used to communicate with the M0.
+ */
+static int iproc_mbox_send_data_m0(struct mbox_chan *chan, void *data)
+{
+	struct iproc_mbox *mbox = dev_get_drvdata(chan->mbox->dev);
+	struct iproc_mbox_msg *msg = (struct iproc_mbox_msg *)data;
+	int err = 0;
+	const int poll_period_us = 5;
+	int max_retries;
+
+	if (!msg)
+		return -EINVAL;
+
+	/* At least 1 attempt for misconfigured clients. */
+	if (chan->cl->tx_tout == 0)
+		max_retries = 1;
+	else
+		max_retries = (chan->cl->tx_tout * 1000) / poll_period_us;
+
+	err = iproc_mbox_send_data_m0_imp(mbox, msg, max_retries,
+		poll_period_us);
+
+	return err;
+}
+
+static int iproc_mbox_startup(struct mbox_chan *chan)
+{
+	/* Do nothing. */
+	return 0;
+}
+
+static void iproc_mbox_shutdown(struct mbox_chan *chan)
+{
+	/* Do nothing. */
+}
+
+static struct mbox_chan_ops iproc_mbox_ops = {
+	.send_data    = iproc_mbox_send_data_m0,
+	.startup      = iproc_mbox_startup,
+	.shutdown     = iproc_mbox_shutdown,
+};
+
+static int __init iproc_mbox_probe(struct platform_device *pdev)
+{
+	int virq;
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	struct iproc_mbox *iproc_mbox;
+	int err;
+	struct device_node *node;
+	const char *mbox_prop_name = "mboxes";
+
+	dev_info(&pdev->dev, "Initializing iproc mailbox controller\n");
+
+	iproc_mbox = devm_kzalloc(dev, sizeof(*iproc_mbox), GFP_KERNEL);
+	if (!iproc_mbox)
+		return -ENOMEM;
+
+	iproc_mbox->dev = dev;
+	spin_lock_init(&iproc_mbox->lock);
+
+	platform_set_drvdata(pdev, iproc_mbox);
+
+	/* Count number of "mboxes" properties to determine # channels. */
+	for_each_of_allnodes(node) {
+		struct property *prop = of_find_property(
+			node, mbox_prop_name, NULL);
+		if (prop) {
+			struct device_node *mbox_phandle = of_parse_phandle(
+				node, mbox_prop_name, 0);
+			if (mbox_phandle == dev->of_node)
+				iproc_mbox->num_chans++;
+		}
+	}
+
+	/*
+	 * Allocate mailbox channels. If the mailbox driver is only being used
+	 * to forward interrupts to the gpio domain then there may be no
+	 * clients.
+	 */
+	if (iproc_mbox->num_chans > 0) {
+		struct mbox_chan *chans = devm_kzalloc(&pdev->dev,
+			sizeof(*chans) * iproc_mbox->num_chans, GFP_KERNEL);
+
+		if (!chans)
+			return -ENOMEM;
+
+		/* Initialize mailbox controller. */
+		iproc_mbox->controller.dev = iproc_mbox->dev;
+		iproc_mbox->controller.num_chans = iproc_mbox->num_chans;
+		iproc_mbox->controller.chans = chans;
+		iproc_mbox->controller.ops = &iproc_mbox_ops;
+		iproc_mbox->controller.txdone_irq = false;
+		iproc_mbox->controller.txdone_poll = false;
+		err = mbox_controller_register(&iproc_mbox->controller);
+		if (err) {
+			dev_err(&pdev->dev, "Register mailbox failed\n");
+			return err;
+		}
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	iproc_mbox->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(iproc_mbox->base)) {
+		dev_err(&pdev->dev, "unable to map I/O memory\n");
+		return PTR_ERR(iproc_mbox->base);
+	}
+
+	iproc_mbox->mbox_irq = irq_of_parse_and_map(dev->of_node, 0);
+	if (!iproc_mbox->mbox_irq) {
+		dev_err(&pdev->dev, "irq_of_parse_and_map failed\n");
+		return -ENODEV;
+	}
+
+	iproc_mbox->irq_domain = irq_domain_add_linear(dev->of_node, 1,
+		&iproc_mbox_irq_ops, iproc_mbox);
+	if (!iproc_mbox->irq_domain) {
+		dev_err(&pdev->dev, "unable to allocate IRQ domain\n");
+		err = -ENXIO;
+		goto dispose_mapping;
+	}
+
+	/* Map irq for AON GPIO interrupt handling into this domain. */
+	virq = irq_create_mapping(iproc_mbox->irq_domain, AON_GPIO_DOMAIN);
+	if (!virq) {
+		dev_err(&pdev->dev, "failed mapping irq into domain\n");
+		err = -ENXIO;
+		goto domain_remove;
+	}
+	dev_dbg(&pdev->dev, "irq for aon gpio domain: %d\n", virq);
+
+	irq_set_chained_handler_and_data(iproc_mbox->mbox_irq,
+		iproc_mbox_irq_handler, iproc_mbox);
+
+	return 0;
+
+domain_remove:
+	irq_domain_remove(iproc_mbox->irq_domain);
+
+dispose_mapping:
+	irq_dispose_mapping(iproc_mbox->mbox_irq);
+
+	return err;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int iproc_mbox_suspend(struct device *dev)
+{
+	struct iproc_mbox *mbox = dev_get_drvdata(dev);
+
+	dev_info(dev,
+		"Suspending mailbox controller: disabling GPIO forwarding\n");
+	iproc_mbox_aon_gpio_forwarding_enable(mbox, false);
+	synchronize_irq(mbox->mbox_irq);
+
+	return 0;
+}
+
+static int iproc_mbox_resume(struct device *dev)
+{
+	struct iproc_mbox *mbox = dev_get_drvdata(dev);
+
+	dev_info(dev,
+		"Resuming mailbox controller: enabling AON GPIO forwarding\n");
+	iproc_mbox_aon_gpio_forwarding_enable(mbox, true);
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(iproc_mbox_pm_ops, iproc_mbox_suspend,
+	iproc_mbox_resume);
+
+struct platform_driver iproc_mbox_driver = {
+	.driver = {
+		.name = "brcm,iproc-mailbox",
+		.of_match_table = iproc_mbox_of_match,
+		.pm = &iproc_mbox_pm_ops,
+	},
+	.probe = iproc_mbox_probe,
+};
+
+static int __init iproc_mbox_init(void)
+{
+	return platform_driver_register(&iproc_mbox_driver);
+}
+arch_initcall(iproc_mbox_init);
+
+MODULE_AUTHOR("Broadcom");
+MODULE_DESCRIPTION("Broadcom iProc Mailbox Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/bcm_iproc_mailbox.h b/include/linux/bcm_iproc_mailbox.h
new file mode 100644
index 0000000..68f37e4
--- /dev/null
+++ b/include/linux/bcm_iproc_mailbox.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 Broadcom.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef _BCM_IPROC_MAILBOX_H_
+#define _BCM_IPROC_MAILBOX_H_
+
+/*
+ * A message to send to the M0 processor.
+ * @cmd Command to send.
+ * @param Parameter corresponding to command.
+ * @wait_ack true if mbox_send_message() should wait for a reply from the M0,
+ *   false if the M0 doesn't reply. This depends on the message being sent.
+ * @reply_code The response code from the M0 for the command sent (wait_ack was
+ *   set to true).
+ */
+struct iproc_mbox_msg {
+	u32       cmd;
+	u32       param;
+	bool      wait_ack;
+	u32       reply_code;
+};
+
+#endif
-- 
1.9.1




More information about the linux-arm-kernel mailing list