平台: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,也是尊重人不是 -_-。
代码位置:
./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;
}
先把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投票的时钟,这个后面再说。
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取出。
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结构体里面的声卡名字。
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里面的一堆麦克偏置相关的东西会这里面被解析出来。
ret = msm_populate_dai_link_component_of_node(card);
if (ret) {
ret = -EPROBE_DEFER;
goto err;
}
解析并填充CPU、CODEC相关的dai。
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相关的控件也会注册添加;
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函数)
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的脉冲个数。
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只能传输两声道数据。
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脚,麦克,欧标-美标兼容相关的代码。
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;
};
};
};
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,即提供时钟信号的那一方称为主模式。
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设置为可唤醒。
/* 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睡眠时钟。
/******
@article{Linux Audio Driver,
Author = { 1byte ≠ 8bit},
Year = { 2020},
}
******/