Renesas R-Car MFIS offers multiple features but most importantly
mailboxes and hwspinlocks. Because they share a common register space
and a common register unprotection mechanism, a single driver was chosen
to handle all dependencies. (MFD and auxiliary bus have been tried as
well, but they failed because of circular dependencies.)
In this first step, the driver implements common register access and a
mailbox controller. hwspinlock support will be added incrementally, once
the subsystem allows out-of-directory drivers.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Acked-by: Jassi Brar <jassisinghbrar@gmail.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Tested-by: Geert Uytterhoeven <geert+renesas@glider.be>
---
Changes since v3:
* use more 'unsigned int' instead of 'int'
* re-ordered declarations to be more xmas-tree like
(don't want to go farther than this)
* added tags from Geert (Thanks!)
drivers/soc/renesas/Kconfig | 9 +
drivers/soc/renesas/Makefile | 1 +
drivers/soc/renesas/rcar-mfis.c | 344 ++++++++++++++++++++++++++++++++
3 files changed, 354 insertions(+)
create mode 100644 drivers/soc/renesas/rcar-mfis.c
diff --git a/drivers/soc/renesas/Kconfig b/drivers/soc/renesas/Kconfig
index 26bed0fdceb0..2ab150d04bb1 100644
--- a/drivers/soc/renesas/Kconfig
+++ b/drivers/soc/renesas/Kconfig
@@ -465,6 +465,15 @@ config ARCH_R9A07G043
endif # RISCV
+config RCAR_MFIS
+ tristate "Renesas R-Car MFIS driver"
+ depends on ARCH_RENESAS || COMPILE_TEST
+ depends on MAILBOX
+ help
+ Select this option to enable the Renesas R-Car MFIS core driver for
+ the MFIS device found on SoCs like R-Car. On families like Gen5, this
+ is needed to communicate with the SCP.
+
config PWC_RZV2M
bool "Renesas RZ/V2M PWC support" if COMPILE_TEST
diff --git a/drivers/soc/renesas/Makefile b/drivers/soc/renesas/Makefile
index 655dbcb08747..81bde85c2178 100644
--- a/drivers/soc/renesas/Makefile
+++ b/drivers/soc/renesas/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_SYS_R9A09G057) += r9a09g057-sys.o
# Family
obj-$(CONFIG_PWC_RZV2M) += pwc-rzv2m.o
+obj-$(CONFIG_RCAR_MFIS) += rcar-mfis.o
obj-$(CONFIG_RST_RCAR) += rcar-rst.o
obj-$(CONFIG_RZN1_IRQMUX) += rzn1_irqmux.o
obj-$(CONFIG_SYSC_RZ) += rz-sysc.o
diff --git a/drivers/soc/renesas/rcar-mfis.c b/drivers/soc/renesas/rcar-mfis.c
new file mode 100644
index 000000000000..059539eb8ab0
--- /dev/null
+++ b/drivers/soc/renesas/rcar-mfis.c
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Renesas R-Car MFIS (Multifunctional Interface) driver
+ *
+ * Copyright (C) Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ * Wolfram Sang <wsa+renesas@sang-engineering.com>
+ */
+#include <dt-bindings/soc/renesas,r8a78000-mfis.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mailbox_controller.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define MFISWPCNTR 0x0900
+#define MFISWACNTR 0x0904
+
+#define MFIS_X5H_IICR(i) ((i) * 0x1000 + 0x00)
+#define MFIS_X5H_EICR(i) ((i) * 0x1000 + 0x04)
+
+#define MFIS_UNPROTECT_KEY 0xACCE0000
+
+struct mfis_priv;
+
+struct mfis_reg {
+ void __iomem *base;
+ resource_size_t start;
+ struct mfis_priv *priv;
+};
+
+struct mfis_info {
+ u32 unprotect_mask;
+ unsigned int mb_num_channels;
+ unsigned int mb_reg_comes_from_dt:1;
+ unsigned int mb_tx_uses_eicr:1;
+ unsigned int mb_channels_are_unidir:1;
+};
+
+struct mfis_chan_priv {
+ u32 reg;
+ int irq;
+};
+
+struct mfis_priv {
+ spinlock_t unprotect_lock; /* guards access to the unprotection reg */
+ struct device *dev;
+ struct mfis_reg common_reg;
+ struct mfis_reg mbox_reg;
+ const struct mfis_info *info;
+
+ /* mailbox private data */
+ struct mbox_controller mbox;
+ struct mfis_chan_priv *chan_privs;
+};
+
+static u32 mfis_read(struct mfis_reg *mreg, unsigned int reg)
+{
+ return ioread32(mreg->base + reg);
+}
+
+static void mfis_write(struct mfis_reg *mreg, u32 reg, u32 val)
+{
+ struct mfis_priv *priv = mreg->priv;
+ u32 unprotect_mask = priv->info->unprotect_mask;
+ u32 unprotect_code;
+ unsigned long flags;
+
+ /*
+ * [Gen4] key: 0xACCE0000, mask: 0x0000FFFF
+ * [Gen5] key: 0xACC00000, mask: 0x000FFFFF
+ */
+ unprotect_code = (MFIS_UNPROTECT_KEY & ~unprotect_mask) |
+ ((mreg->start + reg) & unprotect_mask);
+
+ spin_lock_irqsave(&priv->unprotect_lock, flags);
+ iowrite32(unprotect_code, priv->common_reg.base + MFISWACNTR);
+ iowrite32(val, mreg->base + reg);
+ spin_unlock_irqrestore(&priv->unprotect_lock, flags);
+}
+
+/********************************************************
+ * Mailbox *
+ ********************************************************/
+
+#define mfis_mb_mbox_to_priv(_m) container_of((_m), struct mfis_priv, mbox)
+
+static irqreturn_t mfis_mb_iicr_interrupt(int irq, void *data)
+{
+ struct mbox_chan *chan = data;
+ struct mfis_priv *priv = mfis_mb_mbox_to_priv(chan->mbox);
+ struct mfis_chan_priv *chan_priv = chan->con_priv;
+
+ mbox_chan_received_data(chan, NULL);
+ /* Stop remote(!) doorbell */
+ mfis_write(&priv->mbox_reg, chan_priv->reg, 0);
+
+ return IRQ_HANDLED;
+}
+
+static int mfis_mb_startup(struct mbox_chan *chan)
+{
+ struct mfis_chan_priv *chan_priv = chan->con_priv;
+
+ if (!chan_priv->irq)
+ return 0;
+
+ return request_irq(chan_priv->irq, mfis_mb_iicr_interrupt, 0,
+ dev_name(chan->mbox->dev), chan);
+}
+
+static void mfis_mb_shutdown(struct mbox_chan *chan)
+{
+ struct mfis_chan_priv *chan_priv = chan->con_priv;
+
+ if (chan_priv->irq)
+ free_irq(chan_priv->irq, chan);
+}
+
+static int mfis_mb_iicr_send_data(struct mbox_chan *chan, void *data)
+{
+ struct mfis_priv *priv = mfis_mb_mbox_to_priv(chan->mbox);
+ struct mfis_chan_priv *chan_priv = chan->con_priv;
+
+ /* Our doorbell still active? */
+ if (mfis_read(&priv->mbox_reg, chan_priv->reg) & BIT(0))
+ return -EBUSY;
+
+ /* Start our doorbell */
+ mfis_write(&priv->mbox_reg, chan_priv->reg, BIT(0));
+
+ return 0;
+}
+
+static bool mfis_mb_iicr_last_tx_done(struct mbox_chan *chan)
+{
+ struct mfis_priv *priv = mfis_mb_mbox_to_priv(chan->mbox);
+ struct mfis_chan_priv *chan_priv = chan->con_priv;
+
+ /* Our doorbell still active? */
+ return !(mfis_read(&priv->mbox_reg, chan_priv->reg) & BIT(0));
+}
+
+/* For MFIS variants using the IICR/EICR register pair */
+static const struct mbox_chan_ops mfis_iicr_ops = {
+ .startup = mfis_mb_startup,
+ .shutdown = mfis_mb_shutdown,
+ .send_data = mfis_mb_iicr_send_data,
+ .last_tx_done = mfis_mb_iicr_last_tx_done,
+};
+
+static struct mbox_chan *mfis_mb_of_xlate(struct mbox_controller *mbox,
+ const struct of_phandle_args *sp)
+{
+ struct mfis_priv *priv = mfis_mb_mbox_to_priv(mbox);
+ struct mfis_chan_priv *chan_priv;
+ struct mbox_chan *chan;
+ u32 chan_num, chan_flags;
+ bool tx_uses_eicr, is_only_rx;
+
+ if (sp->args_count != 2)
+ return ERR_PTR(-EINVAL);
+
+ chan_num = sp->args[0];
+ chan_flags = sp->args[1];
+
+ if (chan_num >= priv->info->mb_num_channels)
+ return ERR_PTR(-EINVAL);
+
+ /* Channel layout is described in mfis_mb_probe() */
+ if (priv->info->mb_channels_are_unidir) {
+ is_only_rx = chan_flags & MFIS_CHANNEL_RX;
+ chan = mbox->chans + 2 * chan_num + is_only_rx;
+ } else {
+ is_only_rx = false;
+ chan = mbox->chans + chan_num;
+ }
+
+ if (priv->info->mb_reg_comes_from_dt) {
+ tx_uses_eicr = chan_flags & MFIS_CHANNEL_EICR;
+ if (tx_uses_eicr)
+ chan += mbox->num_chans / 2;
+ } else {
+ tx_uses_eicr = priv->info->mb_tx_uses_eicr;
+ }
+
+ chan_priv = chan->con_priv;
+ chan_priv->reg = (tx_uses_eicr ^ is_only_rx) ? MFIS_X5H_EICR(chan_num) :
+ MFIS_X5H_IICR(chan_num);
+
+ if (!priv->info->mb_channels_are_unidir || is_only_rx) {
+ char irqname[8];
+ char suffix = tx_uses_eicr ? 'i' : 'e';
+
+ /* "ch0i" or "ch0e" */
+ scnprintf(irqname, sizeof(irqname), "ch%u%c", chan_num, suffix);
+
+ chan_priv->irq = of_irq_get_byname(mbox->dev->of_node, irqname);
+ if (chan_priv->irq < 0)
+ return ERR_PTR(chan_priv->irq);
+ if (chan_priv->irq == 0)
+ return ERR_PTR(-ENOENT);
+ }
+
+ return chan;
+}
+
+static int mfis_mb_probe(struct mfis_priv *priv)
+{
+ struct device *dev = priv->dev;
+ struct mbox_controller *mbox;
+ struct mbox_chan *chan;
+ unsigned int num_chan = priv->info->mb_num_channels;
+
+ if (priv->info->mb_channels_are_unidir) {
+ /* Channel layout: Ch0-TX, Ch0-RX, Ch1-TX... */
+ num_chan *= 2;
+ }
+
+ if (priv->info->mb_reg_comes_from_dt) {
+ /* Channel layout: <n> IICR channels, <n> EICR channels */
+ num_chan *= 2;
+ }
+
+ chan = devm_kcalloc(dev, num_chan, sizeof(*chan), GFP_KERNEL);
+ if (!chan)
+ return -ENOMEM;
+
+ priv->chan_privs = devm_kcalloc(dev, num_chan, sizeof(*priv->chan_privs),
+ GFP_KERNEL);
+ if (!priv->chan_privs)
+ return -ENOMEM;
+
+ mbox = &priv->mbox;
+
+ for (unsigned int i = 0; i < num_chan; i++)
+ chan[i].con_priv = &priv->chan_privs[i];
+
+ mbox->chans = chan;
+ mbox->num_chans = num_chan;
+ mbox->txdone_poll = true;
+ mbox->ops = &mfis_iicr_ops;
+ mbox->dev = dev;
+ mbox->of_xlate = mfis_mb_of_xlate;
+
+ return devm_mbox_controller_register(dev, mbox);
+}
+
+/********************************************************
+ * Common *
+ ********************************************************/
+static int mfis_reg_probe(struct platform_device *pdev, struct mfis_priv *priv,
+ struct mfis_reg *mreg, const char *name, bool required)
+{
+ struct resource *res;
+ void __iomem *base;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
+
+ /* If there is no mailbox resource, registers are in the common space */
+ if (!res && !required) {
+ *mreg = priv->common_reg;
+ } else {
+ base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ mreg->base = base;
+ mreg->start = res->start;
+ mreg->priv = priv;
+ }
+
+ return 0;
+}
+
+static int mfis_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mfis_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->info = of_device_get_match_data(dev);
+ if (!priv->info)
+ return -ENOENT;
+
+ spin_lock_init(&priv->unprotect_lock);
+
+ ret = mfis_reg_probe(pdev, priv, &priv->common_reg, "common", true);
+ if (ret)
+ return ret;
+
+ ret = mfis_reg_probe(pdev, priv, &priv->mbox_reg, "mboxes", false);
+ if (ret)
+ return ret;
+
+ return mfis_mb_probe(priv);
+}
+
+static const struct mfis_info mfis_info_r8a78000 = {
+ .unprotect_mask = 0x000fffff,
+ .mb_num_channels = 64,
+ .mb_reg_comes_from_dt = true,
+ .mb_channels_are_unidir = true,
+};
+
+static const struct mfis_info mfis_info_r8a78000_scp = {
+ .unprotect_mask = 0x000fffff,
+ .mb_num_channels = 32,
+ .mb_tx_uses_eicr = true,
+ .mb_channels_are_unidir = true,
+};
+
+static const struct of_device_id mfis_mfd_of_match[] = {
+ { .compatible = "renesas,r8a78000-mfis", .data = &mfis_info_r8a78000, },
+ { .compatible = "renesas,r8a78000-mfis-scp", .data = &mfis_info_r8a78000_scp, },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mfis_mfd_of_match);
+
+static struct platform_driver mfis_driver = {
+ .driver = {
+ .name = "rcar-mfis",
+ .of_match_table = mfis_mfd_of_match,
+ .suppress_bind_attrs = true,
+ },
+ .probe = mfis_probe,
+};
+module_platform_driver(mfis_driver);
+
+MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
+MODULE_AUTHOR("Wolfram Sang <wsa+renesas@sang-engineering.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Renesas R-Car MFIS driver");
--
2.51.0
Answering Sashiko:
> > +static irqreturn_t mfis_mb_iicr_interrupt(int irq, void *data)
> > +{
> > + struct mbox_chan *chan = data;
> > + struct mfis_priv *priv = mfis_mb_mbox_to_priv(chan->mbox);
> > + struct mfis_chan_priv *chan_priv = chan->con_priv;
> > +
> > + mbox_chan_received_data(chan, NULL);
> > + /* Stop remote(!) doorbell */
> > + mfis_write(&priv->mbox_reg, chan_priv->reg, 0);
> Does this clear the correct register for bidirectional channels?
> In mfis_mb_of_xlate(), for bidirectional channels (!mb_channels_are_unidir),
> a single register is stored in chan_priv->reg.
> When the receive interrupt triggers, this writes 0 to chan_priv->reg. If
> this points to the transmit register, it looks like it would fail to clear
> the receive interrupt and inadvertently cancel pending outgoing
> transmissions.
> Could this lead to an interrupt storm?
While technically correct, this does not matter in practice. The HW with
bidirectional channels looks quite different and will need a different
interrupt handler. Sashiko cannot detect this because the part for
bidirectional channels will be added incrementally once HW is available.
> > +static int mfis_mb_startup(struct mbox_chan *chan)
> > +{
> > + struct mfis_chan_priv *chan_priv = chan->con_priv;
> > +
> > + if (!chan_priv->irq)
> > + return 0;
> > +
> > + return request_irq(chan_priv->irq, mfis_mb_iicr_interrupt, 0,
> > + dev_name(chan->mbox->dev), chan);
> > +}
> > +
> > +static void mfis_mb_shutdown(struct mbox_chan *chan)
> > +{
> > + struct mfis_chan_priv *chan_priv = chan->con_priv;
> > +
> > + if (chan_priv->irq)
> > + free_irq(chan_priv->irq, chan);
> > +}
> What happens if request_irq() fails in mfis_mb_startup()?
> If request_irq() returns an error, the mailbox framework handles the startup
> failure by calling the shutdown() callback.
> Since chan_priv->irq is populated earlier during of_xlate(), it will be
> non-zero here. Will this cause free_irq() to be called on an IRQ that was
> never successfully allocated, triggering a warning?
Uuuhh, yes! But this is not a problem of this driver but more of the
subsystem. It is definitely not intuitive that shutdown() is called when
startup() failed. There are more mailbox drivers which fell into this
trap, mostly by freeing an irq they never got. I will have a look at
this, but as said, I think it should be solved on subsystem level.
While this adds another item to my todo-list, it also means that none of
the above issues are related to the driver itself \o/ Looks like it is
good to go upstream, finally!
© 2016 - 2026 Red Hat, Inc.