Core Concepts
ASoC Dynamic Audio Power Management (DAPM) organizes audio hardware into power-controllable units called widgets, which are interconnected to form signal paths. These connections determine how power states propagate across the audio subsystem during stream activity changes.
Each widget belongs to a DAPM context, representing a logical power domain. Contexts align with hardware boundaries:
- One per codec component
- One per platform component
- One per machine (sound card)
The snd_soc_dapm_context structure encapsulates bias level management, event callbacks, and membership in the sound card’s global DAPM infrastructure:
struct snd_soc_dapm_context {
enum snd_soc_bias_level bias_level;
unsigned int idle_bias_off:1;
unsigned int suspend_bias_off:1;
void (*seq_notifier)(struct snd_soc_dapm_context *,
enum snd_soc_dapm_type, int);
struct device *dev;
struct snd_soc_component *component;
struct snd_soc_card *card;
enum snd_soc_bias_level target_bias_level;
struct list_head list;
int (*stream_event)(struct snd_soc_dapm_context *, int);
int (*set_bias_level)(struct snd_soc_dapm_context *,
enum snd_soc_bias_level);
struct snd_soc_dapm_wcache path_sink_cache;
struct snd_soc_dapm_wcache path_source_cache;
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs_dapm;
#endif
};
Bias levels define operational states:
SND_SOC_BIAS_OFF: Full power-downSND_SOC_BIAS_STANDBY: Low-power idle, fast wake-up (<10ms)SND_SOC_BIAS_PREPARE: Pre-stream setupSND_SOC_BIAS_ON: Active operation
Widgets reference their context via w->dapm, and all contexts link into card->dapm_list for unified traversal.
Widget Registration Mechanisms
Widgets are instantiated from driver-defined templates using snd_soc_dapm_new_controls(). This function iterates over an array of snd_soc_dapm_widget descriptors and delegates creation to snd_soc_dapm_new_control_unlocked().
Explicit Widget Creation
Drivers declare static widget arrays—commonly using macros like SOC_DAPM_INPUT() or SOC_DAPM_SPK()—and register them at initialization time:
- Codec & Platform: Registered via
component->driver->dapm_widgetsorcomponent->driver->probe()duringsoc_probe_link_components() - Machine: Registered directly on
card->dapmaftersnd_card_new()
The core allocation logic resides in snd_soc_dapm_new_control_unlocked():
struct snd_soc_dapm_widget *
snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_widget *template)
{
struct snd_soc_dapm_widget *w;
w = dapm_cnew_widget(template);
if (!w)
return ERR_PTR(-ENOMEM);
// Handle special widget types (regulator, clock, pinctrl)
switch (w->id) {
case snd_soc_dapm_regulator_supply:
w->regulator = devm_regulator_get(dapm->dev, w->name);
break;
case snd_soc_dapm_clock_supply:
w->clk = devm_clk_get(dapm->dev, w->name);
break;
// ...
}
// Apply name prefix if present
const char *prefix = soc_dapm_prefix(dapm);
if (prefix)
w->name = kasprintf(GFP_KERNEL, "%s %s", prefix, template->name);
else
w->name = kstrdup_const(template->name, GFP_KERNEL);
// Assign endpoint and power-check behavior
switch (w->id) {
case snd_soc_dapm_mic:
w->is_ep = SND_SOC_DAPM_EP_SOURCE;
w->power_check = dapm_generic_check_power;
break;
case snd_soc_dapm_spk:
case snd_soc_dapm_hp:
w->is_ep = SND_SOC_DAPM_EP_SINK;
w->power_check = dapm_generic_check_power;
break;
case snd_soc_dapm_supply:
case snd_soc_dapm_regulator_supply:
w->is_supply = 1;
w->power_check = dapm_supply_check_power;
break;
// ...
}
w->dapm = dapm;
INIT_LIST_HEAD(&w->list);
INIT_LIST_HEAD(&w->dirty);
list_add_tail(&w->list, &dapm->card->widgets);
for_each_dapm_direction(dir) {
INIT_LIST_HEAD(&w->edges[dir]);
w->endpoints[dir] = -1;
}
w->connected = 1;
return w;
}
Key outcomes:
- Memory is allocated and template data copied
- Endpoint flags (
is_ep) indicate signal flow direction (source/sink) power_checkcallbacks govern dynamic power decisions- Widgets are inserted into
card->widgetsfor later enumeration
DAI Widget Generation
Digital Audio Interface (DAI) widgets are generated dynamically during component registration. Each DAI contributes two widgets:
snd_soc_dapm_dai_in: For playback streamssnd_soc_dapm_dai_out: For capture streams
These are created by snd_soc_dapm_new_dai_widgets():
int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,
struct snd_soc_dai *dai)
{
struct snd_soc_dapm_widget template = { .reg = SND_SOC_NOPM };
struct snd_soc_dapm_widget *w;
if (dai->driver->playback.stream_name) {
template.id = snd_soc_dapm_dai_in;
template.name = dai->driver->playback.stream_name;
template.sname = template.name;
w = snd_soc_dapm_new_control_unlocked(dapm, &template);
if (IS_ERR(w))
return PTR_ERR(w);
w->priv = dai;
dai->playback_widget = w;
}
if (dai->driver->capture.stream_name) {
template.id = snd_soc_dapm_dai_out;
template.name = dai->driver->capture.stream_name;
template.sname = template.name;
w = snd_soc_dapm_new_control_unlocked(dapm, &template);
if (IS_ERR(w))
return PTR_ERR(w);
w->priv = dai;
dai->capture_widget = w;
}
return 0;
}
Each DAI widget stores its parent DAI in w->priv, enabling bidirectional lookup between DAPM and ASoC core structures.
Endpoint Identification Logic
Endpoint status (is_ep) is not static—it evolves based on routing topology. Three mechanisms update it:
- Initial assignment during widget creation (e.g.,
snd_soc_dapm_input → EP_SOURCE) - Dynamic adjustment in
dapm_update_widget_flags()when routes are added/removed:
static void dapm_update_widget_flags(struct snd_soc_dapm_widget *w)
{
switch (w->id) {
case snd_soc_dapm_input:
if (w->dapm->card->fully_routed)
return;
// Check if connected to micbias/mic/etc.
// Clear EP_SOURCE if downstream sink exists
break;
case snd_soc_dapm_output:
// Similar logic for EP_SINK
break;
case snd_soc_dapm_line:
// Derive EP flags from edge presence
break;
}
w->is_ep = ep;
}
- Runtime toggling during stream events via
soc_dapm_dai_stream_event():
SND_SOC_DAPM_STREAM_START: Setsis_eptoEP_SOURCE/EP_SINKSND_SOC_DAPM_STREAM_STOP: Clearsis_ep
Path and Route Construction
Paths represent directed connections between widgets. Routes are declarative specifications used to instantiate those paths.
Explicit Route Registration
Routes are defined as arrays of snd_soc_dapm_route, then registered via snd_soc_dapm_add_routes():
int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_route *route, int num)
{
int i, ret = 0;
mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
for (i = 0; i < num; i++) {
ret = snd_soc_dapm_add_route(dapm, route++);
if (ret < 0)
break;
}
mutex_unlock(&dapm->card->dapm_mutex);
return ret;
}
snd_soc_dapm_add_route() locates source and sink widgets, then invokes snd_soc_dapm_add_path() to construct the connection.
Path construction validates constraints:
- Supply widgets cannot be sinks for non-supply sources
- Conditional controls (
control != NULL) require compatible widget types (mux/demux/mixer) - Static paths (
control == NULL) always connect
static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
struct snd_soc_dapm_widget *wsource,
struct snd_soc_dapm_widget *wsink,
const char *control,
int (*connected)(...))
{
struct snd_soc_dapm_path *path;
// Validation checks omitted
path = kzalloc(sizeof(*path), GFP_KERNEL);
path->node[SND_SOC_DAPM_DIR_IN] = wsource;
path->node[SND_SOC_DAPM_DIR_OUT] = wsink;
if (!control) {
path->connect = 1;
} else {
switch (wsource->id) {
case snd_soc_dapm_demux:
dapm_connect_mux(dapm, path, control, wsource);
break;
}
switch (wsink->id) {
case snd_soc_dapm_mux:
dapm_connect_mux(dapm, path, control, wsink);
break;
case snd_soc_dapm_mixer:
dapm_connect_mixer(dapm, path, control);
break;
}
}
list_add(&path->list, &dapm->card->paths);
list_add(&path->list_node[SND_SOC_DAPM_DIR_IN], &wsource->edges[SND_SOC_DAPM_DIR_IN]);
list_add(&path->list_node[SND_SOC_DAPM_DIR_OUT], &wsink->edges[SND_SOC_DAPM_DIR_OUT]);
// Update endpoint flags and mark dirty
for_each_dapm_direction(dir) {
dapm_update_widget_flags(widgets[dir]);
dapm_mark_dirty(widgets[dir], "Route added");
}
return 0;
}
Critical side effects:
- Paths are added to
card->paths - Forward/backward edges are linked into
widget->edges[dir] - Widgets are marked dirty for subsequent power-state evaluation
Inter-Component Path Linking
Three automated linking phases complete the full audio graph:
-
Codec DAI ↔ Codec Internal Widgets Executed by
snd_soc_dapm_link_dai_widgets()during card instantiation. It matches DAI widgets with internal widgets sharing the samesname(stream name), then creates unconditional paths. -
CPU DAI ↔ Codec DAI Handled by
snd_soc_dapm_connect_dai_link_widgets(). For each BE (back-end) DAI link, it connects corresponding playback/capture widgets between CPU and codec components. -
Platform DAI ↔ Platform Internal Widgets Covered under explicit route registration since platform drivers declare these routes statically.
KControl Initialization
After all widgets and paths exist, snd_soc_dapm_new_widgets() constructs associated ALSA controls:
int snd_soc_dapm_new_widgets(struct snd_soc_card *card)
{
struct snd_soc_dapm_widget *w;
mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
list_for_each_entry(w, &card->widgets, list) {
if (w->new)
continue;
if (w->num_kcontrols) {
w->kcontrols = kcalloc(w->num_kcontrols,
sizeof(struct snd_kcontrol *), GFP_KERNEL);
}
switch (w->id) {
case snd_soc_dapm_switch:
case snd_soc_dapm_mixer:
dapm_new_mixer(w);
break;
case snd_soc_dapm_mux:
dapm_new_mux(w);
break;
case snd_soc_dapm_pga:
dapm_new_pga(w);
break;
}
// Read initial hardware state
if (w->reg >= 0) {
soc_dapm_read(w->dapm, w->reg, &val);
w->power = ((val >> w->shift) & w->mask) == w->on_val;
}
w->new = 1;
dapm_mark_dirty(w, "new widget");
}
dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);
mutex_unlock(&card->dapm_mutex);
return 0;
}
Widget-specific helpers create controls:
dapm_new_mixer()iterates overw->edges[SND_SOC_DAPM_DIR_IN], matching paths by name tow->kcontrol_news[i].name, then callsdapm_create_or_share_kcontrol()dapm_new_mux()handles single-control muxes, linking all paths to one controldapm_new_pga()follows similar patterns for programmable gain amplifiers
The dapm_create_or_share_kcontrol() functon builds the final snd_kcontrol, applies naming conventions (e.g., "Widget Name Control Name"), allocates private data, and registers it with the ALSA control subsystem.
At completion, the DAPM graph contains fully connected widgets, validated paths, and user-controllable switches/mixers—ready for runtime power-state propagation.