Linux Sound Card Registration Guide
Free Linux Kernel Development Course — ASoC Machine Class Drivers, Part 5
In this lecture of our free embedded Linux course we complete the machine driver picture
by walking through Linux sound card registration in the ALSA System on Chip
(ASoC) framework. Every audio machine driver you write eventually boils down to filling one
structure — struct snd_soc_card — and handing it to the core with a single
registration call. Understanding exactly what that call does, and how it stitches together the
DAI links, controls, and DAPM widgets you built in earlier lectures, is what separates a machine
driver that merely compiles from one that actually produces sound. This tutorial is part of our
free Linux kernel development course and free embedded systems course series, and it assumes you
have already read the earlier lectures on DAI links, DAPM routing, and clock/format
configuration in this chapter.
Topics covered in this lecture
What You Will Learn
- What
struct snd_soc_cardrepresents and which fields actually matter in modern kernels - How Linux sound card registration binds CPU DAI, Codec DAI, and Platform together into working PCM devices
- The exact internal steps
devm_snd_soc_register_card()performs - How to assemble DAI links, controls, and DAPM widgets/routes from earlier lectures into one card
- How to parse the card name from Device Tree using
snd_soc_of_parse_card_name() - A complete, original demo machine driver you can build and load
- Common registration mistakes and how to debug a card that fails to register
Prerequisites
- Completion of the earlier lectures in this chapter on DAI links and DAPM routing
- A working cross-compilation setup for your target board’s kernel tree
- Basic familiarity with Device Tree syntax
- ALSA user-space tools (
alsa-utils) installed on the target for testing
Understanding ALSA SoC Sound Card Registration
Every earlier lecture in this chapter built one piece of an audio machine driver — a DAI link
that connects CPU and Codec DAIs, DAPM widgets and routes that describe the physical audio path,
and clock/format configuration for the DAI link. None of those pieces do anything on their own.
They are just data. Sound card registration is the step where the ASoC core takes
all of that data, wires it into a real ALSA sound card, probes every component involved, and
exposes /dev/snd/pcmC0D0p-style device nodes that user-space programs like
aplay and arecord can actually open.
The single structure that holds everything together is struct snd_soc_card. Think of
it as the top-level “table of contents” for one sound card: it points at the array of DAI links,
the array of machine-level ALSA controls, and the arrays of DAPM widgets and routes. When you call
the registration function, the ASoC core walks this structure, resolves every device it references,
and only then creates the sound card that shows up in aplay -l.
Sound Card Registration Flow
Key Fields of struct snd_soc_card
The full definition lives in include/sound/soc.h in current mainline kernels and has
grown many optional callbacks over the years, but for a typical machine driver only a handful of
fields matter day to day:
| Field | Purpose |
|---|---|
name | Human-readable card name shown in aplay -l and /proc/asound/cards |
owner | Module owner, normally THIS_MODULE, prevents unload while the card is in use |
dev | The struct device * of the platform device, must be set before registration |
dai_link / num_links | Array of DAI links and its length, built with SND_SOC_DAILINK_REG() |
controls / num_controls | Machine-level ALSA mixer controls, such as pin switches |
dapm_widgets / num_dapm_widgets | Board-level DAPM widgets (speakers, jacks, mics) |
dapm_routes / num_dapm_routes | Static audio path connections between widgets |
of_dapm_widgets / of_dapm_routes | Same as above but parsed automatically from Device Tree properties instead of C arrays |
fully_routed | Tells DAPM every route is explicitly declared, so unused widgets can be power-gated safely |
probe / late_probe | Optional card-level callbacks that run after component probing finishes |
How devm_snd_soc_register_card() Registers Your Sound Card
Older machine drivers called snd_soc_register_card() directly and had to remember to
call snd_soc_unregister_card() in a matching remove() callback. Modern
drivers should use the device-managed variant instead:
int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card);
Because it is devm_-managed, the kernel automatically unregisters the card when the
underlying platform device is removed or when probe() fails partway through, so a
separate remove() implementation is usually unnecessary. Internally, this single call
performs a well-defined sequence during Linux sound card registration:
- Validates that
card->dev,card->dai_link, andcard->num_linksare set - For every DAI link, resolves the CPU, Codec, and Platform components (by name or Device Tree phandle)
- Calls each component driver’s
probe()and each DAI driver’sprobe() - Registers the machine-level controls from
card->controls - Builds the DAPM widget graph from
dapm_widgets/of_dapm_widgetsand connects it usingdapm_routes/of_dapm_routes - Creates one ALSA PCM device per successfully probed DAI link
- Runs the optional
card->late_probe()callback once everything above succeeds
If any DAI link fails to resolve its components — for example a Device Tree phandle pointing
at a codec node that has not probed yet — registration returns -EPROBE_DEFER and
the kernel retries later automatically. This is normal during early boot and is not a real error.
Reading the Card Name from Device Tree
Rather than hard-coding card->name, most machine drivers let the board Device Tree
supply it, so the same driver binary can serve several boards. The helper for this is:
int snd_soc_of_parse_card_name(struct snd_soc_card *card, const char *propname);
It reads a string property (commonly named "model") from the machine driver’s own
Device Tree node and stores it in card->name. If the property is missing it simply
returns an error and you can fall back to a default name, exactly as shown in the demo driver
below.
Complete Example: ep_soundcard_demo Machine Driver
The following original demo driver, written for this course, ties together a DAI link, machine
controls, and DAPM widgets/routes into one struct snd_soc_card and performs
Linux sound card registration in its probe(). It targets current
mainline ASoC APIs rather than the older interfaces you may see in books written for kernels
before 5.x.
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <sound/soc.h>
/* DAI link component array, resolved from Device Tree in probe() */
SND_SOC_DAILINK_DEFS(ep_link,
DAILINK_COMP_ARRAY(COMP_CPU("ep-cpu-dai")),
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "ep-codec-dai")),
DAILINK_COMP_ARRAY(COMP_EMPTY()));
static struct snd_soc_dai_link ep_dai_link[] = {
{
.name = "EP Audio Link",
.stream_name = "EP Playback",
SND_SOC_DAILINK_REG(ep_link),
},
};
/* Machine-level pin switches exposed as ALSA controls */
static const struct snd_kcontrol_new ep_controls[] = {
SOC_DAPM_PIN_SWITCH("Speaker"),
SOC_DAPM_PIN_SWITCH("Headphone"),
};
/* Board-level DAPM widgets (built in the DAPM routing lecture) */
static const struct snd_soc_dapm_widget ep_widgets[] = {
SND_SOC_DAPM_SPK("Speaker", NULL),
SND_SOC_DAPM_HP("Headphone", NULL),
};
/* Static routes connecting board widgets to codec pins */
static const struct snd_soc_dapm_route ep_routes[] = {
{ "Speaker", NULL, "SPKOUT" },
{ "Headphone", NULL, "HPOUT" },
};
static struct snd_soc_card ep_card = {
.name = "ep-soundcard-demo",
.owner = THIS_MODULE,
.dai_link = ep_dai_link,
.num_links = ARRAY_SIZE(ep_dai_link),
.controls = ep_controls,
.num_controls = ARRAY_SIZE(ep_controls),
.dapm_widgets = ep_widgets,
.num_dapm_widgets = ARRAY_SIZE(ep_widgets),
.dapm_routes = ep_routes,
.num_dapm_routes = ARRAY_SIZE(ep_routes),
.fully_routed = true,
};
static int ep_soundcard_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &ep_card;
struct device_node *np = pdev->dev.of_node;
int ret;
card->dev = &pdev->dev;
ep_dai_link[0].cpus->of_node = of_parse_phandle(np, "ep,cpu-dai", 0);
ep_dai_link[0].codecs->of_node = of_parse_phandle(np, "ep,codec-dai", 0);
ep_dai_link[0].platforms->of_node = ep_dai_link[0].cpus->of_node;
if (!ep_dai_link[0].cpus->of_node || !ep_dai_link[0].codecs->of_node) {
dev_err(&pdev->dev, "missing cpu-dai or codec-dai phandle\n");
return -EINVAL;
}
ret = snd_soc_of_parse_card_name(card, "ep,model");
if (ret)
dev_dbg(&pdev->dev, "ep,model not set, using default card name\n");
return devm_snd_soc_register_card(&pdev->dev, card);
}
static const struct of_device_id ep_soundcard_of_match[] = {
{ .compatible = "ep,soundcard-demo" },
{ }
};
MODULE_DEVICE_TABLE(of, ep_soundcard_of_match);
static struct platform_driver ep_soundcard_driver = {
.driver = {
.name = "ep-soundcard-demo",
.of_match_table = ep_soundcard_of_match,
},
.probe = ep_soundcard_probe,
};
module_platform_driver(ep_soundcard_driver);
MODULE_DESCRIPTION("EmbeddedPathashala sound card registration demo");
MODULE_LICENSE("GPL");
Matching Device Tree Node
sound {
compatible = "ep,soundcard-demo";
ep,model = "EP-Demo-Card";
ep,cpu-dai = <&i2s0>;
ep,codec-dai = <&ep_codec>;
};
Build, Load, and Verify
# Build the module against your kernel tree
make -C /path/to/kernel M=$PWD modules
# Load it on the target board
insmod ep_soundcard_demo.ko
# Check kernel log for successful registration
dmesg | tail -n 10
[ 4.812345] ep-soundcard-demo sound: ep-soundcard-demo <-> ep-cpu-dai mapping ok
[ 4.813001] input: EP-Demo-Card Headphone as /devices/virtual/input/input3
[ 4.813560] ep-soundcard-demo sound: EP-Demo-Card
# Confirm the card is visible to ALSA
aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: EPDemoCard [EP-Demo-Card], device 0: EP Audio Link ep-cpu-dai-0 []
Subdevices: 1/1
Subdevice #0: subdevice #0
# Play a test tone through the registered card
speaker-test -D hw:0,0 -c 2 -t sine
If everything in the chain resolved correctly — DAI link components, controls, widgets, and
routes — the card appears in aplay -l immediately after insmod,
and speaker-test should produce audible output on the routed output.
Registration API Comparison: Old vs Modern
| Older Pattern | Modern Pattern | Why It Changed |
|---|---|---|
snd_soc_register_card() + manual remove() | devm_snd_soc_register_card() | Devres cleans up automatically, removing a common source of unregister-on-error bugs |
codec_dai_name, cpu_dai_name string fields | SND_SOC_DAILINK_DEFS() / SND_SOC_DAILINK_REG() component arrays | Supports multiple CPUs/Codecs/Platforms per link and Device Tree phandles cleanly |
Hand-written DAPM route wiring in probe() | of_dapm_widgets / of_dapm_routes parsed from DT | Keeps board-specific audio paths out of C code entirely |
Real-World Use Cases
- Single-board computers — one machine driver registers a sound card binding an on-SoC I2S controller to an external codec
- Smartphones and tablets — multiple DAI links in one
snd_soc_cardhandle voice call, media playback, and Bluetooth audio paths simultaneously - Industrial embedded audio — announcement or alarm systems that need a minimal, fully-routed card with a fixed set of pin switches
- Generic “simple-audio-card” boards — boards with no special machine logic can skip a custom driver entirely and let the generic simple-card driver perform registration from Device Tree alone
Common Mistakes in Sound Card Registration
| Mistake | Symptom | Fix |
|---|---|---|
Forgetting to set card->dev | Registration crashes or silently fails | Always assign card->dev = &pdev->dev; before calling the register function |
| Codec node not yet probed | devm_snd_soc_register_card() returns -EPROBE_DEFER | This is expected; the kernel automatically retries once the codec driver probes |
| Mismatched widget/route names | Card registers but DAPM route warnings appear in dmesg | Ensure widget names in dapm_widgets exactly match names used in dapm_routes |
Missing fully_routed on a fully static topology | Unused pins stay powered, wasting current | Set card->fully_routed = true once every route is explicitly declared |
Reusing an old remove() alongside devm registration | Double-unregister warning on module unload | Drop the manual remove() callback entirely with devm_snd_soc_register_card() |
Best Practices
- Prefer
devm_snd_soc_register_card()over the plain variant in every new driver - Always set
fully_routedonce your DAPM route table is complete, to enable correct power gating - Use
snd_soc_of_parse_card_name()so one driver binary serves multiple board variants - Treat
-EPROBE_DEFERas a normal condition, not an error, during driver bring-up - Keep the DAI link, control, and widget/route arrays declared
static constwhere possible for smaller memory footprint
Performance and Security Considerations
A correctly registered, fully-routed sound card allows DAPM to power down unused amplifiers,
ADCs, and DACs, directly reducing system power draw on battery-powered embedded devices. From a
security standpoint, machine drivers should validate every Device Tree phandle before use and
fail registration cleanly with a meaningful dev_err() rather than dereferencing a
NULL component pointer, since malformed or attacker-controlled Device Tree blobs on some
bootloader-flashing workflows can otherwise trigger kernel-level faults during probe.
Summary and Key Takeaways
struct snd_soc_cardis the top-level object that ties DAI links, controls, and DAPM widgets/routes into one ALSA sound carddevm_snd_soc_register_card()is the modern, device-managed way to perform Linux sound card registration, removing the need for a manual unregister path- Registration resolves every DAI link’s components, probes them, wires DAPM, and creates one PCM device per link
snd_soc_of_parse_card_name()lets a single driver serve many boards by reading the card name from Device Tree-EPROBE_DEFERduring registration is expected behavior, not a bug, when components probe asynchronously
Conclusion
With sound card registration covered, you now have the complete path from raw
DAI link definitions to a working /dev/snd device: define the link, describe the
audio path with DAPM, configure clocking and format, and finally register everything through
devm_snd_soc_register_card(). This closes out the ASoC Machine Class Drivers chapter
of our free Linux kernel development course. From here, you are ready to write a complete machine
driver for real hardware, or move on to the generic simple-card driver for boards that need no
custom machine logic at all.
Frequently Asked Questions
What is the difference between snd_soc_register_card() and devm_snd_soc_register_card()?
Both perform the same Linux sound card registration work internally, but the devm variant is device-managed, so the kernel automatically unregisters the card when the platform device is removed or probe fails, eliminating the need for a manual remove() callback.
Why does my sound card fail to register with -EPROBE_DEFER?
This means one of the DAI link’s components, usually the codec, has not finished probing yet. The kernel automatically retries registration once that component becomes available, so this is expected behavior during boot, not an error.
Do I need to set card->dev before calling devm_snd_soc_register_card()?
Yes. card->dev must point to the platform device’s struct device before registration, otherwise the devm cleanup path has nothing to attach to and registration can fail or crash.
What does the fully_routed flag actually control?
Setting fully_routed to true tells DAPM that every widget connection is explicitly declared in dapm_routes, allowing it to safely power down any widget with no active route instead of leaving it powered as a precaution.
Can one struct snd_soc_card have more than one DAI link?
Yes. Real devices commonly register several DAI links in num_links, for example separate links for media playback, voice call audio, and Bluetooth SCO audio, all under a single sound card.
What is snd_soc_of_parse_card_name() used for?
It reads a string property, commonly named “model”, from the machine driver’s Device Tree node and stores it in card->name, letting one driver binary provide different card names for different boards.
Is a custom machine driver always required for Linux sound card registration?
No. Boards where the CPU and codec need no special handling can use the kernel’s generic simple-card or simple-card-utils machine driver, which performs sound card registration entirely from Device Tree without any custom C code.
Where can I see the current definition of struct snd_soc_card?
It is defined in include/sound/soc.h in the mainline Linux kernel source tree; always check the version matching your target kernel, since optional fields are added over time.
Continue the Free Linux Kernel Development Course
Explore more free Linux device drivers course lectures and go deeper into embedded Linux audio, kernel subsystems, and driver development on EmbeddedPathashala.
Browse All Lectures Join the Free Course