mailbox: sprd: Add Spreadtrum mailbox driver
authorBaolin Wang <baolin.wang@unisoc.com>
Fri, 22 May 2020 13:31:09 +0000 (21:31 +0800)
committerJassi Brar <jaswinder.singh@linaro.org>
Sat, 30 May 2020 23:05:33 +0000 (18:05 -0500)
The Spreadtrum mailbox controller supports 8 channels to communicate
with MCUs, and it contains 2 different parts: inbox and outbox, which
are used to send and receive messages by IRQ mode.

Signed-off-by: Baolin Wang <baolin.wang@unisoc.com>
Signed-off-by: Jassi Brar <jaswinder.singh@linaro.org>
drivers/mailbox/Kconfig
drivers/mailbox/Makefile
drivers/mailbox/sprd-mailbox.c [new file with mode: 0644]

index 5a577a6..e03f3fb 100644 (file)
@@ -236,4 +236,12 @@ config SUN6I_MSGBOX
          various Allwinner SoCs. This mailbox is used for communication
          between the application CPUs and the power management coprocessor.
 
+config SPRD_MBOX
+       tristate "Spreadtrum Mailbox"
+       depends on ARCH_SPRD || COMPILE_TEST
+       help
+         Mailbox driver implementation for the Spreadtrum platform. It is used
+         to send message between application processors and MCU. Say Y here if
+         you want to build the Spreatrum mailbox controller driver.
+
 endif
index 2e4364e..9caf4ed 100644 (file)
@@ -50,3 +50,5 @@ obj-$(CONFIG_MTK_CMDQ_MBOX)   += mtk-cmdq-mailbox.o
 obj-$(CONFIG_ZYNQMP_IPI_MBOX)  += zynqmp-ipi-mailbox.o
 
 obj-$(CONFIG_SUN6I_MSGBOX)     += sun6i-msgbox.o
