* [PATCH 1/2] dt-bindings: mailbox: add Axiado AX3005 mailbox
2026-07-30 4:36 [PATCH 0/2] mailbox: add Axiado AX3005 mailbox driver Swark Yang
@ 2026-07-30 4:36 ` Swark Yang
2026-07-30 5:59 ` Krzysztof Kozlowski
2026-07-30 4:36 ` [PATCH 2/2] mailbox: add Axiado AX3005 mailbox driver Swark Yang
1 sibling, 1 reply; 5+ messages in thread
From: Swark Yang @ 2026-07-30 4:36 UTC (permalink / raw)
To: Krishnakumar Kesavan, Prasad Bolisetty, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Tzu-Hao Wei,
Karthikeyan Mitran
Cc: linux-kernel, devicetree, linux-arm-kernel, Swark Yang
Add a devicetree binding for the Axiado AX3005 mailbox controller.
The controller provides inter-processor communication channels
between the host CPU and the coprocessor, with separate TX and RX
register regions.
Add the corresponding MAINTAINERS entry covering the binding.
Signed-off-by: Swark Yang <syang@axiado.com>
---
.../bindings/mailbox/axiado,ax3005-mailbox.yaml | 98 ++++++++++++++++++++++
MAINTAINERS | 7 ++
2 files changed, 105 insertions(+)
diff --git a/Documentation/devicetree/bindings/mailbox/axiado,ax3005-mailbox.yaml b/Documentation/devicetree/bindings/mailbox/axiado,ax3005-mailbox.yaml
new file mode 100644
index 000000000000..b4fab96356c1
--- /dev/null
+++ b/Documentation/devicetree/bindings/mailbox/axiado,ax3005-mailbox.yaml
@@ -0,0 +1,98 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mailbox/axiado,ax3005-mailbox.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Axiado AX3005 Mailbox Controller
+
+maintainers:
+ - Krishnakumar Kesavan <kkesavan@axiado.com>
+ - Prasad Bolisetty <pbolisetty@axiado.com>
+ - Swark Yang <syang@axiado.com>
+
+description: |
+ The Axiado mailbox controller provides inter-processor communication
+ channels between subsystems within the Axiado SoC. The hardware has
+ separate TX and RX register regions. Only the RX channels (channels 8-15)
+ generate interrupts; TX channels are purely poll/write-based.
+
+properties:
+ compatible:
+ const: axiado,ax3005-mailbox
+
+ reg:
+ items:
+ - description: TX mailbox register region
+ - description: RX mailbox register region
+
+ reg-names:
+ items:
+ - const: tx
+ - const: rx
+
+ interrupts:
+ description: |
+ One interrupt per RX channel. The hardware exposes eight RX interrupt
+ lines corresponding to channels 8 through 15.
+ minItems: 8
+ maxItems: 8
+
+ interrupt-names:
+ description: |
+ Named interrupt lines for each RX channel, from ch8 to ch15.
+ items:
+ - const: ch8
+ - const: ch9
+ - const: ch10
+ - const: ch11
+ - const: ch12
+ - const: ch13
+ - const: ch14
+ - const: ch15
+
+ "#mbox-cells":
+ description: |
+ Number of cells in a mailbox specifier. A single cell encodes the
+ channel index as seen by the client.
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - interrupts
+ - interrupt-names
+ - "#mbox-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ mailbox@330f2000 {
+ compatible = "axiado,ax3005-mailbox";
+ reg = <0x0 0x330f2000 0x0 0x2000>,
+ <0x0 0x331b0000 0x0 0x1000>;
+ reg-names = "tx", "rx";
+ /* Only RX channels (8-15) have IRQs */
+ interrupts = <GIC_SPI 242 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 243 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 244 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 245 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 246 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 247 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 248 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 249 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "ch8", "ch9", "ch10", "ch11",
+ "ch12", "ch13", "ch14", "ch15";
+ interrupt-parent = <&gic500>;
+ #mbox-cells = <1>;
+ status = "okay";
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index a674e36529f7..af6c3571c9f2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4437,6 +4437,13 @@ F: Documentation/devicetree/bindings/phy/axiado,ax3000-emmc-phy.yaml
F: drivers/phy/axiado/Kconfig
F: drivers/phy/axiado/phy-axiado-emmc.c
+AXIADO MAILBOX DRIVER
+M: Krishnakumar Kesavan <kkesavan@axiado.com>
+M: Prasad Bolisetty <pbolisetty@axiado.com>
+M: Swark Yang <syang@axiado.com>
+S: Supported
+F: Documentation/devicetree/bindings/mailbox/axiado,ax3005-mailbox.yaml
+
AXI SPI ENGINE
M: Michael Hennerich <michael.hennerich@analog.com>
M: Nuno Sá <nuno.sa@analog.com>
--
2.34.1
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 2/2] mailbox: add Axiado AX3005 mailbox driver
2026-07-30 4:36 [PATCH 0/2] mailbox: add Axiado AX3005 mailbox driver Swark Yang
2026-07-30 4:36 ` [PATCH 1/2] dt-bindings: mailbox: add Axiado AX3005 mailbox Swark Yang
@ 2026-07-30 4:36 ` Swark Yang
2026-07-30 4:48 ` sashiko-bot
1 sibling, 1 reply; 5+ messages in thread
From: Swark Yang @ 2026-07-30 4:36 UTC (permalink / raw)
To: Krishnakumar Kesavan, Prasad Bolisetty, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Tzu-Hao Wei,
Karthikeyan Mitran
Cc: linux-kernel, devicetree, linux-arm-kernel, Swark Yang
Add a mailbox controller driver for the Axiado AX3005 SoC.
The controller provides communication channels between
the host CPU and the coprocessor.
The hardware provides 8 TX channels and 8 RX channels
through separate register regions. RX channels use
per-channel interrupts, while TX completion is detected by
polling the FIFO status.
Add the driver path to the existing Axiado mailbox entry in
MAINTAINERS.
Signed-off-by: Swark Yang <syang@axiado.com>
---
MAINTAINERS | 1 +
drivers/mailbox/Kconfig | 10 ++
drivers/mailbox/Makefile | 2 +
drivers/mailbox/axiado-mailbox.c | 379 +++++++++++++++++++++++++++++++++++++++
4 files changed, 392 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index af6c3571c9f2..80db69111889 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4443,6 +4443,7 @@ M: Prasad Bolisetty <pbolisetty@axiado.com>
M: Swark Yang <syang@axiado.com>
S: Supported
F: Documentation/devicetree/bindings/mailbox/axiado,ax3005-mailbox.yaml
+F: drivers/mailbox/axiado-mailbox.c
AXI SPI ENGINE
M: Michael Hennerich <michael.hennerich@analog.com>
diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index 3062ee352f78..ab869769e278 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -399,4 +399,14 @@ config RISCV_SBI_MPXY_MBOX
or HS-mode hypervisor). Say Y here, unless you are sure you do not
need this.
+config AXIADO_MAILBOX
+ tristate "Axiado mailbox driver"
+ depends on ARCH_AXIADO || COMPILE_TEST
+ depends on OF
+ help
+ Enable support for the Axiado mailbox controller. The driver provides
+ communication channels between the host CPU and the coprocessor on
+ Axiado SoCs.
+ If unsure, say N.
+
endif
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index 944d8ea39f34..45dc59bb3e7f 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -84,3 +84,5 @@ obj-$(CONFIG_CIX_MBOX) += cix-mailbox.o
obj-$(CONFIG_BCM74110_MAILBOX) += bcm74110-mailbox.o
obj-$(CONFIG_RISCV_SBI_MPXY_MBOX) += riscv-sbi-mpxy-mbox.o
+
+obj-$(CONFIG_AXIADO_MAILBOX) += axiado-mailbox.o
diff --git a/drivers/mailbox/axiado-mailbox.c b/drivers/mailbox/axiado-mailbox.c
new file mode 100644
index 000000000000..4f1daf394a27
--- /dev/null
+++ b/drivers/mailbox/axiado-mailbox.c
@@ -0,0 +1,379 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2021-2026 Axiado Corporation (or its affiliates).
+ */
+
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/bitfield.h>
+#include <linux/iopoll.h>
+#include <linux/mailbox_controller.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
+
+#define AXIADO_MBOX_TX_CHANS 8 /* 0-7 */
+#define AXIADO_MBOX_RX_CHANS 8 /* 8-15 */
+#define AXIADO_MBOX_CHAN_STRIDE 0x4
+#define AXIADO_MBOX_TX_REG_STRIDE 0x40
+#define AXIADO_MBOX_RX_REG_STRIDE 0x30
+#define AXIADO_MBOX_RX_POLL_US 10
+#define AXIADO_MBOX_RX_TIMEOUT_US 500
+
+/* Mailbox CSR bit definitions */
+#define AXIADO_MBOX_CSR_EMPTY BIT(0) /* 1 = FIFO empty; 0 = data available */
+#define AXIADO_MBOX_CSR_OVERFLOW BIT(2) /* write 1 to clear (W1C) */
+#define AXIADO_MBOX_CSR_UNDERFLOW BIT(3) /* write 1 to clear (W1C) */
+#define AXIADO_MBOX_CSR_FLUSH BIT(4) /* W1TRG flush; startup/shutdown */
+#define AXIADO_MBOX_CSR_LEVEL GENMASK(11, 5)
+
+#define AXIADO_MBOX_CSR_ERRORS (AXIADO_MBOX_CSR_OVERFLOW | \
+ AXIADO_MBOX_CSR_UNDERFLOW)
+
+struct axiado_mbox_data {
+ u8 num_chans;
+ u16 msg_size;
+};
+
+struct axiado_channel_data {
+ void __iomem *mbox_reg;
+ void __iomem *csr_reg;
+ void *rx_buffer;
+ u8 channel_num;
+ int irq;
+ struct mbox_chan *chan;
+ u16 chan_msg_size;
+ bool active;
+};
+
+/* mailbox side */
+struct axiado_mbox {
+ struct mbox_controller mbox;
+ const struct axiado_mbox_data *drv_data;
+ void __iomem *tx_base;
+ void __iomem *rx_base;
+};
+
+/*
+ * Checks OVERFLOW/UNDERFLOW, logs and W1C-clears them if set. Shared by the
+ * TX and RX paths; RX additionally flushes the FIFO afterwards to resync
+ * framing, which TX must not do since it could discard data the peer has
+ * not read yet.
+ */
+static bool axiado_mbox_clear_fifo_errors(struct axiado_channel_data *priv)
+{
+ struct device *dev = priv->chan->mbox->dev;
+ u32 errors;
+
+ errors = readl(priv->csr_reg) & AXIADO_MBOX_CSR_ERRORS;
+ if (!errors)
+ return false;
+
+ writel(errors, priv->csr_reg);
+
+ dev_warn_ratelimited(dev, "Channel %u FIFO error: %#x\n",
+ priv->channel_num, errors);
+
+ return true;
+}
+
+static int axiado_mbox_send_data(struct mbox_chan *chan, void *data)
+{
+ struct axiado_mbox *mb = dev_get_drvdata(chan->mbox->dev);
+ struct axiado_channel_data *priv = chan->con_priv;
+ unsigned int idx = priv->channel_num;
+ u8 tail[sizeof(u32)] = { 0 };
+ const u8 *buf = data;
+ unsigned int tail_len;
+ unsigned int offset;
+ u32 msg_len;
+
+ if (priv->channel_num >= AXIADO_MBOX_TX_CHANS)
+ return -EINVAL;
+
+ if (!data)
+ return -EINVAL;
+
+ /*
+ * The Axiado mailbox message ABI stores the total message length,
+ * including this length word, as a little-endian byte count in the
+ * first word of every message.
+ */
+ msg_len = get_unaligned_le32(data);
+ if (msg_len < sizeof(u32) || msg_len > mb->drv_data->msg_size)
+ return -EINVAL;
+
+ /* Only touch the hardware after validating the message. */
+ axiado_mbox_clear_fifo_errors(priv);
+
+ if (!(readl(priv->csr_reg) & AXIADO_MBOX_CSR_EMPTY)) {
+ dev_warn(mb->mbox.dev, "Channel %u is busy\n", idx);
+ return -EBUSY;
+ }
+
+ for (offset = 0; offset + sizeof(u32) <= msg_len;
+ offset += sizeof(u32))
+ writel(get_unaligned_le32(buf + offset), priv->mbox_reg);
+
+ tail_len = msg_len - offset;
+ if (tail_len) {
+ memcpy(tail, buf + offset, tail_len);
+ writel(get_unaligned_le32(tail), priv->mbox_reg);
+ }
+
+ dev_dbg(mb->mbox.dev, "%s: Ch-%u sent\n", __func__, idx);
+
+ return 0;
+}
+
+static bool axiado_mbox_handle_rx_error(struct axiado_channel_data *priv)
+{
+ if (!axiado_mbox_clear_fifo_errors(priv))
+ return false;
+
+ /* Errors leave RX framing out of sync; flush and resync. */
+ writel(AXIADO_MBOX_CSR_FLUSH, priv->csr_reg);
+
+ return true;
+}
+
+static irqreturn_t axiado_rx_thread(int irq, void *dev_id)
+{
+ struct axiado_channel_data *priv = dev_id;
+ struct mbox_chan *chan = priv->chan;
+ u8 *buf = priv->rx_buffer;
+ unsigned int num_words;
+ unsigned int remaining;
+ unsigned int i;
+ u32 msg_len;
+ u32 word;
+ u32 csr;
+ int ret;
+
+ if (axiado_mbox_handle_rx_error(priv))
+ return IRQ_HANDLED;
+
+ csr = readl(priv->csr_reg);
+ if (csr & AXIADO_MBOX_CSR_EMPTY)
+ return IRQ_NONE;
+
+ /*
+ * The first word contains the total message length in bytes,
+ * including the length word itself.
+ */
+ word = readl(priv->mbox_reg);
+ msg_len = word;
+ put_unaligned_le32(word, buf);
+
+ if (msg_len < sizeof(u32) ||
+ msg_len > priv->chan_msg_size)
+ goto invalid_message;
+
+ num_words = DIV_ROUND_UP(msg_len, sizeof(u32));
+ remaining = num_words - 1;
+
+ /*
+ * The not-empty interrupt may occur as soon as the first DW enters
+ * the FIFO. Wait until all remaining DWs of this message arrive.
+ */
+ if (remaining) {
+ ret = readl_poll_timeout(priv->csr_reg, csr,
+ (csr & AXIADO_MBOX_CSR_ERRORS) ||
+ FIELD_GET(AXIADO_MBOX_CSR_LEVEL, csr) >=
+ remaining,
+ AXIADO_MBOX_RX_POLL_US,
+ AXIADO_MBOX_RX_TIMEOUT_US);
+ if (ret)
+ goto incomplete_message;
+
+ if (axiado_mbox_handle_rx_error(priv))
+ return IRQ_HANDLED;
+ }
+
+ for (i = 1; i < num_words; i++) {
+ word = readl(priv->mbox_reg);
+ put_unaligned_le32(word,
+ buf + i * sizeof(u32));
+ }
+
+ if (axiado_mbox_handle_rx_error(priv))
+ return IRQ_HANDLED;
+
+ if (READ_ONCE(priv->active))
+ mbox_chan_received_data(chan, priv->rx_buffer);
+
+ return IRQ_HANDLED;
+
+incomplete_message:
+ dev_warn_ratelimited(chan->mbox->dev,
+ "Channel %u received an incomplete message\n",
+ priv->channel_num);
+
+invalid_message:
+ writel(AXIADO_MBOX_CSR_FLUSH, priv->csr_reg);
+
+ return IRQ_HANDLED;
+}
+
+static int axiado_mbox_startup(struct mbox_chan *chan)
+{
+ struct axiado_channel_data *priv = chan->con_priv;
+
+ writel(AXIADO_MBOX_CSR_FLUSH, priv->csr_reg);
+ writel(AXIADO_MBOX_CSR_ERRORS, priv->csr_reg);
+ WRITE_ONCE(priv->active, true);
+
+ if (priv->channel_num >= AXIADO_MBOX_TX_CHANS)
+ enable_irq(priv->irq);
+
+ return 0;
+}
+
+static void axiado_mbox_shutdown(struct mbox_chan *chan)
+{
+ struct axiado_channel_data *priv = chan->con_priv;
+
+ WRITE_ONCE(priv->active, false);
+
+ if (priv->channel_num >= AXIADO_MBOX_TX_CHANS)
+ disable_irq(priv->irq);
+
+ writel(AXIADO_MBOX_CSR_FLUSH, priv->csr_reg);
+}
+
+static bool axiado_mbox_last_tx_done(struct mbox_chan *chan)
+{
+ struct axiado_channel_data *priv = chan->con_priv;
+
+ return !!(readl(priv->csr_reg) & AXIADO_MBOX_CSR_EMPTY);
+}
+
+static const struct mbox_chan_ops axiado_mbox_chan_ops = {
+ .send_data = axiado_mbox_send_data,
+ .startup = axiado_mbox_startup,
+ .shutdown = axiado_mbox_shutdown,
+ .last_tx_done = axiado_mbox_last_tx_done,
+};
+
+static int axiado_mbox_probe(struct platform_device *pdev)
+{
+ const struct axiado_mbox_data *drv_data;
+ struct axiado_channel_data *ch_data;
+ struct device *dev = &pdev->dev;
+ struct axiado_mbox *mb;
+ unsigned int irq_idx = 0;
+ unsigned int rx_chan;
+ unsigned int i;
+ int ret;
+
+ drv_data = device_get_match_data(dev);
+ if (!drv_data)
+ return -ENODEV;
+
+ mb = devm_kzalloc(dev, sizeof(*mb), GFP_KERNEL);
+ if (!mb)
+ return -ENOMEM;
+
+ mb->mbox.dev = dev;
+ mb->mbox.num_chans = drv_data->num_chans;
+
+ mb->mbox.chans = devm_kcalloc(&pdev->dev,
+ drv_data->num_chans,
+ sizeof(*mb->mbox.chans),
+ GFP_KERNEL);
+ if (!mb->mbox.chans)
+ return -ENOMEM;
+
+ mb->tx_base = devm_platform_ioremap_resource_byname(pdev, "tx");
+ if (IS_ERR(mb->tx_base))
+ return PTR_ERR(mb->tx_base);
+
+ mb->rx_base = devm_platform_ioremap_resource_byname(pdev, "rx");
+ if (IS_ERR(mb->rx_base))
+ return PTR_ERR(mb->rx_base);
+
+ ch_data = devm_kcalloc(&pdev->dev,
+ drv_data->num_chans,
+ sizeof(*ch_data),
+ GFP_KERNEL);
+ if (!ch_data)
+ return -ENOMEM;
+
+ for (i = 0; i < drv_data->num_chans; i++) {
+ ch_data[i].channel_num = i;
+ ch_data[i].chan = &mb->mbox.chans[i];
+ ch_data[i].chan_msg_size = drv_data->msg_size;
+
+ mb->mbox.chans[i].con_priv = &ch_data[i];
+
+ if (i < AXIADO_MBOX_TX_CHANS) {
+ ch_data[i].mbox_reg = mb->tx_base + (i * AXIADO_MBOX_CHAN_STRIDE);
+ ch_data[i].csr_reg = mb->tx_base + AXIADO_MBOX_TX_REG_STRIDE +
+ (i * AXIADO_MBOX_CHAN_STRIDE);
+ ch_data[i].irq = -1;
+ continue;
+ }
+
+ ch_data[i].rx_buffer = devm_kmalloc(dev, drv_data->msg_size,
+ GFP_KERNEL);
+ if (!ch_data[i].rx_buffer)
+ return -ENOMEM;
+
+ rx_chan = i - AXIADO_MBOX_TX_CHANS;
+ ch_data[i].mbox_reg = mb->rx_base +
+ (rx_chan * AXIADO_MBOX_CHAN_STRIDE);
+ ch_data[i].csr_reg = mb->rx_base + AXIADO_MBOX_RX_REG_STRIDE +
+ (rx_chan * AXIADO_MBOX_CHAN_STRIDE);
+ ch_data[i].irq = platform_get_irq(pdev, irq_idx++);
+ if (ch_data[i].irq < 0)
+ return dev_err_probe(dev, ch_data[i].irq,
+ "Failed to get IRQ for channel %u\n", i);
+
+ ret = devm_request_threaded_irq(dev, ch_data[i].irq, NULL,
+ axiado_rx_thread,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ dev_name(dev), &ch_data[i]);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to request IRQ for channel %u\n", i);
+
+ dev_dbg(dev, "RX chan %u -> irq %d\n", i, ch_data[i].irq);
+ }
+
+ platform_set_drvdata(pdev, mb);
+ mb->drv_data = drv_data;
+ mb->mbox.ops = &axiado_mbox_chan_ops;
+ mb->mbox.txdone_irq = false;
+ mb->mbox.txdone_poll = true;
+ mb->mbox.txpoll_period = 5;
+
+ return devm_mbox_controller_register(dev, &mb->mbox);
+}
+
+static const struct axiado_mbox_data axiado_drv_data = {
+ .num_chans = AXIADO_MBOX_TX_CHANS + AXIADO_MBOX_RX_CHANS,
+ .msg_size = 256,
+};
+
+static const struct of_device_id axiado_mbox_of_match[] = {
+ { .compatible = "axiado,ax3005-mailbox", .data = &axiado_drv_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, axiado_mbox_of_match);
+
+static struct platform_driver axiado_mbox_driver = {
+ .driver = {
+ .name = "axiado-mailbox",
+ .of_match_table = axiado_mbox_of_match,
+ },
+ .probe = axiado_mbox_probe,
+};
+module_platform_driver(axiado_mbox_driver);
+
+MODULE_AUTHOR("AXIADO CORPORATION");
+MODULE_DESCRIPTION("Axiado Mailbox driver");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 5+ messages in thread