* [PATCH v1 0/5] soc: cix: Add the Sky1 AUDSS DMA-350 interrupt path
@ 2026-10-23 9:30 Jelly Jia
2026-10-23 9:30 ` [PATCH v1 1/5] dt-bindings: soc: cix: Describe the Sky1 AUDSS DMA IRQ controller Jelly Jia
` (4 more replies)
0 siblings, 5 replies; 13+ messages in thread
From: Jelly Jia @ 2026-10-23 9:30 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt, robin.murphy, sboyd, bmasney+clk,
jbrunet+clk
Cc: devicetree, Frank.Li, cix-kernel-upstream, dmaengine,
linux-arm-kernel, linux-kernel, Jelly.Jia, linux-clk,
joakim.zhang
Hello,
This series adds the CIX Sky1 AUDSS DMA-350 and its interrupt path through
the audio subsystem CRU. The DMA, CRU and DT changes are posted together
because the controller depends on the CRU interrupt gates, while
dma-channel-mask describes which channels Linux may use. Keeping them
together allows the complete interrupt path to be reviewed. If separate
submissions are preferred, they can be split in the next revision. No
audio client drivers are added here.
It depends on the companion v4 series, "dmaengine: arm-dma350: Add slave
support and Sky1 integration", for slave/cyclic transfers and optional
clock and reset support. Apply that series first.
The CRU gates the AP interrupt path for each of the eight DMA channels
and combines enabled inputs onto GIC SPI 230. Channels may be reserved
for firmware or the DSP; dma-channel-mask limits those available to Linux.
The AP gate bits control interrupt delivery, not channel ownership, and
this series does not implement runtime AP/DSP ownership transfer.
Conor suggested interrupt-map when the routing was described as being
configured by firmware. This revision instead manages each AP gate via
irq_mask and irq_unmask. A static interrupt-map alone would translate
child specifiers to the shared GIC SPI but would not provide that gate
control. Model the CRU as an irqchip so the register owner manages the
gates, without CRU register accesses in the DMA driver. Feedback on this
choice of irqchip rather than a static interrupt-map is welcome.
The gate register exposes no per-channel pending status. The chained
handler dispatches every enabled child, and each DMA channel handler
checks its own status. The children use the untracked flow handler so
inactive channels returning IRQ_NONE do not accumulate spurious-IRQ
counts.
The AUDSS DT describes a 1 MiB peripheral window and a 512 MiB DRAM window
with dma-ranges. The prerequisite series allocates linked commands from
a coherent DMA pool, so successfully allocated commands are reachable
through the controller's DMA address windows.
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
Jelly Jia (5):
dt-bindings: soc: cix: Describe the Sky1 AUDSS DMA IRQ controller
clk: cix: Handle Sky1 AUDSS DMA interrupt gates as an irqchip
dt-bindings: dma: arm,dma-350: Document dma-channel-mask
dmaengine: arm-dma350: Honour dma-channel-mask
arm64: dts: cix: Add the Sky1 AUDSS DMA-350 integration
.../devicetree/bindings/dma/arm,dma-350.yaml | 3 +
.../bindings/soc/cix/cix,sky1-audss-cru.yaml | 44 ++++++-
arch/arm64/boot/dts/cix/sky1.dtsi | 26 +++-
drivers/clk/cix/clk-sky1-audss.c | 122 ++++++++++++++++++
drivers/dma/arm-dma350.c | 20 ++-
5 files changed, 211 insertions(+), 4 deletions(-)
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
prerequisite-patch-id: aad1bc3c30a4825048143a3e0395e696201548e9
prerequisite-patch-id: ac69e4704329a579e556880cffc061b2743e30af
prerequisite-patch-id: ddc9ee115f12a7d2ee7ceff7e0485e7166581756
prerequisite-patch-id: 3d5fb2f7a5b2da42cb013d1c2e0a6e5eac5ffd6a
prerequisite-patch-id: 0e54074dcfd104480233bb076d2424e6ba1bd25e
--
2.54.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v1 1/5] dt-bindings: soc: cix: Describe the Sky1 AUDSS DMA IRQ controller
2026-10-23 9:30 [PATCH v1 0/5] soc: cix: Add the Sky1 AUDSS DMA-350 interrupt path Jelly Jia
@ 2026-10-23 9:30 ` Jelly Jia
2026-10-26 8:12 ` Krzysztof Kozlowski
2026-10-23 9:30 ` [PATCH v1 2/5] clk: cix: Handle Sky1 AUDSS DMA interrupt gates as an irqchip Jelly Jia
` (3 subsequent siblings)
4 siblings, 1 reply; 13+ messages in thread
From: Jelly Jia @ 2026-10-23 9:30 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt, robin.murphy, sboyd, bmasney+clk,
jbrunet+clk
Cc: devicetree, Frank.Li, cix-kernel-upstream, dmaengine,
linux-arm-kernel, linux-kernel, Jelly.Jia, linux-clk,
joakim.zhang
The AUDSS CRU contains a separate AP interrupt gate for each of the eight
DMA-350 channels. The enabled inputs are combined onto one GIC interrupt.
These gates are part of the CRU register block, not the DMA controller.
Describe the CRU as an interrupt-controller in addition to its clock and
reset functions. A child interrupt specifier contains the DMA channel
index, matching the corresponding gate bit; the parent interrupt is the
combined GIC line.
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
---
.../bindings/soc/cix/cix,sky1-audss-cru.yaml | 44 ++++++++++++++++++-
1 file changed, 42 insertions(+), 2 deletions(-)
diff --git a/Documentation/devicetree/bindings/soc/cix/cix,sky1-audss-cru.yaml b/Documentation/devicetree/bindings/soc/cix/cix,sky1-audss-cru.yaml
index 50dd0593e1d9..a296b59516f2 100644
--- a/Documentation/devicetree/bindings/soc/cix/cix,sky1-audss-cru.yaml
+++ b/Documentation/devicetree/bindings/soc/cix/cix,sky1-audss-cru.yaml
@@ -28,6 +28,12 @@ description: |
The SoC syscon NoC (or bus) reset is described via resets. The audio
subsystem power domain is described via power-domains.
+ The same register block contains the eight DMA-350 channel interrupt
+ gates at offset 0x54. When the node is an interrupt-controller, the
+ cell is the DMA channel index (0 through 7). The corresponding gate
+ bit is unmasked while that interrupt is requested, and the eight
+ inputs are combined onto the parent GIC interrupt.
+
properties:
compatible:
const: cix,sky1-audss-cru
@@ -45,6 +51,18 @@ properties:
description:
Reset indices are defined in include/dt-bindings/reset/cix,sky1-audss-cru.h.
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 1
+ description:
+ The cell is the DMA-350 channel index, 0 through 7.
+
+ interrupts:
+ maxItems: 1
+ description:
+ Combined DMA-350 channel interrupt after the CRU gates.
+
clocks:
items:
- description: I2S parent clock for sampling rates multiple of 8kHz.
@@ -75,18 +93,40 @@ required:
- power-domains
- resets
+allOf:
+ - if:
+ required:
+ - interrupt-controller
+ then:
+ required:
+ - '#interrupt-cells'
+ - interrupts
+
additionalProperties: false
examples:
- |
- audss_cru: clock-controller@7110000 {
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ gic: interrupt-controller {
+ #address-cells = <2>;
+ #interrupt-cells = <4>;
+ interrupt-controller;
+ };
+
+ clock-controller@7110000 {
compatible = "cix,sky1-audss-cru";
reg = <0x7110000 0x10000>;
#clock-cells = <1>;
- #reset-cells = <1>;
clocks = <&scmi_clk 76>, <&scmi_clk 78>,
<&scmi_clk 70>, <&scmi_clk 71>;
clock-names = "x8k", "x11k", "sys", "48m";
+ #interrupt-cells = <1>;
+ interrupt-controller;
+ interrupt-parent = <&gic>;
+ interrupts = <GIC_SPI 230 IRQ_TYPE_LEVEL_HIGH 0>;
power-domains = <&smc_devpd 0>;
+ #reset-cells = <1>;
resets = <&s5_syscon 31>;
};
--
2.54.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v1 2/5] clk: cix: Handle Sky1 AUDSS DMA interrupt gates as an irqchip
2026-10-23 9:30 [PATCH v1 0/5] soc: cix: Add the Sky1 AUDSS DMA-350 interrupt path Jelly Jia
2026-10-23 9:30 ` [PATCH v1 1/5] dt-bindings: soc: cix: Describe the Sky1 AUDSS DMA IRQ controller Jelly Jia
@ 2026-10-23 9:30 ` Jelly Jia
2026-10-23 9:53 ` sashiko-bot
2026-10-23 9:30 ` [PATCH v1 3/5] dt-bindings: dma: arm,dma-350: Document dma-channel-mask Jelly Jia
` (2 subsequent siblings)
4 siblings, 1 reply; 13+ messages in thread
From: Jelly Jia @ 2026-10-23 9:30 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt, robin.murphy, sboyd, bmasney+clk,
jbrunet+clk
Cc: devicetree, Frank.Li, cix-kernel-upstream, dmaengine,
linux-arm-kernel, linux-kernel, Jelly.Jia, linux-clk,
joakim.zhang
The AUDSS CRU INFO_AP_IRQC register gates each DMA-350 channel interrupt
onto the shared AP GIC line. Manage these gates with an irqchip in the
CRU driver, keeping access to CRU registers out of the DMA driver. Mask
and unmask the corresponding AP gate through the IRQ core; this controls
interrupt delivery, not AP/DSP channel ownership.
The register exposes gate state but no per-channel pending status. Call
each enabled child's DMA handler to check its channel-local status. Use
the untracked flow handler so inactive children returning IRQ_NONE do not
accumulate spurious-IRQ counts.
Clear the AP gates before installing the chained handler, then enable
them only as Linux requests channel interrupts. Clear them again during
teardown and include the register in the runtime and system sleep save
list. Register irqchip cleanup after runtime-PM cleanup so devres removes
the irqchip while the CRU register block is still clocked.
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
---
drivers/clk/cix/clk-sky1-audss.c | 122 +++++++++++++++++++++++++++++++
1 file changed, 122 insertions(+)
diff --git a/drivers/clk/cix/clk-sky1-audss.c b/drivers/clk/cix/clk-sky1-audss.c
index e096b8121df3..789cbba631d1 100644
--- a/drivers/clk/cix/clk-sky1-audss.c
+++ b/drivers/clk/cix/clk-sky1-audss.c
@@ -2,9 +2,14 @@
// Copyright 2026 Cix Technology Group Co., Ltd.
#include <linux/auxiliary_bus.h>
+#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
@@ -18,6 +23,9 @@
#define INFO_CLK_GATE 0x10
#define INFO_CLK_DIV 0x14
#define INFO_CLK_MUX 0x18
+#define INFO_AP_IRQC 0x54
+#define INFO_AP_IRQC_DMAC_MASK GENMASK(7, 0)
+#define SKY1_AUDSS_DMAC_IRQS 8
#define INFO_MCLK 0x70
#define SKY1_AUDSS_CLK_PARENTS_CNT 4
@@ -28,6 +36,7 @@ static u32 sky1_reg_save[][2] = {
{ INFO_CLK_GATE, 0 },
{ INFO_CLK_DIV, 0 },
{ INFO_CLK_MUX, 0 },
+ { INFO_AP_IRQC, 0 },
{ INFO_MCLK, 0 },
};
@@ -529,6 +538,8 @@ struct sky1_audss_clks_priv {
const struct sky1_audss_clks_devtype_data *devtype_data;
spinlock_t lock;
struct clk_hw_onecell_data *clk_data;
+ struct irq_domain *irq_domain;
+ int parent_irq;
};
#if IS_ENABLED(CONFIG_RESET_SKY1_AUDSS)
@@ -928,6 +939,110 @@ static int sky1_audss_clks_enable(struct sky1_audss_clks_priv *priv)
return err;
}
+static void sky1_audss_irq_mask(struct irq_data *d)
+{
+ struct sky1_audss_clks_priv *priv = irq_data_get_irq_chip_data(d);
+
+ regmap_update_bits(priv->regmap_cru, INFO_AP_IRQC,
+ BIT(d->hwirq), 0);
+}
+
+static void sky1_audss_irq_unmask(struct irq_data *d)
+{
+ struct sky1_audss_clks_priv *priv = irq_data_get_irq_chip_data(d);
+
+ regmap_update_bits(priv->regmap_cru, INFO_AP_IRQC,
+ BIT(d->hwirq), BIT(d->hwirq));
+}
+
+static struct irq_chip sky1_audss_irq_chip = {
+ .name = "sky1-audss-dmac",
+ .irq_mask = sky1_audss_irq_mask,
+ .irq_unmask = sky1_audss_irq_unmask,
+ .flags = IRQCHIP_SKIP_SET_WAKE,
+};
+
+static int sky1_audss_irq_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ if (hwirq >= SKY1_AUDSS_DMAC_IRQS)
+ return -EINVAL;
+
+ irq_set_chip_data(irq, d->host_data);
+ irq_set_chip_and_handler(irq, &sky1_audss_irq_chip,
+ handle_untracked_irq);
+ irq_set_noprobe(irq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops sky1_audss_irq_ops = {
+ .map = sky1_audss_irq_map,
+ .xlate = irq_domain_xlate_onecell,
+};
+
+static void sky1_audss_irq_handler(struct irq_desc *desc)
+{
+ struct sky1_audss_clks_priv *priv = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned long enabled;
+ unsigned int hwirq;
+ u32 val;
+
+ chained_irq_enter(chip, desc);
+
+ if (!regmap_read(priv->regmap_cru, INFO_AP_IRQC, &val)) {
+ enabled = val & INFO_AP_IRQC_DMAC_MASK;
+ for_each_set_bit(hwirq, &enabled, SKY1_AUDSS_DMAC_IRQS)
+ generic_handle_domain_irq(priv->irq_domain, hwirq);
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static void sky1_audss_irq_teardown(void *data)
+{
+ struct sky1_audss_clks_priv *priv = data;
+
+ regmap_update_bits(priv->regmap_cru, INFO_AP_IRQC,
+ INFO_AP_IRQC_DMAC_MASK, 0);
+ irq_set_chained_handler_and_data(priv->parent_irq, NULL, NULL);
+ irq_domain_remove(priv->irq_domain);
+}
+
+static int sky1_audss_irq_init(struct platform_device *pdev,
+ struct sky1_audss_clks_priv *priv)
+{
+ struct device *dev = &pdev->dev;
+ int ret;
+ int irq;
+
+ if (!of_property_read_bool(dev->of_node, "interrupt-controller"))
+ return 0;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ priv->irq_domain = irq_domain_add_linear(dev->of_node,
+ SKY1_AUDSS_DMAC_IRQS,
+ &sky1_audss_irq_ops, priv);
+ if (!priv->irq_domain)
+ return -ENOMEM;
+
+ ret = regmap_update_bits(priv->regmap_cru, INFO_AP_IRQC,
+ INFO_AP_IRQC_DMAC_MASK, 0);
+ if (ret) {
+ irq_domain_remove(priv->irq_domain);
+ return ret;
+ }
+
+ priv->parent_irq = irq;
+ irq_set_chained_handler_and_data(irq, sky1_audss_irq_handler, priv);
+
+ return devm_add_action_or_reset(dev, sky1_audss_irq_teardown, priv);
+}
+
static void sky1_audss_clks_disable(struct sky1_audss_clks_priv *priv)
{
const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data;
@@ -1110,6 +1225,13 @@ static int sky1_audss_clk_probe(struct platform_device *pdev)
goto fail_pm;
}
+ /* Tear the irqchip down before the runtime-PM cleanup disables clocks. */
+ ret = sky1_audss_irq_init(pdev, priv);
+ if (ret) {
+ dev_err_probe(dev, ret, "failed to set up AUDSS DMA IRQs\n");
+ goto fail_pm;
+ }
+
pm_runtime_put_sync(dev);
return 0;
--
2.54.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v1 3/5] dt-bindings: dma: arm,dma-350: Document dma-channel-mask
2026-10-23 9:30 [PATCH v1 0/5] soc: cix: Add the Sky1 AUDSS DMA-350 interrupt path Jelly Jia
2026-10-23 9:30 ` [PATCH v1 1/5] dt-bindings: soc: cix: Describe the Sky1 AUDSS DMA IRQ controller Jelly Jia
2026-10-23 9:30 ` [PATCH v1 2/5] clk: cix: Handle Sky1 AUDSS DMA interrupt gates as an irqchip Jelly Jia
@ 2026-10-23 9:30 ` Jelly Jia
2026-10-26 8:13 ` Krzysztof Kozlowski
2026-10-23 9:30 ` [PATCH v1 4/5] dmaengine: arm-dma350: Honour dma-channel-mask Jelly Jia
2026-10-23 9:30 ` [PATCH v1 5/5] arm64: dts: cix: Add the Sky1 AUDSS DMA-350 integration Jelly Jia
4 siblings, 1 reply; 13+ messages in thread
From: Jelly Jia @ 2026-10-23 9:30 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt, robin.murphy, sboyd, bmasney+clk,
jbrunet+clk
Cc: devicetree, Frank.Li, cix-kernel-upstream, dmaengine,
linux-arm-kernel, linux-kernel, Jelly.Jia, linux-clk,
joakim.zhang
Some DMA-350 integrations reserve channels for firmware or another
processor. Allow the standard dma-channel-mask property so firmware can
describe which channels Linux may use.
DMA-350 implements at most eight channels, so one 32-bit cell is enough;
limit the property to a single cell.
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
---
Documentation/devicetree/bindings/dma/arm,dma-350.yaml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
index 43279878207c..98e7bb651210 100644
--- a/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
+++ b/Documentation/devicetree/bindings/dma/arm,dma-350.yaml
@@ -43,6 +43,9 @@ properties:
description:
Optional reset line, where the integration exposes one.
+ dma-channel-mask:
+ maxItems: 1
+
required:
- compatible
- reg
--
2.54.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v1 4/5] dmaengine: arm-dma350: Honour dma-channel-mask
2026-10-23 9:30 [PATCH v1 0/5] soc: cix: Add the Sky1 AUDSS DMA-350 interrupt path Jelly Jia
` (2 preceding siblings ...)
2026-10-23 9:30 ` [PATCH v1 3/5] dt-bindings: dma: arm,dma-350: Document dma-channel-mask Jelly Jia
@ 2026-10-23 9:30 ` Jelly Jia
2026-10-28 16:47 ` Frank Li
2026-10-23 9:30 ` [PATCH v1 5/5] arm64: dts: cix: Add the Sky1 AUDSS DMA-350 integration Jelly Jia
4 siblings, 1 reply; 13+ messages in thread
From: Jelly Jia @ 2026-10-23 9:30 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt, robin.murphy, sboyd, bmasney+clk,
jbrunet+clk
Cc: devicetree, Frank.Li, cix-kernel-upstream, dmaengine,
linux-arm-kernel, linux-kernel, Jelly.Jia, linux-clk,
joakim.zhang
DMA-350 channels may be reserved for firmware or another processor.
Read the optional dma-channel-mask property and register only the channels
whose bits are set. If the property is absent, preserve the existing
behaviour and expose every hardware channel.
Skip masked channels before accessing their channel registers or
initializing their interrupts and virt-dma state. This avoids clearing
or configuring channels reserved for firmware or another processor.
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
---
drivers/dma/arm-dma350.c | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
index 9cb5b2f03113..32ec6f061ee9 100644
--- a/drivers/dma/arm-dma350.c
+++ b/drivers/dma/arm-dma350.c
@@ -1205,9 +1205,10 @@ static int d350_probe(struct platform_device *pdev)
struct clk *clk;
struct d350 *dmac;
void __iomem *base;
+ u32 chan_mask = U32_MAX;
u32 reg;
int ret, nchan, dw, aw, r, p;
- bool coherent, memset;
+ bool coherent, has_chan_mask, memset;
clk = devm_clk_get_optional_enabled(dev, NULL);
if (IS_ERR(clk))
@@ -1285,6 +1286,19 @@ static int d350_probe(struct platform_device *pdev)
dmac->dma.device_config = d350_config;
INIT_LIST_HEAD(&dmac->dma.channels);
+ /*
+ * dma-channel-mask is optional. Firmware-reserved channels stay
+ * clear; if the property is absent, Linux may use every channel.
+ */
+ has_chan_mask = device_property_present(dev, "dma-channel-mask");
+ if (has_chan_mask) {
+ ret = device_property_read_u32(dev, "dma-channel-mask",
+ &chan_mask);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read dma-channel-mask\n");
+ }
+
reg = readl_relaxed(base + DMANSECCTRL + NSEC_CTRL);
writel_relaxed(reg | INTREN_ANYCHINTR_EN,
base + DMANSECCTRL + NSEC_CTRL);
@@ -1294,6 +1308,10 @@ static int d350_probe(struct platform_device *pdev)
for (int i = 0; i < nchan; i++) {
struct d350_chan *dch = &dmac->channels[i];
+ if (has_chan_mask &&
+ (i >= 32 || !(chan_mask & BIT(i))))
+ continue;
+
dch->base = base + DMACH(i);
writel_relaxed(CH_CMD_CLEAR, dch->base + CH_CMD);
--
2.54.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v1 5/5] arm64: dts: cix: Add the Sky1 AUDSS DMA-350 integration
2026-10-23 9:30 [PATCH v1 0/5] soc: cix: Add the Sky1 AUDSS DMA-350 interrupt path Jelly Jia
` (3 preceding siblings ...)
2026-10-23 9:30 ` [PATCH v1 4/5] dmaengine: arm-dma350: Honour dma-channel-mask Jelly Jia
@ 2026-10-23 9:30 ` Jelly Jia
2026-10-23 10:28 ` sashiko-bot
4 siblings, 1 reply; 13+ messages in thread
From: Jelly Jia @ 2026-10-23 9:30 UTC (permalink / raw)
To: vkoul, robh, krzk+dt, conor+dt, robin.murphy, sboyd, bmasney+clk,
jbrunet+clk
Cc: devicetree, Frank.Li, cix-kernel-upstream, dmaengine,
linux-arm-kernel, linux-kernel, Jelly.Jia, linux-clk,
joakim.zhang
The AUDSS DMA-350 master sees the CPU physical peripheral window at
0x07010000 through DMA address 0x20000000, and DRAM at CPU physical
0xc0000000 through DMA address 0x30000000. Describe the 1 MiB peripheral
window and 512 MiB DRAM window with dma-ranges on the parent bus, and add
the controller's clock and reset.
Route the eight channel interrupts through the AUDSS CRU, whose AP gates
combine them onto GIC SPI 230. List child interrupts in hardware channel
order and set dma-channel-mask to 0xff to make all eight channels
available to Linux.
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
---
arch/arm64/boot/dts/cix/sky1.dtsi | 26 +++++++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/cix/sky1.dtsi b/arch/arm64/boot/dts/cix/sky1.dtsi
index cd498ee7e9a7..6ca94f606463 100644
--- a/arch/arm64/boot/dts/cix/sky1.dtsi
+++ b/arch/arm64/boot/dts/cix/sky1.dtsi
@@ -588,17 +588,41 @@ mbox_pm2ap: mailbox@65a0080 {
cix,mbox-dir = "rx";
};
+ audss-dma-bus {
+ compatible = "simple-bus";
+ ranges;
+ #address-cells = <2>;
+ dma-ranges = <0x0 0x20000000 0x0 0x07010000 0x0 0x00100000>,
+ <0x0 0x30000000 0x0 0xc0000000 0x0 0x20000000>;
+ #size-cells = <2>;
+
+ audss_dmac: dma-controller@7010000 {
+ compatible = "arm,dma-350";
+ reg = <0x0 0x07010000 0x0 0x10000>;
+ clocks = <&audss_cru CLK_DMAC_AXI>;
+ #dma-cells = <1>;
+ dma-channel-mask = <0xff>;
+ interrupt-parent = <&audss_cru>;
+ interrupts = <0>, <1>, <2>, <3>,
+ <4>, <5>, <6>, <7>;
+ resets = <&audss_cru AUDSS_DMAC_SW_RST>;
+ };
+ };
+
audss_cru: clock-controller@7110000 {
compatible = "cix,sky1-audss-cru";
reg = <0x0 0x07110000 0x0 0x10000>;
#clock-cells = <1>;
- #reset-cells = <1>;
clocks = <&scmi_clk CLK_TREE_AUDIO_CLK0>,
<&scmi_clk CLK_TREE_AUDIO_CLK2>,
<&scmi_clk CLK_TREE_AUDIO_CLK4>,
<&scmi_clk CLK_TREE_AUDIO_CLK5>;
clock-names = "x8k", "x11k", "sys", "48m";
+ #interrupt-cells = <1>;
+ interrupt-controller;
+ interrupts = <GIC_SPI 230 IRQ_TYPE_LEVEL_HIGH 0>;
power-domains = <&smc_devpd SKY1_PD_AUDIO>;
+ #reset-cells = <1>;
resets = <&s5_syscon SKY1_AUDIO_HIFI5_NOC_RESET_N>;
};
--
2.54.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v1 2/5] clk: cix: Handle Sky1 AUDSS DMA interrupt gates as an irqchip
2026-10-23 9:30 ` [PATCH v1 2/5] clk: cix: Handle Sky1 AUDSS DMA interrupt gates as an irqchip Jelly Jia
@ 2026-10-23 9:53 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-10-23 9:53 UTC (permalink / raw)
To: Jelly Jia
Cc: devicetree, dmaengine, linux-clk, robh, conor+dt, vkoul, Frank.Li
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] clk: cix: unclocked MMIO access in sky1_audss_irq_teardown() and IRQ callbacks
- [High] clk: cix: invalid wait context in sky1_audss_irq_handler() and mask callbacks
- [High] clk: cix: missing irq_dispose_mapping() in sky1_audss_irq_teardown()
- [High] clk: cix: level IRQ storm with handle_untracked_irq() on lazy IRQ disable
- [Low] clk: cix: missing select IRQ_DOMAIN for CLK_SKY1_AUDSS in Kconfig
--
commit d366d15da9922d1961ddd48a45f4b621f1a77320
Author: Jelly Jia <Jelly.Jia@cixtech.com>
clk: cix: Handle Sky1 AUDSS DMA interrupt gates as an irqchip
This commit adds an irqchip and linear IRQ domain to the CIX Sky1 AUDSS clock
driver to manage the DMA-350 channel interrupt gates in the INFO_AP_IRQC
register. It installs a chained IRQ handler on the shared parent GIC line and
registers a devres teardown action to clean up the irqchip.
> diff --git a/drivers/clk/cix/clk-sky1-audss.c b/drivers/clk/cix/clk-sky1-audss.c
> index e096b8121df30..789cbba631d16 100644
> --- a/drivers/clk/cix/clk-sky1-audss.c
> +++ b/drivers/clk/cix/clk-sky1-audss.c
[ ... ]
> @@ -928,6 +939,110 @@ static int sky1_audss_clks_enable(struct sky1_audss_clks_priv *priv)
> return err;
> }
>
> +static void sky1_audss_irq_mask(struct irq_data *d)
> +{
> + struct sky1_audss_clks_priv *priv = irq_data_get_irq_chip_data(d);
> +
> + regmap_update_bits(priv->regmap_cru, INFO_AP_IRQC,
> + BIT(d->hwirq), 0);
> +}
> +
> +static void sky1_audss_irq_unmask(struct irq_data *d)
> +{
> + struct sky1_audss_clks_priv *priv = irq_data_get_irq_chip_data(d);
> +
> + regmap_update_bits(priv->regmap_cru, INFO_AP_IRQC,
> + BIT(d->hwirq), BIT(d->hwirq));
> +}
[Severity: High]
Will calling regmap_update_bits() in sky1_audss_irq_mask() and
sky1_audss_irq_unmask(), as well as regmap_read() in sky1_audss_irq_handler(),
trigger an invalid wait context warning under CONFIG_PROVE_RAW_LOCK_NESTING
and sleep in atomic context on PREEMPT_RT?
In drivers/clk/cix/clk-sky1-audss.c, priv->regmap_cru is initialized via
devm_regmap_init_mmio() using sky1_audss_regmap_config:
static const struct regmap_config sky1_audss_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
};
Because sky1_audss_regmap_config does not set .use_raw_spinlock = true,
regmap initializes a non-raw spinlock_t for map->spinlock.
The IRQ core invokes .irq_mask and .irq_unmask while holding irq_desc->lock
(a raw_spinlock_t), and sky1_audss_irq_handler() runs in hardirq context.
Acquiring the regmap spinlock_t in those paths violates raw_spinlock_t
nesting rules. Should sky1_audss_regmap_config set .use_raw_spinlock = true?
> +
> +static struct irq_chip sky1_audss_irq_chip = {
> + .name = "sky1-audss-dmac",
> + .irq_mask = sky1_audss_irq_mask,
> + .irq_unmask = sky1_audss_irq_unmask,
> + .flags = IRQCHIP_SKIP_SET_WAKE,
> +};
> +
> +static int sky1_audss_irq_map(struct irq_domain *d, unsigned int irq,
> + irq_hw_number_t hwirq)
> +{
> + if (hwirq >= SKY1_AUDSS_DMAC_IRQS)
> + return -EINVAL;
> +
> + irq_set_chip_data(irq, d->host_data);
> + irq_set_chip_and_handler(irq, &sky1_audss_irq_chip,
> + handle_untracked_irq);
> + irq_set_noprobe(irq);
> +
> + return 0;
> +}
> +
> +static const struct irq_domain_ops sky1_audss_irq_ops = {
> + .map = sky1_audss_irq_map,
> + .xlate = irq_domain_xlate_onecell,
> +};
> +
> +static void sky1_audss_irq_handler(struct irq_desc *desc)
> +{
> + struct sky1_audss_clks_priv *priv = irq_desc_get_handler_data(desc);
> + struct irq_chip *chip = irq_desc_get_chip(desc);
> + unsigned long enabled;
> + unsigned int hwirq;
> + u32 val;
> +
> + chained_irq_enter(chip, desc);
> +
> + if (!regmap_read(priv->regmap_cru, INFO_AP_IRQC, &val)) {
> + enabled = val & INFO_AP_IRQC_DMAC_MASK;
> + for_each_set_bit(hwirq, &enabled, SKY1_AUDSS_DMAC_IRQS)
> + generic_handle_domain_irq(priv->irq_domain, hwirq);
> + }
> +
> + chained_irq_exit(chip, desc);
> +}
[Severity: High]
What happens if a child DMA interrupt is disabled via disable_irq(),
disable_irq_nosync(), or suspend_device_irqs() and that channel later
asserts its level-high interrupt?
Because sky1_audss_irq_chip only provides .irq_mask and .irq_unmask (without
.irq_disable or IRQCHIP_MASK_ON_SUSPEND) and sky1_audss_irq_map() does not
set IRQ_DISABLE_UNLAZY, irq_disable() performs a lazy disable: it marks the
interrupt disabled in software without calling sky1_audss_irq_mask(), leaving
BIT(hwirq) set in INFO_AP_IRQC.
Unlike handle_level_irq() and handle_fasteoi_irq(), handle_untracked_irq()
does not call mask_irq() when an interrupt arrives while disabled:
void handle_untracked_irq(struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
if (!irq_can_handle(desc))
goto out_unlock;
...
When the disabled channel asserts its level interrupt, sky1_audss_irq_handler()
sees BIT(hwirq) set in INFO_AP_IRQC and calls generic_handle_domain_irq() ->
handle_untracked_irq(), which returns immediately without masking the gate or
clearing the source. Once chained_irq_exit() unmasks the parent GIC line,
won't the level-high parent interrupt immediately re-fire in an infinite hard
IRQ storm?
> +
> +static void sky1_audss_irq_teardown(void *data)
> +{
> + struct sky1_audss_clks_priv *priv = data;
> +
> + regmap_update_bits(priv->regmap_cru, INFO_AP_IRQC,
> + INFO_AP_IRQC_DMAC_MASK, 0);
> + irq_set_chained_handler_and_data(priv->parent_irq, NULL, NULL);
> + irq_domain_remove(priv->irq_domain);
> +}
[Severity: High]
Does sky1_audss_irq_teardown() leak mapped irq_desc descriptors and leave
dangling pointers when calling irq_domain_remove(priv->irq_domain) without
first disposing of existing mappings?
When a consumer calls platform_get_irq(), irq_create_fwspec_mapping() maps the
child virq through sky1_audss_irq_map(), setting desc->irq_data.domain to
priv->irq_domain, desc->irq_data.chip_data to priv, and desc->irq_data.chip to
&sky1_audss_irq_chip. Platform consumers do not call irq_dispose_mapping() on
unbind.
Calling irq_domain_remove() directly without iterating over the domain's
hwirqs and calling irq_dispose_mapping(irq_find_mapping(priv->irq_domain,
hwirq)) leaves those virtual IRQ descriptors allocated in the global IRQ tree
with dangling pointers to the freed domain, freed priv, and (if built as a
module and unloaded) unmapped sky1_audss_irq_chip memory.
> +
> +static int sky1_audss_irq_init(struct platform_device *pdev,
> + struct sky1_audss_clks_priv *priv)
> +{
> + struct device *dev = &pdev->dev;
> + int ret;
> + int irq;
> +
> + if (!of_property_read_bool(dev->of_node, "interrupt-controller"))
> + return 0;
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0)
> + return irq;
> +
> + priv->irq_domain = irq_domain_add_linear(dev->of_node,
> + SKY1_AUDSS_DMAC_IRQS,
> + &sky1_audss_irq_ops, priv);
[Severity: Low]
Can this fail to build under CONFIG_COMPILE_TEST=y when CONFIG_IRQ_DOMAIN is
not enabled?
In drivers/clk/cix/Kconfig, CLK_SKY1_AUDSS depends on ARCH_CIX || COMPILE_TEST
and selects AUXILIARY_BUS, REGMAP_MMIO, and RESET_CONTROLLER, but does not
select IRQ_DOMAIN. Because irq_domain_add_linear(), irq_domain_remove(),
irq_domain_xlate_onecell, and generic_handle_domain_irq() are only declared
when CONFIG_IRQ_DOMAIN is enabled, should CLK_SKY1_AUDSS select IRQ_DOMAIN as
well?
[ ... ]
> @@ -1110,6 +1225,13 @@ static int sky1_audss_clk_probe(struct platform_device *pdev)
> goto fail_pm;
> }
>
> + /* Tear the irqchip down before the runtime-PM cleanup disables clocks. */
> + ret = sky1_audss_irq_init(pdev, priv);
> + if (ret) {
> + dev_err_probe(dev, ret, "failed to set up AUDSS DMA IRQs\n");
> + goto fail_pm;
> + }
> +
> pm_runtime_put_sync(dev);
>
> return 0;
[Severity: High]
Can sky1_audss_irq_teardown(), sky1_audss_irq_mask(), sky1_audss_irq_unmask(),
and sky1_audss_irq_handler() access INFO_AP_IRQC while the AUDSS CRU is
runtime-suspended with its NoC reset asserted and parent clocks disabled?
In sky1_audss_clk_runtime_suspend(), suspending the device asserts
priv->rst_noc and disables all four parent clocks:
reset_control_assert(priv->rst_noc);
sky1_audss_clks_disable(priv);
There appear to be three paths where INFO_AP_IRQC is accessed while the CRU is
in this unclocked/reset state:
1. At the end of sky1_audss_clk_probe() (and at fail_pm), pm_runtime_put_sync()
suspends the device immediately if no consumer clock is prepared. Likewise,
during driver unbind, __device_release_driver() runs pm_runtime_put_sync()
before devres_release_all(). Because the device is already runtime-suspended
before devres teardown starts, registering sky1_audss_irq_teardown() after
sky1_audss_clk_rpm_cleanup() does not keep the CRU clocked when
sky1_audss_irq_teardown() calls regmap_update_bits() on INFO_AP_IRQC.
2. In sky1_audss_irq_init(), priv->irq_domain is created without calling
irq_domain_set_pm_device(priv->irq_domain, dev). As a result, requesting or
freeing a child DMA IRQ does not runtime-resume dev before
sky1_audss_irq_unmask() or sky1_audss_irq_mask() accesses INFO_AP_IRQC (and
any gate bit written while suspended would be overwritten by sky1_reg_save
on the next runtime resume).
3. In sky1_audss_clk_pm_ops, pm_runtime_force_suspend() and
pm_runtime_force_resume() are wired to normal system sleep callbacks via
SET_SYSTEM_SLEEP_PM_OPS() rather than noirq callbacks, so the CRU is placed
in reset and unclocked during dpm_suspend() while the chained handler
sky1_audss_irq_handler() and child IRQs are still active.
Should the driver set irq_domain_set_pm_device(), ensure runtime PM is active
around the register write in sky1_audss_irq_teardown(), and move the system
sleep callbacks to noirq?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261023093045.3248630-1-Jelly.Jia@cixtech.com?part=2
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v1 5/5] arm64: dts: cix: Add the Sky1 AUDSS DMA-350 integration
2026-10-23 9:30 ` [PATCH v1 5/5] arm64: dts: cix: Add the Sky1 AUDSS DMA-350 integration Jelly Jia
@ 2026-10-23 10:28 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-10-23 10:28 UTC (permalink / raw)
To: Jelly Jia
Cc: devicetree, dmaengine, linux-clk, robh, conor+dt, vkoul, Frank.Li
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] clk: cix: unclocked MMIO access in sky1_audss_irq_teardown() and IRQ callbacks
- [High] clk: cix: IRQ storm in sky1_audss_irq_handler() due to handle_untracked_irq()
- [High] clk: cix: invalid wait context in sky1_audss_irq_handler() via non-raw regmap
- [High] clk: cix: missing virq disposal before irq_domain_remove() in teardown
- [High] arm64: dts: cix: audss_dmac reset AUDSS_SW_RST lost across AUDSS CRU suspend
--
commit 5fc04062b961f79314208fe23d9fe7413f932103
Author: Jelly Jia <Jelly.Jia@cixtech.com>
arm64: dts: cix: Add the Sky1 AUDSS DMA-350 integration
This commit adds the audss-dma-bus and audss_dmac nodes for the Sky1 AUDSS
DMA-350 controller and wires its clocks, resets, and channel interrupts
through the audss_cru controller.
> diff --git a/arch/arm64/boot/dts/cix/sky1.dtsi b/arch/arm64/boot/dts/cix/sky1.dtsi
> index cd498ee7e9a76..6ca94f606463c 100644
> --- a/arch/arm64/boot/dts/cix/sky1.dtsi
> +++ b/arch/arm64/boot/dts/cix/sky1.dtsi
> @@ -588,17 +588,41 @@ mbox_pm2ap: mailbox@65a0080 {
> cix,mbox-dir = "rx";
> };
>
> + audss-dma-bus {
> + compatible = "simple-bus";
> + ranges;
> + #address-cells = <2>;
> + dma-ranges = <0x0 0x20000000 0x0 0x07010000 0x0 0x00100000>,
> + <0x0 0x30000000 0x0 0xc0000000 0x0 0x20000000>;
> + #size-cells = <2>;
> +
> + audss_dmac: dma-controller@7010000 {
> + compatible = "arm,dma-350";
> + reg = <0x0 0x07010000 0x0 0x10000>;
> + clocks = <&audss_cru CLK_DMAC_AXI>;
> + #dma-cells = <1>;
> + dma-channel-mask = <0xff>;
> + interrupt-parent = <&audss_cru>;
> + interrupts = <0>, <1>, <2>, <3>,
> + <4>, <5>, <6>, <7>;
> + resets = <&audss_cru AUDSS_DMAC_SW_RST>;
[Severity: High]
Will the AUDSS_DMAC_SW_RST reset state for audss_dmac survive a system
suspend and resume cycle of audss_cru?
In drivers/dma/arm-dma350.c, d350_probe() deasserts the reset once at probe
time and the driver defines no power management callbacks:
drivers/dma/arm-dma350.c:d350_probe() {
reset = devm_reset_control_get_optional_exclusive_deasserted(dev, NULL);
if (IS_ERR(reset))
return dev_err_probe(dev, PTR_ERR(reset),
"failed to deassert reset\n");
}
That call goes through sky1_audss_reset_deassert() -> sky1_audss_reset_set()
in drivers/reset/reset-sky1-audss.c to set BIT(15) in AUDSS_SW_RST (0x78).
However, when audss_cru suspends via sky1_audss_clk_runtime_suspend() in
drivers/clk/cix/clk-sky1-audss.c, it saves only the registers listed in
sky1_reg_save[] before asserting priv->rst_noc:
drivers/clk/cix/clk-sky1-audss.c:
static u32 sky1_reg_save[][2] = {
{ INFO_HIFI0, 0 },
{ INFO_CLK_GATE, 0 },
{ INFO_CLK_DIV, 0 },
{ INFO_CLK_MUX, 0 },
{ INFO_AP_IRQC, 0 },
{ INFO_MCLK, 0 },
};
Because AUDSS_SW_RST (0x78) is not included in sky1_reg_save[], asserting
priv->rst_noc and powering down SKY1_PD_AUDIO resets AUDSS_SW_RST, leaving
audss_dmac held in hardware reset after sky1_audss_clk_runtime_resume()
runs.
> + };
> + };
> +
> audss_cru: clock-controller@7110000 {
> compatible = "cix,sky1-audss-cru";
> reg = <0x0 0x07110000 0x0 0x10000>;
> #clock-cells = <1>;
> - #reset-cells = <1>;
> clocks = <&scmi_clk CLK_TREE_AUDIO_CLK0>,
> <&scmi_clk CLK_TREE_AUDIO_CLK2>,
> <&scmi_clk CLK_TREE_AUDIO_CLK4>,
> <&scmi_clk CLK_TREE_AUDIO_CLK5>;
> clock-names = "x8k", "x11k", "sys", "48m";
> + #interrupt-cells = <1>;
> + interrupt-controller;
> + interrupts = <GIC_SPI 230 IRQ_TYPE_LEVEL_HIGH 0>;
[Severity: High]
Can wiring audss_cru as an interrupt-controller trigger unclocked register
accesses to INFO_AP_IRQC while audss_cru is runtime-suspended and held in
reset?
In drivers/clk/cix/clk-sky1-audss.c, sky1_audss_clk_probe() initializes the
irqchip and then drops the runtime PM reference:
drivers/clk/cix/clk-sky1-audss.c:sky1_audss_clk_probe() {
/* Tear the irqchip down before the runtime-PM cleanup disables clocks. */
ret = sky1_audss_irq_init(pdev, priv);
if (ret) {
dev_err_probe(dev, ret, "failed to set up AUDSS DMA IRQs\n");
goto fail_pm;
}
pm_runtime_put_sync(dev);
}
Whenever no consumer clock is prepared, sky1_audss_clk_runtime_suspend()
asserts priv->rst_noc and disables the parent clocks via
sky1_audss_clks_disable(priv):
drivers/clk/cix/clk-sky1-audss.c:sky1_audss_clk_runtime_suspend() {
...
reset_control_assert(priv->rst_noc);
sky1_audss_clks_disable(priv);
}
During driver unbind in __device_release_driver(),
device_links_unbind_consumers() unbinds audss_dmac (unpreparing
CLK_DMAC_AXI), and pm_runtime_put_sync() suspends audss_cru before
device_unbind_cleanup() -> devres_release_all() runs
sky1_audss_irq_teardown():
drivers/clk/cix/clk-sky1-audss.c:sky1_audss_irq_teardown() {
struct sky1_audss_clks_priv *priv = data;
regmap_update_bits(priv->regmap_cru, INFO_AP_IRQC,
INFO_AP_IRQC_DMAC_MASK, 0);
irq_set_chained_handler_and_data(priv->parent_irq, NULL, NULL);
irq_domain_remove(priv->irq_domain);
}
In addition, sky1_audss_irq_init() does not call irq_domain_set_pm_device()
and leaves GIC_SPI 230 active while suspended, so sky1_audss_irq_teardown(),
sky1_audss_irq_mask(), sky1_audss_irq_unmask(), or sky1_audss_irq_handler()
can access INFO_AP_IRQC while priv->rst_noc is asserted and the parent
clocks are disabled.
[Severity: High]
Could routing the level-high GIC_SPI 230 interrupt through
sky1_audss_irq_handler() and handle_untracked_irq() cause an interrupt storm
or spurious interrupt replay?
In drivers/clk/cix/clk-sky1-audss.c, sky1_audss_irq_chip provides only
.irq_mask and .irq_unmask, and sky1_audss_irq_map() sets
handle_untracked_irq without IRQ_DISABLE_UNLAZY or IRQ_NOTHREAD:
drivers/clk/cix/clk-sky1-audss.c:
static struct irq_chip sky1_audss_irq_chip = {
.name = "sky1-audss-dmac",
.irq_mask = sky1_audss_irq_mask,
.irq_unmask = sky1_audss_irq_unmask,
.flags = IRQCHIP_SKIP_SET_WAKE,
};
static int sky1_audss_irq_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hwirq)
{
if (hwirq >= SKY1_AUDSS_DMAC_IRQS)
return -EINVAL;
irq_set_chip_data(irq, d->host_data);
irq_set_chip_and_handler(irq, &sky1_audss_irq_chip,
handle_untracked_irq);
irq_set_noprobe(irq);
return 0;
}
When sky1_audss_irq_handler() runs, it reads the gate mask in INFO_AP_IRQC
and dispatches every unmasked channel bit:
drivers/clk/cix/clk-sky1-audss.c:sky1_audss_irq_handler() {
if (!regmap_read(priv->regmap_cru, INFO_AP_IRQC, &val)) {
enabled = val & INFO_AP_IRQC_DMAC_MASK;
for_each_set_bit(hwirq, &enabled, SKY1_AUDSS_DMAC_IRQS)
generic_handle_domain_irq(priv->irq_domain, hwirq);
}
}
Because handle_untracked_irq() in kernel/irq/chip.c returns early when
!irq_can_handle(desc) without calling mask_irq(desc), and does not mask
IRQS_ONESHOT interrupts:
1. If disable_irq() or suspend_device_irqs() lazily disables a channel, its
bit in INFO_AP_IRQC stays set. If that channel asserts its level-high
interrupt, handle_untracked_irq() neither masks INFO_AP_IRQC nor runs
d350_irq() to clear CH_STATUS, leaving GIC_SPI 230 asserted in an
infinite hard-IRQ loop.
2. Under CONFIG_PREEMPT_RT or threadirqs, request_irq() in
d350_alloc_chan_resources() is force-threaded with IRQF_ONESHOT, and
handle_untracked_irq() leaves the interrupt unmasked while waking the
IRQ thread, re-triggering GIC_SPI 230 continuously.
3. When any enabled channel fires, sky1_audss_irq_handler() calls
generic_handle_domain_irq() for every lazily-disabled sibling channel
still enabled in INFO_AP_IRQC, which sets IRQS_PENDING on idle channels
and causes a spurious replay on enable_irq() or resume_device_irqs().
[Severity: High]
Does accessing priv->regmap_cru from sky1_audss_irq_mask(),
sky1_audss_irq_unmask(), and sky1_audss_irq_handler() trigger an invalid
wait context warning?
In drivers/clk/cix/clk-sky1-audss.c, sky1_audss_regmap_config does not set
.use_raw_spinlock = true:
drivers/clk/cix/clk-sky1-audss.c:
static const struct regmap_config sky1_audss_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
};
As a result, __regmap_init() configures priv->regmap_cru to use a non-raw
spinlock_t (which becomes a sleeping lock on CONFIG_PREEMPT_RT). When
audss_dmac requests or handles its channel interrupts:
- sky1_audss_irq_mask() and sky1_audss_irq_unmask() call
regmap_update_bits(priv->regmap_cru, INFO_AP_IRQC, ...) while the IRQ core
holds desc->lock (a raw_spinlock_t).
- sky1_audss_irq_handler() runs as a chained handler in hard-IRQ context and
calls regmap_read(priv->regmap_cru, INFO_AP_IRQC, &val).
Should sky1_audss_regmap_config set .use_raw_spinlock = true so that
priv->regmap_cru can be safely accessed from raw_spinlock_t and hard-IRQ
contexts?
[Severity: High]
Does sky1_audss_irq_teardown() leak the mapped virq descriptors and leave
dangling pointers when audss_cru is unbound?
When d350_probe() calls platform_get_irq() for the 8 audss_dmac channels,
of_irq_get() -> irq_create_of_mapping() populates priv->irq_domain's
revmap_tree and invokes sky1_audss_irq_map(), storing pointers to priv,
priv->irq_domain, and sky1_audss_irq_chip in each irq_desc.
However, sky1_audss_irq_teardown() calls irq_domain_remove() without first
disposing the mappings via irq_dispose_mapping():
drivers/clk/cix/clk-sky1-audss.c:sky1_audss_irq_teardown() {
struct sky1_audss_clks_priv *priv = data;
regmap_update_bits(priv->regmap_cru, INFO_AP_IRQC,
INFO_AP_IRQC_DMAC_MASK, 0);
irq_set_chained_handler_and_data(priv->parent_irq, NULL, NULL);
irq_domain_remove(priv->irq_domain);
}
In kernel/irq/irqdomain.c, irq_domain_remove() triggers
WARN_ON(!radix_tree_empty(&domain->revmap_tree)) and frees priv->irq_domain,
leaving the 8 allocated irq_desc descriptors in sparse_irqs with dangling
references to the freed domain, devm-freed priv data, and module rodata if
/proc/interrupts or /sys/kernel/irq/<virq>/chip_name is subsequently read.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261023093045.3248630-1-Jelly.Jia@cixtech.com?part=5
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v1 1/5] dt-bindings: soc: cix: Describe the Sky1 AUDSS DMA IRQ controller
2026-10-23 9:30 ` [PATCH v1 1/5] dt-bindings: soc: cix: Describe the Sky1 AUDSS DMA IRQ controller Jelly Jia
@ 2026-10-26 8:12 ` Krzysztof Kozlowski
2026-10-29 5:25 ` Jelly Jia
0 siblings, 1 reply; 13+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-26 8:12 UTC (permalink / raw)
To: Jelly Jia
Cc: vkoul, robh, krzk+dt, conor+dt, robin.murphy, sboyd, bmasney+clk,
jbrunet+clk, devicetree, Frank.Li, cix-kernel-upstream, dmaengine,
linux-arm-kernel, linux-kernel, linux-clk, joakim.zhang
On Fri, Oct 23, 2026 at 05:30:41PM +0800, Jelly Jia wrote:
> examples:
> - |
> - audss_cru: clock-controller@7110000 {
> + #include <dt-bindings/interrupt-controller/arm-gic.h>
> + #include <dt-bindings/interrupt-controller/irq.h>
> +
> + gic: interrupt-controller {
> + #address-cells = <2>;
> + #interrupt-cells = <4>;
> + interrupt-controller;
> + };
Drop node, not relevant.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v1 3/5] dt-bindings: dma: arm,dma-350: Document dma-channel-mask
2026-10-23 9:30 ` [PATCH v1 3/5] dt-bindings: dma: arm,dma-350: Document dma-channel-mask Jelly Jia
@ 2026-10-26 8:13 ` Krzysztof Kozlowski
0 siblings, 0 replies; 13+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-26 8:13 UTC (permalink / raw)
To: Jelly Jia
Cc: vkoul, robh, krzk+dt, conor+dt, robin.murphy, sboyd, bmasney+clk,
jbrunet+clk, devicetree, Frank.Li, cix-kernel-upstream, dmaengine,
linux-arm-kernel, linux-kernel, linux-clk, joakim.zhang
On Fri, Oct 23, 2026 at 05:30:43PM +0800, Jelly Jia wrote:
> Some DMA-350 integrations reserve channels for firmware or another
> processor. Allow the standard dma-channel-mask property so firmware can
> describe which channels Linux may use.
>
> DMA-350 implements at most eight channels, so one 32-bit cell is enough;
> limit the property to a single cell.
>
> Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
> ---
> Documentation/devicetree/bindings/dma/arm,dma-350.yaml | 3 +++
> 1 file changed, 3 insertions(+)
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v1 4/5] dmaengine: arm-dma350: Honour dma-channel-mask
2026-10-23 9:30 ` [PATCH v1 4/5] dmaengine: arm-dma350: Honour dma-channel-mask Jelly Jia
@ 2026-10-28 16:47 ` Frank Li
2026-10-29 5:18 ` Jelly Jia
0 siblings, 1 reply; 13+ messages in thread
From: Frank Li @ 2026-10-28 16:47 UTC (permalink / raw)
To: Jelly Jia
Cc: vkoul, robh, krzk+dt, conor+dt, robin.murphy, sboyd, bmasney+clk,
jbrunet+clk, devicetree, Frank.Li, cix-kernel-upstream, dmaengine,
linux-arm-kernel, linux-kernel, linux-clk, joakim.zhang
On Fri, Oct 23, 2026 at 05:30:44PM +0800, Jelly Jia wrote:
> DMA-350 channels may be reserved for firmware or another processor.
> Read the optional dma-channel-mask property and register only the channels
> whose bits are set. If the property is absent, preserve the existing
> behaviour and expose every hardware channel.
>
> Skip masked channels before accessing their channel registers or
> initializing their interrupts and virt-dma state. This avoids clearing
> or configuring channels reserved for firmware or another processor.
>
> Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
> ---
> drivers/dma/arm-dma350.c | 20 +++++++++++++++++++-
> 1 file changed, 19 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/dma/arm-dma350.c b/drivers/dma/arm-dma350.c
> index 9cb5b2f03113..32ec6f061ee9 100644
> --- a/drivers/dma/arm-dma350.c
> +++ b/drivers/dma/arm-dma350.c
> @@ -1205,9 +1205,10 @@ static int d350_probe(struct platform_device *pdev)
> struct clk *clk;
> struct d350 *dmac;
> void __iomem *base;
> + u32 chan_mask = U32_MAX;
> u32 reg;
> int ret, nchan, dw, aw, r, p;
> - bool coherent, memset;
> + bool coherent, has_chan_mask, memset;
>
> clk = devm_clk_get_optional_enabled(dev, NULL);
> if (IS_ERR(clk))
> @@ -1285,6 +1286,19 @@ static int d350_probe(struct platform_device *pdev)
> dmac->dma.device_config = d350_config;
> INIT_LIST_HEAD(&dmac->dma.channels);
>
> + /*
> + * dma-channel-mask is optional. Firmware-reserved channels stay
> + * clear; if the property is absent, Linux may use every channel.
> + */
> + has_chan_mask = device_property_present(dev, "dma-channel-mask");
> + if (has_chan_mask) {
> + ret = device_property_read_u32(dev, "dma-channel-mask",
> + &chan_mask);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "failed to read dma-channel-mask\n");
> + }
> +
> reg = readl_relaxed(base + DMANSECCTRL + NSEC_CTRL);
> writel_relaxed(reg | INTREN_ANYCHINTR_EN,
> base + DMANSECCTRL + NSEC_CTRL);
> @@ -1294,6 +1308,10 @@ static int d350_probe(struct platform_device *pdev)
> for (int i = 0; i < nchan; i++) {
> struct d350_chan *dch = &dmac->channels[i];
>
> + if (has_chan_mask &&
> + (i >= 32 || !(chan_mask & BIT(i))))
> + continue;
> +
dma-channel-mask is u32 array. If nchan >= 32, you should check
dma-channel-mask[1].
Frank
> dch->base = base + DMACH(i);
> writel_relaxed(CH_CMD_CLEAR, dch->base + CH_CMD);
>
> --
> 2.54.0
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v1 4/5] dmaengine: arm-dma350: Honour dma-channel-mask
2026-10-28 16:47 ` Frank Li
@ 2026-10-29 5:18 ` Jelly Jia
0 siblings, 0 replies; 13+ messages in thread
From: Jelly Jia @ 2026-10-29 5:18 UTC (permalink / raw)
To: Frank Li
Cc: vkoul, robh, krzk+dt, conor+dt, robin.murphy, sboyd, bmasney+clk,
jbrunet+clk, devicetree, Frank.Li, cix-kernel-upstream, dmaengine,
linux-arm-kernel, linux-kernel, linux-clk, joakim.zhang
Hi Frank,
On Wed, Oct 28, 2026 at 12:47:38PM -0400, Frank Li wrote:
> dma-channel-mask is u32 array. If nchan >= 32, you should check
> dma-channel-mask[1].
Thanks for reviewing. Yes, the common property is a u32 array.
For DMA-350, however, the TRM specifies:
"Number of DMA channels between 1 and 8 (NUM_CHANNELS)".
A single u32 therefore covers all supported channels, and patch 3/5
also restricts dma-channel-mask to maxItems: 1.
Best regards,
Jelly
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v1 1/5] dt-bindings: soc: cix: Describe the Sky1 AUDSS DMA IRQ controller
2026-10-26 8:12 ` Krzysztof Kozlowski
@ 2026-10-29 5:25 ` Jelly Jia
0 siblings, 0 replies; 13+ messages in thread
From: Jelly Jia @ 2026-10-29 5:25 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: vkoul, robh, krzk+dt, conor+dt, robin.murphy, sboyd, bmasney+clk,
jbrunet+clk, devicetree, Frank.Li, cix-kernel-upstream, dmaengine,
linux-arm-kernel, linux-kernel, linux-clk, joakim.zhang
Hi Krzysztof,
On Mon, Oct 26, 2026 at 09:12:09AM +0100, Krzysztof Kozlowski wrote:
> > + gic: interrupt-controller {
> > + #address-cells = <2>;
> > + #interrupt-cells = <4>;
> > + interrupt-controller;
> > + };
>
> Drop node, not relevant.
Agreed. I'll remove the GIC node and the corresponding interrupt-parent
reference from the example in v2.
Thanks for reviewing.
Best regards,
Jelly
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-10-29 5:25 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-23 9:30 [PATCH v1 0/5] soc: cix: Add the Sky1 AUDSS DMA-350 interrupt path Jelly Jia
2026-10-23 9:30 ` [PATCH v1 1/5] dt-bindings: soc: cix: Describe the Sky1 AUDSS DMA IRQ controller Jelly Jia
2026-10-26 8:12 ` Krzysztof Kozlowski
2026-10-29 5:25 ` Jelly Jia
2026-10-23 9:30 ` [PATCH v1 2/5] clk: cix: Handle Sky1 AUDSS DMA interrupt gates as an irqchip Jelly Jia
2026-10-23 9:53 ` sashiko-bot
2026-10-23 9:30 ` [PATCH v1 3/5] dt-bindings: dma: arm,dma-350: Document dma-channel-mask Jelly Jia
2026-10-26 8:13 ` Krzysztof Kozlowski
2026-10-23 9:30 ` [PATCH v1 4/5] dmaengine: arm-dma350: Honour dma-channel-mask Jelly Jia
2026-10-28 16:47 ` Frank Li
2026-10-29 5:18 ` Jelly Jia
2026-10-23 9:30 ` [PATCH v1 5/5] arm64: dts: cix: Add the Sky1 AUDSS DMA-350 integration Jelly Jia
2026-10-23 10:28 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox