前言
参考文章:
https://zhuanlan.zhihu.com/p/537564029
https://blog.csdn.net/weixin_47702410/article/details/135789222?spm=1001.2014.3001.5502
一、设备树及原理图及一些audio知识
1.1 mtk audio 架构
MTK使用的是ASOC架构
基础知识:一个audio驱动组成:machine + platform + codec
有关ASOC的一些描述(从别处抄来的linux官方描述)
ASoC 层旨在解决这些问题并提供以下功能:-
1.编解码器(codec)独立性。允许在其他平台和机器上重用编解码器驱动程序。
2.编解码器和 SoC 之间的简单 I2S/PCM 音频接口设置。每个 SoC 接口和编解码器都会向内核注册其音频接口功能,并在已知应用硬件参数时进行匹配和配置。
3.动态音频电源管理 (DAPM)。DAPM 始终自动将编解码器设置为最低功耗状态。这包括根据内部编解码器音频路由和任何活动流来打开/关闭内部电源模块。
4.减少爆音和咔嗒声。通过以正确的顺序打开/关闭编解码器电源(包括使用数字静音),可以减少爆裂声和咔嗒声。ASoC 向编解码器发出何时更改电源状态的信号。
5.机器特定控制:允许机器向声卡添加控制(例如扬声器放大器的音量控制)。
为了实现这一切,ASoC 基本上将嵌入式音频系统拆分为多个可重复使用的组件驱动程序:-
1.Codec class drivers:codec class driver与平台无关,包含音频控件、音频接口功能、编解码器 DAPM 定义和编解码器 IO 函数。如果需要,此类可扩展到 BT、FM 和 MODEM IC。codec class driver应该是可以在任何体系结构和机器上运行的通用代码。
2.Platform class drivers:平台类驱动程序包括音频 DMA 引擎驱动程序、数字音频接口 (DAI) 驱动程序(例如 I2S、AC97、PCM)以及该平台的任何音频 DSP 驱动程序。
3.Machine class driver:机器驱动程序类充当粘合剂,描述并将其他组件驱动程序绑定在一起以形成 ALSA“声卡设备”。它处理任何机器特定的控制和机器级音频事件(例如在播放开始时打开放大器)。
其中machine是起粘合剂的作用,把platform和codec绑定起来,在这里,我们仅关注machine即可(codec由模组厂提供和platform由soc厂提供,machine由我们自己编写)。
这三者主要的功能如下:
machine:主要负责注册card(这个就是声卡,给到上层应用的),并且注册dai-link下的codec(包括cpu dai和codec dai)dai-link一般在C文件中配置,在设备树中对应sound,如下所示
sound: sound {
compatible = "mediatek,mt8167-mt6392";
mediatek,platform = <&afe>;
pinctrl-names = "default", "extamp_on", "extamp_off";
pinctrl-0 = <&aud_pins_default>;
pinctrl-1 = <&aud_pins_extamp_on>;
pinctrl-2 = <&aud_pins_extamp_off>;
status = "okay";
};
platform:platform提供了与codec相对应的接口,如I2S、PCM等等接口,需要为这些接口提供驱动,把音频数据从cpu传递到接口处(下行),或者从接口处传递到CPU处(上行)。即cpu dai的驱动,包括DMA配置和I2S配置,在设备树中对应i2s,如下所示
afe: audio-controller@11140000 {
compatible = "mediatek,mt8167-afe-pcm";
reg = <0 0x11140000 0 0x1000>,
<0 0x11141000 0 0x9000>;
interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_LOW>;
clocks = <&topckgen CLK_TOP_AUDIO>,
<&topckgen CLK_TOP_APLL12_DIV0>,
<&topckgen CLK_TOP_APLL12_DIV1>,
<&topckgen CLK_TOP_APLL12_DIV2>,
<&topckgen CLK_TOP_APLL12_DIV3>,
<&topckgen CLK_TOP_APLL12_DIV4>,
<&topckgen CLK_TOP_APLL12_DIV4B>,
<&topckgen CLK_TOP_APLL12_DIV5>,
<&topckgen CLK_TOP_APLL12_DIV5B>,
<&topckgen CLK_TOP_APLL12_DIV6>,
<&topckgen CLK_TOP_RG_AUD_SPDIF_IN>,
<&topckgen CLK_TOP_RG_AUD_ENGEN1>,
<&topckgen CLK_TOP_RG_AUD_ENGEN2>,
<&topckgen CLK_TOP_RG_AUD1>,
<&topckgen CLK_TOP_RG_AUD2>,
<&topckgen CLK_TOP_AUD_I2S0_M_SEL>,
<&topckgen CLK_TOP_AUD_I2S1_M_SEL>,
<&topckgen CLK_TOP_AUD_I2S2_M_SEL>,
<&topckgen CLK_TOP_AUD_I2S3_M_SEL>,
<&topckgen CLK_TOP_AUD_I2S4_M_SEL>,
<&topckgen CLK_TOP_AUD_I2S5_M_SEL>,
<&topckgen CLK_TOP_AUD_SPDIF_B_SEL>;
clock-names = "top_pdn_audio",
"apll12_div0",
"apll12_div1",
"apll12_div2",
"apll12_div3",
"apll12_div4",
"apll12_div4b",
"apll12_div5",
"apll12_div5b",
"apll12_div6",
"spdif_in",
"engen1",
"engen2",
"aud1",
"aud2",
"i2s0_m_sel",
"i2s1_m_sel",
"i2s2_m_sel",
"i2s3_m_sel",
"i2s4_m_sel",
"i2s5_m_sel",
"spdif_b_sel";
assigned-clocks = <&topckgen CLK_TOP_AUD1_SEL>,
<&topckgen CLK_TOP_AUD2_SEL>,
<&topckgen CLK_TOP_AUD_ENGEN1_SEL>,
<&topckgen CLK_TOP_AUD_ENGEN2_SEL>;
assigned-clock-parents = <&topckgen CLK_TOP_APLL1>,
<&topckgen CLK_TOP_APLL2>,
<&topckgen CLK_TOP_RG_APLL1_D8_EN>,
<&topckgen CLK_TOP_RG_APLL2_D8_EN>;
};
&afe {
/* 0(HDMI) 1(I2S) 2(TDM) */
mediatek,tdm-out-mode = <0>;
}
};
codec:也可以使用外部的codec,codec一般与平台无关,其接口为I2S、PCM与platform是对应的,包含了一些音频的控件(Controls),音频接口,DAMP(动态音频电源管理)的定义和某些Codec IO功能。在设备树中mt8167内部集成,对应codec,如下所示
mt8167_audio_codec: mt8167_audio_codec {
compatible = "mediatek,mt8167-codec";
clocks = <&topckgen CLK_TOP_AUDIO>;
clock-names = "bus";
mediatek,afe-regmap = <&afe>;
mediatek,apmixedsys-regmap = <&apmixedsys>;
mediatek,dmic-wire-mode = <1>; /* 0(ONE_WIRE) 1(TWO_WIRE) */
/* 0(10UF) 1(22UF) 2(33UF) 3(47UF) */
mediatek,headphone-cap-sel = <1>;
};
综上所述,machine的作用就是将platform中的数字音频接口DAI与codec连接起来,就是把主控soc中音频数据 通过DAI(I2S等)传输到codec中,codec对这些数据进行一个处理后做DA的转换,那么声音就可以播放出来了。
后续会对这些代码进行一个简单的分析
1.2 关于pcm声卡的一些知识
关于为什么会有前后端,这里就不细说,详细见前言的参考文章。
前端 Front End PCMs(FE):PCM声卡,每个PCMC0Dx p/c 对应一个后端
(前后端可以通过route进行灵活切换,即一个前端可以通过weight灵活切换多个后端,即可以实现DPCM,后端、route和weight、DPCM后面会解释)
PCMC0D0p //喇叭
PCMC0D1p //耳机
PCMC0D1c //耳机麦
PCMC0D2p //蓝牙耳机
...
见下图
后端 Back End DAIs (BE):喇叭、耳机、蓝牙耳机、FM,其中喇叭、耳机可能接在同一个codec上,蓝牙耳机接在另外一个codec上,见上图
DPCM:动态pcm切换,使用单一T_PCM1设备切换不同后端,可以实现T_PCM1连接喇叭到T_PCM1连接耳机的切换。上层通常使用kcontrol去进行切换即可,大部分的工作都在内核中完成 ,具体如下
-----------上层工作---------------------
tinymix 'Connection T_PCM1 to T_I2S1' 0 //关闭T_PCM1到T_I2S1通路
tinymix 'Connection T_PCM1 to T_I2S2' 1 //打开T_PCM1到T_I2S2通路
-----------内核工作---------------------
trigger(STOP),hw_free(),shutdown() //关闭通路
startup(),hw_params(),prepare(),trigger(START) //打开通路
混音器 Mixer:两个用户往两个pcm写入数据,最终输出到一个后端,如下图所示
(这个在Android中可以在HAL层做,现在HAL中混音,再写入对应pcm设备即可)
Route:route的描述,根据上方有两条路径route:
//用户A播放声音到喇叭
route1:T_PCM1 --kcontrol_a--> MIXER --kcontrol_c--> T_I2S2
//用户B播放声音到喇叭
route2:T_PCM2 --kcontrol_b--> MIXER --kcontrol_c--> T_I2S2
Weight:这个就是路由中的控制器kcontrol_x,如果要打通route1,就需要打开kcontrol_a与kcontrol_c。
tinymix 'kcontrol_a' 1
tinymix 'kcontrol_c' 1
tinyplay music_a.wav -D 0 -d 0
---------------------------
tinymix 'kcontrol_b' 1
tinymix 'kcontrol_c' 1
tinyplay music_b.wav -D 0 -d 1
DAPM :具体名称为Dynamic Audio Power Management,这个主要是根据实际情况开关Weight打开对route,无需全部开启,减少功耗。
1.3原理图
这里分两种情况来看:
1.soc的codec已经设计在ic内部了,直接通过引脚即可输出
2.mt8168内部应该也有codec,但是使用pmic的codec
这里仅对mt8167的进行一个分析,后续再分析mt8168的
二、platform分析
一般来说不必太深究这部分代码,这部分代码一般都是厂商写好的,了解前后端、路由、weight、还有一些切换route时,前后端一些func所做的操作即可。
2.1 route拓扑图
这里先贴个route 的拓扑图
2.2 mt8167-afe-pcm.c
static int mt8167_afe_pcm_dev_probe(struct platform_device *pdev)
{
/**
* 关于下面两个func,有如下说明:见https://www.cnblogs.com/xinghuo123/p/13150582.html
* ASoC有把Platform驱动分为两个部分:snd_soc_platform_driver和snd_soc_dai_driver.
* platform_driver负责管理音频数据,把音频数据通过dma或其他操作传送至cpu dai中
* dai_driver则主要完成cpu一侧的dai的参数配置,
* 同时也会通过一定的途径把必要的dma等参数与snd_soc_platform_driver进行交互。
*/
//创建/销毁pcm设备 传流
ret = snd_soc_register_platform(&pdev->dev,
&mt8167_afe_pcm_platform);
...
//完成dai参数配置,设计路由
ret = snd_soc_register_component(&pdev->dev,
&mt8167_afe_pcm_dai_component,
mt8167_afe_pcm_dais,
ARRAY_SIZE(mt8167_afe_pcm_dais));
}
//关于weight和route,我们这边仅挑出一些简单的进行讲解,其余类似的就不一一描述了
//这个主要是面向PCM设备的,关于PCM设备的创建、释放、与流操作
static const struct snd_soc_platform_driver mt8167_afe_pcm_platform = {
.probe = mt8167_afe_pcm_probe,
.pcm_new = mt8167_afe_pcm_new, /* pcm creation */
.pcm_free = mt8167_afe_pcm_free, /* pcm destruction */
.ops = &mt8167_afe_pcm_ops, /* platform stream pcm ops PCM的流操作*/
};
//这个是注册一些DAI的组件的,像weight和route
static const struct snd_soc_component_driver mt8167_afe_pcm_dai_component = {
.name = "mtk-afe-pcm-dai",
.dapm_widgets = mt8167_afe_pcm_widgets,
.num_dapm_widgets = ARRAY_SIZE(mt8167_afe_pcm_widgets),
.dapm_routes = mt8167_afe_pcm_routes,
.num_dapm_routes = ARRAY_SIZE(mt8167_afe_pcm_routes),
};
//为了方便理解,我们对这些内容写一个拓扑图,根据拓扑图我们就清楚如何去控制对应的weight
static const struct snd_soc_dapm_route mt8167_afe_pcm_routes[] = {
//downlink 下行 播放 的route,只列举了部分
//这里列举了一组,PCM0 Playback后端可以走DL1前端
{"I05", NULL, "DL1"},
{"I06", NULL, "DL1"},
{"O02", "I05 Switch", "I05"},
{"O02", "I06 Switch", "I06"},
{"PCM0 O02", "Switch", "O02"},
{"PCM0 Playback", NULL, "PCM0 O02"},
{"PCM0 Out", NULL, "PCM0 Playback"},
//这里列举了另外一组,这个I2S Playback后端可以走DL1或DL2前端
{"I07", NULL, "DL2"},
{"I08", NULL, "DL2"},
{"O03", "I07 Switch", "I07"},
{"O04", "I08 Switch", "I08"},
{"O03", "I05 Switch", "I05"},
{"O04", "I06 Switch", "I06"},
{"I2S O03_O04", "Switch", "O03"},
{"I2S O03_O04", "Switch", "O04"},
{"I2S Playback", NULL, "I2S O03_O04"},
}
//这些weight会在一张拓扑图中展现出来,我们根据拓扑图去控制weight以实现各路由的切换
static const struct snd_soc_dapm_widget mt8167_afe_pcm_widgets[] = {
SND_SOC_DAPM_MIXER("I05", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("I06", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("I07", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("I08", SND_SOC_NOPM, 0, 0, NULL, 0),
//这里我们仅贴出mt8167_afe_o02_mix的和mt8167_afe_o03_mix
SND_SOC_DAPM_MIXER("O02", SND_SOC_NOPM, 0, 0,
mt8167_afe_o02_mix, ARRAY_SIZE(mt8167_afe_o02_mix)),
SND_SOC_DAPM_MIXER("O03", SND_SOC_NOPM, 0, 0,
mt8167_afe_o03_mix, ARRAY_SIZE(mt8167_afe_o03_mix)),
SND_SOC_DAPM_MIXER("O04", SND_SOC_NOPM, 0, 0,
mt8167_afe_o04_mix, ARRAY_SIZE(mt8167_afe_o04_mix)),
SND_SOC_DAPM_SWITCH("I2S O03_O04", SND_SOC_NOPM, 0, 0,&i2s_o03_o04_enable_ctl),
SND_SOC_DAPM_SWITCH("PCM0 O02", SND_SOC_NOPM, 0, 0, &pcm0_o02_enable_ctl),
//SND_SOC_DAPM_OUTPUT的主要功能是让ALSA能够识别和管理声卡上的音频输出引脚
SND_SOC_DAPM_OUTPUT("PCM0 Out"),
}
//这里基本贴合上方route拓扑图的描述
static const struct snd_kcontrol_new mt8167_afe_o02_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I05 Switch", AFE_CONN1, 5, 1, 0),
SOC_DAPM_SINGLE_AUTODISABLE("I06 Switch", AFE_CONN1, 6, 1, 0),
};
//这里基本贴合上方route拓扑图的描述,I10 Switch是上行的(这里忽略)
static const struct snd_kcontrol_new mt8167_afe_o03_mix[] = {
SOC_DAPM_SINGLE_AUTODISABLE("I05 Switch", AFE_CONN1, 21, 1, 0),
SOC_DAPM_SINGLE_AUTODISABLE("I07 Switch", AFE_CONN1, 23, 1, 0),
SOC_DAPM_SINGLE_AUTODISABLE("I10 Switch", AFE_GAIN1_CONN, 8, 1, 0),
};
//这个是定义前后端的,仅贴了部分前后端
static struct snd_soc_dai_driver mt8167_afe_pcm_dais[] = {
/* FE DAIs: memory intefaces to CPU */
{
.name = "DL1",
.id = MT8167_AFE_MEMIF_DL1,
.suspend = mt8167_afe_dai_suspend,
.resume = mt8167_afe_dai_resume,
.playback = {
.stream_name = "DL1",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE,
},
.ops = &mt8167_afe_dai_ops,
}, {
.name = "DL2",
.id = MT8167_AFE_MEMIF_DL2,
.suspend = mt8167_afe_dai_suspend,
.resume = mt8167_afe_dai_resume,
.playback = {
.stream_name = "DL2",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE,
},
.ops = &mt8167_afe_dai_ops,
},
...
//后端
{
/* BE DAIs */
.name = "I2S",
.id = MT8167_AFE_IO_I2S,
.playback = {
.stream_name = "I2S Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE,
},
.capture = {
.stream_name = "I2S Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE,
},
.ops = &mt8167_afe_i2s_ops,
}, {
/* BE DAIs */
.name = "PCM0",
.id = MT8167_AFE_IO_PCM_BT,
.playback = {
.stream_name = "PCM0 Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000 |
SNDRV_PCM_RATE_16000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.stream_name = "PCM0 Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000 |
SNDRV_PCM_RATE_16000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &mt8167_afe_pcm0_ops,
.symmetric_rates = 1,
},
}
/* FE DAIs */
//这个具体作用看上一小节的基础知识1.2中DPCM的描述,里面会有一个route切换过程,使用的就是如下相关的func,具体里面做了什么,这里就不详细分析了
static const struct snd_soc_dai_ops mt8167_afe_dai_ops = {
.startup = mt8167_afe_dais_startup,
.shutdown = mt8167_afe_dais_shutdown,
.hw_params = mt8167_afe_dais_hw_params,
.hw_free = mt8167_afe_dais_hw_free,
.prepare = mt8167_afe_dais_prepare,
.trigger = mt8167_afe_dais_trigger,
};
/* BE DAIs */
//这个具体作用看上一小节的基础知识1.2中DPCM的描述,里面会有一个route切换过程,使用的就是如下相关的func,具体里面做了什么,这里就不详细分析了
static const struct snd_soc_dai_ops mt8167_afe_i2s_ops = {
.startup = mt8167_afe_i2s_startup,
.shutdown = mt8167_afe_i2s_shutdown,
.hw_params = mt8167_afe_i2s_hw_params,
.prepare = mt8167_afe_i2s_prepare,
};
static const struct snd_soc_dai_ops mt8167_afe_pcm0_ops = {
.startup = mt8167_afe_pcm0_startup,
.shutdown = mt8167_afe_pcm0_shutdown,
.hw_params = mt8167_afe_pcm0_hw_params,
.trigger = mt8167_afe_pcm0_trigger,
};
三、Codec
codec驱动内容:
- Codec DAI和PCM配置:必须能够配置codec的数字音频接口(DAI)和PCM(脉冲编码调制)音频数据的参数。每个编解码器驱动程序必须有一个 struct snd_soc_dai_driver 来定义其 DAI 和 PCM 功能和操作。
- Codec控制IO:使用RegMap API进行寄存器访问。RegMap API提供了一种简化的方式来读写codec的寄存器。
- 混音器和音频控制:提供音频路径的混合和音量控制等功能。
- Codec音频操作:实现codec的基本音频操作,如初始化、启动、停止等。
- DAPM描述:定义Dynamic Audio Power Management(动态音频功率管理)的小部件、路径和控制点,以优化功率消耗。
- DAPM事件处理器:处理DAPM系统中的事件,如音频流的启动和停止,以及功率状态的变化。
- DAC数字静音控制:如果需要,可以提供数字到模拟转换器(DAC)的静音控制功能。
上述部分是从其他博客参考的内容,我们在代码中进行一一对照
static int mt8167_codec_dev_probe(struct platform_device *pdev)
{
...
/* get regmap of codec */
//这里主要是做一个codec寄存器的映射初始化
codec_data->regmap = devm_regmap_init(dev, NULL, codec_data,
&mt8167_codec_regmap_config);
...
/*
* 这部分是我们主要关注的内容,一个codec的注册
* 参数分析:
* 1.dev 设备
* 2.mt8167_codec_driver 这个参数是一个指向snd_soc_component_driver结构体的指针,
* 它定义了组件驱动的操作和行为。
* 这个结构体通常包含了一系列的回调函数,
* 比如初始化(init)、读写寄存器(read/write)、挂起(suspend)和恢复(resume)等,
* 以及可能包含的组件特有的控制元素和调试信息。
* 3.mt8167_codec_dai 这个参数是一个指向snd_soc_dai_driver结构体的指针,
* 它代表了数字音频接口(DAI)的驱动程序。
* DAI是SoC音频组件的一部分,负责处理数字音频流。
* dai_drv结构体包含了DAI的配置信息和操作函数,
* 如启动(startup)、停止(shutdown)、设置格式(set_fmt)等。
*/
return snd_soc_register_codec(dev, &mt8167_codec_driver, &mt8167_codec_dai, 1);
}
3.1 简单分析mt8167_codec_regmap_config
static struct regmap_config mt8167_codec_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_read = codec_reg_read, //读寄存器
.reg_write = codec_reg_write, //写寄存器
.lock = codec_regmap_lock,
.unlock = codec_regmap_unlock,
.cache_type = REGCACHE_NONE,
};
//codec_reg_read最终会调用到module_reg_read
static int module_reg_read(void *context, unsigned int reg, unsigned int *val,
enum regmap_module_id id, unsigned int offset)
{
struct mt8167_codec_priv *codec_data =
(struct mt8167_codec_priv *) context;
int ret = 0;
if (!(codec_data && codec_data->regmap_modules[id]))
return -1;
//最终会通过这个func从寄存器读出值
ret = regmap_read(codec_data->regmap_modules[id],
(reg & (~offset)), val);
return ret;
}
//codec_reg_write最终会调用到module_reg_write
static int module_reg_write(void *context, unsigned int reg, unsigned int val,
enum regmap_module_id id, unsigned int offset)
{
struct mt8167_codec_priv *codec_data =
(struct mt8167_codec_priv *) context;
int ret = 0;
if (!(codec_data && codec_data->regmap_modules[id]))
return -1;
//最终会把值通过这个func写入寄存器
ret = regmap_write(codec_data->regmap_modules[id],
(reg & (~offset)), val);
return ret;
}
3.2 分析mt8167_codec_driver
这个注册的驱动如下:
static struct snd_soc_codec_driver mt8167_codec_driver = {
.probe = mt8167_codec_probe, //主要都是解析设备树及对codec做一些初始化工作
.remove = mt8167_codec_remove,
.suspend = mt8167_codec_suspend, //去使能一些时钟
.resume = mt8167_codec_resume, //使能一些时钟
//关于这个component_driver,dapm_widgets和dapm_routes 跟platform中的也是类似的,这里就不多做分析了
.component_driver = {
.controls = mt8167_codec_controls,
.num_controls = ARRAY_SIZE(mt8167_codec_controls),
.dapm_widgets = mt8167_codec_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(mt8167_codec_dapm_widgets),
.dapm_routes = mt8167_codec_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(mt8167_codec_dapm_routes),
},
};
3.2.1 mt8167_codec_controls
static const struct snd_kcontrol_new mt8167_codec_controls[] = {
//这里的get就是从codec寄存器获取当前增益
//put就是修改codec寄存器设置增益
/* Headset_PGAL_GAIN */
SOC_ENUM_EXT("Headset_PGAL_GAIN",
mt8167_codec_pga_gain_enums[HP_L_PGA_GAIN],
mt8167_codec_pga_gain_get,
mt8167_codec_pga_gain_put),
/* Headset_PGAR_GAIN */
SOC_ENUM_EXT("Headset_PGAR_GAIN",
mt8167_codec_pga_gain_enums[HP_R_PGA_GAIN],
mt8167_codec_pga_gain_get,
mt8167_codec_pga_gain_put),
};
3.2.2 mt8167_codec_dapm_widgets的简单分析
static const struct snd_soc_dapm_widget mt8167_codec_dapm_widgets[] = {
/* stream domain */
//定义了一个数字音频接口输出端点,其中包括了端点名称(wname)、状态名称(stname)等信息
//这里定义codec传输到soc的节点
SND_SOC_DAPM_AIF_OUT_E("AIF TX", "MT8167 Capture", 0,
SND_SOC_NOPM, 0, 0,
mt8167_codec_aif_tx_event,
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
//定义了一个数字音频接口输入端点,其中包括了端点名称(wname)、状态名称(stname)等信息
//这里定义接收soc传输到codec的节点
SND_SOC_DAPM_AIF_IN_E("AIF RX", "MT8167 Playback", 0,
SND_SOC_NOPM, 0, 0,
mt8167_codec_aif_rx_event,
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
//定义了一个数字模拟转换器,这里工作模式是NULL
SND_SOC_DAPM_DAC_E("Left DAC", NULL, AUDIO_CODEC_CON01, 15, 0,
mt8167_codec_left_audio_dac_event,
SND_SOC_DAPM_PRE_PMU |
SND_SOC_DAPM_POST_PMU |
SND_SOC_DAPM_PRE_PMD |
SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_DAC_E("Right DAC", NULL, AUDIO_CODEC_CON01, 14, 0,
mt8167_codec_right_audio_dac_event,
SND_SOC_DAPM_PRE_PMU |
SND_SOC_DAPM_POST_PMU |
SND_SOC_DAPM_PRE_PMD |
SND_SOC_DAPM_POST_PMD),
...
/* platform domain */
//这里定义了输入节点
SND_SOC_DAPM_INPUT("AU_VIN0"),
SND_SOC_DAPM_INPUT("AU_VIN1"),
SND_SOC_DAPM_INPUT("AU_VIN2"),
//这里定义了输出节点
SND_SOC_DAPM_OUTPUT("AU_HPL"),
SND_SOC_DAPM_OUTPUT("AU_HPR"),
SND_SOC_DAPM_OUTPUT("AU_LOL"),
};
3.2.3 mt8167_codec_dapm_routes的简单分析
static const struct snd_soc_dapm_route mt8167_codec_dapm_routes[] = {
/* Power */
{"AIF TX", NULL, "CODEC_CLK"},
{"AIF RX", NULL, "CODEC_CLK"},
{"AIF TX", NULL, "UL_CLK"},
{"AIF RX", NULL, "DL_CLK"},
/* DL to UL loopback */
{"AIF TX Mux", "Aif Rx", "AIF RX"},
/* UL */
{"AIF TX", NULL, "AIF TX Mux"},
{"AU_VIN0", NULL, "AU_MICBIAS0"},
{"AU_VIN2", NULL, "AU_MICBIAS0"},
{"AU_VIN1", NULL, "AU_MICBIAS1"},
/* UL - Analog MIC Path */
{"AIF TX Mux", "Analog MIC", "Left ADC"},
{"AIF TX Mux", "Analog MIC", "Right ADC"},
{"Left ADC", NULL, "Left PGA"},
{"Right ADC", NULL, "Right PGA"},
{"Left PGA", NULL, "Left PGA Mux"},
{"Left PGA", NULL, "Left PGA Mux"},
{"Right PGA", NULL, "Right PGA Mux"},
{"Right PGA", NULL, "Right PGA Mux"},
{"Left PGA Mux", "CH0", "AU_VIN0"},
{"Left PGA Mux", "CH1", "AU_VIN1"},
{"Right PGA Mux", "CH1", "AU_VIN1"},
{"Right PGA Mux", "CH0", "AU_VIN2"},
{"Left PGA", NULL, "Vcm14"},
{"Right PGA", NULL, "Vcm14"},
{"Left ADC", NULL, "UL_Vref24"},
{"Right ADC", NULL, "UL_Vref24"},
/* UL - Digital MIC Path */
{"AIF TX Mux", "Digital MIC", "DMIC"},
{"DMIC", NULL, "Left DMIC"},
{"DMIC", NULL, "Right DMIC"},
{"Left DMIC", NULL, "AU_VIN0"},
{"Right DMIC", NULL, "AU_VIN2"},
{"Left DMIC", NULL, "Vcm14"},
{"Right DMIC", NULL, "Vcm14"},
/* UL - Debug Path (DMIC Data Generator) */
{"DMIC Data Gen", "Switch", "DMIC Data"},
{"AIF TX Mux", "Digital MIC", "DMIC Data Gen"},
/* UL - Debug Path (AMIC Data Generator) */
{"AMIC Data Gen", "Switch", "AMIC Data"},
{"AIF TX Mux", "Analog MIC", "AMIC Data Gen"},
/* DL */
{"AIF RX", NULL, "Vcm14"},
{"Left DAC", NULL, "AIF RX"},
{"Right DAC", NULL, "AIF RX"},
{"Left DAC", NULL, "DL_Vref24"},
{"Right DAC", NULL, "DL_Vref24"},
{"Left DAC", NULL, "DAC_CLK"},
{"Right DAC", NULL, "DAC_CLK"},
{"Left DAC", NULL, "DL_VCM1"},
{"Right DAC", NULL, "DL_VCM1"},
/* DL - Audio Amp Path */
{"Left Audio Amp", NULL, "Left DAC"},
{"Right Audio Amp", NULL, "Right DAC"},
{"Left Audio Amp", NULL, "DL_VCM2"},
{"Right Audio Amp", NULL, "DL_VCM2"},
{"HP Depop VCM", NULL, "Left Audio Amp"},
{"HP Depop VCM", NULL, "Right Audio Amp"},
{"HPOUT Mux", "AUDIO_AMP", "HP Depop VCM"},
{"AU_HPL", NULL, "HPOUT Mux"},
{"AU_HPR", NULL, "HPOUT Mux"},
/* DL - Voice Amp Path */
{"Voice Amp", NULL, "Left DAC"},
{"Voice Amp", NULL, "DL_VCM2"},
{"Voice Amp", NULL, "DL_VCM2"},
{"LINEOUT Mux", "VOICE_AMP", "Voice Amp"},
{"AU_LOL", NULL, "LINEOUT Mux"},
/* DL - Debug Path (Triangular Tone Generator) */
{"SDM Tone Gen", "Switch", "SDM Tone"},
{"AIF RX", NULL, "SDM Tone Gen"},
};
先推Capture的route,由3.2.2已知 input 由如下输入AU_VIN0/1/2,输出是AIF TX 是有如下
这部分是模拟Mic的route,这里可以看到capture的一个route,这里隐藏了一些dapm的控制
其他的,如:playback和数字Mic的route也是差不多的。
3.2 分析mt8167_codec_dai
这里设置了codec中上下行的一些参数
static struct snd_soc_dai_driver mt8167_codec_dai = {
//这个codec的name在platform中会被使用到(下一章节内容)
.name = "mt8167-codec-dai",
/*
* 这里仅做了一个硬件参数的设置回调
* 设置了capture 或 playback 的 rate
*/
.ops = &mt8167_codec_aif_dai_ops,
.playback = {
.stream_name = "MT8167 Playback",
.channels_min = 1,
.channels_max = 2,
.rates = MT8167_CODEC_DL_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.stream_name = "MT8167 Capture",
.channels_min = 1,
.channels_max = 2,
.rates = MT8167_CODEC_UL_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
};
四、Machine
我们这边重点分析mtk的machine,具体有如下代码
还是直接从probe开始看
static int mt8167_evb_dev_probe(struct platform_device *pdev)
{
//mt8167_evb_card这个是声卡的一些属性设置,后面会进行详细分析,放在第一小节分析
struct snd_soc_card *card = &mt8167_evb_card;
struct device *dev = &pdev->dev;
struct device_node *platform_node;
int ret, i;
struct mt8167_evb_priv *card_data;
//从设备树获取platform的设备树节点
platform_node = of_parse_phandle(dev->of_node, "mediatek,platform", 0);
if (!platform_node) {
dev_dbg(dev, "Property 'platform' missing or invalid\n");
return -EINVAL;
}
//这个就是做一个赋值,这里不是核心,先不用管
for (i = 0; i < card->num_links; i++) {
if (mt8167_evb_dais[i].platform_name)
continue;
mt8167_evb_dais[i].platform_of_node = platform_node;
}
card->dev = dev;
card_data = devm_kzalloc(dev, sizeof(struct mt8167_evb_priv),
GFP_KERNEL);
if (!card_data) {
ret = -ENOMEM;
dev_dbg(dev, "%s allocate card private data fail %d\n",
__func__, ret);
return ret;
}
//一般这些都是私有属性的绑定,这里不用管
snd_soc_card_set_drvdata(card, card_data);
//这个是设备树pinctrl的获取,放在第二小节分析
mt8167_evb_gpio_probe(card);
/*
* 下面这些属性是做一些时延,如果这些属性存在的话,具体会在后面分析中体现
*/
of_property_read_u32(dev->of_node,
"mediatek,ext-spk-amp-warmup-time-us",
&card_data->ext_spk_amp_warmup_time_us);
of_property_read_u32(dev->of_node,
"mediatek,ext-spk-amp-shutdown-time-us",
&card_data->ext_spk_amp_shutdown_time_us);
of_property_read_u32(dev->of_node,
"mediatek,hp-spk-amp-warmup-time-us",
&card_data->hp_spk_amp_warmup_time_us);
of_property_read_u32(dev->of_node,
"mediatek,hp-spk-amp-shutdown-time-us",
&card_data->hp_spk_amp_shutdown_time_us);
/*
* 这个func是声卡的注册函数,
* 上述属性设置完后,执行该func即可将platform和codec关联起来
*/
ret = devm_snd_soc_register_card(dev, card);
if (ret)
dev_dbg(dev, "%s snd_soc_register_card fail %d\n",
__func__, ret);
return ret;
}
综上所述,我们关心的内容有如下:
1.mt8167_evb_card
2.mt8167_evb_gpio_probe
需要留意的内容有如下:
时延属性的设置
其中mt8167_evb_card的内容相较于mt8167_evb_gpio_probe的教大,因此我们先把简单的mt8167_evb_gpio_probe分析了
4.1 mt8167_evb_gpio_probe
这个func主要是从设备树获取对应的pinctrl
static int mt8167_evb_gpio_probe(struct snd_soc_card *card)
{
struct mt8167_evb_priv *card_data;
int ret = 0;
int i;
card_data = snd_soc_card_get_drvdata(card);
card_data->pinctrl = devm_pinctrl_get(card->dev);
if (IS_ERR(card_data->pinctrl)) {
ret = PTR_ERR(card_data->pinctrl);
dev_dbg(card->dev, "%s pinctrl_get failed %d\n",
__func__, ret);
goto exit;
}
//寻找设备树中的pinctrl,由设备树来看总共有三个,下面会贴出来
for (i = 0 ; i < PIN_STATE_MAX ; i++) {
card_data->pin_states[i] =
pinctrl_lookup_state(card_data->pinctrl,
mt8167_evb_pinctrl_pin_str[i]);
if (IS_ERR(card_data->pin_states[i])) {
ret = PTR_ERR(card_data->pin_states[i]);
dev_dbg(card->dev, "%s Can't find pinctrl state %s %d\n",
__func__, mt8167_evb_pinctrl_pin_str[i], ret);
}
}
/* default state 这里设置pinctrl为default状态*/
if (!IS_ERR(card_data->pin_states[PIN_STATE_DEFAULT])) {
ret = pinctrl_select_state(card_data->pinctrl,
card_data->pin_states[PIN_STATE_DEFAULT]);
if (ret) {
dev_dbg(card->dev, "%s failed to select state %d\n",
__func__, ret);
goto exit;
}
}
exit:
return ret;
}
设备树如下所示
综上所述,在这一小节,主要是对pin脚的属性控制,后续只需关注如下属性即可:
card_data->pinctrl
card_data->pin_states[XXXX]
4.2 mt8167_evb_card的分析
mt8167_evb_card是这一章节的重点,具体如下
/*
* 属性分析:
* 1.dai_link:这个属性描述了Codec和CPU(platform)之间的连接关系
* 2.controls:这个属性是声卡的一些控制
* 3.dapm_widgets:这个是路由的导通的切换或控制
* 4.dapm_routes:路由的描述
* 5.aux_dev:在snd_soc_card的头文件soc.h中描述如下
* optional auxiliary devices such as amplifiers or codecs with DAI link unused
* 大概意思是,这是一个可选的辅助设备,这个辅助设备是一个不带DAI连接的codec或功放
*/
static struct snd_soc_card mt8167_evb_card = {
.name = "mt-snd-card",
.owner = THIS_MODULE,
.dai_link = mt8167_evb_dais,
.num_links = ARRAY_SIZE(mt8167_evb_dais),
.controls = mt8167_evb_controls,
.num_controls = ARRAY_SIZE(mt8167_evb_controls),
.dapm_widgets = mt8167_evb_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(mt8167_evb_dapm_widgets),
.dapm_routes = mt8167_evb_audio_map,
.num_dapm_routes = ARRAY_SIZE(mt8167_evb_audio_map),
#ifdef CONFIG_MTK_SPEAKER
.aux_dev = mt8167_evb_aux_dev,
.num_aux_devs = ARRAY_SIZE(mt8167_evb_aux_dev),
#endif
};
从上述mt8167_evb_card 中需要分析如下内容:
1.mt8167_evb_dais
2.mt8167_evb_controls
3.mt8167_evb_dapm_widgets
4.mt8167_evb_audio_map
其中先分析简单的内容mt8167_evb_dapm_widgets和mt8167_evb_audio_map和mt8167_evb_controls比较简单,我们先把这三个看到
4.2.1 mt8167_evb_dapm_widgets简单分析
这里的weight大部分都是外部功放的开关
static const struct snd_soc_dapm_widget mt8167_evb_dapm_widgets[] = {
SND_SOC_DAPM_MIC("Mic 1", mt8167_evb_mic1_event),
SND_SOC_DAPM_MIC("Mic 2", mt8167_evb_mic2_event),
SND_SOC_DAPM_MIC("Headset Mic", mt8167_evb_headset_mic_event),
SND_SOC_DAPM_SPK("HP Spk Amp", mt8167_evb_hp_spk_amp_event),
SND_SOC_DAPM_SWITCH("HP Ext Amp",
SND_SOC_NOPM, 0, 0, &mt8167_evb_hp_ext_amp_switch_ctrl),
SND_SOC_DAPM_SPK("Ext Spk Amp", mt8167_evb_ext_spk_amp_wevent),
SND_SOC_DAPM_SWITCH("LINEOUT Ext Amp",
SND_SOC_NOPM, 0, 0, &mt8167_evb_lineout_ext_amp_switch_ctrl),
};
4.2.2 mt8167_evb_controls
这里从源码来看也是对外部功放进行控制
static const struct snd_kcontrol_new mt8167_evb_controls[] = {
/* Ext Spk Amp Switch */
SOC_SINGLE_BOOL_EXT("Ext Spk Amp Switch",
0,
mt8167_evb_ext_spk_amp_get,
mt8167_evb_ext_spk_amp_put),
/* Ext HP Amp Switch */
SOC_SINGLE_BOOL_EXT("Ext HP Amp Switch",
0,
mt8167_evb_ext_hp_amp_get,
mt8167_evb_ext_hp_amp_put),
};
4.2.3 mt8167_evb_audio_map的简单分析
从下面route来看,这个machine的route都是对功放进行控制,连接的对象是外部功放和Codec
static const struct snd_soc_dapm_route mt8167_evb_audio_map[] = {
/* Uplink */
{"AU_VIN0", NULL, "Mic 1"},
{"AU_VIN2", NULL, "Mic 2"},
{"AU_VIN1", NULL, "Headset Mic"},
/* Downlink */
/* use external spk amp via AU_HPL/AU_HPR */
{"HP Ext Amp", "Switch", "AU_HPL"},
{"HP Ext Amp", "Switch", "AU_HPR"},
{"HP Spk Amp", NULL, "HP Ext Amp"},
{"HP Spk Amp", NULL, "HP Ext Amp"},
#ifdef CONFIG_MTK_SPEAKER
/* use internal spk amp of MT6392 */
{"MT6392 AIF RX", NULL, "AU_LOL"},
#endif
/* use external spk amp via AU_LOL */
{"LINEOUT Ext Amp", "Switch", "AU_LOL"},
{"Ext Spk Amp", NULL, "LINEOUT Ext Amp"},
/* ADDA clock - Uplink */
{"AIF TX", NULL, "AFE_CLK"},
{"AIF TX", NULL, "AD_CLK"},
/* ADDA clock - Downlink */
{"AIF RX", NULL, "AFE_CLK"},
{"AIF RX", NULL, "DA_CLK"},
};
注意:这里对4.2.1和4.2.2仅关注外部功放的部分
综合4.2.1和4.2.2,machine的mt8167_evb_controls和mt8167_evb_dapm_widgets都是对外部功放进行控制,最终通过修改
card_data->pinctrl
card_data->pin_states[XXXX]
来对外部功放进行关闭和打开。
而route是也是简单的控制功放,连接codec的输出与外部功放。
4.2.4 mt8167_evb_dais的分析(主要部分)
这里就涉及到前后端的概念了,可以会看第一节中的前后端的一些解释
总的来说machine的mt8167_evb_dais就是对platform和codec的一个绑定
注意,这里的machine dai前后端是可以跟platform一一对应的,下面对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;
/*
* 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;
/* You MUST specify the DAI name within the codec */
const char *codec_dai_name;
enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */
/* 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;
};
可以了解 platform跟codec是如何绑定的
这里主要关注后端BE中的MTK Codec,这个Codec就是我们第三节描述的Codec,这里会把这个Codec与platform的INT ADDA后端进行绑定
注意:platform的dai因为节省篇幅分析route的切换,省略了很多,machine这里的snd_soc_dai_link后端与platform的snd_soc_dai_driver后端数量及名字是一样的
/* Digital audio interface glue - connects codec <---> CPU */
static struct snd_soc_dai_link mt8167_evb_dais[] = {
/* Front End DAI links */
{
.name = "DL1 Playback",
.stream_name = "MultiMedia1_PLayback",
.cpu_dai_name = "DL1", //这个是platform的dai name
.codec_name = "snd-soc-dummy", //这是一个虚拟的codec
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {
SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST
},
.dynamic = 1,
.dpcm_playback = 1,
},
{
.name = "VUL Capture",
.stream_name = "MultiMedia1_Capture",
.cpu_dai_name = "VUL",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {
SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST
},
.dynamic = 1,
.dpcm_capture = 1,
},
{
.name = "HDMI",
.stream_name = "HMDI_PLayback",
.cpu_dai_name = "HDMI",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {
SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST
},
.dynamic = 1,
.dpcm_playback = 1,
},
{
.name = "AWB Capture",
.stream_name = "DL1_AWB_Record",
.cpu_dai_name = "AWB",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {
SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST
},
.dynamic = 1,
.dpcm_capture = 1,
},
{
.name = "DL2 Playback",
.stream_name = "MultiMedia2_PLayback",
.cpu_dai_name = "DL2",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {
SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST
},
.dynamic = 1,
.dpcm_playback = 1,
},
{
.name = "DAI Capture",
.stream_name = "VOIP_Call_BT_Capture",
.cpu_dai_name = "DAI",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {
SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST
},
.dynamic = 1,
.dpcm_capture = 1,
},
{
.name = "TDM Capture",
.stream_name = "TDM_Capture",
.cpu_dai_name = "TDM_IN",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {
SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST
},
.dynamic = 1,
.dpcm_capture = 1,
},
{
.name = "VIRTUAL_MRG",
.stream_name = "MRGRX_PLayback",
.cpu_dai_name = "VIRTURL_MRG",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {
SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST
},
.dynamic = 1,
.dpcm_playback = 1,
},
{
.name = "MRGRX_CAPTURE",
.cpu_dai_name = "AWB",
.stream_name = "MRGRX_CAPTURE",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {
SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST
},
.dynamic = 1,
.dpcm_capture = 1,
},
#ifdef CONFIG_SND_SOC_MTK_BTCVSD
{
.name = "BTCVSD",
.stream_name = "BTCVSD",
.cpu_dai_name = "snd-soc-dummy-dai",
.platform_name = "18000000.mtk-btcvsd-snd",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
},
#endif
/* Back End DAI links */
{
.name = "EXT Codec",
.cpu_dai_name = "I2S",
.no_pcm = 1,
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "2ND EXT Codec",
.cpu_dai_name = "2ND I2S",
.no_pcm = 1,
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "MTK Codec",
.cpu_dai_name = "INT ADDA",
.no_pcm = 1,
.codec_name = "mt8167-codec",
.codec_dai_name = "mt8167-codec-dai",
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "HDMI BE",
.cpu_dai_name = "HDMIO",
.no_pcm = 1,
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
.dpcm_playback = 1,
},
{
.name = "DL BE",
.cpu_dai_name = "DL Input",
.no_pcm = 1,
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dpcm_capture = 1,
},
{
.name = "MRG BT BE",
.cpu_dai_name = "MRG BT",
.no_pcm = 1,
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "PCM0 BE",
.cpu_dai_name = "PCM0",
.no_pcm = 1,
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "TDM IN BE",
.cpu_dai_name = "TDM_IN_IO",
.no_pcm = 1,
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_IF |
SND_SOC_DAIFMT_CBS_CFS,
.dpcm_capture = 1,
},
};
到这里大致就分析完machine了
五、总结
1.platform会有一个DAI的驱动,这个驱动分前后端,每一个前端对应一个PCM设备,然后每一个前端和多个后端可以通过route进行连接(定义),前端与多个后端的route的切换可以使用多个weight进行控制。
总而言之platform一个很重要的作用就是使得前后端可以灵活切换(下面会聊这个功能的作用)
2.codec也是可以跟platform一样进行route的切换,但更重要的功能还是Codec那章节开头描述的内容
3.machine主要还是把platform和codec的绑定到一起,从machine的最后一小节可以看到把8167的codec绑定到platform的后端,这样就可以实现前端不变,后端可以灵活切换codec
即可以实现如下场景:使用蓝牙耳机听音乐(蓝牙的codec),关闭蓝牙耳机,上层应用通过ioctl控制weight控制platform的route切换,切换到另外一个codec,该codec通过喇叭输出音频。这样即可实现,不需要先关闭pcm(蓝牙前端)再重新打开另外一个pcm(喇叭接的codec)的情况下,继续播放音乐。使用一个pcm即可。
至于之前的platform、codec的解耦,这里不再进行详细描述,从代码层面分析即可得到。