Linux DRM驱动HDR支持与VBlank同步调试工程 - 第4节

4.1 Encoder 的作用与实现

在 DRM/KMS 子系统中,Encoder(编码器)位于 CRTC 与 Connector 之间,负责将 CRTC 输出的像素数据流(通常为并行 RGB 或 LVDS 格式)转换为特定显示接口所需的串行信号格式。Encoder 的核心职责包括:

  • 信号编码:将并行像素数据转换为 TMDS(HDMI/DVI)、DisplayPort 串行化数据、eDP 或 LVDS 差分信号。
  • 链路训练:对于 DP/eDP,执行链路训练以协商通道数、速率和电压摆幅。
  • 色彩格式转换:在 HDR 场景下,将 CRTC 输出的 RGB 或 YCbCr 数据转换为 HDMI 或 DP 所需的色彩格式(如 4:4:4、4:2:2、4:2:0)。
  • 辅助通道管理:处理 DDC(I²C)用于 EDID 读取,以及 DP AUX 通道用于 DPCD 寄存器访问。
关键概念: 一个 Connector 通常绑定一个 Encoder,但一个 Encoder 可以支持多个 Connector(例如通过外部多路复用器)。在驱动中,drm_encoder 结构体通过 possible_crtcs 位掩码声明其可连接的 CRTC。

4.1.1 Encoder 驱动实现要点

实现一个 Encoder 驱动需要填充 drm_encoder_helper_funcs 回调函数,核心接口如下表所示:

回调函数 触发时机 典型实现内容
atomic_mode_set 模式设置时 配置 Encoder 的时钟、分频器、输出格式寄存器
atomic_enable Encoder 启用时 启动串行化器、PLL 锁定、链路训练
atomic_disable Encoder 禁用时 关闭输出、进入低功耗模式
atomic_check 原子提交校验时 验证时钟频率、链路带宽是否满足模式需求

以下是一个简化的 Encoder 初始化代码示例,展示如何注册一个内置 HDMI Encoder:

static const struct drm_encoder_helper_funcs hdmi_encoder_helper_funcs = {
    .atomic_mode_set = hdmi_encoder_mode_set,
    .atomic_enable   = hdmi_encoder_enable,
    .atomic_disable  = hdmi_encoder_disable,
    .atomic_check    = hdmi_encoder_atomic_check,
};

static int hdmi_encoder_init(struct drm_device *dev) {
    struct drm_encoder *encoder;
    int ret;

    encoder = drm_encoder_alloc(dev, NULL, DRM_MODE_ENCODER_TMDS, 0);
    if (IS_ERR(encoder))
        return PTR_ERR(encoder);

    encoder->possible_crtcs = drm_crtc_mask(&crtc); // 绑定到特定 CRTC
    drm_encoder_helper_add(encoder, &hdmi_encoder_helper_funcs);

    return 0;
}
调试提示: 如果 Encoder 无法输出信号,首先检查 atomic_mode_set 中是否正确设置了时钟源和分频器。使用 drm_info 打印 Encoder 的 encoder_type 和 possible_crtcs 确认绑定关系。
Encoder 在 DRM 显示流水线中的位置 CRTC 像素数据流 (RGB/YCbCr) Encoder 信号编码/链路训练 TMDS / DP 串行化 Connector 物理接口 HDMI / DP / eDP 说明: Encoder 接收 CRTC 的并行像素数据,编码为串行信号后发送给 Connector。 在 HDR 模式下,Encoder 还需处理色彩深度转换(8bit→10bit)和色彩格式转换。

4.2 Connector 检测与 EDID 读取

Connector(连接器)代表物理显示接口(HDMI、DP、VGA 等),其核心职责是检测显示器是否连接、读取 EDID(Extended Display Identification Data)以获取显示器能力,并将这些信息传递给 DRM 核心以建立显示模式。Connector 的状态检测通常通过硬件 GPIO 引脚(热插拔检测,HPD)或轮询方式实现。

4.2.1 Connector 检测机制

Connector 检测分为两种模式:

  • 热插拔检测(HPD):硬件中断驱动,当显示器插入或拔出时触发中断,驱动调用 drm_helper_hpd_irq_event() 通知核心。
  • 轮询检测:对于不支持 HPD 的接口(如 VGA),驱动在 connector->polled 中设置 DRM_CONNECTOR_POLL_CONNECT 和 DRM_CONNECTOR_POLL_DISCONNECT,核心定时调用 detect 回调。

Connector 的 detect 回调返回以下状态:

返回值 含义 典型处理
connector_status_connected 显示器已连接 读取 EDID,建立模式列表
connector_status_disconnected 显示器未连接 清空模式列表,禁用输出
connector_status_unknown 无法确定状态 尝试读取 EDID 确认
常见陷阱: 某些 DP 转 HDMI 桥接芯片在显示器未连接时仍可能返回 connector_status_connected,因为桥接芯片自身始终在线。此时应通过 EDID 读取是否成功来辅助判断。

4.2.2 EDID 读取流程

EDID 是一块 128 字节(或扩展为 256 字节)的数据块,存储在显示器中,通过 DDC(I²C)或 DP AUX 通道读取。读取流程如下:

  1. 驱动调用 drm_get_edid() 或 drm_do_get_edid(),传入 connector 和 I²C 适配器。
  2. 核心通过 DDC 地址 0x50 读取 EDID 块,校验校验和。
  3. 如果读取成功,核心解析 EDID 并生成 drm_display_mode 列表。
  4. 驱动将 EDID 数据存储在 connector->edid_blob_ptr 中,供用户空间通过 drmModeGetProperty 访问。

