From: Jelly Jia <Jelly.Jia@cixtech.com>
To: vkoul@kernel.org, robh@kernel.org, krzk+dt@kernel.org,
conor+dt@kernel.org, robin.murphy@arm.com, sboyd@kernel.org,
bmasney+clk@redhat.com, jbrunet+clk@baylibre.com
Cc: devicetree@vger.kernel.org, Frank.Li@kernel.org,
cix-kernel-upstream@cixtech.com, dmaengine@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, Jelly.Jia@cixtech.com,
linux-clk@vger.kernel.org, joakim.zhang@cixtech.com
Subject: [PATCH v2 2/5] clk: cix: Handle Sky1 AUDSS DMA interrupt gates as an irqchip
Date: Wed, 4 Nov 2026 20:44:34 +0800 [thread overview]
Message-ID: <20261104124437.296753-3-Jelly.Jia@cixtech.com> (raw)
In-Reply-To: <20261104124437.296753-1-Jelly.Jia@cixtech.com>
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. Poll
each enabled child's DMA handler to check its channel-local status. Use
level flow handling so disabled and oneshot-threaded interrupts remain
masked, and mark the children as polled because IRQ_NONE is expected
when a different channel caused the parent interrupt.
Use a raw regmap lock for the hard-IRQ callbacks and keep the CRU powered
while its IRQ resources are in use or being torn down. Quiesce the parent
IRQ before runtime suspend and move system sleep handling to noirq.
Save and restore the interrupt gates and software reset state with the
other CRU context. Dispose of child mappings before removing the domain.
Signed-off-by: Jelly Jia <Jelly.Jia@cixtech.com>
---
drivers/clk/cix/Kconfig | 1 +
drivers/clk/cix/clk-sky1-audss.c | 181 ++++++++++++++++++++++++++++++-
2 files changed, 179 insertions(+), 3 deletions(-)
diff --git a/drivers/clk/cix/Kconfig b/drivers/clk/cix/Kconfig
index b057ef85fab6..de463ae63cb4 100644
--- a/drivers/clk/cix/Kconfig
+++ b/drivers/clk/cix/Kconfig
@@ -6,6 +6,7 @@ config CLK_SKY1_AUDSS
tristate "Cixtech Sky1 Audio Subsystem Clock Driver"
depends on ARCH_CIX || COMPILE_TEST
select AUXILIARY_BUS
+ select IRQ_DOMAIN
select REGMAP_MMIO
select RESET_CONTROLLER
help
diff --git a/drivers/clk/cix/clk-sky1-audss.c b/drivers/clk/cix/clk-sky1-audss.c
index e096b8121df3..9c8457334e17 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,7 +23,11 @@
#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 INFO_SW_RESETN 0x78
#define SKY1_AUDSS_CLK_PARENTS_CNT 4
#define SKY1_AUDSS_NUM_CLKS (CLK_MCLK4 + 1)
@@ -28,7 +37,9 @@ 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 },
+ { INFO_SW_RESETN, 0 },
};
static const char * const sky1_audss_clk_names[SKY1_AUDSS_CLK_PARENTS_CNT] = {
@@ -519,6 +530,7 @@ static const struct regmap_config sky1_audss_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
+ .use_raw_spinlock = true,
};
struct sky1_audss_clks_priv {
@@ -529,6 +541,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 +942,147 @@ 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,
+ .irq_disable = sky1_audss_irq_mask,
+ .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_level_irq);
+ /*
+ * The CRU exposes gates, not per-channel pending status. The parent
+ * handler polls every enabled channel, so IRQ_NONE is expected and
+ * must not trigger spurious interrupt detection. Keep level flow
+ * control so oneshot IRQ threads run with their channel gate masked.
+ */
+ irq_set_status_flags(irq, IRQ_LEVEL | IRQ_IS_POLLED);
+ 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;
+ unsigned int hwirq;
+ unsigned int virq;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(priv->dev);
+ if (ret < 0)
+ dev_warn(priv->dev, "failed to resume CRU for IRQ teardown: %d\n", ret);
+
+ disable_irq(priv->parent_irq);
+ if (ret >= 0)
+ 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);
+ synchronize_irq(priv->parent_irq);
+ irq_clear_status_flags(priv->parent_irq, IRQ_DISABLE_UNLAZY);
+ priv->parent_irq = 0;
+
+ for (hwirq = 0; hwirq < SKY1_AUDSS_DMAC_IRQS; hwirq++) {
+ virq = irq_find_mapping(priv->irq_domain, hwirq);
+ if (!virq)
+ continue;
+
+ /* Disassociation must not access an unpowered CRU either. */
+ if (ret < 0)
+ irq_set_chip(virq, NULL);
+ irq_dispose_mapping(virq);
+ }
+
+ irq_domain_remove(priv->irq_domain);
+ priv->irq_domain = NULL;
+ if (ret >= 0)
+ pm_runtime_put_sync(priv->dev);
+}
+
+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;
+
+ ret = regmap_update_bits(priv->regmap_cru, INFO_AP_IRQC,
+ INFO_AP_IRQC_DMAC_MASK, 0);
+ if (ret)
+ return ret;
+
+ priv->irq_domain = irq_domain_create_linear(of_fwnode_handle(dev->of_node),
+ SKY1_AUDSS_DMAC_IRQS,
+ &sky1_audss_irq_ops, priv);
+ if (!priv->irq_domain)
+ return -ENOMEM;
+
+ irq_domain_set_pm_device(priv->irq_domain, dev);
+
+ priv->parent_irq = irq;
+ /* The chained handler cannot implement lazy-disable masking. */
+ irq_set_status_flags(irq, IRQ_DISABLE_UNLAZY);
+ 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 +1265,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;
@@ -1125,6 +1287,11 @@ static int __maybe_unused sky1_audss_clk_runtime_suspend(struct device *dev)
const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data;
unsigned long flags;
int i;
+ int ret;
+
+ /* Quiesce the chained handler before registers become inaccessible. */
+ if (priv->parent_irq)
+ disable_irq(priv->parent_irq);
spin_lock_irqsave(&priv->lock, flags);
for (i = 0; i < devtype_data->reg_save_size; i++)
@@ -1132,7 +1299,12 @@ static int __maybe_unused sky1_audss_clk_runtime_suspend(struct device *dev)
devtype_data->reg_save[i][0], &devtype_data->reg_save[i][1]);
spin_unlock_irqrestore(&priv->lock, flags);
- reset_control_assert(priv->rst_noc);
+ ret = reset_control_assert(priv->rst_noc);
+ if (ret) {
+ if (priv->parent_irq)
+ enable_irq(priv->parent_irq);
+ return ret;
+ }
sky1_audss_clks_disable(priv);
@@ -1164,14 +1336,17 @@ static int __maybe_unused sky1_audss_clk_runtime_resume(struct device *dev)
devtype_data->reg_save[i][0], devtype_data->reg_save[i][1]);
spin_unlock_irqrestore(&priv->lock, flags);
+ if (priv->parent_irq)
+ enable_irq(priv->parent_irq);
+
return 0;
}
static const struct dev_pm_ops sky1_audss_clk_pm_ops = {
SET_RUNTIME_PM_OPS(sky1_audss_clk_runtime_suspend,
sky1_audss_clk_runtime_resume, NULL)
- SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
- pm_runtime_force_resume)
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
};
static const struct sky1_audss_clks_devtype_data sky1_devtype_data = {
--
2.54.0
next prev parent reply other threads:[~2026-11-04 12:50 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-04 12:44 [PATCH v2 0/5] soc: cix: Add the Sky1 AUDSS DMA-350 interrupt path Jelly Jia
2026-11-04 12:44 ` [PATCH v2 1/5] dt-bindings: soc: cix: Describe the Sky1 AUDSS DMA IRQ controller Jelly Jia
2026-11-04 12:44 ` Jelly Jia [this message]
2026-11-04 12:44 ` [PATCH v2 3/5] dt-bindings: dma: arm,dma-350: Document dma-channel-mask Jelly Jia
2026-11-04 12:44 ` [PATCH v2 4/5] dmaengine: arm-dma350: Honour dma-channel-mask Jelly Jia
2026-11-04 19:44 ` Frank Li
2026-11-04 12:44 ` [PATCH v2 5/5] arm64: dts: cix: Add the Sky1 AUDSS DMA-350 integration Jelly Jia
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261104124437.296753-3-Jelly.Jia@cixtech.com \
--to=jelly.jia@cixtech.com \
--cc=Frank.Li@kernel.org \
--cc=bmasney+clk@redhat.com \
--cc=cix-kernel-upstream@cixtech.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=dmaengine@vger.kernel.org \
--cc=jbrunet+clk@baylibre.com \
--cc=joakim.zhang@cixtech.com \
--cc=krzk+dt@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-clk@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=robh@kernel.org \
--cc=robin.murphy@arm.com \
--cc=sboyd@kernel.org \
--cc=vkoul@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®