前面一節的內容我們提到,ASoC被分為Machine、Platform和Codec三大部分,其中的Machine驅動負責Platform和Codec之間的耦合以及部分和裝置或板子特定的代碼,再次引用上一節的內容:Machine驅動負責處理機器特有的一些控制項和音頻事件(例如,當播放音頻時,需要先行開啟一個放大器);單獨的Platform和Codec驅動是不能工作的,它必須由Machine驅動把它們結合在一起才能完成整個裝置的音頻處理工作。
ASoC的一切都從Machine驅動開始,包括音效卡的註冊,綁定Platform和Codec驅動等等,下面就讓我們從Machine驅動開始討論吧。
/********************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請註明出處,謝謝。
/********************************************************************************************/
1. 註冊Platform Device
ASoC把音效卡註冊為Platform Device,我們以裝配有WM8994的一款Samsung的開發板SMDK為例子做說明,WM8994是一顆Wolfson生產的多功能Codec晶片。
代碼的位於:/sound/soc/samsung/smdk_wm8994.c,我們關注模組的初始化函數:
static int __init smdk_audio_init(void){int ret;smdk_snd_device = platform_device_alloc("soc-audio", -1);if (!smdk_snd_device)return -ENOMEM;platform_set_drvdata(smdk_snd_device, &smdk);ret = platform_device_add(smdk_snd_device);if (ret)platform_device_put(smdk_snd_device);return ret;}
由此可見,模組初始化時,註冊了一個名為soc-audio的Platform裝置,同時把smdk設到platform_device結構的dev.drvdata欄位中,這裡引出了第一個資料結構snd_soc_card的執行個體smdk,他的定義如下:
static struct 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,},};static struct 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,這些註冊的組件都是在另外相應的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。
還是先從模組的入口看起:
static int __init snd_soc_init(void){ ......return platform_driver_register(&soc_driver);}
soc_driver的定義如下:
/* ASoC platform driver */static struct platform_driver soc_driver = {.driver= {.name= "soc-audio",.owner= THIS_MODULE,.pm= &soc_pm_ops,},.probe= soc_probe,.remove= soc_remove,};
我們看到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的綁定工作,下只是代碼的部分選節,詳細的代碼請直接參考完整的代碼樹。
/* bind DAIs */for (i = 0; i < card->num_links; i++)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函數建立音效卡執行個體:
/* card bind complete so register 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);
然後,依次調用各個子結構的probe函數:
/* initialise the sound card only once */if (card->probe) {ret = card->probe(card);if (ret < 0)goto card_probe_error;}/* early DAI link probe */for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;order++) {for (i = 0; i < card->num_links; i++) {ret = soc_probe_dai_link(card, i, order);if (ret < 0) {pr_err("asoc: failed to instantiate card %s: %d\n", card->name, ret);goto probe_dai_err;}}}for (i = 0; i < card->num_aux_devs; i++) {ret = soc_probe_aux_dev(card, i);if (ret < 0) {pr_err("asoc: failed to add auxiliary devices %s: %d\n", card->name, ret);goto probe_aux_dev_err;}}
在上面的soc_probe_dai_link()函數中做了比較多的事情,把他展開繼續討論:
static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order){ ....../* set default power off timeout */rtd->pmdown_time = pmdown_time;/* probe the cpu_dai */if (!cpu_dai->probed &&cpu_dai->driver->probe_order == order) {if (cpu_dai->driver->probe) {ret = cpu_dai->driver->probe(cpu_dai);}cpu_dai->probed = 1;/* mark cpu_dai as probed 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);}/* probe the platform */if (!platform->probed &&platform->driver->probe_order == order) {ret = soc_probe_platform(card, platform);}/* 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);}/* mark codec_dai as probed and add to card dai list */codec_dai->probed = 1;list_add(&codec_dai->card_list, &card->dai_dev_list);}/* complete DAI probe during last probe */if (order != SND_SOC_COMP_ORDER_LAST)return 0;ret = soc_post_component_init(card, codec, num, 0);if (ret)return ret; ....../* create the pcm */ret = soc_new_pcm(rtd, num); ........return 0;}
該函數出了挨個調用了codec,dai和platform驅動的probe函數外,在最後還調用了soc_new_pcm()函數用於建立標準alsa驅動的pcm邏輯裝置。現在把該函數的部分代碼也貼出來:
/* create a new pcm */int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num){......struct snd_pcm_ops *soc_pcm_ops = &rtd->ops;soc_pcm_ops->open= soc_pcm_open;soc_pcm_ops->close= soc_pcm_close;soc_pcm_ops->hw_params= soc_pcm_hw_params;soc_pcm_ops->hw_free= soc_pcm_hw_free;soc_pcm_ops->prepare= soc_pcm_prepare;soc_pcm_ops->trigger= soc_pcm_trigger;soc_pcm_ops->pointer= soc_pcm_pointer;ret = snd_pcm_new(rtd->card->snd_card, new_name,num, playback, capture, &pcm);/* DAPM dai link stream work */INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);rtd->pcm = pcm;pcm->private_data = rtd;if (platform->driver->ops) {soc_pcm_ops->mmap = platform->driver->ops->mmap;soc_pcm_ops->pointer = platform->driver->ops->pointer;soc_pcm_ops->ioctl = platform->driver->ops->ioctl;soc_pcm_ops->copy = platform->driver->ops->copy;soc_pcm_ops->silence = platform->driver->ops->silence;soc_pcm_ops->ack = platform->driver->ops->ack;soc_pcm_ops->page = platform->driver->ops->page;}if (playback)snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops);if (capture)snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops);if (platform->driver->pcm_new) {ret = platform->driver->pcm_new(rtd);if (ret < 0) {pr_err("asoc: platform pcm constructor failed\n");return ret;}}pcm->private_free = platform->driver->pcm_free;return ret;}
該函數首先初始化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驅動的音效卡註冊函數對音效卡進行註冊:
if (card->late_probe) {ret = card->late_probe(card);if (ret < 0) {dev_err(card->dev, "%s late_probe() failed: %d\n",card->name, ret);goto probe_aux_dev_err;}}snd_soc_dapm_new_widgets(&card->dapm);if (card->fully_routed)list_for_each_entry(codec, &card->codec_dev_list, card_list)snd_soc_dapm_auto_nc_codec_pins(codec);ret = snd_card_register(card->snd_card);if (ret < 0) {printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);goto probe_aux_dev_err;}
至此,整個Machine驅動的初始化已經完成,通過各個子結構的probe調用,實際上,也完成了部分Platfrom驅動和Codec驅動的初始化工作,整個過程可以用一下的順序圖表表示:
圖3.1 基於3.0核心 soc_probe順序圖表
下面的順序圖表是本文章第一個版本,基於核心2.6.35,大家也可以參考一下兩個版本的差異:
圖3.2 基於2.6.35 soc_probe順序圖表