Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Neo Chang" <YLCHANG2@nuvoton.com>
Cc: linux-sound@vger.kernel.org, devicetree@vger.kernel.org,
	robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH v2 2/2] ASoC: codecs: nau8310: Add support for NAU83G10 amplifier
Date: Mon, 09 Nov 2026 05:44:52 +0000	[thread overview]
Message-ID: <sashiko-outbox-204454@kernel.org> (raw)
In-Reply-To: <20261109051251.471879-3-YLCHANG2@nuvoton.com>

Thank you for your contribution! Sashiko AI review found 16 potential
issues to consider:
- [High] ASoC: nau8310: type confusion in nau8310_biq_coeff_put()
- [High] ASoC: nau8310: DAPM state desync and work handling in
  nau8310_dsp_init()
- [High] ASoC: nau8310: uncanceled load_fw_work on probe error and
  component remove
- [Medium] ASoC: nau8310: unchecked regmap_read() in
  nau8310_biq_coeff_get()
- [Medium] ASoC: nau8310: uninitialized kcs_rst on empty reply in
  nau8310_dsp_kcs_setup()
- [Medium] ASoC: nau8310: races on bias_level and clock in
  nau8310_dsp_snd_soc_dapm_put()
- [Medium] ASoC: nau8310: unmuting DAC on error in
  nau8310_dsp_set_kcs_setup()
- [Medium] ASoC: nau8310: broken Sense DAPM pin and ADCDAT_OE lifecycle
  management
- [Medium] ASoC: nau8310: nau8310_set_tdm_slot() bitwise-ORs encoded TX
  slot selectors
- [Medium] ASoC: nau8310: broken clock config for 64kHz/96kHz,
  NAU8310_RATES, and 1x/2x mult
- [Medium] ASoC: nau8310: nau8310_resume() skips firmware load when DSP
  is bypassed
- [Medium] ASoC: nau8310: race between load_fw_work and DAPM clock
  events on resume
- [Medium] ASoC: nau8310: nau8310_dac_oversampl_enum exposes reserved
  register value
- [Low] ASoC: nau8310: broken route cleanup in
  nau8310_dsp_setup_controls()
- [Low] ASoC: nau8310: off-by-one KCS size limit in
  NAU8310_DSP_KCS_DAT_LEN_MAX
- [Low] ASoC: nau8310: wrong dB scale unit in adc_vol_tlv definition
--

commit b7ad2d10b4218feb399fff7738dd084543f6cdfc
Author: Neo Chang <YLCHANG2@nuvoton.com>

ASoC: codecs: nau8310: Add support for NAU83G10 amplifier

This commit adds an ASoC codec driver for the Nuvoton NAU83G10/20 mono
boosted Class-D audio amplifier with integrated DSP and I/V-sense support.

> diff --git a/sound/soc/codecs/nau8310-dsp.c b/sound/soc/codecs/nau8310-dsp.c
> new file mode 100644
> index 0000000000000..bdf49709dfef9
> --- /dev/null
> +++ b/sound/soc/codecs/nau8310-dsp.c

[ ... ]

