ALSA ASOC Platform Driver

  • 一、Platform 驱动作用
  • 二、ASOC Platform Driver 代码分析
    • 2.1 Linux Platform Driver & Platform Device 驱动模型
    • 2.2 在 Probe 函数中注册 ASOC Platform Driver(PCM DMA) & DAI Driver(CPU DAI)
    • 2.3 ASoc Platform Driver(PCM DMA) & DAI Driver(CPU DAI) 的 ops 字段
  • 三、音频数据的 DMA 操作

一、Platform 驱动作用

在前面Linux ALSA 之六:ALSA ASoc 架构已经介绍了什么是 ALSA ASoc,以及从 Hw & Sw 来看 ASoc Driver 都分为 Platform & Machine & Codec Driver,在本节中将介绍 Platform Driver。

如前面所述,Platform 指某款 Soc 平台的音频模块,如 Mediatek、Samsung 等。Platform 部件驱动的主要作用是完成音频数据的管理,最终通过 CPU 的数据音频接口(DAI)把音频数据传送给 Codec 进行处理,最终由 Codec 输出驱动耳机或是喇叭的音频信号。

在具体实现上,ASoC 有把 Platform 驱动分为两个部分:

  • PCM DMA
    即对应 linux-3.0 snd_soc_patform_driver,其主要负责管理音频数据,把音频数据通过 dma 或其他操作传送到 cpu dai 中,即负责控制 dma buffer & 将 dma buffer 中的音频数据搬运到后端的 FIFO,如 DSP PCM FIFO、I2S tx FIFO 等。音频 pcm dma 驱动需要在 platform 部件驱动中通过 snd_soc_register_component() 来注册。(即将 pcm dma 注册为一个只有 component_driver 的 component(没有 dai),在 machine 驱动中会将其绑定到 runtime 中,以便可以访问操作)
  • CPU DAI
    主要完成 cpu 一侧的 dai 的参数配置,同时也会通过一定的途径把必要的 dma 等参数与 PCM DMA 进行交互。在嵌入式系统里面通常指 SoC 的 I2S、PCM 总控制器,负责把音频数据从 I2S tx FIFO 搬运到 I2S rx FIFO(这是回放的情形,录制方向相反)。注意,DAI 是 Digital Audio Interface 的简称,分为 cpu dai 和 codec dai,这两者通过 I2S/PCM 总线连接;AIF 是 Audio Interface 的简称,嵌入式系统中一般是 I2S 和 PCM 接口。

【Note】后面描述用 Pcm Dma Driver 代表 Soc Platform Driver;用 Linux Platform Driver 指的是 linux 驱动模型 platform driver(不要被这两个相像的术语所迷惑,前者只是针对 ASoC 子系统的,后者是来自 Linux 的设备驱动模型)

【Note】在上述中会针对 PCM DMA 单独调用 snd_soc_register_component() 注册一个 component(而非使用 dai 对应的 component),猜测是仿照以前需要专门注册 dma platform driver 的 linux kernel 版本的方式,而目前在新的 linux kernel 版本中 PCM DMA & CPU DAI 可以注册在同一 Component,故将 dma component driver 中需要的操作直接在 dai 对应的 component driver 中。

二、ASOC Platform Driver 代码分析

(下面均以 mt2701-afe-pcm.c 为例进行讲解)

2.1 Linux Platform Driver & Platform Device 驱动模型

该部分其实就是 /sound/soc/medaitek/mt2701/mt2701-afe-pcm.c 中的 platform driver 与 /arch/arm/boot/dts/mt2701.dtsi 中的 platform device 进行匹配,匹配成功后调用 mt2701_afe_pcm_dev_probe() 函数。

1)mt2701-afe-pcm.c