+
+obj-$(CONFIG_SPRD_MBOX)                += sprd-mailbox.o
diff --git a/drivers/mailbox/sprd-mailbox.c b/drivers/mailbox/sprd-mailbox.c
new file mode 100644 (file)
index 0000000..f6fab24
--- /dev/null
@@ -0,0 +1,361 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Spreadtrum mailbox driver
+ *
+ * Copyright (c) 2020 Spreadtrum Communications Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mailbox_controller.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+
+#define SPRD_MBOX_ID           0x0
+#define SPRD_MBOX_MSG_LOW      0x4
+#define SPRD_MBOX_MSG_HIGH     0x8
+#define SPRD_MBOX_TRIGGER      0xc
+#define SPRD_MBOX_FIFO_RST     0x10
+#define SPRD_MBOX_FIFO_STS     0x14
+#define SPRD_MBOX_IRQ_STS      0x18
+#define SPRD_MBOX_IRQ_MSK      0x1c
+#define SPRD_MBOX_LOCK         0x20
+#define SPRD_MBOX_FIFO_DEPTH   0x24
+
+/* Bit and mask definiation for inbox's SPRD_MBOX_FIFO_STS register */
+#define SPRD_INBOX_FIFO_DELIVER_MASK           GENMASK(23, 16)
+#define SPRD_INBOX_FIFO_OVERLOW_MASK           GENMASK(15, 8)
+#define SPRD_INBOX_FIFO_DELIVER_SHIFT          16
+#define SPRD_INBOX_FIFO_BUSY_MASK              GENMASK(7, 0)
+
+/* Bit and mask definiation for SPRD_MBOX_IRQ_STS register */
+#define SPRD_MBOX_IRQ_CLR                      BIT(0)
+
+/* Bit and mask definiation for outbox's SPRD_MBOX_FIFO_STS register */
+#define SPRD_OUTBOX_FIFO_FULL                  BIT(0)
+#define SPRD_OUTBOX_FIFO_WR_SHIFT              16
+#define SPRD_OUTBOX_FIFO_RD_SHIFT              24
+#define SPRD_OUTBOX_FIFO_POS_MASK              GENMASK(7, 0)
+
+/* Bit and mask definiation for inbox's SPRD_MBOX_IRQ_MSK register */
+#define SPRD_INBOX_FIFO_BLOCK_IRQ              BIT(0)
+#define SPRD_INBOX_FIFO_OVERFLOW_IRQ           BIT(1)
+#define SPRD_INBOX_FIFO_DELIVER_IRQ            BIT(2)
+#define SPRD_INBOX_FIFO_IRQ_MASK               GENMASK(2, 0)
+
+/* Bit and mask definiation for outbox's SPRD_MBOX_IRQ_MSK register */
+#define SPRD_OUTBOX_FIFO_NOT_EMPTY_IRQ         BIT(0)
+#define SPRD_OUTBOX_FIFO_IRQ_MASK              GENMASK(4, 0)
+
+#define SPRD_MBOX_CHAN_MAX                     8
+
+struct sprd_mbox_priv {
+       struct mbox_controller  mbox;
+       struct device           *dev;
+       void __iomem            *inbox_base;
+       void __iomem            *outbox_base;
+       struct clk              *clk;
+       u32                     outbox_fifo_depth;
+
+       struct mbox_chan        chan[SPRD_MBOX_CHAN_MAX];
+};
+
+static struct sprd_mbox_priv *to_sprd_mbox_priv(struct mbox_controller *mbox)
+{
+       return container_of(mbox, struct sprd_mbox_priv, mbox);
+}
+
+static u32 sprd_mbox_get_fifo_len(struct sprd_mbox_priv *priv, u32 fifo_sts)
+{
+       u32 wr_pos = (fifo_sts >> SPRD_OUTBOX_FIFO_WR_SHIFT) &
+               SPRD_OUTBOX_FIFO_POS_MASK;
+       u32 rd_pos = (fifo_sts >> SPRD_OUTBOX_FIFO_RD_SHIFT) &
+               SPRD_OUTBOX_FIFO_POS_MASK;
+       u32 fifo_len;
+
+       /*
+        * If the read pointer is equal with write pointer, which means the fifo
+        * is full or empty.
+        */
+       if (wr_pos == rd_pos) {
+               if (fifo_sts & SPRD_OUTBOX_FIFO_FULL)
+                       fifo_len = priv->outbox_fifo_depth;
+               else
+                       fifo_len = 0;
+       } else if (wr_pos > rd_pos) {
+               fifo_len = wr_pos - rd_pos;
+       } else {
+               fifo_len = priv->outbox_fifo_depth - rd_pos + wr_pos;
+       }
+
+       return fifo_len;
+}
+
+static irqreturn_t sprd_mbox_outbox_isr(int irq, void *data)
+{
+       struct sprd_mbox_priv *priv = data;
+       struct mbox_chan *chan;
+       u32 fifo_sts, fifo_len, msg[2];
+       int i, id;
+
+       fifo_sts = readl(priv->outbox_base + SPRD_MBOX_FIFO_STS);
+
+       fifo_len = sprd_mbox_get_fifo_len(priv, fifo_sts);
+       if (!fifo_len) {
+               dev_warn_ratelimited(priv->dev, "spurious outbox interrupt\n");
+               return IRQ_NONE;
+       }
+
+       for (i = 0; i < fifo_len; i++) {
+               msg[0] = readl(priv->outbox_base + SPRD_MBOX_MSG_LOW);
+               msg[1] = readl(priv->outbox_base + SPRD_MBOX_MSG_HIGH);
+               id = readl(priv->outbox_base + SPRD_MBOX_ID);
+
+               chan = &priv->chan[id];
+               mbox_chan_received_data(chan, (void *)msg);
+
+               /* Trigger to update outbox FIFO pointer */
+               writel(0x1, priv->outbox_base + SPRD_MBOX_TRIGGER);
+       }
+
+       /* Clear irq status after reading all message. */
+       writel(SPRD_MBOX_IRQ_CLR, priv->outbox_base + SPRD_MBOX_IRQ_STS);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t sprd_mbox_inbox_isr(int irq, void *data)
+{
+       struct sprd_mbox_priv *priv = data;
+       struct mbox_chan *chan;
+       u32 fifo_sts, send_sts, busy, id;
+
+       fifo_sts = readl(priv->inbox_base + SPRD_MBOX_FIFO_STS);
+
+       /* Get the inbox data delivery status */
+       send_sts = (fifo_sts & SPRD_INBOX_FIFO_DELIVER_MASK) >>
+               SPRD_INBOX_FIFO_DELIVER_SHIFT;
+       if (!send_sts) {
+               dev_warn_ratelimited(priv->dev, "spurious inbox interrupt\n");
+               return IRQ_NONE;
+       }
+
+       while (send_sts) {
+               id = __ffs(send_sts);
+               send_sts &= (send_sts - 1);
+
+               chan = &priv->chan[id];
+
+               /*
+                * Check if the message was fetched by remote traget, if yes,
+                * that means the transmission has been completed.
+                */
+               busy = fifo_sts & SPRD_INBOX_FIFO_BUSY_MASK;
+               if (!(busy & BIT(id)))
+                       mbox_chan_txdone(chan, 0);
+       }
+
+       /* Clear FIFO delivery and overflow status */
+       writel(fifo_sts &
+              (SPRD_INBOX_FIFO_DELIVER_MASK | SPRD_INBOX_FIFO_OVERLOW_MASK),
+              priv->inbox_base + SPRD_MBOX_FIFO_RST);
+
+       /* Clear irq status */
+       writel(SPRD_MBOX_IRQ_CLR, priv->inbox_base + SPRD_MBOX_IRQ_STS);
+
+       return IRQ_HANDLED;
+}
+
+static int sprd_mbox_send_data(struct mbox_chan *chan, void *msg)
+{
+       struct sprd_mbox_priv *priv = to_sprd_mbox_priv(chan->mbox);
+       unsigned long id = (unsigned long)chan->con_priv;
+       u32 *data = msg;
+
+       /* Write data into inbox FIFO, and only support 8 bytes every time */
+       writel(data[0], priv->inbox_base + SPRD_MBOX_MSG_LOW);
+       writel(data[1], priv->inbox_base + SPRD_MBOX_MSG_HIGH);
+
+       /* Set target core id */
+       writel(id, priv->inbox_base + SPRD_MBOX_ID);
+
+       /* Trigger remote request */
+       writel(0x1, priv->inbox_base + SPRD_MBOX_TRIGGER);
+
+       return 0;
+}
+
+static int sprd_mbox_flush(struct mbox_chan *chan, unsigned long timeout)
+{
+       struct sprd_mbox_priv *priv = to_sprd_mbox_priv(chan->mbox);
+       unsigned long id = (unsigned long)chan->con_priv;
+       u32 busy;
+
+       timeout = jiffies + msecs_to_jiffies(timeout);
+
+       while (time_before(jiffies, timeout)) {
+               busy = readl(priv->inbox_base + SPRD_MBOX_FIFO_STS) &
+                       SPRD_INBOX_FIFO_BUSY_MASK;
+               if (!(busy & BIT(id))) {
+                       mbox_chan_txdone(chan, 0);
+                       return 0;
+               }
+
+               udelay(1);
+       }
+
+       return -ETIME;
+}
+
+static int sprd_mbox_startup(struct mbox_chan *chan)
+{
+       struct sprd_mbox_priv *priv = to_sprd_mbox_priv(chan->mbox);
+       u32 val;
+
+       /* Select outbox FIFO mode and reset the outbox FIFO status */
+       writel(0x0, priv->outbox_base + SPRD_MBOX_FIFO_RST);
+
+       /* Enable inbox FIFO overflow and delivery interrupt */
+       val = readl(priv->inbox_base + SPRD_MBOX_IRQ_MSK);
+       val &= ~(SPRD_INBOX_FIFO_OVERFLOW_IRQ | SPRD_INBOX_FIFO_DELIVER_IRQ);
+       writel(val, priv->inbox_base + SPRD_MBOX_IRQ_MSK);
+
+       /* Enable outbox FIFO not empty interrupt */
+       val = readl(priv->outbox_base + SPRD_MBOX_IRQ_MSK);
+       val &= ~SPRD_OUTBOX_FIFO_NOT_EMPTY_IRQ;
+       writel(val, priv->outbox_base + SPRD_MBOX_IRQ_MSK);
+
+       return 0;
+}
+
+static void sprd_mbox_shutdown(struct mbox_chan *chan)
+{
+       struct sprd_mbox_priv *priv = to_sprd_mbox_priv(chan->mbox);
+
+       /* Disable inbox & outbox interrupt */
+       writel(SPRD_INBOX_FIFO_IRQ_MASK, priv->inbox_base + SPRD_MBOX_IRQ_MSK);
+       writel(SPRD_OUTBOX_FIFO_IRQ_MASK, priv->outbox_base + SPRD_MBOX_IRQ_MSK);
+}
+
+static const struct mbox_chan_ops sprd_mbox_ops = {
+       .send_data      = sprd_mbox_send_data,
+       .flush          = sprd_mbox_flush,
+       .startup        = sprd_mbox_startup,
+       .shutdown       = sprd_mbox_shutdown,
+};
+
+static void sprd_mbox_disable(void *data)
+{
+       struct sprd_mbox_priv *priv = data;
+
+       clk_disable_unprepare(priv->clk);
+}
+
+static int sprd_mbox_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct sprd_mbox_priv *priv;
+       int ret, inbox_irq, outbox_irq;
+       unsigned long id;
+
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       priv->dev = dev;
+
+       /*
+        * The Spreadtrum mailbox uses an inbox to send messages to the target
+        * core, and uses an outbox to receive messages from other cores.
+        *
+        * Thus the mailbox controller supplies 2 different register addresses
+        * and IRQ numbers for inbox and outbox.
+        */
+       priv->inbox_base = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(priv->inbox_base))
+               return PTR_ERR(priv->inbox_base);
+
+       priv->outbox_base = devm_platform_ioremap_resource(pdev, 1);
+       if (IS_ERR(priv->outbox_base))
+               return PTR_ERR(priv->outbox_base);
+
+       priv->clk = devm_clk_get(dev, "enable");
+       if (IS_ERR(priv->clk)) {
+               dev_err(dev, "failed to get mailbox clock\n");
+               return PTR_ERR(priv->clk);
+       }
+
+       ret = clk_prepare_enable(priv->clk);
+       if (ret)
+               return ret;
+
+       ret = devm_add_action_or_reset(dev, sprd_mbox_disable, priv);
+       if (ret) {
+               dev_err(dev, "failed to add mailbox disable action\n");
+               return ret;
+       }
+
+       inbox_irq = platform_get_irq(pdev, 0);
+       if (inbox_irq < 0)
+               return inbox_irq;
+
+       ret = devm_request_irq(dev, inbox_irq, sprd_mbox_inbox_isr,
+                              IRQF_NO_SUSPEND, dev_name(dev), priv);
+       if (ret) {
+               dev_err(dev, "failed to request inbox IRQ: %d\n", ret);
+               return ret;
+       }
+
+       outbox_irq = platform_get_irq(pdev, 1);
+       if (outbox_irq < 0)
+               return outbox_irq;
+
+       ret = devm_request_irq(dev, outbox_irq, sprd_mbox_outbox_isr,
+                              IRQF_NO_SUSPEND, dev_name(dev), priv);
+       if (ret) {
+               dev_err(dev, "failed to request outbox IRQ: %d\n", ret);
+               return ret;
+       }
+
+       /* Get the default outbox FIFO depth */
+       priv->outbox_fifo_depth =
+               readl(priv->outbox_base + SPRD_MBOX_FIFO_DEPTH) + 1;
+       priv->mbox.dev = dev;
+       priv->mbox.chans = &priv->chan[0];
+       priv->mbox.num_chans = SPRD_MBOX_CHAN_MAX;
+       priv->mbox.ops = &sprd_mbox_ops;
+       priv->mbox.txdone_irq = true;
+
+       for (id = 0; id < SPRD_MBOX_CHAN_MAX; id++)
+               priv->chan[id].con_priv = (void *)id;
+
+       ret = devm_mbox_controller_register(dev, &priv->mbox);
+       if (ret) {
+               dev_err(dev, "failed to register mailbox: %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static const struct of_device_id sprd_mbox_of_match[] = {
+       { .compatible = "sprd,sc9860-mailbox", },
+       { },
+};
+MODULE_DEVICE_TABLE(of, sprd_mbox_of_match);
+
+static struct platform_driver sprd_mbox_driver = {
+       .driver = {
+               .name = "sprd-mailbox",
+               .of_match_table = sprd_mbox_of_match,
+       },
+       .probe  = sprd_mbox_probe,
+};
+module_platform_driver(sprd_mbox_driver);
+
+MODULE_AUTHOR("Baolin Wang <baolin.wang@unisoc.com>");
+MODULE_DESCRIPTION("Spreadtrum mailbox driver");
+MODULE_LICENSE("GPL v2");