平台驱动程序可以注册PCM驱动程序、CPU DAI驱动程序及其操作函数,为PCM组件预分配缓冲区,并根据需要设置回放和采集操作。换言之,平台驱动程序包含该平台的音频引擎和音频接口驱动程序(如I2S、AC97和PCM)。
平台驱动程序以构成平台的SoC为目标。它涉及平台的DMA(即音频数据在SoC中的每个块之间如何传输)和CPU DAI(即CPU向编解码器发送音频数据的路径或CPU从编解码器获得音频数据的路径)。
平台驱动程序有两个重要的数据结构体:structsnd_soc_component_driver和structsnd_soc_dai_driver。前者负责DMA数据管理,后者负责DAI的参数配置。当然,前文在讨论编解码器类驱动程序时已经描述过这两种数据结构体,因此,本节将仅介绍与平台代码相关的附加概念。
CPU DAI驱动程序
在平台侧,大部分工作都可以由core完成,尤其是与DMA相关的工作。因此,CPU DAI驱动程序通常只提供组件驱动程序结构中的接口名称,而让core完成其余的工作。以下是STM SPDIF驱动程序的示例,它在sound/soc/stm/stm32_spdif.c
中实现:
1 | static const struct snd_soc_dai_ops stm32_spdifrx_pcm_dai_ops = { |
其中snd_soc_component_driver只提供了name信息,件驱动程序和DAI驱动程序都和往常一样通过snd_soc_register_component()注册。 struct snd_soc_dai_driver必须根据实际的DAI属性设置,如果需要,应该设置dai_ops。当然,该设置的很大一部分是由snd_dmaengine_pcm_register()完成的,它将根据提供的设置组件驱动程序的PCM操作。
Platform DMA驱动程序
在声音生态系统中,我们有多种类型的设备:PCM、MIDI、混音器、音序器、计时器等。这里的PCM指的是脉冲编码调制(pulse code modulation),即对连续变化的模拟信号进行采样、量化和编码以产生数字信号。但要注意,这里它是指处理基于采样的数字音频的设备,而不是MIDI等。PCM层(ALSA核心的一部分)负责完成所有数字音频工作,例如,准备板卡以进行采集或回放、启动与设备之间的传输等。简而言之,如果你想回放或采集声音,那么你就需要一个PCM设备。
PCM驱动程序通过覆盖由struct snd_pcm_ops结构体公开的函数指针来帮助执行DMA操作。它与平台无关,仅与SOC DMA引擎上游API交互。然后,DMA引擎与特定于平台的DMA驱动程序交互以获得正确的DMA设置。struct snd_pcm_ops是一个包含一组回调函数的结构体,这些回调函数与有关PCM接口的不同事件相关。在处理ASoC(不是纯粹的ALSA)时,只要你使用通用PCM DMA引擎框架,就永远不需要按原样实例化此结构体。核心会为你完成这项工作。
音频DMA接口
音频DMA驱动程序通过snd_dmaengine_pcm_register()注册。此函数可以为设备注册一个struct snd_dmaengine_pcm_config。下面是它的原型:
1 | /** |
dev是PCM设备的父设备,通常是&pdev->dev
config是特定于平台的PCM配置,其类型为struct snd_dmaengine_pcm_config。下文将详细介绍这个结构体
flags表示描述如何处理DMA通道的附加标志。大多数情况下,它取值为0。但是,其可能的值已在include/sound/dmaengine_pcm.h中定义并且均以SND_DMAENGINE_为前缀。经常使用的标志包括以下3个
- SND_DMAENGINE_PCM_FLAG_COMPAT /* 表示将使用自定义回调函数来请求通道 */
- SND_DMAENGINE_PCM_FLAG_NO_DT /* 要求核心不要尝试通过设备树(DT)请求DMA通道 */
- SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX /* 示PCM是半双工(half-duplex)的,DMA通道在采集和回放之间共享 */
在注册之后,通用PCM DMA引擎框架将构建合适的snd_pcm_ops并设置组件驱动程序的.ops字段。
Linux中经典的DMA操作流程如下:
- dma_request_channel:用于分配slave通道(slave channel)
- dmaengine_slave_config:设置与slave通道和控制器相关参数
- dma_prep_xxx:获取事务的描述符
- dma_cookie = dmaengine_submit(tx):提交事务并抓取DMA cookie
- dma_async_issue_pending(chan):开始传输并等待回调函数通知
在ASOC中,设备树用于将DMA通道映射到PCM设备。 snd_dmaengine_pcm_register()可以通过dmaengine_pcm_request_chan_of()请求DMA通道,为了执行上述前3个步骤,需要为PCM DMA引擎核心提供附加信息,可以通过填充struct snd_dmaengine_pcm_config 来完成。后两步由PCM DMA引擎核心透明处理。
struct snd_dmaengine_pcm_config结构体如下:
1 | struct snd_dmaengine_pcm_config { |
该结构体主要处理DMA通道管理、缓冲区管理和通道配置,具体参数如下:
- prepare_slave_config: 用于为PCM子流填充DMA slave_config。它将从PCM驱动程序的hwparams回调函数中调用。
- compat_request_channel:用于为不使用设备树的platform请求DMA channel。如果设置了它,则.compat_filter_fn将被忽略
- compat_filter_fn:当为不使用设备树的platform请求DMA通道时,它发挥过滤功能,过滤的参数将是DAI的DMA数据
- dma_dev:允许为注册PCM驱动程序的设备以外的设备请求DMA通道,如果设置了它,则将在此设备而不是DAI设备上请求DMA通道
- chan_names:这是请求采集/回放DMA通道时使用的名称数组,如果设备有多个通道,则每个通道具有不同的DMA通道名称
- pcm_hardware:描述了PCM硬件功能
- prealloc_buffer_size:表示预分配音频缓冲区的大小
PCM DMA配置可能不会提供注册API(可能是NULL),在这种情况下你应该通过snd_soc_dai_init_dma_data()提供采集和回放DAI DMA通道配置。通过这种方法,其他元素将从系统核心派生。
流程梳理
数据流程框图如下:
可以看到,音频数据从用户复制到DMA缓冲区,然后,DMA事务将数据移动到平台音频TxFIF,由于它与编解码器的链接(通过它们各自的DAI),这些数据将被发送到编解码器,以通过扬声器回放音频。采集操作流则相反,只是扬声器被麦克风取代。
相关函数流程如下: 原图
参考文献
《Linux设备驱动开发-约翰-马德奥》