Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2105503
  • 博文数量: 389
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 4126
  • 用 户 组: 普通用户
  • 注册时间: 2016-07-04 13:04
文章分类

全部博文(389)

文章存档

2020年(4)

2019年(24)

2018年(135)

2017年(158)

2016年(68)

我的朋友

分类: 嵌入式

2016-09-29 15:25:11

前面一节的内容我们提到,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序列图

阅读(1113) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~