ALSA SoC Machine Audio Routing-Free Linux Device Drivers Course

ALSA SoC Machine Audio Routing
Free Linux Kernel Development Course — ASoC Machine Class Drivers, Lecture 3
DAPM Routing
Device Tree Method
Static Routing Method

Welcome back to our free Linux kernel development course. In the previous lecture of this free embedded Linux course, you built a machine driver using the modern snd_soc_dai_link_component array model to bind a CPU DAI, a codec DAI, and a platform together. A machine driver that only creates a DAI link plays audio, but it does not yet know how the physical signal actually travels from a microphone jack, through the codec, and out to a speaker. That job belongs to audio routing, and it is the missing piece we cover in this lecture of our free linux device drivers course.

What You Will Learn

DAPM audio routing concept Codec pins vs board connectors Device tree audio-routing property snd_soc_of_parse_audio_routing() Static DAPM route tables Building a routed machine driver

Prerequisites

Before You Begin

This lecture assumes you have already completed the previous two lectures of this free embedded systems course module, and that you are comfortable with:

  • Writing a basic ASoC machine driver with snd_soc_dai_link
  • Reading and editing a device tree source (.dts) file
  • Building and loading kernel modules on a target board or QEMU

Why Audio Routing Matters

The ALSA SoC subsystem models an audio chip as a graph of small building blocks called widgets — inputs, outputs, mixers, amplifiers, and power supplies. This graph is called the Dynamic Audio Power Management (DAPM) map. The codec driver already knows about the widgets that live inside the chip itself, such as a line-in pin or a headphone amplifier. What the codec driver cannot know in advance is how your particular board wires those chip pins to the outside world — whether the headphone output goes to a 3.5 mm jack, a built-in speaker, or both. That board-specific wiring is exactly what the machine driver is responsible for describing, and it does so through DAPM routes.

DAPM Routing Overview
Codec Pin (LINE_IN) Board Connector (Line In Jack) Physical Connector
Codec driver defines pins  |  Machine driver defines connectors  |  Route table links them

Codec Pins: What the Chip Already Exposes

Every codec driver declares its internal pins as DAPM widgets using macros such as SND_SOC_DAPM_INPUT() and SND_SOC_DAPM_OUTPUT(). These are real, physical pins on the audio chip. You can discover the exact pin names any codec driver exposes by searching its source file with grep, since the names are just plain strings passed into the macro.

grep -E "SND_SOC_DAPM_(INPUT|OUTPUT|SUPPLY)" sound/soc/codecs/<your_codec>.c

A typical codec driver exposes something like this:

static const struct snd_soc_dapm_widget ep_codec_widgets[] = {
    SND_SOC_DAPM_INPUT("LINE_IN"),
    SND_SOC_DAPM_INPUT("MIC_IN"),
    SND_SOC_DAPM_OUTPUT("HP_OUT"),
    SND_SOC_DAPM_OUTPUT("LINE_OUT"),
    SND_SOC_DAPM_SUPPLY("Mic Bias", EP_CODEC_MIC_CTRL_REG, 3, 0,
                         NULL, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
};

Notice the SND_SOC_DAPM_SUPPLY widget. It is not an audio path pin at all — it models a power rail, such as a microphone bias voltage, that must be switched on before an input can actually capture sound. The machine driver can tie its routes into supply widgets too, effectively extending the codec’s power map.

Board Connectors: What the Machine Driver Adds

Board connectors are usually virtual widgets. They do not correspond to a register bit inside the codec; they are logical labels for physical jacks or speakers soldered onto your board. The machine driver declares them with helper macros such as SND_SOC_DAPM_MIC(), SND_SOC_DAPM_HP(), SND_SOC_DAPM_SPK(), and SND_SOC_DAPM_LINE().

static const struct snd_soc_dapm_widget ep_machine_widgets[] = {
    SND_SOC_DAPM_MIC("EP Mic Jack", NULL),
    SND_SOC_DAPM_LINE("EP Line In Jack", NULL),
    SND_SOC_DAPM_HP("EP Headphone Jack", NULL),
    SND_SOC_DAPM_SPK("EP Onboard Speaker", NULL),
};

At this point we have two independent lists of names — codec pins on one side, board connectors on the other — and neither list knows the other exists. A route is the piece of information that says “this connector is fed by that pin.” ALSA SoC gives you two ways to define routes: from the device tree, or statically inside the driver.

Method 1: Device Tree Audio Routing

The preferred modern approach keeps routing data out of C code entirely, in the board’s device tree, using the audio-routing property. Each route is written as a pair of strings: the sink first, then the source. The sink is whatever receives the signal; the source is whatever produces it.

sound {
    compatible = "ep,ep-audio-routing-demo";
    model = "EP-Audio-Routing-Demo";
    simple-audio-card,widgets =
        "Microphone", "EP Mic Jack",
        "Line",       "EP Line In Jack",
        "Headphone",  "EP Headphone Jack",
        "Speaker",    "EP Onboard Speaker";

    audio-routing =
        "MIC_IN",           "EP Mic Jack",
        "EP Mic Jack",      "Mic Bias",
        "EP Line In Jack",  "LINE_IN",
        "EP Headphone Jack","HP_OUT",
        "EP Onboard Speaker","LINE_OUT";
};

Reading the first pair carefully: "MIC_IN" is the sink and "EP Mic Jack" is the source, meaning the codec’s MIC_IN pin is fed from the board’s mic jack. To pull this property into your driver’s DAPM map at probe time, call snd_soc_of_parse_audio_routing():

int snd_soc_of_parse_audio_routing(struct snd_soc_card *card,
                                    const char *propname);
static int ep_routing_demo_probe(struct platform_device *pdev)
{
    struct snd_soc_card *card = &ep_routing_demo_card;
    int ret;

    card->dev = &pdev->dev;

    ret = snd_soc_of_parse_card_name(card, "model");
    if (ret)
        return ret;

    ret = snd_soc_of_parse_audio_routing(card, "audio-routing");
    if (ret)
        return dev_err_probe(&pdev->dev, ret,
                              "failed to parse audio-routing\n");

    return devm_snd_soc_register_card(&pdev->dev, card);
}

The device tree method is the one you should reach for on any real product, because it lets you retarget the same machine driver binary to a different board revision — with a different jack layout — by editing the .dts file instead of recompiling and reflashing the kernel.

Method 2: Static Routing in the Driver

For quick prototyping, or for a board whose wiring will genuinely never change, you can skip the device tree property entirely and hard-code an array of struct snd_soc_dapm_route directly in the machine driver. Each entry is a three-field struct: sink, control name (usually NULL), and source.

static const struct snd_soc_dapm_route ep_static_routes[] = {
    { "EP Mic Jack",       NULL, "MIC_IN"   },
    { "EP Mic Jack",       NULL, "Mic Bias" },
    { "LINE_IN",           NULL, "EP Line In Jack" },
    { "HP_OUT",            NULL, "EP Headphone Jack" },
    { "EP Onboard Speaker",NULL, "LINE_OUT" },
};

static struct snd_soc_card ep_routing_demo_card = {
    .name           = "EP-Audio-Routing-Demo",
    .owner          = THIS_MODULE,
    .dapm_widgets   = ep_machine_widgets,
    .num_dapm_widgets = ARRAY_SIZE(ep_machine_widgets),
    .dapm_routes    = ep_static_routes,
    .num_dapm_routes = ARRAY_SIZE(ep_static_routes),
};

Assigning .dapm_routes and .num_dapm_routes directly on the snd_soc_card structure is all that is needed — there is no separate parsing call, because nothing needs to be read from the device tree.

Device Tree Routing vs Static Routing

AspectDevice Tree RoutingStatic Routing
Where routes liveaudio-routing property in .dtsC array inside the machine driver
Changing a routeEdit device tree, no rebuild of driverRequires recompiling the kernel module
Best forProduction boards, multiple board revisionsQuick prototyping, fixed reference designs
Parsing call neededYes — snd_soc_of_parse_audio_routing()No

Hands-On: Building and Testing ep_audio_routing_demo

Let’s put both pieces together and build a small, original demo machine driver, then confirm the routes actually get registered.

// ep_routing_demo.c — minimal machine driver demonstrating DAPM routing
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/soc.h>

SND_SOC_DAILINK_DEFS(ep_link,
    DAILINK_COMP_ARRAY(COMP_CPU("ep-cpu-dai")),
    DAILINK_COMP_ARRAY(COMP_CODEC("ep-codec.0", "ep-codec-dai")),
    DAILINK_COMP_ARRAY(COMP_PLATFORM("ep-cpu-dai")));

static struct snd_soc_dai_link ep_dai_link[] = {
    {
        .name = "EP Routing Demo Link",
        SND_SOC_DAILINK_REG(ep_link),
    },
};

static const struct snd_soc_dapm_widget ep_machine_widgets[] = {
    SND_SOC_DAPM_MIC("EP Mic Jack", NULL),
    SND_SOC_DAPM_LINE("EP Line In Jack", NULL),
    SND_SOC_DAPM_HP("EP Headphone Jack", NULL),
    SND_SOC_DAPM_SPK("EP Onboard Speaker", NULL),
};

static struct snd_soc_card ep_routing_demo_card = {
    .name             = "EP-Audio-Routing-Demo",
    .owner            = THIS_MODULE,
    .dai_link         = ep_dai_link,
    .num_links        = ARRAY_SIZE(ep_dai_link),
    .dapm_widgets     = ep_machine_widgets,
    .num_dapm_widgets = ARRAY_SIZE(ep_machine_widgets),
};

static int ep_routing_demo_probe(struct platform_device *pdev)
{
    struct snd_soc_card *card = &ep_routing_demo_card;
    int ret;

    card->dev = &pdev->dev;

    ret = snd_soc_of_parse_card_name(card, "model");
    if (ret)
        return ret;

    ret = snd_soc_of_parse_audio_routing(card, "audio-routing");
    if (ret)
        return dev_err_probe(&pdev->dev, ret,
                              "failed to parse audio-routing\n");

    return devm_snd_soc_register_card(&pdev->dev, card);
}

static const struct of_device_id ep_routing_demo_of_match[] = {
    { .compatible = "ep,ep-audio-routing-demo" },
    { }
};
MODULE_DEVICE_TABLE(of, ep_routing_demo_of_match);

static struct platform_driver ep_routing_demo_driver = {
    .driver = {
        .name = "ep-audio-routing-demo",
        .of_match_table = ep_routing_demo_of_match,
    },
    .probe = ep_routing_demo_probe,
};
module_platform_driver(ep_routing_demo_driver);

MODULE_DESCRIPTION("EmbeddedPathashala ASoC machine routing demo");
MODULE_LICENSE("GPL");

Build it as an out-of-tree module against your configured kernel source tree:

make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
sudo insmod ep_routing_demo.ko

With a matching audio-routing property added to your board’s device tree overlay and reloaded, check that the sound card registered and that the routes are active:

$ dmesg | tail
[   12.441022] ep-audio-routing-demo sound: EP-Audio-Routing-Demo  ep-codec-dai mapping ok
[   12.441980] input: EP-Audio-Routing-Demo Headphone Jack as /devices/virtual/input/input3

$ aplay -l
card 0: EPRoutingDemo [EP-Audio-Routing-Demo], device 0: EP Routing Demo Link ep-codec-dai-0 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0

$ cat /proc/asound/EPRoutingDemo/pcm0p/sub0/hw_params
access: RW_INTERLEAVED
format: S16_LE
channels: 2
rate: 48000

You can also inspect the live DAPM graph the kernel built from your routes through debugfs, which is the fastest way to confirm a route actually took effect without playing real audio:

$ cat /sys/kernel/debug/asoc/EP-Audio-Routing-Demo/dapm/EP\ Headphone\ Jack
EP Headphone Jack: Off  in "EP Routing Demo Link" "EP-Audio-Routing-Demo"

  HP_OUT: Off  1

Extending the Power Map with Supply Widgets

A route does not have to connect two audio-signal widgets. It can also connect a signal widget to a SND_SOC_DAPM_SUPPLY widget defined in the codec driver, such as a mic bias rail. This lets the machine driver tell DAPM: “before this jack can capture sound, first power up this specific supply.” DAPM then handles powering the supply up and down automatically as playback or capture starts and stops, which is the entire point of dynamic audio power management — nothing stays powered when it is not in use.

Power-Aware Routing
EP Mic Jack ← needs MIC_IN pin ← needs Mic Bias supply

Common Mistakes and Troubleshooting

Route silently does nothing

The sink and source names must match the widget strings exactly, including capitalization and spacing. A route referencing a pin name that does not exist in the codec’s widget list is simply ignored — ALSA SoC will not fail the probe for this, so check dmesg for “no dapm match” style warnings.

Mixing both routing methods carelessly

Assigning both .dapm_routes statically and calling snd_soc_of_parse_audio_routing() at the same time is legal, but the two route sets are simply merged. If you don’t intend that, pick one method for a given card and stay consistent.

Forgetting the widget before the route

A route can only reference a widget name that has already been registered, either by the codec driver or by your own .dapm_widgets array. Declaring a route to a connector you forgot to add to ep_machine_widgets[] is one of the most common beginner mistakes.

Best Practices

  • Prefer device tree routing for anything beyond a bring-up prototype — it keeps board variants out of C code.
  • Keep connector names descriptive and board-specific ("EP Headphone Jack", not just "HP") to avoid collisions when a card is later merged with another.
  • Always check /sys/kernel/debug/asoc/<card>/dapm/ after adding new routes instead of guessing from playback behaviour alone.
  • Route supply widgets explicitly rather than assuming a mic bias is always on — this saves real power on battery-powered embedded designs.

Summary and Key Takeaways

Audio routing is the layer that turns a set of independently-defined codec pins and board connectors into one working signal path. Codec drivers describe what pins physically exist on the chip; machine drivers describe the connectors your board actually exposes and the routes that tie the two together, either through the device tree’s audio-routing property with snd_soc_of_parse_audio_routing(), or through a static snd_soc_dapm_route array assigned directly on the sound card. Getting this right is what makes DAPM automatically power up only the parts of the audio chip that are actually in use. In the next lecture of this free linux kernel development course, we will look at clocking and formatting considerations in the machine driver’s snd_soc_ops callbacks.

FAQ

What is the difference between a codec pin and a board connector in ALSA SoC?

A codec pin is a real, physical pin on the audio chip, declared by the codec driver with SND_SOC_DAPM_INPUT() or SND_SOC_DAPM_OUTPUT(). A board connector is usually a virtual widget declared by the machine driver, representing a physical jack or speaker on your board.

Which order do sink and source go in a DAPM route?

The sink (the widget receiving the signal) always comes first, followed by the control name (usually NULL), followed by the source (the widget producing the signal).

Do I need snd_soc_of_parse_audio_routing() if I use static routing?

No. Static routing only requires assigning .dapm_routes and .num_dapm_routes on the snd_soc_card structure directly — there is nothing to parse from the device tree.

Why does my route seem to have no effect on playback?

The most common cause is a typo or mismatch between the widget name used in the route and the widget name declared elsewhere. Check the live DAPM graph under /sys/kernel/debug/asoc/<card>/dapm/ to confirm the route registered.

Can a route connect to a power supply widget instead of an audio signal?

Yes. Routes can connect to SND_SOC_DAPM_SUPPLY widgets such as a mic bias rail, letting DAPM automatically power the supply up and down as needed.

Is device tree routing mandatory for embedded Linux audio drivers?

It is not mandatory, but it is strongly recommended for any board that may see hardware revisions, since it avoids recompiling the kernel every time a connector changes.

Continue Learning Linux Kernel Development — Free

This lecture is part of EmbeddedPathashala’s free Linux kernel development course covering ASoC machine class drivers, device driver programming, and embedded Linux fundamentals.

Browse the Full Course Join the Community

Leave a Reply

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