> +static int nau8310_reply_from_dsp(struct snd_soc_component *cp,
> +	const struct nau8310_cmd_info *cmd_info, int data_size,
> +	void *data)
> +{
> +	struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
> +	struct device *dev = cp->dev;
> +	int ret, frag_len, pad_len_exp, data_count = 0;
> +
> +	ret = nau8310_dsp_replied(nau8310, &frag_len);
> +	if (ret)
> +		return ret;
> +	if (!frag_len || !cmd_info->reply_data)
> +		return 0;
> +	if (!data || frag_len == 1)
> +		return -EINVAL;

[ ... ]

> +static int nau8310_dsp_kcs_setup(struct snd_soc_component *cp, int offset, int size,
> +	const void *data)
> +{
> +	u8 *data_buf = (u8 *)data;
> +	unsigned int kcs_rst;
> +	int retries = 0, ret, data_len, data_rem, addr_offset;
> +
> +	/* Limit full load of KCS_SETUP data and not beyond 3kB offset. */
> +	if (!data || size > NAU8310_DSP_KCS_DAT_LEN_MAX ||
> +		offset > NAU8310_DSP_KCS_OFFSET_MAX)
> +		return -EINVAL;
> +
> +	/* sending fragments for KCS setup */
> +	addr_offset = offset;
> +	data_rem = size;
> +
> +	while (data_rem) {
> +		data_len = min(data_rem, NAU8310_DSP_KCS_TX_MAX);
> +
> +		ret = nau8310_dsp_exec_command(cp, NAU8310_DSP_CMD_SET_KCS_SETUP,
> +			addr_offset, data_len, (void *)data_buf, 0, &kcs_rst);
> +		if (ret) {
> +			if (retries++ < NAU8310_DSP_RETRY_MAX)
> +				continue;
> +			return ret;
> +		}
> +
> +		data_buf += (u8)data_len;
> +		addr_offset += data_len;
> +		data_rem -= data_len;
> +
> +		/* checking KCS result */
> +		ret = nau8310_dsp_exec_command(cp, NAU8310_DSP_CMD_GET_KCS_RSLTS,
> +			0, NAU8310_DSP_DATA_BYTE, NULL, NAU8310_DSP_DATA_BYTE, &kcs_rst);
> +		if (ret)
> +			return ret;
> +		if (kcs_rst != NAU8310_DSP_KCS_RSLTS_SUCCESS)
> +			return -EINVAL;
> +	}
> +
> +	return 0;
> +}

[Severity: Medium]
Can nau8310_dsp_kcs_setup() evaluate an uninitialized kcs_rst if the DSP
replies with frag_len == 0?  When frag_len is 0, nau8310_reply_from_dsp()
returns 0 even when cmd_info->reply_data is true, leaving kcs_rst
unmodified on the stack.

> +
> +static int nau8310_dsp_snd_soc_dapm_put(struct snd_kcontrol *kcontrol,
> +	struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
> +	struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
> +	struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
> +	struct soc_mixer_control *mc =
> +		(struct soc_mixer_control *)kcontrol->private_value;
> +	unsigned int mask = (1 << fls(mc->max)) - 1;
> +	unsigned int val;
> +	int ret;
> +
> +	if (snd_soc_dapm_get_bias_level(dapm) > SND_SOC_BIAS_STANDBY) {
> +		dev_warn_ratelimited(nau8310->dev, "changing path is not allowed during playback");
> +		return -EBUSY;
> +	}
> +
> +	val = (ucontrol->value.integer.value[0] & mask);
> +
> +	mutex_lock(&nau8310->lock);
> +	if (val == NAU8310_DAC_SRC_DSP && !nau8310->load_fw_done) {
> +		dev_warn_ratelimited(nau8310->dev, "Cannot enable DSP: Firmware not ready or disabled");
> +		mutex_unlock(&nau8310->lock);
> +		return -EBUSY;
> +	}
> +	mutex_unlock(&nau8310->lock);
> +
> +	ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol);
> +
> +	if (val == NAU8310_DAC_SRC_BYPASS) {
> +		/* Switch the clock source of DSP to MCLK. */
> +		regmap_update_bits(nau8310->regmap, NAU8310_R04_ENA_CTRL,
> +			NAU8310_DSP_SEL_OSC, 0);
> +	}
> +
> +	return ret;
> +}

[Severity: Medium]
Can nau8310_dsp_snd_soc_dapm_put() race with stream startup and firmware
loading?

CPU0                                      kworker
nau8310_dsp_snd_soc_dapm_put()            nau8310_load_fw_work()
  snd_soc_dapm_get_bias_level(dapm)
  /* val == NAU8310_DAC_SRC_BYPASS */
  mutex_lock(&nau8310->lock)
  mutex_unlock(&nau8310->lock)
                                          mutex_lock(&nau8310->lock)
                                          nau8310_dsp_init()
  snd_soc_dapm_put_volsw()
  regmap_update_bits(...,
    NAU8310_DSP_SEL_OSC, 0)               <-- switches DSP clock off OSC
                                              during firmware init

snd_soc_dapm_get_bias_level(dapm) is checked without holding
card->dapm_mutex and before waiting on nau8310->lock, so playback can
start before snd_soc_dapm_put_volsw() runs.  In addition, when val is
NAU8310_DAC_SRC_BYPASS, NAU8310_DSP_SEL_OSC is cleared outside
nau8310->lock without flushing load_fw_work.

[ ... ]

