From 36f99fec2b3e6c5d1b9e7c8ee44163ca67f503ac Mon Sep 17 00:00:00 2001 From: Douglas Teles Date: Fri, 26 Jun 2026 17:37:19 -0300 Subject: [PATCH] soysauce: speaker output via rk817 external amplifier The R35S/Y3506 loudspeaker is not wired to the rk817 class-D SPKO; it hangs off the analog headphone output through an external amp gated by spk-ctl-gpios. The mainline codec routes "SPK" to the unused class-D, so the speaker is silent. Add patch 0060 (use_ext_amplifier: route the speaker mux through the headphone DACs and toggle spk-ctl via a DAPM event) and the per-device audio playback paths (SPK/HP) so the speaker plays and headphone switching works. Co-Authored-By: Claude Opus 4.8 --- .../001-device_config | 9 + ...7-support-external-speaker-amplifier.patch | 158 ++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 projects/ArchR/packages/linux/patches/6.12-LTS/0060-ASoC-rk817-support-external-speaker-amplifier.patch diff --git a/projects/ArchR/packages/hardware/quirks/devices/Game Console R36S Soysauce/001-device_config b/projects/ArchR/packages/hardware/quirks/devices/Game Console R36S Soysauce/001-device_config index ef6fbfc575..28ecae087f 100755 --- a/projects/ArchR/packages/hardware/quirks/devices/Game Console R36S Soysauce/001-device_config +++ b/projects/ArchR/packages/hardware/quirks/devices/Game Console R36S Soysauce/001-device_config @@ -10,4 +10,13 @@ cat </storage/.config/profile.d/001-device_config DEVICE_TEMP_SENSOR="/sys/devices/virtual/thermal/thermal_zone0/temp" DEVICE_GPU_TEMP_SENSOR="/sys/devices/virtual/thermal/thermal_zone1/temp" +# Headphone/speaker switching (headphone_sense). The rk817 ext-amplifier patch +# routes the "SPK" mux position to the loudspeaker (headphone DACs + external +# amp) and "HP" to the headphone jack, so the mapping is direct (the R36S +# inverts it). DEVICE_HEADPHONE_DEV is the kernel jack input device that emits +# SW_HEADPHONE_INSERT; headphone_sense flips the "Playback Mux" on plug/unplug +# and the codec driver follows it to toggle the speaker amp GPIO. +DEVICE_PLAYBACK_PATH_SPK="SPK" +DEVICE_PLAYBACK_PATH_HP="HP" +DEVICE_HEADPHONE_DEV="/dev/input/by-path/platform-rk817-sound-event" CONF diff --git a/projects/ArchR/packages/linux/patches/6.12-LTS/0060-ASoC-rk817-support-external-speaker-amplifier.patch b/projects/ArchR/packages/linux/patches/6.12-LTS/0060-ASoC-rk817-support-external-speaker-amplifier.patch new file mode 100644 index 0000000000..4af8fe3782 --- /dev/null +++ b/projects/ArchR/packages/linux/patches/6.12-LTS/0060-ASoC-rk817-support-external-speaker-amplifier.patch @@ -0,0 +1,158 @@ +From: ArchR +Subject: [PATCH] ASoC: rk817: support external speaker amplifier (use_ext_amplifier) + +Some RK3326 handhelds (e.g. the R35S / "Soysauce") do not wire the +loudspeaker to the rk817 class-D SPKO output. Instead the speaker hangs +off the analog headphone output (HPOL/HPOR) through an external amplifier +that is enabled by a GPIO (spk-ctl-gpios). This mirrors the downstream +vendor codec's "use_ext_amplifier" path. + +The mainline driver routes the "SPK" mux position to the class-D, so on +those boards the speaker is silent (the class-D output goes nowhere) and, +because the simple-card jack auto-switch selects "SPK" when no headphones +are plugged, the headphone stage is powered down too. + +Add a "rockchip,use-ext-amplifier" property: when set, route the speaker +mux position through the headphone DACs and toggle spk-ctl-gpios via a +DAPM event in sync with the speaker (SPKO) endpoint. Behaviour is +unchanged on boards without the property. + +Signed-off-by: ArchR +--- +--- a/sound/soc/codecs/rk817_codec.c 2026-06-26 12:51:08.341379192 -0300 ++++ b/sound/soc/codecs/rk817_codec.c 2026-06-26 12:52:24.432394444 -0300 +@@ -7,6 +7,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -23,6 +24,15 @@ + struct clk *mclk; + unsigned int stereo_sysclk; + bool mic_in_differential; ++ /* ++ * On boards where the loudspeaker is not wired to the rk817 class-D ++ * SPKO output but to an external amplifier fed from the analog ++ * headphone output (HPOL/HPOR) and gated by a GPIO (the vendor ++ * driver's "use_ext_amplifier" case), the speaker path has to use the ++ * headphone DACs and toggle that GPIO instead of the class-D. ++ */ ++ bool use_ext_amplifier; ++ struct gpio_desc *spk_ctl_gpio; + }; + + /* +@@ -109,6 +119,25 @@ + static const struct snd_kcontrol_new dac_mux = + SOC_DAPM_ENUM("Playback Mux", dac_enum); + ++/* ++ * Drives the external speaker amplifier enable GPIO (spk-ctl-gpios) in sync ++ * with the speaker output path. Only used on use_ext_amplifier boards; on ++ * others spk_ctl_gpio is NULL and this is a no-op. ++ */ ++static int rk817_ext_amp_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 rk817_codec_priv *rk817 = snd_soc_component_get_drvdata(component); ++ ++ if (!rk817->spk_ctl_gpio) ++ return 0; ++ ++ gpiod_set_value_cansleep(rk817->spk_ctl_gpio, ++ SND_SOC_DAPM_EVENT_ON(event) ? 1 : 0); ++ return 0; ++} ++ + static const struct snd_soc_dapm_widget rk817_dapm_widgets[] = { + + /* capture/playback common */ +@@ -160,6 +189,11 @@ + SND_SOC_DAPM_SUPPLY("Class D OCPN 2", RK817_CODEC_ACLASSD_CFG2, 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Class D OCPN 3", RK817_CODEC_ACLASSD_CFG2, 0, 0, NULL, 0), + ++ /* external speaker amplifier enable (use_ext_amplifier boards only) */ ++ SND_SOC_DAPM_SUPPLY("Ext Speaker Amp", SND_SOC_NOPM, 0, 0, ++ rk817_ext_amp_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ + /* playback path headphones */ + SND_SOC_DAPM_SUPPLY("Headphone Charge Pump", RK817_CODEC_AHP_CP, 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Headphone CP Discharge LDO", RK817_CODEC_AHP_CP, 3, 1, NULL, 0), +@@ -279,7 +313,11 @@ + /* mux path for output selection */ + {"Playback Mux", "HP", "DAC L"}, + {"Playback Mux", "HP", "DAC R"}, +- {"Playback Mux", "SPK", "SPK DAC"}, ++ /* ++ * The "SPK" branch of the mux is added at probe time: the class-D ++ * route ("SPK" -> "SPK DAC") for normal boards, or the headphone-DAC ++ * route plus the external amp enable for use_ext_amplifier boards. ++ */ + {"SPKO", NULL, "Playback Mux"}, + {"HPOL", NULL, "Playback Mux"}, + {"HPOR", NULL, "Playback Mux"}, +@@ -423,6 +461,36 @@ + + rk817_init(component); + ++ /* ++ * Wire up the "SPK" branch of the playback mux. The widgets already ++ * exist (added before this probe), the static routes are added after, ++ * so adding routes here is safe and keeps them ahead of the rest. ++ * ++ * Normal boards: route the speaker mux position to the class-D DAC. ++ * use_ext_amplifier boards: the loudspeaker hangs off the analog ++ * headphone output through an external amp, so route the speaker mux ++ * position to the headphone DACs instead and enable that amp via the ++ * spk-ctl GPIO whenever the SPKO endpoint (the card's "Speaker") is on. ++ */ ++ if (rk817->use_ext_amplifier) { ++ static const struct snd_soc_dapm_route ext_amp_routes[] = { ++ {"Playback Mux", "SPK", "DAC L"}, ++ {"Playback Mux", "SPK", "DAC R"}, ++ {"SPKO", NULL, "Ext Speaker Amp"}, ++ }; ++ ++ snd_soc_dapm_add_routes(snd_soc_component_get_dapm(component), ++ ext_amp_routes, ++ ARRAY_SIZE(ext_amp_routes)); ++ } else { ++ static const struct snd_soc_dapm_route classd_route[] = { ++ {"Playback Mux", "SPK", "SPK DAC"}, ++ }; ++ ++ snd_soc_dapm_add_routes(snd_soc_component_get_dapm(component), ++ classd_route, ARRAY_SIZE(classd_route)); ++ } ++ + /* setting initial pll values so that we can continue to leverage simple-audio-card. + * The values aren't important since no parameters are used. + */ +@@ -466,6 +534,22 @@ + rk817->mic_in_differential = + of_property_read_bool(node, "rockchip,mic-in-differential"); + ++ rk817->use_ext_amplifier = ++ of_property_read_bool(node, "rockchip,use-ext-amplifier"); ++ ++ /* ++ * spk-ctl-gpios lives in the "codec" child node, not on the MFD cell's ++ * own device node, so fetch it through that fwnode explicitly. ++ */ ++ rk817->spk_ctl_gpio = devm_fwnode_gpiod_get(dev, of_fwnode_handle(node), ++ "spk-ctl", GPIOD_OUT_LOW, ++ "spk-ctl"); ++ if (IS_ERR(rk817->spk_ctl_gpio)) { ++ dev_dbg(dev, "no spk-ctl gpio: %ld\n", ++ PTR_ERR(rk817->spk_ctl_gpio)); ++ rk817->spk_ctl_gpio = NULL; ++ } ++ + of_node_put(node); + } +