This commit is contained in:
Philip Peterson 2026-09-26 01:25:14 -08:00
parent 477793d581
commit c51070b7a8
25 changed files with 20640 additions and 3 deletions

View file

@ -1,7 +1,55 @@
FROM ghcr.io/ublue-os/aurora:stable ARG BASE_DIGEST=sha256:fbcef6075541eec7c2ca4a91c87c7012455cfba4e5e748d0cf2013d6aa3e66d3
# --- Builds the out-of-tree snd-hda-codec-cs8409 module (iMac19,2 mic fix). ---
# See docs/imac19-2-mic-fix.md and patches/snd-hda-codec-cs8409/ for the full
# story. Kept as its own stage so the build toolchain never lands in the
# final image, and pinned to the same BASE_DIGEST as the final stage so the
# kernel-devel headers installed here can never drift out of sync with the
# kernel that actually ships below.
FROM ghcr.io/ublue-os/aurora@${BASE_DIGEST} AS kmod-builder
RUN set -eux; \
NVR="$(rpm -q kernel-core --qf '%{VERSION}-%{RELEASE}.%{ARCH}')"; \
VERSION="$(rpm -q kernel-core --qf '%{VERSION}')"; \
RELEASE="$(rpm -q kernel-core --qf '%{RELEASE}')"; \
echo "Building snd-hda-codec-cs8409 against kernel ${NVR}"; \
dnf install -y gcc make elfutils-libelf-devel bc perl patch; \
dnf install -y "kernel-devel-${NVR}" \
|| dnf install -y "https://kojipkgs.fedoraproject.org/packages/kernel/${VERSION}/${RELEASE}/x86_64/kernel-devel-${NVR}.rpm"; \
echo "${NVR}" > /kernel-nvr.txt
COPY patches/snd-hda-codec-cs8409/vendor/ /usr/src/snd-hda-codec-cs8409/
COPY patches/snd-hda-codec-cs8409/0001-imac19-2-force-intmike-adc-nid-fallback.patch /tmp/
# --fuzz=0: fail loudly on a context mismatch (e.g. a future kernel bump that
# shifts this file) instead of silently mis-patching.
RUN set -eux; \
cd /usr/src/snd-hda-codec-cs8409; \
patch -p1 --fuzz=0 < /tmp/0001-imac19-2-force-intmike-adc-nid-fallback.patch; \
make KVER="$(cat /kernel-nvr.txt)"; \
mkdir -p /output; \
cp snd-hda-codec-cs8409.ko /output/
# --- Final image. ---
FROM ghcr.io/ublue-os/aurora@${BASE_DIGEST} AS final
COPY overlay/ / COPY overlay/ /
# alsa-tools provides hda-verb, used by cs8409-intmike-power-fixup.service.
RUN dnf install -y alsa-tools
COPY --from=kmod-builder /output/snd-hda-codec-cs8409.ko /tmp/snd-hda-codec-cs8409.ko
# Installs into .../extra/, which kmod's default search order gives
# precedence over the in-tree copy under .../kernel/... — this is the same
# mechanism Fedora's own kmod-* subpackages use to override an in-tree
# module of the same name/modalias. No blacklisting or renaming needed.
RUN set -eux; \
NVR="$(rpm -q kernel-core --qf '%{VERSION}-%{RELEASE}.%{ARCH}')"; \
mkdir -p "/usr/lib/modules/${NVR}/extra"; \
mv /tmp/snd-hda-codec-cs8409.ko "/usr/lib/modules/${NVR}/extra/snd-hda-codec-cs8409.ko"; \
depmod -a "${NVR}"
# System-level units are enabled explicitly. User units are picked up # System-level units are enabled explicitly. User units are picked up
# automatically via overlay/usr/lib/systemd/user-preset/. # automatically via overlay/usr/lib/systemd/user-preset/.
RUN systemctl enable rpm-ostree-cleanup.timer RUN systemctl enable rpm-ostree-cleanup.timer cs8409-intmike-power-fixup.service

View file