> +static int nau8310_dsp_set_kcs_setup(struct snd_soc_component *cp)
> +{

[ ... ]

> +err_loaded:
> +	regmap_update_bits(regmap, NAU8310_R13_MUTE_CTRL, NAU8310_SOFT_MUTE, 0);
> +	if (fw)
> +		release_firmware(fw);
> +err:
> +	nau8310->load_fw_done = false;
> +	return ret;
> +}

[Severity: Medium]
As reported by Mark Brown on v1 [1], doesn't clearing NAU8310_SOFT_MUTE to
0 on the err_loaded path unmute the DAC when firmware setup fails?  In
nau8310_dac_event(), setting NAU8310_SOFT_MUTE mutes the amplifier and 0
unmutes it.

> +
> +int nau8310_dsp_init(struct snd_soc_component *cp)
> +{
> +	struct regmap *regmap = cp->regmap;
> +
> +	nau8310_sw_reset_chip(regmap);
> +
> +	/* wait for the power ready */
> +	msleep(120);
> +
> +	regmap_update_bits(regmap, NAU8310_R1A_DSP_CORE_CTRL2,
> +		NAU8310_DSP_RUNSTALL | NAU8310_DAC_SEL_DSP_OUT, NAU8310_DAC_SEL_DSP_OUT);
> +
> +	return nau8310_dsp_set_kcs_setup(cp);
> +}

[Severity: High]
Does setting NAU8310_DAC_SEL_DSP_OUT directly in nau8310_dsp_init()
desynchronize the hardware state from DAPM?

During nau8310_codec_probe(), nau8310_dsp_setup_controls() registers the
"DAC data from DSP" switch while load_fw_work is sleeping in msleep(120),
so dapm_set_mixer_path_status() reads NAU8310_R1A_DSP_CORE_CTRL2 while
NAU8310_DAC_SEL_DSP_OUT is still 0 and marks the route disconnected.  When
nau8310_dsp_init() then sets NAU8310_DAC_SEL_DSP_OUT via
regmap_update_bits(), hardware routes the DAC from the DSP, but DAPM
leaves the DSP, DSP Clock, Capture Sense, SAR, ADC_I and ADC_V widgets
unpowered during playback.

Also, in nau8310_load_fw_work(), nau8310->lock is released before the err
label clears NAU8310_DAC_SEL_DSP_OUT and NAU8310_DSP_SEL_OSC on failure,
leaving a window where nau8310_dsp_active() is true while load_fw_done is
false.

> +
> +int nau8310_dsp_setup_controls(struct snd_soc_component *cp)
> +{
> +	struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
> +	struct snd_soc_dapm_context *dapm = nau8310->dapm;
> +	int ret;
> +
> +	ret = snd_soc_dapm_new_controls(dapm, nau8310_dsp_dapm_widgets,
> +		ARRAY_SIZE(nau8310_dsp_dapm_widgets));
> +	if (ret) {
> +		dev_err(cp->dev, "Add DSP widget fail (%d)", ret);
> +		goto err;
> +	}
> +
> +	ret = snd_soc_dapm_add_routes(dapm, nau8310_dsp_dapm_routes,
> +		ARRAY_SIZE(nau8310_dsp_dapm_routes));
> +	if (ret) {
> +		dev_err(cp->dev, "Add DSP route fail (%d)", ret);
> +		goto err;
> +	}
> +
> +	return 0;
> +err:
> +	snd_soc_dapm_del_routes(dapm, nau8310_dsp_dapm_routes,
> +		ARRAY_SIZE(nau8310_dsp_dapm_routes));
> +	return ret;
> +}

[Severity: Low]
If snd_soc_dapm_new_controls() fails, doesn't jumping to err call
snd_soc_dapm_del_routes() on routes that were never added?  In addition,
nau8310_dsp_dapm_routes contains a route with a control ("Switch"), which
snd_soc_dapm_del_route() does not support removing, and ASoC core already
cleans up component DAPM widgets and routes when component probe fails.

> diff --git a/sound/soc/codecs/nau8310-dsp.h b/sound/soc/codecs/nau8310-dsp.h
> new file mode 100644
> index 0000000000000..a8301b9df8cbf
> --- /dev/null
> +++ b/sound/soc/codecs/nau8310-dsp.h
> @@ -0,0 +1,111 @@

[ ... ]

