ASoC & DAPM

ASoC와 DAPM을 임베디드 오디오 전력 최적화 관점에서 심층 분석합니다. Codec/Platform/Machine 드라이버 분리 구조, DAI 링크 구성과 포맷 협상, DAPM 위젯·라우트 그래프를 통한 자동 전원 게이팅, 클럭/레귤레이터/잭 감지 연동, Device Tree 카드 바인딩과 멀티코덱 설계, low-power 오디오 경로 구성, pop-noise 방지 및 경로 전환 안정화, debugfs와 오디오 계측 기반 튜닝까지 보드 레벨 오디오 스택 구현에 필요한 실전 내용을 다룹니다.

전제 조건: 디바이스 드라이버DMA 문서를 먼저 읽으세요. 멀티미디어/가속기 경로는 대용량 버퍼 이동과 동기화가 성능의 핵심이므로, 메모리 경로와 큐 모델을 먼저 파악해야 합니다.
일상 비유: 이 주제는 영상 제작 파이프라인과 비슷합니다. 촬영·편집·인코딩 단계가 끊기지 않아야 결과가 나오듯이, 버퍼 큐와 하드웨어 스케줄링의 연속성이 중요합니다.

핵심 요약

  • 초기화 순서 — 탐색, 바인딩, 자원 등록 순서를 점검합니다.
  • 제어/데이터 분리 — 빠른 경로와 설정 경로를 분리 설계합니다.
  • IRQ/작업 분할 — 즉시 처리와 지연 처리를 구분합니다.
  • 안전 한계 — 전원/열/타이밍 임계값을 함께 관리합니다.
  • 운영 복구 — 오류 시 재초기화와 롤백 경로를 준비합니다.

단계별 이해

  1. 장치 수명주기 확인
    probe부터 remove까지 흐름을 점검합니다.
  2. 비동기 경로 설계
    IRQ, 워크큐, 타이머 역할을 분리합니다.
  3. 자원 정합성 검증
    DMA/클록/전원 참조를 교차 확인합니다.
  4. 현장 조건 테스트
    연결 끊김/복구/부하 상황을 재현합니다.

ASoC 프레임워크 심화

ASoC (ALSA System on Chip)는 임베디드/SoC 플랫폼을 위한 고수준 오디오 프레임워크입니다. 코덱, 플랫폼 (CPU DAI + DMA), 머신 (보드 특화) 드라이버를 분리하여 재사용성을 극대화합니다.

ASoC 아키텍처

ASoC는 세 계층으로 구성됩니다:

Machine Driver (Board-specific) snd_soc_card, dai_link[], DAPM routes, jack detection Example: sound/soc/fsl/imx-wm8731.c Platform Driver (CPU DAI) SoC Audio Interface I2S/PCM/TDM controller DMA engine integration Clock management Codec Driver Audio Codec Chip Regmap (I2C/SPI control) DAPM widgets Kcontrols (mixer) Hardware SoC I2S/TDM/PCM DMA Controller Hardware Codec IC (WM8731, RT5640...) DAC/ADC, Amplifier I2S/TDM Bus (BCLK, LRCLK, SDATA) I2C/SPI Control Machine driver는 dai_link로 CPU DAI와 Codec DAI를 연결하여 하나의 PCM 디바이스 생성

Codec Driver 작성

/* sound/soc/codecs/wm8731.c 스타일 간단 예제 */
#include <sound/soc.h>
#include <sound/tlv.h>
#include <linux/regmap.h>

/* Codec 레지스터 맵 */
#define WM8731_LINVOL    0x00
#define WM8731_RINVOL    0x01
#define WM8731_LOUT1V    0x02
#define WM8731_ROUT1V    0x03
#define WM8731_APANA     0x04
#define WM8731_APDIGI    0x05
#define WM8731_PWR       0x06
#define WM8731_IFACE     0x07
#define WM8731_SRATE     0x08
#define WM8731_ACTIVE    0x09
#define WM8731_RESET     0x0f

/* Regmap 설정 */
static const struct regmap_config wm8731_regmap = {
    .reg_bits = 7,
    .val_bits = 9,
    .max_register = WM8731_RESET,
    .cache_type = REGCACHE_RBTREE,
};

