mailbox: add support for ARM Message Handling Unit(MHU) controller
authorSudeep Holla <sudeep.holla@arm.com>
Tue, 15 Apr 2014 14:14:27 +0000 (15:14 +0100)
committerSudeep Holla <sudeep.holla@arm.com>
Mon, 2 Jun 2014 17:20:21 +0000 (18:20 +0100)
This patch adds support for ARM Message Handling Unit(MHU) controller
that provides control logic and interrupt generation to support
inter-processor communication between the Application Processor and
the System Control Processor(SCP).

This support is built on the existing common mailbox framework for
client/protocol drivers and controller drivers of Inter Processor
Communication(IPC). SCP controls most of the power management on the
Application Processors.

Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
drivers/mailbox/Kconfig
drivers/mailbox/Makefile
drivers/mailbox/arm_mhu.c [new file with mode: 0644]
drivers/mailbox/arm_mhu.h [new file with mode: 0644]

index c8b5c13..b772e2a 100644 (file)
@@ -6,6 +6,20 @@ menuconfig MAILBOX
          signals. Say Y if your platform supports hardware mailboxes.
 
 if MAILBOX
+config ARM_MHU_MBOX
+       bool "ARM Message Handling Unit (MHU) Mailbox"
+       help
+         This driver provides support for inter-processor communication
+         between System Control Processor (SCP) with Cortex-M3 processor
+         and Application Processors (AP) on some ARM based systems with
+         MHU peripheral.
+
+         SCP controls most of the power managament on the Application
+         Processors. It offers control and management of: the core/cluster
+         power states, various power domain DVFS including the core/cluster,
+         certain system clocks configuration, thermal sensors and many
+         others.
+
 config PL320_MBOX
        bool "ARM PL320 Mailbox"
        depends on ARM_AMBA
index f3f4ab7..99264d9 100644 (file)
@@ -2,6 +2,7 @@
 
 obj-$(CONFIG_MAILBOX)          += mailbox.o
 
+obj-$(CONFIG_ARM_MHU_MBOX)     += arm_mhu.o
 obj-$(CONFIG_PL320_MBOX)       += pl320.o
 
 obj-$(CONFIG_OMAP_MBOX)                += omap-mailbox.o
