What is The ASoC DAI Driver Structure in Linux Kernel-Free Linux Device Drivers Course

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

15+ min read
Kernel 6.x APIs
Free Linux Kernel Course

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_ops instance, 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_stream describing 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_stream for audio
    playback. Like capture, 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_params handling.
  • 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 implement mute_stream instead, which is
    discussed next.
  • mute_stream — the modern replacement for digital_mute. It
    adds an explicit stream (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 whatever startup set 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_params configured.
  • 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. The int parameter carries the specific trigger command, and drivers
    typically branch on it with a switch statement.

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 implement set_pll or
    set_clkdiv — leave them null.
  • Forgetting that probe() order follows component registration. A DAI’s
    probe runs 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_driver instance per physical DAI on your hardware,
    even when several DAIs share the same underlying regmap or I2C address.
  • Prefer mute_stream over digital_mute in any driver you’re writing
    today; treat digital_mute as read-only knowledge for maintaining old code.
  • Only wire up the ops callbacks your hardware genuinely needs — an emptier
    snd_soc_dai_ops table 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.

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *