[Linux Audio Driver] Android 10 machine driver probe函数分析

本文基于Qualcomm 5G SM6350、android10、kernel version msm - 4.19平台,重点分析machine driver里的msm_asoc_machine_probe函数,涉及声卡解析注册、dai绑定、麦克偏置解析等内容,还介绍了TDM接口、耳机兼容、MI2S模式配置及新增的LPASS音频投票机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0. 背景

平台:Qualcomm 5G SM6350、android10、kernel version: msm-4.19。

本文重点分析machine driver里面的msm_asoc_machine_probe函数,这个是machie驱动的核心代码,涉及声卡解析注册,CPU dai和codec dai绑定,麦克偏置解析配置routing、耳机麦克检测(欧-美标兼容)、MI2S主、从模式配置,以及新增的LPASS音频投票机制。

此外,msm_asoc_machine_probe函数也可以说就干了一件事:
实例化结构体snd_soc_card。

那个,兄弟们转载博客还是希望附上我的转载链接啊,不要直接copy,也是尊重人不是 -_-。
在这里插入图片描述

1. msm_asoc_machine_probe

代码位置:

./vendor/qcom/opensource/audio-kernel/asoc/kona.c

msm_asoc_machine_probe整个函数如下:

static int msm_asoc_machine_probe(struct platform_device *pdev)
{
	struct snd_soc_card *card = NULL;
	struct msm_asoc_mach_data *pdata = NULL;
	const char *mbhc_audio_jack_type = NULL;
	int ret = 0;
	uint index = 0;
	struct clk *lpass_audio_hw_vote = NULL;

	if (!pdev->dev.of_node) {
		dev_err(&pdev->dev, "%s: No platform supplied from device tree\n", __func__);
		return -EINVAL;
	}

	pdata = devm_kzalloc(&pdev->dev,
			sizeof(struct msm_asoc_mach_data), GFP_KERNEL);
	if (!pdata)
		return -ENOMEM;

	of_property_read_u32(pdev->dev.of_node,
				"qcom,lito-is-v2-enabled",
				&pdata->lito_v2_enabled);


	card = populate_snd_card_dailinks(&pdev->dev);
	if (!card) {
		dev_err(&pdev->dev, "%s: Card uninitialized\n", __func__);
		ret = -EINVAL;
		goto err;
	}

	card->dev = &pdev->dev;
	platform_set_drvdata(pdev, card);
	snd_soc_card_set_drvdata(card, pdata);

	ret = snd_soc_of_parse_card_name(card, "qcom,model");
	if (ret) {
		dev_err(&pdev->dev, "%s: parse card name failed, err:%d\n",
			__func__, ret);
		goto err;
	}


	ret = snd_soc_of_parse_audio_routing(card, "qcom,audio-routing");
	if (ret) {
		dev_err(&pdev->dev, "%s: parse audio routing failed, err:%d\n",
			__func__, ret);
		goto err;
	}


	ret = msm_populate_dai_link_component_of_node(card);
	if (ret) {
		ret = -EPROBE_DEFER;
		goto err;
	}


	ret = msm_init_aux_dev(pdev, card);
	if (ret)
		goto err;


	ret = devm_snd_soc_register_card(&pdev->dev, card);
	if (ret == -EPROBE_DEFER) {
		if (codec_reg_done)
			ret = -EINVAL;
		goto err;
	} else if (ret) {
		dev_err(&pdev->dev, "%s: snd_soc_register_card failed (%d)\n",
			__func__, ret);
		goto err;
	}
	dev_err(&pdev->dev, "%s: Sound card %s registered\n", __func__, card->name);


	kona_parse_aw87xx_mode(pdev, pdata);
	pdata->spk_ext_pa_gpio_p = of_parse_phandle(pdev->dev.of_node, 
	"qcom,msm-spk-ext-pa-gpios", 0);
	if (!pdata->spk_ext_pa_gpio_p) {
		pr_err("%s: property %s not detected in node %s\n", __func__, 
		"qcom,msm-spk-ext-pa-gpios", pdev->dev.of_node->full_name);
	}else{
		ret = msm_cdc_pinctrl_select_sleep_state(pdata->spk_ext_pa_gpio_p);
		if (ret < 0) {
			pr_err("%s: gpio set cannot be activated %s\n", __func__, "speaker_pa_gpio");
			return ret;
		}
	}

	ret = of_property_read_u32(pdev->dev.of_node, "qcom,tdm-max-slots",
				   &pdata->tdm_max_slots);
	if (ret) {
		dev_err(&pdev->dev, "%s: No DT match for tdm max slots\n",
			__func__);
	}
	if ((pdata->tdm_max_slots <= 0) || (pdata->tdm_max_slots >
	    TDM_MAX_SLOTS)) {
		pdata->tdm_max_slots = TDM_MAX_SLOTS;
		dev_err(&pdev->dev, "%s: Using default tdm max slot: %d\n",
			__func__, pdata->tdm_max_slots);
	}

	pdata->hph_en1_gpio_p = of_parse_phandle(pdev->dev.of_node,
						"qcom,hph-en1-gpio", 0);
	if (!pdata->hph_en1_gpio_p) {
		dev_dbg(&pdev->dev, "%s: property %s not detected in node %s\n",
			__func__, "qcom,hph-en1-gpio",
			pdev->dev.of_node->full_name);
	}

	pdata->hph_en0_gpio_p = of_parse_phandle(pdev->dev.of_node,
						"qcom,hph-en0-gpio", 0);
	if (!pdata->hph_en0_gpio_p) {
		dev_dbg(&pdev->dev, "%s: property %s not detected in node %s\n",
			__func__, "qcom,hph-en0-gpio",
			pdev->dev.of_node->full_name);
	}

	ret = of_property_read_string(pdev->dev.of_node,
		"qcom,mbhc-audio-jack-type", &mbhc_audio_jack_type);
	if (ret) {
		dev_dbg(&pdev->dev, "%s: Looking up %s property in node %s failed\n",
			__func__, "qcom,mbhc-audio-jack-type",
			pdev->dev.of_node->full_name);
		dev_dbg(&pdev->dev, "Jack type properties set to default\n");
	} else {
		if (!strcmp(mbhc_audio_jack_type, "4-pole-jack")) {
			wcd_mbhc_cfg.enable_anc_mic_detect = false;
			dev_dbg(&pdev->dev, "This hardware has 4 pole jack");
		} else if (!strcmp(mbhc_audio_jack_type, "5-pole-jack")) {
			wcd_mbhc_cfg.enable_anc_mic_detect = true;
			dev_dbg(&pdev->dev, "This hardware has 5 pole jack");
		} else if (!strcmp(mbhc_audio_jack_type, "6-pole-jack")) {
			wcd_mbhc_cfg.enable_anc_mic_detect = true;
			dev_dbg(&pdev->dev, "This hardware has 6 pole jack");
		} else {
			wcd_mbhc_cfg.enable_anc_mic_detect = false;
			dev_dbg(&pdev->dev, "Unknown value, set to default\n");
		}
	}
	/*
	 * Parse US-Euro gpio info from DT. Report no error if us-euro
	 * entry is not found in DT file as some targets do not support
	 * US-Euro detection
	 */
	pdata->us_euro_gpio_p = of_parse_phandle(pdev->dev.of_node,
					"qcom,us-euro-gpios", 0);
	if (!pdata->us_euro_gpio_p) {
		dev_dbg(&pdev->dev, "property %s not detected in node %s",
			"qcom,us-euro-gpios", pdev->dev.of_node->full_name);
	} else {
		dev_dbg(&pdev->dev, "%s detected\n",
			"qcom,us-euro-gpios");
		wcd_mbhc_cfg.swap_gnd_mic = msm_swap_gnd_mic;
	}

	if (wcd_mbhc_cfg.enable_usbc_analog)
		wcd_mbhc_cfg.swap_gnd_mic = msm_usbc_swap_gnd_mic;

	pdata->fsa_handle = of_parse_phandle(pdev->dev.of_node,
					"fsa4480-i2c-handle", 0);
	if (!pdata->fsa_handle)
		dev_dbg(&pdev->dev, "property %s not detected in node %s\n",
			"fsa4480-i2c-handle", pdev->dev.of_node->full_name);

	msm_i2s_auxpcm_init(pdev);
	pdata->dmic01_gpio_p = of_parse_phandle(pdev->dev.of_node,
					      "qcom,cdc-dmic01-gpios",
					       0);
	pdata->dmic23_gpio_p = of_parse_phandle(pdev->dev.of_node,
					      "qcom,cdc-dmic23-gpios",
					       0);
	pdata->dmic45_gpio_p = of_parse_phandle(pdev->dev.of_node,
					      "qcom,cdc-dmic45-gpios",
					       0);
	if (pdata->dmic01_gpio_p)
		msm_cdc_pinctrl_set_wakeup_capable(pdata->dmic01_gpio_p, false);
	if (pdata->dmic23_gpio_p)
		msm_cdc_pinctrl_set_wakeup_capable(pdata->dmic23_gpio_p, false);
	if (pdata->dmic45_gpio_p)
		msm_cdc_pinctrl_set_wakeup_capable(pdata->dmic45_gpio_p, false);

	pdata->mi2s_gpio_p[PRIM_MI2S] = of_parse_phandle(pdev->dev.of_node,
					"qcom,pri-mi2s-gpios", 0);
	pdata->mi2s_gpio_p[SEC_MI2S] = of_parse_phandle(pdev->dev.of_node,
					"qcom,sec-mi2s-gpios", 0);
	pdata->mi2s_gpio_p[TERT_MI2S] = of_parse_phandle(pdev->dev.of_node,
					"qcom,tert-mi2s-gpios", 0);
	pdata->mi2s_gpio_p[QUAT_MI2S] = of_parse_phandle(pdev->dev.of_node,
					"qcom,quat-mi2s-gpios", 0);
	pdata->mi2s_gpio_p[QUIN_MI2S] = of_parse_phandle(pdev->dev.of_node,
					"qcom,quin-mi2s-gpios", 0);
	pdata->mi2s_gpio_p[SEN_MI2S] = of_parse_phandle(pdev->dev.of_node,
					"qcom,sen-mi2s-gpios", 0);
	for (index = PRIM_MI2S; index < MI2S_MAX; index++) {
		if (pdata->mi2s_gpio_p[index])
			msm_cdc_pinctrl_set_wakeup_capable(pdata->mi2s_gpio_p[index], false);
		atomic_set(&(pdata->mi2s_gpio_ref_count[index]), 0);
	}

	/* Register LPASS audio hw vote */
	lpass_audio_hw_vote = devm_clk_get(&pdev->dev, "lpass_audio_hw_vote");
	if (IS_ERR(lpass_audio_hw_vote)) {
		ret = PTR_ERR(lpass_audio_hw_vote);
		dev_dbg(&pdev->dev, "%s: clk get %s failed %d\n",
			__func__, "lpass_audio_hw_vote", ret);
		lpass_audio_hw_vote = NULL;
		ret = 0;
	}
	pdata->lpass_audio_hw_vote = lpass_audio_hw_vote;
	pdata->core_audio_vote_count = 0;

	ret = msm_audio_ssr_register(&pdev->dev);
	if (ret)
		pr_err("%s: Registration with SND event FWK failed ret = %d\n",
			__func__, ret);

	is_initial_boot = true;
	return 0;
err:
	devm_kfree(&pdev->dev, pdata);
	return ret;
}

