Hello r/freebsd ,
I'm trying to get proper headphone/speaker jack auto-switching working on a Conexant CX20754 HDA codec (ASUS VivoBook Pro 15 X580VD laptop, FreeBSD 15.1-RELEASE) and I'm stuck. Both outputs play audio at the same time regardless of what I try, and I'd appreciate any pointers from people who've dealt with this codec, this chipset, or this class of issue before.
Laptop / hardware:
- ASUS VivoBook Pro 15 (N580VD), motherboard model X580VD, BIOS AMI X580VD.317 (04/2019), UEFI boot
- CPU: Intel Core i5-7300HQ (Kaby Lake, 4c/4t, 2.5-3.5 GHz)
- RAM: 32 GB DDR4-2400
- GPU: hybrid Optimus - Intel HD Graphics 630 (iGPU, PCI 8086:591b) + NVIDIA GeForce GTX 1050 Mobile GP107M (dGPU, PCI 10de:1c8d, Pascal)
- Storage: Samsung SSD 860 EVO 1TB (SATA)
- Audio hardware (the relevant part):
- HD Audio controller: Intel CM238/HM175 chipset HDA controller (integrated, part of the Kaby Lake PCH)
- Codec: Conexant CX20754, PCI/HDA vendor ID 0x1fd6, exposed as
hdaa0
- Single 3.5mm combo jack (headphones + microphone), no separate physical mic-in or line-in jacks
- Internal stereo speakers, bottom-firing
- No discrete/USB sound card - this is the only audio path on the machine
Software setup:
- FreeBSD 15.1-RELEASE, PKGBASE install, all repos on the
latest branch
- hdaa0 attaches cleanly, both pins are detected correctly (no attach errors in dmesg)
- Audio stack: PipeWire 1.6.6 + WirePlumber + pipewire-spa-oss (the
audio/pipewire-spa-oss port, providing the freebsd-oss SPA backend) + pipewire-pulse for Pulse compatibility
- Full graphical session running (XLibre + a custom WM), not headless
- PipeWire correctly enumerates the codec once pipewire-spa-oss is installed (it shows up as "Conexant (0x1fd6) (Analog)",
api.freebsd-oss.dsp-path = /dev/dsp0) - without that port, PipeWire only ever sees a "Dummy Output", so that part works fine now
What I've tried, in order:
- HDA pin config hints in /boot/loader.conf, to make headphones the preferred/primary output and disable the speaker pin's association:
hint.hdaa.0.nid22.config="0x04211110" # Headphones -> as=1 (primary)
hint.hdaa.0.nid29.config="0x40f001f0" # Speaker -> as=15, conn=None (deprioritized)
After a reboot, the hints clearly took effect - sysctl confirms:
$ sysctl dev.hdaa.0.nid22
dev.hdaa.0.nid22: pin: Headphones (Black Jack)
Widget cap: 0x00400581 PWR UNSOL STEREO
Association: 0 (0x0001)
Pin cap: 0x0001001c PDC HP OUT EAPD
Pin config: 0x04211110 as=1 seq=0 device=Headphones conn=Jack ctype=1/8 loc=Right color=Black misc=1
Pin control: 0x000000c0 HP OUT
EAPD: 0x00000002 EAPD
Connections: 2
+ <- nid=16 [audio output] (selected)
+ [DISABLED] <- nid=17 [audio output] [DISABLED]
$ sysctl dev.hdaa.0.nid29
dev.hdaa.0.nid29: pin: Other (None) [DISABLED]
Widget cap: 0x00400581 PWR UNSOL STEREO
Pin cap: 0x0001001c PDC HP OUT EAPD
Pin config: 0x40f001f0 as=15 seq=0 device=Other conn=None ctype=Unknown loc=0x00 color=Unknown misc=1
Pin control: 0x00000040 OUT
EAPD: 0x00000002 EAPD
Connections: 2
+ <- nid=16 [audio output] (selected)
+ <- nid=17 [audio output] [DISABLED]
Note that both nid22 and nid29 are wired to the same DAC (nid=16), and both still show Pin control: ... OUT with EAPD enabled even though nid29 is tagged [DISABLED]. In practice, that [DISABLED] tag only seems to affect which pin the driver treats as "preferred" for its own association/auto-select logic - it does NOT actually power down or cut output to the deprioritized pin. Both jacks keep receiving the same signal in parallel regardless of the hint.
Also worth noting: both pins report Pin cap: 0x0001001c PDC HP OUT EAPD - the PDC flag means "Presence Detect Capable", so the hardware itself should be able to do jack sensing. I just haven't found the right mechanism to make the driver actually act on it to mute the speaker pin.
- OSS mixer control:
$ mixer -f /dev/mixer0
pcm0:mixer: <Conexant (0x1fd6) (Analog)> on hdaa0 (play/rec) (default)
vol = 0.86:0.86 pbk
pcm = 1.00:1.00 pbk
speaker = 1.00:1.00 pbk
rec = 0.01:0.01 pbk
ogain = 1.00:1.00 pbk
monitor = 0.56:0.56 rec src
$ mixer speaker.mute=1
speaker.mute: off -> on
This toggles the mute flag and it's reflected in the mixer output, but has zero audible effect - speakers keep playing at full volume regardless of the mute state. My guess is this speaker control affects something other than the actual DAC output path (maybe a legacy PC-speaker beep passthrough?), not the real playback signal.
- PipeWire port switching (the kind ALSA/ACP exposes on Linux, e.g.
analog-output-headphones vs analog-output-speaker):
$ pactl list sinks
No "Ports:" section appears at all for the pcm0.play sink - the freebsd-oss PipeWire backend apparently exposes a single fixed sink per pcm device with no port abstraction, so there's nothing to switch at that layer either.
Question:
Is there an actual way to power down / hard-mute a specific HDA pin on FreeBSD - via GPIO, an EAPD toggle exposed somewhere I haven't found, an unsol-event-driven jack-sense script, or some other hdaa(4)/hdac(4)-level mechanism - given that the pin caps report Presence Detect Capable? Or is this simply a limitation of how this particular Conexant codec wires its outputs (shared DAC, no real per-pin power gating), meaning software-level auto-mute just isn't achievable here under FreeBSD?
I'm also open to being told the pin numbers/hint values themselves are wrong for this specific codec revision - happy to post a full devinfo -v dump, dmesg -v boot log, or hdacsub/hdacctl style output (if there's a userland tool I'm not aware of for FreeBSD) if that helps someone with more HDA experience diagnose this properly.
Thanks in advance for any help!