夜雨聆风学习资料网

ARTICLE · 1253912

安卓驱动[13] (MT8168 + 外挂 IT66122) HDMI 音频移植说明

安卓驱动[13] (MT8168 + 外挂 IT66122) HDMI 音频移植说明

面向:有一点 Linux/Android 基础,但没做过音频移植的同学。目标:不仅知道「改哪里」,还要知道「为什么这么改」「怎么验证」「错了怎么排查」。


0. 一句话总结

MTK 的音频框架只为自家内置 HDMI 设计(依赖 /dev/hdmitx 和 AFE 的 HDMI DAI)。本板是外挂 IT66122,音频只能走I2S3四根线进芯片。所以必须同时在4 个层次上补齐:

层次
文件
作用
① 音频策略
device//_LVDS/audio_policy_config/audio_policy_configuration.xml
让 Android 知道有「HDMI 输出」这个设备
② HAL 编译开关
vendor/.../hardware/audio/mt8168/Android.mk
打开外挂 HDMI 的代码分支
③ HAL 播放路径
.../aud_drv/AudioALSAPlaybackHandlerHDMI.cpp
.../aud_drv/AudioALSAStreamManager.cpp
让 HDMI 走 DL2 + I2S3,而不是不存在的 HMDI_PLayback
④ 内核 DAPM
kernel-5.10/sound/soc/mediatek/mt8168/mt8168-p1.c
kernel-5.10/arch/arm64/boot/dts/mediatek/_LVDS.dts
给 I2S3 播放路径补一个「终点」,DPCM 才会真正启动后端

顺带还修掉了一个与音频无关但很要命的问题:IT66122 的 HPD 中断风暴(见 4.7)。


1. 先搞清楚:正常情况下声音是怎么从 App 流到喇叭的

App │ AudioTrack ▼AudioFlinger / AudioPolicyManager          ← 「走哪个设备」由策略决定 │                                            策略来自 audio_policy_configuration.xml ▼Audio HAL(vendor/.../audio/mt8168)       ← 打开哪个 PCM、设置哪些 mixer 开关 │ ▼ALSA / ASoC ├── FE(Front End,前端):DL1 / DL2 …     ← Android 看见的 PCM 设备 │        │ DPCM(Dynamic PCM)动态连接 │        ▼ └── BE(Back End,后端):I2S / TDM / ADDA  ← 真正接到硬件的口          │ DAPM(动态电源管理)负责「哪里通、哪里断电」          ▼       AFE(Audio Front End,MT8168 的音频控制器)          ▼       I2S 引脚 → codec / HDMI 芯片

几个必须记住的名词:

  • FE / BE:FE 是给应用用的「虚拟声卡」,BE 是真正接硬件的通路。两者用 DPCM 动态连。
  • DAPM:ASoC 的音频路径电源管理。路径不通 = 不供电 = 后端 DAI 根本不会启动(这点是本文档的核心)。
  • PCM:/dev/snd/pcmC0Dxxp,一个 PCM 对应一条 FE。名字会出现在 /proc/asound/pcm。
  • mixer / kcontrol:ALSA 控件,名字形如 "O00 I07 Switch",HAL 用它来「手动接线」。

2. 本板为什么特殊

内置 HDMI 的做法(MTK 参考设计)

  • AFE 里有专门的 HDMI DAI;
  • 内核 mtk_hdmi.c 创建 /dev/hdmitx,并注册 hdmi-codec;
  • HAL 的 HDMITxController 通过 ioctl 配置它;
  • HAL 找 PCM 的名字是 HMDI_PLayback(注意:MTK 自己拼错了,是 HMDI 不是 HDMI,见 
    audio/common/aud_drv/AudioUtility.cpp: 
    keypcmHDMI = String8("HMDI_PLayback"))。

本板(_LVDS)

  • 没有 内置 HDMI,也没有 /dev/hdmitx;
  • 视频走外挂 IT66122(DRM bridge,drivers/gpu/drm/bridge/it66121);
  • 音频只能:AFE 的 2ND I2S(寄存器 AFE_I2S_CON3)→ I2S3 引脚 → IT66122 的 I2S 输入。

