1. 概述

2. soc-core.c代码分析

3. Machine代码分析

3.1 检查struct snd_soc_dai_link结构体,是否符合要求。

3.2 初始化card结构体

3.3 重点snd_soc_instantiate_card

3.3.1 soc_bind_dai_link 根据num_links的值,进行DAIs的bind工作。

3.3.2 其他一些初始化,如cache等

3.3.3 snd_card_new创建一个card

3.3.4 创建声卡的一些其他部件,如dapm_widgets等

3.3.5 调用各个子部件的probe函数、创建PCM、Control设备等、调用到platform->driver->pcm_new的函数

3.3.6 dapm和dai widget做相应的操作

3.3.7 snd_card_register

4. 总结


1. 概述

      在ASOC小节中描述了整个ASOC的架构,其中Machine是ASOC架构中的关键部件,没有Machine部件,单独的Codec和Platform是无法工作的。因此本节则先从Machine部分开始,那应该如何开始呢? 答案当然是从代码入手,先进入ASOC在kernel中的位置:  kernel/sound/soc下。

分析例子:

machine: sound\soc\samsung\s3c24xx_uda134x.c

platform: sound\soc\samsung\s3c24xx-i2s.c

codec:   sound\soc\codecs\uda134x.c

2. soc-core.c代码分析

const struct dev_pm_ops snd_soc_pm_ops = {
	.suspend = snd_soc_suspend,
	.resume = snd_soc_resume,
	.freeze = snd_soc_suspend,
	.thaw = snd_soc_resume,
	.poweroff = snd_soc_poweroff,
	.restore = snd_soc_resume,
};
EXPORT_SYMBOL_GPL(snd_soc_pm_ops);

/* ASoC platform driver */
static struct platform_driver soc_driver = {
	.driver		= {
		.name		= "soc-audio",
		.pm		= &snd_soc_pm_ops,
	},
	.probe		= soc_probe,
	.remove		= soc_remove,
};

static int __init snd_soc_init(void)
{
	snd_soc_debugfs_init();
	snd_soc_util_init();

	return platform_driver_register(&soc_driver);
}
module_init(snd_soc_init);

static void __exit snd_soc_exit(void)
{
	snd_soc_util_exit();
	snd_soc_debugfs_exit();

	platform_driver_unregister(&soc_driver);
}

     注册平台驱动soc_driver,当有同名的“soc-audio”平台设备注册时,soc_probe函数就会被调用。

static struct snd_soc_card myalsa_card = {
	.name = "tx511_UDA1341",
	.owner = THIS_MODULE,
	.dai_link = &s3c2440_uda1341_dai_link,
	.num_links = 1,
};

static void asoc_release(struct device * dev)
{
}

static struct platform_device asoc_dev = {
    .name         = "soc-audio",
    .id       = -1,
    .dev = { 
    	.release = asoc_release, 
	},
};

static int s3c2440_uda1341_init(void)
{
	platform_set_drvdata(&asoc_dev, &myalsa_card);
    platform_device_register(&asoc_dev);    
    return 0;
}

static void s3c2440_uda1341_exit(void)
{
    platform_device_unregister(&asoc_dev);
}

      平台设备又是怎么注册的呢, s3c24xx_uda134x.c中没给出常用的用法,上面代码给出了一个简单的范例,注册“soc-audio”的平台设备,并且在初始化函数中,把要注册的声卡设置到平台设备的dev->driver_data中。当platform_device和platform_driver相匹配的话,就会调用soc_probe函数。

