# Linux 核心設計: Audio Subsystem(1): DAPM
## Overview
[Dynamic Audio Power Management (DAPM)](https://www.alsa-project.org/wiki/DAPM) 是在 Linux 作業系統中,以使音訊子系統中能達最低功耗為目的所設計的機制。其獨立並共存於其他核心電源管理框架,因為相關的管理必須深入到元件(component)級別,而非一般的裝置級別(後者是透過 [Runtime PM 框架](https://hackmd.io/@RinHizakura/SkziZRa9a)所管理)。
DAPM 基於兩個基本元素組成:
* Widget: 是指音訊硬體中可在被使用時被軟體啟用、並在不需要時被停用以節省功耗的各個部分
* Route: 是指 Widget 之間的連接,聲音可以藉此從一個 widget 流向另一個 widget
透過查詢音訊的路由圖(routing graph),DAPM 能根據裝置內的任何音訊串流(capture/playback)活動,獨立地做出電源切換的決策,而無需 userspace 應用程式的干預。
以下圖為例,如果要從類比輸入擷取音頻,只需啟用 ADC(左圖)。如果想要從數位麥克風擷取語音,則需要啟用 DMIC(右圖)。其他與目標 stream 無關的元件,則可以被關閉進而達到省電。

## DAPM Widgets
DAPM Widgets 能分為多種類型。Widgets 通常都具有個別的名稱、暫存器、shift 和 invert 這些特徵(參數)。部分 Widgets 還具有 stream 名稱和 kcontrols 等額外參數。
可以將 Widgets 大致劃分為三種類別。
* Endpoint widgets: 是 stream 的起始點(source)或終止點(sink)。
* [類比數位轉換器 (ADC)](https://zh.wikipedia.org/zh-tw/%E9%A1%9E%E6%AF%94%E6%95%B8%E4%BD%8D%E8%BD%89%E6%8F%9B%E5%99%A8)、[數位轉類比轉換器 (DAC)](https://zh.wikipedia.org/zh-tw/%E6%95%B8%E4%BD%8D%E9%A1%9E%E6%AF%94%E8%BD%89%E6%8F%9B%E5%99%A8)
* 揚聲器、麥克風、Line
* Pass-through widgets: 用於連接其他 widget
* [PGA](https://en.wikipedia.org/wiki/Programmable-gain_amplifier)、Effect
* Mixer, Mux, Demux, Switch
* Supply widgets: 為其他 widget 提供 Clock, current, 或者 voltage
## DAPM 的運作方式
### Phase1: Power State
DAPM 運作的第一階段是確認每個元件的電源狀態。

首先,DAPM 會判斷 source widgets 是否處於 active 狀態(被某個 stream 所利用)且路由到 active sink widget 時,若是,則為其通電。Sink widgets 也類似,若其為 active 且路由到 active source widgets 時,為其通電。

接著,在兩個 endpoint widgets 間路徑的 widgets 都需要被上電。

最後,如果 supply widgets 通往已通電部件的路徑,也向其供電。
### Phase2: Power Sequence
第二階是根據新舊配置之間的差異,計算依照甚麼順序來:
* 為需被暫停的 widget 關電
* 採用新的 route 更改
* 供電給新的需啟用的 widget
可以達到最佳的功耗。
## 實作
### `struct snd_soc_dapm_widget`
[`struct snd_soc_dapm_widget`](https://elixir.bootlin.com/linux/v6.17.7/source/include/sound/soc-dapm.h#L515) 是用來定義每個 widgets 的資料結構。包含了名稱、相關的暫存器、shift 和 invert 等等資訊。
一般而言,驅動的開發者不會直接定義 struct 裡的每個 fied,而是透過 [`SND_SOC_DAPM_*()`](https://elixir.bootlin.com/linux/v6.17.7/source/include/sound/soc-dapm.h#L45) 系列的 macro,舉例而言,以下的 [`adau1372_dapm_widgets`](https://elixir.bootlin.com/linux/v6.17.7/source/sound/soc/codecs/adau1372.c#L361) 動態的定義了許多 widgets
```cpp
static const struct snd_soc_dapm_widget adau1372_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("AIN0"),
SND_SOC_DAPM_INPUT("AIN1"),
SND_SOC_DAPM_INPUT("AIN2"),
SND_SOC_DAPM_INPUT("AIN3"),
SND_SOC_DAPM_INPUT("DMIC0_1"),
SND_SOC_DAPM_INPUT("DMIC2_3"),
SND_SOC_DAPM_SUPPLY("MICBIAS0", ADAU1372_REG_MICBIAS, 4, 0, NULL, 0),
SND_SOC_DAPM_SUPPLY("MICBIAS1", ADAU1372_REG_MICBIAS, 5, 0, NULL, 0),
...
```
### `struct snd_soc_dapm_route`
對於 route 的定義,則是使用 `struct snd_soc_dapm_route`。
```cpp
struct snd_soc_dapm_route {
const char *sink;
const char *control;
const char *source;
/* Note: currently only supported for links where source is a supply */
int (*connected)(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink);
struct snd_soc_dobj dobj;
};
```
例如以下是以靜態定義的方式定義的 route。留意到由於 source 是第三個參數,所以方向是從右到左。
```cpp
static const struct snd_soc_dapm_route adau1372_dapm_routes[] = {
{ "PGA0", NULL, "AIN0" },
{ "PGA1", NULL, "AIN1" },
{ "PGA2", NULL, "AIN2" },
{ "PGA3", NULL, "AIN3" },
```
另外這裡可以發現 source 和 sink 是以字串方式定義而非 widgets 的某種資料結構指標,因此實際的配對是由子系統通過字串的比對完成,這也表示需要很小心輸入錯誤的可能性:laughing:。
### `snd_soc_component_driver`
當 widgets 和 routes 都被建立,最後就是將其註冊至驅動程式的關鍵結構 [`snd_soc_component_driver`](https://elixir.bootlin.com/linux/v6.17.7/source/include/sound/soc-component.h#L67) 之中。
例如以下的 [`adau1372_driver`](https://elixir.bootlin.com/linux/v6.17.7/source/sound/soc/codecs/adau1372.c#L855) 將前面定義的 widgets 和 routes 以靜態方式關聯在一起。
```cpp
static const struct snd_soc_component_driver adau1372_driver = {
.set_bias_level = adau1372_set_bias_level,
.controls = adau1372_controls,
.num_controls = ARRAY_SIZE(adau1372_controls),
.dapm_widgets = adau1372_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(adau1372_dapm_widgets),
.dapm_routes = adau1372_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(adau1372_dapm_routes),
.endianness = 1,
};
```
在某些情況下可能會需要動態註冊 widgets 或 routes,此時則可以通過 [`snd_soc_dapm_new_controls`](https://elixir.bootlin.com/linux/v6.17.7/source/sound/soc/soc-dapm.c#L3814)/[`snd_soc_dapm_add_routes`](https://elixir.bootlin.com/linux/v6.17.7/source/sound/soc/soc-dapm.c#L3159) 等 API。
## 除錯
DAPM 的發生由 kernel 自動管理,不需要 userspace 的介入。不過,如果開發者發現相關問題並想分析的情況下,能夠善用某些介面。
### debugfs
每個 widgets 會以 `/sys/kernel/debug/asoc/$CARD/$COMPONENT/dapm/$WIDGET` 或 `/sys/kernel/debug/asoc/$CARD/dapm/$WIDGET`(card level widget) 的形式呈現一些資訊到 userspace。
### damp-graph
透過 [dapm-graph](https://github.com/torvalds/linux/blob/master/tools/sound/dapm-graph) 這個 script,這個工具可以將 DAPM 圖轉換為 dot 檔案以提供每個 component 之間關係的視覺化,例如以下範例。

## Reference
* [Introduction to DAPM, Linux Power Management for Embedded Audio Devices - Luca Ceresoli, Bootlin](https://www.youtube.com/watch?v=kdqvyY0JFWQ)
* [Introduction to DAPM](https://bootlin.com/pub/conferences/2024/eoss/ceresoli-dapm/ceresoli-dapm.pdf)
* [Advanced Linux Sound Architecture](https://en.wikipedia.org/wiki/Advanced_Linux_Sound_Architecture)
* [Dynamic Audio Power Management for Portable Devices](https://www.kernel.org/doc/html/latest/sound/soc/dapm.html)