기술 노트 · 오디오 & 음성

나만의 CODEC 드라이버 작성 — Part 2: DAC 재생

하드웨어 측에서 DAC를 앰프에 연결하면 그냥 재생됩니다 — 드라이버가 필요 없습니다. 그렇다면 소프트웨어 측에서 소리를 내기 위해 무엇이 필요할까요? Luo Zong (罗总), Rockchip — "罗总开发笔记" WeChat의 글을 번역·주석 달아 정리했습니다.

Attribution & authorization
Original author: Luo Zong (罗总), Rockchip · Published on the WeChat public account "罗总开发笔记" on 2026-08-25.
Original URL: https://mp.weixin.qq.com/s/CqBo2NonYYGqC99ojL--4Q
Republished here with authorization, translated into English and annotated by Bestom. This is Part 2 of the series "How to write your own CODEC driver" — Part 1 (PDM microphone capture) is here.

DAC를 연결하면 그냥 재생되는데, 소프트웨어는 무엇을 해야 할까?

지난 글은 PDM 마이크를 다뤘습니다 — 칩에 제어가 필요 없고 DTS만 설정하면 녹음됩니다. 이번에는 방향을 뒤집어 재생(playback)입니다.

많은 DAC 칩도 똑같이 "손을 안 대도 됩니다": 제어 인터페이스가 필요 없고 I2S 데이터 선과 전원만 연결하면 소리가 납니다. 하드웨어 엔지니어들이 좋아하는 방식 — 공들이지 않아도 됩니다. 그렇다면 Linux에서 이런 DAC를 dummy-codec과 짝지으면 오디오 재생을 위해 드라이버 코드를 몇 줄이나 써야 할까요?

답은 지난번과 같습니다: 0줄. 다만 이번에는 DAI 방향을 뒤집습니다.

Bestom 주석: 이 글은 우리의 오디오 SoM 작업과 자연스레 이어집니다 — 솔루션 → 오디오 & 음성과 스마트 패널·사운드바용 M08D 모듈(RK2108D + RK962)을 참고하세요. Luo Zong이 설명하는 dummy-codec 패턴은 실제 codec 드라이버가 올라오기 전, 초기 기동(bring-up)과 고객 레퍼런스 설계를 위한 오디오 경로를 세우는 정확한 방법입니다.

핵심 포인트

  1. 재생(playback) 방향 = DAI가 .playback을 선언하고, 사운드 카드는 /dev/snd/pcmC0D0p를 노출합니다.
  2. PCM5102A 같은 DAC는 내장 PLL이 있어 BCLK에서 자체 클럭을 파생하므로 MCLK가 필요 없습니다. 진짜 제어 없는 재생.
  3. dummy-codec.playback capability 선언이 이 글의 주제입니다. DTS는 여전히 동일한 3피스 조합. aplay로 바로 재생됩니다.

1. 먼저 방향: PLAYBACK vs CAPTURE

Part 1(PDM 마이크)은 capture 방향 — 데이터가 SoC 안으로 흐름 — 이고 사운드 카드는 pcmC0D0c를 노출했습니다. 이번에는 반대편인 playback — 데이터가 SoC 밖으로 흐름 — 으로 뒤집습니다:

Playback (PLAYBACK) Capture (CAPTURE) ┌──────────────┐ ┌──────────────┐ │ Speaker / │ │ Microphone │ │ Headphone │ │ │ ▲ │ │ ▼ │ DAC │ │ ADC │ ▲ │ │ ▼ │ I2S data │ │ PDM / I2S │ │ ▼ │ ▼ SoC audio IF ← data OUT SoC audio IF ← data IN

Part 1의 DAI capability 요약을 떠올려 보세요:

struct snd_soc_dai_driver {
        .playback = { ... },    /* declared = this stream exists = /dev/snd/pcmC0D0p */
        .capture  = { ... },    /* declared = this stream exists = /dev/snd/pcmC0D0c */
};

2. 주인공: 전혀 제어가 필요 없는 DAC

TI의 PCM5102A — HiFi 세계에서 친숙한 얼굴로 많은 오디오 보드에 쓰입니다. 이 칩의 하드웨어 "요구 사항 목록"을 보세요:

RequirementNeeded?
I2S data lines (BCLK / LRCK / DATA)Yes — 3 wires
MCLK master clockNo — internal PLL derives the clock from BCLK
I2C / SPI control interfaceNo — the chip has no such pins
Register configurationNo — there are no registers

전원을 켜고 I2S 데이터를 넣으면 소리가 납니다. PDM 마이크보다 더 손을 안 대도 됩니다 — "클럭을 줘야 해"라고 생각할 필요조차 없습니다(BCLK는 I2S에 당연히 딸려옵니다).

커널에는 이 칩 전용 드라이버 sound/soc/codecs/pcm5102a.c가 이미 있으며 총 57줄입니다. 핵심은 단일 capability 선언 하나:

static struct snd_soc_dai_driver pcm5102a_dai = {
  .name = "pcm5102a-hifi",
  .playback = {                              /* NOTE: playback only! */
    .channels_min = 2,
    .channels_max = 2,
    .rates = SNDRV_PCM_RATE_8000_384000,
    .formats = SNDRV_PCM_FMTBIT_S16_LE |
         SNDRV_PCM_FMTBIT_S24_LE |
         SNDRV_PCM_FMTBIT_S32_LE
  },
};

static int pcm5102a_probe(struct platform_device *pdev)
{
  return devm_snd_soc_register_component(&pdev->dev, &soc_component_dev_pcm5102a,
      &pcm5102a_dai, 1);
}

끝입니다: "채널 2개 재생 가능"을 선언한 뒤 등록. 하지만 그것조차 필요 없습니다dummy-codec.playback.capture를 모두 선언하므로 PCM5102A의 슬롯을 충분히 커버합니다. 어차피 칩에 제어가 필요 없으니 codec 드라이버는 그저 자리 채움일 뿐 — 어떤 자리 채움도 무방합니다.

dummy_dai.playback 부분

Part 1은 dummy-codec.c.capture(60–70행)를 봤습니다. 이 글은 대칭적으로 그 playback 선언(소스 49–59행)을 봅니다:

struct snd_soc_dai_driver dummy_dai = {
  .name = "dummy_codec",
  .playback = {                           /* ← Playback capability: this article's focus */
    .stream_name = "Dummy Playback",  /* stream name */
    .channels_min = 1,                /* min 1 channel */
    .channels_max = 384,              /* max 384 channels */
    .rates = SNDRV_PCM_RATE_CONTINUOUS,  /* any sample rate */
    .formats = (SNDRV_PCM_FMTBIT_S8 |
          SNDRV_PCM_FMTBIT_S16_LE |
          SNDRV_PCM_FMTBIT_S20_3LE |
          SNDRV_PCM_FMTBIT_S24_LE |
          SNDRV_PCM_FMTBIT_S32_LE),  /* bit depth S8 ~ S32 */
  },
  ...                                     /* .capture was covered in Part 1 */
};

.capture와 글자 하나하나 대칭이며, stream_name"Dummy Playback"으로 바뀝니다. 이 블록이 있으면 pcmC0D0p가 만들어지고 aplay가 열 수 있습니다.

PCM5102A 선언과 비교하면 차이는 "내용물"뿐입니다:

pcm5102a_daidummy_dai
playback2 channels, 8k–384k (per the datasheet)1–384 channels, any sample rate (no real chip — overshoot is free)
capturenoneyes (reserved for capture scenarios)

실제 칩 드라이버의 capability 선언은 데이터시트를 따라야 합니다 — 과하게 선언하면 애플리케이션이 열릴 때 크래시합니다. dummy는 실제 칩과 연결되지 않으므로 과하게 선언해도 괜찮습니다. 실제 제약은 CPU DAI 쪽에서 강제합니다.


3. DTS 설정: 여전히 3피스 조합

Part 1의 PDM과 같은 레시피, 다만 인터페이스와 방향이 다릅니다:

/* ① Codec DAI: dummy-codec placeholder (same driver as Part 1) */
dummy_codec: dummy-codec {
  status = "okay";
  compatible = "rockchip,dummy-codec";
  #sound-dai-cells = <0>;
};

