便攜裝置的動態音訊電源管理

描述

動態音訊電源管理(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)