【ASOC全解析(三)】machine原理和实战
- 一、machine概述与原理
-
- 1.1 machine的主要职责
- 1.2 本文的主要内容
- 二、machine程序示例
-
- 完整的machine驱动代码示例
- 扩展知识:SND_SOC_DAILINK_DEFS
-
- 使用方法说明
- 总结与其它说明
/*****************************************************************************************************************/
声明: 本博客内容均由https://blog.csdn.net/weixin_47702410原创,转载or引用请注明出处,谢谢!
创作不易,如果文章对你有帮助,麻烦点赞 收藏支持~感谢
/*****************************************************************************************************************/
一、machine概述与原理
在ASOC中,Machine层,也称为“机器层”或“板级层”,是ASoC架构中负责将Codec和Platform层连接起来的部分。它是ASoC中与硬件平台相关的部分,通常包含了特定于机器(如开发板或产品)的音频路由和控制逻辑。
1.1 machine的主要职责
Machine层的主要职责包括:
-
定义音频路径:Machine层定义了音频数据在系统中的流动路径。这包括输入(如麦克风)和输出(如扬声器)设备的连接方式,以及它们如何与Codec设备进行交互。
-
控制音频接口:Machine层负责管理和配置音频接口,例如I2S、PCM、AC97等,这些接口用于在Codec和SoC之间传输音频数据。
-
音频路由和混音:Machine层定义了音频信号的路由,例如将哪个音频输入路由到哪个输出,以及如何混合不同的音频源。
-
DAPM(Dynamic Audio Power Management)配置:Machine层配置DAPM策略,以便在不使用某些音频组件时关闭它们的电源,从而节省能源。
-
板级初始化代码:Machine层包含了特定于机器的初始化代码,这些代码在系统启动时执行,用于初始化音频硬件和ASoC组件。
-
提供用户空间接口:Machine层通过ALSA提供了用户空间接口,允许应用程序控制音频硬件,例如调整音量、改变音频路由等。
Machine层通常是由设备制造商或社区开发者针对特定硬件平台编写的,因为它需要详细了解硬件的音频架构和功能。在Linux内核源代码中,Machine层的代码通常位于sound/soc/目录下的与特定板相关的子目录中。
Machine层的实现通常涉及到编写一个Machine驱动,这个驱动会注册一个snd_soc_card结构,该结构描述了音频卡的各种属性,包括DAI(Digital Audio Interface)链接、Codec信息、平台信息以及DAPM路由等。这个驱动还会处理与音频相关的平台特定事件,比如耳机插入检测或按钮控制等。
1.2 本文的主要内容
本文针对DAI的连接为主,进行相关的实践。即将所有组件驱动程序(例如Codec端、platform端和 DAI)粘合在一起的代码。
其它功能会关联ALSA Core层或者DAPM或者指定硬件相关的知识,后续有时间再另作文章介绍。
二、machine程序示例
针对DAI的连接,主要是有三个步骤:
- 1、定义DAI_Link连接codec和platform
- 2、定义声卡,需要定义snd_soc_card
- 3、注册声卡
上面的这三点,主要是调用API函数snd_soc_register_card完成(这个函数很重要,但内容较多,后续有时间再出一版文章详细说明)
完整的machine驱动代码示例
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> SND_SOC_DAILINK_DEFS(mycodec, DAILINK_COMP_ARRAY(COMP_CPU("myplat.0")), DAILINK_COMP_ARRAY(COMP_CODEC("my_codec.0", "my_codec_dai")), DAILINK_COMP_ARRAY(COMP_PLATFORM("myplat.0"))); static int my_card_init(struct snd_soc_pcm_runtime *rtd) { printk("%s,line:%d ",__func__,__LINE__); return 0; } static int my_card_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; //struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_card *card = rtd->card; unsigned int freq; //int ret; int stream_flag; printk("%s,line:%d ",__func__,__LINE__); switch (params_rate(params)) { case 8000: case 12000: case 16000: case 24000: case 32000: case 48000: case 96000: case 192000: freq = 24576000; break; case 11025: case 22050: case 44100: freq = 22579200; break; default: dev_err(card->dev, "invalid rate setting "); return -EINVAL; } /* the substream type: 0->playback, 1->capture */ stream_flag = substream->stream; return 0; } static struct snd_soc_ops my_card_ops = { .hw_params = my_card_hw_params, }; static struct snd_soc_dai_link my_card_dai_link[] = { { .name = "my-codec", .stream_name = "MY-CODEC", SND_SOC_DAILINK_REG(mycodec), /* SND_SOC_DAILINK_REG(mycodec)等效于 .codec_name = "my_codec.0", .codec_dai_name = "my_codec_dai", .cpu_dai_name = "myplat.0", .platform_name = "myplat.0", */ .init = my_card_init, .ops = &my_card_ops, }, }; static struct snd_soc_card snd_soc_my_card = { .name = "my-codec", .owner = THIS_MODULE, .dai_link = my_card_dai_link, .num_links = ARRAY_SIZE(my_card_dai_link), }; static int mymachine_probe(struct platform_device *pdev) { int ret = 0; struct snd_soc_card *card = &snd_soc_my_card; printk("-----%s---- ",__func__); /* register the soc card */ card->dev = &pdev->dev; ret = snd_soc_register_card(card); if (ret < 0) { dev_err(&pdev->dev, "snd_soc_register_card failed %d ", ret); return -1; } return ret; } static int mymachine_remove(struct platform_device *pdev){ printk("-----%s---- ",__func__); return 0; } static void mymachine_pdev_release(struct device *dev){ printk("-----%s---- ",__func__); } static struct platform_device mymachine_pdev = { .name = "mymachine", .dev.release = mymachine_pdev_release, }; static struct platform_driver mymachine_pdrv = { .probe = mymachine_probe, .remove = mymachine_remove, .driver = { .name = "mymachine", }, }; static int __init mymachine_init(void) { int ret; ret = platform_device_register(&mymachine_pdev); if (ret) return ret; ret = platform_driver_register(&mymachine_pdrv); if (ret) platform_device_unregister(&mymachine_pdev); return ret; } static void __exit mymachine_exit(void) { platform_driver_unregister(&mymachine_pdrv); platform_device_unregister(&mymachine_pdev); } module_init(mymachine_init); module_exit(mymachine_exit); MODULE_LICENSE("GPL");
扩展知识:SND_SOC_DAILINK_DEFS
从Linux 5.x开始,Linux使用宏SND_SOC_DAILINK_REG快捷定义DAI_LINK中的有关codec和platform连接关系。
其本质和5.x之前的版本并无实质的变化,仅是在形式上采用了一种更加便捷的定义方法。相关源码主要参考“linux/v5.10.177/source/include/sound/soc.h“
本小节主要扩展解释一下下面的语句:
SND_SOC_DAILINK_DEFS(mycodec, DAILINK_COMP_ARRAY(COMP_CPU("myplat.0")), DAILINK_COMP_ARRAY(COMP_CODEC("my_codec.0", "my_codec_dai")), DAILINK_COMP_ARRAY(COMP_PLATFORM("myplat.0"))); SND_SOC_DAILINK_REG(mycodec),
SND_SOC_DAILINK_DEFS的定义如下:
#define SND_SOC_DAILINK_DEFS(name, cpu, codec, platform...) SND_SOC_DAILINK_DEF(name##_cpus, cpu); SND_SOC_DAILINK_DEF(name##_codecs, codec); SND_SOC_DAILINK_DEF(name##_platforms, platform)
代入得到:
#define SND_SOC_DAILINK_DEFS(mycodec, COMP_CPU("myplat.0"), COMP_CODEC("my_codec.0", "my_codec_dai"), COMP_PLATFORM("myplat.0")) //...
再展开上面的三个宏可以得到:
#define SND_SOC_DAILINK_DEFS(mycodec, { .dai_name = "myplat.0", }, { .name = "my_codec.0", .dai_name = "my_codec_dai", }, { .name = "myplat.0" }) //...
将上面的结果完整代入SND_SOC_DAILINK_DEF,即可以得到完整的SND_SOC_DAILINK_DEFS展开结果:
SND_SOC_DAILINK_DEF(mycodec_cpus, { .dai_name = "myplat.0", }); SND_SOC_DAILINK_DEF(mycodec_codecs, { .name = "my_codec.0", .dai_name = "my_codec_dai", }); SND_SOC_DAILINK_DEF(mycodec_platforms, { .name = "myplat.0" })
进一步代入SND_SOC_DAILINK_DEF可以得到:
static struct snd_soc_dai_link_component mycodec_cpus[] = { .dai_name = "myplat.0", }; static struct snd_soc_dai_link_component mycodec_codecs[] = { .name = "my_codec.0", .dai_name = "my_codec_dai", }; static struct snd_soc_dai_link_component mycodec_platforms[] = { .name = "myplat.0" };
使用方法说明
使用方法
SND_SOC_DAILINK_REG(name),
这个宏展开后如下:
.cpus = mycodec_cpus, .num_cpus = ARRAY_SIZE(mycoded_cpus), .codecs = mycodec_codecs, .num_codecs = ARRAY_SIZE(mycodec_codecs), .platforms = mycodec_platforms, .num_platforms = ARRAY_SIZE(mycodec_platforms),
总结与其它说明
SND_SOC_DAILINK_DEFS(mycodec, DAILINK_COMP_ARRAY(COMP_CPU("myplat.0")), DAILINK_COMP_ARRAY(COMP_CODEC("my_codec.0", "my_codec_dai")), DAILINK_COMP_ARRAY(COMP_PLATFORM("myplat.0"))); static struct snd_soc_dai_link my_card_dai_link[] = { { .name = "my-codec", .stream_name = "MY-CODEC", SND_SOC_DAILINK_REG(mycodec), .init = my_card_init, .ops = &my_card_ops, }, }; //*************************************************************** //最终展开是: static struct snd_soc_dai_link_component mycodec_cpus[] = { .dai_name = "myplat.0", }; static struct snd_soc_dai_link_component mycodec_codecs[] = { .name = "my_codec.0", .dai_name = "my_codec_dai", }; static struct snd_soc_dai_link_component mycodec_platforms[] = { .name = "myplat.0" }; static struct snd_soc_dai_link my_card_dai_link[] = { { .name = "my-codec", .stream_name = "MY-CODEC", .cpus = mycodec_cpus, .num_cpus = ARRAY_SIZE(mycodec_cpus), .codecs = mycodec_codecs, .num_codecs = ARRAY_SIZE(mycodec_codecs), .platforms = mycodec_platforms, .num_platforms = ARRAY_SIZE(mycodec_platforms), .init = my_card_init, .ops = &my_card_ops, }, };
其它说明
Linux源码中也另外提供了三种DAI_Link的定义方式,下面直接分析其内容的含义,先看Linux官方的说明:
//include/sound/soc.h /* * Sample 1 : Single CPU/Codec/Platform * * SND_SOC_DAILINK_DEFS(test, * DAILINK_COMP_ARRAY(COMP_CPU("cpu_dai")), * DAILINK_COMP_ARRAY(COMP_CODEC("codec", "codec_dai")), * DAILINK_COMP_ARRAY(COMP_PLATFORM("platform"))); * * struct snd_soc_dai_link link = { * ... * SND_SOC_DAILINK_REG(test), * }; * * Sample 2 : Multi CPU/Codec, no Platform * * SND_SOC_DAILINK_DEFS(test, * DAILINK_COMP_ARRAY(COMP_CPU("cpu_dai1"), * COMP_CPU("cpu_dai2")), * DAILINK_COMP_ARRAY(COMP_CODEC("codec1", "codec_dai1"), * COMP_CODEC("codec2", "codec_dai2"))); * * struct snd_soc_dai_link link = { * ... * SND_SOC_DAILINK_REG(test), * }; * * Sample 3 : Define each CPU/Codec/Platform manually * * SND_SOC_DAILINK_DEF(test_cpu, * DAILINK_COMP_ARRAY(COMP_CPU("cpu_dai1"), * COMP_CPU("cpu_dai2"))); * SND_SOC_DAILINK_DEF(test_codec, * DAILINK_COMP_ARRAY(COMP_CODEC("codec1", "codec_dai1"), * COMP_CODEC("codec2", "codec_dai2"))); * SND_SOC_DAILINK_DEF(test_platform, * DAILINK_COMP_ARRAY(COMP_PLATFORM("platform"))); * * struct snd_soc_dai_link link = { * ... * SND_SOC_DAILINK_REG(test_cpu, * test_codec, * test_platform), * }; * * Sample 4 : Sample3 without platform * * struct snd_soc_dai_link link = { * ... * SND_SOC_DAILINK_REG(test_cpu, * test_codec); * }; */
这段注释提供了几个示例来说明如何使用SND_SOC_DAILINK_DEFS和SND_SOC_DAILINK_REG宏来定义和注册SoC音频dailink。一个dailink通常包含一个CPU(DMA引擎),一个或多个编解码器,以及一个平台(音频控制器硬件)。
下面是每个示例的解释:
- 示例 1:单个CPU/Codec/Platform
SND_SOC_DAILINK_DEFS(test, DAILINK_COMP_ARRAY(COMP_CPU("cpu_dai")), DAILINK_COMP_ARRAY(COMP_CODEC("codec", "codec_dai")), DAILINK_COMP_ARRAY(COMP_PLATFORM("platform")));
这个示例展示了如何定义一个包含单个CPU、Codec和Platform的dailink。SND_SOC_DAILINK_DEFS宏用于创建三个数组:test_cpus、test_codecs和test_platforms,每个数组包含一个组件。
struct snd_soc_dai_link link = { ... SND_SOC_DAILINK_REG(test), };
然后,SND_SOC_DAILINK_REG宏用于在snd_soc_dai_link结构体中注册这些组件。
- 示例 2:多个CPU/Codec,没有Platform
SND_SOC_DAILINK_DEFS(test, DAILINK_COMP_ARRAY(COMP_CPU("cpu_dai1"), COMP_CPU("cpu_dai2")), DAILINK_COMP_ARRAY(COMP_CODEC("codec1", "codec_dai1"), COMP_CODEC("codec2", "codec_dai2")));
这个示例展示了如何定义一个包含多个CPU和Codec,但没有Platform的dailink。SND_SOC_DAILINK_DEFS宏创建了两个数组:test_cpus和test_codecs,每个数组包含两个组件。
struct snd_soc_dai_link link = { ... SND_SOC_DAILINK_REG(test), };
SND_SOC_DAILINK_REG宏在这里假定Platform是空的,并在snd_soc_dai_link结构体中注册CPU和Codec组件。
- 示例 3:手动定义每个CPU/Codec/Platform
SND_SOC_DAILINK_DEF(test_cpu, DAILINK_COMP_ARRAY(COMP_CPU("cpu_dai1"), COMP_CPU("cpu_dai2"))); SND_SOC_DAILINK_DEF(test_codec, DAILINK_COMP_ARRAY(COMP_CODEC("codec1", "codec_dai1"), COMP_CODEC("codec2", "codec_dai2"))); SND_SOC_DAILINK_DEF(test_platform, DAILINK_COMP_ARRAY(COMP_PLATFORM("platform")));
这个示例展示了如何手动定义每个CPU、Codec和Platform组件。SND_SOC_DAILINK_DEF宏用于创建三个独立的数组。
struct snd_soc_dai_link link = { ... SND_SOC_DAILINK_REG(test_cpu, test_codec, test_platform), };
然后,SND_SOC_DAILINK_REG宏用于在snd_soc_dai_link结构体中注册这些组件。
- 示例 4:没有Platform的示例3
struct snd_soc_dai_link link = { ... SND_SOC_DAILINK_REG(test_cpu, test_codec); };
这个示例与示例3类似,但它省略了Platform组件。SND_SOC_DAILINK_REG宏在这里只注册CPU和Codec组件。
这些示例展示了如何灵活地使用宏来定义和注册不同配置的dailinks,这对于SoC音频驱动的开发非常重要。