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 内部,外部仅需电平转换。
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() 中完成,典型步骤:
- 分配结构体:
drm_encoder_init()或devm_drm_encoder_alloc() - 设置掩码:
possible_crtcs指定可绑定的 CRTC 位图 - 注册辅助回调:
drm_encoder_helper_add() - 绑定 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 的初始化分为硬件探测与模式设置两个阶段。以下流程图展示了从驱动加载到输出使能的完整路径:
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 时钟配置的核心回调,在此完成:
- PLL 配置: 根据像素时钟计算反馈分频比、后分频比,写入 PLL 控制寄存器
- 通道映射: 根据色彩深度设置数据通道映射表(JEIDA 或 VESA 映射标准)
- 摆幅控制: 设置差分电压摆幅(通常 200mV ~ 600mV)和共模电压
- 预加重: 长走线场景开启预加重以补偿高频衰减
LVDS 时钟公式: 串行时钟 = 像素时钟 × 每个像素的串行位数 / 通道数
典型 8bit 单通道:串行时钟 = 像素时钟 × 7(7:1 串行化,1bit 用于控制/同步)
典型 6bit 单通道:串行时钟 = 像素时钟 × 7(3.5 对数据 × 2 次采样)
典型 8bit 单通道:串行时钟 = 像素时钟 × 7(7:1 串行化,1bit 用于控制/同步)
典型 6bit 单通道:串行时钟 = 像素时钟 × 7(3.5 对数据 × 2 次采样)
链路训练通常在 atomic_enable 中启动,具体流程将在第 5 章详细展开。本章仅给出初始化阶段的入口位置。