* [PATCH v2 1/6] dmaengine: arm-dma350: Fix source trigger bit
2026-10-09 5:18 ` [PATCH v2 0/6] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration Jelly Jia
@ 2026-10-09 5:18 ` Jelly Jia
2026-10-09 5:18 ` [PATCH v2 2/6] dmaengine: arm-dma350: Add slave and cyclic transfer support Jelly Jia
` (5 subsequent siblings)
6 siblings, 0 replies; 26+ messages in thread
From: Jelly Jia @ 2026-10-09 5:18 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt, robin.murphy
Cc: devicetree, Frank.Li, cix-kernel-upstream, dmaengine,
linux-arm-kernel, linux-kernel, Jelly.Jia
CH_CTRL_USESRCTRIGIN is the source trigger enable bit in CH_CTRL and
must use bit 25, not bit 26 which is the destination trigger enable bit
(CH_CTRL_USEDESTRIGIN).
The constant has no users yet, but enabling the source trigger input
would silently enable the destination trigger input instead, breaking
any transfer that waits for a peripheral request. Fix it before the
first user arrives.
Fixes: 5d099706449d ("dmaengine: Add Arm DMA-350 driver")
Assisted-by: LLM checkpatch sparse dt_binding_check dtbs_check
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
---
drivers/dma/arm-dma350.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index 09403aca8bb0..4e17130de6c8 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -63,7 +63,7 @@
#define CH_CTRL 0x0c
#define CH_CTRL_USEDESTRIGIN BIT(26)
-#define CH_CTRL_USESRCTRIGIN BIT(26)
+#define CH_CTRL_USESRCTRIGIN BIT(25)
#define CH_CTRL_DONETYPE GENMASK(23, 21)
#define CH_CTRL_REGRELOADTYPE GENMASK(20, 18)
#define CH_CTRL_XTYPE GENMASK(11, 9)
--
2.54.0
^ permalink raw reply [flat|nested] 26+ messages in thread* [PATCH v2 2/6] dmaengine: arm-dma350: Add slave and cyclic transfer support
2026-10-09 5:18 ` [PATCH v2 0/6] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration Jelly Jia
2026-10-09 5:18 ` [PATCH v2 1/6] dmaengine: arm-dma350: Fix source trigger bit Jelly Jia
@ 2026-10-09 5:18 ` Jelly Jia
2026-10-09 5:18 ` [PATCH v2 3/6] dmaengine: arm-dma350: Sync the slave command list before starting Jelly Jia
` (4 subsequent siblings)
6 siblings, 0 replies; 26+ messages in thread
From: Jelly Jia @ 2026-10-09 5:18 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt, robin.murphy
Cc: devicetree, Frank.Li, cix-kernel-upstream, dmaengine,
linux-arm-kernel, linux-kernel, Jelly.Jia
Add DMA slave support to the Arm DMA-350 driver, which so far only
supports memory-to-memory transfers.
Slave transfers are needed to serve peripheral requests: on the CIX
Sky1 SoC, for example, the audio subsystem uses DMA-350 channels to
move PCM data between memory and peripheral FIFOs, which requires both
scatter-gather and cyclic transfers.
Each scatterlist entry becomes one command in a linked list of DMA-350
commands, so a whole slave_sg transfer runs without software
intervention; a cyclic transfer chains the last command back to the
first one, which the controller then executes indefinitely. The trigger
input comes from the DMA specifier and is programmed into CH_TRIGINCFG
with a per-direction block size matching the configured maxburst.
Peripheral addresses are translated through the DMA API and cached per
channel, so repeated transfers to the same FIFO do not remap. The
command list is allocated with kmalloc() and mapped with
dma_map_single() rather than dma_alloc_coherent(), because it is freed
from the virt-dma tasklet which runs with interrupts disabled, and
dma_free_coherent() may only be called with interrupts enabled.
Completion handling is reworked for the new descriptor types: the
handler claims an interrupt only when a channel interrupt is actually
pending, tolerates a spurious interrupt with no active descriptor, and
reports cyclic progress through vchan_cyclic_callback().
Residue reporting walks the command list: the channel link pointer is
read (retrying when the channel steps it between the two halves of the
address) to find the command being executed, and the length of every
command that has not started yet is added to the residue of the current
one.
Assisted-by: LLM checkpatch sparse dt_binding_check dtbs_check
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
---
v1 -> v2:
- Allocate the command list with kmalloc() and map it with
dma_map_single(); it is freed from the virt-dma tasklet, where
dma_free_coherent() may not be called.
- Pick a free trigger-capable channel in .of_xlate() instead of taking
an arbitrary channel and failing when it has no trigger support.
- Pass an explicit "link" flag to the command builders instead of
inferring it from the link address.
- Read the link address registers in a retry loop, since the channel
can step the link pointer between the two reads.
- The parent bus dma-ranges translation moved to the CIX integration
patch, which is its only user.
drivers/dma/arm-dma350.c | 604 +++++++++++++++++++++++++++++++++++++--
1 file changed, 581 insertions(+), 23 deletions(-)
diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index 4e17130de6c8..bbcf4071c3e7 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -3,12 +3,17 @@
// Arm DMA-350 driver
#include <linux/bitfield.h>
+#include <linux/bitops.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/of.h>
+#include <linux/of_dma.h>
#include <linux/module.h>
+#include <linux/overflow.h>
#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
#include "dmaengine.h"
#include "virt-dma.h"
@@ -102,6 +107,10 @@
#define CH_FILLVAL 0x38
#define CH_SRCTRIGINCFG 0x4c
#define CH_DESTRIGINCFG 0x50
+#define CH_TRIGINCFG_BLKSIZE GENMASK(23, 16)
+#define CH_TRIGINCFG_MODE GENMASK(11, 10)
+#define CH_TRIGINCFG_TYPE GENMASK(9, 8)
+#define CH_TRIGINCFG_SEL GENMASK(7, 0)
#define CH_LINKATTR 0x70
#define CH_LINK_SHAREATTR GENMASK(9, 8)
#define CH_LINK_MEMATTR GENMASK(7, 0)
@@ -147,6 +156,7 @@
#define LINK_LINKADDR BIT(30)
#define LINK_LINKADDRHI BIT(31)
+#define D350_SLAVE_CMD_WORDS 14
enum ch_ctrl_donetype {
CH_CTRL_DONETYPE_NONE = 0,
@@ -161,6 +171,18 @@ enum ch_ctrl_xtype {
CH_CTRL_XTYPE_FILL = 3
};
+enum ch_trigincfg_mode {
+ CH_TRIGINCFG_MODE_COMMAND = 0,
+ CH_TRIGINCFG_MODE_DMA_FC = 2,
+ CH_TRIGINCFG_MODE_PERIPH_FC = 3
+};
+
+enum ch_trigincfg_type {
+ CH_TRIGINCFG_TYPE_SW = 0,
+ CH_TRIGINCFG_TYPE_HW = 2,
+ CH_TRIGINCFG_TYPE_INTERNAL = 3
+};
+
enum ch_cfg_shareattr {
SHAREATTR_NSH = 0,
SHAREATTR_OSH = 2,
@@ -178,7 +200,26 @@ struct d350_desc {
u32 command[16];
u16 xsize;
u16 xsizehi;
+ u32 *cmds;
+ dma_addr_t cmds_dma; /* DMA API address of the command list */
+ dma_addr_t cmds_bus; /* DMA350-visible address for CH_LINKADDR */
+ size_t cmds_size;
+ u32 *cmd_len;
+ size_t ncmds;
+ size_t bytes;
+ size_t period_len;
+ size_t periods;
+ size_t period;
u8 tsz;
+ bool cyclic;
+};
+
+struct d350_chan_map {
+ phys_addr_t cpu_addr;
+ dma_addr_t dma_addr;
+ size_t size;
+ enum dma_data_direction dir;
+ bool needs_unmap;
};
struct d350_chan {
@@ -189,10 +230,13 @@ struct d350_chan {
enum dma_status status;
dma_cookie_t cookie;
u32 residue;
+ u32 req;
u8 tsz;
bool has_trig;
bool has_wrap;
bool coherent;
+ struct d350_chan_map map;
+ struct dma_slave_config sconfig;
};
struct d350 {
@@ -212,9 +256,419 @@ static inline struct d350_desc *to_d350_desc(struct virt_dma_desc *vd)
return container_of(vd, struct d350_desc, vd);
}
+static void d350_free_cmds(struct device *dev, struct d350_desc *desc)
+{
+ if (desc->cmds) {
+ dma_unmap_single(dev, desc->cmds_dma, desc->cmds_size,
+ DMA_TO_DEVICE);
+ kfree(desc->cmds);
+ }
+ kfree(desc->cmd_len);
+}
+
static void d350_desc_free(struct virt_dma_desc *vd)
{
- kfree(to_d350_desc(vd));
+ struct d350_desc *desc = to_d350_desc(vd);
+
+ d350_free_cmds(vd->tx.chan->device->dev, desc);
+ kfree(desc);
+}
+
+static int d350_alloc_cmds(struct dma_chan *dchan, struct d350_desc *desc,
+ size_t ncmds)
+{
+ struct device *dev = dchan->device->dev;
+ size_t cmd_size = D350_SLAVE_CMD_WORDS * sizeof(u32);
+
+ if (check_mul_overflow(ncmds, cmd_size, &desc->cmds_size))
+ return -ENOMEM;
+
+ /*
+ * The command list is freed from the virt-dma tasklet, which runs with
+ * interrupts disabled, so it cannot come from dma_alloc_coherent():
+ * dma_free_coherent() may only be called with interrupts enabled.
+ */
+ desc->cmds = kmalloc(desc->cmds_size, GFP_NOWAIT);
+ if (!desc->cmds)
+ return -ENOMEM;
+
+ desc->cmds_dma = dma_map_single(dev, desc->cmds, desc->cmds_size,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(dev, desc->cmds_dma)) {
+ kfree(desc->cmds);
+ desc->cmds = NULL;
+ return -ENOMEM;
+ }
+
+ desc->cmds_bus = desc->cmds_dma;
+ desc->ncmds = ncmds;
+
+ return 0;
+}
+
+static void d350_unmap_resource(struct d350_chan *dch)
+{
+ struct device *dev = dch->vc.chan.device->dev;
+ struct d350_chan_map *map = &dch->map;
+
+ if (map->dir == DMA_NONE)
+ return;
+
+ if (map->needs_unmap)
+ dma_unmap_resource(dev, map->dma_addr, map->size, map->dir, 0);
+
+ map->dir = DMA_NONE;
+ map->needs_unmap = false;
+}
+
+static dma_addr_t d350_map_resource(struct d350_chan *dch,
+ phys_addr_t cpu_addr, size_t size,
+ enum dma_data_direction dir)
+{
+ struct device *dev = dch->vc.chan.device->dev;
+ struct d350_chan_map *map = &dch->map;
+ dma_addr_t dma_addr;
+
+ if (map->dir == dir && map->cpu_addr == cpu_addr &&
+ map->size == size)
+ return map->dma_addr;
+
+ d350_unmap_resource(dch);
+
+ dma_addr = dma_map_resource(dev, cpu_addr, size, dir, 0);
+ if (dma_mapping_error(dev, dma_addr)) {
+ dev_err(dev, "map slave failed ch%u phys=%pa size=%zu dir=%d\n",
+ dch->vc.chan.chan_id, &cpu_addr, size, dir);
+ return DMA_MAPPING_ERROR;
+ }
+ map->needs_unmap = true;
+
+ map->cpu_addr = cpu_addr;
+ map->dma_addr = dma_addr;
+ map->size = size;
+ map->dir = dir;
+
+ return map->dma_addr;
+}
+
+static bool d350_buswidth_supported(u32 widths, enum dma_slave_buswidth width)
+{
+ return width < BITS_PER_TYPE(widths) && (widths & BIT(width));
+}
+
+static int d350_check_slave_config(struct dma_device *dma,
+ struct dma_slave_config *config)
+{
+ u32 maxburst = FIELD_MAX(CH_CFG_MAXBURSTLEN) + 1;
+ u32 widths = dma->src_addr_widths | dma->dst_addr_widths;
+
+ if (!d350_buswidth_supported(widths, config->src_addr_width) ||
+ !d350_buswidth_supported(widths, config->dst_addr_width))
+ return -EINVAL;
+
+ if (config->src_maxburst > maxburst ||
+ config->dst_maxburst > maxburst)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int d350_config(struct dma_chan *chan, struct dma_slave_config *config)
+{
+ struct d350_chan *dch = to_d350_chan(chan);
+ struct dma_device *dma = chan->device;
+ unsigned long flags;
+ int ret;
+
+ ret = d350_check_slave_config(dma, config);
+ if (ret) {
+ dev_err(dma->dev, "invalid slave configuration\n");
+ return ret;
+ }
+
+ spin_lock_irqsave(&dch->vc.lock, flags);
+ if (dch->desc || !list_empty(&dch->vc.desc_allocated) ||
+ !list_empty(&dch->vc.desc_submitted) ||
+ !list_empty(&dch->vc.desc_issued)) {
+ spin_unlock_irqrestore(&dch->vc.lock, flags);
+ return -EBUSY;
+ }
+ spin_unlock_irqrestore(&dch->vc.lock, flags);
+
+ d350_unmap_resource(dch);
+ memcpy(&dch->sconfig, config, sizeof(dch->sconfig));
+
+ return 0;
+}
+
+static struct dma_chan *d350_of_xlate(struct of_phandle_args *dma_spec,
+ struct of_dma *ofdma)
+{
+ struct d350 *dmac = ofdma->of_dma_data;
+ struct d350_chan *dch;
+ struct dma_chan *chan;
+ u32 req;
+ int i;
+
+ if (dma_spec->args_count != 1) {
+ dev_err(dmac->dma.dev, "dma phandle must have one argument\n");
+ return NULL;
+ }
+
+ req = dma_spec->args[0];
+ if (req >= dmac->nreq) {
+ dev_err(dmac->dma.dev, "invalid DMA request %u, have %d\n",
+ req, dmac->nreq);
+ return NULL;
+ }
+
+ for (i = 0; i < dmac->nchan; i++) {
+ dch = &dmac->channels[i];
+ if (!dch->has_trig)
+ continue;
+
+ chan = dma_get_slave_channel(&dch->vc.chan);
+ if (chan) {
+ dch->req = req;
+ return chan;
+ }
+ }
+
+ dev_err(dmac->dma.dev, "no free channel with trigger support\n");
+
+ return NULL;
+}
+
+static u32 d350_device_transcfg(u32 maxburst)
+{
+ return FIELD_PREP(CH_CFG_MAXBURSTLEN, maxburst - 1) |
+ FIELD_PREP(CH_CFG_SHAREATTR, SHAREATTR_OSH) |
+ FIELD_PREP(CH_CFG_MEMATTR, MEMATTR_DEVICE);
+}
+
+static int d350_slave_params(struct d350_chan *dch,
+ enum dma_transfer_direction direction,
+ phys_addr_t *dev_cpu_addr,
+ enum dma_data_direction *dev_dir,
+ enum dma_slave_buswidth *width, u32 *maxburst)
+{
+ struct dma_slave_config *config = &dch->sconfig;
+
+ if (direction == DMA_MEM_TO_DEV) {
+ *dev_cpu_addr = config->dst_addr;
+ *dev_dir = DMA_FROM_DEVICE;
+ *width = config->dst_addr_width;
+ *maxburst = config->dst_maxburst;
+ } else if (direction == DMA_DEV_TO_MEM) {
+ *dev_cpu_addr = config->src_addr;
+ *dev_dir = DMA_TO_DEVICE;
+ *width = config->src_addr_width;
+ *maxburst = config->src_maxburst;
+ } else {
+ return -EINVAL;
+ }
+
+ return *width && *maxburst ? 0 : -EINVAL;
+}
+
+static void d350_fill_slave_cmd(struct d350_chan *dch, struct d350_desc *desc,
+ u32 *cmd, dma_addr_t mem, dma_addr_t dev_dma_addr,
+ size_t len, bool link, dma_addr_t link_addr,
+ enum dma_transfer_direction direction,
+ enum dma_slave_buswidth width, u32 maxburst,
+ enum ch_ctrl_donetype donetype)
+{
+ bool mem_to_dev = direction == DMA_MEM_TO_DEV;
+ u16 xsize, xsizehi;
+ u32 devcfg;
+ u32 memcfg;
+ u32 trigcfg;
+
+ desc->tsz = __ffs(width);
+ xsize = lower_16_bits(len >> desc->tsz);
+ xsizehi = upper_16_bits(len >> desc->tsz);
+ devcfg = d350_device_transcfg(maxburst);
+ memcfg = dch->coherent ? TRANSCFG_WB : TRANSCFG_NC;
+
+ trigcfg = FIELD_PREP(CH_TRIGINCFG_BLKSIZE,
+ mem_to_dev ? maxburst - 1 : 0) |
+ FIELD_PREP(CH_TRIGINCFG_MODE, CH_TRIGINCFG_MODE_PERIPH_FC) |
+ FIELD_PREP(CH_TRIGINCFG_TYPE, CH_TRIGINCFG_TYPE_HW) |
+ FIELD_PREP(CH_TRIGINCFG_SEL, dch->req);
+
+ cmd[0] = LINK_CTRL | LINK_SRCADDR | LINK_SRCADDRHI | LINK_DESADDR |
+ LINK_DESADDRHI | LINK_XSIZE | LINK_XSIZEHI | LINK_SRCTRANSCFG |
+ LINK_DESTRANSCFG | LINK_XADDRINC | LINK_LINKADDR |
+ LINK_LINKADDRHI |
+ (mem_to_dev ? LINK_DESTRIGINCFG : LINK_SRCTRIGINCFG);
+ cmd[1] = (mem_to_dev ? CH_CTRL_USEDESTRIGIN : CH_CTRL_USESRCTRIGIN) |
+ FIELD_PREP(CH_CTRL_TRANSIZE, desc->tsz) |
+ FIELD_PREP(CH_CTRL_XTYPE, CH_CTRL_XTYPE_CONTINUE) |
+ FIELD_PREP(CH_CTRL_DONETYPE, donetype);
+ cmd[2] = lower_32_bits(mem_to_dev ? mem : dev_dma_addr);
+ cmd[3] = upper_32_bits(mem_to_dev ? mem : dev_dma_addr);
+ cmd[4] = lower_32_bits(mem_to_dev ? dev_dma_addr : mem);
+ cmd[5] = upper_32_bits(mem_to_dev ? dev_dma_addr : mem);
+ cmd[6] = FIELD_PREP(CH_XY_SRC, xsize) |
+ FIELD_PREP(CH_XY_DES, xsize);
+ cmd[7] = FIELD_PREP(CH_XY_SRC, xsizehi) |
+ FIELD_PREP(CH_XY_DES, xsizehi);
+ cmd[8] = mem_to_dev ? memcfg : devcfg;
+ cmd[9] = mem_to_dev ? devcfg : memcfg;
+ cmd[10] = mem_to_dev ? FIELD_PREP(CH_XY_SRC, 1) :
+ FIELD_PREP(CH_XY_DES, 1);
+ cmd[11] = trigcfg;
+ cmd[12] = lower_32_bits(link_addr) | (link ? CH_LINKADDR_EN : 0);
+ cmd[13] = upper_32_bits(link_addr);
+}
+
+static struct dma_async_tx_descriptor *
+d350_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl,
+ unsigned int sg_len,
+ enum dma_transfer_direction direction,
+ unsigned long flags, void *context)
+{
+ struct d350_chan *dch = to_d350_chan(dchan);
+ size_t cmd_size = D350_SLAVE_CMD_WORDS * sizeof(u32);
+ enum dma_data_direction dev_dir;
+ enum dma_slave_buswidth width;
+ struct d350_desc *desc;
+ phys_addr_t dev_cpu_addr;
+ dma_addr_t dev_dma_addr, mem;
+ struct scatterlist *sg;
+ u32 maxburst;
+ size_t len;
+ int i;
+
+ if (unlikely(!is_slave_direction(direction) || !sg_len))
+ return NULL;
+
+ if (d350_slave_params(dch, direction, &dev_cpu_addr, &dev_dir, &width,
+ &maxburst))
+ return NULL;
+
+ dev_dma_addr = d350_map_resource(dch, dev_cpu_addr, width, dev_dir);
+ if (dma_mapping_error(dchan->device->dev, dev_dma_addr))
+ return NULL;
+
+ desc = kzalloc_obj(*desc, GFP_NOWAIT);
+ if (!desc)
+ return NULL;
+
+ if (sg_len > 1) {
+ if (d350_alloc_cmds(dchan, desc, sg_len))
+ goto err_free_desc;
+
+ desc->cmd_len = kcalloc(sg_len, sizeof(*desc->cmd_len),
+ GFP_NOWAIT);
+ if (!desc->cmd_len)
+ goto err_free_cmds;
+ }
+
+ for_each_sg(sgl, sg, sg_len, i) {
+ enum ch_ctrl_donetype donetype = CH_CTRL_DONETYPE_CMD;
+ dma_addr_t link_addr = 0;
+ bool link = false;
+ u32 *cmd = desc->command;
+
+ mem = sg_dma_address(sg);
+ len = sg_dma_len(sg);
+ if (!len || (len >> __ffs(width)) > U32_MAX ||
+ !IS_ALIGNED(len | mem | dev_dma_addr, width))
+ goto err_free_cmds;
+
+ if (sg_len > 1) {
+ cmd = desc->cmds + i * D350_SLAVE_CMD_WORDS;
+ if (i < sg_len - 1) {
+ link = true;
+ link_addr = desc->cmds_bus + (i + 1) * cmd_size;
+ donetype = CH_CTRL_DONETYPE_NONE;
+ }
+ desc->cmd_len[i] = len;
+ }
+
+ if (check_add_overflow(desc->bytes, len, &desc->bytes) ||
+ desc->bytes > U32_MAX)
+ goto err_free_cmds;
+
+ d350_fill_slave_cmd(dch, desc, cmd, mem, dev_dma_addr, len,
+ link, link_addr, direction, width, maxburst,
+ donetype);
+ }
+
+ if (sg_len > 1)
+ memcpy(desc->command, desc->cmds, cmd_size);
+
+ return vchan_tx_prep(&dch->vc, &desc->vd, flags);
+
+err_free_cmds:
+ d350_free_cmds(dchan->device->dev, desc);
+err_free_desc:
+ kfree(desc);
+
+ return NULL;
+}
+
+static struct dma_async_tx_descriptor *
+d350_prep_dma_cyclic(struct dma_chan *dchan, dma_addr_t buf_addr,
+ size_t buf_len, size_t period_len,
+ enum dma_transfer_direction direction, unsigned long flags)
+{
+ struct d350_chan *dch = to_d350_chan(dchan);
+ struct d350_desc *desc;
+ phys_addr_t dev_cpu_addr;
+ dma_addr_t dev_dma_addr;
+ enum dma_data_direction dev_dir;
+ enum dma_slave_buswidth width;
+ size_t period, cmd_size;
+ u32 maxburst;
+ int ret;
+
+ if (!buf_len || !period_len || buf_len % period_len ||
+ buf_len > U32_MAX || !is_slave_direction(direction))
+ return NULL;
+
+ ret = d350_slave_params(dch, direction, &dev_cpu_addr, &dev_dir, &width,
+ &maxburst);
+ if (ret)
+ return NULL;
+
+ dev_dma_addr = d350_map_resource(dch, dev_cpu_addr, width, dev_dir);
+ if (dma_mapping_error(dchan->device->dev, dev_dma_addr))
+ return NULL;
+
+ if (!IS_ALIGNED(buf_addr | dev_dma_addr | period_len, width))
+ return NULL;
+
+ desc = kzalloc_obj(*desc, GFP_NOWAIT);
+ if (!desc)
+ return NULL;
+
+ desc->bytes = buf_len;
+ desc->period_len = period_len;
+ desc->periods = buf_len / period_len;
+ desc->cyclic = true;
+
+ if (d350_alloc_cmds(dchan, desc, desc->periods)) {
+ kfree(desc);
+ return NULL;
+ }
+
+ cmd_size = D350_SLAVE_CMD_WORDS * sizeof(u32);
+
+ for (period = 0; period < desc->periods; period++) {
+ u32 *cmd = desc->cmds + period * D350_SLAVE_CMD_WORDS;
+ dma_addr_t mem = buf_addr + period * period_len;
+ dma_addr_t next = desc->cmds_bus +
+ ((period + 1) % desc->periods) * cmd_size;
+
+ d350_fill_slave_cmd(dch, desc, cmd, mem, dev_dma_addr,
+ period_len, true, next, direction, width,
+ maxburst, CH_CTRL_DONETYPE_CMD);
+ }
+ memcpy(desc->command, desc->cmds, cmd_size);
+
+ return vchan_tx_prep(&dch->vc, &desc->vd, flags);
}
static struct dma_async_tx_descriptor *d350_prep_memcpy(struct dma_chan *chan,
@@ -228,6 +682,7 @@ static struct dma_async_tx_descriptor *d350_prep_memcpy(struct dma_chan *chan,
if (!desc)
return NULL;
+ desc->bytes = len;
desc->tsz = __ffs(len | dest | src | (1 << dch->tsz));
desc->xsize = lower_16_bits(len >> desc->tsz);
desc->xsizehi = upper_16_bits(len >> desc->tsz);
@@ -266,6 +721,7 @@ static struct dma_async_tx_descriptor *d350_prep_memset(struct dma_chan *chan,
if (!desc)
return NULL;
+ desc->bytes = len;
desc->tsz = __ffs(len | dest | (1 << dch->tsz));
desc->xsize = lower_16_bits(len >> desc->tsz);
desc->xsizehi = upper_16_bits(len >> desc->tsz);
@@ -339,6 +795,54 @@ static u32 d350_get_residue(struct d350_chan *dch)
return res << dch->desc->tsz;
}
+static u32 d350_get_sg_residue(struct d350_chan *dch)
+{
+ struct d350_desc *desc = dch->desc;
+ size_t cmd_size = D350_SLAVE_CMD_WORDS * sizeof(u32);
+ size_t cmd = 0, i;
+ u32 linkaddr, linkaddrhi, hi_new;
+ u32 residue;
+ u64 next_cmd;
+ int retries = 3; /* 1st time unlucky, 2nd improbable, 3rd just broken */
+
+ if (!desc->cmd_len)
+ return d350_get_residue(dch);
+
+ /*
+ * CH_LINKADDR points at the next command. Match it against the command
+ * array to find the command currently executing, then add every later
+ * command which has not started yet.
+ */
+ /* The channel may step the link pointer between the two reads */
+ hi_new = readl_relaxed(dch->base + CH_LINKADDRHI);
+ do {
+ linkaddrhi = hi_new;
+ linkaddr = readl_relaxed(dch->base + CH_LINKADDR);
+ hi_new = readl_relaxed(dch->base + CH_LINKADDRHI);
+ } while (linkaddrhi != hi_new && --retries);
+
+ next_cmd = (linkaddr & ~CH_LINKADDR_EN) | ((u64)linkaddrhi << 32);
+
+ if (!next_cmd) {
+ cmd = desc->ncmds - 1;
+ } else {
+ for (i = 1; i < desc->ncmds; i++) {
+ if (next_cmd == desc->cmds_bus + i * cmd_size) {
+ cmd = i - 1;
+ break;
+ }
+ }
+ if (i == desc->ncmds)
+ return dch->residue;
+ }
+
+ residue = d350_get_residue(dch);
+ for (i = cmd + 1; i < desc->ncmds; i++)
+ residue += desc->cmd_len[i];
+
+ return residue;
+}
+
static int d350_terminate_all(struct dma_chan *chan)
{
struct d350_chan *dch = to_d350_chan(chan);
@@ -369,7 +873,20 @@ static void d350_synchronize(struct dma_chan *chan)
static u32 d350_desc_bytes(struct d350_desc *desc)
{
- return ((u32)desc->xsizehi << 16 | desc->xsize) << desc->tsz;
+ return desc->bytes;
+}
+
+static u32 d350_get_cyclic_residue(struct d350_desc *desc)
+{
+ return desc->bytes - desc->period * desc->period_len;
+}
+
+static u32 d350_get_active_residue(struct d350_chan *dch)
+{
+ if (dch->desc->cyclic)
+ return d350_get_cyclic_residue(dch->desc);
+
+ return d350_get_sg_residue(dch);
}
static enum dma_status d350_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
@@ -387,7 +904,7 @@ static enum dma_status d350_tx_status(struct dma_chan *chan, dma_cookie_t cookie
if (cookie == dch->cookie) {
status = dch->status;
if (status == DMA_IN_PROGRESS || status == DMA_PAUSED)
- dch->residue = d350_get_residue(dch);
+ dch->residue = d350_get_active_residue(dch);
residue = dch->residue;
} else if ((vd = vchan_find_desc(&dch->vc, cookie))) {
residue = d350_desc_bytes(to_d350_desc(vd));
@@ -469,17 +986,32 @@ static void d350_issue_pending(struct dma_chan *chan)
static irqreturn_t d350_irq(int irq, void *data)
{
struct d350_chan *dch = data;
- struct device *dev = dch->vc.chan.device->dev;
- struct virt_dma_desc *vd = &dch->desc->vd;
+ struct virt_dma_desc *vd;
+ struct d350_desc *desc;
+ u32 residue = 0;
u32 ch_status;
+ u32 irq_status;
+ u32 errinfo = 0;
ch_status = readl(dch->base + CH_STATUS);
- if (!ch_status)
+ irq_status = ch_status & (CH_STAT_INTR_DONE | CH_STAT_INTR_ERR);
+ if (!irq_status)
return IRQ_NONE;
- if (ch_status & CH_STAT_INTR_ERR) {
- u32 errinfo = readl_relaxed(dch->base + CH_ERRINFO);
+ if (irq_status & CH_STAT_INTR_ERR)
+ errinfo = readl_relaxed(dch->base + CH_ERRINFO);
+ writel_relaxed(ch_status, dch->base + CH_STATUS);
+
+ spin_lock(&dch->vc.lock);
+ desc = dch->desc;
+ if (!desc) {
+ spin_unlock(&dch->vc.lock);
+ return IRQ_HANDLED;
+ }
+
+ vd = &desc->vd;
+ if (irq_status & CH_STAT_INTR_ERR) {
if (errinfo & (CH_ERRINFO_AXIRDPOISERR | CH_ERRINFO_AXIRDRESPERR))
vd->tx_result.result = DMA_TRANS_READ_FAILED;
else if (errinfo & CH_ERRINFO_AXIWRRESPERR)
@@ -487,21 +1019,27 @@ static irqreturn_t d350_irq(int irq, void *data)
else
vd->tx_result.result = DMA_TRANS_ABORTED;
- vd->tx_result.residue = d350_get_residue(dch);
- } else if (!(ch_status & CH_STAT_INTR_DONE)) {
- dev_warn(dev, "Unexpected IRQ source? 0x%08x\n", ch_status);
- }
- writel_relaxed(ch_status, dch->base + CH_STATUS);
-
- spin_lock(&dch->vc.lock);
- vchan_cookie_complete(vd);
- if (ch_status & CH_STAT_INTR_DONE) {
- dch->status = DMA_COMPLETE;
- dch->residue = 0;
- d350_start_next(dch);
- } else {
+ residue = d350_get_active_residue(dch);
+ vd->tx_result.residue = residue;
dch->status = DMA_ERROR;
- dch->residue = vd->tx_result.residue;
+ dch->residue = residue;
+ dch->desc = NULL;
+ if (desc->cyclic)
+ vchan_terminate_vdesc(vd);
+ else
+ vchan_cookie_complete(vd);
+ } else {
+ if (desc->cyclic) {
+ desc->period = (desc->period + 1) % desc->periods;
+ dch->residue = d350_get_cyclic_residue(desc);
+ vchan_cyclic_callback(vd);
+ } else {
+ dch->status = DMA_COMPLETE;
+ dch->residue = 0;
+ dch->desc = NULL;
+ vchan_cookie_complete(vd);
+ d350_start_next(dch);
+ }
}
spin_unlock(&dch->vc.lock);
@@ -525,6 +1063,7 @@ static void d350_free_chan_resources(struct dma_chan *chan)
writel_relaxed(0, dch->base + CH_INTREN);
free_irq(dch->irq, dch);
+ d350_unmap_resource(dch);
vchan_free_chan_resources(&dch->vc);
}
@@ -568,23 +1107,32 @@ static int d350_probe(struct platform_device *pdev)
dev_dbg(dev, "DMA-350 r%dp%d with %d channels, %d requests\n", r, p, dmac->nchan, dmac->nreq);
dmac->dma.dev = dev;
+ dmac->dma.src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_UNDEFINED);
+ dmac->dma.dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_UNDEFINED);
for (int i = min(dw, 16); i > 0; i /= 2) {
dmac->dma.src_addr_widths |= BIT(i);
dmac->dma.dst_addr_widths |= BIT(i);
}
- dmac->dma.directions = BIT(DMA_MEM_TO_MEM);
+ dmac->dma.directions = BIT(DMA_MEM_TO_MEM) |
+ BIT(DMA_MEM_TO_DEV) |
+ BIT(DMA_DEV_TO_MEM);
dmac->dma.descriptor_reuse = true;
dmac->dma.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
dmac->dma.device_alloc_chan_resources = d350_alloc_chan_resources;
dmac->dma.device_free_chan_resources = d350_free_chan_resources;
dma_cap_set(DMA_MEMCPY, dmac->dma.cap_mask);
+ dma_cap_set(DMA_SLAVE, dmac->dma.cap_mask);
+ dma_cap_set(DMA_CYCLIC, dmac->dma.cap_mask);
dmac->dma.device_prep_dma_memcpy = d350_prep_memcpy;
+ dmac->dma.device_prep_slave_sg = d350_prep_slave_sg;
+ dmac->dma.device_prep_dma_cyclic = d350_prep_dma_cyclic;
dmac->dma.device_pause = d350_pause;
dmac->dma.device_resume = d350_resume;
dmac->dma.device_terminate_all = d350_terminate_all;
dmac->dma.device_synchronize = d350_synchronize;
dmac->dma.device_tx_status = d350_tx_status;
dmac->dma.device_issue_pending = d350_issue_pending;
+ dmac->dma.device_config = d350_config;
INIT_LIST_HEAD(&dmac->dma.channels);
reg = readl_relaxed(base + DMANSECCTRL + NSEC_CTRL);
@@ -623,6 +1171,8 @@ static int d350_probe(struct platform_device *pdev)
reg |= FIELD_PREP(CH_LINK_MEMATTR, coherent ? MEMATTR_WB : MEMATTR_NC);
writel_relaxed(reg, dch->base + CH_LINKATTR);
+ dch->coherent = coherent;
+ dch->map.dir = DMA_NONE;
dch->vc.desc_free = d350_desc_free;
vchan_init(&dch->vc, &dmac->dma);
}
@@ -638,6 +1188,13 @@ static int d350_probe(struct platform_device *pdev)
if (ret)
return dev_err_probe(dev, ret, "Failed to register DMA device\n");
+ ret = of_dma_controller_register(dev->of_node, d350_of_xlate, dmac);
+ if (ret) {
+ dma_async_device_unregister(&dmac->dma);
+ return dev_err_probe(dev, ret,
+ "Failed to register OF DMA controller\n");
+ }
+
return 0;
}
@@ -645,6 +1202,7 @@ static void d350_remove(struct platform_device *pdev)
{
struct d350 *dmac = platform_get_drvdata(pdev);
+ of_dma_controller_free(pdev->dev.of_node);
dma_async_device_unregister(&dmac->dma);
}
--
2.54.0
^ permalink raw reply [flat|nested] 26+ messages in thread* [PATCH v2 3/6] dmaengine: arm-dma350: Sync the slave command list before starting
2026-10-09 5:18 ` [PATCH v2 0/6] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration Jelly Jia
2026-10-09 5:18 ` [PATCH v2 1/6] dmaengine: arm-dma350: Fix source trigger bit Jelly Jia
2026-10-09 5:18 ` [PATCH v2 2/6] dmaengine: arm-dma350: Add slave and cyclic transfer support Jelly Jia
@ 2026-10-09 5:18 ` Jelly Jia
2026-10-09 5:18 ` [PATCH v2 4/6] dt-bindings: dma: arm,dma-350: Document the CIX Sky1 integration Jelly Jia
` (3 subsequent siblings)
6 siblings, 0 replies; 26+ messages in thread
From: Jelly Jia @ 2026-10-09 5:18 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt, robin.murphy
Cc: devicetree, Frank.Li, cix-kernel-upstream, dmaengine,
linux-arm-kernel, linux-kernel, Jelly.Jia
The slave command list is mapped with dma_map_single(DMA_TO_DEVICE)
before the command words are written into it. For a non-coherent device
this leaves the channel reading stale memory: the direct mapping path
only performs cache maintenance at map time, and a mapping bounced
through the SWIOTLB keeps the copy taken at map time, so writes after
the map never reach the device.
The channel then executes only the first (register-programmed) command
and stops when it follows CH_LINKADDR into the stale list. On Sky1 the
audio playback died after exactly one period (0.34 s at 48 kHz S16
stereo) with "tx data underrun"; the same path is used for multi-entry
slave sg lists.
Sync the command list for the device after filling it in, which also
refreshes the bounce buffer. This is a no-op for a DMA coherent device.
Assisted-by: LLM checkpatch sparse dt_binding_check dtbs_check
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
---
v1 -> v2:
- New patch: the command list is streaming-mapped, so it must be synced
before the channel is started. Without it a non-coherent DMA-350
reads stale command words and the transfer stalls after the first
command.
drivers/dma/arm-dma350.c | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index bbcf4071c3e7..e3fbfcce66e8 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -306,6 +306,21 @@ static int d350_alloc_cmds(struct dma_chan *dchan, struct d350_desc *desc,
return 0;
}
+/*
+ * The command words are written after the command list has been mapped, so
+ * make them visible to the device before the descriptor is submitted. This
+ * also refreshes the bounce buffer when the mapping is bounced through the
+ * SWIOTLB. It is a no-op for a DMA coherent device.
+ */
+static void d350_sync_cmds(struct device *dev, struct d350_desc *desc)
+{
+ if (!desc->cmds)
+ return;
+
+ dma_sync_single_for_device(dev, desc->cmds_bus, desc->cmds_size,
+ DMA_TO_DEVICE);
+}
+
static void d350_unmap_resource(struct d350_chan *dch)
{
struct device *dev = dch->vc.chan.device->dev;
@@ -599,6 +614,8 @@ d350_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl,
if (sg_len > 1)
memcpy(desc->command, desc->cmds, cmd_size);
+ d350_sync_cmds(dchan->device->dev, desc);
+
return vchan_tx_prep(&dch->vc, &desc->vd, flags);
err_free_cmds:
@@ -668,6 +685,8 @@ d350_prep_dma_cyclic(struct dma_chan *dchan, dma_addr_t buf_addr,
}
memcpy(desc->command, desc->cmds, cmd_size);
+ d350_sync_cmds(dchan->device->dev, desc);
+
return vchan_tx_prep(&dch->vc, &desc->vd, flags);
}
--
2.54.0
^ permalink raw reply [flat|nested] 26+ messages in thread* [PATCH v2 4/6] dt-bindings: dma: arm,dma-350: Document the CIX Sky1 integration
2026-10-09 5:18 ` [PATCH v2 0/6] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration Jelly Jia
` (2 preceding siblings ...)
2026-10-09 5:18 ` [PATCH v2 3/6] dmaengine: arm-dma350: Sync the slave command list before starting Jelly Jia
@ 2026-10-09 5:18 ` Jelly Jia
2026-10-09 15:31 ` Conor Dooley
2026-10-12 8:50 ` Krzysztof Kozlowski
2026-10-09 5:18 ` [PATCH v2 5/6] dmaengine: arm-dma350: Add CIX Sky1 integration support Jelly Jia
` (2 subsequent siblings)
6 siblings, 2 replies; 26+ messages in thread
From: Jelly Jia @ 2026-10-09 5:18 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt, robin.murphy
Cc: devicetree, Frank.Li, cix-kernel-upstream, dmaengine,
linux-arm-kernel, linux-kernel, Jelly.Jia
The CIX Sky1 DMA-350 integration is the same device with SoC glue around
it, so it is described by the arm,dma-350 node itself with an additional
"cix,sky1-dma350" compatible rather than by a separate wrapper node.
Extend the binding with the CIX compatible and the resources the
integration needs: a clock, a reset, an optional reserved memory region
used when the controller cannot reach all of system memory, and the
syscon that routes the channel interrupts to the AP interrupt
controller on Sky1. The cix,irq-router property is only valid together
with the CIX compatible.
Assisted-by: LLM checkpatch sparse dt_binding_check dtbs_check
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
---
v1 -> v2:
- Folded the integration into arm,dma-350.yaml: the Sky1 DMA-350 is the
same device, so it keeps the generic compatible plus a device
specific "cix,sky1-dma350" compatible, and the integration resources
are described on the controller node.
- Dropped the separate cix,sky1-dma350.yaml wrapper binding.
.../devicetree/bindings/dma/arm,dma-350.yaml | 33 ++++++++++++++++++-
1 file changed, 32 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
index 429f682f15d8..3b991985c416 100644
--- a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
+++ b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
@@ -11,10 +11,23 @@ maintainers:
allOf:
- $ref: dma-controller.yaml#
+ - if:
+ not:
+ properties:
+ compatible:
+ contains:
+ const: cix,sky1-dma350
+ then:
+ properties:
+ cix,irq-router: false
properties:
compatible:
- const: arm,dma-350
+ oneOf:
+ - const: arm,dma-350
+ - items:
+ - const: cix,sky1-dma350
+ - const: arm,dma-350
reg:
items:
@@ -36,6 +49,24 @@ properties:
const: 1
description: The cell is the trigger input number
+ clocks:
+ maxItems: 1
+
+ resets:
+ maxItems: 1
+
+ memory-region:
+ maxItems: 1
+ description:
+ Reserved memory region used for DMA allocations when the controller
+ cannot reach all of system memory.
+
+ cix,irq-router:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Syscon phandle for the CIX Sky1 subsystem register block that routes
+ DMA-350 channel interrupts to the AP interrupt controller.
+
required:
- compatible
- reg
--
2.54.0
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v2 4/6] dt-bindings: dma: arm,dma-350: Document the CIX Sky1 integration
2026-10-09 5:18 ` [PATCH v2 4/6] dt-bindings: dma: arm,dma-350: Document the CIX Sky1 integration Jelly Jia
@ 2026-10-09 15:31 ` Conor Dooley
2026-10-10 10:23 ` Jelly Jia
2026-10-12 8:50 ` Krzysztof Kozlowski
1 sibling, 1 reply; 26+ messages in thread
From: Conor Dooley @ 2026-10-09 15:31 UTC (permalink / raw)
To: Jelly Jia
Cc: vkoul, robh, krzk+dt, conor+dt, robin.murphy, devicetree,
Frank.Li, cix-kernel-upstream, dmaengine, linux-arm-kernel,
linux-kernel
[-- Attachment #1: Type: text/plain, Size: 2990 bytes --]
On Fri, Oct 09, 2026 at 01:18:44PM +0800, Jelly Jia wrote:
> The CIX Sky1 DMA-350 integration is the same device with SoC glue around
> it, so it is described by the arm,dma-350 node itself with an additional
> "cix,sky1-dma350" compatible rather than by a separate wrapper node.
>
> Extend the binding with the CIX compatible and the resources the
> integration needs: a clock, a reset, an optional reserved memory region
> used when the controller cannot reach all of system memory, and the
> syscon that routes the channel interrupts to the AP interrupt
> controller on Sky1. The cix,irq-router property is only valid together
> with the CIX compatible.
>
> Assisted-by: LLM checkpatch sparse dt_binding_check dtbs_check
> Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
> ---
> v1 -> v2:
> - Folded the integration into arm,dma-350.yaml: the Sky1 DMA-350 is the
> same device, so it keeps the generic compatible plus a device
> specific "cix,sky1-dma350" compatible, and the integration resources
> are described on the controller node.
> - Dropped the separate cix,sky1-dma350.yaml wrapper binding.
> .../devicetree/bindings/dma/arm,dma-350.yaml | 33 ++++++++++++++++++-
> 1 file changed, 32 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> index 429f682f15d8..3b991985c416 100644
> --- a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> +++ b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> @@ -11,10 +11,23 @@ maintainers:
>
> allOf:
> - $ref: dma-controller.yaml#
> + - if:
> + not:
> + properties:
> + compatible:
> + contains:
> + const: cix,sky1-dma350
> + then:
> + properties:
> + cix,irq-router: false
>
> properties:
> compatible:
> - const: arm,dma-350
> + oneOf:
> + - const: arm,dma-350
> + - items:
> + - const: cix,sky1-dma350
> + - const: arm,dma-350
>
> reg:
> items:
> @@ -36,6 +49,24 @@ properties:
> const: 1
> description: The cell is the trigger input number
>
> + clocks:
> + maxItems: 1
> +
> + resets:
> + maxItems: 1
> +
> + memory-region:
> + maxItems: 1
> + description:
> + Reserved memory region used for DMA allocations when the controller
> + cannot reach all of system memory.
> +
> + cix,irq-router:
> + $ref: /schemas/types.yaml#/definitions/phandle
> + description:
> + Syscon phandle for the CIX Sky1 subsystem register block that routes
> + DMA-350 channel interrupts to the AP interrupt controller.
See comments on the driver for more on this, but I'm not convinced that
this should be in this node - especially if this thing controls other
device's routing too.
Thanks,
Conor.
> +
> required:
> - compatible
> - reg
> --
> 2.54.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v2 4/6] dt-bindings: dma: arm,dma-350: Document the CIX Sky1 integration
2026-10-09 15:31 ` Conor Dooley
@ 2026-10-10 10:23 ` Jelly Jia
2026-10-12 18:15 ` Conor Dooley
0 siblings, 1 reply; 26+ messages in thread
From: Jelly Jia @ 2026-10-10 10:23 UTC (permalink / raw)
To: Conor Dooley
Cc: vkoul, robh, krzk+dt, conor+dt, robin.murphy, devicetree,
Frank.Li, cix-kernel-upstream, dmaengine, linux-arm-kernel,
linux-kernel
On Fri, Oct 09, 2026 at 04:31:26PM +0100, Conor Dooley wrote:
> > + cix,irq-router:
> > + $ref: /schemas/types.yaml#/definitions/phandle
> > + description:
> > + Syscon phandle for the CIX Sky1 subsystem register block that routes
> > + DMA-350 channel interrupts to the AP interrupt controller.
>
> See comments on the driver for more on this, but I'm not convinced that
> this should be in this node - especially if this thing controls other
> device's routing too.
>
> Thanks,
> Conor.
Agreed - v3 drops cix,irq-router from the binding completely. We plan
to handle the interrupt gate as firmware/pre-Linux platform setup for
v3, so the DMA node should not describe it. With the property gone, the
CIX-specific conditional in allOf goes away as well, and the binding is
back to the generic controller properties plus the CIX compatible. The
corresponding driver changes are in my reply on the driver patch.
Best regards,
Jelly
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v2 4/6] dt-bindings: dma: arm,dma-350: Document the CIX Sky1 integration
2026-10-10 10:23 ` Jelly Jia
@ 2026-10-12 18:15 ` Conor Dooley
0 siblings, 0 replies; 26+ messages in thread
From: Conor Dooley @ 2026-10-12 18:15 UTC (permalink / raw)
To: Jelly Jia
Cc: vkoul, robh, krzk+dt, conor+dt, robin.murphy, devicetree,
Frank.Li, cix-kernel-upstream, dmaengine, linux-arm-kernel,
linux-kernel
[-- Attachment #1: Type: text/plain, Size: 1441 bytes --]
On Sat, Oct 10, 2026 at 06:23:27PM +0800, Jelly Jia wrote:
> On Fri, Oct 09, 2026 at 04:31:26PM +0100, Conor Dooley wrote:
> > > + cix,irq-router:
> > > + $ref: /schemas/types.yaml#/definitions/phandle
> > > + description:
> > > + Syscon phandle for the CIX Sky1 subsystem register block that routes
> > > + DMA-350 channel interrupts to the AP interrupt controller.
> >
> > See comments on the driver for more on this, but I'm not convinced that
> > this should be in this node - especially if this thing controls other
> > device's routing too.
> >
> > Thanks,
> > Conor.
>
> Agreed - v3 drops cix,irq-router from the binding completely. We plan
> to handle the interrupt gate as firmware/pre-Linux platform setup for
> v3, so the DMA node should not describe it. With the property gone, the
> CIX-specific conditional in allOf goes away as well, and the binding is
> back to the generic controller properties plus the CIX compatible. The
> corresponding driver changes are in my reply on the driver patch.
For correctness, I think you still need to model this by using interrupt-map
in the syscon node, with the syscon as the interrupt parent. If firmware
will program it, you don't need to actually implement anything in a
linux driver (unlike the Microchip and Renesas devices that I linked),
you just need to put the interrupt-map property in the syscon and common
code will do the rest.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v2 4/6] dt-bindings: dma: arm,dma-350: Document the CIX Sky1 integration
2026-10-09 5:18 ` [PATCH v2 4/6] dt-bindings: dma: arm,dma-350: Document the CIX Sky1 integration Jelly Jia
2026-10-09 15:31 ` Conor Dooley
@ 2026-10-12 8:50 ` Krzysztof Kozlowski
1 sibling, 0 replies; 26+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-12 8:50 UTC (permalink / raw)
To: Jelly Jia
Cc: vkoul, robh, krzk+dt, conor+dt, robin.murphy, devicetree,
Frank.Li, cix-kernel-upstream, dmaengine, linux-arm-kernel,
linux-kernel
On Fri, Oct 09, 2026 at 01:18:44PM +0800, Jelly Jia wrote:
> The CIX Sky1 DMA-350 integration is the same device with SoC glue around
> it, so it is described by the arm,dma-350 node itself with an additional
> "cix,sky1-dma350" compatible rather than by a separate wrapper node.
>
> Extend the binding with the CIX compatible and the resources the
> integration needs: a clock, a reset, an optional reserved memory region
> used when the controller cannot reach all of system memory, and the
> syscon that routes the channel interrupts to the AP interrupt
> controller on Sky1. The cix,irq-router property is only valid together
> with the CIX compatible.
>
> Assisted-by: LLM checkpatch sparse dt_binding_check dtbs_check
Only LLM. Drop all other tools, which guidance asked you to put here
build process tools?
How is it even possible to assist here with sparse? And really,
mentioning checkpatch is just wrong. EVERY patch must run through
checkpatch, with no exceptions.
Do not attach (thread) your patchsets to some other threads (unrelated
or older versions). This buries them deep in the mailbox and might
interfere with applying entire sets. See also:
https://elixir.bootlin.com/linux/v6.16-rc2/source/Documentation/process/submitting-patches.rst#L830
> Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
> ---
> v1 -> v2:
> - Folded the integration into arm,dma-350.yaml: the Sky1 DMA-350 is the
> same device, so it keeps the generic compatible plus a device
> specific "cix,sky1-dma350" compatible, and the integration resources
> are described on the controller node.
> - Dropped the separate cix,sky1-dma350.yaml wrapper binding.
> .../devicetree/bindings/dma/arm,dma-350.yaml | 33 ++++++++++++++++++-
> 1 file changed, 32 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> index 429f682f15d8..3b991985c416 100644
> --- a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> +++ b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
> @@ -11,10 +11,23 @@ maintainers:
>
> allOf:
> - $ref: dma-controller.yaml#
> + - if:
> + not:
> + properties:
> + compatible:
> + contains:
> + const: cix,sky1-dma350
> + then:
> + properties:
> + cix,irq-router: false
Entire allOf block goes after required: part.
>
> properties:
> compatible:
> - const: arm,dma-350
> + oneOf:
> + - const: arm,dma-350
> + - items:
> + - const: cix,sky1-dma350
> + - const: arm,dma-350
>
> reg:
> items:
> @@ -36,6 +49,24 @@ properties:
> const: 1
> description: The cell is the trigger input number
>
> + clocks:
> + maxItems: 1
So all devices have clocks and resets?
> +
> + resets:
> + maxItems: 1
> +
> + memory-region:
> + maxItems: 1
and memory-region?
> + description:
> + Reserved memory region used for DMA allocations when the controller
> + cannot reach all of system memory.
> +
> + cix,irq-router:
> + $ref: /schemas/types.yaml#/definitions/phandle
> + description:
> + Syscon phandle for the CIX Sky1 subsystem register block that routes
> + DMA-350 channel interrupts to the AP interrupt controller.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 5/6] dmaengine: arm-dma350: Add CIX Sky1 integration support
2026-10-09 5:18 ` [PATCH v2 0/6] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration Jelly Jia
` (3 preceding siblings ...)
2026-10-09 5:18 ` [PATCH v2 4/6] dt-bindings: dma: arm,dma-350: Document the CIX Sky1 integration Jelly Jia
@ 2026-10-09 5:18 ` Jelly Jia
2026-10-09 15:29 ` Conor Dooley
2026-10-09 5:18 ` [PATCH v2 6/6] arm64: dts: cix: Describe the Sky1 DMA-350 bus integration Jelly Jia
2026-10-12 15:12 ` [PATCH v2 0/6] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration Frank Li
6 siblings, 1 reply; 26+ messages in thread
From: Jelly Jia @ 2026-10-09 5:18 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt, robin.murphy
Cc: devicetree, Frank.Li, cix-kernel-upstream, dmaengine,
linux-arm-kernel, linux-kernel, Jelly.Jia
The CIX Sky1 SoC integrates DMA-350 controllers whose DMA masters see
peripheral resources at different addresses than the CPU, and whose
channel interrupts are gated by a subsystem register block before they
reach the AP interrupt controller.
Add match data for the "cix,sky1-dma350" compatible, describing the
interrupt routing register, and use it to bring the controller up:
enable the clock, reset the controller, and route the channel
interrupts through the register block. Routing is redone after system
resume because that block may lose its state.
The address translation is described in the device tree, not in the
driver: a bus node wrapping the controller carries the dma-ranges of
the integration, the OF core builds dev->dma_range_map from them, and
the DMA API applies the translation to both buffers and resources, so
slave resources are mapped with dma_map_resource() like on any other
device. Integrations without such a bus node keep the identity mapping.
The controller may also be attached to a reserved memory region, for
integrations whose DMA master cannot reach all of system memory.
Assisted-by: LLM checkpatch sparse dt_binding_check dtbs_check
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
---
v1 -> v2:
- Merged the Sky1 integration into arm-dma350.c: the SoC resources are
managed by the DMA-350 driver itself, matched through the
"cix,sky1-dma350" compatible.
- Slave resources are mapped with dma_map_resource() and translated by
the standard DMA API through the dma-ranges of the parent bus node.
- Dropped drivers/dma/cix-sky1-dma350.c and its MAINTAINERS entry.
- ARM_DMA350 now selects MFD_SYSCON for the interrupt router syscon.
drivers/dma/Kconfig | 4 ++
drivers/dma/arm-dma350.c | 92 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 96 insertions(+)
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index ae6a682c9f76..efa6d280bb36 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -97,9 +97,13 @@ config ARM_DMA350
depends on ARM || ARM64 || COMPILE_TEST
select DMA_ENGINE
select DMA_VIRTUAL_CHANNELS
+ select MFD_SYSCON
help
Enable support for the Arm DMA-350 controller.
+ Some integrations need to poke a syscon to route the channel
+ interrupts, so MFD_SYSCON is selected here.
+
config AT_HDMAC
tristate "Atmel AHB DMA support"
depends on ARCH_AT91 || COMPILE_TEST
diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index e3fbfcce66e8..e443a9c3189b 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -4,14 +4,21 @@
#include <linux/bitfield.h>
#include <linux/bitops.h>
+#include <linux/clk.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
+#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/of_dma.h>
+#include <linux/of_reserved_mem.h>
#include <linux/module.h>
#include <linux/overflow.h>
#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
@@ -158,6 +165,9 @@
#define D350_SLAVE_CMD_WORDS 14
+#define SKY1_DMA350_CRU_DMAC_AP_IRQ 0x54
+#define SKY1_DMA350_IRQ_ROUTE_MASK 0xff
+
enum ch_ctrl_donetype {
CH_CTRL_DONETYPE_NONE = 0,
CH_CTRL_DONETYPE_CMD = 1,
@@ -222,6 +232,13 @@ struct d350_chan_map {
bool needs_unmap;
};
+struct d350;
+
+struct d350_data {
+ u32 irq_route_reg;
+ u32 irq_route_mask;
+};
+
struct d350_chan {
struct virt_dma_chan vc;
struct d350_desc *desc;
@@ -241,6 +258,8 @@ struct d350_chan {
struct d350 {
struct dma_device dma;
+ const struct d350_data *data;
+ struct regmap *irq_router;
int nchan;
int nreq;
struct d350_chan channels[] __counted_by(nchan);
@@ -256,6 +275,16 @@ static inline struct d350_desc *to_d350_desc(struct virt_dma_desc *vd)
return container_of(vd, struct d350_desc, vd);
}
+static int d350_route_irqs(struct d350 *dmac)
+{
+ if (!dmac->irq_router)
+ return 0;
+
+ return regmap_update_bits(dmac->irq_router, dmac->data->irq_route_reg,
+ dmac->data->irq_route_mask,
+ dmac->data->irq_route_mask);
+}
+
static void d350_free_cmds(struct device *dev, struct d350_desc *desc)
{
if (desc->cmds) {
@@ -1089,12 +1118,40 @@ static void d350_free_chan_resources(struct dma_chan *chan)
static int d350_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
+ const struct d350_data *data = device_get_match_data(dev);
+ struct reset_control *reset;
+ struct regmap *irq_router = NULL;
+ struct clk *clk;
struct d350 *dmac;
void __iomem *base;
u32 reg;
int ret, nchan, dw, aw, r, p;
bool coherent, memset;
+ clk = devm_clk_get_optional_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk),
+ "failed to enable clock\n");
+
+ reset = devm_reset_control_get_optional_exclusive(dev, NULL);
+ if (IS_ERR(reset))
+ return dev_err_probe(dev, PTR_ERR(reset),
+ "failed to get reset\n");
+
+ if (data && data->irq_route_mask &&
+ of_property_present(dev->of_node, "cix,irq-router")) {
+ irq_router =
+ syscon_regmap_lookup_by_phandle(dev->of_node,
+ "cix,irq-router");
+ if (IS_ERR(irq_router))
+ return dev_err_probe(dev, PTR_ERR(irq_router),
+ "failed to get IRQ router\n");
+ }
+
+ ret = reset_control_reset(reset);
+ if (ret)
+ return ret;
+
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
@@ -1118,8 +1175,14 @@ static int d350_probe(struct platform_device *pdev)
if (!dmac)
return -ENOMEM;
+ dmac->data = data;
+ dmac->irq_router = irq_router;
dmac->nchan = nchan;
+ ret = d350_route_irqs(dmac);
+ if (ret)
+ return ret;
+
reg = readl_relaxed(base + DMAINFO + DMA_BUILDCFG1);
dmac->nreq = FIELD_GET(DMA_CFG_NUM_TRIGGER_IN, reg);
@@ -1201,6 +1264,13 @@ static int d350_probe(struct platform_device *pdev)
dmac->dma.device_prep_dma_memset = d350_prep_memset;
}
+ if (of_property_present(dev->of_node, "memory-region")) {
+ ret = of_reserved_mem_device_init(dev);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to attach reserved memory\n");
+ }
+
platform_set_drvdata(pdev, dmac);
ret = dma_async_device_register(&dmac->dma);
@@ -1223,9 +1293,30 @@ static void d350_remove(struct platform_device *pdev)
of_dma_controller_free(pdev->dev.of_node);
dma_async_device_unregister(&dmac->dma);
+ of_reserved_mem_device_release(&pdev->dev);
+}
+
+static int __maybe_unused d350_resume_noirq(struct device *dev)
+{
+ struct d350 *dmac = dev_get_drvdata(dev);
+
+ if (!dmac)
+ return 0;
+
+ return d350_route_irqs(dmac);
}
+static const struct dev_pm_ops d350_pm = {
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, d350_resume_noirq)
+};
+
+static const struct d350_data d350_sky1_data = {
+ .irq_route_reg = SKY1_DMA350_CRU_DMAC_AP_IRQ,
+ .irq_route_mask = SKY1_DMA350_IRQ_ROUTE_MASK,
+};
+
static const struct of_device_id d350_of_match[] __maybe_unused = {
+ { .compatible = "cix,sky1-dma350", .data = &d350_sky1_data },
{ .compatible = "arm,dma-350" },
{}
};
@@ -1235,6 +1326,7 @@ static struct platform_driver d350_driver = {
.driver = {
.name = "arm-dma350",
.of_match_table = of_match_ptr(d350_of_match),
+ .pm = pm_sleep_ptr(&d350_pm),
},
.probe = d350_probe,
.remove = d350_remove,
--
2.54.0
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v2 5/6] dmaengine: arm-dma350: Add CIX Sky1 integration support
2026-10-09 5:18 ` [PATCH v2 5/6] dmaengine: arm-dma350: Add CIX Sky1 integration support Jelly Jia
@ 2026-10-09 15:29 ` Conor Dooley
2026-10-10 10:22 ` Jelly Jia
0 siblings, 1 reply; 26+ messages in thread
From: Conor Dooley @ 2026-10-09 15:29 UTC (permalink / raw)
To: Jelly Jia
Cc: vkoul, robh, krzk+dt, conor+dt, robin.murphy, devicetree,
Frank.Li, cix-kernel-upstream, dmaengine, linux-arm-kernel,
linux-kernel
[-- Attachment #1: Type: text/plain, Size: 9014 bytes --]
On Fri, Oct 09, 2026 at 01:18:45PM +0800, Jelly Jia wrote:
> The CIX Sky1 SoC integrates DMA-350 controllers whose DMA masters see
> peripheral resources at different addresses than the CPU, and whose
> channel interrupts are gated by a subsystem register block before they
> reach the AP interrupt controller.
>
> Add match data for the "cix,sky1-dma350" compatible, describing the
> interrupt routing register, and use it to bring the controller up:
> enable the clock, reset the controller, and route the channel
> interrupts through the register block. Routing is redone after system
> resume because that block may lose its state.
>
> The address translation is described in the device tree, not in the
> driver: a bus node wrapping the controller carries the dma-ranges of
> the integration, the OF core builds dev->dma_range_map from them, and
> the DMA API applies the translation to both buffers and resources, so
> slave resources are mapped with dma_map_resource() like on any other
> device. Integrations without such a bus node keep the identity mapping.
>
> The controller may also be attached to a reserved memory region, for
> integrations whose DMA master cannot reach all of system memory.
>
> Assisted-by: LLM checkpatch sparse dt_binding_check dtbs_check
> Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
> ---
> v1 -> v2:
> - Merged the Sky1 integration into arm-dma350.c: the SoC resources are
> managed by the DMA-350 driver itself, matched through the
> "cix,sky1-dma350" compatible.
> - Slave resources are mapped with dma_map_resource() and translated by
> the standard DMA API through the dma-ranges of the parent bus node.
> - Dropped drivers/dma/cix-sky1-dma350.c and its MAINTAINERS entry.
> - ARM_DMA350 now selects MFD_SYSCON for the interrupt router syscon.
> drivers/dma/Kconfig | 4 ++
> drivers/dma/arm-dma350.c | 92 ++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 96 insertions(+)
>
> diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
> index ae6a682c9f76..efa6d280bb36 100644
> --- a/drivers/dma/Kconfig
> +++ b/drivers/dma/Kconfig
> @@ -97,9 +97,13 @@ config ARM_DMA350
> depends on ARM || ARM64 || COMPILE_TEST
> select DMA_ENGINE
> select DMA_VIRTUAL_CHANNELS
> + select MFD_SYSCON
> help
> Enable support for the Arm DMA-350 controller.
>
> + Some integrations need to poke a syscon to route the channel
> + interrupts, so MFD_SYSCON is selected here.
> +
> config AT_HDMAC
> tristate "Atmel AHB DMA support"
> depends on ARCH_AT91 || COMPILE_TEST
> diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
> index e3fbfcce66e8..e443a9c3189b 100644
> --- a/drivers/dma/arm-dma350.c
> +++ b/drivers/dma/arm-dma350.c
> @@ -4,14 +4,21 @@
>
> #include <linux/bitfield.h>
> #include <linux/bitops.h>
> +#include <linux/clk.h>
> #include <linux/dmaengine.h>
> #include <linux/dma-mapping.h>
> #include <linux/io.h>
> +#include <linux/mfd/syscon.h>
> #include <linux/of.h>
> #include <linux/of_dma.h>
> +#include <linux/of_reserved_mem.h>
> #include <linux/module.h>
> #include <linux/overflow.h>
> #include <linux/platform_device.h>
> +#include <linux/pm.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/reset.h>
> #include <linux/scatterlist.h>
> #include <linux/slab.h>
>
> @@ -158,6 +165,9 @@
>
> #define D350_SLAVE_CMD_WORDS 14
>
> +#define SKY1_DMA350_CRU_DMAC_AP_IRQ 0x54
> +#define SKY1_DMA350_IRQ_ROUTE_MASK 0xff
> +
> enum ch_ctrl_donetype {
> CH_CTRL_DONETYPE_NONE = 0,
> CH_CTRL_DONETYPE_CMD = 1,
> @@ -222,6 +232,13 @@ struct d350_chan_map {
> bool needs_unmap;
> };
>
> +struct d350;
> +
> +struct d350_data {
> + u32 irq_route_reg;
> + u32 irq_route_mask;
Since this is a fixed value, there's little point having it as part of
your match data. Ditto the reg value, for the same reason. The user is
called directly from your probe code, so you can use the defines
directly.
That said, should you actually be doing this from here? It feels like
a bit of a problem with abstractions where you're fiddling with another
device. Syscon stuff is okay when you're changing something internal to
your device's functionality or something "downstream" of your device -
between you and the output pins. But this is something "upstream" of our
device and I dunno if this is the right way to do it. If you need to set
some sort of interrupt routing, should you actually do something like
microchip,mpfs-irqmux or renesas,rzn1-gpioirqmux and preserve the
abstraction/separation between devices?
Cheers,
Conor.
> +};
> +
> struct d350_chan {
> struct virt_dma_chan vc;
> struct d350_desc *desc;
> @@ -241,6 +258,8 @@ struct d350_chan {
>
> struct d350 {
> struct dma_device dma;
> + const struct d350_data *data;
> + struct regmap *irq_router;
> int nchan;
> int nreq;
> struct d350_chan channels[] __counted_by(nchan);
> @@ -256,6 +275,16 @@ static inline struct d350_desc *to_d350_desc(struct virt_dma_desc *vd)
> return container_of(vd, struct d350_desc, vd);
> }
>
> +static int d350_route_irqs(struct d350 *dmac)
> +{
> + if (!dmac->irq_router)
> + return 0;
> +
> + return regmap_update_bits(dmac->irq_router, dmac->data->irq_route_reg,
> + dmac->data->irq_route_mask,
> + dmac->data->irq_route_mask);
> +}
> +
> static void d350_free_cmds(struct device *dev, struct d350_desc *desc)
> {
> if (desc->cmds) {
> @@ -1089,12 +1118,40 @@ static void d350_free_chan_resources(struct dma_chan *chan)
> static int d350_probe(struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
> + const struct d350_data *data = device_get_match_data(dev);
> + struct reset_control *reset;
> + struct regmap *irq_router = NULL;
> + struct clk *clk;
> struct d350 *dmac;
> void __iomem *base;
> u32 reg;
> int ret, nchan, dw, aw, r, p;
> bool coherent, memset;
>
> + clk = devm_clk_get_optional_enabled(dev, NULL);
> + if (IS_ERR(clk))
> + return dev_err_probe(dev, PTR_ERR(clk),
> + "failed to enable clock\n");
> +
> + reset = devm_reset_control_get_optional_exclusive(dev, NULL);
> + if (IS_ERR(reset))
> + return dev_err_probe(dev, PTR_ERR(reset),
> + "failed to get reset\n");
> +
> + if (data && data->irq_route_mask &&
> + of_property_present(dev->of_node, "cix,irq-router")) {
> + irq_router =
> + syscon_regmap_lookup_by_phandle(dev->of_node,
> + "cix,irq-router");
> + if (IS_ERR(irq_router))
> + return dev_err_probe(dev, PTR_ERR(irq_router),
> + "failed to get IRQ router\n");
> + }
> +
> + ret = reset_control_reset(reset);
> + if (ret)
> + return ret;
> +
> base = devm_platform_ioremap_resource(pdev, 0);
> if (IS_ERR(base))
> return PTR_ERR(base);
> @@ -1118,8 +1175,14 @@ static int d350_probe(struct platform_device *pdev)
> if (!dmac)
> return -ENOMEM;
>
> + dmac->data = data;
> + dmac->irq_router = irq_router;
> dmac->nchan = nchan;
>
> + ret = d350_route_irqs(dmac);
> + if (ret)
> + return ret;
> +
> reg = readl_relaxed(base + DMAINFO + DMA_BUILDCFG1);
> dmac->nreq = FIELD_GET(DMA_CFG_NUM_TRIGGER_IN, reg);
>
> @@ -1201,6 +1264,13 @@ static int d350_probe(struct platform_device *pdev)
> dmac->dma.device_prep_dma_memset = d350_prep_memset;
> }
>
> + if (of_property_present(dev->of_node, "memory-region")) {
> + ret = of_reserved_mem_device_init(dev);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "failed to attach reserved memory\n");
> + }
> +
> platform_set_drvdata(pdev, dmac);
>
> ret = dma_async_device_register(&dmac->dma);
> @@ -1223,9 +1293,30 @@ static void d350_remove(struct platform_device *pdev)
>
> of_dma_controller_free(pdev->dev.of_node);
> dma_async_device_unregister(&dmac->dma);
> + of_reserved_mem_device_release(&pdev->dev);
> +}
> +
> +static int __maybe_unused d350_resume_noirq(struct device *dev)
> +{
> + struct d350 *dmac = dev_get_drvdata(dev);
> +
> + if (!dmac)
> + return 0;
> +
> + return d350_route_irqs(dmac);
> }
>
> +static const struct dev_pm_ops d350_pm = {
> + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, d350_resume_noirq)
> +};
> +
> +static const struct d350_data d350_sky1_data = {
> + .irq_route_reg = SKY1_DMA350_CRU_DMAC_AP_IRQ,
> + .irq_route_mask = SKY1_DMA350_IRQ_ROUTE_MASK,
> +};
> +
> static const struct of_device_id d350_of_match[] __maybe_unused = {
> + { .compatible = "cix,sky1-dma350", .data = &d350_sky1_data },
> { .compatible = "arm,dma-350" },
> {}
> };
> @@ -1235,6 +1326,7 @@ static struct platform_driver d350_driver = {
> .driver = {
> .name = "arm-dma350",
> .of_match_table = of_match_ptr(d350_of_match),
> + .pm = pm_sleep_ptr(&d350_pm),
> },
> .probe = d350_probe,
> .remove = d350_remove,
> --
> 2.54.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v2 5/6] dmaengine: arm-dma350: Add CIX Sky1 integration support
2026-10-09 15:29 ` Conor Dooley
@ 2026-10-10 10:22 ` Jelly Jia
0 siblings, 0 replies; 26+ messages in thread
From: Jelly Jia @ 2026-10-10 10:22 UTC (permalink / raw)
To: Conor Dooley
Cc: vkoul, robh, krzk+dt, conor+dt, robin.murphy, devicetree,
Frank.Li, cix-kernel-upstream, dmaengine, linux-arm-kernel,
linux-kernel
On Fri, Oct 09, 2026 at 04:29:57PM +0100, Conor Dooley wrote:
> struct d350_data {
> u32 irq_route_reg;
> u32 irq_route_mask;
>
> Since this is a fixed value, there's little point having it as part of
> your match data. Ditto the reg value, for the same reason. The user is
> called directly from your probe code, so you can use the defines
> directly.
>
> That said, should you actually be doing this from here? It feels like
> a bit of a problem with abstractions where you're fiddling with another
> device. Syscon stuff is okay when you're changing something internal to
> your device's functionality or something "downstream" of your device -
> between you and the output pins. But this is something "upstream" of our
> device and I dunno if this is the right way to do it. If you need to set
> some sort of interrupt routing, should you actually do something like
> microchip,mpfs-irqmux or renesas,rzn1-gpioirqmux and preserve the
> abstraction/separation between devices?
>
> Cheers,
> Conor.
Agreed on both points. We plan to handle the interrupt gate as
firmware/pre-Linux platform setup for v3, so the upstream DMA driver
will not program it. The v3 I am preparing removes all of this from the
upstream DMA driver:
- the cix,irq-router syscon lookup and the routing write in probe
- the d350_data match data with the route register and mask
- the d350_route_irqs() helper and the resume_noirq hook that re-applied
the routing after system resume
- the MFD_SYSCON select that this pulled in
What remains in the driver is only the integration of the DMA controller
itself: the clock and the reset, plus the address translation that is
described by the dma-ranges of the parent bus node and applied through
the DMA API. The driver no longer reaches into anything outside its own
node.
Best regards,
Jelly
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 6/6] arm64: dts: cix: Describe the Sky1 DMA-350 bus integration
2026-10-09 5:18 ` [PATCH v2 0/6] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration Jelly Jia
` (4 preceding siblings ...)
2026-10-09 5:18 ` [PATCH v2 5/6] dmaengine: arm-dma350: Add CIX Sky1 integration support Jelly Jia
@ 2026-10-09 5:18 ` Jelly Jia
2026-10-12 15:12 ` [PATCH v2 0/6] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration Frank Li
6 siblings, 0 replies; 26+ messages in thread
From: Jelly Jia @ 2026-10-09 5:18 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt, robin.murphy
Cc: devicetree, Frank.Li, cix-kernel-upstream, dmaengine,
linux-arm-kernel, linux-kernel, Jelly.Jia
The FCH DMA-350 controller sits behind an interconnect window, so the
addresses its DMA master uses differ from the CPU physical addresses.
Describe the integration as a bus node carrying the dma-ranges of that
window, with the controller itself as a child, and give the controller
the SoC compatible and the clock feeding it.
The AUDSS DMA-350 controller is described the same way, with its clock,
reset and the AUDSS CRU syscon that gates the channel interrupts before
they reach the AP interrupt controller. It has no consumer in the SoC
dtsi yet: the audio controllers that use it are not described upstream.
Assisted-by: LLM checkpatch sparse dt_binding_check dtbs_check
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
---
v1 -> v2:
- The DMA-350 nodes are no longer wrappers with the generic controller
as a child: each Sky1 DMA-350 is a single device node with the
"cix,sky1-dma350", "arm,dma-350" compatible, its real MMIO region and
the integration resources (clock, reset, interrupt router) that v1 kept
on the wrapper. The integration and the block are one device, and the
MMIO is the same as the generic node it replaces.
- The FCH and AUDSS DMA-350 masters sit behind SoC interconnect windows
in a static DMA address domain: the AUDSS masters see the audio
register and FIFO block at 0x20000000 and the DRAM window at
0x30000000, which the CPU sees at 0x07010000 and 0xc0000000, and the
FCH masters see the 0x040b0000 window at 0x040a0000. Their DMA view of
the address space is not the CPU physical view.
- The windows are static hardware configuration, set up before Linux
starts by the boot firmware / secure world: they are not programmable
from Linux and must not be reconfigured by it. There is nothing for a
Linux interconnect provider to manage here, so the translation cannot
be modeled as one; it is a fixed property of the bus.
- It is described the standard devicetree way instead: a generic
"simple-bus" parent with an identity "ranges;" and the "dma-ranges" of
the window. This is not the rejected v1 wrapper and not a separate DMA
wrapper/integration device: the parent has no device-specific or
vendor wrapper compatible, only the generic "simple-bus" compatible,
no reg, no clocks, no resets, no driver and no private address
translation. Its only job is to carry the static translation, so it is
only a generic bus/address-domain node.
- The "dma-ranges" have to sit on that parent for the OF core to build
dev->dma_range_map: of_dma_configure_id() starts the lookup at the
device's parent (bus_np = __of_get_dma_parent(np) in
drivers/of/device.c), so a "dma-ranges" on the controller node itself
would be ignored. The translation is applied by the DMA API, not by
driver code: the driver maps slave resources with dma_map_resource()
and contains no address parser.
- The same shape is used in mainline: the Agilex5 DMA bus
(dma-bus@10db0000, a simple-bus with dma-ranges whose controller
carries "altr,agilex5-axi-dma", "snps,axi-dma-1.01a") and the Marvell
AC5 buses (bus@80500000 with "ranges;" and "dma-ranges", and
behind-32bit-controller@7f000000, whose only purpose is the DMA
mapping of the devices below it).
arch/arm64/boot/dts/cix/sky1.dtsi | 66 +++++++++++++++++++++++++------
1 file changed, 54 insertions(+), 12 deletions(-)
diff --git a/arch/arm64/boot/dts/cix/sky1.dtsi b/arch/arm64/boot/dts/cix/sky1.dtsi
index 0a820f45feb9..e3bd6c0e383f 100644
--- a/arch/arm64/boot/dts/cix/sky1.dtsi
+++ b/arch/arm64/boot/dts/cix/sky1.dtsi
@@ -518,18 +518,29 @@ iomuxc: pinctrl@4170000 {
reg = <0x0 0x04170000 0x0 0x1000>;
};
- fch_dmac: dma-controller@4190000 {
- compatible = "arm,dma-350";
- reg = <0x0 0x4190000 0x0 0x10000>;
- interrupts = <GIC_SPI 303 IRQ_TYPE_LEVEL_HIGH 0>,
- <GIC_SPI 303 IRQ_TYPE_LEVEL_HIGH 0>,
- <GIC_SPI 303 IRQ_TYPE_LEVEL_HIGH 0>,
- <GIC_SPI 303 IRQ_TYPE_LEVEL_HIGH 0>,
- <GIC_SPI 303 IRQ_TYPE_LEVEL_HIGH 0>,
- <GIC_SPI 303 IRQ_TYPE_LEVEL_HIGH 0>,
- <GIC_SPI 303 IRQ_TYPE_LEVEL_HIGH 0>,
- <GIC_SPI 303 IRQ_TYPE_LEVEL_HIGH 0>;
- #dma-cells = <1>;
+ bus@4190000 {
+ compatible = "simple-bus";
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+ dma-ranges = <0x0 0x040a0000 0x0 0x040b0000 0x0 0x00040000>,
+ <0x0 0x80000000 0x0 0x80000000 0x8 0x00000000>,
+ <0x80 0x00000000 0x80 0x00000000 0x80 0x00000000>;
+
+ fch_dmac: dma-controller@4190000 {
+ compatible = "cix,sky1-dma350", "arm,dma-350";
+ reg = <0x0 0x04190000 0x0 0x10000>;
+ clocks = <&scmi_clk CLK_TREE_FCH_DMA_ACLK>;
+ interrupts = <GIC_SPI 303 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 303 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 303 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 303 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 303 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 303 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 303 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 303 IRQ_TYPE_LEVEL_HIGH 0>;
+ #dma-cells = <1>;
+ };
};
mbox_ap2se: mailbox@5060000 {
@@ -576,6 +587,37 @@ mbox_pm2ap: mailbox@65a0080 {
cix,mbox-dir = "rx";
};
+ bus@7010000 {
+ compatible = "simple-bus";
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+ dma-ranges = <0x0 0x20000000 0x0 0x07010000 0x0 0x00100000>,
+ <0x0 0x30000000 0x0 0xc0000000 0x0 0x20000000>;
+
+ audss_dmac: dma-controller@7010000 {
+ compatible = "cix,sky1-dma350", "arm,dma-350";
+ reg = <0x0 0x07010000 0x0 0x10000>;
+ clocks = <&audss_cru CLK_DMAC_AXI>;
+ resets = <&audss_cru AUDSS_DMAC_SW_RST>;
+ /*
+ * The AUDSS CRU gates each DMAC channel interrupt
+ * before it reaches the GIC. AUDSS_DMAC_INFO_AP_IRQ
+ * is at CRU offset 0x54.
+ */
+ cix,irq-router = <&audss_cru>;
+ interrupts = <GIC_SPI 230 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 230 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 230 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 230 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 230 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 230 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 230 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 230 IRQ_TYPE_LEVEL_HIGH 0>;
+ #dma-cells = <1>;
+ };
+ };
+
audss_cru: clock-controller@7110000 {
compatible = "cix,sky1-audss-cru";
reg = <0x0 0x07110000 0x0 0x10000>;
--
2.54.0
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v2 0/6] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration
2026-10-09 5:18 ` [PATCH v2 0/6] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration Jelly Jia
` (5 preceding siblings ...)
2026-10-09 5:18 ` [PATCH v2 6/6] arm64: dts: cix: Describe the Sky1 DMA-350 bus integration Jelly Jia
@ 2026-10-12 15:12 ` Frank Li
6 siblings, 0 replies; 26+ messages in thread
From: Frank Li @ 2026-10-12 15:12 UTC (permalink / raw)
To: Jelly Jia
Cc: vkoul, robh, krzk+dt, conor+dt, robin.murphy, devicetree,
Frank.Li, cix-kernel-upstream, dmaengine, linux-arm-kernel,
linux-kernel
On Fri, Oct 09, 2026 at 01:18:40PM +0800, Jelly Jia wrote:
> Hello,
>
Please avoid post new version patch set to old thread, which will mess up
patchworks and other tools.
Frank
^ permalink raw reply [flat|nested] 26+ messages in thread