Linux ALSA框架之六:ASoC架构中的Machine

本文详细解析了ASoC驱动初始化的过程,包括如何注册PlatformDevice,如何注册PlatformDriver,以及如何通过Machine驱动将Platform和Codec驱动组合起来,完成整个设备的音频处理工作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前面一节的内容我们提到,ASoC被分为Machine、Platform和Codec三大部分,其中的Machine驱动负责Platform和Codec之间的耦合以及部分和设备或板子特定的代码,再次引用上一节的内容:Machine驱动负责处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打开一个放大器);单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作.

ASoC的一切都从Machine驱动开始,包括声卡的注册,绑定Platform和Codec驱动等等,下面就让我们从Machine驱动开始讨论吧.

1. 注册Platform Device

ASoC把声卡注册为Platform Device,我们以装配有WM8994的一款Samsung的开发板SMDK为例子做说明,WM8994是一颗Wolfson生产的多功能Codec芯片.

代码位于:/sound/soc/samsung/smdk_wm8994.c,我们关注模块的初始化函数:

复制代码
 1 static int __init smdk_audio_init(void)
 2 {
 3     int ret;
 4 
 5     smdk_snd_device = platform_device_alloc("soc-audio", -1);
 6     if (!smdk_snd_device)
 7         return -ENOMEM;
 8 
 9     platform_set_drvdata(smdk_snd_device, &smdk);
10 
11     ret = platform_device_add(smdk_snd_device);
12     if (ret)
13         platform_device_put(smdk_snd_device);
14 
15     return ret;
16 }
复制代码

由此可见,模块初始化时,注册了一个名为soc-audio的Platform设备,同时把smdk设到platform_device结构的dev.drvdata字段中,这里引出了第一个数据结构snd_soc_card的实例smdk,他的定义如下:

复制代码
 1 static struct snd_soc_dai_link smdk_dai[] = {
 2     { /* Primary DAI i/f */
 3         .name = "WM8994 AIF1",
 4         .stream_name = "Pri_Dai",
 5         .cpu_dai_name = "samsung-i2s.0",
 6         .codec_dai_name = "wm8994-aif1",
 7         .platform_name = "samsung-audio",
 8         .codec_name = "wm8994-codec",
 9         .init = smdk_wm8994_init_paiftx,
10         .ops = &smdk_ops,
11     }, { /* Sec_Fifo Playback i/f */
12         .name = "Sec_FIFO TX",
13         .stream_name = "Sec_Dai",
14         .cpu_dai_name = "samsung-i2s.4",
15         .codec_dai_name = "wm8994-aif1",
16         .platform_name = "samsung-audio",
17         .codec_name = "wm8994-codec",
18         .ops = &smdk_ops,
19     },
20 };
21 
22 static struct snd_soc_card smdk = {
23     .name = "SMDK-I2S",
24     .owner = THIS_MODULE,
25     .dai_link = smdk_dai,
26     .num_links = ARRAY_SIZE(smdk_dai),
27 };
复制代码

通过snd_soc_card结构,又引出了Machine驱动的另外两个个数据结构:

snd_soc_dai_link(实例:smdk_dai[] )

snd_soc_ops(实例:smdk_ops )

其中,snd_soc_dai_link中,指定了Platform、Codec、codec_dai、cpu_dai的名字,稍后Machine驱动将会利用这些名字去匹配已经在系统中注册的platform,codec,dai,这些注册的部件都是在另外相应的Platform驱动和Codec驱动的代码文件中定义的,这样看来,Machine驱动的设备初始化代码无非就是选择合适Platform和Codec以及dai,用他们填充以上几个数据结构,然后注册Platform设备即可.当然还要实现连接Platform和Codec的dai_link对应的ops实现,本例就是smdk_ops,它只实现了hw_params函数:smdk_hw_params.

2. 注册Platform Driver

按照Linux的设备模型,有platform_device,就一定会有platform_driver.ASoC的platform_driver在以下文件中定义:sound/soc/soc-core.c.

