ALSA驱动asoc框架之machine(二)

本文详细介绍了ASoC(Advanced Linux Sound Architecture)框架中平台设备与驱动的交互过程,从注册platform_device到匹配并调用对应的platform_driver,再到snd_soc_register_card函数的执行。在这个过程中,涉及到snd_soc_instantiate_cards函数,该函数通过遍历链表匹配DAI、Codec和Platform,并调用它们的probe函数进行初始化。最终,通过snd_card_register注册声卡,完成Machine驱动的初始化。文章深入剖析了核心函数的功能和作用,揭示了ASoC驱动工作的核心流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、注册platform Driver

前面章节ALSA驱动asoc框架之machine(一)说过machine驱动的probe函数会分配一个名为“soc-audio”的平台设备,如zx297520v3_nau8810.c:

static int zx297520v3_nau8810_probe(struct platform_device *pdev)
{
... ...
	zx297520v3_nau8810_snd_device = platform_device_alloc("soc-audio", -1);
	if (!zx297520v3_nau8810_snd_device) {
		printk(KERN_ERR "zx297520v3_nau8810 SoC Audio: Unable to register\n");
		return -ENOMEM;
	}
... ...
	return ret;
}
/* by面朝大海0902 https://blog.csdn.net/lihuan680680/article/details/122406208 */

有平台设备必定有平台驱动,以“soc-audio”搜索,在linux-3.4.x\sound\soc\soc-core.c中搜索到有对应platform driver驱动:

/* ASoC 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,
};

我们知道有同名的platform device和platform driver会触发driver中的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;
... ...
	/* 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;
}
/* by面朝大海0902 https://blog.csdn.net/lihuan680680/article/details/122406208 */

soc_probe函数通过platform_get_drvdata将machine驱动platform_set_drvdatasnd_soc_card实体取出,并调用snd_soc_register_card注册:

/**
 * snd_soc_register_card - Register a card with the ASoC core
 *
 * @card: Card to register
 *
 */
int snd_soc_register_card(struct snd_soc_card *card)
{
... ...
	dev_set_drvdata(card->dev, card);

	snd_soc_initialize_card_lists(card);

	soc_init_card_debugfs(card);

	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];

	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);
	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->dai_link信息拷贝至snd_soc_pcm_runtime中,进行一些初始化,然后调用关键函数snd_soc_instantiate_cards,这个函数比较复杂,我们用<>尖括号将重要函数标注出来,然后单独说明:

static void snd_soc_instantiate_card(struct snd_soc_card *card)
{
... ...
	/* bind DAIs */
	for (i = 0; i < card->num_links; i++)
		soc_bind_dai_link(card, i);//---------------------<1>
... ...
	/* card bind complete so register a sound card */
	ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
			card->owner, 0, &card->snd_card); //-------------------<2>
	if (ret < 0) {
		pr_err("asoc: can't create sound card for card %s: %d\n",
			card->name, ret);
		mutex_unlock(&card->mutex);
		return;
	}
	card->snd_card->dev = card->dev;

	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);
