From: Tim Michals <tcmichals@gmail.com>
To: linux-sunxi@lists.linux.dev, linux-remoteproc@vger.kernel.org
Cc: devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, wens@kernel.org,
jernej.skrabec@gmail.com, samuel@sholland.org,
andersson@kernel.org, mathieu.poirier@linaro.org,
jassisinghbrar@gmail.com, robh@kernel.org, krzk+dt@kernel.org,
conor+dt@kernel.org, Tim Michals <tcmichals@gmail.com>
Subject: [PATCH v2 5/7] remoteproc: sunxi: add Allwinner XuanTie RISC-V remoteproc driver
Date: Sat, 26 Sep 2026 19:20:14 -0500 [thread overview]
Message-ID: <20260927002021.797069-6-tcmichals@gmail.com> (raw)
In-Reply-To: <20260927002021.797069-1-tcmichals@gmail.com>
Add remoteproc driver for the auxiliary Alibaba T-Head XuanTie E907
RISC-V co-processor found on Allwinner A523, A527, and T527 SoCs.
Features implemented:
- SRAM Space 0 and Space 1 memory window translation via Address
Translation Table (ATT) architecture matching imx_rproc conventions.
- Dynamic DRAM carveout and CMA buffer allocation for VirtIO RPMsg.
- CCU reset and clock lifecycle management with boot address setup
prior to core reset de-assertion.
- Dual-channel hardware Message Box integration for kick/vq signalling.
- Workqueue-deferred RPMsg notification for Linux PREEMPT_RT safety.
- Ring-buffer trace0 diagnostic endpoint via debugfs.
Signed-off-by: Tim Michals <tcmichals@gmail.com>
---
drivers/remoteproc/Kconfig | 8 +
drivers/remoteproc/Makefile | 1 +
drivers/remoteproc/sunxi_rproc.c | 922 +++++++++++++++++++++++++++++++
drivers/remoteproc/sunxi_rproc.h | 116 ++++
4 files changed, 1047 insertions(+)
create mode 100644 drivers/remoteproc/sunxi_rproc.c
create mode 100644 drivers/remoteproc/sunxi_rproc.h
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index 73500fdc227d..d35b033b9106 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -388,6 +388,14 @@ config XLNX_R5_REMOTEPROC
It's safe to say N if not interested in using RPU r5f cores.
+config SUNXI_REMOTEPROC
+ tristate "Allwinner XuanTie RISC-V remoteproc support"
+ depends on (ARCH_SUNXI || COMPILE_TEST) && MAILBOX
+ help
+ Say y or m here to support the Allwinner XuanTie E906/E907 RISC-V
+ co-processor on Allwinner A523, A527, and T527 SoCs via the
+ remote processor framework.
+
endif # REMOTEPROC
endmenu
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 689686de0d41..02acefa6d880 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -41,3 +41,4 @@ obj-$(CONFIG_TI_K3_DSP_REMOTEPROC) += ti_k3_dsp_remoteproc.o ti_k3_common.o
obj-$(CONFIG_TI_K3_M4_REMOTEPROC) += ti_k3_m4_remoteproc.o ti_k3_common.o
obj-$(CONFIG_TI_K3_R5_REMOTEPROC) += ti_k3_r5_remoteproc.o ti_k3_common.o
obj-$(CONFIG_XLNX_R5_REMOTEPROC) += xlnx_r5_remoteproc.o
+obj-$(CONFIG_SUNXI_REMOTEPROC) += sunxi_rproc.o
diff --git a/drivers/remoteproc/sunxi_rproc.c b/drivers/remoteproc/sunxi_rproc.c
new file mode 100644
index 000000000000..fbb0ee8a9110
--- /dev/null
+++ b/drivers/remoteproc/sunxi_rproc.c
@@ -0,0 +1,922 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Allwinner XuanTie E906/E907 RISC-V Remote Processor Driver
+ *
+ * Copyright (C) 2024-2026 Allwinner Technology Co., Ltd.
+ * Copyright (C) 2026 Tim Michals <tcmichals@gmail.com>
+ *
+ * CCU-integrated remoteproc driver for XuanTie RISC-V co-processors on
+ * Allwinner SoCs (T527/A527/A523: E906/E907) supporting TCM, SRAM, and DRAM.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/remoteproc.h>
+#include <linux/reset.h>
+
+#include "remoteproc_internal.h"
+#include "sunxi_rproc.h"
+
+#define DRIVER_NAME "sunxi-rproc"
+
+static const struct sunxi_rproc_att sun55i_rproc_att[] = {
+ /* dev addr (remote) , sys addr (host PA) , size , flags */
+ /* Space 0 Core Aliases -> Space 0 Host PA */
+ { E907_SRAM_SPACE0_DA, SUN55I_SRAM_SPACE0_SYS, SUN55I_SRAM_SPACE0_SIZE, ATT_IOMEM },
+ { E907_SRAM_SPACE0_DA_ALT, SUN55I_SRAM_SPACE0_SYS, SUN55I_SRAM_SPACE0_SIZE, ATT_IOMEM },
+ { E907_SRAM_C_DA, SUN55I_SRAM_SPACE0_SYS, SUN55I_SRAM_SPACE0_SIZE, ATT_IOMEM },
+ { SUN55I_SRAM_SPACE0_SYS, SUN55I_SRAM_SPACE0_SYS, SUN55I_SRAM_SPACE0_SIZE, ATT_IOMEM },
+
+ /* Space 1 Core Aliases -> Space 1 Host PA */
+ { E907_SRAM_SPACE1_DA, SUN55I_SRAM_SPACE1_SYS, SUN55I_SRAM_SPACE1_SIZE, ATT_IOMEM },
+ { E907_SRAM_SPACE1_DA_ALT, SUN55I_SRAM_SPACE1_SYS, SUN55I_SRAM_SPACE1_SIZE, ATT_IOMEM },
+ { SUN55I_SRAM_SPACE1_SYS, SUN55I_SRAM_SPACE1_SYS, SUN55I_SRAM_SPACE1_SIZE, ATT_IOMEM },
+};
+
+const struct sunxi_rproc_cfg sun55i_riscv_cfg = {
+ .name = "XuanTie E907 RISC-V",
+ .att = sun55i_rproc_att,
+ .att_size = ARRAY_SIZE(sun55i_rproc_att),
+ .has_remap_reg = true,
+ .boot_reg_offset = E906_STA_ADD_REG,
+};
+
+#if IS_ENABLED(CONFIG_SUNXI_REMOTEPROC_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sun55i_riscv_cfg);
+#endif
+
+static void sunxi_rproc_vq_work(struct work_struct *work)
+{
+ struct sunxi_rproc *priv = container_of(work, struct sunxi_rproc, vq_work);
+
+ rproc_vq_interrupt(priv->rproc, 0);
+ rproc_vq_interrupt(priv->rproc, 1);
+}
+
+static void sunxi_rproc_mb_rx_callback(struct mbox_client *cl, void *data)
+{
+ struct sunxi_rproc *priv = container_of(cl, struct sunxi_rproc, cl);
+
+ schedule_work(&priv->vq_work);
+}
+
+static irqreturn_t sunxi_rproc_crash_handler(int irq, void *data)
+{
+ struct sunxi_rproc *priv = data;
+ struct rproc *rproc = priv->rproc;
+
+ dev_err(priv->dev, "Hardware crash event received from %s core!\n",
+ priv->cfg ? priv->cfg->name : "remote");
+ if (priv->crash_irq_enabled) {
+ disable_irq_nosync(irq);
+ priv->crash_irq_enabled = false;
+ }
+ rproc_report_crash(rproc, RPROC_FATAL_ERROR);
+
+ return IRQ_HANDLED;
+}
+
+int sunxi_rproc_prepare(struct rproc *rproc)
+{
+ struct sunxi_rproc *priv = rproc->priv;
+ const struct sunxi_rproc_cfg *cfg = priv->cfg ? priv->cfg : &sun55i_riscv_cfg;
+ int ret;
+
+ /* 1. Deassert configuration & SRAM bus resets */
+ if (priv->rst_cfg) {
+ ret = reset_control_deassert(priv->rst_cfg);
+ if (ret) {
+ dev_err(priv->dev, "failed to deassert cfg reset: %d\n", ret);
+ return ret;
+ }
+ }
+
+ if (priv->rst_sram) {
+ ret = reset_control_deassert(priv->rst_sram);
+ if (ret) {
+ dev_err(priv->dev, "failed to deassert sram reset: %d\n", ret);
+ goto err_assert_cfg;
+ }
+ }
+
+ if (priv->rst_msgbox) {
+ ret = reset_control_deassert(priv->rst_msgbox);
+ if (ret) {
+ dev_err(priv->dev, "failed to deassert msgbox reset: %d\n", ret);
+ goto err_assert_sram;
+ }
+ }
+
+ /* 2. Enable parent clock (PLL source) */
+ if (priv->clk_parent) {
+ ret = clk_prepare_enable(priv->clk_parent);
+ if (ret) {
+ dev_err(priv->dev, "failed to enable parent clock: %d\n", ret);
+ goto err_assert_msgbox;
+ }
+ }
+
+ /* 3. Enable interconnect bus and SRAM clocks */
+ if (priv->clk_bus) {
+ ret = clk_prepare_enable(priv->clk_bus);
+ if (ret) {
+ dev_err(priv->dev, "failed to enable bus clock: %d\n", ret);
+ goto err_disable_parent;
+ }
+ }
+
+ if (priv->clk_sram) {
+ ret = clk_prepare_enable(priv->clk_sram);
+ if (ret) {
+ dev_err(priv->dev, "failed to enable sram clock: %d\n", ret);
+ goto err_disable_bus;
+ }
+ }
+
+ if (priv->clk_msgbox) {
+ ret = clk_prepare_enable(priv->clk_msgbox);
+ if (ret) {
+ dev_err(priv->dev, "failed to enable msgbox clock: %d\n", ret);
+ goto err_disable_sram_clk;
+ }
+ }
+
+ /* 4. Enable core clock */
+ if (priv->clk_core) {
+ ret = clk_prepare_enable(priv->clk_core);
+ if (ret) {
+ dev_err(priv->dev, "failed to enable core clock: %d\n", ret);
+ goto err_disable_msgbox_clk;
+ }
+ }
+
+ /*
+ * 4b. Enable SRAMA3_2 for MCU_SYS (RISC-V) via REMAP_CTRL_REG bit 1.
+ */
+ if (cfg->has_remap_reg && priv->remap_va) {
+ u32 remap_val = readl(priv->remap_va);
+
+ remap_val |= SUNXI_REMAP_SRAMA3_2_BIT;
+ writel(remap_val, priv->remap_va);
+ dev_dbg(priv->dev, "REMAP_CTRL_REG set to 0x%08x (SRAMA3_2 enabled)\n",
+ readl(priv->remap_va));
+ }
+
+ /*
+ * 5. Cleanly clear Dedicated Local SRAM and Switchable SRAM.
+ * Only clears regions that are actually mapped from Device Tree.
+ * Matches upstream patterns (e.g., imx_rproc / ti_k3_r5_remoteproc) to:
+ * - Prevent ECC/parity noise on uninitialized memory banks.
+ * - Ensure NOBITS / .bss sections start strictly at zero.
+ * - Clear stale trace0 logs/telemetry from previous runs.
+ */
+ if (priv->r_sram_va && priv->r_sram_size)
+ memset_io(priv->r_sram_va, 0, priv->r_sram_size);
+
+ if (priv->r_sram1_va && priv->r_sram1_size)
+ memset_io(priv->r_sram1_va, 0, priv->r_sram1_size);
+
+ return 0;
+
+err_disable_msgbox_clk:
+ if (priv->clk_msgbox)
+ clk_disable_unprepare(priv->clk_msgbox);
+err_disable_sram_clk:
+ if (priv->clk_sram)
+ clk_disable_unprepare(priv->clk_sram);
+err_disable_bus:
+ if (priv->clk_bus)
+ clk_disable_unprepare(priv->clk_bus);
+err_disable_parent:
+ if (priv->clk_parent)
+ clk_disable_unprepare(priv->clk_parent);
+err_assert_msgbox:
+ if (priv->rst_msgbox)
+ reset_control_assert(priv->rst_msgbox);
+err_assert_sram:
+ if (priv->rst_sram)
+ reset_control_assert(priv->rst_sram);
+err_assert_cfg:
+ if (priv->rst_cfg)
+ reset_control_assert(priv->rst_cfg);
+ return ret;
+}
+
+#if IS_ENABLED(CONFIG_SUNXI_REMOTEPROC_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sunxi_rproc_prepare);
+#endif
+
+int sunxi_rproc_unprepare(struct rproc *rproc)
+{
+ struct sunxi_rproc *priv = rproc->priv;
+ const struct sunxi_rproc_cfg *cfg = priv->cfg ? priv->cfg : &sun55i_riscv_cfg;
+
+ /* Symmetrical CCU unwinding */
+ if (cfg->has_remap_reg && priv->remap_va) {
+ u32 remap_val = readl(priv->remap_va);
+
+ remap_val &= ~SUNXI_REMAP_SRAMA3_2_BIT;
+ writel(remap_val, priv->remap_va);
+ }
+
+ if (priv->clk_core)
+ clk_disable_unprepare(priv->clk_core);
+
+ if (priv->clk_msgbox)
+ clk_disable_unprepare(priv->clk_msgbox);
+
+ if (priv->clk_sram)
+ clk_disable_unprepare(priv->clk_sram);
+
+ if (priv->clk_bus)
+ clk_disable_unprepare(priv->clk_bus);
+
+ if (priv->clk_parent)
+ clk_disable_unprepare(priv->clk_parent);
+
+ if (priv->rst_msgbox)
+ reset_control_assert(priv->rst_msgbox);
+
+ if (priv->rst_sram)
+ reset_control_assert(priv->rst_sram);
+
+ if (priv->rst_cfg)
+ reset_control_assert(priv->rst_cfg);
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_SUNXI_REMOTEPROC_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sunxi_rproc_unprepare);
+#endif
+
+int sunxi_rproc_start(struct rproc *rproc)
+{
+ struct sunxi_rproc *priv = rproc->priv;
+ const struct sunxi_rproc_cfg *cfg = priv->cfg ? priv->cfg : &sun55i_riscv_cfg;
+ int ret;
+
+ dev_info(priv->dev, "Starting %s core at entry 0x%llx\n",
+ cfg->name ? cfg->name : "remote", (u64)rproc->bootaddr);
+
+ if (rproc->bootaddr > U32_MAX)
+ return -EINVAL;
+
+ /* Enable crash IRQ now that core is executing */
+ if (priv->crash_irq > 0 && !priv->crash_irq_enabled) {
+ enable_irq(priv->crash_irq);
+ priv->crash_irq_enabled = true;
+ }
+
+ /*
+ * Program boot vector while the core execution reset is held.
+ * The CFG block bus was un-gated during prepare() via rst_cfg.
+ */
+ if (priv->cfg_va) {
+ writel((u32)rproc->bootaddr, priv->cfg_va + cfg->boot_reg_offset);
+ dev_dbg(priv->dev, "STA_ADD set to 0x%08x\n", (u32)rproc->bootaddr);
+ }
+
+ /* Release core execution reset so the core begins execution at bootaddr */
+ if (priv->rst_core) {
+ ret = reset_control_deassert(priv->rst_core);
+ if (ret) {
+ dev_err(priv->dev, "failed to release core reset: %d\n", ret);
+ return ret;
+ }
+ } else if (priv->rst_cfg) {
+ ret = reset_control_deassert(priv->rst_cfg);
+ if (ret) {
+ dev_err(priv->dev, "failed to release cfg reset: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_SUNXI_REMOTEPROC_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sunxi_rproc_start);
+#endif
+
+int sunxi_rproc_stop(struct rproc *rproc)
+{
+ struct sunxi_rproc *priv = rproc->priv;
+ const struct sunxi_rproc_cfg *cfg = priv->cfg ? priv->cfg : &sun55i_riscv_cfg;
+
+ dev_info(priv->dev, "Halting %s core...\n",
+ cfg->name ? cfg->name : "remote");
+
+ /*
+ * Assert reset first so the core stops generating mailbox interrupts,
+ * then drain any work already queued. Reversing this order leaves a
+ * window where a late IRQ re-queues vq_work after cancel_work_sync()
+ * returns, executing on freed resources.
+ */
+ if (priv->rst_core)
+ reset_control_assert(priv->rst_core);
+ else if (priv->rst_cfg)
+ reset_control_assert(priv->rst_cfg);
+
+ /* Disable crash IRQ while core is stopped */
+ if (priv->crash_irq > 0 && priv->crash_irq_enabled) {
+ disable_irq(priv->crash_irq);
+ priv->crash_irq_enabled = false;
+ }
+
+ cancel_work_sync(&priv->vq_work);
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_SUNXI_REMOTEPROC_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sunxi_rproc_stop);
+#endif
+
+void sunxi_rproc_kick(struct rproc *rproc, int vqid)
+{
+ struct sunxi_rproc *priv = rproc->priv;
+ int ret;
+
+ if (!priv->tx_chan)
+ return;
+
+ /*
+ * Use priv->kick_msg rather than a stack-local variable. The mailbox
+ * controller runs with tx_block=false, so mbox_send_message() may
+ * queue the pointer and return before the hardware reads the message.
+ * A stack-local vqid would be a use-after-return at that point.
+ */
+ priv->kick_msg = (u32)vqid;
+ ret = mbox_send_message(priv->tx_chan, &priv->kick_msg);
+ if (ret < 0)
+ dev_err_ratelimited(priv->dev, "failed to send mailbox kick: %d\n", ret);
+
+ mbox_client_txdone(priv->tx_chan, 0);
+}
+
+#if IS_ENABLED(CONFIG_SUNXI_REMOTEPROC_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sunxi_rproc_kick);
+#endif
+
+static int sunxi_rproc_da_to_sys(struct sunxi_rproc *priv, u64 da,
+ size_t len, u64 *sys, bool *is_iomem)
+{
+ const struct sunxi_rproc_cfg *cfg = priv->cfg ? priv->cfg : &sun55i_riscv_cfg;
+ size_t i;
+
+ if (len == 0 || da > U64_MAX - len)
+ return -EINVAL;
+
+ if (cfg->att) {
+ for (i = 0; i < cfg->att_size; i++) {
+ const struct sunxi_rproc_att *att = &cfg->att[i];
+
+ if (da >= att->da && (da + len) <= (att->da + att->size)) {
+ *sys = att->sa + (da - att->da);
+ if (is_iomem)
+ *is_iomem = !!(att->flags & ATT_IOMEM);
+ return 0;
+ }
+ }
+ }
+
+ return -ENOENT;
+}
+
+void *sunxi_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem)
+{
+ struct sunxi_rproc *priv = rproc->priv;
+ u64 sys;
+
+ /*
+ * Reject zero length and any da+len combination that overflows u64.
+ * A crafted ELF with da near U64_MAX could wrap da+len to a small value,
+ * bypassing upper-bound checks and allowing arbitrary kernel memory
+ * to be mapped during firmware loading.
+ */
+ if (len == 0 || da > U64_MAX - len)
+ return NULL;
+
+ /*
+ * 1. Translate core-local device addresses (DA) to system bus
+ * addresses (Host PA) using the SoC address translation table (ATT).
+ */
+ if (sunxi_rproc_da_to_sys(priv, da, len, &sys, is_iomem) == 0) {
+ if (priv->r_sram_va && sys >= priv->r_sram_phys &&
+ (sys + len) <= (priv->r_sram_phys + priv->r_sram_size))
+ return (__force void *)(priv->r_sram_va + (sys - priv->r_sram_phys));
+
+ if (priv->r_sram1_va && sys >= priv->r_sram1_phys &&
+ (sys + len) <= (priv->r_sram1_phys + priv->r_sram1_size))
+ return (__force void *)(priv->r_sram1_va + (sys - priv->r_sram1_phys));
+
+ if (priv->dram_va && sys >= priv->dram_phys &&
+ (sys + len) <= (priv->dram_phys + priv->dram_size))
+ return (__force void *)(priv->dram_va + (sys - priv->dram_phys));
+
+ if (priv->trace_va && sys >= priv->trace_phys &&
+ (sys + len) <= (priv->trace_phys + priv->trace_size))
+ return (__force void *)(priv->trace_va + (sys - priv->trace_phys));
+ }
+
+ /*
+ * 2. Device Tree Memory Regions (Trace buffer, DRAM carveout, or
+ * dynamically-assigned SRAM regions whose host PA is supplied via DT).
+ */
+ if (priv->trace_va && da >= priv->trace_phys &&
+ (da + len) <= (priv->trace_phys + priv->trace_size)) {
+ if (is_iomem)
+ *is_iomem = false;
+ return (__force void *)(priv->trace_va + (da - priv->trace_phys));
+ }
+
+ if (priv->dram_va && da >= priv->dram_phys &&
+ (da + len) <= (priv->dram_phys + priv->dram_size)) {
+ if (is_iomem)
+ *is_iomem = false;
+ return (__force void *)(priv->dram_va + (da - priv->dram_phys));
+ }
+
+ if (priv->r_sram_va && da >= priv->r_sram_phys &&
+ (da + len) <= (priv->r_sram_phys + priv->r_sram_size)) {
+ if (is_iomem)
+ *is_iomem = true;
+ return (__force void *)(priv->r_sram_va + (da - priv->r_sram_phys));
+ }
+
+ if (priv->r_sram1_va && da >= priv->r_sram1_phys &&
+ (da + len) <= (priv->r_sram1_phys + priv->r_sram1_size)) {
+ if (is_iomem)
+ *is_iomem = true;
+ return (__force void *)(priv->r_sram1_va + (da - priv->r_sram1_phys));
+ }
+
+ /*
+ * Return NULL to delegate all DRAM carveouts (vrings, buffers,
+ * code/data placed in DDR) directly to the remoteproc core's
+ * internal carveout table.
+ */
+ return NULL;
+}
+
+#if IS_ENABLED(CONFIG_SUNXI_REMOTEPROC_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sunxi_rproc_da_to_va);
+#endif
+
+static int sunxi_rproc_parse_fw(struct rproc *rproc, const struct firmware *fw)
+{
+ int ret;
+
+ ret = rproc_elf_load_rsc_table(rproc, fw);
+ if (ret == -EINVAL) {
+ dev_dbg(rproc->dev.parent, "no resource table found in ELF\n");
+ return 0;
+ }
+ return ret;
+}
+
+const struct rproc_ops sunxi_rproc_ops = {
+ .prepare = sunxi_rproc_prepare,
+ .unprepare = sunxi_rproc_unprepare,
+ .start = sunxi_rproc_start,
+ .stop = sunxi_rproc_stop,
+ .kick = sunxi_rproc_kick,
+ .da_to_va = sunxi_rproc_da_to_va,
+ .get_boot_addr = rproc_elf_get_boot_addr,
+ .load = rproc_elf_load_segments,
+ .parse_fw = sunxi_rproc_parse_fw,
+ .find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table,
+ .sanity_check = rproc_elf_sanity_check,
+ .coredump = rproc_coredump,
+};
+
+#if IS_ENABLED(CONFIG_SUNXI_REMOTEPROC_KUNIT_TEST)
+EXPORT_SYMBOL_GPL(sunxi_rproc_ops);
+#endif
+
+static int sunxi_rproc_register_mem(struct platform_device *pdev, struct rproc *rproc)
+{
+ struct device *dev = &pdev->dev;
+ struct sunxi_rproc *priv = rproc->priv;
+ struct resource *res;
+
+ /* 1. Map Optional RISC-V CFG Block ("cfg") */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cfg");
+ if (res) {
+ priv->cfg_phys = res->start;
+ priv->cfg_va = devm_ioremap(dev, res->start, resource_size(res));
+ if (!priv->cfg_va)
+ dev_warn(dev, "failed to map 'cfg' registers\n");
+ }
+
+ /* 2. Map Dedicated RISC-V Local SRAM Space 0 ("r_sram" or "sram") */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "r_sram");
+ if (!res)
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram");
+ if (res) {
+ priv->r_sram_phys = res->start;
+ priv->r_sram_size = resource_size(res);
+ priv->r_sram_va = devm_ioremap_wc(dev, res->start, resource_size(res));
+ if (!priv->r_sram_va)
+ return -ENOMEM;
+ }
+
+ /* 3. Map Switchable RISC-V Local SRAM Space 1 ("r_sram1" / SRAMA3_2) - Optional */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "r_sram1");
+ if (res) {
+ priv->r_sram1_phys = res->start;
+ priv->r_sram1_size = resource_size(res);
+ priv->r_sram1_va = devm_ioremap_wc(dev, res->start, resource_size(res));
+ if (!priv->r_sram1_va)
+ dev_warn(dev, "failed to map 'r_sram1' resource\n");
+ }
+
+ /* 4. Map Remap Control Register ("remap" or "sram-for-cpux") - Optional */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "remap");
+ if (!res)
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram-for-cpux");
+ if (res) {
+ priv->remap_phys = res->start;
+ if (resource_size(res) > SUNXI_REMAP_CTRL_OFFSET &&
+ IS_ALIGNED(res->start, PAGE_SIZE)) {
+ void __iomem *base = devm_ioremap(dev, res->start, resource_size(res));
+
+ if (base)
+ priv->remap_va = base + SUNXI_REMAP_CTRL_OFFSET;
+ } else {
+ priv->remap_va = devm_ioremap(dev, res->start, resource_size(res));
+ }
+ if (!priv->remap_va)
+ dev_warn(dev, "failed to map 'remap' register\n");
+ }
+
+ /* 4b. Map Boot DRAM Carveout (Resource "dram" if defined in reg) */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dram");
+ if (res) {
+ priv->dram_phys = res->start;
+ priv->dram_size = resource_size(res);
+ priv->dram_va = devm_memremap(dev, res->start, resource_size(res), MEMREMAP_WB);
+ if (!priv->dram_va)
+ priv->dram_va = (__force void *)devm_ioremap_wc(dev, res->start,
+ resource_size(res));
+ if (!priv->dram_va)
+ dev_warn(dev, "failed to map 'dram' resource\n");
+ }
+
+ dev_info(dev, "Memory resources: r_sram=%s, r_sram1=%s, remap=%s, cfg=%s, dram=%s\n",
+ priv->r_sram_va ? "yes" : "no",
+ priv->r_sram1_va ? "yes" : "no",
+ priv->remap_va ? "yes" : "no",
+ priv->cfg_va ? "yes" : "no",
+ priv->dram_va ? "yes" : "no");
+
+ /* 5. Map Trace Buffer (Resource "trace" if defined in reg) */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "trace");
+ if (res) {
+ priv->trace_phys = res->start;
+ priv->trace_size = resource_size(res);
+ priv->trace_va = devm_memremap(dev, res->start, resource_size(res), MEMREMAP_WB);
+ if (!priv->trace_va)
+ priv->trace_va = (__force void *)devm_ioremap_wc(dev, res->start,
+ resource_size(res));
+ dev_info(dev, "mapped 'trace' mmio resource %pa+%zu\n",
+ &priv->trace_phys, priv->trace_size);
+ }
+
+ return 0;
+}
+
+static int sunxi_rproc_parse_memory_regions(struct rproc *rproc)
+{
+ struct device *dev = rproc->dev.parent;
+ struct device_node *np = dev->of_node;
+ struct sunxi_rproc *priv = rproc->priv;
+ int num_rmems;
+ int i;
+
+ if (!np)
+ return 0;
+
+ num_rmems = of_count_phandle_with_args(np, "memory-region", NULL);
+ if (num_rmems <= 0)
+ return 0;
+
+ /* Bind default DMA pool for dynamic allocations (e.g. vdev0 vrings/buffers) */
+ if (of_reserved_mem_device_init(dev) == 0)
+ priv->has_reserved_mem = true;
+ else
+ dev_dbg(dev, "no dedicated DMA pool assigned from reserved-memory\n");
+
+ /* Register all reserved-memory regions as formal remoteproc carveouts */
+ for (i = 0; i < num_rmems; i++) {
+ struct device_node *rmem_np;
+ struct resource res;
+ const char *name = NULL;
+ struct rproc_mem_entry *mem;
+ void *va;
+
+ rmem_np = of_parse_phandle(np, "memory-region", i);
+ if (!rmem_np)
+ continue;
+
+ if (of_address_to_resource(rmem_np, 0, &res)) {
+ of_node_put(rmem_np);
+ continue;
+ }
+
+ of_property_read_string_index(np, "memory-region-names", i, &name);
+ if (!name)
+ name = rmem_np->name;
+
+ if (name && (strstr(name, "trace") || of_node_name_eq(rmem_np, "trace"))) {
+ priv->trace_phys = res.start;
+ priv->trace_size = resource_size(&res);
+ priv->trace_va = devm_memremap(dev, res.start, resource_size(&res),
+ MEMREMAP_WB);
+ if (!priv->trace_va)
+ priv->trace_va = (__force void *)
+ devm_ioremap_wc(dev, res.start, resource_size(&res));
+ dev_info(dev, "registered trace carveout %pa+%zu (%s)\n",
+ &priv->trace_phys, priv->trace_size, name);
+ } else if (name && (strstr(name, "dram") || strstr(name, "vram"))) {
+ priv->dram_phys = res.start;
+ priv->dram_size = resource_size(&res);
+ priv->dram_va = devm_memremap(dev, res.start, resource_size(&res),
+ MEMREMAP_WB);
+ if (!priv->dram_va)
+ priv->dram_va = (__force void *)
+ devm_ioremap_wc(dev, res.start, resource_size(&res));
+ dev_info(dev, "registered dram carveout %pa+%zu (%s)\n",
+ &priv->dram_phys, priv->dram_size, name);
+ }
+
+ /* Reuse existing SRAM mapping if region overlaps, else ioremap */
+ if (priv->r_sram1_va && res.start == priv->r_sram1_phys)
+ va = (__force void *)priv->r_sram1_va;
+ else if (priv->r_sram_va && res.start == priv->r_sram_phys)
+ va = (__force void *)priv->r_sram_va;
+ else
+ va = (__force void *)devm_ioremap_wc(dev, res.start, resource_size(&res));
+
+ if (va) {
+ mem = rproc_mem_entry_init(dev, va, (dma_addr_t)res.start,
+ resource_size(&res), (u32)res.start,
+ NULL, NULL, "%s", name);
+ if (mem) {
+ mem->is_iomem = true;
+ rproc_add_carveout(rproc, mem);
+ }
+ }
+ of_node_put(rmem_np);
+ }
+
+ return 0;
+}
+
+static int sunxi_rproc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const char *fw_name = "riscv-firmware.elf";
+ struct sunxi_rproc *priv;
+ struct rproc *rproc;
+ int crash_irq;
+ int ret;
+
+ of_property_read_string(dev->of_node, "firmware-name", &fw_name);
+
+ rproc = devm_rproc_alloc(dev, dev_name(dev), &sunxi_rproc_ops,
+ fw_name, sizeof(*priv));
+ if (!rproc) {
+ dev_err(dev, "failed to allocate rproc context\n");
+ return -ENOMEM;
+ }
+
+ ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
+ if (ret) {
+ dev_err(dev, "failed to set 32-bit DMA coherent mask: %d\n", ret);
+ return ret;
+ }
+
+ priv = rproc->priv;
+ priv->rproc = rproc;
+ priv->dev = dev;
+ priv->cfg = of_device_get_match_data(dev);
+ if (!priv->cfg)
+ priv->cfg = &sun55i_riscv_cfg;
+
+ /* 1. Common Clock Framework (CCF) Clocks */
+ priv->clk_parent = devm_clk_get_optional(dev, "parent");
+ if (IS_ERR(priv->clk_parent))
+ return dev_err_probe(dev, PTR_ERR(priv->clk_parent),
+ "failed to get 'parent' clock\n");
+
+ priv->clk_bus = devm_clk_get_optional(dev, "bus");
+ if (IS_ERR(priv->clk_bus))
+ return dev_err_probe(dev, PTR_ERR(priv->clk_bus), "failed to get 'bus' clock\n");
+
+ priv->clk_core = devm_clk_get_optional(dev, "core");
+ if (IS_ERR(priv->clk_core))
+ return dev_err_probe(dev, PTR_ERR(priv->clk_core), "failed to get 'core' clock\n");
+
+ priv->clk_sram = devm_clk_get_optional(dev, "sram");
+ if (IS_ERR(priv->clk_sram))
+ return dev_err_probe(dev, PTR_ERR(priv->clk_sram), "failed to get 'sram' clock\n");
+
+ priv->clk_msgbox = devm_clk_get_optional(dev, "msgbox");
+ if (IS_ERR(priv->clk_msgbox))
+ return dev_err_probe(dev, PTR_ERR(priv->clk_msgbox),
+ "failed to get 'msgbox' clock\n");
+
+ /* 2. Resets */
+ priv->rst_core = devm_reset_control_get_optional_exclusive(dev, "core");
+ if (IS_ERR(priv->rst_core))
+ return dev_err_probe(dev, PTR_ERR(priv->rst_core), "failed to get 'core' reset\n");
+
+ priv->rst_cfg = devm_reset_control_get_optional_exclusive(dev, "cfg");
+ if (IS_ERR(priv->rst_cfg))
+ return dev_err_probe(dev, PTR_ERR(priv->rst_cfg), "failed to get 'cfg' reset\n");
+
+ priv->rst_sram = devm_reset_control_get_optional_exclusive(dev, "sram");
+ if (IS_ERR(priv->rst_sram))
+ return dev_err_probe(dev, PTR_ERR(priv->rst_sram), "failed to get 'sram' reset\n");
+
+ priv->rst_msgbox = devm_reset_control_get_optional_exclusive(dev, "msgbox");
+ if (IS_ERR(priv->rst_msgbox))
+ return dev_err_probe(dev, PTR_ERR(priv->rst_msgbox),
+ "failed to get 'msgbox' reset\n");
+
+ /* 3. Memory Windows (TCM, SRAM, CFG) */
+ ret = sunxi_rproc_register_mem(pdev, rproc);
+ if (ret)
+ return ret;
+
+ /* 4. Dynamic Reserved Memory Carveouts & DMA Pools from Device Tree */
+ ret = sunxi_rproc_parse_memory_regions(rproc);
+ if (ret)
+ return ret;
+
+ /* 5. Optional Hardware Crash Notification IRQ */
+ crash_irq = platform_get_irq_byname_optional(pdev, "crash");
+ if (crash_irq > 0) {
+ ret = devm_request_threaded_irq(dev, crash_irq, NULL,
+ sunxi_rproc_crash_handler,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ "sunxi-rproc-crash",
+ priv);
+ if (ret) {
+ dev_warn(dev, "failed to request crash IRQ %d: %d\n", crash_irq, ret);
+ } else {
+ priv->crash_irq = crash_irq;
+ priv->crash_irq_enabled = false;
+ }
+ }
+
+ /* 6. Mailbox IPC Client */
+ INIT_WORK(&priv->vq_work, sunxi_rproc_vq_work);
+
+ priv->cl.dev = dev;
+ priv->cl.rx_callback = sunxi_rproc_mb_rx_callback;
+ priv->cl.tx_block = false;
+ priv->cl.knows_txdone = true;
+
+ /*
+ * If the hardware mailbox is assigned to userspace (generic-uio) or
+ * lacks #mbox-cells, run RemoteProc in standalone mode.
+ */
+ if (dev->of_node) {
+ struct device_node *mb_node = of_parse_phandle(dev->of_node, "mboxes", 0);
+
+ if (mb_node) {
+ if (of_device_is_compatible(mb_node, "generic-uio") ||
+ !of_property_read_bool(mb_node, "#mbox-cells")) {
+ dev_info(dev, "Mailbox assigned to UIO; running standalone mode\n");
+ of_node_put(mb_node);
+ goto skip_mbox;
+ }
+ of_node_put(mb_node);
+ }
+ }
+
+ priv->tx_chan = mbox_request_channel_byname(&priv->cl, "tx");
+ if (IS_ERR(priv->tx_chan)) {
+ if (PTR_ERR(priv->tx_chan) == -EPROBE_DEFER) {
+ ret = -EPROBE_DEFER;
+ goto err_mem_release;
+ }
+ dev_info(dev, "no tx mailbox channel configured; running standalone mode\n");
+ priv->tx_chan = NULL;
+ }
+
+ if (priv->tx_chan) {
+ priv->rx_chan = mbox_request_channel_byname(&priv->cl, "rx");
+ if (IS_ERR(priv->rx_chan)) {
+ if (PTR_ERR(priv->rx_chan) == -EPROBE_DEFER) {
+ ret = -EPROBE_DEFER;
+ goto err_mbox_release;
+ }
+ dev_info(dev, "no rx mailbox channel configured\n");
+ priv->rx_chan = NULL;
+ }
+ }
+
+skip_mbox:
+ platform_set_drvdata(pdev, rproc);
+
+ ret = rproc_add(rproc);
+ if (ret) {
+ dev_err(dev, "failed to register rproc device: %d\n", ret);
+ goto err_mbox_release;
+ }
+
+ dev_info(dev, "Allwinner %s remoteproc registered (%s)\n",
+ priv->cfg ? priv->cfg->name : "remote", fw_name);
+ return 0;
+
+err_mbox_release:
+ cancel_work_sync(&priv->vq_work);
+ /*
+ * mbox_request_channel_byname() can return ERR_PTR on failure.
+ * Guard with IS_ERR() to avoid calling mbox_free_channel() with
+ * an invalid pointer, which would panic on the first dereference.
+ */
+ if (priv->rx_chan && !IS_ERR(priv->rx_chan))
+ mbox_free_channel(priv->rx_chan);
+ if (priv->tx_chan && !IS_ERR(priv->tx_chan))
+ mbox_free_channel(priv->tx_chan);
+err_mem_release:
+ if (priv->has_reserved_mem)
+ of_reserved_mem_device_release(dev);
+ return ret;
+}
+
+static void sunxi_rproc_remove(struct platform_device *pdev)
+{
+ struct rproc *rproc = platform_get_drvdata(pdev);
+ struct sunxi_rproc *priv = rproc->priv;
+
+ /*
+ * Teardown order is critical:
+ * 1. Disable crash IRQ first so late hardware crash alerts cannot
+ * race against rproc_del() or report crashes on a deleted device.
+ * 2. rproc_del() stops the remote core and tears down VirtIO/vring,
+ * which stops the hardware from generating further mailbox IRQs.
+ * 3. cancel_work_sync() drains any in-flight vq_work. Calling this
+ * before rproc_del() risks a late RX IRQ re-queuing work after
+ * cancel_work_sync() returns, executing on freed priv->rx_chan.
+ * 4. Free mailbox channels only after the workqueue is fully drained.
+ */
+ if (priv->crash_irq > 0 && priv->crash_irq_enabled) {
+ disable_irq(priv->crash_irq);
+ priv->crash_irq_enabled = false;
+ }
+
+ rproc_del(rproc);
+
+ if (priv->rx_chan) {
+ mbox_free_channel(priv->rx_chan);
+ priv->rx_chan = NULL;
+ }
+ if (priv->tx_chan) {
+ mbox_free_channel(priv->tx_chan);
+ priv->tx_chan = NULL;
+ }
+
+ cancel_work_sync(&priv->vq_work);
+
+ if (priv->has_reserved_mem)
+ of_reserved_mem_device_release(&pdev->dev);
+}
+
+static const struct of_device_id sunxi_rproc_of_match[] = {
+ /*
+ * A523, A527, and T527 are the same silicon die (sun55i family).
+ * Use a single compatible string per upstream DT binding policy.
+ */
+ { .compatible = "allwinner,sun55i-a523-rproc", .data = &sun55i_riscv_cfg },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sunxi_rproc_of_match);
+
+static struct platform_driver sunxi_rproc_driver = {
+ .probe = sunxi_rproc_probe,
+ .remove = sunxi_rproc_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = sunxi_rproc_of_match,
+ },
+};
+module_platform_driver(sunxi_rproc_driver);
+
+MODULE_AUTHOR("Allwinner Technology Co., Ltd.");
+MODULE_AUTHOR("Tim Michals <tcmichals@gmail.com>");
+MODULE_DESCRIPTION("Allwinner XuanTie E906/E907 RISC-V Remoteproc Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/remoteproc/sunxi_rproc.h b/drivers/remoteproc/sunxi_rproc.h
new file mode 100644
index 000000000000..5b516b5ac956
--- /dev/null
+++ b/drivers/remoteproc/sunxi_rproc.h
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _SUNXI_RPROC_H_
+#define _SUNXI_RPROC_H_
+
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mailbox_client.h>
+#include <linux/remoteproc.h>
+#include <linux/reset.h>
+
+/* XuanTie E906/E907 core-local view of dedicated SRAM Spaces (Allwinner A523/A527/T527) */
+#define E907_SRAM_C_DA 0x00020000UL
+#define E907_SRAM_SPACE0_DA 0x3ff80000UL
+#define E907_SRAM_SPACE0_DA_ALT 0x3ffc0000UL
+#define E907_SRAM_SPACE1_DA 0x40000000UL
+#define E907_SRAM_SPACE1_DA_ALT 0x40040000UL
+
+/* Allwinner A523/A527/T527 System Bus (Host Physical) Addresses & Window Sizes */
+#define SUN55I_SRAM_SPACE0_SYS 0x07280000UL
+#define SUN55I_SRAM_SPACE0_SIZE 0x00040000UL /* 256 KB */
+#define SUN55I_SRAM_SPACE1_SYS 0x072c0000UL
+#define SUN55I_SRAM_SPACE1_SIZE 0x00040000UL /* 256 KB */
+
+/* Address Translation Table flags */
+#define ATT_IOMEM BIT(30)
+
+struct sunxi_rproc_att {
+ u64 da;
+ u64 sa;
+ size_t size;
+ int flags;
+};
+
+/* XuanTie CFG Block Register Offsets */
+#define E906_CTRL_REG 0x0000
+#define E906_STA_ADD_REG 0x0204
+
+/* Remap Control Register (offset 0x364 in PRCM_R_CCU / MCU_CCU) */
+#define SUNXI_REMAP_CTRL_OFFSET 0x0364
+/* Bit 0: 0 = local RAM for MCU; 1 = share for system */
+#define SUNXI_REMAP_MCU_RAM_BIT BIT(0)
+/* Bit 1: 0 = SRAMA3_2 not shared; 1 = share for MCU_SYS */
+#define SUNXI_REMAP_SRAMA3_2_BIT BIT(1)
+
+struct sunxi_rproc_cfg {
+ const char *name;
+ const struct sunxi_rproc_att *att;
+ size_t att_size;
+ bool has_remap_reg;
+ u32 boot_reg_offset;
+};
+
+extern const struct sunxi_rproc_cfg sun55i_riscv_cfg;
+
+struct sunxi_rproc {
+ struct rproc *rproc;
+ struct device *dev;
+ const struct sunxi_rproc_cfg *cfg;
+
+ /* CCU Clocks & Resets */
+ struct clk *clk_parent;
+ struct clk *clk_bus;
+ struct clk *clk_core;
+ struct clk *clk_sram;
+ struct clk *clk_msgbox;
+ struct reset_control *rst_cfg;
+ struct reset_control *rst_core;
+ struct reset_control *rst_sram;
+ struct reset_control *rst_msgbox;
+
+ /* Hardware Memory Windows (Dedicated SRAM, Switchable SRAM, Remap) */
+ void __iomem *cfg_va;
+ phys_addr_t cfg_phys;
+
+ void __iomem *remap_va;
+ phys_addr_t remap_phys;
+
+ void __iomem *r_sram_va;
+ phys_addr_t r_sram_phys;
+ size_t r_sram_size;
+
+ void __iomem *r_sram1_va;
+ phys_addr_t r_sram1_phys;
+ size_t r_sram1_size;
+
+ void *dram_va;
+ phys_addr_t dram_phys;
+ size_t dram_size;
+
+ /* Trace / DDR Reserved Memory Window */
+ void *trace_va;
+ phys_addr_t trace_phys;
+ size_t trace_size;
+
+ /* Reserved Memory & Mailbox State */
+ bool has_reserved_mem;
+ int crash_irq;
+ bool crash_irq_enabled;
+ struct mbox_client cl;
+ struct mbox_chan *tx_chan;
+ struct mbox_chan *rx_chan;
+ struct work_struct vq_work;
+ u32 kick_msg;
+};
+
+extern const struct rproc_ops sunxi_rproc_ops;
+
+int sunxi_rproc_prepare(struct rproc *rproc);
+int sunxi_rproc_unprepare(struct rproc *rproc);
+int sunxi_rproc_start(struct rproc *rproc);
+int sunxi_rproc_stop(struct rproc *rproc);
+void sunxi_rproc_kick(struct rproc *rproc, int vqid);
+void *sunxi_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem);
+
+#endif /* _SUNXI_RPROC_H_ */
--
2.43.0
next prev parent reply other threads:[~2026-09-27 0:21 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <20260922034711.190253-1-tcmichals@gmail.com>
2026-09-27 0:20 ` [PATCH v2 0/7] remoteproc: sunxi: Add Allwinner XuanTie E907 RemoteProc and Message Box support Tim Michals
2026-09-27 0:20 ` [PATCH v2 1/7] dt-bindings: mailbox: add Allwinner sun55i msgbox schema Tim Michals
2026-09-27 0:20 ` [PATCH v2 2/7] mailbox: sun55i: add Allwinner sun55i/sun60i 4-port Message Box driver Tim Michals
2026-09-27 0:20 ` [PATCH v2 3/7] mailbox: sun55i: add KUnit test suite for sun55i msgbox driver Tim Michals
2026-09-27 0:20 ` [PATCH v2 4/7] dt-bindings: remoteproc: add Allwinner sun55i-rproc schema Tim Michals
2026-09-27 0:20 ` Tim Michals [this message]
2026-09-27 0:20 ` [PATCH v2 6/7] remoteproc: sunxi: add KUnit test suite for sunxi remoteproc driver Tim Michals
2026-09-27 0:20 ` [PATCH v2 7/7] arm64: dts: allwinner: add a523 msgbox and remoteproc nodes Tim Michals
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=20260927002021.797069-6-tcmichals@gmail.com \
--to=tcmichals@gmail.com \
--cc=andersson@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=jassisinghbrar@gmail.com \
--cc=jernej.skrabec@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=linux-sunxi@lists.linux.dev \
--cc=mathieu.poirier@linaro.org \
--cc=robh@kernel.org \
--cc=samuel@sholland.org \
--cc=wens@kernel.org \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®