还是先从模块的入口看起:

1 static int __init snd_soc_init(void)
2 {
3     ......
4     return platform_driver_register(&soc_driver);
5 }

soc_driver的定义如下:

复制代码
 1 /* ASoC platform driver */
 2 static struct platform_driver soc_driver = {
 3     .driver        = {
 4         .name        = "soc-audio",
 5         .owner        = THIS_MODULE,
 6         .pm        = &soc_pm_ops,
 7     },
 8     .probe        = soc_probe,
 9     .remove        = soc_remove,
10 };
复制代码

我们看到platform_driver的name字段为soc-audio,正好与platform_device中的名字相同,按照Linux的设备模型,platform总线会匹配这两个名字相同的device和driver,同时会触发soc_probe的调用,它正是整个ASoC驱动初始化的入口.

3. 初始化入口soc_probe()

soc_probe函数本身很简单,它先从platform_device参数中取出snd_soc_card,然后调用snd_soc_register_card,通过snd_soc_register_card,为snd_soc_pcm_runtime数组申请内存,每一个dai_link对应snd_soc_pcm_runtime数组的一个单元,然后把snd_soc_card中的dai_link配置复制到相应的snd_soc_pcm_runtime中,最后,大部分的工作都在snd_soc_instantiate_card中实现,下面就看看snd_soc_instantiate_card做了些什么:

该函数首先利用card->instantiated来判断该卡是否已经实例化,如果已经实例化则直接返回,否则遍历每一对dai_link,进行codec、platform、dai的绑定工作,下只是代码的部分选节,详细的代码请直接参考完整的代码树.

1     /* bind DAIs */
2     for (i = 0; i < card->num_links; i++)
3         soc_bind_dai_link(card, i);

ASoC定义了三个全局的链表头变量:codec_list、dai_list、platform_list,系统中所有的Codec、DAI、Platform都在注册时连接到这三个全局链表上.soc_bind_dai_link函数逐个扫描这三个链表,根据card->dai_link[]中的名称进行匹配,匹配后把相应的codec,dai和platform实例赋值到card->rtd[]中(snd_soc_pcm_runtime).经过这个过程后,snd_soc_pcm_runtime:(card->rtd)中保存了本Machine中使用的Codec,DAI和Platform驱动的信息.

snd_soc_instantiate_card接着初始化Codec的寄存器缓存,然后调用标准的alsa函数创建声卡实例:

复制代码
1     /* card bind complete so register a sound card */
2     ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
3             card->owner, 0, &card->snd_card);
4     card->snd_card->dev = card->dev;
5 
6     card->dapm.bias_level = SND_SOC_BIAS_OFF;
7     card->dapm.dev = card->dev;
8     card->dapm.card = card;
9     list_add(&card->dapm.list, &card->dapm_list);
复制代码

然后,依次调用各个子结构的probe函数:

复制代码
 1     /* initialise the sound card only once */
 2     if (card->probe) {
 3         ret = card->probe(card);
 4         if (ret < 0)
 5             goto card_probe_error;
 6     }
 7 
 8     /* early DAI link probe */
 9     for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
10             order++) {
11         for (i = 0; i < card->num_links; i++) {
12             ret = soc_probe_dai_link(card, i, order);
13             if (ret < 0) {
14                 pr_err("asoc: failed to instantiate card %s: %d\n",
15                    card->name, ret);
16                 goto probe_dai_err;
17             }
18         }
19     }
20 
21     for (i = 0; i < card->num_aux_devs; i++) {
22         ret = soc_probe_aux_dev(card, i);
23         if (ret < 0) {
24             pr_err("asoc: failed to add auxiliary devices %s: %d\n",
25                    card->name, ret);
26             goto probe_aux_dev_err;
27         }
28     }
复制代码

在上面的soc_probe_dai_link()函数中做了比较多的事情,把他展开继续讨论:

