From: sashiko-bot@kernel.org
To: joakim.zhang@cixtech.com
Cc: conor+dt@kernel.org, devicetree@vger.kernel.org, robh@kernel.org
Subject: Re: [PATCH v2 4/4] ASoC: cdns: Add Cadence I2S-MC controller driver
Date: Mon, 28 Sep 2026 05:48:18 +0000 [thread overview]
Message-ID: <20260928054818.6BDAA1F000FF@smtp.kernel.org> (raw)
In-Reply-To: <20260928053613.2158446-5-joakim.zhang@cixtech.com>
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] ASoC: cdns: inverted clock provider/consumer configuration
- [High] ASoC: cdns: unprotected clock reparenting disrupts concurrent streams
- [High] ASoC: cdns: PM usage counter and clock leaks in probe error paths and unbind
- [High] ASoC: cdns: synchronizing unit resets bypassed by incomplete regmap mask
- [High] ASoC: cdns: unsafe read-modify-write on interrupt status register
- [High] ASoC: cdns: inconsistent DATA_WS_DEL alignment logic across formats
- [Medium] ASoC: cdns: unbalanced disable_irq() corrupts global IRQ descriptor
--
commit 45344ef2691191bb925ca3e036d7808018f75305
Author: Joakim Zhang <joakim.zhang@cixtech.com>
ASoC: cdns: Add Cadence I2S-MC controller driver
Add support for the Cadence I2S-MC multi-channel controller found in
the CIX SKY1 audio subsystem. The driver registers the dmaengine PCM
helper, selects the audio reference clock parent and implements runtime
PM and system suspend/resume.
> diff --git a/sound/soc/cdns/cdns-i2s-mc.c b/sound/soc/cdns/cdns-i2s-mc.c
> new file mode 100644
> --- /dev/null
> +++ b/sound/soc/cdns/cdns-i2s-mc.c
[ ... ]
> +static irqreturn_t cdns_i2s_mc_isr(int irq, void *devid)
> +{
> + struct cdns_i2s_mc_priv *i2s_mc_priv = (struct cdns_i2s_mc_priv *)devid;
> + struct device *dev = i2s_mc_priv->dev;
> + u32 stat;
> +
> + if (regmap_read(i2s_mc_priv->regmap, I2S_INTR_STAT, &stat))
> + return IRQ_NONE;
> +
> + stat &= I2S_INTR_STAT_TDATA_UNDERR | I2S_INTR_STAT_RDATA_OVERR;
> + if (!stat)
> + return IRQ_NONE;
> +
> + /* Clear the status */
> + regmap_update_bits(i2s_mc_priv->regmap, I2S_INTR_STAT, stat, 0);
[Severity: High]
Does this read-modify-write cycle correctly handle write-1-to-clear
semantics? Using regmap_update_bits() reads the register, writes 0 to the
handled bits, and writes the preserved 1s to unhandled bits. If the
register is write-1-to-clear, this could fail to clear handled interrupts
and erroneously clear newly arrived interrupts without handling them. Could
a direct regmap_write() be used here instead?
> +
> + /* Transmitter status */
> + if (stat & I2S_INTR_STAT_TDATA_UNDERR)
> + dev_dbg(dev, "isr: tx data underrun\n");
[ ... ]
> +static int cdns_i2s_mc_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
> +{
[ ... ]
> + /* DAI hardware audio formats */
> + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
> + case SND_SOC_DAIFMT_I2S:
> + dev_conf |= I2S_DEV_CONF_TRAN_I2S_ALIGN_LR;
> + dev_conf &= ~(I2S_DEV_CONF_TRAN_APB_ALIGN_LR |
> + I2S_DEV_CONF_TRAN_DATA_WS_DEL |
> + I2S_DEV_CONF_TRAN_WS_DSP_MODE);
> + break;
> + case SND_SOC_DAIFMT_RIGHT_J:
> + dev_conf |= I2S_DEV_CONF_TRAN_DATA_WS_DEL;
> + dev_conf &= ~(I2S_DEV_CONF_TRAN_I2S_ALIGN_LR |
> + I2S_DEV_CONF_TRAN_APB_ALIGN_LR |
> + I2S_DEV_CONF_TRAN_WS_DSP_MODE);
> + break;
> + case SND_SOC_DAIFMT_LEFT_J:
> + dev_conf |= I2S_DEV_CONF_TRAN_I2S_ALIGN_LR | I2S_DEV_CONF_TRAN_DATA_WS_DEL;
> + dev_conf &= ~(I2S_DEV_CONF_TRAN_APB_ALIGN_LR |
> + I2S_DEV_CONF_TRAN_WS_DSP_MODE);
> + break;
> + case SND_SOC_DAIFMT_DSP_A:
> + dev_conf |= I2S_DEV_CONF_TRAN_I2S_ALIGN_LR | I2S_DEV_CONF_TRAN_DATA_WS_DEL |
> + I2S_DEV_CONF_TRAN_WS_DSP_MODE;
> + dev_conf &= ~I2S_DEV_CONF_TRAN_APB_ALIGN_LR;
> + break;
> + case SND_SOC_DAIFMT_DSP_B:
> + dev_conf |= I2S_DEV_CONF_TRAN_I2S_ALIGN_LR | I2S_DEV_CONF_TRAN_WS_DSP_MODE;
> + dev_conf &= ~(I2S_DEV_CONF_TRAN_APB_ALIGN_LR |
> + I2S_DEV_CONF_TRAN_DATA_WS_DEL);
> + break;
[Severity: High]
Is this delay phase mapping contradictory? The I2S_DEV_CONF_TRAN_DATA_WS_DEL
bit is cleared for SND_SOC_DAIFMT_I2S (1-clock delay) but set for
SND_SOC_DAIFMT_DSP_A (also 1-clock delay). Conversely, it is set for
SND_SOC_DAIFMT_LEFT_J (0-clock delay) but cleared for
SND_SOC_DAIFMT_DSP_B (also 0-clock delay). This seems to result in
misaligned phase for at least two of the supported formats.
> + default:
> + return -EINVAL;
> + }
> +
> + /* DAI hardware clock masters */
> + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
> + case SND_SOC_DAIFMT_CBC_CFC:
> + ctrl &= ~I2S_CTRL_T_MS;
> + break;
[Severity: High]
Does this invert the master/slave configuration? For SND_SOC_DAIFMT_CBC_CFC
(Codec Bit/Frame Clock Consumer), the CPU DAI must act as the clock
provider (Master), but this clears the I2S_CTRL_T_MS bit.
[ ... ]
> + } else if (cpu_dai->id == I2S_MC_AIF2) {
[ ... ]
> + /* DAI hardware clock masters */
> + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
> + case SND_SOC_DAIFMT_CBC_CFC:
> + ctrl &= ~I2S_CTRL_R_MS;
> + break;
[Severity: High]
Similarly, this clears the master bit for the receiver DAI when it should
be set for SND_SOC_DAIFMT_CBC_CFC.
[ ... ]
> +static int cdns_i2s_mc_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params,
> + struct snd_soc_dai *cpu_dai)
> +{
[ ... ]
> + if (rate % 8000 == 0) {
> + /* Sampling rate is a multiple of 8KHz, select "audio-clk0" */
> + clk_parent = i2s_mc_priv->clks[AUDIO_CLK0];
> +
> + ret = clk_set_parent(i2s_mc_priv->clk_i2s, clk_parent);
[Severity: High]
Can this reparenting disrupt a concurrent audio stream? Since both playback
(I2S_MC_AIF1) and capture (I2S_MC_AIF2) share the single clk_i2s clock,
starting a new stream will dynamically reparent the bit clock while the
companion stream might be active, potentially causing data corruption.
[ ... ]
> + regmap_read(i2s_mc_priv->regmap, I2S_CTRL, &ctrl);
> + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
> + is_master_mode = ctrl & I2S_CTRL_T_MS;
> + else
> + is_master_mode = ctrl & I2S_CTRL_R_MS;
> +
> + if (is_master_mode) {
> + if (i2s_clk_rate < rate * 2 * slot_width * I2S_CLK_RATIO_MIN_MASTER) {
[Severity: High]
Because I2S_CTRL_T_MS and I2S_CTRL_R_MS are erroneously cleared for
CBC_CFC in cdns_i2s_mc_set_fmt(), does this validate the clock frequency
against the wrong constraints? The inverted logic cascades here, checking
the slave mode multipliers when it should be checking the master mode.
[ ... ]
> +static int cdns_i2s_mc_dai_probe(struct snd_soc_dai *cpu_dai)
> +{
> + struct cdns_i2s_mc_priv *i2s_mc_priv = snd_soc_dai_get_drvdata(cpu_dai);
> + u32 ctrl = 0, cid_ctrl = 0;
> +
> + /*
> + * Transceiver disable
> + * Transceiver clock disable
> + * All interrupts masked
> + * Reset for transmitter synchronizing unit
> + * Reset for receiver synchronizing unit
> + */
> + ctrl |= FIELD_PREP(I2S_CTRL_I2S_EN, 0);
> + ctrl &= ~(I2S_CTRL_TSYNC_RST | I2S_CTRL_RSYNC_RST);
> + cid_ctrl |= I2S_CID_CTRL_STROBE_TS | I2S_CID_CTRL_STROBE_RS;
> + cid_ctrl &= ~I2S_CID_CTRL_INTREQ_MASK;
> +
> + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, I2S_CTRL_I2S_EN, ctrl);
[Severity: High]
Does this mask inadvertently bypass the hardware reset sequence? The
I2S_CTRL_I2S_EN mask only covers bits 0-7 (GENMASK(7, 0)), completely
filtering out the I2S_CTRL_TSYNC_RST (bit 25) and I2S_CTRL_RSYNC_RST
(bit 26) modifications made to ctrl just above.
[ ... ]
> +static int cdns_i2s_mc_probe(struct platform_device *pdev)
> +{
[ ... ]
> + pm_runtime_get_noresume(&pdev->dev);
> + pm_runtime_set_active(&pdev->dev);
> + ret = devm_pm_runtime_enable(&pdev->dev);
> + if (ret)
> + return dev_err_probe(&pdev->dev, ret, "Failed to enable runtime PM\n");
> +
> + ret = cdns_i2s_mc_clks_enable(i2s_mc_priv);
> + if (ret) {
> + dev_err_probe(&pdev->dev, ret, "Failed to enable clocks\n");
> + pm_runtime_put_noidle(&pdev->dev);
> + return ret;
> + }
> +
> + cdns_i2s_mc_rst(i2s_mc_priv);
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0)
> + return irq;
[Severity: High]
Does this error path leak the PM usage counter and leave the clocks enabled?
The pm_runtime_get_noresume() and cdns_i2s_mc_clks_enable() calls complete
successfully above, but this return does not clean them up.
> + i2s_mc_priv->irq = irq;
> +
> + ret = devm_request_irq(&pdev->dev, irq, cdns_i2s_mc_isr, 0,
> + dev_name(&pdev->dev), i2s_mc_priv);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to request irq: %d\n", ret);
> + pm_runtime_put_noidle(&pdev->dev);
> + return ret;
> + }
[Severity: High]
Similar to the issue above, this drops the PM counter but appears to miss
calling cdns_i2s_mc_clks_disable(), which permanently leaks the enabled
clocks.
[ ... ]
> +static int cdns_i2s_mc_runtime_suspend(struct device *dev)
> +{
> + struct cdns_i2s_mc_priv *i2s_mc_priv = dev_get_drvdata(dev);
> +
> + /*
> + * Disable the IRQ before switching the regcache to cache-only, so an
> + * in-flight handler can never access the registers through a
> + * cache-only regmap. disable_irq() waits for the handler to finish.
> + */
> + disable_irq(i2s_mc_priv->irq);
[Severity: Medium]
Could this lead to an unbalanced IRQ disable depth? If the probe sequence
fails later (e.g., at devm_snd_soc_register_component) or if the device
is unbound while suspended, devm will free the IRQ while it is still deeply
disabled. Freeing a disabled IRQ does not reverse the global descriptor's
disable depth counter, leaving it permanently disabled for future requesters.
[ ... ]
> +static struct platform_driver cdns_i2s_mc_driver = {
> + .probe = cdns_i2s_mc_probe,
> + .driver = {
> + .name = DRV_NAME,
> + .pm = pm_ptr(&cdns_i2s_mc_pm_ops),
> + .of_match_table = cdns_i2s_mc_of_match,
> + },
> +};
[Severity: High]
Is this missing a .remove callback? Without one, unbinding the module
will leave the clocks running permanently and leak hardware resources.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928053613.2158446-1-joakim.zhang@cixtech.com?part=4
next prev parent reply other threads:[~2026-09-28 5:48 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-28 5:36 [PATCH v2 0/4] ASoC: cdns: Add Cadence I2S-SC and I2S-MC controller support joakim.zhang
2026-09-28 5:36 ` [PATCH v2 1/4] ASoC: dt-bindings: Add Cadence I2S-SC controller joakim.zhang
2026-09-28 5:36 ` [PATCH v2 2/4] ASoC: cdns: Add Cadence I2S-SC controller driver joakim.zhang
2026-09-28 5:47 ` sashiko-bot
2026-10-07 9:29 ` Ajay Kumar Nandam
2026-10-09 6:10 ` Joakim Zhang (张强庆)
2026-09-28 5:36 ` [PATCH v2 3/4] ASoC: dt-bindings: Add Cadence I2S-MC controller joakim.zhang
2026-09-28 5:36 ` [PATCH v2 4/4] ASoC: cdns: Add Cadence I2S-MC controller driver joakim.zhang
2026-09-28 5:48 ` sashiko-bot [this message]
2026-10-07 9:53 ` Ajay Kumar Nandam
2026-10-09 6:11 ` Joakim Zhang (张强庆)
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=20260928054818.6BDAA1F000FF@smtp.kernel.org \
--to=sashiko-bot@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=joakim.zhang@cixtech.com \
--cc=robh@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/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