4. 声卡探测流程
声卡探测是音频驱动加载的起点,也是整个音频子系统初始化的第一道关卡。在Linux内核中,声卡驱动通常以 platform_driver 的形式注册到内核,通过设备树匹配触发 probe 函数,进而完成硬件资源获取、DMA缓冲区分配、DAPM拓扑建立以及声卡注册。本章将深入剖析这一完整流程,帮助您建立从驱动入口到硬件初始化的全景视图。
4.1 platform_driver 注册机制
在Linux音频驱动中,绝大多数SoC内部音频控制器(如I2S、PCM、AC97控制器)都挂载在 platform总线 上。驱动开发者需要定义一个 platform_driver 结构体,并通过 platform_driver_register() 将其注册到内核。
4.1.1 platform_driver 结构体定义
static const struct of_device_id asoc_simple_of_match[] = {
{ .compatible = "simple-audio-card", },
{},
};
MODULE_DEVICE_TABLE(of, asoc_simple_of_match);
static struct platform_driver asoc_simple_driver = {
.probe = asoc_simple_probe,
.remove = asoc_simple_remove,
.driver = {
.name = "asoc-simple-card",
.of_match_table = asoc_simple_of_match,
.pm = &snd_soc_pm_ops,
},
};
module_platform_driver(asoc_simple_driver);
上述代码展示了典型的声卡驱动注册模式:
- of_match_table:指向设备树匹配表,用于与设备树节点中的
compatible属性进行匹配。 - probe:匹配成功后调用的初始化函数,是声卡探测的核心。
- remove:驱动卸载时的清理函数。
- module_platform_driver:宏封装了 module_init 和 module_exit,简化驱动注册。
4.1.2 注册流程时序
4.2 设备树匹配详解
设备树(Device Tree)是嵌入式Linux中描述硬件信息的标准方式。声卡驱动通过 compatible 属性与设备树节点建立关联。匹配过程由内核的 platform bus 驱动模型自动完成。
4.2.1 设备树节点示例
/ {
sound_card: sound@0 {
compatible = "simple-audio-card";
simple-audio-card,name = "STM32MP1-Audio";
simple-audio-card,format = "i2s";
simple-audio-card,bitclock-master = &cpu_dai;
simple-audio-card,frame-master = &cpu_dai;
cpu_dai: cpu {
sound-dai = &sai2;
};
codec {
sound-dai = &cs42l51;
};
};
};
设备树节点中的 compatible = "simple-audio-card" 与驱动中 of_match_table 的 .compatible = "simple-audio-card" 形成匹配对。匹配成功后,内核将 device_node 指针传递给 probe 函数。
4.2.2 匹配优先级与规则
| 匹配方式 | 匹配依据 | 优先级 | 说明 |
|---|---|---|---|
| 设备树 compatible | of_match_table | 最高 | 最常用,精确匹配硬件型号 |
| ACPI ID | acpi_match_table | 高 | x86平台使用,嵌入式较少 |
| 名称匹配 | driver.name | 低 | 传统方式,不推荐 |
| ID表匹配 | id_table | 中 | 用于非设备树平台 |
4.3 probe 函数核心逻辑
probe 函数是声卡驱动的“构造函数”,负责完成所有硬件初始化工作。一个典型的声卡 probe 函数包含以下步骤:
- 解析设备树:获取硬件配置信息,如DMA通道、时钟源、GPIO等。
- 获取资源:通过 platform_get_resource 获取 I/O 内存、中断号等。
- 初始化硬件:配置时钟、复位外设、设置DMA引擎。
- 分配DMA缓冲区:使用 dma_alloc_coherent 分配音频数据缓冲区。
- 注册ALSA声卡:调用 snd_soc_register_card 或 devm_snd_soc_register_card。
- 初始化DAPM:建立音频路径和电源管理拓扑。
4.3.1 probe 函数代码框架
static int asoc_simple_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct snd_soc_card *card;
struct resource *res;
int ret;
/* 1. 分配声卡结构体 */
card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
if (!card)
return -ENOMEM;
/* 2. 解析设备树属性 */
ret = snd_soc_of_parse_card_name(card, "simple-audio-card,name");
if (ret < 0) {
dev_err(dev, "Failed to parse card name\n");
return ret;
}
/* 3. 获取硬件资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "Failed to get memory resource\n");
return -ENXIO;
}
/* 4. 映射I/O内存 */
regs = devm_ioremap_resource(dev, res);
if (IS_ERR(regs))
return PTR_ERR(regs);
/* 5. 获取中断号 */
int irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
/* 6. 注册声卡 */
card->dev = dev;
ret = devm_snd_soc_register_card(dev, card);
if (ret) {
dev_err(dev, "Failed to register sound card: %d\n", ret);
return ret;
}
dev_info(dev, "Sound card registered successfully\n");
return 0;
}
4.4 资源获取方法
资源获取是 probe 函数中最重要的环节之一。Linux内核提供了多种API来获取设备树中定义的硬件资源:
4.4.1 常用资源获取API
| API函数 | 获取资源类型 | 返回值 | 典型用法 |
|---|---|---|---|
| platform_get_resource | I/O内存、I/O端口 | struct resource * | 获取寄存器基地址 |
| platform_get_irq | 中断号 | int | 获取DMA完成中断 |
| platform_get_resource_byname | 按名称获取资源 | struct resource * | 多区域寄存器 |
| dma_request_chan | DMA通道 | struct dma_chan * | 获取音频DMA通道 |
| devm_clk_get | 时钟句柄 | struct clk * | 获取音频时钟 |
| devm_regulator_get | 电源调节器 | struct regulator * | 获取音频电源 |
4.4.2 资源获取完整示例
/* 获取I/O内存资源 */
struct resource *res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
void __iomem *base = devm_ioremap_resource(dev, res_mem);
/* 获取中断资源 */
int irq = platform_get_irq(pdev, 0);
ret = devm_request_irq(dev, irq, audio_isr, 0, "audio", priv);
/* 获取DMA通道 */
priv->dma_chan = dma_request_chan(dev, "rx");
if (IS_ERR(priv->dma_chan))
return PTR_ERR(priv->dma_chan);
/* 获取时钟 */
priv->clk = devm_clk_get(dev, "sai_ck");
if (IS_ERR(priv->clk))
return PTR_ERR(priv->clk);
4.5 声卡注册与DMA初始化衔接
资源获取完成后,probe 函数的最后一步是注册声卡。在ALSA框架中,声卡注册通过 snd_soc_register_card 完成,该函数会触发以下关键操作:
- 创建PCM设备:为每个DAI链路创建PCM设备文件(/dev/snd/pcmC0D0p等)。
- 初始化DMA引擎:配置DMA传输参数,包括缓冲区大小、传输方向、回调函数。
- 建立DAPM路径:根据设备树中的音频路由信息建立电源管理拓扑。
- 注册控制接口:创建音量控制、开关控制等ALSA控制接口。
snd_soc_platform_driver 中实现 pcm_new 函数,在此函数中调用 snd_pcm_set_managed_buffer_all 分配DMA缓冲区,并配置DMA引擎的传输参数。
4.5.1 DMA初始化代码片段
static int audio_pcm_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_pcm *pcm = rtd->pcm;
struct snd_pcm_substream *substream;
size_t buffer_size = 64 * 1024; /* 64KB缓冲区 */
/* 为播放和捕获分配DMA缓冲区 */
snd_pcm_set_managed_buffer_all(pcm,
SNDRV_DMA_TYPE_DEV,
rtd->card->dev,
buffer_size,
buffer_size);
/* 配置DMA传输参数 */
substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
if (substream) {
substream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV;
substream->dma_buffer.dev.dev = rtd->card->dev;
}
return 0;
}
4.6 本章小结
声卡探测流程是音频驱动开发的基石,理解这一流程对于后续的DMA传输、音频数据处理至关重要。本章的核心知识点包括:
- platform_driver注册:通过 module_platform_driver 宏简化驱动注册,of_match_table 实现设备树匹配。
- 设备树匹配:compatible 属性是匹配的关键,匹配成功后内核调用 probe 函数。
- probe 函数:完成设备树解析、资源获取、硬件初始化、声卡注册等核心工作。
- 资源获取:使用 platform_get_resource、dma_request_chan、devm_clk_get 等API获取硬件资源。
- 声卡注册:snd_soc_register_card 触发PCM设备创建、DMA初始化、DAPM拓扑建立。
ls -l /dev/snd/ 检查声卡设备文件是否创建成功,这是判断声卡探测是否完成的最直接方法。