/* probes a new socdev */
static int soc_probe(struct platform_device *pdev)
{
	struct snd_soc_card *card = platform_get_drvdata(pdev);

	/*
	 * 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;

	return snd_soc_register_card(card);
}

最终调用snd_soc_register_card注册声卡。

3. Machine代码分析

      samsung平台的machine代码选择为:  s3c24xx_uda134x.c

      此代码先注册平台驱动s3c24xx_uda134x_driver, 当平台驱动和平台设备(以前在arch下,目前在dt中配置)的名字相匹配的时候,就会调用平台驱动中的probe函数s3c24xx_uda134x_probe。

static int s3c24xx_uda134x_probe(struct platform_device *pdev)
{
	struct snd_soc_card *card = &snd_soc_s3c24xx_uda134x;
	struct s3c24xx_uda134x *priv;
	int ret;

	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	mutex_init(&priv->clk_lock);

	card->dev = &pdev->dev;
	snd_soc_card_set_drvdata(card, priv);

	ret = devm_snd_soc_register_card(&pdev->dev, card);
	if (ret)
		dev_err(&pdev->dev, "failed to register card: %d\n", ret);

	return ret;
}

        上面的文章介绍s3c24xx_uda134x.c没有注册“soc-audio”的平台设备,而是通过“s3c24xx_uda134x”平台驱动来完成的,当有同名的设备注册时,上面的s3c24xx_uda134x_probe就会被调用。声卡snd_soc_s3c24xx_uda134x直接赋值给了card的私有数据。

	snd_soc_card_set_drvdata(card, priv);
static struct platform_driver s3c24xx_uda134x_driver = {
	.probe  = s3c24xx_uda134x_probe,
	.driver = {
		.name = "s3c24xx_uda134x",
	},
};
module_platform_driver(s3c24xx_uda134x_driver);

      s3c24xx_uda134x_probe最终也会调用snd_soc_register_card。

int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card)
{
....................

	ptr = devres_alloc(devm_card_release, sizeof(*ptr), GFP_KERNEL);
	if (!ptr)
		return -ENOMEM;

	ret = snd_soc_register_card(card);
..................
}

此处的card就是snd_soc_s3c24xx_uda134x结构。接下来谈论此结构的作用。

static const struct snd_soc_ops s3c24xx_uda134x_ops = {
	.startup = s3c24xx_uda134x_startup,
	.shutdown = s3c24xx_uda134x_shutdown,
	.hw_params = s3c24xx_uda134x_hw_params,
};

static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
	.name = "UDA134X",
	.stream_name = "UDA134X",
	.codec_name = "uda134x-codec",
	.codec_dai_name = "uda134x-hifi",
	.cpu_dai_name = "s3c24xx-iis",
	.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
		   SND_SOC_DAIFMT_CBS_CFS,
	.ops = &s3c24xx_uda134x_ops,
	.platform_name	= "s3c24xx-iis",
};

static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
	.name = "S3C24XX_UDA134X",
	.owner = THIS_MODULE,
	.dai_link = &s3c24xx_uda134x_dai_link,
	.num_links = 1,
};

      其中dai_link结构就是用作连接platform和codec的,指明到底用那个codec,那个platfrom。那是通过什么指定的?  如果有兴趣可以详细看snd_soc_dai_link的注释,此注释写的非常清楚。


struct snd_soc_dai_link {
	/* config - must be set by machine driver */
	const char *name;			/* Codec name */
	const char *stream_name;		/* Stream name */
	/*
	 * You MAY specify the link's CPU-side device, either by device name,
	 * or by DT/OF node, but not both. If this information is omitted,
	 * the CPU-side DAI is matched using .cpu_dai_name only, which hence
	 * must be globally unique. These fields are currently typically used
	 * only for codec to codec links, or systems using device tree.
	 */
	const char *cpu_name;
	struct device_node *cpu_of_node;
	/*
	 * You MAY specify the DAI name of the CPU DAI. If this information is
	 * omitted, the CPU-side DAI is matched using .cpu_name/.cpu_of_node
	 * only, which only works well when that device exposes a single DAI.
	 */
	const char *cpu_dai_name;
	/*
	 * You MUST specify the link's codec, either by device name, or by
	 * DT/OF node, but not both.
	 */
	const char *codec_name;
	struct device_node *codec_of_node;
	/* You MUST specify the DAI name within the codec */
	const char *codec_dai_name;

	struct snd_soc_dai_link_component *codecs;
	unsigned int num_codecs;

	/*
	 * You MAY specify the link's platform/PCM/DMA driver, either by
	 * device name, or by DT/OF node, but not both. Some forms of link
	 * do not need a platform.
	 */
	const char *platform_name;
	struct device_node *platform_of_node;
	int id;	/* optional ID for machine driver link identification */

	const struct snd_soc_pcm_stream *params;
	unsigned int num_params;

	unsigned int dai_fmt;           /* format to set on init */

	enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */

	/* codec/machine specific init - e.g. add machine controls */
	int (*init)(struct snd_soc_pcm_runtime *rtd);

	/* optional hw_params re-writing for BE and FE sync */
	int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,
			struct snd_pcm_hw_params *params);

	/* machine stream operations */
	const struct snd_soc_ops *ops;
	const struct snd_soc_compr_ops *compr_ops;

	/* Mark this pcm with non atomic ops */
	bool nonatomic;

	/* For unidirectional dai links */
	unsigned int playback_only:1;
	unsigned int capture_only:1;

	/* Keep DAI active over suspend */
	unsigned int ignore_suspend:1;

	/* Symmetry requirements */
	unsigned int symmetric_rates:1;
	unsigned int symmetric_channels:1;
	unsigned int symmetric_samplebits:1;

	/* Do not create a PCM for this DAI link (Backend link) */
	unsigned int no_pcm:1;

	/* This DAI link can route to other DAI links at runtime (Frontend)*/
	unsigned int dynamic:1;

	/* DPCM capture and Playback support */
	unsigned int dpcm_capture:1;
	unsigned int dpcm_playback:1;

	/* DPCM used FE & BE merged format */
	unsigned int dpcm_merged_format:1;

	/* pmdown_time is ignored at stop */
	unsigned int ignore_pmdown_time:1;

	struct list_head list; /* DAI link list of the soc card */
	struct snd_soc_dobj dobj; /* For topology */
};

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