... ...
	/* initialise the sound card only once */
	if (card->probe) {
		ret = card->probe(card);//----------------<3>
		if (ret < 0)
			goto card_probe_error;
	}
	/* 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);//-------------------<4>
			if (ret < 0) {
				pr_err("asoc: failed to instantiate card %s: %d\n",
			       card->name, ret);
				goto probe_dai_err;
			}
		}
	}
... ...
	ret = snd_card_register(card->snd_card);//------------------<5>
	if (ret < 0) {
		pr_err("asoc: failed to register soundcard for %s: %d\n",
							card->name, ret);
		goto probe_aux_dev_err;
	}
... ...
}

<1> soc_bind_dai_link函数如下:

static int soc_bind_dai_link(struct snd_soc_card *card, int num)
{
... ...
	/* no, then find CPU DAI from registered DAIs*/
	list_for_each_entry(cpu_dai, &dai_list, list) {
		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;
		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) {
		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 found, so find CODEC DAI from registered DAIs from
		 * this CODEC
		 */
		list_for_each_entry(codec_dai, &dai_list, list) {
			if (codec->dev == codec_dai->dev &&
				!strcmp(codec_dai->name,
					dai_link->codec_dai_name)) {

				rtd->codec_dai = codec_dai;
				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;
	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) {
		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;
		goto out;
	}

	dev_dbg(card->dev, "platform %s not registered\n",
			dai_link->platform_name);
	return 0;

... ...
}

SoC定义了三个全局的链表头变量:codec_listdai_listplatform_list,系统中所有的Codec、DAI、Platform都在注册时添加到这三个全局链表上。
soc_bind_dai_link函数逐个扫描这三个链表,根据card->dai_link[]中的名称进行匹配,匹配后把相应的codec,dai和platform实例赋值到card->rtd[]中(snd_soc_pcm_runtime结构体)。经过这个过程后card->rtd中保存了本Machine中使用的Codec,DAI和Platform驱动的信息。
codec_list、dai_list、platform_list链表的元素从哪里来的呢?这些元素的名字则是由设备的名字(device->name)和对应的驱动的名字(device->driver->name)组成,他们的驱动程序会调用list_add 函数将他们加入到各自的链表中。
/* by面朝大海0902 https://blog.csdn.net/lihuan680680/article/details/122406208 */

<2> snd_card_create函数如下:

int snd_card_create(int idx, const char *xid,
		    struct module *module, int extra_size,
		    struct snd_card **card_ret)
{
... ...
	card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
	if (!card)
		return -ENOMEM;
	if (xid)
		strlcpy(card->id, xid, sizeof(card->id));
	err = 0;
... ...
	snd_cards_lock |= 1 << idx;		/* lock it */
	if (idx >= snd_ecards_limit)
		snd_ecards_limit = idx + 1; /* increase the limit */
	mutex_unlock(&snd_card_mutex);
	card->number = idx;
	card->module = module;
	INIT_LIST_HEAD(&card->devices);
	init_rwsem(&card->controls_rwsem);
	rwlock_init(&card->ctl_files_rwlock);
	mutex_init(&card->user_ctl_lock);
	INIT_LIST_HEAD(&card->controls);
	INIT_LIST_HEAD(&card->ctl_files);
	spin_lock_init(&card->files_lock);
	INIT_LIST_HEAD(&card->files_list);
	init_waitqueue_head(&card->shutdown_sleep);
	atomic_set(&card->refcount, 0);
#ifdef CONFIG_PM
	mutex_init(&card->power_lock);
	init_waitqueue_head(&card->power_sleep);
#endif
	/* the control interface cannot be accessed from the user space until */
	/* snd_cards_bitmask and snd_cards are set with snd_card_register */
	err = snd_ctl_create(card);
	if (err < 0) {
		snd_printk(KERN_ERR "unable to register control minors\n");
		goto __error;
	}
	err = snd_info_card_create(card);
	if (err < 0) {
		snd_printk(KERN_ERR "unable to create card info\n");
		goto __error_ctl;
	}
	if (extra_size > 0)
		card->private_data = (char *)card + sizeof(struct snd_card);
	*card_ret = card;
	return 0;

      __error_ctl:
	snd_device_free_all(card, SNDRV_DEV_CMD_PRE);
      __error:
	kfree(card);
  	return err;
}

snd_card_create函数主要内容为申请内存,确定snd_card编号及初始化,调用snd_ctl_create建立逻辑设备,调用snd_info_card_create建立proc文件中的info节点:通常就是/proc/asound/card0

<4> soc_probe_dai_link函数如下:

static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order)
{
	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 = rtd->codec;
	struct snd_soc_platform *platform = rtd->platform;
	struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai;
	int ret;

	dev_dbg(card->dev, "probe %s dai link %d late %d\n",
			card->name, num, order);

	/* config components */
	codec_dai->codec = codec;
	cpu_dai->platform = platform;
	codec_dai->card = card;
	cpu_dai->card = card;

	/* set default power off timeout */
	rtd->pmdown_time = pmdown_time;

	/* probe the cpu_dai */
	if (!cpu_dai->probed &&
			cpu_dai->driver->probe_order == order) {
		if (!try_module_get(cpu_dai->dev->driver->owner))
			return -ENODEV;

		if (cpu_dai->driver->probe) {
			ret = cpu_dai->driver->probe(cpu_dai);
			if (ret < 0) {
				pr_err("asoc: failed to probe CPU DAI %s: %d\n",
							cpu_dai->name, ret);
				module_put(cpu_dai->dev->driver->owner);
				return ret;
			}
		}
		cpu_dai->probed = 1;
		/* mark cpu_dai as probed and add to card dai list */
		list_add(&cpu_dai->card_list, &card->dai_dev_list);
	}

	/* probe the CODEC */
	if (!codec->probed &&
			codec->driver->probe_order == order) {
		ret = soc_probe_codec(card, codec);
		if (ret < 0)
			return ret;
	}

	/* probe the platform */
	if (!platform->probed &&
			platform->driver->probe_order == order) {
		ret = soc_probe_platform(card, platform);
		if (ret < 0)
			return ret;
	}

	/* probe the CODEC DAI */
	if (!codec_dai->probed && codec_dai->driver->probe_order == order) {
		if (codec_dai->driver->probe) {
			ret = codec_dai->driver->probe(codec_dai);
			if (ret < 0) {
				pr_err("asoc: failed to probe CODEC DAI %s: %d\n",
							codec_dai->name, ret);
				return ret;
			}
		}

		/* mark codec_dai as probed and add to card dai list */
		codec_dai->probed = 1;
		list_add(&codec_dai->card_list, &card->dai_dev_list);
	}

	/* complete DAI probe during last probe */
	if (order != SND_SOC_COMP_ORDER_LAST)
		return 0;

	ret = soc_post_component_init(card, codec, num, 0);
	if (ret)
		return ret;

	ret = device_create_file(rtd->dev, &dev_attr_pmdown_time);
	if (ret < 0)
		pr_warn("asoc: failed to add pmdown_time sysfs:%d\n", ret);

	/* create the pcm */
	ret = soc_new_pcm(rtd, num);//-------------------------<5>
	if (ret < 0) {
		pr_err("asoc: can't create pcm %s :%d\n",
				dai_link->stream_name, ret);
		return ret;
	}

	/* add platform data for AC97 devices */
	if (rtd->codec_dai->driver->ac97_control)
		snd_ac97_dev_add_pdata(codec->ac97, rtd->cpu_dai->ac97_pdata);

	return 0;
}

