ALSA DAPM Widget Domains Explained
Lecture 8 of the ALSA SoC Framework chapter — part of EmbeddedPathashala’s free Linux kernel development course
Welcome back to EmbeddedPathashala’s free linux kernel development course. In the last lecture of this ALSA SoC Framework chapter we wired up DAPM widgets and routes using a plain mixer control array. Today, as part of this free linux device drivers course, we go one level deeper and classify DAPM widgets into their two remaining domains — the audio path domain and the audio stream domain — and see exactly why some widgets need a register/shift/invert triplet while others do not.
What You Will Learn
- Why DAPM widgets are grouped into platform, codec, path, and stream domains
- How register-controlled path widgets (PGA, OUT_DRV, MIXER, MIXER_NAMED_CTL, SWITCH, MUX, DEMUX) differ from the codec-domain widgets covered earlier
- The event-variant (
_E) widget macros and when the DAPM core calls your event handler - Stream-domain widgets — AIF_IN, AIF_OUT, DAC, ADC — and the mandatory stream-name matching rule
- The modern
SND_SOC_DAPM_SUPPLYand array-basedSOC_*_ARRAYwidget macros added in current kernels - How to build, load, and verify an original demo codec driver that exercises all of these widget types
Prerequisites
- Completion of the earlier lectures in this ALSA SoC Framework chapter — DAPM widgets and routes (Lecture 7), ASoC kcontrols (Lectures 5-6)
- Comfort with regmap-backed codec drivers and
snd_soc_component_driver - A working ALSA/ASoC-capable kernel build environment (this free embedded linux course assumes kernel 6.x headers)
Why DAPM Splits Widgets Into Domains
A DAPM widget is really just a node in a power graph, but not every node behaves the same way when the graph is walked. The DAPM core groups widgets into domains purely based on what powers them and what they carry:
- Platform domain — inputs/outputs of the machine (jacks, mic, speaker pins) — no register, defined with
SND_SOC_NOPM. - Codec domain — plain kcontrol-wrapping widgets we covered in Lecture 7.
- Audio path domain — widgets that sit inside the actual signal path and have a real power-control bit in a hardware register.
- Audio stream domain — widgets tied to a PCM stream (playback/capture) rather than a pure analog signal.
This lecture focuses on the last two — the ones you will use in almost every real codec driver.
Audio Path Domain Widgets
Path-domain widgets repackage an ordinary kcontrol array and extend it with a real power bit. Unlike the codec-domain widgets from Lecture 7, the reg and shift fields here are mandatory — they point at the actual enable bit inside the codec so DAPM can power the block up only when a valid audio path reaches it, and power it down the instant that path goes inactive.
| Widget | Represents | kcontrol count |
|---|---|---|
| PGA | Programmable gain amp / attenuator (mic boost, line boost) | 0 or more, from array |
| OUT_DRV | Output driver stage (headphone/line driver enable) | 0 or more, from array |
| MIXER | Combines several signals into one, each input independently switchable | from array |
| MIXER_NAMED_CTL | Same as MIXER but the kcontrol is exposed under its own name, shareable across widgets | from array |
| SWITCH | Single on/off gate in the path | exactly 1 |
| MUX | Selects one of several inputs (enum control) | exactly 1 |
| DEMUX | Routes one input to one of several outputs | exactly 1 |
Their current upstream definitions in include/sound/soc-dapm.h look like this (verified against the mainline tree, not the old textbook signatures):
#define SND_SOC_DAPM_PGA(wname, wreg, wshift, winvert, wcontrols, wncontrols) \
{ .id = snd_soc_dapm_pga, .name = wname, .reg = wreg, .shift = wshift, \
.invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = wncontrols }
#define SND_SOC_DAPM_SWITCH(wname, wreg, wshift, winvert, wcontrols) \
{ .id = snd_soc_dapm_switch, .name = wname, .reg = wreg, .shift = wshift, \
.invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = 1 }
#define SND_SOC_DAPM_MUX(wname, wreg, wshift, winvert, wcontrols) \
{ .id = snd_soc_dapm_mux, .name = wname, .reg = wreg, .shift = wshift, \
.invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = 1 }
Recent kernels also ship simplified SOC_*_ARRAY variants (SOC_PGA_ARRAY, SOC_MIXER_ARRAY, SOC_MIXER_NAMED_CTL_ARRAY) that drop the explicit count parameter and compute num_kcontrols with ARRAY_SIZE() for you — one less place to get the count wrong when you resize a control array.
Event-Variant Macros ( _E suffix )
Every path-domain widget above has a matching _E macro — SND_SOC_DAPM_PGA_E, SND_SOC_DAPM_OUT_DRV_E, SND_SOC_DAPM_MIXER_E, SND_SOC_DAPM_SWITCH_E, SND_SOC_DAPM_MUX_E, and so on. These take two extra parameters: a function pointer (wevent) and an event mask (wflags). The DAPM core calls your function whenever the widget crosses one of the requested transitions:
#define SND_SOC_DAPM_PGA_E(wname, wreg, wshift, winvert, \
wcontrols, wncontrols, wevent, wflags) \
{ .id = snd_soc_dapm_pga, .name = wname, .reg = wreg, .shift = wshift, \
.invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = wncontrols, \
.event = wevent, .event_flags = wflags }
wflags is an OR of SND_SOC_DAPM_PRE_PMU, SND_SOC_DAPM_POST_PMU, SND_SOC_DAPM_PRE_PMD, and SND_SOC_DAPM_POST_PMD — before/after power-up, before/after power-down. This is your hook for anything that can’t be expressed as a single register bit: ramping an amplifier gain gradually, waiting on a hardware-ready line, or sequencing an external regulator.
Audio Stream Domain Widgets
Stream-domain widgets are different again: they don’t represent an analog node, they represent one end of a PCM stream. SND_SOC_DAPM_AIF_IN is the point where audio enters the codec from the CPU side to eventually reach a DAC; SND_SOC_DAPM_AIF_OUT is where audio leaves the ADC on its way back to the CPU. SND_SOC_DAPM_DAC and SND_SOC_DAPM_ADC are the actual converters sitting between the AIF and the analog path widgets.
#define SND_SOC_DAPM_AIF_IN(wname, stname, wslot, wreg, wshift, winvert) \
{ .id = snd_soc_dapm_aif_in, .name = wname, .sname = stname, \
.reg = wreg, .shift = wshift, .invert = winvert }
#define SND_SOC_DAPM_DAC(wname, stname, wreg, wshift, winvert) \
{ .id = snd_soc_dapm_dac, .name = wname, .sname = stname, \
.reg = wreg, .shift = wshift, .invert = winvert }
The critical rule here — and the single most common bug in first-time codec drivers — is the stname (stream name) field. It is not decorative. DAPM matches this string against the stream name declared in your snd_soc_dai_driver.playback.stream_name / .capture.stream_name from Lecture 3. If the strings don’t match exactly, DAPM cannot connect the PCM trigger to the widget graph, and playback silently produces no sound even though every register write succeeds.
One more addition worth knowing for current kernels: SND_SOC_DAPM_CLOCK_SUPPLY has effectively been superseded in most new drivers by the general-purpose SND_SOC_DAPM_SUPPLY widget, which takes the same reg/shift/invert/event/event_flags shape as a PGA but is meant purely for enabling a supply (clock, regulator, bias) that other widgets depend on through a route, rather than for carrying signal itself.
Original Demo: ep_codec Signal Chain
Let’s build a small original ASoC codec driver — ep_codec — that chains a stream widget, a path widget, and an output driver, purely to see the domains cooperate. This is written from scratch for this course, not copied from any textbook or vendor driver.
#define EP_REG_DAC_PWR 0x10
#define EP_REG_MIXER_PWR 0x11
#define EP_REG_OUT_PWR 0x12
static const struct snd_kcontrol_new ep_mixer_controls[] = {
SOC_DAPM_SINGLE("Line In Switch", EP_REG_MIXER_PWR, 0, 1, 0),
SOC_DAPM_SINGLE("DAC Switch", EP_REG_MIXER_PWR, 1, 1, 0),
};
static const struct snd_soc_dapm_widget ep_codec_widgets[] = {
/* stream domain: PCM playback enters here */
SND_SOC_DAPM_AIF_IN("EP AIF IN", "EP Playback", 0, SND_SOC_NOPM, 0, 0),
/* stream domain: converts digital audio to analog */
SND_SOC_DAPM_DAC("EP DAC", "EP Playback", EP_REG_DAC_PWR, 0, 0),
/* path domain: mixes DAC output with an external line-in */
SND_SOC_DAPM_MIXER("EP Output Mixer", EP_REG_MIXER_PWR, 7, 0,
ep_mixer_controls, ARRAY_SIZE(ep_mixer_controls)),
/* path domain: drives the speaker pins, with an event hook */
SND_SOC_DAPM_OUT_DRV_E("EP Speaker Driver", EP_REG_OUT_PWR, 0, 0,
NULL, 0, ep_spk_drv_event,
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_OUTPUT("EP SPK"),
};
static const struct snd_soc_dapm_route ep_codec_routes[] = {
{ "EP DAC", NULL, "EP AIF IN" },
{ "EP Output Mixer", "DAC Switch", "EP DAC" },
{ "EP Speaker Driver", NULL, "EP Output Mixer" },
{ "EP SPK", NULL, "EP Speaker Driver" },
};
static int ep_spk_drv_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
switch (event) {
case SND_SOC_DAPM_POST_PMU:
dev_info(w->dapm->dev, "ep_codec: speaker driver ramp-up complete\n");
break;
case SND_SOC_DAPM_PRE_PMD:
dev_info(w->dapm->dev, "ep_codec: speaker driver ramping down\n");
break;
}
return 0;
}
Registration reuses the same snd_soc_component_driver fields introduced in Lecture 7 — just point .dapm_widgets/.num_dapm_widgets and .dapm_routes/.num_dapm_routes at these arrays inside devm_snd_soc_register_component().
Build and Verify
$ make -C /lib/modules/$(uname -r)/build M=$PWD modules
$ sudo insmod ep_codec.ko
$ dmesg | tail -n 5
[ 102.334112] ep_codec: probe successful, 4 DAPM widgets, 4 routes registered
# enable the playback path and watch the event widget fire
$ amixer -c 0 cset name='DAC Switch' on
$ dmesg | tail -n 2
[ 108.771905] ep_codec: speaker driver ramp-up complete
# inspect live power state per widget
$ cat /sys/kernel/debug/asoc/ep-card/ep-codec/dapm/EP\ Speaker\ Driver
Widget: EP Speaker Driver power: 1
Path Domain vs Stream Domain — Quick Comparison
| Aspect | Audio Path Domain | Audio Stream Domain |
|---|---|---|
| Represents | An analog signal-processing node | One end of a digital PCM stream |
| reg/shift required? | Yes — real hardware power bit | Only if the codec has one; often SND_SOC_NOPM |
| Key extra field | kcontrol array (controls/num_controls) | sname (must match DAI stream name) |
| Typical widgets | PGA, MIXER, SWITCH, MUX, OUT_DRV | AIF_IN, AIF_OUT, DAC, ADC |
Common Mistakes
- Stream-name mismatch — the single biggest cause of “registers look right but no sound” bugs; the AIF/DAC
snamemust be character-for-character identical to the DAI driver’s stream name. - Wrong kcontrol count on SWITCH/MUX/DEMUX — these always take exactly one control; passing an array here breaks DAPM’s mixer/mux setup path.
- Using an _E widget without setting event_flags — the function pointer alone does nothing; forgetting the flags means your handler is never invoked.
- Mixing up MIXER and MIXER_NAMED_CTL — use the named variant only when the same kcontrol genuinely needs to be shared and visible under a fixed name across multiple widgets.
Best Practices
- Keep register/shift/invert values in a chip-specific header, not scattered across the widget table.
- Prefer the array-based
SOC_*_ARRAYmacros for new drivers so control-array resizing can’t desyncnum_kcontrols. - Only reach for an
_Eevent handler when a plain register bit genuinely cannot express the power sequencing you need — extra event handlers add debugging surface. - Validate every route with
debugfs/asoc/.../dapmbefore shipping; a typo in a widget name silently drops a route instead of failing the build.
Summary
Audio path domain widgets (PGA, OUT_DRV, MIXER, MIXER_NAMED_CTL, SWITCH, MUX, DEMUX) always carry a real register/shift/invert triplet because they gate an actual hardware power bit, and each has an _E event-capable sibling for sequencing that a bit alone can’t express. Audio stream domain widgets (AIF_IN, AIF_OUT, DAC, ADC) instead hinge on the stream-name field to bridge the PCM trigger path into the same DAPM graph. Together with the platform and codec domain widgets from earlier lectures, this completes the full widget vocabulary you need for any real-world ASoC codec driver — a core skill in this free linux kernel development course and the wider free embedded systems course track on EmbeddedPathashala.
Frequently Asked Questions
Why do path-domain widgets require a register but codec-domain widgets don’t?
Codec-domain widgets just wrap a kcontrol for exposure through DAPM’s power graph — the underlying register write, if any, is often already handled by the kcontrol’s own get/put callbacks. Path-domain widgets are meant to directly gate a hardware power-enable bit, so DAPM needs the register/shift/invert triplet to read and toggle that bit itself during a power-graph walk.
What happens if I get the stream name wrong on an AIF or DAC widget?
DAPM will register the widget without error, but it will never connect to the PCM trigger path, since the connection is made by matching the stream name string against the DAI driver. Playback or capture will silently fail to reach the widget graph even though ALSA reports the stream as running.
Can a MIXER widget also use the _E event variant?
Yes — SND_SOC_DAPM_MIXER_E exists precisely for cases where mixing requires extra sequencing beyond the register bit, such as a short settle delay after enabling an input.
Is SND_SOC_DAPM_CLOCK_SUPPLY still used in modern drivers?
It still exists, but most current drivers reach for the more general SND_SOC_DAPM_SUPPLY widget for clocks, regulators, and bias supplies, since it shares the same event-capable shape as other path widgets.
Do I need both a MUX and a DEMUX in most codec drivers?
No — most simple codecs only need a MUX for input source selection. DEMUX shows up mainly in codecs that can route a single ADC or DSP output to more than one downstream destination.
Where can I see the live power state of a path-domain widget?
Through the ASoC debugfs tree, typically at /sys/kernel/debug/asoc/<card>/<component>/dapm/<widget name>, which reports whether the widget is currently powered.
Continue the Free Linux Kernel Development Course
Next up: platform/CPU DAI side widgets and DMA-backed stream paths.
Next Lecture Back to ALSA SoC Chapter Index