/* ② CPU DAI: enable the I2S controller + pin mux */
&i2s0 {
  status = "okay";
  pinctrl-names = "default";
  pinctrl-0 = <&i2s0m0_sclk &i2s0m0_lrck
         &i2s0m0_sdi0 &i2s0m0_sdo0>;  /* BCLK / LRCK / data lines */
};

/* ③ Machine: assemble the sound card */
dummy_sound: dummy-sound {
  status = "okay";
  compatible = "rockchip,multicodecs-card";
  rockchip,card-name = "rockchip-dummy";    /* sound-card name */
  rockchip,format = "i2s";                  /* data format */
  rockchip,cpu = <&i2s0>;                   /* CPU DAI = I2S controller */
  rockchip,codec = <&dummy_codec>;          /* Codec DAI = dummy */
};

Part 1 대비 두 가지가 바뀝니다:

Part 1 (PDM capture)This article (DAC playback)
CPU DAI&pdm&i2s0
Data directioncapture (mic → SoC)playback (SoC → DAC)

3피스 구조는 완전히 동일합니다 — 그것이 프레임워크의 가치입니다. 방향을 바꾸고 인터페이스를 바꿔도 레시피는 그대로.


4. 재생 검증

# 1. Sound card registered
cat /proc/asound/cards
#  0 [dummy         ]: rockchip-dummy

# 2. Device node — this time it's the playback (p)
ls /dev/snd/
#  controlC0  pcmC0D0p  timer

# 3. Play
aplay -D hw:0,0 -c 2 -r 48000 -f S16_LE /tmp/sine.wav
# Android: tinyplay /tmp/sine.wav

# 4. Check the file parameters match
ls -l /tmp/sine.wav
# 48000Hz × 2ch × 2 bytes, matches aplay's arguments

데이터 흐름은 Part 1의 정확히 반대입니다:

/tmp/sine.wav │ aplay writes /dev/snd/pcmC0D0p ▼ ring buffer in memory │ DMA moves it ▼ I2S controller (rockchip-i2s, outputs BCLK/LRCK + data) │ 3 wires ▼ PCM5102A (internal PLL self-clocks → DAC → analog) │ ▼ Speaker

5. 요약: 여전히 드라이버 코드 0줄

두 글을 통틀어 capture 하나와 playback 하나, Part 1의 .capture와 이 글의 .playback을 다뤘습니다 — dummy-codec의 capability 선언이 이제 완전히 설명되었습니다. 나머지 코드(startup / probe)는 건드리지 않았고, 여러분이 쓴 것은 DTS뿐입니다.

하지만 모든 하드웨어가 이렇게 쉽지는 않습니다:

이것들이 바로 드라이버가 신호로 "무언가를 하는" 곳입니다. 다음 글은 답합니다: MCLK, SPK-CTL, RESET은 코드의 어디에 있는가? dummy-codec.c의 남은 startupprobe가 드디어 무대에 오릅니다.

Bestom 주석 (실제 드라이버로의 전환): Luo Zong이 설명하는 "쉬운 경로"는 기판 기동(bring-up) 초기에 오디오를 꺼내는 가장 빠른 방법입니다. 실제 codec(PCM51xx, ES72xx, RK3308 내장 codec 등)으로 넘어가면 동일한 3피스 DTS는 그대로 — 바뀌는 것은 MCLK 게이팅, RESET, 보드 레벨 GPIO를 처리해야 하는 codec 드라이버입니다. Bestom은 RK3308(전용 오디오 SoC)과 스마트 패널·사운드바용 M08D 모듈(RK2108D + RK962)에서 dummy-codec에서 귀사의 양산 codec 드라이버로 가교하는 것을 도울 수 있습니다.

시리즈 미리보기 — "How to write your own CODEC driver"

이 글은 시리즈의 Part 2입니다. 소스 코드는 Rockchip BSP kernel 6.1 (v6.1.162) 기준이며, 하드웨어부터 고급 DAPM 아키텍처까지 안내합니다.

참조한 소스 파일: 비교용 sound/soc/codecs/pcm5102a.cdummy-codec.c.