In every earlier lecture of this free Linux device drivers course we hand-wrote a machine driver
in C: we bound a CPU DAI, a codec DAI, added routing, and registered a snd_soc_card
ourselves. But most audio boards on real hardware do not need any of that custom C code at all.
The Linux ALSA SoC (ASoC) core ships a ready-made, generic simple-card machine driver
that can describe an entire sound card purely from the device tree. In this lecture of our free
embedded Linux course we study exactly when the simple-card machine driver is enough, how its
device tree binding is structured on a modern kernel, and how to model codec-less sound cards
(boards where there is no real codec chip at all, such as S/PDIF or HDMI audio output) using the
dummy codec mechanism.
What You Will Learn
- Why the simple-card machine driver exists
- simple-card.yaml device tree structure on kernel 6.x
- dai-link@N subnodes and clock master/slave phandles
- Audio widgets and DAPM routing from the device tree
- Codec-less sound cards using a dummy codec
- playback-only / capture-only DAI link flags
- Building and testing a demo simple-card sound card
Prerequisites
Before this lecture
This lecture builds directly on the earlier lectures of this free Linux kernel development
course: DAI links and the snd_soc_dai_link_component model, DAPM widgets and routes,
DAI clock/format configuration with set_fmt(), and snd_soc_card
registration. If any of these feel unfamiliar, revisit the previous ASoC Machine Class Drivers
lectures before continuing here. Basic device tree syntax (nodes, phandles, #address-cells)
is also assumed.
Understanding the Simple-Card Machine Driver
Everything a machine driver does boils down to three jobs: describe which CPU DAI talks to which
codec DAI, describe the clocking relationship between them, and describe the widgets/routes so DAPM
knows how audio flows on the board. On a huge number of boards, none of those three jobs need any
board-specific C logic — they are pure data. The kernel’s generic simple-card machine
driver (sound/soc/generic/simple-card.c) reads that data straight out of the
device tree and builds the snd_soc_card for you at runtime, with zero custom code.
You reach for the simple-card machine driver whenever your CPU DAI driver and your codec DAI
driver are both already upstream, and your board does not need any special hooks in
.probe(), custom DAPM widgets beyond what the codec driver already exposes, or unusual
runtime PM handling. If your board needs any board-specific logic — a GPIO-controlled amplifier with
custom sequencing, or a codec that needs an unusual clock tree set up in code — you still write a
custom machine driver as covered in the earlier lectures of this Linux device drivers course. The two
approaches are not mutually exclusive either: many boards use simple-card for 90% of the wiring and
only add a small amount of platform glue elsewhere.
Simple-Card Device Tree Binding on a Modern Kernel
On kernel 6.x the binding is documented as a schema at
Documentation/devicetree/bindings/sound/simple-card.yaml, replacing the older free-form
text binding. The root node uses compatible = "simple-audio-card", and each connection
between a CPU DAI and a codec DAI is described inside a simple-audio-card,dai-link@N
subnode. Using indexed dai-link@N subnodes (instead of one flat set of properties) is
what lets a single sound card describe several DAI links — for example, a playback path and a
capture path that use two different physical interfaces.
sound {
compatible = "simple-audio-card";
simple-audio-card,name = "ep-dev-board-sound";
#address-cells = <1>;
#size-cells = <0>;
simple-audio-card,dai-link@0 {
reg = <0>;
format = "i2s";
bitclock-master = <&ep_codec_link>;
frame-master = <&ep_codec_link>;
cpu {
sound-dai = <&ep_sai0>;
};
ep_codec_link: codec {
sound-dai = <&ep_codec>;
clocks = <&ep_mclk>;
};
};
};
Notice that bitclock-master and frame-master are still phandles to a
labelled CPU or codec subnode — whichever side drives BCLK/FSYNC is the “master” for that signal.
This is the same master/frame-master model you already saw in the DAI clock configuration lecture,
just expressed as device tree data instead of a call to snd_soc_dai_set_fmt() in C. The
simple-card core parses this once at probe time and issues the equivalent set_fmt() call
for you, using the modern provider/consumer terminology internally.
Audio Widgets and DAPM Routing in the Device Tree
Just like the custom machine driver you wrote in an earlier lecture, a simple-card sound card still
needs board-level DAPM widgets (jacks, external speakers) and routes connecting them to the codec’s
own pins. These are declared with simple-audio-card,widgets and
simple-audio-card,routing, and internally the core still calls the same
snd_soc_of_parse_audio_routing() helper you used before — simple-card just calls it for
you automatically instead of you calling it from a .probe() function.
simple-audio-card,widgets =
"Microphone", "Mic Jack",
"Headphone", "Headphone Jack",
"Speaker", "Ext Speaker";
simple-audio-card,routing =
"MICIN", "Mic Jack",
"Headphone Jack", "HPOUT",
"Ext Speaker", "SPKOUT";
Building a Codec-less Sound Card with a Dummy Codec
Some boards have no real audio codec chip on the DAI link at all. The classic example is an S/PDIF or HDMI output: the CPU’s audio interface produces an already-formatted digital bitstream that goes straight out to a connector, with no I2C/SPI-controlled codec on the other end to configure. ASoC still expects every DAI link to have two sides, so the simple-card core provides a dummy codec mechanism for exactly this “codec-less” case.
You add a small device tree node with compatible = "linux,snd-soc-dummy" and point the
dai-link’s codec subnode at it instead of a real chip phandle. Because there is no physical codec
driver to probe, the ASoC core simply skips codec-side configuration for that link and lets the CPU
DAI drive the format on its own.
ep_dummy_codec: ep-dummy-codec {
compatible = "linux,snd-soc-dummy";
#sound-dai-cells = <0>;
};
sound-spdif {
compatible = "simple-audio-card";
simple-audio-card,name = "ep-dev-board-spdif";
simple-audio-card,dai-link@0 {
format = "left_j";
playback-only;
cpu {
sound-dai = <&ep_spdif0>;
};
codec {
sound-dai = <&ep_dummy_codec>;
};
};
};
The playback-only and capture-only boolean flags on the
dai-link subnode tell the core that this particular link only ever runs in one direction
— you cannot arm both playback and capture on a link that is physically wired for output only. If a
board needs both directions on separate physical interfaces (for example, S/PDIF-out and S/PDIF-in on
two different pins), you simply add a second dai-link@1 subnode with the
capture-only flag instead of trying to force one link to do both.
Hands-On: A Complete Simple-Card Device Tree
Let’s put both patterns together for a single fictional board, ep_dev_board, which has
one real I2S codec for speaker/headphone/mic and one S/PDIF output. Nothing here is copied from any
vendor board file — the node names are original examples for this free Linux kernel development
course.
/* Codec and CPU DAI nodes, defined elsewhere in the board .dts */
&i2c1 {
ep_codec: audio-codec@1a {
compatible = "ep,ep-codec";
reg = <0x1a>;
#sound-dai-cells = <0>;
clocks = <&ep_mclk>;
};
};
ep_dummy_codec: ep-dummy-codec {
compatible = "linux,snd-soc-dummy";
#sound-dai-cells = <0>;
};
/* Main I2S sound card */
sound {
compatible = "simple-audio-card";
simple-audio-card,name = "ep-dev-board-sound";
simple-audio-card,widgets =
"Microphone", "Mic Jack",
"Headphone", "Headphone Jack",
"Speaker", "Ext Speaker";
simple-audio-card,routing =
"MICIN", "Mic Jack",
"Headphone Jack", "HPOUT",
"Ext Speaker", "SPKOUT";
simple-audio-card,dai-link@0 {
format = "i2s";
bitclock-master = <&ep_link_master>;
frame-master = <&ep_link_master>;
cpu {
sound-dai = <&ep_sai0>;
};
ep_link_master: codec {
sound-dai = <&ep_codec>;
};
};
};
/* Secondary codec-less S/PDIF output */
sound-spdif {
compatible = "simple-audio-card";
simple-audio-card,name = "ep-dev-board-spdif";
simple-audio-card,dai-link@0 {
format = "left_j";
playback-only;
cpu {
sound-dai = <&ep_spdif0>;
};
codec {
sound-dai = <&ep_dummy_codec>;
};
};
};
Build, Boot and Test the Sound Card
No C driver has to be written for either card — CONFIG_SND_SIMPLE_CARD is the only
Kconfig symbol you need enabled, alongside the CPU DAI and codec drivers themselves. Confirm it is
built in or as a module:
$ grep SND_SIMPLE_CARD /boot/config-$(uname -r)
CONFIG_SND_SIMPLE_CARD=y
After booting with the updated device tree, verify both cards were registered by the simple-card
core, then confirm they enumerate correctly with aplay:
$ dmesg | grep -i simple-audio-card
[ 2.101223] asoc-simple-card sound: ep-dev-board-sound ep-sai0-audio mapping ok
[ 2.104590] asoc-simple-card sound-spdif: ep-dev-board-spdif ep-spdif0-audio mapping ok
$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: epdevboardsound [ep-dev-board-sound], device 0: ep-sai0-ep-codec ep-codec-0 []
card 1: epdevboardspdif [ep-dev-board-spdif], device 0: ep-spdif0-dummy snd-soc-dummy-dai-0 []
$ aplay -D hw:0,0 test.wav
Playing WAVE 'test.wav' : Signed 16 bit Little Endian, Rate 48000 Hz, Stereo
Two independent ALSA cards show up — one backed by a real codec driver, one backed entirely by the dummy codec — and neither required a single line of custom machine-driver C code.
Simple-Card vs Custom Machine Driver vs Audio-Graph-Card2
| Approach | Best for | Custom C needed? |
|---|---|---|
| simple-card | Straightforward CPU-codec wiring, single or few DAI links | No |
| Custom machine driver | Board-specific hooks, custom DAPM logic, unusual PM sequencing | Yes |
| audio-graph-card2 | Complex multi-port graph topologies; the upstream direction for new boards | No |
It is worth knowing that upstream ASoC maintainers are steadily steering new features toward
audio-graph-card2 rather than extending simple-card further, since the graph-based
binding scales better to boards with many ports. simple-card remains fully supported and is still the
simplest starting point for a two-node board, which is why this free Linux device drivers course
covers it in depth before you encounter audio-graph-card2 in more advanced designs.
Common Mistakes and Troubleshooting
Forgetting #sound-dai-cells on the dummy codec node
Without #sound-dai-cells = <0>; on the linux,snd-soc-dummy node,
the phandle lookup in the codec subnode fails during device tree parsing and the card
never probes.
Setting both playback-only and capture-only on the same link
The simple-card core treats this as a link with no usable direction at all and will refuse to
register it — split the two directions into separate dai-link@N subnodes instead.
Mismatched bitclock-master / frame-master phandle
The phandle must point to a label placed directly on the cpu or codec
subnode inside the same dai-link. Pointing it at the wrong dai-link’s subnode silently
produces the wrong clock direction and garbled or silent audio.
Best Practices
- Reach for simple-card first; only fall back to a custom machine driver once you hit something it genuinely cannot express
- Keep one dai-link@N subnode per physical interface rather than overloading a single link
- Name your sound cards descriptively (simple-audio-card,name) — it is what shows up in aplay -l and userspace mixers
- Validate your board’s schema with `dtschema` / `make dtbs_check` against simple-card.yaml before flashing
Chapter Summary and What’s Next
This lecture closes out our ASoC Machine Class Drivers chapter in this free Linux kernel
development course. Across the chapter we went from the raw snd_soc_dai_link component
model, through DAPM board routing and DAI clock/format configuration, to full
snd_soc_card registration — and finally to the generic simple-card machine driver, which
lets a huge share of real boards skip writing any machine-driver C code at all, including codec-less
designs built around the dummy codec. That is a complete, modern picture of Linux audio machine
drivers, built from first principles rather than copied from any single board file — exactly the kind
of practical depth this free embedded systems course and free Linux device drivers course aims for.
In the next chapter of this free Linux kernel development course we move away from audio entirely and
start the V4L2 (Video for Linux 2) subsystem, which handles camera and video capture devices on
Linux.
Frequently Asked Questions
What is the simple-card machine driver in ALSA SoC?
It is a generic, built-in ASoC machine driver that builds a complete sound card purely from device tree data — CPU/codec DAI links, clock master relationships, and DAPM widgets/routes — without requiring any board-specific C code.
When should I use simple-card instead of writing my own machine driver?
Use simple-card whenever your board’s audio wiring can be fully described as data: which DAI connects to which, who is the clock master, and what the DAPM routes are. Write a custom machine driver only when you need board-specific probe hooks, PM sequencing, or logic that data alone cannot express.
What is a codec-less sound card?
A codec-less sound card is one where the CPU’s audio interface (such as S/PDIF or HDMI audio) outputs an already-formatted digital signal with no real, configurable codec chip on the other end of the DAI link.
How do I describe a codec-less sound card in the device tree?
Add a node with compatible = "linux,snd-soc-dummy" and
#sound-dai-cells = <0>;, then point the dai-link’s codec subnode at it instead of
a real codec phandle.
What do playback-only and capture-only do in a simple-card dai-link?
They are boolean device tree flags that mark a DAI link as usable in only one direction, which is required for interfaces that are physically wired for output-only or input-only operation.
Is simple-card being replaced by audio-graph-card2?
Upstream ASoC maintainers are directing new binding features toward audio-graph-card2, but simple-card remains fully supported in current kernels and is still the simplest option for straightforward two-node boards.
Do I need any Kconfig option to use simple-card?
Yes — CONFIG_SND_SIMPLE_CARD must be enabled, in addition to your CPU DAI and codec
DAI drivers.
Where can I learn the rest of this free Linux kernel development course?
This lecture is part of EmbeddedPathashala’s free Linux kernel development course, free Linux device drivers course, and free embedded Linux course covering character devices, platform drivers, device tree, I2C/SPI, regmap, IIO, memory management, DMA, and the ALSA SoC framework end to end.
Continue the Free Linux Kernel Development Course
Explore more lectures on ALSA SoC, device tree, character devices, and kernel memory management — all free on EmbeddedPathashala.
Browse All Lectures Join the Free Course