概述
主要分为以下几类:
pcmC0D0p —— Playback
pcmC0D0c —— Capture
controlC0 —— Control,比如各种音频控件开关、音量增益等
一套嵌入式硬件平台(Machine)包含了平台AP(Platform)和音频CODEC芯片(Codec),对应ASoC的三个设备驱动。这三个设备分别注册各自功能的dev设备,但都是以内核platform设备模型来创建.
https://blog.csdn.net/RadianceBlau/article/details/79432661
https://blog.csdn.net/u014310046/article/details/53671853
https://blog.csdn.net/longwang155069/article/details/53392769
ASOC音频架构
为了实现上述的新feature,ASOC将嵌入式音频系统分为三大类可重复使用的驱动程序: Platform, Machine, Codec。
Codec类: Codec即编解码芯片的驱动,此Codec驱动是和平台无关,包含的功能有: 音频的控制接口,音频读写IO接口,以及DAPM的定义等。如果需要的话,此Codec类可以在BT,FM,MODEM模块中不做修改的使用。因此Codec就是一个可重复使用的模块,同一个Codec在不同的SOC中可以使用。
Platform类: 可以理解为某款SOC平台,平台驱动中包括音频DMA引擎驱动,数字接口驱动(I2S, AC97, PCM)以及该平台相关的任何音频DSP驱动。同样此Platform也可以重用,在不同的Machine中可以直接重复使用。
Machine类: Machine可以理解为是一个桥梁,用于在Codec和Platform之间建立联系。此Machine指定了该机器使用那个Platform,那个Codec,最终会通过Machine建立两者之间的联系。
ASoC把音频系统同样分为3大部分:Machine,Platform/CPU和Codec。
Platform :
一般是指某一个SoC平台,比如MT6582, MT6595, MT6752等等,与音频相关的通常包含该SoC中的Clock、AFE、I2S、DMA等等。
Codec : 字面上的意思就是编解码器,Codec里面包含了I2S接口、DAC、ADC、Mixer、PA(功放),通常包含多种输入(Mic、Line-in、I2S、PCM)和多个输出(耳机、喇叭、听筒,Line-out),Codec和Platform一样,是可重用的部件。
Machine :
绑定platform driver和codec driver 。
一、Machine设备驱动 :
Machine设备可以看成是一块嵌入式主板(Board) 或者一块声卡。machine设备驱动是ASoC驱动框架的入口, 主要功能是负责platform/cpu和codec之间的连接和控制,或者响应独立于Platform功能和Codec功能之外的特殊音频事件,如平台GPIO控制外置功放等,这些属于machine本身的特定操作代码,都会放到machine驱动里。
Machine设备驱动的主要功能是定义各种DAI(Digital Audio Interface) links,它的作用是把platform/cpu和codec设备驱动连接起来,形成完整的音频通路。
machine设备的初始化是整个ASoC驱动的入口。 machine设备的probe()会调用snd_soc_register_card()去注册声卡,然后在snd_soc_instantiate_card()里实例化声卡设备的时候,调用Platform和Codec设备各自的probe(),完成这两个设备的初始化。
sound/soc/mediatek/common_int/mtk-soc-machine.c
"mediatek,audio"
.probe = mt_soc_snd_init,
platform_set_drvdata(pdev, &mt_snd_soc_card_mt);
static struct snd_soc_card mt_snd_soc_card_mt = {
.name = "mt-snd-card",
.dai_link = mt_soc_dai_common,
.num_links = ARRAY_SIZE(mt_soc_dai_common),
};
snd_soc_register_card
snd_soc_instantiate_card
1. 根据num_links的值,进行DAIs的bind工作。第一步先bind cpu侧的dai
snd_soc_find_dai 此函数会在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。
snd_soc_find_dai
然后将找到的codec侧的dai也同样赋值给snd_soc_pcm_runtime中的codec_dai中。
3.在platform_list链表中查找platfrom,根据dai_link中的platform_name域。如果没有platform_name,则设置为"snd-soc-dummy"
这样查找完毕之后,snd_soc_pcm_runtime中存储了查找到的codec, dai, platform。
4. 接着初始化注册的codec cache,cache_init代表是否已经初始化过。
5. 然后调用ALSA中的创建card的函数: snd_card_new创建一个card
snd_card_new
6. 然后依次调用各个子部件的probe函数
soc_probe_link_components
soc_probe_link_dais
7. 在soc_probe_link_dais函数中依次调用了cpu_dai, codec_dai侧的probe函数
cpu_dai->driver->probe
soc_probe_codec_dai
8. 最终调用到soc_new_pcm函数创建pcm设备
soc_new_pcm
最中此函数会调用ALSA的标准创建pcm设备的接口: snd_pcm_new,然后会设置pcm相应的ops操作函数集合。然后调用到platform->driver->pcm_new的函数。此处不帖函数了。
9. 接着会在dapm和dai widget做相应的操作,后期会设置control参数,最终会调用到ALSA的注册card的函数snd_card_regist。
snd_card_register
总结: 经过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. 注册平台设备。
其中dai_link结构就是用作连接platform和codec的,指明到底用那个codec,那个platfrom。那是通过什么指定的? 如果有兴趣可以详细看snd_soc_dai_link的注释,此注释写的非常清楚。
{
.name = "I2S0DL1OUTPUT",
.stream_name = MT_SOC_I2SDL1_STREAM_NAME,
.cpu_dai_name = MT_SOC_I2S0DL1_NAME,
.platform_name = MT_SOC_I2S0DL1_PCM,
.codec_dai_name = MT_SOC_CODEC_I2S0TXDAI_NAME,
.codec_name = MT_SOC_CODEC_NAME,
},
二、Platform 设备驱动:
Platform设备可看作是平台AP(SoC主控,或CPU)。 它负责提供嵌入式平台端的音频功能, 如播放、录音、 Voice通话等。
Platform设备驱动主要有两个作用:
(1) transfer:负责平台AP端的audio/voice数据流(stream)和DSP之间的传输;
(2) routing:将stream数据流按照特定的路线对应到其他音频模块中。
ASoC会注册多个Platform设备来负责不同功能的音频模块:
(1) msm-pcm-dsp: 负责audio回放/录音
代码路径: kernel/sound/msm/msm-pcm-q6.c
(2) msm-pcm-voice: 负责voice通话
代码路径: kernel/sound/msm/msm-pcm-voice-v2.c
(3) msm-voip-dsp: 负责VoIP通话
代码路径: kernel/sound/msm/msm-pcm-voip-v2.c
(4) msm-compress-dsp: 负责压缩格式的audio播放
代码路径: kernel/sound/msm/msm-compressed-q6-v2.c
(5) msm-pcm-routing:负责stream数据流的路线指定
代码路径: kernel/sound/msm/msm-pcm-routing-v2.c
代码路径: kernel/sound/msm/msm-pcm-routing-v2.c
kernel-4.4/sound/soc/mediatek/mt6739/mt6739-sound.c
Plateform Dai:
kernel-4.4/sound/soc/mediatek/common_int/mtk-soc-dai-stub.c
在ASOC在Platform部分,主要是平台相关的DMA操作和音频管理。大概流程先将音频数据从内存通过DMA方式传输到CPU侧的dai接口,然后通过CPU的dai接口(通过I2S总线)将数据从达到Codec中,数据会在Codec侧会解码的操作,最终输出到耳机/音箱中。
在platfrom侧的主要功能有: 音频数据管理,音频数据传输通过dma; 数据如何通过cpudai传入到codec dai,已经cpu测dai的配置。
而上述的两大类功能在ASOC中使用两个结构体表示:
snd_soc_dai_driver代表cpu侧的dai驱动,其中包括dai的配置(音频格式,clock,音量等)。
snd_soc_platform_driver代表平台使用的dma驱动,主要是数据的传输等。
和Machine一样,使用snd_soc_platform结构对所有platform设备进行统一抽象。
如何找到Machine对应的Platform呢? 答案也是通过Machine中的snd_soc_dai_link中的platform_name。在内核中搜素platform_name所对应的name。
通常还有另一种方式,会将cpu侧dai的驱动和平台相关的dma驱动分离的。也就是machine中的snd_soc_dai_link的platform_name和cpu_dai_name不相同。而上述的samsung的例子则是platform_name和cpu_dai_name是相同的。不过原理都是相同的最后都会调用snd_soc_add_platform函数注册platform到AOSC core的。
在内核中搜素platform_name所对应的name。
sound/soc/mediatek/common_int/mtk-soc-pcm-dl1-i2s0.c
static const struct of_device_id mt_soc_pcm_dl1_i2s0_of_ids[] = {
{.compatible = "mediatek,mt_soc_pcm_dl1_i2s0",},
{}
};
static struct platform_driver mtk_i2s0_driver = {
.driver = {
.name = "mt-soc-i2s0-pcm",
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = mt_soc_pcm_dl1_i2s0_of_ids,
#endif
},
.probe = mtk_i2s0_probe,
.remove = mtk_i2s0_remove,
};
static struct snd_soc_platform_driver mtk_i2s0_soc_platform = {
.ops = &mtk_i2s0_ops,
.pcm_new = mtk_asoc_pcm_i2s0_new,
.probe = mtk_afe_i2s0_probe,
};
mtk_i2s0_probe
snd_soc_register_platform //mtk_i2s0_soc_platform
调用snd_soc_add_platform函数注册platformd到ASOC core。
snd_soc_add_platform
初始化platform的component, 设置probe, remove回调,最终将platform添加到platform_list中,将platform->component添加到component_list链表中。
在内核中搜素cpu_dai_name所对应的name。
kernel-4.4/sound/soc/mediatek/common_int/mtk-soc-dai-stub.c
static const struct of_device_id mt_soc_dai_stub_of_ids[] = {
{ .compatible = "mediatek,mt_soc_dai_stub", },
{}
};
static struct platform_driver mtk_dai_stub_driver = {
.probe = mtk_dai_stub_dev_probe,
.remove = mtk_dai_stub_dev_remove,
.driver = {
.name = "mt-soc-dai-driver",
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = mt_soc_dai_stub_of_ids,
#endif
},
};
static struct snd_soc_dai_driver mtk_dai_stub_dai[] = {
{
.playback = {
.stream_name = MT_SOC_I2SDL1_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
},
.name = "mt-soc-i2s0dl1dai-driver",
.ops = &mtk_dai_stub_ops,
},
mtk_dai_stub_dev_probe
snd_soc_register_component(&pdev->dev, &mt_dai_component,
mtk_dai_stub_dai, ARRAY_SIZE(mtk_dai_stub_dai));
通过snd_soc_register_component注册一个component组件。传入的参数分别是snd_soc_component_driver和snd_soc_dai_driver
static const struct snd_soc_component_driver mt_dai_component = {
.name = MT_SOC_DAI_NAME,
};
static struct snd_soc_dai_driver mtk_dai_stub_dai[] = {
{
.playback = {
.stream_name = MT_SOC_I2SDL1_STREAM_NAME,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
},
.name = MT_SOC_I2S0DL1_NAME,
.ops = &mtk_dai_stub_ops,
},
根据传入参数,进入到devm_snd_soc_register_component函数分析。其中devm是一种资源管理的方式,不用考虑资源释放,内核会内部做好资源回收。然后进入snd_soc_register_component函数。
这里估计没有使用devm
此函数和snd_soc_register_codec的大体流程一致,都是初始化snd_soc_component的实例,然后注册dai,最终将注册的dai放入到component->dai_list中,然后将分配的component放入到component_list链表中。
上述的步骤只是完成platform的一部分,关于cpu_dai侧的设置,配置。还需要平台相关的dma操作。
总结: 通过machine中的snd_soc_dai_link中的platform_name和cpu_dai_name分别查找平台的dma设备驱动和cpu侧的dai驱动。最终会将这dai保存到component->dai_list中,platform保存到platform_list当中。然后将component放入到component_list链表中。这些数据会在Machine代码的开始过程中进行匹配操作。
关于cpu侧的驱动总结:
1. 分配一个cpu_dai_name的平台驱动,注册。
2. 分配一个struct snd_soc_dai_driver结构,然后设置相应数据。
3. 调用snd_soc_register_component函数注册cpu侧的dai结构。
4. 分配一个struct snd_soc_platform_driver结构,设置相应的数据。
5. 最终调用snd_soc_add_platform函数添加snd_soc_platform_driver结构。
三:CODEC设备驱动:
对于一块嵌入式设备的主板来说,一般会集成一颗音频CODEC芯片。 ASoC架构下的CODEC设备功能和物理CODEC对应, 其在machine的控制下和platform设备连接,负责音频的实际处理,如声音播放(D/A)、声音采集(A/D) 和各种音频control控件的设置。
平台一般会集成一个CODEC单元,也会有添加外部独立CODEC芯片,已达到更好的音质。如Wolfson的WM8998芯片,它是一颗独立的CODEC,基于I2S接口从平台获取音频数据,在其内部经过DAC输出到耳机或speaker。高通有自己的外置CODEC芯片,如WCD9326/9335等,和平台AP的音频数据接口叫Slimbus,其实是和I2S复用的GPIO口。
CODEC芯片可能需要I2C或SPI控制。
COCEC设备支持耳机插拔及按键检测功能。
CODEC设备驱动中定义了大量的mixer、 mux和各种开关的kcontrol,以及DAPM使用的widget和route。
kernel-4.4soundsocmediatekcodecmt6357mtk-soc-codec-6357.c
static const struct snd_kcontrol_new mt6357_snd_controls[] = {
SOC_ENUM_EXT("Audio_Amp_R_Switch", Audio_DL_Enum[0], Audio_AmpR_Get,
Audio_AmpR_Set),
并无snd_soc_dapm_widget
CODEC Dai:
kernel-4.4/sound/soc/mediatek/codec/mt6357/mtk-soc-codec-6357.c
在Machine中已经知道,snd_soc_dai_link结构就指明了该Machine所使用的Platform和Codec。在Codec这边通过codec_dai和Platform侧的cpu_dai相互通信,既然相互通信,就需要遵守一定的规则,其中codec_dai和cpu_dai统一抽象为struct snd_soc_dai结构,而将dai的相关操作使用snd_soc_dai_driver抽象。同时也需要对所有的codec设备进行抽象封装,linux使用snd_soc_codec进行所有codec设备的抽象,而将codec的驱动抽象为snd_soc_codec_driver结构。
所有简单来说,Codec侧有四个重要的数据结构:
struct snd_soc_dai,struct snd_soc_dai_driver,struct snd_soc_codec,struct snd_soc_codec_driver。
如何找到codec的代码呢? 答案是通过machine中的snd_soc_dai_link结构:
.codec_name = "mt-soc-codec",
在内核中搜索codec_name="mt-soc-codec",会在kernel-4.4soundsocmediatekcodecmt6357mtk-soc-codec-6357.c中发现
static struct platform_driver mtk_codec_6357_driver = {
.driver = {
.name = "mt-soc-codec",
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = mt_soc_codec_63xx_of_ids,
#endif
},
.probe = mtk_mt6357_codec_dev_probe,
.remove = mtk_mt6357_codec_dev_remove,
};
mtk_mt6357_codec_dev_probe
snd_soc_register_codec(&pdev->dev,&soc_mtk_codec, mtk_6357_dai_codecs,ARRAY_SIZE(mtk_6357_dai_codecs));
此出通过snd_soc_register_codec函数注册了uda134x的codec,同时传入了snd_soc_codec_driver和snd_soc_dai_driver结构。
static struct snd_soc_codec_driver soc_mtk_codec = {
.probe = mt6357_codec_probe,
.remove = mt6357_codec_remove,
.read = mt6357_read,
.write = mt6357_write,
};
static struct snd_soc_dai_driver mtk_6357_dai_codecs[] = {
{
.name = MT_SOC_CODEC_I2S0TXDAI_NAME,
.ops = &mt6323_aif1_dai_ops,
.playback = {
.stream_name = MT_SOC_I2SDL1_STREAM_NAME,
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SND_SOC_ADV_MT_FMTS,
}
},
static const struct snd_soc_dai_ops mt6323_aif1_dai_ops = {
.prepare = mt63xx_codec_prepare,
};
继续进入snd_soc_register_codec函数分析。
1. 分配一个snd_soc_codec结构,设置component参数。
2. 调用snd_soc_component_initialize函数进行component部件的初始化,会根据snd_soc_codec_driver中的struct snd_soc_component_driver结构设置snd_soc_codec中的component组件,详细代码不在贴出。
snd_soc_component_initialize
3. 根据snd_soc_codec_driver的参数,进行一系列的初始化,
4. 根据snd_soc_dai_driver中的参数设置Format
fixup_codec_formats(&dai_drv[i].playback);
fixup_codec_formats(&dai_drv[i].capture);
5. 调用snd_soc_register_dais接口注册dai,传入参数有dai的驱动,以及dai的参数,因为一个codec不止一个dai接口
snd_soc_register_dais
根据dai的数目,分配snd_soc_dai结构,根据dai的数目设置dai的名字,这是dai的传入参数驱动,然后将此dai加入到component的dai_list中。
6. 根据component的dai_list,对所有的dai设置其所属的codec。
7. 将所有的组间加入到component_list中,然后将注册的codec存入codec_list中。
至此,codec的注册就分析完毕。
总结: 通过调用snd_soc_register_codec函数之后,分配的codec最终放入了codec_list链表中,codec下的component组件全部放入component_list链表中,codec下的dai全部存放入code->component.dai_list中。 可以在上节的Machine中看到,machine匹配codec_dai和cpu_dai也是从code->component.dai_list中获取的。
关于codec侧驱动总结:
1. 分配名字为"codec_name"的平台驱动,注册。
2. 定义struct snd_soc_codec_driver结构,设置,初始化。
3. 定义struct snd_soc_dai_driver结构,设置,初始化。
4. 调用snd_soc_register_codec函数注册codec。
最后
以上就是沉静蜜蜂为你收集整理的ALSA架构分析的全部内容,希望文章能够帮你解决ALSA架构分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复