2. DRM Encoder 驱动框架

DRM Encoder 是内核显示子系统中负责将 CRTC 输出的像素流转换为物理显示接口信号的中间层。对于 LVDS 接口,Encoder 承担了时钟生成、数据映射、链路训练等关键任务。本章深入剖析 DRM Encoder 的核心数据结构、注册流程与回调函数,并给出 LVDS Encoder 的典型初始化流程。

2.1 DRM 核心数据结构关系

DRM 显示框架以 drm_device 为根,通过四个核心组件构成完整显示管线:

数据结构 角色 关键成员
drm_crtc 像素生成与 timing 控制 mode_config, primary, cursor
drm_encoder 像素流 → 物理信号转换 crtc, possible_crtcs, bridge
drm_connector 物理接口检测与 EDID 解析 encoder, status, display_info
drm_bridge Encoder 与 Connector 间的信号调理 encoder, next, funcs
关键概念: LVDS Encoder 通常位于 CRTC 与 LVDS Connector 之间,部分 SoC 将 LVDS 输出集成在 Encoder 内部,外部仅需电平转换。
DRM 核心数据结构关系 drm_device drm_crtc drm_encoder drm_connector drm_bridge 数据流向: CRTC 生成像素时序 → Encoder 转换为 LVDS 信号 → Bridge 调理电平 → Connector 输出 LVDS Encoder 核心职责:时钟分频/倍频、数据通道映射、差分摆幅控制、链路训练 注:部分 SoC 将 LVDS PHY 集成在 Encoder 内部,外部仅需无源电平转换

2.2 Encoder 的注册与回调函数

2.2.1 数据结构定义

每个 DRM Encoder 驱动必须实现 drm_encoder_funcs 和 drm_encoder_helper_funcs 两组回调:

// 核心回调 —— 必须实现
struct drm_encoder_funcs {
    void (*destroy)(struct drm_encoder *encoder);
    // 可选:late_register / early_unregister
};

// 辅助回调 —— 模式设置核心
struct drm_encoder_helper_funcs {
    // 模式验证:检查给定 mode 是否被 Encoder 支持
    int (*atomic_check)(struct drm_encoder *encoder,
                        struct drm_crtc_state *crtc_state,
                        struct drm_connector_state *conn_state);
    // 模式设置:配置 Encoder 硬件寄存器
    void (*atomic_mode_set)(struct drm_encoder *encoder,
                            struct drm_crtc_state *crtc_state,
                            struct drm_connector_state *conn_state);
    // 使能/禁用:控制 Encoder 电源与时钟
    void (*atomic_enable)(struct drm_encoder *encoder,
                          struct drm_atomic_state *state);
    void (*atomic_disable)(struct drm_encoder *encoder,
                           struct drm_atomic_state *state);
};
LVDS 特有: atomic_mode_set 是 LVDS 时钟配置的关键入口,在此回调中完成 PLL 分频比、通道映射、摆幅控制等硬件寄存器写入。

2.2.2 注册流程

Encoder 的注册通常在 bind() 或 probe() 中完成,典型步骤:

  1. 分配结构体: drm_encoder_init() 或 devm_drm_encoder_alloc()
  2. 设置掩码: possible_crtcs 指定可绑定的 CRTC 位图
  3. 注册辅助回调: drm_encoder_helper_add()
  4. 绑定 Bridge/Connector: 通过 drm_bridge_attach() 连接下游
// LVDS Encoder 注册示例
static int lvds_encoder_bind(struct device *dev, struct device *master, void *data)
{
    struct drm_device *drm = data;
    struct lvds_encoder *lvds_enc;
    int ret;

    lvds_enc = devm_drm_encoder_alloc(dev, drm, &lvds_encoder_funcs,
                                      DRM_MODE_ENCODER_LVDS, NULL);
    if (IS_ERR(lvds_enc))
        return PTR_ERR(lvds_enc);

    // 设置 possible_crtcs —— 通常绑定到第一个 CRTC
    lvds_enc->base.possible_crtcs = drm_crtc_mask(&drm->mode_config.crtc_list->crtc);

    // 注册辅助回调
    drm_encoder_helper_add(&lvds_enc->base, &lvds_encoder_helper_funcs);

    // 绑定下游 LVDS bridge
    ret = drm_bridge_attach(&lvds_enc->base, lvds_enc->bridge, NULL,
                            DRM_BRIDGE_ATTACH_NO_CONNECTOR);
    if (ret)
        return ret;

    dev_info(dev, "LVDS Encoder registered\n");
    return 0;
}

