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

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,也是尊重人不是 -_-。
[Linux Audio Driver] Android 10 machine driver probe函数分析_第1张图片

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睡眠时钟。

本文篇幅较长,感谢阅读哦!
[Linux Audio Driver] Android 10 machine driver probe函数分析_第2张图片

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},
}
******/

你可能感兴趣的:(Qualcomm,Audio,linux,android,golang)