引脚(来自《-gpio-list.xlsx》HDMI 分组):

MT8168
网络名
说明
GPIO72
I2S3_BCK
位时钟
GPIO73
I2S3_LRCK
帧时钟
GPIO74
I2S3_MCK
主时钟(这板接了,很多板省掉)
GPIO75
I2S3_DO
数据输出

所以:只要能让 DL2 的数据从 I2S3 出去、并让 IT66122 打开音频输出,就有声音。


3. 改动清单(总表)

#
文件
改什么
一句话理由
1
device//_LVDS/audio_policy_config/audio_policy_configuration.xml
删 fast 混音口;加 hdmi stereo + HDMI Out + route
腾出 DL2 给 HDMI,并让策略认识 HDMI 设备
2
vendor/.../hardware/audio/mt8168/Android.mk-DMTK_EXTERNAL_HDMI_I2S3
打开外挂 HDMI 的编译分支
3
.../aud_drv/AudioALSAPlaybackHandlerHDMI.cpp
改用 DL2 PCM + 设置 O00 I07 / O01 I08 开关
HDMI 不再依赖不存在的 HMDI_PLayback,改为驱动 DL2→I2S3
4
.../aud_drv/AudioALSAStreamManager.cpp
HDMI 使能时不再打开 legacy loopback PCM
那条 PCM 不存在,打开必失败
5
kernel-5.10/sound/soc/mediatek/mt8168/mt8168-p1.c
加 SND_SOC_DAPM_OUTPUT("HDMI I2S3 Out") + 解析 DT 路由
给 I2S3 播放路径一个「终点」,否则 BE 永不启动
6
kernel-5.10/arch/arm64/boot/dts/mediatek/_LVDS.dtsmediatek,hdmi-audio-routing = "HDMI I2S3 Out", "2ND I2S Playback";
把上面的终点接起来
7
同上 .dts
HPD 中断 LEVEL_LOW → EDGE_FALLING
修 i2c1 中断风暴(顺带)

4. 逐项详解

4.1 音频策略:audio_policy_configuration.xml

位置:

device//_LVDS/audio_policy_config/audio_policy_configuration.xml

改了什么

<!-- ① 删掉原来的 fast 混音口(它占用 DL2/MultiMedia2) -->- <mixPortname="fast"role="source"flags="AUDIO_OUTPUT_FLAG_FAST">-     <profile...samplingRates="44100 48000".../>- </mixPort>+ <!-- ② 新增 HDMI 混音口:只声明 48k / 16bit / 立体声 -->+ <mixPortname="hdmi stereo"role="source">+     <profilename=""format="AUDIO_FORMAT_PCM_16_BIT"+              samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>+ </mixPort>+ <!-- ③ 新增 HDMI 输出设备 -->+ <devicePorttagName="HDMI Out"type="AUDIO_DEVICE_OUT_AUX_DIGITAL"role="sink">+     <profilename=""format="AUDIO_FORMAT_PCM_16_BIT"+              samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>+ </devicePort>+ <!-- ④ 把两者连起来 -->+ <routetype="mix"sink="HDMI Out"sources="hdmi stereo"/><!-- ⑤ 原来引用 fast 的几条 route 也要去掉 fast -->- sources="primary output,fast,FM Tuner In"+ sources="primary output,FM Tuner In"

为什么

  1. 删 fast:fast 走的是 DL2 / MultiMedia2 这条 FE。我们要拿 DL2 去喂 HDMI,同一路 FE 不能既做低延迟又做 HDMI,所以把 fast去掉(低延迟请求自动回落到 primary)。
  2. 加 HDMI Out:AudioPolicyManager只认识策略里声明过的设备。没有这段,就算内核里 switch/hdmi/state=1,框架也没有「HDMI 输出」可以选。
  3. 只用 48k / 16bit / 立体声:IT66122 侧我们只按 48k 立体声调通,写宽一点会让框架送来 44.1k/24bit,反而容易出问题。先窄后宽是移植的基本策略。

怎么验证

dumpsys media.audio_policy | grep -A8 ”Available output devices”# 插着 HDMI 时应出现: type: AUDIO_DEVICE_OUT_HDMI

注意:AUDIO_DEVICE_OUT_AUX_DIGITAL 和 AUDIO_DEVICE_OUT_HDMI 在 Android 里是同一个值(0x400),写哪个都能被识别,不用纠结。


4.2 HAL 编译开关:Android.mk

位置:

vendor/mediatek/proprietary/hardware/audio/mt8168/Android.mk

# _LVDS uses external HDMI over I2S3 rather than the TDM engine.ifeq ($(TARGET_DEVICE),_LVDS)LOCAL_CFLAGS += -DMTK_EXTERNAL_HDMI_I2S3else# Force to output clock to HDMI TX before sending real dataLOCAL_CFLAGS += -DMTK_HDMI_FORCE_AUDIO_CLKendif 

为什么:MTK 原来的 MTK_HDMI_FORCE_AUDIO_CLK 是给内置 HDMI用的(先出时钟再出数据)。外挂 I2S 方案不需要这套,反而会被它干扰。用宏隔离,不影响其他项目。


4.3 HAL 播放路径:AudioALSAPlaybackHandlerHDMI.cpp

位置:

vendor/mediatek/proprietary/hardware/audio/mt8168/aud_drv/AudioALSAPlaybackHandlerHDMI.cpp

#ifdef MTK_EXTERNAL_HDMI_I2S3// DL2 (I07/I08) feeds the second I2S output, physically I2S3 on this board.staticstatus_tsetExternalHdmiRoute(struct mixer *mixerInstance, bool enable){    struct mixer_ctl *left  = mixer_get_ctl_by_name(mixerInstance, ”O00 I07 Switch”);    struct mixer_ctl *right = mixer_get_ctl_by_name(mixerInstance, ”O01 I08 Switch”);    ...    const int value = enable ? 1 : 0;    mixer_ctl_set_value(left,  0, value);    mixer_ctl_set_value(right, 0, value);}#endif

原理:MT8168 的 AFE 内部是一张「交叉开关」网,DAPM 里这些节点叫 I00..Ixx(输入)、O00..Oxx(输出),并且已经有内置路由:

/* mt8168-afe-pcm.c */{”I07”, NULL, ”DL2”},{”I08”, NULL, ”DL2”},

也就是说DL2 的数据已经进了 I07/I08,但没有接到 O00/O01(I2S 输出口)。HAL 打开这两个开关,就等于把 DL2 → I07/I08 → O00/O01 → I2S 输出 这条线接通。

为什么用 mixer 而不是 DAPM route?因为这里需要「按需接线」:只有 HDMI 播放时才接通,否则喇叭播放也会被串到 I2S 上。

#ifdef MTK_EXTERNAL_HDMI_I2S3    unsigned int pcmindex  = AudioALSADeviceParser::getInstance()->GetPcmIndexByString(keypcmDl2Meida);    unsigned int cardindex = AudioALSADeviceParser::getInstance()->GetCardIndexByString(keypcmDl2Meida);#else    unsigned int pcmindex  = AudioALSADeviceParser::getInstance()->GetPcmIndexByString(keypcmHDMI);#endif...#ifdef MTK_EXTERNAL_HDMI_I2S3    if (setExternalHdmiRoute(mMixer, true) != NO_ERROR) { ... return NO_INIT; }    mPcm = pcm_open(cardindex, pcmindex, PCM_OUT | PCM_MONOTONIC, &mConfig);    if (mPcm == NULL || !pcm_is_ready(mPcm) || pcm_prepare(mPcm) != 0) {        ALOGE(”%s(), failed to open DL2 for HDMI: %s”, __FUNCTION__, ...);        closePcmDriver();        setExternalHdmiRoute(mMixer, false);        deinitBitConverter();        return NO_INIT;    }#else    openPcmDriver(pcmindex);#endif

为什么:

  • keypcmHDMI = "HMDI_PLayback"
     是 MTK 内置 HDMI 的 PCM 名,本板内核里根本不存在(它的 PCM 名字由 machine driver 决定)。所以照抄必然 pcm_open 失败。
  • keypcmDl2Meida = "MultiMedia2_PLayback"
    (DL2)在本板是存在的,而且 DL2 已经能通过 I07/I08 送到第二个 I2S —— 正好就是 I2S3。
  • 失败时必须把 mixer 开关关回去,否则下次喇叭播放可能串音。

