ALSA System on Chip(ASOC)

此文档仅作为开发随笔记录文档,可作为正式文档做材料参考,但不做正式文档。

 

Written bywolfgang huang(stillinux@gmail.com)

 

此类文档仅记录Android4.1.2+Kernel2.6.37+OMAP3730平台ALSA开发及内核要点,备注好资料应用,以供后续开发人员快速入手,也可作为科普类资料,供其他相关人员学习。

 

ALSA System on Chip(ASOC)


本系列文档的目标是使读者从安卓HAL一直走到硬件层的处理流程,使Kernel的ALSA构架内部细节透露出来。但是由于鄙人水平有限,难免有错误理解,如有错误,请联系,立即改正。

 

下面我们细述Linux音频子系统中的ASOC,对于整体的框架作图如下。



我们接下来分块的对ASOC的子模块在内核中如何启动,注册等运作流程。

 

在linux设备模型下,device与相应的driver相匹配,从而支持linux透过drvier对于其绑定的device做到控制管理。故使对应的设备要在linux下进行良好的工作,则必须正确加载device和driver。


Platform模块:

对于platform的设备启动流程如下:

omap_init_audio->platform_device_register(&omap_pcm);

将omap_pcm的设备注册到platform总线的设备链表上。

 

对于platform的驱动,则有:

snd_omap_pcm_init->platform_driver_register(&omap_pcm_driver);

将omap_pcm_driver注册到platform总线的驱动链表上。

 

由linux驱动模型,当注册driver或device时,都会到其隶属总线的对应链表上去查找匹配的驱动或设备(linux更相信设备)。这样匹配后调用对应的driver->probe,则调用到我们的omap_pcm_probe,其调用snd_soc_register_platform,注册omap_soc_platform到ASOC的platform_list中,且由于device中对于其id设置为-1,则platform的名称设置为dev->driver->name。

 

Note:linux驱动模型下,platform总线绑定的依据有三种,of设备树的dtb方式,还有id_table的方式,最后就是默认的name比对方式。对于其他总线,诸如SPI用modalias比对等方式,此处不做过多细述。

 

Cpu_dai模块:

对于cpu_dai启动流程如下:

omap_init_audio->platform_device_register(&omap_mcbsp1);

将omap_mcbsp的设备注册到platform总线的设备链表上。

 

对于cpu_dai的驱动,则有:

snd_omap_mcbsp_init->platform_driver_register(&asoc_mcbsp_driver);

将asoc_mcbsp_driver注册到platform总线的驱动链表上。

 

由上面分析,我们知道驱动模型绑定后,根据名称都为omap-mcbsp-dai匹配后,调用到其驱动的probe接口,即asoc_mcbsp_probe。其调用snd_soc_register_dai,将omap_mcbsp_dai注册到ASOC的dai_list链表上。

 

 

Codec模块和Codec_dai模块:

在我们使用的OMAP平台上,codec使用的集成的TPS65951,其设备加载过程较为复杂,下面进行细述。

其设备的加载流程:

devkit8000_init->devkit8000_i2c_init->omap_register_i2c_bus(..devkit8000_i2c1_boardinfo)。

tps65951,使用的是twl4030兼容的驱动。

twl_init->i2c_add_driver(&twl_driver)<throughid_table(与上面的boardinfo匹配)>

->twl_probe->add_children->add_child<has_codec成立的条件下>

->platform_device_add()<name=”twl4030-audio”>。

将tps65951中的音频声卡codec设备加入到platform总线的设备链表。

但是此处并没有完成codec资源的解析,其又调用一次匹配后,进行资源的分块解析。

该过程为:

twl4030_codec_init->platform_driver_register(&twl4030_codec_driver);

与上面注册的codec设备匹配后,运行其驱动的probe,即twl4030_codec_probe,

其完成对于codec时钟的设置后,调用mfd_add_devices将获得资源重组到新的platform_device中,并将其加入到platform总线上,其名称为twl4030_codec。此时才是最终的tps65951设备声卡加入的真正设备资源。

对于其真正的设备资源,对应的驱动有:

twl4030_modinit-> platform_driver_register(&twl4030_codec_driver);<name=”twl4030_codec”>。

则匹配后调用其驱动的probe,即twl4030_codec_probe(与上面的有区别)。其调用snd_soc_register_codec,注册了对应的

 