> +#define NAU8310_DSP_KCS_DAT_LEN_BITS		10
> +#define NAU8310_DSP_KCS_DAT_LEN_MAX		((1 << NAU8310_DSP_KCS_DAT_LEN_BITS) - 1)

[Severity: Low]
Should NAU8310_DSP_KCS_DAT_LEN_MAX be (1 << NAU8310_DSP_KCS_DAT_LEN_BITS)
(1024) rather than 1023?  Defining it as 1023 causes
nau8310_dsp_kcs_setup() and nau8310_send_dsp_command() to reject a full 1
KB KCS payload with -EINVAL or -ERANGE.

> diff --git a/sound/soc/codecs/nau8310.c b/sound/soc/codecs/nau8310.c
> new file mode 100644
> index 0000000000000..d5b2a655a367c
> --- /dev/null
> +++ b/sound/soc/codecs/nau8310.c

[ ... ]

> +static int nau8310_biq_coeff_get(struct snd_kcontrol *kcontrol,
> +	struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_soc_component *cp = snd_kcontrol_chip(kcontrol);
> +	struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
> +	struct soc_bytes_ext *params = (void *)kcontrol->private_value;
> +	int i, reg, reg_val;
> +	u16 *val;
> +
> +	val = (u16 *)ucontrol->value.bytes.data;
> +	reg = NAU8310_R80_BIQ0_COE_1;
> +	for (i = 0; i < params->max / sizeof(u16); i++) {
> +		regmap_read(nau8310->regmap, reg + i, &reg_val);
> +		/* conversion of 16-bit integers between native CPU format
> +		 * and big endian format
> +		 */
> +		reg_val = cpu_to_be16(reg_val);
> +		*(val + i) = reg_val;
> +	}
> +
> +	return 0;
> +}

[Severity: Medium]
If regmap_read() fails in nau8310_biq_coeff_get(), doesn't this copy the
uninitialized stack variable reg_val into ucontrol->value.bytes.data and
return 0 to userspace?

