[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