前面一節的內容我們提到,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驅動的初始化工作,整個過程可以用一下的序列圖表示:

 alsa-6-3-1  

                                                                               圖3.1  基於3.0內核  soc_probe序列圖

 

下面的序列圖是本文章第一個版本,基於內核2.6.35,大家也可以參考一下兩個版本的差異:

 alsa-6-3-2  

                                                                               圖3.2  基於2.6.35  soc_probe序列圖
 

 

/*****************************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請注明出處,謝謝!
原文出處
http://blog.csdn.net/droidphone/article/details/7231605
/*****************************************************************************************************/

 

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 立你斯 的頭像
    立你斯

    立你斯學習記錄

    立你斯 發表在 痞客邦 留言(0) 人氣()