Devicetree
 help / color / mirror / Atom feed
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

  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