4. ASoC框架分层
在嵌入式 Linux 音频驱动开发中,ALSA SoC(ASoC)框架通过将音频子系统抽象为 Machine、Platform、Codec 三个独立层次,实现了驱动代码的高度复用与模块化设计。理解这三层架构是编写高质量音频驱动的核心基础。
核心设计思想: 将 CPU 侧 DMA/DAI 控制、外部 Codec 芯片控制、以及板级音频拓扑解耦,使得同一款 Codec 芯片或同一款 CPU 的驱动可以在不同硬件平台上直接复用,无需重复开发。
4.1 三层架构总览
ASoC 的三层分别对应音频数据通路中的不同硬件角色:
- Machine 层: 板级音频拓扑描述,负责将 Platform 和 Codec 绑定在一起,并定义音频路由、时钟、GPIO 等板级约束。
- Platform 层: CPU 侧音频接口驱动,负责 DMA 传输、CPU 侧 DAI(Digital Audio Interface)配置、以及音频数据在内存与 FIFO 之间的搬运。
- Codec 层: 外部音频编解码器驱动,负责 Codec 芯片内部的寄存器控制、音频路径切换、采样率/位深设置、以及 DAPM(动态音频电源管理)控制。
4.2 Machine 层:板级音频拓扑
Machine 层是板级特定的驱动代码,通常位于 sound/soc/ 下的厂商目录中。它的核心职责是描述“这个板子上,CPU 的哪个 DAI 连接了哪个 Codec”。
| 核心数据结构 | 作用 |
|---|---|
snd_soc_card |
代表整个音频子系统的顶层结构,包含名称、DAPM 路由、控件列表等。 |
snd_soc_dai_link |
定义一条音频链路:指定 CPU DAI、Codec DAI、平台名称、以及时钟/格式配置。 |
snd_soc_dai_link_component |
用于在 DAI Link 中精确指定 CPU/Codec/Platform 组件。 |
典型的 Machine 驱动初始化流程:
- 定义
snd_soc_dai_link数组,填充 CPU DAI 名称、Codec 名称、平台名称。 - 定义
snd_soc_card,关联 DAI Link 数组、DAPM 路由表、控件映射。 - 在平台驱动 probe 中调用
devm_snd_soc_register_card()注册声卡。
/* Machine 层 DAI Link 示例 */
static struct snd_soc_dai_link imx_wm8960_dai_link = {
.name = "WM8960 HiFi",
.stream_name = "WM8960 HiFi",
.codec_dai_name = "wm8960-hifi",
.codec_name = "wm8960.0-001a",
.cpu_dai_name = "2030000.sai",
.platform_name = "2030000.sai",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
.ops = &imx_wm8960_ops,
};
开发提示: Machine 层是驱动开发中最常修改的部分。当更换 Codec 芯片或更改音频接口时,通常只需要修改 Machine 层的 DAI Link 配置和 DAPM 路由,Platform 和 Codec 驱动可以保持不变。
4.3 Platform 层:CPU 侧音频引擎
Platform 层负责 CPU 侧的音频数据传输与控制,主要包括两个部分:
- CPU DAI 驱动: 配置 I2S/PCM/TDM 接口的时钟、帧同步、数据格式,对应
snd_soc_dai_driver。 - DMA 引擎驱动: 管理音频数据在内存与 CPU DAI FIFO 之间的 DMA 传输,对应
snd_soc_platform_driver。
| 组件 | 数据结构 | 关键回调 |
|---|---|---|
| CPU DAI | snd_soc_dai_driver |
hw_params、set_fmt、set_sysclk、trigger |
| DMA 平台 | snd_soc_platform_driver |
pcm_new、open、hw_params、pointer |
Platform 层通常由 SoC 厂商提供通用驱动(如 fsl_sai.c、stm32_i2s.c),开发者一般不需要修改,只需在设备树中配置对应的 DAI 节点。
4.4 Codec 层:外部编解码器控制
Codec 层驱动封装了音频编解码芯片的所有硬件操作,包括寄存器读写、音频路径切换、音量控制、动态电源管理(DAPM)等。
Codec 驱动的核心结构:
snd_soc_codec_driver:定义 Codec 的读写函数、控件注册、DAPM 配置。snd_soc_dai_driver:定义 Codec 侧 DAI 的采样率、格式支持范围。snd_kcontrol_new:定义用户空间可访问的控件(音量、开关、EQ 等)。snd_soc_dapm_widget和snd_soc_dapm_route:定义内部音频路径和电源域。
/* Codec DAI 驱动示例(WM8960) */
static struct snd_soc_dai_driver wm8960_dai = {
.name = "wm8960-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = WM8960_RATES,
.formats = WM8960_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = WM8960_RATES,
.formats = WM8960_FORMATS,
},
.ops = &wm8960_dai_ops,
};
4.5 三层之间的交互流程
当用户播放音频时,三层之间的典型交互流程如下:
- 应用层 通过 ALSA 库调用
snd_pcm_writei()写入音频数据。 - Platform 层 的 DMA 引擎将数据从用户空间缓冲区搬运到 CPU DAI 的 FIFO。
- Platform 层 的 CPU DAI 按照 Machine 层配置的格式(I2S、采样率、位深)将数据串行发送到总线。
- Codec 层 的 Codec DAI 接收总线数据,经过内部数字滤波、DAC 转换、音量控制后输出到耳机或扬声器。
- 整个过程中,Machine 层 通过 DAPM 机制动态管理 Codec 内部各模块的电源开关,降低功耗。
4.6 三层架构的复用优势
| 场景 | 需要修改的层 | 可复用的层 |
|---|---|---|
| 同一 CPU 更换不同 Codec | Machine 层(DAI Link + DAPM 路由) | Platform 层(CPU DAI + DMA) |
| 同一 Codec 移植到不同 CPU | Machine 层 + Platform 层 | Codec 层(完全复用) |
| 同一平台更换音频接口(I2S → TDM) | Platform 层(CPU DAI 配置) | Codec 层 + Machine 层(部分) |
| 新增板级音频路由(如增加麦克风) | Machine 层(DAPM 路由 + 控件) | Platform 层 + Codec 层 |
常见误区: 初学者容易将 Codec 芯片的寄存器配置写在 Machine 层中,导致驱动无法跨平台复用。正确的做法是:所有 Codec 芯片相关的寄存器操作、音量控制、DAPM 路径必须封装在 Codec 层驱动中,Machine 层只负责“连接”和“路由”。
4.7 设备树中的三层映射
在实际开发中,三层架构的绑定关系通常通过设备树(Device Tree)来描述:
/* 设备树中的三层映射示例 */
sound {
compatible = "fsl,imx6q-sabresd-wm8960";
model = "imx-wm8960";
cpu-dai = <&sai1>; /* Platform 层:CPU DAI 节点 */
audio-codec = <&wm8960>; /* Codec 层:Codec 节点 */
/* Machine 层:通过 sound 节点将两者绑定 */
};
&sai1 { /* Platform 层:CPU DAI 配置 */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_sai1>;
status = "okay";
};
&wm8960 { /* Codec 层:Codec 配置 */
compatible = "wlf,wm8960";
reg = <0x1a>;
clocks = <&clks IMX6QDL_CLK_CKO>;
clock-names = "mclk";
status = "okay";
};
设备树中的 sound 节点对应 Machine 层,cpu-dai 属性指向 Platform 层的 CPU DAI 节点,audio-codec 属性指向 Codec 层的 Codec 节点。内核在解析设备树时,会自动匹配对应的驱动并建立三层之间的连接。
学习路径建议: 理解三层架构后,建议从修改一个现成的 Machine 驱动开始实践(如将 WM8960 替换为 SGTL5000),观察哪些文件需要修改、哪些可以复用。这是掌握 ASoC 框架最有效的方法。