2. module_platform_driver

先把kona.c文件拉到最后,

static struct platform_driver kona_asoc_machine_driver = {
	.driver = {
		.name = DRV_NAME,
		.owner = THIS_MODULE,
		.pm = &snd_soc_pm_ops,
		.of_match_table = kona_asoc_machine_of_match,
		.suppress_bind_attrs = true,
	},
	.probe = msm_asoc_machine_probe,
	.remove = msm_asoc_machine_remove,
};
module_platform_driver(kona_asoc_machine_driver);

MODULE_DESCRIPTION("ALSA SoC msm");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);
MODULE_DEVICE_TABLE(of, kona_asoc_machine_of_match);
/* module_platform_driver() - Helper macro for drivers that don't do
 * anything special in module init/exit.  This eliminates a lot of
 * boilerplate.  Each module may only use this macro once, and
 * calling it replaces module_init() and module_exit()
 */
#define module_platform_driver(__platform_driver) \
	module_driver(__platform_driver, platform_driver_register, \
			platform_driver_unregister)

module_platform_driver里面包含了驱动注册和卸载,在内存加载设备树,然后kernel解析的时候,之后会加载ko文件,注册这个驱动,填充platform_device结构体。

static const struct of_device_id kona_asoc_machine_of_match[]  = {
	{ .compatible = "qcom,kona-asoc-snd",
	  .data = "codec"},
	{ .compatible = "qcom,kona-asoc-snd-stub",
	  .data = "stub_codec"},
	{},
};

