diff options
Diffstat (limited to 'drivers/mailbox/arm_mhu.c')
-rw-r--r-- | drivers/mailbox/arm_mhu.c | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/drivers/mailbox/arm_mhu.c b/drivers/mailbox/arm_mhu.c new file mode 100644 index 000000000000..28fb4f01b413 --- /dev/null +++ b/drivers/mailbox/arm_mhu.c @@ -0,0 +1,320 @@ +/* + * 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" + +struct device* the_scpi_device; + +#define DRIVER_NAME "arm_mhu" + +/* + * +--------------------+-------+---------------+ + * | 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) ((idx) * 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 + (idx) * 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 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 irqreturn_t mbox_handler(int irq, void *p) +{ + struct mbox_chan *link = (struct mbox_chan *)p; + struct mhu_chan *chan = link->con_priv; + struct mhu_ctlr *ctlr = chan->ctlr; + void __iomem *mbox_base = ctlr->mbox_base; + void __iomem *payload = ctlr->payload_base; + int idx = chan->index; + u32 status = readl(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, payload + RX_PAYLOAD(idx), + data->rx_size); + chan->data = NULL; + mbox_chan_received_data(link, data); + writel(~0, mbox_base + RX_CLEAR(idx)); + } + + return IRQ_HANDLED; +} + +static int mhu_send_data(struct mbox_chan *link, void *msg) +{ + struct mhu_chan *chan = link->con_priv; + struct mhu_ctlr *ctlr = chan->ctlr; + void __iomem *mbox_base = ctlr->mbox_base; + void __iomem *payload = ctlr->payload_base; + struct mhu_data_buf *data = (struct mhu_data_buf *)msg; + int idx = chan->index; + + if (!data) + return -EINVAL; + + chan->data = data; + if (data->tx_buf) + memcpy(payload + TX_PAYLOAD(idx), data->tx_buf, data->tx_size); + writel(data->cmd, mbox_base + TX_SET(idx)); + + return 0; +} + +static int mhu_startup(struct mbox_chan *link) +{ + struct mhu_chan *chan = link->con_priv; + int err, mbox_irq = chan->rx_irq; + + err = request_threaded_irq(mbox_irq, NULL, mbox_handler, IRQF_ONESHOT, + DRIVER_NAME, link); + return err; +} + +static void mhu_shutdown(struct mbox_chan *link) +{ + struct mhu_chan *chan = link->con_priv; + + chan->data = NULL; + free_irq(chan->rx_irq, link); +} + +static bool mhu_last_tx_done(struct mbox_chan *link) +{ + struct mhu_chan *chan = link->con_priv; + struct mhu_ctlr *ctlr = chan->ctlr; + void __iomem *mbox_base = ctlr->mbox_base; + int idx = chan->index; + + return !readl(mbox_base + TX_STATUS(idx)); +} + +static struct mbox_chan_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) +{ + struct mhu_ctlr *ctlr; + struct mhu_chan *chan; + struct device *dev = &pdev->dev; + struct mbox_chan *l; + struct resource *res; + int idx; + 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_ioremap_resource(dev, res); + if (IS_ERR(ctlr->mbox_base)) { + dev_err(dev, "failed to request or ioremap mailbox control\n"); + return PTR_ERR(ctlr->mbox_base); + } + + 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_ioremap_resource(dev, res); + if (IS_ERR(ctlr->payload_base)) { + dev_err(dev, "failed to request or ioremap mailbox payload\n"); + return PTR_ERR(ctlr->payload_base); + } + + ctlr->dev = dev; + platform_set_drvdata(pdev, ctlr); + + l = devm_kzalloc(dev, sizeof(*l) * CHANNEL_MAX, GFP_KERNEL); + if (!l) { + dev_err(dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + ctlr->mbox_con.chans = l; + ctlr->mbox_con.num_chans = CHANNEL_MAX; + ctlr->mbox_con.txdone_irq = true; + ctlr->mbox_con.ops = &mhu_ops; + 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].con_priv = chan; + } + + if (mbox_controller_register(&ctlr->mbox_con)) { + dev_err(dev, "failed to register mailbox controller\n"); + return -ENOMEM; + } + + the_scpi_device = dev; + 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); + devm_kfree(dev, ctlr->mbox_con.chans); + + 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, + }, +}; + +static int __init mhu_init(void) +{ + return platform_driver_register(&mhu_driver); +} +core_initcall(mhu_init); + +static void __exit mhu_exit(void) +{ + platform_driver_unregister(&mhu_driver); +} +module_exit(mhu_exit); + +MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); +MODULE_DESCRIPTION("ARM MHU mailbox driver"); +MODULE_LICENSE("GPL"); |