* [PATCH v1 0/5] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration
@ 2026-09-07 3:33 Jelly Jia
2026-09-07 3:34 ` [PATCH v1 1/5] dmaengine: arm-dma350: Fix source trigger bit Jelly Jia
` (4 more replies)
0 siblings, 5 replies; 9+ messages in thread
From: Jelly Jia @ 2026-09-07 3:33 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt
Cc: devicetree, Frank.Li, robin.murphy, cix-kernel-upstream,
dmaengine, linux-arm-kernel, linux-kernel, Jelly Jia
Hello,
This series adds slave transfer support to the Arm DMA-350 driver and
the CIX Sky1 SoC integration around it.
Patch 1 fixes the CH_CTRL_USESRCTRIGIN definition: the source trigger
enable bit is bit 25, not bit 26 (which is the destination trigger
enable bit).
Patch 2 adds slave transfer support to the Arm DMA-350 driver:
scatter-gather and cyclic preparation, command-list allocation,
per-channel resource mapping including address translation through the
parent bus dma-ranges, and residue reporting. Slave transfers are
needed to serve peripheral requests, for example the audio FIFOs of
the CIX Sky1 audio subsystem.
Patch 3 documents the CIX Sky1 DMA-350 integration binding: a wrapper
node owning the SoC resources (clocks, resets, interrupt routing via
a syscon phandle, optional reserved memory) with the generic Arm
DMA-350 controller as its child.
Patch 4 adds the matching integration driver, which manages those
resources and populates the child controller.
Patch 5 adds the two Sky1 instances (FCH and AUDSS) to the SoC
devicetree.
The series is based on v7.3-rc1.
Testing:
- Built for arm64 with GCC 12.3 as both built-in and module, with no
new warnings; sparse clean.
- checkpatch clean; dt_binding_check and dtbs_check pass for the new
binding and nodes.
- Runtime-tested on CIX Sky1 boards with the FCH and AUDSS DMA-350
instances.
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
Jelly Jia (5):
dmaengine: arm-dma350: Fix source trigger bit
dmaengine: arm-dma350: Add slave transfer support
dt-bindings: dma: Add CIX Sky1 DMA-350 integration
dmaengine: cix-sky1-dma350: Add Sky1 integration driver
arm64: dts: cix: Add Sky1 DMA-350 nodes
.../bindings/dma/cix,sky1-dma350.yaml | 102 +++
MAINTAINERS | 2 +
arch/arm64/boot/dts/cix/sky1.dtsi | 61 +-
drivers/dma/Kconfig | 15 +
drivers/dma/Makefile | 1 +
drivers/dma/arm-dma350.c | 642 +++++++++++++++++-
drivers/dma/cix-sky1-dma350.c | 206 ++++++
7 files changed, 993 insertions(+), 36 deletions(-)
create mode 100644 Documentation/devicetree/bindings/dma/cix,sky1-dma350.yaml
create mode 100644 drivers/dma/cix-sky1-dma350.c
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
--
2.54.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v1 1/5] dmaengine: arm-dma350: Fix source trigger bit
2026-09-07 3:33 [PATCH v1 0/5] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration Jelly Jia
@ 2026-09-07 3:34 ` Jelly Jia
2026-09-07 3:34 ` [PATCH v1 2/5] dmaengine: arm-dma350: Add slave transfer support Jelly Jia
` (3 subsequent siblings)
4 siblings, 0 replies; 9+ messages in thread
From: Jelly Jia @ 2026-09-07 3:34 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt
Cc: devicetree, Frank.Li, robin.murphy, 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")
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] 9+ messages in thread
* [PATCH v1 2/5] dmaengine: arm-dma350: Add slave transfer support
2026-09-07 3:33 [PATCH v1 0/5] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration Jelly Jia
2026-09-07 3:34 ` [PATCH v1 1/5] dmaengine: arm-dma350: Fix source trigger bit Jelly Jia
@ 2026-09-07 3:34 ` Jelly Jia
2026-09-07 3:34 ` [PATCH v1 3/5] dt-bindings: dma: Add CIX Sky1 DMA-350 integration Jelly Jia
` (2 subsequent siblings)
4 siblings, 0 replies; 9+ messages in thread
From: Jelly Jia @ 2026-09-07 3:34 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt
Cc: devicetree, Frank.Li, robin.murphy, 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.
This adds scatter-gather and cyclic preparation, command-list
allocation, per-channel resource mapping, and residue reporting.
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
---
drivers/dma/arm-dma350.c | 640 +++++++++++++++++++++++++++++++++++++--
1 file changed, 617 insertions(+), 23 deletions(-)
diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index 4e17130de6c8..85e8c5e42804 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -3,12 +3,19 @@
// 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_address.h>
+#include <linux/of_dma.h>
#include <linux/module.h>
+#include <linux/overflow.h>
#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
#include "dmaengine.h"
#include "virt-dma.h"
@@ -102,6 +109,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 +158,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 +173,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 +202,26 @@ struct d350_desc {
u32 command[16];
u16 xsize;
u16 xsizehi;
+ u32 *cmds;
+ dma_addr_t cmds_dma; /* DMA API address from dma_alloc_coherent() */
+ 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 +232,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 +258,462 @@ 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_free_coherent(dev, desc->cmds_size, desc->cmds,
+ desc->cmds_dma);
+ 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)
+{
+ size_t cmd_size = D350_SLAVE_CMD_WORDS * sizeof(u32);
+
+ if (check_mul_overflow(ncmds, cmd_size, &desc->cmds_size))
+ return -ENOMEM;
+
+ desc->cmds = dma_alloc_coherent(dchan->device->dev, desc->cmds_size,
+ &desc->cmds_dma, GFP_NOWAIT);
+ if (!desc->cmds)
+ 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;
+}
+
+/*
+ * Translate a CPU physical/resource address to the address visible to DMA350
+ * using the dma-ranges property of its parent bus. This is needed for internal
+ * interconnect windows where the DMA master sees slave peripherals at
+ * different addresses from the CPU.
+ */
+static int d350_xlate_parent_dma_range(struct device *dev, phys_addr_t phys,
+ size_t size, dma_addr_t *dma)
+{
+ struct device_node *parent;
+ struct of_range_parser parser;
+ struct of_range range;
+ int ret = -ENOENT;
+
+ parent = of_get_parent(dev->of_node);
+ if (!parent)
+ return -ENOENT;
+
+ if (of_pci_dma_range_parser_init(&parser, parent))
+ goto out_put;
+
+ for_each_of_range(&parser, &range) {
+ u64 offset;
+
+ if (phys < range.cpu_addr)
+ continue;
+
+ offset = phys - range.cpu_addr;
+ if (offset >= range.size)
+ continue;
+
+ if (size > range.size - offset)
+ continue;
+
+ *dma = range.bus_addr + offset;
+ ret = 0;
+ break;
+ }
+
+out_put:
+ of_node_put(parent);
+ return ret;
+}
+
+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;
+ int ret;
+
+ if (map->dir == dir && map->cpu_addr == cpu_addr &&
+ map->size == size)
+ return map->dma_addr;
+
+ d350_unmap_resource(dch);
+
+ if (!device_iommu_mapped(dev)) {
+ ret = d350_xlate_parent_dma_range(dev, cpu_addr, size,
+ &dma_addr);
+ if (!ret)
+ goto done;
+
+ if (ret != -ENOENT) {
+ dev_err(dev, "translate resource failed ch%u phys=%pa size=%zu\n",
+ dch->vc.chan.chan_id, &cpu_addr, size);
+ return DMA_MAPPING_ERROR;
+ }
+ }
+
+ 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;
+
+done:
+ 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 dma_chan *chan;
+ struct d350_chan *dch;
+ u32 req;
+
+ 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;
+ }
+
+ chan = dma_get_any_slave_channel(&dmac->dma);
+ if (!chan) {
+ dev_err(dmac->dma.dev, "can't get a dma channel\n");
+ return NULL;
+ }
+
+ dch = to_d350_chan(chan);
+ if (!dch->has_trig) {
+ dev_err(dmac->dma.dev, "channel %d has no trigger support\n",
+ chan->chan_id);
+ dma_release_channel(chan);
+ return NULL;
+ }
+ dch->req = req;
+
+ return chan;
+}
+
+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, 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_addr ? 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;
+ 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_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_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, 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 +727,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 +766,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 +840,45 @@ 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 residue;
+ u64 next_cmd;
+
+ 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.
+ */
+ next_cmd = readl_relaxed(dch->base + CH_LINKADDR) & ~CH_LINKADDR_EN;
+ next_cmd |= (u64)readl_relaxed(dch->base + CH_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 +909,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 +940,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 +1022,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 +1055,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 +1099,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 +1143,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 +1207,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 +1224,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 +1238,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] 9+ messages in thread
* [PATCH v1 3/5] dt-bindings: dma: Add CIX Sky1 DMA-350 integration
2026-09-07 3:33 [PATCH v1 0/5] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration Jelly Jia
2026-09-07 3:34 ` [PATCH v1 1/5] dmaengine: arm-dma350: Fix source trigger bit Jelly Jia
2026-09-07 3:34 ` [PATCH v1 2/5] dmaengine: arm-dma350: Add slave transfer support Jelly Jia
@ 2026-09-07 3:34 ` Jelly Jia
2026-09-07 17:15 ` Conor Dooley
2026-09-07 3:34 ` [PATCH v1 4/5] dmaengine: cix-sky1-dma350: Add Sky1 integration driver Jelly Jia
2026-09-07 3:34 ` [PATCH v1 5/5] arm64: dts: cix: Add Sky1 DMA-350 nodes Jelly Jia
4 siblings, 1 reply; 9+ messages in thread
From: Jelly Jia @ 2026-09-07 3:34 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt
Cc: devicetree, Frank.Li, robin.murphy, cix-kernel-upstream,
dmaengine, linux-arm-kernel, linux-kernel, Jelly Jia
Document the CIX Sky1 integration wrapper around an Arm DMA-350 controller.
The wrapper owns SoC integration resources. The child node describes the
generic Arm DMA-350 IP.
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
---
.../bindings/dma/cix,sky1-dma350.yaml | 102 ++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 103 insertions(+)
create mode 100644 Documentation/devicetree/bindings/dma/cix,sky1-dma350.yaml
diff --git a/Documentation/devicetree/bindings/dma/cix,sky1-dma350.yaml b/Documentation/devicetree/bindings/dma/cix,sky1-dma350.yaml
new file mode 100644
index 000000000000..e533f355d135
--- /dev/null
+++ b/Documentation/devicetree/bindings/dma/cix,sky1-dma350.yaml
@@ -0,0 +1,102 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/dma/cix,sky1-dma350.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: CIX Sky1 DMA-350 Integration
+
+maintainers:
+ - CIX Kernel Team <cix-kernel-upstream@cixtech.com>
+
+description:
+ The CIX Sky1 DMA-350 integration driver owns SoC resources around an
+ Arm DMA-350 controller, such as clocks, resets, interrupt routing, and
+ optional reserved memory. Reserved memory is described on this parent node
+ and assigned by the integration driver to the child DMA-350 device. The child
+ node describes the generic DMA-350 IP.
+
+properties:
+ $nodename:
+ pattern: "^dma@[0-9a-f]+$"
+
+ compatible:
+ const: cix,sky1-dma350
+
+ "#address-cells":
+ const: 2
+
+ "#size-cells":
+ const: 2
+
+ ranges: true
+
+ dma-ranges: true
+
+ clocks:
+ maxItems: 1
+
+ resets:
+ maxItems: 1
+
+ power-domains:
+ maxItems: 1
+
+ cix,irq-router:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Syscon phandle for the Sky1 subsystem register block that routes
+ DMA-350 channel interrupts to the AP interrupt controller.
+
+ memory-region:
+ maxItems: 1
+ description:
+ Reserved memory pool assigned by the CIX Sky1 DMA-350 integration driver
+ to the child Arm DMA-350 device.
+
+patternProperties:
+ '^dma-controller@[0-9a-f]+$':
+ $ref: arm,dma-350.yaml#
+
+required:
+ - compatible
+ - "#address-cells"
+ - "#size-cells"
+ - ranges
+ - dma-ranges
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ / {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ interrupt-parent = <&gic>;
+
+ gic: interrupt-controller {
+ #address-cells = <0>;
+ interrupt-controller;
+ #interrupt-cells = <4>;
+ };
+
+ dma@7010000 {
+ compatible = "cix,sky1-dma350";
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges = <0x0 0x0 0x0 0x07010000 0x0 0x10000>;
+ dma-ranges = <0x0 0x20000000 0x0 0x07010000 0x0 0x00100000>;
+ clocks = <&clk 9>;
+ resets = <&rst 15>;
+ cix,irq-router = <&audss_cru>;
+
+ dma-controller@0 {
+ compatible = "arm,dma-350";
+ reg = <0x0 0x0 0x0 0x10000>;
+ interrupts = <GIC_SPI 230 IRQ_TYPE_LEVEL_HIGH 0>;
+ #dma-cells = <1>;
+ };
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 3a19da74d00c..feff8f9ecb2b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2825,6 +2825,7 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
S: Maintained
T: git https://github.com/cixtech/linux-mainline.git
F: Documentation/devicetree/bindings/arm/cix.yaml
+F: Documentation/devicetree/bindings/dma/cix,sky1-dma350.yaml
F: Documentation/devicetree/bindings/mailbox/cix,sky1-mbox.yaml
F: arch/arm64/boot/dts/cix/
F: drivers/mailbox/cix-mailbox.c
--
2.54.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v1 4/5] dmaengine: cix-sky1-dma350: Add Sky1 integration driver
2026-09-07 3:33 [PATCH v1 0/5] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration Jelly Jia
` (2 preceding siblings ...)
2026-09-07 3:34 ` [PATCH v1 3/5] dt-bindings: dma: Add CIX Sky1 DMA-350 integration Jelly Jia
@ 2026-09-07 3:34 ` Jelly Jia
2026-09-07 3:34 ` [PATCH v1 5/5] arm64: dts: cix: Add Sky1 DMA-350 nodes Jelly Jia
4 siblings, 0 replies; 9+ messages in thread
From: Jelly Jia @ 2026-09-07 3:34 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt
Cc: devicetree, Frank.Li, robin.murphy, cix-kernel-upstream,
dmaengine, linux-arm-kernel, linux-kernel, Jelly Jia
Add a CIX Sky1 integration driver for Arm DMA-350 instances.
The driver manages clocks, reset, optional interrupt routing, optional
reserved memory attachment, and populates the child Arm DMA-350
controller.
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
---
MAINTAINERS | 1 +
drivers/dma/Kconfig | 15 +++
drivers/dma/Makefile | 1 +
drivers/dma/cix-sky1-dma350.c | 206 ++++++++++++++++++++++++++++++++++
4 files changed, 223 insertions(+)
create mode 100644 drivers/dma/cix-sky1-dma350.c
diff --git a/MAINTAINERS b/MAINTAINERS
index feff8f9ecb2b..8d972fe2c994 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2828,6 +2828,7 @@ F: Documentation/devicetree/bindings/arm/cix.yaml
F: Documentation/devicetree/bindings/dma/cix,sky1-dma350.yaml
F: Documentation/devicetree/bindings/mailbox/cix,sky1-mbox.yaml
F: arch/arm64/boot/dts/cix/
+F: drivers/dma/cix-sky1-dma350.c
F: drivers/mailbox/cix-mailbox.c
K: \bcix\b
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index ae6a682c9f76..51c881dfa6d3 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -147,6 +147,21 @@ config DMA_BCM2835
select DMA_ENGINE
select DMA_VIRTUAL_CHANNELS
+config CIX_SKY1_DMA350
+ tristate "CIX Sky1 DMA-350 integration support"
+ depends on OF && ARM_DMA350
+ depends on OF_RESERVED_MEM
+ depends on ARCH_CIX || COMPILE_TEST
+ default ARCH_CIX
+ select MFD_SYSCON
+ help
+ Enable support for the CIX Sky1 integration around Arm
+ DMA-350 controllers.
+
+ This driver owns SoC integration resources such as clocks, resets,
+ reserved memory attachment, and interrupt routing registers, while the
+ child controller is handled by the generic Arm DMA-350 driver.
+
config DMA_JZ4780
tristate "JZ4780 DMA support"
depends on MIPS || COMPILE_TEST
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index 14aa086629d5..a69964251f68 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_AT_XDMAC) += at_xdmac.o
obj-$(CONFIG_AXI_DMAC) += dma-axi-dmac.o
obj-$(CONFIG_BCM_SBA_RAID) += bcm-sba-raid.o
obj-$(CONFIG_DMA_BCM2835) += bcm2835-dma.o
+obj-$(CONFIG_CIX_SKY1_DMA350) += cix-sky1-dma350.o
obj-$(CONFIG_DMA_JZ4780) += dma-jz4780.o
obj-$(CONFIG_DMA_SA11X0) += sa11x0-dma.o
obj-$(CONFIG_DMA_SUN4I) += sun4i-dma.o
diff --git a/drivers/dma/cix-sky1-dma350.c b/drivers/dma/cix-sky1-dma350.c
new file mode 100644
index 000000000000..5904453f3481
--- /dev/null
+++ b/drivers/dma/cix-sky1-dma350.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CIX Sky1 DMA-350 integration driver
+ */
+
+#include <linux/clk.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#define SKY1_DMA350_CRU_DMAC_AP_IRQ 0x54
+#define SKY1_DMA350_IRQ_ROUTE_MASK 0xff
+
+struct cix_sky1_dma350 {
+ struct clk_bulk_data *clks;
+ struct reset_control *reset;
+ struct regmap *irq_router;
+ struct device *rmem_dev;
+ int num_clks;
+};
+
+static int cix_sky1_dma350_route_irqs(struct device *dev)
+{
+ struct cix_sky1_dma350 *data = dev_get_drvdata(dev);
+
+ if (!data->irq_router)
+ return 0;
+
+ return regmap_update_bits(data->irq_router,
+ SKY1_DMA350_CRU_DMAC_AP_IRQ,
+ SKY1_DMA350_IRQ_ROUTE_MASK,
+ SKY1_DMA350_IRQ_ROUTE_MASK);
+}
+
+static int cix_sky1_dma350_enable_resources(struct device *dev)
+{
+ struct cix_sky1_dma350 *data = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_bulk_prepare_enable(data->num_clks, data->clks);
+ if (ret)
+ return ret;
+
+ ret = reset_control_reset(data->reset);
+ if (ret)
+ goto err_disable_clks;
+
+ ret = cix_sky1_dma350_route_irqs(dev);
+ if (ret)
+ goto err_disable_clks;
+
+ return 0;
+
+err_disable_clks:
+ clk_bulk_disable_unprepare(data->num_clks, data->clks);
+ return ret;
+}
+
+static void cix_sky1_dma350_disable_resources(struct device *dev)
+{
+ struct cix_sky1_dma350 *data = dev_get_drvdata(dev);
+
+ reset_control_assert(data->reset);
+ clk_bulk_disable_unprepare(data->num_clks, data->clks);
+}
+
+static int cix_sky1_dma350_attach_reserved_mem(struct device *dev)
+{
+ struct cix_sky1_dma350 *data = dev_get_drvdata(dev);
+ struct platform_device *child_pdev;
+ struct device_node *child_np;
+ int ret;
+
+ if (!of_property_present(dev->of_node, "memory-region"))
+ return 0;
+
+ child_np = of_get_compatible_child(dev->of_node, "arm,dma-350");
+ if (!child_np)
+ return -ENODEV;
+
+ child_pdev = of_find_device_by_node(child_np);
+ of_node_put(child_np);
+ if (!child_pdev)
+ return -EPROBE_DEFER;
+
+ /*
+ * Reserved memory is attached after the child has probed. This relies
+ * on arm-dma350 not allocating coherent command buffers in probe;
+ * those allocations happen per descriptor at prep time.
+ */
+ ret = of_reserved_mem_device_init_by_idx(&child_pdev->dev,
+ dev->of_node, 0);
+ if (ret) {
+ put_device(&child_pdev->dev);
+ return ret;
+ }
+
+ data->rmem_dev = &child_pdev->dev;
+
+ return 0;
+}
+
+static void cix_sky1_dma350_release_reserved_mem(struct device *dev)
+{
+ struct cix_sky1_dma350 *data = dev_get_drvdata(dev);
+
+ if (data->rmem_dev) {
+ of_reserved_mem_device_release(data->rmem_dev);
+ put_device(data->rmem_dev);
+ data->rmem_dev = NULL;
+ }
+}
+
+static int cix_sky1_dma350_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cix_sky1_dma350 *data;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, data);
+
+ data->num_clks = devm_clk_bulk_get_all(dev, &data->clks);
+ if (data->num_clks < 0)
+ return dev_err_probe(dev, data->num_clks,
+ "failed to get clocks\n");
+
+ data->reset = devm_reset_control_get_optional_exclusive(dev, NULL);
+ if (IS_ERR(data->reset))
+ return dev_err_probe(dev, PTR_ERR(data->reset),
+ "failed to get reset\n");
+
+ data->irq_router = syscon_regmap_lookup_by_phandle_optional(dev->of_node,
+ "cix,irq-router");
+ if (IS_ERR(data->irq_router))
+ return dev_err_probe(dev, PTR_ERR(data->irq_router),
+ "failed to get IRQ router\n");
+
+ ret = cix_sky1_dma350_enable_resources(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable resources\n");
+
+ ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
+ if (ret)
+ goto err_disable_resources;
+
+ ret = cix_sky1_dma350_attach_reserved_mem(dev);
+ if (ret)
+ goto err_depopulate;
+
+ return 0;
+
+err_depopulate:
+ of_platform_depopulate(dev);
+err_disable_resources:
+ cix_sky1_dma350_disable_resources(dev);
+ return dev_err_probe(dev, ret, "failed to initialize child devices\n");
+}
+
+static void cix_sky1_dma350_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ of_platform_depopulate(dev);
+ cix_sky1_dma350_release_reserved_mem(dev);
+ cix_sky1_dma350_disable_resources(dev);
+}
+
+static int __maybe_unused cix_sky1_dma350_resume_noirq(struct device *dev)
+{
+ return cix_sky1_dma350_route_irqs(dev);
+}
+
+static const struct dev_pm_ops cix_sky1_dma350_pm = {
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, cix_sky1_dma350_resume_noirq)
+};
+
+static const struct of_device_id cix_sky1_dma350_of_match[] = {
+ { .compatible = "cix,sky1-dma350" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, cix_sky1_dma350_of_match);
+
+static struct platform_driver cix_sky1_dma350_driver = {
+ .probe = cix_sky1_dma350_probe,
+ .remove = cix_sky1_dma350_remove,
+ .driver = {
+ .name = "cix-sky1-dma350",
+ .of_match_table = cix_sky1_dma350_of_match,
+ .pm = pm_sleep_ptr(&cix_sky1_dma350_pm),
+ },
+};
+module_platform_driver(cix_sky1_dma350_driver);
+
+MODULE_AUTHOR("Jelly Jia <Jelly.Jia@cixtech.com>");
+MODULE_DESCRIPTION("CIX Sky1 DMA-350 integration driver");
+MODULE_LICENSE("GPL");
--
2.54.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v1 5/5] arm64: dts: cix: Add Sky1 DMA-350 nodes
2026-09-07 3:33 [PATCH v1 0/5] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration Jelly Jia
` (3 preceding siblings ...)
2026-09-07 3:34 ` [PATCH v1 4/5] dmaengine: cix-sky1-dma350: Add Sky1 integration driver Jelly Jia
@ 2026-09-07 3:34 ` Jelly Jia
4 siblings, 0 replies; 9+ messages in thread
From: Jelly Jia @ 2026-09-07 3:34 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt
Cc: devicetree, Frank.Li, robin.murphy, cix-kernel-upstream,
dmaengine, linux-arm-kernel, linux-kernel, Jelly Jia
Add CIX Sky1 DMA-350 integration nodes with child Arm DMA-350 controllers.
The nodes describe the FCH and AUDSS DMA instances.
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
---
arch/arm64/boot/dts/cix/sky1.dtsi | 61 +++++++++++++++++++++++++------
1 file changed, 49 insertions(+), 12 deletions(-)
diff --git a/arch/arm64/boot/dts/cix/sky1.dtsi b/arch/arm64/boot/dts/cix/sky1.dtsi
index 0a820f45feb9..7a5e463dc7c5 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>;
+ cix_fch_dmac: dma@4190000 {
+ compatible = "cix,sky1-dma350";
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges = <0x0 0x0 0x0 0x4190000 0x0 0x10000>;
+ dma-ranges = <0x0 0x040a0000 0x0 0x040b0000 0x0 0x00040000>,
+ <0x0 0x80000000 0x0 0x80000000 0x8 0x00000000>,
+ <0x80 0x00000000 0x80 0x00000000 0x80 0x00000000>;
+ clocks = <&scmi_clk CLK_TREE_FCH_DMA_ACLK>;
+
+ fch_dmac: dma-controller@0 {
+ compatible = "arm,dma-350";
+ reg = <0x0 0x0 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>;
+ };
};
mbox_ap2se: mailbox@5060000 {
@@ -576,6 +587,32 @@ mbox_pm2ap: mailbox@65a0080 {
cix,mbox-dir = "rx";
};
+ cix_audss_dmac: dma@7010000 {
+ compatible = "cix,sky1-dma350";
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges = <0x0 0x0 0x0 0x07010000 0x0 0x10000>;
+ dma-ranges = <0x0 0x20000000 0x0 0x07010000 0x0 0x00100000>,
+ <0x0 0x30000000 0x0 0xc0000000 0x0 0x20000000>;
+ clocks = <&audss_cru CLK_DMAC_AXI>;
+ resets = <&audss_cru AUDSS_DMAC_SW_RST>;
+ cix,irq-router = <&audss_cru>;
+
+ audss_dmac: dma-controller@0 {
+ compatible = "arm,dma-350";
+ reg = <0x0 0x0 0x0 0x10000>;
+ 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] 9+ messages in thread
* Re: [PATCH v1 3/5] dt-bindings: dma: Add CIX Sky1 DMA-350 integration
2026-09-07 3:34 ` [PATCH v1 3/5] dt-bindings: dma: Add CIX Sky1 DMA-350 integration Jelly Jia
@ 2026-09-07 17:15 ` Conor Dooley
2026-09-09 6:05 ` Jelly Jia
0 siblings, 1 reply; 9+ messages in thread
From: Conor Dooley @ 2026-09-07 17:15 UTC (permalink / raw)
To: Jelly Jia
Cc: vkoul, robh, krzk+dt, conor+dt, devicetree, Frank.Li,
robin.murphy, cix-kernel-upstream, dmaengine, linux-arm-kernel,
linux-kernel
[-- Attachment #1: Type: text/plain, Size: 4692 bytes --]
On Mon, Sep 07, 2026 at 11:34:02AM +0800, Jelly Jia wrote:
> Document the CIX Sky1 integration wrapper around an Arm DMA-350 controller.
>
> The wrapper owns SoC integration resources. The child node describes the
> generic Arm DMA-350 IP.
>
> Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
> ---
> .../bindings/dma/cix,sky1-dma350.yaml | 102 ++++++++++++++++++
> MAINTAINERS | 1 +
> 2 files changed, 103 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/dma/cix,sky1-dma350.yaml
>
> diff --git a/Documentation/devicetree/bindings/dma/cix,sky1-dma350.yaml b/Documentation/devicetree/bindings/dma/cix,sky1-dma350.yaml
> new file mode 100644
> index 000000000000..e533f355d135
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/dma/cix,sky1-dma350.yaml
> @@ -0,0 +1,102 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/dma/cix,sky1-dma350.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: CIX Sky1 DMA-350 Integration
> +
> +maintainers:
> + - CIX Kernel Team <cix-kernel-upstream@cixtech.com>
> +
> +description:
> + The CIX Sky1 DMA-350 integration driver owns SoC resources around an
> + Arm DMA-350 controller, such as clocks, resets, interrupt routing, and
> + optional reserved memory. Reserved memory is described on this parent node
> + and assigned by the integration driver to the child DMA-350 device. The child
> + node describes the generic DMA-350 IP.
I cannot really speak to whether this is a correct thing to do with a
dma-350, but it seems to me like something that should be resolved with
a device specific comaptible in the dma-350 node. Someone more familar
with the IP will have to comment on that.
However I would like to know how this impacts the existing dma-350 in he
sky1 devicetree.
> +
> +properties:
> + $nodename:
> + pattern: "^dma@[0-9a-f]+$"
> +
> + compatible:
> + const: cix,sky1-dma350
> +
> + "#address-cells":
> + const: 2
> +
> + "#size-cells":
> + const: 2
> +
> + ranges: true
> +
> + dma-ranges: true
> +
> + clocks:
> + maxItems: 1
> +
> + resets:
> + maxItems: 1
> +
> + power-domains:
> + maxItems: 1
> +
> + cix,irq-router:
> + $ref: /schemas/types.yaml#/definitions/phandle
> + description:
> + Syscon phandle for the Sky1 subsystem register block that routes
> + DMA-350 channel interrupts to the AP interrupt controller.
> +
> + memory-region:
> + maxItems: 1
> + description:
> + Reserved memory pool assigned by the CIX Sky1 DMA-350 integration driver
> + to the child Arm DMA-350 device.
> +
> +patternProperties:
> + '^dma-controller@[0-9a-f]+$':
> + $ref: arm,dma-350.yaml#
> +
> +required:
> + - compatible
> + - "#address-cells"
> + - "#size-cells"
> + - ranges
> + - dma-ranges
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/arm-gic.h>
> +
> + / {
> + #address-cells = <2>;
> + #size-cells = <2>;
> + interrupt-parent = <&gic>;
> +
> + gic: interrupt-controller {
> + #address-cells = <0>;
> + interrupt-controller;
> + #interrupt-cells = <4>;
> + };
> +
> + dma@7010000 {
> + compatible = "cix,sky1-dma350";
> + #address-cells = <2>;
> + #size-cells = <2>;
> + ranges = <0x0 0x0 0x0 0x07010000 0x0 0x10000>;
> + dma-ranges = <0x0 0x20000000 0x0 0x07010000 0x0 0x00100000>;
> + clocks = <&clk 9>;
> + resets = <&rst 15>;
> + cix,irq-router = <&audss_cru>;
> +
> + dma-controller@0 {
> + compatible = "arm,dma-350";
> + reg = <0x0 0x0 0x0 0x10000>;
> + interrupts = <GIC_SPI 230 IRQ_TYPE_LEVEL_HIGH 0>;
> + #dma-cells = <1>;
> + };
> + };
> + };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3a19da74d00c..feff8f9ecb2b 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2825,6 +2825,7 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
> S: Maintained
> T: git https://github.com/cixtech/linux-mainline.git
> F: Documentation/devicetree/bindings/arm/cix.yaml
> +F: Documentation/devicetree/bindings/dma/cix,sky1-dma350.yaml
> F: Documentation/devicetree/bindings/mailbox/cix,sky1-mbox.yaml
> F: arch/arm64/boot/dts/cix/
> F: drivers/mailbox/cix-mailbox.c
> --
> 2.54.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v1 3/5] dt-bindings: dma: Add CIX Sky1 DMA-350 integration
2026-09-07 17:15 ` Conor Dooley
@ 2026-09-09 6:05 ` Jelly Jia
2026-09-09 10:45 ` Conor Dooley
0 siblings, 1 reply; 9+ messages in thread
From: Jelly Jia @ 2026-09-09 6:05 UTC (permalink / raw)
To: Conor Dooley
Cc: Jelly Jia, vkoul, robh, krzk+dt, conor+dt, devicetree, Frank.Li,
robin.murphy, cix-kernel-upstream, dmaengine, linux-arm-kernel,
linux-kernel
Hi Conor,
Thanks for the review.
> I cannot really speak to whether this is a correct thing to do with a
> dma-350, but it seems to me like something that should be resolved with
> a device specific comaptible in the dma-350 node. Someone more familar
> with the IP will have to comment on that.
The wrapper is there to keep the platform integration bits (clocks,
resets, interrupt routing) out of the generic driver: the arm-dma-350
child stays plain so the existing driver binds to it unchanged. I don't
know whether other dma350 integrations need the same resources, so I
did not want to push them into the generic node.
> However I would like to know how this impacts the existing dma-350 in he
> sky1 devicetree.
Patch 5 converts the existing FCH node to this form: the register window
and the eight GIC interrupts are unchanged, the arm-dma-350 controller
becomes the dma-controller@0 child, and the SCMI clock feeding the
instance is added. The AUDSS instance is new. I'll describe this
conversion in the patch 5 commit message in v2.
Best regards,
Jelly
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v1 3/5] dt-bindings: dma: Add CIX Sky1 DMA-350 integration
2026-09-09 6:05 ` Jelly Jia
@ 2026-09-09 10:45 ` Conor Dooley
0 siblings, 0 replies; 9+ messages in thread
From: Conor Dooley @ 2026-09-09 10:45 UTC (permalink / raw)
To: Jelly Jia
Cc: vkoul, robh, krzk+dt, conor+dt, devicetree, Frank.Li,
robin.murphy, cix-kernel-upstream, dmaengine, linux-arm-kernel,
linux-kernel
[-- Attachment #1: Type: text/plain, Size: 1481 bytes --]
On Wed, Sep 09, 2026 at 02:05:12PM +0800, Jelly Jia wrote:
> Hi Conor,
>
> Thanks for the review.
>
> > I cannot really speak to whether this is a correct thing to do with a
> > dma-350, but it seems to me like something that should be resolved with
> > a device specific comaptible in the dma-350 node. Someone more familar
> > with the IP will have to comment on that.
>
> The wrapper is there to keep the platform integration bits (clocks,
> resets, interrupt routing) out of the generic driver: the arm-dma-350
> child stays plain so the existing driver binds to it unchanged. I don't
> know whether other dma350 integrations need the same resources, so I
> did not want to push them into the generic node.
Don't worry about this, given the limited extent of the wrapper driver,
at worst you will end up with a different probe function. The specific
compatible that you'll use will prevent the code relating to these
resources running on other platforms.
Thanks,
Conor.
>
> > However I would like to know how this impacts the existing dma-350 in he
> > sky1 devicetree.
>
> Patch 5 converts the existing FCH node to this form: the register window
> and the eight GIC interrupts are unchanged, the arm-dma-350 controller
> becomes the dma-controller@0 child, and the SCMI clock feeding the
> instance is added. The AUDSS instance is new. I'll describe this
> conversion in the patch 5 commit message in v2.
>
> Best regards,
> Jelly
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-09-09 10:45 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-07 3:33 [PATCH v1 0/5] dmaengine: arm-dma350: Add slave support and CIX Sky1 integration Jelly Jia
2026-09-07 3:34 ` [PATCH v1 1/5] dmaengine: arm-dma350: Fix source trigger bit Jelly Jia
2026-09-07 3:34 ` [PATCH v1 2/5] dmaengine: arm-dma350: Add slave transfer support Jelly Jia
2026-09-07 3:34 ` [PATCH v1 3/5] dt-bindings: dma: Add CIX Sky1 DMA-350 integration Jelly Jia
2026-09-07 17:15 ` Conor Dooley
2026-09-09 6:05 ` Jelly Jia
2026-09-09 10:45 ` Conor Dooley
2026-09-07 3:34 ` [PATCH v1 4/5] dmaengine: cix-sky1-dma350: Add Sky1 integration driver Jelly Jia
2026-09-07 3:34 ` [PATCH v1 5/5] arm64: dts: cix: Add Sky1 DMA-350 nodes Jelly Jia
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®