/* Kcontrols */
static const DECLARE_TLV_DB_SCALE(in_tlv, -3450, 150, 0);
static const DECLARE_TLV_DB_SCALE(out_tlv, -7300, 100, 1);

static const struct snd_kcontrol_new wm8731_controls[] = {
    SOC_DOUBLE_R_TLV("Capture Volume", WM8731_LINVOL, WM8731_RINVOL,
                     0, 31, 0, in_tlv),
    SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8731_LOUT1V,
                     WM8731_ROUT1V, 0, 127, 0, out_tlv),
    SOC_SINGLE("Mic Boost (+20dB)", WM8731_APANA, 0, 1, 0),
};

/* DAPM widgets (나중에 DAPM 섹션에서 상세 설명) */
static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = {
    SND_SOC_DAPM_DAC("DAC", "Playback", WM8731_PWR, 3, 1),
    SND_SOC_DAPM_ADC("ADC", "Capture", WM8731_PWR, 2, 1),
    SND_SOC_DAPM_OUTPUT("LHPOUT"),
    SND_SOC_DAPM_OUTPUT("RHPOUT"),
    SND_SOC_DAPM_INPUT("LLINEIN"),
    SND_SOC_DAPM_INPUT("RLINEIN"),
};

/* DAPM routes */
static const struct snd_soc_dapm_route wm8731_routes[] = {
    {"LHPOUT", NULL, "DAC"},
    {"RHPOUT", NULL, "DAC"},
    {"ADC", NULL, "LLINEIN"},
    {"ADC", NULL, "RLINEIN"},
};

/* DAI ops */
static int wm8731_hw_params(struct snd_pcm_substream *substream,
                           struct snd_pcm_hw_params *params,
                           struct snd_soc_dai *dai)
{
    struct snd_soc_component *component = dai->component;
    unsigned int iface = 0;

    /* 샘플 포맷 설정 */
    switch (params_width(params)) {
    case 16:
        break;
    case 20:
        iface |= 0x04;
        break;
    case 24:
        iface |= 0x08;
        break;
    case 32:
        iface |= 0x0c;
        break;
    }

    snd_soc_component_write(component, WM8731_IFACE, iface);

    /* 샘플레이트 설정 (생략: 복잡한 클럭 계산) */
    return 0;
}

static int wm8731_set_dai_fmt(struct snd_soc_dai *dai,
                             unsigned int fmt)
{
    struct snd_soc_component *component = dai->component;
    unsigned int iface = 0;

    /* Format: I2S, Left-justified, Right-justified, DSP mode A/B */
    switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
    case SND_SOC_DAIFMT_I2S:
        iface |= 0x02;
        break;
    case SND_SOC_DAIFMT_LEFT_J:
        break;
    case SND_SOC_DAIFMT_RIGHT_J:
        iface |= 0x01;
        break;
    case SND_SOC_DAIFMT_DSP_A:
        iface |= 0x03;
        break;
    default:
        return -EINVAL;
    }

    /* Clock master/slave */
    switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
    case SND_SOC_DAIFMT_CBM_CFM: /* Codec is master */
        iface |= 0x40;
        break;
    case SND_SOC_DAIFMT_CBS_CFS: /* Codec is slave */
        break;
    default:
        return -EINVAL;
    }

    snd_soc_component_update_bits(component, WM8731_IFACE, 0x4f, iface);
    return 0;
}

static const struct snd_soc_dai_ops wm8731_dai_ops = {
    .hw_params = wm8731_hw_params,
    .set_fmt = wm8731_set_dai_fmt,
};

/* Codec DAI 정의 */
static struct snd_soc_dai_driver wm8731_dai = {
    .name = "wm8731-hifi",
    .playback = {
        .stream_name = "Playback",
        .channels_min = 1,
        .channels_max = 2,
        .rates = SNDRV_PCM_RATE_8000_96000,
        .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |
                   SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
    },
    .capture = {
        .stream_name = "Capture",
        .channels_min = 1,
        .channels_max = 2,
        .rates = SNDRV_PCM_RATE_8000_96000,
        .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |
                   SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
    },
    .ops = &wm8731_dai_ops,
};

