type
Post
date
Mar 5, 2026
slug
linux_asoc_driver_subsystem
category
🥳嵌入式Linux开发
icon
password
ASoC 三驱动协作流程核心数据结构Machine Driver 示例Codec Driver 核心框架DAPM 动态音频电源管理设备树配置注册流程设备树 phandle 机制什么是 phandleDTS 语法编译后的 DTB 结果内核侧:of_parse_phandleASoC 三层解耦详解问题本质Layer 1 — Codec 驱动Layer 2 — Platform 驱动(CPU DAI + DMA)Layer 3 — Machine 驱动解耦矩阵phandle 在三层解耦中的作用phandle 对各层的影响设计理念
Linux内核中的ASoC(ALSA System on Chip)是ALSA的一个子系统,专为嵌入式SoC处理器和便携式音频编解码器设计。它采用分层架构,将嵌入式音频系统拆分为三类平台无关的驱动:Codec驱动、Platform驱动和Machine驱动,实现了音频硬件的高度抽象化与模块化。
ASoC子系统解决了传统ALSA驱动在嵌入式场景下的几个痛点:
- Codec驱动与SoC CPU紧耦合,难以跨平台复用
- 缺乏对音频路径动态电源管理(DAPM)的支持
- 未考虑嵌入式设备的省电与时钟管理需求
- Machine Driver(板级驱动):
- 连接Codec和Platform,描述板级音频拓扑
- 定义DAI Link,指定CPU DAI与Codec DAI的对应关系
- 处理板级特有的GPIO、时钟、中断和耳机检测等
- Platform Driver(平台驱动):
- 包含CPU DAI驱动和DMA驱动
- CPU DAI驱动配置I2S/AC97/TDM等数字音频接口
- DMA驱动负责音频数据在内存与外设之间的搬运
- Codec Driver(编解码器驱动):
- 平台无关的编解码器控制代码
- 配置ADC/DAC、混音器、音频路径
- 实现DAPM(动态音频电源管理)widgets和routes
ASoC 三驱动协作流程
核心数据结构
snd_soc_card:代表整个声卡,由Machine Driver定义
snd_soc_dai_link:描述CPU DAI与Codec DAI之间的连接
snd_soc_component_driver:通用组件驱动(Platform/Codec均使用)
snd_soc_dai_driver:描述DAI的能力(采样率、通道数、格式等)
Machine Driver 示例
Machine Driver是将Codec和Platform粘合在一起的"胶水"代码:
Codec Driver 核心框架
Codec Driver负责编解码器的寄存器配置、音频路径和电源管理:
DAPM 动态音频电源管理
DAPM(Dynamic Audio Power Management)是ASoC的核心特性,根据音频路径自动管理各组件的电源状态:
DAPM通过定义widgets和routes来描述音频拓扑:
当用户启动播放时,DAPM会自动:
- 遍历从源到目的的所有音频路径
- 仅上电路径上涉及的widget
- 播放停止后自动下电,最大限度节省功耗
设备树配置
在设备树中描述音频系统的硬件连接:
注册流程
三类驱动的注册顺序不固定,ASoC Core通过延迟绑定(deferred probe)机制确保所有组件就绪后才完成声卡注册。
设备树 phandle 机制
什么是 phandle
phandle(pointer handle)是设备树中为节点分配的唯一整数标识符,用于实现跨节点引用。它是一个节点「指向」另一个节点的方式。
DTS 语法
&fake_codec 是语法糖。DTC 编译时将其转换为原始 u32 phandle 值(如 0x05),并在目标节点中注入 phandle = <0x05>; 属性。编译后的 DTB 结果
内核侧:of_parse_phandle
在 Machine Driver 中,
of_parse_phandle 将 phandle 解析回 struct device_node *:第三个参数
0 是索引(用于包含多个 phandle 的属性)。ASoC 三层解耦详解
问题本质
嵌入式音频需要一个 Codec 芯片(如 WM8960)连接到 SoC 音频控制器(如 i.MX SAI)。如果不分层,每种 SoC + Codec 的组合都需要一个单体驱动。N 个 SoC × M 个 Codec = N×M 个驱动。
ASoC 通过三个独立层解决这个问题:
Layer 1 — Codec 驱动
职责范围:音频芯片本身。寄存器操作、DAI 能力声明、电源状态管理。
不感知:连接的是哪个 SoC、哪个 I2S 控制器驱动它、DMA 如何配置。
关键边界:导出
snd_soc_component_driver + snd_soc_dai_driver 对,通过 devm_snd_soc_register_component() 注册后等待绑定。Codec 驱动不主动发起任何绑定操作。Layer 2 — Platform 驱动(CPU DAI + DMA)
职责范围:SoC 音频接口硬件。SAI/I2S 控制器寄存器、DMA 通道管理、PCM 缓冲区操作。
不感知:I2S 总线另一端挂接的是哪款 Codec 芯片。
关键边界:同样导出
snd_soc_component_driver + snd_soc_dai_driver — 与 Layer 1 相同的结构体类型,但描述的是 CPU 侧能力。Layer 3 — Machine 驱动
职责范围:板级接线关系。「在这块板子上,SAI2 连接到 I2C 地址 0x1a 的 WM8960。」
不感知:Codec 或 SoC 音频控制器的内部寄存器映射。
关键边界:定义
snd_soc_dai_link 条目,命名 CPU DAI 和 Codec DAI,然后调用 devm_snd_soc_register_card()。ASoC 核心负责匹配并触发 probe。解耦矩阵
变更场景 | 需要修改 | 保持不变 |
相同 Codec,更换 SoC | Platform + Machine | Codec |
相同 SoC,更换 Codec | Codec + Machine | Platform |
相同板子,修改音频路由 | 仅 Machine | Codec + Platform |
这将驱动数量从 N×M 降低到 N+M 加上薄层 Machine 胶水代码。
phandle 在三层解耦中的作用
Machine Driver 是唯一需要知道绑定哪个具体 Codec 和 CPU DAI 的层。没有 phandle 时,这种绑定关系会硬编码在 C 代码中:
这将物理总线地址直接编译到驱动中。更换 I2C 地址或 SAI 实例就需要重新编译。
phandle 将绑定关系从 C 代码转移到设备树中:
phandle 对各层的影响
层级 | 无 phandle | 有 phandle |
Codec | 无变化 — 本身已自包含 | 无变化 |
Platform | 无变化 — 本身已自包含 | 无变化 |
Machine | C 代码硬编码节点名/路径,每种板级变体需重新编译 | 运行时读取 DT phandle,同一二进制通过更换 DTB 适配不同板级 |
核心洞察:phandle 并不创造三层解耦 — ASoC 的结构体分离实现了这一点。phandle 的作用是完成解耦,消除 Machine 层中最后的编译时依赖。结果是:所有三个.ko文件完全与板级无关,DTB 单独描述板级拓扑。
设计理念
ASoC子系统的设计体现了以下关键理念:
- 三层分离架构:Codec、Platform、Machine三层驱动各司其职,Codec驱动可跨平台复用,Platform驱动可适配不同Codec。
- DAPM动态电源管理:根据音频路径实时状态自动管理组件电源,嵌入式设备功耗显著降低。
- DAI抽象:将I2S、AC97、TDM等数字音频接口统一抽象为DAI,简化驱动开发。
- 设备树支持:通过
simple-audio-card等通用Machine Driver,许多板级音频配置仅需设备树即可完成,无需编写C代码。
- Author:felixfixit
- URL:http://www.felixmicrospace.top/article/linux_asoc_driver_subsystem
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!










