ASOC驱动分析(一)

ASoc是ALSA针对嵌入式设备进行的一次封装。
这里通过分析smdk_wm8994的驱动洞悉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_device的私有数据,然后直接调用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()\n",
		 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\n");
		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\n",
				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\n", 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\n",
				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'\n", 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_list中取出每一个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_list中找到指定的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\n", 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\n",
			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\n",
				dai_link->codec_dai_name);

		goto find_platform;
	}
	dev_dbg(card->dev, "CODEC %s not registered\n",
			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\n",
			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方法,大概就是设置一些寄存器的默认值等等,这里不是重点。
接着就是使用一个循环,对每一个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\n",
               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\n",
                 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\n",
                 ret);
    }
}

设置完成后,就是调用ALSA的注册函数进行声卡的注册了。

ret = snd_card_register(card->snd_card);
if (ret < 0) {
    pr_err("asoc: failed to register soundcard for %s: %d\n",
                        card->name, ret);
    goto probe_aux_dev_err;
}

你可能感兴趣的:(linux)