ALSA SoC DAI Clock Configuration-Free Linux Device Drivers Course

ALSA SoC DAI Clock Configuration

Free Linux Kernel Development Course — ASoC Machine Class Drivers, Lecture 4

Free Linux Kernel Development Course
Free Embedded Linux Course
Free Linux Device Drivers Course

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

CPU DAI ↔ BCLK ↔ Codec DAI
CPU DAI ↔ LRCLK ↔ Codec DAI
Provider drives the clock line → Consumer samples on the edge

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.

RelationshipOld (deprecated) macroCurrent macro
CPU is bit-clock consumer, frame consumer (codec drives both)SND_SOC_DAIFMT_CBM_CFMSND_SOC_DAIFMT_CBP_CFP
CPU drives both bit clock and frame syncSND_SOC_DAIFMT_CBS_CFSSND_SOC_DAIFMT_CBC_CFC
CPU is bit-clock consumer, frame providerSND_SOC_DAIFMT_CBM_CFSSND_SOC_DAIFMT_CBP_CFC
CPU is bit-clock provider, frame consumerSND_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

FlagMeaning
SND_SOC_DAIFMT_I2SStandard I2S: frame syncs one bit before the audio word, most common protocol
SND_SOC_DAIFMT_LEFT_JLeft-justified: data starts right at the frame edge
SND_SOC_DAIFMT_RIGHT_JRight-justified: data ends at the frame edge
SND_SOC_DAIFMT_DSP_APCM/DSP mode, 1-bit data delay — common on Bluetooth and modem-style audio links
SND_SOC_DAIFMT_DSP_BPCM/DSP mode, no data delay — used for TDM
SND_SOC_DAIFMT_AC97AC97 codec link
SND_SOC_DAIFMT_PDMPulse-density modulation, used by many MEMS digital microphones

Signal Inversion Flags

FlagMeaning
SND_SOC_DAIFMT_NB_NFNormal bit clock, normal frame sync — the safe default for I2S
SND_SOC_DAIFMT_NB_IFNormal bit clock, inverted frame sync
SND_SOC_DAIFMT_IB_NFInverted bit clock, normal frame sync
SND_SOC_DAIFMT_IB_IFBoth 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_IN means the DAI receives that clock from outside; SND_SOC_CLOCK_OUT means 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

1. set_fmt() 2. set_pll() 3. set_sysclk() 4. set_clkdiv()

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 aplay hangs with no error.
  • Wrong MCLK-to-Fs ratio — passing a pll_out or sysclk value 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 only set_sysclk() with the fixed MCLK rate.
  • Forgetting to configure both DAIsset_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(), not startup(), 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

Leave a Reply

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