本系列每篇文章的链接:
Linux音频三部曲(1):嵌入式 Linux 音频基础与硬件体系 - 知乎
Linux音频三部曲(2):Linux ASoC驱动深度开发 - 知乎
Linux音频三部曲(3):嵌入式 Linux 音频用户态开发:ALSA、PipeWire、WirePlumber 与 GStreamer 实战 - 知乎
本文基于Bootlin文档面向嵌入式Linux内核驱动开发、音频系统集成与硬件调试工程师。内容以相关内核代码、设备树配置、寄存器操作、DAPM电源管理、Regmap抽象为主,覆盖从ALSA到ASoC的演进、三大组件驱动实现、DT配置、DAPM、调试方法。
如有专业词汇翻译不当、内容理解有误的地方,还望指正。

一、从ALSA到ASoC:架构演进与设计目标
1.1 标准ALSA架构的局限
ALSA(Advanced Linux Sound Architecture) 于2002年随Linux 2.5内核合入主线,提供标准化音频API、播放/捕获流、kcontrol控件接口。标准ALSA设计面向PC架构,一张声卡对应一个设备、一个驱动,驱动内部耦合PCI总线、Codec、DMA、接口控制等全部逻辑。
这种架构在嵌入式场景存在明显缺陷:
- 代码无法复用:不同SoC与Codec组合需重新编写全量驱动;
- 组件耦合严重:Codec驱动与平台驱动绑定,无法独立维护;
- 不支持低功耗:缺乏细粒度音频路径电源管理;
- 无法适配多组件硬件:SoC+Codec+功放+MUX的模块化结构难以描述。
标准ALSA无法满足嵌入式音频可复用、低功耗、多组件、可配置的核心需求,因此ASoC框架被提出并合入内核。
1.2 ASoC架构定义与设计目标
ASoC全称ALSA System on Chip ,2006年合入主线,定位为标准ALSA之上的嵌入式增强层,对外保持原有用户空间API不变,对内实现硬件解耦与组件化。
ASoC核心设计目标:
- 组件化驱动分离:Codec、Platform(CPU DAI)、Machine三层独立;
- 跨平台复用:同一Codec驱动可适配任意SoC,同一DAI驱动可搭配任意Codec;
- 精细化低功耗:基于音频路径的动态电源管理DAPM;
- 设备树化配置:支持simple-audio-card等通用驱动,无需手写C语言机器驱动;
- 支持多链路、多通道、TDM、PDM等复杂嵌入式接口。
ASoC不修改用户空间ABI,原有aplay/arecord/alsamixer等工具无需改动即可使用。
1.3 ASoC三大核心组件
ASoC将音频系统严格拆分为三部分,遵循单一职责、解耦复用原则:
- Codec Driver:负责音频编解码器本身,包含寄存器配置、DAI格式、控件、DAPM、时钟;
- Platform Driver:包含CPU DAI控制器驱动(I2S/SAI/SSI等)与DMA驱动,无板级相关代码;
- Machine Driver:板级驱动,负责绑定Codec与CPU DAI、配置时钟主从、音频路由、GPIO、功放等。
Codec与Platform驱动高度复用,仅Machine驱动与具体电路板相关。这一结构是嵌入式音频驱动开发的基础。
二、ASoC核心数据结构与注册流程
2.1 核心结构体总览
ASoC驱动围绕一组标准化结构体展开,内核定义位于:
include/sound/soc.hinclude/sound/soc-dai.hinclude/sound/soc-component.hinclude/sound/soc-dapm.h
关键结构体:
struct snd_soc_card:代表一张声卡,是Machine驱动的核心载体;struct snd_soc_dai_link:描述一条CPU DAI与Codec DAI的连接链路;struct snd_soc_component:组件抽象,统一表示Codec、CPU DAI、功放、MUX;struct snd_soc_dai_driver:DAI接口驱动,包含格式、时钟、回调;struct snd_soc_dapm_widget:DAPM电源管理节点与路径;struct snd_kcontrol_new:ALSA控件(音量、静音、开关、枚举);struct regmap_config:I2C/SPI寄存器抽象配置。
2.2 声卡注册流程
ASoC声卡注册入口为Machine驱动,标准流程:
- 定义并填充
struct snd_soc_card; - 定义
struct snd_soc_dai_link,绑定CPU DAI与Codec DAI; - 实现必要回调(hw_params、set_sysclk等);
- 调用
devm_snd_soc_register_card注册声卡; - 内核自动匹配并加载Codec与Platform组件;
- 初始化DAPM图、控件、路由;
- 向ALSA核心注册声卡,导出至用户空间。
注册失败常见原因:DAI匹配失败、时钟不可用、寄存器访问错误、DAPM路由未定义。
三、Codec驱动开发完整实现
Codec是音频硬件核心,Codec驱动是ASoC中最复杂的组件,负责寄存器控制、DAI通信、模拟路由、时钟管理、控件、DAPM。