/**
 * 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)
{
	int i, ret;
	struct snd_soc_pcm_runtime *rtd;

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

		ret = soc_init_dai_link(card, link);
		if (ret) {
			dev_err(card->dev, "ASoC: failed to init link %s\n",
				link->name);
			return ret;
		}
	}

	dev_set_drvdata(card->dev, card);

	snd_soc_initialize_card_lists(card);

	INIT_LIST_HEAD(&card->dai_link_list);
	card->num_dai_links = 0;

	INIT_LIST_HEAD(&card->rtd_list);
	card->num_rtd = 0;

	INIT_LIST_HEAD(&card->dapm_dirty);
	INIT_LIST_HEAD(&card->dobj_list);
	card->instantiated = 0;
	mutex_init(&card->mutex);
	mutex_init(&card->dapm_mutex);

	ret = snd_soc_instantiate_card(card);
	if (ret != 0)
		return ret;

	/* deactivate pins to sleep state */
	list_for_each_entry(rtd, &card->rtd_list, list)  {
		struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
		int j;

		for (j = 0; j < rtd->num_codecs; j++) {
			struct snd_soc_dai *codec_dai = rtd->codec_dais[j];
			if (!codec_dai->active)
				pinctrl_pm_select_sleep_state(codec_dai->dev);
		}

		if (!cpu_dai->active)
			pinctrl_pm_select_sleep_state(cpu_dai->dev);
	}

	return ret;
}

我们分几个部分来讲解这个函数。

3.1 检查struct snd_soc_dai_link结构体,是否符合要求。

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

		ret = soc_init_dai_link(card, link);
		if (ret) {
			dev_err(card->dev, "ASoC: failed to init link %s\n",
				link->name);
			return ret;
		}
	}

	dev_set_drvdata(card->dev, card);

3.2 初始化card结构体

	dev_set_drvdata(card->dev, card);

	snd_soc_initialize_card_lists(card);

	INIT_LIST_HEAD(&card->dai_link_list);
	card->num_dai_links = 0;

	INIT_LIST_HEAD(&card->rtd_list);
	card->num_rtd = 0;

	INIT_LIST_HEAD(&card->dapm_dirty);
	INIT_LIST_HEAD(&card->dobj_list);
	card->instantiated = 0;
	mutex_init(&card->mutex);
	mutex_init(&card->dapm_mutex);

3.3 重点snd_soc_instantiate_card

	ret = snd_soc_instantiate_card(card);
	if (ret != 0)
		return ret;

分析 snd_soc_instantiate_card函数的实际操作:

3.3.1 soc_bind_dai_link 根据num_links的值,进行DAIs的bind工作。

	/* bind DAIs */
	for (i = 0; i < card->num_links; i++) {
		ret = soc_bind_dai_link(card, &card->dai_link[i]);
		if (ret != 0)
			goto base_error;
	}

(1)检测dai link是否已经绑定了

	if (soc_is_dai_link_bound(card, dai_link)) {
		dev_dbg(card->dev, "ASoC: dai link %s already bound\n",
			dai_link->name);
		return 0;
	}

检测dai link是否已经绑定了。

(2)分配一个struct snd_soc_pcm_runtime结构

    rtd = soc_new_pcm_runtime(card, dai_link);
	if (!rtd)
		return -ENOMEM;

static struct snd_soc_pcm_runtime *soc_new_pcm_runtime(
	struct snd_soc_card *card, struct snd_soc_dai_link *dai_link)
{
	struct snd_soc_pcm_runtime *rtd;

	rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime), GFP_KERNEL);
	if (!rtd)
		return NULL;

	INIT_LIST_HEAD(&rtd->component_list);
	rtd->card = card;
	rtd->dai_link = dai_link;
	rtd->codec_dais = kzalloc(sizeof(struct snd_soc_dai *) *
					dai_link->num_codecs,
					GFP_KERNEL);
	if (!rtd->codec_dais) {
		kfree(rtd);
		return NULL;
	}

	return rtd;
}

      分配一个struct snd_soc_pcm_runtime结构,然后根据num_links,设置card,复制dai_link等。

(3)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);
		goto _err_defer;
	}
	snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);

      此处dai_link就是在machine中注册的struct snd_soc_dai_link结构体,cpu_dai_name也就是注册的name,最后通过snd_soc_find_dai接口出查找。

/**
 * snd_soc_find_dai - Find a registered DAI
 *
 * @dlc: name of the DAI or the DAI driver and optional component info to match
 *
 * This function will search all registered components and their DAIs to
 * find the DAI of the same name. The component's of_node and name
 * should also match if being specified.
 *
 * Return: pointer of DAI, or NULL if not found.
 */
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;
	struct device_node *component_of_node;

	lockdep_assert_held(&client_mutex);

	/* Find CPU DAI from registered DAIs*/
	list_for_each_entry(component, &component_list, list) {
		component_of_node = component->dev->of_node;
		if (!component_of_node && component->dev->parent)
			component_of_node = component->dev->parent->of_node;

		if (dlc->of_node && component_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)
			    && (!dai->driver->name
				|| strcmp(dai->driver->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中。

(4)然后根据codec的数据,寻找codec侧的dai

	/* Find CODEC from registered CODECs */
	codec_dais = rtd->codec_dais;
	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);
			goto _err_defer;
		}
		snd_soc_rtdcom_add(rtd, codec_dais[i]->component);
	}

         然后将找到的codec侧的dai也同样赋值给snd_soc_pcm_runtime中的codec_dai中。

(5)在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(component, &component_list, list) {
		platform_of_node = component->dev->of_node;
		if (!platform_of_node && component->dev->parent->of_node)
			platform_of_node = component->dev->parent->of_node;

		if (dai_link->platform_of_node) {
			if (platform_of_node != dai_link->platform_of_node)
				continue;
		} else {
			if (strcmp(component->name, platform_name))
				continue;
		}

		snd_soc_rtdcom_add(rtd, component);
	}

这样查找完毕之后,snd_soc_pcm_runtime中存储了查找到的codec, dai,  platform。

	soc_add_pcm_runtime(card, rtd);

3.3.2 其他一些初始化,如cache等

	/* bind aux_devs too */
	for (i = 0; i < card->num_aux_devs; i++) {
		ret = soc_bind_aux_dev(card, i);
		if (ret != 0)
			goto base_error;
	}

	/* add predefined DAI links to the list */
	for (i = 0; i < card->num_links; i++)
		snd_soc_add_dai_link(card, card->dai_link+i);

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

3.3.3 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.3.4 创建声卡的一些其他部件,如dapm_widgets等

	if (card->dapm_widgets)
		snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
					  card->num_dapm_widgets);

	if (card->of_dapm_widgets)
		snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets,
					  card->num_of_dapm_widgets);

