Linux音频驱动-ASOC之Machine

时间:2021-11-13 04:03:41

概述

在ASOC小节中描述了整个ASOC的架构,其中Machine是ASOC架构中的关键部件,没有Machine部件,单独的Codec和Platform是无法工作的。因此本节则先从Machine部分开始,那应该如何开始呢? 答案当然是从代码入手,先进入ASOC在kernel中的位置:  kernel/sound/soc下
[cpp]  view plain  copy
  1. root@test:~/test/kernel/sound/soc$ ls  
  2. adi    au1x  blackfin  codecs   dwc  generic  jz4740   kirkwood  mxs     omap  rockchip  sh    soc-cache.c     soc-core.c  soc-devres.c                 soc-io.c    soc-pcm.c    spear  txx9  
  3. atmel  bcm   cirrus    davinci  fsl  intel    Kconfig  Makefile  nuc900  pxa   samsung   sirf  soc-compress.c  soc-dapm.c  soc-generic-dmaengine-pcm.c  soc-jack.c  soc-utils.c  tegra  ux500  
此目录下就是当前支持ASOC架构的平台,在这里以samsung架构做为参考。kernel版本: 3.18

Machine代码分析

samsung平台的machine代码选择为:  s3c24xx_uda134x.c

此代码先注册平台驱动s3c24xx_uda134x_driver, 当平台驱动和平台设备(以前在arch下,目前在dt中配置)的名字想匹配的时候,就会调用平台驱动中的probe函数s3c24xx_uda134x_probe。

[cpp]  view plain  copy
  1. s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);  
  2. if (!s3c24xx_uda134x_snd_device) {  
  3.     printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "  
  4.            "Unable to register\n");  
  5.     return -ENOMEM;  
  6. }  
  7.   
  8. platform_set_drvdata(s3c24xx_uda134x_snd_device,&snd_soc_s3c24xx_uda134x);  
此出分配名字为"soc-audio"的平台设备,然后将snd_soc_s3c24xx_uda134x设置到平台设备的dev->driver_data中。关于snd_soc_s3c24xx_uda134x结构在后面说明。

既然此处注册"soc-audio"的设备,就会存在名字为"soc-audio"的驱动,搜索"soc-audio",就会发现在soc-core.c中存在。
[cpp]  view plain  copy
  1. /* ASoC platform driver */  
  2. static struct platform_driver soc_driver = {  
  3.     .driver     = {  
  4.         .name       = "soc-audio",  
  5.         .owner      = THIS_MODULE,  
  6.         .pm     = &snd_soc_pm_ops,  
  7.     },  
  8.     .probe      = soc_probe,  
  9.     .remove     = soc_remove,  
  10. };  
  11.   
  12. static int __init snd_soc_init(void)  
  13. {  
  14.     snd_soc_util_init();  
  15.   
  16.     return platform_driver_register(&soc_driver);  
  17. }  
当platform_device和platform_driver相匹配的话,就会调用soc_probe函数。
[cpp]  view plain  copy
  1. static int soc_probe(struct platform_device *pdev)  
  2. {  
  3.     struct snd_soc_card *card = platform_get_drvdata(pdev);  
  4.   
  5.     /* 
  6.      * no card, so machine driver should be registering card 
  7.      * we should not be here in that case so ret error 
  8.      */  
  9.     if (!card)  
  10.         return -EINVAL;  
  11.   
  12.     dev_warn(&pdev->dev,  
  13.          "ASoC: machine %s should use snd_soc_register_card()\n",  
  14.          card->name);  
  15.   
  16.     /* Bodge while we unpick instantiation */  
  17.     card->dev = &pdev->dev;  
  18.   
  19.     return snd_soc_register_card(card);  
  20. }  
此处会调用snd_soc_register_card,会在ASOC core中注册一个card。 此处的card就是snd_soc_s3c24xx_uda134x结构。接下来谈论此结构的作用。