static const struct of_device_id mt2701_afe_pcm_dt_match[] = {{ .compatible = "mediatek,mt2701-audio", .data = &mt2701_soc_v1 },{ .compatible = "mediatek,mt7622-audio", .data = &mt2701_soc_v2 },{},
};
MODULE_DEVICE_TABLE(of, mt2701_afe_pcm_dt_match);static struct platform_driver mt2701_afe_pcm_driver = {.driver = {.name = "mt2701-audio",.of_match_table = mt2701_afe_pcm_dt_match,
#ifdef CONFIG_PM.pm = &mt2701_afe_pm_ops,
#endif},.probe = mt2701_afe_pcm_dev_probe,.remove = mt2701_afe_pcm_dev_remove,
};module_platform_driver(mt2701_afe_pcm_driver);

platform driver 中有注册名为 "mediatek,mt2701-audio"
2)mt2701.dtsi

afe: audio-controller {compatible = "mediatek,mt2701-audio";interrupts =  <GIC_SPI 104 IRQ_TYPE_LEVEL_LOW>,<GIC_SPI 132 IRQ_TYPE_LEVEL_LOW>;interrupt-names  = "afe", "asys";
};

在设备树文件中有注册名为 "mediatek,mt2701-audio"platform device,当 platform driver & platform device 匹配之后则会调用 platform_driver 下的 probe() 函数。

2.2 在 Probe 函数中注册 ASOC Platform Driver(PCM DMA) & DAI Driver(CPU DAI)

1)Platform Driver (PCM DMA)

devm_snd_soc_register_component(&pdev->dev, &mtk_afe_pcm_platform, NULL, 0); //添加一个 dai_drv 为 NULL 的 component_drv => 控制 DMA

在 probe() 函数中会先调用上述函数将 platform driver(struct snd_soc_component_driver mtk_afe_pcm_platform) 注册为一个 component,只有注册后才能被 Machine 驱动使用。它的实现过程:为 snd_soc_component 实例分配内存,初始化相应将 snd_soc_component 实例添加到全局链表 conponent_list 中。


2)DAI Driver (CPU DAI)

//创建 pcm_dais component
devm_snd_soc_register_component(&pdev->dev, &mt2701_afe_pcm_dai_component, mt2701_afe_pcm_dais, ARRAY_SIZE(mt2701_afe_pcm_dais));

在 probe() 函数中后面也会调用上述函数将 dai driver(struct snd_soc_dai_driver mt2701_afe_pcm_dais[]) 注册为一个 component,并且通过 snd_soc_register_dais() 循环将所有的 snd_soc_dai_driver register 为 snd_soc_dai,添加到 component->dai_list 中,最后将该 component 添加到全局链表 component_list 以便后面能被 Machine 驱动使用,代码时序框图如下:

2.3 ASoc Platform Driver(PCM DMA) & DAI Driver(CPU DAI) 的 ops 字段

# Note:对于 platform driver ops 字段为 snd_pcm_ops 结构,DAI Driver ops 字段为 snd_soc_dai_ops 结构,对于两者的 ops 字段都会在打开 pcm substream 并进行操作时被调用,在 Machine 驱动中调用 soc_new_pcm() 时会对 substream->ops 进行赋值,该 ops 被调用时最终都会 call platform driver ops & dai driver ops 相应的回调函数,详细见后面的 Machine 小节。

1)Platform Driver (PCM DM) ops 字段
该 ops 字段是 snd_pcm_ops 结构,实现该结构中的各个回调函数是 soc platform 驱动的主要工作,它们基本都涉及 dma 操作以及 dma buffer 的管理等工作,如 mtk_afe_pcm_patform 对应的定义如下:

const struct snd_pcm_ops mtk_afe_pcm_ops = {.ioctl = snd_pcm_lib_ioctl,//该回调函数返回传送数据的当前位置(pcm 中间层通过调用这个函数来获取 dma 缓冲区的读指针)//一般情况下,在中断函数中调用 snd_pcm_period_elapsed() 或在 pcm 中间层更新 Buffer 时会调用它,然后 pcm 中间层会更新指针位置和计算缓冲区可用空间,唤醒那些在等待的线程.pointer = mtk_afe_pcm_pointer,
};
EXPORT_SYMBOL_GPL(mtk_afe_pcm_ops);const struct snd_soc_component_driver mtk_afe_pcm_platform = {.name = AFE_PCM_NAME,.ops = &mtk_afe_pcm_ops,.pcm_new = mtk_afe_pcm_new,.pcm_free = mtk_afe_pcm_free,
};
EXPORT_SYMBOL_GPL(mtk_afe_pcm_platform);