然后根据platform_driver结构体里面的kona_asoc_machine_of_match绑定设备树里面的compatible。

对应设备树代码位置:

./vendor/qcom/proprietary/devicetree-4.19/qcom/lagoon-audio.dtsi

&q6core {
	lagoon_snd: sound {
		compatible = "qcom,kona-asoc-snd";
		...
		clock-names = "lpass_audio_hw_vote";
		clocks = <&lpass_audio_hw_vote 0>;

		asoc-platform = <&pcm0>, <&pcm1>, <&pcm2>, <&voip>, <&voice>,
						....
		asoc-platform-names = "msm-pcm-dsp.0", "msm-pcm-dsp.1",
				"msm-pcm-dsp.2", "msm-voip-dsp",
				...
		asoc-cpu = <&dai_dp>, <&dai_dp1>,
				<&dai_mi2s0>, <&dai_mi2s1>,
				<&dai_mi2s2>, <&dai_mi2s3>,
				...
		asoc-cpu-names = "msm-dai-q6-dp.0", "msm-dai-q6-dp.1",
				"msm-dai-q6-mi2s.0", "msm-dai-q6-mi2s.1",
				......
		fsa4480-i2c-handle = <&fsa4480>;
	};
};

相关的platform dai、cpu dai也从这个设备树里面获取,这边也看到了lpass_audio_hw_vote,LPASS投票的时钟,这个后面再说。

3. snd_soc_card的dev结构