怎么验证

cat /proc/asound/pcm | grep -i multim# 应有 00-01: MultiMedia2_PLayback ...

4.4 HAL:AudioALSAStreamManager.cpp

#ifdef MTK_EXTERNAL_HDMI_I2S3    // External HDMI is clocked by its active DL2/I2S3 playback stream.    // Keep the legacy TDM PCM free instead of opening it on hotplug.    return NO_ERROR;#endif    ...原有逻辑:热插拔时打开 keypcmHDMI 做 loopback...

为什么:MTK 原来的逻辑是HDMI 一插上就打开一条 loopback PCM,用来「先把时钟打出去,让 HDMI TX 有音频时钟」。本板没有那条 PCM,打开必失败,还会在日志里刷错误、甚至影响后续打开 DL2。既然时钟由 DL2 播放流本身提供,这里直接跳过即可。


4.5 内核:machine driver 补 DAPM 终点

位置:kernel-5.10/sound/soc/mediatek/mt8168/mt8168-p1.c

static const struct snd_soc_dapm_widget mt8168_p1_dapm_widgets[] = {    ...    SND_SOC_DAPM_OUTPUT(”HDMI I2S3 Out”),     /* 新增 */};
/* Boards with external HDMI may terminate the I2S3 playback path. */    if (of_find_property(np, ”mediatek,hdmi-audio-routing”, NULL)) {        ret = snd_soc_of_parse_audio_routing(card, ”mediatek,hdmi-audio-routing”);        if (ret)            return ret;    }

为什么(本节最重要)

ASoC 的 DPCM 里,BE 的播放流会生成一个叫 "" 的 DAPM widget,本板就是2ND I2S Playback。

如果这个 widget 后面没有任何连接(没有 sink),DAPM 会认为「这条路径没人用」,于是不给它上电 —— BE DAI 的 prepare/trigger 根本不会被调用,I2S3 也就没有时钟和数据。

这就是为什么「PCM 打开成功、audio_startup 也跑了,但一点声音都没有」。补一个 HDMI I2S3 Out 作为终点,路径才完整。

用 of_find_property 判断再解析,是为了不影响其他板子——没写这个属性就用默认行为。


4.6 DTS:声明这条路由

位置:kernel-5.10/arch/arm64/boot/dts/mediatek/_LVDS.dts

sound: sound {    compatible = ”mediatek,mt8168-p1”;    mediatek,platform = <&afe>;    mediatek,adsp-platform = <&adsp_pcm>;    /* 左:终点 widget;右:源 widget(BE 的播放流名)*/    mediatek,hdmi-audio-routing = ”HDMI I2S3 Out”,                                  ”2ND I2S Playback”;    ...};

格式说明:snd_soc_of_parse_audio_routing() 期待的是一串"sink, source, sink, source, ..."的字符串,所以第一项是终点、第二项是来源。顺序写反会直接 probe 失败。

怎么验证

cat /sys/kernel/debug/asoc/mt-snd-card/dapm/ 2>/dev/null | tr '\\' '\n' | grep -i hdmi# 看到 HDMI\ I2S3\ Out 说明 widget 建出来了

4.7 顺带修复:IT66122 的 HPD 中断风暴

位置:dts

- interrupts = <68 IRQ_TYPE_LEVEL_LOW>;+ interrupts = <68 IRQ_TYPE_EDGE_FALLING>;

现象:/proc/interrupts 里

85: 275758 ... mt-eint 68 Level it66121-hpd     ← 2 秒涨 335 次

每秒约168 次中断,每次都在线程化 handler 里做 i2c 读 →把 i2c1 总线占死,导致同一总线上的 IT6122(屏)、GT9xx(触摸)全部报 -110 (ETIMEDOUT)。

原因:IT66122 的 INT 是脉冲输出脚,不是电平状态脚;用 LEVEL_LOW 时只要线保持低就一直触发,而驱动并没有把它「清掉」。