则根据驱动模型绑定后,我们知道运行其驱动的probe,即twl4030_codec_probe,其调用snd_soc_register_codec,注册soc_codec_dev_twl4030到ASOC的codec_list,并将对应的twl4030_dai注册到ASOC的dai_list中,twl4030_dai有hifi和voice两种模式,即codec支持这两种音频数据流。其最终根据ASOC的配置环境snd_soc_dai_link,选用对应的配置模式。

 

ASOC(Machine):

最后对应ASOC整体的框架注册过程:

对应其设备注册流程如下:

omap3beagle_soc_init-> platform_device_add(omap3beagle_snd_device);<name=”soc-audio”>

 

对应的驱动注册则有:

snd_soc_init-> platform_driver_register(&soc_driver);根据驱动模型绑定后,则会调用到soc_probe函数。其获取ASOC定义的snd_soc_card配置数据snd_soc_omap3beagle。

snd_soc_card中的成员num_link定义有几个snd_soc_pcm_runtime,对于该处为1,成员dai_link,则定义了该snd_card对应的配置环境数据。

 

其结构定义如下:

[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.     const char *codec_name;     /* for multi-codec */  
  6.     const char *platform_name;  /* for multi-platform */  
  7.     const char *cpu_dai_name;  
  8.     const char *codec_dai_name;  
  9.   
  10.     /* Keep DAI active over suspend */  
  11.     unsigned int ignore_suspend:1;  
  12.   
  13.     /* Symmetry requirements */  
  14.     unsigned int symmetric_rates:1;  
  15.   
  16.     /* codec/machine specific init - e.g. add machine controls */  
  17.     int (*init)(struct snd_soc_pcm_runtime *rtd);  
  18.   
  19.     /* machine stream operations */  
  20.     struct snd_soc_ops *ops;  
  21. };  

根据注释,在观察我们的具体配置:

[cpp]  view plain  copy
  1. static struct snd_soc_dai_link omap3beagle_dai = {  
  2.     .name = "TWL4030",  
  3.     .stream_name = "TWL4030",       //runtime名称  
  4.     .cpu_dai_name = "omap-mcbsp-dai.1"//cpu_dai<I2S>  
  5.     .platform_name = "omap-pcm-audio",  //platform  
  6.     .codec_dai_name = "twl4030-hifi",   //codec_dai,选择高保真音频数据流格式  
  7.     .codec_name = "twl4030-codec",      //tps65951  
  8.     .ops = &omap3beagle_ops,  
  9. };  

soc_probe获取对应的snd_soc_card配置数据后,调用snd_soc_register_card。

[cpp]  view plain  copy
  1. static int snd_soc_register_card(struct snd_soc_card *card)  
  2. {  
  3.     int i;  
  4.   
  5.     if (!card->name || !card->dev)  
  6.         return -EINVAL;  
  7.   
  8.     card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) * card->num_links,  
  9.             GFP_KERNEL);  
  10.     if (card->rtd == NULL)  
  11.         return -ENOMEM;  
  12.   
  13.     for (i = 0; i < card->num_links; i++)  
  14.         card->rtd[i].dai_link = &card->dai_link[i];  
  15.   
  16.     INIT_LIST_HEAD(&card->list);  
  17.     card->instantiated = 0;  
  18.     mutex_init(&card->mutex);  
  19.   
  20.     mutex_lock(&client_mutex);  
  21.     list_add(&card->list, &card_list);  
  22.     snd_soc_instantiate_cards();  
  23.     mutex_unlock(&client_mutex);  
  24.   
  25.     dev_dbg(card->dev, "Registered card '%s'\n", card->name);  
  26.   
  27.     return 0;  
  28. }  


根据配置数据的num_links,分配对应的snd_soc_pcm_runtime,并绑定各自snd_soc_pcm_runtime与dai_link,接着将加入的snd_soc_card加入到card_list,ASOC最多支持八个snd_soc_card。最后调用snd_soc_instantiate_cards。该函数在其他的注册接口也有调用,但是其只有在dai_link中的组件全部都组建完成才能继续往下走。故在此处综述。

snd_soc_instantiate_cards:遍历card_list,依次调用snd_soc_instantiate_card。