3.3.5 调用各个子部件的probe函数、创建PCM、Control设备等、调用到platform->driver->pcm_new的函数

	/* 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++) {
		list_for_each_entry(rtd, &card->rtd_list, list) {
			ret = soc_probe_link_components(card, rtd, order);
			if (ret < 0) {
				dev_err(card->dev,
					"ASoC: failed to instantiate card %d\n",
					ret);
				goto probe_dai_err;
			}
		}
	}

	/* probe auxiliary components */
	ret = soc_probe_aux_devices(card);
	if (ret < 0)
		goto probe_dai_err;

	/* Find new DAI links added during probing components and bind them.
	 * Components with topology may bring new DAIs and DAI links.
	 */
	list_for_each_entry(dai_link, &card->dai_link_list, list) {
		if (soc_is_dai_link_bound(card, dai_link))
			continue;

		ret = soc_init_dai_link(card, dai_link);
		if (ret)
			goto probe_dai_err;
		ret = soc_bind_dai_link(card, dai_link);
		if (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++) {
		list_for_each_entry(rtd, &card->rtd_list, list) {
			ret = soc_probe_link_dais(card, rtd, order);
			if (ret < 0) {
				dev_err(card->dev,
					"ASoC: failed to instantiate card %d\n",
					ret);
				goto probe_dai_err;
			}
		}
	}

(1) 在soc_probe_link_dais函数中依次调用了cpu_dai, codec_dai侧的probe函数


static int soc_probe_link_dais(struct snd_soc_card *card,
		struct snd_soc_pcm_runtime *rtd, int order)
{
	struct snd_soc_dai_link *dai_link = rtd->dai_link;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	int i, ret;

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

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

	ret = soc_probe_dai(cpu_dai, order);
	if (ret)
		return ret;

	/* probe the CODEC DAI */
	for (i = 0; i < rtd->num_codecs; i++) {
		ret = soc_probe_dai(rtd->codec_dais[i], order);
		if (ret)
			return ret;
	}

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

	/* do machine specific initialization */
	if (dai_link->init) {
		ret = dai_link->init(rtd);
		if (ret < 0) {
			dev_err(card->dev, "ASoC: failed to init %s: %d\n",
				dai_link->name, ret);
			return ret;
		}
	}

	if (dai_link->dai_fmt)
		snd_soc_runtime_set_dai_fmt(rtd, dai_link->dai_fmt);

	ret = soc_post_component_init(rtd, dai_link->name);
	if (ret)
		return ret;

#ifdef CONFIG_DEBUG_FS
	/* add DPCM sysfs entries */
	if (dai_link->dynamic)
		soc_dpcm_debugfs_add(rtd);
#endif

	if (cpu_dai->driver->compress_new) {
		/*create compress_device"*/
		ret = cpu_dai->driver->compress_new(rtd, rtd->num);
		if (ret < 0) {
			dev_err(card->dev, "ASoC: can't create compress %s\n",
					 dai_link->stream_name);
			return ret;
		}
	} else {

		if (!dai_link->params) {
			/* create the pcm */
			ret = soc_new_pcm(rtd, rtd->num);
			if (ret < 0) {
				dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",
				       dai_link->stream_name, ret);
				return ret;
			}
			ret = soc_link_dai_pcm_new(&cpu_dai, 1, rtd);
			if (ret < 0)
				return ret;
			ret = soc_link_dai_pcm_new(rtd->codec_dais,
						   rtd->num_codecs, rtd);
			if (ret < 0)
				return ret;
		} else {
			INIT_DELAYED_WORK(&rtd->delayed_work,
						codec2codec_close_delayed_work);

			/* link the DAI widgets */
			ret = soc_link_dai_widgets(card, dai_link, rtd);
			if (ret)
				return ret;
		}
	}

	return 0;
}

(2)最终调用到soc_new_pcm函数创建pcm设备

		if (!dai_link->params) {
			/* create the pcm */
			ret = soc_new_pcm(rtd, rtd->num);
			if (ret < 0) {
				dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",
				       dai_link->stream_name, ret);
				return ret;
			}
			ret = soc_link_dai_pcm_new(&cpu_dai, 1, rtd);
			if (ret < 0)
				return ret;
			ret = soc_link_dai_pcm_new(rtd->codec_dais,
						   rtd->num_codecs, rtd);
			if (ret < 0)
				return ret;

(3)调用到platform->driver->pcm_new的函数


static int soc_link_dai_pcm_new(struct snd_soc_dai **dais, int num_dais,
				struct snd_soc_pcm_runtime *rtd)
{
	int i, ret = 0;

	for (i = 0; i < num_dais; ++i) {
		struct snd_soc_dai_driver *drv = dais[i]->driver;

		if (!rtd->dai_link->no_pcm && drv->pcm_new)
			ret = drv->pcm_new(rtd, dais[i]);
		if (ret < 0) {
			dev_err(dais[i]->dev,
				"ASoC: Failed to bind %s with pcm device\n",
				dais[i]->name);
			return ret;
		}
	}

	return 0;
}

        最中此函数会调用ALSA的标准创建pcm设备的接口:  snd_pcm_new,然后会设置pcm相应的ops操作函数集合。然后调用到platform->driver->pcm_new的函数。此处不帖函数了。

3.3.6 dapm和dai widget做相应的操作

	snd_soc_dapm_link_dai_widgets(card);
	snd_soc_dapm_connect_dai_link_widgets(card);

	if (card->controls)
		snd_soc_add_card_controls(card, card->controls, card->num_controls);

	if (card->dapm_routes)
		snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
					card->num_dapm_routes);

	if (card->of_dapm_routes)
		snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,
					card->num_of_dapm_routes);

	/* try to set some sane longname if DMI is available */
	snd_soc_set_dmi_name(card, NULL);

3.3.7 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;
	}

4. 总结

       总结:  经过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.   注册平台设备。

Logo

更多推荐