2.3 LVDS Encoder 的初始化流程

LVDS Encoder 的初始化分为硬件探测与模式设置两个阶段。以下流程图展示了从驱动加载到输出使能的完整路径:

LVDS Encoder 初始化流程 阶段一:硬件探测 (probe/bind) 解析 DT 节点 获取时钟/复位/GPIO 初始化硬件寄存器 阶段二:模式设置 (atomic_check) 验证 mode 时序参数 检查带宽 < 最大支持 返回 0/错误 阶段三:时钟配置 (atomic_mode_set) 计算 PLL 分频比 配置 LVDS 时钟 设置通道映射 摆幅控制 阶段四:使能输出 (atomic_enable) 使能 PLL 与 LVDS PHY 启动链路训练序列 输出使能完成

2.3.1 硬件探测阶段关键操作

步骤 操作 LVDS 特有说明
1 解析设备树 获取 lvds-encoder 节点、data-lanes 通道数、clock-frequency 参考时钟
2 获取时钟资源 LVDS 专用 PLL 时钟、像素时钟、串行时钟(通常为像素时钟 × 7)
3 复位与电源 拉高 LVDS 核心电源、释放复位信号、等待 PLL 锁定
4 初始化寄存器 设置默认通道映射、摆幅电压(典型 350mV/400mV)、预加重
常见陷阱: LVDS PLL 锁定需要稳定参考时钟。若在 atomic_enable 之前未等待 PLL 锁定,可能导致输出无显示或链路训练失败。建议在 atomic_mode_set 中配置 PLL 后加入 udelay(100) 或轮询锁定状态。

2.3.2 模式设置阶段时序验证

atomic_check 回调中需验证以下参数是否在 LVDS Encoder 能力范围内:

  • 像素时钟: 通常 LVDS 支持 25MHz ~ 135MHz(单通道)或 25MHz ~ 270MHz(双通道)
  • 分辨率: 单通道最大 1366×768@60Hz,双通道最大 1920×1080@60Hz
  • 数据通道数: 单通道 4 对差分数据 + 1 对时钟,双通道 8 对数据 + 2 对时钟
  • 色彩深度: 6bit(18bit 色深)或 8bit(24bit 色深),影响通道映射方式
// atomic_check 示例 —— LVDS 时序验证
static int lvds_encoder_atomic_check(struct drm_encoder *encoder,
                                     struct drm_crtc_state *crtc_state,
                                     struct drm_connector_state *conn_state)
{
    struct drm_display_mode *mode = &crtc_state->adjusted_mode;
    struct lvds_encoder *lvds_enc = to_lvds_encoder(encoder);
    long pixel_clock = mode->clock * 1000; // kHz → Hz

    // 检查像素时钟范围
    if (pixel_clock < lvds_enc->min_pixel_clock ||
        pixel_clock > lvds_enc->max_pixel_clock) {
        DRM_DEBUG_KMS("LVDS pixel clock %ld Hz out of range\n", pixel_clock);
        return -EINVAL;
    }

    // 检查分辨率是否超过单通道限制
    if (mode->hdisplay > 1366 && !lvds_enc->dual_channel) {
        DRM_DEBUG_KMS("Resolution %dx%d requires dual-channel LVDS\n",
                      mode->hdisplay, mode->vdisplay);
        return -EINVAL;
    }

    // 计算串行时钟频率(像素时钟 × 7 或 × 10,取决于编码格式)
    lvds_enc->serial_clock = pixel_clock * 7; // 7:1 串行化
    return 0;
}

2.3.3 时钟配置与链路训练入口

atomic_mode_set 是 LVDS 时钟配置的核心回调,在此完成:

  1. PLL 配置: 根据像素时钟计算反馈分频比、后分频比,写入 PLL 控制寄存器
  2. 通道映射: 根据色彩深度设置数据通道映射表(JEIDA 或 VESA 映射标准)
  3. 摆幅控制: 设置差分电压摆幅(通常 200mV ~ 600mV)和共模电压
  4. 预加重: 长走线场景开启预加重以补偿高频衰减
LVDS 时钟公式: 串行时钟 = 像素时钟 × 每个像素的串行位数 / 通道数
典型 8bit 单通道:串行时钟 = 像素时钟 × 7(7:1 串行化,1bit 用于控制/同步)
典型 6bit 单通道:串行时钟 = 像素时钟 × 7(3.5 对数据 × 2 次采样)

链路训练通常在 atomic_enable 中启动,具体流程将在第 5 章详细展开。本章仅给出初始化阶段的入口位置。