Lazy loaded image
🥳嵌入式Linux开发
Linux 驱动专题 - ASoC 驱动子系统
Words 2860Read Time 8 min
2026-3-5
2026-3-10
type
Post
date
Mar 5, 2026
slug
linux_asoc_driver_subsystem
category
🥳嵌入式Linux开发
icon
password
Linux内核中的ASoC(ALSA System on Chip)是ALSA的一个子系统,专为嵌入式SoC处理器和便携式音频编解码器设计。它采用分层架构,将嵌入式音频系统拆分为三类平台无关的驱动:Codec驱动、Platform驱动和Machine驱动,实现了音频硬件的高度抽象化与模块化。
ASoC子系统解决了传统ALSA驱动在嵌入式场景下的几个痛点:
  • Codec驱动与SoC CPU紧耦合,难以跨平台复用
  • 缺乏对音频路径动态电源管理(DAPM)的支持
  • 未考虑嵌入式设备的省电与时钟管理需求
  1. Machine Driver(板级驱动)
      • 连接Codec和Platform,描述板级音频拓扑
      • 定义DAI Link,指定CPU DAI与Codec DAI的对应关系
      • 处理板级特有的GPIO、时钟、中断和耳机检测等
  1. Platform Driver(平台驱动)
      • 包含CPU DAI驱动和DMA驱动
      • CPU DAI驱动配置I2S/AC97/TDM等数字音频接口
      • DMA驱动负责音频数据在内存与外设之间的搬运
  1. 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会自动:
  1. 遍历从源到目的的所有音频路径
  1. 仅上电路径上涉及的widget
  1. 播放停止后自动下电,最大限度节省功耗

设备树配置

在设备树中描述音频系统的硬件连接:

注册流程

三类驱动的注册顺序不固定,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子系统的设计体现了以下关键理念:
  1. 三层分离架构:Codec、Platform、Machine三层驱动各司其职,Codec驱动可跨平台复用,Platform驱动可适配不同Codec。
  1. DAPM动态电源管理:根据音频路径实时状态自动管理组件电源,嵌入式设备功耗显著降低。
  1. DAI抽象:将I2S、AC97、TDM等数字音频接口统一抽象为DAI,简化驱动开发。
  1. 设备树支持:通过simple-audio-card等通用Machine Driver,许多板级音频配置仅需设备树即可完成,无需编写C代码。
上一篇
Linux驱动专题 - DMA 深度教程
下一篇
Linux 驱动专题 - IIO 子系统(Industrial I/O)

Comments
Loading...