3.1 Codec驱动基本结构
现代内核(≥4.17)统一使用struct snd_soc_component_driver替代旧struct snd_soc_codec_driver,驱动通过devm_snd_soc_register_component注册。
典型注册代码:
static const struct snd_soc_component_driver xxx_codec_driver = {
.name = "xxx-codec",
.probe = xxx_codec_probe,
.remove = xxx_codec_remove,
.controls = xxx_controls,
.num_controls = ARRAY_SIZE(xxx_controls),
.dapm_widgets = xxx_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(xxx_dapm_widgets),
.dapm_routes = xxx_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(xxx_dapm_routes),
.set_sysclk = xxx_set_sysclk,
.set_pll = xxx_set_pll,
.set_bias_level = xxx_set_bias_level,
};
3.2 DAI驱动描述
每个Codec至少实现一个DAI,用于与CPU通信:
static struct snd_soc_dai_driver xxx_dai = {
.name = "xxx-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE,
},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &xxx_dai_ops,
};
3.3 DAI操作回调(dai_ops)
DAI ops是Codec与平台交互的核心接口,必须正确实现:
struct snd_soc_dai_ops {
int (*set_sysclk)(struct snd_soc_dai *dai, int clk_id,
unsigned int freq, int dir);
int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
unsigned int freq_in, unsigned int freq_out);
int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
int (*hw_params)(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai);
int (*trigger)(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai);
int (*mute_stream)(struct snd_soc_dai *dai, int mute, int stream);
};
关键回调说明:
set_fmt:配置接口格式(I2S/Left-J/DSP-A/TDM)与时钟主从;hw_params:根据采样率、位深、通道数配置Codec;set_sysclk:设置MCLK频率与来源;trigger:处理START/STOP等流事件,用于复位、同步;mute_stream:实现软硬件静音,抑制爆音。
3.4 Regmap寄存器抽象
Codec几乎全部通过I2C/SPI配置,内核使用Regmap框架统一抽象,简化读写、缓存、位操作。
典型Regmap配置:
const struct regmap_config xxx_regmap_config = {
.reg_bits = 16,
.val_bits = 8,
.max_register = 0x7F,
.writeable_reg = xxx_writeable_reg,
.readable_reg = xxx_readable_reg,
.volatile_reg = xxx_volatile_reg,
.cache_type = REGCACHE_RBTREE,
};
Regmap提供统一接口:
regmap_read/regmap_writeregmap_update_bits:读---改---写原子操作regcache_sync:缓存同步至硬件
Regmap大幅减少驱动重复代码,兼容I2C/SPI/MMIO。
3.5 Codec时钟配置
Codec依赖稳定时钟才能正常工作,时钟配置流程:
- Machine驱动通过
snd_soc_dai_set_sysclk传入MCLK; - Codec驱动在
set_sysclk中保存频率与来源; hw_params中根据采样率配置PLL与分频;- 生成正确BCLK与LRCLK。
时钟错误会导致无声、爆音等现象。
3.6 控件(kcontrol)实现