[cpp]  view plain  copy
  1. static void snd_soc_instantiate_card(struct snd_soc_card *card)  
  2. {  
  3.     struct platform_device *pdev = to_platform_device(card->dev);  
  4.     int ret, i;  
  5.   
  6.     mutex_lock(&card->mutex);  
  7.   
  8.     if (card->instantiated) {  
  9.         mutex_unlock(&card->mutex);  
  10.         return;  
  11.     }  
  12.   
  13.     /* bind DAIs */  
  14.     for (i = 0; i < card->num_links; i++)  
  15.         soc_bind_dai_link(card, i);  
  16.   
  17.     /* bind completed ? */  
  18.     if (card->num_rtd != card->num_links) {  
  19.         mutex_unlock(&card->mutex);  
  20.         return;  
  21.     }  
  22.   
  23.     /* 
  24.      * #define SNDRV_DEFAULT_IDX1      (-1) 
  25.      * #define SNDRV_DEFAULT_STR1      NULL 
  26.      */  
  27.   
  28.     /* card bind complete so register a sound card */  
  29.     ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,  
  30.             card->owner, 0, &card->snd_card);  
  31.     if (ret < 0) {  
  32.         printk(KERN_ERR "asoc: can't create sound card for card %s\n",  
  33.             card->name);  
  34.         mutex_unlock(&card->mutex);  
  35.         return;  
  36.     }  
  37.     card->snd_card->dev = card->dev;  
  38.   
  39. #ifdef CONFIG_PM  
  40.     /* deferred resume work */  
  41.     INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);  
  42. #endif  
  43.   
  44.     /* initialise the sound card only once */  
  45.     if (card->probe) {  
  46.         ret = card->probe(pdev);  
  47.         if (ret < 0)  
  48.             goto card_probe_error;  
  49.     }  
  50.   
  51.     for (i = 0; i < card->num_links; i++) {  
  52.         ret = soc_probe_dai_link(card, i);  
  53.         if (ret < 0) {  
  54.             pr_err("asoc: failed to instantiate card %s: %d\n",  
  55.                    card->name, ret);  
  56.             goto probe_dai_err;  
  57.         }  
  58.     }  
  59.   
  60.     snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),  
  61.          "%s",  card->name);  
  62.     snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),  
  63.          "%s", card->name);  
  64.   
  65.     ret = snd_card_register(card->snd_card);  
  66.     if (ret < 0) {  
  67.         printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);  
  68.         goto probe_dai_err;  
  69.     }  
  70.   
  71. #ifdef CONFIG_SND_SOC_AC97_BUS  
  72.     /* register any AC97 codecs */  
  73.     for (i = 0; i < card->num_rtd; i++) {  
  74.         ret = soc_register_ac97_dai_link(&card->rtd[i]);  
  75.         if (ret < 0) {  
  76.             printk(KERN_ERR "asoc: failed to register AC97 %s\n", card->name);  
  77.             while (--i >= 0)  
  78.                 soc_unregister_ac97_dai_link(&card->rtd[i]);  
  79.             goto probe_dai_err;  
  80.         }  
  81.     }  
  82. #endif  
  83.   
  84.     card->instantiated = 1;  
  85.     mutex_unlock(&card->mutex);  
  86.     return;  
  87.   
  88. probe_dai_err:  
  89.     for (i = 0; i < card->num_links; i++)  
  90.         soc_remove_dai_link(card, i);  
  91.   
  92. card_probe_error:  
  93.     if (card->remove)  
  94.         card->remove(pdev);  
  95.   
  96.     snd_card_free(card->snd_card);  
  97.   
  98.     mutex_unlock(&card->mutex);  
  99. }  

我们把该函数拆分为四段函数进行分析,分别为soc_bind_dai_link,snd_card_create,soc_probe_dai_link及snd_card_register。AC97我们在此略过,一般嵌入式设备都使用I2S。