复制代码
 1 static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order)
 2 {
 3         ......
 4     /* set default power off timeout */
 5     rtd->pmdown_time = pmdown_time;
 6 
 7     /* probe the cpu_dai */
 8     if (!cpu_dai->probed &&
 9             cpu_dai->driver->probe_order == order) {
10 
11         if (cpu_dai->driver->probe) {
12             ret = cpu_dai->driver->probe(cpu_dai);
13         }
14         cpu_dai->probed = 1;
15         /* mark cpu_dai as probed and add to card dai list */
16         list_add(&cpu_dai->card_list, &card->dai_dev_list);
17     }
18 
19     /* probe the CODEC */
20     if (!codec->probed &&
21             codec->driver->probe_order == order) {
22         ret = soc_probe_codec(card, codec);
23     }
24 
25     /* probe the platform */
26     if (!platform->probed &&
27             platform->driver->probe_order == order) {
28         ret = soc_probe_platform(card, platform);
29     }
30 
31     /* probe the CODEC DAI */
32     if (!codec_dai->probed && codec_dai->driver->probe_order == order) {
33         if (codec_dai->driver->probe) {
34             ret = codec_dai->driver->probe(codec_dai);
35         }
36 
37         /* mark codec_dai as probed and add to card dai list */
38         codec_dai->probed = 1;
39         list_add(&codec_dai->card_list, &card->dai_dev_list);
40     }
41 
42     /* complete DAI probe during last probe */
43     if (order != SND_SOC_COMP_ORDER_LAST)
44         return 0;
45 
46     ret = soc_post_component_init(card, codec, num, 0);
47     if (ret)
48         return ret;
49         ......
50     /* create the pcm */
51     ret = soc_new_pcm(rtd, num);
52         ........
53     return 0;
54 }
复制代码

该函数出了挨个调用了codec,dai和platform驱动的probe函数外,在最后还调用了soc_new_pcm()函数用于创建标准alsa驱动的pcm逻辑设备.现在把该函数的部分代码也贴出来:

复制代码
 1 /* create a new pcm */
 2 int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
 3 {
 4     ......
 5     struct snd_pcm_ops *soc_pcm_ops = &rtd->ops;
 6 
 7     soc_pcm_ops->open    = soc_pcm_open;
 8     soc_pcm_ops->close    = soc_pcm_close;
 9     soc_pcm_ops->hw_params    = soc_pcm_hw_params;
10     soc_pcm_ops->hw_free    = soc_pcm_hw_free;
11     soc_pcm_ops->prepare    = soc_pcm_prepare;
12     soc_pcm_ops->trigger    = soc_pcm_trigger;
13     soc_pcm_ops->pointer    = soc_pcm_pointer;
14 
15     ret = snd_pcm_new(rtd->card->snd_card, new_name,
16             num, playback, capture, &pcm);
17 
18     /* DAPM dai link stream work */
19     INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
20 
21     rtd->pcm = pcm;
22     pcm->private_data = rtd;
23     if (platform->driver->ops) {
24         soc_pcm_ops->mmap = platform->driver->ops->mmap;
25         soc_pcm_ops->pointer = platform->driver->ops->pointer;
26         soc_pcm_ops->ioctl = platform->driver->ops->ioctl;
27         soc_pcm_ops->copy = platform->driver->ops->copy;
28         soc_pcm_ops->silence = platform->driver->ops->silence;
29         soc_pcm_ops->ack = platform->driver->ops->ack;
30         soc_pcm_ops->page = platform->driver->ops->page;
31     }
32 
33     if (playback)
34         snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops);
35 
36     if (capture)
37         snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops);
38 
39     if (platform->driver->pcm_new) {
40         ret = platform->driver->pcm_new(rtd);
41         if (ret < 0) {
42             pr_err("asoc: platform pcm constructor failed\n");
43             return ret;
44         }
45     }
46 
47     pcm->private_free = platform->driver->pcm_free;
48     return ret;
49 }
复制代码

