ALSA System on Chip(ASOC)

此文档仅作为开发随笔记录文档,可作为正式文档做材料参考,但不做正式文档。

 

Written bywolfgang huang(stillinux@gmail.com)

 

此类文档仅记录Android4.1.2+Kernel2.6.37+OMAP3730平台ALSA开发及内核要点,备注好资料应用,以供后续开发人员快速入手,也可作为科普类资料,供其他相关人员学习。

 

ALSA System on Chip(ASOC)


本系列文档的目标是使读者从安卓HAL一直走到硬件层的处理流程,使Kernel的ALSA构架内部细节透露出来。但是由于鄙人水平有限,难免有错误理解,如有错误,请联系,立即改正。

 

下面我们细述linux音频子系统中的ASOC,对于整体的框架作图如下。



我们接下来分块的对ASOC的子模块在内核中如何启动,注册等运作流程。

 

在linux设备模型下,device与相应的driver相匹配,从而支持linux透过drvier对于其绑定的device做到控制管理。故使对应的设备要在linux下进行良好的工作,则必须正确加载device和driver。


Platform模块:

对于platform的设备启动流程如下:

omap_init_audio->platform_device_register(&omap_pcm);

将omap_pcm的设备注册到platform总线的设备链表上。

 

对于platform的驱动,则有:

snd_omap_pcm_init->platform_driver_register(&omap_pcm_driver);

将omap_pcm_driver注册到platform总线的驱动链表上。

 

由linux驱动模型,当注册driver或device时,都会到其隶属总线的对应链表上去查找匹配的驱动或设备(linux更相信设备)。这样匹配后调用对应的driver->probe,则调用到我们的omap_pcm_probe,其调用snd_soc_register_platform,注册omap_soc_platform到ASOC的platform_list中,且由于device中对于其id设置为-1,则platform的名称设置为dev->driver->name。

 

Note:linux驱动模型下,platform总线绑定的依据有三种,of设备树的dtb方式,还有id_table的方式,最后就是默认的name比对方式。对于其他总线,诸如SPI用modalias比对等方式,此处不做过多细述。

 

Cpu_dai模块:

对于cpu_dai启动流程如下:

omap_init_audio->platform_device_register(&omap_mcbsp1);

将omap_mcbsp的设备注册到platform总线的设备链表上。

 

对于cpu_dai的驱动,则有:

snd_omap_mcbsp_init->platform_driver_register(&asoc_mcbsp_driver);

将asoc_mcbsp_driver注册到platform总线的驱动链表上。

 

由上面分析,我们知道驱动模型绑定后,根据名称都为omap-mcbsp-dai匹配后,调用到其驱动的probe接口,即asoc_mcbsp_probe。其调用snd_soc_register_dai,将omap_mcbsp_dai注册到ASOC的dai_list链表上。

 

 

Codec模块和Codec_dai模块:

在我们使用的OMAP平台上,codec使用的集成的TPS65951,其设备加载过程较为复杂,下面进行细述。

其设备的加载流程:

devkit8000_init->devkit8000_i2c_init->omap_register_i2c_bus(..devkit8000_i2c1_boardinfo)。

tps65951,使用的是twl4030兼容的驱动。

twl_init->i2c_add_driver(&twl_driver)<throughid_table(与上面的boardinfo匹配)>

->twl_probe->add_children->add_child<has_codec成立的条件下>

->platform_device_add()<name=”twl4030-audio”>。

将tps65951中的音频声卡codec设备加入到platform总线的设备链表。

但是此处并没有完成codec资源的解析,其又调用一次匹配后,进行资源的分块解析。

该过程为:

twl4030_codec_init->platform_driver_register(&twl4030_codec_driver);

与上面注册的codec设备匹配后,运行其驱动的probe,即twl4030_codec_probe,

其完成对于codec时钟的设置后,调用mfd_add_devices将获得资源重组到新的platform_device中,并将其加入到platform总线上,其名称为twl4030_codec。此时才是最终的tps65951设备声卡加入的真正设备资源。

对于其真正的设备资源,对应的驱动有:

twl4030_modinit-> platform_driver_register(&twl4030_codec_driver);<name=”twl4030_codec”>。

则匹配后调用其驱动的probe,即twl4030_codec_probe(与上面的有区别)。其调用snd_soc_register_codec,注册了对应的

 

