ASOC駆動分析(一)

10740 ワード

ASocはALSAが組み込み機器に対して行う一次パッケージである.ここではsmdk_を解析することによりwm 8994の駆動はAsocのフレームワークを洞察する.ソース:smdk_wm8994.c(sound/soc/samsung)ドライバのエントリはsmdk_audio_init
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;
}

関数はプラットフォームデバイスの構造体ポインタを割り当て、smdkをplatform_に設定します.デバイスのプライベートデータ、platformを直接呼び出します.device_addはこのプラットフォームデバイスを追加します.カーネルのやり方によっては、「soc-audio」という名前のプラットフォームデバイスが追加されている以上、対応するプラットフォームドライバが必ず存在します.したがって、カーネルコードからplatform_を見つけます.driver構造体.
static struct platform_driver soc_driver = {
	.driver		= {
		.name		= "soc-audio",
		.owner		= THIS_MODULE,
		.pm		= &snd_soc_pm_ops,
	},
	.probe		= soc_probe,
	.remove		= soc_remove,
};

この構造体はSoc-coreに存在する.c(sound/soc)にあります.カーネルのマッチングルールに従ってsmdk_wm8994.c platform_を呼び出すdevice_add関数がカーネルにプラットフォームデバイスを追加するとsoc_probe関数が呼び出されます.この関数の呼び出し手順を見てみましょう.
/* probes a new socdev */
static int soc_probe(struct platform_device *pdev)
{
	struct snd_soc_card *card = platform_get_drvdata(pdev);
	int ret = 0;

	/*
	 * no card, so machine driver should be registering card
	 * we should not be here in that case so ret error
	 */
	if (!card)
		return -EINVAL;

	dev_warn(&pdev->dev,
		 "ASoC machine %s should use snd_soc_register_card()
", card->name); /* Bodge while we unpick instantiation */ card->dev = &pdev->dev; ret = snd_soc_register_card(card); if (ret != 0) { dev_err(&pdev->dev, "Failed to register card
"); return ret; } return 0; }

soc_probe関数でplatform_が呼び出されましたget_drvdata関数はsnd_を取得しましたsoc_cardポインタ.明らかに、このポインタはsmdkという構造体を指している.今回のsoc_probeはsmdk_wm8994.cでplatformが呼び出されましたdevice_add関数によるものです.だからここの*card=&smdk.そしてsnd_を呼び出すsoc_register_card.
int snd_soc_register_card(struct snd_soc_card *card)
{
	int i;

	if (!card->name || !card->dev)
		return -EINVAL;

	for (i = 0; i < card->num_links; i++) {
		struct snd_soc_dai_link *link = &card->dai_link[i];

		/*
		 * Codec must be specified by 1 of name or OF node,
		 * not both or neither.
		 */
		if (!!link->codec_name == !!link->codec_of_node) {
			dev_err(card->dev,
				"Neither/both codec name/of_node are set for %s
", link->name); return -EINVAL; } /* * Platform may be specified by either name or OF node, but * can be left unspecified, and a dummy platform will be used. */ if (link->platform_name && link->platform_of_node) { dev_err(card->dev, "Both platform name/of_node are set for %s
", link->name); return -EINVAL; } /* * CPU DAI must be specified by 1 of name or OF node, * not both or neither. */ if (!!link->cpu_dai_name == !!link->cpu_dai_of_node) { dev_err(card->dev, "Neither/both cpu_dai name/of_node are set for %s
", link->name); return -EINVAL; } } dev_set_drvdata(card->dev, card); snd_soc_initialize_card_lists(card); soc_init_card_debugfs(card); /* card->rtd , , card->num_links + card->num_aux_devs snd_soc_pcm_runtime */ 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].dai_link = &card->dai_link[i]; // smdk_dai, smdk_dai card->rtd[i].dai_link INIT_LIST_HEAD(&card->list); INIT_LIST_HEAD(&card->dapm_dirty); card->instantiated = 0; mutex_init(&card->mutex); mutex_init(&card->dapm_mutex); mutex_lock(&client_mutex); list_add(&card->list, &card_list); // crad card_list snd_soc_instantiate_cards(); // , mutex_unlock(&client_mutex); dev_dbg(card->dev, "Registered card '%s'
", card->name); return 0; }

snd_soc_register_card関数の前に一連の判断が行われ,注釈からカーネルは伝達されたcardポインタが対応する要求を満たしているかどうかを調べることである.cardをcard_に追加しますListチェーンテーブルでsnd_を呼び出すsoc_instantiate_cardsはサウンドカードを「インスタンス化」します.
static void snd_soc_instantiate_cards(void)
{
	struct snd_soc_card *card;
	list_for_each_entry(card, &card_list, list)
		snd_soc_instantiate_card(card);
}

さっきcradポインタをチェーンテーブルに入れたのでsnd_soc_instantiate_cards関数では、チェーンテーブルcard_からリストから各cardポインタを取り出し、snd_をそれぞれ呼び出すsoc_instantiate_cardをインスタンス化します.次にsnd_の解析を続行しますsoc_instantiate_card関数.関数が入ってくる最初の操作はDAIバインドです.バインディングとは、cpu側で使用するDAIインタフェースがどれであるか、codec側のDAIインタフェースがどれであるかを指定することであり、結局、両方が同じタイプのインタフェースを使用することを保証してこそ、正常な通信が可能である.
/* bind DAIs */
for (i = 0; i < card->num_links; i++)
    soc_bind_dai_link(card, i);

関数でforループを使用して、linkごとにsoc_を呼び出します.bind_dai_link関数をバインドします.バインドの原理:dai_listでcpu側のcpuを見つけた_dai、そしてcodec_からリストで指定したcodecを見つけて、codec側のdaiを探して、最後にplatformを探して、見つけた情報を全部保存してrtdに保存します.
static int soc_bind_dai_link(struct snd_soc_card *card, int num)
{
	struct snd_soc_dai_link *dai_link = &card->dai_link[num];
	struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
	struct snd_soc_codec *codec;
	struct snd_soc_platform *platform;
	struct snd_soc_dai *codec_dai, *cpu_dai;
	const char *platform_name;

	if (rtd->complete)
		return 1;
	dev_dbg(card->dev, "binding %s at idx %d
", dai_link->name, num); /* do we already have the CPU DAI for this link ? */ if (rtd->cpu_dai) { goto find_codec; } /* no, then find CPU DAI from registered DAIs*/ list_for_each_entry(cpu_dai, &dai_list, list) { // dai_list , cpu_dai, cpu_dai_name cpu_dai, :"samsung-i2s.0" if (dai_link->cpu_dai_of_node) { if (cpu_dai->dev->of_node != dai_link->cpu_dai_of_node) continue; } else { if (strcmp(cpu_dai->name, dai_link->cpu_dai_name)) // continue; } rtd->cpu_dai = cpu_dai; // cpu_dai rtd 。 goto find_codec; } dev_dbg(card->dev, "CPU DAI %s not registered
", dai_link->cpu_dai_name); find_codec: /* do we already have the CODEC for this link ? */ if (rtd->codec) { goto find_platform; } /* no, then find CODEC from registered CODECs*/ list_for_each_entry(codec, &codec_list, list) { // codec_list , codec, codec_name codec, : "wm8994-codec" if (dai_link->codec_of_node) { if (codec->dev->of_node != dai_link->codec_of_node) continue; } else { if (strcmp(codec->name, dai_link->codec_name)) // continue; } rtd->codec = codec; // codec rtd 。 /* * CODEC found, so find CODEC DAI from registered DAIs from * this CODEC */ list_for_each_entry(codec_dai, &dai_list, list) { // codec, codec DAI, : "wm8994-aif1" if (codec->dev == codec_dai->dev && !strcmp(codec_dai->name, dai_link->codec_dai_name)) { rtd->codec_dai = codec_dai; // dai rtd goto find_platform; } } dev_dbg(card->dev, "CODEC DAI %s not registered
", dai_link->codec_dai_name); goto find_platform; } dev_dbg(card->dev, "CODEC %s not registered
", dai_link->codec_name); find_platform: /* do we need a platform? */ if (rtd->platform) goto out; /* if there's no platform we match on the empty platform */ platform_name = dai_link->platform_name; // : "samsung-audio" if (!platform_name && !dai_link->platform_of_node) platform_name = "snd-soc-dummy"; /* no, then find one from the set of registered platforms */ list_for_each_entry(platform, &platform_list, list) { // platform_list , "samsung-audio" platform if (dai_link->platform_of_node) { if (platform->dev->of_node != dai_link->platform_of_node) continue; } else { if (strcmp(platform->name, platform_name)) continue; } rtd->platform = platform; // platform rtd goto out; } dev_dbg(card->dev, "platform %s not registered
", dai_link->platform_name); return 0; out: /* mark rtd as complete if we found all 4 of our client devices */ if (rtd->codec && rtd->codec_dai && rtd->platform && rtd->cpu_dai) { rtd->complete = 1; card->num_rtd++; } return 1; }

DAIバインドが完了したら、snd_を呼び出します.soc_init_codec_Cacheはcodecのレジスタに対していくつかのデフォルトの設定を行い、ここでは異なるcodecには異なるinit方法があり、おそらくいくつかのレジスタのデフォルト値を設定するなど、ここでは重点ではありません.次に,1つのループを用いて,各DAIを初期化する.
/* 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
", card->name, ret); goto probe_dai_err; } } }

ここでsoc_probe_dai_link関数では、上に見つかったcpuをそれぞれ呼び出します.daiのprobe関数は,見つかったcodecのprobe関数を呼び出し,platformを見つけたprobe関数を呼び出し,codec側のDAIのprobe関数を呼び出す.そしてsoc_を呼び出すnew_pcmインタフェースは対応するPCM論理デバイスを作成する.初期の初期化が完了すると、snd_も呼び出されます.soc_dai_set_fmt関数は、各daiインタフェースにいくつかのフォーマットを設定します.
for (i = 0; i < card->num_links; i++) {
    dai_link = &card->dai_link[i];

    if (dai_link->dai_fmt) {
        ret = snd_soc_dai_set_fmt(card->rtd[i].codec_dai,
                      dai_link->dai_fmt);
        if (ret != 0 && ret != -ENOTSUPP)
            dev_warn(card->rtd[i].codec_dai->dev,
                 "Failed to set DAI format: %d
", ret); ret = snd_soc_dai_set_fmt(card->rtd[i].cpu_dai, dai_link->dai_fmt); if (ret != 0 && ret != -ENOTSUPP) dev_warn(card->rtd[i].cpu_dai->dev, "Failed to set DAI format: %d
", ret); } }

設定が完了すると、ALSAの登録関数を呼び出してサウンドカードの登録を行います.
ret = snd_card_register(card->snd_card);
if (ret < 0) {
    pr_err("asoc: failed to register soundcard for %s: %d
", card->name, ret); goto probe_aux_dev_err; }