Connect a DAC to an amp on the hardware side and it just plays — no driver needed. So on the software side, what does it take to produce sound? Translated and annotated from Luo Zong (罗总), Rockchip — "罗总开发笔记" WeChat.
Last article covered the PDM microphone — the chip needs no control, configure DTS and it records. This time we flip the direction — playback.
Many DAC chips are just as "hands-off": no control interface required, connect the I2S data lines and power, and they output sound. Hardware engineers love these — zero effort. So on Linux, if we pair such a DAC with a dummy-codec, how many lines of driver code do we need to write to play audio?
The answer is the same as last time: zero. But this time we flip the DAI direction.
.playback, the sound card exposes /dev/snd/pcmC0D0p..playback capability declaration in dummy-codec is what this article covers. DTS is still the same three-piece combo. aplay plays directly.Part 1 (PDM mic) was the capture direction — data flows into the SoC — and the sound card exposed pcmC0D0c. This time we flip to the other side — playback — data flows out of the SoC:
Recall the DAI capability summary from Part 1:
struct snd_soc_dai_driver {
.playback = { ... }, /* declared = this stream exists = /dev/snd/pcmC0D0p */
.capture = { ... }, /* declared = this stream exists = /dev/snd/pcmC0D0c */
};
Take TI's PCM5102A — a familiar face in the HiFi world, used on many audio boards. Look at its hardware "requirements list":
| Requirement | Needed? |
|---|---|
| I2S data lines (BCLK / LRCK / DATA) | Yes — 3 wires |
| MCLK master clock | No — internal PLL derives the clock from BCLK |
| I2C / SPI control interface | No — the chip has no such pins |
| Register configuration | No — there are no registers |
Power it up, feed it I2S data, it makes sound. Even more hands-off than the PDM mic — you don't even have to think about "I must give it a clock" (BCLK comes with I2S anyway).
The kernel already has a dedicated driver for this chip — sound/soc/codecs/pcm5102a.c, 57 lines total. The core is a single capability declaration:
static struct snd_soc_dai_driver pcm5102a_dai = {
.name = "pcm5102a-hifi",
.playback = { /* NOTE: playback only! */
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_384000,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE
},
};
static int pcm5102a_probe(struct platform_device *pdev)
{
return devm_snd_soc_register_component(&pdev->dev, &soc_component_dev_pcm5102a,
&pcm5102a_dai, 1);
}
That's it: declare "I can play 2 channels", then register. But you don't even need it — dummy-codec declares both .playback and .capture, so it covers PCM5102A's slot more than adequately. The chip needs no control anyway, so the codec driver is just a placeholder — and any placeholder will do.
.playback portion in dummy_daiPart 1 looked at dummy-codec.c's .capture (lines 60–70). This article symmetrically looks at its playback declaration (source lines 49–59):
struct snd_soc_dai_driver dummy_dai = {
.name = "dummy_codec",
.playback = { /* ← Playback capability: this article's focus */
.stream_name = "Dummy Playback", /* stream name */
.channels_min = 1, /* min 1 channel */
.channels_max = 384, /* max 384 channels */
.rates = SNDRV_PCM_RATE_CONTINUOUS, /* any sample rate */
.formats = (SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S20_3LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE), /* bit depth S8 ~ S32 */
},
... /* .capture was covered in Part 1 */
};
Symmetric to .capture character-for-character, just the stream_name changes to "Dummy Playback". With this block present, pcmC0D0p is created and aplay can open it.
Compared with PCM5102A's declaration, the only difference is "guts":
pcm5102a_dai | dummy_dai | |
|---|---|---|
| playback | 2 channels, 8k–384k (per the datasheet) | 1–384 channels, any sample rate (no real chip — overshoot is free) |
| capture | none | yes (reserved for capture scenarios) |
A real chip driver's capability declaration must stick to the datasheet — over-declare and applications crash on open. dummy doesn't connect a real chip, so over-declaring is fine; the CPU DAI side enforces the real constraints.
Same recipe as Part 1's PDM, just a different interface and direction:
/* ① Codec DAI: dummy-codec placeholder (same driver as Part 1) */
dummy_codec: dummy-codec {
status = "okay";
compatible = "rockchip,dummy-codec";
#sound-dai-cells = <0>;
};
/* ② CPU DAI: enable the I2S controller + pin mux */
&i2s0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2s0m0_sclk &i2s0m0_lrck
&i2s0m0_sdi0 &i2s0m0_sdo0>; /* BCLK / LRCK / data lines */
};
/* ③ Machine: assemble the sound card */
dummy_sound: dummy-sound {
status = "okay";
compatible = "rockchip,multicodecs-card";
rockchip,card-name = "rockchip-dummy"; /* sound-card name */
rockchip,format = "i2s"; /* data format */
rockchip,cpu = <&i2s0>; /* CPU DAI = I2S controller */
rockchip,codec = <&dummy_codec>; /* Codec DAI = dummy */
};
Two things change vs. Part 1:
| Part 1 (PDM capture) | This article (DAC playback) | |
|---|---|---|
| CPU DAI | &pdm | &i2s0 |
| Data direction | capture (mic → SoC) | playback (SoC → DAC) |
The three-piece structure is identical — that's the value of the framework. Change direction, change interface, the recipe stays.
# 1. Sound card registered
cat /proc/asound/cards
# 0 [dummy ]: rockchip-dummy
# 2. Device node — this time it's the playback (p)
ls /dev/snd/
# controlC0 pcmC0D0p timer
# 3. Play
aplay -D hw:0,0 -c 2 -r 48000 -f S16_LE /tmp/sine.wav
# Android: tinyplay /tmp/sine.wav
# 4. Check the file parameters match
ls -l /tmp/sine.wav
# 48000Hz × 2ch × 2 bytes, matches aplay's arguments
The data flow is the exact reverse of Part 1:
Across both articles, one capture and one playback, we've covered Part 1's .capture and this article's .playback — dummy-codec's capability declarations are now fully explained. The rest of the code (startup / probe) has not been touched, and you've only written DTS.
But not all hardware is this easy:
These are exactly the places where a driver "does something" with signals. The next article answers: where in the code do MCLK, SPK-CTL, RESET live? dummy-codec.c's remaining startup and probe finally come on stage.
dummy-codec to your production codec driver on RK3308 (dedicated audio SoC) and on the M08D module (RK2108D + RK962) for smart panels and soundbars.
This article is Part 2 of the series. Source code is based on the Rockchip BSP kernel 6.1 (v6.1.162) — taking you from the hardware all the way up to advanced DAPM architecture.
startup opens MCLK, probe resets, Machine handles board GPIOs.Source files referenced: sound/soc/codecs/pcm5102a.c (comparison) and dummy-codec.c.