物理链路篇
转自:https://me.csdn.net/zyuanyun
Linux ALSA 音频系统:物理链路篇
1. Overview
硬件平台及软件版本:
- Kernel - 3.4.5
- SoC - Samsung exynos
- CODEC - WM8994
- Machine - goni_wm8994
- Userspace - tinyalsa
Linux ALSA 音频系统架构大致如下:
+--------+ +--------+ +--------+
|tinyplay| |tinycap | |tinymix |
+--------+ +--------+ +--------+
| ^ ^
V | V
+--------------------------------+
| ALSA Library API |
| (tinyalsa, alsa-lib) |
+--------------------------------+
user space ^
-------------------------------|---------------------
kernel space V
+--------------------------------+
| ALSA CORE |
| +-------+ +-------+ +------+ |
| | PCM | |CONTROL| | MIDI |...|
| +-------+ +-------+ +------+ |
+--------------------------------+
|
+--------------------------------+
| ASoC CORE |
+--------------------------------+
|
+--------------------------------+
| hardware driver |
| +-------+ +--------+ +-----+ |
| |Machine| |Platform| |Codec| |
| +-------+ +--------+ +-----+ |
+--------------------------------+
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- Native ALSA Application:tinyplay/tinycap/tinymix,这些用户程序直接调用 alsa 用户库接口来实现放音、录音、控制
- ALSA Library API:alsa 用户库接口,常见有 tinyalsa、alsa-lib
- ALSA CORE:alsa 核心层,向上提供逻辑设备(PCM/CTL/MIDI/TIMER/…)系统调用,向下驱动硬件设备(Machine/I2S/DMA/CODEC)
- ASoC CORE:asoc 是建立在标准 alsa core 基础上,为了更好支持嵌入式系统和应用于移动设备的音频 codec 的一套软件体系
- Hardware Driver:音频硬件设备驱动,由三大部分组成,分别是 Machine、Platform、Codec
本主题不遵循自顶而下的原则,而先从硬件设备驱动说起,毕竟这些是看得见摸得着听得到的东西,容易对其有着直观的理解。
//
// 声明:本文由 http://blog.csdn.net/zyuanyun 原创,转载请注明出处,谢谢!
//
ALSA/ASoC 中硬件设备关系:
+------------------------------------------+
| Machine |
| +--------------+ +--------------+ |
| | Platform | | Codec | |
| | | I2S | | |
| | cpu_dai|<---->|codec_dai | |
| | | | | |
| +--------------+ +--------------+ |
+------------------------------------------+
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
-
Platform:指某款 SoC 平台的音频模块,如 exynos、omap、qcom 等等。Platform 又可细分两部分:
- cpu dai:在嵌入式系统里面通常指 SoC 的 I2S、PCM 总线控制器,负责把音频数据从 I2S tx FIFO 搬运到 CODEC(这是回放的情形,录制则方向相反)。cpu_dai 通过
snd_soc_register_dai()
来注册。注:DAI 是 Digital Audio Interface 的简称,分为 cpu_dai 和 codec_dai,这两者通过 I2S/PCM 总线连接;AIF 是 Audio Interface 的简称,嵌入式系统中一般是 I2S 和 PCM 接口。 - pcm dma:负责把 dma buffer 中的音频数据搬运到 I2S tx FIFO。值得留意的是:某些情形下是不需要 dma 操作的,比如 Modem 和 CODEC 直连,因为 Modem 本身已经把数据送到 FIFO 了,这时只需启动 codec_dai 接收数据即可;该情形下,Machine 驱动 dai_link 中需要设定
.platform_name = "snd-soc-dummy",
这是虚拟 dma 驱动,实现见sound/soc/soc-utils.c
。音频 dma 驱动通过snd_soc_register_platform()
来注册,故也常用 platform 来指代音频 dma 驱动(这里的 platform 需要与 SoC Platform 区分开)。
- cpu dai:在嵌入式系统里面通常指 SoC 的 I2S、PCM 总线控制器,负责把音频数据从 I2S tx FIFO 搬运到 CODEC(这是回放的情形,录制则方向相反)。cpu_dai 通过
-
Codec:对于回放来说,userspace 送过来的音频数据是经过采样量化的数字信号,在 codec 经过 DAC 转换成模拟信号然后输出到外放或耳机,这样我们就可以听到声音了。Codec 字面意思是编解码器,但芯片里面的功能部件很多,常见的有 AIF、DAC、ADC、Mixer、PGA、Line-in、Line-out,有些高端的 codec 芯片还有 EQ、DSP、SRC、DRC、AGC、Echo-Canceller、Noise-Suppression 等部件。
-
Machine:指某款机器,通过配置 dai_link 把 cpu_dai、codec_dai、modem_dai 各个音频接口给链结成一条条音频链路,然后注册
snd_soc_card
。和上面两个不一样,Platform 和 CODEC 驱动一般是可以重用的,而 Machine 有它特定的硬件特性,几乎是不可重用的。所谓的硬件特性指:SoC Platform 与 Codec 的差异;DAIs 之间的链结方式;通过某个 GPIO 打开 Amplifier;通过某个 GPIO 检测耳机插拔;使用某个时钟如 MCLK/External-OSC 作为 I2S、CODEC 的时钟源等等。
从上面的描述来看,对于回放的情形,PCM 数据流向大致是:
copy_from_user DMA I2S DAC
^ ^ ^ ^
+---------+ | +----------+ | +-----------+ | +-----+ | +------+
|userspace+-------->DMA Buffer+------->I2S TX FIFO+------->CODEC+------->SPK/HP|
+---------+ +----------+ +-----------+ +-----+ +------+
- 1
- 2
- 3
- 4
- 5
几个音频物理链路的概念:
dai_link:machine 驱动中定义的音频数据链路,它指定链路用到的 codec、codec_dai、cpu_dai、platform。比如对于 goni_wm8994 平台的 media 链路:codec="wm8994-codec"、codec_dai="wm8994-aif1"、cpu_dai="samsung-i2s"、platform="samsung-audio"
,这四者就构成了一条音频数据链路用于多媒体声音的回放和录制。一个系统可能有多个音频数据链路,比如 media 和 voice,因此可以定义多个 dai_link 。如 WM8994 的典型设计,有三个 dai_link,分别是 AP<>AIF1
的 “HIFI”(多媒体声音链路),BP<>AIF2
的 “Voice”(通话语音链路),以及 BT<>AIF3
(蓝牙 SCO 语音链路)。
代码如下:
static struct snd_soc_dai_link goni_dai[] = {
{
.name = "WM8994",
.stream_name = "WM8994 HiFi",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm8994-aif1",
.platform_name = "samsung-audio",
.codec_name = "wm8994-codec.0-001a",
.init = goni_wm8994_init,
.ops = &goni_hifi_ops,
}, {
.name = "WM8994 Voice",
.stream_name = "Voice",
.cpu_dai_name = "goni-voice-dai",
.codec_dai_name = "wm8994-aif2",
.codec_name = "wm8994-codec.0-001a",
.ops = &goni_voice_ops,
},
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
hw constraints:指平台本身的硬件限制,如所能支持的通道数/采样率/数据格式、DMA 支持的数据周期大小(period size)、周期次数(period count)等,通过 snd_pcm_hardware
结构体描述:
static const struct snd_pcm_hardware dma_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_U16_LE |
SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_S8,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128*1024,
.period_bytes_min = PAGE_SIZE,
.period_bytes_max = PAGE_SIZE*2,
.periods_min = 2,
.periods_max = 128,
.fifo_size = 32,
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
hw params:用户层设置的硬件参数,如 channels、sample rate、pcm format、period size、period count;这些参数受 hw constraints 约束。
sw params:用户层设置的软件参数,如 start threshold、stop threshold、silence threshold。
2. ASoC
ASoC:ALSA System on Chip,是建立在标准 ALSA 驱动之上,为了更好支持嵌入式系统和应用于移动设备的音频 codec 的一套软件体系,它依赖于标准 ALSA 驱动框架。内核文档 Documentation/alsa/soc/overview.txt
中详细介绍了 ASoC 的设计初衷,这里不一一引用,简单陈述如下:
- 独立的 codec 驱动,标准的 ALSA 驱动框架里面 codec 驱动往往与 SoC/CPU 耦合过于紧密,不利于在多样化的平台/机器上移植复用
- 方便 codec 与 SoC 通过 PCM/I2S 总线建立链接
- 动态音频电源管理 DAPM,使得 codec 任何时候都工作在最低功耗状态,同时负责音频路由的创建
- POPs 和 click 音抑制弱化处理,在 ASoC 中通过正确的音频部件上下电次序来实现
- Machine 驱动的特定控制,比如耳机、麦克风的插拔检测,外放功放的开关
在概述中已经介绍了 ASoC 硬件设备驱动的三大构成:Codec、Platform 和 Machine,下面列举各驱动的功能构成:
ASoC Codec Driver:
- Codec DAI 和 PCM 的配置信息
- Codec 的控制接口,如 I2C/SPI
- Mixer 和其他音频控件
- Codec 的音频接口函数,见
snd_soc_dai_ops
结构体定义 - DAPM 描述信息
- DAPM 事件处理句柄
- DAC 数字静音控制
ASoC Platform Driver: 包括 dma 和 cpu_dai 两部分:
- dma 驱动实现音频 dma 操作,具体见
snd_pcm_ops
结构体定义 - cpu_dai 驱动实现音频数字接口控制器的描述和配置
ASoC Machine Driver:
- 作为链结 Platform 和 Codec 的载体,它必须配置 dai_link 为音频数据链路指定 Platform 和 Codec
- 处理机器特有的音频控件和音频事件,例如回放时打开外放功放
硬件设备驱动相关结构体:
- snd_soc_codec_driver:音频编解码芯片描述及操作函数,如控件/微件/音频路由的描述信息、时钟配置、IO 控制等
- snd_soc_dai_driver:音频数据接口描述及操作函数,根据 codec 端和 soc 端,分为 codec_dai 和 cpu_dai
- snd_soc_platform_driver:音频 dma 设备描述及操作函数
- snd_soc_dai_link:音频链路描述及板级操作函数
下面是 goni_wm8994 类图,从这个类图中,我们可以大致了解 goni_wm8994 整个音频驱动组成:
3. Codec
上一章提到 codec_drv 的几个组成部分,下面逐一介绍,基本是以内核文档 Documentation/sound/alsa/soc/codec.txt
中的内容为脉络来分析的。Codec 的作用,之前已有描述,本章主要罗列下 Codec driver 中重要的数据结构及注册流程。
我们先看看 Codec 的硬件框图,以 WM8994 为例:
其中有着各种功能部件,包括但不限于 :
Widget | Description |
---|---|
ADC | 把麦克风拾取的模拟信号转换成数字信号 |
DAC | 把音频接口过来的数字信号转换成模拟信号 |
AIF | 音频数字接口,用于 Codec 与其他器件(如AP、BB等)之间的数据传输 |
MIXER | 混音器,把多路输入信号混合成单路输出 |
DRC | 动态范围调节 |
LHPF | 高低通滤波 |
3.1. Codec DAI and PCM configuration
codec_dai 和 pcm 配置信息通过结构体 snd_soc_dai_driver
描述,包括 dai 的能力描述和操作接口,snd_soc_dai_driver
最终会被注册到 soc-core 中。
/* * Digital Audio Interface Driver. * * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97 * operations and capabilities. Codec and platform drivers will register this * structure for every DAI they have. * This structure covers the clocking, formating and ALSA operations for each * interface. */ struct snd_soc_dai_driver { /* DAI description */ const char *name; unsigned int id; int ac97_control;
<span class="token comment">/* DAI driver callbacks */</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>probe<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>remove<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>suspend<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>resume<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* ops */</span> <span class="token keyword">const</span> <span class="token keyword">struct</span> snd_soc_dai_ops <span class="token operator">*</span>ops<span class="token punctuation">;</span> <span class="token comment">/* DAI capabilities */</span> <span class="token keyword">struct</span> snd_soc_pcm_stream capture<span class="token punctuation">;</span> <span class="token keyword">struct</span> snd_soc_pcm_stream playback<span class="token punctuation">;</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> symmetric_rates<span class="token punctuation">:</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">/* probe ordering - for components with runtime dependencies */</span> <span class="token keyword">int</span> probe_order<span class="token punctuation">;</span> <span class="token keyword">int</span> remove_order<span class="token punctuation">;</span>
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- name:codec_dai 的名称标识,dai_link 通过配置 codec_dai_name 来找到对应的 codec_dai;
- probe:codec_dai 的初始化函数,注册声卡时回调;
- playback:回放能力描述,如回放设备所支持的声道数、采样率、音频格式;
- capture:录制能力描述,如录制设备所支持声道数、采样率、音频格式;
- ops:codec_dai 的操作函数集,这些函数集非常重要,用于 dai 的时钟配置、格式配置、硬件参数配置。
例子,wm8994 有三个 dai,这里只列其一:
static const struct snd_soc_dai_ops wm8994_aif1_dai_ops = {
.set_sysclk = wm8994_set_dai_sysclk,
.set_fmt = wm8994_set_dai_fmt,
.hw_params = wm8994_hw_params,
.shutdown = wm8994_aif_shutdown,
.digital_mute = wm8994_aif_mute,
.set_pll = wm8994_set_fll,
.set_tristate = wm8994_set_tristate,
};
static struct snd_soc_dai_driver wm8994_dai[] = {
{
.name = “wm8994-aif1”,
.id = 1,
.playback = {
.stream_name = “AIF1 Playback”,
.channels_min = 1,
.channels_max = 2,
.rates = WM8994_RATES,
.formats = WM8994_FORMATS,
.sig_bits = 24,
},
.capture = {
.stream_name = “AIF1 Capture”,
.channels_min = 1,
.channels_max = 2,
.rates = WM8994_RATES,
.formats = WM8994_FORMATS,
.sig_bits = 24,
},
.ops = &wm8994_aif1_dai_ops,
},
// …
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
3.2. Codec control IO
移动设备的音频 Codec,其控制接口一般是 I2C 或 SPI,控制接口用于读写 codec 的寄存器。在 snd_soc_codec_driver
结构体中,有如下字段描述 Codec 的控制接口:
/* codec IO */
unsigned int (*read)(struct snd_soc_codec *, unsigned int);
int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
int (*display_register)(struct snd_soc_codec *, char *,
size_t, unsigned int);
int (*volatile_register)(struct snd_soc_codec *, unsigned int);
int (*readable_register)(struct snd_soc_codec *, unsigned int);
int (*writable_register)(struct snd_soc_codec *, unsigned int);
unsigned int reg_cache_size;
short reg_cache_step;
short reg_word_size;
const void *reg_cache_default;
short reg_access_size;
const struct snd_soc_reg_access *reg_access_default;
enum snd_soc_compress_type compress_type;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- read:读寄存器;
- write:写寄存器;
- volatile_register:判断指定的寄存器是否是 volatile 属性;假如是,则读取寄存器时不是读 cache,而直接访问硬件;
- readable_register:判断指定的寄存器是否可读;
- reg_cache_default:寄存器的缺省值;
- reg_cache_size:缺省的寄存器值数组大小;
- reg_word_size:寄存器宽度。
在 Linux-3.4.5 中,很多 codec 的控制接口都改用 regmap 了。soc-core 中判断是否用的是 regmap,如果是,则调用 regmap 接口,见如下函数:
int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg, unsigned int mask, unsigned int value) { bool change; unsigned int old, new; int ret;
<span class="token keyword">if</span> <span class="token punctuation">(</span>codec<span class="token operator">-></span>using_regmap<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 当前使用 regmap,调用 regmap 接口,其中 codec->control_data 是 regmap 私有数据</span> ret <span class="token operator">=</span> <span class="token function">regmap_update_bits_check</span><span class="token punctuation">(</span>codec<span class="token operator">-></span>control_data<span class="token punctuation">,</span> reg<span class="token punctuation">,</span> mask<span class="token punctuation">,</span> value<span class="token punctuation">,</span> <span class="token operator">&</span>change<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token comment">// 非 regmap,调用 snd_soc_codec_driver 实现的 read/write 回调</span> ret <span class="token operator">=</span> <span class="token function">snd_soc_read</span><span class="token punctuation">(</span>codec<span class="token punctuation">,</span> reg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> old <span class="token operator">=</span> ret<span class="token punctuation">;</span> new <span class="token operator">=</span> <span class="token punctuation">(</span>old <span class="token operator">&</span> <span class="token operator">~</span>mask<span class="token punctuation">)</span> <span class="token operator">|</span> <span class="token punctuation">(</span>value <span class="token operator">&</span> mask<span class="token punctuation">)</span><span class="token punctuation">;</span> change <span class="token operator">=</span> old <span class="token operator">!=</span> new<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>change<span class="token punctuation">)</span> ret <span class="token operator">=</span> <span class="token function">snd_soc_write</span><span class="token punctuation">(</span>codec<span class="token punctuation">,</span> reg<span class="token punctuation">,</span> new<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> <span class="token keyword">return</span> change<span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
使用 regmap,使得控制接口抽象化,codec_drv 不用关心当前控制方式是什么;regmap 在线调试目录是 /sys/kernel/debug/regmap
。关于 wm8994 的 regmap 描述,请自行查阅 driver/mfd/wm8994-regmap.c
。
3.3. Mixers and audio controls
音频控件多用于部件开关和音量的设定,音频控件可通过 soc.h
中的宏来定义,例如单一型控件:
#define SOC_SINGLE(xname, reg, shift, max, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
.put = snd_soc_put_volsw, \
.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
- 1
- 2
- 3
- 4
- 5
这种控件只有一个设置量,一般用于部件开关。宏定义的参数说明:
- xname:控件的名称标识;
- reg:控件对应的寄存器地址;
- shift:控件控制位在寄存器中的偏移;
- max:控件设置值范围;
- invert:设定值是否取反。
其他类型控件类似,不一一介绍了。
上述只是宏定义,音频控件真正的结构是 snd_kcontrol_new
:
struct snd_kcontrol_new {
snd_ctl_elem_iface_t iface; /* interface identifier */
unsigned int device; /* device/client number */
unsigned int subdevice; /* subdevice (substream) number */
const unsigned char *name; /* ASCII name of item */
unsigned int index; /* index of item */
unsigned int access; /* access rights */
unsigned int count; /* count of same elements */
snd_kcontrol_info_t *info;
snd_kcontrol_get_t *get;
snd_kcontrol_put_t *put;
union {
snd_kcontrol_tlv_rw_t *c;
const unsigned int *p;
} tlv;
unsigned long private_value;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
Codec 初始化时,通过 snd_soc_add_codec_controls()
把所有定义好的音频控件注册到 alsa-core ,上层可以通过 tinymix、alsa_amixer 等工具查看修改这些控件的设定。
3.4. Codec audio operations
Codec 音频操作接口通过结构体 snd_soc_dai_ops
描述:
struct snd_soc_dai_ops { /* * DAI clocking configuration, all optional. * Called by soc_card drivers, normally in their hw_params. */ int (*set_sysclk)(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir); int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source, unsigned int freq_in, unsigned int freq_out); int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
<span class="token comment">/* * DAI format configuration * Called by soc_card drivers, normally in their hw_params. */</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>set_fmt<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">,</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> fmt<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>set_tdm_slot<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">,</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> tx_mask<span class="token punctuation">,</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> rx_mask<span class="token punctuation">,</span> <span class="token keyword">int</span> slots<span class="token punctuation">,</span> <span class="token keyword">int</span> slot_width<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>set_channel_map<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">,</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> tx_num<span class="token punctuation">,</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> <span class="token operator">*</span>tx_slot<span class="token punctuation">,</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> rx_num<span class="token punctuation">,</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> <span class="token operator">*</span>rx_slot<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>set_tristate<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">,</span> <span class="token keyword">int</span> tristate<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* * DAI digital mute - optional. * Called by soc-core to minimise any pops. */</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>digital_mute<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span>dai<span class="token punctuation">,</span> <span class="token keyword">int</span> mute<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* * ALSA PCM audio operations - all optional. * Called by soc-core during audio PCM operations. */</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>startup<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_pcm_substream <span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token punctuation">(</span><span class="token operator">*</span>shutdown<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_pcm_substream <span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>hw_params<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_pcm_substream <span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">struct</span> snd_pcm_hw_params <span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>hw_free<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_pcm_substream <span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>prepare<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_pcm_substream <span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>trigger<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_pcm_substream <span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">,</span> <span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* * For hardware based FIFO caused delay reporting. * Optional. */</span> snd_pcm_sframes_t <span class="token punctuation">(</span><span class="token operator">*</span>delay<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_pcm_substream <span class="token operator">*</span><span class="token punctuation">,</span> <span class="token keyword">struct</span> snd_soc_dai <span class="token operator">*</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
注释比较详细的了,Codec 音频操作接口分为 5 大部分:时钟配置、格式配置、数字静音、PCM 音频接口、FIFO 延迟。着重说下时钟配置及格式配置接口:
- set_sysclk:codec_dai 系统时钟设置,当上层打开 pcm 设备时,需要回调该接口设置 Codec 的系统时钟,Codec 才能正常工作;
- set_pll:Codec FLL 设置,Codec 一般接了一个 MCLK 输入时钟,回调该接口基于 MCLK 来产生 Codec FLL 时钟,接着 codec_dai 的 sysclk、bclk、lrclk 均可从 FLL 分频出来(假设 Codec 作为 master);
- set_fmt:codec_dai 格式设置,具体见
soc-dai.h
;SND_SOC_DAIFMT_I2S
:音频数据是 I2S 格式,常用于多媒体音频;SND_SOC_DAIFMT_DSP_A
:音频数据是 PCM 格式,常用于通话语音;SND_SOC_DAIFMT_CBM_CFM
:Codec 作为 master,BCLK 和 LRCLK 由 Codec 提供;SND_SOC_DAIFMT_CBS_CFS
:Codec 作为 slave,BCLK 和 LRCLK 由 SoC/CPU 提供;
- hw_params:codec_dai 硬件参数设置,根据上层设定的声道数、采样率、数据格式,来配置 codec_dai 相关寄存器。
以上接口一般在 Machine 驱动中回调,我们看看 Machine 驱动 goni_wm8994.c
的 goni_hifi_hw_params()
函数:
static int goni_hifi_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; unsigned int pll_out = 24000000; // 这是 MCLK 的时钟频率,Codec 的源时钟 int ret = 0;
<span class="token comment">/* set the cpu DAI configuration */</span> ret <span class="token operator">=</span> <span class="token function">snd_soc_dai_set_fmt</span><span class="token punctuation">(</span>cpu_dai<span class="token punctuation">,</span> SND_SOC_DAIFMT_I2S <span class="token operator">|</span> SND_SOC_DAIFMT_NB_NF <span class="token operator">|</span> SND_SOC_DAIFMT_CBM_CFM<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> <span class="token comment">/* set codec DAI configuration */</span> ret <span class="token operator">=</span> <span class="token function">snd_soc_dai_set_fmt</span><span class="token punctuation">(</span>codec_dai<span class="token punctuation">,</span> SND_SOC_DAIFMT_I2S <span class="token operator">|</span> SND_SOC_DAIFMT_NB_NF <span class="token operator">|</span> SND_SOC_DAIFMT_CBM_CFM<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> <span class="token comment">/* set the codec FLL */</span> ret <span class="token operator">=</span> <span class="token function">snd_soc_dai_set_pll</span><span class="token punctuation">(</span>codec_dai<span class="token punctuation">,</span> WM8994_FLL1<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> pll_out<span class="token punctuation">,</span> <span class="token function">params_rate</span><span class="token punctuation">(</span>params<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">256</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> <span class="token comment">/* set the codec system clock */</span> ret <span class="token operator">=</span> <span class="token function">snd_soc_dai_set_sysclk</span><span class="token punctuation">(</span>codec_dai<span class="token punctuation">,</span> WM8994_SYSCLK_FLL1<span class="token punctuation">,</span> <span class="token function">params_rate</span><span class="token punctuation">(</span>params<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">256</span><span class="token punctuation">,</span> SND_SOC_CLOCK_IN<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
其中 snd_soc_dai_set_fmt()
实际上会调用 cpu_dai 或 codec_dai 的 set_fmt()
回调, snd_soc_dai_set_pll()
、snd_soc_dai_set_sysclk()
也类似。
- MCLK 作为 Codec 的源时钟,频率为 24Mhz;
- 设置 cpu_dai 和 codec_dai 格式:数据格式是 I2S;Codec 作为 master,BCLK 和 LRCLK 由 Codec 提供;
- 设置 codec_dai 的 FLL1:时钟源是 MCLK,时钟源频率是 24Mhz,目的时钟频率是 256fs(fs 是采样频率);
- 设置 codec_dai 的系统时钟:时钟源是 FLL1,系统时钟频率是 256fs。
对于 dai(codec_dai 和 cpu_dai),都要非常留意时钟设置,它很关键又复杂,设置错误将会导致很多问题,典型如下:
- 系统无声:检查 Codec 系统时钟、codec_dai 位时钟和帧时钟是否使能;
- 声音失真:检查音频数据的采样率是否和 codec_dai 帧时钟一致;
- 断续破音:检查 Codec 系统时钟和位时钟、帧时钟是否同步,出现这种情况,可能是因为 sysclk 和 BCLK/LRCLK 不是由同一个时钟源分频出来的。
如下是一个典型的音频系统时钟设置(Codec works as master mode):
+---------------------------------------------------------------------- -+
| CODEC | +-----------+
| +---------+ | | |
| SLIMCLK+--> | | | |
| | | | | |
| AIFnBCLK+-> | +---------+ | | |
| | | +-----+ | | | | |
| AIFnLRCLK+> FLL_SRC +---> FLL +---+ | +---->AIFnBCLK+--> |
| | | +-----+ | | | | | |
| MCLK1+---> | | +---------+ | +---->AIFnLRCLK+-> |
| ^ | | +-------> | | | | | Processor |
| | MCLK2+> | | | | AIFn | | | |
| | ^ +---------+ SLIMCLK+--> | | | | | |
| | | | +----> <----+AIFnRX<----+ |
| | | AIFnBCLK+-> SYSCLK | | | | | |
| | | | | | +----+AIFnTX+----> |
| | | MCLK1+----> | | | | | |
| | | | | +---------+ | | |
| | | MCLK2+----> | | | |
| | | +---------+ | | |
+------+-----------------------------------------------------------------+ +-----------+
|
+-+----------+
| Oscillator |
+------------+
AIF Master Mode, Using MCLK and FLL as Reference
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- MCLK1 由外部晶振提供时钟
- Codec FLL 选取 MCLK1 作为时钟源,分出想要的时钟频率
- SYSCLK 选取 FLL 作为时钟源,产生系统时钟
- BCLK、LRCLK 由 Codec 产生提供,即 Codec 作为 Master
3.5. DAPM description
概念:Dynamic Audio Power Management,动态音频电源管理,为移动 Linux 设备设计,使得音频系统任何时候都工作在最低功耗状态。
目的:使能最少的必要的部件,令音频系统正常工作。
原理:当音频路径发生改变(比如上层使用 tinymix 工具设置音频通路)时,或发生数据流事件(比如启动或停止播放)时,都会触发 dapm 去遍历所有邻近的音频部件,检查是否存在完整的音频路径(complete path:满足条件的音频路径,该路径上任意一个部件往前遍历能到达输入端点如 DAC/Mic/Linein,往后遍历能到达输出端点如 ADC/HP/SPK),如果存在完整的音频路径,则该路径上面的所有部件都是需要上电的,其他部件则下电。
部件上下电都是 dapm 根据策略自主控制的,外部无法干预,可以说 dapm 是一个专门为音频系统设计的自成体系的电源管理模块,独立于 Linux 电源管理之外。即使 SoC 休眠了,Codec 仍可以在正常工作,试想下这个情景:语音通话,modem_dai 连接到 codec_dai,语音数据不经过 SoC,因此这种情形下 SoC 可以进入睡眠以降低功耗,只保持 Codec 正常工作就行了。
dapm 原理及实现非常精妙,我认为是 ALSA/ASoC 中最值得钻研的一个点了。
如下是多媒体外放回放通路:
在这个例子中,codec 中的音频通路是:AIF1>DAC1>OUTMIXER>SPKOUT
;AIF1 是输入端点,SPKOUT 是输出端点,因此这条通路是一个 complete path,这通路上的所有部件都是需要上电的,与此同时,其他部件需要下电。
而音频部件由于上下电瞬间的瞬态冲击会产生爆破音,我们称之为 POPs。POPs 是电气特性,我们无法彻底消除,只能硬件软件上优化削弱到人耳辨识不出的程度。DAPM 中,部件的上下电有严格的顺序以抑制爆破音,总的来说:上电次序是从输入端点到输出端点,下电次序是从输出端点到输入端点。
驱动中如何创建 dapm widget 和 dapm route?以 最典型的 mixer widget 为例:Mixes several analog signals into a single analog signal. 它可以把几路模拟信号混合到一路输出,如 WM8994 的 SPKMIXL:
如图,SPKMIXL 有 5 路输入,分别是:MIXINL、IN1LP、DAC1L、DAC2L、MIXEROUTL,因此这里可以构成 5 条通路。
- 如下 5 个控件控制 SPKMIXL 输入:
static const struct snd_kcontrol_new left_speaker_mixer[] = {
SOC_DAPM_SINGLE("DAC2 Switch", WM8994_SPEAKER_MIXER, 9, 1, 0),
SOC_DAPM_SINGLE("Input Switch", WM8994_SPEAKER_MIXER, 7, 1, 0),
SOC_DAPM_SINGLE("IN1LP Switch", WM8994_SPEAKER_MIXER, 5, 1, 0),
SOC_DAPM_SINGLE("Output Switch", WM8994_SPEAKER_MIXER, 3, 1, 0),
SOC_DAPM_SINGLE("DAC1 Switch", WM8994_SPEAKER_MIXER, 1, 1, 0),
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 定义 SPKMIXL 的 dapm widget:
SND_SOC_DAPM_MIXER_E("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0,
left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer),
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
- 1
- 2
- 3
留意 WM8994_POWER_MANAGEMENT_3
寄存器的 bit8 正是控制 SPKMIXL 上下电的。
- 定义 SPKMIXL 相关路由:
static const struct snd_soc_dapm_route intercon[] = {
// ...
{ "SPKL", "DAC1 Switch", "DAC1L" },
{ "SPKL", "DAC2 Switch", "DAC2L" },
- 1
- 2
- 3
- 4
最终上层会看到两个控件:“SPKL DAC1 Switch”,“SPKL DAC2 Switch”;前者用于 “SPKL” 选中 “DAC1L” 作为输入,后者用于 “SPKL” 选中 “DAC2L” 作为输入。
但控件 “SPKLDAC1 Switch” 或 “SPKL DAC2 Switch” 的打开,不代表能使得 “SPKL” 上电。只有当 “SPKL” 位于完整的音频路径中时,“SPKL” 才会上电。
3.6. Codec register
当 platform_driver:
static struct platform_driver wm8994_codec_driver = {
.driver = {
.name = "wm8994-codec",
.owner = THIS_MODULE,
.pm = &wm8994_pm_ops,
},
.probe = wm8994_probe,
.remove = __devexit_p(wm8994_remove),
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
与 .name = "wm8994-codec"
的 platform_device(该 platform_device 在 driver/mfd/wm8994-core.c
中注册)匹配后,立即回调 wm8994_probe()
注册 Codec:
static int __devinit wm8994_probe(struct platform_device *pdev) { struct wm8994_priv *wm8994;
wm8994 <span class="token operator">=</span> <span class="token function">devm_kzalloc</span><span class="token punctuation">(</span><span class="token operator">&</span>pdev<span class="token operator">-></span>dev<span class="token punctuation">,</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span><span class="token keyword">struct</span> wm8994_priv<span class="token punctuation">)</span><span class="token punctuation">,</span> GFP_KERNEL<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>wm8994 <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span> <span class="token function">platform_set_drvdata</span><span class="token punctuation">(</span>pdev<span class="token punctuation">,</span> wm8994<span class="token punctuation">)</span><span class="token punctuation">;</span> wm8994<span class="token operator">-></span>wm8994 <span class="token operator">=</span> <span class="token function">dev_get_drvdata</span><span class="token punctuation">(</span>pdev<span class="token operator">-></span>dev<span class="token punctuation">.</span>parent<span class="token punctuation">)</span><span class="token punctuation">;</span> wm8994<span class="token operator">-></span>pdata <span class="token operator">=</span> <span class="token function">dev_get_platdata</span><span class="token punctuation">(</span>pdev<span class="token operator">-></span>dev<span class="token punctuation">.</span>parent<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">snd_soc_register_codec</span><span class="token punctuation">(</span><span class="token operator">&</span>pdev<span class="token operator">-></span>dev<span class="token punctuation">,</span> <span class="token operator">&</span>soc_codec_dev_wm8994<span class="token punctuation">,</span> wm8994_dai<span class="token punctuation">,</span> <span class="token function">ARRAY_SIZE</span><span class="token punctuation">(</span>wm8994_dai<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
snd_soc_register_codec
:将 codec_driver 和 codec_dai_driver 注册到 soc-core。
/**
* snd_soc_register_codec - Register a codec with the ASoC core
*
* @codec: codec to register
*/
int snd_soc_register_codec(struct device *dev,
const struct snd_soc_codec_driver *codec_drv,
struct snd_soc_dai_driver *dai_drv,
int num_dai)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 创建一个
snd_soc_codec
实例,包含 codec_drv(snd_soc_dai_driver
)相关信息,封装给 soc-core 使用,相关代码段如下:
struct snd_soc_codec *codec;
<span class="token function">dev_dbg</span><span class="token punctuation">(</span>dev<span class="token punctuation">,</span> <span class="token string">"codec register %s\n"</span><span class="token punctuation">,</span> <span class="token function">dev_name</span><span class="token punctuation">(</span>dev<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> codec <span class="token operator">=</span> <span class="token function">kzalloc</span><span class="token punctuation">(</span><span class="token keyword">sizeof</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_codec<span class="token punctuation">)</span><span class="token punctuation">,</span> GFP_KERNEL<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>codec <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span> <span class="token comment">/* create CODEC component name */</span> codec<span class="token operator">-></span>name <span class="token operator">=</span> <span class="token function">fmt_single_name</span><span class="token punctuation">(</span>dev<span class="token punctuation">,</span> <span class="token operator">&</span>codec<span class="token operator">-></span>id<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>codec<span class="token operator">-></span>name <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">kfree</span><span class="token punctuation">(</span>codec<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// 初始化 Codec 的寄存器缓存配置及读写接口</span> codec<span class="token operator">-></span>write <span class="token operator">=</span> codec_drv<span class="token operator">-></span>write<span class="token punctuation">;</span> codec<span class="token operator">-></span>read <span class="token operator">=</span> codec_drv<span class="token operator">-></span>read<span class="token punctuation">;</span> codec<span class="token operator">-></span>volatile_register <span class="token operator">=</span> codec_drv<span class="token operator">-></span>volatile_register<span class="token punctuation">;</span> codec<span class="token operator">-></span>readable_register <span class="token operator">=</span> codec_drv<span class="token operator">-></span>readable_register<span class="token punctuation">;</span> codec<span class="token operator">-></span>writable_register <span class="token operator">=</span> codec_drv<span class="token operator">-></span>writable_register<span class="token punctuation">;</span> codec<span class="token operator">-></span>ignore_pmdown_time <span class="token operator">=</span> codec_drv<span class="token operator">-></span>ignore_pmdown_time<span class="token punctuation">;</span> codec<span class="token operator">-></span>dapm<span class="token punctuation">.</span>bias_level <span class="token operator">=</span> SND_SOC_BIAS_OFF<span class="token punctuation">;</span> codec<span class="token operator">-></span>dapm<span class="token punctuation">.</span>dev <span class="token operator">=</span> dev<span class="token punctuation">;</span> codec<span class="token operator">-></span>dapm<span class="token punctuation">.</span>codec <span class="token operator">=</span> codec<span class="token punctuation">;</span> codec<span class="token operator">-></span>dapm<span class="token punctuation">.</span>seq_notifier <span class="token operator">=</span> codec_drv<span class="token operator">-></span>seq_notifier<span class="token punctuation">;</span> codec<span class="token operator">-></span>dapm<span class="token punctuation">.</span>stream_event <span class="token operator">=</span> codec_drv<span class="token operator">-></span>stream_event<span class="token punctuation">;</span> codec<span class="token operator">-></span>dev <span class="token operator">=</span> dev<span class="token punctuation">;</span> codec<span class="token operator">-></span>driver <span class="token operator">=</span> codec_drv<span class="token punctuation">;</span> codec<span class="token operator">-></span>num_dai <span class="token operator">=</span> num_dai<span class="token punctuation">;</span> <span class="token function">mutex_init</span><span class="token punctuation">(</span><span class="token operator">&</span>codec<span class="token operator">-></span>mutex<span class="token punctuation">)</span><span class="token punctuation">;</span>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 把以上 codec 实例插入到
codec_list
链表中(声卡注册时会遍历该链表,找到 dai_link 声明的 codec 并绑定):
list_add(&codec->list, &codec_list);
- 1
- 把 codec_drv 中的
snd_soc_dai_driver
(wm8994 有 3 个 dai,分别是 aif1、aif2、aif3)注册到 soc-core:
/* register any DAIs */
if (num_dai) {
ret = snd_soc_register_dais(dev, dai_drv, num_dai);
if (ret < 0)
goto fail;
}
- 1
- 2
- 3
- 4
- 5
- 6
snd_soc_register_dais()
会把 dai 插入到 dai_list
链表中(声卡注册时会遍历该链表,找到 dai_link 声明的 codec_dai 并绑定):
list_add(&dai->list, &dai_list);
- 1
最后顺便提下 codec 和 codec_dai 的区别:codec 指音频芯片共有的部分,包括 codec 初始化函数、控制接口、寄存器缓存、控件、dapm 部件、音频路由、偏置电压设置函数等描述信息;而 codec_dai 指 codec 上的音频接口驱动描述,包括时钟配置、格式配置、能力描述等等,各个接口的描述信息不一定都是一致的,所以每个音频接口都有着各自的驱动描述。
我开始时认为:codec_dai 从属于 codec,dai_link 没有必要同时声明 codec 和 codec_dai,应该可以实现codec_dai 就能找到它对应的父设备 codec 的方法。后来想到系统上如果有两个以上的 codec,而恰好不同 codec 上的 codec_dai 有重名的话,此时就必须同时声明 codec 和 codec_dai 才能找到正确的音频接口了。
4. Platform
概述中提到音频 Platform 驱动主要用于音频数据传输,这里又细分为两步:
- 启动 dma 设备,把音频数据从 dma buffer 搬运到 cpu_dai FIFO,这部分驱动用
snd_soc_platform_driver
描述,后面分析用 pcm_dma 指代它。 - 启动数字音频接口控制器(I2S/PCM/AC97),把音频数据从 cpu_dai FIFO 传送到 codec_dai,这部分驱动用
snd_soc_dai_driver
描述,后面分析用 cpu_dai 指代它。
那么 dma buffer 中的音频数据从何而来?保留这个问题,在后面章节 pcm native 分析。
我们浏览下 platform_drv 中的几个重要结构体,其中浅蓝色部分是 cpu_dai 相关的,浅绿色部分是 pcm_dma 相关的。snd_soc_dai
是 cpu_dai 注册时所创建的 dai 实例,snd_soc_platform
是 pcm_dma 注册时所创建的 platform 实例,这些实例方便 soc-core 管理。
4.1. cpu dai
一个典型的 I2S 总线控制器框图:
各模块描述如下,摘自 S3C44B0 的数据手册:
Bus interface, register bank, and state machine(BRFC) - Bus interface logic and FIFO access are controlled by the state machine.
3-bit dual prescaler(IPSR) - One prescaler is used as the master clock generator of the IIS bus interface and the other is used as the external CODEC clock generator.
16-byte FIFOs(TXFIFO, RXFIFO) - In transmit data transfer, data are written to TXFIFO, and, in the receive data transfer, data are read from RXFIFO.
Master IISCLK generaor(SCLKG) - In master mode, serial bit clock is generated from the master clock.
Channel generator and state machine(CHNC) - IISCLK and IISLRCK are generated and controlled by the channel state machine.
16-bit shift register(SFTR) - Parallel data is shifted to serial data output in the transmit mode, and serial data input is shifted to parallel data in the receive mode.
再回顾下 I2S 总线协议,这是音频驱动开发最基本的内容了:
- BCLK:位时钟,对应数字音频的每一位数据;BCLK = 声道数 * 采样频率 * 采样位数;
- LRCLK:帧时钟,构成一个完整的声音单元;双声道的情况下,LRCLK = 0 时表示是左声道的数据,= 1 时表示是右声道的数据;LRCLK = 采样频率;
- DACDAT:下行数据;
- ADCDAT:上行数据;
- 数据的最高位总是出现在 LRCLK 跳变后的第 2 个 BCLK 脉冲处。
对于 cpu_dai 驱动,从上面的类图我们可知,主要工作有:
- 实现 dai 操作函数,见
snd_soc_dai_ops
定义,用于配置和操作音频数字接口控制器,如时钟配置set_sysclk()
、格式配置set_fmt()
、硬件参数配置hw_params()
、启动/停止数据传输trigger()
等; - 实现
probe
函数(初始化)、remove
函数(卸载)、suspend/resume
函数(电源管理); - 初始化
snd_soc_dai_driver
实例,包括回放和录制的能力描述、dai 操作函数集、probe/remove
回调、电源管理相关的suspend/resume
回调; - 通过
snd_soc_register_dai()
把初始化完成的snd_soc_dai_driver
注册到 soc-core:首先创建一个snd_soc_dai
实例,然后把该snd_soc_dai
实例插入到dai_list
链表(声卡注册时会遍历该链表,找到 dai_link 声明的 cpu_dai 并绑定)。
/** * snd_soc_register_dai - Register a DAI with the ASoC core * * @dai: DAI to register */ int snd_soc_register_dai(struct device *dev, struct snd_soc_dai_driver *dai_drv) { struct snd_soc_dai *dai;
dai <span class="token operator">=</span> <span class="token function">kzalloc</span><span class="token punctuation">(</span><span class="token keyword">sizeof</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_dai<span class="token punctuation">)</span><span class="token punctuation">,</span> GFP_KERNEL<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>dai <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span> <span class="token comment">/* create DAI component name */</span> dai<span class="token operator">-></span>name <span class="token operator">=</span> <span class="token function">fmt_single_name</span><span class="token punctuation">(</span>dev<span class="token punctuation">,</span> <span class="token operator">&</span>dai<span class="token operator">-></span>id<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>dai<span class="token operator">-></span>name <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">kfree</span><span class="token punctuation">(</span>dai<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span> <span class="token punctuation">}</span> dai<span class="token operator">-></span>dev <span class="token operator">=</span> dev<span class="token punctuation">;</span> dai<span class="token operator">-></span>driver <span class="token operator">=</span> dai_drv<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>dai<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token punctuation">)</span> dai<span class="token operator">-></span>driver<span class="token operator">-></span>ops <span class="token operator">=</span> <span class="token operator">&</span>null_dai_ops<span class="token punctuation">;</span> <span class="token function">mutex_lock</span><span class="token punctuation">(</span><span class="token operator">&</span>client_mutex<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">list_add</span><span class="token punctuation">(</span><span class="token operator">&</span>dai<span class="token operator">-></span>list<span class="token punctuation">,</span> <span class="token operator">&</span>dai_list<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">mutex_unlock</span><span class="token punctuation">(</span><span class="token operator">&</span>client_mutex<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
dai 操作函数的实现是 cpu_dai 驱动的主体,需要配置好相关寄存器让 I2S/PCM 总线控制器正常运转,snd_soc_dai_ops
字段的详细说明见 3.4. Codec audio operations
章节。
cpu_dai 驱动应该算是这个系列中最简单的一环,因此不多花费笔墨在这里了。倒是某些平台上,dma 设备信息(总线地址、通道号、传输单元大小)是在这里初始化的,这点要留意,这些 dma 设备信息在 pcm_dma 驱动中用到。以 Exynos 平台为例,代码位置 sound/soc/samsung/i2s.c
。
Samsung Exynos 平台的音频 dma 设备信息用 s3c_dma_params
结构体描述:
struct s3c_dma_params {
struct s3c2410_dma_client *client; /* stream identifier */
int channel; /* Channel ID */
dma_addr_t dma_addr;
int dma_size; /* Size of the DMA transfer */
unsigned ch;
struct samsung_dma_ops *ops;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- client:流标识符
- channel:通道号
- dma_addr:设备的总线地址,这里通常指向 I2S tx FIFO 或 I2S rx FIFO 首地址
- dma_size:dma 传输单元大小
- ops:平台 dma 操作函数
sound/soc/samsung/i2s.c
中设置 dma 设备信息的相关代码片段:
struct i2s_dai {
// ...
/* Driver for this DAI */
struct snd_soc_dai_driver i2s_dai_drv;
/* DMA parameters */
struct s3c_dma_params dma_playback; // playback dma 描述信息
struct s3c_dma_params dma_capture; // capture dma 描述信息
struct s3c_dma_params idma_playback;// playback idma 描述信息,idma 仅用于回放,用于三星平台的 LPA(低功耗音频)模式
// ...
};
static __devinit int samsung_i2s_probe(struct platform_device *pdev)
{
// …
// 从 platform_device 中取得 resource,得到 playback dma 通道号
res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!res) {
dev_err(&pdev->dev, “Unable to get I2S-TX dma resource\n”);
return -ENXIO;
}
dma_pl_chan = res->start; // dma_pl_chan 中的 pl 是 playback 简写
<span class="token comment">// 从 platform_device 中取得 resource,得到 capture dma 通道号</span>
res <span class="token operator">=</span> <span class="token function">platform_get_resource</span><span class="token punctuation">(</span>pdev<span class="token punctuation">,</span> IORESOURCE_DMA<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>res<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">dev_err</span><span class="token punctuation">(</span><span class="token operator">&</span>pdev<span class="token operator">-></span>dev<span class="token punctuation">,</span> <span class="token string">"Unable to get I2S-RX dma resource\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token operator">-</span>ENXIO<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
dma_cp_chan <span class="token operator">=</span> res<span class="token operator">-></span>start<span class="token punctuation">;</span> <span class="token comment">// dma_cp_chan 中的 cp 是 capture 的简写</span>
<span class="token comment">// 从 platform_device 中取得 resource,得到 playback idma 通道号</span>
res <span class="token operator">=</span> <span class="token function">platform_get_resource</span><span class="token punctuation">(</span>pdev<span class="token punctuation">,</span> IORESOURCE_DMA<span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>res<span class="token punctuation">)</span>
dma_pl_sec_chan <span class="token operator">=</span> res<span class="token operator">-></span>start<span class="token punctuation">;</span>
<span class="token keyword">else</span>
dma_pl_sec_chan <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token comment">// 从 platform_device 中取得 resource,得到 I2S 的基地址</span>
res <span class="token operator">=</span> <span class="token function">platform_get_resource</span><span class="token punctuation">(</span>pdev<span class="token punctuation">,</span> IORESOURCE_MEM<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>res<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">dev_err</span><span class="token punctuation">(</span><span class="token operator">&</span>pdev<span class="token operator">-></span>dev<span class="token punctuation">,</span> <span class="token string">"Unable to get I2S SFR address\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token operator">-</span>ENXIO<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">request_mem_region</span><span class="token punctuation">(</span>res<span class="token operator">-></span>start<span class="token punctuation">,</span> <span class="token function">resource_size</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token string">"samsung-i2s"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">dev_err</span><span class="token punctuation">(</span><span class="token operator">&</span>pdev<span class="token operator">-></span>dev<span class="token punctuation">,</span> <span class="token string">"Unable to request SFR region\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token operator">-</span>EBUSY<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
regs_base <span class="token operator">=</span> res<span class="token operator">-></span>start<span class="token punctuation">;</span>
<span class="token comment">// ...</span>
pri_dai<span class="token operator">-></span>dma_playback<span class="token punctuation">.</span>dma_addr <span class="token operator">=</span> regs_base <span class="token operator">+</span> I2STXD<span class="token punctuation">;</span> <span class="token comment">// 设置 playback dma 设备地址为 I2S tx FIFO 地址</span>
pri_dai<span class="token operator">-></span>dma_capture<span class="token punctuation">.</span>dma_addr <span class="token operator">=</span> regs_base <span class="token operator">+</span> I2SRXD<span class="token punctuation">;</span> <span class="token comment">// 设置 capture dma 设备地址为 I2S rx FIFO 地址</span>
pri_dai<span class="token operator">-></span>dma_playback<span class="token punctuation">.</span>client <span class="token operator">=</span>
<span class="token punctuation">(</span><span class="token keyword">struct</span> s3c2410_dma_client <span class="token operator">*</span><span class="token punctuation">)</span><span class="token operator">&</span>pri_dai<span class="token operator">-></span>dma_playback<span class="token punctuation">;</span>
pri_dai<span class="token operator">-></span>dma_capture<span class="token punctuation">.</span>client <span class="token operator">=</span>
<span class="token punctuation">(</span><span class="token keyword">struct</span> s3c2410_dma_client <span class="token operator">*</span><span class="token punctuation">)</span><span class="token operator">&</span>pri_dai<span class="token operator">-></span>dma_capture<span class="token punctuation">;</span>
pri_dai<span class="token operator">-></span>dma_playback<span class="token punctuation">.</span>channel <span class="token operator">=</span> dma_pl_chan<span class="token punctuation">;</span> <span class="token comment">// 设置 playback dma 通道号</span>
pri_dai<span class="token operator">-></span>dma_capture<span class="token punctuation">.</span>channel <span class="token operator">=</span> dma_cp_chan<span class="token punctuation">;</span> <span class="token comment">// 设置 capture dma 通道号</span>
pri_dai<span class="token operator">-></span>src_clk <span class="token operator">=</span> i2s_cfg<span class="token operator">-></span>src_clk<span class="token punctuation">;</span>
pri_dai<span class="token operator">-></span>dma_playback<span class="token punctuation">.</span>dma_size <span class="token operator">=</span> <span class="token number">4</span><span class="token punctuation">;</span> <span class="token comment">// 设置 playback dma 传输单元大小为 4 个字节</span>
pri_dai<span class="token operator">-></span>dma_capture<span class="token punctuation">.</span>dma_size <span class="token operator">=</span> <span class="token number">4</span><span class="token punctuation">;</span> <span class="token comment">// 设置 capture dma 传输单元大小为 4 个字节</span>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
我们再看看 Board 初始化时,如何设定这些 resource,文件 arch/arm/mach-exynos/dev-audio.c
:
static struct resource exynos4_i2s0_resource[] = {
[0] = {
.start = EXYNOS4_PA_I2S0, // start 字段保存的是 I2S 基地址
.end = EXYNOS4_PA_I2S0 + 0x100 - 1,
.flags = IORESOURCE_MEM, // 标识为 MEM 资源
},
[1] = {
.start = DMACH_I2S0_TX, // start 字段保存的是用于回放的 dma 通道号
.end = DMACH_I2S0_TX,
.flags = IORESOURCE_DMA, // 标识为 DMA 资源
},
[2] = {
.start = DMACH_I2S0_RX, // start 字段保存的是用于录制的 dma 通道号
.end = DMACH_I2S0_RX,
.flags = IORESOURCE_DMA, // 标识为 DMA 资源
},
[3] = {
.start = DMACH_I2S0S_TX, // start 字段保存的是用于回放的 idma 通道号
.end = DMACH_I2S0S_TX,
.flags = IORESOURCE_DMA, // 标识为 DMA 资源
},
};
struct platform_device exynos4_device_i2s0 = {
.name = “samsung-i2s”, // platform_device 名称标识为 “samsung-i2s”,与 i2s.c 中的samsung_i2s_driver 匹配
.id = 0,
.num_resources = ARRAY_SIZE(exynos4_i2s0_resource),
.resource = exynos4_i2s0_resource,
.dev = {
.platform_data = &i2sv5_pdata,
},
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
当 samsung_i2s_driver 初始化时,通过 platform_get_resource()
函数来获取 platform_device 声明的 resource。
struct resource 结构中我们通常关心 start、end 和 flags 这 3 个字段,分别标明资源的开始值、结束值和类型。flags 可以为 IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA 等。start、end 的含义会随着 flags 而变更,如当 flags 为 IORESOURCE_MEM 时,start、end 分别表示该 platform_device 占据的内存的开始地址和结束地址;当 flags 为 IORESOURCE_IRQ 时,start、end 分别表示该 platform_device 使用的中断号的开始值和结束值,如果只使用了 1 个中断号,开始和结束值相同。对于同种类型的资源而言,可以有多份,譬如说某设备占据了 2 个内存区域,则可以定义 2 个 IORESOURCE_MEM 资源。
摘自:http://21cnbao.blog.51cto.com/109393/337609
4.2. pcm dma
PCM 数据管理可以说是 ALSA 系统中最核心的部分,这部分的工作有两个(回放情形):
copy_from_user
把用户态的音频数据拷贝到 dma buffer 中;- 启动 dma 设备把音频数据从 dma buffer 传送到 I2S tx FIFO。
当数据送到 I2S tx FIFO 后,剩下的是启动 I2S 控制器把数据传送到 Codec,然后 DAC 把音频数字信号转换成模拟信号,再输出到 SPK/HP。关于 I2S (cpu_dai) 和 Codec,在以上两章已经描述清楚了。
为什么要使用 dma 传输?两个原因:首先在数据传输过程中,不需要 cpu 的参与,节省 cpu 的开销;其次传输速度快,提高硬件设备的吞吐量。对于 ARM,它不能直接把数据从 A 地址搬运到 B 地址,只能把数据从 A 地址搬运到一个寄存器,然后再从这个寄存器搬运到 B 地址;而 dma 有突发(Burst)传输能力,这种模式下一次能传输几个甚至十几个字节的数据,尤其适合大数据的高速传输。一个 dma 传输块里面,可以划分为若干个周期,每传输完一个周期产生一个中断。
写这个文档的初衷是为了描述清楚 pcm 数据流向,这里先剖析 pcm_dma 驱动,以便后面 pcm native 的分析。以 Exynos 平台为例,代码位置 sound/soc/samsung/dma.c
。
- 浅绿色:pcm_dma 驱动共有的结构及接口定义
- 浅灰色:samsung exynos 平台特有的实现
- 浅紫色:pcm native 关键结构
- 浅橙色:
snd_soc_platform
是pcm_dma 注册时所创建的 platform 实例
snd_pcm_substream
是 pcm native 关键结构体,上图可以看出这个结构体包含了音频数据传输所需的重要信息:pcm ops 操作函数集和 dma buffer。
我们先看看 dma 设备相关的结构,对于回放来说,dma 设备把内存缓冲区的音频数据传送到 I2S tx FIFO;对于录制来说,dma 设备把 I2S rx FIFO 的音频数据传送到内存缓存区。因此在 dma 设备传输之前,必须确定 data buffer 和 I2S FIFO 的信息。
snd_dma_buffer:数据缓存区,用于保存从用户态拷贝过来的音频数据;包含 dma buffer 的物理首地址,虚拟首地址、大小等信息;其中物理地址用于设定 dma 传输的源地址(回放情形)或目的地址(录制情形),虚拟地址用于与用户态之间的音频数据拷贝。
s3c_dma_params:dma 设备描述,包括设备总线地址(回放情形下为 I2S tx FIFO 首地址,设置为 dma 传输的目的地址)、dma 通道号、dma 传输单元大小,这些信息在 i2s.c
中初始化(具体见上一小节)。
runtime_data:dma 运行期信息
- state:记录 dma 设备状态,启动或停止;
- dma_loaded:dma 装载计数,每当启动一次 dma 传输,该计数加一;每当完成一次 dma 传输,该计数减一;
- dma_period:dma 周期数据大小;
- dma_start:指向 dma buffer 物理首地址;
- dma_pos:记录 dma buffer 当前指针位置,当 dma 每传输一次,都会更新该指针;
- dma_end:dma buffer 结束位置;
- params:dma 设备描述信息,包括设备总线地址、dma 通道号、传输单元大小。
4.2.1. pcm operations
操作函数的实现是本模块的主体,见 snd_pcm_ops
结构体描述:
struct snd_pcm_ops {
int (*open)(struct snd_pcm_substream *substream);
int (*close)(struct snd_pcm_substream *substream);
int (*ioctl)(struct snd_pcm_substream * substream,
unsigned int cmd, void *arg);
int (*hw_params)(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params);
int (*hw_free)(struct snd_pcm_substream *substream);
int (*prepare)(struct snd_pcm_substream *substream);
int (*trigger)(struct snd_pcm_substream *substream, int cmd);
snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
int (*copy)(struct snd_pcm_substream *substream, int channel,
snd_pcm_uframes_t pos,
void __user *buf, snd_pcm_uframes_t count);
int (*silence)(struct snd_pcm_substream *substream, int channel,
snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
struct page *(*page)(struct snd_pcm_substream *substream,
unsigned long offset);
int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
int (*ack)(struct snd_pcm_substream *substream);
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
下面介绍几个重要的接口:
- open:打开 pcm 逻辑设备时,回调该函数设定 dma 设备的硬件约束;并申请一个私有结构,保存 dma 设备资源如通道号、传输单元、缓冲区信息、IO 信息等,保存在
runtime->private_data
。代码如下:
static const struct snd_pcm_hardware dma_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_U16_LE |
SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_S8,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128*1024,
.period_bytes_min = PAGE_SIZE,
.period_bytes_max = PAGE_SIZE*2,
.periods_min = 2,
.periods_max = 128,
.fifo_size = 32,
};
static int dma_open(struct snd_pcm_substream substream)
{
struct snd_pcm_runtime runtime = substream->runtime;
struct runtime_data *prtd;
<span class="token function">pr_debug</span><span class="token punctuation">(</span><span class="token string">"Entered %s\n"</span><span class="token punctuation">,</span> <span class="token constant">__func__</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 设置 dma 设备的硬件约束</span>
<span class="token function">snd_pcm_hw_constraint_integer</span><span class="token punctuation">(</span>runtime<span class="token punctuation">,</span> SNDRV_PCM_HW_PARAM_PERIODS<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">snd_soc_set_runtime_hwparams</span><span class="token punctuation">(</span>substream<span class="token punctuation">,</span> <span class="token operator">&</span>dma_hardware<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 为 runtime_data 分配内存,用于保存 dma 资源,包括缓冲区信息、IO 设备信息、通道号、传输单元大小 </span>
prtd <span class="token operator">=</span> <span class="token function">kzalloc</span><span class="token punctuation">(</span><span class="token keyword">sizeof</span><span class="token punctuation">(</span><span class="token keyword">struct</span> runtime_data<span class="token punctuation">)</span><span class="token punctuation">,</span> GFP_KERNEL<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>prtd <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span>
<span class="token function">spin_lock_init</span><span class="token punctuation">(</span><span class="token operator">&</span>prtd<span class="token operator">-></span>lock<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// runtime 的私有数据指向 runtime_data </span>
runtime<span class="token operator">-></span>private_data <span class="token operator">=</span> prtd<span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- hw_params:设置硬件参数时(
cmd=SNDRV_PCM_IOCTL_HW_PARAMS
),回调该函数初始化 dma 资源,包括通道号、传输单元、缓冲区信息、IO 设备信息等。代码如下:
static int dma_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_pcm_runtime *runtime = substream->runtime; struct runtime_data *prtd = runtime->private_data; struct snd_soc_pcm_runtime *rtd = substream->private_data; unsigned long totbytes = params_buffer_bytes(params); struct s3c_dma_params *dma = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); // 从 cpu_dai 驱动 i2s.c 取得 dma 设备资源 struct samsung_dma_info dma_info;
<span class="token comment">/* return if this is a bufferless transfer e.g. * codec <--> BT codec or GSM modem -- lg FIXME */</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>dma<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">/* this may get called several times by oss emulation * with different params -HW */</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>prtd<span class="token operator">-></span>params <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* prepare DMA */</span> prtd<span class="token operator">-></span>params <span class="token operator">=</span> dma<span class="token punctuation">;</span> <span class="token comment">// 该字段保存的是 dma 设备资源,如 I2S tx FIFO 地址、dma 通道号、dma 传输单元等</span> prtd<span class="token operator">-></span>params<span class="token operator">-></span>ops <span class="token operator">=</span> <span class="token function">samsung_dma_get_ops</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 平台的 dma 操作函数,这些操作函数实现见:arch/arm/plat-samsung/dma-ops.c</span> <span class="token comment">//...</span> prtd<span class="token operator">-></span>params<span class="token operator">-></span>ch <span class="token operator">=</span> prtd<span class="token operator">-></span>params<span class="token operator">-></span>ops<span class="token operator">-></span><span class="token function">request</span><span class="token punctuation">(</span> prtd<span class="token operator">-></span>params<span class="token operator">-></span>channel<span class="token punctuation">,</span> <span class="token operator">&</span>dma_info<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">snd_pcm_set_runtime_buffer</span><span class="token punctuation">(</span>substream<span class="token punctuation">,</span> <span class="token operator">&</span>substream<span class="token operator">-></span>dma_buffer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 这里把 dma buffer 相关信息赋给 substream runtime,注意 dma_buffer 在创建 pcm 逻辑设备时分配</span> runtime<span class="token operator">-></span>dma_bytes <span class="token operator">=</span> totbytes<span class="token punctuation">;</span> <span class="token function">spin_lock_irq</span><span class="token punctuation">(</span><span class="token operator">&</span>prtd<span class="token operator">-></span>lock<span class="token punctuation">)</span><span class="token punctuation">;</span> prtd<span class="token operator">-></span>dma_loaded <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> prtd<span class="token operator">-></span>dma_period <span class="token operator">=</span> <span class="token function">params_period_bytes</span><span class="token punctuation">(</span>params<span class="token punctuation">)</span><span class="token punctuation">;</span> prtd<span class="token operator">-></span>dma_start <span class="token operator">=</span> runtime<span class="token operator">-></span>dma_addr<span class="token punctuation">;</span> <span class="token comment">// dma buffer 物理首地址</span> prtd<span class="token operator">-></span>dma_pos <span class="token operator">=</span> prtd<span class="token operator">-></span>dma_start<span class="token punctuation">;</span> prtd<span class="token operator">-></span>dma_end <span class="token operator">=</span> prtd<span class="token operator">-></span>dma_start <span class="token operator">+</span> totbytes<span class="token punctuation">;</span> <span class="token function">spin_unlock_irq</span><span class="token punctuation">(</span><span class="token operator">&</span>prtd<span class="token operator">-></span>lock<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- prepare:当数据已准备好(
cmd=SNDRV_PCM_IOCTL_PREPARE
),回调该函数告知 dma 设备数据已就绪。代码如下:
static int dma_prepare(struct snd_pcm_substream *substream) { struct runtime_data *prtd = substream->runtime->private_data; int ret = 0;
<span class="token function">pr_debug</span><span class="token punctuation">(</span><span class="token string">"Entered %s\n"</span><span class="token punctuation">,</span> <span class="token constant">__func__</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* return if this is a bufferless transfer e.g. * codec <--> BT codec or GSM modem -- lg FIXME */</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>prtd<span class="token operator">-></span>params<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">/* flush the DMA channel */</span> prtd<span class="token operator">-></span>params<span class="token operator">-></span>ops<span class="token operator">-></span><span class="token function">flush</span><span class="token punctuation">(</span>prtd<span class="token operator">-></span>params<span class="token operator">-></span>ch<span class="token punctuation">)</span><span class="token punctuation">;</span> prtd<span class="token operator">-></span>dma_loaded <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">// 初始化 dma 装载计数</span> prtd<span class="token operator">-></span>dma_pos <span class="token operator">=</span> prtd<span class="token operator">-></span>dma_start<span class="token punctuation">;</span> <span class="token comment">// 设置 dma buffer 当前指针为 dma buffer 首地址</span> <span class="token comment">/* enqueue dma buffers */</span> <span class="token function">dma_enqueue</span><span class="token punctuation">(</span>substream<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 插入到 dma 传输队列中</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
dma_enqueue()
函数,把当前 dma buffer 插入到 dma 传输队列中。当触发 trigger()
启动 dma 设备传输后,将会把 dma buffer 数据传送到 FIFO(回放情形)。
注意:每次 dma 传输完一个周期的数据后,都要调用 snd_pcm_period_elapsed()
告知 pcm native 一个周期的数据已经传送到 FIFO 上了,然后再次调用 dma_enqueue()
,dma 传输…如此循环,直到触发 trigger()
停止 dma 传输。
- trigger:数据传送 开始/停止/暂停/恢复 时,回调该函数启动或停止 dma 传输(当上层第一次调用
pcm_write()
时,触发trigger()
启动 dma 传输;当上层调用pcm_stop()
或pcm_drop()
时,触发trigger()
停止 dma 传输)。trigger()
函数里面的操作必须是原子的,不能调用可能睡眠的操作,并且应尽量简单。代码如下:
static int dma_trigger(struct snd_pcm_substream *substream, int cmd) { struct runtime_data *prtd = substream->runtime->private_data; int ret = 0;
<span class="token function">pr_debug</span><span class="token punctuation">(</span><span class="token string">"Entered %s\n"</span><span class="token punctuation">,</span> <span class="token constant">__func__</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">spin_lock</span><span class="token punctuation">(</span><span class="token operator">&</span>prtd<span class="token operator">-></span>lock<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>cmd<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> SNDRV_PCM_TRIGGER_START<span class="token punctuation">:</span> <span class="token keyword">case</span> SNDRV_PCM_TRIGGER_RESUME<span class="token punctuation">:</span> <span class="token keyword">case</span> SNDRV_PCM_TRIGGER_PAUSE_RELEASE<span class="token punctuation">:</span> prtd<span class="token operator">-></span>state <span class="token operator">|</span><span class="token operator">=</span> ST_RUNNING<span class="token punctuation">;</span> prtd<span class="token operator">-></span>params<span class="token operator">-></span>ops<span class="token operator">-></span><span class="token function">trigger</span><span class="token punctuation">(</span>prtd<span class="token operator">-></span>params<span class="token operator">-></span>ch<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 启动 dma 传输</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> SNDRV_PCM_TRIGGER_STOP<span class="token punctuation">:</span> <span class="token keyword">case</span> SNDRV_PCM_TRIGGER_SUSPEND<span class="token punctuation">:</span> <span class="token keyword">case</span> SNDRV_PCM_TRIGGER_PAUSE_PUSH<span class="token punctuation">:</span> prtd<span class="token operator">-></span>state <span class="token operator">&</span><span class="token operator">=</span> <span class="token operator">~</span>ST_RUNNING<span class="token punctuation">;</span> prtd<span class="token operator">-></span>params<span class="token operator">-></span>ops<span class="token operator">-></span><span class="token function">stop</span><span class="token punctuation">(</span>prtd<span class="token operator">-></span>params<span class="token operator">-></span>ch<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 停止 dma 传输</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">default</span><span class="token punctuation">:</span> ret <span class="token operator">=</span> <span class="token operator">-</span>EINVAL<span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">spin_unlock</span><span class="token punctuation">(</span><span class="token operator">&</span>prtd<span class="token operator">-></span>lock<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- pointer:dma 每完成一次传输,都会调用该函数获得传输数据的当前位置,这样 pcm native 可计算 dma buffer 指针位置及可用空间。该函数也是原子的。代码如下:
static snd_pcm_uframes_t dma_pointer(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct runtime_data *prtd = runtime->private_data; unsigned long res;
res <span class="token operator">=</span> prtd<span class="token operator">-></span>dma_pos <span class="token operator">-</span> prtd<span class="token operator">-></span>dma_start<span class="token punctuation">;</span> <span class="token comment">// 当前位置减去首地址,其实就是已传输数据的大小</span> <span class="token comment">/* we seem to be getting the odd error from the pcm library due * to out-of-bounds pointers. this is maybe due to the dma engine * not having loaded the new values for the channel before being * called... (todo - fix ) */</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>res <span class="token operator">>=</span> <span class="token function">snd_pcm_lib_buffer_bytes</span><span class="token punctuation">(</span>substream<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>res <span class="token operator">==</span> <span class="token function">snd_pcm_lib_buffer_bytes</span><span class="token punctuation">(</span>substream<span class="token punctuation">)</span><span class="token punctuation">)</span> res <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token function">bytes_to_frames</span><span class="token punctuation">(</span>substream<span class="token operator">-></span>runtime<span class="token punctuation">,</span> res<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 单位转化为 frames</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
4.2.2. dma buffer allocation
在 4.2.1. pcm operations
小节,数次提及 dma buffer,即 dma 数据缓冲区。dma buffer 的分配,一般发生在 pcm_dma 驱动初始化阶段 probe()
或 pcm 逻辑设备创建阶段 pcm_new()
。代码如下:
static int preallocate_dma_buffer(struct snd_pcm *pcm, int stream) { struct snd_pcm_substream *substream = pcm->streams[stream].substream; struct snd_dma_buffer *buf = &substream->dma_buffer; size_t size = dma_hardware.buffer_bytes_max; // 缓冲区大小不得超过 hardware 中的buffer_bytes_max
buf<span class="token operator">-></span>dev<span class="token punctuation">.</span>type <span class="token operator">=</span> SNDRV_DMA_TYPE_DEV<span class="token punctuation">;</span> buf<span class="token operator">-></span>dev<span class="token punctuation">.</span>dev <span class="token operator">=</span> pcm<span class="token operator">-></span>card<span class="token operator">-></span>dev<span class="token punctuation">;</span> buf<span class="token operator">-></span>private_data <span class="token operator">=</span> <span class="token constant">NULL</span><span class="token punctuation">;</span> buf<span class="token operator">-></span>area <span class="token operator">=</span> <span class="token function">dma_alloc_writecombine</span><span class="token punctuation">(</span>pcm<span class="token operator">-></span>card<span class="token operator">-></span>dev<span class="token punctuation">,</span> size<span class="token punctuation">,</span> <span class="token operator">&</span>buf<span class="token operator">-></span>addr<span class="token punctuation">,</span> GFP_KERNEL<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// area 字段是 dma buffer 虚拟首地址,addr 字段是 dma buffer 物理首地址</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>buf<span class="token operator">-></span>area<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span> buf<span class="token operator">-></span>bytes <span class="token operator">=</span> size<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
static int dma_new(struct snd_soc_pcm_runtime rtd)
{
struct snd_card card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm;
int ret = 0;
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>card<span class="token operator">-></span>dev<span class="token operator">-></span>dma_mask<span class="token punctuation">)</span>
card<span class="token operator">-></span>dev<span class="token operator">-></span>dma_mask <span class="token operator">=</span> <span class="token operator">&</span>dma_mask<span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>card<span class="token operator">-></span>dev<span class="token operator">-></span>coherent_dma_mask<span class="token punctuation">)</span>
card<span class="token operator">-></span>dev<span class="token operator">-></span>coherent_dma_mask <span class="token operator">=</span> <span class="token function">DMA_BIT_MASK</span><span class="token punctuation">(</span><span class="token number">32</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>pcm<span class="token operator">-></span>streams<span class="token punctuation">[</span>SNDRV_PCM_STREAM_PLAYBACK<span class="token punctuation">]</span><span class="token punctuation">.</span>substream<span class="token punctuation">)</span> <span class="token punctuation">{</span>
ret <span class="token operator">=</span> <span class="token function">preallocate_dma_buffer</span><span class="token punctuation">(</span>pcm<span class="token punctuation">,</span> <span class="token comment">// 为回放子流分配的 dma buffer</span>
SNDRV_PCM_STREAM_PLAYBACK<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>ret<span class="token punctuation">)</span>
<span class="token keyword">goto</span> out<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>pcm<span class="token operator">-></span>streams<span class="token punctuation">[</span>SNDRV_PCM_STREAM_CAPTURE<span class="token punctuation">]</span><span class="token punctuation">.</span>substream<span class="token punctuation">)</span> <span class="token punctuation">{</span>
ret <span class="token operator">=</span> <span class="token function">preallocate_dma_buffer</span><span class="token punctuation">(</span>pcm<span class="token punctuation">,</span> <span class="token comment">// 为录制子流分配的 dma buffer</span>
SNDRV_PCM_STREAM_CAPTURE<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>ret<span class="token punctuation">)</span>
<span class="token keyword">goto</span> out<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
out:
return ret;
}
static struct snd_soc_platform_driver samsung_asoc_platform = {
.ops = &dma_ops,
.pcm_new = dma_new,
.pcm_free = dma_free_dma_buffers,
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
当 soc-core 调用 soc_new_pcm()
创建 pcm 逻辑设备时,会回调 pcm_new()
完成 dma buffer 内存分配,注意回放子流和录制子流有着各自的 dma buffer。
4.2.3. pcm dma register
上两个小节,我们介绍了 pcm_dma 接口函数的作用及实现和 dma buffer 的分配,本小节分析 pcm_dma 注册过程。
当 platform_driver:
static struct platform_driver asoc_dma_driver = {
.driver = {
.name = "samsung-audio",
.owner = THIS_MODULE,
},
.probe = samsung_asoc_platform_probe,
.remove = __devexit_p(samsung_asoc_platform_remove),
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
与 .name = "samsung-audio"
的 platform_device(该 platform_device 在 arch/arm/plat-samsung/devs.c
中注册)匹配后,系统会回调 samsung_asoc_platform_probe()
注册 platform:
static struct snd_soc_platform_driver samsung_asoc_platform = {
.ops = &dma_ops,
.pcm_new = dma_new,
.pcm_free = dma_free_dma_buffers,
};
static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)
{
return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
snd_soc_register_platform:将 platform_drv 注册到 soc-core。
- 创建一个
snd_soc_platform
实例,包含 platform_drv(snd_soc_platform_driver
)的相关信息,封装给 soc-core 使用; - 把以上创建的 platform 实例插入到
platform_list
链表上(声卡注册时会遍历该链表,找到 dai_link 声明的 platform 并绑定)。
代码实现:
/** * snd_soc_register_platform - Register a platform with the ASoC core * * @platform: platform to register */ int snd_soc_register_platform(struct device *dev, struct snd_soc_platform_driver *platform_drv) { struct snd_soc_platform *platform;
platform <span class="token operator">=</span> <span class="token function">kzalloc</span><span class="token punctuation">(</span><span class="token keyword">sizeof</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_platform<span class="token punctuation">)</span><span class="token punctuation">,</span> GFP_KERNEL<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>platform <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span> <span class="token comment">/* create platform component name */</span> platform<span class="token operator">-></span>name <span class="token operator">=</span> <span class="token function">fmt_single_name</span><span class="token punctuation">(</span>dev<span class="token punctuation">,</span> <span class="token operator">&</span>platform<span class="token operator">-></span>id<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>platform<span class="token operator">-></span>name <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">kfree</span><span class="token punctuation">(</span>platform<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span> <span class="token punctuation">}</span> platform<span class="token operator">-></span>dev <span class="token operator">=</span> dev<span class="token punctuation">;</span> platform<span class="token operator">-></span>driver <span class="token operator">=</span> platform_drv<span class="token punctuation">;</span> platform<span class="token operator">-></span>dapm<span class="token punctuation">.</span>dev <span class="token operator">=</span> dev<span class="token punctuation">;</span> platform<span class="token operator">-></span>dapm<span class="token punctuation">.</span>platform <span class="token operator">=</span> platform<span class="token punctuation">;</span> platform<span class="token operator">-></span>dapm<span class="token punctuation">.</span>stream_event <span class="token operator">=</span> platform_drv<span class="token operator">-></span>stream_event<span class="token punctuation">;</span> <span class="token function">mutex_init</span><span class="token punctuation">(</span><span class="token operator">&</span>platform<span class="token operator">-></span>mutex<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">mutex_lock</span><span class="token punctuation">(</span><span class="token operator">&</span>client_mutex<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">list_add</span><span class="token punctuation">(</span><span class="token operator">&</span>platform<span class="token operator">-></span>list<span class="token punctuation">,</span> <span class="token operator">&</span>platform_list<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">mutex_unlock</span><span class="token punctuation">(</span><span class="token operator">&</span>client_mutex<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
至此,完成了 Platform 驱动的实现。回放情形下,pcm_dma 设备负责把 dma buffer 中的数据搬运到 I2S tx FIFO,I2S 总线控制器负责把 I2S tx FIFO 中的数据传送到 Codec。至于 alsa 如何把音频数据从 userspace 拷贝到 dma buffer,如何管理 dma buffer,如何启动 I2S 和 DMA 传输,这里面一环扣一环,见后续 pcm native 分析。
5. Machine
章节 3. Codec
和 4. Platform
介绍了 Codec、Platform 驱动,但仅有 Codec、Platform 驱动是不能工作的,需要一个角色把 codec、codec_dai、cpu_dai、platform 给链结起来才能构成一个完整的音频链路,这个角色就由 machine_drv 承担了。如下是一个典型的智能手机音频框图:
+------------+ +---------------------+ +------------+
| | | | | |
| | + CODEC + | |
| AP +------>AIF1 AIF3+------> PA +->SPK
| | + +-----+ +-----+ + | |
| | | | DSP | | DAC | | | |
+------------+ | +-----+ +-----+ | +------------+
| +-----+ +-----+ |
| | DSP | | DAC | |
| +-----+ +-----+ |
+------------+ | +-----+ +-----+ | +------------+
| | | | DSP | | ADC | | | |
| | + +-----+ +-----+ + | |
| BB +------>AIF2 +-----+ +-----+ AIF4+------> BTSCO |
| | + | DSP | | ADC | + | |
| | | +-----+ +-----+ | | |
+------------+ +----------+----------+ +------------+
| | |
+MIC +HP +EARP
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
组成了 4 个音频链路(dai_link):
- AP<>AIF1:AP(应用处理器)与 Codec 之间的链路,多媒体声音
- BB<>AIF2:BB(基带处理器)与 Codec 之间的链路,通话语音
- PA<>AIF3:PA(智能功率放大器)与 Codec 之间的链路,外放输出
- BTSCO<>AIF4:BTSCO(蓝牙)与 Codec 之间的链路,蓝牙耳机输出
snd_soc_dai_link
结构体:
struct snd_soc_dai_link { /* config - must be set by machine driver */ const char *name; /* Codec name */ const char *stream_name; /* Stream name */ const char *codec_name; /* for multi-codec */ const struct device_node *codec_of_node; const char *platform_name; /* for multi-platform */ const struct device_node *platform_of_node; const char *cpu_dai_name; const struct device_node *cpu_dai_of_node; const char *codec_dai_name;
<span class="token keyword">unsigned</span> <span class="token keyword">int</span> dai_fmt<span class="token punctuation">;</span> <span class="token comment">/* format to set on init */</span> <span class="token comment">/* Keep DAI active over suspend */</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> ignore_suspend<span class="token punctuation">:</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">/* Symmetry requirements */</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> symmetric_rates<span class="token punctuation">:</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">/* pmdown_time is ignored at stop */</span> <span class="token keyword">unsigned</span> <span class="token keyword">int</span> ignore_pmdown_time<span class="token punctuation">:</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">/* codec/machine specific init - e.g. add machine controls */</span> <span class="token keyword">int</span> <span class="token punctuation">(</span><span class="token operator">*</span>init<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token keyword">struct</span> snd_soc_pcm_runtime <span class="token operator">*</span>rtd<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* machine stream operations */</span> <span class="token keyword">struct</span> snd_soc_ops <span class="token operator">*</span>ops<span class="token punctuation">;</span>
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
注释比较详细,重点介绍如下几个字段:
- codec_name:音频链路需要绑定的 codec 名称,声卡注册时会遍历
codec_list
,找到同名的 codec 并绑定; - platform_name:音频链路需要绑定的 platform 名称,声卡注册时会遍历
platform_list
,找到同名的 platform 并绑定; - cpu_dai_name:音频链路需要绑定的 cpu_dai 名称,声卡注册时会遍历
dai_list
,找到同名的 dai 并绑定; - codec_dai_name:音频链路需要绑定的 codec_dai 名称,声卡注册时会遍历
dai_list
,找到同名的 dai 并绑定; - ops:重点留意 hw_params() 回调,一般来说这个回调是要实现的,用于配置 codec、codec_dai、cpu_dai 的数据格式和系统时钟。在
3.4. Codec audio operations
小节中有描述。
goni_wm8994.c
中的 dai_link 定义,两个音频链路分别用于 Media 和 Voice:
static struct snd_soc_dai_link goni_dai[] = {
{
.name = "WM8994",
.stream_name = "WM8994 HiFi",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm8994-aif1",
.platform_name = "samsung-audio",
.codec_name = "wm8994-codec.0-001a",
.init = goni_wm8994_init,
.ops = &goni_hifi_ops,
}, {
.name = "WM8994 Voice",
.stream_name = "Voice",
.cpu_dai_name = "goni-voice-dai",
.codec_dai_name = "wm8994-aif2",
.codec_name = "wm8994-codec.0-001a",
.ops = &goni_voice_ops,
},
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
除了 dai_link,机器中一些特定的音频控件和音频事件也可以在 machine_drv 定义,如耳机插拔检测、外部功放打开关闭等。
我们再分析 machine_drv 初始化过程:
static struct snd_soc_card goni = { .name = "goni", .owner = THIS_MODULE, .dai_link = goni_dai, .num_links = ARRAY_SIZE(goni_dai),
<span class="token punctuation">.</span>dapm_widgets <span class="token operator">=</span> goni_dapm_widgets<span class="token punctuation">,</span> <span class="token punctuation">.</span>num_dapm_widgets <span class="token operator">=</span> <span class="token function">ARRAY_SIZE</span><span class="token punctuation">(</span>goni_dapm_widgets<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">.</span>dapm_routes <span class="token operator">=</span> goni_dapm_routes<span class="token punctuation">,</span> <span class="token punctuation">.</span>num_dapm_routes <span class="token operator">=</span> <span class="token function">ARRAY_SIZE</span><span class="token punctuation">(</span>goni_dapm_routes<span class="token punctuation">)</span><span class="token punctuation">,</span>
};
static int __init goni_init(void)
{
int ret;
goni_snd_device <span class="token operator">=</span> <span class="token function">platform_device_alloc</span><span class="token punctuation">(</span><span class="token string">"soc-audio"</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>goni_snd_device<span class="token punctuation">)</span>
<span class="token keyword">return</span> <span class="token operator">-</span>ENOMEM<span class="token punctuation">;</span>
<span class="token comment">/* register voice DAI here */</span>
ret <span class="token operator">=</span> <span class="token function">snd_soc_register_dai</span><span class="token punctuation">(</span><span class="token operator">&</span>goni_snd_device<span class="token operator">-></span>dev<span class="token punctuation">,</span> <span class="token operator">&</span>voice_dai<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>ret<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">platform_device_put</span><span class="token punctuation">(</span>goni_snd_device<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> ret<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">platform_set_drvdata</span><span class="token punctuation">(</span>goni_snd_device<span class="token punctuation">,</span> <span class="token operator">&</span>goni<span class="token punctuation">)</span><span class="token punctuation">;</span>
ret <span class="token operator">=</span> <span class="token function">platform_device_add</span><span class="token punctuation">(</span>goni_snd_device<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>ret<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">snd_soc_unregister_dai</span><span class="token punctuation">(</span><span class="token operator">&</span>goni_snd_device<span class="token operator">-></span>dev<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">platform_device_put</span><span class="token punctuation">(</span>goni_snd_device<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> ret<span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 创建一个
.name="soc-audio"
的 platform_device 实例; - 设置 platform_device 的私有数据
snd_soc_card
; - 然后注册 platform_device 到系统中;
- 再然后呢?好像没有了…
但是真的没有了吗?别忘了,platform_device 还有个好伙伴 platform_driver 跟它配对。而 .name="soc-audio"
的 platform_driver 定义在 soc-core.c
中:
/* ASoC platform driver */
static struct platform_driver soc_driver = {
.driver = {
.name = "soc-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = soc_probe,
.remove = soc_remove,
};
static int __init snd_soc_init(void)
{
// …
snd_soc_util_init();
return platform_driver_register(&soc_driver);
}
module_init(snd_soc_init);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
两者匹配后,soc_probe()
会被调用,继而调用 snd_soc_register_card()
注册声卡。由于该过程很冗长,这里不一一贴代码分析了,但整个流程是比较简单的,流程图如下:
- 取出 platform_device 的私有数据,该私有数据就是
snd_soc_card
; snd_soc_register_card()
为每个 dai_link 分配一个snd_soc_pcm_runtime
实例,别忘了之前提过snd_soc_pcm_runtime
是 ASoC 的桥梁,保存着 codec、codec_dai、cpu_dai、platform 等硬件设备实例。- 随后的工作都在
snd_soc_instantiate_card()
进行: - 遍历
dai_list
、codec_list
、platform_list
链表,为每个音频链路找到对应的 cpu_dai、codec_dai、codec、platform;找到的 cpu_dai、codec_dai、codec、platform 保存到snd_soc_pcm_runtime
,完成音频链路的设备绑定; - 调用
snd_card_create()
创建声卡; soc_probe_dai_link()
依次回调 cpu_dai、codec、platform、codec_dai 的probe()
函数,完成各音频设备的初始化,随后调用soc_new_pcm()
创建 pcm 逻辑设备(因为涉及到本系列的重点内容,后面具体分析这个函数);- 最后调用
snd_card_register()
注册声卡。
soc_new_pcm
源码分析:
/* create a new pcm */ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) { struct snd_soc_codec *codec = rtd->codec; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_pcm_ops *soc_pcm_ops = &rtd->ops; struct snd_pcm *pcm; char new_name[64]; int ret = 0, playback = 0, capture = 0;
<span class="token comment">// 初始化 snd_soc_pcm_runtime 的 ops 字段,成员函数其实依次调用 machine、codec_dai、cpu_dai、platform 的回调;如 soc_pcm_hw_params:</span> <span class="token comment">// |-> rtd->dai_link->ops->hw_params() </span> <span class="token comment">// |-> codec_dai->driver->ops->hw_params() </span> <span class="token comment">// |-> cpu_dai->driver->ops->hw_params()</span> <span class="token comment">// |-> platform->driver->ops->hw_params()</span> <span class="token comment">// 在这里把底层硬件的操作接口抽象起来,pcm native 不用知道底层硬件细节</span> soc_pcm_ops<span class="token operator">-></span>open <span class="token operator">=</span> soc_pcm_open<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>close <span class="token operator">=</span> soc_pcm_close<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>hw_params <span class="token operator">=</span> soc_pcm_hw_params<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>hw_free <span class="token operator">=</span> soc_pcm_hw_free<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>prepare <span class="token operator">=</span> soc_pcm_prepare<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>trigger <span class="token operator">=</span> soc_pcm_trigger<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>pointer <span class="token operator">=</span> soc_pcm_pointer<span class="token punctuation">;</span> <span class="token comment">/* check client and interface hw capabilities */</span> <span class="token function">snprintf</span><span class="token punctuation">(</span>new_name<span class="token punctuation">,</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span>new_name<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"%s %s-%d"</span><span class="token punctuation">,</span> rtd<span class="token operator">-></span>dai_link<span class="token operator">-></span>stream_name<span class="token punctuation">,</span> codec_dai<span class="token operator">-></span>name<span class="token punctuation">,</span> num<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>codec_dai<span class="token operator">-></span>driver<span class="token operator">-></span>playback<span class="token punctuation">.</span>channels_min<span class="token punctuation">)</span> playback <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>codec_dai<span class="token operator">-></span>driver<span class="token operator">-></span>capture<span class="token punctuation">.</span>channels_min<span class="token punctuation">)</span> capture <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">// 创建 pcm 逻辑设备</span> ret <span class="token operator">=</span> <span class="token function">snd_pcm_new</span><span class="token punctuation">(</span>rtd<span class="token operator">-></span>card<span class="token operator">-></span>snd_card<span class="token punctuation">,</span> new_name<span class="token punctuation">,</span> num<span class="token punctuation">,</span> playback<span class="token punctuation">,</span> capture<span class="token punctuation">,</span> <span class="token operator">&</span>pcm<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">printk</span><span class="token punctuation">(</span>KERN_ERR <span class="token string">"asoc: can't create pcm for codec %s\n"</span><span class="token punctuation">,</span> codec<span class="token operator">-></span>name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* DAPM dai link stream work */</span> <span class="token function">INIT_DELAYED_WORK</span><span class="token punctuation">(</span><span class="token operator">&</span>rtd<span class="token operator">-></span>delayed_work<span class="token punctuation">,</span> close_delayed_work<span class="token punctuation">)</span><span class="token punctuation">;</span> rtd<span class="token operator">-></span>pcm <span class="token operator">=</span> pcm<span class="token punctuation">;</span> pcm<span class="token operator">-></span>private_data <span class="token operator">=</span> rtd<span class="token punctuation">;</span> <span class="token comment">// pcm 的私有数据指向 snd_soc_pcm_runtime</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>platform<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 初始化 snd_soc_pcm_runtime 的 ops 字段,这些与 pcm_dma 操作相关,一般我们只用留意 pointer 回调</span> soc_pcm_ops<span class="token operator">-></span>mmap <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token operator">-></span>mmap<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>pointer <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token operator">-></span>pointer<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>ioctl <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token operator">-></span>ioctl<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>copy <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token operator">-></span>copy<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>silence <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token operator">-></span>silence<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>ack <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token operator">-></span>ack<span class="token punctuation">;</span> soc_pcm_ops<span class="token operator">-></span>page <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span>ops<span class="token operator">-></span>page<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>playback<span class="token punctuation">)</span> <span class="token function">snd_pcm_set_ops</span><span class="token punctuation">(</span>pcm<span class="token punctuation">,</span> SNDRV_PCM_STREAM_PLAYBACK<span class="token punctuation">,</span> soc_pcm_ops<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 把 soc_pcm_ops 赋给 playback substream 的 ops 字段</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>capture<span class="token punctuation">)</span> <span class="token function">snd_pcm_set_ops</span><span class="token punctuation">(</span>pcm<span class="token punctuation">,</span> SNDRV_PCM_STREAM_CAPTURE<span class="token punctuation">,</span> soc_pcm_ops<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 把 soc_pcm_ops 赋给 capture substream 的 ops 字段</span> <span class="token comment">// 回调 dma 驱动的 pcm_new(),进行 dma buffer 内存分配和 dma 设备初始化</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>platform<span class="token operator">-></span>driver<span class="token operator">-></span>pcm_new<span class="token punctuation">)</span> <span class="token punctuation">{</span> ret <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span><span class="token function">pcm_new</span><span class="token punctuation">(</span>rtd<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ret <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">pr_err</span><span class="token punctuation">(</span><span class="token string">"asoc: platform pcm constructor failed\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> pcm<span class="token operator">-></span>private_free <span class="token operator">=</span> platform<span class="token operator">-></span>driver<span class="token operator">-></span>pcm_free<span class="token punctuation">;</span> <span class="token function">printk</span><span class="token punctuation">(</span>KERN_INFO <span class="token string">"asoc: %s <-> %s mapping ok\n"</span><span class="token punctuation">,</span> codec_dai<span class="token operator">-></span>name<span class="token punctuation">,</span> cpu_dai<span class="token operator">-></span>name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> ret<span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
可见 soc_new_pcm()
最主要的工作是创建 pcm 逻辑设备,创建回放子流和录制子流实例,并初始化回放子流和录制子流的 pcm 操作函数(数据搬运时,需要调用这些函数来驱动 codec、codec_dai、cpu_dai、dma 设备工作)。
</div>
<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-b6c3c6d139.css" rel="stylesheet">
<div class="more-toolbox">
<div class="left-toolbox">
<ul class="toolbox-list">
<li class="tool-item tool-active is-like tool-clicked"><a href="javascript:;"><svg class="icon" aria-hidden="true">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#csdnc-thumbsup"></use>
</svg><span class="name">点赞</span>
<span class="count">25</span>
</a></li>