/* Component driver */
static const struct snd_soc_component_driver soc_component_dev_wm8731 = {
    .controls = wm8731_controls,
    .num_controls = ARRAY_SIZE(wm8731_controls),
    .dapm_widgets = wm8731_dapm_widgets,
    .num_dapm_widgets = ARRAY_SIZE(wm8731_dapm_widgets),
    .dapm_routes = wm8731_routes,
    .num_dapm_routes = ARRAY_SIZE(wm8731_routes),
};

/* I2C probe */
static int wm8731_i2c_probe(struct i2c_client *i2c)
{
    struct regmap *regmap;
    int ret;

    regmap = devm_regmap_init_i2c(i2c, &wm8731_regmap);
    if (IS_ERR(regmap))
        return PTR_ERR(regmap);

    ret = devm_snd_soc_register_component(&i2c->dev,
                                            &soc_component_dev_wm8731,
                                            &wm8731_dai, 1);
    return ret;
}

static const struct i2c_device_id wm8731_i2c_id[] = {
    { "wm8731", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, wm8731_i2c_id);

static struct i2c_driver wm8731_i2c_driver = {
    .driver = {
        .name = "wm8731",
    },
    .probe_new = wm8731_i2c_probe,
    .id_table = wm8731_i2c_id,
};
module_i2c_driver(wm8731_i2c_driver);

Platform Driver 작성

/* CPU DAI + DMA (간단화된 예제) */
static int my_i2s_hw_params(struct snd_pcm_substream *substream,
                           struct snd_pcm_hw_params *params,
                           struct snd_soc_dai *dai)
{
    struct my_i2s *i2s = snd_soc_dai_get_drvdata(dai);
    unsigned int rate = params_rate(params);
    unsigned int channels = params_channels(params);
    unsigned int width = params_width(params);

    /* I2S 컨트롤러 클럭 설정 */
    unsigned int bclk = rate * channels * width;
    my_i2s_set_clk(i2s, bclk);

    return 0;
}

static int my_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
    struct my_i2s *i2s = snd_soc_dai_get_drvdata(dai);
    u32 ctrl = 0;

    switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
    case SND_SOC_DAIFMT_I2S:
        ctrl |= I2S_MODE_I2S;
        break;
    case SND_SOC_DAIFMT_LEFT_J:
        ctrl |= I2S_MODE_LEFT_J;
        break;
    default:
        return -EINVAL;
    }

    switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
    case SND_SOC_DAIFMT_CBS_CFS: /* CPU is master */
        ctrl |= I2S_MASTER;
        break;
    case SND_SOC_DAIFMT_CBM_CFM: /* Codec is master */
        break;
    default:
        return -EINVAL;
    }

    writel(ctrl, i2s->regs + I2S_CTRL);
    return 0;
}

static const struct snd_soc_dai_ops my_i2s_dai_ops = {
    .hw_params = my_i2s_hw_params,
    .set_fmt = my_i2s_set_fmt,
};

static struct snd_soc_dai_driver my_i2s_dai = {
    .playback = {
        .channels_min = 2,
        .channels_max = 8,
        .rates = SNDRV_PCM_RATE_8000_192000,
        .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
                   SNDRV_PCM_FMTBIT_S32_LE,
    },
    .capture = {
        .channels_min = 2,
        .channels_max = 8,
        .rates = SNDRV_PCM_RATE_8000_192000,
        .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
                   SNDRV_PCM_FMTBIT_S32_LE,
    },
    .ops = &my_i2s_dai_ops,
};

/* Platform component (DMA) */
static const struct snd_soc_component_driver my_platform_component = {
    .name = "my-platform",
    .pcm_construct = my_pcm_new,  /* DMA 버퍼 할당 */
};