改后:Edge,计数停在 0,i2c1 恢复干净。

这条和 HDMI 音频没有直接关系,但如果不修,音频调试时的 i2c 读写会随机失败,表现为「一会儿能读到 chip id、一会儿读不到」,极难排查。


5. 完整验证流程

5.1 编译

./build/build__LVDS.sh  → 1 (only build)  → 1 (all)  → 2 (userdebug)

5.2 烧写(关键!)

必须烧super.img:

out/target/product/_LVDS/merged/super.img

只烧 boot.img 不会有任何效果 —— 因为ite66121_hdmi.ko、mt8168-p1.ko、audio.primary.mt8168.so 都不在 boot 分区里,它们在 vendor_dlkm / vendor 里,而这两个都打包在 super.img。

5.3 五条命令逐层确认

# ① 驱动/模块层:HDMI 芯片与音频 codeccat /proc/modules | grep -i hdmi#   期望: ite66121_hdmi ...# ② ASoC 层:卡、DAI、DAPMcat /proc/asound/cards#   期望: 0 [mtsndcard] : mt-snd-cardls /sys/kernel/debug/asoc/mt-snd-card/dapm/ | tr '\\' '\n' | grep -i hdmi#   期望: HDMI\ I2S3\ Out# ③ 热插拔状态cat /sys/class/switch/hdmi/state# 期望 1(插着线)cat /sys/class/extcon/HDMI_audio_extcon/state# 期望 HDMI=1# ④ 框架层:策略认不认 HDMIdumpsys media.audio_policy | grep -A8 ”Available output devices”#   期望出现: type: AUDIO_DEVICE_OUT_HDMI# ⑤ 播放时:PCM 真的开了吗cat /proc/asound/card0/pcm1p/sub0/status# DL2 = pcmC0D1p#   播放中应为 RUNNING

5.4 出声判断

  • 显示器/电视喇叭有声音 → 完成。
  • 无声但 ⑤ 是 RUNNING → 问题在 IT66122 侧(采样率/位宽/MCLK 配置),查 dmesg | grep -i "HDMITX\|it66121"。

6. 排错速查表

现象
最可能原因
怎么查
Available output devices
 只有 SPEAKER
策略 XML 没生效 / 没进镜像
adb shell cat /vendor/etc/audio_policy_configuration.xml
 看是不是新的
pcm_open
 报 no such device
HAL 用了不存在的 PCM 名
cat /proc/asound/pcm
 对照 keypcm* 字符串
PCM 能开、status 是 RUNNING,但无声
DAPM 路径没闭合 → I2S3 没时钟
ls .../dapm/
 看 HDMI I2S3 Out 在不在;tinymix 看 O00 I07 Switch 是否为 1
声音卡顿/杂音/变调
采样率或位宽不匹配
看芯片配置 HDMITX_EnableAudioOutput(...) 的 fs 与 PCM 实际 rate
一播放 HDMI 视频就黑一下
音频使能时重配了 AFE(hdmitx_SetupAFE)
不要每次 startup 都调 HDMITX_SetAudioOutput(),改成幂等/只在参数变化时调
i2c 读写随机超时
HPD 中断风暴
grep it66121-hpd /proc/interrupts
 看计数涨不涨
改了代码但现象不变
只烧了 boot.img
必须烧 super.img

7. 踩过的坑(血泪经验)

坑 1:只烧 boot.img

内核驱动是模块(.ko),在 vendor_dlkm 里 → 打包进 super.img。只烧 boot 会让人以为「代码没生效」,白白浪费几轮。

坑 2:ko_order_table.csv 改了但模块没进镜像

MTK 的 gki_ko.mk 用

ko_order_table_contents := $(shell cat $(ko_order_table) | cut -d , -f 1-6 ...)