pdata = devm_kzalloc(&pdev->dev,
		sizeof(struct msm_asoc_mach_data), GFP_KERNEL);
if (!pdata)
	return -ENOMEM;

msm_asoc_mach_data结构体内存分配。

of_property_read_u32(pdev->dev.of_node,
			"qcom,lito-is-v2-enabled",
			&pdata->lito_v2_enabled);

//获取设备树里面的qcom,lito-is-v2-enabled值,存到&pdata->lito_v2_enabled。

	card = populate_snd_card_dailinks(&pdev->dev);
	if (!card) {
		dev_err(&pdev->dev, "%s: Card uninitialized\n", __func__);
		ret = -EINVAL;
		goto err;
	}

获取 platform_device相关dai(cpu dai、 codec dai从上面的lagoon-audio.dtsi设备树获得的),填充到snd_soc_card结构体里面。

	card->dev = &pdev->dev;
	platform_set_drvdata(pdev, card);
	snd_soc_card_set_drvdata(card, pdata);

设备驱动绑定之后的device结构体信息,存储到snd_soc_card的dev结构体成员里面;然后存储card以及pdata的指针,

需要的时候,通过platform_get_drvdata和snd_soc_card_get_drvdata取出。

4. 解析声卡名

ret = snd_soc_of_parse_card_name(card, "qcom,model");
if (ret) {
	dev_err(&pdev->dev, "%s: parse card name failed, err:%d\n",
		__func__, ret);
	goto err;
}

./vendor/qcom/proprietary/devicetree-4.19/qcom/lagoon-audio-overlay.dtsi

  &lagoon_snd {
	qcom,model = "lito-lagoonmtp-snd-card";

qcom,model会被函数snd_soc_of_parse_card_name解析出来,声卡名字会被存放到card->name,即实例化了snd_soc_card结构体里面的声卡名字。

5. 解析麦克偏置

ret = snd_soc_of_parse_audio_routing(card, "qcom,audio-routing");
if (ret) {
	dev_err(&pdev->dev, "%s: parse audio routing failed, err:%d\n",
		__func__, ret);
	goto err;
}
qcom,audio-routing =
	"AMIC1", "MIC BIAS1",
	"MIC BIAS1", "Analog Mic1",
    "AMIC2", "MIC BIAS2",
	"MIC BIAS2", "Analog Mic2",
	"AMIC3", "MIC BIAS3",

lagoon-audio-overlay.dtsi里面的一堆麦克偏置相关的东西会这里面被解析出来。

6. 解析dai

ret = msm_populate_dai_link_component_of_node(card);
if (ret) {
	ret = -EPROBE_DEFER;
	goto err;
}

解析并填充CPU、CODEC相关的dai。

7. 填充snd_soc_aux_dev结构体

ret = msm_init_aux_dev(pdev, card);
if (ret)
	goto err;
 struct snd_soc_aux_dev {
 	const char *name;		/* Codec name */
 
 	/*
 	 * specify multi-codec either by device name, or by
 	 * DT/OF node, but not both.
 	 */
 	const char *codec_name;
 	struct device_node *codec_of_node;
 
 	/* codec/machine specific init - e.g. add machine controls */
 	int (*init)(struct snd_soc_component *component);
 };

msm_init_aux_dev函数的作用是填充snd_soc_aux_dev结构体,把AUX相关的codec注册到ALSA核心,如果采用了硬件上的WSA设计,WSA CODEC相关的控件也会注册添加;

8. 声卡注册

ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret == -EPROBE_DEFER) {
	if (codec_reg_done)
		ret = -EINVAL;
	goto err;
} else if (ret) {
	dev_err(&pdev->dev, "%s: snd_soc_register_card failed (%d)\n",
		__func__, ret);
	goto err;
}
int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card)
{
	struct snd_soc_card **ptr;
	int ret;

	ptr = devres_alloc(devm_card_release, sizeof(*ptr), GFP_KERNEL);
	if (!ptr)
		return -ENOMEM;

	ret = snd_soc_register_card(card);
	if (ret == 0) {
		*ptr = card;
		devres_add(dev, ptr);
	} else {
		devres_free(ptr);
	}

	return ret;
}
EXPORT_SYMBOL_GPL(devm_snd_soc_register_card);

devm_snd_soc_register_card这个函数很关键,全局函数devm_snd_soc_register_card会调用snd_soc_register_card函数,通过传递进来的card指针,即snd_soc_card结构体指针,指定codec dai name,cpu dai name,platform name。

相关的dai已由前文所说通过msm_populate_dai_link_component_of_node函数填充了snd_soc_dai_link结构体,而这个结构体是snd_soc_card结构体的成员变量。

接着在snd_soc_register_card函数里面,会调用snd_soc_initialize_card_lists初始化widgets、paths、dapm_list等链表头。再接着snd_soc_instantiate_card函数绑定各种dai_link,通过snd_card_new函数创建声卡,初始化声卡绑定的dai_link。(调用相关dai的probe函数)

