alsa音效卡驅動分析總結 (二)
alsa音效卡驅動分析總結
來自:http://blog.chinaunix.net/uid-20672559-id-3515392.html現在我們開始分析ASOC:
ASoC被分為Machine、Platform和Codec三大部分。其中的Machine驅動負責Platform和Codec之間的耦合和裝置或板子特定的程式碼。
看起來挺複雜,其實需要我們做的事情並不多,大部分核心已經完成。下面我們分析哪些是我們需要自己做的:
codec驅動:負責音訊解碼。這部分程式碼完全無平臺無關,裝置原廠提供,我們只需要把它加進核心編譯就好了。
platform驅動:與處理器晶片相關,這部分程式碼在該晶片商用之前方案產商提供的
machine驅動:好了,到了最關鍵的地方了,machine驅動是耦合platform和codec驅動,同時與上層互動的程式碼。由於上層是標準的alsa架構,所以下層介面肯定要做了統一,所以我很負責的告訴你,這部分由machine本身的platform驅動和platform裝置組成(請跟asoc的platform驅動區別),platform驅動核心幫我們完成了,所以你無須過多的關心你的驅動怎麼跟上層alsa怎麼衍接的問題,我們只需要註冊一個machine的platform裝置以及完成platform和codec耦合就ok
asoc的關係圖如下:(以下適應於
上圖把asoc架構顯示的淋漓盡致,如果你分析了asoc你就會發現上圖描述的結構以及函式真的一個都跑不了。
Machie:
Machine platform device:(~/sound/soc/samsung/smdk_wm8994.c)
- smdk_snd_device = platform_device_alloc("soc-audio", -1);
- if (!smdk_snd_device)
- return
- platform_set_drvdata(smdk_snd_device, &smdk);
- ret = platform_device_add(smdk_snd_device);
- staticstruct snd_soc_dai_link smdk_dai[] = {
- { /* Primary DAI i/f */
- .name = "WM8994 AIF1",
- .stream_name = "Pri_Dai",
- .cpu_dai_name = "samsung-i2s.0",
- .codec_dai_name = "wm8994-aif1",
- .platform_name = "samsung-audio",
- .codec_name = "wm8994-codec",
- .init = smdk_wm8994_init_paiftx,
- .ops = &smdk_ops,
- }, { /* Sec_Fifo Playback i/f */
- .name = "Sec_FIFO TX",
- .stream_name = "Sec_Dai",
- .cpu_dai_name = "samsung-i2s.4",
- .codec_dai_name = "wm8994-aif1",
- .platform_name = "samsung-audio",
- .codec_name = "wm8994-codec",
- .ops = &smdk_ops,
- },
- };
- staticstruct snd_soc_card smdk = {
- .name = "SMDK-I2S",
- .owner = THIS_MODULE,
- .dai_link = smdk_dai,
- .num_links = ARRAY_SIZE(smdk_dai),
- };
通過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。
snd_soc_ops連線Platform和Codec的dai_link對應的ops操作函式,本例就是smdk_ops,它只實現了hw_params函式:smdk_hw_params。
到此為止,最主要的部分machine的平臺設備註冊我們完成了。
下面我們關注machine平臺驅動部分(這部分核心不需要我們實現,但我們需要知道它是怎麼工作的)
ASoC的platform_driver在以下檔案中定義:sound/soc/soc-core.c。
還是先從模組的入口看起:
[cpp] view plaincopy
- staticint __init snd_soc_init(void)
- {
- ......
- return platform_driver_register(&soc_driver);
- }
soc_driver的定義如下:
[cpp] view plaincopy
- /* ASoC platform driver */
- staticstruct platform_driver soc_driver = {
- .driver = {
- .name = "soc-audio", //確保你註冊machine平臺裝置和它保持一致
- .owner = THIS_MODULE,
- .pm = &soc_pm_ops,
- },
- .probe = soc_probe,
- .remove = soc_remove,
- };
初始化入口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的繫結工作,下只是程式碼的部分選節,詳細的程式碼請直接參考完整的程式碼樹。
static int soc_probe(struct platform_device *pdev){ struct snd_soc_card *card =platform_get_drvdata(pdev);//別忘記了machine的platform_set_drvdata //取出snd_soc_card ............. ret =snd_soc_register_card(card);//註冊
............ }
下面我們看snd_soc_register_card()函式:
int snd_soc_register_card(struct snd_soc_card *card){。。。。。。。。 card->rtd =kzalloc(sizeof(struct snd_soc_pcm_runtime) * (card->num_links + card->num_aux_devs), GFP_KERNEL);//為snd_soc_pcm_runtime陣列申請記憶體,每一個dai_link對應一個snd_soc_pcm_runtime陣列單元。。。。。。。。 for (i = 0; i <card->num_links; i++) card->rtd[i].dai_link= &card->dai_link[i];//把snd_soc_card中的dai_link複製到相應的snd_soc_pcm_runtime
。。。。。。。。
snd_soc_instantiate_cards();//將呼叫snd_soc_instantiate_card()//最為重要}
下面我們分析snd_soc_instantiate_card()函式:
static void snd_soc_instantiate_card(struct snd_soc_card*card){。。。。。。。。 if (card->instantiated) { //判斷該卡是否已經例項化,如果是就返回 mutex_unlock(&card->mutex); return; } /* bind DAIs */ for (i = 0; i <card->num_links; i++) //否則遍例每一對dai_link,進行codec,flatrom,dai的繫結工作 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驅動的資訊。
*****************************************************************************************************************************************************************/ /* bind completed ? */ if (card->num_rtd !=card->num_links) { mutex_unlock(&card->mutex); return; }。。。。。。。。。 /* card bind complete soregister a sound card */ ret =snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, card->owner,0, &card->snd_card);
。。。。。。。。。 card->snd_card->dev =card->dev; card->dapm.bias_level =SND_SOC_BIAS_OFF; card->dapm.dev =card->dev; card->dapm.card = card; list_add(&card->dapm.list,&card->dapm_list);//初始化codec快取,建立音效卡例項。。。。。。。。。。。。。
//下面是最重要的probe匹配工作 if (card->probe) { ret =card->probe(card); if (ret < 0) gotocard_probe_error; }。。。。。。。。。 ret =soc_probe_dai_link(card, i, order);//主要的耦合連結工作在此函式完成。。。。。。。。。 ret =soc_probe_aux_dev(card, i);。。。。。。。。。 if (card->late_probe) {//最後的音效卡初始化工作, ret =card->late_probe(card); }。。。。。。。。。 ret =snd_card_register(card->snd_card);//然後呼叫標準的alsa驅動的音效卡函式進行音效卡註冊
。。。。。。。。。 }到這裡音效卡已經註冊好了,音效卡可以正常工作了,我們再分析最後一個函式,也就是它們是怎麼匹配的 soc_probe_dai_link():
static int soc_probe_dai_link(struct snd_soc_card *card,int num, int order){。。。。。。。。。。 /* probe the cpu_dai */ if (!cpu_dai->probed&& cpu_dai->driver->probe_order== order) { if(!try_module_get(cpu_dai->dev->driver->owner)) return -ENODEV; if(cpu_dai->driver->probe) { ret =cpu_dai->driver->probe(cpu_dai); if (ret < 0){ printk(KERN_ERR"asoc: failed to probe CPU DAI %s\n", cpu_dai->name); module_put(cpu_dai->dev->driver->owner); returnret; } } cpu_dai->probed = 1; /* mark cpu_dai asprobed and add to card dai list */ list_add(&cpu_dai->card_list,&card->dai_dev_list); } /* probe the CODEC */ if (!codec->probed&& codec->driver->probe_order== order) { ret =soc_probe_codec(card, codec); if (ret < 0) return ret; } /* probe the platform */ if (!platform->probed&& platform->driver->probe_order== order) { ret = soc_probe_platform(card,platform); if (ret < 0) return ret; } /* probe the CODEC DAI */ if (!codec_dai->probed&& codec_dai->driver->probe_order == order) { if(codec_dai->driver->probe) { ret =codec_dai->driver->probe(codec_dai); if (ret < 0){ printk(KERN_ERR"asoc: failed to probe CODEC DAI %s\n", codec_dai->name); returnret; } } /* mark codec_dai asprobed and add to card dai list */ codec_dai->probed =1; list_add(&codec_dai->card_list,&card->dai_dev_list); } /* complete DAI probe duringlast probe */ if (order !=SND_SOC_COMP_ORDER_LAST) return 0;
。。。。。。。。。。 /* create the pcm */ ret = soc_new_pcm(rtd, num);//如果上面都匹配成功將建立標準的alsa的pcm邏輯裝置。。。。。。。。。。 }好了,到此為止我們最主要的部分machine部分分析完成了。