diff --git a/drivers/mailbox/arm_mhu.c b/drivers/mailbox/arm_mhu.c
new file mode 100644 (file)
index 0000000..158cba4
--- /dev/null
@@ -0,0 +1,313 @@
+/*
+ * Driver for the Message Handling Unit (MHU) which is the peripheral in
+ * the Compute SubSystem (CSS) providing a mechanism for inter-processor
+ * communication between System Control Processor (SCP) with Cortex-M3
+ * processor and Application Processors (AP).
+ *
+ * The MHU peripheral provides a mechanism to assert interrupt signals to
+ * facilitate inter-processor message passing between the SCP and the AP.
+ * The message payload can be deposited into main memory or on-chip memories.
+ * The MHU supports three bi-directional channels - low priority, high
+ * priority and secure(can't be used in non-secure execution modes)
+ *
+ * Copyright (C) 2014 ARM Ltd.
+ *
+ * Author: Sudeep Holla <sudeep.holla@arm.com>
+ *
+ * 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/>.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/mailbox_controller.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#include "arm_mhu.h"
+
+#define DRIVER_NAME            CONTROLLER_NAME"_drv"
+
+/*
+ * +--------------------+-------+---------------+
+ * |  Hardware Register | Offset|  Driver View  |
+ * +--------------------+-------+---------------+
+ * |  SCP_INTR_L_STAT   | 0x000 |  RX_STATUS(L) |
+ * |  SCP_INTR_L_SET    | 0x008 |  RX_SET(L)    |
+ * |  SCP_INTR_L_CLEAR  | 0x010 |  RX_CLEAR(L)  |
+ * +--------------------+-------+---------------+
+ * |  SCP_INTR_H_STAT   | 0x020 |  RX_STATUS(H) |
+ * |  SCP_INTR_H_SET    | 0x028 |  RX_SET(H)    |
+ * |  SCP_INTR_H_CLEAR  | 0x030 |  RX_CLEAR(H)  |
+ * +--------------------+-------+---------------+
+ * |  CPU_INTR_L_STAT   | 0x100 |  TX_STATUS(L) |
+ * |  CPU_INTR_L_SET    | 0x108 |  TX_SET(L)    |
+ * |  CPU_INTR_L_CLEAR  | 0x110 |  TX_CLEAR(L)  |
+ * +--------------------+-------+---------------+
+ * |  CPU_INTR_H_STAT   | 0x120 |  TX_STATUS(H) |
+ * |  CPU_INTR_H_SET    | 0x128 |  TX_SET(H)    |
+ * |  CPU_INTR_H_CLEAR  | 0x130 |  TX_CLEAR(H)  |
+ * +--------------------+-------+---------------+
+*/
+#define RX_OFFSET(chan)                ((chan) * 0x20)
+#define RX_STATUS(chan)                RX_OFFSET(chan)
+#define RX_SET(chan)           (RX_OFFSET(chan) + 0x8)
+#define RX_CLEAR(chan)         (RX_OFFSET(chan) + 0x10)
+
+#define TX_OFFSET(chan)                (0x100 + (chan) * 0x20)
+#define TX_STATUS(chan)                TX_OFFSET(chan)
+#define TX_SET(chan)           (TX_OFFSET(chan) + 0x8)
+#define TX_CLEAR(chan)         (TX_OFFSET(chan) + 0x10)
+
+/*
+ * +---------------+-------+----------------+
+ * |    Payload    | Offset|  Driver View   |
+ * +---------------+-------+----------------+
+ * |  SCP->AP Low  | 0x000 |  RX_PAYLOAD(L) |
+ * |  SCP->AP High | 0x400 |  RX_PAYLOAD(H) |
+ * +---------------+-------+----------------+
+ * |  AP->SCP Low  | 0x200 |  TX_PAYLOAD(H) |
+ * |  AP->SCP High | 0x600 |  TX_PAYLOAD(H) |
+ * +---------------+-------+----------------+
+*/
+#define PAYLOAD_MAX_SIZE       0x200
+#define PAYLOAD_OFFSET         0x400
+#define RX_PAYLOAD(chan)       ((chan) * PAYLOAD_OFFSET)
+#define TX_PAYLOAD(chan)       ((chan) * PAYLOAD_OFFSET + PAYLOAD_MAX_SIZE)
+
+struct mhu_chan {
+       int index;
+       int rx_irq;
+       struct mbox_link link;
+       struct mhu_ctlr *ctlr;
+       struct mhu_data_buf *data;
+};
+
+struct mhu_ctlr {
+       struct device *dev;
+       void __iomem *mbox_base;
+       void __iomem *payload_base;
+       struct mbox_controller mbox_con;
+       struct mhu_chan channels[CHANNEL_MAX];
+};
+
+static inline struct mhu_chan *to_mhu_chan(struct mbox_link *lnk)
+{
+       if (!lnk)
+               return NULL;
+
+       return container_of(lnk, struct mhu_chan, link);
+}
+
+static irqreturn_t mbox_handler(int irq, void *p)
+{
+       struct mhu_chan *chan = to_mhu_chan(p);
+       struct mhu_ctlr *ctlr = chan->ctlr;
+       int idx = chan->index;
+       u32 status = readl(ctlr->mbox_base + RX_STATUS(idx));
+
+       if (status && irq == chan->rx_irq) {
+               struct mhu_data_buf *data = chan->data;
+               if (!data)
+                       return IRQ_NONE;        /* spurious */
+               if (data->rx_buf)
+                       memcpy(data->rx_buf,
+                              ctlr->payload_base + RX_PAYLOAD(idx),
+                              data->rx_size);
+               chan->data = NULL;
+               writel(~0, ctlr->mbox_base + RX_CLEAR(idx));
+               mbox_link_received_data(p, data);
+       }
+
+       return IRQ_HANDLED;
+}
+
+static int mhu_send_data(struct mbox_link *link, void *msg)
+{
+       struct mhu_chan *chan = to_mhu_chan(link);
+       struct mhu_ctlr *ctlr = chan->ctlr;
+       struct mhu_data_buf *data = msg;
+
+       if (!data)
+               return -EINVAL;
+
+       chan->data = data;
+       if (data->tx_buf)
+               memcpy(ctlr->payload_base + TX_PAYLOAD(chan->index),
+                      data->tx_buf, data->tx_size);
+       writel(data->cmd, ctlr->mbox_base + TX_SET(chan->index));
+
+       return 0;
+}
+
+static int mhu_startup(struct mbox_link *link, void *ignored)
+{
+       struct mhu_chan *chan = to_mhu_chan(link);
+
+       chan->data = NULL;
+       return request_threaded_irq(chan->rx_irq, NULL, mbox_handler,
+                                   IRQF_ONESHOT, link->link_name, link);
+}
+
+static void mhu_shutdown(struct mbox_link *link)
+{
+       struct mhu_chan *chan = to_mhu_chan(link);
+
+       chan->data = NULL;
+       free_irq(chan->rx_irq, link);
+}
+
+static bool mhu_last_tx_done(struct mbox_link *link)
+{
+       struct mhu_chan *chan = to_mhu_chan(link);
+       struct mhu_ctlr *ctlr = chan->ctlr;
+       return readl(ctlr->mbox_base + TX_STATUS(chan->index)) == 0;
+}
+
+static struct mbox_link_ops mhu_ops = {
+       .send_data = mhu_send_data,
+       .startup = mhu_startup,
+       .shutdown = mhu_shutdown,
+       .last_tx_done = mhu_last_tx_done,
+};
+
+static int mhu_probe(struct platform_device *pdev)
+{
+       int idx;
+       struct mhu_ctlr *ctlr;
+       struct mhu_chan *chan;
+       struct mbox_link **l;
+       struct resource *res;
+       struct device *dev = &pdev->dev;
+       static const char *const channel_names[] = {
+               CHANNEL_LOW_PRIORITY,
+               CHANNEL_HIGH_PRIORITY
+       };
+
+       ctlr = devm_kzalloc(dev, sizeof(*ctlr), GFP_KERNEL);
+       if (!ctlr) {
+               dev_err(dev, "failed to allocate memory\n");
+               return -ENOMEM;
+       }
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res) {
+               dev_err(dev, "failed to get mailbox memory resource\n");
+               return -ENXIO;
+       }
+
+       ctlr->mbox_base = devm_request_and_ioremap(dev, res);
+       if (!ctlr->mbox_base) {
+               dev_err(dev, "failed to request or ioremap mailbox control\n");
+               return -EADDRNOTAVAIL;
+       }
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+       if (!res) {
+               dev_err(dev, "failed to get payload memory resource\n");
+               return -ENXIO;
+       }
+
+       ctlr->payload_base = devm_request_and_ioremap(dev, res);
+       if (!ctlr->payload_base) {
+               dev_err(dev, "failed to request or ioremap mailbox payload\n");
+               return -EADDRNOTAVAIL;
+       }
+
+       ctlr->dev = dev;
+       platform_set_drvdata(pdev, ctlr);
+
+       l = devm_kzalloc(dev, sizeof(*l) * (CHANNEL_MAX + 1), GFP_KERNEL);
+       if (!l) {
+               dev_err(dev, "failed to allocate memory\n");
+               return -ENOMEM;
+       }
+
+       ctlr->mbox_con.links = l;
+       ctlr->mbox_con.txdone_poll = true;
+       ctlr->mbox_con.txpoll_period = 10;
+       ctlr->mbox_con.ops = &mhu_ops;
+       snprintf(ctlr->mbox_con.controller_name, 16, CONTROLLER_NAME);
+       ctlr->mbox_con.dev = dev;
+
+       for (idx = 0; idx < CHANNEL_MAX; idx++) {
+               chan = &ctlr->channels[idx];
+               chan->index = idx;
+               chan->ctlr = ctlr;
+               chan->rx_irq = platform_get_irq(pdev, idx);
+               if (chan->rx_irq < 0) {
+                       dev_err(dev, "failed to get interrupt for %s\n",
+                               channel_names[idx]);
+                       return -ENXIO;
+               }
+               l[idx] = &chan->link;
+               snprintf(l[idx]->link_name, 16, channel_names[idx]);
+       }
+
+       if (mbox_controller_register(&ctlr->mbox_con)) {
+               dev_err(dev, "failed to register mailbox controller\n");
+               return -ENOMEM;
+       }
+       _dev_info(dev, "registered mailbox controller %s\n",
+                 ctlr->mbox_con.controller_name);
+       return 0;
+}
+
+static int mhu_remove(struct platform_device *pdev)
+{
+       struct mhu_ctlr *ctlr = platform_get_drvdata(pdev);
+       struct device *dev = &pdev->dev;
+
+       mbox_controller_unregister(&ctlr->mbox_con);
+       _dev_info(dev, "unregistered mailbox controller %s\n",
+                 ctlr->mbox_con.controller_name);
+       devm_kfree(dev, ctlr->mbox_con.links);
+
+       devm_iounmap(dev, ctlr->payload_base);
+       devm_iounmap(dev, ctlr->mbox_base);
+
+       platform_set_drvdata(pdev, NULL);
+       devm_kfree(dev, ctlr);
+       return 0;
+}
+
+static struct of_device_id mhu_of_match[] = {
+       {.compatible = "arm,mhu"},
+       {},
+};
+
+MODULE_DEVICE_TABLE(of, mhu_of_match);
+
+static struct platform_driver mhu_driver = {
+       .probe = mhu_probe,
+       .remove = mhu_remove,
+       .driver = {
+                  .name = DRIVER_NAME,
+                  .of_match_table = mhu_of_match,
+                  },
+};
+module_platform_driver(mhu_driver);
+
+MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
+MODULE_DESCRIPTION("ARM MHU mailbox driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mailbox/arm_mhu.h b/drivers/mailbox/arm_mhu.h
new file mode 100644 (file)
index 0000000..3b53433
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * ARM Message Handling Unit (MHU) driver header
+ *
+ * Copyright (C) 2014 ARM Ltd.
+ *
+ * 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/>.
+ */
+#define CONTROLLER_NAME                "mhu_ctlr"
+
+#define CHANNEL_MAX            2
+#define CHANNEL_LOW_PRIORITY   "cpu_to_scp_low"
+#define CHANNEL_HIGH_PRIORITY  "cpu_to_scp_high"
+
+struct mhu_data_buf {
+       u32 cmd;
+       int tx_size;
+       void *tx_buf;
+       int rx_size;
+       void *rx_buf;
+       void *cl_data;
+};