便携设备的动态音频电源管理

描述

动态音频电源管理 (DAPM) 的设计目的是允许便携式 Linux 设备始终在音频子系统内使用最小的功耗。它独立于其他内核电源管理框架,因此可以轻松地与它们共存。

DAPM 对所有用户空间应用程序也是完全透明的,因为所有电源切换都在 ASoC 核心内完成。用户空间应用程序不需要代码更改或重新编译。DAPM 根据设备内的任何音频流(捕获/播放)活动和音频混音器设置做出电源切换决策。

DAPM 基于两个基本元素,称为小部件和路由

  • 一个 小部件 是音频硬件的每个部分,可以在使用时由软件启用,并在不使用时禁用以节省功耗

  • 一个 路由 是当声音可以从一个小部件流向另一个小部件时存在的小部件之间的互连

所有 DAPM 电源切换决策都是通过查阅音频路由图自动做出的。此图特定于每个声卡并跨越整个声卡,因此某些 DAPM 路由连接属于不同组件的两个小部件(例如,CODEC 的 LINE OUT 引脚和放大器的输入引脚)。

STM32MP1-DK1 声卡的图显示在图片中

Example DAPM graph

您还可以使用 tools/sound/dapm-graph 实用程序为您的声卡生成兼容的图。

DAPM 电源域

DAPM 中有 4 个电源域

编解码器偏置域

VREF、VMID(核心编解码器和音频电源)

通常在编解码器探测/删除和挂起/恢复时控制,但如果不需要用于侧音等的电源,也可以在流式传输时设置。

平台/机器域

物理连接的输入和输出

是平台/机器和用户操作特定的,由机器驱动程序配置并响应异步事件,例如插入 HP 时

路径域

音频子系统信号路径

当用户更改混音器和多路复用设置时自动设置。例如,alsamixer、amixer。

流域

DAC 和 ADC。

当分别启动和停止流播放/捕获时启用和禁用。例如,aplay、arecord。

DAPM 小部件

音频 DAPM 小部件分为多种类型

混音器

将多个模拟信号混合成单个模拟信号。

多路复用器

一种模拟开关,仅输出多个输入中的一个。

PGA

一种可编程增益放大器或衰减小部件。

ADC

模数转换器

DAC

数模转换器

开关

一个模拟开关

输入

一个编解码器输入引脚

输出

一个编解码器输出引脚

耳机

耳机(和可选的插孔)

麦克风

麦克风(和可选的插孔)

线路

线路输入/输出(和可选的插孔)

扬声器

扬声器

电源

其他小部件使用的电源或时钟电源小部件。

稳压器

为音频组件供电的外部稳压器。

时钟

为音频组件供时钟的外部时钟。

AIF 输入

音频接口输入(带有 TDM 插槽掩码)。

AIF 输出

音频接口输出(带有 TDM 插槽掩码)。

信号发生器

信号发生器。

DAI 输入

数字音频接口输入。

DAI 输出

数字音频接口输出。

DAI 链接

两个 DAI 结构之间的 DAI 链接

Pre

特殊的 PRE 小部件(在所有其他小部件之前执行)

Post

特殊的 POST 小部件(在所有其他小部件之后执行)

缓冲区

DSP 内的小部件间音频数据缓冲区。

调度器

调度组件/管道处理工作的 DSP 内部调度器。

效果

执行音频处理效果的小部件。

SRC

DSP 或 CODEC 内的采样率转换器

ASRC

DSP 或 CODEC 内的异步采样率转换器

编码器

将音频数据从一种格式(通常为 PCM)编码为另一种通常更压缩的格式的小部件。

解码器

将音频数据从压缩格式解码为未压缩格式(如 PCM)的小部件。

(小部件在 include/sound/soc-dapm.h 中定义)

可以通过任何组件驱动程序类型将小部件添加到声卡。soc-dapm.h 中定义了便捷的宏,可用于快速构建编解码器和机器 DAPM 小部件的小部件列表。

大多数小部件都有一个名称、寄存器、移位和反转。某些小部件具有流名称和 kcontrols 的额外参数。

流域小部件

流小部件与流电源域相关,仅包含 ADC(模数转换器)、DAC(数模转换器)、AIF 输入和 AIF 输出。

流小部件具有以下格式

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)

可以使用控件和 num_controls 成员设置任何小部件 kcontrols。

例如,混音器小部件(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),

编解码器 (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 数组静态实现,并通过 dapm_widgetsnum_dapm_widgets 字段简单地声明 struct snd_soc_component_driver

类似地,连接它们的路由在 static const struct snd_soc_dapm_route 数组中静态实现,并通过同一结构的 dapm_routesnum_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 个输入(源)

  1. 线路旁路输入

  2. DAC(HiFi 播放)

  3. 麦克风侧音输入

此示例中的每个输入都与一个 kcontrol 相关联(在上面的示例中定义),并通过其 kcontrol 名称连接到输出混音器。我们现在可以将目标小部件(相对于音频信号)与其源小部件连接起来。

/* output mixer */
{"Output Mixer", "Line Bypass Switch", "Line Input"},
{"Output Mixer", "HiFi Playback Switch", "DAC"},
{"Output Mixer", "Mic Sidetone Switch", "Mic Bias"},

所以我们有

  • 目标小部件 <=== 路径名称 <=== 源小部件,或

  • 接收器、路径、源,或

  • 输出 混音器 通过 HiFi 播放 开关 连接到 DAC

当没有路径名称连接小部件时(例如,直接连接),我们为路径名称传递 NULL。

互连是通过调用以下函数创建的:

snd_soc_dapm_connect_input(codec, sink, path, source);

最后,在所有 widget 和互连都已向核心注册后,必须调用 snd_soc_dapm_new_widgets()。这将导致核心扫描编解码器和机器,以便内部 DAPM 状态与机器的物理状态匹配。

机器 Widget 互连

机器 widget 互连的创建方式与编解码器的互连方式相同,并且直接将编解码器引脚连接到机器级别的 widget。

例如,将扬声器输出编解码器引脚连接到内部扬声器。

/* ext speaker connected to codec pins LOUT2, ROUT2  */
{"Ext Spk", NULL , "ROUT2"},
{"Ext Spk", NULL , "LOUT2"},

这允许 DAPM 打开和关闭已连接(和正在使用)的引脚以及 NC 的引脚。

端点 Widget

端点是机器内音频信号的起点或终点(widget),包括编解码器。 例如:

  • 耳机插孔

  • 内部扬声器

  • 内部麦克风

  • 麦克风插孔

  • 编解码器引脚

端点被添加到 DAPM 图中,以便可以确定它们的用途以节省功耗。例如,NC 编解码器引脚将被关闭,未连接的插孔也可以被关闭。

DAPM Widget 事件

需要实现比 DAPM 能做的更复杂行为的 Widget 可以通过设置函数指针来设置自定义的“事件处理程序”。 一个例子是需要启用 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 获取所有支持事件的其他 widget。

事件类型

事件 widget 支持以下事件类型

/* 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)