[cpp]  view plain  copy
  1. static int soc_bind_dai_link(struct snd_soc_card *card, int num)  
  2. {  
  3.     struct snd_soc_dai_link *dai_link = &card->dai_link[num];  
  4.     struct snd_soc_pcm_runtime *rtd = &card->rtd[num];  
  5.     struct snd_soc_codec *codec;  
  6.     struct snd_soc_platform *platform;  
  7.     struct snd_soc_dai *codec_dai, *cpu_dai;  
  8.   
  9.     if (rtd->complete)  
  10.         return 1;  
  11.     dev_dbg(card->dev, "binding %s at idx %d\n", dai_link->name, num);  
  12.   
  13.     /* do we already have the CPU DAI for this link ? */  
  14.     if (rtd->cpu_dai) {  
  15.         goto find_codec;  
  16.     }  
  17.     /* no, then find CPU DAI from registered DAIs*/  
  18.     list_for_each_entry(cpu_dai, &dai_list, list) {  
  19.         //依次遍历,查找是否有dai_link按名称指定的cpu_dai  
  20.         if (!strcmp(cpu_dai->name, dai_link->cpu_dai_name)) {  
  21.   
  22.             if (!try_module_get(cpu_dai->dev->driver->owner))  
  23.                 return -ENODEV;  
  24.   
  25.             rtd->cpu_dai = cpu_dai;  
  26.             goto find_codec;  
  27.         }  
  28.     }  
  29.     dev_dbg(card->dev, "CPU DAI %s not registered\n",  
  30.             dai_link->cpu_dai_name);  
  31.   
  32. find_codec:  
  33.     /* do we already have the CODEC for this link ? */  
  34.     if (rtd->codec) {  
  35.         goto find_platform;  
  36.     }  
  37.   
  38.     /* no, then find CODEC from registered CODECs*/  
  39.     list_for_each_entry(codec, &codec_list, list) {  
  40.         //依次遍历,查找是否有dai_link按名称指定的codec  
  41.         if (!strcmp(codec->name, dai_link->codec_name)) {  
  42.             rtd->codec = codec;  
  43.   
  44.             if (!try_module_get(codec->dev->driver->owner))  
  45.                 return -ENODEV;  
  46.   
  47.             /* CODEC found, so find CODEC DAI from registered DAIs from this CODEC*/  
  48.             list_for_each_entry(codec_dai, &dai_list, list) {  
  49.                 //依次遍历,查找是否有dai_link按名称指定的codec_dai  
  50.                 if (codec->dev == codec_dai->dev &&  
  51.                         !strcmp(codec_dai->name, dai_link->codec_dai_name)) {  
  52.                     rtd->codec_dai = codec_dai;  
  53.                     goto find_platform;  
  54.                 }  
  55.             }  
  56.             dev_dbg(card->dev, "CODEC DAI %s not registered\n",  
  57.                     dai_link->codec_dai_name);  
  58.   
  59.             goto find_platform;  
  60.         }  
  61.     }  
  62.     dev_dbg(card->dev, "CODEC %s not registered\n",  
  63.             dai_link->codec_name);  
  64.   
  65. find_platform:  
  66.     /* do we already have the CODEC DAI for this link ? */  
  67.     if (rtd->platform) {  
  68.         goto out;  
  69.     }  
  70.     /* no, then find CPU DAI from registered DAIs*/  
  71.     list_for_each_entry(platform, &platform_list, list) {  
  72.         //依次遍历,查找是否有dai_link按名称指定的platform  
  73.         if (!strcmp(platform->name, dai_link->platform_name)) {  
  74.   
  75.             if (!try_module_get(platform->dev->driver->owner))  
  76.                 return -ENODEV;  
  77.   
  78.             rtd->platform = platform;  
  79.             goto out;  
  80.         }  
  81.     }  
  82.   
  83.     dev_dbg(card->dev, "platform %s not registered\n",  
  84.             dai_link->platform_name);  
  85.     return 0;  
  86.   
  87. out:  
  88.     /* mark rtd as complete if we found all 4 of our client devices */  
  89.     if (rtd->codec && rtd->codec_dai && rtd->platform && rtd->cpu_dai) {  
  90.         rtd->complete = 1;  
  91.         card->num_rtd++;  
  92.     }  
  93.     return 1;  
  94. }  

其完成的工作单调而统一,就是依次比对dai_link指定的四大模块,缺一不可,且对应于多个num_links的还需要一一绑定。找到对应的dai_link指定的模块后,就将runtime与其对应模块进行绑定。


[cpp]  view plain  copy
  1. int snd_card_create(int idx, const char *xid,  
  2.             struct module *module, int extra_size,  
  3.             struct snd_card **card_ret)  
  4. {  
  5.     struct snd_card *card;  
  6.     int err, idx2;  
  7.   
  8.     if (snd_BUG_ON(!card_ret))  
  9.         return -EINVAL;  
  10.     *card_ret = NULL;  
  11.   
  12.     if (extra_size < 0)  
  13.         extra_size = 0;  
  14.     card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);  
  15.     ……  
  16.     /* the control interface cannot be accessed from the user space until */  
  17.     /* snd_cards_bitmask and snd_cards are set with snd_card_register */  
  18.     err = snd_ctl_create(card);  
  19.     if (err < 0) {  
  20.         snd_printk(KERN_ERR "unable to register control minors\n");  
  21.         goto __error;  
  22.     }  
  23.     err = snd_info_card_create(card);  
  24.     if (err < 0) {  
  25.         snd_printk(KERN_ERR "unable to create card info\n");  
  26.         goto __error_ctl;  
  27.     }  
  28.     ……  
  29.     return err;  
  30. }  

