3. 实现Rockchip Encoder驱动
任务说明
本案例要求在Rockchip DRM框架中实现一个Encoder驱动,完成CRTC输出信号到外部接口(HDMI/MIPI DSI)的转换。核心工作是注册drm_encoder并实现其atomic_check与atomic_mode_set回调,用于验证显示模式与当前Encoder的兼容性,并在模式切换时配置硬件输出参数。
驱动框架与依赖
本驱动基于Linux内核DRM子系统,依赖drm_atomic_helper提供的默认回调。硬件寄存器操作通过regmap完成,设备树节点需提供寄存器映射地址。以下代码聚焦Encoder核心逻辑,实际项目中需在probe函数中完成drm_encoder_init注册,并通过component框架与CRTC绑定。
关键完整代码
#include <drm/drm_encoder.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <linux/regmap.h>
/* 硬件寄存器定义(示例) */
#define ENCODER_CTRL_REG 0x00
#define ENCODER_MODE_REG 0x04
#define MODE_HDMI 0x01
#define MODE_MIPI_DSI 0x02
struct rockchip_encoder {
struct drm_encoder base;
struct regmap *regmap;
int interface_type; /* 0=HDMI, 1=MIPI DSI */
};
static inline struct rockchip_encoder *
to_rockchip_encoder(struct drm_encoder *encoder)
{
return container_of(encoder, struct rockchip_encoder, base);
}
static int rockchip_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct rockchip_encoder *rk_encoder = to_rockchip_encoder(encoder);
struct drm_display_mode *mode = &crtc_state->mode;
/* 检查分辨率是否超出接口能力 */
if (rk_encoder->interface_type == 0) { /* HDMI */
if (mode->clock > 340000) /* 340MHz 像素时钟上限 */
return -EINVAL;
} else { /* MIPI DSI */
if (mode->clock > 150000) /* 150MHz 像素时钟上限 */
return -EINVAL;
}
/* 检查时序参数有效性 */
if (mode->hdisplay == 0 || mode->vdisplay == 0)
return -EINVAL;
return 0;
}
static void rockchip_encoder_atomic_mode_set(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct rockchip_encoder *rk_encoder = to_rockchip_encoder(encoder);
struct drm_display_mode *mode = &crtc_state->mode;
u32 ctrl_val = 0;
u32 mode_val;
/* 根据接口类型选择输出模式 */
if (rk_encoder->interface_type == 0) {
mode_val = MODE_HDMI;
ctrl_val |= BIT(0); /* 使能HDMI输出 */
} else {
mode_val = MODE_MIPI_DSI;
ctrl_val |= BIT(1); /* 使能MIPI DSI输出 */
}
/* 配置输出模式 */
regmap_write(rk_encoder->regmap, ENCODER_MODE_REG, mode_val);
/* 配置像素时钟分频(示例:按模式时钟设置) */
ctrl_val |= (mode->clock / 1000) & 0xFFFF;
regmap_write(rk_encoder->regmap, ENCODER_CTRL_REG, ctrl_val);
}
static const struct drm_encoder_helper_funcs
rockchip_encoder_helper_funcs = {
.atomic_check = rockchip_encoder_atomic_check,
.atomic_mode_set = rockchip_encoder_atomic_mode_set,
};
static void rockchip_encoder_destroy(struct drm_encoder *encoder)
{
drm_encoder_cleanup(encoder);
}
static const struct drm_encoder_funcs rockchip_encoder_funcs = {
.destroy = rockchip_encoder_destroy,
};
/* 初始化函数——在probe中调用 */
int rockchip_encoder_init(struct drm_device *drm_dev,
struct rockchip_encoder *rk_encoder,
int interface_type)
{
int ret;
rk_encoder->interface_type = interface_type;
ret = drm_encoder_init(drm_dev, &rk_encoder->base,
&rockchip_encoder_funcs,
DRM_MODE_ENCODER_TMDS, NULL);
if (ret)
return ret;
drm_encoder_helper_add(&rk_encoder->base,
&rockchip_encoder_helper_funcs);
return 0;
}
EXPORT_SYMBOL_GPL(rockchip_encoder_init);
编译与集成
- 将上述代码保存为
rockchip_encoder.c,加入内核源码drivers/gpu/drm/rockchip/目录。 - 在Kconfig中添加配置项
config DRM_ROCKCHIP_ENCODER,依赖DRM_ROCKCHIP。 - 在Makefile中添加
obj-$(CONFIG_DRM_ROCKCHIP_ENCODER) += rockchip_encoder.o。 - 设备树节点需提供
reg属性指向寄存器基地址,并在驱动probe中通过syscon_node_to_regmap获取regmap实例。
验证方法
- 编译内核并烧录至RK3568/RK3588平台,启动后检查dmesg确认Encoder注册成功:
dmesg | grep -i encoder,应看到类似[drm] Initialized encoder的日志。 - 使用modetest工具列出当前Encoder:
modetest -M rockchip -e,确认Encoder ID存在且类型为TMDS。 - 连接HDMI显示器并设置分辨率:
modetest -M rockchip -s 32:1920x1080,观察屏幕是否正常输出。若输出异常,检查atomic_check返回的EINVAL是否因像素时钟超限。 - 验证
atomic_mode_set生效:在驱动中临时添加printk打印寄存器值,对比设置前后ENCODER_CTRL_REG的值是否随分辨率变化。
通过判定标准
- Encoder成功注册且无错误日志。
- HDMI接口在1080p@60Hz下正常显示,无花屏或信号丢失。
- 切换分辨率至4K@30Hz时,
atomic_check正确返回-EINVAL(因超过340MHz上限),验证模式过滤逻辑生效。