Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/PAF/audio.md

48 KiB

Audio — WCD9340 / SLIMbus

Current — 2026-08-07

This is a working phone audio stack, not an active microphone bring-up.

  • Runtime: PipeWire + WirePlumber + pipewire-pulse + pipewire-alsa. Native PulseAudio and the ABI-provider shims are historical experiments, not the running authority.
  • HiFi: both CS35L36 speakers play; the WCD9340 microphone captures clean mono audio through DMIC4 → DEC5/TX5. Casey uses it regularly for STT.
  • Calls: the Voice Call UCM profile, souveraine-callaudio, souveraine-q6voiced, and souveraine-callaudiod carry real two-way calls and the earpiece/speaker path.
  • Persistence: WirePlumber restores volumes and the selected profile. Do not add another volume writer; role policy remains TASK-39.
  • One hardware debt remains: the kernel permanently holds codec sysclk and MICB2 from probe. Audio works, but the hold costs idle power and blocks a clean suspend story. TASK-07 owns removing it.

The July “UN-PIN TEST PASSED” below removed the userspace always-open mic pin. It did not remove kernel commit cd7758eae's sysclk + MICB2 hold; that similar noun is the likely source of the remembered “we resolved it.”

The implementation lives in Pixel3Arch under pkgs/souveraine-ucm-blueline, pkgs/blueline-callaudio (package name souveraine-callaudio), pkgs/souveraine-callaudiod, and pkgs/q6voiced (package name souveraine-q6voiced).

Everything below is the evidence ledger. Headings such as “still open” describe the date beneath them unless the current block above repeats the defect.

PIPEWIRE RESTORED — 2026-07-19 late evening

The PA-native arrangement is retired. Live state, all verified on-device:

  • Stack: pipewire 1.6.8 + wireplumber 0.5.15 + pipewire-pulse + pipewire-alsa. The shims (pipewire-abi-provider, pulse-native-provider) and the patched pulseaudio-q6 are removed (rollback pkg kept at ~/build/pulseaudio-q6-r3/; /etc/pulse backed up as /etc/pulse.bak-20260719). The enable-pipewire gate drop-ins were deleted — PipeWire is unconditional now.
  • UCM discovery works under ACP/wireplumber. HiFi verb → Speaker sink + Mic source appear named from the UCM comments. The old alsaucm -c G3 -2 failure is an id-vs-longname CLI quirk; -c "Google Pixel 3" parses both verbs fine. Real clients are unaffected.
  • Playback: 51-blueline-audio-s16.conf (S16LE + disable-mmap + generous periods, recovered verbatim from 8920c3c) reinstated; chime confirmed audible on glass.
  • Capture: mic source pinned mono S16LE 48k, suspend disabled, period 4800 / headroom 9600 (90-blueline.conf). Nonzero on every test capture.
  • UN-PIN TEST PASSED. With the kernel sysclk hold (cd7758eae) and no loopback hold, two consecutive same-map reopens both captured nonzero (451k/470k nonzero samples). The userspace mic pin is DELETED — kernel hold alone is sufficient. Cold-boot confirmation pending.
  • New kernel bug found and fixed: any xrun → userspace snd_pcm_prepare() re-enters wcd934x prepare without shutdown → slim_stream_prepare -EINVAL "Stream already Prepared" retried ~50/s forever (PipeWire recover loop starved the whole graph). Fix f56b1ae08 on mic-race-fix: per-dai prepared flag, re-prepare no-ops, stream stays armed (alternation-law friendly). Wireplumber headroom masks the common case until the kernel is installed.
  • VoiceCall.conf hw:0,5hw:0,2 fixed on-device + repo.
  • Voice-call discovery: VoiceMMode1 (hw:0,2) open fails -EINVAL until the voice BE routes (SLIMBUS_0_RX Voice Mixer VoiceMMode1, VoiceMMode1 Capture Mixer SLIMBUS_0_TX) are applied — DPCM no-backend rejection. With routes applied the PCM opens. This is why q6voiced logged "Failed to open tx/rx: Invalid argument". Because the PCM is unprobeable, ACP can never synthesize a VoiceCall profile, so callaudiod's profile-switching path is structurally dead on this card. Wrong — see 2026-07-29. ACP never probes hw:0,2; it probes the PCMs the verb declares, and the profile was lost for two reasons of our own making. Shipped blueline-callaudio-hook (system service): MM CallAdded → UCM voice verb, last call terminating → HiFi verb restore. q6voiced then opens an already-routed PCM.

PHONE CALLS WORK — TWO-WAY AUDIO ON A REAL BEARER — 2026-07-29

Incoming call, answered, audio both directions, confirmed by ear at both ends. Five defects had to fall, every one of them ours:

  • Verb named VoiceCall. alsa-lib's SND_USE_CASE_VERB_VOICECALL is "Voice Call", with the space, and callaudiod accepts only a profile carrying that prefix (cad-pulse.c:508). Renamed.
  • The voice verb gave MultiMedia1 no backend. HiFi routes it through QUAT_TDM_RX_0; the voice verb routed nothing, so opening hw:0,0 under the verb returned -EINVAL and ACP dropped the profile — "Profile 'Voice Call' mapping 'Voice Call: Speaker: sink': output PCM open failed", spa-acp-tool -v -v. That, not an unprobeable hw:0,2, is why no voice profile ever appeared. The card carries one now.
  • Downlink went to SLIMBUS_0_RX. blueline's earpiece and loudspeaker are both CS35L36 amps on QUAT_TDM_RX_0; the WCD9340 is the uplink and the headset jack. Voice sent to the codec reached nothing — audible as a call the far end heard perfectly while this end heard silence.
  • A front-end takes exactly one backend. These mixers are not additive: enabling a second silently clears the first, measured in both directions. Setting QUAT and SLIMBUS together is not belt-and-braces, it is a no-op.
  • q6voiced latches a failed open. tinyalsa's pcm_open() returns a non-NULL handle on failure and q6voiced_open() guards on if (v->tx), so one lost race silences the whole call. Reachable on any outgoing call: mm_state_is_active() counts DIALING, emitted in the same instant as CallAdded. Incoming calls are safe only by accident — RINGING_IN is not counted active, so their open lands well after the routes. Patched in souveraine-q6voiced to release and retry.

