ALSA音频框架理解:machine
前面一節(jié)的內(nèi)容我們提到,ASoC被分為Machine、Platform和Codec三大部分,其中的Machine驅(qū)動(dòng)負(fù)責(zé)Platform和Codec之間的耦合以及部分和設(shè)備或板子特定的代碼,再次引用上一節(jié)的內(nèi)容:Machine驅(qū)動(dòng)負(fù)責(zé)處理機(jī)器特有的一些控件和音頻事件(例如,當(dāng)播放音頻時(shí),需要先行打開一個(gè)放大器);單獨(dú)的Platform和Codec驅(qū)動(dòng)是不能工作的,它必須由Machine驅(qū)動(dòng)把它們結(jié)合在一起才能完成整個(gè)設(shè)備的音頻處理工作。
ASoC的一切都從Machine驅(qū)動(dòng)開始,包括聲卡的注冊,綁定Platform和Codec驅(qū)動(dòng)等等,下面就讓我們從Machine驅(qū)動(dòng)開始討論吧。
現(xiàn)在直接分析最普通的聲卡wm8904的驅(qū)動(dòng),由點(diǎn)到面學(xué)習(xí)alsa的整個(gè)驅(qū)動(dòng)流程。
驅(qū)動(dòng)現(xiàn)在編寫的流程即解析dts->匹配驅(qū)動(dòng)->調(diào)用驅(qū)動(dòng)入口,那么我們直接分析dts開始。
查看dts:
sound {compatible = "atmel,asoc-wm8904";pinctrl-names = "default";pinctrl-0 = <&pinctrl_pck0_as_mck>;atmel,model = "wm8904 @ AT91SAM9N12EK";atmel,audio-routing ="Headphone Jack", "HPOUTL","Headphone Jack", "HPOUTR","IN2L", "Line In Jack","IN2R", "Line In Jack","Mic", "MICBIAS","IN1L", "Mic";atmel,ssc-controller = <&ssc0>;atmel,audio-codec = <&wm8904>;};由dts文件可知,配置的聲卡為 atmel,asoc-wm8904,這是一個(gè)典型的machine平臺(tái)驅(qū)動(dòng)設(shè)備,因此直接查找到所在的驅(qū)動(dòng)文件:
?
?
Platform驅(qū)動(dòng)匹配直接調(diào)用probe函數(shù)。
static int atmel_asoc_wm8904_probe(struct platform_device *pdev){struct snd_soc_card *card = &atmel_asoc_wm8904_card;struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink;int id, ret;card->dev = &pdev->dev;ret = atmel_asoc_wm8904_dt_init(pdev);id = of_alias_get_id((struct device_node *)dailink->cpu_of_node, "ssc");ret = atmel_ssc_set_audio(id);ret = snd_soc_register_card(card); //核心入口return 0;err_set_audio:atmel_ssc_put_audio(id);return ret;}從上面我們可知,machine除了鏈接platform和code,還要做一些其他硬件的初始化,這不難理解,比如,如果硬件播放需要是能一個(gè)功放,那么功放的控制既不屬于codec,也不屬于soc上的音頻控制引腳,這個(gè)功放控制使能也許只在當(dāng)前板子上才存在,這種不屬于soc音頻控制所需共性的資源,即在machine中初始化。
即machine核心函數(shù):
? snd_soc_register_card(card);
上文我們一直談到了一個(gè)觀念,machine驅(qū)動(dòng)的核心作用是匹配在鏈表中匹配platform和dai,codec。 因此可以在card查看關(guān)聯(lián)信息:
static struct snd_soc_card atmel_asoc_wm8904_card = {.name = "atmel_asoc_wm8904",.owner = THIS_MODULE,.dai_link = &atmel_asoc_wm8904_dailink,.num_links = 1,.dapm_widgets = atmel_asoc_wm8904_dapm_widgets,.num_dapm_widgets = ARRAY_SIZE(atmel_asoc_wm8904_dapm_widgets),.fully_routed = true,};我們發(fā)現(xiàn)card->dai_link就保存這我們當(dāng)前所有完整鏈路的所需資源,一個(gè)聲卡可能有多個(gè)dailink,前文幾經(jīng)討論過了,我們這里只分析一條即可。
static struct snd_soc_dai_link atmel_asoc_wm8904_dailink = {.name = "WM8904",.stream_name = "WM8904 PCM",.codec_dai_name = "wm8904-hifi",.dai_fmt = SND_SOC_DAIFMT_I2S| SND_SOC_DAIFMT_NB_NF| SND_SOC_DAIFMT_CBM_CFM,.ops = &atmel_asoc_wm8904_ops,};整個(gè)流程執(zhí)行到這里,其實(shí)還算十分簡單,簡言概之,dts配置了一個(gè)sound節(jié)點(diǎn),匹配調(diào)用machine的驅(qū)動(dòng)probe,在probe事先把描述platform、codec關(guān)系的card->dai-link填充好,然后調(diào)用snd_soc_register_card(card),即完成了machine驅(qū)動(dòng)的編寫。
這里不難猜出snd_soc_register_card中會(huì)按照card->dai-link中所描述的codec、dai,去從codec_list、dai_list上遍歷匹配,找到對應(yīng)的codec、dai驅(qū)動(dòng)。
int snd_soc_register_card(struct snd_soc_card *card){if (!card->name || !card->dev)return -EINVAL;dev_set_drvdata(card->dev, card); snd_soc_initialize_card_lists(card);INIT_LIST_HEAD(&card->dai_link_list);INIT_LIST_HEAD(&card->rtd_list);card->num_rtd = 0;INIT_LIST_HEAD(&card->dapm_dirty);INIT_LIST_HEAD(&card->dobj_list);card->instantiated = 0;/* ===============以上初始化card中所有l(wèi)ist,特別注意rtd_list=====================*/mutex_init(&card->mutex);mutex_init(&card->dapm_mutex);spin_lock_init(&card->dpcm_lock);return snd_soc_bind_card(card); //核心入口}這里將card->instantiated = 0,其實(shí)就是設(shè)置一個(gè)flag,當(dāng)snd_card初始化完成就把設(shè)置為1。以防止多次初始化,static int snd_soc_bind_card(struct snd_soc_card *card){struct snd_soc_pcm_runtime *rtd;int ret;ret = snd_soc_instantiate_card(card); //核心入口/* deactivate pins to sleep state */for_each_card_rtds(card, rtd) {struct snd_soc_dai *cpu_dai = rtd->cpu_dai;struct snd_soc_dai *codec_dai;int j;for_each_rtd_codec_dai(rtd, j, codec_dai) {if (!codec_dai->active)pinctrl_pm_select_sleep_state(codec_dai->dev);}if (!cpu_dai->active)pinctrl_pm_select_sleep_state(cpu_dai->dev);}return ret;}[Click and drag to move]這里我們所有猜想的核心執(zhí)行都在snd_soc_instantiate_card加以實(shí)現(xiàn)。這里只節(jié)選部分代碼,完整請查閱代碼數(shù)。static int snd_soc_instantiate_card(struct snd_soc_card *card){struct snd_soc_pcm_runtime *rtd;struct snd_soc_dai_link *dai_link;int ret, i, order;mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_INIT);...... ....../*dapm的作用以后分析*/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);...... ....../* bind DAIs */for_each_card_prelinks(card, i, dai_link) {ret = soc_bind_dai_link(card, dai_link);if (ret != 0)goto probe_end;}/* add predefined DAI links to the list */for_each_card_prelinks(card, i, dai_link)snd_soc_add_dai_link(card, dai_link);/* card bind complete so register a sound card */ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);soc_init_card_debugfs(card);#ifdef CONFIG_DEBUG_FSsnd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);#endif#ifdef CONFIG_PM_SLEEP/* deferred resume work */INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);#endifif (card->dapm_widgets)snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,card->num_dapm_widgets);if (card->of_dapm_widgets)snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets,card->num_of_dapm_widgets);/* initialise the sound card only once */if (card->probe) {ret = card->probe(card);if (ret < 0)goto probe_end;}/* probe all components used by DAI links on this card */for_each_comp_order(order) {for_each_card_rtds(card, rtd) {ret = soc_probe_link_components(card, rtd, order);if (ret < 0) {dev_err(card->dev,"ASoC: failed to instantiate card %d\n",ret);goto probe_end;}}}/* probe auxiliary components */ret = soc_probe_aux_devices(card);if (ret < 0)goto probe_end;/** Find new DAI links added during probing components and bind them.* Components with topology may bring new DAIs and DAI links.*/for_each_card_links(card, dai_link) {if (soc_is_dai_link_bound(card, dai_link))continue;ret = soc_init_dai_link(card, dai_link);if (ret)goto probe_end;ret = soc_bind_dai_link(card, dai_link);if (ret)goto probe_end;}/* probe all DAI links on this card */for_each_comp_order(order) {for_each_card_rtds(card, rtd) {ret = soc_probe_link_dais(card, rtd, order);if (ret < 0) {dev_err(card->dev,"ASoC: failed to instantiate card %d\n",ret);goto probe_end;}}}snd_soc_dapm_link_dai_widgets(card);snd_soc_dapm_connect_dai_link_widgets(card);if (card->controls)snd_soc_add_card_controls(card, card->controls,card->num_controls);if (card->dapm_routes)snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,card->num_dapm_routes);if (card->of_dapm_routes)snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,card->num_of_dapm_routes);/* try to set some sane longname if DMI is available */snd_soc_set_dmi_name(card, NULL);snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),"%s", card->name);snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),"%s", card->long_name ? card->long_name : card->name);snprintf(card->snd_card->driver, sizeof(card->snd_card->driver),"%s", card->driver_name ? card->driver_name : card->name);}if (card->late_probe) {ret = card->late_probe(card);}snd_soc_dapm_new_widgets(card);ret = snd_card_register(card->snd_card);card->instantiated = 1;...... ......mutex_unlock(&card->mutex);mutex_unlock(&client_mutex);return ret;}ASoC定義了三個(gè)全局的鏈表頭變量:codec_list、dai_list、platform_list,系統(tǒng)中所有的Codec、DAI、Platform都在注冊時(shí)連接到這三個(gè)全局鏈表上。soc_bind_dai_link函數(shù)逐個(gè)掃描這三個(gè)鏈表,根據(jù)card->dai_link[]中的名稱進(jìn)行匹配,匹配后把相應(yīng)的codec,dai和platform實(shí)例賦值到card->rtd[]中(snd_soc_pcm_runtime)。經(jīng)過這個(gè)過程后,snd_soc_pcm_runtime:(card->rtd)中保存了本Machine中使用的Codec,DAI和Platform驅(qū)動(dòng)的信息,這里為什么要有一個(gè)rtd的數(shù)組,其實(shí)很簡單,machine驅(qū)動(dòng)只會(huì)在加載的時(shí)候執(zhí)行一次,假設(shè)我們存在多個(gè)dai-link,即多條有效的數(shù)據(jù)鏈路,那么每一條的都存在codec、dai的驅(qū)動(dòng)信息,因此多條dai-link的信息就要用數(shù)組來表示,每一個(gè)rtd數(shù)組項(xiàng)表示一個(gè)dai-link的資源,為什么叫
runtime,個(gè)人猜想可能是因?yàn)檫@個(gè)數(shù)組項(xiàng)中的內(nèi)容是在運(yùn)行掃描后生成的。至于是不是就不知道了。
?
然后調(diào)用標(biāo)準(zhǔn)的alsa函數(shù)創(chuàng)建聲卡實(shí)例:
ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);
?
然后,依次調(diào)用各個(gè)子結(jié)構(gòu)的probe函數(shù),這里我們知道card->rtd[]中存了一個(gè)dailink的Codec,DAI和Platform驅(qū)動(dòng)的信息,在最后還調(diào)用了soc_new_pcm()函數(shù)用于創(chuàng)建標(biāo)準(zhǔn)alsa驅(qū)動(dòng)的pcm邏輯設(shè)備
for_each_comp_order(order) {for_each_card_rtds(card, rtd) {ret = soc_probe_link_components(card, rtd, order);if (ret < 0) {dev_err(card->dev,"ASoC: failed to instantiate card %d\n",ret);goto probe_end;}}}--------------------------------------------------------------------------------------------------------------static int soc_probe_dai(struct snd_soc_dai *dai, int order){if (dai->probed ||dai->driver->probe_order != order)return 0;if (dai->driver->probe) {int ret = dai->driver->probe(dai);if (ret < 0) {dev_err(dai->dev, "ASoC: failed to probe DAI %s: %d\n",dai->name, ret);return ret;}}dai->probed = 1;return 0;}static int soc_link_dai_pcm_new(struct snd_soc_dai **dais, int num_dais,struct snd_soc_pcm_runtime *rtd){int i, ret = 0;for (i = 0; i < num_dais; ++i) {struct snd_soc_dai_driver *drv = dais[i]->driver;if (drv->pcm_new)ret = drv->pcm_new(rtd, dais[i]);if (ret < 0) {dev_err(dais[i]->dev,"ASoC: Failed to bind %s with pcm device\n",dais[i]->name);return ret;}}return 0;}static int soc_probe_link_dais(struct snd_soc_card *card,struct snd_soc_pcm_runtime *rtd, int order){struct snd_soc_dai_link *dai_link = rtd->dai_link;struct snd_soc_dai *cpu_dai = rtd->cpu_dai;struct snd_soc_rtdcom_list *rtdcom;struct snd_soc_component *component;struct snd_soc_dai *codec_dai;int i, ret, num;/* probe the CPU DAI */ret = soc_probe_dai(cpu_dai, order);/* probe the CODEC DAI */for_each_rtd_codec_dai(rtd, i, codec_dai) {ret = soc_probe_dai(codec_dai, order);}...... .......ret = soc_new_pcm(rtd, num);return 0;}創(chuàng)建pcm,這里是alsa和asoc開始結(jié)合的部分,alsa中的pcm的fops實(shí)質(zhì)是執(zhí)行的rtd->fops,這里非常非常的重要。該函數(shù)首先初始化snd_soc_runtime中的snd_pcm_ops字段,也就是rtd->ops中的部分成員,例如open,close,hw_params等,緊接著調(diào)用標(biāo)準(zhǔn)alsa驅(qū)動(dòng)中的創(chuàng)建pcm的函數(shù)snd_pcm_new()創(chuàng)建聲卡的pcm實(shí)例,pcm的private_data字段設(shè)置為該runtime變量rtd,然后用platform驅(qū)動(dòng)中的snd_pcm_ops替換部分pcm中的snd_pcm_ops字段,最后,調(diào)用platform驅(qū)動(dòng)的pcm_new回調(diào),該回調(diào)實(shí)現(xiàn)該platform下的dma內(nèi)存申請和dma初始化等相關(guān)工作。
?
如果存在card->late_probe,則執(zhí)行。
?
最后執(zhí)行ret = snd_card_register(card->snd_card);
到這里,整個(gè)Machine驅(qū)動(dòng)的初始化已經(jīng)完成,通過各個(gè)子結(jié)構(gòu)的probe調(diào)用,實(shí)際上,也完成了部分Platfrom驅(qū)動(dòng)和Codec驅(qū)動(dòng)的初始化工作,完成了snd_card和pcm設(shè)備的添加。以后的部分進(jìn)入了alsa框架部分。
總結(jié)一下本篇的內(nèi)容,即配置dts觸發(fā)machine驅(qū)動(dòng)執(zhí)行probe函數(shù),在probe中初始化了和當(dāng)前板子相關(guān)的一些硬件,如打開功放等等,然后調(diào)用snd_soc_register_card下的snd_soc_instantiate_card函數(shù)進(jìn)行platform、codec、dai驅(qū)動(dòng)的匹配,匹配完成后將這些信息保存在rtd[]中備用,依次執(zhí)行platform、codec、dai驅(qū)動(dòng)的probe函數(shù),這里知道platform驅(qū)動(dòng)只要初始化soc時(shí)鐘內(nèi)存和音頻接口。Codec主要初始化外置codec芯片,dai主要初始化傳輸配置相關(guān),執(zhí)行到這里,意味著我們一條鏈路所需要的硬件初始化即完成了,最后注冊snd_card聲卡,并實(shí)例化當(dāng)中的pcm設(shè)備,進(jìn)入alsa框架了。當(dāng)這里,我們可以看到/dev/snd存在聲卡設(shè)備節(jié)點(diǎn)了。
總結(jié)
以上是生活随笔為你收集整理的ALSA音频框架理解:machine的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 糟糕的C语言睡眠排序算法
- 下一篇: 看看高手做的ARM开发板