[cpp]  view plain  copy
  1. static struct snd_soc_ops s3c24xx_uda134x_ops = {  
  2.     .startup = s3c24xx_uda134x_startup,  
  3.     .shutdown = s3c24xx_uda134x_shutdown,  
  4.     .hw_params = s3c24xx_uda134x_hw_params,  
  5. };  
  6.   
  7. static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {  
  8.     .name = "UDA134X",  
  9.     .stream_name = "UDA134X",  
  10.     .codec_name = "uda134x-codec",  
  11.     .codec_dai_name = "uda134x-hifi",  
  12.     .cpu_dai_name = "s3c24xx-iis",  
  13.     .ops = &s3c24xx_uda134x_ops,  
  14.     .platform_name  = "s3c24xx-iis",  
  15. };  
  16.   
  17. static struct snd_soc_card snd_soc_s3c24xx_uda134x = {  
  18.     .name = "S3C24XX_UDA134X",  
  19.     .owner = THIS_MODULE,  
  20.     .dai_link = &s3c24xx_uda134x_dai_link,  
  21.     .num_links = 1,  
  22. };  
其中dai_link结构就是用作连接platform和codec的,指明到底用那个codec,那个platfrom。那是通过什么指定的?  如果有兴趣可以详细看snd_soc_dai_link的注释,此注释写的非常清楚。
[cpp]  view plain  copy
  1. struct snd_soc_dai_link {  
  2.     /* config - must be set by machine driver */  
  3.     const char *name;           /* Codec name */  
  4.     const char *stream_name;        /* Stream name */  
  5.     /* 
  6.      * You MAY specify the link's CPU-side device, either by device name, 
  7.      * or by DT/OF node, but not both. If this information is omitted, 
  8.      * the CPU-side DAI is matched using .cpu_dai_name only, which hence 
  9.      * must be globally unique. These fields are currently typically used 
  10.      * only for codec to codec links, or systems using device tree. 
  11.      */  
  12.     const char *cpu_name;  
  13.     struct device_node *cpu_of_node;  
  14.     /* 
  15.      * You MAY specify the DAI name of the CPU DAI. If this information is 
  16.      * omitted, the CPU-side DAI is matched using .cpu_name/.cpu_of_node 
  17.      * only, which only works well when that device exposes a single DAI. 
  18.      */  
  19.     const char *cpu_dai_name;  
  20.     /* 
  21.      * You MUST specify the link's codec, either by device name, or by 
  22.      * DT/OF node, but not both. 
  23.      */  
  24.     const char *codec_name;  
  25.     struct device_node *codec_of_node;  
  26.     /* You MUST specify the DAI name within the codec */  
  27.     const char *codec_dai_name;  
  28.   
  29.     struct snd_soc_dai_link_component *codecs;  
  30.     unsigned int num_codecs;  
  31.   
  32.     /* 
  33.      * You MAY specify the link's platform/PCM/DMA driver, either by 
  34.      * device name, or by DT/OF node, but not both. Some forms of link 
  35.      * do not need a platform. 
  36.      */  
  37.     const char *platform_name;  
  38.     struct device_node *platform_of_node;  
  39.     int be_id;  /* optional ID for machine driver BE identification */  
  40.   
  41.     const struct snd_soc_pcm_stream *params;  
  42.   
  43.     unsigned int dai_fmt;           /* format to set on init */  
  44.   
  45.     enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */  
  46.   
  47.     /* Keep DAI active over suspend */  
  48.     unsigned int ignore_suspend:1;  
  49.   
  50.     /* Symmetry requirements */  
  51.     unsigned int symmetric_rates:1;  
  52.     unsigned int symmetric_channels:1;  
  53.     unsigned int symmetric_samplebits:1;  
  54.   
  55.     /* Do not create a PCM for this DAI link (Backend link) */  
  56.     unsigned int no_pcm:1;  
  57.   
  58.     /* This DAI link can route to other DAI links at runtime (Frontend)*/  
  59.     unsigned int dynamic:1;  
  60.   
  61.     /* DPCM capture and Playback support */  
  62.     unsigned int dpcm_capture:1;  
  63.     unsigned int dpcm_playback:1;  
  64.   
  65.     /* pmdown_time is ignored at stop */  
  66.     unsigned int ignore_pmdown_time:1;  
  67.   
  68.     /* codec/machine specific init - e.g. add machine controls */  
  69.     int (*init)(struct snd_soc_pcm_runtime *rtd);  
  70.   
  71.     /* optional hw_params re-writing for BE and FE sync */  
  72.     int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,  
  73.             struct snd_pcm_hw_params *params);  
  74.   
  75.     /* machine stream operations */  
  76.     const struct snd_soc_ops *ops;  
  77.     const struct snd_soc_compr_ops *compr_ops;  
  78.   
  79.     /* For unidirectional dai links */  
  80.     bool playback_only;  
  81.     bool capture_only;  
  82. };  