其中 component->driver->pcm_new() 会在 Machine 调用 soc_new_pcm() 创建 pcm device 时会调用(详细见 Machine 小节),主要实现是调用 snd_pcm_lib_preallocate_pages_for_all() 函数对 DMA 进行预分配

对于 ops->pointer() 返回传送数据的当前位置。pcm 中间层通过调用这个函数来获取 dma 缓冲区的读指针。一般情况下,在中断函数中调用 snd_pcm_period_elapsed() 或在 pcm 中间层更新 Buffer 时会调用它,然后 pcm 中间层会更新指针位置和计算缓冲区可用空间,唤醒那些在等待的线程,代码示例实现如下:

static snd_pcm_uframes_t mtk_afe_pcm_pointer(struct snd_pcm_substream *substream)
{struct snd_soc_pcm_runtime *rtd = substream->private_data;struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, AFE_PCM_NAME);struct mtk_base_afe *afe = snd_soc_component_get_drvdata(component);struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id];const struct mtk_base_memif_data *memif_data = memif->data;struct regmap *regmap = afe->regmap;struct device *dev = afe->dev;int reg_ofs_base = memif_data->reg_ofs_base;int reg_ofs_cur = memif_data->reg_ofs_cur;unsigned int hw_ptr = 0, hw_base = 0;int ret, pcm_ptr_bytes;//读 reg 获取 current offsetret = regmap_read(regmap, reg_ofs_cur, &hw_ptr);if (ret || hw_ptr == 0) {dev_err(dev, "%s hw_ptr err\n", __func__);pcm_ptr_bytes = 0;goto POINTER_RETURN_FRAMES;}//读 reg 获取 base offsetret = regmap_read(regmap, reg_ofs_base, &hw_base);if (ret || hw_base == 0) {dev_err(dev, "%s hw_ptr err\n", __func__);pcm_ptr_bytes = 0;goto POINTER_RETURN_FRAMES;}//计算得出在缓冲区中 pcm_ptrpcm_ptr_bytes = hw_ptr - hw_base;POINTER_RETURN_FRAMES://由于 pcm 中间层都是以 frames 为单位//For pcm://    1 frame => format2bytes*channels;//  1 sample => format2bytesreturn bytes_to_frames(substream->runtime, pcm_ptr_bytes);
}

ops 各个函数均需要取得一个 snd_pcm_runtime 结构体指针,这个指针可以通过 substream->runtime 来获得。snd_pcm_runtime 是运行时的信息,当打开一个 pcm substream 时,pcm 中间层就会为该 pcm substream 分配一个 pcm_runtime,它拥有很多种信息:hw_params、sw_params 配置拷贝,缓冲区指针信息,mmap 记录,自旋锁等。snd_pcm_runtime 对于驱动程序操作集函数都是只读的,仅 pcm 中间层可以改变或更新这些信息。


2)DAI Driver (CPU DAI) ops 字段
snd_soc_dai_driver 结构需要自己根据不同的 soc 芯片进行定义,关键字介绍如下:

成员 作用
probe、remove 回调函数,分别在声卡加载和卸载时被调用
suspend、resume 电源管理回调函数
ops 指向 snd_soc_dai_ops 结构,用于配置和控制该 dai
playback snd_soc_pcm_stream 结构,用于指出该 dai 支持的声道数、采样率、数据格式等
capture snd_soc_pcm_stream 结构,用于指出该 dai 支持的声道数、采样率、数据格式等

mt2701-afe-pcm.c 定义 snd_soc_dai_driver 结构体数据代码示例如下:

static struct snd_soc_dai_driver mt2701_afe_pcm_dais[] = {/* FE DAIs: memory intefaces to CPU */{.name = "PCMO0",.id = MT2701_MEMIF_DL1,.suspend = mtk_afe_dai_suspend,.resume = mtk_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 = &mt2701_single_memif_dai_ops,},{.name = "PCM_multi",.id = MT2701_MEMIF_DLM,.suspend = mtk_afe_dai_suspend,.resume = mtk_afe_dai_resume,.playback = {.stream_name = "DLM",.channels_min = 1,.channels_max = 8,.rates = SNDRV_PCM_RATE_8000_192000,.formats = (SNDRV_PCM_FMTBIT_S16_LE| SNDRV_PCM_FMTBIT_S24_LE| SNDRV_PCM_FMTBIT_S32_LE)},.ops = &mt2701_dlm_memif_dai_ops,},{.name = "PCM0",.id = MT2701_MEMIF_UL1,.suspend = mtk_afe_dai_suspend,.resume = mtk_afe_dai_resume,.capture = {.stream_name = "UL1",.channels_min = 1,.channels_max = 2,.rates = SNDRV_PCM_RATE_8000_48000,.formats = (SNDRV_PCM_FMTBIT_S16_LE| SNDRV_PCM_FMTBIT_S24_LE| SNDRV_PCM_FMTBIT_S32_LE)},.ops = &mt2701_single_memif_dai_ops,},{.name = "PCM1",.id = MT2701_MEMIF_UL2,.suspend = mtk_afe_dai_suspend,.resume = mtk_afe_dai_resume,.capture = {.stream_name = "UL2",.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 = &mt2701_single_memif_dai_ops,},...
}

定义如上,其中 ops 字段指向一个 snd_soc_dai_ops 结构,该结构实际上是一组回调函数集合,dai 的配置和控制几乎都是通过这些回调函数来实现的,其定义代码示例如下:

/* FE DAIs */
static const struct snd_soc_dai_ops mt2701_single_memif_dai_ops = {.startup    = mt2701_simple_fe_startup,.shutdown   = mtk_afe_fe_shutdown,.hw_params   = mt2701_simple_fe_hw_params,.hw_free  = mtk_afe_fe_hw_free,.prepare  = mtk_afe_fe_prepare,.trigger  = mtk_afe_fe_trigger,
};

标准的 snd_soc_ops 回调,通常由 soc-core 在进行 pcm 操作时调用:

  • startup
  • shutdown
  • hw_params
  • hw_free
  • prepare
  • trigger

三、音频数据的 DMA 操作

# Note:关于 DMA 操作一般会涉及源地址 & 目的地址配置,但是对于 MTK 比较特殊,比如 mt2701-afe-pcm.c,FE DAI 的话仅仅设置了源地址,而没有设置目的地址,理解上是由于使用的 DMA Engine 已经绑定好相应的 I2S/PCM 等 IO 口,即目的地址已被固定,故只要设置了源地址 & Enable Dma Engine 后则将源地址 audio data 搬到 I2S/PCM 等 IO 口,最后由 I2S/PCM 等接口传送到 DSP Input Port;BE DAI 的话源地址 & 目的地址均不需要要设置,理解上是 cpu dai <-> 也均已经在硬件上固定。

From Linux Kernel Document 一句话:

DMA IO to/from DSP Buffers(if applicable)

如前面“Linux ALSA 之三:简单的 ALSA Driver 实现”之 3.2 设置 PCM 操作中描述所知,当需要分配 DMA Memory 时会在 hw_params 中调用 snd_pcm_lib_malloc_pages() 函数进行分配 DMA Memory,并且将 DMA Memory 对应的物理地址设置给 DMA Engine,即设置 DMA Engine 源地址。同上,在打开 Pcm Substream 时也会调用 Platform Driver ops->hw_params & Dai Driver ops->hw_params,故在实现时可以在两者之一的 hw_params 操作如上内容。

参考链接:
linux-alsa详解5 ASOC-platform

Linux ALSA 之八:ALSA ASOC Platform Driver相关推荐

  1. PCM data flow - part 4: ASoC platform driver

    http://blog.csdn.net/azloong/article/details/17317829 概述中提到音频Platform驱动主要作用是音频数据的传输,这里又细分为两步: ·     ...

  2. Linux ALSA声卡驱动之八:ASoC架构中的Platform

