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

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

/********************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/********************************************************************************************/

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. smdk_snd_device = platform_device_alloc("soc-audio", -1);
  5. if (!smdk_snd_device)
  6. return -ENOMEM;
  7. platform_set_drvdata(smdk_snd_device, &smdk);
  8. ret = platform_device_add(smdk_snd_device);
  9. if (ret)
  10. platform_device_put(smdk_snd_device);
  11. return ret;
  12. }

由此可见,模块初始化时,注册了一个名为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. static struct snd_soc_card smdk = {
  22. .name = "SMDK-I2S",
  23. .owner = THIS_MODULE,
  24. .dai_link = smdk_dai,
  25. .num_links = ARRAY_SIZE(smdk_dai),
  26. };

通过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. card->dapm.bias_level = SND_SOC_BIAS_OFF;
  6. card->dapm.dev = card->dev;
  7. card->dapm.card = card;
  8. 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. /* early DAI link probe */
  8. for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
  9. order++) {
  10. for (i = 0; i < card->num_links; i++) {
  11. ret = soc_probe_dai_link(card, i, order);
  12. if (ret < 0) {
  13. pr_err("asoc: failed to instantiate card %s: %d\n",
  14. card->name, ret);
  15. goto probe_dai_err;
  16. }
  17. }
  18. }
  19. for (i = 0; i < card->num_aux_devs; i++) {
  20. ret = soc_probe_aux_dev(card, i);
  21. if (ret < 0) {
  22. pr_err("asoc: failed to add auxiliary devices %s: %d\n",
  23. card->name, ret);
  24. goto probe_aux_dev_err;
  25. }
  26. }

在上面的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. /* probe the cpu_dai */
  7. if (!cpu_dai->probed &&
  8. cpu_dai->driver->probe_order == order) {
  9. if (cpu_dai->driver->probe) {
  10. ret = cpu_dai->driver->probe(cpu_dai);
  11. }
  12. cpu_dai->probed = 1;
  13. /* mark cpu_dai as probed and add to card dai list */
  14. list_add(&cpu_dai->card_list, &card->dai_dev_list);
  15. }
  16. /* probe the CODEC */
  17. if (!codec->probed &&
  18. codec->driver->probe_order == order) {
  19. ret = soc_probe_codec(card, codec);
  20. }
  21. /* probe the platform */
  22. if (!platform->probed &&
  23. platform->driver->probe_order == order) {
  24. ret = soc_probe_platform(card, platform);
  25. }
  26. /* probe the CODEC DAI */
  27. if (!codec_dai->probed && codec_dai->driver->probe_order == order) {
  28. if (codec_dai->driver->probe) {
  29. ret = codec_dai->driver->probe(codec_dai);
  30. }
  31. /* mark codec_dai as probed and add to card dai list */
  32. codec_dai->probed = 1;
  33. list_add(&codec_dai->card_list, &card->dai_dev_list);
  34. }
  35. /* complete DAI probe during last probe */
  36. if (order != SND_SOC_COMP_ORDER_LAST)
  37. return 0;
  38. ret = soc_post_component_init(card, codec, num, 0);
  39. if (ret)
  40. return ret;
  41. ......
  42. /* create the pcm */
  43. ret = soc_new_pcm(rtd, num);
  44. ........
  45. return 0;
  46. }

该函数出了挨个调用了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. soc_pcm_ops->open    = soc_pcm_open;
  7. soc_pcm_ops->close   = soc_pcm_close;
  8. soc_pcm_ops->hw_params   = soc_pcm_hw_params;
  9. soc_pcm_ops->hw_free = soc_pcm_hw_free;
  10. soc_pcm_ops->prepare = soc_pcm_prepare;
  11. soc_pcm_ops->trigger = soc_pcm_trigger;
  12. soc_pcm_ops->pointer = soc_pcm_pointer;
  13. ret = snd_pcm_new(rtd->card->snd_card, new_name,
  14. num, playback, capture, &pcm);
  15. /* DAPM dai link stream work */
  16. INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
  17. rtd->pcm = pcm;
  18. pcm->private_data = rtd;
  19. if (platform->driver->ops) {
  20. soc_pcm_ops->mmap = platform->driver->ops->mmap;
  21. soc_pcm_ops->pointer = platform->driver->ops->pointer;
  22. soc_pcm_ops->ioctl = platform->driver->ops->ioctl;
  23. soc_pcm_ops->copy = platform->driver->ops->copy;
  24. soc_pcm_ops->silence = platform->driver->ops->silence;
  25. soc_pcm_ops->ack = platform->driver->ops->ack;
  26. soc_pcm_ops->page = platform->driver->ops->page;
  27. }
  28. if (playback)
  29. snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops);
  30. if (capture)
  31. snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops);
  32. if (platform->driver->pcm_new) {
  33. ret = platform->driver->pcm_new(rtd);
  34. if (ret < 0) {
  35. pr_err("asoc: platform pcm constructor failed\n");
  36. return ret;
  37. }
  38. }
  39. pcm->private_free = platform->driver->pcm_free;
  40. return ret;
  41. }

