前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux音频驱动-ASOC之Machine

Linux音频驱动-ASOC之Machine

作者头像
DragonKingZhu
发布2020-03-24 15:44:24
2.2K0
发布2020-03-24 15:44:24
举报

概述

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

root@test:~/test/kernel/sound/soc$ ls
adi    au1x  blackfin  codecs   dwc  generic  jz4740   kirkwood  mxs     omap  rockchip  sh    soc-cache.c     soc-core.c  soc-devres.c                 soc-io.c    soc-pcm.c    spear  txx9
atmel  bcm   cirrus    davinci  fsl  intel    Kconfig  Makefile  nuc900  pxa   samsung   sirf  soc-compress.c  soc-dapm.c  soc-generic-dmaengine-pcm.c  soc-jack.c  soc-utils.c  tegra  ux500

此目录下就是当前支持ASOC架构的平台,在这里以samsung架构做为参考。kernel版本: 3.18

Machine代码分析

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

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

	s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
	if (!s3c24xx_uda134x_snd_device) {
		printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
		       "Unable to register\n");
		return -ENOMEM;
	}

	platform_set_drvdata(s3c24xx_uda134x_snd_device,&snd_soc_s3c24xx_uda134x);

此出分配名字为"soc-audio"的平台设备,然后将snd_soc_s3c24xx_uda134x设置到平台设备的dev->driver_data中。关于snd_soc_s3c24xx_uda134x结构在后面说明。

既然此处注册"soc-audio"的设备,就会存在名字为"soc-audio"的驱动,搜索"soc-audio",就会发现在soc-core.c中存在。

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

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

	return platform_driver_register(&soc_driver);
}

当platform_device和platform_driver相匹配的话,就会调用soc_probe函数。

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,会在ASOC core中注册一个card。 此处的card就是snd_soc_s3c24xx_uda134x结构。接下来谈论此结构的作用。

static 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",
	.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 be_id;	/* optional ID for machine driver BE identification */

	const struct snd_soc_pcm_stream *params;

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

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

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

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

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

	/* For unidirectional dai links */
	bool playback_only;
	bool capture_only;
};

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

1. 根据struct snd_soc_dai_link结构体的个数,此处是一个,检测下需要设置的name是否已经设置。

if (link->platform_name && link->platform_of_node) {
	dev_err(card->dev,
	"ASoC: Both platform name/of_node are set for %s\n",link->name);
	return -EINVAL;
}

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

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].card = card;
	card->rtd[i].dai_link = &card->dai_link[i];
	card->rtd[i].codec_dais = devm_kzalloc(card->dev,
				sizeof(struct snd_soc_dai *) *
				(card->rtd[i].dai_link->num_codecs),
				GFP_KERNEL);
	if (card->rtd[i].codec_dais == NULL)
		return -ENOMEM;
}

3. 然后所有的重点工作全部在snd_soc_instantiate_card函数中实现。

分析snd_soc_instantiate_card函数的实际操作:

1. 根据num_links的值,进行DAIs的bind工作。第一步先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);
		return -EPROBE_DEFER;
	}

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

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

	/* Find CPU DAI from registered DAIs*/
	list_for_each_entry(component, &component_list, list) {
		if (dlc->of_node && component->dev->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))
				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中。

2. 然后根据codec的数据,寻找codec侧的dai。

/* Find CODEC from registered CODECs */
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);
		return -EPROBE_DEFER;
	}
}

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

3. 在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(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->component.name, platform_name))
			continue;
	}

	rtd->platform = platform;
}

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

4. 接着初始化注册的codec cache,cache_init代表是否已经初始化过。

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

5. 然后调用ALSA中的创建card的函数: 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;
}

6. 然后依次调用各个子部件的probe函数

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

7. 在soc_probe_link_dais函数中依次调用了cpu_dai, codec_dai侧的probe函数

/* probe the cpu_dai */
if (!cpu_dai->probed &&
		cpu_dai->driver->probe_order == order) {
	if (cpu_dai->driver->probe) {
		ret = cpu_dai->driver->probe(cpu_dai);
		if (ret < 0) {
			dev_err(cpu_dai->dev,
				"ASoC: failed to probe CPU DAI %s: %d\n",
				cpu_dai->name, ret);
			return ret;
		}
	}
	cpu_dai->probed = 1;
}

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

8. 最终调用到soc_new_pcm函数创建pcm设备:

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

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

9. 接着会在dapm和dai widget做相应的操作,后期会设置control参数,最终会调用到ALSA的注册card的函数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;
}

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • Machine代码分析
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档