static int my_i2s_probe(struct platform_device *pdev)
{
    struct my_i2s *i2s;
    int ret;

    i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
    if (!i2s)
        return -ENOMEM;

    i2s->regs = devm_platform_ioremap_resource(pdev, 0);
    platform_set_drvdata(pdev, i2s);

    ret = devm_snd_soc_register_component(&pdev->dev,
                                            &my_platform_component,
                                            &my_i2s_dai, 1);
    return ret;
}

Machine Driver 작성

/* sound/soc/fsl/imx-wm8731.c 스타일 */
static struct snd_soc_dai_link my_board_dai_link = {
    .name = "WM8731",
    .stream_name = "WM8731 HiFi",
    .cpus = &(struct snd_soc_dai_link_component){
        .dai_name = "my-i2s-dai",
    },
    .num_cpus = 1,
    .codecs = &(struct snd_soc_dai_link_component){
        .dai_name = "wm8731-hifi",
    },
    .num_codecs = 1,
    .platforms = &(struct snd_soc_dai_link_component){
        .name = "my-platform",
    },
    .num_platforms = 1,
    .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
               SND_SOC_DAIFMT_CBS_CFS, /* I2S, CPU is master */
};

static struct snd_soc_card my_board_card = {
    .name = "MyBoard-WM8731",
    .owner = THIS_MODULE,
    .dai_link = &my_board_dai_link,
    .num_links = 1,
};

static int my_board_probe(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    struct device_node *cpu_np, *codec_np;

    /* Device Tree에서 DAI 노드 읽기 */
    cpu_np = of_parse_phandle(np, "cpu-dai", 0);
    codec_np = of_parse_phandle(np, "audio-codec", 0);

    my_board_dai_link.cpus->of_node = cpu_np;
    my_board_dai_link.codecs->of_node = codec_np;
    my_board_dai_link.platforms->of_node = cpu_np;

    my_board_card.dev = &pdev->dev;

    return devm_snd_soc_register_card(&pdev->dev, &my_board_card);
}

static const struct of_device_id my_board_dt_ids[] = {
    { .compatible = "myvendor,myboard-audio", },
    { }
};

static struct platform_driver my_board_driver = {
    .driver = {
        .name = "myboard-audio",
        .of_match_table = my_board_dt_ids,
    },
    .probe = my_board_probe,
};
module_platform_driver(my_board_driver);

DAI 포맷

포맷 설명 용도
SND_SOC_DAIFMT_I2S I2S (Philips) 가장 일반적인 스테레오 포맷
SND_SOC_DAIFMT_LEFT_J Left Justified MSB가 LRCLK 엣지에 정렬
SND_SOC_DAIFMT_RIGHT_J Right Justified LSB가 LRCLK 엣지에 정렬
SND_SOC_DAIFMT_DSP_A DSP Mode A 1 BCLK 지연
SND_SOC_DAIFMT_DSP_B DSP Mode B 지연 없음
SND_SOC_DAIFMT_AC97 AC'97 레거시 AC'97 버스
SND_SOC_DAIFMT_PDM PDM (Pulse Density Modulation) MEMS 마이크

Device Tree Binding 예제

/* arch/arm/boot/dts/myboard.dts */
i2s0: i2s@40030000 {
    compatible = "myvendor,my-i2s";
    reg = <0x40030000 0x1000>;
    interrupts = <45>;
    clocks = <&clk_i2s>;
    dmas = <&dma 10>, <&dma 11>;
    dma-names = "tx", "rx";
    #sound-dai-cells = <0>;
};

wm8731: codec@1a {
    compatible = "wlf,wm8731";
    reg = <0x1a>;
    #sound-dai-cells = <0>;
    clocks = <&clk_mclk>;
    clock-names = "mclk";
};

sound {
    compatible = "myvendor,myboard-audio";
    cpu-dai = <&i2s0>;
    audio-codec = <&wm8731>;
};

또는 simple-audio-card 사용:

sound {
    compatible = "simple-audio-card";
    simple-audio-card,name = "MyBoard Audio";
    simple-audio-card,format = "i2s";
    simple-audio-card,mclk-fs = <256>;

    simple-audio-card,cpu {
        sound-dai = <&i2s0>;
    };

    simple-audio-card,codec {
        sound-dai = <&wm8731>;
    };
};