该函数首先初始化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. snd_soc_dapm_new_widgets(&card->dapm);
  10. if (card->fully_routed)
  11. list_for_each_entry(codec, &card->codec_dev_list, card_list)
  12. snd_soc_dapm_auto_nc_codec_pins(codec);
  13. ret = snd_card_register(card->snd_card);
  14. if (ret < 0) {
  15. printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);
  16. goto probe_aux_dev_err;
  17. }

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

图3.1  基于3.0内核  soc_probe序列图

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

图3.2  基于2.6.35  soc_probe序列图

Linux ALSA声卡驱动之六:ASoC架构中的Machine的更多相关文章

  1. Linux ALSA声卡驱动之八:ASoC架构中的Platform

    1.  Platform驱动在ASoC中的作用 前面几章内容已经说过,ASoC被分为Machine,Platform和Codec三大部件,Platform驱动的主要作用是完成音频数据的管理,最终通过C ...

  2. Linux ALSA声卡驱动之七:ASoC架构中的Codec

    1.  Codec简介(ad/da) 在移动设备中,Codec的作用可以归结为4种,分别是: 对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号 对Mic.Linein或者其他输入源的模拟信 ...

  3. Linux ALSA声卡驱动之五:移动设备中的ALSA(ASoC)

    转自http://blog.csdn.net/droidphone/article/details/7165482 1.  ASoC的由来 ASoC--ALSA System on Chip ,是建立 ...

  4. Linux ALSA声卡驱动之二:声卡的创建

    1. struct snd_card 1.1. snd_card是什么 snd_card可以说是整个ALSA音频驱动最顶层的一个结构,整个声卡的软件逻辑结构开始于该结构,几乎所有与声音相关的逻辑设备都 ...

  5. Linux ALSA声卡驱动之一:ALSA架构简介

    声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢! 一.  概述 ALSA是Advanced Linux Sound Architecture ...

  6. Linux ALSA声卡驱动之一:ALSA架构简介【转】

    本文转载自:http://blog.csdn.net/droidphone/article/details/6271122 声明:本博内容均由http://blog.csdn.net/droidpho ...

  7. Linux ALSA声卡驱动之三:PCM设备的创建

    声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢! 1. PCM是什么 模数转换 模拟信号经过pcm(脉冲编码调制)后为pcm数据: PCM是 ...

  8. Linux ALSA声卡驱动之四:Control设备的创建

    声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢! Control接口 Control接口主要让用户空间的应用程序(alsa-lib)可以访问 ...

  9. 36、ALSA声卡驱动和应用

    (注意:内核上电的时候会把一些没运行的控制器模块的时钟都关掉,所有在写驱动的时候需要在使用的使用使用clk_get和clk_enable使能时钟) (说明:与ALSA声卡对应的是OSS架构,第二期视频 ...

随机推荐

  1. oracle文件 结构01

    1.减少数据的冗余(例如使用id) 2.保证数据库一致性 关联表越多越慢 主机名 hosts 文件 ntp 时间同步

  2. EF-基础用法

    一丶LINQ TO SQL 语法 基本格式:  from c in 表名 where 条件 select c 二丶LINQ简介 LINQ是Language Integrated Query的简称,它是 ...

  3. 14: curl#6 - "Could not resolve host: mirrorlist.centos.org; 未知的错误"

    14: curl#6 - "Could not resolve host: mirrorlist.centos.org; 未知的错误" One of the configured ...

  4. Android 项目Log日志输出优化

    概述 Android开发过程中经常需要向控制台输出日志信息,有些人还在用Log.i(tag,msg)的形式或者system.out.println(msg)方式吗?本篇文章对日志信息输出进行优化,以达 ...

  5. 线性DP LIS浅谈

    LIS问题 什么是LIS? 百度百科 最长上升子序列(Longest Increasing Subsequence,LIS),在计算机科学上是指一个序列中最长的单调递增的子序列. 怎么求LIS? O( ...

  6. Python学习-字符串函数操作1

    字符串的函数操作 capitalize():可以将字符串首字母变为大写 返回值:首字符大写后的新字符串 str = "liu" print(str.capitalize()); / ...

  7. Python学习第二阶段day1 内置函数,序列化,软件目录开发规范

    内置函数 1.abs()  求绝对值 2.all()    所有元素为真才返回真 all( [1,1,2,3,-1] ) 值为True 3.any()   所有元素为假才返回假  any([0,0,0 ...

  8. 【Codeforces 1030D】Vasya and Triangle

    [链接] 我是链接,点我呀:) [题意] 题意 [题解] 参考这篇题解:https://blog.csdn.net/mitsuha_/article/details/82825862 为什么可以保证m ...

  9. noip模拟赛 abcd

    [问题描述]有4个长度为N的数组a,b,c,d.现在需要你选择N个数构成数组e,数组e满足a[i]≤e[i]≤b[i]以及 并且使得 最大.[输入格式]输入文件名为abcd.in.输入文件共 N+1 ...

  10. graylog 市场

    https://marketplace.graylog.org/addons/246dc332-7da7-4016-b2f9-b00f722a8e79