[PATCH v1 2/5] clk: cix: Handle Sky1 AUDSS DMA interrupt gates as an irqchip

From: Jelly Jia

Date: Fri Oct 23 2026 - 05:33:59 EST


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@xxxxxxxxxxx>
---
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