1. 程式人生 > >ASOC之Machine

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的作用。

  1. 根據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;
    }
  2. 分配一個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函式的實際操作:

  1. 根據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中。

  1. 然後根據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中。

  2. 在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。

  1. 接著初始化註冊的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;
    }

  2. 然後呼叫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;
    }

  3. 然後依次呼叫各個子部件的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;
    	}
    }
    

    }

  4. 在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;

  5. 最終呼叫到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的函式。此處不帖函數了。

  1. 接著會在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驅動需要做的:

  1. 註冊名為"soc-audio"的平臺裝置。
  2. 分配一個struct snd_soc_card結構體,然後設定其中的dai_link。對其中的dai_link再次設定。
  3. 將struct snd_soc_card結構放入到平臺裝置的dev的私有資料中。
  4. 註冊平臺裝置。