Two further findings:

  • The mic died after every call, not just occasionally. alsaucm holds its UCM context in-process, so a one-shot set _verb HiFi has no previous verb to dismantle and never runs its DisableSequence. TX5 and TX7 both stayed mapped into AIF1_CAP, SLIM_0_TX carried two channels into a mono front-end, and capture read exact zeros. The hook now unmaps the outgoing leg explicitly.
  • callaudiod cannot own earpiece/speaker on this card. It switches a sink port (cad-pulse.c:961) and needs both port types on one sink, but ACP models UCM devices as one profile per combination — Voice Call (Earpiece, Mic) and Voice Call (Mic, Speaker). ConflictingDevices does not change it. enchilada sidesteps this by giving each transducer its own PCM; blueline has both amps behind one. The toggle is therefore blueline-callaudio-hook speaker on|off, writing the amps directly.

ROOT CAUSE FOUND AND FIXED — MICS CAPTURE REAL AUDIO — 2026-07-19

The wall was the codec master clock. Mainline DT fed the WCD9340's extclk from RPMH_LN_BB_CLK2 (inherited from the reference-board dtsi). On blueline, Android's own DT gives LN_BB_CLK2 to the Pixel Visual Core (mnh-sm) and feeds the codec from clock_rpmh index 4 = LN_BB_CLK3 (verified by decompiling the phone's dtbo_b; audio_ext_clk_lnbb<&clock_rpmh 0x04>). The RPMh vote on the wrong buffer succeeds, so nothing errors — the codec just never receives 19.2 MHz. That single fault explained the entire residue:

  • eFuse sensing timed out every boot (-110, status=0x0) — needs MCLK, no ports involved. After the fix: eFuse sense complete: status=0x01 out14=0xda out15=0x01 — first time ever on mainline.
  • Analog AND digital capture read zeros (whole audio core unclocked) while every SLIM control write ACKed (SLIMbus clock is separate).
  • IRQ 213 (codec SLIM data line) never fired in the machine's life; after the fix it fires on every capture.
  • MBHC read garbage impedance at probe.

Fix: &wcd9340 { clocks = <&rpmhcc RPMH_LN_BB_CLK3>; } in sdm845-google-common.dtsi (committed in the kernel tree). Deployed initially as a verified one-byte DTB patch (/boot/dtbs/sdm845-google-blueline.dtb, backup .lnbb2-orig beside it; kexec chain reads the DTB from /boot, NOT from the boot_a image — see below).

Live capture results after the fix (kernel g8cbfbb939327, same boot):

  • AMIC1 → ADC1 → DEC0 → TX0: 121,724 / 144,000 nonzero samples
  • DMIC0 → DEC7 → TX7 (hw:0,1): 94,198 nonzero
  • DMIC4 → DEC5 → TX5 (Android triple-mic route): 81,545 nonzero
  • DMIC3 → DEC6 → TX6 (Android triple-mic route): 92,216 nonzero
  • DMIC4 → DEC7 → TX7 (Android handset-mic route): 124,050 nonzero

All three physical mics (DMIC0/3/4 per Android's triple-mic path) and the analog path produce real ambient audio through plain ALSA hw:0,1no HAL, no ADM usecase, no ACDB OEM tree in the loop. This falsifies the "ADM usecase routing is required for samples to flow" hypothesis from the 2026-07-19 live-reconciliation doc, and retroactively confirms every "codec registers are bit-perfect but silent" audit: the registers were right; the silicon was unclocked.

Boot chain discovery (why kernel deploys "never landed")

boot_a holds pocketboot (LinuxBoot, a 7.2.0-rc1 first-stage kernel). It kexecs /boot/vmlinuz-blueline + /boot/dtbs/sdm845-google-blueline.dtb per /boot/extlinux/extlinux.conf on Arch_root (linux,booted-from-kexec in the live chosen node). Flashing boot_a via fastboot does NOT change the running kernel/DTB — install to /boot on the rootfs instead.

Then-open capture intermittency — superseded later on 2026-07-19

  • Ordering effect observed on the first post-fix boot: DMIC3/4 through MUX6/MUX7 read zero until the DEC5/DEC6 (Android-native) routes ran once; after that, the previously-dead MUX7+DMIC4 handset route worked. Some first-use state sticks. Full codec+IFD regmap snapshots of the known-good state saved on-phone (/tmp/codec-good-state.txt, /tmp/ifd-good-state.txt — copy off before they evaporate; /tmp is tmpfs).
  • The last test of the session (speaker-tone loopback) read all-zero again right after a string of successes — cause unestablished. Per the live-reconciliation doc's warning, record airplane/radio/mode state before interpreting ANY zero capture from now on.
  • Android runtime parity notes for the diff: Android recording state has 0xd25 = 0x11 (ADC MUX3/7 mode reg) where ours reads 0x00 even while working; pads 0x8015-0x8018 = 0x00, 0x8019/1a = 0x0a — identical to what our kernel programs (my in-session debugfs pad pokes don't persist).
  • Regmap-cache caveat from the reconciliation doc applies to OUR debugfs reads of IFD/PGD registers too: prefer live-read paths when auditing those.

THE ALTERNATION LAW — exact zombie mechanism (2026-07-19, evening)

Every capture result from all three boots today reduces to ONE deterministic rule: a SLIM TX capture produces samples only when its channel map differs from the previous capture session's map. Re-opening with the identical map always reads zeros. Verified live: TX5→TX5 = work,zero; TX5→TX6→TX5 = work,work,work; boot-first TX7 always zero (ch135 is pre-touched by ADSP boot/cal state, so the first TX7 session counts as a same-map reopen). Everything earlier read as input-specific (DMIC3/4 dead), power-related (PA holding sysclk), or "priming" was test-order artifact of this law.

  • Codec side is fully correct per-session (DEF_ACT_CHAN wbuf identical between dead and working sessions except TID; teardown calls slim_stream_unprepare which sends DEACTIVATE/REMOVE/RECONFIGURE per drivers/slimbus/stream.c).
  • Clock gates during a zombie capture are bit-identical to Android's working recording (0x711=0x91 0xd41=0x01 0xd42=0x0d).
  • Conclusion: the ADSP's SLIM-master/AFE side treats an identical re-DEFINE as a no-op and never re-arms the data path. Android never hits this because its HAL/ADM re-arms per session (the reconciliation doc's ADM hypothesis, refined: not "ADM required for routing" but "ADM masks the stale-restart").
  • NEXT INSTRUMENTATION: log outgoing MCs in qcom_slim_ngd xfer path and diff same-map vs changed-map session teardown/setup on the wire; then candidate kernel fix = force a map perturbation or explicit port disconnect+reconnect on every SLIM capture start.
  • Userspace mitigation shipped meanwhile (see 90-blueline-alsa.pa): a null-sink loopback holds blueline_mic open from PA start — the boot-time open is on virgin TX5 (works), and a held-open source never re-opens, so the law is never violated. VALIDATED on true cold boot 2026-07-19 12:09: blueline_mic RUNNING at boot, 262k/263k nonzero samples first client, two back-to-back client sessions both live (peaks 5256/5590/26044). Costs continuous capture power (remove once the kernel fix lands).

THE BOOT RACE — pinned boot capture is a coin flip; kernel + config were red herrings (2026-07-19 evening)

Journal survey of ALL 9 boots of 2026-07-19 (journalctl -b -8 .. -b 0, MICDBG sysclk lines at the pin's boot-time capture open):

boots kernel at pin open MICB2 outcome
-7 g323b297 users=2 skip (joined a live hold) 0x50 262k WORKING
-3 gbdf098937 users=2 skip 0x50 never tested (!)
-8 g323b297 users=1 full bring-up after users=0 HW POWER-OFF 0x10 zero
-6 -5 -4, 0 gbdf098937 same losing pattern 0x10 zero
-2 -1 gfdb22e5 same losing pattern 0x10 zero

An early boot-time actor grabs codec sysclk ~3s after boot, holds ~3s, and has MICB2's 0x40 bit up while it holds. If PA's pin lands INSIDE that window the capture joins as user 2 and samples flow; if it lands after the release (codec did a HW POWER-OFF), capture does its own full bring-up and reads zero on every route for the rest of the boot — the same persistent zombie the PA-stop experiment produced. Same kernel both wins and loses (g323b297: won -7, lost -8; gbdf098937: won -3, lost four). Therefore:

  • QRTR=y (fdb22e5/f245323) is exonerated. Every "dead on fdb22e5" observation was a lost race, sampled. Config stays QRTR=m / PD_MAPPER=m (Kconfig forces PD_MAPPER ≤ QRTR anyway); it is orthogonal to the mic.
  • The CHAN_CTRL-remove commit (bdf098937) is exonerated for "dead at boot" — its code path is open-inert (only adds disable_stream, verified by diff) and it won the race on boot -3.
  • The un-pin question is STILL unanswered. It needs a race-winning boot: pinned baseline nonzero, pactl unload-module the loopback, then same-map reopen; nonzero reopen = pin can be deleted.
  • The early holder is unidentified; MBHC headset-detect is the prime suspect (boot-time codec user, raises MICB2 for impedance sensing). MICB2 0x50-vs-0x10 tracks outcome 8/8 but is NOT yet proven causal — it may just fingerprint the hold. Verify before building on it: read Android's ANA_MICB2 during a working slot-B recording, and/or set the 0x40 bit via debugfs during a fresh lost-race boot's capture.
  • Kernel fix direction (pick after causality check): wire the DMIC supply (micbias) into the capture DAPM path, or hold sysclk from probe until first capture so the boot window cannot close early.

Afternoon findings — zombie state + wedge reproduction (2026-07-19)

  • Cold-boot experiment (radios all on, recorded): first arecord after boot opened ONLY the FE — zero kernel-side codec/BE activity, zeros. Second and third captures (any route) worked. The morning "DEC5 unlocks MUX7" story is partly this: two distinct zero-classes exist — (a) BE-never-opened, (b) BE-open-but-silent.
  • Stopping PulseAudio (releasing the card → last sysclk user drops → wcd934x_disable_ana_bias_and_syclk powers the codec down) put capture into a persistent all-zero state on EVERY route, and restarting PA did not recover it. Mixer state is bit-identical PA-up vs PA-down (diff empty), so it is a power-lifecycle effect, not mixer state.
  • In the zombie state, live volatile reads DURING capture show the analog domain healthy: ANA_BIAS=0x80 BUCK=0x07 MICB4=0x90. The bias/buck theory is dead. Codec-internal clock gates (0xd41/0xd42) were not yet live-readable — instrumented build 323b29715 makes them volatile and logs enable/disable/sido lifecycle + per-capture clock-gate reads.
  • Manually zeroing CLK_SYS_MCLK_PRG (0x711) via debugfs wedged the SLIM bus into the historical -110 NACK loop (Failed to read IRQ status: -110, Error Interrupt 0x82000000, 1 Hz). One historical "SLIM wedge" class is therefore reproducible as pure codec-clock-state loss — consistent with the 2026-07-19 warning that not every wedge was a bus/driver fault. Phone needs a cold boot after this (self-inflicted, cause exactly known).
  • Upstream comparison: enable_ana_bias_and_sysclk/disable and the sido-cache skip are upstream-inherited structure, not our divergence; our only body change is the Android-parity buck sequence (captures work with it). No structural smoking gun found by code audit — hence instrumentation.

SLIM NACK loop confirmed boot-invariant on a real cold boot — 2026-07-15

  • Fixed three misidentified raw-hex register writes in wcd934x_apply_blueline_android_tx7_capture_setup() (kernel 5199244ee0ee): 0x0b07-0x0b3f were RX playback compander CTL6 registers (not TX per-path enables), 0x0c96 is WCD934X_SWR_AHB_BRIDGE_ACCESS_STATUS (a status register, not a TX enable — writing it was the suspected cause of a SLIM wedge seen on the prior build 9610ef9cf45a), and 0x0d1e/0x0d25 are ADC_MUX0_CFG1/ADC_MUX3_CFG1 (TX0/TX3, not TX7/TX8 as the old comment claimed). Verified against downstream techpack/include/asoc/wcd934x_registers.h. This fix did NOT resolve the SLIM wedge — see below.
  • The recurring error is a genuine NACK, not a bus hang: qcom,slim-ngd-ctrl: Error Interrupt received 0x82000000 decodes to NGD_INT_TX_MSG_SENT (BIT 31) | NGD_INT_TX_NACKED_2 (BIT 25) — the message transmitted and the target actively refused it. The target is LA:0xcf (the WCD934x codec's assigned logical address, confirmed not SLIM_LA_MGR/0xff), message code 0x60 = SLIM_MSG_MC_REQUEST_VALUE. The read is regmap-irq's generic regmap_irq_thread() polling the codec's IRQ status registers (drivers/base/regmap/regmap-irq.c) — the codec's IRQ line is IRQF_TRIGGER_HIGH (level), so a failed status read never clears it and it refires every ~1s, forever, from ~1-2min post-boot onward.
  • Tested with a user-operated true power-off (shutdown -h now, not fastboot reboot) followed by manual power-on, specifically to rule out the in-place-ADSP-restart degradation b0b813f warned about. Result: the NACK loop reproduces identically on this real cold boot (confirmed climbing at 110s uptime, same MC:0x60,LA:0xcf signature). This is not a warm-reboot or ADSP-restart artifact — it is boot-invariant, matching the entry below from earlier the same day.
  • blueline-hexagonrpcd-adsp.service (b0b813f, still uncommitted to gitea as of this entry) attaches cleanly on this same cold boot — no fluence/ECNS/AVS module errors, only the unrelated lowi_lp.conf miss. So the ADSP audio-PD HexagonFS gap is fixed and not the current blocker; capture still fails at the ALSA hw_params layer (arecord "Unable to install hw params") before the ADSP module question is even reachable, because the SLIM NACK loop blocks it upstream.
  • Current standing blocker, plainly: the SLIM NACK loop on LA:0xcf. Not yet root-caused. Two real, separate bugs were found and fixed tonight (the register-write bug above, and the ADSP HexagonFS gap in b0b813f); this is a third, still-open one.

Cold-boot SLIM-NGD correction — 2026-07-14

  • A user-operated full power-off while unplugged, followed by power-on, still logs qcom,slim-ngd-ctrl ... QMI wait timeout at 12:29:52, immediately after ADSP boot/WCD934x probe, followed by repeated SLIM SAT: Rcvd master capability. This happened before any ALSA/Pulse microphone client opened.
  • Therefore the timeout/capability loop is not merely a warm-reboot or long-uptime artifact. ADSP, CDSP, SLPI, and MSS all reach running, so the fault is specifically in the ADSP↔NGD SLIM initialization exchange and is now the primary boot-invariant microphone lead. Do not restart ADSP or NGD while investigating; preserve this state for tracing.
  • The adjacent WCD MBHC threshold/impedance messages concern headset/accessory detection (including the available USB-to-mic adapter), not the target internal DMIC4 → DEC7 → TX7 capture path.

Q6 VoiceMMode1 call path — staged 2026-07-14

  • This is deliberately separate from the still-silent raw MultiMedia1/2 capture path. Android proves the board microphone works; normal PCM remains an ADM/calibration problem, while modem calls use the hostless Q6Voice path.
  • The missing board wiring was real: sdm845.dtsi left Q6 MVM/CVS/CVP disabled and blueline had no VoiceMMode1 DAI/card link. Kernel commit 551d3bb1b enables those services and instantiates the link on the tested Type-C baseline. Its matching module tree is installed on the phone at 7.1.1-sdm845-g551d3bb1bd35; its boot image is staged locally.
  • The preserved prior device package, found at archive/pmos-20260709/pmaports/device/testing/device-google-blueline/, is the authoritative board data: VoiceMMode1 enumerates as hw:0,5, not the raw MultiMedia devices. Its exact VoiceCall.conf routing (SLIMBUS_0_RX Voice Mixer VoiceMMode1, VoiceMMode1 Capture Mixer SLIMBUS_0_TX, DMIC4→DEC7→TX7) is installed in the Arch UCM overlay.
  • q6voiced-blueline was built and installed natively, with that exact PCM-5 configuration. Its system unit is deliberately disabled until the new kernel cold-boot confirms 00-05: VoiceMMode1; enabling it on the old kernel would create meaningless restart noise. First test must be a clean cold boot and an actual ModemManager call before any HiFi/raw-capture experiment, because the historical second call failure was SLIMBUS RX already claimed by earlier HiFi activity.

Live state — 2026-07-12 evening (mic)

Live Android Recorder ground truth — 2026-07-14: On the rooted Lineage slot-B reference system (4.9.337-gb929452c5c49), an ordinary org.lineageos.recorder recording was observed live, not inferred from XML. It opens MultiMedia1 / PCM device 0 (not MultiMedia2): its userspace input is mono S16_LE at 8 kHz, while the active backend is SLIMBUS_0_TX, mono S16_LE at 48 kHz. The downstream HAL enables DMIC4 -> DEC7 -> TX7; SLIM_0_TX Channels=One; and its active capture app-type control is [69938, 4, 48000, 3]: downstream's documented field order makes that app_type=69938 (the Qualcomm default TX-path app type), acdb_dev_id=4, sample_rate=48000, backend_id=3. The capture track was active and non-silent in AudioFlinger. Crucially, Android's dedicated WCD SLIM-interface debug dump during that recording read 0x032=0x80, 0x057=0x05, 0x097=0x21, and 0x11c=0x80. (tavil-slim-pgd regmap is codec register space, not this interface-device space.) Every earlier Arch microphone negative test targeted hw:0,1 / MultiMedia2 at 48 kHz. The exact MultiMedia1 hw:0,0 8-kHz FE / 48-kHz SLIMBUS backend retest then ran on Arch with TX7/DMIC4 and the active speaker RX route preserved: its 8-second raw payload was 128,000 bytes / zero non-zero samples. (An earlier apparent 19-sample result was immediately identified as arecord's 44-byte WAV header and corrected with -t raw.) This closes the FE/rate mismatch as a microphone fix. The Android HAL's ADM identity and ACDB path is now the leading differential: it programs this FE's app type, topology, and device calibration, whereas mainline q6routing passes zeros.

DEF_ACT_CHAN client-field test — 2026-07-13: The Android downstream NGD driver encodes the first DEF_ACT_CHAN payload byte as (data_fmt << 5) | (laddr & 0x1f); mainline had sent the full WCD9340 logical address (0xcf) and consequently put its high bits into the data-format field. The one-line correction was built on the current Type-C/G56 test base as 3ceab71eded0, its matching module tree was installed before flashing, and the resulting 7.1.1-sdm845-g3ceab71eded0 boot was verified on glass. A controlled single-direction DMIC4 → DEC7 → TX7 → MultiMedia2 48 kHz mono capture (TX6 disabled) produced 384000 bytes / 192000 samples, with 0 non-zero bytes and signed-16 range 0..0. ALSA state restored (only the known read-only HPH controls complained); no new SLIM/Q6/NGD timeout or audio error appeared. This fix is a valid protocol correction and must remain, but it is not sufficient to make blueline microphone capture work.

Android DMIC-pad + analog split test — 2026-07-13: Android's WCD9340 initial register table writes DMIC3_CLK_PINCFG/DMIC3_DATA_PINCFG (0x8019/0x801a) to 0x0a; mainline defined but never programmed those registers. That exact pair was added to WCD934x component initialization, built as 959f0d5e6, and booted as 7.1.1-sdm845-g959f0d5e65fc. The normal Android handset route DMIC4 -> DEC7 -> TX7 -> SLIMBUS_0_TX -> MultiMedia2 remained exactly zero (384000 bytes / 192000 signed-16 samples / zero non-zero bytes), without a capture-time SLIM, Q6, or ADSP error. A separate reversible replay of Android's analog fallback AMIC1 -> ADC1 -> DEC6 -> TX6 also produced the same exact zero stream. Thus neither the DMIC3 pad configuration nor a bottom-DMIC/flex-only fault can explain the failure: the common remaining boundary is codec TX channel activation and/or the ADM/ADSP capture path. The only ALSA restore errors were the known read-only HPH impedance/type controls.

ADM/calibration lead — 2026-07-13 (refined 2026-07-14): Mainline q6routing_stream_open() unconditionally passes NULL_COPP_TOPOLOGY and app/acdb values of zero to q6adm_open(). Pixel Android's msm-pcm-routing-v2.c starts with that fallback but looks up topology, app type, and ACDB device ID by FE/BE/session from audio-calibration tables before opening ADM. Do not replace the topology with a guessed constant: the live Android MultiMedia1 + SLIMBUS_0_TX recorder has now identified app_type=69938, acdb_dev_id=4, sample_rate=48000 (and backend ID 3). The exact ADM topology was decoded from the extracted board-matched ACDB on 2026-07-14; see the next entry.

ADM identity-only test — 2026-07-14 (negative): Kernel 7.1.1-sdm845-g3b9cf346af3a passed Android's observed app_type=69938 and acdb_dev_id=4 only for live SLIMBUS_0_TX capture, leaving the topology NULL rather than inventing one. The matching modules were installed before the pmOS-chain boot_a image was flashed; the running kernel and modules matched. A controlled MultiMedia1 / hw:0,0, mono S16_LE 8 kHz raw capture with the Android-equivalent DMIC4 -> DEC7 -> TX7 route yielded 128,000 bytes with zero non-zero samples. ALSA state was restored (only the known read-only HPH controls refused writes). This eliminates the two ADM identity numbers alone as a microphone fix. The remaining Android differential is the calibration-selected ADM topology and/or downstream codec-side programming.

Board ACDB topology decoded — 2026-07-14: The local Lineage vendor blob blobs/los-vendor/etc/acdbdata/OEM/sdm845-tavil-b1-snd-card/Handset_cal.acdb is not merely a future reference: acdb-ldp decodes its HANDSET_MIC device 4 configuration map as 69938 -> 0x00010315 (at 48 kHz). Downstream's apr_audio-v2.h names 0x00010315 as ADM_CMD_COPP_OPENOPOLOGY_ID_MIC_MONO_AUDIO_COPP; it is the specific Android mono-microphone COPP, rather than the mainline null passthrough 0x00010312 or a guessed default. This explains why the identity-only test was necessarily incomplete. The present 7.1.1 tree also contains q6core's optional ACDB topology-registration code, but blueline's q6core DT node has no qcom,acdb-name and no Global_cal.acdb is installed as firmware, so that path is currently dormant.

ADM topology + AudProc test — 2026-07-14 (negative): The current kernel opens SLIMBUS_0_TX capture with topology 0x10315, app type 69938, and ACDB device 4, then parses and sends the board ACDB's complete 524-byte AudProc table. Live dmesg confirms SLIMBUS_0_TX applied ACDB AudProc calibration: device=4 rate=48000 app=69938 (524 bytes), but capture remains exactly zero. This eliminates the selected COPP topology plus AudProc table as a complete fix; Android also sends the separate AudVol table.

AFE calibration delivery audit — 2026-07-14: All three local Pixel Handset ACDB variants agree. Device 4 has no AFECLUT0 common-calibration row, and its AFE_CMN_TOPID_V2 device property is 0x000112fb, Qualcomm's AFE_TX_TOPOLOGY_ID_NONE. The only device-4 AFE datum is a 48-kHz AV-sync delay of 578, which cannot account for an all-zero stream. Qualcomm's SDM670 ACDB loader passes the same device ID 4 to AFE (there is no hidden remap to the unrelated AFE-common IDs 100/128/130/149), tries topology, common table, then hardware delay. Therefore adding an AFE common/topology sender here would deliver no microphone payload. Do not revisit AFE delivery without new Android evidence.

ADM AudVol + explicit SLIM-error test — 2026-07-14 (negative): Device 4, app type 69938, volume index 0 decodes to a 40-byte table: soft-volume stepping parameters and mono gain 0x2000 (unity). Kernel 7.1.1-sdm845-gfb8f067e12f2 parses AVOLLUT0/AVOLCDFT/AVOLCDOT with the same validated generic table builder and sends AudProc then AudVol, matching Android's order. The same build propagates previously ignored failures from SLIM port connection and WCD934x stream prepare/enable. A controlled 5-second DMIC4 -> DEC7 -> TX7 -> MultiMedia2 mono 48-kHz capture completed normally and produced 480,000 bytes / 240,000 samples, all exactly zero. Live dmesg confirms SLIMBUS_0_TX applied ACDB AudProc/AudVol calibration: device=4 rate=48000 app=69938 (524/40 bytes) and contains no capture-time SLIM connect/prepare/enable, WCD934x, Q6, permission, or missing-file error. Therefore neither the missing AudVol table nor a silently discarded SLIM setup failure explains the zero stream.

Do not stream the ADSP-owned 217:250:0:0 debugfs regmap registers file during capture: a filtered read blocked after the capture had completed and had to be killed. This is consistent with the earlier observation that IFD 0x097 is controlled by the ADSP SLIM master; it is not a safe codec-style bulk register snapshot path and yielded no new state evidence.

True cold-boot re-validation — 2026-07-13: Kernel 7.1.1-sdm845-g44d43e6ef336 includes the WCD934x .prepare/.shutdown stream-lifecycle experiment. On a physical shutdown/power-on with native WCN boot healthy, the controlled DMIC4 → DEC7 → TX7 → MultiMedia2 raw capture was again exactly zero (192000 bytes, 0 non-zero bytes). The temporary mic-ngd trace proves the entire kernel-visible sequence ran successfully: q6afe_port_start ret=0, slim_stream_enable entered, qcom_slim_ngd_enable_stream entered and returned 0. There was no SLIM/Q6 timeout. ALSA was restored immediately; the three HPH-impedance restore errors are documented read-only controls. This falsifies the prepare-order patch as a microphone fix and makes silent ADSP-side channel activation (not userspace, Q6 return handling, or NGD error propagation) the current boundary.

Stable-SLPI follow-up capture — 2026-07-13: After the repaired sensor registry cleared CHRE's five-minute watchdog and the SSC desktop sensor clients were exercised successfully, the same safe single-direction TX7/DMIC4 route was captured as 48 kHz mono raw audio for five seconds. It produced 480,000 bytes / 240,000 samples, all exactly zero. ALSA state was restored by trap; SLPI, hexagonrpcd, and iio-sensor-proxy stayed active, with no MC:0x2c, -110, or CHRE fatal. The sensor repair therefore improves platform stability but does not open the microphone path.

NGD/update audit — 2026-07-13: The running 7.1.1 source already contains the complete upstream May-2026 NGD probe/SSR race-fix series that landed for 7.2-rc: deferred IRQ enable, controller-owned workqueue initialization, safe SSR/PDR cleanup, and the tx_lock ABBA deadlock fix. It therefore must not be re-applied as a speculative microphone patch. This boot still logged one QMI wait timeout; that is the driver's one-second wait for the remote QMI service, not an EPERM or file-access failure. It remains a boot-stability lead, but the subsequent raw capture's core CONNECT_SOURCE transaction returned 0 and the audio data was still all zero.

  • The on-device session's mic doc (collected to docs/tasks/mic-slim-tx-20260712.md, source phone ~/projects/unfinished/pixel3-microphone/) found every capture start at ~23h uptime logging 3x TX timed out:MC:0x2c / failed:-110 — the SLIMbus CONNECT_SRC user message to the ADSP master never answered — and called that the localized root cause.
  • Re-test after a WARM reboot (same day, laptop-driven): the 0x2c timeout did not reproduce — but the boot was NOT clean. That boot was produced by sudo reboot (warm), and its dmesg shows QMI wait timeout at 8.1s plus the repeating "Rcvd master capability" retry loop — the documented degraded slim-ngd state that warm reboots carry over (same TZ-poison family as the WiFi MSA issue; only an UNPLUGGED cold power-off clears it). Within that caveat: a hw:0,1 capture with the doc's exact TX7/DMIC4 arming logged 37 slim transfers — all MC 0x60/0x68 value messages, zero errors, zero timeouts — and was still bit-for-bit zero, with IFD 0097/0320/0330/0970 all 00 mid-capture. Both datapoints (0x2c timeout at 23h uptime; silent no-activation here) need re-validation on a true cold boot before either is trusted.
  • No root-cause conclusion is valid yet. The -110 may be a degraded-NGD artifact of long uptime / remoteproc restarts, while the silent all-zero capture may be a separate normal-state failure; the only attempted contrast was tainted by a warm reboot. Kernel-visible Q6 and NGD success plus Android's IFD 0x097=21 versus mainline 00 remain observations, not a proven boot-invariant activation failure.
  • Note: 0320/0330 read 00 here vs c0/01 in the 07-11 canonical 2ch trace — arming differed (single TX7 vs TX7+TX8); observation only.
  • What the phone doc contributes regardless: OnePlus 6 (enchilada) mic works on pmOS 6.x kernels with this same driver stack (6.x->7.x regression candidate in slimbus/ngd/wcd934x, or blueline-specific ADSP behavior); pmOS's blueline UCM ships with the whole mic section commented out (nobody has solved this on mainline); operational hazards — never restart the adsp remoteproc while debugging slim (NGD stays degraded until reboot, reproducible), never leave MultiMedia2 with both SLIMBUS_0_RX and SLIMBUS_0_TX routes enabled (q6adm/q6asm wedge); QUAT_TDM_TX_0 in DT would give a WCD-free CS35L36 VMON/IMON capture reference.
  • Refined next steps: trace the qcom_slim_ngd_enable_stream user-message exchange on a fresh boot (is CONNECT_SRC sent and ACKed, or skipped?); diff drivers/slimbus/{qcom-ngd-ctrl.c,stream.c,messaging.c} + wcd934x.c against pmOS's 6.x sdm845 tree; if an enchilada is handy, boot this 7.1.1 kernel on it to split regression vs blueline-specific; ask #sdm845-mainline about silent no-activation with working value messages.

Upstream/device-branch archaeology — 2026-07-12 (no phone test)

  • The current sdm845/7.1-dev upstream line is active (its tip is two months newer than our 7.1.1 base) but its audio delta is the CS35L36 speaker DTS support already mined for the working speaker route; it contains no WCD934x microphone/SLIM-TX fix. Do not hold a cold-boot test for an imagined pending 7.1 microphone update.
  • Real candidate to test after a true cold baseline: historical joel/op6-audio-fixes-wip commit 1d5776ca changes WCD934x SLIM lifecycle from .trigger (enable on PCM START) to .prepare plus .shutdown (enable before START, disable on close). Our current wcd934x.c still has the old .trigger implementation. This is not upstream and not proof of a fix, but it moves the slim_stream_enable() / CONNECT_SRC exchange out of PCM START into the backend prepare phase. Static inspection confirms the Q6 SLIM DAI starts its AFE port in .prepare, while DPCM prepares BEs before the FE; that makes this a concrete stream-ordering hypothesis rather than a generic codec tweak. First make an instrumentation-only patch or trace plan that establishes the exact per-DAI callback order; only then consider a rollback-approved kernel test.
  • Related branch commits were inspected and are not default candidates for blueline DMIC capture: 75ad5bb0 adds separate AIF2/AIF3 SLIM DAI links for OnePlus use cases (our AIF1 SLIMBUS_0_TX link already matches its AIF1 portion); 4a05bee2 and beryllium 07078b5c repair MBHC/MIC_BIAS2 headset handling, not the DMIC4/MIC_BIAS4 path. Keep them as topology references, not a grab-bag patch series.
  • Candidate build/deploy and cold-boot result (2026-07-13): the lifecycle change was ported exactly onto the running Type-C-bisect base (56b0a053b) as 44d43e6ef / Gitea branch mic-slim-prepare-order-g56, built on ArchDev, module tree installed, and boot image flashed to slot A. Casey then performed an unplugged shutdown and power-on; on 7.1.1-sdm845-g44d43e6ef336, the controlled DMIC4 → DEC7 → TX7 → MultiMedia2 hw:0,1 capture still contained only zero samples. No MC:0x2c / -110 occurred during that capture. Thus the prepare-order hypothesis is a valid deployed negative result, not an untested lead; retain the kernel as the current microphone-test baseline.

Live state — 2026-07-12 (speakers)

  • BOTH speakers now fire: the earlier "speaker playback works" state was in fact top-speaker-only — both CS35L36 amps had Channel Mux = RX1, so the bottom loudspeaker never received a channel. Fixed with the pmOS backup image's routing (Left=RX1 top, Right=RX2 bottom, plus LDM select / PDM ramps / zero-cross / per-amp ASPTX sources) in the overlay UCM (commit 2f39c06), installed on-device and persisted via alsactl store. Verified by ear: distinct left/right/stereo tones.
  • Phone-side handoff doc ~/projects/unfinished/pixel3-speaker-routing/ (README + baseline state + test tones) documents the diagnosis trail; its "next step" (pmOS MRs online) was superseded by reading the local pmos-20260703 rootfs image directly (debugfs, no mount needed).
  • Regression and guardrail — 2026-07-13: the live TDM split was still correct (Left Channel Mux=RX1, Right Channel Mux=RX2), but both amp and boost switches were off. Re-enabled all four and stored the corrected state on the phone. docs/tasks/asound-baseline-20260712.state is a pre-fix diagnostic snapshot (Right Channel Mux=RX1); it must never be passed to alsactl restore, because doing so regresses the bottom speaker. Use the deployed UCM and current /var/lib/alsa/asound.state as the restore source.

Prior live state — 2026-07-11

  • Speaker playback works end-to-end through PipeWire, UCM, QUAT TDM and the CS35L36 amplifiers (top speaker only — see above).
  • arecord -D hw:0,1 -f S16_LE -r 48000 -c 2 (the canonical MultiMedia2 front-end, whose SLIMBUS_0_TX link is enabled) opens and runs, but capture is bit-for-bit zero. This is a hardware/ASoC transport investigation, not a PipeWire source-selection failure; no PipeWire source can exist until ALSA produces samples.
  • This work did not start iio-sensor-proxy. There is no evidence that the sensor-client HACK or SLPI wake-lockup mechanism gates this audio path.

Verified zero-capture path — historical 2026-07-11

The controlled route is Android's documented handset/endfire pair:

  • DMIC4 -> DEC7 -> SLIM TX7 and DMIC3 -> DEC8 -> SLIM TX8.
  • Q6 function tracing during arecord observed, in order: q6slim_set_channel_map, wcd934x_hw_params, q6afe_slim_port_prepare, q6afe_port_start, and wcd934x_trigger.
  • A temporary argument probe observed the two expected shared-channel IDs delivered to Q6 when TX6 is off: tx_num=2, 0x87, 0x88 (135, 136). The resulting 478,078 signed-16 samples on hw:0,1 were all zero.
  • The WCD9340 interface device (217:250:0:0, WCD9340-IFC-DEV) receives the expected driver writes for both ports: TX7 map/config 0x11c=80, 0x11d=01, 0x57=05; TX8 0x120=80, 0x121=01, 0x58=05. Its live interrupt enables are 0x32=c0 and 0x33=01, so their absence is not the explanation for silence.
  • On that same canonical front-end, q6afe_port_start() returns 0, then slim_stream_enable() invokes qcom_slim_ngd_enable_stream(), which returns 0 after the controller's DEF_ACT_CHAN and RECONFIG_NOW operations. Kernel-visible Q6 and NGD schedule activation therefore succeeded.
  • Corrected DAPM observation: a clean live hw:0,1 capture powers codec-side AIF1 CAP and AIF1_CAP Mixer. Its prepare phase calls wcd934x_codec_enable_slim and two wcd934x_codec_enable_dec events. The earlier report that these callbacks never fired was false: the trace missed the prepare-phase calls. Do not use that earlier result as evidence.
  • The persistent mixer state leaves CDC_IF TX7 MUX and CDC_IF TX8 MUX at ZERO, despite the corresponding AIF mixer switches and DMIC muxes being selected. Consequently the TX7/TX8 branch is off during an ordinary capture and no wcd934x_codec_enable_dmic event occurs. This is a real UCM route omission, not an ADSP conclusion.
  • A controlled, automatically reverted test set those muxes to DEC7 and DEC8 and disabled stray TX6. It powered the entire intended branch and invoked wcd934x_codec_enable_dmic twice (with matching shutdown calls). The independent two-second PCM capture was nevertheless 384,000 zero bytes. Thus the codec DAPM path and physical DMIC endpoints are viable once the muxes are selected, but that route omission is not the final silence cause.
  • This is not a missing FE-to-BE connection: runtime ASoC state reports MultiMedia2 Capture: start with SLIM Capture 1: start as its active backend, and the codec's AIF/decimator event callbacks run. With the CDC muxes selected, the DMIC callbacks run too; the unresolved point at that date was after the fully powered codec capture path.
  • Reducing the route to the downstream-style single TX7/DMIC4 channel did not change the result: a four-second hw:0,1 mono capture contained 192,000 zero samples. Channel count/configuration is therefore not the activation trigger.

What the 2026-07-11 evidence did and did not say

Android's captured recording state has IFD 0x097=21; mainline reads 00. During a five-second live TX7/TX8 capture, writing 0097 21 through the debugfs regmap returned success but read back 0097: 00, and all 478,078 samples remained zero. This confirms that a userspace register replay cannot enable that state on this path. It does not establish what 0x097 means or prove that it is the root cause. Do not turn it into an unconditional codec write or call it an “ADSP-master fix” without an implementation-level source or Android-side transition capture.

TX6 is enabled by the current mixer default alongside TX7/TX8. It makes the unconstrained map three channels (0x86, 0x87, 0x88), while disabling it produces the correct TX7/TX8 pair above. That is a real UCM/machine-routing cleanup item, but it did not restore capture and is not the root cause.

Proposed next work on 2026-07-11 — superseded

  1. Capture the working Android interface-device transition, or find the downstream owner/meaning of IFD 0x097; compare the actual write sequence, not just idle/recording register snapshots.
  2. Add the TX7/TX8 CDC_IF mux selections to the eventual microphone UCM enable sequence (and keep TX6 off for this two-channel route). Do not expose a broken PipeWire source before its capture is non-zero.
  3. Repeat the WCD9340 interface/ADSP comparison only with that fully powered codec path. The still-zero full-path test strengthens, but does not prove, the relevance of the non-sticking Android IFD 0x097=21 delta. Capture an Android transition or find the downstream owner before making a firmware or register claim. Do not flash a kernel test without an explicit rollback-approved deployment plan.
  4. Only after that, compare the remaining post-activation state with Android at the firmware boundary. Q6 and NGD success alone do not show that the codec/ADSP hardware emits decimator samples.
  5. Fix the stray TX6 default separately in the persistent UCM/machine route, then verify it with the channel-map probe. Do not claim mic support until a live capture contains non-zero samples.

Instrumentation

/usr/local/sbin/blueline-lockup-trace is a root helper deployed from rootfs-overlay/. mic-start traces the Q6/WCD stream functions and the three WCD codec DAPM event callbacks; mic-map-start, mic-ifc-start, and mic-ngd-start collect Q6 channel-map, WCD interface-register-write, and Q6/NGD return traces respectively. Trace sessions are temporary and must always be paired with the matching *-stop command; the current phone was checked clean afterwards (tracing_on=0, tracer nop, no kprobes).

2026-07-18 full Android power-state reconciliation

A clean, single-channel PulseAudio/UCM capture on kernel 4c3a94153 proved that the intended TX7/DMIC4 path is selected and fully ACKed, but every one of 55,680 S16 samples is exactly zero. A simultaneous live codec dump exposed two remaining Android/mainline power-state differences which earlier tests had not actually eliminated:

  • ANA_MICB4 is 0x50 on mainline versus 0x90 during Android recording. The DT opt-in qcom,blueline-dmic-micbias-pullup is on the codec@1,0 node, but wcd934x_codec_parse_data() queried dev->parent->of_node (the SLIM controller). Therefore micbias_pullup was always false and the earlier claim that pull-up mode had been tested is invalid. The code now queries dev->of_node, selecting Qualcomm's downstream MICB_PULLUP_ENABLE path (0x90).
  • ANA_BUCK_CTL is 0x00 on mainline versus 0x07 on Android. Qualcomm's resource manager sets RCO followed by buck PRE_EN1, PRE_EN2, and HIGH_ACCURACY_EN, with 100 us between stages. The mainline driver had dropped this sequence. It is restored as ordered masked writes, including the reverse transition; this is not the harmful unsequenced Android end-state replay removed earlier.

The same correction (a75371b49) also matches Android's board state by programming only DMIC interface 3 pads (the real DMIC3/4 inputs) and avoids leaving TX7 at the transient 150 Hz HPF setting (CFG0=0xd0) indefinitely; Android's steady recording state is CFG0=0x90. One boot-time log reports whether pull-up mode was parsed, and the capture log reports live BUCK and all MICB registers so the next capture validates the complete state in one pass.

2026-07-18 eFuse initialization regression and cache fix

On the clean 3b85e688f boot, the complete downstream AFE codec-control map registered successfully and TX7 again reached port_start, CONNECT_SOURCE, DEF_ACT_CHAN, and RECONFIG_NOW with positive ACKs. PulseAudio captured 760,318 raw bytes (380,159 S16 samples), all exactly zero. The journal exposed a real codec boot failure: wcd934x_enable_efuse_sensing read EFUSE_STATUS=0 and reported that sensing never completed.

History showed that 1c095f7a7 had added the downstream-required internal-SIDO start before eFuse sensing, but c1b6371c6 removed the call while dropping unrelated experiments. The exact downstream SIDO/buck transition helper was later restored by a75371b49 without reconnecting it to eFuse init. The audit also found that downstream treats EFUSE_CTL, all eFuse value outputs, and status as volatile, while mainline cached them in REGCACHE_MAPLE.

Commit 82355eac0 batches the complete correction: internal SIDO before the sense enable, the required 5 ms delay before RCO/BG, downstream-parity volatile register handling for the eFuse/clock/buck sequence, live completion polling, register error propagation, and explicit status/out14/out15 logging. Do not interpret another zero capture until a cold boot proves the codec prints eFuse sense complete; otherwise the physical codec trim/version state is still uninitialized even though SLIM management commands ACK normally.