From: Ben Levinsky <ben.levinsky@amd.com>
To: <jassisinghbrar@gmail.com>, <andersson@kernel.org>,
<mathieu.poirier@linaro.org>, <robh@kernel.org>,
<krzk+dt@kernel.org>, <conor+dt@kernel.org>
Cc: <michal.simek@amd.com>, <linux-remoteproc@vger.kernel.org>,
<devicetree@vger.kernel.org>,
<linux-arm-kernel@lists.infradead.org>,
<linux-kernel@vger.kernel.org>, <ben.levinsky@amd.com>,
<tanmay.shah@amd.com>
Subject: [PATCH 4/6] mailbox: Add AMD PL Mailbox soft IP controller
Date: Wed, 14 Oct 2026 14:34:24 -0700 [thread overview]
Message-ID: <20261014213426.2957480-5-ben.levinsky@amd.com> (raw)
In-Reply-To: <20261014213426.2957480-1-ben.levinsky@amd.com>
Add a mailbox-framework controller for the AMD programmable-logic
Mailbox soft IP. Expose separate transmit and receive FIFO channels.
Enable the interface clock only while a channel is started, before any
register access, and disable it last when the channel shuts down. Keep
provider probe free of register accesses and leave the IRQ disabled until
a client starts the receive channel. Initialize each FIFO when its channel
starts, use receive-threshold interrupts for incoming words, and poll the
send-threshold status for transmit completion. Stop that polling from
accessing registers once the transmit channel begins to shut down.
Carry mailbox payloads in struct zynqmp_ipi_message, limited to the FIFO
depth given by the device tree. The FIFO transfers 32-bit words and the
hardware conveys neither a byte count nor the FIFO occupancy, so a
transmit length that is not a multiple of four is zero-padded to a whole
word.
On a receive-threshold interrupt, read up to one FIFO depth of words and
deliver them as one message, then acknowledge the interrupt. The event
latches only when the FIFO goes from empty to nonempty, so a word that
arrives between the drain and the acknowledge, or words left by the
bounded drain, raise no new event. If the FIFO is nonempty after the
acknowledge, toggle the threshold to latch a new event for them.
Assisted-by: LLM sparse
Signed-off-by: Ben Levinsky <ben.levinsky@amd.com>
---
MAINTAINERS | 7 +
drivers/mailbox/Kconfig | 10 +
drivers/mailbox/Makefile | 1 +
drivers/mailbox/amd-pl-mailbox.c | 369 +++++++++++++++++++++++++++++++
4 files changed, 387 insertions(+)
create mode 100644 drivers/mailbox/amd-pl-mailbox.c
diff --git a/MAINTAINERS b/MAINTAINERS
index f53047aa8d3c..4e28e0c311c0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1287,6 +1287,13 @@ S: Supported
T: git https://gitlab.freedesktop.org/agd5f/linux.git
F: drivers/gpu/drm/amd/pm/
+AMD PROGRAMMABLE LOGIC MAILBOX DRIVER
+M: Ben Levinsky <ben.levinsky@amd.com>
+L: linux-kernel@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/mailbox/amd,zynqmp-pl-mbox-2.1.yaml
+F: drivers/mailbox/amd-pl-mailbox.c
+
AMD PSTATE DRIVER
M: Huang Rui <ray.huang@amd.com>
M: Mario Limonciello <mario.limonciello@amd.com>
diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index ab869769e278..3c3f262c903c 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -8,6 +8,16 @@ menuconfig MAILBOX
if MAILBOX
+config AMD_PL_MAILBOX
+ tristate "AMD PL Mailbox soft IP controller"
+ depends on HAS_IOMEM
+ depends on OF
+ help
+ Mailbox controller for the AMD Programmable Logic Mailbox soft IP.
+ The controller exposes transmit and receive FIFO channels to mailbox
+ clients. It supports interrupt-driven receive notification and
+ polling-based transmit completion through the common mailbox framework.
+
config ARM_MHU
tristate "ARM MHU Mailbox"
depends on ARM_AMBA
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index 45dc59bb3e7f..5614d7e1e2f8 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -4,6 +4,7 @@
obj-$(CONFIG_MAILBOX) += mailbox.o
obj-$(CONFIG_MAILBOX_TEST) += mailbox-test.o
+obj-$(CONFIG_AMD_PL_MAILBOX) += amd-pl-mailbox.o
obj-$(CONFIG_ARM_MHU) += arm_mhu.o arm_mhu_db.o
diff --git a/drivers/mailbox/amd-pl-mailbox.c b/drivers/mailbox/amd-pl-mailbox.c
new file mode 100644
index 000000000000..bd6595332f53
--- /dev/null
+++ b/drivers/mailbox/amd-pl-mailbox.c
@@ -0,0 +1,369 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mailbox/zynqmp-ipi-message.h>
+#include <linux/mailbox_controller.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define AMD_PL_MBOX_WRDATA 0x00
+#define AMD_PL_MBOX_RDDATA 0x08
+#define AMD_PL_MBOX_STATUS 0x10
+#define AMD_PL_MBOX_ERROR 0x14
+#define AMD_PL_MBOX_SEND_THRESHOLD 0x18
+#define AMD_PL_MBOX_RECV_THRESHOLD 0x1c
+#define AMD_PL_MBOX_IRQ_STATUS 0x20
+#define AMD_PL_MBOX_IRQ_ENABLE 0x24
+#define AMD_PL_MBOX_CTRL 0x2c
+
+#define AMD_PL_MBOX_STS_RX_EMPTY BIT(0)
+#define AMD_PL_MBOX_STS_TX_FULL BIT(1)
+#define AMD_PL_MBOX_STS_TX_THRESHOLD BIT(2)
+#define AMD_PL_MBOX_IRQ_RX_THRESHOLD BIT(1)
+#define AMD_PL_MBOX_IRQ_ERROR BIT(2)
+#define AMD_PL_MBOX_CTRL_TX_RESET BIT(0)
+#define AMD_PL_MBOX_CTRL_RX_RESET BIT(1)
+
+/* FIFO depth range of the IP, in 32-bit words. */
+#define AMD_PL_MBOX_FIFO_LEN_MIN 16
+#define AMD_PL_MBOX_FIFO_LEN_MAX 8192
+
+/*
+ * The send threshold is active while send FIFO occupancy is less than or
+ * equal to the programmed value. The receive threshold is active while
+ * receive FIFO occupancy is greater than the programmed value. The related
+ * interrupt status bits latch when a threshold condition becomes active,
+ * and a new event takes priority over a simultaneous status clear.
+ *
+ * A send threshold of zero makes the status bit indicate an empty TX FIFO.
+ * A receive threshold of zero raises an interrupt for the first received
+ * word after the RX FIFO becomes empty.
+ */
+
+enum {
+ AMD_PL_MBOX_CHAN_TX = 0,
+ AMD_PL_MBOX_CHAN_RX = 1,
+ AMD_PL_MBOX_NUM_CHANS = 2,
+};
+
+/**
+ * struct amd_pl_mbox - AMD PL Mailbox controller state
+ * @regs: mapped register aperture
+ * @clk: interface clock, enabled while a channel is started
+ * @irq: receive interrupt
+ * @fifo_len: FIFO depth in 32-bit words
+ * @tx_lock: serializes TX completion polling against TX shutdown
+ * @tx_active: TX channel is started and its registers may be accessed
+ * @controller: common mailbox controller
+ * @chans: transmit and receive mailbox channels
+ * @rx_msg: receive buffer passed to mailbox clients
+ */
+struct amd_pl_mbox {
+ void __iomem *regs;
+ struct clk *clk;
+ int irq;
+ u32 fifo_len;
+ spinlock_t tx_lock; /* protects tx_active */
+ bool tx_active;
+ struct mbox_controller controller;
+ struct mbox_chan chans[AMD_PL_MBOX_NUM_CHANS];
+ struct zynqmp_ipi_message *rx_msg;
+};
+
+static int amd_pl_mbox_send_data(struct mbox_chan *chan, void *data)
+{
+ struct amd_pl_mbox *mb = chan->con_priv;
+ struct zynqmp_ipi_message *msg = data;
+ size_t offset;
+
+ if (chan != &mb->chans[AMD_PL_MBOX_CHAN_TX])
+ return -EINVAL;
+
+ /* The TX FIFO is empty here, since TX completion waits for it. */
+ if (!msg || !msg->len || msg->len > mb->fifo_len * sizeof(u32))
+ return -EINVAL;
+
+ for (offset = 0; offset < msg->len; offset += sizeof(u32)) {
+ size_t count = min(sizeof(u32), msg->len - offset);
+ u32 word = 0;
+
+ if (readl(mb->regs + AMD_PL_MBOX_STATUS) &
+ AMD_PL_MBOX_STS_TX_FULL)
+ return -EBUSY;
+
+ memcpy(&word, &msg->data[offset], count);
+ writel(word, mb->regs + AMD_PL_MBOX_WRDATA);
+ }
+
+ return 0;
+}
+
+static bool amd_pl_mbox_last_tx_done(struct mbox_chan *chan)
+{
+ struct amd_pl_mbox *mb = chan->con_priv;
+ unsigned long flags;
+ bool done = true;
+
+ /*
+ * The mailbox core can poll after shutdown() has started, while the
+ * interface clock is being disabled. Do not access registers then.
+ */
+ spin_lock_irqsave(&mb->tx_lock, flags);
+ if (mb->tx_active)
+ /* SIT is zero, so the threshold status means TX FIFO empty. */
+ done = readl(mb->regs + AMD_PL_MBOX_STATUS) &
+ AMD_PL_MBOX_STS_TX_THRESHOLD;
+ spin_unlock_irqrestore(&mb->tx_lock, flags);
+
+ return done;
+}
+
+static int amd_pl_mbox_startup(struct mbox_chan *chan)
+{
+ struct amd_pl_mbox *mb = chan->con_priv;
+ unsigned long flags;
+ u32 irq_enable;
+ int ret;
+
+ if (chan != &mb->chans[AMD_PL_MBOX_CHAN_TX] &&
+ chan != &mb->chans[AMD_PL_MBOX_CHAN_RX])
+ return -EINVAL;
+
+ ret = clk_prepare_enable(mb->clk);
+ if (ret)
+ return ret;
+
+ if (chan == &mb->chans[AMD_PL_MBOX_CHAN_TX]) {
+ writel(AMD_PL_MBOX_CTRL_TX_RESET, mb->regs + AMD_PL_MBOX_CTRL);
+ writel(0, mb->regs + AMD_PL_MBOX_SEND_THRESHOLD);
+
+ spin_lock_irqsave(&mb->tx_lock, flags);
+ mb->tx_active = true;
+ spin_unlock_irqrestore(&mb->tx_lock, flags);
+
+ return 0;
+ }
+
+ writel(AMD_PL_MBOX_CTRL_RX_RESET, mb->regs + AMD_PL_MBOX_CTRL);
+ /* Interrupt when the RX FIFO changes from empty to nonempty. */
+ writel(0, mb->regs + AMD_PL_MBOX_RECV_THRESHOLD);
+ writel(AMD_PL_MBOX_IRQ_RX_THRESHOLD | AMD_PL_MBOX_IRQ_ERROR,
+ mb->regs + AMD_PL_MBOX_IRQ_STATUS);
+ irq_enable = readl(mb->regs + AMD_PL_MBOX_IRQ_ENABLE);
+ irq_enable |= AMD_PL_MBOX_IRQ_RX_THRESHOLD | AMD_PL_MBOX_IRQ_ERROR;
+ writel(irq_enable, mb->regs + AMD_PL_MBOX_IRQ_ENABLE);
+ enable_irq(mb->irq);
+
+ return 0;
+}
+
+static void amd_pl_mbox_shutdown(struct mbox_chan *chan)
+{
+ struct amd_pl_mbox *mb = chan->con_priv;
+ unsigned long flags;
+ u32 irq_enable;
+
+ if (chan == &mb->chans[AMD_PL_MBOX_CHAN_TX]) {
+ spin_lock_irqsave(&mb->tx_lock, flags);
+ mb->tx_active = false;
+ spin_unlock_irqrestore(&mb->tx_lock, flags);
+
+ writel(AMD_PL_MBOX_CTRL_TX_RESET, mb->regs + AMD_PL_MBOX_CTRL);
+ clk_disable_unprepare(mb->clk);
+ return;
+ }
+
+ if (chan != &mb->chans[AMD_PL_MBOX_CHAN_RX])
+ return;
+
+ disable_irq(mb->irq);
+ irq_enable = readl(mb->regs + AMD_PL_MBOX_IRQ_ENABLE);
+ irq_enable &= ~(AMD_PL_MBOX_IRQ_RX_THRESHOLD | AMD_PL_MBOX_IRQ_ERROR);
+ writel(irq_enable, mb->regs + AMD_PL_MBOX_IRQ_ENABLE);
+ writel(AMD_PL_MBOX_IRQ_RX_THRESHOLD | AMD_PL_MBOX_IRQ_ERROR,
+ mb->regs + AMD_PL_MBOX_IRQ_STATUS);
+ writel(AMD_PL_MBOX_CTRL_RX_RESET, mb->regs + AMD_PL_MBOX_CTRL);
+ clk_disable_unprepare(mb->clk);
+}
+
+static const struct mbox_chan_ops amd_pl_mbox_ops = {
+ .send_data = amd_pl_mbox_send_data,
+ .last_tx_done = amd_pl_mbox_last_tx_done,
+ .startup = amd_pl_mbox_startup,
+ .shutdown = amd_pl_mbox_shutdown,
+};
+
+static void amd_pl_mbox_rx(struct amd_pl_mbox *mb)
+{
+ /* data[] follows a size_t in a kzalloc() buffer: u32 aligned. */
+ u32 *rx_words = (u32 *)mb->rx_msg->data;
+ u32 words;
+
+ /*
+ * The hardware reports neither the peer's message length nor the FIFO
+ * occupancy. Read until the FIFO is empty, up to one FIFO depth, and
+ * deliver the words as one message.
+ */
+ for (words = 0; words < mb->fifo_len; words++) {
+ if (readl(mb->regs + AMD_PL_MBOX_STATUS) &
+ AMD_PL_MBOX_STS_RX_EMPTY)
+ break;
+
+ rx_words[words] = readl(mb->regs + AMD_PL_MBOX_RDDATA);
+ }
+
+ if (words) {
+ mb->rx_msg->len = words * sizeof(u32);
+ mbox_chan_received_data(&mb->chans[AMD_PL_MBOX_CHAN_RX],
+ mb->rx_msg);
+ }
+}
+
+static void amd_pl_mbox_rx_rearm(struct amd_pl_mbox *mb)
+{
+ /*
+ * The receive event latches only when the FIFO goes from empty to
+ * nonempty. A word that arrived after the drain, but before the event
+ * was acknowledged, had its event cleared, and words left by the
+ * bounded drain never raised one. Raise the threshold and lower it
+ * again to latch a new event, so that the interrupt fires for them.
+ * This cannot latch an event while the FIFO is full, which happens only
+ * if the peer writes a full FIFO depth during one interrupt.
+ */
+ if (!(readl(mb->regs + AMD_PL_MBOX_STATUS) &
+ AMD_PL_MBOX_STS_RX_EMPTY)) {
+ writel(mb->fifo_len - 1, mb->regs + AMD_PL_MBOX_RECV_THRESHOLD);
+ writel(0, mb->regs + AMD_PL_MBOX_RECV_THRESHOLD);
+ }
+}
+
+static irqreturn_t amd_pl_mbox_irq(int irq, void *data)
+{
+ struct amd_pl_mbox *mb = data;
+ u32 pending_intr;
+ u32 irq_enable;
+ u32 irq_status;
+
+ irq_status = readl(mb->regs + AMD_PL_MBOX_IRQ_STATUS);
+ irq_enable = readl(mb->regs + AMD_PL_MBOX_IRQ_ENABLE);
+ pending_intr = irq_status & irq_enable;
+
+ if (!pending_intr) {
+ dev_warn_ratelimited(mb->controller.dev,
+ "unexpected interrupt: status %#x enable %#x\n",
+ irq_status, irq_enable);
+ return IRQ_NONE;
+ }
+
+ if (pending_intr & AMD_PL_MBOX_IRQ_RX_THRESHOLD)
+ amd_pl_mbox_rx(mb);
+
+ if (pending_intr & AMD_PL_MBOX_IRQ_ERROR)
+ dev_warn_ratelimited(mb->controller.dev,
+ "mailbox FIFO error: %#x\n",
+ readl(mb->regs + AMD_PL_MBOX_ERROR));
+
+ writel(pending_intr, mb->regs + AMD_PL_MBOX_IRQ_STATUS);
+
+ if (pending_intr & AMD_PL_MBOX_IRQ_RX_THRESHOLD)
+ amd_pl_mbox_rx_rearm(mb);
+
+ return IRQ_HANDLED;
+}
+
+static struct mbox_chan *amd_pl_mbox_xlate(struct mbox_controller *ctlr,
+ const struct of_phandle_args *sp)
+{
+ if (sp->args_count != 1 || sp->args[0] >= AMD_PL_MBOX_NUM_CHANS)
+ return ERR_PTR(-EINVAL);
+
+ return &ctlr->chans[sp->args[0]];
+}
+
+static int amd_pl_mbox_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct amd_pl_mbox *mb;
+ int ret;
+ int i;
+
+ mb = devm_kzalloc(dev, sizeof(*mb), GFP_KERNEL);
+ if (!mb)
+ return -ENOMEM;
+
+ spin_lock_init(&mb->tx_lock);
+
+ mb->fifo_len = AMD_PL_MBOX_FIFO_LEN_MIN;
+ ret = of_property_read_u32(dev->of_node, "amd,mbox-pl-fifo-len",
+ &mb->fifo_len);
+ if (ret && ret != -EINVAL)
+ return dev_err_probe(dev, ret, "failed to read FIFO length\n");
+
+ if (mb->fifo_len < AMD_PL_MBOX_FIFO_LEN_MIN ||
+ mb->fifo_len > AMD_PL_MBOX_FIFO_LEN_MAX)
+ return dev_err_probe(dev, -EINVAL, "invalid FIFO length %u\n",
+ mb->fifo_len);
+
+ mb->rx_msg = devm_kzalloc(dev, struct_size(mb->rx_msg, data,
+ mb->fifo_len * sizeof(u32)),
+ GFP_KERNEL);
+ if (!mb->rx_msg)
+ return -ENOMEM;
+
+ mb->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mb->regs))
+ return PTR_ERR(mb->regs);
+
+ mb->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(mb->clk))
+ return dev_err_probe(dev, PTR_ERR(mb->clk), "failed to get clock\n");
+
+ mb->irq = platform_get_irq(pdev, 0);
+ if (mb->irq < 0)
+ return mb->irq;
+
+ for (i = 0; i < AMD_PL_MBOX_NUM_CHANS; i++)
+ mb->chans[i].con_priv = mb;
+
+ mb->controller.dev = dev;
+ mb->controller.ops = &amd_pl_mbox_ops;
+ mb->controller.chans = mb->chans;
+ mb->controller.num_chans = AMD_PL_MBOX_NUM_CHANS;
+ mb->controller.txdone_poll = true;
+ mb->controller.txpoll_period = 1;
+ mb->controller.of_xlate = amd_pl_mbox_xlate;
+
+ ret = devm_request_irq(dev, mb->irq, amd_pl_mbox_irq,
+ IRQF_NO_AUTOEN,
+ dev_name(dev), mb);
+ if (ret)
+ return ret;
+
+ return devm_mbox_controller_register(dev, &mb->controller);
+}
+
+static const struct of_device_id amd_pl_mbox_of_match[] = {
+ { .compatible = "amd,zynqmp-pl-mbox-2.1" },
+ { .compatible = "amd,versal-pl-mbox-2.1" },
+ { .compatible = "amd,versal-net-pl-mbox-2.1" },
+ { .compatible = "amd,versal2-pl-mbox-2.1" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, amd_pl_mbox_of_match);
+
+static struct platform_driver amd_pl_mbox_driver = {
+ .probe = amd_pl_mbox_probe,
+ .driver = {
+ .name = "amd-pl-mailbox",
+ .of_match_table = amd_pl_mbox_of_match,
+ },
+};
+module_platform_driver(amd_pl_mbox_driver);
+
+MODULE_DESCRIPTION("AMD PL Mailbox soft IP controller");
+MODULE_LICENSE("GPL");
--
2.34.1
next prev parent reply other threads:[~2026-10-14 21:34 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-14 21:34 [PATCH 0/6] Add AMD PL Mailbox and RPMsg support for the AMD BRAM remoteproc Ben Levinsky
2026-10-14 21:34 ` [PATCH 1/6] remoteproc: amd_mbv_bram: Bound device name formatting Ben Levinsky
2026-10-14 21:48 ` sashiko-bot
2026-10-14 21:34 ` [PATCH 2/6] remoteproc: amd_mbv_bram: Restore reset GPIO direction after PL reconfig Ben Levinsky
2026-10-14 21:44 ` sashiko-bot
2026-10-14 21:34 ` [PATCH 3/6] dt-bindings: mailbox: Add AMD PL Mailbox IP Ben Levinsky
2026-10-14 21:34 ` Ben Levinsky [this message]
2026-10-14 21:41 ` [PATCH 4/6] mailbox: Add AMD PL Mailbox soft IP controller sashiko-bot
2026-10-14 21:34 ` [PATCH 5/6] dt-bindings: remoteproc: Add RPMsg resources to AMD BRAM rproc Ben Levinsky
2026-10-14 21:34 ` [PATCH 6/6] remoteproc: amd_mbv_bram: Add RPMsg support Ben Levinsky
2026-10-14 21:45 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261014213426.2957480-5-ben.levinsky@amd.com \
--to=ben.levinsky@amd.com \
--cc=andersson@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=jassisinghbrar@gmail.com \
--cc=krzk+dt@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-remoteproc@vger.kernel.org \
--cc=mathieu.poirier@linaro.org \
--cc=michal.simek@amd.com \
--cc=robh@kernel.org \
--cc=tanmay.shah@amd.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.