ASOC之Machine
前言
Machine是ASOC架構中的關鍵部件,沒有Machine部件,Codec和Platform是無法工作的。分析核心版本為4.17
Machine程式碼分析
以smdk_wm8580.c為例。整體結構是先註冊平臺驅動,當平臺驅動和平臺裝置的名字相匹配的時候,就會呼叫平臺驅動裡的probe函式。
1.入口函式:註冊平臺裝置
分配一個名為“soc-audio”的platform_device
並且賦值給smdk_snd_device
,將smdk
結構體裡的相關裝置資訊設定為smdk_snd_device
這個平臺的驅動資料,然後將smdk_snd_device
加入平臺裝置列表。module_init
2.平臺裝置驅動
在soc-core.c
檔案中有對應的平臺裝置驅動,如果匹配成功就會呼叫probe
函式。
此處會呼叫snd_soc_register_card,會在ASOC core中註冊一個card。 此處的card就是smdk_ops結構。接下來談論此結構的作用。
其中dai_link結構就是用作連線platform和codec的,指明到底用那個codec,那個platfrom。那是通過什麼指定的? 如果有興趣可以詳細看snd_soc_dai_link的註釋,此註釋寫的非常清楚。
.cpu_dai_name: 用於指定cpu側的dai名字,也就是所謂的cpu側的數字音訊介面,一般都是i2S介面。如果省略則會使用cpu_name/cou_of_name。
.codec_dai_name: 用於codec側的dai名字,不可以省略。
.codec_name: 用於指定codec晶片。不可以省略。
.platform_name: 用於指定cpu側平臺驅動,通常都是DMA驅動,用於傳輸。
.ops: audio的相關操作函式集合。
再次回到snd_soc_register_card函式中,繼續分析Machine的作用。
- 根據struct snd_soc_dai_link結構體的個數,此處是一個,檢測下需要設定的name是否已經設定。
if (link->platform_name && link->platform_of_node) {
dev_err(card->dev,
“ASoC: Both platform name/of_node are set for %s\n”,link->name);
return -EINVAL;
} - 分配一個struct snd_soc_pcm_runtime結構,然後根據num_links,設定card,複製dai_link等。
card->rtd = devm_kzalloc(card->dev,sizeof(struct snd_soc_pcm_runtime) *
(card->num_links + card->num_aux_devs),GFP_KERNEL);
if (card->rtd == NULL)
return -ENOMEM;
card->num_rtd = 0;
card->rtd_aux = &card->rtd[card->num_links];
for (i = 0; i < card->num_links; i++) {
card->rtd[i].card = card;
card->rtd[i].dai_link = &card->dai_link[i];
card->rtd[i].codec_dais = devm_kzalloc(card->dev,
sizeof(struct snd_soc_dai *) *
(card->rtd[i].dai_link->num_codecs),
GFP_KERNEL);
if (card->rtd[i].codec_dais == NULL)
return -ENOMEM;
}
3. 然後所有的重點工作全部在snd_soc_instantiate_card函式中實現。
分析snd_soc_instantiate_card函式的實際操作:
- 根據num_links的值,進行DAIs的bind工作。第一步先bind cpu側的dai
cpu_dai_component.name = dai_link->cpu_name;
cpu_dai_component.of_node = dai_link->cpu_of_node;
cpu_dai_component.dai_name = dai_link->cpu_dai_name;
rtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component);
if (!rtd->cpu_dai) {
dev_err(card->dev, “ASoC: CPU DAI %s not registered\n”,
dai_link->cpu_dai_name);
return -EPROBE_DEFER;
}
此處dai_link就是在machine中註冊的struct snd_soc_dai_link結構體,cpu_dai_name也就是註冊的name,最後通過snd_soc_find_dai接口出查詢。
static struct snd_soc_dai *snd_soc_find_dai(
const struct snd_soc_dai_link_component *dlc)
{
struct snd_soc_component *component;
struct snd_soc_dai *dai;
/* Find CPU DAI from registered DAIs*/
list_for_each_entry(component, &component_list, list) {
if (dlc->of_node && component->dev->of_node != dlc->of_node)
continue;
if (dlc->name && strcmp(component->name, dlc->name))
continue;
list_for_each_entry(dai, &component->dai_list, list) {
if (dlc->dai_name && strcmp(dai->name, dlc->dai_name))
continue;
return dai;
}
}
return NULL;
}
此函式會在component_list連結串列中先找到相同的name,然後在component->dai_list中查詢是否有相同的dai_name。此處的component_list是在註冊codec和platform中的時候設定的。會在codec和platform的時候會詳細介紹。在此處找到註冊的cpu_dai之後,存在snd_soc_pcm_runtime中的cpu_dai中。
-
然後根據codec的資料,尋找codec側的dai。
/* Find CODEC from registered CODECs */
for (i = 0; i < rtd->num_codecs; i++) {
codec_dais[i] = snd_soc_find_dai(&codecs[i]);
if (!codec_dais[i]) {
dev_err(card->dev, “ASoC: CODEC DAI %s not registered\n”,
codecs[i].dai_name);
return -EPROBE_DEFER;
}
}
然後將找到的codec側的dai也同樣賦值給snd_soc_pcm_runtime中的codec_dai中。 -
在platform_list連結串列中查詢platfrom,根據dai_link中的platform_name域。如果沒有platform_name,則設定為"snd-soc-dummy"
/* if there’s no platform we match on the empty platform */
platform_name = dai_link->platform_name;
if (!platform_name && !dai_link->platform_of_node)
platform_name = “snd-soc-dummy”;/* find one from the set of registered platforms */ list_for_each_entry(platform, &platform_list, list) { if (dai_link->platform_of_node) { if (platform->dev->of_node != dai_link->platform_of_node) continue; } else { if (strcmp(platform->component.name, platform_name)) continue; } rtd->platform = platform; }
這樣查詢完畢之後,snd_soc_pcm_runtime中儲存了查詢到的codec, dai, platform。
-
接著初始化註冊的codec cache,cache_init代表是否已經初始化過。
/* initialize the register cache for each available codec */
list_for_each_entry(codec, &codec_list, list) {
if (codec->cache_init)
continue;
ret = snd_soc_init_codec_cache(codec);
if (ret < 0)
goto base_error;
} -
然後呼叫ALSA中的建立card的函式: snd_card_new建立一個card
/* 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);
if (ret < 0) {
dev_err(card->dev,
“ASoC: can’t create sound card for card %s: %d\n”,
card->name, ret);
goto base_error;
} -
然後依次呼叫各個子部件的probe函式
/* initialise the sound card only once */
if (card->probe) {
ret = card->probe(card);
if (ret < 0)
goto card_probe_error;
}/* probe all components used by DAI links on this card */
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_link_components(card, i, order);
if (ret < 0) {
dev_err(card->dev,
“ASoC: failed to instantiate card %d\n”,
ret);
goto probe_dai_err;
}
}
}/* probe all DAI links on this card */ 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_link_dais(card, i, order); if (ret < 0) { dev_err(card->dev, "ASoC: failed to instantiate card %d\n", ret); goto probe_dai_err; } }
}
-
在soc_probe_link_dais函式中依次呼叫了cpu_dai, codec_dai側的probe函式
/* 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);
if (ret < 0) {
dev_err(cpu_dai->dev,
“ASoC: failed to probe CPU DAI %s: %d\n”,
cpu_dai->name, ret);
return ret;
}
}
cpu_dai->probed = 1;
}/* probe the CODEC DAI */
for (i = 0; i < rtd->num_codecs; i++) {
ret = soc_probe_codec_dai(card, rtd->codec_dais[i], order);
if (ret)
return ret; -
最終呼叫到soc_new_pcm函式建立pcm裝置:
if (!dai_link->params) {
/* create the pcm */
ret = soc_new_pcm(rtd, num);
if (ret < 0) {
dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",
dai_link->stream_name, ret);
return ret;
}
最中此函式會呼叫ALSA的標準建立pcm裝置的介面: snd_pcm_new,然後會設定pcm相應的ops操作函式集合。然後呼叫到platform->driver->pcm_new的函式。此處不帖函數了。
-
接著會在dapm和dai widget做相應的操作,後期會設定control引數,最終會呼叫到ALSA的註冊card的函式snd_card_register。
ret = snd_card_register(card->snd_card);
if (ret < 0) {
dev_err(card->dev, “ASoC: failed to register soundcard %d\n”,
ret);
goto probe_aux_dev_err;
}
總結: 經過Machine的驅動的註冊,Machine會根據註冊以"soc-audio"為名字的平臺裝置,然後在同名的平臺的驅動的probe函式中,會根據snd_soc_dai_link結構體中的name,進行匹配查詢相應的codec, codec_dai,platform, cpu_dai。找到之後將這些值全部放入結構體snd_soc_pcm_runtime的相應位置,然後註冊card,依次呼叫codec, platform,cpu_dai側相應的probe函式進行初始化,接著建立pcm裝置,註冊card到系統中。其實ASOC也就是在ALSA的基礎上又再次封裝了一次,讓寫驅動更方便,簡便。
這樣封裝之後,就可以大大簡化驅動的編寫,關於Machine驅動需要做的:
- 註冊名為"soc-audio"的平臺裝置。
- 分配一個struct snd_soc_card結構體,然後設定其中的dai_link。對其中的dai_link再次設定。
- 將struct snd_soc_card結構放入到平臺裝置的dev的私有資料中。
- 註冊平臺裝置。