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

描述

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

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

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

  • 一个 小部件 (widget) 是音频硬件的每个部分,在使用时可以通过软件启用,不使用时可以禁用以节省电量

  • 一个 路由 (route) 是小部件之间的互连,当声音可以在一个小部件和另一个小部件之间流动时存在

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

STM32MP1-DK1 声卡的图示见图片

Example DAPM graph

您还可以使用 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_driverdapm_widgetsnum_dapm_widgets 字段声明。

类似地,连接它们的路由在 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"},

所以我们有

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

  • 接收器、路径、源,或者

  • 输出混音器 (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)