3. Linux内核音频驱动模型
本章深入剖析Linux内核中音频驱动的核心架构模型。音频驱动并非孤立存在,而是构建在字符设备、platform总线、I2C/SPI协议以及设备树(Device Tree)等多个内核子系统之上。理解这些模型的协作关系,是掌握ALSA框架整合的关键前提。
3.1 字符设备驱动模型:音频设备的用户空间接口
在Linux中,音频设备对用户空间呈现为字符设备(Character Device)。应用程序通过标准的文件操作接口(open、read、write、ioctl、mmap、poll)与音频硬件交互,而无需关心底层硬件的具体细节。
字符设备驱动的核心数据结构是 struct file_operations,它定义了设备文件与内核驱动之间的操作映射表。对于音频设备,关键的file_operations回调包括:
| 回调函数 | 用途 | ALSA中的典型实现 |
|---|---|---|
.open |
打开音频设备,初始化硬件和软件状态 | snd_pcm_open() / snd_ctl_open() |
.release |
关闭设备,释放资源 | snd_pcm_release() |
.read / .write |
PCM数据流的读写(非mmap模式) | snd_pcm_read() / snd_pcm_write() |
.ioctl |
控制命令,如设置采样率、格式、音量 | snd_pcm_ioctl() / snd_ctl_ioctl() |
.mmap |
将DMA缓冲区映射到用户空间,实现零拷贝 | snd_pcm_mmap() |
.poll |
查询设备是否可读/可写,支持非阻塞I/O | snd_pcm_poll() |
register_chrdev(),而是通过ALSA核心API(如 snd_pcm_new())间接完成字符设备的创建与注册。ALSA会为每个PCM设备分配主设备号(通常为116)和次设备号。
3.2 Platform驱动模型:SoC内部音频控制器的标准框架
对于集成在SoC(System on Chip)内部的音频控制器(如I2S、AC97、PDM接口),Linux内核推荐使用platform驱动模型进行管理。Platform驱动不依赖传统的物理总线(如PCI、USB),而是通过虚拟的platform总线进行设备与驱动的匹配。
Platform驱动模型在音频子系统中的典型应用场景:
- CPU DAI驱动: 代表SoC内部的数字音频接口(如Samsung I2S、TI McASP、STI FSAudio)。
- DMA引擎驱动: 负责音频数据的DMA传输,通常也注册为platform驱动。
- 机器驱动(Machine Driver): 将CPU DAI、Codec和DMA引擎绑定在一起,形成完整的声卡。
Platform驱动的核心数据结构是 struct platform_driver,其 .probe 函数是音频设备初始化的入口点。
// 典型的音频platform驱动模板
static const struct of_device_id audio_of_match[] = {
{ .compatible = "vendor,my-audio-controller" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, audio_of_match);
static struct platform_driver my_audio_driver = {
.probe = my_audio_probe,
.remove = my_audio_remove,
.driver = {
.name = "my-audio-controller",
.of_match_table = audio_of_match,
.pm = &my_audio_pm_ops,
},
};
module_platform_driver(my_audio_driver);
.probe 函数中,驱动应完成以下工作:1. 解析设备树节点,获取寄存器地址、中断号、DMA通道等资源。
2. 映射I/O内存(
devm_ioremap_resource())。3. 初始化硬件时钟、复位和电源管理。
4. 注册DMA引擎(
dma_request_slave_channel())。5. 调用ALSA API创建PCM设备(
snd_pcm_new())并设置操作集。
3.3 I2C/SPI设备注册:音频Codec的物理总线连接
大多数外部音频Codec芯片(如WM8960、TLV320AIC3x、ES8316)通过I2C或SPI总线与SoC连接。这些Codec驱动在内核中注册为I2C或SPI设备驱动,负责控制Codec的内部寄存器,实现音量调节、采样率切换、音频路由等功能。
I2C设备驱动的注册流程:
- 定义I2C设备ID表: 使用
struct i2c_device_id和of_match_table进行设备匹配。 - 实现I2C驱动结构体: 填充
struct i2c_driver的.probe、.remove、.id_table字段。 - 在probe中初始化Codec: 读取设备ID、复位芯片、初始化寄存器默认值。
- 注册到ALSA SoC框架: 调用
devm_snd_soc_register_component()将Codec注册为ALSA组件。
// I2C音频Codec驱动注册示例
static const struct i2c_device_id audio_codec_id[] = {
{ "wm8960", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, audio_codec_id);
static const struct of_device_id audio_codec_of_match[] = {
{ .compatible = "wlf,wm8960" },
{ }
};
MODULE_DEVICE_TABLE(of, audio_codec_of_match);
static struct i2c_driver audio_codec_i2c_driver = {
.driver = {
.name = "wm8960",
.of_match_table = audio_codec_of_match,
},
.probe = audio_codec_i2c_probe,
.remove = audio_codec_i2c_remove,
.id_table = audio_codec_id,
};
module_i2c_driver(audio_codec_i2c_driver);
3.4 设备树与音频设备:硬件描述的标准化
设备树(Device Tree)是嵌入式Linux中描述硬件拓扑的标准方式。对于音频子系统,设备树节点定义了SoC音频控制器、外部Codec、时钟源、DMA通道以及它们之间的连接关系。
一个典型的音频设备树节点包含以下关键属性:
| 属性 | 含义 | 示例值 |
|---|---|---|
compatible |
驱动匹配字符串 | "wlf,wm8960" |
reg |
I2C从设备地址或SPI片选号 | <0x1a> |
clocks |
音频主时钟(MCLK)的phandle | <&clks IMX6QDL_CLK_CKO> |
clock-names |
时钟名称,用于驱动中获取 | "mclk" |
#sound-dai-cells |
DAI链接标识符的单元数 | <0> 或 <1> |
VDDA-supply |
模拟电源 regulator | <®_audio_3v3> |
以下是一个完整的音频设备树片段,展示了CPU DAI、Codec和机器驱动的绑定关系:
// 音频Codec节点
&i2c2 {
audio_codec: wm8960@1a {
compatible = "wlf,wm8960";
reg = <0x1a>;
clocks = <&clks IMX6QDL_CLK_CKO>;
clock-names = "mclk";
#sound-dai-cells = <0>;
AVDD-supply = <®_audio_3v3>;
DVDD-supply = <®_audio_1v8>;
};
};
// CPU DAI节点(I2S控制器)
&ssi1 {
status = "okay";
#sound-dai-cells = <0>;
};
// 机器驱动节点(绑定CPU DAI和Codec)
sound {
compatible = "my-board,audio-card";
model = "MyBoard Audio";
audio-cpu = <&ssi1>;
audio-codec = <&audio_codec>;
audio-routing =
"Headphone Jack", "HP_L",
"Headphone Jack", "HP_R",
"MIC_IN", "Mic Jack";
};
3.5 音频驱动模型整体架构图
下图展示了字符设备、platform驱动、I2C/SPI设备注册与设备树在音频驱动中的协作关系:
3.6 模型协作流程:从设备树到声卡注册
当系统启动时,音频驱动的初始化遵循以下典型流程:
- 设备树解析: 内核解析DTB文件,为每个
compatible匹配的节点创建platform_device、i2c_client或spi_device。 - 驱动匹配与probe: 总线驱动(platform、i2c、spi)调用匹配驱动的
.probe函数。 - 资源获取: 驱动通过
of_property_read_*系列API从设备树节点读取寄存器地址、中断、时钟、DMA通道等资源。 - 组件注册: CPU DAI驱动和Codec驱动分别调用
devm_snd_soc_register_component()注册到ASoC核心。 - 声卡绑定: 机器驱动(Machine Driver)在probe中创建
snd_soc_card,并通过snd_soc_register_card()将CPU DAI、Codec和DMA引擎绑定在一起。 - 字符设备创建: ASoC核心自动为每个PCM DAI创建字符设备(如
/dev/snd/pcmC0D0p),用户空间即可通过标准文件I/O访问音频硬件。
• 字符设备模型 提供用户空间访问接口,ALSA封装了底层注册细节。
• Platform驱动 管理SoC内部音频控制器和DMA引擎,通过设备树匹配。
• I2C/SPI驱动 控制外部Codec的寄存器,实现音频参数配置。
• 设备树 是硬件描述的“事实标准”,驱动通过解析设备树获取资源,实现与硬件平台的解耦。
• 四种模型在ASoC框架下协同工作,共同构成完整的嵌入式音频驱动体系。