背光问题

路径: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>;
};
  1. U-Boot 显示 Logo 时,显示链路最终使用 CPLL 216 MHz。
  2. Linux 初始化 CRU 时,根据公共 rv1106.dtsi 把 CPLL 从 216 MHz 改成 1 GHz。
  3. 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 的连续性。

修改文件总览

序号文件当前行号修改内容
1sysdrv/source/kernel/arch/arm/boot/dts/jw_rv1106-lcd.dtsi109-122在板级 CRU 节点覆盖完整默认频率列表,把 CPLL 保持为 216 MHz
2同上124-128保留 VOP 对 CPLL 216 MHz 的要求,确保显示初始化目标明确
3sysdrv/source/uboot/u-boot/drivers/clk/rockchip/clk_rv1106.c35增加 216 MHz PLL 参数表项
4同上962-1013重写 DCLK 父时钟和分频选择,限制分频为 1~32并对写入值做掩码
5同上1182-1193PLL 改频成功后同步 CPLL/GPLL 软件缓存
6sysdrv/source/uboot/u-boot/drivers/video/drm/panel-lh24030c50.c275-280删除固定 get_mode 回调,让框架读取 DTS timing 和 bus_flags
7sysdrv/source/kernel/drivers/gpu/drm/rockchip/rockchip_drm_vop.c3221-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;

选择规则:

  1. 使用 DIV_ROUND_UP,保证实际输出不高于请求值。
  2. 只接受 1~32 的硬件合法分频。
  3. 如果两个父时钟都合法,选择实际输出最高、即最接近请求值的组合。
  4. 相同结果时保留 CPLL,减少无意义的父时钟切换。

4.4 7 MHz 请求的最终结果

父时钟计算分频是否合法实际 DCLK
CPLL 216 MHzceil(216 / 7) = 31是,31 ≤ 32216 / 31 = 6.967741935 MHz
GPLL 1188 MHzceil(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 的过程。