1. S/PDIF DIT — dummy codec component
Understand why a digital transmitter can be represented as an ASoC component without a physical codec, how its DAI advertises PCM constraints, and how its DAPM output route models the signal endpoint.
From the first PCM sample to a reviewed upstream patch. Codec drivers, DAPM, clocking, Device Tree, the physical bench, and the next Rust frontier.
ASoC Field Manual · Linux Audio / From digital samples to upstream patches. A working reference for engineers who need to understand, implement, diagnose and submit Linux ASoC drivers. Read sequentially or jump to an incident.
This guide is aimed at embedded Linux developers who already know the kernel basics. It concentrates on what happens on a board, where to look in kernel source, which assumptions break, and how to demonstrate that a driver actually works. The examples are illustrative, not guaranteed drop-in drivers: consult the exact kernel tree and device datasheet before coding.
| Your situation | Begin with | Then |
|---|---|---|
aplay -l shows no cards |
Probe & binding | Diagnostics → Hardware lab |
| Card exists, silence at the jack | DAPM & controls | Clocks → Signal tracing |
| Writing a new I²C codec driver | Codec walkthrough | Regmap → DAI ops → DAPM |
| Audio breaks only at 44.1 kHz | Clocking & formats | Hardware lab → XRUNs |
| Board DTS needs a sound card | Device Tree | Machine links → Probe |
| Preparing first ASoC RFC | Upstream | Tests → Rust frontier |
# Inside a checked-out Linux kernel tree
ls sound/soc/
ls sound/soc/codecs/
ls sound/soc/fsl/
rg 'struct snd_soc_component_driver' sound/soc/codecs | head
rg 'devm_snd_soc_register_component' sound/soc/codecs | head
rg 'struct snd_soc_dai_ops' include/sound/soc-dai.h
rg 'struct snd_soc_dai_link' include/sound/soc.h
The manual's examples reference upstream ASoC overview, DAPM, clocking, machine drivers, patch process, and real SGTL5000 codec and i.MX card sources; see the References chapter for links.
CHECK = read-only inspection; CHANGE = may reconfigure hardware; RISK = can be loud, destructive or disrupt production. Commands that write registers, change mixer volume, modify clocking, or reload drivers require an expendable development board. Take a baseline first. Always start with a quiet volume, known speaker/headphone load and current-limited supply when appropriate.
Before ASoC, keep three separate notions straight: samples, frames, and serial slots. Each affects your registers and constraints.
A sample is an instantaneous value for one channel. An interleaved stereo PCM frame contains one left and one right sample. At 48,000 frames/s, stereo S16_LE data carries 48,000 × 2 × 16 = 1,536,000 payload bits/s before serialization, padding or framing. PCM period/buffer sizes count frames in many ALSA APIs; byte/frame conversions depend on format and channels.
Common sample formats: S16_LE, S24_LE (24 valid bits in a 32-bit container), S24_3LE (3 packed bytes/sample), S32_LE. Do not confuse valid bits, memory container width and I²S slot width. A 24-bit sample may travel in a 32-bit slot.
Example: 48 kHz, stereo, 32-bit slots →
BCLK = 48,000 × 2 × 32 = 3.072 MHz. MCLK might be256 × Fs = 12.288 MHzfor a device that supports that ratio, but do not assume any ratio without checking its datasheet.
Playback: application → ALSA PCM ring buffer → DMA → CPU DAI shift register → serial wires → codec DAC → analog output. Capture: microphone → codec ADC → serial wires → CPU DAI → DMA → ALSA ring buffer → application.
A CPU DAI configures the SoC serial engine (SSI/SAI/I²S), while the codec DAI configures the endpoint audio interface. The machine/card configuration tells the core which ones are connected and who provides clocks.
| Signal | Common name | Purpose |
|---|---|---|
| BCLK | SCK / bit clock | Shift one serial bit per active edge |
| LRCLK | WS / frame sync | Channel/frame boundary (often Fs in stereo) |
| SDOUT / SDIN | SD / TX / RX | Serial sample payload |
| MCLK | SYSCLK | Optional or required clock input for codec PLL/DAC/ADC |
| I²C / SPI | control bus | Configure codec registers; not the PCM sample stream |
I²S conventionally carries a one-BCLK delay after LRCLK transition. Left-justified and DSP/TDM modes differ. Align both ends on polarity, slot allocation, sample delay and clock-provider roles.
Lab question: if aplay reports a successful write but output is silent, which five independent parts of the chain would you inspect before editing the driver?
ASoC sits on top of ALSA to let reusable components form an audio card. The crucial boundaries are the CPU DAI, codec DAI, PCM/DMA component, and machine/card description. Add DAPM for power-aware audio routing.
| Kernel object | Why it exists | Usually provided by |
|---|---|---|
snd_soc_component_driver |
Lifecycle, mixer controls, DAPM, optional PCM callbacks | Codec or SoC-side component |
snd_soc_dai_driver |
Audio interface capabilities and callbacks | Codec/CPU DAI driver |
snd_soc_dai_ops |
startup, set_fmt, hw_params, set_sysclk, etc. |
Driver implementing a DAI |
snd_soc_card |
Assembled sound-card instance | Machine/card driver |
snd_soc_dai_link |
Connects CPU, codec and platform endpoints | Machine/card driver |
snd_soc_pcm_runtime |
Bound runtime association for one link | ASoC core |
snd_soc_dapm_widget |
Route graph and power-switchable hardware blocks | Codec and machine |
A component's registration does not guarantee a sound card. The card generally appears when matching pieces exist and binding/probing completes. Missing components can cause probe deferral or failed card registration.
Older docs discuss “codec / platform / machine” classes. Modern ASoC is component-based: a component isn't necessarily an external codec; the SoC-side PCM engine and digital interfaces can be components too. Check include/sound/soc*.h and actual drivers for your tree. Avoid copying decade-old callback signatures mechanically.
For the NXP i.MX53 with SGTL5000: an I²C codec driver exposes an audio DAI; an i.MX SSI DAI exposes the other side; DMA powers the PCM samples; the machine driver binds the pieces. Useful source files: sound/soc/codecs/sgtl5000.c, sound/soc/fsl/imx-sgtl5000.c, plus the relevant SoC DAI/DMA drivers.
Exercise: locate the snd_soc_dai_link setup in imx-sgtl5000.c. Identify codec DAI name, CPU DAI identification, platform side and when the sysclk is configured. Don't assume a generic simple-audio-card implementation represents every legacy board.
The board-specific machine/card driver describes how the CPU DAI, codec DAI, clocks, routing, and optional amplifiers fit together. The CPU DAI owns the SoC-side serial interface; the codec component owns codec controls, DAPM widgets/routes, and codec-specific register behavior. The PCM/platform side supplies the data-transfer engine where applicable.
Reading rule: follow one working board from Device Tree match → card registration → DAI link setup → stream hw_params → trigger → DAPM power-up. A driver is only one part of that contract.
When reading an unfamiliar board, map the contracts in this order: machine/card link → CPU DAI and PCM platform → codec component/DAI → Device Tree connections. The card joins endpoints; the CPU DAI controls the SoC serial port; the PCM platform moves samples (often via DMA); the codec component owns chip controls and DAPM; firmware describes the actual board topology. A file name alone does not tell you which layer owns a failure.
If you can describe the lifecycle without looking at the source, you can debug most ASoC integration failures methodically.
snd_soc_card and DAI links; ASoC binds the required pieces. The system may wait for other drivers with -EPROBE_DEFER.open /dev/snd/pcmC?D?p
└─ startup constraints, DAI startup, clock/power preparation as appropriate
hw_params(rate, channels, format, buffer)
└─ machine link + CPU/codec DAI hw_params + PCM/DMA allocation/setup
prepare
└─ prepare DMA and hardware for transfer
trigger(START)
└─ start engine / streams → DMA advances ring-buffer pointer
write / period notifications / pointer updates
trigger(STOP) → hw_free → shutdown → close
This is an ordering guide, not a promised identical callback sequence. ASoC versions, topology, compressed audio paths, and driver implementation details affect exact order. Inspect sound/soc/soc-pcm.c, runtime code and instrument tracepoints.
startup: per-stream constraints or activation; don't assume final rate/format yet.hw_params: final negotiated hardware parameters are available; set clock divider, data width and channel slots.set_fmt / set_sysclk / set_tdm_slot: control hardware protocol and clocking; callbacks may be invoked by machine setup or driver paths, depending on configuration.prepare: prepares a configured stream to run.trigger: start/stop/pause transition where latency and locking matter. Don't sleep from a callback that can execute in atomic context; verify callback contract.shutdown: inverse cleanup for stream lifetime; distinguish per-device lifecycle from per-PCM-stream lifecycle.| Observed stage | Strongest initial lead |
|---|---|
| No I²C device, no probe | Bus wiring / address / DT enumeration / reset / power |
| Codec probes, no ALSA card | Machine card, unresolved link, missing DAI, deferred probe |
| Card appears, no PCM playback device | DAI link design, PCM engine, no_pcm, DPCM FE/BE configuration |
| PCM opens but rejects rate | Constraints, codec and CPU DAI supported formats/rates |
aplay runs but silence |
DAPM routing, mixer, clock, amplifier enable, pins |
| Starts then XRUN | DMA/rate/timing/interrupts/period configuration |
Important: dmesg saying “codec detected” alone means little about the rest of this chain.
When playback fails, move one layer at a time rather than changing several drivers at once:
hw_params succeeds.Keep a known-good command, kernel revision, dmesg excerpt, and measured clock values with every reproduction.
A DAI (Digital Audio Interface) is a configuration boundary. Both endpoints must agree on sample encoding and clocks. An I²C codec's control bus is separate from its digital audio DAI.
snd_soc_dai_ops| Callback | Purpose | Typical failure |
|---|---|---|
set_fmt |
I²S vs left/right justified vs DSP; polarity; clock roles | Codec interprets shifted samples |
set_sysclk |
External/system clock frequency and direction | PLL/divider can't derive Fs |
set_pll |
Configure codec PLL, when supported | Invalid rate range or unlocked PLL |
set_tdm_slot |
Time-slot masks/count/slot width | Wrong channel positions |
hw_params |
Configure rate, sample width, channels | Unsupported combination |
mute_stream |
Digital stream mute | Pops or unexpectedly silent playback |
startup / shutdown |
Per-stream setup/teardown | Missing constraints or leaks |
Callback signatures, enum names and layout must be taken from your exact tree. E.g. modern SND_SOC_DAIFMT_CBC_CFC notation expresses consumer of bit clock and frame clock; older source may use older provider/master terminology.
slots × slot_width × Fs commonly determines BCLK, not channels × valid_sample_bits × Fs.Sample rate = 48,000 frames/s
Slots = 4
Slot width = 32 bits
BCLK = 48,000 × 4 × 32 = 6.144 MHz
Live channels = 2 (slots 0 and 2, for example)
Even with only two live channels, four slots still consume 128 serial bits/frame. Check the controller supports the desired inactive-slot behavior.
On a scope / analyzer, measure BCLK and LRCLK, then decode data relative to frame sync. Wrong BCLK: inspect divider. Correct BCLK, shifted samples: inspect set_fmt protocol and polarity. Correct clocks/data, no analog sound: inspect codec DAC path, DAPM and supplies.
Useful source: sound/soc/codecs/sgtl5000.c implements format, hw_params, MCLK constraints and mute handling. It is intentionally more realistic than a toy driver's two callbacks.
Most “audio works at 48k, fails at 44.1k” bugs are clocking mismatches. Work from measured signals backward, not from the requested ALSA rate forward.
MCLK / SYSCLK is the codec's reference clock (from an oscillator or SoC clock). BCLK serializes bits. LRCLK/FS marks frames. Some codecs derive internal DAC/ADC clocks from PLLs and dividers; some can operate without an external MCLK in specific modes. These are independent constraints.
48,000 Hz × 256 = 12.288 MHz (one possible codec SYSCLK)
44,100 Hz × 256 = 11.2896 MHz (different clock family)
48,000 Hz × 2 slots × 32 = 3.072 MHz BCLK
Ratios above are examples. Check the exact codec datasheet's permitted ratios, PLL lock criteria and oversampling modes. Don't assume a codec accepts arbitrary MCLK.
A DAI link normally needs a coherent choice about who produces BCLK and LRCLK. MCLK may be sourced separately. Naming in older docs: “master/slave”; newer preferred terms: “provider/consumer.” Inspect bitclock-master, frame-master and format properties in relevant bindings.
rate, channels, PCM sample format, slot width, BCLK/LRCLK polarity and clock providers.set_sysclk, set_fmt, hw_params; compare programmed rates with measured ones.assigned-clock-rates may change the input to a parent/divider, but doesn't guarantee the codec sees that frequency.See the kernel clocking documentation for framework terminology, then read your codec and controller source for the actual constraints.
A correct codec driver may work on zero boards if the Device Tree card/link topology is wrong. DT describes devices and connectivity; it is not a substitute for runtime verification.
#sound-dai-cells when used by a binding.simple-audio-card or an audio graph binding./ {
sound {
compatible = "simple-audio-card";
simple-audio-card,name = "example-board-audio";
simple-audio-card,format = "i2s";
/* Provider/consumer clock configuration depends on bindings */
simple-audio-card,cpu {
sound-dai = <&i2s0>;
};
simple-audio-card,codec {
sound-dai = <&codec>;
};
};
};
&i2c1 {
status = "okay";
codec: codec@1a {
compatible = "vendor,actual-codec"; /* replace with real binding */
reg = <0x1a>;
#sound-dai-cells = <0>;
/* supplies, clocks, etc. per binding */
};
};
&i2s0 {
status = "okay";
/* DMA, clocks and pinctrl per SoC binding */
};
Concept only: vendor,actual-codec is not a real device and the example is intentionally incomplete. Use your exact codec's YAML schema; don't copy into a production DTS without pinmux, supplies, clock role and DAI details.
sound-dai and phandle argument cells#sound-dai-cells = <0> means the reference carries no additional DAI-selector cells. Multi-DAI components might use one or more selector cells depending on binding. Never interpret sound-dai = <&device 1> without checking the provider's number of DAI cells and schema.
reg belongs to the parent busFor codec@1a under I²C, reg = <0x1a> is an I²C address, not a CPU physical MMIO base. For an SoC I²S controller, reg typically describes MMIO ranges with parent address/size cells and possibly translation via ranges. Don't mix them.
# From the kernel source root; exact DT_SCHEMA_FILES matching depends on tree
make dt_binding_check DT_SCHEMA_FILES=/sound/
make ARCH=arm dtbs_check DT_SCHEMA_FILES=/sound/
# Decode a binary for investigation; may lose source provenance
dtc -I dtb -O dts -o /tmp/board.decoded.dts arch/arm/boot/dts/.../board.dtb
# Check running, bootloader-supplied blob if accessible
ls /sys/firmware/fdt /proc/device-tree 2>/dev/null
DTB cannot reveal original DTS/DTSI override history. Review source files and preprocessing behavior to learn who set the final property. Bindings can use unevaluatedProperties: false / additionalProperties: false where appropriate; learn schema conventions rather than hard-coding allowed properties.
The i.MX53 Voipac SGTL5000 example uses a dedicated fsl,imx-audio-sgtl5000 sound binding with ssi-controller and audio-codec phandles. That's not the same wiring syntax as the simple-audio-card example. Look at sound/soc/fsl/imx-sgtl5000.c and the matching bindings before translating.
Start from the board DTS and the codec's compatible, then follow the match table to the driver. Search for the card's sound-dai references and compare the binding schema with the board's clock-provider, bus-format, supply and routing properties. Useful source-reading commands:
grep -R 'compatible.*wm8731' sound/ arch/
grep -R 'SND_SOC_DAPM' sound/soc/codecs
git log --oneline -- sound/soc/codecs/wm8731.c
git blame sound/soc/codecs/wm8731.c
Treat snippets in tutorials as navigation aids, not as substitutes for the exact source and YAML binding in the kernel revision being built.
A codec typically has two registration surfaces: the bus driver that discovers/manages it, and the ASoC component/DAI registered by that driver. Keeping these roles separate helps avoid ownership, lifetime and deferral bugs.
// Schematic API shape — not a standalone buildable kernel module.
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <sound/soc.h>
static const struct snd_soc_dai_ops demo_dai_ops = {
.hw_params = demo_hw_params,
.set_fmt = demo_set_fmt,
};
static struct snd_soc_dai_driver demo_dai = {
.name = "demo-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &demo_dai_ops,
};
static const struct snd_soc_component_driver demo_component = {
.name = "demo-codec",
/* controls, DAPM widgets and routes go here */
};
static int demo_i2c_probe(struct i2c_client *client)
{
struct demo_priv *priv;
int ret;
priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* Acquire supplies, reset, clock, regmap; validate chip ID. */
i2c_set_clientdata(client, priv);
ret = demo_hw_init(client, priv); /* your implementation */
if (ret)
return dev_err_probe(&client->dev, ret, "init failed\n");
return devm_snd_soc_register_component(&client->dev,
&demo_component,
&demo_dai, 1);
}
No demo_priv, demo_hw_init, demo_hw_params or demo_set_fmt implementations are supplied here: this is a contract sketch, not a compilable driver. Real driver needs OF/I²C match tables, driver registration, Kconfig/Makefile, resource management, safety and format coverage.
Bus probe: obtain mandatory supplies/clock/reset, establish regmap, check chip ID/revision, initialize safe register defaults, then register component. ASoC component: controls, DAPM, optional component callbacks/PCM services. DAI callbacks: serial audio format/clock/stream-specific logic. Machine/card: board-specific links and analog endpoints; avoid embedding a fixed board's routing into a reusable codec.
devm_* resource ownership is tied to the device, not to each PCM stream. priv must remain alive for all callbacks until unregistration. If your driver uses un-managed work, IRQs or worker threads, cancel or synchronize them before lifetime ends. The right cleanup order depends on resource ownership and callback execution context.
dev_err_probe() for a missing dependency. -EPROBE_DEFER may be correct for not-yet-ready suppliers.The upstream sound/soc/codecs/spdif_transmitter.c is a useful contrast with a register-heavy codec: it provides a component descriptor, a playback DAI capability declaration, one DAPM output and a platform-probe registration path. It does not program a physical codec over I²C/SPI. The DAPM route connects the Playback stream to spdif-out; the controller is responsible for emitting the digital S/PDIF stream. Read the complete upstream file in the interactive source walkthrough.
Regmap separates how the hardware is accessed (I²C/SPI/MMIO) from how the driver talks about register values, defaults, read permissions and caching.
static bool demo_volatile_reg(struct device *dev, unsigned int reg)
{
return reg == DEMO_STATUS || reg == DEMO_IRQ_STATUS;
}
static const struct regmap_config demo_regmap_config = {
.reg_bits = 8, /* device register address width */
.val_bits = 16, /* register data width */
.max_register = DEMO_MAX_REGISTER,
.volatile_reg = demo_volatile_reg,
.cache_type = REGCACHE_MAPLE, /* example; verify availability in tree */
};
/* In I2C probe, AFTER appropriate device resources are prepared: */
priv->regmap = devm_regmap_init_i2c(client, &demo_regmap_config);
if (IS_ERR(priv->regmap))
return dev_err_probe(&client->dev, PTR_ERR(priv->regmap),
"regmap init failed\n");
Choose register/val widths, endianness and cache from the datasheet. Check actual cache types in your kernel: examples use typical values, not a portable guarantee. Regmap caching can hide physical register values after suspended power-off.
regmap_read, regmap_write, regmap_update_bits: read/modify/write register fields, serialized as documented by regmap.regmap_bulk_read/write: useful for adjacent registers if bus semantics permit.regcache_cache_only(map, true) prevents writes to powered-off hardware while updating cached state (depending on cache).regcache_mark_dirty(map), regcache_sync(map) help restore state after register loss; sequence is critical around power domains and reset.regmap_irq_chip can centralize mapped interrupt handling for devices with interrupt/status/mask registers.Acquire supplies + clocks + reset GPIO
→ enable essential supplies
→ assert/deassert reset with specified timings
→ wait for oscillator / analog stabilization
→ read chip ID (if supported)
→ configure safe defaults / mute
→ register ASoC component
→ use DAPM/runtime PM for on-demand blocks
Not every codec permits this sequence; consult hardware docs for rail ordering and bias settle times. Never assume a regulator's regulator-always-on means the device can ignore power sequencing.
| Mistake | Symptom |
|---|---|
| Mark every register nonvolatile | Stale status reads, confusing jack detection |
| Forget regcache sync after power loss | First playback after resume silent |
| Confuse reset delay with PLL lock time | Intermittent probe or garbled startup |
| Writes to powered-off bus | I²C failures after suspend |
| Unbalanced clock enable / PM reference | Audio works once or never powers down |
Practical exercise: compare cached control values with actual silicon after cold boot vs resume. Ensure debug dumps are safe and don't trigger read-to-clear status unexpectedly.
The WM8731 driver combines a register-default table, a volatile reset register, controls expressed as register fields, and regulator/clock handling. When tracing a control, follow its ALSA control declaration to the register and bit field, then check how regcache is synchronized across power transitions. The dedicated WM8731 source walkthrough presents the complete upstream file line by line.
Dynamic Audio Power Management powers only the hardware blocks required by active routes and streams. It is not a software equalizer, and it is not simply the Linux runtime PM framework. It responds to stream state, mixer state, and routing, not just driver probe.
A widget represents an audio block or endpoint: DAC, ADC, PGA, MIXER, MUX, SUPPLY, INPUT, OUTPUT, AIF_IN, AIF_OUT. Routes define how a signal could flow, optionally conditioned by mixer/mux controls.
static const struct snd_soc_dapm_widget demo_widgets[] = {
SND_SOC_DAPM_DAC("DAC", "Playback", DEMO_POWER, 0, 0),
SND_SOC_DAPM_OUTPUT("LINEOUT"),
};
static const struct snd_soc_dapm_route demo_routes[] = {
/* Sink, optional control name, source */
{ "LINEOUT", NULL, "DAC" },
};
static const struct snd_soc_component_driver demo_component = {
.dapm_widgets = demo_widgets,
.num_dapm_widgets = ARRAY_SIZE(demo_widgets),
.dapm_routes = demo_routes,
.num_dapm_routes = ARRAY_SIZE(demo_routes),
};
Illustrative only: real widgets need correct register names and controls, and complete paths from stream to physical output. Route string names must match exactly. Sink/source order is easy to reverse!
debugfs path for your board/kernel.# CHECK: debugfs may not be mounted or CONFIG_DEBUG_FS may be off
sudo mount -t debugfs debugfs /sys/kernel/debug 2>/dev/null || true
find /sys/kernel/debug/asoc -maxdepth 4 -type f 2>/dev/null | head -40
# Browse widget files after identifying your card; paths vary by kernel.
Use SND_SOC_DAPM_PRE_PMU, POST_PMU, PRE_PMD, POST_PMD (as supported by widget and event flags) for strict device-specific sequencing. This can avoid pops by muting before removing analog power and delaying during analog bias stabilization.
Be precise about locking and sleepability. An event may execute under DAPM locks; callbacks must honor execution context, avoid recursion into operations that deadlock, and use only justified delays.
Card and PCM appear; register dump indicates all rails on; playback runs; DAPM shows DAC powered but LINEOUT off. Likely route/control/endpoint inconsistency. Fix graph rather than forcing register power bits in hw_params and breaking future suspend.
A PCM stream can run while the output remains silent. Verify that the active route reaches a powered output widget, that mixer switches and volume controls permit the signal, and that the codec's output pins and external amplifier are enabled. DAPM describes the logical power graph; it does not prove the external analog wiring is correct.
In the WM8731, DAPM widgets describe the DAC/ADC, output mixer, input mux, line input, microphone bias and physical endpoints. Routes connect those widgets and may use control names such as HiFi Playback Switch or Input Select. If a stream opens but no signal appears, inspect the route graph and active controls as well as the serial bus; DAPM can correctly leave an unconnected path powered down.
Userspace sees kcontrols through ALSA (amixer, alsamixer). In ASoC, controls frequently map register bitfields and also participate in the DAPM route graph.
/* Simplified illustration: correct register/bitfield constants required */
static const DECLARE_TLV_DB_SCALE(demo_dac_tlv, -6300, 100, 0);
static const struct snd_kcontrol_new demo_controls[] = {
SOC_SINGLE_TLV("DAC Playback Volume", DEMO_DAC_VOL,
0, 63, 0, demo_dac_tlv),
SOC_SINGLE("DAC Playback Switch", DEMO_CTRL, 0, 1, 0),
};
In real codecs volume scales are rarely guessed. Read the datasheet: is step size linear in dB? Is register encoding inverted? Are extreme values “mute”? Does the control represent per-channel registers? Correct range and TLV scaling matter to user-visible behavior.
aplay -l # list hardware devices (cards)
cat /proc/asound/cards
amixer -c 0 scontrols # simple controls, if exposed
amixer -c 0 controls # all control metadata
amixer -c 0 contents # detailed values and types
alsamixer -c 0 # interactive; start at low gain
amixer -c 0 set 'Some Control' ... is a write operation and can affect volume instantly. Never set arbitrary “100%” values on an unknown board with headphones connected.
snd_soc_jack represents a physical jack state to ASoC/ALSA. Reporting may come from codec IRQ, GPIO, ADC button ladder or other mechanisms. Machine-level jack pins can switch DAPM endpoints based on the state; the sound card may expose ALSA jack controls or input events depending on drivers.
Distinguish digital gain (PCM/DSP), DAC output levels, analog PGA/headphone amplifier, external power amplifier and speaker acoustics. Poor gain staging can clip even at conservative mixer percentages. Pops can result from bias/VMID settling or abrupt unmute; the solution is sequencing, not hiding the noise with a userspace delay.
Test: headphones unplugged vs plugged, mic capture levels, repeat rapid open/close and suspend/resume. If jack events are interrupt-driven, ensure no stale or bouncing state after resume.
The machine/card layer assembles the system. It can be handwritten C, simple-audio-card, audio-graph-card, or a subsystem-specific card framework. Be cautious about duplicating responsibilities: a reusable codec driver should not hardcode your evaluation board's connectors.
snd_soc_card
├─ dai_link[0]: CPU DAI ↔ codec DAI (+ PCM component)
├─ dai_link[1]: optional second link / HDMI / amplifier / DSP
├─ board DAPM widgets: Speaker, Headphone, Mic
└─ board routes: Headphone Jack ← codec output
DAI link fields and binding syntax have evolved. Current ASoC supports multi-component links and helper macros; use a current in-tree driver in the same subsystem rather than copying obsolete codec_name/platform_name single-string patterns.
Dynamic PCM adds Front End (FE) links visible to userspace and Back End (BE) links connecting DSP and hardware endpoints. An FE may route dynamically to different BEs, change runtime paths, and operate with no_pcm backends. aplay -l showing an FE does not imply every backend codec is active.
| Problem | Check |
|---|---|
-EPROBE_DEFER forever |
Component/DAI never registered, missing clock/regulator, mismatched DT phandle |
| Card registered but wrong PCM names | DAI link names and FE/BE type |
| Playback opens but BE not active | DPCM routing, DAPM mux, FE/BE constraints |
| No sound after capture starts | Shared clock/PLL ownership and symmetric-rate constraints |
Read sound/soc/soc-core.c, soc-pcm.c, soc-dapm.c and current audio graph/simple card implementations to see when ASoC core makes its decisions.
The best way to debug an ASoC board is to move from the lowest observable layer to the next, recording evidence at each step. Avoid changing five things at once.
| Gate | Evidence of success | Failing next move |
|---|---|---|
| 0: DT enumerates device | of_node exists; correct compatible/address |
Inspect DT status, parent bus and phandles |
| 1: Control bus | I²C transaction / safe chip-ID read | Check power, pullups, reset, pins |
| 2: Codec probe | driver bound, regmap initialized |
Inspect dmesg, driver match / dependencies |
| 3: Card registration | cat /proc/asound/cards |
Inspect machine driver, DAI link, deferral |
| 4: PCM exposure | aplay -l, aplay -D hw:C,D ... |
Check CPU DMA component and card links |
| 5: Digital clocks | Correct BCLK/LRCLK/MCLK while running | Clock tree, fmt, pad mux, dividers |
| 6: Digital data | Serial waveform contains samples | DMA/DAI transfer/format/trigger |
| 7: Analog path | Signal at codec pin | DAPM, gain, mute, codec supplies |
| 8: Product path | Speaker/headphone/test input functional | External amp enable, jack/route, shielding |
uname -a
cat /proc/asound/cards
aplay -l
arecord -l
cat /proc/interrupts | head -60
dmesg --color=never | grep -Ei 'asoc|snd|audio|codec|i2s|ssi|sai|defer'
find /sys/bus/i2c/devices -maxdepth 2 -name driver -type l -print 2>/dev/null
You can use i2cdetect only after examining whether probing unknown addresses could interfere with hardware. Some devices respond badly to SMBus probe sequences. A bus scanner is not a universal safe diagnostic.
Keep a known-good kernel and DTB available. If using Yocto, capture the image recipe, kernel source revision and module deployment mechanism. Don't replace production boot artifacts without an A/B or serial-console recovery path. A driver module can often be tested with an out-of-tree build, but a new in-tree codec usually changes Kconfig/Makefile/bindings and may need a kernel rebuild and reboot.
Exit criterion: reproduce the same playback/capture on cold boot and after suspend/resume, not just one lucky development-board run.
cd linux
grep -R 'compatible.*spdif' sound/soc
grep -R 'SND_SOC_DAPM' sound/soc/codecs
git log --oneline -- sound/soc/codecs/
git blame sound/soc/codecs/spdif_transmitter.c
Read the binding, probe path, nearby drivers, and file history together. Kernel APIs evolve, so verify every example against the target kernel revision rather than transplanting code from an unrelated version.
A simple S/PDIF DIT driver registers its component and DAI from platform probe, with no chip registers to initialize. A physical codec such as WM8731 additionally needs a control bus, regmap, clock/supply management, reset behavior and safe power sequencing. Do not copy the minimal DIT probe shape into a physical codec driver without those responsibilities. The code walkthrough page includes both upstream files.
Kernel audio failures are evidence problems. Work from an observed failure boundary: card not registered, PCM not advancing, DAPM disconnected, or analog stage silent.
cat /proc/asound/cards
cat /proc/asound/pcm
ls -l /dev/snd/
cat /proc/asound/card0/pcm0p/sub0/status 2>/dev/null
cat /proc/asound/card0/pcm0p/sub0/hw_params 2>/dev/null
find /sys/kernel/debug/asoc -maxdepth 3 -type f 2>/dev/null | head
Paths like pcm0p are illustrative: enumerate your actual cards/devices and confirm the node exists. ASoC debugfs files vary with kernel configuration and version.
# Start playback in another terminal, then inspect rapidly:
watch -n 0.5 'cat /proc/asound/card0/pcm0p/sub0/status'
# Compare hw_ptr / appl_ptr progression (where exposed)
If the DMA pointer stalls, investigate DMA channel, IRQs, trigger and CPU DAI. If pointers progress but output is silent, move down the chain to serial pins and DAPM.
# CHECK: available event names are kernel-configuration-dependent
sudo mount -t tracefs tracefs /sys/kernel/tracing 2>/dev/null || true
sudo grep -Ei 'snd|asoc|soc|pcm' /sys/kernel/tracing/available_events | head -60
# Pick an EVENT confirmed in available_events, not a guessed name.
# Then, as root, use set_event / trace to capture a short run:
# echo 'subsystem:event' > /sys/kernel/tracing/set_event
# echo 1 > /sys/kernel/tracing/tracing_on
# cat /sys/kernel/tracing/trace
# echo 0 > /sys/kernel/tracing/tracing_on
# : > /sys/kernel/tracing/set_event
Tracing, dynamic debug and debugfs are not guaranteed available. For instrumentation you control, add temporary tracepoints or dev_dbg() and avoid printing heavily in time-sensitive callbacks. For upstream, keep only useful permanent diagnostics.
# CHECK support first
ls /proc/dynamic_debug/control
# Example inspection: do not blindly enable every line in a hot path
sudo grep 'sound/soc' /proc/dynamic_debug/control | head
Use targeted file/function filters. Document exact debug commands in your test log, and restore the original settings afterward.
Check device dependencies, -EPROBE_DEFER and whether a supplier (regulator/clock/GPIO) has registered. On supported kernels /sys/kernel/debug/devices_deferred lists deferring devices. Remember that missing ASoC card can be a symptom of a codec, CPU DAI or unrelated supplier not ready.
A logic analyzer validates framing (BCLK/LRCLK/data); oscilloscope checks signal integrity, voltage levels and slew. Analog scope probes should be high impedance, properly grounded and not cause accidental shorts. Never attach a bench scope ground clip indiscriminately to an isolated or mains-referenced circuit.
A codec driver isn't proven until you can explain what voltage, clock and waveform physically appears on its pins. Keep a reproducible notebook for each configuration.
A pure 1 kHz sine at conservative level is a starting test (it won't identify all channel/format bugs). For channel mapping, use L-only and R-only tones; for clipping/gain, stepped amplitudes; for sample rate, compare measured frequency and LRCLK to expected value.
# Use aplay with a WAV file whose actual header is 48kHz, stereo, 16-bit.
# Replace CARD,DEV based on `aplay -l`; no resampling with `hw:`.
aplay -D hw:CARD,DEV test-48k-stereo.wav
# Capture: only if card supports it; adapt format to hardware
arecord -D hw:CARD,DEV -f S16_LE -c 2 -r 48000 -d 10 capture.wav
# Inspect supported hardware params (some versions/syntax differ)
aplay --dump-hw-params -D hw:CARD,DEV test-48k-stereo.wav
hw: avoids transparent plugin resampling, which is crucial when testing the codec's actual hardware constraints. plughw: is useful for applications but can hide failures by adapting formats. Expect noisy captures, test fixture artifacts and user-space tool differences.
| Measurement | What it reveals |
|---|---|
| DC offset on outputs | Unsafe bias or output path configuration |
| Small-signal 1 kHz tone | Basic DAC/ADC functionality |
| L/R isolated stimulus | Channel swap and cross-talk |
| Step/ramp amplitude | Gain range, clipping, pops |
| Silence with output enabled | Noise floor, hum, clock spurs |
| Stop/start repeats | Sequencing pops and stuck clocks |
| Power suspend/resume | Regcache, reset and power-domain bugs |
Risk: Do not connect speaker amplifier outputs directly to a sound-card line input; do not connect a low-impedance load to a line output that cannot drive it. Respect common-ground and differential output requirements.
Board: revision B / serial REDACTED
Kernel / commit: 6.x / <git commit>
DTB SHA256: <sha256>
Codec / revision: <model + die revision>
Supplies / reset: measured values + timing
I2S mode: provider, polarity, bit delay, slots
PCM: 48k stereo S16_LE / period & buffer
MCLK / BCLK / LRCLK: measured, not just configured
Tools: aplay version / analyzer model
Audio: successful/failed, measured issues
Artifacts: dmesg, trace, scope shot, recording
Follow the signal from board wiring and Device Tree through the CPU DAI, serial bus, codec DAC/ADC, amplifier, and endpoint. At each boundary, ask what evidence proves the stage is working.
Use the existing interactive signal-path graph above as a map, then validate each connection with logs, register reads, or measurements.
Tap each block to inspect what it does and what evidence to collect.
Start with symptom → discriminating test → evidence → smallest change. Avoid assuming any single symptom implicates the codec.
| Symptom | First discriminating test | Common directions |
|---|---|---|
No card in aplay -l |
dmesg + /sys/bus/*/devices + deferred devices |
machine link, probe deferral, DT match |
| I²C ACK but driver does not bind | readlink .../driver + codec ID read |
wrong compatible, reset/rails, register width |
Card exists, aplay refuses format |
--dump-hw-params on hw: |
supported DAI rates, channels, formats, clock policy |
aplay succeeds, no BCLK |
Scope during active PCM | DAI trigger, clock gate, pinmux, PM |
| BCLK/LRCLK present, data line static | Logic analyzer + PCM hw_ptr | wrong data pin, DMA, TDM masks, mute |
| Valid data, analog silence | Inspect DAPM + mux + analog rails | muted DAC, missing route, amp enable |
| White noise / harsh static | Decode bit alignment and slot width | incorrect I²S vs LJ, clock edge or data width |
| Audio too fast/slow | Measure LRCLK | clock divider or clock family mismatch |
| Only one channel plays | L-only/R-only test + DAPM routes | channel mapping, mute, slot mask |
| XRUN after seconds | PCM status pointer + IRQ counters | DMA/period, CPU load, clock drift |
| Works once only | Cold boot and warm restart trace | PM refcount, register cache, mute sequencing |
| Resume loses sound | Power rail/regcache before/after | dirty cache, clocks, reset recovery |
| Crackling at startup | Analog output + DAPM event sequence | VMID/bias ramp, mute unmute order |
| Jack button not detected | IRQ/GPIO and input events | polarity, debounce, PM, jack report |
Build a four-column incident log: hypothesis / measurement / observation / next experiment. Freeze every other variable. If testing a new codec, make one format work (e.g. 48k stereo S16_LE) before adding rates/channels/advanced features.
The ALSA default device isn't always the hardware device. aplay without -D hw:... can pass through plugins and resampling. “Driver bound” doesn't prove audio. “No warnings” doesn't prove schema correctness. “MCLK configured” doesn't prove it toggles. “I²S captured” doesn't prove analog path. Let each measurement answer one question.
Your SGTL5000 I²C registers read successfully, card appears and playback stream advances. BCLK = 3.072 MHz, LRCLK = 48 kHz, decoded I²S data looks valid, but analog lineout is silent. Next actions: inspect DAPM route and widget power, DAC digital mute, analog output-enable bits, regulator rails, external amplifier GPIO, and analog pin with a high-impedance scope probe. Rewriting DMA is low-priority because digital transfer has been observed.
| Symptom | First checks |
|---|---|
| No sound card | Device Tree match, probe logs, deferred probes, card registration |
| Card exists but PCM will not open | PCM device nodes, stream direction, busy state, constraints |
| PCM starts but no serial clocks | DAI link format, clock provider, hw_params, trigger path |
| Clocks exist but output is silent | DAPM route, codec mute/gain, output driver, amplifier enable, analog wiring |
Change one variable at a time and capture evidence before applying a patch.
hw_params.Change one variable at a time and capture the working/failing configuration. The DIT source helps isolate the component/DAI contract; the WM8731 source shows the additional clock, register and power paths.
Audio regressions frequently hide outside the happy path: suspend/resume, capture, two streams, different sample-rate families, and module removal. Design tests around interfaces and failure modes, not just the green LED.
# Inside kernel checkout; target may need prerequisites
make W=1 -j"$(nproc)" sound/soc/codecs/
# Or configure a targeted architecture/kernel and build it; the exact target
# is dependent on the tree and Kconfig settings.
# Inspect expected generated config
rg '^CONFIG_SND_SOC|^CONFIG_SND_PCM|^CONFIG_REGMAP' .config
# Review patch style (adjust patch path)
./scripts/checkpatch.pl --strict outgoing/0001-*.patch
# Sparse where supported; check tool version / arch
make C=1 sound/soc/codecs/
Caution: Building a directory may not compile a driver that's disabled by Kconfig. Verify the object exists and use multiple =y, =m, =n configurations where sensible. sparse, dt-schema, and toolchains may need installation.
| Scenario | Objective |
|---|---|
| Cold power-on x10 | Detect supply/reset/PLL races |
| 48k and 44.1k, playback | Distinct rate families / PLL |
| 8k / 16k / 96k if supported | Rate constraints, divider corners |
| Mono/stereo; 16/24/32 valid-bit formats | Slot/data alignment |
| Capture separate and simultaneous | Direction sharing and MCLK ownership |
| Short periods vs large periods | DMA latency/XRUN robustness |
| 100 stream open/close cycles | PM/clock refcounts, stale state |
| Suspend/resume x20 | regcache, clock/regulator restart |
| Jack insertion while suspended | Wake policy, IRQ, DAPM path |
| Unplug power during active PCM | Recoverability / safe stop if hardware permits |
| Module reload / driver unbind on test board | Resource lifecycle; not on production |
| Stress CPU/network alongside audio | Timing robustness, IRQ priorities |
Do not claim a test passed without its exact kernel/board/setup/format. Record known limitations, e.g. if playback is verified but capture wiring is absent.
#!/bin/sh
set -eu
DEV="${1:?usage: test.sh CARD,DEV WAV}"
WAV="${2:?usage: test.sh CARD,DEV WAV}"
printf 'kernel: '; uname -r
cat /proc/asound/cards
aplay -D "hw:$DEV" --dump-hw-params "$WAV"
for n in 1 2 3 4 5; do
printf 'loop %s\n' "$n"
aplay -D "hw:$DEV" "$WAV"
done
This only tests repeated playback, not audible quality or safe gain. Use low-volume fixture and inspect failures and dmesg afterwards. Add Python/Shell scripts carefully with clear return codes and proper cleanup. For precise latency or THD+N, use calibrated external test gear.
Use static analysis, targeted KUnit tests for isolated computations (format, divider selection, constraints), fault-injection for allocation/error paths, and reproducible CI builds with changing compiler/kernel configurations. KUnit cannot simulate real analog hardware; CI green doesn't replace bench testing.
This is a reading lab based on real upstream code. It bridges the architecture into three source artifacts: a codec driver, a machine driver and a board DTB. It isn't a recipe to rewrite a mature upstream codec.
The supplied i.MX53 Voipac DTB declares a sound node with ssi-controller and audio-codec phandles. Its SGTL5000 codec sits on I²C at 7-bit address 0x0a, with a clock and supply references. Those three things describe three different types of relationships: data link, control bus, and power/clock dependency.
struct i2c_driver and the of_device_id table. Record the compatible string and private structure lifecycle.devm_snd_soc_register_component and identify the component and DAI descriptors.set_fmt and explain supported clock roles, I²S/LJ/RJ/DSP formats and invalid combinations.hw_params and write down the format/rate/clock constraints.mute_stream and DAPM routes/events; explain why muting might use a digital block rather than shutting the entire analog DAC.snd_soc_dai_set_sysclk use and determine what supplies the codec clock.sgtl5000_priv and which is provided by the ASoC framework?hw_params?Card missing but codec bound: inspect machine driver and CPU DAI registration, deferred probe, clock provider and sound node. Card exists but left channel silent: run L/R isolated stimulus, observe physical SD frame/slot data, inspect DAPM/controls. Keep those diagnoses separate.
Choose a target to expose a specific abstraction problem. Complexity is a feature only when it produces useful learning.
| Lab | Hardware | You learn | Limit |
|---|---|---|---|
| A: Minimal I²S DAC | PCM5102A-like fixed-function DAC | Simple component/DAI, format expectations | No I²C regmap or complex DAPM; some drivers can be platform-only |
| B: I²C stereo codec | SGTL5000 or WM8731 class | Regmap, supplies, set_fmt, hw_params, DAPM |
Significant chip-specific sequencing |
| C: DSP/multi-DAI codec | feature-rich codec/amp | Firmware, topology, DPCM, multiple links | Too large for first Rust PoC |
A playback-only DAC without register bus still needs a DAI advertised to ASoC and board audio links. It may not need PCM runtime data handling or hw_params complexity if hardware's accepted format is truly fixed. You still need correct lifecycle and hardware routing.
A controllable codec adds a bus driver, register defaults/cache, reset/power management, clocks, stream format validation, mixer controls and DAPM. That is a much stronger stress test for abstractions but increases the scope substantially.
Prefer a board with known audio wiring, working C driver and measurable test points. Keep a verified DTS and ASoC card configuration. For a hardware-free prototype, implement registration tests and callback signatures, but do not claim DAC playback until there is a CPU DAI + codec + link + physical device.
First identify hardware and register component/DAI; then exactly one 48 kHz, stereo, S16_LE playback mode; then proper MCLK/format validation; then minimal DAPM output path; then capture, additional rates, jack, PM and polished kcontrols. Resist adding features before each prior layer is observable and tested.
The codec knows how to consume/produce serial samples. The DMA engine and ALSA PCM layer manage buffer movement and stream timing. Their failures are often misdiagnosed as codec problems.
hw_ptr): location of samples already played/recorded by hardware, reflected via driver pointer and period notifications.appl_ptr): samples written or read by application.DMAengine PCM helpers are common on ARM SoCs. Controllers may provide dma-names = "tx", "rx" and dmas references. Actual DMA channels, bit widths, handshake IDs and burst configuration must match the SoC binding/driver.
cat /proc/asound/pcm
cat /proc/asound/card0/pcm0p/sub0/status 2>/dev/null
cat /proc/interrupts | grep -Ei 'dma|sai|ssi|i2s'
dmesg | grep -Ei 'dma|xrun|underrun|overrun|snd|asoc'
Watch period progress and CPU DAI registers while streaming. If hw_ptr does not advance, trace PCM trigger, DMA submission, DMA IRQ and runtime PM. If it advances but BCLK is absent, controller/DMA might not be connected to the intended physical output.
At 48 kHz, a period of 240 frames represents 5 ms. Two periods provide only ~10 ms ring capacity—sensitive to scheduling gaps. Larger buffers tolerate jitter but increase latency. The ideal settings depend on product requirements, DMA hardware, CPU load and userspace scheduling. Don't “fix” a kernel DMA bug by hiding it behind huge buffers without measuring.
aplay warnings aren't all codec bugsWhen using ALSA default / PipeWire routing, plugins can resample and route to other devices. Start reproducible tests with hw:C,D. A -EBUSY may indicate an exclusive substream already in use; -EINVAL from hw_params may indicate unsupported data configuration. Different errors point to different layers.
Capture timestamping, mmap vs read/write, DMA coherent vs noncoherent buffers, cache maintenance, 64-bit pointer wrapping, underrun recovery, and DPCM FE/BE interactions deserve explicit testing where relevant. Never assume you need to hand-write an ALSA PCM driver for a generic DMAengine-capable SoC DAI; check existing helpers first.
A codec can register and stream perfectly but fail only after suspend. This is often a regmap + supply + clock + DAPM sequencing bug.
DAPM reacts to active signal paths and powers audio blocks; runtime PM can gate an otherwise idle device's power/clock; system sleep PM handles suspend/resume of the whole system; regmap cache can preserve software register state while hardware loses it. These interact but aren't interchangeable.
playback active
→ trigger STOP
→ DAPM disables DAC / output amplifier in correct order
→ runtime suspend gates MCLK and device supply
→ hardware register contents lost
→ resume re-enables supply and reset timing
→ regcache marks dirty/syncs when hardware accessible
→ DAPM restores needed active path
→ playback starts again
If a codec is powered off, make sure regmap cache-only mode and restore sequencing match actual silicon. Syncing a cache too early, before reset deassert and clock availability, can fail silently or return bus errors.
Sequence mute → ramp/bias settle → power enable → unmute as required by hardware, and reverse appropriately on shutdown. A zero-valued PCM sample is not enough to avoid an analog bias transient. DAPM event sequencing and codec-specific mute registers are your tools; exact delays must come from datasheet and measurement.
A warning on kernel upgrades: power management callback ownership and regmap API details move over time. Verify which callback executes in which context and whether the relevant ASoC helper already manages the resources.
The upstream submission is a reviewable explanation of a specific problem and its verified solution. A patch isn't just a diff, and a green compile isn't evidence of functional hardware behavior.
Read MAINTAINERS for SOUND - SOC LAYER / relevant codec, the appropriate subsystem tree, mailing lists and reviewing conventions. ASoC is managed through maintainers' trees; your patch should apply cleanly to a relevant current base, typically maintainer or recent integration tree as appropriate. Check the exact preferred branch rather than guessing.
dt-bindings: sound: Document example-vendor,example-codec (only if new binding is needed).ASoC: codecs: Add ... codec driver — with Kconfig and Makefile.For a Rust ASoC foundation RFC, separate binding/trait API, any C glue, minimal demonstration and tests if useful; justify every abstraction through real codec requirements. Discuss architecture with Mark Brown, Liam Girdwood and Rust maintainers before building a broad wrapper library.
# Work in the official kernel git tree
git status --short
git switch -c asoc/my-codec
# edit, compile, test, commit each logical unit
git diff --check
git log --oneline -5
# Generate series against the agreed base branch
git format-patch --base=auto --cover-letter -o outgoing/ BASE_BRANCH
# Review all patch emails yourself
./scripts/checkpatch.pl --strict outgoing/*.patch
./scripts/get_maintainer.pl outgoing/0001-*.patch
# Confirm email identity / route before ANY real send
git send-email --dry-run outgoing/*.patch
Replace BASE_BRANCH with the actual tree/branch; --base=auto works best with a properly configured topical branch. Check get_maintainer.pl output, don't blindly CC dozens of unrelated addresses. If the series includes binding + ASoC + board patches, determine recipients for each file.
=n, =m, =y)? Suspend/resume?ASoC: codecs: Fix demo codec clock divider selection
The codec currently selects the 48 kHz divider for all sample rates.
This causes playback at 44.1 kHz to run at an incorrect clock.
Choose the divider from the actual requested PCM sample rate.
Tested on <board revision> with <codec revision>, measuring LRCLK
at 44.1 and 48 kHz before and after the change.
Signed-off-by: Developer Name <address@example.org>
Example only. Fixes: appropriate for genuine introduced bugs, with accurate originating commit. Only add Tested-by: / Reviewed-by: with permission and actual evidence. Use Co-developed-by: and proper associated sign-offs for co-authors.
Send plain text patches using git send-email; preserve threads when responding, not when arbitrarily linking independent revision histories. A v2 needs a clear after---- changelog of what changed since v1. Respond with technical evidence to criticism. If no response, follow subsystem expectations rather than bumping daily.
Docs: submitting patches · submission checklist · coding style.
For driver patches, inspect the complete source file, nearby call sites, binding schema, git log and git blame. Avoid sending a patch based only on a copied tutorial excerpt: validate against the exact target branch and include build/runtime evidence. The separate interactive driver source walkthroughs use mainline source files as their source of truth.
For new hardware, the YAML schema is part of the interface between firmware description and drivers. Write the binding for the device, not for your board's particular wiring preferences. Separate reusable codec requirements from machine-level integration.
$id, $schema, title, maintainers, description as applicable; check current conventions.compatible identifiers for supported silicon/variants.reg, supplies, clocks, reset GPIO, interrupt and #sound-dai-cells according to real hardware.required includes genuinely mandatory properties; avoid making optional features mandatory.unevaluatedProperties: false / additionalProperties: false in the correct context and schema design.examples: compile and use realistic phandle providers / cell counts.# This is illustrative YAML, not a valid complete schema.
properties:
compatible:
const: vendor,sample-codec
reg:
maxItems: 1
'#sound-dai-cells':
const: 0
AVDD-supply:
description: Analog supply
required:
- compatible
- reg
- '#sound-dai-cells'
Real schemas need the canonical schema headers, declared types and examples. See the binding authoring guide.
make dt_binding_check DT_SCHEMA_FILES=/sound/
make ARCH=arm dtbs_check DT_SCHEMA_FILES=/sound/
# DT_SCHEMA_FILES is an optional filter, not a substitute for full checks.
Review dt_binding_check output first; schema errors can cause dtbs_check to skip a binding and mislead you. A valid schema doesn't guarantee physical hardware wiring. Keep MAINTAINERS, board DTS changes and ASoC review routes in mind.
Rust support should not invent alternate device bindings if a C driver already describes the codec. Rust driver probe must consume the same stable DT/firmware description and obey the same lifecycle and device-model invariants.
Goal: explore the minimum safe abstractions required to implement an ASoC component and DAI from Rust, without claiming that current upstream has a complete Rust ASoC API. Before implementation, seek subsystem and Rust maintainer guidance, because API/lifetime design determines reviewability.
Review these actual kernel structures in your target tree:
rg 'struct snd_soc_component_driver' include/sound/
rg 'struct snd_soc_dai_driver' include/sound/
rg 'struct snd_soc_dai_ops' include/sound/
rg 'devm_snd_soc_register_component' sound/soc/ include/sound/
rg 'snd_soc_component_get_drvdata|snd_soc_dai_get_drvdata' sound/soc/codecs/ | head
Map C contract → Rust ownership for every callback. Who owns component data? Who keeps callbacks alive? Are callbacks invoked after unregister begins? Can callbacks sleep? How are raw pointers represented safely? What cleanup runs on registration failure? Where can mutable access be shared across two simultaneous streams?
make LLVM=1 rustavailable is the first gate.hw_params) that validates one supported PCM mode.| Problem | A Rust abstraction needs to demonstrate |
|---|---|
snd_soc_component_driver static data |
Safe callback table lifetime and ownership |
snd_soc_dai_driver descriptor |
Compile-time supported caps/ops, validated names |
drvdata raw pointer |
Typed device state, borrow/sync rules and teardown safety |
| Errors / errno | Map Rust errors to existing C expected return codes |
| Callback context | What is allowed to block or sleep, at each call site? |
| DAPM static arrays | Safe C ABI declarations or validated build-time construction |
| Mixed C / Rust | Tight, reviewed FFI boundary; avoid unsafe in every codec |
Avoid writing a giant “sound crate” before a real codec shows the need. A Rust abstraction should make a real driver easier to understand and safer, not simply mirror every C struct.
rustc --version
clang --version
bindgen --version
make LLVM=1 rustavailable
# Follow the target tree's rust/quick-start guide for compiler constraints
make LLVM=1 rust-analyzer
Rust tool versions are tightly coupled to the kernel being built; the distro's default compiler can be unsupported. The upstream Rust Quick Start is a living reference.
Write two pages, not 2,000 lines of speculative code: (a) exact minimal C contracts and lifetime diagram, (b) annotated example of a codec's registration and hw_params from Rust, plus limitations. Include what you're not proposing (complete DAPM or regmap bindings yet). Ask ASoC and Rust maintainers where they see ownership and API issues before coding the full framework.
These exercises start with reading, proceed to source code and end on hardware. Capture findings in your own notes; avoid turning it into a checklist you never use.
Open sgtl5000.c. Record bus matching, regmap, supplies, snd_soc_component_driver, snd_soc_dai_driver and snd_soc_dai_ops. Build a diagram mapping probe → component registration → DAI link → hw_params → DAPM. For each arrow, note the callback or API responsible.
Deliverable: a 1-page “ASoC codec lifecycle” drawing with 5 source references.
Situation: SGTL5000 I²C ID reads correctly, but aplay -l reports no cards. Don't touch format code yet. Check DT sound node, ssi-controller/audio-codec references (binding-specific), CPU DAI registration, machine/card probe, deferred suppliers and ASoC component names.
Deliverable: four observations proving where binding stops. Bonus: find one deliberately broken sound DT reference and explain its effect.
On a supported development board, request 48k stereo playback, observe actual BCLK and LRCLK, infer slot width, and compare to the DAI setting. Do the same at 44.1k if supported. Confirm your codec's SYSCLK relation from datasheet, not from assumptions.
Deliverable: table with requested, configured and measured clock values and sample framing screenshot.
Use a known-good output and safe test volume. Disable a mixer route through ALSA controls (where controllable), inspect the DAPM widget state, reenable it, and demonstrate changes to hardware power in debugfs or instrumentation.
Deliverable: a DAPM path sketch including endpoints and controls; before/after observation.
Write a private toy codec using a real target and datasheet. Begin with I²C probe, chip ID and safe reset; add regmap; component/DAI registration; fixed playback format; DAPM; additional rate formats; suspend/resume. Build each stage and add a negative test. Never guess register constants.
Deliverable: commits with working stages and board test evidence, not necessarily an upstream patch.
Take a simple bug fix or documentation improvement. Make it build; run affected tests, git diff --check, checkpatch, relevant DT schema tests and get_maintainer.pl; generate a cover letter that describes why the change is needed and exactly how it was verified. Do not send until reviewed.
Deliverable: local format-patch series and clear test matrix.
Take only devm_snd_soc_register_component plus a no-op or constraint-checking DAI callback. Annotate every pointer's owner and lifetime, where Rust might require Pin, and what unsafe must prove. Compare whether a C bridge or generated bindings yield a smaller trusted boundary in the target tree.
Deliverable: a maintainer-readable RFC problem statement, architecture sketch and known limitations.
A fast reference when you're at a board with serial console attached.
aplay -l && arecord -l
cat /proc/asound/cards
cat /proc/asound/pcm
amixer -c 0 scontrols
amixer -c 0 contents
aplay --dump-hw-params -D hw:0,0 test.wav
aplay -D hw:0,0 test.wav
arecord -D hw:0,0 -f S16_LE -c 2 -r 48000 -d 10 /tmp/test.wav
Adjust card index and test-file format. Start low volume. amixer output differs with drivers.
rg 'devm_snd_soc_register_component' sound/soc/codecs/
rg 'snd_soc_dai_set_sysclk|snd_soc_dai_set_fmt' sound/soc/
git log --oneline -- sound/soc/codecs/sgtl5000.c
uname -r
lsmod | grep -Ei 'snd|soc|codec|regmap'
dmesg | grep -Ei 'asoc|snd|codec|i2s|sai|ssi|dma|defer'
ls -l /sys/bus/i2c/devices/
cat /proc/interrupts
find /sys/kernel/debug/asoc -maxdepth 4 -type f 2>/dev/null
cat /sys/kernel/tracing/available_events 2>/dev/null | grep -Ei 'snd|asoc'
Never assume debugfs is enabled or that register reads are harmless. Select specific paths relevant to your device.
make LLVM=1 rustavailable
make dt_binding_check DT_SCHEMA_FILES=/sound/
make dtbs_check DT_SCHEMA_FILES=/sound/
git diff --check
./scripts/checkpatch.pl --strict /path/to/patch.patch
./scripts/get_maintainer.pl /path/to/patch.patch
git format-patch --base=auto --cover-letter -o outgoing/ BASE_BRANCH
git send-email --dry-run outgoing/*.patch
hw: device accepts stated formats and rejects unsupported combinations.| Term | Meaning |
|---|---|
| ASoC | ALSA System on Chip framework coordinating components, DAIs, machine links and DAPM |
| ALSA PCM | Linux API/engine for sampled audio streams and ring buffers |
| DAI | Digital Audio Interface: serialized audio endpoint with format and clock capabilities |
| CPU DAI | SoC serial audio endpoint, e.g. SSI/SAI/I²S |
| Codec DAI | Audio endpoint exposed by external DAC/ADC/codec driver |
| DAI link | Describes CPU/codec/PCM component connection on a sound card |
snd_soc_card |
Assembled ASoC card instance |
| Regmap | Generic register-access abstraction with caching, width, readable/volatile rules |
| DAPM | Dynamic Audio Power Management; route-driven block power control |
| Kcontrol | ALSA mixer/control element exposed to userspace |
| MCLK/SYSCLK | Codec/system reference clock, usually above sample rate |
| BCLK | Serial audio bit clock |
| LRCLK/WS/FS | Channel/frame sync clock |
| I²S | Digital audio bus protocol with one-bit data delay relative to channel edge |
| TDM | Multi-slot serial frame, often more slots than channels |
hw_params |
Agreed PCM rate, format, channels and buffer geometry for a stream |
| DMAengine PCM | Helpers for DMA-driven ALSA PCM on suitable SoCs |
| DPCM | Dynamic PCM FE/BE model, often in DSP/complex SoCs |
| FE / BE | Front End userspace PCM / Back End hardware link |
| XRUN | Playback underrun / capture overrun |
-EPROBE_DEFER |
Probe should retry after a required supplier becomes available |
dt_binding_check |
Validates DT YAML binding schemas |
dtbs_check |
Validates compiled Device Trees against schemas |
get_maintainer.pl |
Finds kernel maintainers/lists from patch paths and metadata |
include/sound/soc.h ASoC structures and APIs
include/sound/soc-dai.h DAI definitions (version-dependent)
include/sound/soc-dapm.h DAPM widgets, routes and API
sound/soc/soc-core.c card and component lifecycle
sound/soc/soc-pcm.c PCM runtime and DAI orchestration
sound/soc/soc-dapm.c DAPM graph/power logic
sound/soc/codecs/ codec implementations
sound/soc/fsl/ NXP/Freescale ASoC drivers
sound/soc/generic/ generic card drivers
Documentation/sound/soc/ subsystem manual
Documentation/devicetree/bindings/sound/ bindings
Documentation/process/ upstream workflow
Paths and symbols evolve. Use rg/git log in the exact tested kernel commit.
This manual is an independent learning and field-use guide, not official upstream documentation. It is based on public Linux kernel documentation and source patterns; exact structures/callbacks change between releases. Always build against the kernel tree you intend to ship.
Specialized SoC audio DSP firmware architectures (SOF/SoundWire), ASoC topology v2, compressed-offload internals, each vendor's register sets, all USB offload mechanisms, every PCM timestamp/audio-policy corner case, and every Rust sound RFC deserve their own manuals. Treat this as a deep practical foundation and navigation atlas, not a claim to document every internal kernel path.
Text and examples in this guide are newly written educational explanations; upstream links point to materials governed by their own licenses. The schematic code intentionally omits implementation details and real hardware constants. No source copying from kernel files was necessary for the tutorial. If you publish, review code snippets and choose a license for the guide itself.
Last editorial pass: October 2026. For every kernel upgrade: reread current include/sound/soc*.h, test example snippets or mark them schematic, verify links, and update the test matrix. Keep edition separate from the kernel you validated against.
The S/PDIF transmitter and WM8731 source walkthroughs are the only separate learning page added from the contributor guide. The main manual integrates architecture, hardware checks, source navigation, debugging and upstream workflow into the chapters where they belong.
This chapter links to three integrated, offline source explorers. Each page contains the complete bundled source and a line-by-line explanation focused on the ASoC design: component/DAI registration, stream constraints, DAPM graph semantics, register access, clocking, bias and power sequencing, and the relationship between callbacks and the core.
Understand why a digital transmitter can be represented as an ASoC component without a physical codec, how its DAI advertises PCM constraints, and how its DAPM output route models the signal endpoint.
Study a real codec driver: register defaults and Regmap, ALSA controls, DAPM widgets/routes, clock-divider selection, DAI format negotiation, mute, startup constraints, and bias-level transitions. The page also explains the register header.
Open WM8731 register map and private-state header → · Inspect get_coeff() fallback → · Inspect hw_params →
Reading method: read the architecture explanation first, then use the source line references to follow each callback into the ASoC core. These are bundled source snapshots; compare the bundled snapshot to the target kernel revision before patch review. Line anchors refer to the bundled snapshot, not a moving upstream branch.
Source fact states only what the selected C statement performs. Framework context explains where ASoC or ALSA connects it to stream lifecycle. Review consideration is explicitly an inference, risk or verification question—not a claim that a bug has been reproduced on hardware.
Snapshot discipline: the bundled code is an educational snapshot, not an assertion that it is today’s upstream master. Always compare the actual source and binding revision used by your hardware.