第一章:RK显示驱动概览
做嵌入式显示开发这么多年,我接触过不少平台。但瑞芯微的显示架构,说实话,是我个人觉得最清晰的一套。今天咱们就来聊聊RK平台显示驱动的基础框架。
RK平台显示架构
先看整体架构。RK的显示系统,说白了就是三层结构:
- 应用层:负责合成和提交显示内容
- HAL层:硬件抽象,屏蔽底层差异
- 内核层:直接操作VOP等硬件模块
嗯,这里要注意。很多新手容易把HAL层和内核层搞混。我刚开始做RK3399项目时,也踩过这个坑。后来发现,HAL层主要负责buffer管理和合成策略,而内核层才是真正跟寄存器打交道的。
核心要点:应用层通过SurfaceFlinger(Android)或Weston(Linux)提交图层,经过HAL层处理后,最终由内核驱动写入VOP的寄存器。
VOP模块介绍
VOP,全称Video Output Processor。你可以把它理解成显示数据的"调度中心"。它负责从内存中读取framebuffer,然后按照时序输出到显示接口。
我记得第一次看VOP的数据手册时,被那些寄存器搞得头大。其实你只要抓住几个关键点:
- Win层:VOP内部有多个win(窗口),每个win可以绑定一个图层
- DMA引擎:负责从DDR搬运数据到VOP内部
- 时序发生器:生成行场同步信号
为什么会设计多个win层?因为实际产品中,往往需要叠加多个图层。比如视频播放时,视频层+UI层+字幕层,每个层独立更新,互不干扰。
我的经验:在RK3288上调试双屏异显时,我习惯把主屏绑定到win0,副屏绑定到win1。这样调试起来逻辑清晰,不容易乱。
显示数据流分析
数据是怎么从应用程序跑到屏幕上的?咱们一步步拆解:
- 应用创建buffer:通过gralloc分配图形buffer
- 填充数据:GPU或CPU渲染内容到buffer
- 提交到HAL:通过drmModeSetCrtc或queueBuffer提交
- 内核调度:drm驱动将buffer地址写入VOP寄存器
- VOP读取:DMA引擎按帧同步信号读取数据
- 输出显示:数据经编码后送到HDMI/MIPI/LVDS等接口
你想想看,这整个过程必须在16.6ms内完成(60fps)。一旦超时,就会出现掉帧或撕裂。我在项目中遇到过最典型的问题,就是buffer提交太慢导致画面卡顿。
避坑指南:我曾经在RK3566上调试4K显示,发现帧率上不去。查了两天才找到原因——VOP的DMA带宽被其他外设占用了。后来通过调整内存控制器优先级才解决。
关键数据结构
驱动里最核心的数据结构就这几个:
| 结构体 | 作用 |
|---|---|
| rockchip_vop | VOP设备实例,包含所有win和寄存器基址 |
| vop_win | 单个窗口的描述,包含buffer地址、格式、尺寸 |
| vop_dma | DMA传输控制,管理数据搬运 |
看代码时,我建议你从rockchip_vop_probe函数入手。这个函数会初始化所有硬件资源,是理解整个驱动的入口。
// 典型的VOP初始化流程
static int rockchip_vop_probe(struct platform_device *pdev)
{
struct rockchip_vop *vop;
// 分配VOP结构体
vop = devm_kzalloc(dev, sizeof(*vop), GFP_KERNEL);
// 映射寄存器地址
vop->regs = devm_ioremap_resource(dev, res);
// 初始化所有win层
for (i = 0; i < VOP_MAX_WIN; i++)
vop_win_init(vop, i);
// 注册drm CRTC
drm_crtc_init_with_planes(dev, crtc, primary, cursor, ...);
}
嗯,这段代码看起来简单,但实际调试时坑不少。比如寄存器地址映射失败,或者win层数量跟硬件不匹配,都会导致黑屏。
总结一下
RK的显示驱动,核心就是VOP这个硬件模块。你只要搞懂了它怎么管理win层、怎么搬运数据、怎么生成时序,剩下的就是配置各种接口协议了。
我个人习惯,拿到一个新板子,先看dts里VOP的配置,再看驱动probe流程。这样能快速建立起整体认知。下一章咱们会深入VOP的寄存器配置,到时候再细聊。
一句话记住:显示驱动就是"应用层画图 → 内核层搬运 → VOP输出"这条流水线。任何环节出问题,屏幕都不会亮。
公众号:蓝海资料掘金营,微信deep3321