From 54b768312734e746c0610f36eec4d330b0a7ef6d Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Wed, 15 Apr 2026 00:00:47 -0700 Subject: [PATCH] native: speaker-first PCM selection + per-PCM name diagnostic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the SOF buffer-sizing fix. Amp toggles dropped from 10,686 to 4 per boot (big win) but speakers still silent. Suspicion: ac-native opens hw:0,0 blindly, but on SOF JSL topologies card0 exposes multiple playback PCMs and the Chromebook convention isn't universal — pcm0 may be Headset (RT5682) while Speakers (MAX98360A) sits on pcm1, or vice versa. Opening the wrong one means no audio ever reaches the amp, regardless of whether DAPM + SD_MODE are correct. Fix: before the legacy hw:0,0 → hw:1,0 → … fallback chain, scan /proc/asound/cardN/pcmMp/info for name/id strings. Skip HDMI (monitor audio, not speaker). Prefer "Speaker" match; accept "HiFi" / "Jack" (SOF combined front-end name) as fallback. If neither exists we drop through to the original hardcoded list so nothing regresses on boards without /proc/asound. Also dump each PCM's id + name in pre-launch.log so the next boot tells us which PCM is actually the speaker device, end to end. Co-Authored-By: Claude Opus 4.6 (1M context) --- fedac/native/initramfs/init | 11 ++++++++++ fedac/native/src/audio.c | 42 +++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/fedac/native/initramfs/init b/fedac/native/initramfs/init index 410bd5792d..0b5fdae352 100755 --- a/fedac/native/initramfs/init +++ b/fedac/native/initramfs/init @@ -250,6 +250,17 @@ if [ -d /sys/bus/platform/devices/jsl_rt5682_def ]; then fi echo "Sound class:" >> $LOG ls /sys/class/sound/ 2>/dev/null >> $LOG +# Per-PCM names — SOF topologies name PCMs like "Headset (*)", "Speakers (*)", +# "HDMI1 (*)" in /proc/asound/card0/pcmNp/info. If ac-native is opening the +# wrong PCM (speaker vs headphone), this answers it in one glance. +echo "Card 0 PCM names:" >> $LOG +for p in /proc/asound/card0/pcm*p/info; do + [ -r "$p" ] || continue + dev=$(dirname "$p" | sed 's|.*/||') + name=$(grep -E '^name:' "$p" 2>/dev/null | head -1) + id=$(grep -E '^id:' "$p" 2>/dev/null | head -1) + echo " $dev: $id / $name" >> $LOG +done echo "ASoC debugfs:" >> $LOG mount -t debugfs debugfs /sys/kernel/debug 2>/dev/null ls /sys/kernel/debug/asoc/ 2>/dev/null >> $LOG diff --git a/fedac/native/src/audio.c b/fedac/native/src/audio.c index 177634be3a..b315ce7372 100644 --- a/fedac/native/src/audio.c +++ b/fedac/native/src/audio.c @@ -1756,6 +1756,48 @@ ACAudio *audio_init(void) { int err = -1; int card_idx = 0; + // Smart PCM selection — on SOF topologies (Chromebooks), card0 typically + // exposes multiple playback PCMs with distinct names: "Headset (*)", + // "Speakers (*)", "HDMI1 (*)"… hw:0,0 blindly is fine on HDA-direct + // codecs but opens the wrong endpoint on SOF. Scan /proc/asound and + // prefer the PCM whose name contains "Speaker" (fall back to anything + // containing "HiFi" which is the common SOF FE name for both-path). + // HDMI PCMs are explicitly skipped — they'd play on monitor, not speakers. + for (int c = 0; c < 4 && err < 0; c++) { + for (int d = 0; d < 8 && err < 0; d++) { + char info_path[64]; + snprintf(info_path, sizeof(info_path), "/proc/asound/card%d/pcm%dp/info", c, d); + FILE *ip = fopen(info_path, "r"); + if (!ip) continue; + char line[256], id_str[96] = "", name_str[96] = ""; + while (fgets(line, sizeof(line), ip)) { + if (!strncmp(line, "id: ", 4)) snprintf(id_str, sizeof(id_str), "%s", line + 4); + if (!strncmp(line, "name: ", 6)) snprintf(name_str, sizeof(name_str), "%s", line + 6); + } + fclose(ip); + // Skip HDMI — those route to monitor audio, not internal speaker. + if (strstr(id_str, "HDMI") || strstr(name_str, "HDMI")) continue; + // Prefer Speaker name; accept HiFi (SOF combined FE) as fallback. + int is_speaker = (strstr(id_str, "Speaker") || strstr(name_str, "Speaker")); + int is_combined = (strstr(id_str, "HiFi") || strstr(name_str, "HiFi") || + strstr(id_str, "Jack") || strstr(name_str, "Jack")); + if (!is_speaker && !is_combined) continue; + char dev_str[16]; + snprintf(dev_str, sizeof(dev_str), "hw:%d,%d", c, d); + err = snd_pcm_open(&pcm, dev_str, SND_PCM_STREAM_PLAYBACK, 0); + if (err >= 0) { + fprintf(stderr, "[audio] SOF PCM match: %s (id=%.*s)\n", + dev_str, (int)strcspn(id_str, "\n"), id_str); + snprintf(audio->audio_device, sizeof(audio->audio_device), "%s", dev_str); + card_idx = c; + // Prefer speaker name over combined — if we found a Speaker, + // stop here; otherwise keep scanning in case a later PCM is + // the speaker (cheap sort: Speaker beats HiFi by continuing). + if (is_speaker) break; + } + } + } + // AC_AUDIO_DEVICE override — try the env var device before the hardcoded list. const char *env_dev = getenv("AC_AUDIO_DEVICE"); if (env_dev && env_dev[0]) { -- 2.54.0