Linux 音频子系统完整梳理:ALSA、ASoC、DAPM、Codec、Machine、es8389 与 rk‑multicodecs 全解析
Linux 音频子系统完整梳理:ALSA、ASoC、DAPM、Codec、Machine、es8389 与 rk‑multicodecs 全解析
一、整体大图:ALSA → ASoC → DAPM → Codec/Machine
先给你一个总的结构图:
ALSA(用户空间接口)
├── PCM(aplay/arecord)
├── Mixer(amixer)
└── Controls(音量、开关、路由)
ASoC(SoC 音频框架)
├── CPU DAI(I2S/PCM/TDM 控制器)
├── Codec DAI(ES8389 等音频编解码器)
└── Machine Driver(板级 glue:rk-multicodecs.c)
└── DAPM(Dynamic Audio Power Management)
├── Widgets(节点:DAC/ADC/HP/SPK/Power)
└── Routes(边:谁连谁)
- ALSA:对用户空间暴露统一接口(aplay、amixer 用的就是它)。
- ASoC:专门为 SoC/嵌入式设计的 ALSA 扩展,负责把 CPU DAI、Codec、板级硬件 glue 在一起。
- DAPM:ASoC 里的“动态音频电源管理”,负责音频路径、电源、功放的自动开关。
- Codec Driver(es8389.c):描述 Codec 芯片内部的音频结构和寄存器。
- Machine Driver(rk‑multicodecs.c):描述板级硬件(耳机、喇叭、功放 GPIO、耳机检测等)。
二、ALSA 与 ASoC:谁管什么?
1. ALSA(Advanced Linux Sound Architecture)
- 提供:
- PCM:播放/录音(aplay/arecord)
- Mixer/Controls:音量、开关、路由(amixer)
- 不关心:
- 板子上是 ES8389 还是 ES8316
- 喇叭是哪个 GPIO 控制
- 耳机插孔怎么接
2. ASoC(ALSA System‑on‑Chip)
ASoC 把一个 SoC 音频系统拆成三块:
- CPU DAI:SoC 内部的 I2S/PCM 控制器(比如 Rockchip I2S)
- Codec DAI:外部音频 Codec(比如 ES8389)
- Machine Driver:把 CPU + Codec + 板级硬件 glue 在一起(rk‑multicodecs.c)
你现在看到的两个文件:
es8389.c→ Codec Driverrk-multicodecs.c→ Machine Driver
就是这两层的典型实现。
三、Codec Driver vs Machine Driver:谁负责什么?
1. Codec Driver(以 es8389.c 为例)
负责:
- Codec 内部的 DAPM Widgets:
DACL/DACRADCL/ADCROUTL MUX/OUTR MUXHPOL/HPORADC Mixer/IF DACL Mixer等
- Codec 内部的 Routes:
DACL → IF DACL1 → IF DACL Mixer → OUTL MUX → HPOL
- Codec 的 寄存器配置:
- 采样率、位宽、时钟分频(
coeff_div) - 上电/下电序列(
es8389_set_bias_level()) - 音量寄存器(
DACL Playback Volume等)
- 采样率、位宽、时钟分频(
- DAI 格式:
- I2S / Left Justified / DSP_A / DSP_B
- MCLK / SYSCLK 设置
一句话:
Codec Driver 描述的是“芯片内部世界”。
2. Machine Driver(以 rk‑multicodecs.c 为例)
负责:
- 板级 DAPM Widgets:
SND_SOC_DAPM_HP("Headphone", NULL), SND_SOC_DAPM_SPK("Speaker", NULL), SND_SOC_DAPM_MIC("Main Mic", NULL), SND_SOC_DAPM_MIC("Headset Mic", NULL), SND_SOC_DAPM_SUPPLY("Speaker Power", ..., mc_spk_event, ...), SND_SOC_DAPM_SUPPLY("Headphone Power", ..., mc_hp_event, ...), - 板级 功放电源控制:
mc_spk_event()/mc_hp_event()里通过 GPIO 控制功放上电/断电
- 板级 耳机检测 / ADC 按键:
hp_det_gpio+adc+input_dev
- 声卡整体 glue:
snd_soc_cardsnd_soc_dai_link- CPU DAI ↔ Codec DAI 连接
- DAPM 路由中与板级相关的部分(通常通过
audio-routingDT 属性描述)
一句话:
Machine Driver 描述的是“板子外部世界”:耳机孔、喇叭、功放、电源、检测。
四、DAPM:动态音频电源管理的核心概念
DAPM 是 ASoC 的核心,它管理:
- 哪些模块需要上电
- 哪些路径需要打开
- 功放什么时候开/关
- 避免 pop noise(“啪”的一声)
- 自动省电(不用时自动断电)
1. DAPM Widget(节点)
你看到的这些宏:
SND_SOC_DAPM_DAC("DACL", ...)
SND_SOC_DAPM_ADC("ADCL", ...)
SND_SOC_DAPM_MIXER("ADC Mixer", ...)
SND_SOC_DAPM_MUX("OUTL MUX", ...)
SND_SOC_DAPM_HP("Headphone", ...)
SND_SOC_DAPM_SPK("Speaker", ...)
SND_SOC_DAPM_SUPPLY("Headphone Power", ...)
本质上都是:
音频拓扑图里的“节点”
有的传音频(DAC/ADC/Mixer/MUX),有的是电源(Supply),有的是外设(HP/SPK/MIC)。
2. DAPM Route(边)
Codec 里:
{"OUTL MUX", "normal", "IF DACL Mixer"},
{"HPOL", NULL, "OUTL MUX"},
Machine 里(通常通过 DT audio-routing):
Headphone → HPOL
Speaker → SPK_OUT
ASoC 会把 Codec + Machine 的所有 Widgets 和 Routes 合并成一个完整的 DAPM 图。
五、DAPM Supply:为什么要用它,而不是自己写 GPIO?
你问过一个非常关键的问题:
“我直接在 IRQ 或 poll 里控制 GPIO 不就行了?
为什么还要搞一个SND_SOC_DAPM_SUPPLY这么复杂?”
1. Supply 的定义(以 Headphone Power 为例)
SND_SOC_DAPM_SUPPLY("Headphone Power",
SND_SOC_NOPM, 0, 0,
mc_hp_event,
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
含义:
- 定义一个名为
"Headphone Power"的电源节点 - 不绑定具体寄存器(
SND_SOC_NOPM),由回调控制 - 当 DAPM 决定“耳机路径需要上电”时:
- 触发
POST_PMU→ 调用mc_hp_event()→ 打开功放 GPIO
- 触发
- 当 DAPM 决定“耳机路径不再使用”时:
- 触发
PRE_PMD→ 调用mc_hp_event()→ 关闭功放 GPIO
- 触发
2. 为什么不能只用 IRQ/poll?
因为音频电源时序必须严格:
- 播放开始:
- Codec bias 上电
- DAC 上电
- Mixer/MUX 打开
- 最后 打开功放(避免 pop)
- 播放结束:
- 先 关功放
- 再关 Mixer/DAC
- 最后关 bias
你自己写 GPIO 根本拿不到这些状态,只能“猜时机”;
而 DAPM 是整个音频系统的状态机,它知道:
- 播放/录音是否开始
- 哪条路径被使用
- 哪些 Widget 需要上电/断电
Supply 的意义就是:
把“功放电源”这个动作挂到 DAPM 状态机里,让它在正确的时机自动执行。
六、DAPM 事件:POST_PMU / PRE_PMD 是什么?
你看到的:
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD
它们不是函数,而是 事件标志(bit flags),定义在 include/sound/soc-dapm.h:
#define SND_SOC_DAPM_PRE_PMU 0x1
#define SND_SOC_DAPM_POST_PMU 0x2
#define SND_SOC_DAPM_PRE_PMD 0x4
#define SND_SOC_DAPM_POST_PMD 0x8
含义:
- PRE_PMU:上电前
- POST_PMU:上电后
- PRE_PMD:断电前
- POST_PMD:断电后
DAPM 状态机在执行电源切换时,会根据这些标志调用你的回调:
mc_hp_event(widget, SND_SOC_DAPM_POST_PMU);
mc_hp_event(widget, SND_SOC_DAPM_PRE_PMD);
你在回调里做的通常是:
case SND_SOC_DAPM_POST_PMU:
gpiod_set_value_cansleep(hp_ctl_gpio, 1);
break;
case SND_SOC_DAPM_PRE_PMD:
gpiod_set_value_cansleep(hp_ctl_gpio, 0);
break;
七、Codec 上电:最终一定会落到 es8389 的寄存器
你问的最后一个关键问题是:
“那最后还是会调用到 es8389 的寄存器?完成上电?”
答案是:一定会。
完整链路是这样的:
播放开始时:
- 用户空间:
aplay test.wav - ALSA PCM:打开播放流
- ASoC DAPM:
- 发现 Playback 路径被使用
- 激活相关 Widgets(DACL、OUTL MUX、Headphone 等)
- DAPM 状态机:
- 调用 Codec Driver 的
es8389_set_bias_level(SND_SOC_BIAS_ON) - Codec Driver 里:
clk_prepare_enable(mclk); regmap_write(... RESET_REG00, 0x01); regmap_write(... CLK_OFF1_REG03, 0xC3); regmap_write(... DAC_RESET_REG4D, 0x00); - 调用 Machine Driver 的
mc_hp_event(POST_PMU)打开功放 GPIO
- 调用 Codec Driver 的
播放结束时:
- PCM 关闭播放流
- DAPM 状态机:
- 调用
mc_hp_event(PRE_PMD)关闭功放 - 调用
es8389_set_bias_level(SND_SOC_BIAS_STANDBY/OFF)关闭 Codec 内部模块
- 调用
所以:
DAPM 决定“什么时候上电/下电”,
Codec Driver 决定“上电/下电时具体写哪些寄存器”。
八、amixer 在这套体系里扮演什么角色?
amixer 操作的是 ALSA Controls,这些控件来自:
- Codec Driver 暴露的控件(
es8389_snd_controls[]) - Codec/Machine 暴露的 DAPM 控件(Widgets/MUX/Mixer)
例如:
static const struct snd_kcontrol_new es8389_snd_controls[] = {
SOC_SINGLE_TLV("DACL Playback Volume", ES8389_DACL_VOL_REG46, ...),
SOC_ENUM("ADC MUX", es8389_dmic_mux_enum),
...
};
这些控件会出现在:
amixer -c 0 controls
amixer -c 0 scontents
amixer 能做的事情包括:
- 改 Codec 寄存器(音量、增益、MUX 选择)
- 改 DAPM Widget 状态(比如 enable/disable Headphone)
- 间接触发 DAPM 路由变化 → 触发功放电源事件
所以你看到:
amixer 里既有 Codec 的控件(DACL Playback Volume),
也有 Machine 的控件(Headphone、Speaker、Speaker Power)。
九、常见实战操作:你已经在做的几件事
1. 不插耳机也能播放:强制打开 DAPM pin
在 Machine Driver 的 probe 里:
snd_soc_dapm_force_enable_pin(&card->dapm, "Headphone");
snd_soc_dapm_force_enable_pin(&card->dapm, "Speaker");
snd_soc_dapm_sync(&card->dapm);
效果:
- 不再依赖 hp-det GPIO
- Headphone/Speaker 路径永远视为“启用”
- 播放时 DAPM 会自动打开 Codec + 功放
2. 修改默认音量:推荐在 Codec Driver 里改
在 es8389_probe() 里:
regmap_write(es8389->regmap, ES8389_DACL_VOL_REG46, 0xA0);
regmap_write(es8389->regmap, ES8389_DACR_VOL_REG47, 0xA0);
或者用 amixer 在用户空间设置,并写入启动脚本。
3. 打印完整 DAPM 路由图
ls /sys/kernel/debug/asoc/
cat /sys/kernel/debug/asoc/<card>/dapm
cat /sys/kernel/debug/asoc/<card>/widgets
cat /sys/kernel/debug/asoc/<card>/routes
可以看到 Codec + Machine 合并后的完整拓扑。
十、最后给你一个“脑内模型”
你可以把整个系统想象成这样:
-
Codec Driver(es8389.c):
“我负责芯片内部的世界——DAC/ADC/Mixer/MUX/寄存器/上电时序。” -
Machine Driver(rk‑multicodecs.c):
“我负责板子外部的世界——耳机孔、喇叭、功放 GPIO、耳机检测、ADC 按键。” -
DAPM:
“我负责全局状态机——谁在用、谁要上电、谁要断电、什么时候开功放、什么时候关。” -
ALSA / amixer / aplay:
“我负责让用户空间有一个统一的接口——你不用关心底下有多少 Codec、有多少 GPIO。”
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)