mt8167 audio驱动的配置及代码分析


前言

参考文章:
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 的拓扑图
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/a7e534d540db45e69deb6beac2939891.png

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驱动内容:

  1. Codec DAI和PCM配置:必须能够配置codec的数字音频接口(DAI)和PCM(脉冲编码调制)音频数据的参数。每个编解码器驱动程序必须有一个 struct snd_soc_dai_driver 来定义其 DAI 和 PCM 功能和操作。
  2. Codec控制IO:使用RegMap API进行寄存器访问。RegMap API提供了一种简化的方式来读写codec的寄存器。
  3. 混音器和音频控制:提供音频路径的混合和音量控制等功能。
  4. Codec音频操作:实现codec的基本音频操作,如初始化、启动、停止等。
  5. DAPM描述:定义Dynamic Audio Power Management(动态音频功率管理)的小部件、路径和控制点,以优化功率消耗。
  6. DAPM事件处理器:处理DAPM系统中的事件,如音频流的启动和停止,以及功率状态的变化。
  7. 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的解耦,这里不再进行详细描述,从代码层面分析即可得到。

如有错误,请指正。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值