上一篇文章讲了 PDM 麦克风——芯片无需控制,配置好 DTS 就能录音。这次我们调转方向——播放。
很多 DAC 芯片同样"甩手不管":无需控制接口,接上 I2S 数据线与电源,就输出声音。硬件工程师喜欢这类——零工作量。那么在 Linux 上,如果我们把这样的 DAC 配上一个 dummy-codec,要播放出声需要写多少行驱动代码?
答案和上次一样:零行。但这次我们调转了 DAI 的方向。
.playback,声卡暴露 /dev/snd/pcmC0D0p。dummy-codec 中的 .playback 能力声明正是本文覆盖的内容。DTS 仍是同样的三件套。aplay 直接播放。第 1 篇(PDM 麦克风)是 capture 方向——数据流入 SoC——声卡暴露 pcmC0D0c。这次我们翻到另一边——playback——数据流出 SoC:
回顾第 1 篇的 DAI 能力小结:
struct snd_soc_dai_driver {
.playback = { ... }, /* declared = this stream exists = /dev/snd/pcmC0D0p */
.capture = { ... }, /* declared = this stream exists = /dev/snd/pcmC0D0c */
};
以 TI 的 PCM5102A 为例——HiFi 圈的熟面孔,用于许多音频板。看看它的硬件"需求清单":
| 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 |
上电、喂 I2S 数据,就出声。比 PDM 麦克风还省心——你甚至不用想"我得给它时钟"(BCLK 随 I2S 自带)。
内核已有这颗芯片的专用驱动——sound/soc/codecs/pcm5102a.c,总共 57 行。核心只是一句能力声明:
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);
}
仅此而已:声明"我能播 2 声道",然后注册。但你甚至不需要它——dummy-codec 同时声明了 .playback 和 .capture,所以它绰绰有余地覆盖了 PCM5102A 的槽位。芯片本就无需控制,所以 Codec 驱动只是占位符——任何占位符都行。
dummy_dai 中的 .playback 部分第 1 篇看了 dummy-codec.c 的 .capture(第 60–70 行)。本文对称地看它的播放声明(源码第 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 */
};
与 .capture 逐字符对称,仅 stream_name 改为 "Dummy Playback"。有了这段,pcmC0D0p 被创建,aplay 可打开它。
与 PCM5102A 的声明相比,唯一的差别在"肚量":
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) |
真实芯片驱动的能力声明必须贴合 datasheet——过度声明会在打开时让应用崩溃。dummy 不接真实芯片,所以过度声明无妨;真正的约束由 CPU DAI 侧强制。
与第 1 篇 PDM 同样的配方,只是接口与方向不同:
/* ① 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 */
};
与第 1 篇相比有两处变化:
| Part 1 (PDM capture) | This article (DAC playback) | |
|---|---|---|
| CPU DAI | &pdm | &i2s0 |
| Data direction | capture (mic → SoC) | playback (SoC → DAC) |
三件套结构完全一致——这正是框架的价值。换方向、换接口,配方不变。
# 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
数据流与第 1 篇正好相反:
两篇文章,一篇 capture 一篇 playback,我们覆盖了第 1 篇的 .capture 与本文的 .playback——dummy-codec 的能力声明现已完整讲清。其余代码(startup / probe)尚未触及,你只写了 DTS。
但并非所有硬件都这么轻松:
这些正是驱动"用信号做点事"的地方。下一篇文章回答:在代码的哪里放 MCLK、SPK-CTL、RESET?dummy-codec.c 剩余的 startup 与 probe 终于登场。
dummy-codec 桥接到量产 Codec 驱动,用于智能面板与声霸。
本文是该系列的 第 2 篇。源码基于 Rockchip BSP 内核 6.1(v6.1.162)——带你从硬件一路深入到高级 DAPM 架构。
startup 打开 MCLK,probe 复位,Machine 处理板级 GPIO。参考源文件:sound/soc/codecs/pcm5102a.c(对比)与 dummy-codec.c。