则根据驱动模型绑定后,我们知道运行其驱动的probe,即twl4030_codec_probe,其调用snd_soc_register_codec,注册soc_codec_dev_twl4030到ASOC的codec_list,并将对应的twl4030_dai注册到ASOC的dai_list中,twl4030_dai有hifi和voice两种模式,即codec支持这两种音频数据流。其最终根据ASOC的配置环境snd_soc_dai_link,选用对应的配置模式。

 

ASOC(Machine):

最后对应ASOC整体的框架注册过程:

对应其设备注册流程如下:

omap3beagle_soc_init-> platform_device_add(omap3beagle_snd_device);<name=”soc-audio”>

 

对应的驱动注册则有:

snd_soc_init-> platform_driver_register(&soc_driver);根据驱动模型绑定后,则会调用到soc_probe函数。其获取ASOC定义的snd_soc_card配置数据snd_soc_omap3beagle。

snd_soc_card中的成员num_link定义有几个snd_soc_pcm_runtime,对于该处为1,成员dai_link,则定义了该snd_card对应的配置环境数据。

 

其结构定义如下:

struct snd_soc_dai_link {
	/* config - must be set by machine driver */
	const char *name;			/* Codec name */
	const char *stream_name;		/* Stream name */
	const char *codec_name;		/* for multi-codec */
	const char *platform_name;	/* for multi-platform */
	const char *cpu_dai_name;
	const char *codec_dai_name;

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

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

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

	/* machine stream operations */
	struct snd_soc_ops *ops;
};

根据注释,在观察我们的具体配置:

static struct snd_soc_dai_link omap3beagle_dai = {
	.name = "TWL4030",
	.stream_name = "TWL4030",		//runtime名称
	.cpu_dai_name = "omap-mcbsp-dai.1",	//cpu_dai<I2S>
	.platform_name = "omap-pcm-audio",	//platform
	.codec_dai_name = "twl4030-hifi",	//codec_dai,选择高保真音频数据流格式
	.codec_name = "twl4030-codec",		//tps65951
	.ops = &omap3beagle_ops,
};

soc_probe获取对应的snd_soc_card配置数据后,调用snd_soc_register_card。

static int snd_soc_register_card(struct snd_soc_card *card)
{
	int i;

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

	card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) * card->num_links,
			GFP_KERNEL);
	if (card->rtd == NULL)
		return -ENOMEM;

	for (i = 0; i < card->num_links; i++)
		card->rtd[i].dai_link = &card->dai_link[i];

	INIT_LIST_HEAD(&card->list);
	card->instantiated = 0;
	mutex_init(&card->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;
}


根据配置数据的num_links,分配对应的snd_soc_pcm_runtime,并绑定各自snd_soc_pcm_runtime与dai_link,接着将加入的snd_soc_card加入到card_list,ASOC最多支持八个snd_soc_card。最后调用snd_soc_instantiate_cards。该函数在其他的注册接口也有调用,但是其只有在dai_link中的组件全部都组建完成才能继续往下走。故在此处综述。

snd_soc_instantiate_cards:遍历card_list,依次调用snd_soc_instantiate_card。


static void snd_soc_instantiate_card(struct snd_soc_card *card)
{
	struct platform_device *pdev = to_platform_device(card->dev);
	int ret, i;

	mutex_lock(&card->mutex);

	if (card->instantiated) {
		mutex_unlock(&card->mutex);
		return;
	}

	/* bind DAIs */
	for (i = 0; i < card->num_links; i++)
		soc_bind_dai_link(card, i);

	/* bind completed ? */
	if (card->num_rtd != card->num_links) {
		mutex_unlock(&card->mutex);
		return;
	}

	/*
	 * #define SNDRV_DEFAULT_IDX1      (-1)
 	 * #define SNDRV_DEFAULT_STR1      NULL
	 */

	/* card bind complete so register a sound card */
	ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
			card->owner, 0, &card->snd_card);
	if (ret < 0) {
		printk(KERN_ERR "asoc: can't create sound card for card %s\n",
			card->name);
		mutex_unlock(&card->mutex);
		return;
	}
	card->snd_card->dev = card->dev;

#ifdef CONFIG_PM
	/* deferred resume work */
	INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
