Linux 驱动之 ALSA(四)Platform 驱动
平台驱动程序可以注册 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 设备驱动开发-约翰-马德奥》