9. 客制化的外部PA代码

kona_parse_aw87xx_mode(pdev, pdata);
if (!pdata->spk_ext_pa_gpio_p) {
	pr_err("%s: property %s not detected in node %s\n", __func__
	, "qcom,msm-spk-ext-pa-gpios", pdev->dev.of_node->full_name);
}else{
	ret = msm_cdc_pinctrl_select_sleep_state(pdata->spk_ext_pa_gpio_p);
	if (ret < 0) {
		pr_err("%s: gpio set cannot be activated %s\n", __func__, "speaker_pa_gpio");
		return ret;
	}
}

kona_parse_aw87xx_mode函数是自定义的,目的是解析外部smart K 、PA的脉冲个数。

10. TDM接口

if ((pdata->tdm_max_slots <= 0) || (pdata->tdm_max_slots >
    TDM_MAX_SLOTS)) {
	pdata->tdm_max_slots = TDM_MAX_SLOTS;
	dev_err(&pdev->dev, "%s: Using default tdm max slot: %d\n",
		__func__, pdata->tdm_max_slots);
}

从这些代码里面发现,此平台支持TDM接口,相比于I2S接口,PCM接口应用更加灵活。通过时分复用(TDM, Time Division Multiplexing)方式,PCM接口支持同时传输多达N个(N>8)声道的数据,减少了管脚数目实际上是减少I2S的“组”数,因为每组I2S只能传输两声道数据。

11. 耳机

	ret = of_property_read_string(pdev->dev.of_node,
		"qcom,mbhc-audio-jack-type", &mbhc_audio_jack_type);
	.......
	/*
	 * Parse US-Euro gpio info from DT. Report no error if us-euro
	 * entry is not found in DT file as some targets do not support
	 * US-Euro detection
	 */
	pdata->us_euro_gpio_p = of_parse_phandle(pdev->dev.of_node,
					"qcom,us-euro-gpios", 0);
  ......

	if (wcd_mbhc_cfg.enable_usbc_analog)
		wcd_mbhc_cfg.swap_gnd_mic = msm_usbc_swap_gnd_mic;

这部分是耳机PIN脚,麦克,欧标-美标兼容相关的代码。

12. 废弃暂时没有作用的代码

pdata->fsa_handle = of_parse_phandle(pdev->dev.of_node,
				"fsa4480-i2c-handle", 0);
if (!pdata->fsa_handle)
	dev_dbg(&pdev->dev, "property %s not detected in node %s\n",
		"fsa4480-i2c-handle", pdev->dev.of_node->full_name);

fsa4480-i2c-handle最终是控制gpio84,但是这个设计在我司硬件上废弃了。
更新 2021.09.07
fsa芯片是高通参考设计,给USB模拟耳机用的。
更新 2021.09.07

 &qupv3_se10_i2c {
     status = "ok";
     fsa4480: fsa4480@42 {
         compatible = "qcom,fsa4480-i2c";
         reg = <0x42>;
         pinctrl-names = "default";
         pinctrl-0 = <&fsa_usbc_ana_en>;
     };
 };

         fsa_usbc_ana_en_n@84 {
             fsa_usbc_ana_en: fsa_usbc_ana_en {
                 mux {
                     pins = "gpio84";
                     function = "gpio";
                 };

                 config {
                     pins = "gpio84";
                     drive-strength = <2>;
                     bias-disable;
                     output-low;
                 };
             };
         };

13. MI2S主从模式配置

msm_i2s_auxpcm_init(pdev);
 static void msm_i2s_auxpcm_init(struct platform_device *pdev)
 {
 	int count = 0;
 	u32 mi2s_master_slave[MI2S_MAX];
 	int ret = 0;
 
 	for (count = 0; count < MI2S_MAX; count++) {
 		mutex_init(&mi2s_intf_conf[count].lock);
 		mi2s_intf_conf[count].ref_cnt = 0;
 	}
 
 	ret = of_property_read_u32_array(pdev->dev.of_node,
 			"qcom,msm-mi2s-master",
 			mi2s_master_slave, MI2S_MAX);
 	if (ret) {
 		dev_dbg(&pdev->dev, "%s: no qcom,msm-mi2s-master in DT node\n",
 			__func__);
 	} else {
 		for (count = 0; count < MI2S_MAX; count++) {
 			mi2s_intf_conf[count].msm_is_mi2s_master =
 				mi2s_master_slave[count];
 		}
 	}
 }

这个函数还是比较重要的,本质上是解析qcom,msm-mi2s-master设备树,配置各个mi2s的主、从模式。

./vendor/qcom/proprietary/devicetree-4.19/qcom/lagoon-audio-overlay.dtsi

 &lagoon_snd {
     qcom,model = "lito-lagoonmtp-snd-card";
     qcom,msm-mi2s-master = <1>, <1>, <1>, <1>, <1>, <1>;

高通文档有介绍这个设备树含义是配置不同的I2S模式的:

 qcom,msm-mi2s-master = <1>, <1>, <1>, <1>, <1>, <1> – Use this property to configure
 primary, secondary, tertiary, quaternary, quinary, and senary MI2S to Master/Slave mode.
 – Master mode – 1
 – Slave mode – 0

这里需要注意的是,主模式、从模式,对于CPU和CODEC芯片来说都是相对而言的,一般我们约定提供SCK,即提供时钟信号的那一方称为主模式。

14. 数字麦克

pdata->dmic01_gpio_p = of_parse_phandle(pdev->dev.of_node,
				      "qcom,cdc-dmic01-gpios",
				       0);
pdata->dmic23_gpio_p = of_parse_phandle(pdev->dev.of_node,
				      "qcom,cdc-dmic23-gpios",
				       0);
pdata->dmic45_gpio_p = of_parse_phandle(pdev->dev.of_node,
				      "qcom,cdc-dmic45-gpios",
				       0);
if (pdata->dmic01_gpio_p)
	msm_cdc_pinctrl_set_wakeup_capable(pdata->dmic01_gpio_p, false);
if (pdata->dmic23_gpio_p)
	msm_cdc_pinctrl_set_wakeup_capable(pdata->dmic23_gpio_p, false);
if (pdata->dmic45_gpio_p)
	msm_cdc_pinctrl_set_wakeup_capable(pdata->dmic45_gpio_p, false);

这些都是解析数字麦克用的设备树,dmic01用到gpi0133、gpio134这个与i2s总线Quinary MI2S不可同时用,dmic45用到了gpio139、gpio140,这个与Senary MI2S不可同时使用。

for (index = PRIM_MI2S; index < MI2S_MAX; index++) {
	if (pdata->mi2s_gpio_p[index])
		msm_cdc_pinctrl_set_wakeup_capable(pdata->mi2s_gpio_p[index], false);
	atomic_set(&(pdata->mi2s_gpio_ref_count[index]), 0);
}

msm_cdc_pinctrl_set_wakeup_capable函数可以把对应的pinctrl设置为可唤醒。

15. LPASS audio hw vote

	/* Register LPASS audio hw vote */
	lpass_audio_hw_vote = devm_clk_get(&pdev->dev, "lpass_audio_hw_vote");
	if (IS_ERR(lpass_audio_hw_vote)) {
		ret = PTR_ERR(lpass_audio_hw_vote);
		dev_dbg(&pdev->dev, "%s: clk get %s failed %d\n",
			__func__, "lpass_audio_hw_vote", ret);
		lpass_audio_hw_vote = NULL;
		ret = 0;
	}
	pdata->lpass_audio_hw_vote = lpass_audio_hw_vote;
	pdata->core_audio_vote_count = 0;

	ret = msm_audio_ssr_register(&pdev->dev);
	if (ret)
		pr_err("%s: Registration with SND event FWK failed ret = %d\n",
			__func__, ret);

	is_initial_boot = true;
	return 0;
err:
	devm_kfree(&pdev->dev, pdata);
	return ret;
}

./vendor/qcom/proprietary/devicetree-4.19/qcom/lagoon-audio.dtsi

 &audio_apr {
     q6core: qcom,q6core-audio {
         compatible = "qcom,q6core-audio";

         lpass_core_hw_vote: vote_lpass_core_hw {
             compatible = "qcom,audio-ref-clk";
             qcom,codec-ext-clk-src = <AUDIO_LPASS_CORE_HW_VOTE>;
             #clock-cells = <1>;
         };

         lpass_audio_hw_vote: vote_lpass_audio_hw {
             compatible = "qcom,audio-ref-clk";
             qcom,codec-ext-clk-src = <AUDIO_LPASS_AUDIO_HW_VOTE>;
             #clock-cells = <1>;
         };

新增LPASS音频投票机制,这个新增的代码在之前低端的平台machine driver没找到相关代码,在参考链接1的高通文档有介绍:

共享时钟需要有关闭表决权,从而关闭PMIC处的核心晶振振荡器(CXO)缓存,之后可以进入低功耗模式,有助于减少设备空闲时的功耗,之后CXO会在PMIC中生成32 kHz睡眠时钟。

本文篇幅较长,感谢阅读哦!
在这里插入图片描述

16.参考链接

  1. https://developer.qualcomm.com/qfile/35136/lm80-p0436-73_a_qualcomm_snapdragon_410e_processor_apq8016e_system_power_overview.pdf
    (这个高通文档可以输入到浏览器直接下载,不用上外网)

17. 作者注

/******
@article{Linux Audio Driver,
Author = { 1byte ≠ 8bit},
Year = { 2020},
}
******/
<think>我们正在讨论ALSA ASoC框架中的Platform Driver,特别是其中的CPU DAI Driver。根据引用内容,Platform驱动分为snd_soc_platform_driver和snd_soc_dai_driver两部分。其中,snd_soc_dai_driver就是CPU DAI Driver。 ### 一、CPU DAI Driver的定义与作用 1. **DAI概念** DAI(Digital Audio Interface)即数字音频接口,是SoC与Codec之间传输音频数据的硬件接口,常见类型包括I2S、PCM、AC97等[^1][^3]。 2. **CPU DAI Driver的核心作用** - **配置CPU端DAI硬件**:设置采样率、位宽、时钟极性、主从模式等参数[^3] - **建立音频数据传输通道**:作为音频数据从Platform DMA到外部Codec的桥梁[^1] - **与DMA引擎交互**:传递DMA参数(如FIFO地址、传输周期)给snd_soc_platform_driver[^1] ```mermaid graph LR A[PCM数据] --> B(snd_soc_platform_driver) B -->|通过DMA传输| C(CPU DAI Driver) C -->|配置DAI硬件| D[Codec] ``` 3. **与Codec DAI的区别** | 组件 | 控制对象 | 驱动位置 | |-----------------|---------------|----------------| | **CPU DAI** | SoC内部音频接口 | Platform驱动侧 | | **Codec DAI** | 外部编解码器 | Codec驱动侧 | ### 二、CPU DAI Driver的关键实现 1. **数据结构** 通过`snd_soc_dai_driver`结构体定义(位于`include/sound/soc-dai.h`): ```c struct snd_soc_dai_driver { const char *name; // DAI名称,如"i2s-0" int (*probe)(struct snd_soc_dai *dai); int (*remove)(struct snd_soc_dai *dai); // 配置DAI参数 int (*set_sysclk)(...); // 设置系统时钟 int (*set_fmt)(...); // 设置主从模式/数据格式 // PCM操作 int (*startup)(...); // 流启动回调 int (*shutdown)(...); // 流关闭回调 int (*hw_params)(...); // 配置硬件参数(采样率/位宽) ... }; ``` 2. **典型配置流程** 当用户空间设置音频参数时,CPU DAI的配置流程如下: ```c // 1. 设置时钟源和频率(例如44.1kHz主时钟) dai_driver->set_sysclk(dai, clk_id, 4410000, SND_SOC_CLOCK_OUT); // 2. 设置数据格式(例如I2S主模式) dai_driver->set_fmt(dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS); // 3. 配置硬件参数(例如16位采样、双声道) dai_driver->hw_params(dai, ¶ms); // params包含位深/声道数等 ``` ### 三、与Platform_driver的交互 1. **数据流协作** 音频数据通过DMA从内存传输到CPU DAI的FIFO,由CPU DAI驱动将数据按配置的时序发送到Codec[^1]: ```mermaid sequenceDiagram DMA引擎->>+CPU DAI FIFO: 填充音频数据 CPU DAI驱动->>CPU DAI FIFO: 按I2S时序读取数据 CPU DAI驱动->>Codec: 通过DAI引脚发送数据 ``` 2. **参数传递机制** - DMA参数(如周期大小、DMA地址)通过`snd_soc_dai_dma_data`结构传递[^1]: ```c struct snd_soc_dai_dma_data { dma_addr_t addr; // DMA物理地址 size_t bytes; // 传输字节数 enum snd_dma_type type; // DMA类型(如连续内存) }; ``` - 在`hw_params`阶段,Platform_driver调用`dai->driver->ops->hw_params()`并获取DMA配置 ### 四、在Machine驱动中的整合 Machine驱动通过`snd_soc_dai_link`结构将CPU DAI与Codec DAI绑定[^2]: ```c static struct snd_soc_dai_link my_dai_link = { .name = "MyDAI", .stream_name = "MyAudio", .cpu_dai_name = "samsung-i2s.0", // 对应CPU DAI驱动名 .codec_dai_name = "tlv320aic3x-hifi", .platform_name = "samsung-pcm-audio", // Platform_driver名 .codec_name = "tlv320aic3x-codec", .init = my_dai_init, }; ``` > 注:当`platform_name`与`cpu_dai_name`相同时(如三星平台),表明Platform_driver和CPU DAI_driver由同一驱动注册[^2] --- §§相关问题§§ 1. 如何为自定义I2S控制器编写CPU DAI驱动?需要实现哪些核心回调函数? 2. 当使用非标准音频接口(如PDM麦克风)时,CPU DAI驱动需要做哪些特殊配置? 3. Machine驱动如何解决多个CPU DAI(如I2S0/I2S1)与Codec之间的路由选择? 4. 在异步采样率转换(ASRC)场景中,CPU DAI驱动如何与ASRC模块协同工作? 5. 如何通过Debugfs监控CPU DAI接口的实时状态(如FIFO填充水平、时钟抖动)?</think>### Platform Driver 中的 CPU DAI Driver 详解 #### 一、CPU DAI Driver 的定义与作用 1. **基本概念** CPU DAI(Digital Audio Interface)Driver 是 Platform Driver 的核心组件之一,负责管理 **SoC 芯片内部的数字音频接口**(如 I2S、PCM、AC97 等)。它作为音频数据传输的**硬件抽象层**,直接控制 SoC 的音频接口寄存器[^1][^3]。 2. **核心职责** ```mermaid graph LR A[音频数据] --> B(Platform_driver) B -->|DMA传输| C[CPU DAI Driver] C -->|配置时序/格式| D[Codec DAI] D --> E[外部Codec芯片] ``` - **硬件参数配置**:设置采样率、位宽、主从模式、时钟极性等[^3] - **数据传输桥梁**:将 Platform_driver 的 DMA 数据流转发到物理 DAI 接口 - **协议适配**:确保 SoC 与外部 Codec 的时序匹配(如 I2S 帧同步信号) - **错误处理**:检测并处理 FIFO 溢出/欠载等硬件异常 #### 二、与 Platform_driver 的交互机制 1. **协作关系** CPU DAI Driver 和 Platform_driver(DMA 部分)共同构成完整的音频数据处理链: ```c // 典型ASoC驱动结构 static struct snd_soc_dai_driver my_cpu_dai = { // CPU DAI驱动定义 .name = "i2s-0", .playback = { .channels_min = 2, .rates = SNDRV_PCM_RATE_48000, }, .ops = &my_dai_ops // 包含hw_params/trigger等回调 }; static struct snd_soc_platform_driver my_platform = { // Platform_driver定义 .ops = &my_pcm_ops, // 包含pcm_open/hw_params等 .pcm_new = my_pcm_new, }; ``` 2. **关键交互点** - **参数同步**:在 `hw_params` 阶段,Platform_driver 通过 `snd_soc_dai_set_sysclk()` 等 API 将参数传递给 CPU DAI - **数据传输**:Platform_driver 的 DMA 引擎从内存读取数据 → 写入 CPU DAI 的 FIFO → 通过物理引脚输出 - **状态管理**:CPU DAI 通过中断通知 Platform_driver 传输状态(如 `SNDRV_PCM_TRIGGER_START`) #### 三、在 Machine 驱动中的整合方式 1. **绑定机制** Machine 驱动通过 `snd_soc_dai_link` 结构将 CPU DAI 与 Codec DAI 关联: ```c static struct snd_soc_dai_link my_dai_link = { .name = "Primary DAI", .stream_name = "Playback", .cpu_dai_name = "i2s-0", // 引用CPU DAI驱动名 .codec_dai_name = "tlv320-dai", // 外部Codec DAI .platform_name = "dma-audio", // Platform_driver名 .codec_name = "tlv320-codec", }; ``` 2. **两种实现模式** | 模式 | 特点 | 适用场景 | |---------------------|----------------------------------------------------------------------|-----------------------| | **耦合模式** | `platform_name` 和 `cpu_dai_name` 相同(如三星平台)[^2] | 高度集成的SoC | | **分离模式** | `platform_name`(DMA驱动)与 `cpu_dai_name`(DAI驱动)独立注册[^2] | 模块化设计的多芯片方案 | #### 四、典型操作流程(以播放为例) 1. **启动序列** ```mermaid sequenceDiagram 用户空间->>+Platform_driver: snd_pcm_open() Platform_driver->>+CPU DAI: set_sysclk(48MHz) CPU DAI->>-Platform_driver: 时钟就绪 Platform_driver->>CPU DAI: set_fmt(I2S_MASTER) Platform_driver->>DMA: 配置内存缓冲区 Platform_driver->>CPU DAI: trigger(START) CPU DAI->>物理引脚: 输出I2S时序信号 ``` 2. **数据传输** $$ \text{数据流:} \underbrace{\text{内存}}_{\text{DMA访问}} \xrightarrow{\text{总线}} \underbrace{\text{FIFO}}_{\text{CPU DAI}} \xrightarrow{\text{SCK/WS/SD}} \text{Codec芯片} $$ #### 五、设计要点总结 1. **硬件抽象** CPU DAI Driver 封装了 SoC 音频接口的寄存器操作,使上层无需关注硬件差异。 2. **协议实现** 需严格遵循音频接口标准(如 I2S 的 $$ \text{WS} = \frac{f_{sysclk}}{f_{s} \times \text{通道数} \times \text{位深}} $$ 时序计算)。 3. **性能优化** - 使用双缓冲降低延迟 - 支持多DAI协同(如 I2S0 + I2S1 组成8声道) - 动态时钟管理(如 44.1kHz → 48kHz 无缝切换) ---
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值