
1. 问题现象与项目背景先说结论这次排查的是 RK3326 平台上一块 MIPI DSI 接口的屏幕休眠唤醒之后出现黑屏但背光亮着的现象。用一句话概括就是系统进 suspend 再 resume 之后背光驱动和电源都正常恢复了但 LCD 面板没有真正被点亮屏幕看起来像一块发光但无内容的白板/黑板。这个现象在 RK 平台调试 MIPI 屏幕时非常典型而且坑得很有规律。它不像是完全点不亮那种“从头开始查线序、查初始化序列”的入门问题而是属于“本来能亮、睡一觉起来不亮了”的深水区问题。也就是说屏本身、排线、初始化代码大概率是没问题的问题出在休眠唤醒过程中MIPI DSI 控制器和面板之间的状态没有同步好。什么人会碰到这个问题嵌入式驱动工程师、BSP 工程师、以及做 RK 方案整机定制的人。尤其是用 RK3326 这类主打低功耗、常用于带屏 IPC、智能门锁、学习平板、楼宇对讲等设备的芯片时休眠唤醒是必测场景这个 Bug 一旦存在客户那边基本上必现很难绕过去。这个标题看起来只是一个“现象描述”但背后其实牵涉到几个关键知识点RK 平台的休眠唤醒流程、MIPI DSI 控制器的 reset 时序、LCD 驱动里 panel 的 suspend/resume 回调、以及背光与 DSI 的同步关系。我会把这次排查的思路、关键代码位置、修改方案和踩坑记录都写下来供参考。2. 现象背后的原理拆解黑屏有背光到底说明什么2.1 MIPI DSI 点屏的基本流程要理解为什么“黑屏有背光”先要理解一块 MIPI 屏从上电到显示内容需要经过哪些环节。很多人一上来就怀疑初始化序列但其实到了休眠唤醒阶段初始化序列往往不是主要原因而是时序问题。正常点亮一块 MIPI DSI 屏大致要经历以下几个环节供电VCC、VCI、IOVCC 等各路电源必须按面板规格书上电。复位RESX 引脚拉低再拉高让面板内部逻辑复位。初始化通过 DSI 命令如 ST7701S 的 init code配置面板的扫描方向、电压、gamma 等。输出使能DSI 控制器切换到 video mode 或者 command mode开始发送像素数据。背光背光 IC 使能LED 亮起。任何一个环节断了屏幕都不会正常显示。而“黑屏有背光”这句话其实已经帮我们排除了供电和背光这两个环节的问题。剩下最可疑的就是复位时序、DSI 控制器状态、以及面板内部的 sleep out / display on 命令是否在唤醒后重新执行了。关键点在于休眠的时候系统为了省电通常会关掉 DSI 控制器的时钟、关掉 panel 的电源或者至少进入 sleep mode但背光 IC 可能因为硬件设计原因并没有被真正断电。这就导致唤醒后背光先亮而面板还在“睡觉”于是出现了——有光但没有画面的现象。2.2 休眠唤醒时 DSI 链路发生了什么在 Linux 内核里RK3326 平台的 DRM/KMS 驱动框架下休眠唤醒会走一套完整的回调链。简单说suspend 时从 DRM 层到 connector 层再到 panel 层逐级执行.disable()、.unprepare()resume 时反过来逐级执行.prepare()、.enable()。这里有个很容易被忽略的细节MIPI DSI 链路的物理层是高速串行差分信号进入 suspend 之后控制器会把 PHY 的 power down 掉PLL 也会停止整个 DSI 输出变成高阻态或者全零电平。而面板侧的接收端已经失去了参考时钟内部的 line buffer、寄存器状态可能会出现随机变化。如果 resume 时代码只是把背光打开了却没有把 DSI 链路的复位、命令重发、初始化流程重新走一遍那面板收到的就是一堆“无法理解”的信号自然就黑屏了。这个问题的本质就是resume 时的恢复顺序和延时和面板内部的状态恢复时序不匹配。2.3 背光先亮带来的干扰还有一个很容易踩的坑背光先亮会让面板在尚未收到有效显示数据的时候就处于“被观察”状态。如果你在调试时只是盯着屏幕看很容易误判成“面板坏了”或者“DSI 信号没出来”。实际上背光先亮只是硬件设计上的默认行为不代表 DSI 就真的没工作。在调试这种问题时我习惯先把背光强制关掉然后再去看 DSI 的波形和日志这样能排除背光干扰专注于面板是否真的被唤醒了。3. 针对 RK3326 平台的具体排查思路3.1 先确认代码路径和内核版本这次的排查是在 RK3326 平台上使用的内核版本是基于 Rockchip 官方 4.4 内核分支DRM 框架是传统的rockchip_drmpanel 驱动是自己移植的 ST7701S 或类似 MIPI 屏幕驱动。如果你打算按这个思路排查第一步是确认几件事内核里 MIPI DSI 的 controller 是rockchip_mipi_dsi.c。panel 驱动是panel-simple.c还是自己独立写的文件。dts 里backlight、panel、dsi节点的关系是否配置正确。确认 suspend/resume 回调是注册在mipi_dsi_driver上还是走 DRM panel helper。这一步的目的是搞清楚当前代码走的恢复链路是“标准 DRM 框架链路”还是“绕过了框架手动控制的链路”。两种情况下排查的方法完全不同。3.2 从 dmesg 日志里找痕迹如果问题必现可以先抓一份完整的休眠唤醒日志重点关注几个关键词rockchip-mipi-dsipanel-simplebacklightdsi相关的enable/disable/prepare/unprepare打印很多 BSP 的调试打印默认是开着的如果没开可以在 dts 里打开rockchip,debug或者直接加printk验证调用顺序。我在日志里最常看到的情况是suspend: panel_simple_suspend: disable backlight mipi_dsi: dsi_disable: ... mipi_dsi: dsi_unprepare: ... resume: panel_simple_resume: enable backlight mipi_dsi: dsi_prepare: ... mipi_dsi: dsi_enable: ...如果日志顺序是“先开背光后 enable dsi”那问题基本锁定背光时序和 DSI 时序需要调整。3.3 命令行快速验证法在还没有改代码之前可以先做一个快速验证进入系统后通过 sysfs 手动把屏幕休眠再唤醒一次。比如echo 0 /sys/class/backlight/backlight/brightness echo 4 /sys/class/gpio/export ...不过更直接的方式是echo mem /sys/power/state看恢复后是否黑屏。如果必现再通过dmesg看最后停在哪里基本就能锁定是哪个回调没有执行或者哪个回调执行顺序不对。如果手动 suspend 一次正常第二次不正常那就更倾向于“状态累积错误”例如寄存器没恢复或者 DSI PLL 没有重新 lock 上。4. 实操修复过程与代码级解读4.1 第一步调整 panel 驱动中的 prepare/unprepare 顺序这个问题的标准解法是确保 resume 时DSI 面板在背光亮起之前完成初始化。更准确地说是要保证整个 MIPI 链路的恢复顺序是DSI controller 恢复时钟和 PHY。Panel 重新上电、reset 释放。发送初始化序列 / 至少发送Sleep Out和Display On命令。最后打开背光。以panel-simple或自写的 panel 驱动为例要重点关注这几个回调static int panel_simple_prepare(struct drm_panel *panel) { // 1. regulator 重新上电 // 2. 拉高/拉低 reset 引脚 // 3. 发初始化命令 // 4. 调 mipi_dsi_dcs_set_display_on() } static int panel_simple_enable(struct drm_panel *panel) { // 打开 backlight }如果原代码里把display_on放在enable里而背光也是enable里打开那就要检查先后顺序。如果背光先于 DSI 命令发送就会黑屏有背光。一个典型的修复方式是把背光打开的动作延后比如从enable挪到一个unprepare之后或者用msleep强制延时。4.2 第二步检查 dsi controller 的 reset 与 lane 配置如果 panel 驱动顺序没问题下一步检查 DSI 控制器的rockchip_mipi_dsi.c里的dsi_enter_ulps和dsi_exit_ulps。RK3326 的 DSI 控制器在 suspend 时可能会进入 ULPSUltra-Low Power State如果退出 ULPS 的时序不对lane 上会一直处于低功耗状态导致后续的数据传输失败。关键函数大致是static void dsi_enter_ulps(struct dw_mipi_dsi *dsi) { // 进入低功耗 } static void dsi_exit_ulps(struct dw_mipi_dsi *dsi) { // 退出低功耗恢复高速传输 }常见的坑是dsi_exit_ulps里恢复 PLL 后没有等待稳定就立刻发数据。此时加延时udelay(100);或者调用接口phy_power_on(dsi-phy);需要确认 PHY 是否在 resume 时被正确上电。因为有些屏的初始化命令如果发得太早PHY 都没准备好命令就会丢弃。4.3 第三步确认 dts 中 reset-gpios、power-supply 配置还有一类情况黑屏有背光是硬件资源没有正确重新初始化导致的。比如说 reset 引脚在休眠唤醒时如果 GPIO 子系统把该 pin 的状态保存后没有正确恢复面板就一直处于复位状态。在 dts 里要注意几个点dsi { status okay; rockchip,cmd-mode; panel0 { compatible st7701s; reg 0; reset-gpios gpio2 5 GPIO_ACTIVE_LOW; backlight backlight; power-supply vcc_lcd; }; };如果reset-gpios配置了驱动里应该通过gpiod_set_value_cansleep(panel-reset_gpio, 1)来释放复位。如果这里没有做好唤醒后屏幕就是“处于复位状态的黑屏”。另外电源供电也不能忽略。有些硬件上LCD 的电源和核心板是共用一路 LDO系统 suspend 时如果把这路电关了resume 时没按正确时序重新打开也会出现背光亮但面板没内容的现象。4.4 第四步针对 ST7701S 这种常见屏幕的特别处理如果用的是 ST7701S它的初始化代码很长有很多厂商自定义命令。在休眠唤醒场景下其实不需要把整个 init code 全部重新发一遍但至少要确保以下命令被执行0x11Sleep Out0x29Display On很多驱动在unprepare时执行Sleep In0x10在prepare时执行Sleep Out0x11在enable时执行Display On0x29。这个分工看起来清晰但实际跑起来会因为各回调的调用时机、延时而踩坑。我建议的做法是在prepare的最后直接把Sleep Out 必要的 long write 命令发掉然后延时 120ms 以上再返回。这个延时很关键ST7701S 的 Sleep Out 退出时间通常在 120ms 左右市面上很多驱动只延时 20ms导致屏幕状态还没有准备好Display On命令就丢了。static int st7701s_prepare(struct drm_panel *panel) { // 上电、释放 reset msleep(50); mipi_dsi_dcs_exit_sleep_mode(dsi); msleep(120); // 按需重发初始化命令 st7701s_init(dsi); return 0; }4.5 第五步背光延时和 PWM 配置如果确认 DSI 链路都没有问题背光还是在屏幕还没准备好的时候亮了可以在背光驱动里加一个brightness设置的延时或者用 backlight 的fb_notifier机制在屏幕真正显示内容后再开背光。比较直接的方法是在 panel 的enable回调里设置背光并且保证调用顺序在 DSI 发包之后static int panel_simple_enable(struct drm_panel *panel) { struct panel_simple *p to_panel_simple(panel); // 确保 display on 命令已经发出去 if (p-dsi) mipi_dsi_dcs_set_display_on(p-dsi); // 延后打开背光 backlight_enable(p-backlight); return 0; }如果背光是 PWM 控制还需要确认 pwm 周期在 resume 后是否恢复。有些 PWM 控制器 suspend 时会保存寄存器但如果 dts 里没有配置enable-gpios可能导致背光 IC 一直处于关断状态这个就不是本文“黑屏有背光”的问题了但排查时也要留意。5. 常见问题与排查技巧实录5.1 必现黑屏优先怀疑时序如果休眠唤醒后 100% 黑屏优先怀疑的是时序问题。比如 reset 拉高后没有等稳定就发命令或者 Sleep Out 后延时不够。一个非常有效的排查技巧在prepare里加长延时如果延时加长了屏幕就亮了那说明就是时序不够。比如把 Sleep Out 后的延时从 120ms 加到 200ms如果问题消失基本就可以收工。5.2 偶发黑屏优先怀疑电源或时钟如果休眠唤醒后有时正常、有时黑屏这类问题往往和电源、时钟稳定性有关。重点检查几项PLL lock 是否正常。各路 LDO 的电压在 resume 后是否爬升到目标值。DSI lane 上是否有信号完整性干扰。偶发问题很难查我会习惯在 resume 路径里加一些延时放宽时序窗口看能否把偶发变成必现再一步步缩小范围。5.3 第二次休眠唤醒才黑屏状态未清理干净如果第一次正常、第二次黑屏基本可以断定某处状态没有在 suspend 时被正确复位。最常见的是unprepare里没有发 Sleep In导致 panel 内部寄存器在下一次唤醒时处于异常状态。解决办法是在 suspend 的unprepare阶段把面板彻底关机mipi_dsi_dcs_enter_sleep_mode(dsi); msleep(50);必要时把 regulator 和 reset 也一并关掉确保每次唤醒都是“冷启动”。5.4 调试工具和波形的使用如果你手头有逻辑分析仪或者示波器可以直接量 DSI 的 CLK 和 Data lane 波形看唤醒后是否有高速信号输出。注意 MIPI DSI 的 clock lane 在高速模式才能传输数据如果测量发现 CLK 一直是 LP 状态那么 DSI 控制器可能没有真正退出 ULPS。示波器测量时我一般会同时抓三路信号Reset 引脚电平变化。DSI Clock lane 是否有高速跳变。背光 enable 信号。根据三路信号的先后顺序就能快速判断是“背光太早”还是“DSI 太晚”。这个方法比看日志直观得多。6. 对 RK 平台相关经验的延伸与扩展建议6.1 不同 MIPI 屏的兼容性考虑这次是在 RK3326 上调试 ST7701S同样的排查思路也可用于 RK3288、RK3399、RK3566、RK3588 等平台。只要走的是 Rockchip 的 DRM/MIPI DSI 框架suspend/resume 链路都是类似的。不同的只是rockchip_mipi_dsi.c里的 PHY 参数和时钟配置。RK3588 的 DSI 支持更多 lane 和更高分辨率但休眠唤醒的逻辑几乎一致。6.2 把“休眠唤醒黑屏”变成一次完整验证在做整机产品时我建议不仅要测系统级 suspend/resume还要单独测一下“关背光不休眠”、“关电源不关背光”等场景。很多黑屏有背光的问题本质上是“某个外设处于中间状态”而这些中间状态往往只有在特定操作顺序下才会触发。比如有些设备会做“按键熄屏但系统不休眠”此时 MIPI 面板会收到 Display Off 命令背光关闭但 DSI 链路仍然高速工作。如果此时系统触发了一次休眠再唤醒就会出现面板状态与驱动预期不一致的情况也会表现为黑屏有背光。6.3 一个实用的补丁思路如果时间紧迫不想深挖可以在 resume 时强制把 panel 整个重新初始化static int panel_resume(struct device *dev) { struct panel_simple *panel dev_get_drvdata(dev); drm_panel_prepare(panel); drm_panel_enable(panel); return 0; }这种“暴力恢复”虽然不优雅但能解决大部分问题。代价是恢复时间会变长对于要求低功耗快唤醒的产品来说需要评估是否可接受。6.4 借助日志和关键代码定位最后再分享一个经验排查这类问题不要一上来就改代码先把 dts 里和 LCD 相关的节点梳理一遍再确认驱动回调函数的调用顺序最后再动手。很多时候黑屏有背光的问题并不仅仅是软件的问题也可能是硬件设计上背光控制和 DSI 复位没有关联导致的。如果能在硬件上把背光 enable 引脚和 panel 的 reset 引脚用时序控制起来这类问题的排查成本会大大降低。7. 排查结论与个人经验分享这次 RK3326 MIPI 休眠唤醒黑屏有背光的问题最终定位到是resume时背光 enable 的时机早于 DSI 面板初始化完成导致屏幕有背光但无图像。通过调整 panel 驱动里的prepare/enable回调顺序并在 Sleep Out 后增加足够的延时问题得到了解决。调试过程中让我印象最深的是这种问题往往不会给你任何明显的报错日志里一切正常电压正常背光正常DSI 波形也看起来有输出但屏幕就是不显示。这种情况下唯一可靠的方法是确认各个信号的相对时序而不是孤立地看某一路信号。我个人在实际操作中比较推荐的做法是在panel_simple_prepare的开头加一个高精度的ktime打印记录每一段的耗时。这样即使现象偶发也能从时间戳里看出哪一步耗时异常从而找到思路。另外建议所有做 RK 平台 BSP 的朋友在 bringup 阶段就主动做一次完整的休眠唤醒测试不要等客户反馈。这种黑屏有背光的问题在开发阶段发现并解决的成本远低于量产后再去改。就这么一个顺序调整可能就省下你和客户一整周的联调时间。