#endif

	/* initialise the sound card only once */
	if (card->probe) {
		ret = card->probe(pdev);
		if (ret < 0)
			goto card_probe_error;
	}

	for (i = 0; i < card->num_links; i++) {
		ret = soc_probe_dai_link(card, i);
		if (ret < 0) {
			pr_err("asoc: failed to instantiate card %s: %d\n",
			       card->name, ret);
			goto probe_dai_err;
		}
	}

	snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),
		 "%s",  card->name);
	snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),
		 "%s", card->name);

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

#ifdef CONFIG_SND_SOC_AC97_BUS
	/* register any AC97 codecs */
	for (i = 0; i < card->num_rtd; i++) {
		ret = soc_register_ac97_dai_link(&card->rtd[i]);
		if (ret < 0) {
			printk(KERN_ERR "asoc: failed to register AC97 %s\n", card->name);
			while (--i >= 0)
				soc_unregister_ac97_dai_link(&card->rtd[i]);
			goto probe_dai_err;
		}
	}
#endif

	card->instantiated = 1;
	mutex_unlock(&card->mutex);
	return;

probe_dai_err:
	for (i = 0; i < card->num_links; i++)
		soc_remove_dai_link(card, i);

card_probe_error:
	if (card->remove)
		card->remove(pdev);

	snd_card_free(card->snd_card);

	mutex_unlock(&card->mutex);
}