$(shell …)不会给 make 建立文件依赖→ 改 csv 不会触发重新生成。必须删掉 out/.kati_stamp-、out/soong/make_vars-.mk、out/soong/Android-.mk、out/build-.ninja 强制重算。(而且产品目录是 device//_LVDS/,不是 / —— 改错项目同样无效。)

坑 3:在 audio_startup 里调 HDMITX_SetAudioOutput()

HAL 会反复 open/close PCM(开机探测、每次播放),每次调用都会跑 hdmitx_SetupAFE() 重配芯片的模拟前端,把正在输出的 TMDS 打断 → HDMI 画面掉一下。正确做法:只在 hw_params(拿到了真实采样率/位宽)时配一次,并且做成幂等。

坑 4:以为 switch/hdmi 就能让框架认到

MTK 改过 WiredAccessoryManager,它除了标准 switch,还会监听extcon:

WiredAccessoryManager: observing HDMI_audio_extcon

所以驱动的:

extcon_set_state_sync(hdmi_extcon, EXTCON_DISP_HDMI, ...) 才是真正喂给框架的那条路。但另外还要注意 notify 的时机——曾经因为在 power_down() 里把 hdmi_stopping=true 设上,导致 HPD 通知被 continue 跳过,switch/hdmi/state 永远 0。

坑 5:LK 阶段 i2c 是空桩

platform/mt8168/mt_i2c.c 里 i2c_read/i2c_write/i2c_write_read/i2c_hw_init 全是 return 0。LK 阶段读 IT6122/IT66122 的 chip id 永远是 0x00 0x00。要接上同文件里真实的 mtk_i2c_read/write/write_read 才行。

坑 6:GPIO 表里不存在的脚

LK 的面板驱动里曾把 GPIO22 当背光使能、GPIO23 当 BIAS_ENP。但《-gpio-list.xlsx》里:GPIO22 本板不存在,GPIO23 是 WIFI2_EN(USB WiFi 供电)。误拉高 GPIO23 会顺手给 WiFi 模块上电。


8. 相关文件索引

音频策略

  • device//_LVDS/audio_policy_config/audio_policy_configuration.xml

HAL

  • vendor/mediatek/proprietary/hardware/audio/mt8168/Android.mk
  • vendor/mediatek/proprietary/hardware/audio/mt8168/aud_drv/AudioALSAPlaybackHandlerHDMI.cpp
  • vendor/mediatek/proprietary/hardware/audio/mt8168/aud_drv/AudioALSAStreamManager.cpp
  • vendor/mediatek/proprietary/hardware/audio/common/aud_drv/AudioUtility.cpp
    (keypcm* 字符串定义)

内核

  • kernel-5.10/sound/soc/mediatek/mt8168/mt8168-p1.c
    (machine driver / DAPM / 路由)
  • kernel-5.10/sound/soc/mediatek/mt8168/mt8168-afe-pcm.c
    (AFE:I07/I08、CON3、MCLK)
  • kernel-5.10/drivers/gpu/drm/bridge/it66121/hdmi_drv.c
    (IT66122 驱动,HPD/extcon/音频寄存器)
  • kernel-5.10/arch/arm64/boot/dts/mediatek/_LVDS.dts
    (sound 节点、HPD 中断)

构建

  • device//_LVDS/ko_order_table.csv
  • kernel-5.10/arch/arm64/configs/_LVDS_defconfig

9. 一张图记住全部改动

┌──────────────── audio_policy_configuration.xml ───────────────┐   App ──► AudioFlinger                                                        │                   │  (1) 删 fast  (2) 加 hdmi stereo / HDMI Out / route          │                   └──────────────────────────────────────────────────────────────┘                                          │                                          ▼                   ┌──────────────── HAL: AudioALSAPlaybackHandlerHDMI ────────────┐                   │  (3) 打开 MultiMedia2_PLayback(DL2)而不是 HMDI_PLayback      │                   │  (4) mixer 置 ”O00 I07 Switch” / ”O01 I08 Switch” = 1         │                   └──────────────────────────────────────────────────────────────┘                                          │                                          ▼   ALSA:DL2 (FE) ──DPCM──► 2ND I2S (BE) ──DAPM route──► ”HDMI I2S3 Out”                   ▲              (5)(6) mt8168-p1.c + _LVDS.dts                   │  AFE:DL2 → I07/I08 → O00/O01 → CON3 → I2S3 pins                   ▼        GPIO72/73/74/75 ──► IT66122 I2S 输入 ──► HDMI ──► 电视喇叭

相关学习资料