以下是一个典型的 Connector get_modes 回调实现:

static int hdmi_connector_get_modes(struct drm_connector *connector) {
    struct edid *edid;
    int ret;

    // 通过 DDC I²C 读取 EDID
    edid = drm_get_edid(connector, &hdmi_i2c_adapter);
    if (!edid) {
        drm_err(connector->dev, "No EDID found on HDMI connector\n");
        return 0;
    }

    // 将 EDID 解析为 drm_display_mode 并添加到 connector
    drm_connector_update_edid_property(connector, edid);
    ret = drm_add_edid_modes(connector, edid);

    kfree(edid);
    return ret;
}
调试技巧: 使用 cat /sys/kernel/debug/dri/0/HDMI-A-1/edid > edid.bin 导出 EDID 原始数据,然后用 edid-decode 工具解析,验证显示器支持的 HDR 元数据(如 HDR static metadata 块)。
Connector 检测与 EDID 读取流程 HPD 中断 / 轮询触发 detect() 回调 Connected Disconnected Unknown 读取 EDID 尝试读取 EDID EDID 读取失败时,Connector 应返回 0 个模式,并保持 Disconnected 状态。

4.3 DP/HDMI 桥接

在实际嵌入式系统中,SoC 原生显示接口可能与目标显示器不匹配,此时需要外部桥接芯片进行协议转换。常见的桥接场景包括:

  • DP 转 HDMI:将 SoC 的 DisplayPort 输出转换为 HDMI 信号,常用于电视或显示器。
  • HDMI 转 DP:较少见,用于某些特殊适配器。
  • LVDS 转 HDMI:将并行 LVDS 信号转换为 HDMI,用于老旧 SoC。
  • eDP 转 DP:用于扩展坞或外接显示器。

4.3.1 桥接芯片驱动架构

在 DRM 驱动中,桥接芯片通常实现为 Bridge 驱动,使用 drm_bridge 框架。Bridge 位于 Encoder 与 Connector 之间,可以串联多个 Bridge 形成链。核心结构如下:

组件 角色 示例芯片
SoC Encoder 输出 DP 串行信号 Rockchip RK3399 DP
Bridge (DP→HDMI) 协议转换、电平转换 IT66121, LT8619C
Connector 物理 HDMI 接口 HDMI Type A

Bridge 驱动的核心回调包括:

  • attach:将 Bridge 连接到 Encoder 或上游 Bridge。
  • detect:检测下游显示器状态(通过桥接芯片的 HPD 引脚)。
  • get_edid:通过桥接芯片的 I²C 或 AUX 通道读取 EDID。
  • mode_set:配置桥接芯片的输出时序和色彩格式。
HDR 桥接注意事项: 桥接芯片必须支持 HDR 元数据透传(如 HDMI 2.0b 的 HDR 静态元数据 InfoFrame)。在 mode_set 中,需要配置桥接芯片的 HDR_METADATA 寄存器,将 DRM 的 hdr_output_metadata 属性写入芯片。

4.3.2 桥接芯片调试要点

调试 DP/HDMI 桥接时,常见问题及排查方法如下:

  • 无显示输出:检查桥接芯片的电源、复位时序,使用示波器测量 HPD 引脚电平。
  • EDID 读取失败:确认 I²C 总线地址(通常为 0x50),检查桥接芯片是否处于透传模式。
  • 色彩异常:验证 mode_set 中是否设置了正确的色彩深度(8/10/12bit)和色彩空间(RGB/YCbCr)。
  • HDR 不生效:使用 drm_info 查看 Connector 的 HDR_OUTPUT_METADATA 属性是否已设置,并确认桥接芯片支持 HDR InfoFrame 注入。
// 示例:在 Bridge 的 mode_set 中配置 HDR 元数据
static void bridge_mode_set(struct drm_bridge *bridge,
                            const struct drm_display_mode *mode,
                            const struct drm_display_mode *adjusted_mode) {
    struct bridge_priv *priv = bridge_to_priv(bridge);
    struct drm_connector *connector = bridge->connector;
    struct drm_connector_state *state = connector->state;

    // 配置基础时序
    reg_write(priv, REG_VIDEO_TIMING, mode->clock);

    // 如果启用了 HDR,写入 HDR 元数据
    if (state->hdr_output_metadata) {
        struct hdr_output_metadata *hdr = state->hdr_output_metadata;
        reg_write(priv, REG_HDR_EOTF, hdr->hdmi_metadata_type1.eotf);
        reg_write(priv, REG_HDR_LUMINANCE, hdr->hdmi_metadata_type1.max_luminance);
    }
}
硬件调试警告: 某些桥接芯片(如 LT8619C)在初始化时需要严格的电源上电序列(例如 1.2V → 1.8V → 3.3V,间隔 5ms)。如果上电顺序错误,芯片可能无法响应 I²C 或输出时钟。务必参考芯片数据手册的时序图。
DP→HDMI 桥接芯片架构 SoC DP Encoder DP 串行信号 Bridge 芯片 DP→HDMI 转换 (IT66121) HDMI Connector 物理接口 辅助通道: I²C (DDC) 用于 EDID 读取,通过 Bridge 芯片透传到 HDMI 显示器。 HPD 信号由 Bridge 芯片检测后通过 GPIO 中断通知 SoC。