@ -5,7 +5,27 @@ Quine distro of Linux based on uBlue Aurora
## Building ## Building
`Containerfile` builds a custom bootc image on top of `ghcr.io/ublue-os/aurora:stable`, `Containerfile` builds a custom bootc image on top of `ghcr.io/ublue-os/aurora:stable`,
layering in everything under `overlay/`: layering in everything under `overlay/`, plus a `kmod-builder` stage that
compiles the CS8409 mic-fix kernel module (see
[Hardware-specific patches](#hardware-specific-patches) below).
[`Taskfile.yml`](Taskfile.yml) (requires [`task`](https://taskfile.dev)) is
the supported way to drive all of this — run `task` with no arguments to
list what's available. Notably:
- `task build-image` — builds the image locally (`podman build`, as root).
- `task build-iso` — builds a bootable install ISO from that image via
`bootc-image-builder`, landing in `output/bootiso/install.iso`.
- `task test:stage` / `task test:boot` — stage the locally-built image as
this machine's next boot target (`bootc switch`), optionally rebooting
into it immediately. The fast way to test a change — especially the
kernel module — against the real running system, without building an ISO
or reinstalling anything.
- `task revert` — rolls back to the previously-booted deployment and
reboots, undoing a `test:boot` switch (or any other deployment change) if
something's wrong.
Equivalently, by hand:
```sh ```sh
podman build -t quine-linux -f Containerfile . podman build -t quine-linux -f Containerfile .
@ -46,3 +66,19 @@ Weekly, runs as the user, no privilege needed: `podman system prune -af`,
`flatpak uninstall --unused -y`, clears `~/.cache`. Auto-enabled for every `flatpak uninstall --unused -y`, clears `~/.cache`. Auto-enabled for every
user via `overlay/usr/lib/systemd/user-preset/90-quine-linux.preset` — no user via `overlay/usr/lib/systemd/user-preset/90-quine-linux.preset` — no
manual `systemctl --user enable` needed. manual `systemctl --user enable` needed.
### overlay/etc/systemd/system/cs8409-intmike-power-fixup.service
Hardware-specific fixup for the iMac19,2 internal mic (see below).
## Hardware-specific patches
`docs/` and `patches/` hold write-ups and out-of-tree kernel module patches
for hardware quirks on the machines this image runs on, that don't fit
anywhere else in this repo:
- [`docs/imac19-2-mic-fix.md`](docs/imac19-2-mic-fix.md) — the internal mic
on iMac19,2 capturing near-silence despite maxed gain; root cause, fix,
and current verification status.
- `patches/snd-hda-codec-cs8409/` — the vendored driver source and patch
backing that fix, built into `Containerfile`'s `kmod-builder` stage.

0
RUNME_PERIODICALLY.sh Normal file → Executable file
View file

77
Taskfile.yml Normal file
View file

@ -0,0 +1,77 @@
version: '3'
vars:
IMAGE: quine-linux
OUTPUT_DIR: ./output
tasks:
default:
desc: List available tasks
cmds:
- task --list
silent: true
build-image:
desc: >-
Build the quine-linux bootc container image, as root (so bootc and
bootc-image-builder -- both of which read root's default
containers-storage location -- can see the result).
cmds:
- sudo podman build -t {{.IMAGE}} -f Containerfile .
sources:
- Containerfile
- overlay/**/*
- patches/**/*
build-iso:
desc: >-
Build a bootable install ISO from the local quine-linux image, via
bootc-image-builder. Output lands in {{.OUTPUT_DIR}}/bootiso/install.iso.
deps: [build-image]
cmds:
- mkdir -p {{.OUTPUT_DIR}}
- >-
sudo podman run --rm --privileged --pull=newer
--security-opt label=type:unconfined_t
-v {{.OUTPUT_DIR}}:/output
-v /var/lib/containers/storage:/var/lib/containers/storage
quay.io/centos-bootc/bootc-image-builder:latest
--type iso --local {{.IMAGE}}
generates:
- "{{.OUTPUT_DIR}}/bootiso/install.iso"
status:
desc: Show current bootc deployment status (what's booted, what's staged/rollback).
cmds:
- bootc status
test:stage:
desc: >-
Build the local image and stage it as the next-boot target on THIS
machine (bootc switch, no reboot yet). This is the fast way to test a
change -- especially the CS8409 mic-fix kernel module -- against the
real running system without building an ISO or reinstalling anything.
Reboot separately when ready: `task test:boot`, or `sudo systemctl reboot`.
deps: [build-image]
cmds:
- sudo bootc switch --transport containers-storage localhost/{{.IMAGE}}
- bootc status --booted
- echo "Staged localhost/{{.IMAGE}} for next boot. Run task test:boot when ready to reboot into it."
test:boot:
desc: Stage the local image (if not already) and reboot into it now.
prompt: This will reboot this machine into the locally-built {{.IMAGE}} image. Continue?
deps: [test:stage]
cmds:
- sudo systemctl reboot
revert:
desc: >-
Roll back to the previously-booted deployment and reboot into it --
undoes a `test:boot` switch (or any other deployment change) if
something's wrong. Safe even if nothing was ever switched: bootc/
rpm-ostree always keep the prior deployment around for exactly this.
prompt: This will reboot this machine back into the previous deployment. Continue?
cmds:
- sudo bootc rollback
- sudo systemctl reboot

235
docs/imac19-2-mic-fix.md Normal file
View file

@ -0,0 +1,235 @@
# iMac19,2 internal mic: silent capture despite maxed gain
## TL;DR
The internal mic shows up normally in PipeWire/ALSA/Discord, with fully
adjustable gain controls, but captures almost nothing regardless of how high
any gain is pushed. Root cause: the CS8409 codec driver's HDA auto-config
binds the exposed capture PCM stream to the wrong ADC node, orphaning the
real internal-mic ADC entirely — its mixer controls work, but nothing is
ever routed through them. A fix for this exact bug, confirmed on a sibling
Mac model, is packaged into this repo's build (`patches/snd-hda-codec-cs8409/`)
but **not yet verified on this exact iMac19,2 hardware** — see
[Current verification status](#current-verification-status).
## Hardware / software identification
- Model: Apple iMac19,2 (2019, 21.5")
- Distro: Aurora (uBlue/Fedora Kinoite bootc image), Kinoite variant
`44.20260908.1`
- Kernel: `7.1.8-200.fc44.x86_64`
- Audio codec: Cirrus Logic CS8409, HDA vendor id `0x10138409`, subsystem id
`0x106b0f00` (confirmed via `/proc/asound/card0/codec#0`)
- Secure Boot: unsupported/disabled on this hardware (`mokutil --sb-state`)
— a documented non-goal of this fix; see [Open risks](#open-risks--unknowns).
## Symptom
Mic appears as a normal PipeWire source (`Built-in Audio Analog Stereo`) and
ALSA capture device (`CS8409 Analog`, card 0). All of the following were
confirmed at 100% (or higher) with no effect on captured level:
- ALSA `Internal Mic` capture volume: 100% (63/63), unmuted
- ALSA `Internal Mic Boost`: 100% (2/2)
- PipeWire source software volume: tested at 1.0, 4.0, and 8.0 (up to +18dB)
- Discord's own in-app input-volume slider: maxed
Direct measurement, bypassing Discord and any GUI entirely, via
`pw-record --target <mic source>` / `arecord`, analyzed with
`ffmpeg -af volumedetect`:
| Gain setting | mean_volume | max_volume |
|---|---|---|
| PipeWire source @ 1.0x (100%, all ALSA gain also maxed) | -80.8 dB | -71.2 dB |
| PipeWire source @ 4.0x | -59.7 dB | -50.3 dB |
| PipeWire source @ 8.0x | -59.7 dB | -50.3 dB (uncontrolled re-test, not a true ceiling — see note below) |
The 1x → 4x jump is real, reproducible signal (matches the expected
amplification). The identical 4x/8x reading is *not* evidence of a hard
ceiling — it came from two takes that weren't spoken into with controlled,
identical volume/distance, so it's not a clean A/B. Even at its best
measured point (-50dB max), this is far too quiet for usable speech (normal
speech should peak around -12 to -6dB) — consistent with "gain is working,
but there's very little real signal to amplify in the first place."
## Ruled out, with evidence
- **Wrong/muted output device** — a real but separate issue found early on:
the *output* sink was muted at 0% (unrelated to the mic). Fixed
independently (`wpctl set-mute @DEFAULT_AUDIO_SINK@ 0`).
- **PipeWire/ALSA mute or wrong default device** — `wpctl status`,
`pactl list sources`, and `amixer` all independently agreed the mic source
was unmuted and at 100%+ from three different vantage points.
- **Discord-side gain/config** — Discord's own input-volume slider was
already at maximum; its `settings.json` doesn't store per-account
voice/mic settings in plaintext (those sync via Discord's own account
storage), so this was ruled out by the maxed-slider observation rather
than a config file.
- **The camera providing the audio instead of the codec** — `arecord -l`
shows exactly **one** ALSA capture device (`CS8409 Analog`, card 0); the
FaceTime HD Camera (USB `05ac:8511`) exposes no audio interface to Linux
at all (checked via `lsusb` + absence from `arecord -l`/`arecord -L`).
- **Apple T2 chip audio routing** — ruled out: no Apple PCI bridge/T2 device
present (`lspci -nn`), and iMac19,2 (2019 21.5") predates T2's presence in
Macs' internal-mic path regardless.
- **Missing Flatpak/portal permission for Discord's mic access** —
`wpctl status` showed Discord's own `WEBRTC VoiceEngine` client (verified
by cross-referencing the sandboxed PID against the host process list) with
an *active* capture stream connected to the real hardware node — a
permission denial would show no connected stream at all, not merely low
level.
## Root cause
Traced to a specific, very recent (2026-08-19) upstream bug report and fix
on a sibling Mac model:
- **Issue**: [`egorenar/snd-hda-codec-cs8409` #5, "mic input"](https://github.com/egorenar/snd-hda-codec-cs8409/issues/5)
- **Diagnostic dead-end + panic warning + D3 discovery** (2026-08-17):
https://github.com/egorenar/snd-hda-codec-cs8409/issues/5#issuecomment-5314393975
- **Root cause + fix, confirmed working** (2026-08-19):
https://github.com/egorenar/snd-hda-codec-cs8409/issues/5#issuecomment-5338459155
- **Fork/PR carrying Linux 6.17+ HDA API compatibility**:
[PR #30](https://github.com/egorenar/snd-hda-codec-cs8409/pull/30),
`daviddoll/snd-hda-codec-cs8409@kernel-6.17-hda-compat`, open/unmerged.
In `patch_cirrus_apple.h`, function `cs_8409_apple_boot_init()`, the
`SNDRV_PCM_STREAM_CAPTURE` branch only handles two cases for the capture
stream's `hinfo->nid`:
- `hinfo->nid == spec->intmike_adc_nid` — the real internal-mic ADC, set up
correctly.
- `hinfo->nid == 0x1a` — the headset-mic node, set up correctly.
There's no fallback for when HDA auto-config lands the exposed capture PCM
stream on **neither** — e.g. landing on node `0x12`, wired to an unconfigured
external Line-In pin (`0x34`) with no physical signal path at all. When this
happens, the real internal-mic ADC (`spec->intmike_adc_nid`, `0x23` for our
subsystem-id group) has all its mixer/gain/switch controls correctly present
and adjustable — which is exactly why raising every gain control had no
effect — but is never attached to any PCM stream, so it's completely
orphaned. Confirmed on the sibling model via `journalctl -kf` during a live
`arecord` test: the internal-mic-specific prepare hook was never called,
only the generic default path ran.
Our subsystem id (`0x106b0f00`) is grouped together with the sibling model's
family (`0x106b1000`, `0x106b0f00`, `0x106b0e00`) in this same file's
`intmike_adc_nid` assignment logic — all three get `intmike_nid = 0x45`,
`intmike_adc_nid = 0x23` — matching our own kernel log
(`snd_hda_codec_cs8409 hdaudioC0D0: ... Internal Mic=0x45`). So this is a
plausible direct match, though **confirmed only on iMac18,1, not this exact
iMac19,2** — see [Open risks](#open-risks--unknowns).
On the sibling model, applying the fix took capture from -91dB pure digital
silence to a real -16.9dB mean / -0.0dB max signal.
## The fix
See [`patches/snd-hda-codec-cs8409/0001-imac19-2-force-intmike-adc-nid-fallback.patch`](../patches/snd-hda-codec-cs8409/0001-imac19-2-force-intmike-adc-nid-fallback.patch)
for the actual diff (source of truth — not duplicated here). In short: if
the capture stream's `hinfo->nid` matches neither the internal-mic ADC nor
the headset-mic node, force it onto the internal-mic ADC before the existing
setup code runs.
### A second, related issue (defensive, unconfirmed on this model)
On the sibling model, the internal-mic ADC node also came up in HDA power
state D3 (suspended) after cold boot, failing capture with an I/O error
until forced to D0 via `hda-verb ... 0x23 SET_POWER_STATE 0x0` — not
persistent across reboots. This repo includes a defensive systemd unit
(`cs8409-intmike-power-fixup.service`, running
`overlay/usr/libexec/quine-linux/cs8409-intmike-power-fixup.sh`) that logs
the power state and retries forcing D0 a few seconds after boot. **Whether
iMac19,2 actually hits this at all is unconfirmed** — its own journal output
after the real test boot is the evidence that will settle it.
## Packaging approach
The fix is built into this repo's own bootc image rather than hand-patched
live on the running system, specifically to avoid a documented panic risk
(see [Open risks](#open-risks--unknowns)) and to get free, cheap rollback via
`bootc rollback`/`rpm-ostree rollback` if anything goes wrong.
- `patches/snd-hda-codec-cs8409/vendor/` — a verbatim copy of the 16 files
from the pinned upstream commit (`daviddoll/snd-hda-codec-cs8409` @
`14e00de4290e090afd7632bc6d5c724131a19e7b`), vendored rather than fetched
at build time because this is an unmerged personal-fork branch that could
be force-pushed or deleted with no notice. See
[`PROVENANCE.md`](../patches/snd-hda-codec-cs8409/PROVENANCE.md) for the
full origin/license/rationale writeup (license: `GPL-2.0-or-later`, one
file `GPL-2.0-only`, per each file's own original SPDX header).
- `patches/snd-hda-codec-cs8409/0001-imac19-2-force-intmike-adc-nid-fallback.patch` —
the actual functional change, kept separate from the vendored copy so it's
easy to re-diff if/when PR #30 lands upstream and this vendoring can be
dropped.
- `Containerfile` — a new `kmod-builder` stage installs kernel headers
matching the exact kernel shipped in the `aurora:stable` base image
(pinned by digest, shared between both stages via a single `ARG`, so they
can't drift apart), applies the patch, and builds the module with the
vendored `Makefile`. The final stage copies the resulting `.ko` into
`/usr/lib/modules/<kernel-version>/extra/` (the same convention Fedora's
own `kmod-*` subpackages use to override an in-tree module of the same
name/modalias — no blacklisting or renaming needed) and re-runs `depmod`
against the final image's own module tree.
- DKMS was deliberately **not** used: it exists to rebuild modules at
boot/install time when a kernel updates later on a mutable host. On this
bootc image, a new kernel only ever arrives bundled inside a freshly built
image, at which point the `kmod-builder` stage reruns from scratch anyway
— DKMS's rebuild-on-boot machinery is redundant weight here.
## Current verification status
**Not yet tested on real hardware as of 2026-09-26.** The patch has been
verified to apply cleanly (`git apply --check`) and produce the intended
diff against the vendored source. The Containerfile has been written and a
local `podman build` sanity check (module compiles, `vermagic` matches the
target kernel) is the next step. Actually switching this machine to boot the
built image (`bootc switch`/`rpm-ostree rebase` + reboot) is a separate,
explicitly confirmed step, not yet taken, given this hardware/kernel/fix
combination is entirely unverified. This section will be updated with real
results (measured dB, Discord input-meter behavior, and whether the D3
power-state unit actually fired) once that test happens.
## Open risks / unknowns
- **Kernel API mismatch risk**: the vendored fork targets Linux 6.17+ HDA
API; our kernel is 7.1.8. Untested whether it compiles cleanly without
further porting — but this fails safely at `podman build` time, before
ever touching the running system.
- **Unconfirmed on this exact model**: the fix is confirmed only on
iMac18,1, not this iMac19,2/subsystem-id combination, even though the
`intmike_adc_nid` code path is shared across both per direct source
inspection.
- **D3 power-state issue unconfirmed on this model** — only confirmed on the
sibling; the defensive unit's journal is the intended way to settle this.
- **Module search-path precedence** (`extra/` overriding in-tree
`kernel/...`) is asserted from documented Fedora/kmod convention, not yet
verified on this image — check `modinfo -F filename
snd_hda_codec_cs8409` after the real test boot.
- **Live-unbind panic risk**: a live `unbind`/`rmmod`/`modprobe`/`bind` cycle
of this module while PipeWire has the sink active caused an unrecoverable
freeze on the sibling model. This is exactly why all testing here goes
through a full image build + reboot, never a live module reload.
- **Secure Boot non-goal**: this approach assumes Secure Boot stays
disabled on this hardware. If it's ever enabled, this entire approach
breaks (an unsigned out-of-tree `.ko` gets rejected at load) and would
need MOK enrollment + module signing added to the builder stage.
- **Personal-fork provenance risk**: the ultimate origin of the 6.17+
compatibility work is an unmerged personal-fork PR (#30). Vendoring
mitigates the "it could disappear" risk but doesn't change that this
hasn't gone through upstream review.
- `quine-linux`'s own `rpm-ostree-cleanup.timer` prunes the rollback
deployment weekly — worth being aware of before testing an unverified
change on this hardware, though no action has been taken on this yet.
## References
- https://github.com/egorenar/snd-hda-codec-cs8409/issues/5
- https://github.com/egorenar/snd-hda-codec-cs8409/issues/5#issuecomment-5314393975
- https://github.com/egorenar/snd-hda-codec-cs8409/issues/5#issuecomment-5338459155
- https://github.com/egorenar/snd-hda-codec-cs8409/pull/30
- https://github.com/daviddoll/snd-hda-codec-cs8409/tree/kernel-6.17-hda-compat
- https://github.com/rojoxstar/imac19-2-cs8409-linux (iMac19,2-specific
project; primarily speaker/playback and Apple-DSP research, explicitly
does not claim mic-capture support — investigated and set aside in favor
of the fix above, which directly targets the observed symptom)

View file

@ -0,0 +1,16 @@
[Unit]
Description=Force CS8409 internal-mic ADC out of D3 power state (iMac19,2 mic fix)
# See docs/imac19-2-mic-fix.md. Confirmed needed on a sibling model
# (iMac18,1); unconfirmed on iMac19,2 as of the date in that doc. This unit
# is defensive: it logs the power state it finds either way, so its journal
# is the evidence for whether this model actually needs it.
After=sound.target systemd-udev-settle.service
ConditionPathExists=/dev/snd/hwC0D0
[Service]
Type=oneshot
RemainAfterExit=no
ExecStart=/usr/libexec/quine-linux/cs8409-intmike-power-fixup.sh
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,30 @@
#!/bin/bash
# Forces the CS8409 internal-mic ADC (nid 0x23 on iMac19,2/18,x-family
# subsystem ids) out of D3 power state on boot. See
# docs/imac19-2-mic-fix.md in the quine-linux repo for the full story.
#
# Confirmed needed on a sibling model (iMac18,1); unconfirmed on iMac19,2.
# This logs the power state on every attempt, so the journal for this unit
# is the evidence for whether this model needs it at all. Always exits 0 --
# this is a best-effort defensive fixup, not something that should fail boot.
set -u
dev=/dev/snd/hwC0D0
nid=0x23
log() { echo "cs8409-intmike-power-fixup: $*"; }
log "initial state: $(hda-verb "$dev" "$nid" GET_POWER_STATE 0x0 2>&1 | tail -1)"
for i in $(seq 1 10); do
hda-verb "$dev" "$nid" SET_POWER_STATE 0x0 >/dev/null 2>&1
sleep 1
state="$(hda-verb "$dev" "$nid" GET_POWER_STATE 0x0 2>&1 | tail -1)"
log "attempt $i state: $state"
case "$state" in
*0x0*) log "reached D0 after $i attempt(s)"; exit 0 ;;
esac
done
log "gave up after 10 attempts, last state: $state"
exit 0

View file

@ -0,0 +1,22 @@
--- a/patch_cirrus_apple.h
+++ b/patch_cirrus_apple.h
@@ -1257,6 +1257,19 @@
}
else if (stream == SNDRV_PCM_STREAM_CAPTURE)
{
+ // iMac19,2 mic fix: HDA auto-config sometimes lands this
+ // capture PCM stream's nid on neither the internal-mic ADC
+ // nor the headset-mic node (e.g. it lands on an unwired
+ // Line-In node such as 0x12), orphaning the real internal
+ // mic entirely -- its mixer/gain controls stay present and
+ // adjustable, but nothing is ever routed through them.
+ // Force it back onto the internal-mic ADC in that case.
+ // See egorenar/snd-hda-codec-cs8409#5 (comment 2026-08-19).
+ if (hinfo->nid != spec->intmike_adc_nid && hinfo->nid != 0x1a)
+ {
+ mycodec_dbg(codec, "cs_8409_apple_boot_init capture stream nid 0x%02x is not intmike (0x%02x) or headset (0x1a), forcing to intmike\n", hinfo->nid, spec->intmike_adc_nid);
+ hinfo->nid = spec->intmike_adc_nid;
+ }
//if (hinfo->nid == 0x22)
if (hinfo->nid == spec->intmike_adc_nid)
{

View file

@ -0,0 +1,60 @@
# Provenance: vendored `snd-hda-codec-cs8409` source
## Origin
- Upstream project: [`egorenar/snd-hda-codec-cs8409`](https://github.com/egorenar/snd-hda-codec-cs8409)
(out-of-tree Linux HDA codec driver adding Apple Cirrus CS8409/CS42L42/CS42L83
support beyond what's in mainline).
- Actual source vendored here comes from a fork/branch with Linux 6.17+ HDA
API compatibility work, not yet merged upstream:
[`daviddoll/snd-hda-codec-cs8409`](https://github.com/daviddoll/snd-hda-codec-cs8409),
branch `kernel-6.17-hda-compat`, submitted as
[PR #30](https://github.com/egorenar/snd-hda-codec-cs8409/pull/30)
("Port out-of-tree CS8409 driver for Linux 6.17+ HDA API") — open, unmerged
as of the date below.
- **Pinned commit:** `14e00de4290e090afd7632bc6d5c724131a19e7b`
(2026-04-04, "Port for Linux 6.17+ HDA codec API and modpost fixes").
- **Date vendored:** 2026-09-26.
## Why vendored instead of fetched at build time
This is an unmerged personal-fork branch, not a stable upstream release —
it can be force-pushed, renamed, or deleted with no notice. Vendoring a
verbatim copy here (original SPDX headers untouched) makes the quine-linux
build offline-capable, immune to that repo's fate, and keeps the
corresponding source actually present in this repo's own git history
alongside the binary it produces, which is the most direct way to satisfy
GPL-2.0-or-later's source-availability obligation.
## What's in `vendor/`
All 16 files from the pinned commit, unmodified:
```
Makefile, README.md, .gitignore,
hda_auto_parser.h, hda_generic.h, hda_jack.h, hda_local.h,
patch_cirrus_apple.h, patch_cirrus_boot84.h, patch_cirrus_hda_generic_copy.h,
patch_cirrus_new84.h, patch_cirrus_real84.h, patch_cirrus_real84_i2c.h,
patch_cs8409-tables.c, patch_cs8409.c, patch_cs8409.h
```
## License
`GPL-2.0-or-later` (verified directly from the SPDX headers present in the
vendored files, e.g. `patch_cs8409.c`, `patch_cirrus_apple.h`,
`patch_cs8409.h`, `hda_generic.h`). One file, `patch_cs8409-tables.c`, carries
`GPL-2.0-only` specifically — the combined work is distributed here under
`GPL-2.0-or-later` for the files that specify it, and `GPL-2.0-only` for that
one file, matching each file's own original header.
## Our changes
The actual functional fix lives as a separate patch, applied on top of this
vendored tree at image-build time, not folded into the vendored copy:
- `0001-imac19-2-force-intmike-adc-nid-fallback.patch` — see
`../../docs/imac19-2-mic-fix.md` for the full root-cause writeup.
Keeping "what upstream wrote" (this directory) and "what we changed" (the
`.patch` file) separate makes it trivial to re-diff against a future upstream
merge of PR #30, and to drop this vendoring entirely once/if that lands.

View file

@ -0,0 +1,8 @@
*.cmd
*.ko
*.o
Module.symvers
modules.order
*.mod
*.mod.c
.DS_Store

View file

@ -0,0 +1,29 @@
# Build from this directory: make (or sudo make install)
# Do not run only "make -C /lib/modules/$(uname -r)/build M=..." — that skips
# CFLAGS_MODULE below and used to break the tree before patch_cs8409.h defaulted
# APPLE_CODECS. If you must call kbuild by hand, repeat the same CFLAGS_MODULE
# string as in the "all:" recipe.
snd-hda-codec-cs8409-objs := patch_cs8409.o patch_cs8409-tables.o
obj-$(CONFIG_SND_HDA_CODEC_CS8409) += snd-hda-codec-cs8409.o
# debug build flags
#KBUILD_EXTRA_CFLAGS = "-DCONFIG_SND_DEBUG=1 -DMYSOUNDDEBUGFULL -DAPPLE_PINSENSE_FIXUP -DAPPLE_CODECS -DCONFIG_SND_HDA_RECONFIG=1 -Wno-unused-variable -Wno-unused-function"
# normal build flags
KBUILD_EXTRA_CFLAGS = "-DAPPLE_PINSENSE_FIXUP -DAPPLE_CODECS -DCONFIG_SND_HDA_RECONFIG=1 -Wno-unused-variable -Wno-unused-function"
ifdef KVER
KDIR := /lib/modules/$(KVER)
else
KDIR := /lib/modules/$(shell uname -r)
endif
all:
make -C $(KDIR)/build CFLAGS_MODULE=$(KBUILD_EXTRA_CFLAGS) M=$(shell pwd) modules
clean:
make -C $(KDIR)/build M=$(shell pwd) clean
install:
mkdir -p $(KDIR)/updates/
cp snd-hda-codec-cs8409.ko $(KDIR)/updates/
depmod -a

View file

@ -0,0 +1,92 @@
# snd-hda-codec-cs8409 (out-of-tree)
Out-of-tree Linux kernel module for the **Cirrus Logic CS8409** HD Audio bridge and related fixups (including **Apple** machine quirks). It mirrors the in-kernel ALSA HDA codec driver logic but can be built and updated without rebuilding the whole kernel.
## Requirements
- Linux kernel headers for the kernel you are running
(e.g. on Debian/Ubuntu/Pop!_OS: `linux-headers-$(uname -r)`)
- Toolchain able to build external modules (`gcc`, `make`, etc.)
- Build from a shell where `uname -r` matches the target kernel (or set `KVER` — see below)
## Build
Always build **from this directory** using the provided `Makefile` so module C flags are applied correctly:
```bash
cd /path/to/snd-hda-codec-cs8409
make
```
Do **not** run only:
```text
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
```
without passing the same `CFLAGS_MODULE` values as in the `Makefile` `all:` recipe. The Makefile sets defines such as `APPLE_CODECS` and `CONFIG_SND_HDA_RECONFIG` that this tree expects.
### Optional: build for another installed kernel
```bash
make KVER=6.18.7-76061807-generic
```
(Use the directory name under `/lib/modules/` for that kernel.)
### Install module into `updates/` (takes precedence over in-tree module)
```bash
sudo make install
sudo depmod -a # install target runs depmod; repeat if you copy the .ko manually
```
Reload the audio stack or reboot as appropriate on your distribution (e.g. unload/reload `snd-hda-intel` and related modules, or reboot).
## Kernel compatibility
Recent work in this repository targets **modern kernels (6.17+)** where the ALSA HDA stack uses:
- `struct hda_codec_driver` with `probe` / `remove` instead of the old `codec->patch_ops` / `.free` callbacks
- `snd_hda_gen_remove()` instead of the former `snd_hda_gen_free()` export (a compatibility wrapper in `hda_generic.h` maps the old name for this module)
The module has been verified to build and run on **6.18.x** (e.g. Pop!_OS). Older kernels may still work if the symbols and APIs match; if not, use a branch or commit intended for that era.
## Tested on
| Environment | Kernel | Notes |
|-------------|--------|--------|
| **Pop!_OS** | `6.18.7-76061807-generic` | `make` and `sudo make install` succeed; audio confirmed working after reload/reboot as needed on the test machine. |
Always install **kernel headers** that match your running kernel (`linux-headers-$(uname -r)` on Debian-based systems).
## Development
The **entire set of changes** in this repository as published here—the **kernel 6.17+ HDA API port** (`hda_codec_driver`, variant ops, `snd_hda_gen_remove` compatibility), **Makefile** / **`.gitignore`** updates, **README**, and related integration—was **written and iterated using [Cursor](https://cursor.com)** (AI-assisted editor). The CS8409 codec logic and Apple extensions remain **derived from Cirrus Logic and the Linux ALSA HDA codebase**; original **SPDX** and **copyright** lines in the source files still apply.
## Configuration flags (Makefile)
`KBUILD_EXTRA_CFLAGS` enables, among other things:
- `APPLE_CODECS` — Apple-specific paths in `patch_cirrus_apple.h`
- `APPLE_PINSENSE_FIXUP`
- `CONFIG_SND_HDA_RECONFIG`
A debug-oriented line is commented out in the `Makefile` (extra logging defines).
## License
The sources are under **GPL-2.0-or-later** (see SPDX headers in the individual files). This tree includes code attributed to Cirrus Logic and Takashi Iwai / the ALSA project, consistent with the upstream HDA codec drivers.
## Troubleshooting
| Issue | What to check |
|--------|----------------|
| `No rule to make target .../build` | Install kernel headers for `uname -r`. |
| Many errors about missing `cs8409_spec` fields | Build with this repo’s `make` (see above), not bare kbuild without `CFLAGS_MODULE`. |
| `modpost: "snd_hda_gen_free" undefined` | Use a tree that includes the `hda_generic.h` wrapper for `snd_hda_gen_remove` (kernel **≥ 6.17**). |
| Compiler differs from kernel | Usually a warning only; same major `gcc` as the distro kernel is ideal. |
## Contributing
Pull requests and reports against specific **kernel version** and **hardware** (PCI subsystem ID, machine model) help keep this module useful for others.

View file

@ -0,0 +1,118 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* BIOS auto-parser helper functions for HD-audio
*
* Copyright (c) 2012 Takashi Iwai <tiwai@suse.de>
*/
#ifndef __SOUND_HDA_AUTO_PARSER_H
#define __SOUND_HDA_AUTO_PARSER_H
#include "hda_local.h"
/*
* Helper for automatic pin configuration
*/
enum {
AUTO_PIN_MIC,
AUTO_PIN_LINE_IN,
AUTO_PIN_CD,
AUTO_PIN_AUX,
AUTO_PIN_LAST
};
enum {
AUTO_PIN_LINE_OUT,
AUTO_PIN_SPEAKER_OUT,
AUTO_PIN_HP_OUT
};
#define AUTO_CFG_MAX_OUTS HDA_MAX_OUTS
#define AUTO_CFG_MAX_INS 18
struct auto_pin_cfg_item {
hda_nid_t pin;
int type;
unsigned int is_headset_mic:1;
unsigned int is_headphone_mic:1; /* Mic-only in headphone jack */
unsigned int has_boost_on_pin:1;
int order;
};
struct auto_pin_cfg;
const char *hda_get_autocfg_input_label(struct hda_codec *codec,
const struct auto_pin_cfg *cfg,
int input);
int snd_hda_get_pin_label(struct hda_codec *codec, hda_nid_t nid,
const struct auto_pin_cfg *cfg,
char *label, int maxlen, int *indexp);
enum {
INPUT_PIN_ATTR_UNUSED, /* pin not connected */
INPUT_PIN_ATTR_INT, /* internal mic/line-in */
INPUT_PIN_ATTR_DOCK, /* docking mic/line-in */
INPUT_PIN_ATTR_NORMAL, /* mic/line-in jack */
INPUT_PIN_ATTR_REAR, /* mic/line-in jack in rear */
INPUT_PIN_ATTR_FRONT, /* mic/line-in jack in front */
INPUT_PIN_ATTR_LAST = INPUT_PIN_ATTR_FRONT,
};
int snd_hda_get_input_pin_attr(unsigned int def_conf);
struct auto_pin_cfg {
int line_outs;
/* sorted in the order of Front/Surr/CLFE/Side */
hda_nid_t line_out_pins[AUTO_CFG_MAX_OUTS];
int speaker_outs;
hda_nid_t speaker_pins[AUTO_CFG_MAX_OUTS];
int hp_outs;
int line_out_type; /* AUTO_PIN_XXX_OUT */
hda_nid_t hp_pins[AUTO_CFG_MAX_OUTS];
int num_inputs;
struct auto_pin_cfg_item inputs[AUTO_CFG_MAX_INS];
int dig_outs;
hda_nid_t dig_out_pins[2];
hda_nid_t dig_in_pin;
hda_nid_t mono_out_pin;
int dig_out_type[2]; /* HDA_PCM_TYPE_XXX */
int dig_in_type; /* HDA_PCM_TYPE_XXX */
};
/* bit-flags for snd_hda_parse_pin_def_config() behavior */
#define HDA_PINCFG_NO_HP_FIXUP (1 << 0) /* no HP-split */
#define HDA_PINCFG_NO_LO_FIXUP (1 << 1) /* don't take other outs as LO */
#define HDA_PINCFG_HEADSET_MIC (1 << 2) /* Try to find headset mic; mark seq number as 0xc to trigger */
#define HDA_PINCFG_HEADPHONE_MIC (1 << 3) /* Try to find headphone mic; mark seq number as 0xd to trigger */
int snd_hda_parse_pin_defcfg(struct hda_codec *codec,
struct auto_pin_cfg *cfg,
const hda_nid_t *ignore_nids,
unsigned int cond_flags);
/* older function */
#define snd_hda_parse_pin_def_config(codec, cfg, ignore) \
snd_hda_parse_pin_defcfg(codec, cfg, ignore, 0)
static inline int auto_cfg_hp_outs(const struct auto_pin_cfg *cfg)
{
return (cfg->line_out_type == AUTO_PIN_HP_OUT) ?
cfg->line_outs : cfg->hp_outs;
}
static inline const hda_nid_t *auto_cfg_hp_pins(const struct auto_pin_cfg *cfg)
{
return (cfg->line_out_type == AUTO_PIN_HP_OUT) ?
cfg->line_out_pins : cfg->hp_pins;
}
static inline int auto_cfg_speaker_outs(const struct auto_pin_cfg *cfg)
{
return (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) ?
cfg->line_outs : cfg->speaker_outs;
}
static inline const hda_nid_t *auto_cfg_speaker_pins(const struct auto_pin_cfg *cfg)
{
return (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) ?
cfg->line_out_pins : cfg->speaker_pins;
}
#endif /* __SOUND_HDA_AUTO_PARSER_H */

View file

@ -0,0 +1,366 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Generic BIOS auto-parser helper functions for HD-audio
*
* Copyright (c) 2012 Takashi Iwai <tiwai@suse.de>
*/
#ifndef __SOUND_HDA_GENERIC_H
#define __SOUND_HDA_GENERIC_H
#include <linux/leds.h>
#include <linux/version.h>
#include "hda_auto_parser.h"
struct hda_jack_callback;
/* table entry for multi-io paths */
struct hda_multi_io {
hda_nid_t pin; /* multi-io widget pin NID */
hda_nid_t dac; /* DAC to be connected */
unsigned int ctl_in; /* cached input-pin control value */
};
/* Widget connection path
*
* For output, stored in the order of DAC -> ... -> pin,
* for input, pin -> ... -> ADC.
*
* idx[i] contains the source index number to select on of the widget path[i];
* e.g. idx[1] is the index of the DAC (path[0]) selected by path[1] widget
* multi[] indicates whether it's a selector widget with multi-connectors
* (i.e. the connection selection is mandatory)
* vol_ctl and mute_ctl contains the NIDs for the assigned mixers
*/
#define MAX_NID_PATH_DEPTH 10
enum {
NID_PATH_VOL_CTL,
NID_PATH_MUTE_CTL,
NID_PATH_BOOST_CTL,
NID_PATH_NUM_CTLS
};
struct nid_path {
int depth;
hda_nid_t path[MAX_NID_PATH_DEPTH];
unsigned char idx[MAX_NID_PATH_DEPTH];
unsigned char multi[MAX_NID_PATH_DEPTH];
unsigned int ctls[NID_PATH_NUM_CTLS]; /* NID_PATH_XXX_CTL */
bool active:1; /* activated by driver */
bool pin_enabled:1; /* pins are enabled */
bool pin_fixed:1; /* path with fixed pin */
bool stream_enabled:1; /* stream is active */
};
/* mic/line-in auto switching entry */
#define MAX_AUTO_MIC_PINS 3
struct automic_entry {
hda_nid_t pin; /* pin */
int idx; /* imux index, -1 = invalid */
unsigned int attr; /* pin attribute (INPUT_PIN_ATTR_*) */
};
/* active stream id */
enum { STREAM_MULTI_OUT, STREAM_INDEP_HP };
/* PCM hook action */
enum {
HDA_GEN_PCM_ACT_OPEN,
HDA_GEN_PCM_ACT_PREPARE,
HDA_GEN_PCM_ACT_CLEANUP,
HDA_GEN_PCM_ACT_CLOSE,
};
/* DAC assignment badness table */
struct badness_table {
int no_primary_dac; /* no primary DAC */
int no_dac; /* no secondary DACs */
int shared_primary; /* primary DAC is shared with main output */
int shared_surr; /* secondary DAC shared with main or primary */
int shared_clfe; /* third DAC shared with main or primary */
int shared_surr_main; /* secondary DAC sahred with main/DAC0 */
};
extern const struct badness_table hda_main_out_badness;
extern const struct badness_table hda_extra_out_badness;
struct hda_gen_spec {
char stream_name_analog[32]; /* analog PCM stream */
const struct hda_pcm_stream *stream_analog_playback;
const struct hda_pcm_stream *stream_analog_capture;
char stream_name_alt_analog[32]; /* alternative analog PCM stream */
const struct hda_pcm_stream *stream_analog_alt_playback;
const struct hda_pcm_stream *stream_analog_alt_capture;
char stream_name_digital[32]; /* digital PCM stream */
const struct hda_pcm_stream *stream_digital_playback;
const struct hda_pcm_stream *stream_digital_capture;
/* PCM */
unsigned int active_streams;
struct mutex pcm_mutex;
/* playback */
struct hda_multi_out multiout; /* playback set-up
* max_channels, dacs must be set
* dig_out_nid and hp_nid are optional
*/
hda_nid_t alt_dac_nid;
hda_nid_t follower_dig_outs[3]; /* optional - for auto-parsing */
int dig_out_type;
/* capture */
unsigned int num_adc_nids;
hda_nid_t adc_nids[AUTO_CFG_MAX_INS];
hda_nid_t dig_in_nid; /* digital-in NID; optional */
hda_nid_t mixer_nid; /* analog-mixer NID */
hda_nid_t mixer_merge_nid; /* aamix merge-point NID (optional) */
const char *input_labels[HDA_MAX_NUM_INPUTS];
int input_label_idxs[HDA_MAX_NUM_INPUTS];
/* capture setup for dynamic dual-adc switch */
hda_nid_t cur_adc;
unsigned int cur_adc_stream_tag;
unsigned int cur_adc_format;
/* capture source */
struct hda_input_mux input_mux;
unsigned int cur_mux[3];
/* channel model */
/* min_channel_count contains the minimum channel count for primary
* outputs. When multi_ios is set, the channels can be configured
* between min_channel_count and (min_channel_count + multi_ios * 2).
*
* ext_channel_count contains the current channel count of the primary
* out. This varies in the range above.
*
* Meanwhile, const_channel_count is the channel count for all outputs
* including headphone and speakers. It's a constant value, and the
* PCM is set up as max(ext_channel_count, const_channel_count).
*/
int min_channel_count; /* min. channel count for primary out */
int ext_channel_count; /* current channel count for primary */
int const_channel_count; /* channel count for all */
/* PCM information */
struct hda_pcm *pcm_rec[3]; /* used in build_pcms() */
/* dynamic controls, init_verbs and input_mux */
struct auto_pin_cfg autocfg;
struct snd_array kctls;
hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS];
hda_nid_t imux_pins[HDA_MAX_NUM_INPUTS];
unsigned int dyn_adc_idx[HDA_MAX_NUM_INPUTS];
/* shared hp/mic */
hda_nid_t shared_mic_vref_pin;
hda_nid_t hp_mic_pin;
int hp_mic_mux_idx;
/* DAC/ADC lists */
int num_all_dacs;
hda_nid_t all_dacs[16];
int num_all_adcs;
hda_nid_t all_adcs[AUTO_CFG_MAX_INS];
/* path list */
struct snd_array paths;
/* path indices */
int out_paths[AUTO_CFG_MAX_OUTS];
int hp_paths[AUTO_CFG_MAX_OUTS];
int speaker_paths[AUTO_CFG_MAX_OUTS];
int aamix_out_paths[3];
int digout_paths[AUTO_CFG_MAX_OUTS];
int input_paths[HDA_MAX_NUM_INPUTS][AUTO_CFG_MAX_INS];
int loopback_paths[HDA_MAX_NUM_INPUTS];
int loopback_merge_path;
int digin_path;
/* auto-mic stuff */
int am_num_entries;
struct automic_entry am_entry[MAX_AUTO_MIC_PINS];
/* for pin sensing */
/* current status; set in hda_generic.c */
unsigned int hp_jack_present:1;
unsigned int line_jack_present:1;
unsigned int speaker_muted:1; /* current status of speaker mute */
unsigned int line_out_muted:1; /* current status of LO mute */
/* internal states of automute / autoswitch behavior */
unsigned int auto_mic:1;
unsigned int automute_speaker:1; /* automute speaker outputs */
unsigned int automute_lo:1; /* automute LO outputs */
/* capabilities detected by parser */
unsigned int detect_hp:1; /* Headphone detection enabled */
unsigned int detect_lo:1; /* Line-out detection enabled */
unsigned int automute_speaker_possible:1; /* there are speakers and either LO or HP */
unsigned int automute_lo_possible:1; /* there are line outs and HP */
/* additional parameters set by codec drivers */
unsigned int master_mute:1; /* master mute over all */
unsigned int keep_vref_in_automute:1; /* Don't clear VREF in automute */
unsigned int line_in_auto_switch:1; /* allow line-in auto switch */
unsigned int auto_mute_via_amp:1; /* auto-mute via amp instead of pinctl */
/* parser behavior flags; set before snd_hda_gen_parse_auto_config() */
unsigned int suppress_auto_mute:1; /* suppress input jack auto mute */
unsigned int suppress_auto_mic:1; /* suppress input jack auto switch */
/* other parse behavior flags */
unsigned int need_dac_fix:1; /* need to limit DACs for multi channels */
unsigned int hp_mic:1; /* Allow HP as a mic-in */
unsigned int suppress_hp_mic_detect:1; /* Don't detect HP/mic */
unsigned int no_primary_hp:1; /* Don't prefer HP pins to speaker pins */
unsigned int no_multi_io:1; /* Don't try multi I/O config */
unsigned int multi_cap_vol:1; /* allow multiple capture xxx volumes */
unsigned int inv_dmic_split:1; /* inverted dmic w/a for conexant */
unsigned int own_eapd_ctl:1; /* set EAPD by own function */
unsigned int keep_eapd_on:1; /* don't turn off EAPD automatically */
unsigned int vmaster_mute_led:1; /* add SPK-LED flag to vmaster mute switch */
unsigned int mic_mute_led:1; /* add MIC-LED flag to capture mute switch */
unsigned int indep_hp:1; /* independent HP supported */
unsigned int prefer_hp_amp:1; /* enable HP amp for speaker if any */
unsigned int add_stereo_mix_input:2; /* add aamix as a capture src */
unsigned int add_jack_modes:1; /* add i/o jack mode enum ctls */
unsigned int power_down_unused:1; /* power down unused widgets */
unsigned int dac_min_mute:1; /* minimal = mute for DACs */
unsigned int suppress_vmaster:1; /* don't create vmaster kctls */
/* other internal flags */
unsigned int no_analog:1; /* digital I/O only */
unsigned int dyn_adc_switch:1; /* switch ADCs (for ALC275) */
unsigned int indep_hp_enabled:1; /* independent HP enabled */
unsigned int have_aamix_ctl:1;
unsigned int hp_mic_jack_modes:1;
unsigned int skip_verbs:1; /* don't apply verbs at snd_hda_gen_init() */
/* additional mute flags (only effective with auto_mute_via_amp=1) */
u64 mute_bits;
/* bitmask for skipping volume controls */
u64 out_vol_mask;
/* badness tables for output path evaluations */
const struct badness_table *main_out_badness;
const struct badness_table *extra_out_badness;
/* preferred pin/DAC pairs; an array of paired NIDs */
const hda_nid_t *preferred_dacs;
/* loopback mixing mode */
bool aamix_mode;
/* digital beep */
hda_nid_t beep_nid;
/* for virtual master */
hda_nid_t vmaster_nid;
unsigned int vmaster_tlv[4];
struct hda_vmaster_mute_hook vmaster_mute;
struct hda_loopback_check loopback;
struct snd_array loopback_list;
/* multi-io */
int multi_ios;
struct hda_multi_io multi_io[4];
/* hooks */
void (*init_hook)(struct hda_codec *codec);
void (*automute_hook)(struct hda_codec *codec);
void (*cap_sync_hook)(struct hda_codec *codec,
struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
/* PCM hooks */
void (*pcm_playback_hook)(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
struct snd_pcm_substream *substream,
int action);
void (*pcm_capture_hook)(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
struct snd_pcm_substream *substream,
int action);
/* automute / autoswitch hooks */
void (*hp_automute_hook)(struct hda_codec *codec,
struct hda_jack_callback *cb);
void (*line_automute_hook)(struct hda_codec *codec,
struct hda_jack_callback *cb);
void (*mic_autoswitch_hook)(struct hda_codec *codec,
struct hda_jack_callback *cb);
/* leds */
struct led_classdev *led_cdevs[NUM_AUDIO_LEDS];
};
/* values for add_stereo_mix_input flag */
enum {
HDA_HINT_STEREO_MIX_DISABLE, /* No stereo mix input */
HDA_HINT_STEREO_MIX_ENABLE, /* Add stereo mix input */
HDA_HINT_STEREO_MIX_AUTO, /* Add only if auto-mic is disabled */
};
int snd_hda_gen_spec_init(struct hda_gen_spec *spec);
int snd_hda_gen_init(struct hda_codec *codec);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 17, 0)
void snd_hda_gen_remove(struct hda_codec *codec);
static inline void snd_hda_gen_free(struct hda_codec *codec)
{
snd_hda_gen_remove(codec);
}
#else
void snd_hda_gen_free(struct hda_codec *codec);
#endif
int snd_hda_get_path_idx(struct hda_codec *codec, struct nid_path *path);
struct nid_path *snd_hda_get_path_from_idx(struct hda_codec *codec, int idx);
struct nid_path *
snd_hda_add_new_path(struct hda_codec *codec, hda_nid_t from_nid,
hda_nid_t to_nid, int anchor_nid);
void snd_hda_activate_path(struct hda_codec *codec, struct nid_path *path,
bool enable, bool add_aamix);
struct snd_kcontrol_new *
snd_hda_gen_add_kctl(struct hda_gen_spec *spec, const char *name,
const struct snd_kcontrol_new *temp);
int snd_hda_gen_parse_auto_config(struct hda_codec *codec,
struct auto_pin_cfg *cfg);
int snd_hda_gen_build_controls(struct hda_codec *codec);
int snd_hda_gen_build_pcms(struct hda_codec *codec);
/* standard jack event callbacks */
void snd_hda_gen_hp_automute(struct hda_codec *codec,
struct hda_jack_callback *jack);
void snd_hda_gen_line_automute(struct hda_codec *codec,
struct hda_jack_callback *jack);
void snd_hda_gen_mic_autoswitch(struct hda_codec *codec,
struct hda_jack_callback *jack);
void snd_hda_gen_update_outputs(struct hda_codec *codec);
int snd_hda_gen_check_power_status(struct hda_codec *codec, hda_nid_t nid);
unsigned int snd_hda_gen_path_power_filter(struct hda_codec *codec,
hda_nid_t nid,
unsigned int power_state);
void snd_hda_gen_stream_pm(struct hda_codec *codec, hda_nid_t nid, bool on);
int snd_hda_gen_fix_pin_power(struct hda_codec *codec, hda_nid_t pin);
int snd_hda_gen_add_mute_led_cdev(struct hda_codec *codec,
int (*callback)(struct led_classdev *,
enum led_brightness));
int snd_hda_gen_add_micmute_led_cdev(struct hda_codec *codec,
int (*callback)(struct led_classdev *,
enum led_brightness));
bool snd_hda_gen_shutup_speakers(struct hda_codec *codec);
#endif /* __SOUND_HDA_GENERIC_H */

View file

@ -0,0 +1,195 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Jack-detection handling for HD-audio
*
* Copyright (c) 2011 Takashi Iwai <tiwai@suse.de>
*/
#ifndef __SOUND_HDA_JACK_H
#define __SOUND_HDA_JACK_H
#include <linux/err.h>
#include <sound/jack.h>
struct auto_pin_cfg;
struct hda_jack_tbl;
struct hda_jack_callback;
typedef void (*hda_jack_callback_fn) (struct hda_codec *, struct hda_jack_callback *);
struct hda_jack_callback {
hda_nid_t nid;
int dev_id;
hda_jack_callback_fn func;
unsigned int private_data; /* arbitrary data */
unsigned int unsol_res; /* unsolicited event bits */
struct hda_jack_tbl *jack; /* associated jack entry */
struct hda_jack_callback *next;
};
struct hda_jack_tbl {
hda_nid_t nid;
int dev_id;
unsigned char tag; /* unsol event tag */
struct hda_jack_callback *callback;
/* jack-detection stuff */
unsigned int pin_sense; /* cached pin-sense value */
unsigned int jack_detect:1; /* capable of jack-detection? */
unsigned int jack_dirty:1; /* needs to update? */
unsigned int phantom_jack:1; /* a fixed, always present port? */
unsigned int block_report:1; /* in a transitional state - do not report to userspace */
hda_nid_t gating_jack; /* valid when gating jack plugged */
hda_nid_t gated_jack; /* gated is dependent on this jack */
hda_nid_t key_report_jack; /* key reports to this jack */
int type;
int button_state;
struct snd_jack *jack;
};
struct hda_jack_keymap {
enum snd_jack_types type;
int key;
};
struct hda_jack_tbl *
snd_hda_jack_tbl_get_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id);
/**
* snd_hda_jack_tbl_get - query the jack-table entry for the given NID
* @codec: the HDA codec
* @nid: pin NID to refer to
*/
static inline struct hda_jack_tbl *
snd_hda_jack_tbl_get(struct hda_codec *codec, hda_nid_t nid)
{
return snd_hda_jack_tbl_get_mst(codec, nid, 0);
}
struct hda_jack_tbl *
snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec,
unsigned char tag, int dev_id);
void snd_hda_jack_tbl_disconnect(struct hda_codec *codec);
void snd_hda_jack_tbl_clear(struct hda_codec *codec);
void snd_hda_jack_set_dirty_all(struct hda_codec *codec);
int snd_hda_jack_detect_enable(struct hda_codec *codec, hda_nid_t nid,
int dev_id);
struct hda_jack_callback *
snd_hda_jack_detect_enable_callback_mst(struct hda_codec *codec, hda_nid_t nid,
int dev_id, hda_jack_callback_fn func);
/**
* snd_hda_jack_detect_enable - enable the jack-detection
* @codec: the HDA codec
* @nid: pin NID to enable
* @func: callback function to register
*
* In the case of error, the return value will be a pointer embedded with
* errno. Check and handle the return value appropriately with standard
* macros such as @IS_ERR() and @PTR_ERR().
*/
static inline struct hda_jack_callback *
snd_hda_jack_detect_enable_callback(struct hda_codec *codec, hda_nid_t nid,
hda_jack_callback_fn cb)
{
return snd_hda_jack_detect_enable_callback_mst(codec, nid, 0, cb);
}
int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid,
hda_nid_t gating_nid);
int snd_hda_jack_bind_keymap(struct hda_codec *codec, hda_nid_t key_nid,
const struct hda_jack_keymap *keymap,
hda_nid_t jack_nid);
void snd_hda_jack_set_button_state(struct hda_codec *codec, hda_nid_t jack_nid,
int button_state);
u32 snd_hda_jack_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id);
/* the jack state returned from snd_hda_jack_detect_state() */
enum {
HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT, HDA_JACK_PHANTOM,
};
int snd_hda_jack_detect_state_mst(struct hda_codec *codec, hda_nid_t nid,
int dev_id);
/**
* snd_hda_jack_detect_state - query pin Presence Detect status
* @codec: the CODEC to sense
* @nid: the pin NID to sense
*
* Query and return the pin's Presence Detect status, as either
* HDA_JACK_NOT_PRESENT, HDA_JACK_PRESENT or HDA_JACK_PHANTOM.
*/
static inline int
snd_hda_jack_detect_state(struct hda_codec *codec, hda_nid_t nid)
{
return snd_hda_jack_detect_state_mst(codec, nid, 0);
}
/**
* snd_hda_jack_detect_mst - Detect the jack
* @codec: the HDA codec
* @nid: pin NID to check jack detection
* @dev_id: pin device entry id
*/
static inline bool
snd_hda_jack_detect_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id)
{
return snd_hda_jack_detect_state_mst(codec, nid, dev_id) !=
HDA_JACK_NOT_PRESENT;
}
/**
* snd_hda_jack_detect - Detect the jack
* @codec: the HDA codec
* @nid: pin NID to check jack detection
*/
static inline bool
snd_hda_jack_detect(struct hda_codec *codec, hda_nid_t nid)
{
return snd_hda_jack_detect_mst(codec, nid, 0);
}
bool is_jack_detectable(struct hda_codec *codec, hda_nid_t nid);
int snd_hda_jack_add_kctl_mst(struct hda_codec *codec, hda_nid_t nid,
int dev_id, const char *name, bool phantom_jack,
int type, const struct hda_jack_keymap *keymap);
/**
* snd_hda_jack_add_kctl - Add a kctl for the given pin
* @codec: the HDA codec
* @nid: pin NID to assign
* @name: string name for the jack
* @phantom_jack: flag to deal as a phantom jack
* @type: jack type bits to be reported, 0 for guessing from pincfg
* @keymap: optional jack / key mapping
*
* This assigns a jack-detection kctl to the given pin. The kcontrol
* will have the given name and index.
*/
static inline int
snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid,
const char *name, bool phantom_jack,
int type, const struct hda_jack_keymap *keymap)
{
return snd_hda_jack_add_kctl_mst(codec, nid, 0,
name, phantom_jack, type, keymap);
}
int snd_hda_jack_add_kctls(struct hda_codec *codec,
const struct auto_pin_cfg *cfg);
void snd_hda_jack_report_sync(struct hda_codec *codec);
void snd_hda_jack_unsol_event(struct hda_codec *codec, unsigned int res);
void snd_hda_jack_poll_all(struct hda_codec *codec);
#endif /* __SOUND_HDA_JACK_H */

View file

@ -0,0 +1,763 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Universal Interface for Intel High Definition Audio Codec
*
* Local helper functions
*
* Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
*/
#ifndef __SOUND_HDA_LOCAL_H
#define __SOUND_HDA_LOCAL_H
/* We abuse kcontrol_new.subdev field to pass the NID corresponding to
* the given new control. If id.subdev has a bit flag HDA_SUBDEV_NID_FLAG,
* snd_hda_ctl_add() takes the lower-bit subdev value as a valid NID.
*
* Note that the subdevice field is cleared again before the real registration
* in snd_hda_ctl_add(), so that this value won't appear in the outside.
*/
#define HDA_SUBDEV_NID_FLAG (1U << 31)
#define HDA_SUBDEV_AMP_FLAG (1U << 30)
/*
* for mixer controls
*/
#define HDA_COMPOSE_AMP_VAL_OFS(nid,chs,idx,dir,ofs) \
((nid) | ((chs)<<16) | ((dir)<<18) | ((idx)<<19) | ((ofs)<<23))
#define HDA_AMP_VAL_MIN_MUTE (1<<29)
#define HDA_COMPOSE_AMP_VAL(nid,chs,idx,dir) \
HDA_COMPOSE_AMP_VAL_OFS(nid, chs, idx, dir, 0)
/* mono volume with index (index=0,1,...) (channel=1,2) */
#define HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, channel, xindex, dir, flags) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
.subdevice = HDA_SUBDEV_AMP_FLAG, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \
.info = snd_hda_mixer_amp_volume_info, \
.get = snd_hda_mixer_amp_volume_get, \
.put = snd_hda_mixer_amp_volume_put, \
.tlv = { .c = snd_hda_mixer_amp_tlv }, \
.private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, dir) | flags }
/* stereo volume with index */
#define HDA_CODEC_VOLUME_IDX(xname, xcidx, nid, xindex, direction) \
HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, 3, xindex, direction, 0)
/* mono volume */
#define HDA_CODEC_VOLUME_MONO(xname, nid, channel, xindex, direction) \
HDA_CODEC_VOLUME_MONO_IDX(xname, 0, nid, channel, xindex, direction, 0)
/* stereo volume */
#define HDA_CODEC_VOLUME(xname, nid, xindex, direction) \
HDA_CODEC_VOLUME_MONO(xname, nid, 3, xindex, direction)
/* stereo volume with min=mute */
#define HDA_CODEC_VOLUME_MIN_MUTE(xname, nid, xindex, direction) \
HDA_CODEC_VOLUME_MONO_IDX(xname, 0, nid, 3, xindex, direction, \
HDA_AMP_VAL_MIN_MUTE)
/* mono mute switch with index (index=0,1,...) (channel=1,2) */
#define HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
.subdevice = HDA_SUBDEV_AMP_FLAG, \
.info = snd_hda_mixer_amp_switch_info, \
.get = snd_hda_mixer_amp_switch_get, \
.put = snd_hda_mixer_amp_switch_put, \
.private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, direction) }
/* stereo mute switch with index */
#define HDA_CODEC_MUTE_IDX(xname, xcidx, nid, xindex, direction) \
HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, 3, xindex, direction)
/* mono mute switch */
#define HDA_CODEC_MUTE_MONO(xname, nid, channel, xindex, direction) \
HDA_CODEC_MUTE_MONO_IDX(xname, 0, nid, channel, xindex, direction)
/* stereo mute switch */
#define HDA_CODEC_MUTE(xname, nid, xindex, direction) \
HDA_CODEC_MUTE_MONO(xname, nid, 3, xindex, direction)
#ifdef CONFIG_SND_HDA_INPUT_BEEP
/* special beep mono mute switch with index (index=0,1,...) (channel=1,2) */
#define HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
.subdevice = HDA_SUBDEV_AMP_FLAG, \
.info = snd_hda_mixer_amp_switch_info, \
.get = snd_hda_mixer_amp_switch_get_beep, \
.put = snd_hda_mixer_amp_switch_put_beep, \
.private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, direction) }
#else
/* no digital beep - just the standard one */
#define HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, xcidx, nid, ch, xidx, dir) \
HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, ch, xidx, dir)
#endif /* CONFIG_SND_HDA_INPUT_BEEP */
/* special beep mono mute switch */
#define HDA_CODEC_MUTE_BEEP_MONO(xname, nid, channel, xindex, direction) \
HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, 0, nid, channel, xindex, direction)
/* special beep stereo mute switch */
#define HDA_CODEC_MUTE_BEEP(xname, nid, xindex, direction) \
HDA_CODEC_MUTE_BEEP_MONO(xname, nid, 3, xindex, direction)
extern const char *snd_hda_pcm_type_name[];
int snd_hda_mixer_amp_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo);
int snd_hda_mixer_amp_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int snd_hda_mixer_amp_tlv(struct snd_kcontrol *kcontrol, int op_flag,
unsigned int size, unsigned int __user *_tlv);
int snd_hda_mixer_amp_switch_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo);
int snd_hda_mixer_amp_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
#ifdef CONFIG_SND_HDA_INPUT_BEEP
int snd_hda_mixer_amp_switch_get_beep(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int snd_hda_mixer_amp_switch_put_beep(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
#endif
/* lowlevel accessor with caching; use carefully */
#define snd_hda_codec_amp_read(codec, nid, ch, dir, idx) \
snd_hdac_regmap_get_amp(&(codec)->core, nid, ch, dir, idx)
int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid,
int ch, int dir, int idx, int mask, int val);
int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid,
int direction, int idx, int mask, int val);
int snd_hda_codec_amp_init(struct hda_codec *codec, hda_nid_t nid, int ch,
int direction, int idx, int mask, int val);
int snd_hda_codec_amp_init_stereo(struct hda_codec *codec, hda_nid_t nid,
int dir, int idx, int mask, int val);
void snd_hda_set_vmaster_tlv(struct hda_codec *codec, hda_nid_t nid, int dir,
unsigned int *tlv);
struct snd_kcontrol *snd_hda_find_mixer_ctl(struct hda_codec *codec,
const char *name);
int __snd_hda_add_vmaster(struct hda_codec *codec, char *name,
unsigned int *tlv, const char * const *followers,
const char *suffix, bool init_follower_vol,
unsigned int access, struct snd_kcontrol **ctl_ret);
#define snd_hda_add_vmaster(codec, name, tlv, followers, suffix, access) \
__snd_hda_add_vmaster(codec, name, tlv, followers, suffix, true, access, NULL)
int snd_hda_codec_reset(struct hda_codec *codec);
void snd_hda_codec_disconnect_pcms(struct hda_codec *codec);
#define snd_hda_regmap_sync(codec) snd_hdac_regmap_sync(&(codec)->core)
struct hda_vmaster_mute_hook {
/* below two fields must be filled by the caller of
* snd_hda_add_vmaster_hook() beforehand
*/
struct snd_kcontrol *sw_kctl;
void (*hook)(void *, int);
/* below are initialized automatically */
struct hda_codec *codec;
};
int snd_hda_add_vmaster_hook(struct hda_codec *codec,
struct hda_vmaster_mute_hook *hook);
void snd_hda_sync_vmaster_hook(struct hda_vmaster_mute_hook *hook);
/* amp value bits */
#define HDA_AMP_MUTE 0x80
#define HDA_AMP_UNMUTE 0x00
#define HDA_AMP_VOLMASK 0x7f
/*
* SPDIF I/O
*/
int snd_hda_create_dig_out_ctls(struct hda_codec *codec,
hda_nid_t associated_nid,
hda_nid_t cvt_nid, int type);
#define snd_hda_create_spdif_out_ctls(codec, anid, cnid) \
snd_hda_create_dig_out_ctls(codec, anid, cnid, HDA_PCM_TYPE_SPDIF)
int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid);
/*
* input MUX helper
*/
#define HDA_MAX_NUM_INPUTS 36
struct hda_input_mux_item {
char label[32];
unsigned int index;
};
struct hda_input_mux {
unsigned int num_items;
struct hda_input_mux_item items[HDA_MAX_NUM_INPUTS];
};
int snd_hda_input_mux_info(const struct hda_input_mux *imux,
struct snd_ctl_elem_info *uinfo);
int snd_hda_input_mux_put(struct hda_codec *codec,
const struct hda_input_mux *imux,
struct snd_ctl_elem_value *ucontrol, hda_nid_t nid,
unsigned int *cur_val);
int snd_hda_add_imux_item(struct hda_codec *codec,
struct hda_input_mux *imux, const char *label,
int index, int *type_idx);
/*
* Multi-channel / digital-out PCM helper
*/
enum { HDA_FRONT, HDA_REAR, HDA_CLFE, HDA_SIDE }; /* index for dac_nidx */
enum { HDA_DIG_NONE, HDA_DIG_EXCLUSIVE, HDA_DIG_ANALOG_DUP }; /* dig_out_used */
#define HDA_MAX_OUTS 5
struct hda_multi_out {
int num_dacs; /* # of DACs, must be more than 1 */
const hda_nid_t *dac_nids; /* DAC list */
hda_nid_t hp_nid; /* optional DAC for HP, 0 when not exists */
hda_nid_t hp_out_nid[HDA_MAX_OUTS]; /* DACs for multiple HPs */
hda_nid_t extra_out_nid[HDA_MAX_OUTS]; /* other (e.g. speaker) DACs */
hda_nid_t dig_out_nid; /* digital out audio widget */
const hda_nid_t *follower_dig_outs;
int max_channels; /* currently supported analog channels */
int dig_out_used; /* current usage of digital out (HDA_DIG_XXX) */
int no_share_stream; /* don't share a stream with multiple pins */
int share_spdif; /* share SPDIF pin */
/* PCM information for both analog and SPDIF DACs */
unsigned int analog_rates;
unsigned int analog_maxbps;
u64 analog_formats;
unsigned int spdif_rates;
unsigned int spdif_maxbps;
u64 spdif_formats;
};
int snd_hda_create_spdif_share_sw(struct hda_codec *codec,
struct hda_multi_out *mout);
int snd_hda_multi_out_dig_open(struct hda_codec *codec,
struct hda_multi_out *mout);
int snd_hda_multi_out_dig_close(struct hda_codec *codec,
struct hda_multi_out *mout);
int snd_hda_multi_out_dig_prepare(struct hda_codec *codec,
struct hda_multi_out *mout,
unsigned int stream_tag,
unsigned int format,
struct snd_pcm_substream *substream);
int snd_hda_multi_out_dig_cleanup(struct hda_codec *codec,
struct hda_multi_out *mout);
int snd_hda_multi_out_analog_open(struct hda_codec *codec,
struct hda_multi_out *mout,
struct snd_pcm_substream *substream,
struct hda_pcm_stream *hinfo);
int snd_hda_multi_out_analog_prepare(struct hda_codec *codec,
struct hda_multi_out *mout,
unsigned int stream_tag,
unsigned int format,
struct snd_pcm_substream *substream);
int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec,
struct hda_multi_out *mout);
/*
* generic proc interface
*/
#ifdef CONFIG_SND_PROC_FS
int snd_hda_codec_proc_new(struct hda_codec *codec);
#else
static inline int snd_hda_codec_proc_new(struct hda_codec *codec) { return 0; }
#endif
#define SND_PRINT_BITS_ADVISED_BUFSIZE 16
void snd_print_pcm_bits(int pcm, char *buf, int buflen);
/*
* Misc
*/
int snd_hda_add_new_ctls(struct hda_codec *codec,
const struct snd_kcontrol_new *knew);
/*
* Fix-up pin default configurations and add default verbs
*/
struct hda_pintbl {
hda_nid_t nid;
u32 val;
};
struct hda_model_fixup {
const int id;
const char *name;
};
struct hda_fixup {
int type;
bool chained:1; /* call the chained fixup(s) after this */
bool chained_before:1; /* call the chained fixup(s) before this */
int chain_id;
union {
const struct hda_pintbl *pins;
const struct hda_verb *verbs;
void (*func)(struct hda_codec *codec,
const struct hda_fixup *fix,
int action);
} v;
};
/*
* extended form of snd_pci_quirk:
* for PCI SSID matching, use SND_PCI_QUIRK() like before;
* for codec SSID matching, use the new HDA_CODEC_QUIRK() instead
*/
struct hda_quirk {
unsigned short subvendor; /* PCI subvendor ID */
unsigned short subdevice; /* PCI subdevice ID */
unsigned short subdevice_mask; /* bitmask to match */
bool match_codec_ssid; /* match only with codec SSID */
int value; /* value */
#ifdef CONFIG_SND_DEBUG_VERBOSE
const char *name; /* name of the device (optional) */
#endif
};
#ifdef CONFIG_SND_DEBUG_VERBOSE
#define HDA_CODEC_QUIRK(vend, dev, xname, val) \
{ _SND_PCI_QUIRK_ID(vend, dev), .value = (val), .name = (xname),\
.match_codec_ssid = true }
#else
#define HDA_CODEC_QUIRK(vend, dev, xname, val) \
{ _SND_PCI_QUIRK_ID(vend, dev), .value = (val), \
.match_codec_ssid = true }
#endif
struct snd_hda_pin_quirk {
unsigned int codec; /* Codec vendor/device ID */
unsigned short subvendor; /* PCI subvendor ID */
const struct hda_pintbl *pins; /* list of matching pins */
#ifdef CONFIG_SND_DEBUG_VERBOSE
const char *name;
#endif
int value; /* quirk value */
};
#ifdef CONFIG_SND_DEBUG_VERBOSE
#define SND_HDA_PIN_QUIRK(_codec, _subvendor, _name, _value, _pins...) \
{ .codec = _codec,\
.subvendor = _subvendor,\
.name = _name,\
.value = _value,\
.pins = (const struct hda_pintbl[]) { _pins, {0, 0}} \
}
#else
#define SND_HDA_PIN_QUIRK(_codec, _subvendor, _name, _value, _pins...) \
{ .codec = _codec,\
.subvendor = _subvendor,\
.value = _value,\
.pins = (const struct hda_pintbl[]) { _pins, {0, 0}} \
}
#endif
#define HDA_FIXUP_ID_NOT_SET -1
#define HDA_FIXUP_ID_NO_FIXUP -2
/* fixup types */
enum {
HDA_FIXUP_INVALID,
HDA_FIXUP_PINS,
HDA_FIXUP_VERBS,
HDA_FIXUP_FUNC,
HDA_FIXUP_PINCTLS,
};
/* fixup action definitions */
enum {
HDA_FIXUP_ACT_PRE_PROBE,
HDA_FIXUP_ACT_PROBE,
HDA_FIXUP_ACT_INIT,
HDA_FIXUP_ACT_BUILD,
HDA_FIXUP_ACT_FREE,
};
int snd_hda_add_verbs(struct hda_codec *codec, const struct hda_verb *list);
void snd_hda_apply_verbs(struct hda_codec *codec);
void snd_hda_apply_pincfgs(struct hda_codec *codec,
const struct hda_pintbl *cfg);
void snd_hda_apply_fixup(struct hda_codec *codec, int action);
void __snd_hda_apply_fixup(struct hda_codec *codec, int id, int action, int depth);
void snd_hda_pick_fixup(struct hda_codec *codec,
const struct hda_model_fixup *models,
const struct hda_quirk *quirk,
const struct hda_fixup *fixlist);
void snd_hda_pick_pin_fixup(struct hda_codec *codec,
const struct snd_hda_pin_quirk *pin_quirk,
const struct hda_fixup *fixlist,
bool match_all_pins);
/* helper macros to retrieve pin default-config values */
#define get_defcfg_connect(cfg) \
((cfg & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT)
#define get_defcfg_association(cfg) \
((cfg & AC_DEFCFG_DEF_ASSOC) >> AC_DEFCFG_ASSOC_SHIFT)
#define get_defcfg_location(cfg) \
((cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT)
#define get_defcfg_sequence(cfg) \
(cfg & AC_DEFCFG_SEQUENCE)
#define get_defcfg_device(cfg) \
((cfg & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT)
#define get_defcfg_misc(cfg) \
((cfg & AC_DEFCFG_MISC) >> AC_DEFCFG_MISC_SHIFT)
/* amp values */
#define AMP_IN_MUTE(idx) (0x7080 | ((idx)<<8))
#define AMP_IN_UNMUTE(idx) (0x7000 | ((idx)<<8))
#define AMP_OUT_MUTE 0xb080
#define AMP_OUT_UNMUTE 0xb000
#define AMP_OUT_ZERO 0xb000
/* pinctl values */
#define PIN_IN (AC_PINCTL_IN_EN)
#define PIN_VREFHIZ (AC_PINCTL_IN_EN | AC_PINCTL_VREF_HIZ)
#define PIN_VREF50 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_50)
#define PIN_VREFGRD (AC_PINCTL_IN_EN | AC_PINCTL_VREF_GRD)
#define PIN_VREF80 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_80)
#define PIN_VREF100 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_100)
#define PIN_OUT (AC_PINCTL_OUT_EN)
#define PIN_HP (AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN)
#define PIN_HP_AMP (AC_PINCTL_HP_EN)
unsigned int snd_hda_get_default_vref(struct hda_codec *codec, hda_nid_t pin);
unsigned int snd_hda_correct_pin_ctl(struct hda_codec *codec,
hda_nid_t pin, unsigned int val);
int _snd_hda_set_pin_ctl(struct hda_codec *codec, hda_nid_t pin,
unsigned int val, bool cached);
/**
* _snd_hda_set_pin_ctl - Set a pin-control value safely
* @codec: the codec instance
* @pin: the pin NID to set the control
* @val: the pin-control value (AC_PINCTL_* bits)
*
* This function sets the pin-control value to the given pin, but
* filters out the invalid pin-control bits when the pin has no such
* capabilities. For example, when PIN_HP is passed but the pin has no
* HP-drive capability, the HP bit is omitted.
*
* The function doesn't check the input VREF capability bits, though.
* Use snd_hda_get_default_vref() to guess the right value.
* Also, this function is only for analog pins, not for HDMI pins.
*/
static inline int
snd_hda_set_pin_ctl(struct hda_codec *codec, hda_nid_t pin, unsigned int val)
{
return _snd_hda_set_pin_ctl(codec, pin, val, false);
}
/**
* snd_hda_set_pin_ctl_cache - Set a pin-control value safely
* @codec: the codec instance
* @pin: the pin NID to set the control
* @val: the pin-control value (AC_PINCTL_* bits)
*
* Just like snd_hda_set_pin_ctl() but write to cache as well.
*/
static inline int
snd_hda_set_pin_ctl_cache(struct hda_codec *codec, hda_nid_t pin,
unsigned int val)
{
return _snd_hda_set_pin_ctl(codec, pin, val, true);
}
int snd_hda_codec_get_pin_target(struct hda_codec *codec, hda_nid_t nid);
int snd_hda_codec_set_pin_target(struct hda_codec *codec, hda_nid_t nid,
unsigned int val);
#define for_each_hda_codec_node(nid, codec) \
for ((nid) = (codec)->core.start_nid; (nid) < (codec)->core.end_nid; (nid)++)
/* Set the codec power_state flag to indicate to allow unsol event handling;
* see hda_codec_unsol_event() in hda_bind.c. Calling this might confuse the
* state tracking, so use with care.
*/
static inline void snd_hda_codec_allow_unsol_events(struct hda_codec *codec)
{
codec->core.dev.power.power_state = PMSG_ON;
}
/*
* get widget capabilities
*/
static inline u32 get_wcaps(struct hda_codec *codec, hda_nid_t nid)
{
if (nid < codec->core.start_nid ||
nid >= codec->core.start_nid + codec->core.num_nodes)
return 0;
return codec->wcaps[nid - codec->core.start_nid];
}
/* get the widget type from widget capability bits */
static inline int get_wcaps_type(unsigned int wcaps)
{
if (!wcaps)
return -1; /* invalid type */
return (wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
}
static inline unsigned int get_wcaps_channels(u32 wcaps)
{
unsigned int chans;
chans = (wcaps & AC_WCAP_CHAN_CNT_EXT) >> 13;
chans = ((chans << 1) | 1) + 1;
return chans;
}
static inline void snd_hda_override_wcaps(struct hda_codec *codec,
hda_nid_t nid, u32 val)
{
if (nid >= codec->core.start_nid &&
nid < codec->core.start_nid + codec->core.num_nodes)
codec->wcaps[nid - codec->core.start_nid] = val;
}
u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction);
int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir,
unsigned int caps);
/**
* snd_hda_query_pin_caps - Query PIN capabilities
* @codec: the HD-auio codec
* @nid: the NID to query
*
* Query PIN capabilities for the given widget.
* Returns the obtained capability bits.
*
* When cap bits have been already read, this doesn't read again but
* returns the cached value.
*/
static inline u32
snd_hda_query_pin_caps(struct hda_codec *codec, hda_nid_t nid)
{
return snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP);
}
/**
* snd_hda_override_pin_caps - Override the pin capabilities
* @codec: the CODEC
* @nid: the NID to override
* @caps: the capability bits to set
*
* Override the cached PIN capabilitiy bits value by the given one.
*
* Returns zero if successful or a negative error code.
*/
static inline int
snd_hda_override_pin_caps(struct hda_codec *codec, hda_nid_t nid,
unsigned int caps)
{
return snd_hdac_override_parm(&codec->core, nid, AC_PAR_PIN_CAP, caps);
}
bool snd_hda_check_amp_caps(struct hda_codec *codec, hda_nid_t nid,
int dir, unsigned int bits);
#define nid_has_mute(codec, nid, dir) \
snd_hda_check_amp_caps(codec, nid, dir, (AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE))
#define nid_has_volume(codec, nid, dir) \
snd_hda_check_amp_caps(codec, nid, dir, AC_AMPCAP_NUM_STEPS)
/* flags for hda_nid_item */
#define HDA_NID_ITEM_AMP (1<<0)
struct hda_nid_item {
struct snd_kcontrol *kctl;
unsigned int index;
hda_nid_t nid;
unsigned short flags;
};
int snd_hda_ctl_add(struct hda_codec *codec, hda_nid_t nid,
struct snd_kcontrol *kctl);
int snd_hda_add_nid(struct hda_codec *codec, struct snd_kcontrol *kctl,
unsigned int index, hda_nid_t nid);
void snd_hda_ctls_clear(struct hda_codec *codec);
/*
* hwdep interface
*/
#ifdef CONFIG_SND_HDA_HWDEP
int snd_hda_create_hwdep(struct hda_codec *codec);
#else
static inline int snd_hda_create_hwdep(struct hda_codec *codec) { return 0; }
#endif
void snd_hda_sysfs_init(struct hda_codec *codec);
void snd_hda_sysfs_clear(struct hda_codec *codec);
extern const struct attribute_group *snd_hda_dev_attr_groups[];
#ifdef CONFIG_SND_HDA_RECONFIG
const char *snd_hda_get_hint(struct hda_codec *codec, const char *key);
int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key);
int snd_hda_get_int_hint(struct hda_codec *codec, const char *key, int *valp);
#else
static inline
const char *snd_hda_get_hint(struct hda_codec *codec, const char *key)
{
return NULL;
}
static inline
int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key)
{
return -ENOENT;
}
static inline
int snd_hda_get_int_hint(struct hda_codec *codec, const char *key, int *valp)
{
return -ENOENT;
}
#endif
/*
* power-management
*/
void snd_hda_schedule_power_save(struct hda_codec *codec);
struct hda_amp_list {
hda_nid_t nid;
unsigned char dir;
unsigned char idx;
};
struct hda_loopback_check {
const struct hda_amp_list *amplist;
int power_on;
};
int snd_hda_check_amp_list_power(struct hda_codec *codec,
struct hda_loopback_check *check,
hda_nid_t nid);
/* check whether the actual power state matches with the target state */
static inline bool
snd_hda_check_power_state(struct hda_codec *codec, hda_nid_t nid,
unsigned int target_state)
{
return snd_hdac_check_power_state(&codec->core, nid, target_state);
}
static inline unsigned int snd_hda_sync_power_state(struct hda_codec *codec,
hda_nid_t nid,
unsigned int target_state)
{
return snd_hdac_sync_power_state(&codec->core, nid, target_state);
}
unsigned int snd_hda_codec_eapd_power_filter(struct hda_codec *codec,
hda_nid_t nid,
unsigned int power_state);
void snd_hda_codec_shutdown(struct hda_codec *codec);
/*
* AMP control callbacks
*/
/* retrieve parameters from private_value */
#define get_amp_nid_(pv) ((pv) & 0xffff)
#define get_amp_nid(kc) get_amp_nid_((kc)->private_value)
#define get_amp_channels(kc) (((kc)->private_value >> 16) & 0x3)
#define get_amp_direction_(pv) (((pv) >> 18) & 0x1)
#define get_amp_direction(kc) get_amp_direction_((kc)->private_value)
#define get_amp_index_(pv) (((pv) >> 19) & 0xf)
#define get_amp_index(kc) get_amp_index_((kc)->private_value)
#define get_amp_offset(kc) (((kc)->private_value >> 23) & 0x3f)
#define get_amp_min_mute(kc) (((kc)->private_value >> 29) & 0x1)
/*
* enum control helper
*/
int snd_hda_enum_helper_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo,
int num_items, const char * const *texts);
#define snd_hda_enum_bool_helper_info(kcontrol, uinfo) \
snd_hda_enum_helper_info(kcontrol, uinfo, 0, NULL)
/*
* CEA Short Audio Descriptor data
*/
struct cea_sad {
int channels;
int format; /* (format == 0) indicates invalid SAD */
int rates;
int sample_bits; /* for LPCM */
int max_bitrate; /* for AC3...ATRAC */
int profile; /* for WMAPRO */
};
#define ELD_FIXED_BYTES 20
#define ELD_MAX_SIZE 256
#define ELD_MAX_MNL 16
#define ELD_MAX_SAD 16
/*
* ELD: EDID Like Data
*/
struct parsed_hdmi_eld {
/*
* all fields will be cleared before updating ELD
*/
int baseline_len;
int eld_ver;
int cea_edid_ver;
char monitor_name[ELD_MAX_MNL + 1];
int manufacture_id;
int product_id;
u64 port_id;
int support_hdcp;
int support_ai;
int conn_type;
int aud_synch_delay;
int spk_alloc;
int sad_count;
struct cea_sad sad[ELD_MAX_SAD];
};
struct hdmi_eld {
bool monitor_present;
bool eld_valid;
int eld_size;
char eld_buffer[ELD_MAX_SIZE];
struct parsed_hdmi_eld info;
};
int snd_hdmi_get_eld_size(struct hda_codec *codec, hda_nid_t nid);
int snd_hdmi_get_eld(struct hda_codec *codec, hda_nid_t nid,
unsigned char *buf, int *eld_size);
int snd_hdmi_parse_eld(struct hda_codec *codec, struct parsed_hdmi_eld *e,
const unsigned char *buf, int size);
void snd_hdmi_show_eld(struct hda_codec *codec, struct parsed_hdmi_eld *e);
void snd_hdmi_eld_update_pcm_info(struct parsed_hdmi_eld *e,
struct hda_pcm_stream *hinfo);
int snd_hdmi_get_eld_ati(struct hda_codec *codec, hda_nid_t nid,
unsigned char *buf, int *eld_size,
bool rev3_or_later);
#ifdef CONFIG_SND_PROC_FS
void snd_hdmi_print_eld_info(struct hdmi_eld *eld,
struct snd_info_buffer *buffer,
hda_nid_t pin_nid, int dev_id, hda_nid_t cvt_nid);
void snd_hdmi_write_eld_info(struct hdmi_eld *eld,
struct snd_info_buffer *buffer);
#endif
#define SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE 80
void snd_print_channel_allocation(int spk_alloc, char *buf, int buflen);
void snd_hda_codec_display_power(struct hda_codec *codec, bool enable);
/*
*/
#define codec_err(codec, fmt, args...) \
dev_err(hda_codec_dev(codec), fmt, ##args)
#define codec_warn(codec, fmt, args...) \
dev_warn(hda_codec_dev(codec), fmt, ##args)
#define codec_info(codec, fmt, args...) \
dev_info(hda_codec_dev(codec), fmt, ##args)
#define codec_dbg(codec, fmt, args...) \
dev_dbg(hda_codec_dev(codec), fmt, ##args)
#endif /* __SOUND_HDA_LOCAL_H */

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,432 @@
/* playback mute control with the software mute bit check */
static void sync_auto_mute_bits(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct hda_gen_spec *spec = codec->spec;
if (spec->auto_mute_via_amp) {
hda_nid_t nid = get_amp_nid(kcontrol);
bool enabled = !((spec->mute_bits >> nid) & 1);
ucontrol->value.integer.value[0] &= enabled;
ucontrol->value.integer.value[1] &= enabled;
}
}
static int hda_gen_mixer_mute_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
sync_auto_mute_bits(kcontrol, ucontrol);
return snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
}
/*
* Bound mute controls
*/
#define AMP_VAL_IDX_SHIFT 19
#define AMP_VAL_IDX_MASK (0x0f<<19)
static int hda_gen_bind_mute_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
unsigned long pval;
int err;
mutex_lock(&codec->control_mutex);
pval = kcontrol->private_value;
kcontrol->private_value = pval & ~AMP_VAL_IDX_MASK; /* index 0 */
err = snd_hda_mixer_amp_switch_get(kcontrol, ucontrol);
kcontrol->private_value = pval;
mutex_unlock(&codec->control_mutex);
return err;
}
static int hda_gen_bind_mute_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
unsigned long pval;
int i, indices, err = 0, change = 0;
sync_auto_mute_bits(kcontrol, ucontrol);
mutex_lock(&codec->control_mutex);
pval = kcontrol->private_value;
indices = (pval & AMP_VAL_IDX_MASK) >> AMP_VAL_IDX_SHIFT;
for (i = 0; i < indices; i++) {
kcontrol->private_value = (pval & ~AMP_VAL_IDX_MASK) |
(i << AMP_VAL_IDX_SHIFT);
err = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
if (err < 0)
break;
change |= err;
}
kcontrol->private_value = pval;
mutex_unlock(&codec->control_mutex);
return err < 0 ? err : change;
}
enum {
HDA_CTL_WIDGET_VOL,
HDA_CTL_WIDGET_MUTE,
HDA_CTL_BIND_MUTE,
};
static const struct snd_kcontrol_new control_templates[] = {
HDA_CODEC_VOLUME(NULL, 0, 0, 0),
/* only the put callback is replaced for handling the special mute */
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.subdevice = HDA_SUBDEV_AMP_FLAG,
.info = snd_hda_mixer_amp_switch_info,
.get = snd_hda_mixer_amp_switch_get,
.put = hda_gen_mixer_mute_put, /* replaced */
.private_value = HDA_COMPOSE_AMP_VAL(0, 3, 0, 0),
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.info = snd_hda_mixer_amp_switch_info,
.get = hda_gen_bind_mute_get,
.put = hda_gen_bind_mute_put, /* replaced */
.private_value = HDA_COMPOSE_AMP_VAL(0, 3, 0, 0),
},
};
/* add the powersave loopback-list entry */
static int add_loopback_list(struct hda_gen_spec *spec, hda_nid_t mix, int idx)
{
struct hda_amp_list *list;
list = snd_array_new(&spec->loopback_list);
if (!list)
return -ENOMEM;
list->nid = mix;
list->dir = HDA_INPUT;
list->idx = idx;
spec->loopback.amplist = spec->loopback_list.list;
return 0;
}
static int is_input_pin(struct hda_codec *codec, hda_nid_t nid)
{
unsigned int pincap = snd_hda_query_pin_caps(codec, nid);
return (pincap & AC_PINCAP_IN) != 0;
}
//static int create_in_jack_mode(struct hda_codec *codec, hda_nid_t pin)
//{
// struct hda_gen_spec *spec = codec->spec;
// struct snd_kcontrol_new *knew;
// char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
// unsigned int defcfg;
//
// if (pin == spec->hp_mic_pin)
// return 0; /* already done in create_out_jack_mode() */
//
// /* no jack mode for fixed pins */
// defcfg = snd_hda_codec_get_pincfg(codec, pin);
// if (snd_hda_get_input_pin_attr(defcfg) == INPUT_PIN_ATTR_INT)
// return 0;
//
// /* no multiple vref caps? */
// if (get_in_jack_num_items(codec, pin) <= 1)
// return 0;
//
// get_jack_mode_name(codec, pin, name, sizeof(name));
// knew = snd_hda_gen_add_kctl(spec, name, &in_jack_mode_enum);
// if (!knew)
// return -ENOMEM;
// knew->private_value = pin;
// return 0;
//}
static void print_nid_path(struct hda_codec *codec,
const char *pfx, struct nid_path *path)
{
char buf[40];
char *pos = buf;
int i;
*pos = 0;
for (i = 0; i < path->depth; i++)
pos += scnprintf(pos, sizeof(buf) - (pos - buf), "%s%02x",
pos != buf ? ":" : "",
path->path[i]);
codec_dbg(codec, "%s path: depth=%d '%s'\n", pfx, path->depth, buf);
}
/* check whether the given two widgets can be connected */
static bool is_reachable_path(struct hda_codec *codec,
hda_nid_t from_nid, hda_nid_t to_nid)
{
if (!from_nid || !to_nid)
return false;
return snd_hda_get_conn_index(codec, to_nid, from_nid, true) >= 0;
}
/* fill the label for each input at first */
static int fill_input_pin_labels(struct hda_codec *codec)
{
struct hda_gen_spec *spec = codec->spec;
const struct auto_pin_cfg *cfg = &spec->autocfg;
int i;
for (i = 0; i < cfg->num_inputs; i++) {
hda_nid_t pin = cfg->inputs[i].pin;
const char *label;
int j, idx;
if (!is_input_pin(codec, pin))
continue;
label = hda_get_autocfg_input_label(codec, cfg, i);
idx = 0;
for (j = i - 1; j >= 0; j--) {
if (spec->input_labels[j] &&
!strcmp(spec->input_labels[j], label)) {
idx = spec->input_label_idxs[j] + 1;
break;
}
}
spec->input_labels[i] = label;
spec->input_label_idxs[i] = idx;
}
return 0;
}
/* Parse the codec tree and retrieve ADCs */
static int fill_adc_nids(struct hda_codec *codec)
{
struct hda_gen_spec *spec = codec->spec;
hda_nid_t nid;
hda_nid_t *adc_nids = spec->adc_nids;
int max_nums = ARRAY_SIZE(spec->adc_nids);
int nums = 0;
for_each_hda_codec_node(nid, codec) {
unsigned int caps = get_wcaps(codec, nid);
int type = get_wcaps_type(caps);
if (type != AC_WID_AUD_IN || (caps & AC_WCAP_DIGITAL))
continue;
adc_nids[nums] = nid;
if (++nums >= max_nums)
break;
}
spec->num_adc_nids = nums;
codec_dbg(codec, "fill_adc_nids num nids %d\n",nums);
/* copy the detected ADCs to all_adcs[] */
spec->num_all_adcs = nums;
memcpy(spec->all_adcs, spec->adc_nids, nums * sizeof(hda_nid_t));
return nums;
}
#define update_pin_ctl(codec, pin, val) \
snd_hda_codec_write_cache(codec, pin, 0, \
AC_VERB_SET_PIN_WIDGET_CONTROL, val)
/* set the pinctl target value and write it if requested */
static void set_pin_target(struct hda_codec *codec, hda_nid_t pin,
unsigned int val, bool do_write)
{
if (!pin)
return;
val = snd_hda_correct_pin_ctl(codec, pin, val);
snd_hda_codec_set_pin_target(codec, pin, val);
if (do_write)
update_pin_ctl(codec, pin, val);
}
/* nid, dir and idx */
#define AMP_VAL_COMPARE_MASK (0xffff | (1U << 18) | (0x0f << 19))
/* check whether the given ctl is already assigned in any path elements */
static bool is_ctl_used(struct hda_codec *codec, unsigned int val, int type)
{
struct hda_gen_spec *spec = codec->spec;
const struct nid_path *path;
int i;
val &= AMP_VAL_COMPARE_MASK;
snd_array_for_each(&spec->paths, i, path) {
if ((path->ctls[type] & AMP_VAL_COMPARE_MASK) == val)
return true;
}
return false;
}
/* check whether a control with the given (nid, dir, idx) was assigned */
static bool is_ctl_associated(struct hda_codec *codec, hda_nid_t nid,
int dir, int idx, int type)
{
unsigned int val = HDA_COMPOSE_AMP_VAL(nid, 3, idx, dir);
return is_ctl_used(codec, val, type);
}
/* return true if either a volume or a mute amp is found for the given
* aamix path; the amp has to be either in the mixer node or its direct leaf
*/
static bool look_for_mix_leaf_ctls(struct hda_codec *codec, hda_nid_t mix_nid,
hda_nid_t pin, unsigned int *mix_val,
unsigned int *mute_val)
{
int idx, num_conns;
const hda_nid_t *list;
hda_nid_t nid;
idx = snd_hda_get_conn_index(codec, mix_nid, pin, true);
if (idx < 0)
return false;
*mix_val = *mute_val = 0;
if (nid_has_volume(codec, mix_nid, HDA_INPUT))
*mix_val = HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT);
if (nid_has_mute(codec, mix_nid, HDA_INPUT))
*mute_val = HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT);
if (*mix_val && *mute_val)
return true;
/* check leaf node */
num_conns = snd_hda_get_conn_list(codec, mix_nid, &list);
if (num_conns < idx)
return false;
nid = list[idx];
if (!*mix_val && nid_has_volume(codec, nid, HDA_OUTPUT) &&
!is_ctl_associated(codec, nid, HDA_OUTPUT, 0, NID_PATH_VOL_CTL))
*mix_val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
if (!*mute_val && nid_has_mute(codec, nid, HDA_OUTPUT) &&
!is_ctl_associated(codec, nid, HDA_OUTPUT, 0, NID_PATH_MUTE_CTL))
*mute_val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
return *mix_val || *mute_val;
}
/* add dynamic controls from template */
static struct snd_kcontrol_new *
add_control(struct hda_gen_spec *spec, int type, const char *name,
int cidx, unsigned long val)
{
struct snd_kcontrol_new *knew;
knew = snd_hda_gen_add_kctl(spec, name, &control_templates[type]);
if (!knew)
return NULL;
knew->index = cidx;
if (get_amp_nid_(val))
knew->subdevice = HDA_SUBDEV_AMP_FLAG;
if (knew->access == 0)
knew->access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
knew->private_value = val;
return knew;
}
static int add_control_with_pfx(struct hda_gen_spec *spec, int type,
const char *pfx, const char *dir,
const char *sfx, int cidx, unsigned long val)
{
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
int len;
len = snprintf(name, sizeof(name), "%s %s %s", pfx, dir, sfx);
if (snd_BUG_ON(len >= sizeof(name)))
return -EINVAL;
if (!add_control(spec, type, name, cidx, val))
return -ENOMEM;
return 0;
}
#define add_pb_vol_ctrl(spec, type, pfx, val) \
add_control_with_pfx(spec, type, pfx, "Playback", "Volume", 0, val)
#define add_pb_sw_ctrl(spec, type, pfx, val) \
add_control_with_pfx(spec, type, pfx, "Playback", "Switch", 0, val)
#define __add_pb_vol_ctrl(spec, type, pfx, cidx, val) \
add_control_with_pfx(spec, type, pfx, "Playback", "Volume", cidx, val)
#define __add_pb_sw_ctrl(spec, type, pfx, cidx, val) \
add_control_with_pfx(spec, type, pfx, "Playback", "Switch", cidx, val)
/* create input playback/capture controls for the given pin */
static int new_analog_input(struct hda_codec *codec, int input_idx,
hda_nid_t pin, const char *ctlname, int ctlidx,
hda_nid_t mix_nid)
{
struct hda_gen_spec *spec = codec->spec;
struct nid_path *path;
unsigned int mix_val, mute_val;
int err, idx;
if (!look_for_mix_leaf_ctls(codec, mix_nid, pin, &mix_val, &mute_val))
return 0;
path = snd_hda_add_new_path(codec, pin, mix_nid, 0);
if (!path)
return -EINVAL;
print_nid_path(codec, "loopback", path);
spec->loopback_paths[input_idx] = snd_hda_get_path_idx(codec, path);
idx = path->idx[path->depth - 1];
if (mix_val) {
err = __add_pb_vol_ctrl(spec, HDA_CTL_WIDGET_VOL, ctlname, ctlidx, mix_val);
if (err < 0)
return err;
path->ctls[NID_PATH_VOL_CTL] = mix_val;
}
if (mute_val) {
err = __add_pb_sw_ctrl(spec, HDA_CTL_WIDGET_MUTE, ctlname, ctlidx, mute_val);
if (err < 0)
return err;
path->ctls[NID_PATH_MUTE_CTL] = mute_val;
}
path->active = true;
path->stream_enabled = true; /* no DAC/ADC involved */
err = add_loopback_list(spec, mix_nid, idx);
if (err < 0)
return err;
if (spec->mixer_nid != spec->mixer_merge_nid &&
!spec->loopback_merge_path) {
path = snd_hda_add_new_path(codec, spec->mixer_nid,
spec->mixer_merge_nid, 0);
if (path) {
print_nid_path(codec, "loopback-merge", path);
path->active = true;
path->pin_fixed = true; /* static route */
path->stream_enabled = true; /* no DAC/ADC involved */
spec->loopback_merge_path =
snd_hda_get_path_idx(codec, path);
}
}
return 0;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,623 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* patch_cs8409-tables.c -- HD audio interface patch for Cirrus Logic CS8409 HDA bridge chip
*
* Copyright (C) 2021 Cirrus Logic, Inc. and
* Cirrus Logic International Semiconductor Ltd.
*
* Author: Lucas Tanure <tanureal@opensource.cirrus.com>
*/
#include "patch_cs8409.h"
/******************************************************************************
* CS42L42 Specific Data
*
******************************************************************************/
static const DECLARE_TLV_DB_SCALE(cs42l42_dac_db_scale, CS42L42_HP_VOL_REAL_MIN * 100, 100, 1);
static const DECLARE_TLV_DB_SCALE(cs42l42_adc_db_scale, CS42L42_AMIC_VOL_REAL_MIN * 100, 100, 1);
const struct snd_kcontrol_new cs42l42_dac_volume_mixer = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.subdevice = (HDA_SUBDEV_AMP_FLAG | HDA_SUBDEV_NID_FLAG),
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ),
.info = cs42l42_volume_info,
.get = cs42l42_volume_get,
.put = cs42l42_volume_put,
.tlv = { .p = cs42l42_dac_db_scale },
.private_value = HDA_COMPOSE_AMP_VAL_OFS(CS8409_PIN_ASP1_TRANSMITTER_A, 3, CS8409_CODEC0,
HDA_OUTPUT, CS42L42_VOL_DAC) | HDA_AMP_VAL_MIN_MUTE
};
const struct snd_kcontrol_new cs42l42_adc_volume_mixer = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.subdevice = (HDA_SUBDEV_AMP_FLAG | HDA_SUBDEV_NID_FLAG),
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ),
.info = cs42l42_volume_info,
.get = cs42l42_volume_get,
.put = cs42l42_volume_put,
.tlv = { .p = cs42l42_adc_db_scale },
.private_value = HDA_COMPOSE_AMP_VAL_OFS(CS8409_PIN_ASP1_RECEIVER_A, 1, CS8409_CODEC0,
HDA_INPUT, CS42L42_VOL_ADC) | HDA_AMP_VAL_MIN_MUTE
};
const struct hda_pcm_stream cs42l42_48k_pcm_analog_playback = {
.rates = SNDRV_PCM_RATE_48000, /* fixed rate */
};
const struct hda_pcm_stream cs42l42_48k_pcm_analog_capture = {
.rates = SNDRV_PCM_RATE_48000, /* fixed rate */
};
/******************************************************************************
* BULLSEYE / WARLOCK / CYBORG Specific Arrays
* CS8409/CS42L42
******************************************************************************/
const struct hda_verb cs8409_cs42l42_init_verbs[] = {
{ CS8409_PIN_AFG, AC_VERB_SET_GPIO_WAKE_MASK, 0x0018 }, /* WAKE from GPIO 3,4 */
{ CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_PROC_STATE, 0x0001 }, /* Enable VPW processing */
{ CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_COEF_INDEX, 0x0002 }, /* Configure GPIO 6,7 */
{ CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_PROC_COEF, 0x0080 }, /* I2C mode */
{ CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_COEF_INDEX, 0x005b }, /* Set I2C bus speed */
{ CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_PROC_COEF, 0x0200 }, /* 100kHz I2C_STO = 2 */
{} /* terminator */
};
static const struct hda_pintbl cs8409_cs42l42_pincfgs[] = {
{ CS8409_PIN_ASP1_TRANSMITTER_A, 0x042120f0 }, /* ASP-1-TX */
{ CS8409_PIN_ASP1_RECEIVER_A, 0x04a12050 }, /* ASP-1-RX */
{ CS8409_PIN_ASP2_TRANSMITTER_A, 0x901000f0 }, /* ASP-2-TX */
{ CS8409_PIN_DMIC1_IN, 0x90a00090 }, /* DMIC-1 */
{} /* terminator */
};
static const struct hda_pintbl cs8409_cs42l42_pincfgs_no_dmic[] = {
{ CS8409_PIN_ASP1_TRANSMITTER_A, 0x042120f0 }, /* ASP-1-TX */
{ CS8409_PIN_ASP1_RECEIVER_A, 0x04a12050 }, /* ASP-1-RX */
{ CS8409_PIN_ASP2_TRANSMITTER_A, 0x901000f0 }, /* ASP-2-TX */
{} /* terminator */
};
/* Vendor specific HW configuration for CS42L42 */
static const struct cs8409_i2c_param cs42l42_init_reg_seq[] = {
{ CS42L42_I2C_TIMEOUT, 0xB0 },
{ CS42L42_ADC_CTL, 0x00 },
{ 0x1D02, 0x06 },
{ CS42L42_ADC_VOLUME, 0x9F },
{ CS42L42_OSC_SWITCH, 0x01 },
{ CS42L42_MCLK_CTL, 0x02 },
{ CS42L42_SRC_CTL, 0x03 },
{ CS42L42_MCLK_SRC_SEL, 0x00 },
{ CS42L42_ASP_FRM_CFG, 0x13 },
{ CS42L42_FSYNC_P_LOWER, 0xFF },
{ CS42L42_FSYNC_P_UPPER, 0x00 },
{ CS42L42_ASP_CLK_CFG, 0x20 },
{ CS42L42_SPDIF_CLK_CFG, 0x0D },
{ CS42L42_ASP_RX_DAI0_CH1_AP_RES, 0x02 },
{ CS42L42_ASP_RX_DAI0_CH1_BIT_MSB, 0x00 },
{ CS42L42_ASP_RX_DAI0_CH1_BIT_LSB, 0x00 },
{ CS42L42_ASP_RX_DAI0_CH2_AP_RES, 0x02 },
{ CS42L42_ASP_RX_DAI0_CH2_BIT_MSB, 0x00 },
{ CS42L42_ASP_RX_DAI0_CH2_BIT_LSB, 0x20 },
{ CS42L42_ASP_RX_DAI0_CH3_AP_RES, 0x02 },
{ CS42L42_ASP_RX_DAI0_CH3_BIT_MSB, 0x00 },
{ CS42L42_ASP_RX_DAI0_CH3_BIT_LSB, 0x80 },
{ CS42L42_ASP_RX_DAI0_CH4_AP_RES, 0x02 },
{ CS42L42_ASP_RX_DAI0_CH4_BIT_MSB, 0x00 },
{ CS42L42_ASP_RX_DAI0_CH4_BIT_LSB, 0xA0 },
{ CS42L42_ASP_RX_DAI0_EN, 0x0C },
{ CS42L42_ASP_TX_CH_EN, 0x01 },
{ CS42L42_ASP_TX_CH_AP_RES, 0x02 },
{ CS42L42_ASP_TX_CH1_BIT_MSB, 0x00 },
{ CS42L42_ASP_TX_CH1_BIT_LSB, 0x00 },
{ CS42L42_ASP_TX_SZ_EN, 0x01 },
{ CS42L42_PWR_CTL1, 0x0A },
{ CS42L42_PWR_CTL2, 0x84 },
{ CS42L42_MIXER_CHA_VOL, 0x3F },
{ CS42L42_MIXER_CHB_VOL, 0x3F },
{ CS42L42_MIXER_ADC_VOL, 0x3f },
{ CS42L42_HP_CTL, 0x0D },
{ CS42L42_MIC_DET_CTL1, 0xB6 },
{ CS42L42_TIPSENSE_CTL, 0xC2 },
{ CS42L42_HS_CLAMP_DISABLE, 0x01 },
{ CS42L42_HS_SWITCH_CTL, 0xF3 },
{ CS42L42_PWR_CTL3, 0x20 },
{ CS42L42_RSENSE_CTL2, 0x00 },
{ CS42L42_RSENSE_CTL3, 0x00 },
{ CS42L42_TSENSE_CTL, 0x80 },
{ CS42L42_HS_BIAS_CTL, 0xC0 },
{ CS42L42_PWR_CTL1, 0x02, 10000 },
{ CS42L42_ADC_OVFL_INT_MASK, 0xff },
{ CS42L42_MIXER_INT_MASK, 0xff },
{ CS42L42_SRC_INT_MASK, 0xff },
{ CS42L42_ASP_RX_INT_MASK, 0xff },
{ CS42L42_ASP_TX_INT_MASK, 0xff },
{ CS42L42_CODEC_INT_MASK, 0xff },
{ CS42L42_SRCPL_INT_MASK, 0xff },
{ CS42L42_VPMON_INT_MASK, 0xff },
{ CS42L42_PLL_LOCK_INT_MASK, 0xff },
{ CS42L42_TSRS_PLUG_INT_MASK, 0xff },
{ CS42L42_DET_INT1_MASK, 0xff },
{ CS42L42_DET_INT2_MASK, 0xff },
};
/* Vendor specific hw configuration for CS8409 */
const struct cs8409_cir_param cs8409_cs42l42_hw_cfg[] = {
/* +PLL1/2_EN, +I2C_EN */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG1, 0xb008 },
/* ASP1/2_EN=0, ASP1_STP=1 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG2, 0x0002 },
/* ASP1/2_BUS_IDLE=10, +GPIO_I2C */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG3, 0x0a80 },
/* ASP1.A: TX.LAP=0, TX.LSZ=24 bits, TX.LCS=0 */
{ CS8409_PIN_VENDOR_WIDGET, ASP1_A_TX_CTRL1, 0x0800 },
/* ASP1.A: TX.RAP=0, TX.RSZ=24 bits, TX.RCS=32 */
{ CS8409_PIN_VENDOR_WIDGET, ASP1_A_TX_CTRL2, 0x0820 },
/* ASP2.A: TX.LAP=0, TX.LSZ=24 bits, TX.LCS=0 */
{ CS8409_PIN_VENDOR_WIDGET, ASP2_A_TX_CTRL1, 0x0800 },
/* ASP2.A: TX.RAP=1, TX.RSZ=24 bits, TX.RCS=0 */
{ CS8409_PIN_VENDOR_WIDGET, ASP2_A_TX_CTRL2, 0x2800 },
/* ASP1.A: RX.LAP=0, RX.LSZ=24 bits, RX.LCS=0 */
{ CS8409_PIN_VENDOR_WIDGET, ASP1_A_RX_CTRL1, 0x0800 },
/* ASP1.A: RX.RAP=0, RX.RSZ=24 bits, RX.RCS=0 */
{ CS8409_PIN_VENDOR_WIDGET, ASP1_A_RX_CTRL2, 0x0800 },
/* ASP1: LCHI = 00h */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_ASP1_CLK_CTRL1, 0x8000 },
/* ASP1: MC/SC_SRCSEL=PLL1, LCPR=FFh */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_ASP1_CLK_CTRL2, 0x28ff },
/* ASP1: MCEN=0, FSD=011, SCPOL_IN/OUT=0, SCDIV=1:4 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_ASP1_CLK_CTRL3, 0x0062 },
/* ASP2: LCHI=1Fh */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_ASP2_CLK_CTRL1, 0x801f },
/* ASP2: MC/SC_SRCSEL=PLL1, LCPR=3Fh */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_ASP2_CLK_CTRL2, 0x283f },
/* ASP2: 5050=1, MCEN=0, FSD=010, SCPOL_IN/OUT=1, SCDIV=1:16 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_ASP2_CLK_CTRL3, 0x805c },
/* DMIC1_MO=10b, DMIC1/2_SR=1 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_DMIC_CFG, 0x0023 },
/* ASP1/2_BEEP=0 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_BEEP_CFG, 0x0000 },
/* ASP1/2_EN=1, ASP1_STP=1 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG2, 0x0062 },
/* -PLL2_EN */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG1, 0x9008 },
/* TX2.A: pre-scale att.=0 dB */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PRE_SCALE_ATTN2, 0x0000 },
/* ASP1/2_xxx_EN=1, ASP1/2_MCLK_EN=0, DMIC1_SCL_EN=1 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PAD_CFG_SLW_RATE_CTRL, 0xfc03 },
/* test mode on */
{ CS8409_PIN_VENDOR_WIDGET, 0xc0, 0x9999 },
/* GPIO hysteresis = 30 us */
{ CS8409_PIN_VENDOR_WIDGET, 0xc5, 0x0000 },
/* test mode off */
{ CS8409_PIN_VENDOR_WIDGET, 0xc0, 0x0000 },
{} /* Terminator */
};
const struct cs8409_cir_param cs8409_cs42l42_bullseye_atn[] = {
/* EQ_SEL=1, EQ1/2_EN=0 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_CTRL1, 0x4000 },
/* +EQ_ACC */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0x4000 },
/* +EQ2_EN */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_CTRL1, 0x4010 },
/* EQ_DATA_HI=0x0647 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0x0647 },
/* +EQ_WRT, +EQ_ACC, EQ_ADR=0, EQ_DATA_LO=0x67 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc0c7 },
/* EQ_DATA_HI=0x0647 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0x0647 },
/* +EQ_WRT, +EQ_ACC, EQ_ADR=1, EQ_DATA_LO=0x67 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc1c7 },
/* EQ_DATA_HI=0xf370 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0xf370 },
/* +EQ_WRT, +EQ_ACC, EQ_ADR=2, EQ_DATA_LO=0x71 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc271 },
/* EQ_DATA_HI=0x1ef8 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0x1ef8 },
/* +EQ_WRT, +EQ_ACC, EQ_ADR=3, EQ_DATA_LO=0x48 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc348 },
/* EQ_DATA_HI=0xc110 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0xc110 },
/* +EQ_WRT, +EQ_ACC, EQ_ADR=4, EQ_DATA_LO=0x5a */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc45a },
/* EQ_DATA_HI=0x1f29 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0x1f29 },
/* +EQ_WRT, +EQ_ACC, EQ_ADR=5, EQ_DATA_LO=0x74 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc574 },
/* EQ_DATA_HI=0x1d7a */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0x1d7a },
/* +EQ_WRT, +EQ_ACC, EQ_ADR=6, EQ_DATA_LO=0x53 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc653 },
/* EQ_DATA_HI=0xc38c */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0xc38c },
/* +EQ_WRT, +EQ_ACC, EQ_ADR=7, EQ_DATA_LO=0x14 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc714 },
/* EQ_DATA_HI=0x1ca3 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0x1ca3 },
/* +EQ_WRT, +EQ_ACC, EQ_ADR=8, EQ_DATA_LO=0xc7 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc8c7 },
/* EQ_DATA_HI=0xc38c */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W1, 0xc38c },
/* +EQ_WRT, +EQ_ACC, EQ_ADR=9, EQ_DATA_LO=0x14 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0xc914 },
/* -EQ_ACC, -EQ_WRT */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PFE_COEF_W2, 0x0000 },
{} /* Terminator */
};
struct sub_codec cs8409_cs42l42_codec = {
.addr = CS42L42_I2C_ADDR,
.reset_gpio = CS8409_CS42L42_RESET,
.irq_mask = CS8409_CS42L42_INT,
.init_seq = cs42l42_init_reg_seq,
.init_seq_num = ARRAY_SIZE(cs42l42_init_reg_seq),
.hp_jack_in = 0,
.mic_jack_in = 0,
.paged = 1,
.suspended = 1,
.no_type_dect = 0,
};
/******************************************************************************
* Dolphin Specific Arrays
* CS8409/ 2 X CS42L42
******************************************************************************/
const struct hda_verb dolphin_init_verbs[] = {
{ 0x01, AC_VERB_SET_GPIO_WAKE_MASK, DOLPHIN_WAKE }, /* WAKE from GPIO 0,4 */
{ CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_PROC_STATE, 0x0001 }, /* Enable VPW processing */
{ CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_COEF_INDEX, 0x0002 }, /* Configure GPIO 6,7 */
{ CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_PROC_COEF, 0x0080 }, /* I2C mode */
{ CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_COEF_INDEX, 0x005b }, /* Set I2C bus speed */
{ CS8409_PIN_VENDOR_WIDGET, AC_VERB_SET_PROC_COEF, 0x0200 }, /* 100kHz I2C_STO = 2 */
{} /* terminator */
};
static const struct hda_pintbl dolphin_pincfgs[] = {
{ 0x24, 0x022210f0 }, /* ASP-1-TX-A */
{ 0x25, 0x010240f0 }, /* ASP-1-TX-B */
{ 0x34, 0x02a21050 }, /* ASP-1-RX */
{} /* terminator */
};
/* Vendor specific HW configuration for CS42L42 */
static const struct cs8409_i2c_param dolphin_c0_init_reg_seq[] = {
{ CS42L42_I2C_TIMEOUT, 0xB0 },
{ CS42L42_ADC_CTL, 0x00 },
{ 0x1D02, 0x06 },
{ CS42L42_ADC_VOLUME, 0x9F },
{ CS42L42_OSC_SWITCH, 0x01 },
{ CS42L42_MCLK_CTL, 0x02 },
{ CS42L42_SRC_CTL, 0x03 },
{ CS42L42_MCLK_SRC_SEL, 0x00 },
{ CS42L42_ASP_FRM_CFG, 0x13 },
{ CS42L42_FSYNC_P_LOWER, 0xFF },
{ CS42L42_FSYNC_P_UPPER, 0x00 },
{ CS42L42_ASP_CLK_CFG, 0x20 },
{ CS42L42_SPDIF_CLK_CFG, 0x0D },
{ CS42L42_ASP_RX_DAI0_CH1_AP_RES, 0x02 },
{ CS42L42_ASP_RX_DAI0_CH1_BIT_MSB, 0x00 },
{ CS42L42_ASP_RX_DAI0_CH1_BIT_LSB, 0x00 },
{ CS42L42_ASP_RX_DAI0_CH2_AP_RES, 0x02 },
{ CS42L42_ASP_RX_DAI0_CH2_BIT_MSB, 0x00 },
{ CS42L42_ASP_RX_DAI0_CH2_BIT_LSB, 0x20 },
{ CS42L42_ASP_RX_DAI0_EN, 0x0C },
{ CS42L42_ASP_TX_CH_EN, 0x01 },
{ CS42L42_ASP_TX_CH_AP_RES, 0x02 },
{ CS42L42_ASP_TX_CH1_BIT_MSB, 0x00 },
{ CS42L42_ASP_TX_CH1_BIT_LSB, 0x00 },
{ CS42L42_ASP_TX_SZ_EN, 0x01 },
{ CS42L42_PWR_CTL1, 0x0A },
{ CS42L42_PWR_CTL2, 0x84 },
{ CS42L42_HP_CTL, 0x0D },
{ CS42L42_MIXER_CHA_VOL, 0x3F },
{ CS42L42_MIXER_CHB_VOL, 0x3F },
{ CS42L42_MIXER_ADC_VOL, 0x3f },
{ CS42L42_MIC_DET_CTL1, 0xB6 },
{ CS42L42_TIPSENSE_CTL, 0xC2 },
{ CS42L42_HS_CLAMP_DISABLE, 0x01 },
{ CS42L42_HS_SWITCH_CTL, 0xF3 },
{ CS42L42_PWR_CTL3, 0x20 },
{ CS42L42_RSENSE_CTL2, 0x00 },
{ CS42L42_RSENSE_CTL3, 0x00 },
{ CS42L42_TSENSE_CTL, 0x80 },
{ CS42L42_HS_BIAS_CTL, 0xC0 },
{ CS42L42_PWR_CTL1, 0x02, 10000 },
{ CS42L42_ADC_OVFL_INT_MASK, 0xff },
{ CS42L42_MIXER_INT_MASK, 0xff },
{ CS42L42_SRC_INT_MASK, 0xff },
{ CS42L42_ASP_RX_INT_MASK, 0xff },
{ CS42L42_ASP_TX_INT_MASK, 0xff },
{ CS42L42_CODEC_INT_MASK, 0xff },
{ CS42L42_SRCPL_INT_MASK, 0xff },
{ CS42L42_VPMON_INT_MASK, 0xff },
{ CS42L42_PLL_LOCK_INT_MASK, 0xff },
{ CS42L42_TSRS_PLUG_INT_MASK, 0xff },
{ CS42L42_DET_INT1_MASK, 0xff },
{ CS42L42_DET_INT2_MASK, 0xff }
};
static const struct cs8409_i2c_param dolphin_c1_init_reg_seq[] = {
{ CS42L42_I2C_TIMEOUT, 0xB0 },
{ CS42L42_ADC_CTL, 0x00 },
{ 0x1D02, 0x06 },
{ CS42L42_ADC_VOLUME, 0x9F },
{ CS42L42_OSC_SWITCH, 0x01 },
{ CS42L42_MCLK_CTL, 0x02 },
{ CS42L42_SRC_CTL, 0x03 },
{ CS42L42_MCLK_SRC_SEL, 0x00 },
{ CS42L42_ASP_FRM_CFG, 0x13 },
{ CS42L42_FSYNC_P_LOWER, 0xFF },
{ CS42L42_FSYNC_P_UPPER, 0x00 },
{ CS42L42_ASP_CLK_CFG, 0x20 },
{ CS42L42_SPDIF_CLK_CFG, 0x0D },
{ CS42L42_ASP_RX_DAI0_CH1_AP_RES, 0x02 },
{ CS42L42_ASP_RX_DAI0_CH1_BIT_MSB, 0x00 },
{ CS42L42_ASP_RX_DAI0_CH1_BIT_LSB, 0x80 },
{ CS42L42_ASP_RX_DAI0_CH2_AP_RES, 0x02 },
{ CS42L42_ASP_RX_DAI0_CH2_BIT_MSB, 0x00 },
{ CS42L42_ASP_RX_DAI0_CH2_BIT_LSB, 0xA0 },
{ CS42L42_ASP_RX_DAI0_EN, 0x0C },
{ CS42L42_ASP_TX_CH_EN, 0x00 },
{ CS42L42_ASP_TX_CH_AP_RES, 0x02 },
{ CS42L42_ASP_TX_CH1_BIT_MSB, 0x00 },
{ CS42L42_ASP_TX_CH1_BIT_LSB, 0x00 },
{ CS42L42_ASP_TX_SZ_EN, 0x00 },
{ CS42L42_PWR_CTL1, 0x0E },
{ CS42L42_PWR_CTL2, 0x84 },
{ CS42L42_HP_CTL, 0x0D },
{ CS42L42_MIXER_CHA_VOL, 0x3F },
{ CS42L42_MIXER_CHB_VOL, 0x3F },
{ CS42L42_MIXER_ADC_VOL, 0x3f },
{ CS42L42_MIC_DET_CTL1, 0xB6 },
{ CS42L42_TIPSENSE_CTL, 0xC2 },
{ CS42L42_HS_CLAMP_DISABLE, 0x01 },
{ CS42L42_HS_SWITCH_CTL, 0xF3 },
{ CS42L42_PWR_CTL3, 0x20 },
{ CS42L42_RSENSE_CTL2, 0x00 },
{ CS42L42_RSENSE_CTL3, 0x00 },
{ CS42L42_TSENSE_CTL, 0x80 },
{ CS42L42_HS_BIAS_CTL, 0xC0 },
{ CS42L42_PWR_CTL1, 0x06, 10000 },
{ CS42L42_ADC_OVFL_INT_MASK, 0xff },
{ CS42L42_MIXER_INT_MASK, 0xff },
{ CS42L42_SRC_INT_MASK, 0xff },
{ CS42L42_ASP_RX_INT_MASK, 0xff },
{ CS42L42_ASP_TX_INT_MASK, 0xff },
{ CS42L42_CODEC_INT_MASK, 0xff },
{ CS42L42_SRCPL_INT_MASK, 0xff },
{ CS42L42_VPMON_INT_MASK, 0xff },
{ CS42L42_PLL_LOCK_INT_MASK, 0xff },
{ CS42L42_TSRS_PLUG_INT_MASK, 0xff },
{ CS42L42_DET_INT1_MASK, 0xff },
{ CS42L42_DET_INT2_MASK, 0xff }
};
/* Vendor specific hw configuration for CS8409 */
const struct cs8409_cir_param dolphin_hw_cfg[] = {
/* +PLL1/2_EN, +I2C_EN */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG1, 0xb008 },
/* ASP1_EN=0, ASP1_STP=1 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG2, 0x0002 },
/* ASP1/2_BUS_IDLE=10, +GPIO_I2C */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG3, 0x0a80 },
/* ASP1.A: TX.LAP=0, TX.LSZ=24 bits, TX.LCS=0 */
{ CS8409_PIN_VENDOR_WIDGET, ASP1_A_TX_CTRL1, 0x0800 },
/* ASP1.A: TX.RAP=0, TX.RSZ=24 bits, TX.RCS=32 */
{ CS8409_PIN_VENDOR_WIDGET, ASP1_A_TX_CTRL2, 0x0820 },
/* ASP1.B: TX.LAP=0, TX.LSZ=24 bits, TX.LCS=128 */
{ CS8409_PIN_VENDOR_WIDGET, ASP1_B_TX_CTRL1, 0x0880 },
/* ASP1.B: TX.RAP=0, TX.RSZ=24 bits, TX.RCS=160 */
{ CS8409_PIN_VENDOR_WIDGET, ASP1_B_TX_CTRL2, 0x08a0 },
/* ASP1.A: RX.LAP=0, RX.LSZ=24 bits, RX.LCS=0 */
{ CS8409_PIN_VENDOR_WIDGET, ASP1_A_RX_CTRL1, 0x0800 },
/* ASP1.A: RX.RAP=0, RX.RSZ=24 bits, RX.RCS=0 */
{ CS8409_PIN_VENDOR_WIDGET, ASP1_A_RX_CTRL2, 0x0800 },
/* ASP1: LCHI = 00h */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_ASP1_CLK_CTRL1, 0x8000 },
/* ASP1: MC/SC_SRCSEL=PLL1, LCPR=FFh */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_ASP1_CLK_CTRL2, 0x28ff },
/* ASP1: MCEN=0, FSD=011, SCPOL_IN/OUT=0, SCDIV=1:4 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_ASP1_CLK_CTRL3, 0x0062 },
/* ASP1/2_BEEP=0 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_BEEP_CFG, 0x0000 },
/* ASP1_EN=1, ASP1_STP=1 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG2, 0x0022 },
/* -PLL2_EN */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_DEV_CFG1, 0x9008 },
/* ASP1_xxx_EN=1, ASP1_MCLK_EN=0 */
{ CS8409_PIN_VENDOR_WIDGET, CS8409_PAD_CFG_SLW_RATE_CTRL, 0x5400 },
/* test mode on */
{ CS8409_PIN_VENDOR_WIDGET, 0xc0, 0x9999 },
/* GPIO hysteresis = 30 us */
{ CS8409_PIN_VENDOR_WIDGET, 0xc5, 0x0000 },
/* test mode off */
{ CS8409_PIN_VENDOR_WIDGET, 0xc0, 0x0000 },
{} /* Terminator */
};
struct sub_codec dolphin_cs42l42_0 = {
.addr = DOLPHIN_C0_I2C_ADDR,
.reset_gpio = DOLPHIN_C0_RESET,
.irq_mask = DOLPHIN_C0_INT,
.init_seq = dolphin_c0_init_reg_seq,
.init_seq_num = ARRAY_SIZE(dolphin_c0_init_reg_seq),
.hp_jack_in = 0,
.mic_jack_in = 0,
.paged = 1,
.suspended = 1,
.no_type_dect = 0,
};
struct sub_codec dolphin_cs42l42_1 = {
.addr = DOLPHIN_C1_I2C_ADDR,
.reset_gpio = DOLPHIN_C1_RESET,
.irq_mask = DOLPHIN_C1_INT,
.init_seq = dolphin_c1_init_reg_seq,
.init_seq_num = ARRAY_SIZE(dolphin_c1_init_reg_seq),
.hp_jack_in = 0,
.mic_jack_in = 0,
.paged = 1,
.suspended = 1,
.no_type_dect = 1,
};
/******************************************************************************
* CS8409 Patch Driver Structs
* Arrays Used for all projects using CS8409
******************************************************************************/
const struct hda_quirk cs8409_fixup_tbl[] = {
SND_PCI_QUIRK(0x1028, 0x0A11, "Bullseye", CS8409_BULLSEYE),
SND_PCI_QUIRK(0x1028, 0x0A12, "Bullseye", CS8409_BULLSEYE),
SND_PCI_QUIRK(0x1028, 0x0A23, "Bullseye", CS8409_BULLSEYE),
SND_PCI_QUIRK(0x1028, 0x0A24, "Bullseye", CS8409_BULLSEYE),
SND_PCI_QUIRK(0x1028, 0x0A25, "Bullseye", CS8409_BULLSEYE),
SND_PCI_QUIRK(0x1028, 0x0A29, "Bullseye", CS8409_BULLSEYE),
SND_PCI_QUIRK(0x1028, 0x0A2A, "Bullseye", CS8409_BULLSEYE),
SND_PCI_QUIRK(0x1028, 0x0A2B, "Bullseye", CS8409_BULLSEYE),
SND_PCI_QUIRK(0x1028, 0x0A77, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0A78, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0A79, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0A7A, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0A7D, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0A7E, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0A7F, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0A80, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0AB0, "Warlock", CS8409_WARLOCK),
SND_PCI_QUIRK(0x1028, 0x0AB2, "Warlock", CS8409_WARLOCK),
SND_PCI_QUIRK(0x1028, 0x0AB1, "Warlock", CS8409_WARLOCK),
SND_PCI_QUIRK(0x1028, 0x0AB3, "Warlock", CS8409_WARLOCK),
SND_PCI_QUIRK(0x1028, 0x0AB4, "Warlock", CS8409_WARLOCK),
SND_PCI_QUIRK(0x1028, 0x0AB5, "Warlock", CS8409_WARLOCK),
SND_PCI_QUIRK(0x1028, 0x0ACF, "Dolphin", CS8409_DOLPHIN),
SND_PCI_QUIRK(0x1028, 0x0AD0, "Dolphin", CS8409_DOLPHIN),
SND_PCI_QUIRK(0x1028, 0x0AD1, "Dolphin", CS8409_DOLPHIN),
SND_PCI_QUIRK(0x1028, 0x0AD2, "Dolphin", CS8409_DOLPHIN),
SND_PCI_QUIRK(0x1028, 0x0AD3, "Dolphin", CS8409_DOLPHIN),
SND_PCI_QUIRK(0x1028, 0x0AD9, "Warlock", CS8409_WARLOCK),
SND_PCI_QUIRK(0x1028, 0x0ADA, "Warlock", CS8409_WARLOCK),
SND_PCI_QUIRK(0x1028, 0x0ADB, "Warlock", CS8409_WARLOCK),
SND_PCI_QUIRK(0x1028, 0x0ADC, "Warlock", CS8409_WARLOCK),
SND_PCI_QUIRK(0x1028, 0x0ADF, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0AE0, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0AE1, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0AE2, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0AE9, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0AEA, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0AEB, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0AEC, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0AED, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0AEE, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0AEF, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0AF0, "Cyborg", CS8409_CYBORG),
SND_PCI_QUIRK(0x1028, 0x0AF4, "Warlock", CS8409_WARLOCK),
SND_PCI_QUIRK(0x1028, 0x0AF5, "Warlock", CS8409_WARLOCK),
SND_PCI_QUIRK(0x1028, 0x0B92, "Warlock MLK", CS8409_WARLOCK_MLK),
SND_PCI_QUIRK(0x1028, 0x0B93, "Warlock MLK Dual Mic", CS8409_WARLOCK_MLK_DUAL_MIC),
SND_PCI_QUIRK(0x1028, 0x0B94, "Warlock MLK", CS8409_WARLOCK_MLK),
SND_PCI_QUIRK(0x1028, 0x0B95, "Warlock MLK Dual Mic", CS8409_WARLOCK_MLK_DUAL_MIC),
SND_PCI_QUIRK(0x1028, 0x0B96, "Warlock MLK", CS8409_WARLOCK_MLK),
SND_PCI_QUIRK(0x1028, 0x0B97, "Warlock MLK Dual Mic", CS8409_WARLOCK_MLK_DUAL_MIC),
SND_PCI_QUIRK(0x1028, 0x0BA5, "Odin", CS8409_ODIN),
SND_PCI_QUIRK(0x1028, 0x0BA6, "Odin", CS8409_ODIN),
SND_PCI_QUIRK(0x1028, 0x0BA8, "Odin", CS8409_ODIN),
SND_PCI_QUIRK(0x1028, 0x0BAA, "Odin", CS8409_ODIN),
SND_PCI_QUIRK(0x1028, 0x0BAE, "Odin", CS8409_ODIN),
SND_PCI_QUIRK(0x1028, 0x0BB2, "Warlock MLK", CS8409_WARLOCK_MLK),
SND_PCI_QUIRK(0x1028, 0x0BB3, "Warlock MLK", CS8409_WARLOCK_MLK),
SND_PCI_QUIRK(0x1028, 0x0BB4, "Warlock MLK", CS8409_WARLOCK_MLK),
SND_PCI_QUIRK(0x1028, 0x0BB5, "Warlock N3 15 TGL-U Nuvoton EC", CS8409_WARLOCK),
SND_PCI_QUIRK(0x1028, 0x0BB6, "Warlock V3 15 TGL-U Nuvoton EC", CS8409_WARLOCK),
SND_PCI_QUIRK(0x1028, 0x0BB8, "Warlock MLK", CS8409_WARLOCK_MLK),
SND_PCI_QUIRK(0x1028, 0x0BB9, "Warlock MLK Dual Mic", CS8409_WARLOCK_MLK_DUAL_MIC),
SND_PCI_QUIRK(0x1028, 0x0BBA, "Warlock MLK", CS8409_WARLOCK_MLK),
SND_PCI_QUIRK(0x1028, 0x0BBB, "Warlock MLK Dual Mic", CS8409_WARLOCK_MLK_DUAL_MIC),
SND_PCI_QUIRK(0x1028, 0x0BBC, "Warlock MLK", CS8409_WARLOCK_MLK),
SND_PCI_QUIRK(0x1028, 0x0BBD, "Warlock MLK Dual Mic", CS8409_WARLOCK_MLK_DUAL_MIC),
SND_PCI_QUIRK(0x1028, 0x0BD4, "Dolphin", CS8409_DOLPHIN),
SND_PCI_QUIRK(0x1028, 0x0BD5, "Dolphin", CS8409_DOLPHIN),
SND_PCI_QUIRK(0x1028, 0x0BD6, "Dolphin", CS8409_DOLPHIN),
SND_PCI_QUIRK(0x1028, 0x0BD7, "Dolphin", CS8409_DOLPHIN),
SND_PCI_QUIRK(0x1028, 0x0BD8, "Dolphin", CS8409_DOLPHIN),
SND_PCI_QUIRK(0x1028, 0x0C43, "Dolphin", CS8409_DOLPHIN),
SND_PCI_QUIRK(0x1028, 0x0C50, "Dolphin", CS8409_DOLPHIN),
SND_PCI_QUIRK(0x1028, 0x0C51, "Dolphin", CS8409_DOLPHIN),
SND_PCI_QUIRK(0x1028, 0x0C52, "Dolphin", CS8409_DOLPHIN),
SND_PCI_QUIRK(0x1028, 0x0C73, "Dolphin", CS8409_DOLPHIN),
SND_PCI_QUIRK(0x1028, 0x0C75, "Dolphin", CS8409_DOLPHIN),
SND_PCI_QUIRK(0x1028, 0x0C7D, "Dolphin", CS8409_DOLPHIN),
SND_PCI_QUIRK(0x1028, 0x0C7F, "Dolphin", CS8409_DOLPHIN),
{} /* terminator */
};
/* Dell Inspiron models with cs8409/cs42l42 */
const struct hda_model_fixup cs8409_models[] = {
{ .id = CS8409_BULLSEYE, .name = "bullseye" },
{ .id = CS8409_WARLOCK, .name = "warlock" },
{ .id = CS8409_WARLOCK_MLK, .name = "warlock mlk" },
{ .id = CS8409_WARLOCK_MLK_DUAL_MIC, .name = "warlock mlk dual mic" },
{ .id = CS8409_CYBORG, .name = "cyborg" },
{ .id = CS8409_DOLPHIN, .name = "dolphin" },
{ .id = CS8409_ODIN, .name = "odin" },
{}
};
const struct hda_fixup cs8409_fixups[] = {
[CS8409_BULLSEYE] = {
.type = HDA_FIXUP_PINS,
.v.pins = cs8409_cs42l42_pincfgs,
.chained = true,
.chain_id = CS8409_FIXUPS,
},
[CS8409_WARLOCK] = {
.type = HDA_FIXUP_PINS,
.v.pins = cs8409_cs42l42_pincfgs,
.chained = true,
.chain_id = CS8409_FIXUPS,
},
[CS8409_WARLOCK_MLK] = {
.type = HDA_FIXUP_PINS,
.v.pins = cs8409_cs42l42_pincfgs,
.chained = true,
.chain_id = CS8409_FIXUPS,
},
[CS8409_WARLOCK_MLK_DUAL_MIC] = {
.type = HDA_FIXUP_PINS,
.v.pins = cs8409_cs42l42_pincfgs,
.chained = true,
.chain_id = CS8409_FIXUPS,
},
[CS8409_CYBORG] = {
.type = HDA_FIXUP_PINS,
.v.pins = cs8409_cs42l42_pincfgs,
.chained = true,
.chain_id = CS8409_FIXUPS,
},
[CS8409_FIXUPS] = {
.type = HDA_FIXUP_FUNC,
.v.func = cs8409_cs42l42_fixups,
},
[CS8409_DOLPHIN] = {
.type = HDA_FIXUP_PINS,
.v.pins = dolphin_pincfgs,
.chained = true,
.chain_id = CS8409_DOLPHIN_FIXUPS,
},
[CS8409_DOLPHIN_FIXUPS] = {
.type = HDA_FIXUP_FUNC,
.v.func = dolphin_fixups,
},
[CS8409_ODIN] = {
.type = HDA_FIXUP_PINS,
.v.pins = cs8409_cs42l42_pincfgs_no_dmic,
.chained = true,
.chain_id = CS8409_FIXUPS,
},
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,540 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* HD audio interface patch for Cirrus Logic CS8409 HDA bridge chip
*
* Copyright (C) 2021 Cirrus Logic, Inc. and
* Cirrus Logic International Semiconductor Ltd.
*/
#ifndef __CS8409_PATCH_H
#define __CS8409_PATCH_H
/*
* Apple-specific fields and patch_cirrus_apple.h require APPLE_CODECS. The local
* Makefile passes -DAPPLE_CODECS via CFLAGS_MODULE; default here so a plain
* "make -C .../build M=$(pwd) modules" without those flags still compiles.
*/
#ifndef APPLE_CODECS
#define APPLE_CODECS 1
#endif
#include <linux/pci.h>
#include <sound/tlv.h>
#include <linux/workqueue.h>
#include <sound/cs42l42.h>
#include <sound/hda_codec.h>
#include "hda_local.h"
#include "hda_auto_parser.h"
#include "hda_jack.h"
#include "hda_generic.h"
/* CS8409 Specific Definitions */
enum cs8409_pins {
CS8409_PIN_ROOT,
CS8409_PIN_AFG,
CS8409_PIN_ASP1_OUT_A,
CS8409_PIN_ASP1_OUT_B,
CS8409_PIN_ASP1_OUT_C,
CS8409_PIN_ASP1_OUT_D,
CS8409_PIN_ASP1_OUT_E,
CS8409_PIN_ASP1_OUT_F,
CS8409_PIN_ASP1_OUT_G,
CS8409_PIN_ASP1_OUT_H,
CS8409_PIN_ASP2_OUT_A,
CS8409_PIN_ASP2_OUT_B,
CS8409_PIN_ASP2_OUT_C,
CS8409_PIN_ASP2_OUT_D,
CS8409_PIN_ASP2_OUT_E,
CS8409_PIN_ASP2_OUT_F,
CS8409_PIN_ASP2_OUT_G,
CS8409_PIN_ASP2_OUT_H,
CS8409_PIN_ASP1_IN_A,
CS8409_PIN_ASP1_IN_B,
CS8409_PIN_ASP1_IN_C,
CS8409_PIN_ASP1_IN_D,
CS8409_PIN_ASP1_IN_E,
CS8409_PIN_ASP1_IN_F,
CS8409_PIN_ASP1_IN_G,
CS8409_PIN_ASP1_IN_H,
CS8409_PIN_ASP2_IN_A,
CS8409_PIN_ASP2_IN_B,
CS8409_PIN_ASP2_IN_C,
CS8409_PIN_ASP2_IN_D,
CS8409_PIN_ASP2_IN_E,
CS8409_PIN_ASP2_IN_F,
CS8409_PIN_ASP2_IN_G,
CS8409_PIN_ASP2_IN_H,
CS8409_PIN_DMIC1,
CS8409_PIN_DMIC2,
CS8409_PIN_ASP1_TRANSMITTER_A,
CS8409_PIN_ASP1_TRANSMITTER_B,
CS8409_PIN_ASP1_TRANSMITTER_C,
CS8409_PIN_ASP1_TRANSMITTER_D,
CS8409_PIN_ASP1_TRANSMITTER_E,
CS8409_PIN_ASP1_TRANSMITTER_F,
CS8409_PIN_ASP1_TRANSMITTER_G,
CS8409_PIN_ASP1_TRANSMITTER_H,
CS8409_PIN_ASP2_TRANSMITTER_A,
CS8409_PIN_ASP2_TRANSMITTER_B,
CS8409_PIN_ASP2_TRANSMITTER_C,
CS8409_PIN_ASP2_TRANSMITTER_D,
CS8409_PIN_ASP2_TRANSMITTER_E,
CS8409_PIN_ASP2_TRANSMITTER_F,
CS8409_PIN_ASP2_TRANSMITTER_G,
CS8409_PIN_ASP2_TRANSMITTER_H,
CS8409_PIN_ASP1_RECEIVER_A,
CS8409_PIN_ASP1_RECEIVER_B,
CS8409_PIN_ASP1_RECEIVER_C,
CS8409_PIN_ASP1_RECEIVER_D,
CS8409_PIN_ASP1_RECEIVER_E,
CS8409_PIN_ASP1_RECEIVER_F,
CS8409_PIN_ASP1_RECEIVER_G,
CS8409_PIN_ASP1_RECEIVER_H,
CS8409_PIN_ASP2_RECEIVER_A,
CS8409_PIN_ASP2_RECEIVER_B,
CS8409_PIN_ASP2_RECEIVER_C,
CS8409_PIN_ASP2_RECEIVER_D,
CS8409_PIN_ASP2_RECEIVER_E,
CS8409_PIN_ASP2_RECEIVER_F,
CS8409_PIN_ASP2_RECEIVER_G,
CS8409_PIN_ASP2_RECEIVER_H,
CS8409_PIN_DMIC1_IN,
CS8409_PIN_DMIC2_IN,
CS8409_PIN_BEEP_GEN,
CS8409_PIN_VENDOR_WIDGET
};
enum cs8409_coefficient_index_registers {
CS8409_DEV_CFG1,
CS8409_DEV_CFG2,
CS8409_DEV_CFG3,
CS8409_ASP1_CLK_CTRL1,
CS8409_ASP1_CLK_CTRL2,
CS8409_ASP1_CLK_CTRL3,
CS8409_ASP2_CLK_CTRL1,
CS8409_ASP2_CLK_CTRL2,
CS8409_ASP2_CLK_CTRL3,
CS8409_DMIC_CFG,
CS8409_BEEP_CFG,
ASP1_RX_NULL_INS_RMV,
ASP1_Rx_RATE1,
ASP1_Rx_RATE2,
ASP1_Tx_NULL_INS_RMV,
ASP1_Tx_RATE1,
ASP1_Tx_RATE2,
ASP2_Rx_NULL_INS_RMV,
ASP2_Rx_RATE1,
ASP2_Rx_RATE2,
ASP2_Tx_NULL_INS_RMV,
ASP2_Tx_RATE1,
ASP2_Tx_RATE2,
ASP1_SYNC_CTRL,
ASP2_SYNC_CTRL,
ASP1_A_TX_CTRL1,
ASP1_A_TX_CTRL2,
ASP1_B_TX_CTRL1,
ASP1_B_TX_CTRL2,
ASP1_C_TX_CTRL1,
ASP1_C_TX_CTRL2,
ASP1_D_TX_CTRL1,
ASP1_D_TX_CTRL2,
ASP1_E_TX_CTRL1,
ASP1_E_TX_CTRL2,
ASP1_F_TX_CTRL1,
ASP1_F_TX_CTRL2,
ASP1_G_TX_CTRL1,
ASP1_G_TX_CTRL2,
ASP1_H_TX_CTRL1,
ASP1_H_TX_CTRL2,
ASP2_A_TX_CTRL1,
ASP2_A_TX_CTRL2,
ASP2_B_TX_CTRL1,
ASP2_B_TX_CTRL2,
ASP2_C_TX_CTRL1,
ASP2_C_TX_CTRL2,
ASP2_D_TX_CTRL1,
ASP2_D_TX_CTRL2,
ASP2_E_TX_CTRL1,
ASP2_E_TX_CTRL2,
ASP2_F_TX_CTRL1,
ASP2_F_TX_CTRL2,
ASP2_G_TX_CTRL1,
ASP2_G_TX_CTRL2,
ASP2_H_TX_CTRL1,
ASP2_H_TX_CTRL2,
ASP1_A_RX_CTRL1,
ASP1_A_RX_CTRL2,
ASP1_B_RX_CTRL1,
ASP1_B_RX_CTRL2,
ASP1_C_RX_CTRL1,
ASP1_C_RX_CTRL2,
ASP1_D_RX_CTRL1,
ASP1_D_RX_CTRL2,
ASP1_E_RX_CTRL1,
ASP1_E_RX_CTRL2,
ASP1_F_RX_CTRL1,
ASP1_F_RX_CTRL2,
ASP1_G_RX_CTRL1,
ASP1_G_RX_CTRL2,
ASP1_H_RX_CTRL1,
ASP1_H_RX_CTRL2,
ASP2_A_RX_CTRL1,
ASP2_A_RX_CTRL2,
ASP2_B_RX_CTRL1,
ASP2_B_RX_CTRL2,
ASP2_C_RX_CTRL1,
ASP2_C_RX_CTRL2,
ASP2_D_RX_CTRL1,
ASP2_D_RX_CTRL2,
ASP2_E_RX_CTRL1,
ASP2_E_RX_CTRL2,
ASP2_F_RX_CTRL1,
ASP2_F_RX_CTRL2,
ASP2_G_RX_CTRL1,
ASP2_G_RX_CTRL2,
ASP2_H_RX_CTRL1,
ASP2_H_RX_CTRL2,
CS8409_I2C_ADDR,
CS8409_I2C_DATA,
CS8409_I2C_CTRL,
CS8409_I2C_STS,
CS8409_I2C_QWRITE,
CS8409_I2C_QREAD,
CS8409_SPI_CTRL,
CS8409_SPI_TX_DATA,
CS8409_SPI_RX_DATA,
CS8409_SPI_STS,
CS8409_PFE_COEF_W1, /* Parametric filter engine coefficient write 1*/
CS8409_PFE_COEF_W2,
CS8409_PFE_CTRL1,
CS8409_PFE_CTRL2,
CS8409_PRE_SCALE_ATTN1,
CS8409_PRE_SCALE_ATTN2,
CS8409_PFE_COEF_MON1, /* Parametric filter engine coefficient monitor 1*/
CS8409_PFE_COEF_MON2,
CS8409_ASP1_INTRN_STS,
CS8409_ASP2_INTRN_STS,
CS8409_ASP1_RX_SCLK_COUNT,
CS8409_ASP1_TX_SCLK_COUNT,
CS8409_ASP2_RX_SCLK_COUNT,
CS8409_ASP2_TX_SCLK_COUNT,
CS8409_ASP_UNS_RESP_MASK,
CS8409_LOOPBACK_CTRL = 0x80,
CS8409_PAD_CFG_SLW_RATE_CTRL = 0x82, /* Pad Config and Slew Rate Control (CIR = 0x0082) */
};
/* CS42L42 Specific Definitions */
#define CS8409_MAX_CODECS 8
#define CS42L42_VOLUMES (4U)
#define CS42L42_HP_VOL_REAL_MIN (-63)
#define CS42L42_HP_VOL_REAL_MAX (0)
#define CS42L42_AMIC_VOL_REAL_MIN (-97)
#define CS42L42_AMIC_VOL_REAL_MAX (12)
#define CS42L42_REG_AMIC_VOL_MASK (0x00FF)
#define CS42L42_HSTYPE_MASK (0x03)
#define CS42L42_I2C_TIMEOUT_US (20000)
#define CS42L42_I2C_SLEEP_US (2000)
#define CS42L42_PDN_TIMEOUT_US (250000)
#define CS42L42_PDN_SLEEP_US (2000)
#define CS42L42_ANA_MUTE_AB (0x0C)
#define CS42L42_FULL_SCALE_VOL_MASK (2)
#define CS42L42_FULL_SCALE_VOL_0DB (0)
#define CS42L42_FULL_SCALE_VOL_MINUS6DB (1)
/* Dell BULLSEYE / WARLOCK / CYBORG Specific Definitions */
#define CS42L42_I2C_ADDR (0x48 << 1)
#define CS8409_CS42L42_RESET GENMASK(5, 5) /* CS8409_GPIO5 */
#define CS8409_CS42L42_INT GENMASK(4, 4) /* CS8409_GPIO4 */
#define CS8409_CYBORG_SPEAKER_PDN GENMASK(2, 2) /* CS8409_GPIO2 */
#define CS8409_WARLOCK_SPEAKER_PDN GENMASK(1, 1) /* CS8409_GPIO1 */
#define CS8409_CS42L42_HP_PIN_NID CS8409_PIN_ASP1_TRANSMITTER_A
#define CS8409_CS42L42_SPK_PIN_NID CS8409_PIN_ASP2_TRANSMITTER_A
#define CS8409_CS42L42_AMIC_PIN_NID CS8409_PIN_ASP1_RECEIVER_A
#define CS8409_CS42L42_DMIC_PIN_NID CS8409_PIN_DMIC1_IN
#define CS8409_CS42L42_DMIC_ADC_PIN_NID CS8409_PIN_DMIC1
/* Dolphin */
#define DOLPHIN_C0_I2C_ADDR (0x48 << 1)
#define DOLPHIN_C1_I2C_ADDR (0x49 << 1)
#define DOLPHIN_HP_PIN_NID CS8409_PIN_ASP1_TRANSMITTER_A
#define DOLPHIN_LO_PIN_NID CS8409_PIN_ASP1_TRANSMITTER_B
#define DOLPHIN_AMIC_PIN_NID CS8409_PIN_ASP1_RECEIVER_A
#define DOLPHIN_C0_INT GENMASK(4, 4)
#define DOLPHIN_C1_INT GENMASK(0, 0)
#define DOLPHIN_C0_RESET GENMASK(5, 5)
#define DOLPHIN_C1_RESET GENMASK(1, 1)
#define DOLPHIN_WAKE (DOLPHIN_C0_INT | DOLPHIN_C1_INT)
enum {
CS8409_BULLSEYE,
CS8409_WARLOCK,
CS8409_WARLOCK_MLK,
CS8409_WARLOCK_MLK_DUAL_MIC,
CS8409_CYBORG,
CS8409_FIXUPS,
CS8409_DOLPHIN,
CS8409_DOLPHIN_FIXUPS,
CS8409_ODIN,
};
enum {
CS8409_CODEC0,
CS8409_CODEC1
};
enum {
CS42L42_VOL_ADC,
CS42L42_VOL_DAC,
};
#define CS42L42_ADC_VOL_OFFSET (CS42L42_VOL_ADC)
#define CS42L42_DAC_CH0_VOL_OFFSET (CS42L42_VOL_DAC)
#define CS42L42_DAC_CH1_VOL_OFFSET (CS42L42_VOL_DAC + 1)
struct cs8409_i2c_param {
unsigned int addr;
unsigned int value;
unsigned int delay;
};
struct cs8409_cir_param {
unsigned int nid;
unsigned int cir;
unsigned int coeff;
};
#ifdef APPLE_CODECS
struct unsol_item {
struct list_head list;
unsigned int idx;
unsigned int res;
};
struct hda_cvt_setup_apple {
hda_nid_t nid;
u8 stream_tag;
u8 channel_id;
u16 format_id;
unsigned char active; /* cvt is currently used */
unsigned char dirty; /* setups should be cleared */
};
#endif
struct sub_codec {
struct hda_codec *codec;
unsigned int addr;
unsigned int reset_gpio;
unsigned int irq_mask;
const struct cs8409_i2c_param *init_seq;
unsigned int init_seq_num;
unsigned int hp_jack_in:1;
unsigned int mic_jack_in:1;
#ifdef APPLE_CODECS
unsigned int linein_jack_in:1;
#endif
unsigned int suspended:1;
unsigned int paged:1;
unsigned int last_page;
unsigned int hsbias_hiz;
unsigned int full_scale_vol:1;
unsigned int no_type_dect:1;
s8 vol[CS42L42_VOLUMES];
};
/*
* Per-machine callbacks; older kernels stored these in codec->patch_ops (removed
* in favor of struct hda_codec_driver + ops->probe/remove/...).
*/
struct cs8409_variant_ops {
int (*build_controls)(struct hda_codec *codec);
int (*build_pcms)(struct hda_codec *codec);
int (*init)(struct hda_codec *codec);
void (*unsol_event)(struct hda_codec *codec, unsigned int res);
int (*suspend)(struct hda_codec *codec);
int (*resume)(struct hda_codec *codec);
int (*check_power_status)(struct hda_codec *codec, hda_nid_t nid);
void (*destroy)(struct hda_codec *codec);
};
struct cs8409_spec {
struct hda_gen_spec gen;
struct hda_codec *codec;
const struct cs8409_variant_ops *vops;
struct sub_codec *scodecs[CS8409_MAX_CODECS];
unsigned int num_scodecs;
unsigned int gpio_mask;
unsigned int gpio_dir;
unsigned int gpio_data;
int speaker_pdn_gpio;
struct mutex i2c_mux;
unsigned int i2c_clck_enabled;
unsigned int dev_addr;
struct delayed_work i2c_clk_work;
unsigned int playback_started:1;
unsigned int capture_started:1;
unsigned int init_done:1;
unsigned int build_ctrl_done:1;
#ifdef APPLE_CODECS
// additional data for Apple 8409 system
unsigned int spdif_detect:1;
unsigned int spdif_present:1;
unsigned int sense_b:1;
hda_nid_t vendor_nid;
/* digital beep */
hda_nid_t beep_nid;
/* for MBP SPDIF control */
int (*spdif_sw_put)(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
// so it appears we have "concurrency" in the linux HDA code
// in that if unsolicited responses occur which perform extensive verbs
// the hda verbs are intermixed with eg extensive start playback verbs
// on OSX we appear to have blocks of verbs during which unsolicited responses
// are logged but the unsolicited verbs occur after the verb block
// this flag is used to flag such verb blocks and the list will store the
// responses
// we use a pre-allocated list - if we have more than 10 outstanding unsols
// we will drop
// not clear if mutexes would be the way to go
int block_unsol;
struct list_head unsol_list;
struct unsol_item unsol_items_prealloc[10];
int unsol_items_prealloc_used[10];
// add in specific nids for the intmike and linein as they seem to swap
// between macbook pros (14,3) and imacs (18,3)
int intmike_nid;
int linein_nid;
int intmike_adc_nid;
int linein_amp_nid;
// the following flag bits also need swapping
int reg9_intmike_dmic_mo;
int reg9_linein_dmic_mo;
int reg82_intmike_dmic_scl;
int reg82_linein_dmic_scl;
// add explicit stream format store entries as per hda_codec using a local definition
// of hda_cvt_setup (which is local to hda_codec.c)
// also use explicit nid versions
// (except that means either need explicit functions for each nid or have to lookup
// nid each time want to use in a generic function with nid argument)
struct hda_cvt_setup_apple nid_0x02;
struct hda_cvt_setup_apple nid_0x03;
struct hda_cvt_setup_apple nid_0x0a;
struct hda_cvt_setup_apple nid_0x22;
struct hda_cvt_setup_apple nid_0x23;
struct hda_cvt_setup_apple nid_0x1a;
// new item to deal with jack presence as Apple (and now Dell) seems to have barfed
// the HDA spec by using a separate headphone chip
int jack_present;
// save the type of headphone connected
int headset_type;
// if headphone has mike or not
int have_mike;
// if headphone has buttons or not
int have_buttons;
// current stream channel count
int stream_channels;
// set when playing for plug/unplug events while playing
int playing;
// set when capturing for plug/unplug events while capturing
int capturing;
// changing coding - OSX sets up the format on plugin
// then does some minimal setup when start play
// initial coding delayed any format setup till actually play
// this works for no mike but not for mike - we need to initialize
// the mike on plugin
// this flag will be set when we have done the format setup
// so know if need to do it on play or not
// now need 2 flags - one for play and one for capture
int headset_play_format_setup_needed;
int headset_capture_format_setup_needed;
int headset_presetup_done;
int use_data;
// this is new item for dealing with headset plugins
// so can distinguish which phase we are in if have multiple interrupts
// not really used now have analyzed interrupts properly
int headset_phase;
// another dirty hack item to manage the different headset enable codes
int headset_enable;
int play_init;
int capture_init;
// new item to limit times we redo unmute/play
struct timespec64 last_play_time;
// record the first play time - we have a problem there
// some initial plays that I dont understand - so skip any setup
// till sometime after the first play
struct timespec64 first_play_time;
#endif
/* verb exec op override */
int (*exec_verb)(struct hdac_device *dev, unsigned int cmd, unsigned int flags,
unsigned int *res);
};
extern const struct snd_kcontrol_new cs42l42_dac_volume_mixer;
extern const struct snd_kcontrol_new cs42l42_adc_volume_mixer;
int cs42l42_volume_info(struct snd_kcontrol *kctrl, struct snd_ctl_elem_info *uinfo);
int cs42l42_volume_get(struct snd_kcontrol *kctrl, struct snd_ctl_elem_value *uctrl);
int cs42l42_volume_put(struct snd_kcontrol *kctrl, struct snd_ctl_elem_value *uctrl);
extern const struct hda_pcm_stream cs42l42_48k_pcm_analog_playback;
extern const struct hda_pcm_stream cs42l42_48k_pcm_analog_capture;
extern const struct hda_quirk cs8409_fixup_tbl[];
extern const struct hda_model_fixup cs8409_models[];
extern const struct hda_fixup cs8409_fixups[];
extern const struct hda_verb cs8409_cs42l42_init_verbs[];
extern const struct cs8409_cir_param cs8409_cs42l42_hw_cfg[];
extern const struct cs8409_cir_param cs8409_cs42l42_bullseye_atn[];
extern struct sub_codec cs8409_cs42l42_codec;
extern const struct hda_verb dolphin_init_verbs[];
extern const struct cs8409_cir_param dolphin_hw_cfg[];
extern struct sub_codec dolphin_cs42l42_0;
extern struct sub_codec dolphin_cs42l42_1;
void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix, int action);
void dolphin_fixups(struct hda_codec *codec, const struct hda_fixup *fix, int action);
#endif