snd_soc_component_driver
devm_snd_soc_register_component
Linux sound driver
What You Will Learn
In the previous lecture of this free Linux kernel development course we split ASoC into its
machine, platform, and codec building blocks. This lecture zooms into the codec side and answers the practical
question every driver author eventually asks: what exactly does an ASoC codec driver have to provide, and how
does the kernel talk to it? By the end of this lecture you will be able to:
snd_soc_component_driver anatomy
Register I/O via regmap
DAI + component registration
A working demo codec driver
Prerequisites
This lecture assumes you already understand the machine/platform/codec split and the DAI concept covered in
the previous ASoC lecture of this free linux device drivers course. You should also be
comfortable with basic I2C client drivers and the regmap API, since a real codec driver almost
always talks to hardware over I2C or SPI.
Why the Codec Driver Is Platform-Independent
A codec chip — say, an audio CODEC sitting on I2C with a DAC, an ADC, and a small analog mixer — behaves the
same electrically whether it is soldered next to an ARM SoC, a RISC-V SoC, or an x86 embedded board. The bits it
understands over its control bus, and the registers that turn its DAC on, don’t change with the host processor.
That is exactly why ASoC keeps the codec driver separate from the platform (DMA/CPU DAI) driver: the codec driver
should be reusable, unmodified, across every board that happens to carry that chip. All of the board-specific
wiring — which GPIO enables the amplifier, which clock feeds the codec’s MCLK pin — is deliberately kept out of
the codec driver and pushed into the machine driver instead.
Because of this, a well-written codec driver has exactly one job: describe the chip. It must not assume
anything about the DMA engine moving audio samples, and it must not assume anything about the board it is
mounted on. Everything else is somebody else’s problem.
The Four Things a Codec Driver Must Provide
Every ASoC codec driver has to satisfy four requirements before the ASoC core will treat it as a usable
component:
1. A DAI and PCM interface
The codec exposes one or more struct snd_soc_dai_driver instances describing its audio
interfaces (I2S, TDM, PDM, and so on) — sample rates, formats, and channel limits it supports.
2. Control I/O hooks
A way to read and write the chip’s internal registers, almost always through an I2C or SPI regmap
instance today, rather than hand-rolled bus transactions.
3. Userspace-visible kcontrols
Volume sliders, mute switches, input/output source selectors — anything amixer or
alsamixer should be able to manipulate — exposed as snd_kcontrol_new entries.
4. DAPM widgets and routes (optional but expected)
Dynamic Audio Power Management widgets describing the chip’s internal signal paths, so unused blocks of
the codec can be powered down automatically. We’ll go deep on DAPM in the next lecture.
Anatomy of struct snd_soc_component_driver
Since the ASoC core unified the old snd_soc_codec and snd_soc_platform abstractions
years ago, both codec and platform drivers register themselves through the very same structure:
struct snd_soc_component_driver. The single mandatory field across the whole structure is
name — it’s what the machine driver’s DAI links use to match this component when the sound card is
assembled. Everything else is optional and only filled in where it applies to your chip.
struct snd_soc_component_driver {
const char *name;
/* default controls/widgets/routes, applied right after probe() */
const struct snd_kcontrol_new *controls;
unsigned int num_controls;
const struct snd_soc_dapm_widget *dapm_widgets;
unsigned int num_dapm_widgets;
const struct snd_soc_dapm_route *dapm_routes;
unsigned int num_dapm_routes;
int (*probe)(struct snd_soc_component *component);
void (*remove)(struct snd_soc_component *component);
int (*suspend)(struct snd_soc_component *component);
int (*resume)(struct snd_soc_component *component);
/* legacy direct register access - most modern drivers use regmap instead */
unsigned int (*read)(struct snd_soc_component *component, unsigned int reg);
int (*write)(struct snd_soc_component *component, unsigned int reg,
unsigned int val);
int (*set_sysclk)(struct snd_soc_component *component, int clk_id,
int source, unsigned int freq, int dir);
int (*set_pll)(struct snd_soc_component *component, int pll_id,
int source, unsigned int freq_in, unsigned int freq_out);
int (*set_jack)(struct snd_soc_component *component,
struct snd_soc_jack *jack, void *data);
const struct snd_pcm_ops *ops;
/* idle_bias_on: keep the codec biased even at DAPM_BIAS_STANDBY */
unsigned int idle_bias_on:1;
unsigned int use_pmdown_time:1;
unsigned int non_legacy_dai_naming:1;
};
A few of these fields deserve special attention because their meaning is easy to misread:
| Field | What it actually means |
|---|---|
probe |
Runs when the machine driver’s card registration binds this component — not when the I2C/SPI driver’s own probe() runs. |
read/write |
A fallback path. Any driver built on regmap should register the regmap directly and skip these entirely. |
idle_bias_on |
Tells DAPM the codec must stay biased even in standby — needed for chips with slow bias ramp-up times. |
num_controls/num_dapm_widgets/num_dapm_routes |
Plain element counts for the arrays above — almost always filled with ARRAY_SIZE(). |
|
v
regmap_init_i2c() / regmap_init_spi()
|
v
devm_snd_soc_register_component(dev, &ep_codec_component,
&ep_codec_dai, 1)
|
v
ASoC core stores struct snd_soc_component
|
v
Machine driver’s snd_soc_dai_link matches by “name”
|
v
devm_snd_soc_register_card() binds machine + platform + codec
A Minimal Working Codec Driver
Below is an original, from-scratch demo codec driver — ep_codec — built around
regmap and a single volume control. It is intentionally minimal so the registration flow stays
visible; a real driver would add DAPM widgets and more controls following the same pattern.
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/tlv.h>
#define EP_CODEC_REG_VOLUME 0x02
#define EP_CODEC_REG_POWER 0x03
static const struct reg_default ep_codec_reg_defaults[] = {
{ EP_CODEC_REG_VOLUME, 0x18 },
{ EP_CODEC_REG_POWER, 0x00 },
};
static const struct regmap_config ep_codec_regmap_cfg = {
.reg_bits = 8,
.val_bits = 8,
.max_register = EP_CODEC_REG_POWER,
.reg_defaults = ep_codec_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(ep_codec_reg_defaults),
.cache_type = REGCACHE_RBTREE,
};
static const DECLARE_TLV_DB_SCALE(ep_codec_vol_tlv, -6350, 50, 0);
static const struct snd_kcontrol_new ep_codec_controls[] = {
SOC_SINGLE_TLV("Playback Volume", EP_CODEC_REG_VOLUME,
0, 0x7f, 0, ep_codec_vol_tlv),
};
static int ep_codec_probe(struct snd_soc_component *component)
{
dev_info(component->dev, "ep_codec: component probed\n");
return 0;
}
static const struct snd_soc_component_driver ep_codec_component = {
.name = "ep-codec",
.probe = ep_codec_probe,
.controls = ep_codec_controls,
.num_controls = ARRAY_SIZE(ep_codec_controls),
.idle_bias_on = 1,
.use_pmdown_time = 1,
};
static struct snd_soc_dai_driver ep_codec_dai = {
.name = "ep-codec-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
};
static int ep_codec_i2c_probe(struct i2c_client *client)
{
struct regmap *regmap;
regmap = devm_regmap_init_i2c(client, &ep_codec_regmap_cfg);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
return devm_snd_soc_register_component(&client->dev,
&ep_codec_component,
&ep_codec_dai, 1);
}
static const struct i2c_device_id ep_codec_i2c_id[] = {
{ "ep-codec", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, ep_codec_i2c_id);
static const struct of_device_id ep_codec_of_match[] = {
{ .compatible = "ep,ep-codec" },
{ }
};
MODULE_DEVICE_TABLE(of, ep_codec_of_match);
static struct i2c_driver ep_codec_i2c_driver = {
.driver = {
.name = "ep-codec",
.of_match_table = ep_codec_of_match,
},
.probe = ep_codec_i2c_probe,
.id_table = ep_codec_i2c_id,
};
module_i2c_driver(ep_codec_i2c_driver);
MODULE_DESCRIPTION("EmbeddedPathashala demo ASoC codec driver");
MODULE_LICENSE("GPL");
Once this module loads against a matching I2C device node, dmesg shows the component probe
firing as soon as a machine driver assembles a card around it:
[ 4.812345] ep-codec 0-001a: ep_codec: component probed
[ 4.812501] asoc-simple-card sound: ep-codec-hifi <-> ep-cpu-dai mapping ok
[ 4.813012] input: ep-codec Headphone Jack as /devices/virtual/...
Run amixer scontrols afterward and you’ll see Playback Volume listed as a
controllable mixer element, proving the snd_kcontrol_new array made it all the way to userspace.
Real-World Use Cases
This exact pattern — regmap-backed I2C codec, a handful of volume/mute controls, DAI stream limits — is how
the majority of production ASoC codec drivers in the kernel tree are structured, from simple line-out DACs to
full-featured smartphone codecs with dozens of DAPM widgets. The differences between a toy driver like the one
above and a shipping one are almost entirely in the number of controls and DAPM routes, not in the registration
pattern itself.
Common Mistakes and Troubleshooting
Putting board wiring inside the codec driver
If your codec driver reads a GPIO to detect a headphone jack that’s wired differently on every board, that
logic belongs in the machine driver, not here. Mixing the two defeats the entire point of the component
split.
Forgetting num_controls / num_dapm_widgets
A stale count after adding a control is a classic silent bug — the new control simply never appears in
amixer. Always drive these counts from ARRAY_SIZE().
Implementing .read/.write instead of using regmap
Hand-rolled register access loses regmap’s caching, debugfs register dump, and locking for free. Only use
the raw hooks for genuinely unusual buses regmap doesn’t cover.
Best Practices
- Always initialize regmap with a real
reg_defaultstable andREGCACHE_RBTREEso
suspend/resume doesn’t require re-reading every register from hardware. - Keep
namestable across kernel versions — machine drivers and device tree overlays key off
it. - Use
devm_*variants (devm_snd_soc_register_component,
devm_regmap_init_i2c) so cleanup happens automatically on probe failure or unbind. - Security-wise, never expose a raw “write any register” debug kcontrol in a production build — it lets
userspace poke arbitrary hardware state. - Performance-wise, prefer regmap’s cached reads over bus transactions inside hot paths like volume
updates, which can fire frequently from a userspace mixer app.
Summary and Key Takeaways
An ASoC codec driver’s entire contract with the kernel is the struct snd_soc_component_driver
plus one or more snd_soc_dai_driver instances, registered together through
devm_snd_soc_register_component(). It must stay platform-independent: DAI capabilities, register
access, userspace controls, and DAPM description are its job; board wiring is the machine driver’s job. Getting
this separation right is what lets the same codec driver work, unmodified, across every board that carries that
chip. In the next lecture we’ll build directly on the dapm_widgets and dapm_routes
fields shown here and walk through Dynamic Audio Power Management in depth.
FAQ
Do I always need a read/write callback in snd_soc_component_driver?
No. If your driver registers a regmap instance, the ASoC core routes register access through it
automatically; leave .read/.write unset.
What’s the difference between snd_soc_component and snd_soc_component_driver?
snd_soc_component_driver is the static template you write; snd_soc_component is
the live kernel object the ASoC core allocates from it at registration time.
Can one codec chip register more than one DAI?
Yes — chips with separate I2S and PDM interfaces, for example, register multiple
snd_soc_dai_driver entries in the same devm_snd_soc_register_component() call.
Where do DAPM widgets fit into this structure?
They’re pointed to by dapm_widgets/dapm_routes in the same structure; we cover
building them in the next lecture.
Is probe() here the same as the I2C driver’s probe()?
No — the I2C probe() runs when the chip is detected on the bus; the component’s
probe() runs later, when a machine driver assembles a sound card around it.
Why use devm_snd_soc_register_component instead of the non-devm version?
It ties the component’s lifetime to the device, so unregistration happens automatically on driver
unbind or probe failure, avoiding manual cleanup bugs.
Is this the same driver model used for platform (DMA) drivers?
Yes — platform drivers register through the exact same snd_soc_component_driver structure,
just filling in DMA-relevant fields like ops instead of codec-specific ones.
Next: Dynamic Audio Power Management (DAPM)
Continue this free Linux kernel development course with the DAPM widgets and routes this codec driver
only pointed to.