该函数首先初始化snd_soc_runtime中的snd_pcm_ops字段,也就是rtd->ops中的部分成员,例如open,close,hw_params等,紧接着调用标准alsa驱动中的创建pcm的函数snd_pcm_new()创建声卡的pcm实例,pcm的private_data字段设置为该runtime变量rtd,然后用platform驱动中的snd_pcm_ops替换部分pcm中的snd_pcm_ops字段,最后,调用platform驱动的pcm_new回调,该回调实现该platform下的dma内存申请和dma初始化等相关工作.到这里,声卡和他的pcm实例创建完成.

回到snd_soc_instantiate_card函数,完成snd_card和snd_pcm的创建后,接着对dapm和dai支持的格式做出一些初始化合设置工作后,调用了card->late_probe(card)进行一些最后的初始化合设置工作,最后则是调用标准alsa驱动的声卡注册函数对声卡进行注册:

复制代码
 1     if (card->late_probe) {
 2         ret = card->late_probe(card);
 3         if (ret < 0) {
 4             dev_err(card->dev, "%s late_probe() failed: %d\n",
 5                 card->name, ret);
 6             goto probe_aux_dev_err;
 7         }
 8     }
 9 
10     snd_soc_dapm_new_widgets(&card->dapm);
11 
12     if (card->fully_routed)
13         list_for_each_entry(codec, &card->codec_dev_list, card_list)
14             snd_soc_dapm_auto_nc_codec_pins(codec);
15 
16     ret = snd_card_register(card->snd_card);
17     if (ret < 0) {
18         printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);
19         goto probe_aux_dev_err;
20     }
复制代码

至此,整个Machine驱动的初始化已经完成,通过各个子结构的probe调用,实际上,也完成了部分Platfrom驱动和Codec驱动的初始化工作,整个过程可以用一下的序列图表示:

      图3.1  基于3.0内核soc_probe序列图

下面的序列图是本文章第一个版本,基于内核2.6.35,大家也可以参考一下两个版本的差异:

      图3.2  基于2.6.35  soc_probe序列图

本文转自:http://blog.youkuaiyun.com/droidphone/article/details/7231605