DAPM 심화

DAPM (Dynamic Audio Power Management)는 ASoC의 핵심 기능으로, 오디오 경로를 기반으로 전원 도메인을 자동으로 켜고 끄는 지능형 전원 관리 시스템입니다. Widget 그래프와 오디오 경로 추적으로 사용하지 않는 컴포넌트의 전원을 자동 차단하여 전력 소비를 최소화합니다.

DAPM Widget 타입

Widget 타입 설명 예시
SND_SOC_DAPM_INPUT 입력 핀 (외부) Mic, Line In
SND_SOC_DAPM_OUTPUT 출력 핀 (외부) Headphone, Speaker
SND_SOC_DAPM_MIC 마이크 바이어스 포함 Internal Mic
SND_SOC_DAPM_HP 헤드폰 출력 Headphone Jack
SND_SOC_DAPM_SPK 스피커 출력 External Speaker
SND_SOC_DAPM_LINE 라인 입출력 Line Out, Line In
SND_SOC_DAPM_ADC Analog-to-Digital Converter Left ADC, Right ADC
SND_SOC_DAPM_DAC Digital-to-Analog Converter Left DAC, Right DAC
SND_SOC_DAPM_MIXER 믹서 (여러 입력 합성) Output Mixer
SND_SOC_DAPM_MUX 멀티플렉서 (하나 선택) Input Mux (Line/Mic/CD)
SND_SOC_DAPM_DEMUX 디멀티플렉서 Output Router
SND_SOC_DAPM_PGA Programmable Gain Amplifier Mic Boost, Volume Control
SND_SOC_DAPM_SUPPLY 전원 공급 (다른 widget 의존) VREF, Clock, Bias
SND_SOC_DAPM_REGULATOR_SUPPLY Regulator 전원 AVDD, DVDD
SND_SOC_DAPM_CLOCK_SUPPLY 클럭 소스 MCLK
SND_SOC_DAPM_AIF_IN 오디오 인터페이스 입력 I2S RX
SND_SOC_DAPM_AIF_OUT 오디오 인터페이스 출력 I2S TX
SND_SOC_DAPM_PRE 스트림 시작 전 이벤트 Pre-charge circuit
SND_SOC_DAPM_POST 스트림 종료 후 이벤트 Pop noise reduction
SND_SOC_DAPM_SWITCH On/Off 스위치 Capture Switch

DAPM Route 정의

Route는 widget 간 오디오 경로를 정의합니다:

static const struct snd_soc_dapm_widget wm8731_widgets[] = {
    /* Outputs */
    SND_SOC_DAPM_OUTPUT("LHPOUT"),
    SND_SOC_DAPM_OUTPUT("RHPOUT"),
    SND_SOC_DAPM_OUTPUT("LOUT"),
    SND_SOC_DAPM_OUTPUT("ROUT"),

    /* Inputs */
    SND_SOC_DAPM_INPUT("LLINEIN"),
    SND_SOC_DAPM_INPUT("RLINEIN"),
    SND_SOC_DAPM_INPUT("MICIN"),

    /* DACs */
    SND_SOC_DAPM_DAC("DAC", "Playback", WM8731_PWR, 3, 1),

    /* ADCs */
    SND_SOC_DAPM_ADC("ADC", "Capture", WM8731_PWR, 2, 1),

    /* Mixers */
    SND_SOC_DAPM_MIXER("Output Mixer", WM8731_PWR, 4, 1, NULL, 0),

    /* Input Mux */
    SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &wm8731_input_mux),

    /* Mic Bias */
    SND_SOC_DAPM_SUPPLY("Mic Bias", WM8731_PWR, 1, 0, NULL, 0),
};

static const struct snd_soc_dapm_route wm8731_routes[] = {
    /* Playback path */
    {"Output Mixer", NULL, "DAC"},
    {"LHPOUT", NULL, "Output Mixer"},
    {"RHPOUT", NULL, "Output Mixer"},
    {"LOUT", NULL, "Output Mixer"},
    {"ROUT", NULL, "Output Mixer"},

    /* Capture path */
    {"Input Mux", "Line", "LLINEIN"},
    {"Input Mux", "Line", "RLINEIN"},
    {"Input Mux", "Mic", "MICIN"},
    {"ADC", NULL, "Input Mux"},

    /* Mic bias */
    {"MICIN", NULL, "Mic Bias"},
};