.cpu_dai_name:      用于指定cpu侧的dai名字,也就是所谓的cpu侧的数字音频接口,一般都是i2S接口。如果省略则会使用cpu_name/cou_of_name。
.codec_dai_name:  用于codec侧的dai名字,不可以省略。
.codec_name:         用于指定codec芯片。不可以省略。
.platform_name:     用于指定cpu侧平台驱动,通常都是DMA驱动,用于传输。
.ops:                         audio的相关操作函数集合。

再次回到 snd_soc_register_card函数中,继续分析Machine的作用。

1.  根据struct snd_soc_dai_link结构体的个数,此处是一个,检测下需要设置的name是否已经设置。
[cpp]  view plain  copy
  1. if (link->platform_name && link->platform_of_node) {  
  2.     dev_err(card->dev,  
  3.     "ASoC: Both platform name/of_node are set for %s\n",link->name);  
  4.     return -EINVAL;  
  5. }  
2.  分配一个struct snd_soc_pcm_runtime结构,然后根据num_links,设置card,复制dai_link等。
[cpp]  view plain  copy
  1. card->rtd = devm_kzalloc(card->dev,sizeof(struct snd_soc_pcm_runtime) *  
  2.              (card->num_links + card->num_aux_devs),GFP_KERNEL);  
  3.     if (card->rtd == NULL)  
  4.         return -ENOMEM;  
  5.     card->num_rtd = 0;  
  6.     card->rtd_aux = &card->rtd[card->num_links];  
  7.   
  8. for (i = 0; i < card->num_links; i++) {  
  9.     card->rtd[i].card = card;  
  10.     card->rtd[i].dai_link = &card->dai_link[i];  
  11.     card->rtd[i].codec_dais = devm_kzalloc(card->dev,  
  12.                 sizeof(struct snd_soc_dai *) *  
  13.                 (card->rtd[i].dai_link->num_codecs),  
  14.                 GFP_KERNEL);  
  15.     if (card->rtd[i].codec_dais == NULL)  
  16.         return -ENOMEM;  
  17. }  
3.  然后所有的重点工作全部在snd_soc_instantiate_card函数中实现。

分析 snd_soc_instantiate_card函数的实际操作:
1.   根据num_links的值,进行DAIs的bind工作。第一步先bind cpu侧的dai
[cpp]  view plain  copy
  1. cpu_dai_component.name = dai_link->cpu_name;  
  2. cpu_dai_component.of_node = dai_link->cpu_of_node;  
  3. cpu_dai_component.dai_name = dai_link->cpu_dai_name;  
  4. rtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component);  
  5. if (!rtd->cpu_dai) {  
  6.     dev_err(card->dev, "ASoC: CPU DAI %s not registered\n",  
  7.         dai_link->cpu_dai_name);  
  8.     return -EPROBE_DEFER;  
  9. }  
