Linux Sound Card Registration Guide-Free Linux Device Drivers Course

Linux Sound Card Registration Guide

Free Linux Kernel Development Course — ASoC Machine Class Drivers, Part 5

15+ min read
Kernel 6.x reference
Free Linux device drivers course

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

struct snd_soc_card devm_snd_soc_register_card() snd_soc_of_parse_card_name() ALSA SoC sound card registration Linux audio machine driver

What You Will Learn

  • What struct snd_soc_card represents 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

Machine driver probe()fill struct snd_soc_carddevm_snd_soc_register_card()bind CPU DAIbind Codec DAIbind Platformprobe() each componentcreate PCM device/dev/snd/pcmC0D0p ready

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:

FieldPurpose
nameHuman-readable card name shown in aplay -l and /proc/asound/cards
ownerModule owner, normally THIS_MODULE, prevents unload while the card is in use
devThe struct device * of the platform device, must be set before registration
dai_link / num_linksArray of DAI links and its length, built with SND_SOC_DAILINK_REG()
controls / num_controlsMachine-level ALSA mixer controls, such as pin switches
dapm_widgets / num_dapm_widgetsBoard-level DAPM widgets (speakers, jacks, mics)
dapm_routes / num_dapm_routesStatic audio path connections between widgets
of_dapm_widgets / of_dapm_routesSame as above but parsed automatically from Device Tree properties instead of C arrays
fully_routedTells DAPM every route is explicitly declared, so unused widgets can be power-gated safely
probe / late_probeOptional 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:

  1. Validates that card->dev, card->dai_link, and card->num_links are set
  2. For every DAI link, resolves the CPU, Codec, and Platform components (by name or Device Tree phandle)
  3. Calls each component driver’s probe() and each DAI driver’s probe()
  4. Registers the machine-level controls from card->controls
  5. Builds the DAPM widget graph from dapm_widgets/of_dapm_widgets and connects it using dapm_routes/of_dapm_routes
  6. Creates one ALSA PCM device per successfully probed DAI link
  7. 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 PatternModern PatternWhy 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 fieldsSND_SOC_DAILINK_DEFS() / SND_SOC_DAILINK_REG() component arraysSupports 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 DTKeeps 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_card handle 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

MistakeSymptomFix
Forgetting to set card->devRegistration crashes or silently failsAlways assign card->dev = &pdev->dev; before calling the register function
Codec node not yet probeddevm_snd_soc_register_card() returns -EPROBE_DEFERThis is expected; the kernel automatically retries once the codec driver probes
Mismatched widget/route namesCard registers but DAPM route warnings appear in dmesgEnsure widget names in dapm_widgets exactly match names used in dapm_routes
Missing fully_routed on a fully static topologyUnused pins stay powered, wasting currentSet card->fully_routed = true once every route is explicitly declared
Reusing an old remove() alongside devm registrationDouble-unregister warning on module unloadDrop 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_routed once 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_DEFER as a normal condition, not an error, during driver bring-up
  • Keep the DAI link, control, and widget/route arrays declared static const where 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_card is the top-level object that ties DAI links, controls, and DAPM widgets/routes into one ALSA sound card
  • devm_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_DEFER during 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

Leave a Reply

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