我们把该函数拆分为四段函数进行分析,分别为soc_bind_dai_link,snd_card_create,soc_probe_dai_link及snd_card_register。AC97我们在此略过,一般嵌入式设备都使用I2S。

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;

	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_link按名称指定的cpu_dai
		if (!strcmp(cpu_dai->name, dai_link->cpu_dai_name)) {

			if (!try_module_get(cpu_dai->dev->driver->owner))
				return -ENODEV;

			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) {
		//依次遍历,查找是否有dai_link按名称指定的codec
		if (!strcmp(codec->name, dai_link->codec_name)) {
			rtd->codec = codec;

			if (!try_module_get(codec->dev->driver->owner))
				return -ENODEV;

			/* CODEC found, so find CODEC DAI from registered DAIs from this CODEC*/
			list_for_each_entry(codec_dai, &dai_list, list) {
				//依次遍历,查找是否有dai_link按名称指定的codec_dai
				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 already have the CODEC DAI for this link ? */
	if (rtd->platform) {
		goto out;
	}
	/* no, then find CPU DAI from registered DAIs*/
	list_for_each_entry(platform, &platform_list, list) {
		//依次遍历,查找是否有dai_link按名称指定的platform
		if (!strcmp(platform->name, dai_link->platform_name)) {

			if (!try_module_get(platform->dev->driver->owner))
				return -ENODEV;

			rtd->platform = platform;
			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_link指定的四大模块,缺一不可,且对应于多个num_links的还需要一一绑定。找到对应的dai_link指定的模块后,就将runtime与其对应模块进行绑定。


int snd_card_create(int idx, const char *xid,
		    struct module *module, int extra_size,
		    struct snd_card **card_ret)
{
	struct snd_card *card;
	int err, idx2;

	if (snd_BUG_ON(!card_ret))
		return -EINVAL;
	*card_ret = NULL;

	if (extra_size < 0)
		extra_size = 0;
	card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
	……
	/* 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;
	}
	……
  	return err;
}

其分配对应的snd_card,并分配对应的id,且ASOC最大支持八个snd_card。之后调用snd_ctl_create->snd_device_new(card, SNDRV_DEV_CONTROL,…);注册该snd_card绑定的mixer控制设备节点,controlCx。且其目前只是创建,在后面snd_card_register的时候注册到用户空间设备节点。该函数调用snd_info_card_create,创建/proc/asound/cardX,向用户透露信息。


static int soc_probe_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 = 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\n", card->name, num);

	/* config components */
	codec_dai->codec = codec;
	codec->card = card;
	cpu_dai->platform = platform;
	rtd->card = card;
	rtd->dev.parent = card->dev;
	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) {
		if (cpu_dai->driver->probe) {
			ret = cpu_dai->driver->probe(cpu_dai);
			if (ret < 0) {
				printk(KERN_ERR "asoc: failed to probe CPU DAI %s\n",
						cpu_dai->name);
				return ret;
			}
		}
		cpu_dai->probed = 1;
		/* mark cpu_dai as probed and add to card cpu_dai list */
		list_add(&cpu_dai->card_list, &card->dai_dev_list);
	}

	/* probe the CODEC */
	if (!codec->probed) {
		if (codec->driver->probe) {
			ret = codec->driver->probe(codec);
			if (ret < 0) {
				printk(KERN_ERR "asoc: failed to probe CODEC %s\n",
						codec->name);
				return ret;
			}
		}

		soc_init_codec_debugfs(codec);

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

	/* probe the platform */
	if (!platform->probed) {
		if (platform->driver->probe) {
			ret = platform->driver->probe(platform);
			if (ret < 0) {
				printk(KERN_ERR "asoc: failed to probe platform %s\n",
						platform->name);
				return ret;
			}
		}
		/* mark platform as probed and add to card platform list */
		platform->probed = 1;
		list_add(&platform->card_list, &card->platform_dev_list);
	}

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

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

	/* DAPM dai link stream work */
	INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);

	/* now that all clients have probed, initialise the DAI link */
	if (dai_link->init) {
		ret = dai_link->init(rtd);
		if (ret < 0) {
			printk(KERN_ERR "asoc: failed to init %s\n", dai_link->stream_name);
			return ret;
		}
	}

	/* Make sure all DAPM widgets are instantiated */
	snd_soc_dapm_new_widgets(codec);
	snd_soc_dapm_sync(codec);

	/* register the rtd device */
	rtd->dev.release = rtd_release;
	rtd->dev.init_name = dai_link->name;
	ret = device_register(&rtd->dev);	/* TWL4030 in the /sys/devices/platform/soc-audio */
	if (ret < 0) {
		printk(KERN_ERR "asoc: failed to register DAI runtime device %d\n", ret);
		return ret;
	}

	rtd->dev_registered = 1;
	ret = device_create_file(&rtd->dev, &dev_attr_pmdown_time);
	if (ret < 0)
		printk(KERN_WARNING "asoc: failed to add pmdown_time sysfs\n");

	/* add DAPM sysfs entries for this codec */
	ret = snd_soc_dapm_sys_add(&rtd->dev);
	if (ret < 0)
		printk(KERN_WARNING "asoc: failed to add codec dapm sysfs entries\n");

	/* add codec sysfs entries */
	ret = device_create_file(&rtd->dev, &dev_attr_codec_reg);
	if (ret < 0)
		printk(KERN_WARNING "asoc: failed to add codec sysfs files\n");

	/* create the pcm */
	ret = soc_new_pcm(rtd, num);
	if (ret < 0) {
		printk(KERN_ERR "asoc: can't create pcm %s\n", dai_link->stream_name);
		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;
}

该函数对四个模块依次调用probe接口,这些probe函数不进行细述,主要是对于声卡的初始化,并把声卡内部的控件加入到系统snd_kcontrol中,其他模块未做主要事宜或者没有probe接口。接着初始化close_delayed_work工作队列,其在关闭接口时,用于延时调用,防止pop刺耳噪音。接下来对于DAPM的初始化和同步。开始DAPM将加入的codec->dapm_widget进行分类型初始化,并绑定相关的电源操作接口,最后以snd_kcontrol提供用户控制。调用snd_soc_dapm_sync,对加入的控件分类加到上电和下电的链表中,并根据情况对不同的链表上下电处理。之后就是相关sysfs属性文件的注册,注册到sysfs文件系统中。最后调用soc_new_pcm,根据对应配置的codec_dai的packback和capture创建对应的pcm设备空间,并完成其初始化,绑定对应的操作接口,但是其也要在最后snd_card_register的时候才能注册到sysfs和devtmpfs中。

snd_card_register->snd_device_register_all:完成controlCx,pcmCxDx(p/c)的到sysfs和devtmpfs文件系统的注册。<timer在alsa_timer_init -> snd_register_device 完成,不过我们不关注>

 

 

通过上述分析,我们知道了音频子系统,用户操作的接口在内核中是经过了怎样的流程才注册到用户空间。对于具体操作这些设备节点,又会是怎样的流程我们在后续的文档进行分析。对于访问具体的控件,调整路径,电源控制,都留在后续的文档一一分析。

 

下面我们以图表的形式分析这些设备节点的注册过程。











  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值