> +
> +static int nau8310_biq_coeff_put(struct snd_kcontrol *kcontrol,
> +	struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
> +	struct snd_soc_component *cp = snd_soc_dapm_to_component(dapm);
> +	struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
> +	struct soc_bytes_ext *params = (void *)kcontrol->private_value;
> +	void *data;
> +	u16 *val, value;
> +	int i, reg, ret;

[Severity: High]
Doesn't calling snd_soc_dapm_kcontrol_to_dapm(kcontrol) in
nau8310_biq_coeff_put() cause type confusion on kcontrol->private_data and
dereference an invalid pointer?

"BIQ Coefficients" is registered in nau8310_snd_controls as a standard
component control, so kcontrol->private_data is struct snd_soc_component *,
not struct dapm_kcontrol_data *.  Writing to this control will crash in
snd_soc_dapm_kcontrol_to_dapm(); it should use
snd_soc_kcontrol_component(kcontrol) like the other controls.

Also, both nau8310_biq_coeff_get() and nau8310_biq_coeff_put() store and
read __be16 values through int reg_val and u16 *val instead of __be16 *,
which triggers sparse endianness warnings.

[ ... ]

> +static const char * const nau8310_dac_oversampl[] = {
> +	"64", "256", "128", "", "32" };
> +
> +static const struct soc_enum nau8310_dac_oversampl_enum =
> +	SOC_ENUM_SINGLE(NAU8310_R29_DAC_CTRL1, NAU8310_DAC_OVERSAMPLE_SFT,
> +		ARRAY_SIZE(nau8310_dac_oversampl), nau8310_dac_oversampl);

[Severity: Medium]
Does exposing index 3 ("") in nau8310_dac_oversampl_enum allow userspace
to write the reserved value 3 to NAU8310_R29_DAC_CTRL1?  In osr_dac_sel[],
index 3 has osr = 0, which causes nau8310_clock_check() and
nau8310_hw_params() to fail with -EINVAL.  Using SOC_VALUE_ENUM_SINGLE()
would avoid exposing the reserved setting.

[ ... ]

> +static const DECLARE_TLV_DB_MINMAX(adc_vol_tlv, 0, 24125);
> +static const DECLARE_TLV_DB_MINMAX(pga_gain_tlv, 0, 1600);

[Severity: Low]
Should the maximum gain in adc_vol_tlv be 2412 (24.12 dB) instead of 24125?
DECLARE_TLV_DB_MINMAX() takes values in 0.01 dB units, so 24125 reports
241.25 dB to userspace for 0xc1 steps of 0.125 dB.

[ ... ]

> +static int nau8310_clock_event(struct snd_soc_dapm_widget *w,
> +	struct snd_kcontrol *kcontrol, int event)
> +{
> +	struct snd_soc_component *cp = snd_soc_dapm_to_component(w->dapm);
> +	struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
> +
> +	switch (event) {
> +	case SND_SOC_DAPM_PRE_PMU:
> +		/* Switch the clock source of DSP to MCLK. */
> +		regmap_update_bits(nau8310->regmap, NAU8310_R04_ENA_CTRL,
> +			NAU8310_DSP_SEL_OSC, 0);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}

[Severity: Medium]
As reported by Mark Brown on v1 [1], can nau8310_clock_event() and
nau8310_dsp_clock_event() race with nau8310_load_fw_work() during system
resume?

CPU0                                      kworker
snd_soc_card_resume_deferred()
  nau8310_resume()
    nau8310_dsp_fw_load()
      /* sets NAU8310_DSP_SEL_OSC */
      schedule_work(&load_fw_work)
  snd_soc_dapm_sync()
    nau8310_clock_event()
      regmap_update_bits(...,
        NAU8310_DSP_SEL_OSC, 0)           nau8310_load_fw_work()
                                            nau8310_dsp_init()
                                              <-- DSP clock switched off
                                                  OSC during firmware load

nau8310_clock_event() clears NAU8310_DSP_SEL_OSC without holding
nau8310->lock or flushing load_fw_work, and nau8310_dsp_clock_event() does
not flush load_fw_work before taking nau8310->lock and clearing
NAU8310_DSP_SEL_OSC.

[ ... ]

> +static int nau8310_adc_event(struct snd_soc_dapm_widget *w,
> +	struct snd_kcontrol *kcontrol, int event)
> +{
> +	struct snd_soc_component *cp = snd_soc_dapm_to_component(w->dapm);
> +	struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
> +	struct regmap *regmap = nau8310->regmap;
> +
> +	switch (event) {
> +	case SND_SOC_DAPM_POST_PMU:
> +		regmap_update_bits(regmap, NAU8310_R0E_I2S_PCM_CTRL2,
> +			NAU8310_I2S_ADCDAT_OE_MASK, NAU8310_I2S_ADCDAT_OE_EN);
> +		break;
> +	case SND_SOC_DAPM_POST_PMD:
> +		regmap_update_bits(regmap, NAU8310_R0E_I2S_PCM_CTRL2,
> +			NAU8310_I2S_ADCDAT_OE_MASK, NAU8310_I2S_ADCDAT_OE_DIS);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}

[ ... ]

> +static int nau8310_clock_apply(struct nau8310 *nau8310,
> +	const struct nau8310_srate_attr *srate_table,
> +	int n1_sel, int mclk_mult_sel, int n2_sel, int dsp_mult_sel)
> +{
> +	struct regmap *regmap = nau8310->regmap;
> +	int mult_sel;
> +	unsigned int ctrl_val = 0;
> +
> +	static const unsigned int mclkx_val[] = {
> +		/* multiplier 4x, i.e. 2^2 */
> +		[2] = NAU8310_MCLK4XEN,
> +		/* multiplier 8x, i.e. 2^3 */
> +		[3] = NAU8310_MCLK8XEN | NAU8310_MCLK4XEN,
> +		/* multiplier 16x, i.e. 2^4 */
> +		[4] = NAU8310_MCLK16XEN | NAU8310_MCLK8XEN | NAU8310_MCLK4XEN,
> +	};
> +
> +	if (srate_table->adc_div < 0 ||
> +		dsp_mult_sel < 0 || dsp_mult_sel >= ARRAY_SIZE(dsp_src_mult) ||
> +		n2_sel < 0 || n2_sel >= ARRAY_SIZE(mclk_n2_div) ||
> +		n1_sel < 0 || n1_sel >= ARRAY_SIZE(mclk_n1_div)) {
> +		dev_err(nau8310->dev, "The fs %d isn't support in DSP.", srate_table->fs);
> +		return -EINVAL;
> +	}

[ ... ]

> +	if (mult_sel < 2 || mult_sel > 4)
> +		return -EINVAL;
> +
> +	regmap_update_bits(regmap, NAU8310_R68_ANALOG_CONTROL_7,
> +		NAU8310_MCLKX_MASK, mclkx_val[mult_sel]);
> +
> +	return 0;
> +}
> +
> +static int nau8310_calc_dsp_clk(int mclk, int fs, int mult_param, int n1_param)
> +{
> +	int dsp_clk = (mclk << mult_param) / n1_param;
> +
> +	if (dsp_clk >= (fs * DSP_CLK_MIN_MULTI_VALUE) && dsp_clk <= DSP_CLK_MAX)
> +		return dsp_clk;
> +
> +	return 0;
> +}

[Severity: Medium]
Can 64 kHz or 96 kHz ever succeed in nau8310_clock_process(), even when
the DSP is bypassed?

nau8310_clock_process() unconditionally calls nau8310_clkdsp_choose() ->
nau8310_calc_dsp_clk(), which requires fs * 2000 <= dsp_clk <= 102400000;
for 64 kHz and 96 kHz, fs * 2000 exceeds 102400000, so it always fails
with -EINVAL.  For 96 kHz, target_srate_table[] also sets adc_div = -1,
which nau8310_clock_apply() unconditionally rejects, and NAU8310_RATES
advertises SNDRV_PCM_RATE_8000_192000 even though 11.025, 22.05, 88.2,
176.4 and 192 kHz are absent from target_srate_table[].

In addition, nau8310_clock_apply() rejects mult_sel < 2 (1x and 2x
multipliers in mclk_src_mult[] and dsp_src_mult[]) with -EINVAL after
already writing NAU8310_R03_CLK_CTRL, NAU8310_R40_CLK_DET_CTRL and
NAU8310_R04_ENA_CTRL.

[ ... ]

> +static int nau8310_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
> +	unsigned int rx_mask, int slots, int slot_width)
> +{

[ ... ]

> +	for (i = 0; i < NAU8310_TDM_MAX_SLOTS; i++)
> +		if (tx_mask & BIT(i)) {
> +			ctrl_val |= nau8310_tx_slot_map[i];
> +			dev_dbg(dev, "NAU8310_ADC_%c_SEL_SLOT%d",
> +				(i % 2 == 0) ? 'V' : 'I', i);
> +		}

[Severity: Medium]
Doesn't bitwise-ORing nau8310_tx_slot_map[i] across all set bits in
tx_mask corrupt NAU8310_ADC_V_SEL and NAU8310_ADC_I_SEL when multiple even
or odd slots are set in tx_mask?  NAU8310_ADC_V_SEL and NAU8310_ADC_I_SEL
are 3-bit binary-encoded fields (0x4..0x7), so ORing slot 2 (0x5) and slot
4 (0x6) produces slot 6 (0x7).  Also, slots is a signed int only checked
against !slots and slots > NAU8310_TDM_MAX_SLOTS, so negative slots or
mask bits >= slots are not rejected.

[ ... ]

> +static int nau8310_startup(struct snd_pcm_substream *substream,
> +	struct snd_soc_dai *dai)
> +{
> +	struct snd_soc_component *cp = dai->component;
> +	struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
> +
> +	flush_work(&nau8310->load_fw_work);
> +
> +	if (nau8310_dsp_active(cp) && !nau8310->load_fw_done) {
> +		dev_warn(nau8310->dev, "DSP firmware is not ready yet!");
> +		return -EBUSY;
> +	}
> +
> +	if (nau8310_dsp_active(cp))
> +		snd_soc_dapm_enable_pin(nau8310->dapm, "Sense");
> +
> +	return 0;
> +}
> +
> +static void nau8310_shutdown(struct snd_pcm_substream *substream,
> +	struct snd_soc_dai *dai)
> +{
> +	struct snd_soc_component *cp = dai->component;
> +	struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
> +
> +	if (!nau8310_dsp_active(cp))
> +		return;
> +
> +	snd_soc_dapm_disable_pin(nau8310->dapm, "Sense");
> +}

[Severity: Medium]
Does the "Sense" pin and NAU8310_I2S_ADCDAT_OE handling work across
bypass, full-duplex, and idle states?

1. "Sense" is a SND_SOC_DAPM_SIGGEN widget and is never disabled in
nau8310_codec_probe() or nau8310_resume().  Enabling "DAC data from DSP"
or "ALC gain selection mode" while idle completes a Sense -> DAC ->
Speaker path, powering up the codec and raising bias_level above
SND_SOC_BIAS_STANDBY so nau8310_dsp_snd_soc_dapm_put() rejects further
path changes with -EBUSY.

2. nau8310_shutdown() disables "Sense" without checking if the other
stream direction is still active and without calling snd_soc_dapm_sync().

3. Once nau8310_shutdown() disables "Sense", switching to DSP bypass mode
leaves "Sense" disabled because nau8310_startup() only enables "Sense"
when nau8310_dsp_active(cp) is true, preventing Capture (ADC_OUT) and ALC
sense from powering up.

4. Both "ALC gain selection mode" and "ADC_OUT" share nau8310_adc_event(),
so powering down either widget unconditionally sets
NAU8310_I2S_ADCDAT_OE_DIS even if the other widget is still active.

[ ... ]

> +static int nau8310_codec_probe(struct snd_soc_component *cp)
> +{
> +	struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
> +	struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(cp);
> +	int ret = 0;
> +
> +	nau8310->dapm = dapm;
> +
> +	/* Loading DSP firmware */
> +	ret = nau8310_dsp_fw_load(nau8310);
> +	if (ret) {
> +		dev_err(nau8310->dev, "Loading DSP firmware failed(%d)", ret);
> +		goto err;
> +	}
> +
> +	ret = nau8310_dsp_setup_controls(cp);
> +	if (ret) {
> +		dev_err(nau8310->dev, "DSP setup controls failed(%d)", ret);
> +		goto err;
> +	}
> +
> +err:
> +	return ret;
> +}

[Severity: High]
Does nau8310_codec_probe() leave nau8310->load_fw_work running if
nau8310_dsp_setup_controls() fails or when the component is later removed?

nau8310_dsp_fw_load() schedules load_fw_work before
nau8310_dsp_setup_controls() runs, but the error path does not call
cancel_work_sync(&nau8310->load_fw_work), and soc_comp_dev_nau8310 has no
.remove callback to cancel the work if component probe fails later or the
sound card is unbound while the I2C device stays bound.

[ ... ]

> +static int __maybe_unused nau8310_resume(struct snd_soc_component *cp)
> +{
> +	struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
> +	struct regmap *regmap = nau8310->regmap;
> +	int ret = 0;
> +
> +	regcache_cache_only(regmap, false);
> +	ret = regcache_sync(regmap);
> +	if (ret)
> +		return ret;
> +
> +	if (nau8310_dsp_active(cp)) {
> +		ret = nau8310_dsp_fw_load(nau8310);
> +		if (ret)
> +			dev_err(nau8310->dev, "Loading DSP firmware failed(%d)", ret);
> +	}
> +
> +	return ret;
> +}

[Severity: Medium]
If the system suspends and resumes while the DSP is bypassed
(!nau8310_dsp_active(cp)), doesn't skipping nau8310_dsp_fw_load() leave
nau8310->load_fw_done set to true even though the DSP lost its firmware
across suspend?  If userspace later enables "DAC data from DSP",
nau8310_dsp_snd_soc_dapm_put() will see load_fw_done == true and route
audio through the uninitialized DSP.

[1] https://lore.kernel.org/r/179304693306.199157.5210648249546181520.b4-review@b4

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261109051251.471879-1-YLCHANG2@nuvoton.com?part=2

  reply	other threads:[~2026-11-09  5:44 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-09  5:12 [PATCH v2 0/2] ASoC: codecs: nau8310: Add NAU83G10 amplifier Neo Chang
2026-11-09  5:12 ` [PATCH v2 1/2] ASoC: dt-bindings: nuvoton,nau8310: Add NAU83G10 Neo Chang
2026-11-09  5:23   ` sashiko-bot
2026-11-09 13:11   ` Krzysztof Kozlowski
2026-11-09  5:12 ` [PATCH v2 2/2] ASoC: codecs: nau8310: Add support for NAU83G10 amplifier Neo Chang
2026-11-09  5:44   ` sashiko-bot [this message]
2026-11-09 13:15   ` Krzysztof Kozlowski
2026-11-09 19:48   ` Mark Brown

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=sashiko-outbox-204454@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=YLCHANG2@nuvoton.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=linux-sound@vger.kernel.org \
    --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