DAPM 전원 시퀀싱 이벤트

Widget은 전원 상태 변화 시 이벤트 콜백을 받을 수 있습니다:

static int my_amp_event(struct snd_soc_dapm_widget *w,
                       struct snd_kcontrol *kcontrol,
                       int event)
{
    struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);

    switch (event) {
    case SND_SOC_DAPM_PRE_PMU:  /* Power-up 전 */
        dev_dbg(component->dev, "Amp powering up\\n");
        /* Pre-charge 회로 활성화 */
        break;

    case SND_SOC_DAPM_POST_PMU: /* Power-up 후 */
        /* 앰프 언뮤트 (pop noise 방지 위해 지연) */
        msleep(50);
        snd_soc_component_update_bits(component, AMP_CTRL, MUTE_BIT, 0);
        break;

    case SND_SOC_DAPM_PRE_PMD:  /* Power-down 전 */
        /* 앰프 뮤트 (pop noise 방지) */
        snd_soc_component_update_bits(component, AMP_CTRL, MUTE_BIT, MUTE_BIT);
        msleep(50);
        break;

    case SND_SOC_DAPM_POST_PMD: /* Power-down 후 */
        dev_dbg(component->dev, "Amp powered down\\n");
        break;
    }

    return 0;
}

SND_SOC_DAPM_PGA_E("Headphone Amp", SND_SOC_NOPM, 0, 0, NULL, 0,
                    my_amp_event,
                    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
                    SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),

DAPM Controls (동적 경로)

DAPM mixer와 mux는 오디오 경로를 동적으로 변경할 수 있습니다:

/* Input Mux 정의 */
static const char * const input_mux_texts[] = {
    "Line", "Mic", "CD", "Aux"
};

static SOC_ENUM_SINGLE_DECL(input_mux_enum, INPUT_MUX_REG, 0,
                          input_mux_texts);

static const struct snd_kcontrol_new input_mux_control =
    SOC_DAPM_ENUM("Route", input_mux_enum);

SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &input_mux_control),

/* Mixer 정의 (여러 입력 합성) */
static const struct snd_kcontrol_new output_mixer_controls[] = {
    SOC_DAPM_SINGLE("DAC Switch", MIXER_REG, 0, 1, 0),
    SOC_DAPM_SINGLE("Line Bypass Switch", MIXER_REG, 1, 1, 0),
    SOC_DAPM_SINGLE("Mic Sidetone Switch", MIXER_REG, 2, 1, 0),
};

SND_SOC_DAPM_MIXER("Output Mixer", SND_SOC_NOPM, 0, 0,
                     output_mixer_controls,
                     ARRAY_SIZE(output_mixer_controls)),

DAPM 그래프 시각화

Capture Path LLINEIN RLINEIN MICIN Mic Bias (SUPPLY) Input Mux Line / Mic / CD (MUX) Input PGA (Volume) ADC (A/D Conv) AIF IN (I2S to CPU) Playback Path AIF OUT (I2S from CPU) DAC (D/A Conv) Output Mixer DAC + Bypass + Sidetone (MIXER) Line Bypass HP Amp (PGA) SPK Amp (PGA) LHPOUT (Headphone) RHPOUT (Headphone) LSPK (Speaker) RSPK (Speaker) DAPM은 활성 경로의 모든 widget만 전원 ON (사용 안 하는 DAC/ADC/Amp 자동 OFF → 전력 절감) Powered ON Powered OFF External I/O Audio Interface
자동 전원 관리: 사용자가 재생 또는 녹음을 시작하면 DAPM은 활성 경로를 추적하여 필요한 widget만 전원을 켭니다. 예: 헤드폰 재생 시 DAC → Output Mixer → HP Amp → LHPOUT/RHPOUT 경로의 widget만 ON, 스피커 앰프와 ADC는 OFF 상태 유지.