The ASoC DAI Driver Structure-Free Linux Device Drivers Course
A field-by-field walkthrough of struct snd_soc_dai_driver and struct snd_soc_dai_ops
In the previous lecture of this free Linux kernel development course, we introduced
struct snd_soc_component_driver — the structure that abstracts a codec or a
platform device as a whole inside the ASoC (ALSA System on Chip) framework. But a component
doesn’t do anything with audio on its own; it exposes one or more Digital Audio Interfaces
(DAIs), and each of those DAIs needs its own description of what it can do and how to control
it. That description lives in struct snd_soc_dai_driver, and this lecture walks
through it field by field, along with the callback table it points to,
struct snd_soc_dai_ops.
What You Will Learn
- Why DAI drivers are registered separately from component drivers
- Every field of struct snd_soc_dai_driver explained
- The symmetric_rates / symmetric_channels / symmetric_samplebits flags
- The full struct snd_soc_dai_ops callback table
- What each individual DAI ops callback is for
- digital_mute vs. the modern mute_stream callback
Prerequisites
This lecture assumes you’re already comfortable with struct snd_soc_component_driver
and devm_snd_soc_register_component() from the previous lecture in this free Linux
device drivers course. Basic C function-pointer familiarity is assumed throughout.
Where struct snd_soc_dai_driver Fits
Every codec — or, more generally, every “component,” since the same registration path is
shared by codec and platform devices — must expose the DAIs it owns, along with each DAI’s
capabilities and operations. This is done by filling in and registering one instance of
struct snd_soc_dai_driver per DAI the component has. A component with a single I2S
interface registers one DAI driver; a component multiplexing several interfaces registers one
per interface.
These DAI driver instances are exported through the same
devm_snd_soc_register_component() call used to register the component driver itself.
That function takes a pointer to the struct snd_soc_component_driver the DAI drivers
belong to, and the resulting component is inserted into the ASoC core’s global component list
(component_list, defined in sound/soc/soc-core.c), ready to be picked up
by a machine driver before the sound card itself is registered.
struct snd_soc_dai_driver is defined in include/sound/soc-dai.h, and
it covers three things for a given interface: its clocking, its data format, and the ALSA
operations that drive it.
struct snd_soc_dai_driver, Field by Field
struct snd_soc_dai_driver {
/* DAI description */
const char *name;
/* DAI driver callbacks */
int (*probe)(struct snd_soc_dai *dai);
int (*remove)(struct snd_soc_dai *dai);
int (*suspend)(struct snd_soc_dai *dai);
int (*resume)(struct snd_soc_dai *dai);
/* ops */
const struct snd_soc_dai_ops *ops;
/* DAI capabilities */
struct snd_soc_pcm_stream capture;
struct snd_soc_pcm_stream playback;
unsigned int symmetric_rates:1;
unsigned int symmetric_channels:1;
unsigned int symmetric_samplebits:1;
};
Only the members relevant to day-to-day driver-writing are shown above. Here is what each one
means:
- name — the name of the DAI interface. This is how the machine driver
refers to this specific DAI later when it wires up a link between a codec DAI and a CPU
DAI. - probe — the DAI driver’s probe function. It runs when the component driver
this DAI belongs to is probed by the machine driver — in practice, when the machine driver
registers a sound card with the ASoC core. - remove — invoked when the component driver this DAI driver belongs to is
unregistered. - suspend and resume — power management callbacks, run
around system suspend and resume respectively. - ops — a pointer to a
struct snd_soc_dai_opsinstance, which
supplies the callbacks used to configure and control the DAI. This is covered in detail in the
next section. - capture — a
struct snd_soc_pcm_streamdescribing the hardware
parameters supported for audio capture on this DAI: channel count, sample rate, data format,
and so on. It does not need to be initialized at all if the DAI doesn’t support capture. - playback — the equivalent
struct snd_soc_pcm_streamfor audio
playback. Likecapture, it can be left uninitialized if playback isn’t
supported. - symmetric_rates, symmetric_channels,
symmetric_samplebits — single-bit flags telling the ASoC core that if this DAI
is already streaming in one direction, a stream opened in the other direction must use a
matching sample rate, channel count, or sample width, respectively. This matters on hardware
where playback and capture share a single clock generator or PLL and genuinely cannot run at
independent settings simultaneously.
Note that struct snd_soc_pcm_stream — the type behind both capture
and playback — is itself a separate structure with its own set of fields (sample rate
range, format bitmask, channel range) that deserves its own detailed treatment; that’s reserved
for the next lecture in this course rather than covered here.
DAI Operations: struct snd_soc_dai_ops
The actual behavior of a DAI — how it configures its clock, how it configures its data format,
and how it responds to PCM lifecycle events like open, prepare, and trigger — is abstracted by
instances of struct snd_soc_dai_ops. If you need to run some setup on the device
before an audio transfer starts, that logic typically lives in one of these callbacks — the
prepare callback, for example, exists specifically for that kind of pre-transfer
setup.
The structure, also from include/sound/soc-dai.h, is considerably larger than
snd_soc_dai_driver itself:
struct snd_soc_dai_ops {
int (*set_sysclk)(struct snd_soc_dai *dai, int clk_id,
unsigned int freq, int dir);
int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
unsigned int freq_in, unsigned int freq_out);
int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);
int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
int (*xlate_tdm_slot_mask)(unsigned int slots,
unsigned int *tx_mask,
unsigned int *rx_mask);
int (*set_tdm_slot)(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask,
int slots, int slot_width);
int (*set_channel_map)(struct snd_soc_dai *dai,
unsigned int tx_num, unsigned int *tx_slot,
unsigned int rx_num, unsigned int *rx_slot);
int (*get_channel_map)(struct snd_soc_dai *dai,
unsigned int *tx_num, unsigned int *tx_slot,
unsigned int *rx_num, unsigned int *rx_slot);
int (*set_tristate)(struct snd_soc_dai *dai, int tristate);
int (*set_sdw_stream)(struct snd_soc_dai *dai, void *stream,
int direction);
int (*digital_mute)(struct snd_soc_dai *dai, int mute);
int (*mute_stream)(struct snd_soc_dai *dai, int mute, int stream);
int (*startup)(struct snd_pcm_substream *, struct snd_soc_dai *);
void (*shutdown)(struct snd_pcm_substream *, struct snd_soc_dai *);
int (*hw_params)(struct snd_pcm_substream *,
struct snd_pcm_hw_params *, struct snd_soc_dai *);
int (*hw_free)(struct snd_pcm_substream *, struct snd_soc_dai *);
int (*prepare)(struct snd_pcm_substream *, struct snd_soc_dai *);
int (*trigger)(struct snd_pcm_substream *, int, struct snd_soc_dai *);
};
A codec or platform driver registers one of these structures for every DAI it has — this is
what makes the structure generic rather than codec-specific. The machine driver later uses it,
together with snd_soc_dai_driver, to build the link between the codec and the SoC.
Every driver implements only the callbacks its hardware actually needs; there is no requirement
to fill in the entire table.
Going through each callback in turn:
- set_sysclk — configures the DAI’s master clock (MCLK) or system clock
(SYSCLK) input. Called by the sound card driver, normally from its own
hw_paramshandling. - set_pll — configures and enables an on-chip PLL, deriving an output
frequency from whatever clock is feeding it. - set_clkdiv — sets a clock divider, used to derive the DAI’s bit clock and
frame clock from a higher-frequency system or master clock. As a rule of thumb, it’s best to
keep the bit and frame clocks as low as the required audio format allows, since this saves
system power. - set_bclk_ratio — configures a fixed ratio between the bit clock (BCLK) and
the sample rate, for hardware that expresses its clocking requirement that way rather than
through an absolute frequency. - set_fmt — configures the DAI’s hardware audio format: I2S, left-justified,
DSP mode, and so on, along with clock polarity and the DAI’s master/slave role. - xlate_tdm_slot_mask — translates a requested slot count into concrete
transmit and receive slot bitmasks for Time Division Multiplexing (TDM) mode. If a driver
doesn’t implement this, the ASoC core falls back to a generic default mask generator. - set_tdm_slot — configures TDM slot assignment directly: which slots are
active for transmit and receive, how many slots exist in the frame, and how wide each slot
is. - set_channel_map / get_channel_map — configure and query,
respectively, the mapping between logical audio channels and physical TDM slot numbers. - set_tristate — puts the DAI’s output pins into a high-impedance
(tristate) state, so other devices can drive a shared bus without contention. - set_sdw_stream — associates a SoundWire stream with this DAI, for hardware
connected over the SoundWire bus rather than a traditional I2S/TDM link. - digital_mute — the original, direction-less mute callback: called by the
core to mute or unmute the DAI’s digital audio path. On current mainline kernels this callback
is considered legacy — new drivers should implementmute_streaminstead, which is
discussed next. - mute_stream — the modern replacement for
digital_mute. It
adds an explicitstream(direction) argument, letting a driver mute playback and
capture independently instead of assuming mute always means the same thing regardless of
direction. Most current codec drivers implement this one rather than
digital_mute. - startup — runs when a capture or playback substream is opened.
- shutdown — the counterpart to
startup, run when the
substream is closed; it undoes whateverstartupset up. - hw_params — runs once the stream’s hardware parameters (rate, format,
channel count) have been negotiated and finalized; this is typically where a driver programs
the corresponding configuration into hardware registers. - hw_free — releases whatever
hw_paramsconfigured. - prepare — runs once the stream is ready to start, immediately before
playback or capture begins. - trigger — runs on start, stop, and pause/resume events for the PCM
substream. Theintparameter carries the specific trigger command, and drivers
typically branch on it with aswitchstatement.
The Three Callback Classes — A Preview
As the source material for this chapter puts it, the callbacks inside
snd_soc_dai_ops can basically be divided into three classes, and a driver only
implements the ones relevant to its actual hardware. Looking at the table above, the grouping is
fairly intuitive even before it’s spelled out formally:
- Callbacks concerned purely with clocking —
set_sysclk,set_pll,set_clkdiv,
set_bclk_ratio. - Callbacks concerned with format and channel/slot configuration —
set_fmt,xlate_tdm_slot_mask,set_tdm_slot,
set_channel_map,get_channel_map,set_tristate,
set_sdw_stream. - Callbacks concerned with the PCM streaming lifecycle —
startup,shutdown,hw_params,hw_free,
prepare,trigger, plus the mute callbacks.
The next lecture in this course picks this grouping up formally and walks through
struct snd_soc_pcm_stream — the structure behind the capture and
playback fields we skipped over here — before moving on to how a codec exposes
user-adjustable controls to userspace.
Common Mistakes and Troubleshooting
- Implementing digital_mute() on new drivers. It still exists in the
structure for backward compatibility, but new drivers should implement
mute_stream()instead — it’s direction-aware and is what current codec drivers in
mainline actually use. - Assuming every callback must be implemented. A DAI fed from a fixed
external clock with no on-chip PLL has no reason to implementset_pllor
set_clkdiv— leave them null. - Forgetting that probe() order follows component registration. A DAI’s
proberuns as part of the owning component being registered with the ASoC core by
the machine driver — not as part of your platform driver’s own.probe()
function. - Confusing set_tdm_slot with set_channel_map. The former decides which
physical slots are active at all; the latter decides which logical audio channel lands on which
of those already-active slots. They solve different problems and are often both needed on
complex TDM hardware.
Best Practices
- Register one
snd_soc_dai_driverinstance per physical DAI on your hardware,
even when several DAIs share the same underlying regmap or I2C address. - Prefer
mute_streamoverdigital_mutein any driver you’re writing
today; treatdigital_muteas read-only knowledge for maintaining old code. - Only wire up the ops callbacks your hardware genuinely needs — an emptier
snd_soc_dai_opstable is easier to review and keeps dead code paths out of your
driver. - Keep clock-related callbacks (
set_sysclk,set_pll,
set_clkdiv) separate in your own code from format-related ones
(set_fmt, TDM callbacks) even though they live in the same struct — it mirrors how
the ASoC core itself documents and calls them.
Summary and Key Takeaways
struct snd_soc_dai_driver is how a codec or platform component describes each
individual DAI it owns: its name, its lifecycle callbacks, its capture/playback capabilities, and
— through the embedded ops pointer — everything about how it’s configured and
driven. struct snd_soc_dai_ops is the callback table behind that ops
pointer, and its members split naturally into clocking callbacks, format/TDM callbacks, and PCM
streaming lifecycle callbacks, with only the direction-aware mute_stream being the
modern replacement worth calling out explicitly among them. One instance of
snd_soc_dai_driver is required per DAI, and all of them are registered together with
the component driver through devm_snd_soc_register_component().
The next lecture in this free Linux kernel development course looks at
struct snd_soc_pcm_stream in detail — the structure behind the capture
and playback fields introduced here — before moving into how ASoC exposes
user-adjustable controls to userspace.
Frequently Asked Questions
Why is snd_soc_dai_driver a separate structure from snd_soc_component_driver?
A single component can expose more than one DAI — for example a codec with both a
Bluetooth-facing interface and a headset-facing interface. Separating the two lets the ASoC
core register one component alongside several independent DAI descriptions.
Do I have to initialize both capture and playback in snd_soc_dai_driver?
No. Either field can be left uninitialized entirely if the DAI doesn’t support that
direction — a playback-only DAI simply doesn’t fill in the capture field.
What do the symmetric_rates, symmetric_channels, and symmetric_samplebits flags do?
They tell the ASoC core that once a stream is running in one direction, a stream opened in
the other direction must use a matching rate, channel count, or sample width — necessary on
hardware where both directions share a single clock source.
Should new drivers implement digital_mute or mute_stream?
mute_stream. It carries an explicit stream direction argument and is what current mainline
codec drivers implement; digital_mute remains in the structure mainly for backward
compatibility with older drivers.
What’s the difference between set_tdm_slot and set_channel_map?
set_tdm_slot decides which physical TDM slots are active for transmit and receive.
set_channel_map decides which logical audio channel is mapped onto which of those slots — a
separate, finer-grained concern.
Does every DAI driver need to implement every callback in snd_soc_dai_ops?
No. A driver implements only the callbacks relevant to its hardware — a DAI with a fixed
external clock, for instance, has no need for set_pll or set_clkdiv.
Continue Learning Linux Kernel Development — Free
This lecture is part of EmbeddedPathashala’s free Linux kernel development course and free
Linux device drivers course, working through the ASoC audio framework structure by structure.
