From: sashiko-bot@kernel.org
To: "Xing Zheng" <zhengxing@rock-chips.com>, linux-sound@vger.kernel.org
Cc: devicetree@vger.kernel.org, robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH v2 2/3] ASoC: codecs: rk3308: restore audio and headphone support
Date: Sun, 01 Nov 2026 05:11:43 +0000 [thread overview]
Message-ID: <sashiko-outbox-192279@kernel.org> (raw)
In-Reply-To: <20261101044149.2018744-3-zhengxing@rock-chips.com>
Thank you for your contribution! Sashiko AI review found 11 potential issue(s) to consider:
- [Medium] ASoC: rk3308: rk3308_codec_adc_event() clobbers MIC Capture Switch state
- [Medium] ASoC: rk3308: broken mute and HPMIX_WORK sequencing in rk3308_codec_dac_event()
- [Medium] ASoC: rk3308: redundant PMU and invalid POP_SOUND in rk3308_codec_output_event()
- [Medium] ASoC: rk3308: zero-cross detection disabled in rk3308_codec_initialize()
- [Medium] ASoC: rk3308: rk3308_codec_dac_event() enables inactive DAPM outputs
- [Medium] ASoC: rk3308: HPOUT subseq inverts power order against machine HP widget
- [Medium] ASoC: rk3308: CH1_IN_SEL and CH2_IN_SEL not converted to SND_SOC_NOPM
- [Medium] ASoC: rk3308: leak of devm_kmemdup() widgets in rk3308_codec_add_widgets()
- [Medium] ASoC: rk3308: non-atomic route and dac_output update in rk3308_codec_set_output()
- [Medium] ASoC: rk3308: race on mixer registers in rk3308_codec_dac_event()
- [Medium] ASoC: rk3308: lock inversion on io_lock in rk3308_codec_mute_stream()
--
commit a099049aa9bf21b04f0558d74e37572e81c21ec8
Author: Xing Zheng <zhengxing@rock-chips.com>
ASoC: codecs: rk3308: restore audio and headphone support
This commit restores the vendor analog enable and disable sequencing,
settling delays, gain ramps, and ADC resynchronization across codec
revisions A, B, and C using DAPM supply events and virtual intermediate
widgets. It also adds per-channel microphone boost controls, ADC stereo
group routing, selectable DAC output routing, and headphone jack detection.
> diff --git a/sound/soc/codecs/rk3308_codec.c b/sound/soc/codecs/rk3308_codec.c
> index a3d41cd8150d0..1894e1a93fbf8 100644
> --- a/sound/soc/codecs/rk3308_codec.c
> +++ b/sound/soc/codecs/rk3308_codec.c
[ ... ]
> @@ -78,7 +184,288 @@ static SOC_ENUM_SINGLE_DECL(rk3308_codec_hpf_cutoff_enum56, RK3308_ADC_DIG_CON04
> static SOC_ENUM_SINGLE_DECL(rk3308_codec_hpf_cutoff_enum78, RK3308_ADC_DIG_CON04(3), 0,
> rk3308_codec_hpf_cutoff_text);
>
> +static int rk3308_codec_output_routes(struct rk3308_codec_priv *rk3308)
> +{
> + static const struct snd_soc_dapm_route routes[][2] = {
> + {
> + { "L_LINEOUT_EN", NULL, "DAC_L_HPMIX_SEL" },
> + { "R_LINEOUT_EN", NULL, "DAC_R_HPMIX_SEL" },
> + }, {
> + { "L_HPOUT_EN", NULL, "DAC_L_HPMIX_SEL" },
> + { "R_HPOUT_EN", NULL, "DAC_R_HPMIX_SEL" },
> + },
> + };
> + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(rk3308->component);
> + u32 mask = rk3308->dac_output == 11 ? 3 :
> + rk3308->dac_output == 0 ? 1 : 2;
> + int i, ret;
> +
> + for (i = 0; i < ARRAY_SIZE(routes); i++) {
> + if (!((mask ^ rk3308->output_routes_mask) & BIT(i)))
> + continue;
> + if (mask & BIT(i))
> + ret = snd_soc_dapm_add_routes(dapm, routes[i], ARRAY_SIZE(routes[i]));
> + else
> + ret = snd_soc_dapm_del_routes(dapm, routes[i], ARRAY_SIZE(routes[i]));
> + if (ret)
> + return ret;
> + rk3308->output_routes_mask ^= BIT(i);
> + }
> +
> + return 0;
> +}
> +
> +static int rk3308_codec_set_output(struct rk3308_codec_priv *rk3308, unsigned int output)
> +{
> + unsigned int saved = rk3308->dac_output;
> + int ret;
> +
> + rk3308->dac_output = output;
> + ret = rk3308_codec_output_routes(rk3308);
> + if (ret) {
> + rk3308->dac_output = saved;
> + rk3308_codec_output_routes(rk3308);
> + }
> + if (!ret && rk3308->hpdet_jack)
> + snd_soc_jack_report(rk3308->hpdet_jack,
> + output ? SND_JACK_HEADPHONE : 0, SND_JACK_HEADPHONE);
> + if (!ret)
> + ret = snd_soc_dapm_sync(snd_soc_component_to_dapm(rk3308->component));
> + return ret;
> +}
[Severity: Medium]
Can updating rk3308->dac_output and the DAPM routes in
rk3308_codec_set_output() race with concurrent DAPM power sequencing?
In rk3308_codec_set_output(), rk3308->dac_output is modified under
rk3308->io_lock before card->dapm_mutex is acquired in
rk3308_codec_output_routes() and snd_soc_dapm_sync(). Meanwhile,
rk3308_codec_dac_event() reads rk3308->dac_output while holding
card->dapm_mutex (without holding rk3308->io_lock):
bool lineout = rk3308->dac_output != 1;
bool headphone = rk3308->dac_output != 0;
Because dapm_power_widgets() holds card->dapm_mutex across
rk3308_codec_set_bias_level() and the delays in rk3308_codec_dac_event(), a
concurrent call to rk3308_codec_set_output() via rk3308_codec_output_put()
or rk3308_codec_hpdet_work() can overwrite rk3308->dac_output and block on
card->dapm_mutex in rk3308_codec_output_routes(), causing
rk3308_codec_dac_event() to execute with the new rk3308->dac_output value
while DAPM is still powering up the widget list computed from the old route
graph.
In addition, each call to snd_soc_dapm_del_routes() and
snd_soc_dapm_add_routes() in rk3308_codec_output_routes() acquires and
releases card->dapm_mutex separately. When switching from Line Out
(dac_output == 0, mask == 1) to Headphone (dac_output == 1, mask == 2),
routes[0] is deleted in the first iteration and card->dapm_mutex is dropped
before routes[1] is added in the second iteration. If another thread runs a
DAPM sync in that window, DAPM sees no connected output routes from
DAC_L_HPMIX_SEL and DAC_R_HPMIX_SEL and powers down the active DAC.
[ ... ]
> @@ -175,107 +562,350 @@ static const struct snd_kcontrol_new rk3308_codec_controls[] = {
[ ... ]
> +/* DAPM calls this before the machine driver enables either output amplifier. */
> +static int rk3308_codec_dac_event(struct snd_soc_dapm_widget *w,
> + struct snd_kcontrol *kcontrol, int event)
> +{
> + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
> + struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
> + unsigned int line, hp, aif1, aif2, digital_gain = 0, gain_l, gain_r;
> + int gain;
> + unsigned int line_unmute = BIT(RK3308_DAC_L_LINEOUT_MUTE_SFT) |
> + BIT(RK3308_DAC_R_LINEOUT_MUTE_SFT);
> + bool lineout = rk3308->dac_output != 1;
> + bool headphone = rk3308->dac_output != 0;
[Severity: Medium]
Does deriving lineout and headphone from rk3308->dac_output instead of the
DAPM widget power states cause rk3308_codec_dac_event() to enable inactive
outputs?
rk3308_codec_probe() initializes rk3308->dac_output to 11 (Both). On boards
that only route LINEOUT or HPOUT in Device Tree audio-routing (or when
headphone detection is not used, such as on revision A), the unconnected
output widgets have new_power == 0, but rk3308_codec_dac_event() still sees
both lineout and headphone as true and enables both RK3308_DAC_L/R_LINEOUT_EN
and RK3308_DAC_L/R_HPOUT_EN while ramping the headphone gain.
> +
> + rk3308->dac_analog = w;
> + regmap_read(rk3308->regmap, RK3308_DAC_ANA_CON04, &line);
> + regmap_read(rk3308->regmap, RK3308_DAC_ANA_CON03, &hp);
> + regmap_read(rk3308->regmap, RK3308_DAC_ANA_CON05, &gain_l);
> + regmap_read(rk3308->regmap, RK3308_DAC_ANA_CON06, &gain_r);
> + gain_l &= RK3308_DAC_x_HPOUT_GAIN_MSK;
> + gain_r &= RK3308_DAC_x_HPOUT_GAIN_MSK;
> + if (event == SND_SOC_DAPM_PRE_PMU) {
> + if (headphone) {
> + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON05,
> + RK3308_DAC_x_HPOUT_GAIN_MSK, 0);
> + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON06,
> + RK3308_DAC_x_HPOUT_GAIN_MSK, 0);
> + }
[Severity: Medium]
What happens if userspace reads or writes the Headphone Playback Volume,
Headphone Playback Switch, or Line Out Playback Switch controls while
rk3308_codec_dac_event() is running?
rk3308_codec_dac_event() runs under card->dapm_mutex and snapshots
RK3308_DAC_ANA_CON03..06 into hp, line, gain_l, and gain_r, then zeroes or
ramps RK3308_DAC_ANA_CON05/06 and clears the unmute bits in
RK3308_DAC_ANA_CON03/04 across multiple sleeps (~10-14 ms) before writing the
snapshots back. Because snd_soc_get_volsw() and snd_soc_put_volsw() run
under card->controls_rwsem without acquiring card->dapm_mutex, concurrent
mixer reads can observe transient zeroed/muted register values and
concurrent mixer writes to RK3308_DAC_ANA_CON03..06 will be overwritten when
rk3308_codec_dac_event() restores its initial snapshots.
[ ... ]
> + /* Steps 15-21: select I2S and restore the user mute/gain settings. */
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON12,
> + RK3308_DAC_L_HPMIX_SEL_MSK | RK3308_DAC_R_HPMIX_SEL_MSK,
> + RK3308_DAC_L_HPMIX_I2S | RK3308_DAC_R_HPMIX_I2S);
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON13,
> + (RK3308_DAC_L_HPMIX_UNMUTE | RK3308_DAC_R_HPMIX_UNMUTE),
> + (RK3308_DAC_L_HPMIX_UNMUTE | RK3308_DAC_R_HPMIX_UNMUTE));
> + if (headphone)
> + for (gain = 0; gain <= max(gain_l, gain_r); gain++) {
> + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON05,
> + RK3308_DAC_x_HPOUT_GAIN_MSK,
> + min_t(unsigned int, gain, gain_l));
> + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON06,
> + RK3308_DAC_x_HPOUT_GAIN_MSK,
> + min_t(unsigned int, gain, gain_r));
> + }
> + rk3308_codec_resync_adcs(rk3308);
> + } else {
> + /* Vendor DAC disable steps 3-19, after the external amps are off. */
> + if (hp & (RK3308_DAC_L_HPOUT_EN | RK3308_DAC_R_HPOUT_EN))
> + for (gain = max(gain_l, gain_r); gain >= 0; gain--) {
> + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON05,
> + RK3308_DAC_x_HPOUT_GAIN_MSK,
> + min_t(unsigned int, gain, gain_l));
> + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON06,
> + RK3308_DAC_x_HPOUT_GAIN_MSK,
> + min_t(unsigned int, gain, gain_r));
> + usleep_range(200, 300);
> + }
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON12,
> + RK3308_DAC_L_HPMIX_SEL_MSK | RK3308_DAC_R_HPMIX_SEL_MSK,
> + RK3308_DAC_L_HPMIX_NONE | RK3308_DAC_R_HPMIX_NONE);
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON03,
> + RK3308_DAC_L_HPOUT_UNMUTE |
> + RK3308_DAC_R_HPOUT_UNMUTE, 0);
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON02,
> + (RK3308_DAC_L_DAC_WORK | RK3308_DAC_R_DAC_WORK), 0);
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON03,
> + (RK3308_DAC_L_HPOUT_EN | RK3308_DAC_R_HPOUT_EN), 0);
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON04, line_unmute, 0);
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON04,
> + (RK3308_DAC_L_LINEOUT_EN | RK3308_DAC_R_LINEOUT_EN), 0);
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON13,
> + (RK3308_DAC_L_HPMIX_EN | RK3308_DAC_R_HPMIX_EN), 0);
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON02,
> + (RK3308_DAC_L_DAC_EN | RK3308_DAC_R_DAC_EN), 0);
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON02,
> + (RK3308_DAC_L_CLK_EN | RK3308_DAC_R_CLK_EN), 0);
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON02,
> + (RK3308_DAC_L_REF_EN | RK3308_DAC_R_REF_EN), 0);
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON01,
> + RK3308_DAC_HPOUT_POP_SOUND_L_MSK |
> + RK3308_DAC_HPOUT_POP_SOUND_R_MSK,
> + RK3308_DAC_HPOUT_POP_SOUND_L_INIT | RK3308_DAC_HPOUT_POP_SOUND_R_INIT);
> + if (rk3308->codec_ver >= ACODEC_VERSION_B)
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON15,
> + RK3308_DAC_LINEOUT_POP_SOUND_L_MSK |
> + RK3308_DAC_LINEOUT_POP_SOUND_R_MSK,
> + RK3308_DAC_L_SEL_DC_FROM_VCM | RK3308_DAC_R_SEL_DC_FROM_VCM);
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON01,
> + (RK3308_DAC_BUF_REF_L_EN | RK3308_DAC_BUF_REF_R_EN), 0);
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON00,
> + RK3308_DAC_CURRENT_EN, 0);
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON03,
> + (RK3308_DAC_L_HPOUT_WORK | RK3308_DAC_R_HPOUT_WORK), 0);
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON13,
> + (RK3308_DAC_L_HPMIX_WORK | RK3308_DAC_R_HPMIX_WORK),
> + (RK3308_DAC_L_HPMIX_WORK | RK3308_DAC_R_HPMIX_WORK));
[Severity: Medium]
Should the SND_SOC_DAPM_PRE_PMU and SND_SOC_DAPM_POST_PMD branches in
rk3308_codec_dac_event() be muting the outputs during power-up and clearing
RK3308_DAC_L/R_HPMIX_WORK and RK3308_DAC_L/R_HPMIX_UNMUTE on power-down?
In rk3308_codec_initialize() and at the end of SND_SOC_DAPM_POST_PMD, the
driver sets the unmute bits in RK3308_DAC_ANA_CON03 and RK3308_DAC_ANA_CON04
so the ALSA switches remain readable while the DAC is off. At the start of
rk3308_codec_dac_event(), line and hp are read from RK3308_DAC_ANA_CON04 and
RK3308_DAC_ANA_CON03, and the comment at steps 15-21 says "select I2S and
restore the user mute/gain settings". However, SND_SOC_DAPM_PRE_PMU never
clears the unmute bits in RK3308_DAC_ANA_CON03 or RK3308_DAC_ANA_CON04
before enabling the outputs, and never uses line or hp to restore them at
steps 15-21. As a result, LINEOUT_EN and HPOUT_EN/HPOUT_WORK are powered on
while already unmuted before DAC_REF_EN, DAC_CLK_EN, DAC_EN, DAC_WORK, and
HPMIX_SEL are configured.
Additionally, in the SND_SOC_DAPM_POST_PMD branch,
RK3308_DAC_L_HPMIX_UNMUTE | RK3308_DAC_R_HPMIX_UNMUTE in
RK3308_DAC_ANA_CON13 is never cleared, and the update to
RK3308_DAC_L_HPMIX_WORK | RK3308_DAC_R_HPMIX_WORK passes
(RK3308_DAC_L_HPMIX_WORK | RK3308_DAC_R_HPMIX_WORK) instead of 0 as the
value argument, leaving HPMIX unmuted and in WORK state across power-down.
> + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON05,
> + RK3308_DAC_x_HPOUT_GAIN_MSK, gain_l);
> + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON06,
> + RK3308_DAC_x_HPOUT_GAIN_MSK, gain_r);
> + rk3308_codec_resync_adcs(rk3308);
> + /* Preserve ALSA switches while the analog output stages are off. */
> + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON03,
> + RK3308_DAC_L_HPOUT_UNMUTE | RK3308_DAC_R_HPOUT_UNMUTE,
> + hp & (RK3308_DAC_L_HPOUT_UNMUTE | RK3308_DAC_R_HPOUT_UNMUTE));
> + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON04,
> + line_unmute, line & line_unmute);
> + }
>
> return 0;
> }
>
> +static int rk3308_codec_adc_event(struct snd_soc_dapm_widget *w,
> + struct snd_kcontrol *kcontrol, int event)
> +{
> + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
> + struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
> + unsigned int grp = w->shift;
> + unsigned int zerocross;
> +
> + if (event == SND_SOC_DAPM_PRE_PMU) {
> + regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON07(grp),
> + RK3308_ADC_CH1_IN_SEL_MSK | RK3308_ADC_CH2_IN_SEL_MSK,
> + RK3308_ADC_CH1_IN_MIC | RK3308_ADC_CH2_IN_MIC);
> + regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp),
> + RK3308_ADC_CH1_MIC_UNMUTE | RK3308_ADC_CH2_MIC_UNMUTE);
[Severity: Medium]
Doesn't setting RK3308_ADC_CH1_MIC_UNMUTE | RK3308_ADC_CH2_MIC_UNMUTE in
rk3308_codec_adc_event() on SND_SOC_DAPM_PRE_PMU overwrite the user's
MIC1..MIC8 Capture Switch settings?
In rk3308_codec_controls[], MIC1 Capture Switch through MIC8 Capture Switch
are backed by bits 3 (RK3308_ADC_CH1_MIC_UNMUTE) and 7
(RK3308_ADC_CH2_MIC_UNMUTE) of RK3308_ADC_ANA_CON00(0..3), and
rk3308_codec_initialize() already sets BIT(3) | BIT(7) once after reset so
those controls remain adjustable. Unconditionally setting those bits on every
SND_SOC_DAPM_PRE_PMU in rk3308_codec_adc_event() forces any muted microphone
channels back to unmuted whenever capture starts.
[ ... ]
> +/* Output-only route changes must not restart the shared DAC analog supply. */
> +static int rk3308_codec_output_event(struct snd_soc_dapm_widget *w,
> + bool hp, int event)
> +{
> + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
> + struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
> + unsigned int shift = hp ? w->shift - 1 : w->shift;
> + unsigned int reg = hp ? RK3308_DAC_ANA_CON03 : RK3308_DAC_ANA_CON04;
> + unsigned int mask = (hp ? (RK3308_DAC_L_HPOUT_EN | RK3308_DAC_L_HPOUT_WORK) :
> + RK3308_DAC_L_LINEOUT_EN) << shift;
>
> - if (rk3308->codec_ver != ACODEC_VERSION_B)
> + if (!rk3308->dac_analog)
> return 0;
>
> - switch (event) {
> - case SND_SOC_DAPM_POST_PMU:
> - val = RK3308_DAC_L_SEL_LINEOUT_FROM_INTERNAL << w->shift;
> - break;
> - case SND_SOC_DAPM_POST_PMD:
> - val = RK3308_DAC_L_SEL_DC_FROM_INTERNAL << w->shift;
> - break;
> - default:
> + /*
> + * ASoC computes widget new_power before running DAPM events.
> + * If the shared supply is also going down, its shutdown event runs
> + * the complete vendor disable sequence. Recheck this ordering when
> + * updating the ASoC core.
> + */
> + if (event == SND_SOC_DAPM_PRE_PMD && !rk3308->dac_analog->new_power)
> return 0;
> + if (event == SND_SOC_DAPM_PRE_PMU) {
> + if (hp) {
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON01,
> + RK3308_DAC_HPOUT_POP_SOUND_x_MSK << shift,
> + RK3308_DAC_HPOUT_POP_SOUND_x_WORK << shift);
> + rk3308_codec_analog_update(rk3308, reg,
> + RK3308_DAC_L_HPOUT_EN << shift,
> + RK3308_DAC_L_HPOUT_EN << shift);
> + }
> + rk3308_codec_analog_update(rk3308, reg,
> + hp ? RK3308_DAC_L_HPOUT_WORK << shift : mask,
> + hp ? RK3308_DAC_L_HPOUT_WORK << shift : mask);
> + } else {
> + if (hp)
> + rk3308_codec_analog_update(rk3308, reg, RK3308_DAC_L_HPOUT_EN << shift, 0);
> + rk3308_codec_analog_update(rk3308, reg,
> + hp ? RK3308_DAC_L_HPOUT_WORK << shift : mask, 0);
> + if (hp)
> + rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON01,
> + RK3308_DAC_HPOUT_POP_SOUND_x_MSK << shift, 0);
> }
[Severity: Medium]
In rk3308_codec_output_event(), why is 0 written to
RK3308_DAC_HPOUT_POP_SOUND_x_MSK << shift on SND_SOC_DAPM_PRE_PMD instead of
RK3308_DAC_HPOUT_POP_SOUND_x_INIT << shift (0x1 << shift) as in
rk3308_codec_dac_event() and rk3308_codec_set_bias_level()?
Also, on playback power-up, DAC_CURRENT_EN (snd_soc_dapm_supply) runs
rk3308_codec_dac_event(SND_SOC_DAPM_PRE_PMU) and sets rk3308->dac_analog = w
before the L/R_HPOUT_EN and L/R_LINEOUT_EN (snd_soc_dapm_out_drv) widgets
run rk3308_codec_output_event(SND_SOC_DAPM_PRE_PMU). Because there is no
SND_SOC_DAPM_PRE_PMU check corresponding to the SND_SOC_DAPM_PRE_PMD check,
rk3308_codec_output_event() re-executes these output register writes and
delays after rk3308_codec_dac_event() has already completed the full enable
sequence and headphone gain ramp.
[ ... ]
> @@ -400,33 +1027,116 @@ static const struct snd_soc_dapm_widget rk3308_codec_dapm_widgets[] = {
>
> SND_SOC_DAPM_SUPPLY("DAC_BUF_REF_L", RK3308_DAC_ANA_CON01, 2, 0, NULL, 0),
> SND_SOC_DAPM_SUPPLY("DAC_BUF_REF_R", RK3308_DAC_ANA_CON01, 6, 0, NULL, 0),
> - SND_SOC_DAPM_OUT_DRV_E("HPOUT_POP_SOUND_L", SND_SOC_NOPM, 0, 0, NULL, 0,
> - rk3308_codec_pop_sound_set,
> - SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
> - SND_SOC_DAPM_OUT_DRV_E("HPOUT_POP_SOUND_R", SND_SOC_NOPM, 4, 0, NULL, 0,
> - rk3308_codec_pop_sound_set,
> - SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
> - SND_SOC_DAPM_SUPPLY("DAC_OUTPUT_VCM", SND_SOC_NOPM, 0, 0,
> - rk3308_codec_dac_output_vcm_event,
> - SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
> - SND_SOC_DAPM_POST_PMD),
> - SND_SOC_DAPM_OUT_DRV("L_HPOUT_EN", RK3308_DAC_ANA_CON03, 1, 0, NULL, 0),
> - SND_SOC_DAPM_OUT_DRV("R_HPOUT_EN", RK3308_DAC_ANA_CON03, 5, 0, NULL, 0),
> - SND_SOC_DAPM_OUT_DRV("L_HPOUT_WORK", RK3308_DAC_ANA_CON03, 2, 0, NULL, 0),
> - SND_SOC_DAPM_OUT_DRV("R_HPOUT_WORK", RK3308_DAC_ANA_CON03, 6, 0, NULL, 0),
> + {
> + .id = snd_soc_dapm_out_drv, .name = "L_HPOUT_EN",
> + SND_SOC_DAPM_INIT_REG_VAL(SND_SOC_NOPM, 1, 0),
> + .subseq = 1, /* Enable HPOUT after the HPMIX output drivers. */
> + .event = rk3308_codec_hpout_event,
> + .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD,
> + },
> + {
> + .id = snd_soc_dapm_out_drv, .name = "R_HPOUT_EN",
> + SND_SOC_DAPM_INIT_REG_VAL(SND_SOC_NOPM, 5, 0),
> + .subseq = 1, /* Enable HPOUT after the HPMIX output drivers. */
> + .event = rk3308_codec_hpout_event,
> + .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD,
> + },
[Severity: Medium]
Does setting .subseq = 1 on L_HPOUT_EN and R_HPOUT_EN invert the power
ordering relative to machine SND_SOC_DAPM_HP widgets?
In sound/soc/soc-dapm.c, snd_soc_dapm_out_drv and snd_soc_dapm_hp have the
same sequence priority in both dapm_up_seq and dapm_down_seq, so
dapm_seq_compare() orders them by subseq (ascending on power-up, descending
on power-down). Meanwhile, rk3308_codec_add_widgets() converts the HPMIX
output drivers into SND_SOC_NOPM widgets with no events.
With .subseq = 1 on L_HPOUT_EN and R_HPOUT_EN, a machine SND_SOC_DAPM_HP
widget (subseq 0) powers up before rk3308_codec_hpout_event() enables the
codec HPOUT stage, and powers down after rk3308_codec_hpout_event() disables
the codec HPOUT stage when switching output routes.
[ ... ]
> +static int rk3308_codec_add_widgets(struct snd_soc_component *component)
> +{
> + struct snd_soc_dapm_widget *widgets;
> + int i, grp, ret;
> +
> + widgets = devm_kmemdup(component->dev, rk3308_codec_dapm_widgets,
> + sizeof(rk3308_codec_dapm_widgets), GFP_KERNEL);
> + if (!widgets)
> + return -ENOMEM;
[Severity: Medium]
Does rk3308_codec_add_widgets() leak the widgets array allocated with
devm_kmemdup()?
snd_soc_dapm_new_controls() duplicates each widget via dapm_cnew_widget() and
does not keep the caller's template array. Because rk3308_codec_add_widgets()
is called from rk3308_codec_probe() (the ASoC component probe callback) and
allocates widgets on component->dev with devm_kmemdup(), the temporary array
is kept for the lifetime of the platform device and an additional copy is
allocated on every card unbind/rebind or probe retry cycle.
> + for (i = 0; i < ARRAY_SIZE(rk3308_codec_dapm_widgets); i++) {
> + struct snd_soc_dapm_widget *w = &widgets[i];
> + unsigned int reg = w->reg;
> +
> + if (reg >= RK3308_DAC_ANA_CON00 && reg <= RK3308_DAC_ANA_CON13) {
> + w->reg = SND_SOC_NOPM;
> + if (reg == RK3308_DAC_ANA_CON04)
> + w->event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD;
> + if (reg == RK3308_DAC_ANA_CON00) {
> + w->subseq = 1;
> + w->event = rk3308_codec_dac_event;
> + w->event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD;
> + }
> + }
> + for (grp = 0; grp < ADC_LR_GROUP_MAX; grp++) {
> + if (reg == RK3308_ADC_ANA_CON00(grp) ||
> + reg == RK3308_ADC_ANA_CON02(grp) ||
> + reg == RK3308_ADC_ANA_CON05(grp))
> + w->reg = SND_SOC_NOPM;
[Severity: Medium]
Should RK3308_ADC_ANA_CON07(0) (used by CH1_IN_SEL and CH2_IN_SEL) also be
converted to SND_SOC_NOPM here?
CH1_IN_SEL and CH2_IN_SEL are defined in rk3308_codec_dapm_widgets[] as
SND_SOC_DAPM_PGA widgets backed by RK3308_ADC_ANA_CON07(0) shifts 4 and 6,
while rk3308_codec_initialize() and rk3308_codec_adc_event() also program
RK3308_ADC_CH1_IN_MIC | RK3308_ADC_CH2_IN_MIC in RK3308_ADC_ANA_CON07(grp).
Because CH1_IN_SEL and CH2_IN_SEL keep w->reg = RK3308_ADC_ANA_CON07(0):
1. During card instantiation, snd_soc_dapm_new_widgets() sees bits 4 and 6
set by rk3308_codec_initialize() on the unpowered PGA widgets and clears
them back to 0.
2. On capture stop, CH1_IN_SEL and CH2_IN_SEL (snd_soc_dapm_pga,
dapm_down_seq = 6) clear bits 4 and 6 in RK3308_ADC_ANA_CON07(0) while the
ADC, ALC, and MIC stages are still enabled and in WORK mode, before
ADC_CURRENT_EN12 (snd_soc_dapm_supply, dapm_down_seq = 14) runs
rk3308_codec_adc_event(SND_SOC_DAPM_POST_PMD).
[ ... ]
> @@ -775,6 +1480,24 @@ static int rk3308_codec_hw_params(struct snd_pcm_substream *substream,
> rk3308_codec_adc_dig_config(rk3308, params);
> }
>
> +static int rk3308_codec_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
> +{
> + struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(dai->component);
> + int gain;
> +
> + if (rk3308->codec_ver > ACODEC_VERSION_B || stream != SNDRV_PCM_STREAM_PLAYBACK)
> + return 0;
> + mutex_lock(&rk3308->io_lock);
> + for (gain = mute ? 2 : 7; mute ? gain <= 7 : gain >= 2; gain += mute ? 1 : -1) {
> + regmap_update_bits(rk3308->regmap, RK3308_DAC_DIG_CON04,
> + RK3308_DAC_CIC_IF_GAIN_MSK, gain);
> + usleep_range(200, 300);
> + }
> + mutex_unlock(&rk3308->io_lock);
> +
> + return 0;
> +}
[Severity: Medium]
Can acquiring rk3308->io_lock in rk3308_codec_mute_stream() cause a lock
inversion against card->dapm_mutex?
rk3308_codec_output_put(), rk3308_codec_hpdet_work(), and
rk3308_codec_set_jack() acquire rk3308->io_lock and then acquire
card->dapm_mutex via snd_soc_dapm_add_routes(), snd_soc_dapm_del_routes(),
snd_soc_jack_report(), snd_soc_dapm_sync(), and snd_soc_dapm_mutex_lock().
However, ASoC can invoke .mute_stream() via dapm_dai_link_event() ->
snd_soc_dai_digital_mute() while card->dapm_mutex is already held, inverting
the lock order to dapm_mutex -> io_lock.
[ ... ]
> @@ -853,89 +1577,143 @@ static int rk3308_codec_initialize(struct rk3308_codec_priv *rk3308)
[ ... ]
> /* recover DAC digital gain to 0 dB (reset value is 0xff, undocumented) */
> if (rk3308->codec_ver == ACODEC_VERSION_C)
> regmap_write(rk3308->regmap, RK3308_DAC_DIG_CON04,
> RK3308BS_DAC_DIG_GAIN_0DB);
>
> - /*
> - * Unconditionally enable zero-cross detection (needed for AGC,
> - * harmless without AGC)
> - */
> - for (grp = 0; grp < ADC_LR_GROUP_MAX; grp++)
> - regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON02(grp),
> - RK3308_ADC_CH1_ZEROCROSS_DET_EN |
> - RK3308_ADC_CH2_ZEROCROSS_DET_EN);
> -
> return 0;
> }
[Severity: Medium]
Was removing the RK3308_ADC_CH1_ZEROCROSS_DET_EN |
RK3308_ADC_CH2_ZEROCROSS_DET_EN initialization from rk3308_codec_initialize()
intentional?
Nothing else in the driver sets RK3308_ADC_CH1_ZEROCROSS_DET_EN or
RK3308_ADC_CH2_ZEROCROSS_DET_EN after rk3308_codec_reset() zeroes
RK3308_ADC_ANA_CON02(grp). Consequently, zero-cross detection remains
disabled on all ADC channels, and the code in rk3308_codec_adc_event() that
saves, clears, and restores those bits during SND_SOC_DAPM_POST_PMD always
reads and restores 0.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261101044149.2018744-1-zhengxing@rock-chips.com?part=2
next prev parent reply other threads:[~2026-11-01 5:11 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-01 4:01 [PATCH v1 0/6] RK3308 codec configuration and headphone detection Xing Zheng
2026-11-01 4:01 ` [PATCH v1 1/6] ASoC: codecs: rk3308: restore MICBIAS voltage after reset Xing Zheng
2026-11-01 4:01 ` [PATCH v1 2/6] ASoC: codecs: rk3308: expose per-channel microphone boost Xing Zheng
2026-11-01 4:13 ` sashiko-bot
2026-11-01 4:01 ` [PATCH v1 3/6] ASoC: dt-bindings: rk3308-codec: describe ADC routing and jack detection Xing Zheng
2026-11-01 4:01 ` [PATCH v1 4/6] ASoC: codecs: rk3308: support ADC stereo-group routing Xing Zheng
2026-11-01 4:01 ` [PATCH v1 5/6] ASoC: codecs: rk3308: report B/C headphone detection through set_jack Xing Zheng
2026-11-01 4:01 ` [PATCH v1 6/6] arm64: dts: rockchip: describe RK3308 codec detection resources Xing Zheng
2026-11-01 4:41 ` [PATCH v2 0/3] RK3308 codec audio sequencing and headphone support Xing Zheng
2026-11-01 4:41 ` [PATCH v2 1/3] ASoC: dt-bindings: rk3308-codec: describe detection and ADC routing Xing Zheng
2026-11-01 22:45 ` Mark Brown
2026-11-01 4:41 ` [PATCH v2 2/3] ASoC: codecs: rk3308: restore audio and headphone support Xing Zheng
2026-11-01 5:11 ` sashiko-bot [this message]
2026-11-01 4:41 ` [PATCH v2 3/3] arm64: dts: rockchip: restore RK3308 codec detection resources Xing Zheng
2026-11-01 5:01 ` sashiko-bot
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-192279@kernel.org \
--to=sashiko-bot@kernel.org \
--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 \
--cc=zhengxing@rock-chips.com \
/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