* [PATCH v1 0/6] RK3308 codec configuration and headphone detection
@ 2026-11-01 4:01 ` Xing Zheng
0 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:01 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
This series ports selected RK3308 codec configuration fixes and features
from the Rockchip SDK to current upstream ASoC. It preserves the recently
merged revision-B support (157f7cba77a9) and existing upstream DAPM power
sequences.
The first two patches preserve the configured MICBIAS voltage across the
component reset and expose the separate microphone boost controls. The
remaining codec patches document and configure ADC group routing and
report the B/C headphone detector through set_jack. Output routing stays
with the machine driver and its jack pin associations.
The last patch describes only the SoC detection resources. Mark can take
patches 1-5 through ASoC; Heiko can take patch 6 through the Rockchip tree
once the binding changes are available.
The SDK-only multicodecs driver, VAD sound cards and EVB routing fragments
are not introduced by this series. The larger SDK analogue power-sequence
rewrite also remains outside this first upstream series.
Validation:
- Based on broonie/for-next at
11ae63bd9030349a0136fb2edd1622a4a029edc8.
- The same six patches apply with git am --no-3way to Linus master at
36aad21d50d1af3f6954bb0c988257a908202477; range-diff is unchanged.
- Both baselines build an arm64 defconfig Image with
CONFIG_SND_SOC_RK3308=y using Arm GNU Toolchain 14.3.1.
- W=1 codec object builds pass on both baselines.
- All six RK3308 DTBs listed in each baseline build and pass the codec
schema checks; dt_binding_check including its example also passes.
- Strict checkpatch reports no errors, warnings or checks for all six
patches.
- An ASan/UBSan host probe exercises the actual driver functions against
a property/register model: 24 valid and 601 invalid ADC mappings,
480 stream configurations, 15 boost settings, 24 MICBIAS/reset cases,
8 polarity cases and 6 DAPM clock/error-path cases.
No new board playback, capture, headphone hotplug, suspend or analogue
pop measurements have been performed on these upstream kernels. The host
register model does not replace hardware validation.
Xing Zheng (6):
ASoC: codecs: rk3308: restore MICBIAS voltage after reset
ASoC: codecs: rk3308: expose per-channel microphone boost
ASoC: dt-bindings: rk3308-codec: describe ADC routing and jack
detection
ASoC: codecs: rk3308: support ADC stereo-group routing
ASoC: codecs: rk3308: report B/C headphone detection through set_jack
arm64: dts: rockchip: describe RK3308 codec detection resources
.../bindings/sound/rockchip,rk3308-codec.yaml | 54 +++
arch/arm64/boot/dts/rockchip/rk3308.dtsi | 4 +
sound/soc/codecs/rk3308_codec.c | 359 +++++++++++++++++-
3 files changed, 411 insertions(+), 6 deletions(-)
base-commit: 11ae63bd9030349a0136fb2edd1622a4a029edc8
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v1 0/6] RK3308 codec configuration and headphone detection
@ 2026-11-01 4:01 ` Xing Zheng
0 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:01 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
This series ports selected RK3308 codec configuration fixes and features
from the Rockchip SDK to current upstream ASoC. It preserves the recently
merged revision-B support (157f7cba77a9) and existing upstream DAPM power
sequences.
The first two patches preserve the configured MICBIAS voltage across the
component reset and expose the separate microphone boost controls. The
remaining codec patches document and configure ADC group routing and
report the B/C headphone detector through set_jack. Output routing stays
with the machine driver and its jack pin associations.
The last patch describes only the SoC detection resources. Mark can take
patches 1-5 through ASoC; Heiko can take patch 6 through the Rockchip tree
once the binding changes are available.
The SDK-only multicodecs driver, VAD sound cards and EVB routing fragments
are not introduced by this series. The larger SDK analogue power-sequence
rewrite also remains outside this first upstream series.
Validation:
- Based on broonie/for-next at
11ae63bd9030349a0136fb2edd1622a4a029edc8.
- The same six patches apply with git am --no-3way to Linus master at
36aad21d50d1af3f6954bb0c988257a908202477; range-diff is unchanged.
- Both baselines build an arm64 defconfig Image with
CONFIG_SND_SOC_RK3308=y using Arm GNU Toolchain 14.3.1.
- W=1 codec object builds pass on both baselines.
- All six RK3308 DTBs listed in each baseline build and pass the codec
schema checks; dt_binding_check including its example also passes.
- Strict checkpatch reports no errors, warnings or checks for all six
patches.
- An ASan/UBSan host probe exercises the actual driver functions against
a property/register model: 24 valid and 601 invalid ADC mappings,
480 stream configurations, 15 boost settings, 24 MICBIAS/reset cases,
8 polarity cases and 6 DAPM clock/error-path cases.
No new board playback, capture, headphone hotplug, suspend or analogue
pop measurements have been performed on these upstream kernels. The host
register model does not replace hardware validation.
Xing Zheng (6):
ASoC: codecs: rk3308: restore MICBIAS voltage after reset
ASoC: codecs: rk3308: expose per-channel microphone boost
ASoC: dt-bindings: rk3308-codec: describe ADC routing and jack
detection
ASoC: codecs: rk3308: support ADC stereo-group routing
ASoC: codecs: rk3308: report B/C headphone detection through set_jack
arm64: dts: rockchip: describe RK3308 codec detection resources
.../bindings/sound/rockchip,rk3308-codec.yaml | 54 +++
arch/arm64/boot/dts/rockchip/rk3308.dtsi | 4 +
sound/soc/codecs/rk3308_codec.c | 359 +++++++++++++++++-
3 files changed, 411 insertions(+), 6 deletions(-)
base-commit: 11ae63bd9030349a0136fb2edd1622a4a029edc8
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v1 1/6] ASoC: codecs: rk3308: restore MICBIAS voltage after reset
2026-11-01 4:01 ` Xing Zheng
@ 2026-11-01 4:01 ` Xing Zheng
-1 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:01 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
The platform probe programs the requested MICBIAS voltage before
registering the component. The component probe subsequently resets the
codec, losing that setting before capture starts.
Cache the validated voltage selector and apply it during component
initialization after reset. Keep the reset selector when the property is
absent.
Fixes: 4ed0915f5bc4 ("ASoC: codecs: Add RK3308 internal audio codec driver")
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
sound/soc/codecs/rk3308_codec.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/sound/soc/codecs/rk3308_codec.c b/sound/soc/codecs/rk3308_codec.c
index a3d41cd8150d..7a18ff45e391 100644
--- a/sound/soc/codecs/rk3308_codec.c
+++ b/sound/soc/codecs/rk3308_codec.c
@@ -46,6 +46,7 @@ struct rk3308_codec_priv {
struct clk *mclk_tx;
struct snd_soc_component *component;
unsigned char codec_ver;
+ u32 micbias_level;
};
static struct clk_bulk_data rk3308_codec_clocks[] = {
@@ -840,6 +841,10 @@ static int rk3308_codec_initialize(struct rk3308_codec_priv *rk3308)
{
int grp;
+ regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON07(0),
+ RK3308_ADC_LEVEL_RANGE_MICBIAS_MSK,
+ rk3308->micbias_level);
+
/*
* Init ADC digital vol to 0 dB (reset value is 0xff, undocumented).
* Range: -97dB ~ +32dB.
@@ -997,9 +1002,8 @@ static int rk3308_codec_set_micbias_level(struct rk3308_codec_priv *rk3308)
"Invalid value %u for 'rockchip,micbias-avdd-percent'\n",
percent);
- regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON07(0),
- RK3308_ADC_LEVEL_RANGE_MICBIAS_MSK,
- mult << RK3308_ADC_LEVEL_RANGE_MICBIAS_SFT);
+ /* The component probe resets the codec after this property is parsed. */
+ rk3308->micbias_level = mult << RK3308_ADC_LEVEL_RANGE_MICBIAS_SFT;
return 0;
}
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v1 1/6] ASoC: codecs: rk3308: restore MICBIAS voltage after reset
@ 2026-11-01 4:01 ` Xing Zheng
0 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:01 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
The platform probe programs the requested MICBIAS voltage before
registering the component. The component probe subsequently resets the
codec, losing that setting before capture starts.
Cache the validated voltage selector and apply it during component
initialization after reset. Keep the reset selector when the property is
absent.
Fixes: 4ed0915f5bc4 ("ASoC: codecs: Add RK3308 internal audio codec driver")
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
sound/soc/codecs/rk3308_codec.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/sound/soc/codecs/rk3308_codec.c b/sound/soc/codecs/rk3308_codec.c
index a3d41cd8150d..7a18ff45e391 100644
--- a/sound/soc/codecs/rk3308_codec.c
+++ b/sound/soc/codecs/rk3308_codec.c
@@ -46,6 +46,7 @@ struct rk3308_codec_priv {
struct clk *mclk_tx;
struct snd_soc_component *component;
unsigned char codec_ver;
+ u32 micbias_level;
};
static struct clk_bulk_data rk3308_codec_clocks[] = {
@@ -840,6 +841,10 @@ static int rk3308_codec_initialize(struct rk3308_codec_priv *rk3308)
{
int grp;
+ regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON07(0),
+ RK3308_ADC_LEVEL_RANGE_MICBIAS_MSK,
+ rk3308->micbias_level);
+
/*
* Init ADC digital vol to 0 dB (reset value is 0xff, undocumented).
* Range: -97dB ~ +32dB.
@@ -997,9 +1002,8 @@ static int rk3308_codec_set_micbias_level(struct rk3308_codec_priv *rk3308)
"Invalid value %u for 'rockchip,micbias-avdd-percent'\n",
percent);
- regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON07(0),
- RK3308_ADC_LEVEL_RANGE_MICBIAS_MSK,
- mult << RK3308_ADC_LEVEL_RANGE_MICBIAS_SFT);
+ /* The component probe resets the codec after this property is parsed. */
+ rk3308->micbias_level = mult << RK3308_ADC_LEVEL_RANGE_MICBIAS_SFT;
return 0;
}
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v1 2/6] ASoC: codecs: rk3308: expose per-channel microphone boost
2026-11-01 4:01 ` Xing Zheng
@ 2026-11-01 4:01 ` Xing Zheng
-1 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:01 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
The existing MIC Capture Volume controls adjust the ALC gain stage,
leaving the preceding microphone boost stage inaccessible. Add
independent boost controls for all eight inputs without changing the
existing controls or reset gains.
Use the revision-specific dB scales. Revision A permits only the 0 dB
and 20 dB encodings; reject its reserved intermediate settings while
allowing all four settings on revisions B and C.
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
sound/soc/codecs/rk3308_codec.c | 80 ++++++++++++++++++++++++++++++++-
1 file changed, 79 insertions(+), 1 deletion(-)
diff --git a/sound/soc/codecs/rk3308_codec.c b/sound/soc/codecs/rk3308_codec.c
index 7a18ff45e391..fc7e20e82700 100644
--- a/sound/soc/codecs/rk3308_codec.c
+++ b/sound/soc/codecs/rk3308_codec.c
@@ -66,6 +66,84 @@ static const DECLARE_TLV_DB_RANGE(rk3308_codec_dac_lineout_gain_tlv,
3, 3, TLV_DB_SCALE_ITEM(0, 0, 0),
);
+static const DECLARE_TLV_DB_RANGE(rk3308_codec_adc_mic_gain_tlv_a,
+ 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
+ 3, 3, TLV_DB_SCALE_ITEM(2000, 0, 0),
+);
+
+static const DECLARE_TLV_DB_RANGE(rk3308_codec_adc_mic_gain_tlv_b,
+ 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
+ 1, 1, TLV_DB_SCALE_ITEM(660, 0, 0),
+ 2, 2, TLV_DB_SCALE_ITEM(1300, 0, 0),
+ 3, 3, TLV_DB_SCALE_ITEM(2000, 0, 0),
+);
+
+static int rk3308_codec_mic_boost_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+ long gain = ucontrol->value.integer.value[0];
+
+ if (rk3308->codec_ver == ACODEC_VERSION_A && gain != 0 && gain != 3)
+ return -EINVAL;
+
+ return snd_soc_put_volsw(kcontrol, ucontrol);
+}
+
+static const struct snd_kcontrol_new rk3308_codec_mic_boost_controls[] = {
+ SOC_SINGLE_EXT_TLV("MIC1 Boost Volume", RK3308_ADC_ANA_CON01(0),
+ RK3308_ADC_CH1_MIC_GAIN_SFT, RK3308_ADC_CH1_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC2 Boost Volume", RK3308_ADC_ANA_CON01(0),
+ RK3308_ADC_CH2_MIC_GAIN_SFT, RK3308_ADC_CH2_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC3 Boost Volume", RK3308_ADC_ANA_CON01(1),
+ RK3308_ADC_CH1_MIC_GAIN_SFT, RK3308_ADC_CH1_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC4 Boost Volume", RK3308_ADC_ANA_CON01(1),
+ RK3308_ADC_CH2_MIC_GAIN_SFT, RK3308_ADC_CH2_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC5 Boost Volume", RK3308_ADC_ANA_CON01(2),
+ RK3308_ADC_CH1_MIC_GAIN_SFT, RK3308_ADC_CH1_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC6 Boost Volume", RK3308_ADC_ANA_CON01(2),
+ RK3308_ADC_CH2_MIC_GAIN_SFT, RK3308_ADC_CH2_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC7 Boost Volume", RK3308_ADC_ANA_CON01(3),
+ RK3308_ADC_CH1_MIC_GAIN_SFT, RK3308_ADC_CH1_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC8 Boost Volume", RK3308_ADC_ANA_CON01(3),
+ RK3308_ADC_CH2_MIC_GAIN_SFT, RK3308_ADC_CH2_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+};
+
+static int rk3308_codec_add_mic_boost_controls(struct snd_soc_component *component)
+{
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(rk3308_codec_mic_boost_controls); i++) {
+ struct snd_kcontrol_new control = rk3308_codec_mic_boost_controls[i];
+
+ if (rk3308->codec_ver == ACODEC_VERSION_A)
+ control.tlv.p = rk3308_codec_adc_mic_gain_tlv_a;
+ ret = snd_soc_add_component_controls(component, &control, 1);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static const char * const rk3308_codec_hpf_cutoff_text[] = {
"20 Hz", "245 Hz", "612 Hz"
};
@@ -891,7 +969,7 @@ static int rk3308_codec_probe(struct snd_soc_component *component)
rk3308_codec_reset(component);
rk3308_codec_initialize(rk3308);
- return 0;
+ return rk3308_codec_add_mic_boost_controls(component);
}
static int rk3308_codec_set_bias_level(struct snd_soc_component *component,
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v1 2/6] ASoC: codecs: rk3308: expose per-channel microphone boost
@ 2026-11-01 4:01 ` Xing Zheng
0 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:01 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
The existing MIC Capture Volume controls adjust the ALC gain stage,
leaving the preceding microphone boost stage inaccessible. Add
independent boost controls for all eight inputs without changing the
existing controls or reset gains.
Use the revision-specific dB scales. Revision A permits only the 0 dB
and 20 dB encodings; reject its reserved intermediate settings while
allowing all four settings on revisions B and C.
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
sound/soc/codecs/rk3308_codec.c | 80 ++++++++++++++++++++++++++++++++-
1 file changed, 79 insertions(+), 1 deletion(-)
diff --git a/sound/soc/codecs/rk3308_codec.c b/sound/soc/codecs/rk3308_codec.c
index 7a18ff45e391..fc7e20e82700 100644
--- a/sound/soc/codecs/rk3308_codec.c
+++ b/sound/soc/codecs/rk3308_codec.c
@@ -66,6 +66,84 @@ static const DECLARE_TLV_DB_RANGE(rk3308_codec_dac_lineout_gain_tlv,
3, 3, TLV_DB_SCALE_ITEM(0, 0, 0),
);
+static const DECLARE_TLV_DB_RANGE(rk3308_codec_adc_mic_gain_tlv_a,
+ 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
+ 3, 3, TLV_DB_SCALE_ITEM(2000, 0, 0),
+);
+
+static const DECLARE_TLV_DB_RANGE(rk3308_codec_adc_mic_gain_tlv_b,
+ 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
+ 1, 1, TLV_DB_SCALE_ITEM(660, 0, 0),
+ 2, 2, TLV_DB_SCALE_ITEM(1300, 0, 0),
+ 3, 3, TLV_DB_SCALE_ITEM(2000, 0, 0),
+);
+
+static int rk3308_codec_mic_boost_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+ long gain = ucontrol->value.integer.value[0];
+
+ if (rk3308->codec_ver == ACODEC_VERSION_A && gain != 0 && gain != 3)
+ return -EINVAL;
+
+ return snd_soc_put_volsw(kcontrol, ucontrol);
+}
+
+static const struct snd_kcontrol_new rk3308_codec_mic_boost_controls[] = {
+ SOC_SINGLE_EXT_TLV("MIC1 Boost Volume", RK3308_ADC_ANA_CON01(0),
+ RK3308_ADC_CH1_MIC_GAIN_SFT, RK3308_ADC_CH1_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC2 Boost Volume", RK3308_ADC_ANA_CON01(0),
+ RK3308_ADC_CH2_MIC_GAIN_SFT, RK3308_ADC_CH2_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC3 Boost Volume", RK3308_ADC_ANA_CON01(1),
+ RK3308_ADC_CH1_MIC_GAIN_SFT, RK3308_ADC_CH1_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC4 Boost Volume", RK3308_ADC_ANA_CON01(1),
+ RK3308_ADC_CH2_MIC_GAIN_SFT, RK3308_ADC_CH2_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC5 Boost Volume", RK3308_ADC_ANA_CON01(2),
+ RK3308_ADC_CH1_MIC_GAIN_SFT, RK3308_ADC_CH1_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC6 Boost Volume", RK3308_ADC_ANA_CON01(2),
+ RK3308_ADC_CH2_MIC_GAIN_SFT, RK3308_ADC_CH2_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC7 Boost Volume", RK3308_ADC_ANA_CON01(3),
+ RK3308_ADC_CH1_MIC_GAIN_SFT, RK3308_ADC_CH1_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC8 Boost Volume", RK3308_ADC_ANA_CON01(3),
+ RK3308_ADC_CH2_MIC_GAIN_SFT, RK3308_ADC_CH2_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+};
+
+static int rk3308_codec_add_mic_boost_controls(struct snd_soc_component *component)
+{
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(rk3308_codec_mic_boost_controls); i++) {
+ struct snd_kcontrol_new control = rk3308_codec_mic_boost_controls[i];
+
+ if (rk3308->codec_ver == ACODEC_VERSION_A)
+ control.tlv.p = rk3308_codec_adc_mic_gain_tlv_a;
+ ret = snd_soc_add_component_controls(component, &control, 1);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static const char * const rk3308_codec_hpf_cutoff_text[] = {
"20 Hz", "245 Hz", "612 Hz"
};
@@ -891,7 +969,7 @@ static int rk3308_codec_probe(struct snd_soc_component *component)
rk3308_codec_reset(component);
rk3308_codec_initialize(rk3308);
- return 0;
+ return rk3308_codec_add_mic_boost_controls(component);
}
static int rk3308_codec_set_bias_level(struct snd_soc_component *component,
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v1 3/6] ASoC: dt-bindings: rk3308-codec: describe ADC routing and jack detection
2026-11-01 4:01 ` Xing Zheng
@ 2026-11-01 4:01 ` Xing Zheng
-1 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:01 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
Describe the internal ADC stereo-group mapping to I2S2 SDI inputs and
constrain it to a permutation of the four hardware groups.
Document the detection GRF, codec and headphone interrupts, and the
board-specific detection wiring for codec revisions B and C. Keep these
resources optional for existing device trees.
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
.../bindings/sound/rockchip,rk3308-codec.yaml | 54 +++++++++++++++++++
1 file changed, 54 insertions(+)
diff --git a/Documentation/devicetree/bindings/sound/rockchip,rk3308-codec.yaml b/Documentation/devicetree/bindings/sound/rockchip,rk3308-codec.yaml
index 2cf229a076f0..68859c64b72b 100644
--- a/Documentation/devicetree/bindings/sound/rockchip,rk3308-codec.yaml
+++ b/Documentation/devicetree/bindings/sound/rockchip,rk3308-codec.yaml
@@ -36,6 +36,46 @@ properties:
description:
Phandle to the General Register Files (GRF)
+ rockchip,detect-grf:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Phandle to the detection GRF used for headphone insertion and
+ removal interrupts on codec revisions B and C.
+
+ interrupts:
+ items:
+ - description: Codec interrupt
+ - description: Detection GRF headphone interrupt for revisions B and C
+
+ interrupt-names:
+ items:
+ - const: codec
+ - const: hpdet
+
+ rockchip,no-hp-det:
+ type: boolean
+ description: The internal headphone detection input is not connected.
+
+ rockchip,hp-jack-reversed:
+ type: boolean
+ description: The headphone detection input has inverted insertion polarity.
+
+ rockchip,adc-grps-route:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ description: |
+ ADC stereo-group mapping to the four I2S2 SDI inputs. Each entry
+ selects the ADC group connected to the corresponding SDI input,
+ in SDI0 through SDI3 order. Group IDs 0 through 3 represent
+ MIC1/MIC2, MIC3/MIC4, MIC5/MIC6 and MIC7/MIC8 respectively.
+ All four groups must appear exactly once. For example, <1 2 3 0>
+ connects groups 1, 2, 3 and 0 to SDI0, SDI1, SDI2 and SDI3.
+ If omitted, the GRF routing is left unchanged.
+ minItems: 4
+ maxItems: 4
+ uniqueItems: true
+ items:
+ enum: [ 0, 1, 2, 3 ]
+
clocks:
items:
- description: clock for TX
@@ -72,6 +112,14 @@ properties:
enum: [ 50, 55, 60, 65, 70, 75, 80, 85 ]
+dependentRequired:
+ rockchip,detect-grf:
+ - interrupts
+ interrupt-names:
+ - interrupts
+ rockchip,hp-jack-reversed:
+ - rockchip,detect-grf
+
required:
- compatible
- reg
@@ -85,11 +133,17 @@ additionalProperties: false
examples:
- |
#include <dt-bindings/clock/rk3308-cru.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
audio_codec: audio-codec@ff560000 {
compatible = "rockchip,rk3308-codec";
reg = <0xff560000 0x10000>;
rockchip,grf = <&grf>;
+ rockchip,detect-grf = <&detect_grf>;
+ interrupts = <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "codec", "hpdet";
+ rockchip,adc-grps-route = <1 2 3 0>;
clock-names = "mclk_tx", "mclk_rx", "hclk";
clocks = <&cru SCLK_I2S2_8CH_TX_OUT>,
<&cru SCLK_I2S2_8CH_RX_OUT>,
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v1 3/6] ASoC: dt-bindings: rk3308-codec: describe ADC routing and jack detection
@ 2026-11-01 4:01 ` Xing Zheng
0 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:01 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
Describe the internal ADC stereo-group mapping to I2S2 SDI inputs and
constrain it to a permutation of the four hardware groups.
Document the detection GRF, codec and headphone interrupts, and the
board-specific detection wiring for codec revisions B and C. Keep these
resources optional for existing device trees.
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
.../bindings/sound/rockchip,rk3308-codec.yaml | 54 +++++++++++++++++++
1 file changed, 54 insertions(+)
diff --git a/Documentation/devicetree/bindings/sound/rockchip,rk3308-codec.yaml b/Documentation/devicetree/bindings/sound/rockchip,rk3308-codec.yaml
index 2cf229a076f0..68859c64b72b 100644
--- a/Documentation/devicetree/bindings/sound/rockchip,rk3308-codec.yaml
+++ b/Documentation/devicetree/bindings/sound/rockchip,rk3308-codec.yaml
@@ -36,6 +36,46 @@ properties:
description:
Phandle to the General Register Files (GRF)
+ rockchip,detect-grf:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Phandle to the detection GRF used for headphone insertion and
+ removal interrupts on codec revisions B and C.
+
+ interrupts:
+ items:
+ - description: Codec interrupt
+ - description: Detection GRF headphone interrupt for revisions B and C
+
+ interrupt-names:
+ items:
+ - const: codec
+ - const: hpdet
+
+ rockchip,no-hp-det:
+ type: boolean
+ description: The internal headphone detection input is not connected.
+
+ rockchip,hp-jack-reversed:
+ type: boolean
+ description: The headphone detection input has inverted insertion polarity.
+
+ rockchip,adc-grps-route:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ description: |
+ ADC stereo-group mapping to the four I2S2 SDI inputs. Each entry
+ selects the ADC group connected to the corresponding SDI input,
+ in SDI0 through SDI3 order. Group IDs 0 through 3 represent
+ MIC1/MIC2, MIC3/MIC4, MIC5/MIC6 and MIC7/MIC8 respectively.
+ All four groups must appear exactly once. For example, <1 2 3 0>
+ connects groups 1, 2, 3 and 0 to SDI0, SDI1, SDI2 and SDI3.
+ If omitted, the GRF routing is left unchanged.
+ minItems: 4
+ maxItems: 4
+ uniqueItems: true
+ items:
+ enum: [ 0, 1, 2, 3 ]
+
clocks:
items:
- description: clock for TX
@@ -72,6 +112,14 @@ properties:
enum: [ 50, 55, 60, 65, 70, 75, 80, 85 ]
+dependentRequired:
+ rockchip,detect-grf:
+ - interrupts
+ interrupt-names:
+ - interrupts
+ rockchip,hp-jack-reversed:
+ - rockchip,detect-grf
+
required:
- compatible
- reg
@@ -85,11 +133,17 @@ additionalProperties: false
examples:
- |
#include <dt-bindings/clock/rk3308-cru.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
audio_codec: audio-codec@ff560000 {
compatible = "rockchip,rk3308-codec";
reg = <0xff560000 0x10000>;
rockchip,grf = <&grf>;
+ rockchip,detect-grf = <&detect_grf>;
+ interrupts = <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "codec", "hpdet";
+ rockchip,adc-grps-route = <1 2 3 0>;
clock-names = "mclk_tx", "mclk_rx", "hclk";
clocks = <&cru SCLK_I2S2_8CH_TX_OUT>,
<&cru SCLK_I2S2_8CH_RX_OUT>,
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v1 4/6] ASoC: codecs: rk3308: support ADC stereo-group routing
2026-11-01 4:01 ` Xing Zheng
@ 2026-11-01 4:01 ` Xing Zheng
-1 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:01 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
Allow boards to describe the internal ADC-group mux with rockchip,adc-
grps-route. Validate all four group IDs before updating the GRF and
configure the physical ADC groups selected for the requested PCM channel
count.
Reject duplicate and out-of-range IDs and incomplete mappings. Preserve
the existing identity mapping and leave the GRF unchanged when the
property is absent.
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
sound/soc/codecs/rk3308_codec.c | 44 +++++++++++++++++++++++++++++++--
1 file changed, 42 insertions(+), 2 deletions(-)
diff --git a/sound/soc/codecs/rk3308_codec.c b/sound/soc/codecs/rk3308_codec.c
index fc7e20e82700..319b2391dd9b 100644
--- a/sound/soc/codecs/rk3308_codec.c
+++ b/sound/soc/codecs/rk3308_codec.c
@@ -28,6 +28,7 @@
#define ADC_LR_GROUP_MAX 4
+#define GRF_SOC_CON1 0x304
#define GRF_CHIP_ID 0x800
enum {
@@ -47,6 +48,7 @@ struct rk3308_codec_priv {
struct snd_soc_component *component;
unsigned char codec_ver;
u32 micbias_level;
+ u32 adc_grps[ADC_LR_GROUP_MAX];
};
static struct clk_bulk_data rk3308_codec_clocks[] = {
@@ -794,7 +796,7 @@ static int rk3308_codec_adc_dig_config(struct rk3308_codec_priv *rk3308,
* grp 3 = ADC7 and ADC8
*/
u32 used_adc_grps;
- int grp;
+ int grp, idx;
switch (params_channels(params)) {
case 1:
@@ -829,7 +831,8 @@ static int rk3308_codec_adc_dig_config(struct rk3308_codec_priv *rk3308,
return -EINVAL;
}
- for (grp = 0; grp < used_adc_grps; grp++) {
+ for (idx = 0; idx < used_adc_grps; idx++) {
+ grp = rk3308->adc_grps[idx];
regmap_update_bits(rk3308->regmap,
RK3308_ADC_DIG_CON03(grp),
RK3308_ADC_L_CH_BIST_MSK | RK3308_ADC_R_CH_BIST_MSK,
@@ -1086,6 +1089,39 @@ static int rk3308_codec_set_micbias_level(struct rk3308_codec_priv *rk3308)
return 0;
}
+static int rk3308_codec_adc_grps_route(struct rk3308_codec_priv *rk3308)
+{
+ struct device_node *np = rk3308->dev->of_node;
+ unsigned int seen = 0, val = 0;
+ int grp, ret;
+
+ for (grp = 0; grp < ADC_LR_GROUP_MAX; grp++)
+ rk3308->adc_grps[grp] = grp;
+
+ if (!of_find_property(np, "rockchip,adc-grps-route", NULL))
+ return 0;
+
+ if (of_property_count_u32_elems(np, "rockchip,adc-grps-route") !=
+ ADC_LR_GROUP_MAX)
+ return -EINVAL;
+
+ ret = of_property_read_u32_array(np, "rockchip,adc-grps-route",
+ rk3308->adc_grps, ADC_LR_GROUP_MAX);
+ if (ret)
+ return ret;
+
+ for (grp = 0; grp < ADC_LR_GROUP_MAX; grp++) {
+ u32 input = rk3308->adc_grps[grp];
+
+ if (input >= ADC_LR_GROUP_MAX || (seen & BIT(input)))
+ return -EINVAL;
+ seen |= BIT(input);
+ val |= input << (grp * 2);
+ }
+
+ return regmap_write(rk3308->grf, GRF_SOC_CON1, (0xff << 16) | val);
+}
+
static int rk3308_codec_platform_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
@@ -1131,6 +1167,10 @@ static int rk3308_codec_platform_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, rk3308);
+ err = rk3308_codec_adc_grps_route(rk3308);
+ if (err)
+ return dev_err_probe(dev, err, "Invalid ADC group routing\n");
+
err = rk3308_codec_set_micbias_level(rk3308);
if (err)
return err;
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v1 4/6] ASoC: codecs: rk3308: support ADC stereo-group routing
@ 2026-11-01 4:01 ` Xing Zheng
0 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:01 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
Allow boards to describe the internal ADC-group mux with rockchip,adc-
grps-route. Validate all four group IDs before updating the GRF and
configure the physical ADC groups selected for the requested PCM channel
count.
Reject duplicate and out-of-range IDs and incomplete mappings. Preserve
the existing identity mapping and leave the GRF unchanged when the
property is absent.
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
sound/soc/codecs/rk3308_codec.c | 44 +++++++++++++++++++++++++++++++--
1 file changed, 42 insertions(+), 2 deletions(-)
diff --git a/sound/soc/codecs/rk3308_codec.c b/sound/soc/codecs/rk3308_codec.c
index fc7e20e82700..319b2391dd9b 100644
--- a/sound/soc/codecs/rk3308_codec.c
+++ b/sound/soc/codecs/rk3308_codec.c
@@ -28,6 +28,7 @@
#define ADC_LR_GROUP_MAX 4
+#define GRF_SOC_CON1 0x304
#define GRF_CHIP_ID 0x800
enum {
@@ -47,6 +48,7 @@ struct rk3308_codec_priv {
struct snd_soc_component *component;
unsigned char codec_ver;
u32 micbias_level;
+ u32 adc_grps[ADC_LR_GROUP_MAX];
};
static struct clk_bulk_data rk3308_codec_clocks[] = {
@@ -794,7 +796,7 @@ static int rk3308_codec_adc_dig_config(struct rk3308_codec_priv *rk3308,
* grp 3 = ADC7 and ADC8
*/
u32 used_adc_grps;
- int grp;
+ int grp, idx;
switch (params_channels(params)) {
case 1:
@@ -829,7 +831,8 @@ static int rk3308_codec_adc_dig_config(struct rk3308_codec_priv *rk3308,
return -EINVAL;
}
- for (grp = 0; grp < used_adc_grps; grp++) {
+ for (idx = 0; idx < used_adc_grps; idx++) {
+ grp = rk3308->adc_grps[idx];
regmap_update_bits(rk3308->regmap,
RK3308_ADC_DIG_CON03(grp),
RK3308_ADC_L_CH_BIST_MSK | RK3308_ADC_R_CH_BIST_MSK,
@@ -1086,6 +1089,39 @@ static int rk3308_codec_set_micbias_level(struct rk3308_codec_priv *rk3308)
return 0;
}
+static int rk3308_codec_adc_grps_route(struct rk3308_codec_priv *rk3308)
+{
+ struct device_node *np = rk3308->dev->of_node;
+ unsigned int seen = 0, val = 0;
+ int grp, ret;
+
+ for (grp = 0; grp < ADC_LR_GROUP_MAX; grp++)
+ rk3308->adc_grps[grp] = grp;
+
+ if (!of_find_property(np, "rockchip,adc-grps-route", NULL))
+ return 0;
+
+ if (of_property_count_u32_elems(np, "rockchip,adc-grps-route") !=
+ ADC_LR_GROUP_MAX)
+ return -EINVAL;
+
+ ret = of_property_read_u32_array(np, "rockchip,adc-grps-route",
+ rk3308->adc_grps, ADC_LR_GROUP_MAX);
+ if (ret)
+ return ret;
+
+ for (grp = 0; grp < ADC_LR_GROUP_MAX; grp++) {
+ u32 input = rk3308->adc_grps[grp];
+
+ if (input >= ADC_LR_GROUP_MAX || (seen & BIT(input)))
+ return -EINVAL;
+ seen |= BIT(input);
+ val |= input << (grp * 2);
+ }
+
+ return regmap_write(rk3308->grf, GRF_SOC_CON1, (0xff << 16) | val);
+}
+
static int rk3308_codec_platform_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
@@ -1131,6 +1167,10 @@ static int rk3308_codec_platform_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, rk3308);
+ err = rk3308_codec_adc_grps_route(rk3308);
+ if (err)
+ return dev_err_probe(dev, err, "Invalid ADC group routing\n");
+
err = rk3308_codec_set_micbias_level(rk3308);
if (err)
return err;
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v1 5/6] ASoC: codecs: rk3308: report B/C headphone detection through set_jack
2026-11-01 4:01 ` Xing Zheng
@ 2026-11-01 4:01 ` Xing Zheng
-1 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:01 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
Use the detection GRF interrupt to report headphone insertion and
removal on codec revisions B and C. Keep detection optional for existing
device trees and retain index-based IRQ lookup for trees without
interrupt names.
Enable the detector only while a machine driver has registered a jack.
Cancel its work and disable its IRQ before releasing the jack. Keep the
revision-C DAC clock running while monitoring, and restore normal DAPM
clock ownership when monitoring stops.
Report jack status without changing codec routes from the worker: jack
pin associations and output policy belong to the machine driver.
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
sound/soc/codecs/rk3308_codec.c | 225 ++++++++++++++++++++++++++++++++
1 file changed, 225 insertions(+)
diff --git a/sound/soc/codecs/rk3308_codec.c b/sound/soc/codecs/rk3308_codec.c
index 319b2391dd9b..4e3e38a8571f 100644
--- a/sound/soc/codecs/rk3308_codec.c
+++ b/sound/soc/codecs/rk3308_codec.c
@@ -10,15 +10,19 @@
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/init.h>
+#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/util_macros.h>
+#include <linux/workqueue.h>
#include <sound/core.h>
+#include <sound/jack.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
@@ -31,6 +35,14 @@
#define GRF_SOC_CON1 0x304
#define GRF_CHIP_ID 0x800
+#define RK3308_HPDET_COUNTER 0x0030
+#define RK3308_HPDET_CON 0x0034
+#define RK3308_HPDET_STATUS 0x0038
+#define RK3308_HPDET_STATUS_CLR 0x003c
+#define RK3308_HPDET_EDGES (BIT(0) | BIT(1))
+/* 200 ms at the detection block's 100 MHz clock. */
+#define RK3308_HPDET_FILTER_CYCLES 20000000
+
enum {
ACODEC_VERSION_A = 'A',
ACODEC_VERSION_B,
@@ -49,6 +61,17 @@ struct rk3308_codec_priv {
unsigned char codec_ver;
u32 micbias_level;
u32 adc_grps[ADC_LR_GROUP_MAX];
+ struct regmap *detect_grf;
+ /* Serialize detector configuration and jack lifetime changes. */
+ struct mutex hpdet_lock;
+ struct snd_soc_jack *hpdet_jack;
+ struct delayed_work hpdet_work;
+ unsigned int hpdet_status;
+ int hpdet_irq;
+ bool hpdet_enabled;
+ bool hpdet_seen_irq;
+ bool hpdet_clock_forced;
+ bool hp_jack_reversed;
};
static struct clk_bulk_data rk3308_codec_clocks[] = {
@@ -1015,7 +1038,205 @@ static int rk3308_codec_set_bias_level(struct snd_soc_component *component,
return 0;
}
+static bool rk3308_codec_hpdet_inserted(struct rk3308_codec_priv *rk3308,
+ unsigned int status)
+{
+ status &= RK3308_HPDET_EDGES;
+ if (rk3308->hp_jack_reversed)
+ return status == 0 || status == BIT(1);
+
+ return status == BIT(0);
+}
+
+static void rk3308_codec_hpdet_work(struct work_struct *work)
+{
+ struct rk3308_codec_priv *rk3308 =
+ container_of(to_delayed_work(work), struct rk3308_codec_priv, hpdet_work);
+ unsigned int status;
+ int ret;
+
+ if (!rk3308->component->card->instantiated) {
+ mod_delayed_work(system_power_efficient_wq, &rk3308->hpdet_work,
+ msecs_to_jiffies(100));
+ return;
+ }
+
+ if (READ_ONCE(rk3308->hpdet_seen_irq)) {
+ status = READ_ONCE(rk3308->hpdet_status);
+ } else {
+ ret = regmap_read(rk3308->detect_grf, RK3308_HPDET_STATUS, &status);
+ if (ret)
+ return;
+ }
+
+ /* The machine driver owns the jack pins and output-routing policy. */
+ snd_soc_jack_report(rk3308->hpdet_jack,
+ rk3308_codec_hpdet_inserted(rk3308, status) ?
+ SND_JACK_HEADPHONE : 0, SND_JACK_HEADPHONE);
+}
+
+static irqreturn_t rk3308_codec_hpdet_irq(int irq, void *data)
+{
+ struct rk3308_codec_priv *rk3308 = data;
+ unsigned int status;
+ int ret;
+
+ ret = regmap_read(rk3308->detect_grf, RK3308_HPDET_STATUS, &status);
+ if (ret || !(status & RK3308_HPDET_EDGES))
+ return IRQ_NONE;
+
+ ret = regmap_write(rk3308->detect_grf, RK3308_HPDET_STATUS_CLR,
+ status & RK3308_HPDET_EDGES);
+ if (ret)
+ return IRQ_HANDLED;
+
+ WRITE_ONCE(rk3308->hpdet_status, status);
+ WRITE_ONCE(rk3308->hpdet_seen_irq, true);
+ mod_delayed_work(system_power_efficient_wq, &rk3308->hpdet_work,
+ msecs_to_jiffies(10));
+
+ return IRQ_HANDLED;
+}
+
+static int rk3308_codec_hpdet_clock(struct rk3308_codec_priv *rk3308, bool on)
+{
+ struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(rk3308->component);
+ int ret;
+
+ if (rk3308->codec_ver != ACODEC_VERSION_C || on == rk3308->hpdet_clock_forced)
+ return 0;
+
+ /* Revision C needs the DAC clock while the detector is monitoring. */
+ snd_soc_dapm_mutex_lock(dapm);
+ if (on) {
+ ret = snd_soc_dapm_force_enable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+ } else {
+ ret = snd_soc_dapm_disable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+ if (!ret)
+ ret = snd_soc_dapm_enable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+ }
+ if (!ret) {
+ rk3308->hpdet_clock_forced = on;
+ ret = snd_soc_dapm_sync_unlocked(dapm);
+ }
+ snd_soc_dapm_mutex_unlock(dapm);
+
+ return ret;
+}
+
+static int rk3308_codec_set_jack(struct snd_soc_component *component,
+ struct snd_soc_jack *jack, void *data)
+{
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+ int ret;
+
+ if (!rk3308->detect_grf)
+ return jack ? -EOPNOTSUPP : 0;
+
+ mutex_lock(&rk3308->hpdet_lock);
+ if (rk3308->hpdet_enabled) {
+ disable_irq(rk3308->hpdet_irq);
+ rk3308->hpdet_enabled = false;
+ }
+ cancel_delayed_work_sync(&rk3308->hpdet_work);
+ rk3308->hpdet_jack = NULL;
+
+ ret = regmap_write(rk3308->detect_grf, RK3308_HPDET_CON,
+ RK3308_HPDET_EDGES << 16);
+ if (ret)
+ goto disable;
+ ret = regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON00,
+ RK3308_DAC_HEADPHONE_DET_EN);
+ if (ret || !jack)
+ goto disable;
+
+ ret = rk3308_codec_hpdet_clock(rk3308, true);
+ if (ret)
+ goto disable;
+ ret = regmap_write(rk3308->detect_grf, RK3308_HPDET_COUNTER,
+ RK3308_HPDET_FILTER_CYCLES);
+ if (ret)
+ goto disable;
+ ret = regmap_write(rk3308->detect_grf, RK3308_HPDET_STATUS_CLR,
+ RK3308_HPDET_EDGES);
+ if (ret)
+ goto disable;
+ ret = regmap_set_bits(rk3308->regmap, RK3308_DAC_ANA_CON00,
+ RK3308_DAC_HEADPHONE_DET_EN);
+ if (ret)
+ goto disable;
+ ret = regmap_write(rk3308->detect_grf, RK3308_HPDET_CON,
+ (RK3308_HPDET_EDGES << 16) | RK3308_HPDET_EDGES);
+ if (ret)
+ goto disable;
+
+ rk3308->hpdet_jack = jack;
+ WRITE_ONCE(rk3308->hpdet_seen_irq, false);
+ rk3308->hpdet_enabled = true;
+ /* Wait for the 200 ms hardware filter before the initial report. */
+ mod_delayed_work(system_power_efficient_wq, &rk3308->hpdet_work,
+ msecs_to_jiffies(210));
+ enable_irq(rk3308->hpdet_irq);
+ mutex_unlock(&rk3308->hpdet_lock);
+
+ return 0;
+
+disable:
+ regmap_write(rk3308->detect_grf, RK3308_HPDET_CON, RK3308_HPDET_EDGES << 16);
+ regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON00,
+ RK3308_DAC_HEADPHONE_DET_EN);
+ if (ret)
+ rk3308_codec_hpdet_clock(rk3308, false);
+ else
+ ret = rk3308_codec_hpdet_clock(rk3308, false);
+ mutex_unlock(&rk3308->hpdet_lock);
+
+ return ret;
+}
+
+static void rk3308_codec_remove(struct snd_soc_component *component)
+{
+ rk3308_codec_set_jack(component, NULL, NULL);
+}
+
+static int rk3308_codec_hpdet_init(struct platform_device *pdev,
+ struct rk3308_codec_priv *rk3308)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ int ret;
+
+ if (rk3308->codec_ver == ACODEC_VERSION_A ||
+ of_property_read_bool(np, "rockchip,no-hp-det") ||
+ !of_find_property(np, "rockchip,detect-grf", NULL))
+ return 0;
+
+ rk3308->detect_grf = syscon_regmap_lookup_by_phandle(np, "rockchip,detect-grf");
+ if (IS_ERR(rk3308->detect_grf))
+ return dev_err_probe(dev, PTR_ERR(rk3308->detect_grf),
+ "Failed to get detection GRF\n");
+
+ if (of_find_property(np, "interrupt-names", NULL))
+ rk3308->hpdet_irq = platform_get_irq_byname(pdev, "hpdet");
+ else
+ rk3308->hpdet_irq = platform_get_irq(pdev, 1);
+ if (rk3308->hpdet_irq < 0)
+ return rk3308->hpdet_irq;
+
+ mutex_init(&rk3308->hpdet_lock);
+ INIT_DELAYED_WORK(&rk3308->hpdet_work, rk3308_codec_hpdet_work);
+ rk3308->hp_jack_reversed = of_property_read_bool(np, "rockchip,hp-jack-reversed");
+ ret = devm_request_threaded_irq(dev, rk3308->hpdet_irq, NULL, rk3308_codec_hpdet_irq,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN, "rk3308-hpdet", rk3308);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request headphone IRQ\n");
+
+ return 0;
+}
+
static const struct snd_soc_component_driver rk3308_codec_component_driver = {
+ .remove = rk3308_codec_remove,
+ .set_jack = rk3308_codec_set_jack,
.probe = rk3308_codec_probe,
.set_bias_level = rk3308_codec_set_bias_level,
.controls = rk3308_codec_controls,
@@ -1175,6 +1396,10 @@ static int rk3308_codec_platform_probe(struct platform_device *pdev)
if (err)
return err;
+ err = rk3308_codec_hpdet_init(pdev, rk3308);
+ if (err)
+ return err;
+
err = devm_snd_soc_register_component(dev, &rk3308_codec_component_driver,
&rk3308_codec_dai_driver, 1);
if (err)
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v1 5/6] ASoC: codecs: rk3308: report B/C headphone detection through set_jack
@ 2026-11-01 4:01 ` Xing Zheng
0 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:01 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
Use the detection GRF interrupt to report headphone insertion and
removal on codec revisions B and C. Keep detection optional for existing
device trees and retain index-based IRQ lookup for trees without
interrupt names.
Enable the detector only while a machine driver has registered a jack.
Cancel its work and disable its IRQ before releasing the jack. Keep the
revision-C DAC clock running while monitoring, and restore normal DAPM
clock ownership when monitoring stops.
Report jack status without changing codec routes from the worker: jack
pin associations and output policy belong to the machine driver.
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
sound/soc/codecs/rk3308_codec.c | 225 ++++++++++++++++++++++++++++++++
1 file changed, 225 insertions(+)
diff --git a/sound/soc/codecs/rk3308_codec.c b/sound/soc/codecs/rk3308_codec.c
index 319b2391dd9b..4e3e38a8571f 100644
--- a/sound/soc/codecs/rk3308_codec.c
+++ b/sound/soc/codecs/rk3308_codec.c
@@ -10,15 +10,19 @@
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/init.h>
+#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/util_macros.h>
+#include <linux/workqueue.h>
#include <sound/core.h>
+#include <sound/jack.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
@@ -31,6 +35,14 @@
#define GRF_SOC_CON1 0x304
#define GRF_CHIP_ID 0x800
+#define RK3308_HPDET_COUNTER 0x0030
+#define RK3308_HPDET_CON 0x0034
+#define RK3308_HPDET_STATUS 0x0038
+#define RK3308_HPDET_STATUS_CLR 0x003c
+#define RK3308_HPDET_EDGES (BIT(0) | BIT(1))
+/* 200 ms at the detection block's 100 MHz clock. */
+#define RK3308_HPDET_FILTER_CYCLES 20000000
+
enum {
ACODEC_VERSION_A = 'A',
ACODEC_VERSION_B,
@@ -49,6 +61,17 @@ struct rk3308_codec_priv {
unsigned char codec_ver;
u32 micbias_level;
u32 adc_grps[ADC_LR_GROUP_MAX];
+ struct regmap *detect_grf;
+ /* Serialize detector configuration and jack lifetime changes. */
+ struct mutex hpdet_lock;
+ struct snd_soc_jack *hpdet_jack;
+ struct delayed_work hpdet_work;
+ unsigned int hpdet_status;
+ int hpdet_irq;
+ bool hpdet_enabled;
+ bool hpdet_seen_irq;
+ bool hpdet_clock_forced;
+ bool hp_jack_reversed;
};
static struct clk_bulk_data rk3308_codec_clocks[] = {
@@ -1015,7 +1038,205 @@ static int rk3308_codec_set_bias_level(struct snd_soc_component *component,
return 0;
}
+static bool rk3308_codec_hpdet_inserted(struct rk3308_codec_priv *rk3308,
+ unsigned int status)
+{
+ status &= RK3308_HPDET_EDGES;
+ if (rk3308->hp_jack_reversed)
+ return status == 0 || status == BIT(1);
+
+ return status == BIT(0);
+}
+
+static void rk3308_codec_hpdet_work(struct work_struct *work)
+{
+ struct rk3308_codec_priv *rk3308 =
+ container_of(to_delayed_work(work), struct rk3308_codec_priv, hpdet_work);
+ unsigned int status;
+ int ret;
+
+ if (!rk3308->component->card->instantiated) {
+ mod_delayed_work(system_power_efficient_wq, &rk3308->hpdet_work,
+ msecs_to_jiffies(100));
+ return;
+ }
+
+ if (READ_ONCE(rk3308->hpdet_seen_irq)) {
+ status = READ_ONCE(rk3308->hpdet_status);
+ } else {
+ ret = regmap_read(rk3308->detect_grf, RK3308_HPDET_STATUS, &status);
+ if (ret)
+ return;
+ }
+
+ /* The machine driver owns the jack pins and output-routing policy. */
+ snd_soc_jack_report(rk3308->hpdet_jack,
+ rk3308_codec_hpdet_inserted(rk3308, status) ?
+ SND_JACK_HEADPHONE : 0, SND_JACK_HEADPHONE);
+}
+
+static irqreturn_t rk3308_codec_hpdet_irq(int irq, void *data)
+{
+ struct rk3308_codec_priv *rk3308 = data;
+ unsigned int status;
+ int ret;
+
+ ret = regmap_read(rk3308->detect_grf, RK3308_HPDET_STATUS, &status);
+ if (ret || !(status & RK3308_HPDET_EDGES))
+ return IRQ_NONE;
+
+ ret = regmap_write(rk3308->detect_grf, RK3308_HPDET_STATUS_CLR,
+ status & RK3308_HPDET_EDGES);
+ if (ret)
+ return IRQ_HANDLED;
+
+ WRITE_ONCE(rk3308->hpdet_status, status);
+ WRITE_ONCE(rk3308->hpdet_seen_irq, true);
+ mod_delayed_work(system_power_efficient_wq, &rk3308->hpdet_work,
+ msecs_to_jiffies(10));
+
+ return IRQ_HANDLED;
+}
+
+static int rk3308_codec_hpdet_clock(struct rk3308_codec_priv *rk3308, bool on)
+{
+ struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(rk3308->component);
+ int ret;
+
+ if (rk3308->codec_ver != ACODEC_VERSION_C || on == rk3308->hpdet_clock_forced)
+ return 0;
+
+ /* Revision C needs the DAC clock while the detector is monitoring. */
+ snd_soc_dapm_mutex_lock(dapm);
+ if (on) {
+ ret = snd_soc_dapm_force_enable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+ } else {
+ ret = snd_soc_dapm_disable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+ if (!ret)
+ ret = snd_soc_dapm_enable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+ }
+ if (!ret) {
+ rk3308->hpdet_clock_forced = on;
+ ret = snd_soc_dapm_sync_unlocked(dapm);
+ }
+ snd_soc_dapm_mutex_unlock(dapm);
+
+ return ret;
+}
+
+static int rk3308_codec_set_jack(struct snd_soc_component *component,
+ struct snd_soc_jack *jack, void *data)
+{
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+ int ret;
+
+ if (!rk3308->detect_grf)
+ return jack ? -EOPNOTSUPP : 0;
+
+ mutex_lock(&rk3308->hpdet_lock);
+ if (rk3308->hpdet_enabled) {
+ disable_irq(rk3308->hpdet_irq);
+ rk3308->hpdet_enabled = false;
+ }
+ cancel_delayed_work_sync(&rk3308->hpdet_work);
+ rk3308->hpdet_jack = NULL;
+
+ ret = regmap_write(rk3308->detect_grf, RK3308_HPDET_CON,
+ RK3308_HPDET_EDGES << 16);
+ if (ret)
+ goto disable;
+ ret = regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON00,
+ RK3308_DAC_HEADPHONE_DET_EN);
+ if (ret || !jack)
+ goto disable;
+
+ ret = rk3308_codec_hpdet_clock(rk3308, true);
+ if (ret)
+ goto disable;
+ ret = regmap_write(rk3308->detect_grf, RK3308_HPDET_COUNTER,
+ RK3308_HPDET_FILTER_CYCLES);
+ if (ret)
+ goto disable;
+ ret = regmap_write(rk3308->detect_grf, RK3308_HPDET_STATUS_CLR,
+ RK3308_HPDET_EDGES);
+ if (ret)
+ goto disable;
+ ret = regmap_set_bits(rk3308->regmap, RK3308_DAC_ANA_CON00,
+ RK3308_DAC_HEADPHONE_DET_EN);
+ if (ret)
+ goto disable;
+ ret = regmap_write(rk3308->detect_grf, RK3308_HPDET_CON,
+ (RK3308_HPDET_EDGES << 16) | RK3308_HPDET_EDGES);
+ if (ret)
+ goto disable;
+
+ rk3308->hpdet_jack = jack;
+ WRITE_ONCE(rk3308->hpdet_seen_irq, false);
+ rk3308->hpdet_enabled = true;
+ /* Wait for the 200 ms hardware filter before the initial report. */
+ mod_delayed_work(system_power_efficient_wq, &rk3308->hpdet_work,
+ msecs_to_jiffies(210));
+ enable_irq(rk3308->hpdet_irq);
+ mutex_unlock(&rk3308->hpdet_lock);
+
+ return 0;
+
+disable:
+ regmap_write(rk3308->detect_grf, RK3308_HPDET_CON, RK3308_HPDET_EDGES << 16);
+ regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON00,
+ RK3308_DAC_HEADPHONE_DET_EN);
+ if (ret)
+ rk3308_codec_hpdet_clock(rk3308, false);
+ else
+ ret = rk3308_codec_hpdet_clock(rk3308, false);
+ mutex_unlock(&rk3308->hpdet_lock);
+
+ return ret;
+}
+
+static void rk3308_codec_remove(struct snd_soc_component *component)
+{
+ rk3308_codec_set_jack(component, NULL, NULL);
+}
+
+static int rk3308_codec_hpdet_init(struct platform_device *pdev,
+ struct rk3308_codec_priv *rk3308)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ int ret;
+
+ if (rk3308->codec_ver == ACODEC_VERSION_A ||
+ of_property_read_bool(np, "rockchip,no-hp-det") ||
+ !of_find_property(np, "rockchip,detect-grf", NULL))
+ return 0;
+
+ rk3308->detect_grf = syscon_regmap_lookup_by_phandle(np, "rockchip,detect-grf");
+ if (IS_ERR(rk3308->detect_grf))
+ return dev_err_probe(dev, PTR_ERR(rk3308->detect_grf),
+ "Failed to get detection GRF\n");
+
+ if (of_find_property(np, "interrupt-names", NULL))
+ rk3308->hpdet_irq = platform_get_irq_byname(pdev, "hpdet");
+ else
+ rk3308->hpdet_irq = platform_get_irq(pdev, 1);
+ if (rk3308->hpdet_irq < 0)
+ return rk3308->hpdet_irq;
+
+ mutex_init(&rk3308->hpdet_lock);
+ INIT_DELAYED_WORK(&rk3308->hpdet_work, rk3308_codec_hpdet_work);
+ rk3308->hp_jack_reversed = of_property_read_bool(np, "rockchip,hp-jack-reversed");
+ ret = devm_request_threaded_irq(dev, rk3308->hpdet_irq, NULL, rk3308_codec_hpdet_irq,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN, "rk3308-hpdet", rk3308);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request headphone IRQ\n");
+
+ return 0;
+}
+
static const struct snd_soc_component_driver rk3308_codec_component_driver = {
+ .remove = rk3308_codec_remove,
+ .set_jack = rk3308_codec_set_jack,
.probe = rk3308_codec_probe,
.set_bias_level = rk3308_codec_set_bias_level,
.controls = rk3308_codec_controls,
@@ -1175,6 +1396,10 @@ static int rk3308_codec_platform_probe(struct platform_device *pdev)
if (err)
return err;
+ err = rk3308_codec_hpdet_init(pdev, rk3308);
+ if (err)
+ return err;
+
err = devm_snd_soc_register_component(dev, &rk3308_codec_component_driver,
&rk3308_codec_dai_driver, 1);
if (err)
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v1 6/6] arm64: dts: rockchip: describe RK3308 codec detection resources
2026-11-01 4:01 ` Xing Zheng
@ 2026-11-01 4:01 ` Xing Zheng
-1 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:01 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
Describe the detection GRF phandle and the codec and headphone
interrupts for the internal audio codec. Name the interrupts so
consumers can identify the revision-B/C headphone detector without
depending on resource indices.
Keep board audio routes and external amplifier GPIOs out of the SoC
description.
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
arch/arm64/boot/dts/rockchip/rk3308.dtsi | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3308.dtsi b/arch/arm64/boot/dts/rockchip/rk3308.dtsi
index 31c25de2d689..fb4465eda574 100644
--- a/arch/arm64/boot/dts/rockchip/rk3308.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3308.dtsi
@@ -836,6 +836,10 @@ codec: codec@ff560000 {
compatible = "rockchip,rk3308-codec";
reg = <0x0 0xff560000 0x0 0x10000>;
rockchip,grf = <&grf>;
+ rockchip,detect-grf = <&detect_grf>;
+ interrupts = <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "codec", "hpdet";
clock-names = "mclk_tx", "mclk_rx", "hclk";
clocks = <&cru SCLK_I2S2_8CH_TX_OUT>,
<&cru SCLK_I2S2_8CH_RX_OUT>,
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v1 6/6] arm64: dts: rockchip: describe RK3308 codec detection resources
@ 2026-11-01 4:01 ` Xing Zheng
0 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:01 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
Describe the detection GRF phandle and the codec and headphone
interrupts for the internal audio codec. Name the interrupts so
consumers can identify the revision-B/C headphone detector without
depending on resource indices.
Keep board audio routes and external amplifier GPIOs out of the SoC
description.
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
arch/arm64/boot/dts/rockchip/rk3308.dtsi | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3308.dtsi b/arch/arm64/boot/dts/rockchip/rk3308.dtsi
index 31c25de2d689..fb4465eda574 100644
--- a/arch/arm64/boot/dts/rockchip/rk3308.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3308.dtsi
@@ -836,6 +836,10 @@ codec: codec@ff560000 {
compatible = "rockchip,rk3308-codec";
reg = <0x0 0xff560000 0x0 0x10000>;
rockchip,grf = <&grf>;
+ rockchip,detect-grf = <&detect_grf>;
+ interrupts = <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "codec", "hpdet";
clock-names = "mclk_tx", "mclk_rx", "hclk";
clocks = <&cru SCLK_I2S2_8CH_TX_OUT>,
<&cru SCLK_I2S2_8CH_RX_OUT>,
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 25+ messages in thread
* Re: [PATCH v1 2/6] ASoC: codecs: rk3308: expose per-channel microphone boost
2026-11-01 4:01 ` Xing Zheng
(?)
@ 2026-11-01 4:13 ` sashiko-bot
-1 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-11-01 4:13 UTC (permalink / raw)
To: Xing Zheng, linux-sound; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] ASoC: codecs: rk3308: non-contiguous mic boost range on rev A
--
commit eb32aeb2bd3d3cce70cc5e7d254a08bf85314017
Author: Xing Zheng <zhengxing@rock-chips.com>
ASoC: codecs: rk3308: expose per-channel microphone boost
This commit adds per-channel microphone boost controls for all eight ADC
inputs on the RK3308 codec, using revision-specific TLV dB scales and
rejecting intermediate gain settings on revision A.
> diff --git a/sound/soc/codecs/rk3308_codec.c b/sound/soc/codecs/rk3308_codec.c
> index 7a18ff45e391b..fc7e20e82700b 100644
> --- a/sound/soc/codecs/rk3308_codec.c
> +++ b/sound/soc/codecs/rk3308_codec.c
> @@ -66,6 +66,84 @@ static const DECLARE_TLV_DB_RANGE(rk3308_codec_dac_lineout_gain_tlv,
> 3, 3, TLV_DB_SCALE_ITEM(0, 0, 0),
> );
>
> +static const DECLARE_TLV_DB_RANGE(rk3308_codec_adc_mic_gain_tlv_a,
> + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
> + 3, 3, TLV_DB_SCALE_ITEM(2000, 0, 0),
> +);
> +
> +static const DECLARE_TLV_DB_RANGE(rk3308_codec_adc_mic_gain_tlv_b,
> + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
> + 1, 1, TLV_DB_SCALE_ITEM(660, 0, 0),
> + 2, 2, TLV_DB_SCALE_ITEM(1300, 0, 0),
> + 3, 3, TLV_DB_SCALE_ITEM(2000, 0, 0),
> +);
> +
> +static int rk3308_codec_mic_boost_put(struct snd_kcontrol *kcontrol,
> + struct snd_ctl_elem_value *ucontrol)
> +{
> + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
> + struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
> + long gain = ucontrol->value.integer.value[0];
> +
> + if (rk3308->codec_ver == ACODEC_VERSION_A && gain != 0 && gain != 3)
> + return -EINVAL;
> +
> + return snd_soc_put_volsw(kcontrol, ucontrol);
> +}
> +
> +static const struct snd_kcontrol_new rk3308_codec_mic_boost_controls[] = {
> + SOC_SINGLE_EXT_TLV("MIC1 Boost Volume", RK3308_ADC_ANA_CON01(0),
> + RK3308_ADC_CH1_MIC_GAIN_SFT, RK3308_ADC_CH1_MIC_GAIN_MAX, 0,
> + snd_soc_get_volsw, rk3308_codec_mic_boost_put,
> + rk3308_codec_adc_mic_gain_tlv_b),
[Severity: Medium]
On codec revision A (ACODEC_VERSION_A), rk3308_codec_probe() ->
rk3308_codec_add_mic_boost_controls() in sound/soc/codecs/rk3308_codec.c
registers the controls in rk3308_codec_mic_boost_controls[] using
SOC_SINGLE_EXT_TLV() with RK3308_ADC_CH1_MIC_GAIN_MAX (3) and
snd_soc_info_volsw().
When userspace queries SNDRV_CTL_IOCTL_ELEM_INFO on ACODEC_VERSION_A,
snd_soc_info_volsw() advertises an integer control with min = 0 and max = 3,
indicating that integer steps 0, 1, 2, and 3 are all valid. However, writing
intermediate steps 1 or 2 via SNDRV_CTL_IOCTL_ELEM_WRITE ->
rk3308_codec_mic_boost_put() returns -EINVAL, and
rk3308_codec_adc_mic_gain_tlv_a leaves control values 1 and 2 unmapped in the
TLV dB range.
In ALSA, integer controls are expected to expose a contiguous range of valid
steps from min to max without holes, as verified by
test_ctl_write_valid_integer() in tools/testing/selftests/alsa/mixer-test.c:
for (i = 0; i < snd_ctl_elem_info_get_count(ctl->info); i++) {
for (j = snd_ctl_elem_info_get_min(ctl->info);
j <= snd_ctl_elem_info_get_max(ctl->info); j += step) {
snd_ctl_elem_value_set_integer(val, i, j);
err = write_and_verify(ctl, val, NULL);
if (err != 0)
fail = true;
}
}
Wouldn't advertising 0..3 on ACODEC_VERSION_A while rejecting values 1 and 2
cause single-step mixer adjustments (such as incrementing from 0 to 1), TLV
dB lookups on intermediate values, and the ALSA mixer kselftest to fail? Could
revision A expose a contiguous 0..1 control range (mapping 0 -> 0 and 1 -> 3
in the register) instead?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261101040125.1864141-1-zhengxing@rock-chips.com?part=2
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v2 0/3] RK3308 codec audio sequencing and headphone support
2026-11-01 4:01 ` Xing Zheng
@ 2026-11-01 4:41 ` Xing Zheng
-1 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:41 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
Restore the full RK3308 codec analog power sequencing and headphone
support from the downstream 6.12 implementation, adapted to the current
ASoC interfaces. Keep the complete codec source and register definitions
in one patch, alongside the binding and SoC detection-resource changes.
The codec change includes revision-specific ADC/DAC supply sequencing,
reference-current and headphone gain ramps, A/B CIC fades, active ADC
resynchronization, MICBIAS restoration, microphone boost controls, ADC
stereo-group routing and B/C headphone detection. Machine drivers retain
ownership of the external amplifier GPIOs.
The SDK-only multicodecs shutdown fix and EVB/VAD routing patch are
outside this series because their target drivers and board files are
not present in the upstream tree.
Patches 1-2 are for the ASoC tree; patch 3 is for the Rockchip DT tree.
The detection resources remain optional for existing device trees.
Changes since v1:
- Restore the full analog sequencing omitted from the initial posting.
- Consolidate all codec C/header changes into a single patch.
- Use the original binding descriptions and legacy IRQ lookup rules.
- Preserve upstream automatic DAI format selection and adapt accessors.
- Use sleepable waits while preserving downstream minimum delay requests.
- Keep the SDK-only machine-driver and EVB/VAD work deferred.
Previous posting:
https://lore.kernel.org/r/20261101040125.1864141-1-zhengxing@rock-chips.com
Validation:
- ARM64 defconfig plus built-in RK3308 codec, Image and all six RK3308
DTBs build on broonie/for-next and the Linus baseline with GCC 14.3.1.
- Codec W=1 builds, binding/example validation and codec-schema checks
on the six DTBs pass; strict checkpatch reports no diagnostics.
- The three patches apply to the Linus baseline without 3-way fallback;
range-diff reports all three patches unchanged.
- Extracted SDK/upstream analog functions match across 11520 modeled
register traces, including operation order and minimum delay requests,
under ASan/UBSan. Parameter/error-path probes also pass.
These host models do not verify actual hardware settling time.
No new playback, capture, hotplug, suspend/resume or electrical timing
measurements have been performed on hardware with this upstream series.
Xing Zheng (3):
ASoC: dt-bindings: rk3308-codec: describe detection and ADC routing
ASoC: codecs: rk3308: restore audio and headphone support
arm64: dts: rockchip: restore RK3308 codec detection resources
.../bindings/sound/rockchip,rk3308-codec.yaml | 55 +
arch/arm64/boot/dts/rockchip/rk3308.dtsi | 4 +
sound/soc/codecs/rk3308_codec.c | 1155 ++++++++++++++---
sound/soc/codecs/rk3308_codec.h | 18 +-
4 files changed, 1064 insertions(+), 168 deletions(-)
base-commit: 11ae63bd9030349a0136fb2edd1622a4a029edc8
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v2 0/3] RK3308 codec audio sequencing and headphone support
@ 2026-11-01 4:41 ` Xing Zheng
0 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:41 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
Restore the full RK3308 codec analog power sequencing and headphone
support from the downstream 6.12 implementation, adapted to the current
ASoC interfaces. Keep the complete codec source and register definitions
in one patch, alongside the binding and SoC detection-resource changes.
The codec change includes revision-specific ADC/DAC supply sequencing,
reference-current and headphone gain ramps, A/B CIC fades, active ADC
resynchronization, MICBIAS restoration, microphone boost controls, ADC
stereo-group routing and B/C headphone detection. Machine drivers retain
ownership of the external amplifier GPIOs.
The SDK-only multicodecs shutdown fix and EVB/VAD routing patch are
outside this series because their target drivers and board files are
not present in the upstream tree.
Patches 1-2 are for the ASoC tree; patch 3 is for the Rockchip DT tree.
The detection resources remain optional for existing device trees.
Changes since v1:
- Restore the full analog sequencing omitted from the initial posting.
- Consolidate all codec C/header changes into a single patch.
- Use the original binding descriptions and legacy IRQ lookup rules.
- Preserve upstream automatic DAI format selection and adapt accessors.
- Use sleepable waits while preserving downstream minimum delay requests.
- Keep the SDK-only machine-driver and EVB/VAD work deferred.
Previous posting:
https://lore.kernel.org/r/20261101040125.1864141-1-zhengxing@rock-chips.com
Validation:
- ARM64 defconfig plus built-in RK3308 codec, Image and all six RK3308
DTBs build on broonie/for-next and the Linus baseline with GCC 14.3.1.
- Codec W=1 builds, binding/example validation and codec-schema checks
on the six DTBs pass; strict checkpatch reports no diagnostics.
- The three patches apply to the Linus baseline without 3-way fallback;
range-diff reports all three patches unchanged.
- Extracted SDK/upstream analog functions match across 11520 modeled
register traces, including operation order and minimum delay requests,
under ASan/UBSan. Parameter/error-path probes also pass.
These host models do not verify actual hardware settling time.
No new playback, capture, hotplug, suspend/resume or electrical timing
measurements have been performed on hardware with this upstream series.
Xing Zheng (3):
ASoC: dt-bindings: rk3308-codec: describe detection and ADC routing
ASoC: codecs: rk3308: restore audio and headphone support
arm64: dts: rockchip: restore RK3308 codec detection resources
.../bindings/sound/rockchip,rk3308-codec.yaml | 55 +
arch/arm64/boot/dts/rockchip/rk3308.dtsi | 4 +
sound/soc/codecs/rk3308_codec.c | 1155 ++++++++++++++---
sound/soc/codecs/rk3308_codec.h | 18 +-
4 files changed, 1064 insertions(+), 168 deletions(-)
base-commit: 11ae63bd9030349a0136fb2edd1622a4a029edc8
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v2 1/3] ASoC: dt-bindings: rk3308-codec: describe detection and ADC routing
2026-11-01 4:41 ` Xing Zheng
@ 2026-11-01 4:41 ` Xing Zheng
-1 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:41 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
Describe the optional detect-GRF phandle and named interrupt resources
used by the B/C headphone detector. Retain the legacy interrupt order
and index-based lookup when names are absent. Document the detector
disable and reversed-jack properties and their resource dependencies.
Describe the ADC stereo-group routing consumed by the codec driver.
Require four unique group IDs in the range 0-3, in I2S2 SDI order,
matching the driver's validation. Leave GRF routing unchanged when
the property is absent.
Extend the example with headphone-detection resources and ADC routing.
Keep machine-driver amplifier GPIOs and debug interfaces outside this
codec binding.
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
Changes in v2:
- Retain the original detection descriptions and legacy IRQ ordering.
- Keep the binding change in its own patch before the codec change.
.../bindings/sound/rockchip,rk3308-codec.yaml | 55 +++++++++++++++++++
1 file changed, 55 insertions(+)
diff --git a/Documentation/devicetree/bindings/sound/rockchip,rk3308-codec.yaml b/Documentation/devicetree/bindings/sound/rockchip,rk3308-codec.yaml
index 2cf229a076f0..60c6140dc5df 100644
--- a/Documentation/devicetree/bindings/sound/rockchip,rk3308-codec.yaml
+++ b/Documentation/devicetree/bindings/sound/rockchip,rk3308-codec.yaml
@@ -36,6 +36,49 @@ properties:
description:
Phandle to the General Register Files (GRF)
+ rockchip,detect-grf:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Phandle to the detection GRF used for headphone insertion and
+ removal interrupts on codec revisions B and C.
+
+ interrupts:
+ description:
+ Keep the legacy interrupt order. If interrupt-names is omitted,
+ the driver uses the second interrupt for B/C headphone detection.
+ items:
+ - description: Legacy codec headphone detection interrupt
+ - description: Detection GRF headphone interrupt for revisions B and C
+
+ interrupt-names:
+ items:
+ - const: codec
+ - const: hpdet
+
+ rockchip,no-hp-det:
+ type: boolean
+ description: Disable the internal headphone detector.
+
+ rockchip,hp-jack-reversed:
+ type: boolean
+ description: Reverse the headphone detection polarity.
+
+ rockchip,adc-grps-route:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ description: |
+ ADC stereo-group mapping to the four I2S2 SDI inputs. Each entry
+ selects the ADC group connected to the corresponding SDI input,
+ in SDI0 through SDI3 order. Group IDs 0 through 3 represent
+ MIC1/MIC2, MIC3/MIC4, MIC5/MIC6 and MIC7/MIC8 respectively.
+ All four groups must appear exactly once. For example, <1 2 3 0>
+ connects groups 1, 2, 3 and 0 to SDI0, SDI1, SDI2 and SDI3.
+ If omitted, the GRF routing is left unchanged.
+ minItems: 4
+ maxItems: 4
+ uniqueItems: true
+ items:
+ enum: [ 0, 1, 2, 3 ]
+
clocks:
items:
- description: clock for TX
@@ -72,6 +115,12 @@ properties:
enum: [ 50, 55, 60, 65, 70, 75, 80, 85 ]
+dependentRequired:
+ rockchip,detect-grf:
+ - interrupts
+ interrupt-names:
+ - interrupts
+
required:
- compatible
- reg
@@ -85,11 +134,17 @@ additionalProperties: false
examples:
- |
#include <dt-bindings/clock/rk3308-cru.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
audio_codec: audio-codec@ff560000 {
compatible = "rockchip,rk3308-codec";
reg = <0xff560000 0x10000>;
rockchip,grf = <&grf>;
+ rockchip,detect-grf = <&detect_grf>;
+ interrupts = <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "codec", "hpdet";
+ rockchip,adc-grps-route = <1 2 3 0>;
clock-names = "mclk_tx", "mclk_rx", "hclk";
clocks = <&cru SCLK_I2S2_8CH_TX_OUT>,
<&cru SCLK_I2S2_8CH_RX_OUT>,
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 1/3] ASoC: dt-bindings: rk3308-codec: describe detection and ADC routing
@ 2026-11-01 4:41 ` Xing Zheng
0 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:41 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
Describe the optional detect-GRF phandle and named interrupt resources
used by the B/C headphone detector. Retain the legacy interrupt order
and index-based lookup when names are absent. Document the detector
disable and reversed-jack properties and their resource dependencies.
Describe the ADC stereo-group routing consumed by the codec driver.
Require four unique group IDs in the range 0-3, in I2S2 SDI order,
matching the driver's validation. Leave GRF routing unchanged when
the property is absent.
Extend the example with headphone-detection resources and ADC routing.
Keep machine-driver amplifier GPIOs and debug interfaces outside this
codec binding.
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
Changes in v2:
- Retain the original detection descriptions and legacy IRQ ordering.
- Keep the binding change in its own patch before the codec change.
.../bindings/sound/rockchip,rk3308-codec.yaml | 55 +++++++++++++++++++
1 file changed, 55 insertions(+)
diff --git a/Documentation/devicetree/bindings/sound/rockchip,rk3308-codec.yaml b/Documentation/devicetree/bindings/sound/rockchip,rk3308-codec.yaml
index 2cf229a076f0..60c6140dc5df 100644
--- a/Documentation/devicetree/bindings/sound/rockchip,rk3308-codec.yaml
+++ b/Documentation/devicetree/bindings/sound/rockchip,rk3308-codec.yaml
@@ -36,6 +36,49 @@ properties:
description:
Phandle to the General Register Files (GRF)
+ rockchip,detect-grf:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Phandle to the detection GRF used for headphone insertion and
+ removal interrupts on codec revisions B and C.
+
+ interrupts:
+ description:
+ Keep the legacy interrupt order. If interrupt-names is omitted,
+ the driver uses the second interrupt for B/C headphone detection.
+ items:
+ - description: Legacy codec headphone detection interrupt
+ - description: Detection GRF headphone interrupt for revisions B and C
+
+ interrupt-names:
+ items:
+ - const: codec
+ - const: hpdet
+
+ rockchip,no-hp-det:
+ type: boolean
+ description: Disable the internal headphone detector.
+
+ rockchip,hp-jack-reversed:
+ type: boolean
+ description: Reverse the headphone detection polarity.
+
+ rockchip,adc-grps-route:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ description: |
+ ADC stereo-group mapping to the four I2S2 SDI inputs. Each entry
+ selects the ADC group connected to the corresponding SDI input,
+ in SDI0 through SDI3 order. Group IDs 0 through 3 represent
+ MIC1/MIC2, MIC3/MIC4, MIC5/MIC6 and MIC7/MIC8 respectively.
+ All four groups must appear exactly once. For example, <1 2 3 0>
+ connects groups 1, 2, 3 and 0 to SDI0, SDI1, SDI2 and SDI3.
+ If omitted, the GRF routing is left unchanged.
+ minItems: 4
+ maxItems: 4
+ uniqueItems: true
+ items:
+ enum: [ 0, 1, 2, 3 ]
+
clocks:
items:
- description: clock for TX
@@ -72,6 +115,12 @@ properties:
enum: [ 50, 55, 60, 65, 70, 75, 80, 85 ]
+dependentRequired:
+ rockchip,detect-grf:
+ - interrupts
+ interrupt-names:
+ - interrupts
+
required:
- compatible
- reg
@@ -85,11 +134,17 @@ additionalProperties: false
examples:
- |
#include <dt-bindings/clock/rk3308-cru.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
audio_codec: audio-codec@ff560000 {
compatible = "rockchip,rk3308-codec";
reg = <0xff560000 0x10000>;
rockchip,grf = <&grf>;
+ rockchip,detect-grf = <&detect_grf>;
+ interrupts = <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "codec", "hpdet";
+ rockchip,adc-grps-route = <1 2 3 0>;
clock-names = "mclk_tx", "mclk_rx", "hclk";
clocks = <&cru SCLK_I2S2_8CH_TX_OUT>,
<&cru SCLK_I2S2_8CH_RX_OUT>,
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 2/3] ASoC: codecs: rk3308: restore audio and headphone support
2026-11-01 4:41 ` Xing Zheng
@ 2026-11-01 4:41 ` Xing Zheng
-1 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:41 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
Restore the vendor analog enable and disable order and settling delays
through shared DAPM supply events for codec revisions A, B and C. Use
virtual intermediate widgets to prevent coalesced or repeated analog
writes without adding startup, shutdown or hw_free DAI operations.
Restore reference-current ramps, HPMIX reset, headphone gain ramps and
A/B CIC fades. Preserve stream parameters and ALSA volume and mute
settings. Re-sync active ADCs after DAC reference changes and after
ADC supply events, before external output amplifiers are enabled.
Apply the configured MICBIAS voltage after reset and wait 50 ms before
enabling ADC analog stages. Expose per-channel microphone boost and
validate the ADC stereo-group routing. Keep revision-specific MIC boost
restrictions, LINEOUT VCM/DC controls, digital gain and drive strength.
Select LINEOUT, HPOUT or both through DAPM without enabling inactive
outputs. Restore B/C headphone detection through set_jack and the
named detect-GRF interrupt, retaining legacy interrupt index lookup.
Leave external headphone amplifier GPIO ownership to the machine
DAPM widget and keep debug interfaces outside this change.
Replace the existing revision B output events with the shared analog
sequence. Retain upstream automatic DAI format selection and use the
current ASoC component, control and bias accessors.
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
Changes in v2:
- Combine the codec C and header changes into one complete patch.
- Restore full ADC/DAC analog sequencing, ramps, fades and ADC resync.
- Preserve upstream automatic DAI format selection and adapt accessors.
- Replace sleepable-path busy waits without reducing minimum delays.
sound/soc/codecs/rk3308_codec.c | 1155 ++++++++++++++++++++++++++-----
sound/soc/codecs/rk3308_codec.h | 18 +-
2 files changed, 1005 insertions(+), 168 deletions(-)
diff --git a/sound/soc/codecs/rk3308_codec.c b/sound/soc/codecs/rk3308_codec.c
index a3d41cd8150d..1894e1a93fbf 100644
--- a/sound/soc/codecs/rk3308_codec.c
+++ b/sound/soc/codecs/rk3308_codec.c
@@ -2,7 +2,7 @@
/*
* Rockchip RK3308 internal audio codec driver
*
- * Copyright (c) 2018, Fuzhou Rockchip Electronics Co., Ltd All rights reserved.
+ * Copyright (c) 2018-2026, Rockchip Electronics Co., Ltd All rights reserved.
* Copyright (c) 2024, Vivax-Metrotech Ltd
*/
@@ -10,14 +10,18 @@
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/init.h>
+#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/util_macros.h>
+#include <linux/workqueue.h>
+#include <sound/jack.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
@@ -28,8 +32,16 @@
#define ADC_LR_GROUP_MAX 4
+#define GRF_SOC_CON1 0x304
#define GRF_CHIP_ID 0x800
+#define DETECT_GRF_ACODEC_HPDET_COUNTER 0x0030
+#define DETECT_GRF_ACODEC_HPDET_CON 0x0034
+#define DETECT_GRF_ACODEC_HPDET_STATUS 0x0038
+#define DETECT_GRF_ACODEC_HPDET_STATUS_CLR 0x003c
+#define DEFAULT_HPDET_COUNT 20000000 /* 200 ms at 100 MHz */
+#define HPDET_BOTH_NEG_POS (BIT(0) | BIT(1))
+
enum {
ACODEC_VERSION_A = 'A',
ACODEC_VERSION_B,
@@ -40,12 +52,28 @@ struct rk3308_codec_priv {
const struct device *dev;
struct regmap *regmap;
struct regmap *grf;
+ struct regmap *detect_grf;
struct reset_control *reset;
struct clk *hclk;
struct clk *mclk_rx;
struct clk *mclk_tx;
struct snd_soc_component *component;
unsigned char codec_ver;
+ u32 adc_grps[ADC_LR_GROUP_MAX];
+ u32 micbias_level;
+ /* Serialize output controls, detector work and jack lifetime changes. */
+ struct mutex io_lock;
+ u32 dac_output;
+ u32 output_routes_mask;
+ struct snd_soc_dapm_widget *dac_analog;
+ struct snd_soc_jack *hpdet_jack;
+ struct delayed_work hpdet_work;
+ int hpdet_irq;
+ bool hpdet_enabled;
+ bool hpdet_seen_irq;
+ bool hp_plugged;
+ bool hp_jack_reversed;
+
};
static struct clk_bulk_data rk3308_codec_clocks[] = {
@@ -65,6 +93,84 @@ static const DECLARE_TLV_DB_RANGE(rk3308_codec_dac_lineout_gain_tlv,
3, 3, TLV_DB_SCALE_ITEM(0, 0, 0),
);
+static const DECLARE_TLV_DB_RANGE(rk3308_codec_adc_mic_gain_tlv_a,
+ 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
+ 3, 3, TLV_DB_SCALE_ITEM(2000, 0, 0),
+);
+
+static const DECLARE_TLV_DB_RANGE(rk3308_codec_adc_mic_gain_tlv_b,
+ 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
+ 1, 1, TLV_DB_SCALE_ITEM(660, 0, 0),
+ 2, 2, TLV_DB_SCALE_ITEM(1300, 0, 0),
+ 3, 3, TLV_DB_SCALE_ITEM(2000, 0, 0),
+);
+
+static int rk3308_codec_mic_boost_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+ long gain = ucontrol->value.integer.value[0];
+
+ if (rk3308->codec_ver == ACODEC_VERSION_A && gain != 0 && gain != 3)
+ return -EINVAL;
+
+ return snd_soc_put_volsw(kcontrol, ucontrol);
+}
+
+static const struct snd_kcontrol_new rk3308_codec_mic_boost_controls[] = {
+ SOC_SINGLE_EXT_TLV("MIC1 Boost Volume", RK3308_ADC_ANA_CON01(0),
+ RK3308_ADC_CH1_MIC_GAIN_SFT, RK3308_ADC_CH1_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC2 Boost Volume", RK3308_ADC_ANA_CON01(0),
+ RK3308_ADC_CH2_MIC_GAIN_SFT, RK3308_ADC_CH2_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC3 Boost Volume", RK3308_ADC_ANA_CON01(1),
+ RK3308_ADC_CH1_MIC_GAIN_SFT, RK3308_ADC_CH1_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC4 Boost Volume", RK3308_ADC_ANA_CON01(1),
+ RK3308_ADC_CH2_MIC_GAIN_SFT, RK3308_ADC_CH2_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC5 Boost Volume", RK3308_ADC_ANA_CON01(2),
+ RK3308_ADC_CH1_MIC_GAIN_SFT, RK3308_ADC_CH1_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC6 Boost Volume", RK3308_ADC_ANA_CON01(2),
+ RK3308_ADC_CH2_MIC_GAIN_SFT, RK3308_ADC_CH2_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC7 Boost Volume", RK3308_ADC_ANA_CON01(3),
+ RK3308_ADC_CH1_MIC_GAIN_SFT, RK3308_ADC_CH1_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC8 Boost Volume", RK3308_ADC_ANA_CON01(3),
+ RK3308_ADC_CH2_MIC_GAIN_SFT, RK3308_ADC_CH2_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+};
+
+static int rk3308_codec_add_mic_boost_controls(struct snd_soc_component *component)
+{
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(rk3308_codec_mic_boost_controls); i++) {
+ struct snd_kcontrol_new control = rk3308_codec_mic_boost_controls[i];
+
+ if (rk3308->codec_ver == ACODEC_VERSION_A)
+ control.tlv.p = rk3308_codec_adc_mic_gain_tlv_a;
+ ret = snd_soc_add_component_controls(component, &control, 1);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static const char * const rk3308_codec_hpf_cutoff_text[] = {
"20 Hz", "245 Hz", "612 Hz"
};
@@ -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;
+}
+
+static const char * const rk3308_codec_output_text[] = { "Line Out", "Headphone", "Both" };
+static SOC_ENUM_SINGLE_EXT_DECL(rk3308_codec_output_enum, rk3308_codec_output_text);
+
+static int rk3308_codec_output_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+
+ mutex_lock(&rk3308->io_lock);
+ ucontrol->value.enumerated.item[0] = rk3308->dac_output == 11 ? 2 : rk3308->dac_output;
+ mutex_unlock(&rk3308->io_lock);
+
+ return 0;
+}
+
+static int rk3308_codec_output_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+ int ret = 0;
+
+ if (value >= ARRAY_SIZE(rk3308_codec_output_text))
+ return -EINVAL;
+ value = value == 2 ? 11 : value;
+ mutex_lock(&rk3308->io_lock);
+ if (value != rk3308->dac_output) {
+ ret = rk3308_codec_set_output(rk3308, value);
+ if (!ret)
+ ret = 1;
+ }
+ mutex_unlock(&rk3308->io_lock);
+ return ret;
+}
+
+static bool rk3308_codec_hpdet_inserted(struct rk3308_codec_priv *rk3308,
+ unsigned int status)
+{
+ if (rk3308->hp_jack_reversed)
+ return status == 0 || status == BIT(1);
+
+ return status == BIT(0);
+}
+
+static void rk3308_codec_hpdet_work(struct work_struct *work)
+{
+ struct rk3308_codec_priv *rk3308 =
+ container_of(to_delayed_work(work), struct rk3308_codec_priv, hpdet_work);
+ unsigned int status;
+ int ret;
+
+ mutex_lock(&rk3308->io_lock);
+ if (!rk3308->component || !rk3308->hpdet_jack)
+ goto out;
+ if (!rk3308->component->card->instantiated) {
+ mod_delayed_work(system_power_efficient_wq, &rk3308->hpdet_work,
+ msecs_to_jiffies(100));
+ goto out;
+ }
+
+ /* The threaded IRQ retains the last edge after acknowledging it. */
+ if (!rk3308->hpdet_seen_irq) {
+ ret = regmap_read(rk3308->detect_grf, DETECT_GRF_ACODEC_HPDET_STATUS, &status);
+ if (ret)
+ goto out;
+ rk3308->hp_plugged = rk3308_codec_hpdet_inserted(rk3308, status);
+ }
+
+ ret = rk3308_codec_set_output(rk3308, rk3308->hp_plugged ? 1 : 0);
+ if (ret) {
+ dev_err_ratelimited(rk3308->dev, "Failed to switch headphone output: %d\n", ret);
+ goto out;
+ }
+
+out:
+ mutex_unlock(&rk3308->io_lock);
+}
+
+static irqreturn_t rk3308_codec_hpdet_irq(int irq, void *data)
+{
+ struct rk3308_codec_priv *rk3308 = data;
+ unsigned int status;
+ int ret;
+
+ mutex_lock(&rk3308->io_lock);
+ ret = regmap_read(rk3308->detect_grf, DETECT_GRF_ACODEC_HPDET_STATUS, &status);
+ if (ret || !(status & HPDET_BOTH_NEG_POS))
+ goto out;
+ ret = regmap_write(rk3308->detect_grf, DETECT_GRF_ACODEC_HPDET_STATUS_CLR, status);
+ if (ret)
+ goto out;
+
+ if (rk3308->hpdet_jack) {
+ rk3308->hp_plugged = rk3308_codec_hpdet_inserted(rk3308, status);
+ rk3308->hpdet_seen_irq = true;
+ mod_delayed_work(system_power_efficient_wq, &rk3308->hpdet_work,
+ msecs_to_jiffies(10));
+ }
+out:
+ mutex_unlock(&rk3308->io_lock);
+ return IRQ_HANDLED;
+}
+
+static int rk3308_codec_set_jack(struct snd_soc_component *component,
+ struct snd_soc_jack *jack, void *data)
+{
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+ struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
+ bool enabled;
+ int ret = 0;
+
+ if (!rk3308->detect_grf)
+ return jack ? -EOPNOTSUPP : 0;
+
+ mutex_lock(&rk3308->io_lock);
+ enabled = rk3308->hpdet_enabled;
+ rk3308->hpdet_enabled = false;
+ rk3308->hpdet_jack = NULL;
+ mutex_unlock(&rk3308->io_lock);
+ if (enabled)
+ disable_irq(rk3308->hpdet_irq);
+ cancel_delayed_work_sync(&rk3308->hpdet_work);
+
+ mutex_lock(&rk3308->io_lock);
+ ret = regmap_write(rk3308->detect_grf, DETECT_GRF_ACODEC_HPDET_CON,
+ HPDET_BOTH_NEG_POS << 16);
+ if (ret)
+ goto out;
+ ret = regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON00,
+ RK3308_DAC_HEADPHONE_DET_EN);
+ if (ret)
+ goto out;
+
+ /* C needs the internal DAC clock even when no PCM is running. */
+ if (rk3308->codec_ver == ACODEC_VERSION_C) {
+ snd_soc_dapm_mutex_lock(dapm);
+ if (jack) {
+ ret = snd_soc_dapm_force_enable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+ } else {
+ ret = snd_soc_dapm_disable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+ if (!ret)
+ ret = snd_soc_dapm_enable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+ }
+ if (!ret)
+ ret = snd_soc_dapm_sync_unlocked(dapm);
+ snd_soc_dapm_mutex_unlock(dapm);
+ if (ret)
+ goto out;
+ }
+ if (!jack)
+ goto out;
+
+ ret = regmap_write(rk3308->detect_grf, DETECT_GRF_ACODEC_HPDET_COUNTER,
+ DEFAULT_HPDET_COUNT);
+ if (ret)
+ goto out;
+ ret = regmap_write(rk3308->detect_grf, DETECT_GRF_ACODEC_HPDET_STATUS_CLR,
+ HPDET_BOTH_NEG_POS);
+ if (ret)
+ goto out;
+ ret = regmap_set_bits(rk3308->regmap, RK3308_DAC_ANA_CON00,
+ RK3308_DAC_HEADPHONE_DET_EN);
+ if (ret)
+ goto out;
+ ret = regmap_write(rk3308->detect_grf, DETECT_GRF_ACODEC_HPDET_CON,
+ (HPDET_BOTH_NEG_POS << 16) | HPDET_BOTH_NEG_POS);
+ if (ret)
+ goto out;
+
+ rk3308->hpdet_jack = jack;
+ rk3308->hpdet_seen_irq = false;
+ rk3308->hpdet_enabled = true;
+ /* Allow the 200 ms hardware filter to settle before the initial report. */
+ mod_delayed_work(system_power_efficient_wq, &rk3308->hpdet_work,
+ msecs_to_jiffies(210));
+ enable_irq(rk3308->hpdet_irq);
+out:
+ if (ret && jack) {
+ regmap_write(rk3308->detect_grf, DETECT_GRF_ACODEC_HPDET_CON,
+ HPDET_BOTH_NEG_POS << 16);
+ regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON00,
+ RK3308_DAC_HEADPHONE_DET_EN);
+ if (rk3308->codec_ver == ACODEC_VERSION_C) {
+ snd_soc_dapm_mutex_lock(dapm);
+ snd_soc_dapm_disable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+ snd_soc_dapm_enable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+ snd_soc_dapm_sync_unlocked(dapm);
+ snd_soc_dapm_mutex_unlock(dapm);
+ }
+ }
+ mutex_unlock(&rk3308->io_lock);
+ return ret;
+}
+
+static int rk3308_codec_hpdet_init(struct platform_device *pdev,
+ struct rk3308_codec_priv *rk3308)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ int ret;
+
+ if (rk3308->codec_ver == ACODEC_VERSION_A ||
+ of_property_read_bool(np, "rockchip,no-hp-det") ||
+ !of_find_property(np, "rockchip,detect-grf", NULL))
+ return 0;
+
+ rk3308->detect_grf = syscon_regmap_lookup_by_phandle(np, "rockchip,detect-grf");
+ if (IS_ERR(rk3308->detect_grf))
+ return dev_err_probe(dev, PTR_ERR(rk3308->detect_grf),
+ "Failed to get detect GRF\n");
+ if (of_find_property(np, "interrupt-names", NULL))
+ rk3308->hpdet_irq = platform_get_irq_byname(pdev, "hpdet");
+ else
+ rk3308->hpdet_irq = platform_get_irq(pdev, 1);
+ if (rk3308->hpdet_irq < 0)
+ return rk3308->hpdet_irq;
+
+ rk3308->hp_jack_reversed = of_property_read_bool(np, "rockchip,hp-jack-reversed");
+ INIT_DELAYED_WORK(&rk3308->hpdet_work, rk3308_codec_hpdet_work);
+ ret = devm_request_threaded_irq(dev, rk3308->hpdet_irq, NULL, rk3308_codec_hpdet_irq,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN, "acodec-hpdet", rk3308);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request headphone IRQ\n");
+
+ return 0;
+}
+
static const struct snd_kcontrol_new rk3308_codec_controls[] = {
+ SOC_ENUM_EXT("DAC Output Route", rk3308_codec_output_enum,
+ rk3308_codec_output_get, rk3308_codec_output_put),
/* Despite the register names, these set the gain when AGC is OFF */
SOC_SINGLE_RANGE_TLV("MIC1 Capture Volume",
RK3308_ADC_ANA_CON03(0),
@@ -175,107 +562,350 @@ static const struct snd_kcontrol_new rk3308_codec_controls[] = {
1, 2, 0, rk3308_codec_dac_hpmix_gain_tlv),
};
-static int rk3308_codec_pop_sound_set(struct snd_soc_dapm_widget *w,
- struct snd_kcontrol *kcontrol,
- int event)
+/* Keep each vendor analog step separate, including its settling time. */
+static void rk3308_codec_analog_update(struct rk3308_codec_priv *rk3308,
+ unsigned int reg, unsigned int mask,
+ unsigned int value)
{
- 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 val = (event == SND_SOC_DAPM_POST_PMU) ?
- RK3308_DAC_HPOUT_POP_SOUND_x_WORK :
- RK3308_DAC_HPOUT_POP_SOUND_x_INIT;
- unsigned int mask = RK3308_DAC_HPOUT_POP_SOUND_x_MSK;
+ regmap_update_bits(rk3308->regmap, reg, mask, value);
+ usleep_range(20, 40);
+}
- regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON01,
- mask << w->shift, val << w->shift);
+/* DAC reference changes also affect ADCs that were already running. */
+static void rk3308_codec_resync_adcs(struct rk3308_codec_priv *rk3308)
+{
+ unsigned int work[ADC_LR_GROUP_MAX];
+ int grp;
- return 0;
+ for (grp = 0; grp < ADC_LR_GROUP_MAX; grp++) {
+ regmap_read(rk3308->regmap, RK3308_ADC_ANA_CON05(grp), &work[grp]);
+ work[grp] &= RK3308_ADC_CH1_ADC_WORK | RK3308_ADC_CH2_ADC_WORK;
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON05(grp), work[grp]);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON02(grp), work[grp] >> 1);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp), work[grp]);
+ }
+ usleep_range(200, 250);
+ for (grp = 0; grp < ADC_LR_GROUP_MAX; grp++) {
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON05(grp), work[grp]);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON02(grp), work[grp] >> 1);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp), work[grp]);
+ }
}
-static int rk3308_codec_dac_output_vcm_event(struct snd_soc_dapm_widget *w,
- struct snd_kcontrol *kcontrol,
- int event)
+/* 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 val;
+ 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;
+
+ 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);
+ }
+ regmap_read(rk3308->regmap, RK3308_DAC_DIG_CON01, &aif1);
+ regmap_read(rk3308->regmap, RK3308_DAC_DIG_CON02, &aif2);
+ if (rk3308->codec_ver == ACODEC_VERSION_C)
+ regmap_read(rk3308->regmap, RK3308_DAC_DIG_CON04, &digital_gain);
+ regmap_clear_bits(rk3308->regmap, RK3308_GLB_CON, RK3308_DAC_DIG_WORK);
+ usleep_range(10000, 11000);
+ regmap_set_bits(rk3308->regmap, RK3308_GLB_CON, RK3308_DAC_DIG_WORK);
+ regmap_write(rk3308->regmap, RK3308_DAC_DIG_CON01, aif1);
+ regmap_write(rk3308->regmap, RK3308_DAC_DIG_CON02, aif2);
+ if (rk3308->codec_ver == ACODEC_VERSION_C)
+ regmap_write(rk3308->regmap, RK3308_DAC_DIG_CON04, digital_gain);
+ /* Vendor DAC enable steps 1-6: current, buffers, POP and HPMIX. */
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON00,
+ RK3308_DAC_CURRENT_EN, RK3308_DAC_CURRENT_EN);
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON01,
+ (RK3308_DAC_BUF_REF_L_EN | RK3308_DAC_BUF_REF_R_EN),
+ (RK3308_DAC_BUF_REF_L_EN | RK3308_DAC_BUF_REF_R_EN));
+ usleep_range(1000, 1100);
+ 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_WORK | RK3308_DAC_HPOUT_POP_SOUND_R_WORK);
+ if (lineout && 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_INTERNAL | RK3308_DAC_R_SEL_DC_FROM_INTERNAL);
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON13,
+ (RK3308_DAC_L_HPMIX_EN | RK3308_DAC_R_HPMIX_EN),
+ (RK3308_DAC_L_HPMIX_EN | RK3308_DAC_R_HPMIX_EN));
+ usleep_range(1000, 1100);
+ regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON13,
+ RK3308_DAC_L_HPMIX_WORK | RK3308_DAC_R_HPMIX_WORK);
+ usleep_range(50, 100);
+ 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));
+ /* Steps 7-14: outputs, DC selector, reference, clock, DAC and WORK. */
+ if (lineout)
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON04,
+ (RK3308_DAC_L_LINEOUT_EN |
+ RK3308_DAC_R_LINEOUT_EN),
+ (RK3308_DAC_L_LINEOUT_EN | RK3308_DAC_R_LINEOUT_EN));
+ if (headphone) {
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON03,
+ (RK3308_DAC_L_HPOUT_EN | RK3308_DAC_R_HPOUT_EN),
+ (RK3308_DAC_L_HPOUT_EN | RK3308_DAC_R_HPOUT_EN));
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON03,
+ (RK3308_DAC_L_HPOUT_WORK |
+ RK3308_DAC_R_HPOUT_WORK),
+ (RK3308_DAC_L_HPOUT_WORK | RK3308_DAC_R_HPOUT_WORK));
+ }
+ 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,
+ lineout ? (RK3308_DAC_L_SEL_LINEOUT_FROM_INTERNAL |
+ RK3308_DAC_R_SEL_LINEOUT_FROM_INTERNAL) :
+ (RK3308_DAC_L_SEL_DC_FROM_INTERNAL |
+ RK3308_DAC_R_SEL_DC_FROM_INTERNAL));
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON02,
+ (RK3308_DAC_L_REF_EN | RK3308_DAC_R_REF_EN),
+ (RK3308_DAC_L_REF_EN | RK3308_DAC_R_REF_EN));
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON02,
+ (RK3308_DAC_L_CLK_EN | RK3308_DAC_R_CLK_EN),
+ (RK3308_DAC_L_CLK_EN | RK3308_DAC_R_CLK_EN));
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON02,
+ (RK3308_DAC_L_DAC_EN | RK3308_DAC_R_DAC_EN),
+ (RK3308_DAC_L_DAC_EN | RK3308_DAC_R_DAC_EN));
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON02,
+ (RK3308_DAC_L_DAC_WORK | RK3308_DAC_R_DAC_WORK),
+ (RK3308_DAC_L_DAC_WORK | RK3308_DAC_R_DAC_WORK));
+ /* 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));
+ 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);
+ }
- if (rk3308->codec_ver != ACODEC_VERSION_B)
- return 0;
+ return 0;
+}
- switch (event) {
- case SND_SOC_DAPM_PRE_PMU:
- /* Keep the DAC outputs at VCM while their reference is charged. */
- regmap_update_bits(rk3308->regmap, 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);
- regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14,
- RK3308_DAC_CURRENT_CHARGE_MSK, 1);
- regmap_set_bits(rk3308->regmap, RK3308_DAC_ANA_CON14,
- RK3308_DAC_VCM_LINEOUT_EN);
- for (val = 1; val <= 0xf; val++) {
- regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14,
- RK3308_DAC_CURRENT_CHARGE_MSK, val);
- usleep_range(200, 400);
- }
- msleep(20);
- break;
- case SND_SOC_DAPM_POST_PMU:
- regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON15,
- RK3308_DAC_LINEOUT_POP_SOUND_L_MSK |
- RK3308_DAC_LINEOUT_POP_SOUND_R_MSK,
- RK3308_DAC_L_SEL_DC_FROM_INTERNAL |
- RK3308_DAC_R_SEL_DC_FROM_INTERNAL);
- break;
- case SND_SOC_DAPM_POST_PMD:
- regmap_update_bits(rk3308->regmap, 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);
- regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14,
- RK3308_DAC_CURRENT_CHARGE_MSK, 1);
- regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON14,
- RK3308_DAC_VCM_LINEOUT_EN);
- break;
- default:
- break;
+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);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON06(grp),
+ RK3308_ADC_CURRENT_EN);
+ usleep_range(400, 500);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp),
+ RK3308_ADC_CH1_BUF_REF_EN | RK3308_ADC_CH2_BUF_REF_EN);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp),
+ RK3308_ADC_CH1_MIC_EN | RK3308_ADC_CH2_MIC_EN);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON02(grp),
+ RK3308_ADC_CH1_ALC_EN | RK3308_ADC_CH2_ALC_EN);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON05(grp),
+ RK3308_ADC_CH1_CLK_EN | RK3308_ADC_CH2_CLK_EN);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON05(grp),
+ RK3308_ADC_CH1_ADC_EN | RK3308_ADC_CH2_ADC_EN);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON05(grp),
+ RK3308_ADC_CH1_ADC_WORK | RK3308_ADC_CH2_ADC_WORK);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON02(grp),
+ RK3308_ADC_CH1_ALC_WORK | RK3308_ADC_CH2_ALC_WORK);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp),
+ RK3308_ADC_CH1_MIC_WORK | RK3308_ADC_CH2_MIC_WORK);
+ } else {
+ regmap_read(rk3308->regmap, RK3308_ADC_ANA_CON02(grp), &zerocross);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON02(grp),
+ RK3308_ADC_CH1_ZEROCROSS_DET_EN |
+ RK3308_ADC_CH2_ZEROCROSS_DET_EN);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON05(grp),
+ RK3308_ADC_CH1_ADC_EN | RK3308_ADC_CH2_ADC_EN);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON05(grp),
+ RK3308_ADC_CH1_CLK_EN | RK3308_ADC_CH2_CLK_EN);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON02(grp),
+ RK3308_ADC_CH1_ALC_EN | RK3308_ADC_CH2_ALC_EN);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp),
+ RK3308_ADC_CH1_MIC_EN | RK3308_ADC_CH2_MIC_EN);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp),
+ RK3308_ADC_CH1_BUF_REF_EN | RK3308_ADC_CH2_BUF_REF_EN);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON06(grp),
+ RK3308_ADC_CURRENT_EN);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON05(grp),
+ RK3308_ADC_CH1_ADC_WORK | RK3308_ADC_CH2_ADC_WORK);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON02(grp),
+ RK3308_ADC_CH1_ALC_WORK | RK3308_ADC_CH2_ALC_WORK);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp),
+ RK3308_ADC_CH1_MIC_WORK | RK3308_ADC_CH2_MIC_WORK);
+ regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON02(grp),
+ RK3308_ADC_CH1_ZEROCROSS_DET_EN |
+ RK3308_ADC_CH2_ZEROCROSS_DET_EN,
+ zerocross & (RK3308_ADC_CH1_ZEROCROSS_DET_EN |
+ RK3308_ADC_CH2_ZEROCROSS_DET_EN));
}
return 0;
}
-static int rk3308_codec_lineout_event(struct snd_soc_dapm_widget *w,
- struct snd_kcontrol *kcontrol,
- int event)
+/* 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 mask = RK3308_DAC_LINEOUT_POP_SOUND_L_MSK << w->shift;
- unsigned int val;
+ 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);
}
-
- regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON15, mask, val);
- usleep_range(20, 40);
+ if (!hp && rk3308->codec_ver >= ACODEC_VERSION_B)
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON15,
+ RK3308_DAC_LINEOUT_POP_SOUND_L_MSK << shift,
+ (event == SND_SOC_DAPM_PRE_PMU ?
+ RK3308_DAC_L_SEL_LINEOUT_FROM_INTERNAL :
+ RK3308_DAC_L_SEL_DC_FROM_VCM) << shift);
return 0;
}
+static int rk3308_codec_lineout_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ return rk3308_codec_output_event(w, false, event);
+}
+
+static int rk3308_codec_hpout_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ return rk3308_codec_output_event(w, true, event);
+}
+
static const struct snd_soc_dapm_widget rk3308_codec_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("MIC1"),
SND_SOC_DAPM_INPUT("MIC2"),
@@ -309,12 +939,9 @@ static const struct snd_soc_dapm_widget rk3308_codec_dapm_widgets[] = {
SND_SOC_DAPM_REG(snd_soc_dapm_mic, "MIC7_WORK", RK3308_ADC_ANA_CON00(3), 2, 1, 1, 0),
SND_SOC_DAPM_REG(snd_soc_dapm_mic, "MIC8_WORK", RK3308_ADC_ANA_CON00(3), 6, 1, 1, 0),
- /*
- * In theory MIC1 and MIC2 can switch to LINE IN, but this is not
- * supported so all we can do is enabling the MIC input.
- */
- SND_SOC_DAPM_REG(snd_soc_dapm_adc, "CH1_IN_SEL", RK3308_ADC_ANA_CON07(0), 4, 1, 1, 0),
- SND_SOC_DAPM_REG(snd_soc_dapm_adc, "CH2_IN_SEL", RK3308_ADC_ANA_CON07(0), 6, 1, 1, 0),
+ /* Select the MIC input before enabling the ADC/ALC stages. */
+ SND_SOC_DAPM_PGA("CH1_IN_SEL", RK3308_ADC_ANA_CON07(0), 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("CH2_IN_SEL", RK3308_ADC_ANA_CON07(0), 6, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("ADC1_BUF_REF_EN", RK3308_ADC_ANA_CON00(0), 0, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("ADC2_BUF_REF_EN", RK3308_ADC_ANA_CON00(0), 4, 0, NULL, 0),
@@ -355,14 +982,14 @@ static const struct snd_soc_dapm_widget rk3308_codec_dapm_widgets[] = {
SND_SOC_DAPM_REG(snd_soc_dapm_adc, "ADC7_EN", RK3308_ADC_ANA_CON05(3), 1, 1, 1, 0),
SND_SOC_DAPM_REG(snd_soc_dapm_adc, "ADC8_EN", RK3308_ADC_ANA_CON05(3), 5, 1, 1, 0),
- SND_SOC_DAPM_ADC("ADC1_WORK", "Capture", RK3308_ADC_ANA_CON05(0), 2, 0),
- SND_SOC_DAPM_ADC("ADC2_WORK", "Capture", RK3308_ADC_ANA_CON05(0), 6, 0),
- SND_SOC_DAPM_ADC("ADC3_WORK", "Capture", RK3308_ADC_ANA_CON05(1), 2, 0),
- SND_SOC_DAPM_ADC("ADC4_WORK", "Capture", RK3308_ADC_ANA_CON05(1), 6, 0),
- SND_SOC_DAPM_ADC("ADC5_WORK", "Capture", RK3308_ADC_ANA_CON05(2), 2, 0),
- SND_SOC_DAPM_ADC("ADC6_WORK", "Capture", RK3308_ADC_ANA_CON05(2), 6, 0),
- SND_SOC_DAPM_ADC("ADC7_WORK", "Capture", RK3308_ADC_ANA_CON05(3), 2, 0),
- SND_SOC_DAPM_ADC("ADC8_WORK", "Capture", RK3308_ADC_ANA_CON05(3), 6, 0),
+ SND_SOC_DAPM_ADC("ADC1_WORK", "HiFi Capture", RK3308_ADC_ANA_CON05(0), 2, 0),
+ SND_SOC_DAPM_ADC("ADC2_WORK", "HiFi Capture", RK3308_ADC_ANA_CON05(0), 6, 0),
+ SND_SOC_DAPM_ADC("ADC3_WORK", "HiFi Capture", RK3308_ADC_ANA_CON05(1), 2, 0),
+ SND_SOC_DAPM_ADC("ADC4_WORK", "HiFi Capture", RK3308_ADC_ANA_CON05(1), 6, 0),
+ SND_SOC_DAPM_ADC("ADC5_WORK", "HiFi Capture", RK3308_ADC_ANA_CON05(2), 2, 0),
+ SND_SOC_DAPM_ADC("ADC6_WORK", "HiFi Capture", RK3308_ADC_ANA_CON05(2), 6, 0),
+ SND_SOC_DAPM_ADC("ADC7_WORK", "HiFi Capture", RK3308_ADC_ANA_CON05(3), 2, 0),
+ SND_SOC_DAPM_ADC("ADC8_WORK", "HiFi Capture", RK3308_ADC_ANA_CON05(3), 6, 0),
/* The "ALC" name from the TRM is misleading, these are needed even without ALC/AGC */
SND_SOC_DAPM_REG(snd_soc_dapm_adc, "ALC1_WORK", RK3308_ADC_ANA_CON02(0), 1, 1, 1, 0),
@@ -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,
+ },
SND_SOC_DAPM_OUTPUT("HPOUT_L"),
SND_SOC_DAPM_OUTPUT("HPOUT_R"),
- SND_SOC_DAPM_OUT_DRV_E("L_LINEOUT_EN", RK3308_DAC_ANA_CON04, 0, 0,
- NULL, 0, rk3308_codec_lineout_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
- SND_SOC_DAPM_OUT_DRV_E("R_LINEOUT_EN", RK3308_DAC_ANA_CON04, 4, 0,
- NULL, 0, rk3308_codec_lineout_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_OUT_DRV_E("L_LINEOUT_EN", RK3308_DAC_ANA_CON04, 0, 0, NULL, 0,
+ rk3308_codec_lineout_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_OUT_DRV_E("R_LINEOUT_EN", RK3308_DAC_ANA_CON04, 4, 0, NULL, 0,
+ rk3308_codec_lineout_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_OUTPUT("LINEOUT_L"),
SND_SOC_DAPM_OUTPUT("LINEOUT_R"),
};
+/* The vendor flow settles MICBIAS before starting any MIC/ALC stages. */
+static int rk3308_codec_micbias_settle(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ msleep(50);
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget rk3308_codec_bias_widget =
+ SND_SOC_DAPM_SUPPLY_S("MICBIAS Settled", 1, SND_SOC_NOPM, 0, 0,
+ rk3308_codec_micbias_settle, SND_SOC_DAPM_POST_PMU);
+
+static const struct snd_soc_dapm_route rk3308_codec_bias_routes[] = {
+ { "MICBIAS Settled", NULL, "MICBIAS1" },
+ { "MICBIAS Settled", NULL, "MICBIAS2" },
+ { "ADC_CURRENT_EN12", NULL, "MICBIAS Settled" },
+ { "ADC_CURRENT_EN34", NULL, "MICBIAS Settled" },
+ { "ADC_CURRENT_EN56", NULL, "MICBIAS Settled" },
+ { "ADC_CURRENT_EN78", NULL, "MICBIAS Settled" },
+};
+
+static void rk3308_codec_seq_notifier(struct snd_soc_component *component,
+ enum snd_soc_dapm_type type, int subseq)
+{
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+
+ /* Reinitialize together after the last ADC group, as in the vendor flow. */
+ if (type == snd_soc_dapm_supply && subseq == 2)
+ rk3308_codec_resync_adcs(rk3308);
+}
+
+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;
+ 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;
+ if (reg == RK3308_ADC_ANA_CON06(grp)) {
+ w->reg = SND_SOC_NOPM;
+ w->shift = grp;
+ w->subseq = 2;
+ w->event = rk3308_codec_adc_event;
+ w->event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD;
+ }
+ }
+ }
+ ret = snd_soc_dapm_new_controls(snd_soc_component_to_dapm(component),
+ widgets, ARRAY_SIZE(rk3308_codec_dapm_widgets));
+ if (ret)
+ return ret;
+ ret = snd_soc_dapm_new_controls(snd_soc_component_to_dapm(component),
+ &rk3308_codec_bias_widget, 1);
+ if (ret)
+ return ret;
+ return snd_soc_dapm_add_routes(snd_soc_component_to_dapm(component),
+ rk3308_codec_bias_routes,
+ ARRAY_SIZE(rk3308_codec_bias_routes));
+}
+
static const struct snd_soc_dapm_route rk3308_codec_dapm_routes[] = {
{ "MICBIAS1", NULL, "MICBIAS Current" },
{ "MICBIAS2", NULL, "MICBIAS Current" },
@@ -558,20 +1268,14 @@ static const struct snd_soc_dapm_route rk3308_codec_dapm_routes[] = {
{ "HPOUT_R", NULL, "DAC_BUF_REF_R" },
{ "L_HPOUT_EN", NULL, "DAC_L_HPMIX_SEL" },
{ "R_HPOUT_EN", NULL, "DAC_R_HPMIX_SEL" },
- { "L_HPOUT_EN", NULL, "DAC_OUTPUT_VCM" },
- { "R_HPOUT_EN", NULL, "DAC_OUTPUT_VCM" },
- { "L_HPOUT_WORK", NULL, "L_HPOUT_EN" },
- { "R_HPOUT_WORK", NULL, "R_HPOUT_EN" },
- { "HPOUT_POP_SOUND_L", NULL, "L_HPOUT_WORK" },
- { "HPOUT_POP_SOUND_R", NULL, "R_HPOUT_WORK" },
- { "HPOUT_L", NULL, "HPOUT_POP_SOUND_L" },
- { "HPOUT_R", NULL, "HPOUT_POP_SOUND_R" },
+ { "HPOUT_L", NULL, "L_HPOUT_EN" },
+ { "HPOUT_R", NULL, "R_HPOUT_EN" },
{ "L_LINEOUT_EN", NULL, "DAC_L_HPMIX_SEL" },
{ "R_LINEOUT_EN", NULL, "DAC_R_HPMIX_SEL" },
- { "L_LINEOUT_EN", NULL, "DAC_OUTPUT_VCM" },
- { "R_LINEOUT_EN", NULL, "DAC_OUTPUT_VCM" },
+ { "LINEOUT_L", NULL, "DAC_BUF_REF_L" },
{ "LINEOUT_L", NULL, "L_LINEOUT_EN" },
+ { "LINEOUT_R", NULL, "DAC_BUF_REF_R" },
{ "LINEOUT_R", NULL, "R_LINEOUT_EN" },
};
@@ -715,7 +1419,7 @@ static int rk3308_codec_adc_dig_config(struct rk3308_codec_priv *rk3308,
* grp 3 = ADC7 and ADC8
*/
u32 used_adc_grps;
- int grp;
+ int grp, idx;
switch (params_channels(params)) {
case 1:
@@ -750,7 +1454,8 @@ static int rk3308_codec_adc_dig_config(struct rk3308_codec_priv *rk3308,
return -EINVAL;
}
- for (grp = 0; grp < used_adc_grps; grp++) {
+ for (idx = 0; idx < used_adc_grps; idx++) {
+ grp = rk3308->adc_grps[idx];
regmap_update_bits(rk3308->regmap,
RK3308_ADC_DIG_CON03(grp),
RK3308_ADC_L_CH_BIST_MSK | RK3308_ADC_R_CH_BIST_MSK,
@@ -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;
+}
+
static const u64 rk3308_selectable_formats =
SND_SOC_POSSIBLE_DAIFMT_I2S |
SND_SOC_POSSIBLE_DAIFMT_RIGHT_J |
@@ -785,6 +1508,7 @@ static const u64 rk3308_selectable_formats =
SND_SOC_POSSIBLE_DAIFMT_IB_IF;
static const struct snd_soc_dai_ops rk3308_codec_dai_ops = {
+ .mute_stream = rk3308_codec_mute_stream,
.hw_params = rk3308_codec_hw_params,
.set_fmt = rk3308_codec_set_dai_fmt,
.auto_selectable_formats = &rk3308_selectable_formats,
@@ -853,89 +1577,143 @@ static int rk3308_codec_initialize(struct rk3308_codec_priv *rk3308)
}
}
+ regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON07(0),
+ RK3308_ADC_LEVEL_RANGE_MICBIAS_MSK, rk3308->micbias_level);
+
+ regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON07(0),
+ RK3308_ADC_CH1_IN_SEL_MSK | RK3308_ADC_CH2_IN_SEL_MSK,
+ RK3308_ADC_CH1_IN_MIC | RK3308_ADC_CH2_IN_MIC);
+
+ /* Match the vendor analog defaults; controls remain adjustable. */
+ for (grp = 0; grp < ADC_LR_GROUP_MAX; grp++)
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp),
+ BIT(3) | BIT(7));
+
+ regmap_set_bits(rk3308->regmap, RK3308_DAC_ANA_CON03,
+ BIT(RK3308_DAC_L_HPOUT_MUTE_SFT) |
+ BIT(RK3308_DAC_R_HPOUT_MUTE_SFT));
+ regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON05,
+ RK3308_DAC_x_HPOUT_GAIN_MSK, RK3308_DAC_x_HPOUT_GAIN_0DB);
+ regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON06,
+ RK3308_DAC_x_HPOUT_GAIN_MSK, RK3308_DAC_x_HPOUT_GAIN_0DB);
+ regmap_set_bits(rk3308->regmap, RK3308_DAC_ANA_CON04,
+ BIT(RK3308_DAC_L_LINEOUT_MUTE_SFT) |
+ BIT(RK3308_DAC_R_LINEOUT_MUTE_SFT));
+ regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON04,
+ RK3308_DAC_L_LINEOUT_GAIN_MSK |
+ RK3308_DAC_R_LINEOUT_GAIN_MSK,
+ RK3308_DAC_L_LINEOUT_GAIN_MSK |
+ RK3308_DAC_R_LINEOUT_GAIN_MSK);
+
/* set HPMIX default gains (reset value is 0, which is illegal) */
regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON12,
RK3308_DAC_L_HPMIX_GAIN_MSK |
RK3308_DAC_R_HPMIX_GAIN_MSK,
- RK3308_DAC_L_HPMIX_GAIN_NDB_6 |
- RK3308_DAC_R_HPMIX_GAIN_NDB_6);
+ RK3308_DAC_L_HPMIX_GAIN_0DB | RK3308_DAC_R_HPMIX_GAIN_0DB);
/* 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;
}
static int rk3308_codec_probe(struct snd_soc_component *component)
{
struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+ int ret;
+ ret = rk3308_codec_add_widgets(component);
+ if (ret)
+ return ret;
rk3308->component = component;
+ rk3308->dac_output = 11;
+ rk3308->output_routes_mask = 3;
rk3308_codec_reset(component);
rk3308_codec_initialize(rk3308);
- return 0;
+ return rk3308_codec_add_mic_boost_controls(component);
}
+/* Reference-current ramps are shared by playback and capture. */
static int rk3308_codec_set_bias_level(struct snd_soc_component *component,
- enum snd_soc_bias_level level)
+ enum snd_soc_bias_level level)
{
struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
+ unsigned int v;
+ bool on = level == SND_SOC_BIAS_STANDBY;
- switch (level) {
- case SND_SOC_BIAS_ON:
- break;
- case SND_SOC_BIAS_PREPARE:
- break;
- case SND_SOC_BIAS_STANDBY:
- if (snd_soc_dapm_get_bias_level(dapm) != SND_SOC_BIAS_OFF)
- break;
-
- /* Sequence from TRM Section 8.6.3 "Power Up" */
- regmap_set_bits(rk3308->regmap, RK3308_DAC_ANA_CON02,
- RK3308_DAC_L_DAC_EN | RK3308_DAC_R_DAC_EN);
- regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON10(0),
- RK3308_ADC_CURRENT_CHARGE_MSK, 1);
- regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON10(0),
- RK3308_ADC_REF_EN);
+ if (level != SND_SOC_BIAS_OFF && !on)
+ return 0;
+ if (on && snd_soc_dapm_get_bias_level(dapm) != SND_SOC_BIAS_OFF)
+ return 0;
+ if (on) {
+ regmap_update_bits(rk3308->regmap, 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)
+ regmap_update_bits(rk3308->regmap, 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);
+ }
+ regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON10(0),
+ RK3308_ADC_CURRENT_CHARGE_MSK, 1);
+ if (rk3308->codec_ver >= ACODEC_VERSION_B)
+ regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14,
+ RK3308_DAC_CURRENT_CHARGE_MSK, 1);
+ regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON10(0),
+ RK3308_ADC_REF_EN, on ? RK3308_ADC_REF_EN : 0);
+ if (rk3308->codec_ver >= ACODEC_VERSION_B)
+ regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14,
+ RK3308_DAC_VCM_LINEOUT_EN,
+ on ? RK3308_DAC_VCM_LINEOUT_EN : 0);
+ for (v = 1; v <= 0x7f; v++) {
regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON10(0),
- RK3308_ADC_CURRENT_CHARGE_MSK, 0x7f);
- msleep(20); /* estimated value */
- break;
- case SND_SOC_BIAS_OFF:
- /* Sequence from TRM Section 8.6.4 "Power Down" */
+ RK3308_ADC_CURRENT_CHARGE_MSK, v);
+ usleep_range(200, 250);
+ }
+ for (v = 1; rk3308->codec_ver >= ACODEC_VERSION_B && v <= 0xf; v++) {
+ regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14,
+ RK3308_DAC_CURRENT_CHARGE_MSK, v);
+ usleep_range(200, 250);
+ }
+ msleep(20);
+ if (on) {
regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON10(0),
- RK3308_ADC_CURRENT_CHARGE_MSK, 1);
- regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON10(0),
- RK3308_ADC_REF_EN);
- regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON02,
- RK3308_DAC_L_DAC_EN | RK3308_DAC_R_DAC_EN);
- msleep(20); /* estimated value */
- break;
+ RK3308_ADC_CURRENT_CHARGE_MSK, 0x7c);
+ if (rk3308->codec_ver == ACODEC_VERSION_C) {
+ /* C uses the larger HPOUT and LINEOUT drive strength. */
+ regmap_write(rk3308->regmap, RK3308_DAC_ANA_CON07, 0x11);
+ regmap_write(rk3308->regmap, RK3308_DAC_ANA_CON08, 0x11);
+ }
}
+
return 0;
}
+static void rk3308_codec_remove(struct snd_soc_component *component)
+{
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+
+ rk3308_codec_set_jack(component, NULL, NULL);
+ mutex_lock(&rk3308->io_lock);
+ rk3308->component = NULL;
+ mutex_unlock(&rk3308->io_lock);
+}
+
static const struct snd_soc_component_driver rk3308_codec_component_driver = {
+ .remove = rk3308_codec_remove,
+ .set_jack = rk3308_codec_set_jack,
.probe = rk3308_codec_probe,
.set_bias_level = rk3308_codec_set_bias_level,
+ .seq_notifier = rk3308_codec_seq_notifier,
.controls = rk3308_codec_controls,
.num_controls = ARRAY_SIZE(rk3308_codec_controls),
- .dapm_widgets = rk3308_codec_dapm_widgets,
- .num_dapm_widgets = ARRAY_SIZE(rk3308_codec_dapm_widgets),
.dapm_routes = rk3308_codec_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(rk3308_codec_dapm_routes),
};
@@ -971,6 +1749,7 @@ static int rk3308_codec_get_version(struct rk3308_codec_priv *rk3308)
}
dev_info(rk3308->dev, "Found codec version %c\n", rk3308->codec_ver);
+
return 0;
}
@@ -997,13 +1776,46 @@ static int rk3308_codec_set_micbias_level(struct rk3308_codec_priv *rk3308)
"Invalid value %u for 'rockchip,micbias-avdd-percent'\n",
percent);
- regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON07(0),
- RK3308_ADC_LEVEL_RANGE_MICBIAS_MSK,
- mult << RK3308_ADC_LEVEL_RANGE_MICBIAS_SFT);
+ /* Applied after the component resets the codec. */
+ rk3308->micbias_level = mult << RK3308_ADC_LEVEL_RANGE_MICBIAS_SFT;
return 0;
}
+/* Legacy boards describe the internal ADC-to-I2S2 SDI mux here. */
+static int rk3308_codec_adc_grps_route(struct rk3308_codec_priv *rk3308)
+{
+ struct device_node *np = rk3308->dev->of_node;
+ unsigned int seen = 0, val = 0;
+ int grp, ret;
+
+ for (grp = 0; grp < ADC_LR_GROUP_MAX; grp++)
+ rk3308->adc_grps[grp] = grp;
+
+ if (!of_find_property(np, "rockchip,adc-grps-route", NULL))
+ return 0;
+
+ if (of_property_count_u32_elems(np, "rockchip,adc-grps-route") !=
+ ADC_LR_GROUP_MAX)
+ return -EINVAL;
+
+ ret = of_property_read_u32_array(np, "rockchip,adc-grps-route",
+ rk3308->adc_grps, ADC_LR_GROUP_MAX);
+ if (ret)
+ return ret;
+
+ for (grp = 0; grp < ADC_LR_GROUP_MAX; grp++) {
+ u32 input = rk3308->adc_grps[grp];
+
+ if (input >= ADC_LR_GROUP_MAX || (seen & BIT(input)))
+ return -EINVAL;
+ seen |= BIT(input);
+ val |= input << (grp * 2);
+ }
+
+ return regmap_write(rk3308->grf, GRF_SOC_CON1, (0xff << 16) | val);
+}
+
static int rk3308_codec_platform_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
@@ -1017,6 +1829,7 @@ static int rk3308_codec_platform_probe(struct platform_device *pdev)
return -ENOMEM;
rk3308->dev = dev;
+ mutex_init(&rk3308->io_lock);
rk3308->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
if (IS_ERR(rk3308->grf))
@@ -1049,10 +1862,18 @@ static int rk3308_codec_platform_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, rk3308);
+ err = rk3308_codec_adc_grps_route(rk3308);
+ if (err)
+ return dev_err_probe(dev, err, "Invalid ADC group routing\n");
+
err = rk3308_codec_set_micbias_level(rk3308);
if (err)
return err;
+ err = rk3308_codec_hpdet_init(pdev, rk3308);
+ if (err)
+ return err;
+
err = devm_snd_soc_register_component(dev, &rk3308_codec_component_driver,
&rk3308_codec_dai_driver, 1);
if (err)
diff --git a/sound/soc/codecs/rk3308_codec.h b/sound/soc/codecs/rk3308_codec.h
index a4226b235ab7..5d591c3d5f0d 100644
--- a/sound/soc/codecs/rk3308_codec.h
+++ b/sound/soc/codecs/rk3308_codec.h
@@ -2,7 +2,7 @@
/*
* Rockchip RK3308 internal audio codec driver -- register definitions
*
- * Copyright (c) 2018, Fuzhou Rockchip Electronics Co., Ltd All rights reserved.
+ * Copyright (c) 2018-2026, Rockchip Electronics Co., Ltd All rights reserved.
* Copyright (c) 2022, Vivax-Metrotech Ltd
*/
@@ -471,6 +471,19 @@
#define RK3308_DAC_HPOUT_POP_SOUND_x_WORK 0x2
#define RK3308_DAC_HPOUT_POP_SOUND_x_INIT 0x1
+#define RK3308_DAC_HPOUT_POP_SOUND_L_MSK \
+ (RK3308_DAC_HPOUT_POP_SOUND_x_MSK << RK3308_DAC_HPOUT_POP_SOUND_L_SFT)
+#define RK3308_DAC_HPOUT_POP_SOUND_L_WORK \
+ (RK3308_DAC_HPOUT_POP_SOUND_x_WORK << RK3308_DAC_HPOUT_POP_SOUND_L_SFT)
+#define RK3308_DAC_HPOUT_POP_SOUND_L_INIT \
+ (RK3308_DAC_HPOUT_POP_SOUND_x_INIT << RK3308_DAC_HPOUT_POP_SOUND_L_SFT)
+#define RK3308_DAC_HPOUT_POP_SOUND_R_MSK \
+ (RK3308_DAC_HPOUT_POP_SOUND_x_MSK << RK3308_DAC_HPOUT_POP_SOUND_R_SFT)
+#define RK3308_DAC_HPOUT_POP_SOUND_R_WORK \
+ (RK3308_DAC_HPOUT_POP_SOUND_x_WORK << RK3308_DAC_HPOUT_POP_SOUND_R_SFT)
+#define RK3308_DAC_HPOUT_POP_SOUND_R_INIT \
+ (RK3308_DAC_HPOUT_POP_SOUND_x_INIT << RK3308_DAC_HPOUT_POP_SOUND_R_SFT)
+
/* RK3308_DAC_ANA_CON02 - REG: 0x0448 */
#define RK3308_DAC_R_DAC_WORK BIT(7)
#define RK3308_DAC_R_DAC_EN BIT(6)
@@ -488,6 +501,8 @@
#define RK3308_DAC_L_HPOUT_WORK BIT(2)
#define RK3308_DAC_L_HPOUT_EN BIT(1)
#define RK3308_DAC_L_HPOUT_MUTE_SFT 0
+#define RK3308_DAC_R_HPOUT_UNMUTE BIT(RK3308_DAC_R_HPOUT_MUTE_SFT)
+#define RK3308_DAC_L_HPOUT_UNMUTE BIT(RK3308_DAC_L_HPOUT_MUTE_SFT)
/* RK3308_DAC_ANA_CON04 - REG: 0x0450 */
#define RK3308_DAC_x_LINEOUT_GAIN_MAX 0x3
@@ -511,6 +526,7 @@
/* RK3308_DAC_ANA_CON05 - REG: 0x0454, step is 1.5db */
/* RK3308_DAC_ANA_CON06 - REG: 0x0458, step is 1.5db */
#define RK3308_DAC_x_HPOUT_GAIN_MAX 0x1e
+#define RK3308_DAC_x_HPOUT_GAIN_0DB 0x1a
#define RK3308_DAC_x_HPOUT_GAIN_SFT 0
#define RK3308_DAC_x_HPOUT_GAIN_MSK (0x1f << RK3308_DAC_x_HPOUT_GAIN_SFT)
#define RK3308_DAC_x_HPOUT_GAIN_MIN (0x00 << RK3308_DAC_x_HPOUT_GAIN_SFT)
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 2/3] ASoC: codecs: rk3308: restore audio and headphone support
@ 2026-11-01 4:41 ` Xing Zheng
0 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:41 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
Restore the vendor analog enable and disable order and settling delays
through shared DAPM supply events for codec revisions A, B and C. Use
virtual intermediate widgets to prevent coalesced or repeated analog
writes without adding startup, shutdown or hw_free DAI operations.
Restore reference-current ramps, HPMIX reset, headphone gain ramps and
A/B CIC fades. Preserve stream parameters and ALSA volume and mute
settings. Re-sync active ADCs after DAC reference changes and after
ADC supply events, before external output amplifiers are enabled.
Apply the configured MICBIAS voltage after reset and wait 50 ms before
enabling ADC analog stages. Expose per-channel microphone boost and
validate the ADC stereo-group routing. Keep revision-specific MIC boost
restrictions, LINEOUT VCM/DC controls, digital gain and drive strength.
Select LINEOUT, HPOUT or both through DAPM without enabling inactive
outputs. Restore B/C headphone detection through set_jack and the
named detect-GRF interrupt, retaining legacy interrupt index lookup.
Leave external headphone amplifier GPIO ownership to the machine
DAPM widget and keep debug interfaces outside this change.
Replace the existing revision B output events with the shared analog
sequence. Retain upstream automatic DAI format selection and use the
current ASoC component, control and bias accessors.
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
Changes in v2:
- Combine the codec C and header changes into one complete patch.
- Restore full ADC/DAC analog sequencing, ramps, fades and ADC resync.
- Preserve upstream automatic DAI format selection and adapt accessors.
- Replace sleepable-path busy waits without reducing minimum delays.
sound/soc/codecs/rk3308_codec.c | 1155 ++++++++++++++++++++++++++-----
sound/soc/codecs/rk3308_codec.h | 18 +-
2 files changed, 1005 insertions(+), 168 deletions(-)
diff --git a/sound/soc/codecs/rk3308_codec.c b/sound/soc/codecs/rk3308_codec.c
index a3d41cd8150d..1894e1a93fbf 100644
--- a/sound/soc/codecs/rk3308_codec.c
+++ b/sound/soc/codecs/rk3308_codec.c
@@ -2,7 +2,7 @@
/*
* Rockchip RK3308 internal audio codec driver
*
- * Copyright (c) 2018, Fuzhou Rockchip Electronics Co., Ltd All rights reserved.
+ * Copyright (c) 2018-2026, Rockchip Electronics Co., Ltd All rights reserved.
* Copyright (c) 2024, Vivax-Metrotech Ltd
*/
@@ -10,14 +10,18 @@
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/init.h>
+#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/util_macros.h>
+#include <linux/workqueue.h>
+#include <sound/jack.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
@@ -28,8 +32,16 @@
#define ADC_LR_GROUP_MAX 4
+#define GRF_SOC_CON1 0x304
#define GRF_CHIP_ID 0x800
+#define DETECT_GRF_ACODEC_HPDET_COUNTER 0x0030
+#define DETECT_GRF_ACODEC_HPDET_CON 0x0034
+#define DETECT_GRF_ACODEC_HPDET_STATUS 0x0038
+#define DETECT_GRF_ACODEC_HPDET_STATUS_CLR 0x003c
+#define DEFAULT_HPDET_COUNT 20000000 /* 200 ms at 100 MHz */
+#define HPDET_BOTH_NEG_POS (BIT(0) | BIT(1))
+
enum {
ACODEC_VERSION_A = 'A',
ACODEC_VERSION_B,
@@ -40,12 +52,28 @@ struct rk3308_codec_priv {
const struct device *dev;
struct regmap *regmap;
struct regmap *grf;
+ struct regmap *detect_grf;
struct reset_control *reset;
struct clk *hclk;
struct clk *mclk_rx;
struct clk *mclk_tx;
struct snd_soc_component *component;
unsigned char codec_ver;
+ u32 adc_grps[ADC_LR_GROUP_MAX];
+ u32 micbias_level;
+ /* Serialize output controls, detector work and jack lifetime changes. */
+ struct mutex io_lock;
+ u32 dac_output;
+ u32 output_routes_mask;
+ struct snd_soc_dapm_widget *dac_analog;
+ struct snd_soc_jack *hpdet_jack;
+ struct delayed_work hpdet_work;
+ int hpdet_irq;
+ bool hpdet_enabled;
+ bool hpdet_seen_irq;
+ bool hp_plugged;
+ bool hp_jack_reversed;
+
};
static struct clk_bulk_data rk3308_codec_clocks[] = {
@@ -65,6 +93,84 @@ static const DECLARE_TLV_DB_RANGE(rk3308_codec_dac_lineout_gain_tlv,
3, 3, TLV_DB_SCALE_ITEM(0, 0, 0),
);
+static const DECLARE_TLV_DB_RANGE(rk3308_codec_adc_mic_gain_tlv_a,
+ 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
+ 3, 3, TLV_DB_SCALE_ITEM(2000, 0, 0),
+);
+
+static const DECLARE_TLV_DB_RANGE(rk3308_codec_adc_mic_gain_tlv_b,
+ 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
+ 1, 1, TLV_DB_SCALE_ITEM(660, 0, 0),
+ 2, 2, TLV_DB_SCALE_ITEM(1300, 0, 0),
+ 3, 3, TLV_DB_SCALE_ITEM(2000, 0, 0),
+);
+
+static int rk3308_codec_mic_boost_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+ long gain = ucontrol->value.integer.value[0];
+
+ if (rk3308->codec_ver == ACODEC_VERSION_A && gain != 0 && gain != 3)
+ return -EINVAL;
+
+ return snd_soc_put_volsw(kcontrol, ucontrol);
+}
+
+static const struct snd_kcontrol_new rk3308_codec_mic_boost_controls[] = {
+ SOC_SINGLE_EXT_TLV("MIC1 Boost Volume", RK3308_ADC_ANA_CON01(0),
+ RK3308_ADC_CH1_MIC_GAIN_SFT, RK3308_ADC_CH1_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC2 Boost Volume", RK3308_ADC_ANA_CON01(0),
+ RK3308_ADC_CH2_MIC_GAIN_SFT, RK3308_ADC_CH2_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC3 Boost Volume", RK3308_ADC_ANA_CON01(1),
+ RK3308_ADC_CH1_MIC_GAIN_SFT, RK3308_ADC_CH1_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC4 Boost Volume", RK3308_ADC_ANA_CON01(1),
+ RK3308_ADC_CH2_MIC_GAIN_SFT, RK3308_ADC_CH2_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC5 Boost Volume", RK3308_ADC_ANA_CON01(2),
+ RK3308_ADC_CH1_MIC_GAIN_SFT, RK3308_ADC_CH1_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC6 Boost Volume", RK3308_ADC_ANA_CON01(2),
+ RK3308_ADC_CH2_MIC_GAIN_SFT, RK3308_ADC_CH2_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC7 Boost Volume", RK3308_ADC_ANA_CON01(3),
+ RK3308_ADC_CH1_MIC_GAIN_SFT, RK3308_ADC_CH1_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+ SOC_SINGLE_EXT_TLV("MIC8 Boost Volume", RK3308_ADC_ANA_CON01(3),
+ RK3308_ADC_CH2_MIC_GAIN_SFT, RK3308_ADC_CH2_MIC_GAIN_MAX, 0,
+ snd_soc_get_volsw, rk3308_codec_mic_boost_put,
+ rk3308_codec_adc_mic_gain_tlv_b),
+};
+
+static int rk3308_codec_add_mic_boost_controls(struct snd_soc_component *component)
+{
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(rk3308_codec_mic_boost_controls); i++) {
+ struct snd_kcontrol_new control = rk3308_codec_mic_boost_controls[i];
+
+ if (rk3308->codec_ver == ACODEC_VERSION_A)
+ control.tlv.p = rk3308_codec_adc_mic_gain_tlv_a;
+ ret = snd_soc_add_component_controls(component, &control, 1);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static const char * const rk3308_codec_hpf_cutoff_text[] = {
"20 Hz", "245 Hz", "612 Hz"
};
@@ -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;
+}
+
+static const char * const rk3308_codec_output_text[] = { "Line Out", "Headphone", "Both" };
+static SOC_ENUM_SINGLE_EXT_DECL(rk3308_codec_output_enum, rk3308_codec_output_text);
+
+static int rk3308_codec_output_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+
+ mutex_lock(&rk3308->io_lock);
+ ucontrol->value.enumerated.item[0] = rk3308->dac_output == 11 ? 2 : rk3308->dac_output;
+ mutex_unlock(&rk3308->io_lock);
+
+ return 0;
+}
+
+static int rk3308_codec_output_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+ int ret = 0;
+
+ if (value >= ARRAY_SIZE(rk3308_codec_output_text))
+ return -EINVAL;
+ value = value == 2 ? 11 : value;
+ mutex_lock(&rk3308->io_lock);
+ if (value != rk3308->dac_output) {
+ ret = rk3308_codec_set_output(rk3308, value);
+ if (!ret)
+ ret = 1;
+ }
+ mutex_unlock(&rk3308->io_lock);
+ return ret;
+}
+
+static bool rk3308_codec_hpdet_inserted(struct rk3308_codec_priv *rk3308,
+ unsigned int status)
+{
+ if (rk3308->hp_jack_reversed)
+ return status == 0 || status == BIT(1);
+
+ return status == BIT(0);
+}
+
+static void rk3308_codec_hpdet_work(struct work_struct *work)
+{
+ struct rk3308_codec_priv *rk3308 =
+ container_of(to_delayed_work(work), struct rk3308_codec_priv, hpdet_work);
+ unsigned int status;
+ int ret;
+
+ mutex_lock(&rk3308->io_lock);
+ if (!rk3308->component || !rk3308->hpdet_jack)
+ goto out;
+ if (!rk3308->component->card->instantiated) {
+ mod_delayed_work(system_power_efficient_wq, &rk3308->hpdet_work,
+ msecs_to_jiffies(100));
+ goto out;
+ }
+
+ /* The threaded IRQ retains the last edge after acknowledging it. */
+ if (!rk3308->hpdet_seen_irq) {
+ ret = regmap_read(rk3308->detect_grf, DETECT_GRF_ACODEC_HPDET_STATUS, &status);
+ if (ret)
+ goto out;
+ rk3308->hp_plugged = rk3308_codec_hpdet_inserted(rk3308, status);
+ }
+
+ ret = rk3308_codec_set_output(rk3308, rk3308->hp_plugged ? 1 : 0);
+ if (ret) {
+ dev_err_ratelimited(rk3308->dev, "Failed to switch headphone output: %d\n", ret);
+ goto out;
+ }
+
+out:
+ mutex_unlock(&rk3308->io_lock);
+}
+
+static irqreturn_t rk3308_codec_hpdet_irq(int irq, void *data)
+{
+ struct rk3308_codec_priv *rk3308 = data;
+ unsigned int status;
+ int ret;
+
+ mutex_lock(&rk3308->io_lock);
+ ret = regmap_read(rk3308->detect_grf, DETECT_GRF_ACODEC_HPDET_STATUS, &status);
+ if (ret || !(status & HPDET_BOTH_NEG_POS))
+ goto out;
+ ret = regmap_write(rk3308->detect_grf, DETECT_GRF_ACODEC_HPDET_STATUS_CLR, status);
+ if (ret)
+ goto out;
+
+ if (rk3308->hpdet_jack) {
+ rk3308->hp_plugged = rk3308_codec_hpdet_inserted(rk3308, status);
+ rk3308->hpdet_seen_irq = true;
+ mod_delayed_work(system_power_efficient_wq, &rk3308->hpdet_work,
+ msecs_to_jiffies(10));
+ }
+out:
+ mutex_unlock(&rk3308->io_lock);
+ return IRQ_HANDLED;
+}
+
+static int rk3308_codec_set_jack(struct snd_soc_component *component,
+ struct snd_soc_jack *jack, void *data)
+{
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+ struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
+ bool enabled;
+ int ret = 0;
+
+ if (!rk3308->detect_grf)
+ return jack ? -EOPNOTSUPP : 0;
+
+ mutex_lock(&rk3308->io_lock);
+ enabled = rk3308->hpdet_enabled;
+ rk3308->hpdet_enabled = false;
+ rk3308->hpdet_jack = NULL;
+ mutex_unlock(&rk3308->io_lock);
+ if (enabled)
+ disable_irq(rk3308->hpdet_irq);
+ cancel_delayed_work_sync(&rk3308->hpdet_work);
+
+ mutex_lock(&rk3308->io_lock);
+ ret = regmap_write(rk3308->detect_grf, DETECT_GRF_ACODEC_HPDET_CON,
+ HPDET_BOTH_NEG_POS << 16);
+ if (ret)
+ goto out;
+ ret = regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON00,
+ RK3308_DAC_HEADPHONE_DET_EN);
+ if (ret)
+ goto out;
+
+ /* C needs the internal DAC clock even when no PCM is running. */
+ if (rk3308->codec_ver == ACODEC_VERSION_C) {
+ snd_soc_dapm_mutex_lock(dapm);
+ if (jack) {
+ ret = snd_soc_dapm_force_enable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+ } else {
+ ret = snd_soc_dapm_disable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+ if (!ret)
+ ret = snd_soc_dapm_enable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+ }
+ if (!ret)
+ ret = snd_soc_dapm_sync_unlocked(dapm);
+ snd_soc_dapm_mutex_unlock(dapm);
+ if (ret)
+ goto out;
+ }
+ if (!jack)
+ goto out;
+
+ ret = regmap_write(rk3308->detect_grf, DETECT_GRF_ACODEC_HPDET_COUNTER,
+ DEFAULT_HPDET_COUNT);
+ if (ret)
+ goto out;
+ ret = regmap_write(rk3308->detect_grf, DETECT_GRF_ACODEC_HPDET_STATUS_CLR,
+ HPDET_BOTH_NEG_POS);
+ if (ret)
+ goto out;
+ ret = regmap_set_bits(rk3308->regmap, RK3308_DAC_ANA_CON00,
+ RK3308_DAC_HEADPHONE_DET_EN);
+ if (ret)
+ goto out;
+ ret = regmap_write(rk3308->detect_grf, DETECT_GRF_ACODEC_HPDET_CON,
+ (HPDET_BOTH_NEG_POS << 16) | HPDET_BOTH_NEG_POS);
+ if (ret)
+ goto out;
+
+ rk3308->hpdet_jack = jack;
+ rk3308->hpdet_seen_irq = false;
+ rk3308->hpdet_enabled = true;
+ /* Allow the 200 ms hardware filter to settle before the initial report. */
+ mod_delayed_work(system_power_efficient_wq, &rk3308->hpdet_work,
+ msecs_to_jiffies(210));
+ enable_irq(rk3308->hpdet_irq);
+out:
+ if (ret && jack) {
+ regmap_write(rk3308->detect_grf, DETECT_GRF_ACODEC_HPDET_CON,
+ HPDET_BOTH_NEG_POS << 16);
+ regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON00,
+ RK3308_DAC_HEADPHONE_DET_EN);
+ if (rk3308->codec_ver == ACODEC_VERSION_C) {
+ snd_soc_dapm_mutex_lock(dapm);
+ snd_soc_dapm_disable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+ snd_soc_dapm_enable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+ snd_soc_dapm_sync_unlocked(dapm);
+ snd_soc_dapm_mutex_unlock(dapm);
+ }
+ }
+ mutex_unlock(&rk3308->io_lock);
+ return ret;
+}
+
+static int rk3308_codec_hpdet_init(struct platform_device *pdev,
+ struct rk3308_codec_priv *rk3308)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ int ret;
+
+ if (rk3308->codec_ver == ACODEC_VERSION_A ||
+ of_property_read_bool(np, "rockchip,no-hp-det") ||
+ !of_find_property(np, "rockchip,detect-grf", NULL))
+ return 0;
+
+ rk3308->detect_grf = syscon_regmap_lookup_by_phandle(np, "rockchip,detect-grf");
+ if (IS_ERR(rk3308->detect_grf))
+ return dev_err_probe(dev, PTR_ERR(rk3308->detect_grf),
+ "Failed to get detect GRF\n");
+ if (of_find_property(np, "interrupt-names", NULL))
+ rk3308->hpdet_irq = platform_get_irq_byname(pdev, "hpdet");
+ else
+ rk3308->hpdet_irq = platform_get_irq(pdev, 1);
+ if (rk3308->hpdet_irq < 0)
+ return rk3308->hpdet_irq;
+
+ rk3308->hp_jack_reversed = of_property_read_bool(np, "rockchip,hp-jack-reversed");
+ INIT_DELAYED_WORK(&rk3308->hpdet_work, rk3308_codec_hpdet_work);
+ ret = devm_request_threaded_irq(dev, rk3308->hpdet_irq, NULL, rk3308_codec_hpdet_irq,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN, "acodec-hpdet", rk3308);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request headphone IRQ\n");
+
+ return 0;
+}
+
static const struct snd_kcontrol_new rk3308_codec_controls[] = {
+ SOC_ENUM_EXT("DAC Output Route", rk3308_codec_output_enum,
+ rk3308_codec_output_get, rk3308_codec_output_put),
/* Despite the register names, these set the gain when AGC is OFF */
SOC_SINGLE_RANGE_TLV("MIC1 Capture Volume",
RK3308_ADC_ANA_CON03(0),
@@ -175,107 +562,350 @@ static const struct snd_kcontrol_new rk3308_codec_controls[] = {
1, 2, 0, rk3308_codec_dac_hpmix_gain_tlv),
};
-static int rk3308_codec_pop_sound_set(struct snd_soc_dapm_widget *w,
- struct snd_kcontrol *kcontrol,
- int event)
+/* Keep each vendor analog step separate, including its settling time. */
+static void rk3308_codec_analog_update(struct rk3308_codec_priv *rk3308,
+ unsigned int reg, unsigned int mask,
+ unsigned int value)
{
- 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 val = (event == SND_SOC_DAPM_POST_PMU) ?
- RK3308_DAC_HPOUT_POP_SOUND_x_WORK :
- RK3308_DAC_HPOUT_POP_SOUND_x_INIT;
- unsigned int mask = RK3308_DAC_HPOUT_POP_SOUND_x_MSK;
+ regmap_update_bits(rk3308->regmap, reg, mask, value);
+ usleep_range(20, 40);
+}
- regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON01,
- mask << w->shift, val << w->shift);
+/* DAC reference changes also affect ADCs that were already running. */
+static void rk3308_codec_resync_adcs(struct rk3308_codec_priv *rk3308)
+{
+ unsigned int work[ADC_LR_GROUP_MAX];
+ int grp;
- return 0;
+ for (grp = 0; grp < ADC_LR_GROUP_MAX; grp++) {
+ regmap_read(rk3308->regmap, RK3308_ADC_ANA_CON05(grp), &work[grp]);
+ work[grp] &= RK3308_ADC_CH1_ADC_WORK | RK3308_ADC_CH2_ADC_WORK;
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON05(grp), work[grp]);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON02(grp), work[grp] >> 1);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp), work[grp]);
+ }
+ usleep_range(200, 250);
+ for (grp = 0; grp < ADC_LR_GROUP_MAX; grp++) {
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON05(grp), work[grp]);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON02(grp), work[grp] >> 1);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp), work[grp]);
+ }
}
-static int rk3308_codec_dac_output_vcm_event(struct snd_soc_dapm_widget *w,
- struct snd_kcontrol *kcontrol,
- int event)
+/* 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 val;
+ 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;
+
+ 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);
+ }
+ regmap_read(rk3308->regmap, RK3308_DAC_DIG_CON01, &aif1);
+ regmap_read(rk3308->regmap, RK3308_DAC_DIG_CON02, &aif2);
+ if (rk3308->codec_ver == ACODEC_VERSION_C)
+ regmap_read(rk3308->regmap, RK3308_DAC_DIG_CON04, &digital_gain);
+ regmap_clear_bits(rk3308->regmap, RK3308_GLB_CON, RK3308_DAC_DIG_WORK);
+ usleep_range(10000, 11000);
+ regmap_set_bits(rk3308->regmap, RK3308_GLB_CON, RK3308_DAC_DIG_WORK);
+ regmap_write(rk3308->regmap, RK3308_DAC_DIG_CON01, aif1);
+ regmap_write(rk3308->regmap, RK3308_DAC_DIG_CON02, aif2);
+ if (rk3308->codec_ver == ACODEC_VERSION_C)
+ regmap_write(rk3308->regmap, RK3308_DAC_DIG_CON04, digital_gain);
+ /* Vendor DAC enable steps 1-6: current, buffers, POP and HPMIX. */
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON00,
+ RK3308_DAC_CURRENT_EN, RK3308_DAC_CURRENT_EN);
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON01,
+ (RK3308_DAC_BUF_REF_L_EN | RK3308_DAC_BUF_REF_R_EN),
+ (RK3308_DAC_BUF_REF_L_EN | RK3308_DAC_BUF_REF_R_EN));
+ usleep_range(1000, 1100);
+ 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_WORK | RK3308_DAC_HPOUT_POP_SOUND_R_WORK);
+ if (lineout && 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_INTERNAL | RK3308_DAC_R_SEL_DC_FROM_INTERNAL);
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON13,
+ (RK3308_DAC_L_HPMIX_EN | RK3308_DAC_R_HPMIX_EN),
+ (RK3308_DAC_L_HPMIX_EN | RK3308_DAC_R_HPMIX_EN));
+ usleep_range(1000, 1100);
+ regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON13,
+ RK3308_DAC_L_HPMIX_WORK | RK3308_DAC_R_HPMIX_WORK);
+ usleep_range(50, 100);
+ 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));
+ /* Steps 7-14: outputs, DC selector, reference, clock, DAC and WORK. */
+ if (lineout)
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON04,
+ (RK3308_DAC_L_LINEOUT_EN |
+ RK3308_DAC_R_LINEOUT_EN),
+ (RK3308_DAC_L_LINEOUT_EN | RK3308_DAC_R_LINEOUT_EN));
+ if (headphone) {
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON03,
+ (RK3308_DAC_L_HPOUT_EN | RK3308_DAC_R_HPOUT_EN),
+ (RK3308_DAC_L_HPOUT_EN | RK3308_DAC_R_HPOUT_EN));
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON03,
+ (RK3308_DAC_L_HPOUT_WORK |
+ RK3308_DAC_R_HPOUT_WORK),
+ (RK3308_DAC_L_HPOUT_WORK | RK3308_DAC_R_HPOUT_WORK));
+ }
+ 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,
+ lineout ? (RK3308_DAC_L_SEL_LINEOUT_FROM_INTERNAL |
+ RK3308_DAC_R_SEL_LINEOUT_FROM_INTERNAL) :
+ (RK3308_DAC_L_SEL_DC_FROM_INTERNAL |
+ RK3308_DAC_R_SEL_DC_FROM_INTERNAL));
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON02,
+ (RK3308_DAC_L_REF_EN | RK3308_DAC_R_REF_EN),
+ (RK3308_DAC_L_REF_EN | RK3308_DAC_R_REF_EN));
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON02,
+ (RK3308_DAC_L_CLK_EN | RK3308_DAC_R_CLK_EN),
+ (RK3308_DAC_L_CLK_EN | RK3308_DAC_R_CLK_EN));
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON02,
+ (RK3308_DAC_L_DAC_EN | RK3308_DAC_R_DAC_EN),
+ (RK3308_DAC_L_DAC_EN | RK3308_DAC_R_DAC_EN));
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON02,
+ (RK3308_DAC_L_DAC_WORK | RK3308_DAC_R_DAC_WORK),
+ (RK3308_DAC_L_DAC_WORK | RK3308_DAC_R_DAC_WORK));
+ /* 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));
+ 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);
+ }
- if (rk3308->codec_ver != ACODEC_VERSION_B)
- return 0;
+ return 0;
+}
- switch (event) {
- case SND_SOC_DAPM_PRE_PMU:
- /* Keep the DAC outputs at VCM while their reference is charged. */
- regmap_update_bits(rk3308->regmap, 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);
- regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14,
- RK3308_DAC_CURRENT_CHARGE_MSK, 1);
- regmap_set_bits(rk3308->regmap, RK3308_DAC_ANA_CON14,
- RK3308_DAC_VCM_LINEOUT_EN);
- for (val = 1; val <= 0xf; val++) {
- regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14,
- RK3308_DAC_CURRENT_CHARGE_MSK, val);
- usleep_range(200, 400);
- }
- msleep(20);
- break;
- case SND_SOC_DAPM_POST_PMU:
- regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON15,
- RK3308_DAC_LINEOUT_POP_SOUND_L_MSK |
- RK3308_DAC_LINEOUT_POP_SOUND_R_MSK,
- RK3308_DAC_L_SEL_DC_FROM_INTERNAL |
- RK3308_DAC_R_SEL_DC_FROM_INTERNAL);
- break;
- case SND_SOC_DAPM_POST_PMD:
- regmap_update_bits(rk3308->regmap, 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);
- regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14,
- RK3308_DAC_CURRENT_CHARGE_MSK, 1);
- regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON14,
- RK3308_DAC_VCM_LINEOUT_EN);
- break;
- default:
- break;
+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);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON06(grp),
+ RK3308_ADC_CURRENT_EN);
+ usleep_range(400, 500);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp),
+ RK3308_ADC_CH1_BUF_REF_EN | RK3308_ADC_CH2_BUF_REF_EN);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp),
+ RK3308_ADC_CH1_MIC_EN | RK3308_ADC_CH2_MIC_EN);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON02(grp),
+ RK3308_ADC_CH1_ALC_EN | RK3308_ADC_CH2_ALC_EN);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON05(grp),
+ RK3308_ADC_CH1_CLK_EN | RK3308_ADC_CH2_CLK_EN);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON05(grp),
+ RK3308_ADC_CH1_ADC_EN | RK3308_ADC_CH2_ADC_EN);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON05(grp),
+ RK3308_ADC_CH1_ADC_WORK | RK3308_ADC_CH2_ADC_WORK);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON02(grp),
+ RK3308_ADC_CH1_ALC_WORK | RK3308_ADC_CH2_ALC_WORK);
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp),
+ RK3308_ADC_CH1_MIC_WORK | RK3308_ADC_CH2_MIC_WORK);
+ } else {
+ regmap_read(rk3308->regmap, RK3308_ADC_ANA_CON02(grp), &zerocross);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON02(grp),
+ RK3308_ADC_CH1_ZEROCROSS_DET_EN |
+ RK3308_ADC_CH2_ZEROCROSS_DET_EN);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON05(grp),
+ RK3308_ADC_CH1_ADC_EN | RK3308_ADC_CH2_ADC_EN);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON05(grp),
+ RK3308_ADC_CH1_CLK_EN | RK3308_ADC_CH2_CLK_EN);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON02(grp),
+ RK3308_ADC_CH1_ALC_EN | RK3308_ADC_CH2_ALC_EN);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp),
+ RK3308_ADC_CH1_MIC_EN | RK3308_ADC_CH2_MIC_EN);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp),
+ RK3308_ADC_CH1_BUF_REF_EN | RK3308_ADC_CH2_BUF_REF_EN);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON06(grp),
+ RK3308_ADC_CURRENT_EN);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON05(grp),
+ RK3308_ADC_CH1_ADC_WORK | RK3308_ADC_CH2_ADC_WORK);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON02(grp),
+ RK3308_ADC_CH1_ALC_WORK | RK3308_ADC_CH2_ALC_WORK);
+ regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp),
+ RK3308_ADC_CH1_MIC_WORK | RK3308_ADC_CH2_MIC_WORK);
+ regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON02(grp),
+ RK3308_ADC_CH1_ZEROCROSS_DET_EN |
+ RK3308_ADC_CH2_ZEROCROSS_DET_EN,
+ zerocross & (RK3308_ADC_CH1_ZEROCROSS_DET_EN |
+ RK3308_ADC_CH2_ZEROCROSS_DET_EN));
}
return 0;
}
-static int rk3308_codec_lineout_event(struct snd_soc_dapm_widget *w,
- struct snd_kcontrol *kcontrol,
- int event)
+/* 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 mask = RK3308_DAC_LINEOUT_POP_SOUND_L_MSK << w->shift;
- unsigned int val;
+ 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);
}
-
- regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON15, mask, val);
- usleep_range(20, 40);
+ if (!hp && rk3308->codec_ver >= ACODEC_VERSION_B)
+ rk3308_codec_analog_update(rk3308, RK3308_DAC_ANA_CON15,
+ RK3308_DAC_LINEOUT_POP_SOUND_L_MSK << shift,
+ (event == SND_SOC_DAPM_PRE_PMU ?
+ RK3308_DAC_L_SEL_LINEOUT_FROM_INTERNAL :
+ RK3308_DAC_L_SEL_DC_FROM_VCM) << shift);
return 0;
}
+static int rk3308_codec_lineout_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ return rk3308_codec_output_event(w, false, event);
+}
+
+static int rk3308_codec_hpout_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ return rk3308_codec_output_event(w, true, event);
+}
+
static const struct snd_soc_dapm_widget rk3308_codec_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("MIC1"),
SND_SOC_DAPM_INPUT("MIC2"),
@@ -309,12 +939,9 @@ static const struct snd_soc_dapm_widget rk3308_codec_dapm_widgets[] = {
SND_SOC_DAPM_REG(snd_soc_dapm_mic, "MIC7_WORK", RK3308_ADC_ANA_CON00(3), 2, 1, 1, 0),
SND_SOC_DAPM_REG(snd_soc_dapm_mic, "MIC8_WORK", RK3308_ADC_ANA_CON00(3), 6, 1, 1, 0),
- /*
- * In theory MIC1 and MIC2 can switch to LINE IN, but this is not
- * supported so all we can do is enabling the MIC input.
- */
- SND_SOC_DAPM_REG(snd_soc_dapm_adc, "CH1_IN_SEL", RK3308_ADC_ANA_CON07(0), 4, 1, 1, 0),
- SND_SOC_DAPM_REG(snd_soc_dapm_adc, "CH2_IN_SEL", RK3308_ADC_ANA_CON07(0), 6, 1, 1, 0),
+ /* Select the MIC input before enabling the ADC/ALC stages. */
+ SND_SOC_DAPM_PGA("CH1_IN_SEL", RK3308_ADC_ANA_CON07(0), 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("CH2_IN_SEL", RK3308_ADC_ANA_CON07(0), 6, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("ADC1_BUF_REF_EN", RK3308_ADC_ANA_CON00(0), 0, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("ADC2_BUF_REF_EN", RK3308_ADC_ANA_CON00(0), 4, 0, NULL, 0),
@@ -355,14 +982,14 @@ static const struct snd_soc_dapm_widget rk3308_codec_dapm_widgets[] = {
SND_SOC_DAPM_REG(snd_soc_dapm_adc, "ADC7_EN", RK3308_ADC_ANA_CON05(3), 1, 1, 1, 0),
SND_SOC_DAPM_REG(snd_soc_dapm_adc, "ADC8_EN", RK3308_ADC_ANA_CON05(3), 5, 1, 1, 0),
- SND_SOC_DAPM_ADC("ADC1_WORK", "Capture", RK3308_ADC_ANA_CON05(0), 2, 0),
- SND_SOC_DAPM_ADC("ADC2_WORK", "Capture", RK3308_ADC_ANA_CON05(0), 6, 0),
- SND_SOC_DAPM_ADC("ADC3_WORK", "Capture", RK3308_ADC_ANA_CON05(1), 2, 0),
- SND_SOC_DAPM_ADC("ADC4_WORK", "Capture", RK3308_ADC_ANA_CON05(1), 6, 0),
- SND_SOC_DAPM_ADC("ADC5_WORK", "Capture", RK3308_ADC_ANA_CON05(2), 2, 0),
- SND_SOC_DAPM_ADC("ADC6_WORK", "Capture", RK3308_ADC_ANA_CON05(2), 6, 0),
- SND_SOC_DAPM_ADC("ADC7_WORK", "Capture", RK3308_ADC_ANA_CON05(3), 2, 0),
- SND_SOC_DAPM_ADC("ADC8_WORK", "Capture", RK3308_ADC_ANA_CON05(3), 6, 0),
+ SND_SOC_DAPM_ADC("ADC1_WORK", "HiFi Capture", RK3308_ADC_ANA_CON05(0), 2, 0),
+ SND_SOC_DAPM_ADC("ADC2_WORK", "HiFi Capture", RK3308_ADC_ANA_CON05(0), 6, 0),
+ SND_SOC_DAPM_ADC("ADC3_WORK", "HiFi Capture", RK3308_ADC_ANA_CON05(1), 2, 0),
+ SND_SOC_DAPM_ADC("ADC4_WORK", "HiFi Capture", RK3308_ADC_ANA_CON05(1), 6, 0),
+ SND_SOC_DAPM_ADC("ADC5_WORK", "HiFi Capture", RK3308_ADC_ANA_CON05(2), 2, 0),
+ SND_SOC_DAPM_ADC("ADC6_WORK", "HiFi Capture", RK3308_ADC_ANA_CON05(2), 6, 0),
+ SND_SOC_DAPM_ADC("ADC7_WORK", "HiFi Capture", RK3308_ADC_ANA_CON05(3), 2, 0),
+ SND_SOC_DAPM_ADC("ADC8_WORK", "HiFi Capture", RK3308_ADC_ANA_CON05(3), 6, 0),
/* The "ALC" name from the TRM is misleading, these are needed even without ALC/AGC */
SND_SOC_DAPM_REG(snd_soc_dapm_adc, "ALC1_WORK", RK3308_ADC_ANA_CON02(0), 1, 1, 1, 0),
@@ -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,
+ },
SND_SOC_DAPM_OUTPUT("HPOUT_L"),
SND_SOC_DAPM_OUTPUT("HPOUT_R"),
- SND_SOC_DAPM_OUT_DRV_E("L_LINEOUT_EN", RK3308_DAC_ANA_CON04, 0, 0,
- NULL, 0, rk3308_codec_lineout_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
- SND_SOC_DAPM_OUT_DRV_E("R_LINEOUT_EN", RK3308_DAC_ANA_CON04, 4, 0,
- NULL, 0, rk3308_codec_lineout_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_OUT_DRV_E("L_LINEOUT_EN", RK3308_DAC_ANA_CON04, 0, 0, NULL, 0,
+ rk3308_codec_lineout_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_OUT_DRV_E("R_LINEOUT_EN", RK3308_DAC_ANA_CON04, 4, 0, NULL, 0,
+ rk3308_codec_lineout_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_OUTPUT("LINEOUT_L"),
SND_SOC_DAPM_OUTPUT("LINEOUT_R"),
};
+/* The vendor flow settles MICBIAS before starting any MIC/ALC stages. */
+static int rk3308_codec_micbias_settle(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ msleep(50);
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget rk3308_codec_bias_widget =
+ SND_SOC_DAPM_SUPPLY_S("MICBIAS Settled", 1, SND_SOC_NOPM, 0, 0,
+ rk3308_codec_micbias_settle, SND_SOC_DAPM_POST_PMU);
+
+static const struct snd_soc_dapm_route rk3308_codec_bias_routes[] = {
+ { "MICBIAS Settled", NULL, "MICBIAS1" },
+ { "MICBIAS Settled", NULL, "MICBIAS2" },
+ { "ADC_CURRENT_EN12", NULL, "MICBIAS Settled" },
+ { "ADC_CURRENT_EN34", NULL, "MICBIAS Settled" },
+ { "ADC_CURRENT_EN56", NULL, "MICBIAS Settled" },
+ { "ADC_CURRENT_EN78", NULL, "MICBIAS Settled" },
+};
+
+static void rk3308_codec_seq_notifier(struct snd_soc_component *component,
+ enum snd_soc_dapm_type type, int subseq)
+{
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+
+ /* Reinitialize together after the last ADC group, as in the vendor flow. */
+ if (type == snd_soc_dapm_supply && subseq == 2)
+ rk3308_codec_resync_adcs(rk3308);
+}
+
+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;
+ 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;
+ if (reg == RK3308_ADC_ANA_CON06(grp)) {
+ w->reg = SND_SOC_NOPM;
+ w->shift = grp;
+ w->subseq = 2;
+ w->event = rk3308_codec_adc_event;
+ w->event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD;
+ }
+ }
+ }
+ ret = snd_soc_dapm_new_controls(snd_soc_component_to_dapm(component),
+ widgets, ARRAY_SIZE(rk3308_codec_dapm_widgets));
+ if (ret)
+ return ret;
+ ret = snd_soc_dapm_new_controls(snd_soc_component_to_dapm(component),
+ &rk3308_codec_bias_widget, 1);
+ if (ret)
+ return ret;
+ return snd_soc_dapm_add_routes(snd_soc_component_to_dapm(component),
+ rk3308_codec_bias_routes,
+ ARRAY_SIZE(rk3308_codec_bias_routes));
+}
+
static const struct snd_soc_dapm_route rk3308_codec_dapm_routes[] = {
{ "MICBIAS1", NULL, "MICBIAS Current" },
{ "MICBIAS2", NULL, "MICBIAS Current" },
@@ -558,20 +1268,14 @@ static const struct snd_soc_dapm_route rk3308_codec_dapm_routes[] = {
{ "HPOUT_R", NULL, "DAC_BUF_REF_R" },
{ "L_HPOUT_EN", NULL, "DAC_L_HPMIX_SEL" },
{ "R_HPOUT_EN", NULL, "DAC_R_HPMIX_SEL" },
- { "L_HPOUT_EN", NULL, "DAC_OUTPUT_VCM" },
- { "R_HPOUT_EN", NULL, "DAC_OUTPUT_VCM" },
- { "L_HPOUT_WORK", NULL, "L_HPOUT_EN" },
- { "R_HPOUT_WORK", NULL, "R_HPOUT_EN" },
- { "HPOUT_POP_SOUND_L", NULL, "L_HPOUT_WORK" },
- { "HPOUT_POP_SOUND_R", NULL, "R_HPOUT_WORK" },
- { "HPOUT_L", NULL, "HPOUT_POP_SOUND_L" },
- { "HPOUT_R", NULL, "HPOUT_POP_SOUND_R" },
+ { "HPOUT_L", NULL, "L_HPOUT_EN" },
+ { "HPOUT_R", NULL, "R_HPOUT_EN" },
{ "L_LINEOUT_EN", NULL, "DAC_L_HPMIX_SEL" },
{ "R_LINEOUT_EN", NULL, "DAC_R_HPMIX_SEL" },
- { "L_LINEOUT_EN", NULL, "DAC_OUTPUT_VCM" },
- { "R_LINEOUT_EN", NULL, "DAC_OUTPUT_VCM" },
+ { "LINEOUT_L", NULL, "DAC_BUF_REF_L" },
{ "LINEOUT_L", NULL, "L_LINEOUT_EN" },
+ { "LINEOUT_R", NULL, "DAC_BUF_REF_R" },
{ "LINEOUT_R", NULL, "R_LINEOUT_EN" },
};
@@ -715,7 +1419,7 @@ static int rk3308_codec_adc_dig_config(struct rk3308_codec_priv *rk3308,
* grp 3 = ADC7 and ADC8
*/
u32 used_adc_grps;
- int grp;
+ int grp, idx;
switch (params_channels(params)) {
case 1:
@@ -750,7 +1454,8 @@ static int rk3308_codec_adc_dig_config(struct rk3308_codec_priv *rk3308,
return -EINVAL;
}
- for (grp = 0; grp < used_adc_grps; grp++) {
+ for (idx = 0; idx < used_adc_grps; idx++) {
+ grp = rk3308->adc_grps[idx];
regmap_update_bits(rk3308->regmap,
RK3308_ADC_DIG_CON03(grp),
RK3308_ADC_L_CH_BIST_MSK | RK3308_ADC_R_CH_BIST_MSK,
@@ -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;
+}
+
static const u64 rk3308_selectable_formats =
SND_SOC_POSSIBLE_DAIFMT_I2S |
SND_SOC_POSSIBLE_DAIFMT_RIGHT_J |
@@ -785,6 +1508,7 @@ static const u64 rk3308_selectable_formats =
SND_SOC_POSSIBLE_DAIFMT_IB_IF;
static const struct snd_soc_dai_ops rk3308_codec_dai_ops = {
+ .mute_stream = rk3308_codec_mute_stream,
.hw_params = rk3308_codec_hw_params,
.set_fmt = rk3308_codec_set_dai_fmt,
.auto_selectable_formats = &rk3308_selectable_formats,
@@ -853,89 +1577,143 @@ static int rk3308_codec_initialize(struct rk3308_codec_priv *rk3308)
}
}
+ regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON07(0),
+ RK3308_ADC_LEVEL_RANGE_MICBIAS_MSK, rk3308->micbias_level);
+
+ regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON07(0),
+ RK3308_ADC_CH1_IN_SEL_MSK | RK3308_ADC_CH2_IN_SEL_MSK,
+ RK3308_ADC_CH1_IN_MIC | RK3308_ADC_CH2_IN_MIC);
+
+ /* Match the vendor analog defaults; controls remain adjustable. */
+ for (grp = 0; grp < ADC_LR_GROUP_MAX; grp++)
+ regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp),
+ BIT(3) | BIT(7));
+
+ regmap_set_bits(rk3308->regmap, RK3308_DAC_ANA_CON03,
+ BIT(RK3308_DAC_L_HPOUT_MUTE_SFT) |
+ BIT(RK3308_DAC_R_HPOUT_MUTE_SFT));
+ regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON05,
+ RK3308_DAC_x_HPOUT_GAIN_MSK, RK3308_DAC_x_HPOUT_GAIN_0DB);
+ regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON06,
+ RK3308_DAC_x_HPOUT_GAIN_MSK, RK3308_DAC_x_HPOUT_GAIN_0DB);
+ regmap_set_bits(rk3308->regmap, RK3308_DAC_ANA_CON04,
+ BIT(RK3308_DAC_L_LINEOUT_MUTE_SFT) |
+ BIT(RK3308_DAC_R_LINEOUT_MUTE_SFT));
+ regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON04,
+ RK3308_DAC_L_LINEOUT_GAIN_MSK |
+ RK3308_DAC_R_LINEOUT_GAIN_MSK,
+ RK3308_DAC_L_LINEOUT_GAIN_MSK |
+ RK3308_DAC_R_LINEOUT_GAIN_MSK);
+
/* set HPMIX default gains (reset value is 0, which is illegal) */
regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON12,
RK3308_DAC_L_HPMIX_GAIN_MSK |
RK3308_DAC_R_HPMIX_GAIN_MSK,
- RK3308_DAC_L_HPMIX_GAIN_NDB_6 |
- RK3308_DAC_R_HPMIX_GAIN_NDB_6);
+ RK3308_DAC_L_HPMIX_GAIN_0DB | RK3308_DAC_R_HPMIX_GAIN_0DB);
/* 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;
}
static int rk3308_codec_probe(struct snd_soc_component *component)
{
struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+ int ret;
+ ret = rk3308_codec_add_widgets(component);
+ if (ret)
+ return ret;
rk3308->component = component;
+ rk3308->dac_output = 11;
+ rk3308->output_routes_mask = 3;
rk3308_codec_reset(component);
rk3308_codec_initialize(rk3308);
- return 0;
+ return rk3308_codec_add_mic_boost_controls(component);
}
+/* Reference-current ramps are shared by playback and capture. */
static int rk3308_codec_set_bias_level(struct snd_soc_component *component,
- enum snd_soc_bias_level level)
+ enum snd_soc_bias_level level)
{
struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
+ unsigned int v;
+ bool on = level == SND_SOC_BIAS_STANDBY;
- switch (level) {
- case SND_SOC_BIAS_ON:
- break;
- case SND_SOC_BIAS_PREPARE:
- break;
- case SND_SOC_BIAS_STANDBY:
- if (snd_soc_dapm_get_bias_level(dapm) != SND_SOC_BIAS_OFF)
- break;
-
- /* Sequence from TRM Section 8.6.3 "Power Up" */
- regmap_set_bits(rk3308->regmap, RK3308_DAC_ANA_CON02,
- RK3308_DAC_L_DAC_EN | RK3308_DAC_R_DAC_EN);
- regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON10(0),
- RK3308_ADC_CURRENT_CHARGE_MSK, 1);
- regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON10(0),
- RK3308_ADC_REF_EN);
+ if (level != SND_SOC_BIAS_OFF && !on)
+ return 0;
+ if (on && snd_soc_dapm_get_bias_level(dapm) != SND_SOC_BIAS_OFF)
+ return 0;
+ if (on) {
+ regmap_update_bits(rk3308->regmap, 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)
+ regmap_update_bits(rk3308->regmap, 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);
+ }
+ regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON10(0),
+ RK3308_ADC_CURRENT_CHARGE_MSK, 1);
+ if (rk3308->codec_ver >= ACODEC_VERSION_B)
+ regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14,
+ RK3308_DAC_CURRENT_CHARGE_MSK, 1);
+ regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON10(0),
+ RK3308_ADC_REF_EN, on ? RK3308_ADC_REF_EN : 0);
+ if (rk3308->codec_ver >= ACODEC_VERSION_B)
+ regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14,
+ RK3308_DAC_VCM_LINEOUT_EN,
+ on ? RK3308_DAC_VCM_LINEOUT_EN : 0);
+ for (v = 1; v <= 0x7f; v++) {
regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON10(0),
- RK3308_ADC_CURRENT_CHARGE_MSK, 0x7f);
- msleep(20); /* estimated value */
- break;
- case SND_SOC_BIAS_OFF:
- /* Sequence from TRM Section 8.6.4 "Power Down" */
+ RK3308_ADC_CURRENT_CHARGE_MSK, v);
+ usleep_range(200, 250);
+ }
+ for (v = 1; rk3308->codec_ver >= ACODEC_VERSION_B && v <= 0xf; v++) {
+ regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14,
+ RK3308_DAC_CURRENT_CHARGE_MSK, v);
+ usleep_range(200, 250);
+ }
+ msleep(20);
+ if (on) {
regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON10(0),
- RK3308_ADC_CURRENT_CHARGE_MSK, 1);
- regmap_clear_bits(rk3308->regmap, RK3308_ADC_ANA_CON10(0),
- RK3308_ADC_REF_EN);
- regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON02,
- RK3308_DAC_L_DAC_EN | RK3308_DAC_R_DAC_EN);
- msleep(20); /* estimated value */
- break;
+ RK3308_ADC_CURRENT_CHARGE_MSK, 0x7c);
+ if (rk3308->codec_ver == ACODEC_VERSION_C) {
+ /* C uses the larger HPOUT and LINEOUT drive strength. */
+ regmap_write(rk3308->regmap, RK3308_DAC_ANA_CON07, 0x11);
+ regmap_write(rk3308->regmap, RK3308_DAC_ANA_CON08, 0x11);
+ }
}
+
return 0;
}
+static void rk3308_codec_remove(struct snd_soc_component *component)
+{
+ struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+
+ rk3308_codec_set_jack(component, NULL, NULL);
+ mutex_lock(&rk3308->io_lock);
+ rk3308->component = NULL;
+ mutex_unlock(&rk3308->io_lock);
+}
+
static const struct snd_soc_component_driver rk3308_codec_component_driver = {
+ .remove = rk3308_codec_remove,
+ .set_jack = rk3308_codec_set_jack,
.probe = rk3308_codec_probe,
.set_bias_level = rk3308_codec_set_bias_level,
+ .seq_notifier = rk3308_codec_seq_notifier,
.controls = rk3308_codec_controls,
.num_controls = ARRAY_SIZE(rk3308_codec_controls),
- .dapm_widgets = rk3308_codec_dapm_widgets,
- .num_dapm_widgets = ARRAY_SIZE(rk3308_codec_dapm_widgets),
.dapm_routes = rk3308_codec_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(rk3308_codec_dapm_routes),
};
@@ -971,6 +1749,7 @@ static int rk3308_codec_get_version(struct rk3308_codec_priv *rk3308)
}
dev_info(rk3308->dev, "Found codec version %c\n", rk3308->codec_ver);
+
return 0;
}
@@ -997,13 +1776,46 @@ static int rk3308_codec_set_micbias_level(struct rk3308_codec_priv *rk3308)
"Invalid value %u for 'rockchip,micbias-avdd-percent'\n",
percent);
- regmap_update_bits(rk3308->regmap, RK3308_ADC_ANA_CON07(0),
- RK3308_ADC_LEVEL_RANGE_MICBIAS_MSK,
- mult << RK3308_ADC_LEVEL_RANGE_MICBIAS_SFT);
+ /* Applied after the component resets the codec. */
+ rk3308->micbias_level = mult << RK3308_ADC_LEVEL_RANGE_MICBIAS_SFT;
return 0;
}
+/* Legacy boards describe the internal ADC-to-I2S2 SDI mux here. */
+static int rk3308_codec_adc_grps_route(struct rk3308_codec_priv *rk3308)
+{
+ struct device_node *np = rk3308->dev->of_node;
+ unsigned int seen = 0, val = 0;
+ int grp, ret;
+
+ for (grp = 0; grp < ADC_LR_GROUP_MAX; grp++)
+ rk3308->adc_grps[grp] = grp;
+
+ if (!of_find_property(np, "rockchip,adc-grps-route", NULL))
+ return 0;
+
+ if (of_property_count_u32_elems(np, "rockchip,adc-grps-route") !=
+ ADC_LR_GROUP_MAX)
+ return -EINVAL;
+
+ ret = of_property_read_u32_array(np, "rockchip,adc-grps-route",
+ rk3308->adc_grps, ADC_LR_GROUP_MAX);
+ if (ret)
+ return ret;
+
+ for (grp = 0; grp < ADC_LR_GROUP_MAX; grp++) {
+ u32 input = rk3308->adc_grps[grp];
+
+ if (input >= ADC_LR_GROUP_MAX || (seen & BIT(input)))
+ return -EINVAL;
+ seen |= BIT(input);
+ val |= input << (grp * 2);
+ }
+
+ return regmap_write(rk3308->grf, GRF_SOC_CON1, (0xff << 16) | val);
+}
+
static int rk3308_codec_platform_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
@@ -1017,6 +1829,7 @@ static int rk3308_codec_platform_probe(struct platform_device *pdev)
return -ENOMEM;
rk3308->dev = dev;
+ mutex_init(&rk3308->io_lock);
rk3308->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
if (IS_ERR(rk3308->grf))
@@ -1049,10 +1862,18 @@ static int rk3308_codec_platform_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, rk3308);
+ err = rk3308_codec_adc_grps_route(rk3308);
+ if (err)
+ return dev_err_probe(dev, err, "Invalid ADC group routing\n");
+
err = rk3308_codec_set_micbias_level(rk3308);
if (err)
return err;
+ err = rk3308_codec_hpdet_init(pdev, rk3308);
+ if (err)
+ return err;
+
err = devm_snd_soc_register_component(dev, &rk3308_codec_component_driver,
&rk3308_codec_dai_driver, 1);
if (err)
diff --git a/sound/soc/codecs/rk3308_codec.h b/sound/soc/codecs/rk3308_codec.h
index a4226b235ab7..5d591c3d5f0d 100644
--- a/sound/soc/codecs/rk3308_codec.h
+++ b/sound/soc/codecs/rk3308_codec.h
@@ -2,7 +2,7 @@
/*
* Rockchip RK3308 internal audio codec driver -- register definitions
*
- * Copyright (c) 2018, Fuzhou Rockchip Electronics Co., Ltd All rights reserved.
+ * Copyright (c) 2018-2026, Rockchip Electronics Co., Ltd All rights reserved.
* Copyright (c) 2022, Vivax-Metrotech Ltd
*/
@@ -471,6 +471,19 @@
#define RK3308_DAC_HPOUT_POP_SOUND_x_WORK 0x2
#define RK3308_DAC_HPOUT_POP_SOUND_x_INIT 0x1
+#define RK3308_DAC_HPOUT_POP_SOUND_L_MSK \
+ (RK3308_DAC_HPOUT_POP_SOUND_x_MSK << RK3308_DAC_HPOUT_POP_SOUND_L_SFT)
+#define RK3308_DAC_HPOUT_POP_SOUND_L_WORK \
+ (RK3308_DAC_HPOUT_POP_SOUND_x_WORK << RK3308_DAC_HPOUT_POP_SOUND_L_SFT)
+#define RK3308_DAC_HPOUT_POP_SOUND_L_INIT \
+ (RK3308_DAC_HPOUT_POP_SOUND_x_INIT << RK3308_DAC_HPOUT_POP_SOUND_L_SFT)
+#define RK3308_DAC_HPOUT_POP_SOUND_R_MSK \
+ (RK3308_DAC_HPOUT_POP_SOUND_x_MSK << RK3308_DAC_HPOUT_POP_SOUND_R_SFT)
+#define RK3308_DAC_HPOUT_POP_SOUND_R_WORK \
+ (RK3308_DAC_HPOUT_POP_SOUND_x_WORK << RK3308_DAC_HPOUT_POP_SOUND_R_SFT)
+#define RK3308_DAC_HPOUT_POP_SOUND_R_INIT \
+ (RK3308_DAC_HPOUT_POP_SOUND_x_INIT << RK3308_DAC_HPOUT_POP_SOUND_R_SFT)
+
/* RK3308_DAC_ANA_CON02 - REG: 0x0448 */
#define RK3308_DAC_R_DAC_WORK BIT(7)
#define RK3308_DAC_R_DAC_EN BIT(6)
@@ -488,6 +501,8 @@
#define RK3308_DAC_L_HPOUT_WORK BIT(2)
#define RK3308_DAC_L_HPOUT_EN BIT(1)
#define RK3308_DAC_L_HPOUT_MUTE_SFT 0
+#define RK3308_DAC_R_HPOUT_UNMUTE BIT(RK3308_DAC_R_HPOUT_MUTE_SFT)
+#define RK3308_DAC_L_HPOUT_UNMUTE BIT(RK3308_DAC_L_HPOUT_MUTE_SFT)
/* RK3308_DAC_ANA_CON04 - REG: 0x0450 */
#define RK3308_DAC_x_LINEOUT_GAIN_MAX 0x3
@@ -511,6 +526,7 @@
/* RK3308_DAC_ANA_CON05 - REG: 0x0454, step is 1.5db */
/* RK3308_DAC_ANA_CON06 - REG: 0x0458, step is 1.5db */
#define RK3308_DAC_x_HPOUT_GAIN_MAX 0x1e
+#define RK3308_DAC_x_HPOUT_GAIN_0DB 0x1a
#define RK3308_DAC_x_HPOUT_GAIN_SFT 0
#define RK3308_DAC_x_HPOUT_GAIN_MSK (0x1f << RK3308_DAC_x_HPOUT_GAIN_SFT)
#define RK3308_DAC_x_HPOUT_GAIN_MIN (0x00 << RK3308_DAC_x_HPOUT_GAIN_SFT)
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 3/3] arm64: dts: rockchip: restore RK3308 codec detection resources
2026-11-01 4:41 ` Xing Zheng
@ 2026-11-01 4:41 ` Xing Zheng
-1 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:41 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
Provide the codec detect-GRF phandle and SPI 114/115 interrupt
resources with the same trigger type and ordering as the vendor DTS.
Name the interrupts codec and hpdet so the driver can request the
B/C headphone detector by name while retaining the legacy index 1.
Keep these SoC resources separate from EVB sound-card routing and
external amplifier GPIO configuration.
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
Changes in v2:
- Retain the same SoC detection resources as v1; no functional change.
- Renumber after consolidating the full codec implementation.
arch/arm64/boot/dts/rockchip/rk3308.dtsi | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3308.dtsi b/arch/arm64/boot/dts/rockchip/rk3308.dtsi
index 31c25de2d689..fb4465eda574 100644
--- a/arch/arm64/boot/dts/rockchip/rk3308.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3308.dtsi
@@ -836,6 +836,10 @@ codec: codec@ff560000 {
compatible = "rockchip,rk3308-codec";
reg = <0x0 0xff560000 0x0 0x10000>;
rockchip,grf = <&grf>;
+ rockchip,detect-grf = <&detect_grf>;
+ interrupts = <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "codec", "hpdet";
clock-names = "mclk_tx", "mclk_rx", "hclk";
clocks = <&cru SCLK_I2S2_8CH_TX_OUT>,
<&cru SCLK_I2S2_8CH_RX_OUT>,
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 3/3] arm64: dts: rockchip: restore RK3308 codec detection resources
@ 2026-11-01 4:41 ` Xing Zheng
0 siblings, 0 replies; 25+ messages in thread
From: Xing Zheng @ 2026-11-01 4:41 UTC (permalink / raw)
To: broonie, heiko, linux-sound
Cc: luca.ceresoli, lgirdwood, perex, tiwai, linux-kernel, robh,
krzk+dt, conor+dt, devicetree, linux-arm-kernel, linux-rockchip,
huangtao, sugar.zhang, lxt, xing.zheng
Provide the codec detect-GRF phandle and SPI 114/115 interrupt
resources with the same trigger type and ordering as the vendor DTS.
Name the interrupts codec and hpdet so the driver can request the
B/C headphone detector by name while retaining the legacy index 1.
Keep these SoC resources separate from EVB sound-card routing and
external amplifier GPIO configuration.
Signed-off-by: Xing Zheng <zhengxing@rock-chips.com>
---
Changes in v2:
- Retain the same SoC detection resources as v1; no functional change.
- Renumber after consolidating the full codec implementation.
arch/arm64/boot/dts/rockchip/rk3308.dtsi | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3308.dtsi b/arch/arm64/boot/dts/rockchip/rk3308.dtsi
index 31c25de2d689..fb4465eda574 100644
--- a/arch/arm64/boot/dts/rockchip/rk3308.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3308.dtsi
@@ -836,6 +836,10 @@ codec: codec@ff560000 {
compatible = "rockchip,rk3308-codec";
reg = <0x0 0xff560000 0x0 0x10000>;
rockchip,grf = <&grf>;
+ rockchip,detect-grf = <&detect_grf>;
+ interrupts = <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "codec", "hpdet";
clock-names = "mclk_tx", "mclk_rx", "hclk";
clocks = <&cru SCLK_I2S2_8CH_TX_OUT>,
<&cru SCLK_I2S2_8CH_RX_OUT>,
_______________________________________________
Linux-rockchip mailing list
Linux-rockchip@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply related [flat|nested] 25+ messages in thread
* Re: [PATCH v2 3/3] arm64: dts: rockchip: restore RK3308 codec detection resources
2026-11-01 4:41 ` Xing Zheng
(?)
@ 2026-11-01 5:01 ` sashiko-bot
-1 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-11-01 5:01 UTC (permalink / raw)
To: Xing Zheng, linux-sound; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] ASoC: codecs: rk3308: dangling dac_analog and component pointers on unbind
- [Medium] ASoC: codecs: rk3308: rk3308_codec_adc_event() overwrites MIC mute state
- [Medium] ASoC: codecs: rk3308: leaked devm_kmemdup() widget array on component probe
--
commit a099049aa9bf21b04f0558d74e37572e81c21ec8
Author: Xing Zheng <zhengxing@rock-chips.com>
ASoC: codecs: rk3308: restore audio and headphone support
This commit restores the vendor analog enable/disable sequences, reference
current ramps, headphone detection, and output routing for the RK3308 audio
codec across revisions A, B, and C.
> 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
[ ... ]
> +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]
Does rk3308_codec_adc_event() overwrite the user-configured microphone mute
state when capture starts?
Bits 3 (RK3308_ADC_CH1_MIC_UNMUTE) and 7 (RK3308_ADC_CH2_MIC_UNMUTE) of
RK3308_ADC_ANA_CON00(grp) directly back the MIC1..8 Capture Switch controls
in rk3308_codec_controls:
SOC_SINGLE("MIC1 Capture Switch", RK3308_ADC_ANA_CON00(0), 3, 1, 0),
SOC_SINGLE("MIC2 Capture Switch", RK3308_ADC_ANA_CON00(0), 7, 1, 0),
And rk3308_codec_initialize() already sets the initial unmuted default after
codec reset:
/* Match the vendor analog defaults; controls remain adjustable. */
for (grp = 0; grp < ADC_LR_GROUP_MAX; grp++)
regmap_set_bits(rk3308->regmap, RK3308_ADC_ANA_CON00(grp),
BIT(3) | BIT(7));
If userspace mutes a microphone channel via MIC1..8 Capture Switch while
capture is idle and then starts a capture stream, DAPM powers up the
ADC_CURRENT_EN12..78 supply widgets and runs rk3308_codec_adc_event() with
SND_SOC_DAPM_PRE_PMU, which unconditionally sets RK3308_ADC_CH1_MIC_UNMUTE |
RK3308_ADC_CH2_MIC_UNMUTE and clobbers the mute setting.
[ ... ]
> +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;
[ ... ]
> + ret = snd_soc_dapm_new_controls(snd_soc_component_to_dapm(component),
> + widgets, ARRAY_SIZE(rk3308_codec_dapm_widgets));
> + if (ret)
> + return ret;
[Severity: Medium]
Does rk3308_codec_add_widgets() leak the widgets template array across
component probe cycles?
snd_soc_dapm_new_controls() -> snd_soc_dapm_new_control_unlocked() ->
dapm_cnew_widget() duplicates each widget individually with kmemdup() and
does not retain a pointer to the input widgets array after returning.
Because rk3308_codec_add_widgets() runs from the component probe callback
rk3308_codec_probe() and allocates widgets with devm_kmemdup() on the
underlying platform device (component->dev), the temporary array remains
allocated for the lifetime of the platform device and allocates another copy
on every sound card unbind/rebind or deferred probe cycle. Could this use
kmemdup() and kfree() (or __free(kfree)) instead?
[ ... ]
> static int rk3308_codec_probe(struct snd_soc_component *component)
> {
> struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
> + int ret;
>
> + ret = rk3308_codec_add_widgets(component);
> + if (ret)
> + return ret;
> rk3308->component = component;
> + rk3308->dac_output = 11;
> + rk3308->output_routes_mask = 3;
>
> rk3308_codec_reset(component);
> rk3308_codec_initialize(rk3308);
>
> - return 0;
> + return rk3308_codec_add_mic_boost_controls(component);
> }
[ ... ]
> +static void rk3308_codec_remove(struct snd_soc_component *component)
> +{
> + struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
> +
> + rk3308_codec_set_jack(component, NULL, NULL);
> + mutex_lock(&rk3308->io_lock);
> + rk3308->component = NULL;
> + mutex_unlock(&rk3308->io_lock);
> +}
[Severity: High]
Can rk3308->dac_analog and rk3308->component become dangling pointers across
sound card unbind/rebind or component probe failure?
In rk3308_codec_dac_event(), the driver caches the component's DAC_CURRENT_EN
DAPM widget pointer:
rk3308->dac_analog = w;
When the sound card is unbound, snd_soc_component_remove() calls
rk3308_codec_remove() and then snd_soc_dapm_free(), which frees all DAPM
widgets belonging to the component. Neither rk3308_codec_remove() nor
rk3308_codec_probe() clears rk3308->dac_analog. If the card is rebound and
rk3308_codec_output_event() runs before rk3308_codec_dac_event(), it will
see a non-NULL rk3308->dac_analog and dereference the freed widget:
if (!rk3308->dac_analog)
return 0;
...
if (event == SND_SOC_DAPM_PRE_PMD && !rk3308->dac_analog->new_power)
return 0;
Additionally, in rk3308_codec_probe(), rk3308->component is set before
calling rk3308_codec_add_mic_boost_controls(component). If that call fails,
snd_soc_component_probe() aborts before marking the component as probed, so
rk3308_codec_remove() is not called during error cleanup and
rk3308->component is left pointing to an unprobed component.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261101044149.2018744-1-zhengxing@rock-chips.com?part=3
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 2/3] ASoC: codecs: rk3308: restore audio and headphone support
2026-11-01 4:41 ` Xing Zheng
(?)
@ 2026-11-01 5:11 ` sashiko-bot
-1 siblings, 0 replies; 25+ messages in thread
From: sashiko-bot @ 2026-11-01 5:11 UTC (permalink / raw)
To: Xing Zheng, linux-sound; +Cc: devicetree, robh, conor+dt
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
^ permalink raw reply [flat|nested] 25+ messages in thread
end of thread, other threads:[~2026-11-01 5:11 UTC | newest]
Thread overview: 25+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-01 4:01 [PATCH v1 0/6] RK3308 codec configuration and headphone detection Xing Zheng
2026-11-01 4:01 ` 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 ` 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:01 ` 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 ` 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 ` 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 ` 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:01 ` 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 ` 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 4:41 ` Xing Zheng
2026-11-01 4:41 ` [PATCH v2 2/3] ASoC: codecs: rk3308: restore audio and headphone support Xing Zheng
2026-11-01 4:41 ` Xing Zheng
2026-11-01 5:11 ` sashiko-bot
2026-11-01 4:41 ` [PATCH v2 3/3] arm64: dts: rockchip: restore RK3308 codec detection resources Xing Zheng
2026-11-01 4:41 ` Xing Zheng
2026-11-01 5:01 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.