kcontrol是用户空间控制音频参数的统一接口,ASoC提供大量宏简化定义:
常用宏:
SOC_SINGLE:单比特开关/单通道控制;SOC_DOUBLE:左右声道双控;SOC_ENUM:枚举选择(去加重、通道选择);SOC_SINGLE_TLV:带dB映射的音量控制;SOC_DOUBLE_R_RANGE_TLV:左右分离、带范围音量。
示例:
static const DECLARE_TLV_DB_SCALE(dac_tlv, -10050, 50, 1);
SOC_SINGLE_TLV("Master Playback Volume", REG_DAC_VOL, 0, 255, 0, dac_tlv),
SOC_DOUBLE("ADC1 Mute Switch", REG_ADC_MUTE, 0, 1, 1, 0),
控件命名遵循ALSA约定,如Master Playback Volume、Capture Volume,便于alsamixer自动识别。
四、Platform(CPU DAI)驱动开发
Platform驱动包含CPU DAI控制器 与DMA两部分,负责数字接口时序与数据搬运。
4.1 CPU DAI驱动职责
- 实现I2S/SAI/SSI/McASP等控制器时序;
- 提供时钟生成与分频;
- 配置格式、位宽、通道、TDM时隙;
- 与DMA联动,实现FIFO读写;
- 提供
set_sysclk、set_fmt、hw_params回调。
典型CPU DAI:NXP SAI、Atmel SSC、TI McASP、Rockchip I2S、Allwinner DAI。
4.2 DMA处理
ASoC推荐使用标准DMAengine框架,驱动只需:
- 定义DMA通道地址与FIFO位置;
- 调用
snd_soc_dai_init_dma_data绑定DMA参数; - 调用
devm_snd_dmaengine_pcm_register注册PCM设备。
示例:
struct snd_dmaengine_dai_dma_data playback_dma = {
.addr = 0xXXXXXXX,
.maxburst = 1,
};
snd_soc_dai_init_dma_data(dai, &playback_dma, &capture_dma);
DMA配置错误会导致数据中断等现象。
4.3 TDM与时隙配置
多通道场景使用TDM,驱动需实现set_tdm_slot回调:
int (*set_tdm_slot)(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask,
int slots, int slot_width);
- slots:总时隙数(4/8/16);
- slot_width:每时隙位宽(16/24/32);
- tx_mask/rx_mask:激活时隙位图。
CPU DAI与Codec必须配置完全一致的TDM参数。
五、Machine驱动与设备树(DT)配置
Machine驱动连接Codec与CPU DAI。现代内核优先使用设备树+通用simple-audio-card,无需手写C语言驱动。
5.1 simple-audio-card标准绑定
simple-audio-card是内核通用音频声卡驱动,驱动路径:sound/soc/generic/simple-card.c。
必备属性:
compatible = "simple-audio-card";simple-audio-card,name:声卡名称;simple-audio-card,format:音频格式(i2s/left_j/dsp-a/tdm);simple-audio-card,mclk-fs:MCLK与采样率比率(256/384);cpu子节点:sound-dai = <&dai>;codec子节点:sound-dai = <&codec>;bitclock-master/frame-master:时钟主从配置。
5.2 时钟主从配置
时钟主从决定BCLK/LRCLK来源,通过dai_fmt配置:
SND_SOC_DAIFMT_CBP_CFP:Codec提供位时钟+帧时钟(推荐);SND_SOC_DAIFMT_CBC_CFC:CPU提供全部时钟。
设备树中直接使用:
bitclock-master = <&codec_dai>;
frame-master = <&codec_dai>;
主从配置错误是嵌入式音频最常见故障。
5.3 设备树完整示例(i.MX6UL + ADAU1372)
&i2c1 {
adau1372: codec@3c {
compatible = "adi,adau1372";
reg = <0x3c>;
#sound-dai-cells = <0>;
clocks = <&clk IMX6UL_CLK_SAI2>;
clock-names = "mclk";
};
};
&sai2 {
status = "okay";
pinctrl-0 = <&pinctrl_sai2>;
fsl,sai-mclk-direction-output;
assigned-clocks = <&clks IMX6UL_CLK_SAI2>;
assigned-clock-rates = <24576000>;
};
sound {
compatible = "simple-audio-card";
simple-audio-card,name = "imx6ul-adau1372";
simple-audio-card,format = "i2s";
simple-audio-card,mclk-fs = <512>;
bitclock-master = <&adau1372_dai>;
frame-master = <&adau1372_dai>;
simple-audio-card,dai-link@0 {
cpu {
sound-dai = <&sai2>;
};
codec {
sound-dai = <&adau1372>;
};
};
};
5.4 音频路由(Routing)配置
Routing用于描述板级模拟连接,如麦克风→ADC、DAC→耳机。
设备树配置:
simple-audio-card,widgets =
"Microphone", "Built-in Mic",
"Headphone", "Headphone Jack";
simple-audio-card,routing =
"MICIN", "Built-in Mic",
"Headphone Jack", "HPOUTL",
"Headphone Jack", "HPOUTR";
Routing直接决定DAPM路径是否连通。
5.5 手写C语言Machine驱动
简单平台可手写C语言Machine驱动,核心是struct snd_soc_card与struct snd_soc_dai_link:
static struct snd_soc_dai_link atmel_wm8904_dailink = {
.name = "WM8904",
.stream_name = "WM8904 PCM",
.dai_fmt = SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBP_CFP,
.ops = &atmel_asoc_wm8904_ops,
};
static struct snd_soc_card atmel_asoc_wm8904_card = {
.name = "atmel-asoc-wm8904",
.dai_link = &atmel_asoc_wm8904_dailink,
.num_links = 1,
};
static int atmel_asoc_wm8904_probe(struct platform_device *pdev)
{
card->dev = &pdev->dev;
return devm_snd_soc_register_card(&pdev->dev, card);
}
现代产品优先使用设备树方案,维护更简单。
六、ASoC DAPM动态电源管理
DAPM(Dynamic Audio Power Management) 是ASoC低功耗核心,自动根据音频路径上下电模块,对用户空间完全透明。