其分配对应的snd_card,并分配对应的id,且ASOC最大支持八个snd_card。之后调用snd_ctl_create->snd_device_new(card, SNDRV_DEV_CONTROL,…);注册该snd_card绑定的mixer控制设备节点,controlCx。且其目前只是创建,在后面snd_card_register的时候注册到用户空间设备节点。该函数调用snd_info_card_create,创建/proc/asound/cardX,向用户透露信息。


[cpp]  view plain  copy
  1. static int soc_probe_dai_link(struct snd_soc_card *card, int num)  
  2. {  
  3.     struct snd_soc_dai_link *dai_link = &card->dai_link[num];  
  4.     struct snd_soc_pcm_runtime *rtd = &card->rtd[num];  
  5.     struct snd_soc_codec *codec = rtd->codec;  
  6.     struct snd_soc_platform *platform = rtd->platform;  
  7.     struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai;  
  8.     int ret;  
  9.   
  10.     dev_dbg(card->dev, "probe %s dai link %d\n", card->name, num);  
  11.   
  12.     /* config components */  
  13.     codec_dai->codec = codec;  
  14.     codec->card = card;  
  15.     cpu_dai->platform = platform;  
  16.     rtd->card = card;  
  17.     rtd->dev.parent = card->dev;  
  18.     codec_dai->card = card;  
  19.     cpu_dai->card = card;  
  20.   
  21.     /* set default power off timeout */  
  22.     rtd->pmdown_time = pmdown_time;  
  23.   
  24.     /* probe the cpu_dai */  
  25.     if (!cpu_dai->probed) {  
  26.         if (cpu_dai->driver->probe) {  
  27.             ret = cpu_dai->driver->probe(cpu_dai);  
  28.             if (ret < 0) {  
  29.                 printk(KERN_ERR "asoc: failed to probe CPU DAI %s\n",  
  30.                         cpu_dai->name);  
  31.                 return ret;  
  32.             }  
  33.         }  
  34.         cpu_dai->probed = 1;  
  35.         /* mark cpu_dai as probed and add to card cpu_dai list */  
  36.         list_add(&cpu_dai->card_list, &card->dai_dev_list);  
  37.     }  
  38.   
  39.     /* probe the CODEC */  
  40.     if (!codec->probed) {  
  41.         if (codec->driver->probe) {  
  42.             ret = codec->driver->probe(codec);  
  43.             if (ret < 0) {  
  44.                 printk(KERN_ERR "asoc: failed to probe CODEC %s\n",  
  45.                         codec->name);  
  46.                 return ret;  
  47.             }  
  48.         }  
  49.   
  50.         soc_init_codec_debugfs(codec);  
  51.   
  52.         /* mark codec as probed and add to card codec list */  
  53.         codec->probed = 1;  
  54.         list_add(&codec->card_list, &card->codec_dev_list);  
  55.     }  
  56.   
  57.     /* probe the platform */  
  58.     if (!platform->probed) {  
  59.         if (platform->driver->probe) {  
  60.             ret = platform->driver->probe(platform);  
  61.             if (ret < 0) {  
  62.                 printk(KERN_ERR "asoc: failed to probe platform %s\n",  
  63.                         platform->name);  
  64.                 return ret;  
  65.             }  
  66.         }  
  67.         /* mark platform as probed and add to card platform list */  
  68.         platform->probed = 1;  
  69.         list_add(&platform->card_list, &card->platform_dev_list);  
  70.     }  
  71.   
  72.     /* probe the CODEC DAI */  
  73.     if (!codec_dai->probed) {  
  74.         if (codec_dai->driver->probe) {  
  75.             ret = codec_dai->driver->probe(codec_dai);  
  76.             if (ret < 0) {  
  77.                 printk(KERN_ERR "asoc: failed to probe CODEC DAI %s\n",  
  78.                         codec_dai->name);  
  79.                 return ret;  
  80.             }  
  81.         }  
  82.   
  83.         /* mark cpu_dai as probed and add to card cpu_dai list */  
  84.         codec_dai->probed = 1;  
  85.         list_add(&codec_dai->card_list, &card->dai_dev_list);  
  86.     }  
  87.   
  88.     /* DAPM dai link stream work */  
  89.     INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);  
  90.   
  91.     /* now that all clients have probed, initialise the DAI link */  
  92.     if (dai_link->init) {  
  93.         ret = dai_link->init(rtd);  
  94.         if (ret < 0) {  
  95.             printk(KERN_ERR "asoc: failed to init %s\n", dai_link->stream_name);  
  96.             return ret;  
  97.         }  
  98.     }  
  99.   
  100.     /* Make sure all DAPM widgets are instantiated */  
  101.     snd_soc_dapm_new_widgets(codec);  
  102.     snd_soc_dapm_sync(codec);  
  103.   
  104.     /* register the rtd device */  
  105.     rtd->dev.release = rtd_release;  
  106.     rtd->dev.init_name = dai_link->name;  
  107.     ret = device_register(&rtd->dev);    /* TWL4030 in the /sys/devices/platform/soc-audio */  
  108.     if (ret < 0) {  
  109.         printk(KERN_ERR "asoc: failed to register DAI runtime device %d\n", ret);  
  110.         return ret;  
  111.     }  
  112.   
  113.     rtd->dev_registered = 1;  
  114.     ret = device_create_file(&rtd->dev, &dev_attr_pmdown_time);  
  115.     if (ret < 0)  
  116.         printk(KERN_WARNING "asoc: failed to add pmdown_time sysfs\n");  
  117.   
  118.     /* add DAPM sysfs entries for this codec */  
  119.     ret = snd_soc_dapm_sys_add(&rtd->dev);  
  120.     if (ret < 0)  
  121.         printk(KERN_WARNING "asoc: failed to add codec dapm sysfs entries\n");  
  122.   
  123.     /* add codec sysfs entries */  
  124.     ret = device_create_file(&rtd->dev, &dev_attr_codec_reg);  
  125.     if (ret < 0)  
  126.         printk(KERN_WARNING "asoc: failed to add codec sysfs files\n");  
  127.   
  128.     /* create the pcm */  
  129.     ret = soc_new_pcm(rtd, num);  
  130.     if (ret < 0) {  
  131.         printk(KERN_ERR "asoc: can't create pcm %s\n", dai_link->stream_name);  
  132.         return ret;  
  133.     }  
  134.   
  135.     /* add platform data for AC97 devices */  
  136.     if (rtd->codec_dai->driver->ac97_control)  
  137.         snd_ac97_dev_add_pdata(codec->ac97, rtd->cpu_dai->ac97_pdata);  
  138.   
  139.     return 0;  
  140. }  