此处dai_link就是在machine中注册的struct snd_soc_dai_link结构体,cpu_dai_name也就是注册的name,最后通过snd_soc_find_dai接口出查找。
[cpp]  view plain  copy
  1. static struct snd_soc_dai *snd_soc_find_dai(  
  2.     const struct snd_soc_dai_link_component *dlc)  
  3. {  
  4.     struct snd_soc_component *component;  
  5.     struct snd_soc_dai *dai;  
  6.   
  7.     /* Find CPU DAI from registered DAIs*/  
  8.     list_for_each_entry(component, &component_list, list) {  
  9.         if (dlc->of_node && component->dev->of_node != dlc->of_node)  
  10.             continue;  
  11.         if (dlc->name && strcmp(component->name, dlc->name))  
  12.             continue;  
  13.         list_for_each_entry(dai, &component->dai_list, list) {  
  14.             if (dlc->dai_name && strcmp(dai->name, dlc->dai_name))  
  15.                 continue;  
  16.   
  17.             return dai;  
  18.         }  
  19.     }  
  20.   
  21.     return NULL;  
  22. }  
此函数会在component_list链表中先找到相同的name,然后在component->dai_list中查找是否有相同的dai_name。此处的component_list是在注册codec和platform中的时候设置的。会在codec和platform的时候会详细介绍。在此处找到注册的cpu_dai之后,存在snd_soc_pcm_runtime中的cpu_dai中。

2.  然后根据codec的数据,寻找codec侧的dai。
[cpp]  view plain  copy
  1. /* Find CODEC from registered CODECs */  
  2. for (i = 0; i < rtd->num_codecs; i++) {  
  3.     codec_dais[i] = snd_soc_find_dai(&codecs[i]);  
  4.     if (!codec_dais[i]) {  
  5.         dev_err(card->dev, "ASoC: CODEC DAI %s not registered\n",  
  6.             codecs[i].dai_name);  
  7.         return -EPROBE_DEFER;  
  8.     }  
  9. }  
然后将找到的codec侧的dai也同样赋值给snd_soc_pcm_runtime中的codec_dai中。

3.  在platform_list链表中查找platfrom,根据dai_link中的platform_name域。如果没有platform_name,则设置为"snd-soc-dummy"

[cpp]  view plain  copy
  1. /* if there's no platform we match on the empty platform */  
  2. platform_name = dai_link->platform_name;  
  3. if (!platform_name && !dai_link->platform_of_node)  
  4.     platform_name = "snd-soc-dummy";  
  5.   
  6. /* find one from the set of registered platforms */  
  7. list_for_each_entry(platform, &platform_list, list) {  
  8.     if (dai_link->platform_of_node) {  
  9.         if (platform->dev->of_node !=  
  10.             dai_link->platform_of_node)  
  11.             continue;  
  12.     } else {  
  13.         if (strcmp(platform->component.name, platform_name))  
  14.             continue;  
  15.     }  
  16.   
  17.     rtd->platform = platform;  
  18. }  

这样查找完毕之后,snd_soc_pcm_runtime中存储了查找到的codec, dai,  platform。

4.  接着初始化注册的codec cache,cache_init代表是否已经初始化过。
[cpp]  view plain  copy
  1. /* initialize the register cache for each available codec */  
  2. list_for_each_entry(codec, &codec_list, list) {  
  3.     if (codec->cache_init)  
  4.         continue;  
  5.     ret = snd_soc_init_codec_cache(codec);  
  6.     if (ret < 0)  
  7.         goto base_error;  
  8. }  

5.  然后调用ALSA中的创建card的函数:  snd_card_new创建一个card
[cpp]  view plain  copy
  1. /* card bind complete so register a sound card */  
  2. ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,  
  3.         card->owner, 0, &card->snd_card);  
  4. if (ret < 0) {  
  5.     dev_err(card->dev,  
  6.         "ASoC: can't create sound card for card %s: %d\n",  
  7.         card->name, ret);  
  8.     goto base_error;  
  9. }  