6.1 DAPM基本概念
- Widget:可独立电源控制的硬件单元(ADC/DAC/MICBIAS/PGA/MUX/OUT);
- Route:Widget之间的音频连接;
- Path:从输入到输出的完整音频路径;
- Power:路径连通则自动上电,断开则自动下电。

DAPM基于图遍历算法,无需驱动手动管理电源。
6.2 Widget类型
- 端点:
SND_SOC_DAPM_INPUT、OUTPUT、MIC、SPEAKER、HEADPHONE;

- 处理:
SND_SOC_DAPM_ADC、DAC、PGA、MIXER、MUX;

- 电源:
SND_SOC_DAPM_SUPPLY、MICBIAS;

- 虚拟:
SND_SOC_DAPM_STREAM。
定义示例:
static const struct snd_soc_dapm_widget xxx_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("AINL"),
SND_SOC_DAPM_INPUT("AINR"),
SND_SOC_DAPM_ADC("ADC", "Capture", REG_ADC_PD, 0, 1),
SND_SOC_DAPM_DAC("DAC", "Playback", REG_DAC_PD, 0, 1),
SND_SOC_DAPM_OUTPUT("HPOUTL"),
SND_SOC_DAPM_OUTPUT("HPOUTR"),
SND_SOC_DAPM_MICBIAS("MICBIAS", REG_MICBIAS, 0, 1),
};
6.3 Route定义
Route表示音频可流通路径:
static const struct snd_soc_dapm_route xxx_dapm_routes[] = {
{ "ADC", NULL, "AINL" },
{ "ADC", NULL, "AINR" },
{ "HPOUTL", NULL, "DAC" },
{ "HPOUTR", NULL, "DAC" },
{ "MICBIAS", NULL, "Mic Jack" },
};
Route错误会导致DAPM无法上电,出现无声但寄存器正常的问题。
6.4 DAPM工作流程
- 用户打开播放/捕获流;
- ALSA触发hw_params;
- DAPM遍历所有Widget;
- 标记连通路径上的Widget为上电;
- 按顺序上电(偏置→模拟→数字→输出);
- 停止时逆序下电,抑制爆音。
6.5 DAPM调试工具
- debugfs路径:
/sys/kernel/debug/asoc/<card>/<component>/dapm/; cat xxx_widget可查看开关状态;dapm-graph、vizdapm生成可视化拓扑图;

- 常见问题:Widget始终Off,说明Route未连通或控件未打开。
七、辅助设备驱动:功放、MUX
嵌入式音频常包含功放、模拟开关、MIC MUX等,ASoC提供通用驱动。
7.1 simple-amplifier
GPIO控制功放,无需I2C配置:
speaker_amp: audio-amplifier {
compatible = "simple-audio-amplifier";
enable-gpios = <&pio 7 7 GPIO_ACTIVE_HIGH>;
sound-name-prefix = "Speaker Amp";
};
在声卡中通过simple-audio-card,aux-devs挂载。
7.2 simple-mux
GPIO模拟多路选择器:
mic_mux: mic-mux {
compatible = "simple-audio-mux";
mux-gpios = <&gpio5 5 GPIO_ACTIVE_LOW>;
sound-name-prefix = "Mic Mux";
};
路由配置:
"Mic Mux OUT", "Mic Mux IN1",
"Mic Mux OUT", "Mic Mux IN2"
八、总结
ASoC是嵌入式Linux音频的标准框架,其核心价值在于组件化复用、设备树化配置、DAPM低功耗、标准化API。