背光问题
路径:kernel/drivers/gpu/drm/rockchip/rockchip_rgb.c
在 static int rockchip_rgb_encoder_loader_protect(struct drm_encoder *encoder,bool on)函数
return 0;之前加上这两句代码
if (on && rgb->panel)
return drm_panel_enable(rgb->panel);
问题现象
启动时 U-Boot Logo 可以显示,但从 U-Boot 切换到 Linux Kernel Logo 的瞬间会出现两条横向花带或撕裂痕迹。即使 U-Boot Logo 和 Kernel Logo 使用同一张图片,画面仍然不连续。
1 Kernel 启动时 CPLL 被连续重配两次
SoC 公共设备树:
sysdrv/source/kernel/arch/arm/boot/dts/rv1106.dtsi:701-714
其 assigned-clocks 中第二个时钟是 CPLL,而对应的第二个默认频率是 1 GHz:
assigned-clocks =
<&cru PLL_GPLL>, <&cru PLL_CPLL>,
...;
assigned-clock-rates =
<1188000000>, <1000000000>,
...;
板级 LCD (jw_rv1106-lcd.dtsi)配置原本又在 VOP 节点要求:
&vop {
assigned-clocks = <&cru PLL_CPLL>;
assigned-clock-rates = <216000000>;
};
- U-Boot 显示 Logo 时,显示链路最终使用 CPLL 216 MHz。
- Linux 初始化 CRU 时,根据公共
rv1106.dtsi把 CPLL 从 216 MHz 改成 1 GHz。 - Linux 随后初始化 VOP 时,根据板级 VOP 配置又把 CPLL从 1 GHz 改回 216 MHz。
Kernel 的 RK3036/RK3328 类 PLL 参数更新过程位于:
sysdrv/source/kernel/drivers/clk/rockchip/clk-pll.c:581-586
if (!(pll->flags & ROCKCHIP_PLL_FIXED_MODE)) {
cur_parent = pll_mux_ops->get_parent(&pll_mux->hw);
if (cur_parent == PLL_MODE_NORM) {
pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_SLOW);
rate_change_remuxed = 1;
}
}
每次 PLL 改频都会先切到 slow mode,再更新 PLL 参数并等待锁定。显示控制器仍在扫描屏幕时,DCLK 因父 PLL 切换而短暂异常,因此一次改频可能污染当前帧的一部分。连续两次改频与观察到的两条横向花带相符。
2 U-Boot DCLK 分频值超过硬件 5 位范围
寄存器定义位于:
sysdrv/source/uboot/u-boot/arch/arm/include/asm/arch-rockchip/cru_rv1106.h:175-181
DCLK_VOP_SEL_SHIFT = 8,
DCLK_VOP_SEL_MASK = 0x1 << DCLK_VOP_SEL_SHIFT,
DCLK_VOP_DIV_SHIFT = 3,
DCLK_VOP_DIV_MASK = 0x1f << DCLK_VOP_DIV_SHIFT,
DCLK_VOP_DIV_MASK 只有 5 位,因此:
- 寄存器编码范围:0~31。
- 实际分频范围:1~32。
- 大于 32 的分频值不能写入该字段。
旧代码在 CPLL 不能整除请求频率时直接选择 GPLL:
if ((priv->cpll_hz % rate) == 0) {
sel = DCLK_VOP_SEL_CPLL;
div = DIV_ROUND_UP(priv->cpll_hz, rate);
} else {
sel = DCLK_VOP_SEL_GPLL;
div = DIV_ROUND_UP(priv->gpll_hz, rate);
}
rk_clrsetreg(...,
sel << DCLK_VOP_SEL_SHIFT |
(div - 1) << DCLK_VOP_DIV_SHIFT);
7 MHz 请求对应的 GPLL 分频为:
ceil(1188 MHz / 7 MHz) = 170
170 明显超过硬件最大分频 32。旧代码写入的是:
(div - 1) << 3
= 169 << 3
= 0x548
写寄存器时的有效更新掩码包含 DCLK 分频位 3~7 和父时钟选择位 8。0x548 在这些位上的有效部分为 0x148:
- 分频字段最终为 9,即硬件实际除以 10。
- 溢出的位同时把 bit 8 置 1,错误地选择 CPLL。
- 如果 CPLL 已经是 216 MHz,最终 DCLK 可能变成
216 / 10 = 21.6 MHz。
这远高于屏幕可接受的 7 MHz,存在花屏风险。
3 U-Boot 固定 get_mode 绕过了 DTS 像素时钟边沿
U-Boot 显示框架的选择顺序位于:
sysdrv/source/uboot/u-boot/drivers/video/drm/rockchip_display.c:536-550
if (panel->funcs->get_mode)
return panel->funcs->get_mode(panel, mode);
if (dev_of_valid(panel->dev) &&
!display_get_timing_from_dts(panel, mode, &conn_state->bus_flags)) {
...
}
只要面板驱动实现 get_mode,框架就立即返回,不再读取 DTS,也不会读取 DTS 中的 pixelclk-active。
板级 DTS 当前配置位于:
sysdrv/source/kernel/arch/arm/boot/dts/jw_rv1106-lcd.dtsi:31-44
clock-frequency = <7000000>;
...
pixelclk-active = <0>;
旧的固定 get_mode 只填写分辨率、porch 和同步极性,没有填写 conn_state->bus_flags。这样 U-Boot 与 Kernel 对像素时钟边沿的解释不同,交接时 VOP 会改变 DCLK 极性,可能造成瞬间错采样。
4 Kernel 用错误单位比较当前 DCLK
旧代码把 DRM 模式中的 kHz 数值乘以 100,再与 clk_get_rate() 返回的 Hz 比较:
u32 crtc_clock = adjusted_mode->crtc_clock * 100;
...
crtc_clock != clk_get_rate(vop->dclk)
对于本屏幕:
adjusted_mode->crtc_clock 约为 6968 kHz
旧比较值约为 696800
实际 clk_get_rate() 约为 6967741 Hz
两者必然不等,导致 Kernel 总是认为显示模式发生变化。
误判后的调用路径位于:
sysdrv/source/kernel/drivers/gpu/drm/rockchip/rockchip_drm_vop.c:3295-3297
s->mode_update = vop_crtc_mode_update(crtc);
if (s->mode_update)
vop_disable_all_planes(vop);
这会关闭 U-Boot 保留下来的显示平面并重新配置显示,破坏 Loader Logo 到 Kernel Logo 的连续性。
修改文件总览
| 序号 | 文件 | 当前行号 | 修改内容 |
|---|---|---|---|
| 1 | sysdrv/source/kernel/arch/arm/boot/dts/jw_rv1106-lcd.dtsi | 109-122 | 在板级 CRU 节点覆盖完整默认频率列表,把 CPLL 保持为 216 MHz |
| 2 | 同上 | 124-128 | 保留 VOP 对 CPLL 216 MHz 的要求,确保显示初始化目标明确 |
| 3 | sysdrv/source/uboot/u-boot/drivers/clk/rockchip/clk_rv1106.c | 35 | 增加 216 MHz PLL 参数表项 |
| 4 | 同上 | 962-1013 | 重写 DCLK 父时钟和分频选择,限制分频为 1~32并对写入值做掩码 |
| 5 | 同上 | 1182-1193 | PLL 改频成功后同步 CPLL/GPLL 软件缓存 |
| 6 | sysdrv/source/uboot/u-boot/drivers/video/drm/panel-lh24030c50.c | 275-280 | 删除固定 get_mode 回调,让框架读取 DTS timing 和 bus_flags |
| 7 | sysdrv/source/kernel/drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 3221-3227 | 按与 mode_fixup 相同的 kHz 舍入规则比较 DCLK |
每一处修改的详细记录
1 板级 DTS 固定 Kernel 初始化阶段的 CPLL
文件:
sysdrv/source/kernel/arch/arm/boot/dts/jw_rv1106-lcd.dtsi
新增位置:
109-122 行
新增内容:
&cru {
/*
* Keep CPLL at the loader rate during kernel clock initialization.
* Reprogramming CPLL switches its output to 24 MHz slow mode and
* visibly corrupts the loader-logo handoff.
*/
assigned-clock-rates =
<1188000000>, <216000000>,
<1104000000>,
<400000000>, <200000000>,
<100000000>, <300000000>,
<100000000>, <100000000>,
<200000000>;
};
修改原因:
- 设备树属性覆盖是整项替换,不能只写第二个 CPLL 数值。
- 必须保持与公共 DTS 的
assigned-clocks数量和顺序一致。 - 列表第二项从公共默认的
1000000000改为216000000。 - 其他 GPLL、ARMCLK 和总线时钟频率保持原值,避免影响无关模块。
- CPLL 最终状态本来就会被 VOP 设置为 216 MHz;该修改没有改变系统最终频率,只是消除了 Linux 启动中间的 1 GHz 过渡。
保留位置:
124-128 行
&vop {
loader_protect;
assigned-clocks = <&cru PLL_CPLL>;
assigned-clock-rates = <216000000>;
status = "okay";
};
保留它的原因:
- 明确 VOP 显示链路要求 CPLL 216 MHz。
- 对 U-Boot/Kernel 不同设备探测顺序提供冗余保证。
- 当前 CPLL 已经是 216 MHz 时,时钟框架不会再次执行实质性的 PLL 参数变更。
2 U-Boot 增加 216 MHz PLL 参数表项
文件:
sysdrv/source/uboot/u-boot/drivers/clk/rockchip/clk_rv1106.c
新增位置:
35 行
RK3036_PLL_RATE(216000000, 1, 72, 4, 2, 1, 0),
参数计算:
24 MHz × fbdiv 72 / refdiv 1 / postdiv1 4 / postdiv2 2
= 216 MHz
修改原因:
- 板级 DTS 明确请求 CPLL 216 MHz。
- U-Boot 通用 PLL 代码虽然支持自动计算部分频率,但显式表项更加确定,并与 Kernel RV1106 PLL 表中的 216 MHz 参数一致。
- 避免不同 U-Boot 配置或自动计算路径对该关键显示父时钟产生差异。
3 U-Boot 在 PLL 改频后同步软件缓存
文件:
sysdrv/source/uboot/u-boot/drivers/clk/rockchip/clk_rv1106.c
修改位置:
- CPLL:
1182-1187 行 - GPLL:
1188-1193 行
当前代码:
case PLL_CPLL:
ret = rockchip_pll_set_rate(&rv1106_pll_clks[CPLL], priv->cru,
CPLL, rate);
if (!ret)
priv->cpll_hz = rate;
break;
case PLL_GPLL:
ret = rockchip_pll_set_rate(&rv1106_pll_clks[GPLL], priv->cru,
GPLL, rate);
if (!ret)
priv->gpll_hz = rate;
break;
修改前的问题:
rockchip_pll_set_rate()已经改变硬件 PLL。priv->cpll_hz和priv->gpll_hz却仍保存初始化时的旧值。- 板级 DTS 把 CPLL 改为 216 MHz 后,DCLK 计算仍可能把 CPLL 当作 1 GHz。
- 后续分频和父时钟选择基于错误输入,最终寄存器设置也会错误。
修改后的行为:
- 只有硬件 PLL 设置成功时才更新缓存。
- DCLK 计算使用与硬件一致的 216 MHz CPLL。
- GPLL 同样修复,避免以后其他 assigned-clock-rates 修改 GPLL 时出现同类问题。
4 U-Boot 重写 VOP DCLK 分频选择
文件:
sysdrv/source/uboot/u-boot/drivers/clk/rockchip/clk_rv1106.c
函数:
rv1106_vop_set_clk(),当前 958-1020 行
4.1 从寄存器掩码计算最大分频
位置:
962-966 行
ulong best_rate = 0, candidate_rate;
u32 best_div = 0, div;
const u32 max_div =
(DCLK_VOP_DIV_MASK >> DCLK_VOP_DIV_SHIFT) + 1;
int sel = DCLK_VOP_SEL_CPLL;
结果:
DCLK_VOP_DIV_MASK = 0x1f << 3
字段最大编码 = 0x1f = 31
实际最大分频 = 31 + 1 = 32
不再使用没有边界检查的任意整数分频。
4.2 拒绝 0 Hz 请求
位置:
985-986 行
if (!rate)
return -EINVAL;
防止 DIV_ROUND_UP() 除以 0。
4.3 分别评估 CPLL 和 GPLL
位置:
- CPLL:
988-992 行 - GPLL:
994-1002 行 - 无合法组合:
1004-1005 行
div = DIV_ROUND_UP(priv->cpll_hz, rate);
if (div && div <= max_div) {
best_div = div;
best_rate = priv->cpll_hz / div;
}
div = DIV_ROUND_UP(priv->gpll_hz, rate);
if (div && div <= max_div) {
candidate_rate = priv->gpll_hz / div;
if (candidate_rate > best_rate) {
sel = DCLK_VOP_SEL_GPLL;
best_div = div;
best_rate = candidate_rate;
}
}
if (!best_div)
return -EINVAL;
选择规则:
- 使用
DIV_ROUND_UP,保证实际输出不高于请求值。 - 只接受 1~32 的硬件合法分频。
- 如果两个父时钟都合法,选择实际输出最高、即最接近请求值的组合。
- 相同结果时保留 CPLL,减少无意义的父时钟切换。
4.4 7 MHz 请求的最终结果
| 父时钟 | 计算分频 | 是否合法 | 实际 DCLK |
|---|---|---|---|
| CPLL 216 MHz | ceil(216 / 7) = 31 | 是,31 ≤ 32 | 216 / 31 = 6.967741935 MHz |
| GPLL 1188 MHz | ceil(1188 / 7) = 170 | 否,170 > 32 | 不使用 |
最终选择:
父时钟:CPLL 216 MHz
实际分频:31
寄存器分频编码:31 - 1 = 30
实际像素时钟:约 6.967742 MHz
该结果低于屏幕的 7 MHz 上限。
按照当前 porch:
HTOTAL = 240 + 38 + 10 + 10 = 298
VTOTAL = 320 + 8 + 4 + 4 = 336
刷新率约为 6,967,742 / 298 / 336 = 69.59 Hz
4.5 对寄存器写入值显式做掩码
位置:
1007-1013 行
rk_clrsetreg(&cru->clksel_con[23],
DCLK_VOP_SEL_MASK |
DCLK_VOP_DIV_MASK,
((sel << DCLK_VOP_SEL_SHIFT) &
DCLK_VOP_SEL_MASK) |
(((best_div - 1) << DCLK_VOP_DIV_SHIFT) &
DCLK_VOP_DIV_MASK));
修改原因:
- 即使上层以后再次出现错误值,也不允许分频字段污染父时钟选择位。
- 父时钟选择值只能写入
DCLK_VOP_SEL_MASK。 - 分频值只能写入
DCLK_VOP_DIV_MASK。 - 与前面的最大分频检查组成双重保护。
5 删除 U-Boot 固定 get_mode,统一 DTS 时序和边沿
文件:
sysdrv/source/uboot/u-boot/drivers/video/drm/panel-lh24030c50.c
删除内容在修改前的位置:
- 固定
lh24030c50_get_mode():修改前275-296 行。 - 函数表中的
.get_mode = lh24030c50_get_mode:修改前302 行。
修改后的函数表位置:
275-280 行
static const struct rockchip_panel_funcs lh24030c50_funcs = {
.prepare = lh24030c50_prepare,
.unprepare = lh24030c50_unprepare,
.enable = lh24030c50_enable,
.disable = lh24030c50_disable,
};
修改原因:
- 不再注册
get_mode后,U-Boot 显示框架会进入display_get_timing_from_dts()。 - U-Boot 与 Kernel 都读取同一个 DTS:
- 240 × 320。
- 相同的前后肩和同步宽度。
clock-frequency = 7000000。pixelclk-active = 0。
- U-Boot 因此能够正确填写
conn_state->bus_flags。 - U-Boot 和 Kernel 的 RV1106 VOP 最终使用相同的 DCLK 极性,交接时不再翻转像素采样边沿。
没有改变的内容:
- LCD 初始化命令。
- RGB666 总线格式。
- BPC=6。
- 分辨率和 porch。
- HSYNC、VSYNC、DE 极性。
- Logo 图片名称。
6 Kernel 使用统一的 kHz 舍入比较 DCLK
文件:
sysdrv/source/kernel/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
修改位置:
- 当前 DCLK 转 kHz:
3221 行。 - 比较条件:
3227 行。
当前代码:
u32 dclk_khz = DIV_ROUND_UP(clk_get_rate(vop->dclk), 1000);
...
adjusted_mode->crtc_clock != dclk_khz
为什么不能只把旧代码的 * 100 改成 * 1000:
实际 DCLK 是 CPLL 的整数分频结果:
216000000 / 31 = 6967741.935... Hz
clk_get_rate() 的整数结果约为 6967741 Hz
DRM 模式时钟以整数 kHz 表示,模式修正代码已经在:
rockchip_drm_vop.c:3093-3095
使用以下规则:
adj_mode->crtc_clock =
DIV_ROUND_UP(clk_round_rate(vop->dclk,
adj_mode->crtc_clock * 1000),
1000);
因此模式时钟是:
ceil(6967741 / 1000) = 6968 kHz
如果直接比较 Hz:
6968 × 1000 = 6968000 Hz
6968000 != 6967741
仍然会被误判。
新代码把当前硬件 DCLK 也按照完全相同的规则转换为 kHz:
adjusted_mode->crtc_clock = 6968 kHz
dclk_khz = 6968 kHz
比较结果 = 相等
这样只有分辨率、同步参数或实际时钟真正改变时,Kernel 才会关闭平面并重设模式。
修复后的预期启动时序
U-Boot CRU 初始化
↓
板级 assigned-clock-rates 将 CPLL 设置为 216 MHz
↓
U-Boot VOP 请求 7 MHz
↓
选择 CPLL / 31,DCLK ≈ 6.967742 MHz
↓
U-Boot 从 DTS 读取 pixelclk-active=0
↓
显示 U-Boot Logo
↓
Linux CRU 初始化读取同一板级覆盖:CPLL 仍为 216 MHz
↓
不发生 216 MHz → 1 GHz 的中间切换
↓
VOP 节点再次请求 216 MHz,当前频率已经相同
↓
Kernel 当前 DCLK 与 adjusted_mode 都按 6968 kHz 比较
↓
不误判 mode_update,不关闭 Loader 显示平面
↓
Kernel Logo 接管
预期消除:
- 两次 CPLL slow-mode 造成的两条横向花带。
- U-Boot DCLK 分频溢出导致的超频花屏。
- U-Boot/Kernel 像素时钟边沿切换。
- Kernel 错误关闭显示平面导致的 Logo 跳变。
编译验证
1 U-Boot
执行:
cd /root/sdk/rv1106
./build.sh uboot
结果:
- 返回码:0。
drivers/clk/rockchip/clk_rv1106.o实际重新编译。drivers/video/drm/panel-lh24030c50.o实际重新编译。- U-Boot、SPL、TPL 和镜像打包成功。
- 新
uboot.img已安装到输出目录。
2 Kernel
执行:
cd /root/sdk/rv1106
./build.sh kernel
结果:
- 返回码:0。
rockchip_drm_vop.c参与完整 Kernel 编译并链接到 vmlinux。jw_borad_v1.dtb重新生成。resource.img和boot.img重新生成。- 新
boot.img已安装到输出目录。
最终 DTB 验证
最终 DTB:
/root/sdk/rv1106/sysdrv/source/kernel/arch/arm/boot/dts/jw_borad_v1.dtb
1 CRU 完整 assigned-clock-rates
执行:
fdtget -t u \
/root/sdk/rv1106/sysdrv/source/kernel/arch/arm/boot/dts/jw_borad_v1.dtb \
/clock-controller@ff3a0000 assigned-clock-rates
输出:
1188000000 216000000 1104000000 400000000 200000000
100000000 300000000 100000000 100000000 200000000
第二项已经是 CPLL 216 MHz。
2 VOP 指定 CPLL 频率
执行:
fdtget -t u \
/root/sdk/rv1106/sysdrv/source/kernel/arch/arm/boot/dts/jw_borad_v1.dtb \
/vop@ff990000 assigned-clock-rates
输出:
216000000
3 像素时钟边沿
执行:
fdtget -t u \
/root/sdk/rv1106/sysdrv/source/kernel/arch/arm/boot/dts/jw_borad_v1.dtb \
/panel/display-timings/panel-timing pixelclk-active
输出:
0
启动后如已启用 debugfs,可检查:
cat /sys/kernel/debug/clk/clk_summary | grep -E 'cpll|dclk_vop'
期望值:
- CPLL 约为
216000000 Hz。 - DCLK VOP 约为
6967741 Hz或显示为约6968 kHz。 - 启动过程中不应再出现 CPLL 先到 1 GHz、再回到 216 MHz 的过程。