    1.  Platform驱动在ASoC中的作用 前面几章内容已经说过,ASoC被分为Machine,Platform和Codec三大部件,Platform驱动的主要作用是完成音频数据的管理,最终通过C ...

  3. Linux ALSA 之九:ALSA ASOC Codec Driver

    ALSA ASOC Codec Driver 一.Codec 简介 二.Codec 注册 2.1 Codec Driver 的 Platform Driver & Platform Devic ...

  4. Linux ALSA 之十:ALSA ASOC Machine Driver

    ALSA ASOC Machine Driver 一.Machine 简介 二.ASoC Machine Driver 2.1 Machine Driver 的 Platform Driver &am ...

  5. linux内核之alsa,Linux操作系统Alsa音频编程

    一.前序 这里了解一下各个参数的含义以及一些基本概念. 声音是连续模拟量,计算机将它离散化之后用数字表示,就有了以下几个名词术语. 样本长度(sample):样本是记录音频数据最基本的单位,计算机对每 ...

  6. Linux 音频驱动(二) ASoC音频驱动之Platform驱动

    目录 1. 简介 2. 源码分析 2.1. CPU DAI 2.1.1. 数据结构struct snd_soc_dai_driver 2.1.2. 注册CPU DAI:snd_soc_register ...

  7. Linux项目设计:ALSA库安装(声卡)、语音识别、文字转语音、语音转文字

    文章目录 一.ALSA库的安装使用 (一)基本概念 (二)交叉编译 ALSA 库及其工具集 (三)ALSA程序模块 二.科大讯飞语音识别 (一)下载语音识别包 (二)文字转语音包使用 (三)语音转文字 ...

  8. linux4.9下alsa架构,[Alsa]4, wm8524 Kernel音频子系统入口

    上篇说到音频子系统的环境搭建和ASoC,我们会发现这样一个问题,对于已有的,已驱动的音频Codec,我们可以很方便地用aplayer.arecorder来录放音频,但是这表象背后到底隐藏了什么不为人知 ...

  9. Linux 音频驱动(一) ASoC音频框架简介

    目录 1. ALSA简介 2. ASoC音频驱动构成 3. PCM数据流 4. 数据结构简介 5. ASoC音频驱动注册流程 1. ALSA简介 Native ALSA Application:tin ...

最新文章

  1. NOIP2017游记
  2. 【转】jenkins 忘记admin用户账号密码
  3. Docker的官网在线--中文教程
  4. 设置在VS2005的IDE中迅速打开xaml文件
  5. 前端培训什么机构好?有什么好的学习方法能少走弯路?
  6. aws rds监控慢sql_AWS RDS SQL Server中的高级Windows身份验证配置
  7. BootStrap-
  8. SharePoint Framework 简介
  9. 在UITextView显示HTML,以及NSAttributedString乱码问题解决 swift
  10. 攻防世界 web2 write up
  11. linux下 复制文件显示进度 alias cp
  12. dw2xls已升级至pb11.5
  13. 用友2003年度NC_SCM项目经理/高级顾问认证考试试题及答案
  14. am3352 软时钟老是漂移 rx-8025时钟 rx-8025SA时钟
  15. 中国电科发布新型智慧城市顶层设计
  16. Win11桌面切换快捷键是什么?Win11快速切换桌面的方法
  17. mysql版网络验证自动发卡功能
  18. 知网html阅读需要花钱么,自己花钱怎么在知网上查重
  19. L1 L2正则化和优化器的weight_decay参数
  20. 单位弹性需求曲线形状_2021经济学考研:需求曲线形状与弹性的关系

热门文章

  1. [ExpOS]开发经验
  2. 使用 Apache Commons CLI 解析命令行参数示例
  3. clip-path介绍
  4. 有声计算机英语单词,背单词软件_【CRI】看得到的疼痛(有声)_沪江英语
  5. 真正的ps切图方法(前端必看)
  6. 免费的车辆违章车首页接口封装
  7. MySQL优化系列14-优化MySQL内存
  8. Unity3D课程学习笔记(一)
  9. python简笔画蚂蚁_使用python turtle绘制简笔画大白-Go语言中文社区
  10. 【A40i】全志A40i方案项目指引