<think>我们之前已经讨论过ALSA Core和ASoC Core的级关系,但用户再次提出类似问题,可能是希望获得更清晰或不同角度的解释。 因此,我将从架构级、功能划分和交互流程三个方面进行更系统的说明,重点强调“上下关系”在代码和运行时的体现。 ### 详细解析:ALSA Core与ASoC Core的级关系 #### 1. 架构级(从底硬件到用户空间) ```mermaid graph TD H[硬件] --> |寄存器操作| D(ASoC Core) D --> |构建音频组件| C(ALSA Core) C --> |暴露设备节点| U(用户空间) ``` #### 2. 功能划分 | **组件** | **核心职责** | **依赖关系** | |-------------------|-----------------------------------------------------------------------------|----------------------------------| | **ALSA Core** | 提供跨平台的通用音频框架:<br>- PCM设备抽象<br>- 控制接口(Ctl)<br>- 定时器/中断处理 | 独立于具体硬件,位于内核音频栈顶 | | **ASoC Core** | 针对嵌入式SoC的扩展:<br>- DAI(数字音频接口)管理<br>- DPCM动态路由<br>- 低功耗优化 | 必须依赖ALSA Core的基础设施 | #### 3. 代码面的上下关系 - **ALSA Core 基础结构** (include/sound/core.h): ```c struct snd_card { struct list_head devices; // 挂载PCM/Control等设备 /* ... */ }; ``` - **ASoC 扩展实现** (sound/soc/soc-core.c): ```c // ASoC直接复用ALSA Core的card结构 int snd_soc_register_card(struct snd_soc_card *card) { struct snd_card *snd_card = card->snd_card; // 将ASoC组件绑定到ALSA card snd_soc_dapm_init(&card->dapm, card, NULL); } ``` #### 4. 运行时交互流程 ```mermaid sequenceDiagram User Space->>ALSA Core: open("/dev/snd/pcmC0D0p") ALSA Core->>ASoC Core: 调用snd_soc_pcm_open() ASoC Core->>Hardware: 配置DAI时钟/使能CODEC ASoc Core-->>ALSA Core: 返回stream handle ALSA Core-->>User Space: 返回文件描述符 ``` #### 5. 关键结论 - **上下关系**:ASoC Core是**构建在ALSA Core之上的子框架**,类似于插件机制: - ALSA Core提供通用音频设备模型(如PCM设备节点生成) - ASoC Core填充具体操作(通过`struct snd_soc_ops`) - **设计本质**: `ALSA Core = 通用音频框架` + `ASoC Core = 嵌入式扩展包` §§相关问题§§ 1. 在编写ASoC Machine驱动时,如何通过snd_soc_card结构关联到ALSA Core的snd_card? 2. 当ALSA Core收到用户空间的ioctl(SNDRV_PCM_IOCTL_HW_PARAMS)调用时,如何路由到ASoC的具体实现? 3. ASoC的DPCM机制是如何在ALSA的PCM运行时(runtime)基础上扩展动态路径选择的? 4. 在支持多核SoC的音频系统中,ASoC Core如何协调不同CPU的DMA引擎与ALSA Core的缓冲区管理? 5. 如何通过ALSA Core提供的debugfs接口追踪ASoCDAI的时钟配置错误?</think>### ALSA框架ALSA Core与ASoC Core的级关系详解 #### 🔍 核心架构关系(自底向上) ```mermaid graph TD H[硬件] --> A[ASoC Core] A --> B[ALSA Core] B --> U[用户空间] ``` #### 📊 功能职责对比 | **组件** | **所属级** | **核心职责** | **依赖关系** | |----------|--------------|--------------|--------------| | **ALSA Core** | Linux内核通用 | 提供音频设备抽象、PCM核心框架、用户空间API | 不依赖ASoC | | **ASoC Core** | Linux内核嵌入式 | 实现SoC音频硬件集成、多组件协调、低功耗管理 | 必须依赖ALSA Core | #### ⚙️ 交互关系详解 1. **初始化依赖**: ```c // ASoC驱动必须通过ALSA Core注册 (sound/soc/soc-core.c) int snd_soc_register_card(struct snd_soc_card *card) { // 首先创建ALSA核心结构 ret = snd_card_new(dev, SNDRV_DEFAULT_IDX1, card->name, THIS_MODULE, 0, &card->snd_card); // 然后注册ASoC特定组件 snd_soc_instantiate_card(card); } ``` 2. **数据处理流程**: ``` 用户空间调用write() → ALSA Core: snd_pcm_lib_write() → ASoC Core: soc_pcm_platform->ops.pointer() → DMA引擎: dmaengine_pcm_trigger() → 硬件 ``` 3. **控制流示例**: ```mermaid sequenceDiagram 用户空间->>ALSA Core: ioctl(SNDRV_CTL_IOCTL_ELEM_WRITE) ALSA Core->>ASoC Core: snd_soc_put_volsw() ASoC Core->>CODEC驱动: snd_soc_component_write() CODEC驱动->>硬件: I2C寄存器写入 ``` #### 🧩 组件集成关系 ``` ALSA音频栈 ├── ALSA Core │ ├── PCM核心 (snd_pcm) │ ├── 控制接口 (snd_ctl) │ └── 定时器/中断核心 └── ASoC Core (依赖ALSA Core) ├── Machine驱动 (板级配置) ├── Platform驱动 (DMA/CPU DAI) └── CODEC驱动 (音频编解码) ``` #### 💡 关键结论 1. **级关系**:ASoC Core是**构建在ALSA Core之上的嵌入式扩展**,不是平行关系 2. **依赖方向**:ASoC驱动必须通过ALSA Core注册,所有用户空间访问都经过ALSA Core路由 3. **功能划分**: - ALSA Core提供**跨平台通用接口** - ASoC Core解决**SoC特有问题**: * 多DAI组件协调 * 动态音频路由 * 低功耗状态管理 4. **代码体现**:所有ASoC驱动最终都嵌入到`snd_card`结构中(ALSA Core的核心结构)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值