ARTICLE · 1253912
安卓驱动[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 | ||
vendor/.../hardware/audio/mt8168/Android.mk | ||
.../aud_drv/AudioALSAPlaybackHandlerHDMI.cpp.../aud_drv/AudioALSAStreamManager.cpp | HMDI_PLayback | |
kernel-5.10/sound/soc/mediatek/mt8168/mt8168-p1.ckernel-5.10/arch/arm64/boot/dts/mediatek/_LVDS.dts |
顺带还修掉了一个与音频无关但很要命的问题: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 里有专门的 HDMIDAI;内核 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 分组):
所以:只要能让 DL2 的数据从 I2S3 出去、并让 IT66122 打开音频输出,就有声音。
3. 改动清单(总表)
device//_LVDS/audio_policy_config/audio_policy_configuration.xml | fast 混音口;加 hdmi stereo + HDMI Out + route | ||
vendor/.../hardware/audio/mt8168/Android.mk | -DMTK_EXTERNAL_HDMI_I2S3 | ||
.../aud_drv/AudioALSAPlaybackHandlerHDMI.cpp | O00 I07 / O01 I08 开关 | HMDI_PLayback,改为驱动 DL2→I2S3 | |
.../aud_drv/AudioALSAStreamManager.cpp | |||
kernel-5.10/sound/soc/mediatek/mt8168/mt8168-p1.c | SND_SOC_DAPM_OUTPUT("HDMI I2S3 Out") + 解析 DT 路由 | ||
kernel-5.10/arch/arm64/boot/dts/mediatek/_LVDS.dts | mediatek,hdmi-audio-routing = "HDMI I2S3 Out", "2ND I2S Playback"; | ||
.dts | LEVEL_LOW → EDGE_FALLING |
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"
为什么
删 fast:fast 走的是 DL2 / MultiMedia2 这条 FE。我们要拿 DL2 去喂 HDMI,同一路 FE 不能既做低延迟又做 HDMI,所以把 fast去掉(低延迟请求自动回落到 primary)。 加 HDMI Out:AudioPolicyManager只认识策略里声明过的设备。没有这段,就算内核里 switch/hdmi/state=1,框架也没有「HDMI 输出」可以选。 只用 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_I2S3unsigned int pcmindex = AudioALSADeviceParser::getInstance()->GetPcmIndexByString(keypcmDl2Meida);unsigned int cardindex = AudioALSADeviceParser::getInstance()->GetCardIndexByString(keypcmDl2Meida);#elseunsigned int pcmindex = AudioALSADeviceParser::getInstance()->GetPcmIndexByString(keypcmHDMI);#endif...#ifdef MTK_EXTERNAL_HDMI_I2S3if (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;}#elseopenPcmDriver(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 | adb shell cat /vendor/etc/audio_policy_configuration.xml | |
pcm_open | cat /proc/asound/pcmkeypcm* 字符串 | |
status 是 RUNNING,但无声 | ls .../dapm/HDMI I2S3 Out 在不在;tinymix 看 O00 I07 Switch 是否为 1 | |
HDMITX_EnableAudioOutput(...) 的 fs 与 PCM 实际 rate | ||
hdmitx_SetupAFE) | HDMITX_SetAudioOutput(),改成幂等/只在参数变化时调 | |
grep it66121-hpd /proc/interrupts | ||
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.mkvendor/mediatek/proprietary/hardware/audio/mt8168/aud_drv/AudioALSAPlaybackHandlerHDMI.cppvendor/mediatek/proprietary/hardware/audio/mt8168/aud_drv/AudioALSAStreamManager.cppvendor/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.csvkernel-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 ──► 电视喇叭















































