6.  然后依次调用各个子部件的probe函数
[cpp]  view plain  copy
  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. /* probe all components used by DAI links on this card */  
  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_link_components(card, i, order);  
  13.         if (ret < 0) {  
  14.             dev_err(card->dev,  
  15.                 "ASoC: failed to instantiate card %d\n",  
  16.                 ret);  
  17.             goto probe_dai_err;  
  18.         }  
  19.     }  
  20. }  
  21.   
  22. /* probe all DAI links on this card */  
  23. for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;  
  24.         order++) {  
  25.     for (i = 0; i < card->num_links; i++) {  
  26.         ret = soc_probe_link_dais(card, i, order);  
  27.         if (ret < 0) {  
  28.             dev_err(card->dev,  
  29.                 "ASoC: failed to instantiate card %d\n",  
  30.                 ret);  
  31.             goto probe_dai_err;  
  32.         }  
  33.     }  
  34. }  

7.  在soc_probe_link_dais函数中依次调用了cpu_dai, codec_dai侧的probe函数
[cpp]  view plain  copy
  1. /* probe the cpu_dai */  
  2. if (!cpu_dai->probed &&  
  3.         cpu_dai->driver->probe_order == order) {  
  4.     if (cpu_dai->driver->probe) {  
  5.         ret = cpu_dai->driver->probe(cpu_dai);  
  6.         if (ret < 0) {  
  7.             dev_err(cpu_dai->dev,  
  8.                 "ASoC: failed to probe CPU DAI %s: %d\n",  
  9.                 cpu_dai->name, ret);  
  10.             return ret;  
  11.         }  
  12.     }  
  13.     cpu_dai->probed = 1;  
  14. }  
  15.   
  16. /* probe the CODEC DAI */  
  17. for (i = 0; i < rtd->num_codecs; i++) {  
  18.     ret = soc_probe_codec_dai(card, rtd->codec_dais[i], order);  
  19.     if (ret)  
  20.         return ret;  

8.   最终调用到soc_new_pcm函数创建pcm设备:
[cpp]  view plain  copy
  1. if (!dai_link->params) {  
  2.         /* create the pcm */  
  3.         ret = soc_new_pcm(rtd, num);  
  4.         if (ret < 0) {  
  5.         dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",  
  6.     dai_link->stream_name, ret);  
  7.     return ret;  
  8. }  
最中此函数会调用ALSA的标准创建pcm设备的接口:  snd_pcm_new,然后会设置pcm相应的ops操作函数集合。然后调用到platform->driver->pcm_new的函数。此处不帖函数了。

9.  接着会在dapm和dai widget做相应的操作,后期会设置control参数,最终会调用到ALSA的注册card的函数snd_card_register。
[cpp]  view plain  copy
  1. ret = snd_card_register(card->snd_card);  
  2. if (ret < 0) {  
  3.     dev_err(card->dev, "ASoC: failed to register soundcard %d\n",  
  4.             ret);  
  5.     goto probe_aux_dev_err;  
  6. }  

总结:  经过Machine的驱动的注册,Machine会根据注册以"soc_audio"为名字的平台设备,然后在同名的平台的驱动的probe函数中,会根据snd_soc_dai_link结构体中的name,进行匹配查找相应的codec, codec_dai,platform, cpu_dai。找到之后将这些值全部放入结构体snd_soc_pcm_runtime的相应位置,然后注册card,依次调用codec, platform,cpu_dai侧相应的probe函数进行初始化,接着创建pcm设备,注册card到系统中。其实ASOC也就是在ALSA的基础上又再次封装了一次,让写驱动更方便,简便。

这样封装之后,就可以大大简化驱动的编写,关于Machine驱动需要做的:
1.   注册名为"soc-audio"的平台设备。
2.   分配一个struct snd_soc_card结构体,然后设置其中的dai_link。对其中的dai_link再次设置。
3.   将struct snd_soc_card结构放入到平台设备的dev的私有数据中。
4.   注册平台设备。