4. 声卡探测流程

声卡探测是音频驱动加载的起点,也是整个音频子系统初始化的第一道关卡。在Linux内核中,声卡驱动通常以 platform_driver 的形式注册到内核,通过设备树匹配触发 probe 函数,进而完成硬件资源获取、DMA缓冲区分配、DAPM拓扑建立以及声卡注册。本章将深入剖析这一完整流程,帮助您建立从驱动入口到硬件初始化的全景视图。

核心要点: 声卡探测的本质是“驱动-设备”匹配过程。驱动通过 platform_driver_register() 注册,设备树节点提供硬件描述信息,匹配成功后内核调用 probe 函数,驱动在此函数中完成资源申请与初始化。

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 注册流程时序

注册流程: 驱动加载 → module_platform_driver → platform_driver_register → 内核遍历已注册设备 → 匹配设备树节点 → 匹配成功 → 调用 probe 函数。
platform_driver 注册与匹配流程 驱动加载 platform_driver_register 内核匹配 设备树节点 匹配成功 调用 probe 函数 匹配失败则 probe 不会被调用 驱动继续等待其他设备

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 中 用于非设备树平台
常见陷阱: 设备树中的 compatible 字符串必须与驱动中的 of_match_table 完全一致,包括大小写和标点符号。一个常见的错误是驱动中写的是 "simple-audio-card",而设备树中写的是 "simple_audio_card"(下划线代替连字符),导致匹配失败。

4.3 probe 函数核心逻辑

probe 函数是声卡驱动的“构造函数”,负责完成所有硬件初始化工作。一个典型的声卡 probe 函数包含以下步骤:

  1. 解析设备树:获取硬件配置信息,如DMA通道、时钟源、GPIO等。
  2. 获取资源:通过 platform_get_resource 获取 I/O 内存、中断号等。
  3. 初始化硬件:配置时钟、复位外设、设置DMA引擎。
  4. 分配DMA缓冲区:使用 dma_alloc_coherent 分配音频数据缓冲区。
  5. 注册ALSA声卡:调用 snd_soc_register_card 或 devm_snd_soc_register_card。
  6. 初始化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);
probe 函数资源获取与初始化流程 probe 函数入口 解析设备树属性 获取I/O内存资源 获取中断资源 获取DMA通道 获取时钟资源 注册ALSA声卡 probe 完成

4.5 声卡注册与DMA初始化衔接

资源获取完成后,probe 函数的最后一步是注册声卡。在ALSA框架中,声卡注册通过 snd_soc_register_card 完成,该函数会触发以下关键操作:

  • 创建PCM设备:为每个DAI链路创建PCM设备文件(/dev/snd/pcmC0D0p等)。
  • 初始化DMA引擎:配置DMA传输参数,包括缓冲区大小、传输方向、回调函数。
  • 建立DAPM路径:根据设备树中的音频路由信息建立电源管理拓扑。
  • 注册控制接口:创建音量控制、开关控制等ALSA控制接口。
关键衔接点: DMA传输的初始化通常在声卡注册后的 pcm_new 回调中完成。驱动开发者需要在 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拓扑建立。
实践建议: 在调试声卡探测问题时,建议在 probe 函数的关键步骤添加 dev_dbg 打印,通过 dmesg 观察驱动加载过程。同时,使用 ls -l /dev/snd/ 检查声卡设备文件是否创建成功,这是判断声卡探测是否完成的最直接方法。