[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音频投票机制。

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, 
<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、付费专栏及课程。

余额充值