soc_probe_dai_link函数会调用各个子结构的Probe函数,包括:
cpu_dai->driver->probe
soc_probe_codec —> driver->probe(codec)
soc_probe_platform —>driver->probe(platform)
codec_dai->driver->probe
最后调用soc_new_pcm函数

<5>snd_card_register函数如下:

int snd_card_register(struct snd_card *card)
{
	int err;

	if (snd_BUG_ON(!card))
		return -EINVAL;

	if (!card->card_dev) {
		card->card_dev = device_create(sound_class, card->dev,
					       MKDEV(0, 0), card,
					       "card%i", card->number);
		if (IS_ERR(card->card_dev))
			card->card_dev = NULL;
	}

	if ((err = snd_device_register_all(card)) < 0)
		return err;
	mutex_lock(&snd_card_mutex);
	if (snd_cards[card->number]) {
		/* already registered */
		mutex_unlock(&snd_card_mutex);
		return 0;
	}
	if (*card->id) {
		/* make a unique id name from the given string */
		char tmpid[sizeof(card->id)];
		memcpy(tmpid, card->id, sizeof(card->id));
		snd_card_set_id_no_lock(card, tmpid, tmpid);
	} else {
		/* create an id from either shortname or longname */
		const char *src;
		src = *card->shortname ? card->shortname : card->longname;
		snd_card_set_id_no_lock(card, src,
					retrieve_id_from_card_name(src));
	}
	snd_cards[card->number] = card;
	mutex_unlock(&snd_card_mutex);
	init_info_for_card(card);

	if (card->card_dev) {
		err = device_create_file(card->card_dev, &card_id_attrs);
		if (err < 0)
			return err;
		err = device_create_file(card->card_dev, &card_number_attrs);
		if (err < 0)
			return err;
	}

	return 0;
}
/* by面朝大海0902 https://blog.csdn.net/lihuan680680/article/details/122406208 */

注册声卡,注册完成后会产生card*,如下:

/sys/class/sound # ls
card0      pcmC0D0c   pcmC0D1c   pcmC0D2c   pcmC0D3c   timer
controlC0  pcmC0D0p   pcmC0D1p   pcmC0D2p   pcmC0D3p

那么我们对该函数做一些总结:

1、把相应的codec,dai和platform实例赋值到card->rtd[]中,再添加到card->rtd_list链表中,rtd也就是一snd_soc_pcm_runtime结构体。
2、添加card->dai_link+i->list到card->dai_link_list
3、遍历这个全局codec_list结构体,为codec申请空间
4、标准的alsa函数创建声卡实例
5、如果有的话,调用card的probe:card->probe(card)
6、扫描card->rtd_list链表,调用cpu_dai、codec_dais、platform的component->probe。
7、扫描card->rtd_list链表 ,调用各个子结构(cpu_dai、codec_dais、platform)的probe函数,还通过soc_new_pcm函数创建了pcm逻辑设备。
8、对dapm和dai支持的格式做出一些初始化合设置工作后,调用了 card->late_probe(card)进行一些最后的初始化合设置工作
9、调用标准alsa驱动的声卡注册函数对声卡进行注册

至此,整个Machine驱动的初始化已经完成,通过各个子结构的probe调用,实际上,也完成了部分Platfrom驱动和Codec驱动的初始化工作。/* by面朝大海0902 https://blog.csdn.net/lihuan680680/article/details/122406208 */

asoc 框架中 codec driver分析可以参考:ALSA驱动asoc框架之Codec
asoc 框架中 platform driver分析可以参考:ALSA驱动asoc框架之Platform

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值