• Linux音频-machine类


    前言

    有文章写的很好,转载(chao)了,原文连接

    openwrt源码的根目录下,./build_dir/toolchain-mipsel_24kc_gcc-11.2.0_musl/linux-5.10.176/Documentation/sound/soc/目录下,有对machine, platform, codec的描述。

    在查看此节之前,需要具备Linux设备树、Linux系统platform总线驱动的大致了解,否则读起来可能晦涩难懂。

    关于下午字段的搜索,可以在对应目录下,使用grep "目标字符串" * -nrwgrep "目标字符串" * -nr命令来搜索。

    概述

    在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
    
    • 1
    • 2
    • 3

    此目录下就是当前支持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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    此出分配名字为"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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    当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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    此处会调用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,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    其中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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    .cpu_dai_name: 用于指定cpu侧的dai名字,也就是所谓的cpu侧的数字音频接口,一般都是i2S接口。如果省略则会使用cpu_name/cpu_of_node。

    .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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 分配一个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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1. 然后所有的重点工作全部在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;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    此处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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    此函数会在component_list链表中先找到相同的name,然后在component->dai_list中查找是否有相同的dai_name。此处的component_list是在注册codec和platform中的时候设置的。会在codec和platform的时候会详细介绍。在此处找到注册的cpu_dai之后,存在snd_soc_pcm_runtime中的cpu_dai中。

    1. 然后根据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;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

    1. 在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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

    1. 接着初始化注册的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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 然后调用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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 然后依次调用各个子部件的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;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    1. 在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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    1. 最终调用到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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    1. 接着会在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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

  • 相关阅读:
    C++ 类
    swagger接口导入到数据库实现
    模型效果优化,试一下多种交叉验证的方法(系统实操)
    猿创征文 | kafka框架从入门到精通(全)
    Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
    牛客网Verilog刷题 | 快速入门-基础语法
    大模型的实践应用2-基于BERT模型训练医疗智能诊断问答的运用研究,协助医生进行疾病诊断
    Ubuntu18 vscode配置Ceres的调试
    Java从文件路径中获取文件名的方法
    认识Oracle
  • 原文地址:https://blog.csdn.net/weixin_43871650/article/details/134027728