前面一節的內容我們提到,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,我們關注模塊的初始化函數:
[cpp]?view plain?copy
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,他的定義如下:
[cpp]?view plain?copy
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。
還是先從模塊的入口看起:
[cpp]?view plain?copy
static?int?__init?snd_soc_init(void)??
{??
......??
return?platform_driver_register(&soc_driver);??
}??
soc_driver的定義如下:
[cpp]?view plain?copy
/*?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的綁定工作,下只是代碼的部分選節,詳細的代碼請直接參考完整的代碼樹。
[cpp]?view plain?copy
/*?bind?DAIs?*/??
for?(i?=?0;?i?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函數創建聲卡實例:?
[cpp]?view plain?copy
/*?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函數:
[cpp]?view plain?copy
/*?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?num_links;?i++)?{??
ret?=?soc_probe_dai_link(card,?i,?order);??
if?(ret?0)?{??
pr_err("asoc:?failed?to?instantiate?card?%s:?%d ",??
card->name,?ret);??
goto?probe_dai_err;??
}??
}??
}??
for?(i?=?0;?i?num_aux_devs;?i++)?{??
ret?=?soc_probe_aux_dev(card,?i);??
if?(ret?0)?{??
pr_err("asoc:?failed?to?add?auxiliary?devices?%s:?%d ",??
card->name,?ret);??
goto?probe_aux_dev_err;??
}??
}??
在上面的soc_probe_dai_link()函數中做了比較多的事情,把他展開繼續討論:
[cpp]?view plain?copy
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邏輯設備。現在把該函數的部分代碼也貼出來:
?
[cpp]?view plain?copy
/*?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 ");??
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驅動的聲卡注冊函數對聲卡進行注冊:
[cpp]?view plain?copy
if?(card->late_probe)?{??
ret?=?card->late_probe(card);??
if?(ret?0)?{??
dev_err(card->dev,?"%s?late_probe()?failed:?%d ",??
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 ",?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序列圖
?
評論
查看更多