便携设备的动态音频电源管理¶
描述¶
动态音频电源管理(DAPM)旨在让便携式 Linux 设备始终在音频子系统中使用最小的电量。它独立于其他内核电源管理框架,因此可以轻松地与它们共存。
DAPM 对所有用户空间应用程序都是完全透明的,因为所有电源切换都在 ASoC 核心内部完成。用户空间应用程序无需代码更改或重新编译。DAPM 根据任何音频流(捕获/回放)活动和设备中的音频混音器设置来做出电源切换决策。
DAPM 基于两个基本元素,称为小部件 (widgets) 和路由 (routes)
一个 小部件 (widget) 是音频硬件的每个部分,在使用时可以通过软件启用,不使用时可以禁用以节省电量
一个 路由 (route) 是小部件之间的互连,当声音可以在一个小部件和另一个小部件之间流动时存在
所有 DAPM 电源切换决策都通过查阅音频路由图自动做出。此图特定于每个声卡并覆盖整个声卡,因此一些 DAPM 路由连接属于不同组件的两个小部件(例如,CODEC 的 LINE OUT 引脚和放大器的输入引脚)。
STM32MP1-DK1 声卡的图示见图片
您还可以使用 tools/sound/dapm-graph 工具为您的声卡生成兼容图。
DAPM 功率域¶
DAPM 内有 4 个功率域
- Codec 偏置域
VREF, VMID(核心编解码器和音频电源)
通常在编解码器探测/移除和暂停/恢复时控制,尽管如果不需要侧音等电源,也可以在流时间设置。
- 平台/机器域
物理连接的输入和输出
是平台/机器和用户操作特有的,由机器驱动程序配置并响应异步事件,例如插入耳机时
- 路径域
音频子系统信号路径
当用户更改混音器和多路复用器设置时自动设置。例如 alsamixer, amixer。
- 流域
DAC 和 ADC。
分别在流回放/捕获开始和停止时启用和禁用。例如 aplay, arecord。
DAPM 小部件¶
音频 DAPM 小部件分为多种类型
- 混音器 (Mixer)
将多个模拟信号混音成一个模拟信号。
- 多路复用器 (Mux)
一个模拟开关,只输出多个输入中的一个。
- PGA
一个可编程增益放大器或衰减小部件。
- ADC
模拟到数字转换器
- DAC
数字到模拟转换器
- 开关 (Switch)
一个模拟开关
- 输入 (Input)
一个编解码器输入引脚
- 输出 (Output)
一个编解码器输出引脚
- 耳机 (Headphone)
耳机(和可选的插孔)
- 麦克风 (Mic)
麦克风(和可选的插孔)
- 线路 (Line)
线路输入/输出(和可选的插孔)
- 扬声器 (Speaker)
扬声器 (Speaker)
- 供电 (Supply)
其他小部件使用的电源或时钟供电小部件。
- 稳压器 (Regulator)
为音频组件供电的外部稳压器。
- 时钟 (Clock)
为音频组件供时钟的外部时钟。
- AIF IN
音频接口输入(带 TDM 时隙掩码)。
- AIF OUT
音频接口输出(带 TDM 时隙掩码)。
- Siggen
信号发生器。
- DAI IN
数字音频接口输入。
- DAI OUT
数字音频接口输出。
- DAI Link
两个 DAI 结构之间的 DAI 链路
- Pre
特殊 PRE 小部件(在所有其他小部件之前执行)
- Post
特殊 POST 小部件(在所有其他小部件之后执行)
- 缓冲器 (Buffer)
DSP 内的小部件间音频数据缓冲器。
- 调度器 (Scheduler)
DSP 内部调度器,调度组件/流水线处理工作。
- 效果 (Effect)
执行音频处理效果的小部件。
- SRC
DSP 或 CODEC 中的采样率转换器
- ASRC
DSP 或 CODEC 中的异步采样率转换器
- 编码器 (Encoder)
将音频数据从一种格式(通常是 PCM)编码为另一种通常是更压缩格式的小部件。
- 解码器 (Decoder)
将音频数据从压缩格式解码为未压缩格式(如 PCM)的小部件。
(小部件在 include/sound/soc-dapm.h 中定义)
小部件可以由任何组件驱动程序类型添加到声卡中。soc-dapm.h 中定义了方便的宏,可用于快速构建编解码器和机器 DAPM 小部件列表。
大多数小部件都有名称、寄存器、移位和反转。一些小部件有额外的流名称和 kcontrols 参数。
流域小部件¶
流小部件与流功率域相关,仅包含 ADC(模拟到数字转换器)、DAC(数字到模拟转换器)、AIF IN 和 AIF OUT。
流小部件格式如下
SND_SOC_DAPM_DAC(name, stream name, reg, shift, invert),
SND_SOC_DAPM_AIF_IN(name, stream, slot, reg, shift, invert)
注意:流名称必须与您的编解码器 snd_soc_dai_driver 中的相应流名称匹配。
例如:HiFi 回放和捕获的流小部件
SND_SOC_DAPM_DAC("HiFi DAC", "HiFi Playback", REG, 3, 1),
SND_SOC_DAPM_ADC("HiFi ADC", "HiFi Capture", REG, 2, 1),
例如:AIF 的流小部件
SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0),
路径域小部件¶
路径域小部件能够控制或影响音频子系统内的音频信号或音频路径。它们具有以下形式
SND_SOC_DAPM_PGA(name, reg, shift, invert, controls, num_controls)
任何小部件 kcontrols 都可以使用 controls 和 num_controls 成员进行设置。
例如:混音器小部件(kcontrols 首先声明)
/* Output Mixer */
static const snd_kcontrol_new_t wm8731_output_mixer_controls[] = {
SOC_DAPM_SINGLE("Line Bypass Switch", WM8731_APANA, 3, 1, 0),
SOC_DAPM_SINGLE("Mic Sidetone Switch", WM8731_APANA, 5, 1, 0),
SOC_DAPM_SINGLE("HiFi Playback Switch", WM8731_APANA, 4, 1, 0),
};
SND_SOC_DAPM_MIXER("Output Mixer", WM8731_PWR, 4, 1, wm8731_output_mixer_controls,
ARRAY_SIZE(wm8731_output_mixer_controls)),
如果您不想让混音器元素以混音器小部件的名称作为前缀,您可以使用 SND_SOC_DAPM_MIXER_NAMED_CTL。其参数与 SND_SOC_DAPM_MIXER 相同。
机器域小部件¶
机器小部件与编解码器小部件不同,它们没有与之关联的编解码器寄存器位。每个可以独立供电的机器音频组件(非编解码器或 DSP)都被分配一个机器小部件。例如:
扬声器放大器
麦克风偏置
插孔连接器
机器小部件可以有一个可选的回调。
例如:用于外部麦克风的插孔连接器小部件,当麦克风插入时启用麦克风偏置
static int spitz_mic_bias(struct snd_soc_dapm_widget* w, int event)
{
gpio_set_value(SPITZ_GPIO_MIC_BIAS, SND_SOC_DAPM_EVENT_ON(event));
return 0;
}
SND_SOC_DAPM_MIC("Mic Jack", spitz_mic_bias),
编解码器(偏置)域¶
编解码器偏置功率域没有小部件,由编解码器 DAPM 事件处理程序处理。当编解码器功率状态因任何流事件或内核 PM 事件而改变时,会调用此处理程序。
虚拟小部件¶
有时在编解码器或机器音频图中存在没有相应软电源控制的小部件。在这种情况下,需要创建一个虚拟小部件——一个没有控制位的小部件,例如:
SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
这可用于在软件中将两条信号路径合并在一起。
注册 DAPM 控制¶
在许多情况下,DAPM 小部件在编解码器驱动程序中以 static const struct snd_soc_dapm_widget
数组的形式静态实现,并简单地通过 struct snd_soc_component_driver
的 dapm_widgets
和 num_dapm_widgets
字段声明。
类似地,连接它们的路由在 static const struct snd_soc_dapm_route
数组中静态实现,并通过相同结构的 dapm_routes
和 num_dapm_routes
字段声明。
声明了上述内容后,驱动程序注册将负责填充它们
static const struct snd_soc_dapm_widget wm2000_dapm_widgets[] = {
SND_SOC_DAPM_OUTPUT("SPKN"),
SND_SOC_DAPM_OUTPUT("SPKP"),
...
};
/* Target, Path, Source */
static const struct snd_soc_dapm_route wm2000_audio_map[] = {
{ "SPKN", NULL, "ANC Engine" },
{ "SPKP", NULL, "ANC Engine" },
...
};
static const struct snd_soc_component_driver soc_component_dev_wm2000 = {
...
.dapm_widgets = wm2000_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(wm2000_dapm_widgets),
.dapm_routes = wm2000_audio_map,
.num_dapm_routes = ARRAY_SIZE(wm2000_audio_map),
...
};
在更复杂的情况下,DAPM 小部件和/或路由列表只能在探测时得知。例如,当驱动程序支持具有不同功能集的不同模型时,就会发生这种情况。在这些情况下,可以通过调用 snd_soc_dapm_new_controls()
和 snd_soc_dapm_add_routes()
以编程方式注册实现特定功能的独立小部件和路由数组。
编解码器/DSP 小部件互连¶
小部件通过音频路径(称为互连)在编解码器、平台和机器内部相互连接。必须定义每个互连才能创建所有小部件之间音频路径的图。
这通过编解码器或 DSP 的图(以及机器音频系统的原理图)最容易实现,因为它需要通过音频信号路径将小部件连接在一起。
例如,WM8731 输出混音器 (wm8731.c) 有 3 个输入(源)
线路旁路输入
DAC (HiFi 回放)
麦克风侧音输入
本例中的每个输入都有一个与之关联的 kcontrol(在上面的示例中定义),并通过其 kcontrol 名称连接到输出混音器。我们现在可以将目标小部件(相对于音频信号)与其源小部件连接起来。
/* output mixer */
{"Output Mixer", "Line Bypass Switch", "Line Input"},
{"Output Mixer", "HiFi Playback Switch", "DAC"},
{"Output Mixer", "Mic Sidetone Switch", "Mic Bias"},
所以我们有
目标小部件 <=== 路径名称 <=== 源小部件,或者
接收器、路径、源,或者
输出混音器 (Output Mixer)
通过HiFi 回放开关 (HiFi Playback Switch)
连接到DAC
。
当没有路径名称连接小部件时(例如直接连接),我们将路径名称设置为 NULL。
互连通过调用创建
snd_soc_dapm_connect_input(codec, sink, path, source);
最后,必须在所有小部件和互连都已向核心注册后调用 snd_soc_dapm_new_widgets()
。这会使核心扫描编解码器和机器,以便内部 DAPM 状态与机器的物理状态匹配。
机器小部件互连¶
机器小部件互连的创建方式与编解码器互连相同,并直接将编解码器引脚连接到机器级小部件。
例如:将扬声器输出编解码器引脚连接到内部扬声器。
/* ext speaker connected to codec pins LOUT2, ROUT2 */
{"Ext Spk", NULL , "ROUT2"},
{"Ext Spk", NULL , "LOUT2"},
这允许 DAPM 分别打开和关闭已连接(且正在使用)的引脚和 NC 引脚。
端点小部件¶
端点是机器内部音频信号的起点或终点(小部件),包括编解码器。例如:
耳机插孔
内置扬声器
内置麦克风
麦克风插孔
编解码器引脚
端点被添加到 DAPM 图中,以便可以确定它们的用法以节省电量。例如,NC 编解码器引脚将被关闭,未连接的插孔也可以关闭。
DAPM 小部件事件¶
需要实现比 DAPM 更复杂行为的小部件可以通过设置函数指针来设置自定义“事件处理程序”。一个例子是电源需要启用 GPIO
static int sof_es8316_speaker_power_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
if (SND_SOC_DAPM_EVENT_ON(event))
gpiod_set_value_cansleep(gpio_pa, true);
else
gpiod_set_value_cansleep(gpio_pa, false);
return 0;
}
static const struct snd_soc_dapm_widget st_widgets[] = {
...
SND_SOC_DAPM_SUPPLY("Speaker Power", SND_SOC_NOPM, 0, 0,
sof_es8316_speaker_power_event,
SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU),
};
有关支持事件的所有其他小部件,请参阅 soc-dapm.h。
事件类型¶
事件小部件支持以下事件类型
/* dapm event types */
#define SND_SOC_DAPM_PRE_PMU 0x1 /* before widget power up */
#define SND_SOC_DAPM_POST_PMU 0x2 /* after widget power up */
#define SND_SOC_DAPM_PRE_PMD 0x4 /* before widget power down */
#define SND_SOC_DAPM_POST_PMD 0x8 /* after widget power down */
#define SND_SOC_DAPM_PRE_REG 0x10 /* before audio path setup */
#define SND_SOC_DAPM_POST_REG 0x20 /* after audio path setup */
#define SND_SOC_DAPM_WILL_PMU 0x40 /* called at start of sequence */
#define SND_SOC_DAPM_WILL_PMD 0x80 /* called at start of sequence */
#define SND_SOC_DAPM_PRE_POST_PMD (SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD)
#define SND_SOC_DAPM_PRE_POST_PMU (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU)