RK3576 HDMI 引脚复用与驱动深度分析
记录一次将 HDMI CEC/SCL/SDA 引脚释放为 GPIO 的实战过程,涉及 Rockchip 平台、Synopsys DW HDMI IP 驱动、设备树 pinctrl 覆盖,以及 TMDS/FRL/DDC/EDID 等概念的梳理。
一、问题背景
硬件设计将 `GPIO4_C0`(SoC 的 `hdmi_tx_cec_m0` 引脚)用作 RK628 芯片的 reset 脚。内核启动时出现 GPIO 争抢错误:
[ 2.658605] rockchip-pinctrl pinctrl: pin gpio4-16 already requested by 27da0000.hdmi;
cannot claim for 2-0050
[ 2.658636] rockchip-pinctrl pinctrl: pin-144 (2-0050) status -22
[ 2.658647] rockchip-pinctrl pinctrl: could not request pin 144 (gpio4-16) from group
rk628-reset on device rockchip-pinctrl
根因:** SoC 的 HDMI 驱动通过 pinctrl 占用了 GPIO4_C0(CEC 功能),rk628 驱动尝试将其用作 reset GPIO 时报 `-EBUSY`。此外硬件还希望将 DDC 引脚(GPIO4_C2、GPIO4_C3)也释放出来做普通 I2C。
二、HDMI 四根引脚详解
RK3576 的 HDMI TX 通过 `hdmi_txm0_pins` pinctrl 组复用以下引脚(均在 GPIO4 bank):
| 引脚 | 编号 | Function | 协议角色 | 能否去掉 |
|---|---|---|---|---|
| GPIO4_C0 | pin-144 | `hdmi_tx_cec_m0` | CEC(消费电子控制) | ✅ 可以 |
| GPIO4_C1 | pin-145 | `hdmi_tx_hpdin_m0` | HPD(热插拔检测) | ❌ 建议保留 |
| GPIO4_C2 | pin-146 | `hdmi_tx_scl` | DDC SCL(EDID I2C 时钟) | ✅ 可以 |
| GPIO4_C3 | pin-147 | `hdmi_tx_sda` | DDC SDA(EDID I2C 数据) | ✅ 可以 |
2.1 CEC — Consumer Electronics Control
CEC 是 HDMI 协议中的**可选**功能,用于电视、机顶盒、功放等设备间的联动控制(如:打开电视时自动打开功放)。
在 Linux 驱动中:
// dw-hdmi-qp.c:4742 if (of_property_read_bool(np, "cec-enable")) { hdmi->cec_enable = true; irq = platform_get_irq(pdev, 1); // 拿 CEC 中断 };// 不写 cec-enable → hdmi->cec_enable 保持 false
// → dw_hdmi_qp_register_cec() 直接 return 0,不注册设备
结论:没有 `cec-enable` 属性时,CEC 功能完全静默——不注册平台设备、不申请中断、不占用任何软件资源。去掉 pinctrl 中的 CEC 引脚不会导致任何错误。
2.2 HPD — Hot Plug Detect
HPD 是 HDMI PHY 用来判断"对端是否插入了显示器"的**唯一硬件信号**。他的工作方式不是 GPIO 输入,而是通过 pinctrl 将引脚的电平路由到 HDMI IP 内部的 IOC GRF 寄存器:
// dw_hdmi-rockchip.c:4400 dw_hdmi_rk3576_read_hpd() { regmap_read(hdmi->regmap, RK3576_IOC_HDMITX_HPD_STATUS, &val); if (val & RK3576_HDMITX_LEVEL_INT) return connector_status_connected; // 读到高电平 → 认为有设备 else return connector_status_disconnected; // 读到低电平 → 认为无设备 }HPD 在整个 HDMI 管线中的作用:
- **DRM connector detect:** 决定是否上报"已连接"
- **FRL 训练状态机:** 训练过程中每 500μs 轮询一次,HPD 为低则退出
- **DDC 读写操作:** 每次 I2C 传输前检查 HPD,确保设备还在
如果 HPD 一直为低:
- DRM 框架认为没有显示器 → 不启动视频管线 → **黑屏**
- 可通过 `force-output` DT 属性绕过 DRM 层检查
- 但 FRL 模式和 DDC 操作中仍会直接读 HPD 寄存器
建议:在 GPIO4_C1 上加一个 10KΩ 上拉电阻到 3.3V,让 PHY 读到持续高电平。一毛钱成本,零软件副作用。
2.3 DDC / SCL+SDA — Display Data Channel
DDC 是 HDMI 标准规定的一个 I2C 总线,用来读显示器的 EDID(Extended Display Identification Data)。EDID 是一个 128/256 字节的数据块,描述了显示器支持的分辨率、刷新率、色彩空间、音频格式等能力。
需要注意的是,HDMI 的 DDC 不是用 SoC 的通用 I2C 控制器,而是 **HDMI IP 内部的专用 I2C 控制器**。驱动通过操作 HDMI 寄存器来读写 EDID:
// dw-hdmi-qp.c — DDC 读 EDID 流程 dw_hdmi_connector_get_modes() → edid = drm_get_edid(connector, hdmi->ddc); // hdmi->ddc 是内部注册的 i2c_adapter → drm_add_edid_modes(connector, edid); // 把 EDID 里的分辨率加进 mode list ```没有 DDC 时,驱动有兜底逻辑:
} else { hdmi->hdmi_data.sink_is_hdmi = true; ret = rockchip_drm_add_modes_noedid(connector); // 加标准分辨率(1080p、720p 等) dev_info(hdmi->dev, "failed to get edid\n"); }结论:去掉 DDC 不会报错,驱动会用标准分辨率兜底。如果你的面板是固定分辨率,这是完全可接受的权衡。
三、TMDS vs FRL:两代 HDMI 传输协议
3.1 概念
| | TMDS | FRL |
|---|---|---|
| 全称 | Transition Minimized Differential Signaling | Fixed Rate Link |
| 引入版本 | HDMI 1.0 ~ 2.0 | HDMI 2.1 |
| 最大带宽 | 18 Gbps | 48 Gbps |
| 支持的典型分辨率 | 4K@60Hz、1440p@144Hz | 8K@60Hz、4K@120Hz |
| 链路建立 | 无需协商,直接驱动 | 需要源端和显示端握手训练 |
| 类比 | 国道:通车就是通车 | 高速公路:上之前要先取卡 |
3.2 模式选择逻辑
// dw_hdmi-rockchip.c:1112 if (!max_frl_rate || (tmdsclk < HDMI20_MAX_RATE && mode.clock < HDMI20_MAX_RATE)) { hdmi->link_cfg.frl_mode = false; // → TMDS } else { hdmi->link_cfg.frl_mode = true; // → FRL }FRL 需要**同时满足**:
1. `max_frl_rate != 0` — 显示器通过 EDID 声明支持 FRL(需要 DDC)
2. 像素时钟 ≥ 600MHz — 高分辨率/高刷新率场景
多数嵌入式场景走 TMDS。** 低分辨率面板 + 无 DDC → 100% TMDS。
FRL 训练状态机在 `dw-hdmi-qp.c` 中由 `dw_hdmi_qp_flt_work()` 驱动,入口条件:
// dw-hdmi-qp.c:3813 if (link_cfg && link_cfg->frl_mode) queue_work(hdmi->workqueue, &hdmi->flt_work); // 只在 FRL 模式下触发TMDS 模式下,`dw_hdmi_qp_is_disabled()` 永远不会被调用,HPD 检查仅限于 DRM connector detect 层。
四、驱动架构
arch/arm64/boot/dts/rockchip/ ├── rk3576.dtsi ← HDMI 节点定义(reg、中断、时钟、pinctrl-0) ├── rk3576-pinctrl.dtsi ← hdmi_txm0_pins、hdmi_tx_scl、hdmi_tx_sda 等 pinctrl 组 └── x1013.dts ← 你的板级 DTS,在这里覆盖/添加属性 drivers/gpu/drm/ ├── bridge/synopsys/ │ ├── dw-hdmi-qp.c ← Synopsys DW HDMI QP 通用核心 │ ├── dw-hdmi-qp-cec.c ← CEC 子模块(平台设备) │ └── dw-hdmi-qp-hdcp.c ← HDCP 子模块 └── rockchip/ └── dw_hdmi-rockchip.c ← Rockchip 胶水层(GRF、时钟、pinctrl) drivers/misc/rk628/ ├── rk628.c ← rk628 主驱动(HDMI in → DSI out) └── rk628_hdmitx.c ← rk628 自带 HDMI TX分层设计:** Synopsys 提供通用的 HDMI IP 核心驱动,Rockchip 写一层"胶水代码"处理 SoC 特有的寄存器、时钟、中断映射。同样的 `dw-hdmi-qp.c` 也在全志、ST、NXP 的平台上使用。
五、实际修改方案
5.1 新增不含 CEC 的 pinctrl 组
&pinctrl { /* ... rk628 pinctrl ... */ hdmi_tx_nocrc { hdmi_txm0_nocrc: hdmi-txm0-nocrc { rockchip,pins = /* hdmi_tx_hpdin_m0 — 只保留 HPD,去掉 CEC */ <4 RK_PC1 9 &pcfg_pull_none>; }; }; };原来的 `hdmi_txm0_pins` 捆绑了 CEC 和 HPD 两根脚,但我们只需要 HPD。新建一个只含 HPD 的组,彻底解耦。
5.2 覆盖 HDMI 节点的 pinctrl
&hdmi { status = "okay"; enable-gpios = <&gpio2 RK_PB0 GPIO_ACTIVE_HIGH>; /* * 覆盖 rk3576.dtsi 中的 pinctrl-0: * - hdmi_txm0_pins(CEC + HPD)→ hdmi_txm0_nocrc(只 HPD) * - 去掉 hdmi_tx_scl + hdmi_tx_sda(不需要 DDC/EDID) * * 释放的引脚: * GPIO4_C0 → rk628 reset * GPIO4_C2 + GPIO4_C3 → GPIO / 软件 I2C */ pinctrl-names = "default"; pinctrl-0 = <&hdmi_txm0_nocrc>; };```
5.3 硬件
GPIO4_C1 上拉 10KΩ 到 3.3V,保证 HPD 读到高电平。
5.4 验证
# 启动后检查 HDMI 日志 dmesg | grep -E "hdmi|rk628|tmds|cec"# 期望输出(无报错):
# dw hdmi qp use tmds mode
# rk628 probe 成功(不再有 gpio4-16 冲突)
```
六、关键结论
1. **CEC 可选:** 没有 `cec-enable` 属性时驱动完全跳过,去掉引脚无副作用。
2. **DDC 可选:** 不去读 EDID 时驱动用兜底分辨率,去掉 SCL/SDA 不报错。
3. **HPD 建议保留:** PHY 层需要它判断设备状态。可通过硬件上拉或 `force-output` DT 属性处理。
4. **低分辨率 = TMDS:** FRL 只在超高清场景下开启,你的场景全程 TMDS,FRL 相关代码不会执行。
5. **pinctrl 组名对驱动透明:** 驱动通过 `devm_pinctrl_get()` 拿整个设备的 pinctrl,只按 `pinctrl-names`(如 "default"、"idle")匹配状态,不知道组的名字。改 DT 即可,C 代码一行不动。
6. **Synopsys IP 分层:** Rockchip 的 HDMI 核心来自 Synopsys DesignWare,Rockchip 只负责 SoC 特有的胶水层。CEC、DDC、EDID、音频 N/CTS 等协议逻辑是通用代码。
八.RK628F奇怪问题
如果想使用hdmi中的ddc复用做普通i2c的时候记得把rk628f的hdmi的ddc的电阻拆掉,不然会干扰到edid部分导致芯片锁hdmi信号的流程
