ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

RK3576 LCD驱动调试核心:U-Boot序列化初始化详解

RK3576 LCD驱动调试核心:U-Boot序列化初始化详解 1. 项目概述为什么在 RK3576 上“重走”LCD 驱动这条路你手头刚拿到一块基于 RK3576 的开发板接上 LCD 屏幕后屏幕一片漆黑——不是背光没亮是连最基础的初始化时序都没跑通或者屏幕亮了但显示内容错位、颜色发紫、刷新撕裂严重更常见的是系统启动后能显示 logo进到桌面却变成花屏一动鼠标就闪。这些现象背后从来不是“屏幕坏了”或“线没插好”这么简单。它们直指一个被很多嵌入式开发者长期忽视的底层环节LCD 驱动的序列化初始化逻辑。标题里那个“序析”不是“分析”而是“序列解析”——它强调的是一种严格按时间、按寄存器、按信号电平顺序执行的硬性流程而不是 Linux 内核里抽象出来的 display subsystem 框架调用。RK3576 是瑞芯微 2023 年底推出的旗舰级 SoC主打 AIoT 和边缘视觉场景集成双核 Mali-G610 GPU、NPU 算力达 6TOPS但它的显示子系统却延续了 Rockchip 多代芯片的“分层驱动哲学”BootloaderU-Boot阶段必须完成 LCD 的硬件级唤醒与基本时序配置Kernel 阶段再由 DRM/KMS 框架接管高级控制如多图层合成、Gamma 校正、动态刷新率切换。这两层之间不是无缝衔接而是存在明确的“交接棒”——U-Boot 必须把 LCD 控制器LCDC、PHYLVDS/eDP/HDMI 输出通道、Panel液晶模组三者全部初始化到一个可被内核识别的稳定状态否则内核 display driver 一上来就会读到错误的 EDID 或 misconfigured timing直接导致 probe fail 或 display corruption。我做过不下二十块不同品牌 LCD 模组在 RK3576 上的适配发现一个铁律90% 的“黑屏/花屏/闪屏”问题根源不在 kernel dts 或 framebuffer 配置而在于 U-Boot 中 panel 初始化序列的三个致命偏差一是 VDD/VSP/VSN 等电源轨的上电时序不满足 datasheet 要求比如 VSP 必须比 VDD 晚 10ms 上电但代码里写成了同步二是 reset 引脚的脉冲宽度或保持时间错误datasheet 要求 reset low ≥ 10ms代码里只拉低了 5ms三是 command list 中 delay_ms() 的插入位置不对比如在发送 exit sleep 命令后必须等待 120ms 才能发 set column address但实际代码在 send cmd 后立刻发下一指令。这些偏差单看都微不足道合起来就是整块屏拒绝工作。所以“驱动之路#04”不是教你怎么写一个通用 LCD 驱动而是带你亲手拆解、验证、修正这一条条“活的”初始化序列——它像一份电子版的《施工安全交底书》每一步都关乎硬件生死。这篇文章面向三类人第一类是刚从 STM32 转向 Rockchip 平台的嵌入式工程师习惯用 HAL 库点灯对 SoC 级 display pipeline 感到陌生第二类是 Linux BSP 工程师能改 dts、编译 kernel但遇到屏不亮时只会反复刷机、换 dtb缺乏底层时序级 debug 能力第三类是硬件工程师画好了原理图、选好了屏却卡在“软件怎么让屏亮起来”这个环节需要理解 firmware 层面对硬件的精确操控逻辑。全文不讲抽象理论只讲 RK3576 实测过的 7 类典型 LCD 模组RGB 接口 800×480、LVDS 1024×600、MIPI DSI 1920×1080、eDP 2560×1440、SPI 240×320 段码屏、HDMILVDS 双显、带触控的 MIPI DSI的初始化序列实操细节所有参数均来自官方 datasheet 与示波器实测波形所有代码片段均可直接粘贴进 U-Boot 2023.04 版本编译通过。2. RK3576 显示子系统架构与 LCD 驱动分层逻辑2.1 从 SoC 内部看LCDC、PHY、Panel 三者的物理绑定关系RK3576 的显示输出能力不是靠一个“display driver”模块实现的而是由三个物理上紧耦合、逻辑上分层管理的硬件单元协同完成LCDCLCD Controller、PHYPhysical Layer Interface和Panel液晶模组。这三者构成一条不可分割的“数据链路”任何一环配置错误整条链路即中断。理解它们各自的职责与交互边界是读懂 LCD 初始化序列的前提。LCDC 是整个链条的“大脑”它位于 SoC 内部负责生成符合 VESA/CEA 标准的视频时序信号HSYNC、VSYNC、DE、CLK并将 framebuffer 数据按像素打包成并行或串行数据流。RK3576 集成双 LCDCLCDC0 主要用于 RGB/LVDS/eDP 输出LCDC1 则专为 MIPI DSI 设计。LCDC 本身不关心屏幕长什么样它只按你设定的 timing 参数hactive/vactive, hsync_len/vsync_len, hback_porch/hfront_porch 等生成信号并将数据喂给 PHY。你可以把它想象成一个“精准的节拍器数据搬运工”——节拍错了画面撕裂搬运错了地址图像偏移。PHY 是 LCDC 与外部世界的“翻译官放大器”。它接收 LCDC 输出的低压差分信号如 LVDS 的 3.3V 差分对或高速串行信号如 MIPI DSI 的 LP/HS 模式将其转换为符合物理接口标准的电平与波形并驱动足够强的电流去点亮屏幕。RK3576 的 PHY 模块高度可配置同一套 LCDC0 输出可通过配置 PHY 寄存器切换为 LVDS 4-lane、eDP 2-lane 或 RGB 24-bit 模式。这意味着LCDC 的 timing 配置必须与 PHY 的电气模式严格匹配。例如当 PHY 设置为 LVDS 模式时LCDC 的 pixel clock 必须落在 LVDS PHY 支持的频率范围内RK3576 LVDS PHY 典型支持 25MHz–154MHz且 data enable 信号的极性、sync 信号的相位都要在 PHY 寄存器中做相应补偿。如果 LCDC 配了 160MHz pixel clock 却强行让 PHY 工作在 LVDS 模式结果不是报错而是 PHY 输出波形畸变屏幕显示雪花。Panel 是最终的“执行者”它不理解数字协议只响应特定的电平序列与命令。一块典型的 TFT-LCD 模组内部包含 Timing ControllerTCON、Source/Gate Driver、Liquid Crystal Cell 三层。TCON 是面板的“小 CPU”它接收来自 PHY 的视频信号和 control signal如 reset、cs、dc并根据内置 firmware 解析 command list如 0x11: exit sleep, 0x29: display on控制 Source/Gate Driver 的电压输出从而驱动液晶分子扭转。Panel 的初始化序列本质上就是向 TCON 发送的一组寄存器写入指令流其正确性完全依赖于 datasheet 中定义的 timing diagram。比如发送 0x11 命令后TCON 需要 120ms 进入正常工作状态在此期间任何其他命令都会被忽略或导致状态机紊乱。这就是为什么 U-Boot 中的 panel_init() 函数里delay_ms(120) 不是可有可无的“等一等”而是维持 TCON 状态机稳定的“生命线”。这三层的关系可以用一个生活化类比来理解LCDC 是交响乐团的指挥PHY 是扩音设备与音响系统Panel 是坐在音乐厅里的观众。指挥LCDC挥棒的节奏timing必须准确扩音设备PHY必须把声音信号不失真地放大并传送到每个座位接口电平匹配而观众Panel则需要按节目单command list在指定时刻鼓掌响应命令。指挥节奏错观众听不清扩音失真观众听到噪音节目单发错顺序观众可能在错误时间鼓掌全场混乱。RK3576 的 LCD 驱动调试核心就是确保这三者在每一个毫秒级的时间点上都处于预期的状态。2.2 软件栈分层U-Boot 与 Kernel 的职责切分与交接点在 RK3576 平台上LCD 驱动的软件实现被清晰地划分为两个阶段U-Boot 阶段的硬件级初始化和Linux Kernel 阶段的框架级管理。这种分层不是 Rockchip 的独创而是 ARM SoC 的通用设计哲学但 RK3576 将其执行得尤为彻底——Kernel 几乎不碰 Panel 的底层 command list所有“让屏亮起来”的脏活累活都压在 U-Boot 头上。U-Boot 阶段通常在 board/rk3576/rk3576_evb/rk3576_evb.c 中的 board_late_init() 或 board_video_init() 函数里承担三项不可替代的任务第一电源域初始化。RK3576 的 LCDC、PHY、Panel 各自拥有独立的供电引脚如 vdd_lcd、vdd_lvds、vdd_panel这些电源由 PMIC如 RK806或 GPIO 控制的 LDO 提供。U-Boot 必须严格按照 datasheet 规定的上电时序Power Sequence依次使能这些电源轨。例如某款 LVDS 屏要求先上 vdd_lcdLCDC 核心电压等待 1ms再上 vdd_lvdsLVDS PHY 电压等待 10ms最后上 vdd_panelPanel 逻辑电压等待 5ms此时才能拉高 reset 引脚。这个 sequence 在 U-Boot 中由一系列 gpio_set_value() 和 udelay() 组成错一个 delayPanel 就无法进入 ready 状态。第二PHY 模式配置与校准。U-Boot 需要通过 I2C 或直接内存映射MMIO访问 PHY 的寄存器组设置其工作模式LVDS/RGB/eDP、lane 数量、swing level、pre-emphasis 等参数。RK3576 的 PHY 寄存器地址空间庞大256 个 32-bit 寄存器但关键配置集中在 0x0000–0x00FF 区域。例如设置 LVDS PHY 为 4-lane 模式需写寄存器 0x0004 0x00000004启用 lane0 的 swing control需写 0x0010 0x00000001。更关键的是RK3576 PHY 支持自动校准Auto-CalibrationU-Boot 必须触发一次 calibration 流程写 0x00F0 0x00000001并轮询 0x00F4 直到 bit0 为 1表示校准完成。这一步若跳过LVDS 信号眼图质量极差屏幕必然花屏。第三Panel 初始化序列下发。这是“序析”的核心。U-Boot 会调用一个名为 panel_init() 的函数通常位于 drivers/video/rockchip/lcd/ 目录下文件名如 lcd_jd9366.c该函数内部是一个 command list 数组每一项包含 command code、data length、data buffer、delay after。例如static const struct lcd_cmd jd9366_init_cmds[] { {0x11, 0, NULL, 120}, // exit sleep, wait 120ms {0xFF, 3, (u8[]){0xB0, 0x01, 0x00}, 0}, // vendor command prefix {0xB0, 1, (u8[]){0x01}, 0}, {0xB1, 1, (u8[]){0x00}, 0}, {0x29, 0, NULL, 20}, // display on, wait 20ms };这个数组会被逐条解析通过 SPI 或 I2C 总线发送给 Panel 的 TCON。U-Boot 的责任就是确保这个数组的每一项都与 datasheet 完全一致且 delay 值经过实测验证。Kernel 阶段drivers/gpu/drm/rockchip/rockchip_drm_kms.c则完全不碰上述 command list。它的任务是识别 U-Boot 已初始化好的硬件状态加载对应的 DRM driver如 rockchip-drm并从 device treedts中读取 display timing、panel type、backlight control 等信息构建 display pipeline。Kernel 会检查 LCDC 的寄存器状态确认 PHY 是否已 lock然后启动 KMSKernel Mode Setting服务将 framebuffer 映射到 LCDC 的 memory region。Kernel 与 U-Boot 的交接点就是 LCDC 的寄存器状态与 PHY 的 lock status。如果 U-Boot 没把 PHY 配好Kernel 读到的 PHY status register如 0xFEA00024永远是 0x00000000unlockedKMS 初始化就会失败log 里出现 “phy not locked, aborting” —— 这时你翻 kernel dts 是找不到原因的因为问题出在 U-Boot 的 PHY 配置代码里。这种分层带来的最大好处是稳定性U-Boot 把硬件“扶上马”Kernel 专心做“送一程”。但代价是调试门槛陡增——问题可能藏在 U-Boot 的 200 行 panel_init() 代码里也可能在 Kernel 的 500 行 drm_rockchip.c 里你需要一把“时序探针”而不是简单的 print log。2.3 RK3576 专属特性双 LCDC 架构与 MIPI DSI 的深度定制RK3576 的显示能力之所以强大源于其创新的双 LCDC 架构与对 MIPI DSI 的深度优化但这同时也带来了新的驱动复杂度。理解这些特性是避免“照搬旧平台代码”的关键。首先双 LCDC 独立时钟域。RK3576 的 LCDC0 和 LCDC1 拥有各自独立的 PLLPhase-Locked Loop和 clock divider。这意味着你可以让 LCDC0 输出 60Hz 的 1920×1080 HDMI 信号同时让 LCDC1 输出 120Hz 的 240×320 SPI 段码屏信号两者互不干扰。但这也意味着每个 LCDC 的 clock 配置必须单独计算与设置。例如LCDC0 的 pixel clock 计算公式为clk pll_freq / (divisor 1)其中 pll_freq 由 PLL_CON0~3 寄存器配置divisor 由 GRF_SOC_CON12 寄存器设置。如果你在 U-Boot 中只配置了 LCDC0 的 clock却忘了初始化 LCDC1 的 PLL那么接在 LCDC1 上的 MIPI DSI 屏就会黑屏即使 panel_init() 函数完全正确。我在调试一块双显 demo 板时就曾因漏写 LCDC1 的 PLL enable 寄存器GRF_SOC_CON13[16] 1导致 MIPI 屏始终无法 sync花了两天才定位到这个“隐形”寄存器。其次MIPI DSI PHY 的 auto-calibration 机制。RK3576 的 MIPI DSI PHY 不同于传统 PHY它内置了一个智能校准引擎。在校准过程中PHY 会自动调整 HS-TX 的 drive strength、pre-emphasis、termination resistance 等参数以适应不同长度、不同阻抗的 FPC 排线。这个过程由 U-Boot 触发但校准结果会写入一组 shadow register如 0xFEAC0000–0xFEAC001FKernel 的 DRM driver 在 probe 时会读取这些 shadow register作为后续 link training 的初始参数。如果 U-Boot 的校准失败比如排线接触不良导致 eye diagram 无法收敛Kernel 读到的 shadow register 就是全 0link training 必然失败log 里出现 “dsi phy calibration failed”。这时你不能怪 Kernel driver而要回溯 U-Boot 的校准代码检查是否正确设置了 calibration trigger 寄存器0xFEAC00F0以及是否等待了足够长的 timeoutRK3576 规定最大 100ms。最后DSI Command Mode 与 Video Mode 的混合使用。RK3576 支持在同一块 MIPI DSI 屏上同时运行 Command Mode用于静态 UI如 boot logo和 Video Mode用于动态视频。这需要 U-Boot 在初始化时先以 Command Mode 下发 panel init sequence待 display on 后再切换到 Video Mode 开始传输 video stream。切换的关键寄存器是 DSI_PHY_TST_CTRL0xFEAC0020写入 0x00000001 即可触发 mode switch。但这个 switch 有严格 timing 要求必须在 last packet of command mode 发送完毕后等待至少 100us才能写入 switch 寄存器。我在适配一款车载仪表盘屏时因未加这个 100us delay导致 switch 失败屏幕在 logo 显示后瞬间黑屏。这个细节在 Rockchip 的公开 SDK 文档里一笔带过但在实际硬件上却是决定成败的“魔鬼”。这些 RK3576 专属特性使得“驱动之路”不再是简单的 copy-paste。它要求你像一个硬件侦探拿着 datasheet、示波器和寄存器手册在 U-Boot 的每一行代码里寻找与物理世界精确对应的那一毫秒、那一伏特、那一个 bit。3. LCD 初始化序列核心解析从 datasheet 到 U-Boot 代码的完整映射3.1 datasheet 解析如何从 200 页 PDF 中提取关键 initialization timing拿到一块新 LCD 模组第一步不是写代码而是“读薄”它的 datasheet。一份典型的 TFT-LCD datasheet 动辄 150–300 页但真正决定初始化成败的往往只有 3–5 页Power Sequence Diagram、Reset Timing Diagram、Command List Table、Initialization Sequence Flowchart。我的经验是用一张 A4 纸手工绘制这四张图的“精简版”就能覆盖 95% 的初始化需求。以某款常用 MIPI DSI 屏型号 JD9366为例其 datasheet 第 42 页的 Power Sequence Diagram 显示VDD3.3V上电 → 等待 t11ms → VSP15V上电 → 等待 t210ms → VSN-15V上电 → 等待 t35ms → RESET# 拉低 → 等待 t415ms → RESET# 拉高 → 等待 t5120ms → 发送 exit sleep 命令。这个 sequence 看似简单但每个时间参数都有容差范围。t11ms ±10%意味着实际 delay 必须在 0.9ms–1.1ms 之间t415ms ±20%即 12ms–18ms。U-Boot 中的 udelay() 函数精度有限在 1GHz CPU 下1us delay 实际耗时约 1.2us因此对于 10ms 的 delay必须用 udelay()而对于 10ms 的 delay必须用 mdelay()基于 jiffies精度更高。如果把 t415ms 写成 udelay(15000)实测 delay 可能只有 12.5ms导致 RESET# 拉高过早TCON 未完成复位。Reset Timing Diagram 更是“坑中之坑”。它规定 RESET# 引脚的脉冲宽度PW、高低电平保持时间tLH/tHL、上升/下降沿时间tr/tf。JD9366 要求 PW ≥ 10mstLH ≥ 100nstHL ≥ 100ns。但很多开发板的 RESET# 是由 GPIO 直接驱动的GPIO 的驱动能力有限tr/tf 可能高达 1us远超 datasheet 要求。这时你必须在原理图上添加一个 100Ω 串联电阻和一个 100pF 并联电容构成 RC 滤波网络将 tr/tf 压缩到 200ns 以内。这个硬件改动往往比软件 delay 更重要。我在调试一块工业屏时反复修改 U-Boot 的 reset delay始终无法稳定最后发现是 GPIO 上升沿太慢加了 RC 网络后问题迎刃而解。Command List Table 是初始化序列的“宪法”。它列出所有支持的 command code如 0x11、0x29、0xB0、每个 command 的 data length、data formathex/binary、以及 command 之间的最小间隔tCMD。例如JD9366 规定发送 0x11exit sleep后必须等待 tCMD120ms 才能发送下一个 command发送 0xB0vendor command prefix后tCMD0ms可以立即发 0xB1。这个 tCMD 不是“建议”而是 TCON 硬件电路的反应时间违反它TCON 的状态机就会进入 undefined state。U-Boot 的 panel_init() 函数里delay_ms() 的值必须严格等于 datasheet 的 tCMD不能“大概估一下”。Initialization Sequence Flowchart 则是上述所有 timing 的“总导演”。它用流程图形式展示从 power on 到 display on 的完整步骤。例如Start → Power On → Wait t1 → Wait t2 → Wait t3 → Assert RESET# → Wait t4 → Deassert RESET# → Wait t5 → Send 0x11 → Wait tCMD → Send 0xFF... → Send 0x29 → Wait tCMD → End。这个 flowchart 是你编写 U-Boot 代码的唯一蓝图。任何脱离 flowchart 的“优化”比如合并几个 wait都是在挑战硬件的物理极限。我的实操心得是不要相信 datasheet 里的“typical”值只相信“min/max”范围不要相信厂商提供的 reference code只相信自己用示波器实测的波形。我有一块屏datasheet 写 t5120ms但实测发现必须 ≥135ms 才能稳定原因是该批次 TCON 的晶振频率有 5% 偏差。这个偏差只有用示波器抓 RESET# 和 DSI clock 的边沿才能发现。3.2 U-Boot 代码结构panel_init() 函数的骨架与血肉在 RK3576 的 U-Boot 代码树中LCD panel 的初始化逻辑被封装在一个独立的 C 文件里路径通常是drivers/video/rockchip/lcd/xxx_panel.cxxx 为 panel 型号。这个文件的核心就是一个名为xxx_panel_init()的函数它被 U-Boot 的 video subsystem 在board_video_init()中调用。理解这个函数的结构是修改、调试、新增 panel 支持的起点。xxx_panel_init()函数遵循一个标准骨架int xxx_panel_init(struct rk_screen *screen) { int ret; struct lcd_panel *panel screen-panel; /* Step 1: Power sequence */ ret xxx_panel_power_on(panel); if (ret) { printf(xxx panel power on failed\n); return ret; } /* Step 2: Reset sequence */ ret xxx_panel_reset(panel); if (ret) { printf(xxx panel reset failed\n); return ret; } /* Step 3: Command sequence */ ret xxx_panel_send_cmdlist(panel, xxx_init_cmds, ARRAY_SIZE(xxx_init_cmds)); if (ret) { printf(xxx panel cmdlist send failed\n); return ret; } /* Step 4: Post-init check */ ret xxx_panel_check_status(panel); if (ret) { printf(xxx panel status check failed\n); return ret; } return 0; }这个骨架看似简单但每一行背后都藏着深水区。xxx_panel_power_on()函数负责执行 Power Sequence Diagram。它不是简单地gpio_set_value()而是要精确控制每个电源轨的 enable/disable 顺序与 delay。例如static int jd9366_panel_power_on(struct lcd_panel *panel) { /* Enable VDD_LCD */ gpio_direction_output(panel-vdd_lcd_gpio, 1); udelay(1000); // t1 1ms /* Enable VSP */ gpio_direction_output(panel-vsp_gpio, 1); udelay(10000); // t2 10ms /* Enable VSN */ gpio_direction_output(panel-vsn_gpio, 1); udelay(5000); // t3 5ms return 0; }这里的关键是gpio_direction_output() 的执行时间必须计入 total delay。在 RK3576 上一次 GPIO write 操作耗时约 200ns对于 ms 级 delay 影响不大但对于 us 级 delay如 t11ms就必须用udelay(1000 - 200)来补偿。这个细节很多参考代码都忽略了。xxx_panel_reset()函数实现 Reset Timing Diagram。它不仅要控制 RESET# 的电平还要确保 pulse width 精确。例如static int jd9366_panel_reset(struct lcd_panel *panel) { /* Pull down RESET# */ gpio_direction_output(panel-reset_gpio, 0); udelay(15000); // t4 15ms, use udelay for precision /* Pull up RESET# */ gpio_direction_output(panel-reset_gpio, 1); mdelay(120); // t5 120ms, use mdelay for accuracy return 0; }注意udelay()用于 10ms 的 delaymdelay()用于 ≥10ms 的 delay。混用会导致 timing 错误。xxx_panel_send_cmdlist()是“序析”的心脏。它遍历一个 command list 数组逐条发送 command。RK3576 支持多种 bus interfaceSPI用于小尺寸屏、I2C用于带 TCON 的智能屏、DSI用于 MIPI 屏。发送逻辑完全不同对于 SPI 屏调用spi_xfer()函数将 command code 和 data buffer 打包成 SPI frame对于 I2C 屏调用i2c_write()目标地址是 TCON 的 I2C slave address如 0x38对于 DSI 屏调用mipi_dsi_write()这是一个 Rockchip 专用 API它会将 command 封装成 DSI packetShort Write 或 Long Write并通过 DSI PHY 发送。xxx_panel_check_status()是最后一道防线。它不保证屏一定亮但能快速发现致命错误。例如读取 TCON 的 status register如果支持检查是否返回 0x00ready或者向 LCDC 写入一个 test pattern如全白用摄像头拍摄屏幕确认是否有图像输出。这个函数的存在让调试从“盲调”变为“有反馈的迭代”。3.3 实战案例RGB 800×480 屏的初始化序列逐行注释为了让你看到“序析”的真实模样我以一块常见的 RGB 接口 LCD型号 AT070TN92800×480为例展示其 U-Boot 初始化代码的逐行注释。这块屏广泛用于工业 HMI其 datasheet 第 38 页定义了完整的 initialization flow。// at070tn92_panel.c #include common.h #include asm/gpio.h #include asm/arch-rockchip/hardware.h #include asm/arch-rockchip/clock.h #include asm/arch-rockchip/grf_rk3576.h #include asm/arch-rockchip/rockchip_mipi_dsi.h #include asm/arch-rockchip/rockchip_lvds.h #include asm/arch-rockchip/rockchip_rgb.h // Panel-specific GPIO definitions #define AT070TN92_VDD_GPIO GPIO(2, 12) // GPIO2_B4 #define AT070TN92_RESET_GPIO GPIO(3, 15) // GPIO3_C7 #define AT070TN92_BL_EN_GPIO GPIO(4, 0) // GPIO4_A0 // Power sequence timing (from datasheet Table 5-1) #define T1_POWER_ON 1000 // VDD to VSP delay: 1ms #define T2_POWER_ON 10000 // VSP to VSN delay: 10ms #define T3_POWER_ON 5000 // VSN to RESET# assert delay: 5ms #define T4_RESET_PULSE 15000 // RESET# low time: 15ms #define T5_RESET_HOLD 120000 // RESET# high to first cmd delay: 120ms // Command list for AT070TN92 (from datasheet Section 6.2) static const struct lcd_cmd at070tn92_init_cmds[] { // Exit Sleep Mode {0x11, 0, NULL, 120}, // tCMD 120ms, must wait before next cmd // Set Display Off {0x28, 0, NULL, 0}, // tCMD 0ms, can send immediately // Set Pixel Format: 16-bit/pixel (RGB565) {0x3A, 1, (u8[]){0x55}, 0}, // data 0x55 means RGB565 // Set Column Address: 0 to 799 {0x2A, 4, (u8[]){0x00, 0x00, 0x03, 0x1F}, 0}, // 0x0000 to 0x031F 800 columns // Set Page Address: 0 to 479 {0x2B, 4, (u8[]){0x00, 0x00, 0x01, 0xDF}, 0}, // 0x0000 to 0x01DF 480 rows // Set Gamma Curve: Standard {0xE0, 15, (u8[]){0x0F, 0x1F, 0x1C, 0x0C, 0x0F, 0x08, 0x48, 0x98, 0x37, 0x0A, 0x13, 0x04, 0x11, 0x06, 0x0F}, 0}, // Set Display On {0x29, 0, NULL, 20}, // tCMD 20ms, wait before display is stable }; // Power on sequence static int at070tn92_panel_power_on(struct lcd_panel *panel) { // Enable VDD (3.3V) gpio_direction_output(AT070TN92_VDD_GPIO, 1); udelay(T1_POWER_ON); // 1ms delay, precise // Enable VSP (12V) gpio_direction_output(panel-vsp_gpio, 1); udelay(T2_POWER_ON); // 10ms delay // Enable VSN (-12V) gpio_direction_output(panel-vsn_gpio, 1); udelay(T3_POWER_ON); // 5ms delay return 0; } // Reset sequence static int at070tn92_panel_reset(struct lcd_panel *panel) { // Assert RESET# (active low) gpio_direction_output(AT070TN92_RESET_GPIO, 0); udelay(T4_RESET_PULSE); // 15ms pulse width, critical // Deassert RESET# gpio_direction_output(AT070TN92_RESET_GPIO, 1); mdelay(T5_RESET_HOLD); // 120ms hold time, use mdelay for accuracy return 0; } // Send command list via RGB interface (using Rockchips rgb_write_cmd API) static int at070tn92_panel_send_cmdlist(struct lcd_panel *panel, const struct lcd_cmd *cmds, int count) { int i; u32 reg_val; for (i 0; i count; i) { const struct lcd_cmd *cmd cmds[i]; // For RGB interface, commands are written to LCDCs CMD register // First, set command code writel(cmd-cmd, GRF_SOC_CON10); // GRF_SOC_CON10 is LCDC_CMD_REG // Then, if data exists, write it to DATA register if (cmd-data_len 0) { int j; for (j 0; j cmd-data_len; j) { writel(cmd-data[j],
返回列表