该函数对四个模块依次调用probe接口,这些probe函数不进行细述,主要是对于声卡的初始化,并把声卡内部的控件加入到系统snd_kcontrol中,其他模块未做主要事宜或者没有probe接口。接着初始化close_delayed_work工作队列,其在关闭接口时,用于延时调用,防止pop刺耳噪音。接下来对于DAPM的初始化和同步。开始DAPM将加入的codec->dapm_widget进行分类型初始化,并绑定相关的电源操作接口,最后以snd_kcontrol提供用户控制。调用snd_soc_dapm_sync,对加入的控件分类加到上电和下电的链表中,并根据情况对不同的链表上下电处理。之后就是相关sysfs属性文件的注册,注册到sysfs文件系统中。最后调用soc_new_pcm,根据对应配置的codec_dai的packback和capture创建对应的pcm设备空间,并完成其初始化,绑定对应的操作接口,但是其也要在最后snd_card_register的时候才能注册到sysfs和devtmpfs中。

snd_card_register->snd_device_register_all:完成controlCx,pcmCxDx(p/c)的到sysfs和devtmpfs文件系统的注册。<timer在alsa_timer_init -> snd_register_device 完成,不过我们不关注>

 

 

通过上述分析,我们知道了音频子系统,用户操作的接口在内核中是经过了怎样的流程才注册到用户空间。对于具体操作这些设备节点,又会是怎样的流程我们在后续的文档进行分析。对于访问具体的控件,调整路径,电源控制,都留在后续的文档一一分析。

 

下面我们以图表的形式分析这些设备节点的注册过程。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值