Free Linux Kernel Development Course — ASoC Machine Class Drivers, Lecture 4
Every ALSA SoC audio card in the free Linux kernel development course eventually
faces one question that decides whether sound comes out clean or not at all: who is in
charge of the clock, and what shape is the digital audio data in? This lecture is
part of our free linux device drivers course and continues the ASoC machine driver series by
covering DAI clock and format configuration — the part of hw_params() that
tells the CPU DAI and the codec DAI to agree on bit clock direction, frame sync direction,
audio protocol, and signal polarity before a single sample is ever transferred.
What You Will Learn
- Why DAI clock and format configuration exists in ASoC
- Bit clock vs frame sync, and provider vs consumer roles
- snd_soc_dai_set_fmt() and its format flags
- snd_soc_dai_set_sysclk(), set_pll(), set_clkdiv()
- Old master/slave macros vs modern provider/consumer macros
- Writing a real hw_params() in a machine driver
- Common clocking mistakes and how to debug them
Prerequisites
This lecture assumes you have already gone through the earlier lectures in this free
linux kernel development course: the basics of ASoC machine drivers and snd_soc_dai_link
(ldd2ch6_1), the modern component-array DAI link model with
SND_SOC_DAILINK_DEFS (ldd2ch6_2), and DAPM routing between codec
pins and board connectors (ldd2ch6_3). You should also be comfortable building
and loading out-of-tree kernel modules.
Why DAI Clock and Format Configuration Matters
An I2S or PCM audio link is a synchronous serial bus. There is no separate clock chip negotiation handshake like you’d see in USB — the two ends, the CPU DAI (the SoC’s audio serial controller) and the codec DAI (the external or on-chip audio codec), must already agree, before the very first bit moves, on three independent things:
- Who drives the bit clock (BCLK) — the signal that toggles once per audio bit
- Who drives the frame sync (LRCLK/FSYNC) — the signal that marks left/right channel boundaries
- What protocol the data follows — I2S, left-justified, right-justified, DSP/PCM, TDM, AC97, or PDM — and on which clock edge data is valid
Get any one of these three wrong and you don’t get a kernel panic — you get silence, static, a channel swap, or audio playing at double speed. This is precisely why the ASoC core groups all three concerns under one API family instead of leaving each codec driver to invent its own ioctl-style configuration.
Bit Clock and Frame Sync Roles
Old Master/Slave Terms vs Modern Provider/Consumer Terms
If you’re reading an older Linux Device Drivers book or an older kernel tree, you’ll see DAI
clock roles described with CBM_CFM, CBS_CFS, and CBM_CFS —
short for “codec bit clock master, codec frame master” and so on. Mainline Linux moved away from
master/slave terminology; the current kernel defines the same four clocking relationships using
provider/consumer language, and the old macros now alias to the new ones for backward
compatibility. When you write new machine driver code in this free embedded linux course, use
the modern names.
| Relationship | Old (deprecated) macro | Current macro |
|---|---|---|
| CPU is bit-clock consumer, frame consumer (codec drives both) | SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_CBP_CFP |
| CPU drives both bit clock and frame sync | SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_CBC_CFC |
| CPU is bit-clock consumer, frame provider | SND_SOC_DAIFMT_CBM_CFS | SND_SOC_DAIFMT_CBP_CFC |
| CPU is bit-clock provider, frame consumer | — | SND_SOC_DAIFMT_CBC_CFP |
Read the new macro names left to right as “clock-bit <role>, clock-frame <role>”:
P means that side provides (drives) that clock line, C means it
consumes (receives) it. Most embedded designs use SND_SOC_DAIFMT_CBP_CFP — the
codec provides both clocks from its internal PLL, and the SoC’s CPU DAI just listens — because
codecs typically integrate a cleaner, jitter-tolerant clock source than deriving BCLK straight
off the SoC’s audio subsystem clock.
snd_soc_dai_set_fmt(): Configuring the DAI Format
This single call configures the clock-provider relationship, the audio protocol, and the signal inversion in one bitmasked value:
int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt);
The fmt argument is built by OR-ing exactly one flag from each of three groups.
The kernel defines these groups with distinct bitmasks (SND_SOC_DAIFMT_FORMAT_MASK,
SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK, and SND_SOC_DAIFMT_INV_MASK) so the
core can split them back apart internally.
Audio Protocol Flags
| Flag | Meaning |
|---|---|
| SND_SOC_DAIFMT_I2S | Standard I2S: frame syncs one bit before the audio word, most common protocol |
| SND_SOC_DAIFMT_LEFT_J | Left-justified: data starts right at the frame edge |
| SND_SOC_DAIFMT_RIGHT_J | Right-justified: data ends at the frame edge |
| SND_SOC_DAIFMT_DSP_A | PCM/DSP mode, 1-bit data delay — common on Bluetooth and modem-style audio links |
| SND_SOC_DAIFMT_DSP_B | PCM/DSP mode, no data delay — used for TDM |
| SND_SOC_DAIFMT_AC97 | AC97 codec link |
| SND_SOC_DAIFMT_PDM | Pulse-density modulation, used by many MEMS digital microphones |
Signal Inversion Flags
| Flag | Meaning |
|---|---|
| SND_SOC_DAIFMT_NB_NF | Normal bit clock, normal frame sync — the safe default for I2S |
| SND_SOC_DAIFMT_NB_IF | Normal bit clock, inverted frame sync |
| SND_SOC_DAIFMT_IB_NF | Inverted bit clock, normal frame sync |
| SND_SOC_DAIFMT_IB_IF | Both inverted — occasionally needed for PCM-mode Bluetooth or modem codecs |
Getting inversion wrong doesn’t crash anything; it usually shows up as data that’s shifted by exactly one bit, which sounds like distorted or garbled audio rather than silence — a good first thing to suspect when a new codec is “almost” working.
Configuring the Clock Source: sysclk, PLL, and Divider
Once the format and clock roles are fixed, the machine driver still has to tell each DAI what physical clock to actually use and at what rate. Three helpers cover this:
int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
unsigned int freq, int dir);
int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source,
unsigned int freq_in, unsigned int freq_out);
int snd_soc_dai_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div);
set_sysclk()selects which clock input feeds the DAI’s system clock and in which direction.SND_SOC_CLOCK_INmeans the DAI receives that clock from outside;SND_SOC_CLOCK_OUTmeans the DAI generates and exports it.set_pll()configures an on-chip PLL when the available input clock (say, a 12.288 MHz crystal) needs to be multiplied or divided to reach the frequency the audio interface actually needs.set_clkdiv()configures a divider so the final bit clock and frame clock run at the lowest rate that still satisfies the sample rate and word length — lower clock rates mean lower power draw, which matters a lot on battery-powered embedded audio designs.
All three are optional per DAI driver — the ASoC core simply checks whether the underlying
snd_soc_dai_ops callback is implemented before calling it, so a codec that
generates its clocks from a fixed external crystal with no software-controlled PLL can leave
set_pll unimplemented entirely.
Typical hw_params() Clock Configuration Sequence
Building an Example: ep_clk_demo Machine Driver
Let’s put this together in an original, minimal machine driver for this free linux device
drivers course. The example assumes a codec that acts as clock provider on both lines
(SND_SOC_DAIFMT_CBP_CFP), uses standard I2S framing, and derives its own PLL
output from a 12.288 MHz reference clock supplied by the board.
hw_params() Implementation
#include <sound/soc.h>
#include <sound/pcm_params.h>
#define EP_CLK_DEMO_MCLK_HZ 12288000
#define EP_CLK_DEMO_PLL_ID 0
#define EP_CLK_DEMO_SYSCLK_ID 0
static int ep_clk_demo_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
unsigned int rate = params_rate(params);
unsigned int pll_out = rate * 256;
int ret;
/* codec is bit-clock provider and frame provider */
ret = snd_soc_dai_set_fmt(codec_dai,
SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBP_CFP);
if (ret dev, "ep_clk_demo: codec set_fmt failed: %d\n", ret);
return ret;
}
/* CPU DAI mirrors the same relationship */
ret = snd_soc_dai_set_fmt(cpu_dai,
SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBP_CFP);
if (ret dev, "ep_clk_demo: cpu set_fmt failed: %d\n", ret);
return ret;
}
/* derive pll_out from the board's fixed 12.288 MHz reference */
ret = snd_soc_dai_set_pll(codec_dai, EP_CLK_DEMO_PLL_ID, 0,
EP_CLK_DEMO_MCLK_HZ, pll_out);
if (ret dev, "ep_clk_demo: set_pll failed: %d\n", ret);
return ret;
}
/* route the PLL output into the codec's system clock */
ret = snd_soc_dai_set_sysclk(codec_dai, EP_CLK_DEMO_SYSCLK_ID,
pll_out, SND_SOC_CLOCK_IN);
if (ret dev, "ep_clk_demo: set_sysclk failed: %d\n", ret);
return ret;
}
dev_info(rtd->dev, "ep_clk_demo: rate=%u pll_out=%u configured\n",
rate, pll_out);
return 0;
}
static const struct snd_soc_ops ep_clk_demo_ops = {
.hw_params = ep_clk_demo_hw_params,
};
Device Tree Reference Clock Snippet
&i2c1 {
ep_codec: audio-codec@1a {
compatible = "ep,demo-codec";
reg = <0x1a>;
clocks = <&audio_mclk>;
clock-names = "mclk";
};
};
audio_mclk: audio-mclk {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <12288000>;
};
Build, Load, and Test
$ make -C /lib/modules/$(uname -r)/build M=$PWD modules
$ sudo insmod ep_clk_demo.ko
$ dmesg | tail -n 5
[ 812.331022] ep_clk_demo: ep_clk_demo: rate=48000 pll_out=12288000 configured
[ 812.331190] ep-demo-card ep-demo-card: ep_clk_demo <-> ep-codec.1-001a mapping ok
$ aplay -D hw:0,0 test_48k.wav
Playing WAVE 'test_48k.wav' : Signed 16 bit Little Endian, Rate 48000 Hz, Stereo
Notice that pll_out equals rate * 256 — a common convention
where the system clock runs at 256 times the sample rate (an “Fs × 256” master clock).
Many codecs document their required MCLK-to-Fs ratio explicitly in their datasheet; always
check that value rather than assuming 256 for every part.
Common Mistakes and Troubleshooting
- Mismatched provider/consumer roles — if both the CPU
DAI and codec DAI are configured as bit-clock provider (or both as consumer), you’ll typically
get no clock activity at all or two drivers fighting over the same line. Check with a scope or
logic analyzer on BCLK/LRCLK if
aplayhangs with no error. - Wrong MCLK-to-Fs ratio — passing a
pll_outorsysclkvalue the codec doesn’t support silently produces distorted or pitch-shifted audio instead of an error, since the codec just locks to whatever it’s given. - Calling set_pll() when there’s no PLL to configure — for
codecs clocked directly from a fixed external oscillator, skip
set_pll()and call onlyset_sysclk()with the fixed MCLK rate. - Forgetting to configure both DAIs —
set_fmt()must be called on both the CPU DAI and the codec DAI with a matching (mirrored) relationship; setting it on only one side leaves the other DAI in its power-on default configuration. - Using deprecated CBM_CFM style macros — they still compile since they alias to the new provider/consumer macros, but mixing old and new names in the same driver hurts readability and can confuse anyone reviewing your code against a recent kernel tree.
Best Practices, Performance, and Power Considerations
- Prefer the codec as clock provider when the codec has a higher-quality internal PLL than the SoC’s audio clock tree — lower jitter directly improves audio SNR.
- Use
set_clkdiv()to keep the actual bit clock as low as the sample format allows; unnecessarily high BCLK rates burn power for no audible benefit. - Always propagate and log the return value of every
snd_soc_dai_set_*call — clocking failures are silent at the audio layer and easy to miss without explicit error logging. - Keep clock/format configuration inside
hw_params(), notstartup(), whenever the configuration depends on the negotiated sample rate or format —hw_params()is the first callback where those parameters are known.
Summary and Key Takeaways
DAI clock and format configuration is the handshake that makes an ASoC audio link actually
produce sound instead of silence or noise. snd_soc_dai_set_fmt() fixes the protocol,
clock-provider relationship, and signal polarity; snd_soc_dai_set_pll(),
set_sysclk(), and set_clkdiv() route and shape the actual clock signal
feeding each DAI. Modern kernels describe clock roles with provider/consumer macros
(CBP_CFP, CBC_CFC, CBP_CFC, CBC_CFP) instead
of the older master/slave naming, and both DAIs on a link must be configured with matching,
mirrored roles. In the next lecture of this free linux kernel development course, we move from
configuring individual DAIs to registering the complete struct snd_soc_card — the
object that ties the machine driver, DAI links, and DAPM routing together into one working sound
card.
Frequently Asked Questions
What is the difference between SND_SOC_DAIFMT_CBP_CFP and CBC_CFC?
CBP_CFP means the codec provides (drives) both the bit clock and frame sync while the CPU DAI consumes them. CBC_CFC is the reverse — the CPU DAI drives both clock lines and the codec just listens. Most designs use CBP_CFP because codec PLLs are usually cleaner clock sources.
Is SND_SOC_DAIFMT_CBM_CFM still valid in current kernels?
Yes, it still compiles because it’s aliased to the new provider/consumer macro, but new driver code in this free linux device drivers course should use the current CBP_CFP/CBC_CFC/CBP_CFC/CBC_CFP names for clarity and consistency with mainline.
Do I always need to call snd_soc_dai_set_pll()?
No. Only call it if the DAI driver actually implements a PLL callback and your board needs frequency translation. Codecs clocked directly from a fixed-rate external oscillator that already matches the required system clock rate can skip set_pll() entirely.
What happens if I configure the wrong signal inversion flag?
The audio usually still plays but sounds distorted, shifted, or has swapped channels, because the receiving side samples data on the wrong clock edge. It rarely produces a hard error, which makes it a common silent bug in new codec bring-up.
Where should hw_params() clock configuration code live in a machine driver?
Inside the struct snd_soc_ops assigned to the DAI link’s .ops
field, specifically its hw_params callback, since sample rate and format are only
known once ALSA negotiates hardware parameters for that stream.
Can the CPU DAI and codec DAI use different format flags?
The protocol and inversion flags should match between both DAIs since they describe the same physical bus. The clock-provider flag must be complementary — if the codec is provider, the CPU DAI’s own set_fmt() call should reflect it as consumer of that same relationship, which is why this course’s example mirrors the CBP_CFP flag on both DAIs.
Continue the Free Linux Kernel Development Course
Next up: registering the complete ASoC sound card with struct snd_soc_card.
Next Lecture Browse Full Course Index