3、VOP驱动详解:VOP寄存器配置、图层叠加原理、VOP中断处理
好,咱们今天来啃一块硬骨头——VOP驱动。说实话,VOP(Video Output Processor)是瑞芯微显示链路里最核心的模块之一。你想想看,CPU算好了图像数据,GPU也渲染完了,最后怎么变成屏幕上的像素?就是靠VOP。我刚开始接触这块时,也觉得寄存器多到头皮发麻,但摸清楚套路后,其实就那么几件事。
3.1 VOP寄存器配置——别被几百个寄存器吓到
VOP的寄存器,说白了分三大类:控制类、时序类、图层类。我个人习惯先把它们按功能分组,而不是按地址顺序去读。
核心寄存器组速览
- SYS_CTRL:全局使能、软复位、时钟选择。嗯,上电第一件事就是配它。
- DSP_CTRL0/1:输出格式、颜色空间、像素时钟极性。这里配错了,屏幕直接花掉。
- WINx_CTRL0~3:每个图层的控制寄存器,包括使能、格式、透明度、裁剪区域。
- WINx_VIR:虚拟分辨率,说白了就是显存里一行有多少字节。这个我踩过坑,后面说。
- POST_CTRL:后处理控制,比如伽马校正、Dithering。
举个例子,配置一个最简单的1080p输出,你需要设置的行同步、场同步、前后肩参数如下表。这些值在瑞芯微的TRM里都能找到,但不同分辨率要自己算。
| 参数 | 1920x1080@60Hz | 说明 |
|---|---|---|
| H_PW | 44 | 行同步脉冲宽度 |
| H_BP | 148 | 行后肩 |
| H_ACT | 1920 | 有效像素 |
| H_FP | 88 | 行前肩 |
| V_PW | 5 | 场同步脉冲宽度 |
| V_BP | 36 | 场后肩 |
| V_ACT | 1080 | 有效行数 |
| V_FP | 4 | 场前肩 |
我的小技巧:配时序寄存器时,我习惯先把H_ACT和V_ACT写对,然后从TRM里抄一组标准值。如果屏幕显示偏移,优先调H_BP和V_BP,别乱动H_PW。
3.2 图层叠加原理——说白了就是画布叠画布
VOP支持多个图层(Win0~Win3),每个图层可以独立配置格式、位置、透明度。它们按照Win0在最底层,Win3在最顶层的顺序叠加。你想想看,就像在Photoshop里叠图层,只不过这里是硬件帮你做。
每个图层有以下几个关键属性:
- 格式:ARGB8888、RGB565、YUV420等。我建议UI层用ARGB8888,视频层用YUV420,省带宽。
- 透明度:全局Alpha(整个图层统一透明度)和像素Alpha(每个像素自带透明度)。
- 裁剪区域:可以只显示源图像的一部分,比如只显示左上角100x100的区域。
- 位置:图层在屏幕上的起始坐标(X,Y)。
这里有个容易搞混的点:全局Alpha和像素Alpha的混合方式。如果启用了像素Alpha,全局Alpha会作为缩放因子。公式是:最终Alpha = 像素Alpha × 全局Alpha / 255。我曾经在项目里把全局Alpha设成了128,结果图标半透明了,排查了半天才发现是这里的问题。
避坑指南:我曾经在配置Win1图层时,忘了设置VIR(虚拟宽度)寄存器。结果图像显示出来是斜的,一行数据错位到了下一行。VIR必须等于实际显存中一行的字节数,而不是屏幕宽度。比如1920x1080的ARGB8888,VIR应该是1920×4=7680字节。
图层叠加的优先级很简单:Win3 > Win2 > Win1 > Win0。但要注意,如果两个图层有重叠区域,上层图层的像素会覆盖下层。如果上层像素的Alpha为0,则完全透明,下层可见。嗯,这里有个性能点:尽量让不透明的UI图层放在上层,视频层放在下层,可以减少不必要的混合计算。
3.3 VOP中断处理——别让CPU空转
VOP的中断主要有三种:帧同步中断(VSYNC)、行中断(LINE_FLAG)、缓冲区切换中断(BUFFER_CHANGE)。我个人最常用的是VSYNC中断,用来做双缓冲切换和帧率统计。
中断处理的流程大致如下:
- VOP产生中断信号,送到CPU的GIC(通用中断控制器)。
- CPU跳转到中断服务函数(ISR)。
- ISR里读取VOP的中断状态寄存器,判断是哪种中断。
- 处理完后写1清除中断标志位。
代码示例(伪代码,实际驱动里用C语言):
void vop_isr(void) {
uint32_t status = readl(VOP_INTR_STATUS);
if (status & VSYNC_INT) {
// 帧同步到来,可以切换显示缓冲区
vop_swap_buffers();
// 统计帧率
fps_counter++;
// 清除中断
writel(VOP_INTR_CLR, VSYNC_INT);
}
if (status & LINE_FLAG_INT) {
// 行中断,可以用来做局部刷新
// 比如只更新屏幕中间一行
writel(VOP_INTR_CLR, LINE_FLAG_INT);
}
}
经验之谈:中断服务函数里千万别做耗时操作,比如内存分配、打印日志。我见过有人直接在ISR里调用printk,结果系统直接卡死。正确的做法是只做标志位设置,具体工作在底半部(比如工作队列或tasklet)里处理。
还有一个容易被忽略的点:中断屏蔽。在初始化VOP时,记得先屏蔽所有中断,等所有配置完成后再使能。否则配置过程中VOP可能因为状态不稳定而误触发中断,导致系统异常。我刚开始做驱动时,就因为这个原因,每次配置完VOP都会莫名其妙进一次中断,查了好久才发现是初始化顺序的问题。
行中断(LINE_FLAG)是个好东西,但用的人不多。你可以设置它在扫描到第N行时触发中断。比如你想实现一个滚动字幕效果,可以在行中断里修改图层的Y坐标,让字幕逐行移动。不过要注意,行中断频率很高(1080p下每秒约64.8万次),处理逻辑必须极其轻量。
总结一下VOP中断的关键点:
- VSYNC中断:最常用,用于帧同步和双缓冲切换。
- LINE_FLAG中断:用于逐行控制,但注意性能开销。
- 中断处理要快,慢操作移到底半部。
- 初始化时先屏蔽中断,配置完再使能。
好了,VOP驱动这块的核心内容就这些。寄存器配置别怕,按功能分组去读;图层叠加理解成画布叠画布,注意透明度和VIR;中断处理记住快进快出。下一章咱们聊聊显示缓冲区的管理,那个才是真正考验内存规划能力的地方。