ARTICLE DETAIL

资讯详情

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

MTK平台LCD驱动实战:从零点亮屏幕与避坑指南

MTK平台LCD驱动实战:从零点亮屏幕与避坑指南 调试LCD屏幕这些年踩过的坑比自己挖的还多。屏幕点不亮、花屏、闪屏、颜色发蓝发红这些问题每个都能折腾你大半天最后发现多半是MTK平台LCD驱动里某个细节没做对。这篇文章我想用一套完整的实战思路带你把MTK平台下的Android屏幕从零开始点亮把项目里真正会遇到的坑和关键参数都捋一遍。先说清楚这篇文章是写给谁的。如果你正在做Android BSP移植、嵌入式Linux驱动开发或者刚接手一个基于MTK平台的硬件方案需要适配屏幕这篇内容会非常对口。你要是做应用层的可以绕道了不耽误你时间。我尽量把每个关键步骤背后的意图讲透而不是只丢几个现成的代码片段给你。1. 摸清整体链路一条屏幕点亮请求是怎么走通的很多人拿到MTK项目就开始改代码连显示链路是什么样都没搞清楚出了问题自然抓瞎。我建议你动手之前先花半小时把屏幕点亮这条链路弄清楚后面调试的时候思路会清晰很多。MTK平台的LCD点亮逻辑和你在教科书里看到的标准Linux显示栈不太一样它有自己的一套调度方式特别是bootloader阶段和内核阶段各有一份驱动。1.1 从LK到Kernel屏幕点亮为什么要走两遍MTK平台有个叫LKLittle Kernel的bootloader阶段在这个阶段系统就会尝试点亮屏幕用于显示开机logo。很多人忽略了这一点只改了内核里的驱动结果开机logo点不亮系统起来后屏幕却正常或者反过来。所以做MTK平台的显示屏适配你至少要改两处LK阶段的驱动和Kernel阶段的驱动。两边各有一份lcm_drv结构体代码逻辑基本上一样。改了其中一边忘记另外一边就会遇到开机阶段和系统阶段表现不一致的诡异问题。Kernel阶段的驱动在drivers/misc/mediatek/lcm/目录下LK阶段的代码则在bootable/bootloader的相应目录下。你拿到一个屏厂的初始化序列之后两边都要同步更新。我自己的习惯是先把Kernel那边调试通了再照着把LK同步过去。1.2 框架层级划分你的改动是在哪一层把MTK显示链路按层次拆开看大概是这么个结构Android应用发起屏幕操作请求经过SurfaceFlinger统一合成交给显示硬件抽象层。HAL层调用内核的显示驱动接口MTK平台上有自己的一套DISP/MDP框架不同于高通的DRM/KMS标准框架。内核显示驱动最终找到匹配的LCM驱动把初始化序列通过DSI控制器发到屏幕模组。你在实际开发中需要改动的核心就是那层LCM驱动。它负责告诉系统“我这块屏是什么型号、用什么时序、初始化要发哪些命令”。想明白了这层关系你改代码的时候心里就有底了。屏幕上电、时序配置、初始化序列归根结底都是LCM驱动在做事情。1.3 开发流程总览从拿到规格书到点亮屏幕一个完整的MTK屏幕适配项目大致可以分成下面这几步拿到屏厂的规格书、初始化序列代码、参考电路图。核对硬件连接确认数据lane、时钟lane、复位脚、背光脚都对应正确。编写或修改设备树DTS配置把屏参、时序、GPIO信息配好。修改LCM驱动把初始化序列和回调函数填充进去。编译LK和Kernel镜像烧录验证。看日志和屏幕现象反复微调初始化序列、时序参数直至稳定。每一步都有值得单独讲的细节。下面我按这个顺序一段一段拆开来说。2. 配置不可乱碰设备树DTS里的LCD参数逐项解析设备树是整个屏幕配置的入口MTK平台虽然保留了一些传统的平台数据方式但主流方案已经全面转向DTS设备树。你打开项目对应主板的DTS文件会看到lcm相关的节点里面密密麻麻的参数特别容易看晕。2.1 找对位置DTS文件在哪里改MTK工程的设备树文件一般放在arch/arm64/boot/dts/目录老平台也可能是arch/arm/boot/dts/。项目会有一个主DTS和一个或多个DTSI文件LCD的配置节点通常写在主DTS里也可以放在独立的lcm相关DTSI中再用#include引进来。实际操作中用grep搜lcm关键词就能定位到相关节点grep -rn lcm arch/arm64/boot/dts/你的项目目录/看到lcm_params、lcm_physical_params这些节点基本就是屏幕配置的核心位置了。2.2 核心节点参数说明每项都代表什么以一块1080x1920的MIPI DSI屏幕为例DTS里常见的关键节点结构大致如下lcm_params { lcm-params-type LCM_TYPE_DSI; lcm-params-resolution 1080 1920; lcm-params-vertical-sync-pulse 4; lcm-params-vertical-back-porch 8; lcm-params-horizontal-back-porch 60; lcm-params-horizontal-front-porch 60; lcm-params-lane-number 4; lcm-params-pclk 150; lcm-params-dsi-controller 0; };这里的lcm-params-type指明屏幕接口类型DSI接口基本已经成为智能机和平板的主流。resolution不用多说就是宽高像素。后面几个 porch 和 pulse 参数是同步时序屏厂规格书里一般会直接给出来照填就行。lane-number是MIPI数据通道数常见为4 lane。pclk是像素时钟频率单位通常是MHz。2.3 时序参数背后的计算逻辑很多初学者直接照搬屏厂给的时序参数不关心它们是怎么算出来的。其实理解了计算关系之后后面排查花屏问题会轻松很多。MIPI DSI的时钟频率和帧率强相关核心公式是DSI总像素时钟 (H_Active H_BackPorch H_FrontPorch H_SyncPulse) × (V_Active V_BackPorch V_FrontPorch V_SyncPulse) × 帧率然后根据数据位宽和lane数量计算DSI时钟频率DSI_CLK 总像素时钟 × 每像素位数 / lane数比如1080x1920的屏60Hz刷新率24bpp4 lane带垂直同步脉冲4、垂直后沿8、水平后沿60、水平前沿60这些典型值算出来的像素时钟大约在148MHz到160MHz之间。DTS里lcm-params-pclk配置成150MHz左右是比较合理的。这里的总像素时钟理解为“屏幕每秒要处理的全部像素点”就可以类比为流水线上单位时间需要处理的工件数量。lane数越多相当于流水线分工越细每条通道需要传的数据就越少。如果配置的pclk偏低屏幕可能出现刷新率不足、闪屏、或者背光滚动条纹的问题。如果偏高则可能出现花屏、图像撕裂。所以这个值需要结合示波器实测微调不可以完全照搬。DTS里还有一个东西容易忽略就是lcm_gpio节点复位脚、背光使能脚、CABC脚这些GPIO定义都在这里。硬件上GPIO接错了软件改再多也白搭。3. LCM驱动代码实现从初始化序列到各回调函数设备树只是告诉系统“有一块这样的屏”真正干活的还是LCM驱动本身。MTK平台LCM驱动是一个典型的字符设备驱动加平台驱动范式核心逻辑全部围绕一个LCM_DRIVER结构体展开。3.1 驱动结构体解析每个回调函数的职责看一下drivers/misc/mediatek/lcm/下已有的某个屏幕驱动文件它的核心结构是这样static LCM_DRIVER nt35595_hd720_dsi_vdo_lcm_drv { .name nt35595_hd720_dsi_vdo, .set_util_funcs lcm_set_util_funcs, .get_params lcm_get_params, .compare_id lcm_compare_id, .init lcm_init, .suspend lcm_suspend, .resume lcm_resume, };set_util_funcs工具函数注册MTK LK和Kernel共用一套用于设置复位GPIO电平、延时、DSI命令发送这些底层操作。get_params把DTS里的参数或者编译期参数回传给框架供上层计算时钟和时序。compare_id读取屏幕ID寄存器和匹配表中的ID比较用来判断当前驱动是否支持这块屏。匹配不上的话屏幕就点不亮。init屏幕初始化入口包含完整的init序列下发。suspend/resume休眠和唤醒时的命令处理一般是屏幕进入睡眠模式、关闭显示、再唤醒后重新初始化。这块结构体是双方约定的接口不要轻易增删字段否则LK和Kernel两边版本不一致会导致奇怪的编译或运行问题。入口处还有lcm_drv的导出和匹配表不同平台写法略有差异。新平台有的已经改成标准compatible匹配方式老平台还在用lcm_compare_id轮询匹配。3.2 初始化序列屏厂的代码要这样放进去屏厂一般会给你一份类似这样的初始化序列我们拿到手先要解析成命令数组然后调用DSI发送接口逐条下发。MTK常见的工具函数接口是lcm_util.dsi_set_cmd()每个命令对应一个数据结构static struct LCM_DSI_CMD lcm_init_cmds[] { // 进入命令模式 {0x00, 1, {0x00}}, // 读取命令通常是进入page 0 {0xFF, 3, {0xFF, 0x20, 0x00}}, // 多个寄存器写入持续init {0x35, 1, {0x00}}, // 退出命令模式 {0x00, 1, {0x00}}, ... };每条命令的格式通常是{命令码, 参数个数, 参数数组}。有些屏厂给的初始化序列里还带着delay指令用于等待某些电压或寄存器就绪。MTK框架里可以在数据结构里增加延时字段也可以直接调用mdelay()。发送初始化序列的函数一般是这么写的static void lcm_init(void) { unsigned int i; for (i 0; i sizeof(lcm_init_cmds) / sizeof(LCM_DSI_CMD); i) { lcm_util.dsi_set_cmd(lcm_init_cmds[i]); if (lcm_init_cmds[i].delay 0) mdelay(lcm_init_cmds[i].delay); } }需要特别注意初始化序列每一行都不能删。早期我在一个项目上觉得某条读寄存器指令没用删掉之后屏幕颜色整体偏绿查了一整天才定位到是初始化序列少了一条。屏厂给的每一行要么是配置显示驱动IC的工作模式要么是设置电压要么是调整伽马都有存在的意义。3.3 compare_id这块驱动到底匹不匹配你的屏compare_id的作用是从MIPI DSI总线读取屏幕驱动IC内部寄存器判断当前连接的屏是不是这块屏对应的型号。常见实现方式如下static unsigned int lcm_compare_id(void) { unsigned int id 0; unsigned char buf[4] {0}; // 读0xDA寄存器返回3个字节的ID lcm_util.dsi_set_cmd(0xDA, 3, buf); id (buf[0] 16) | (buf[1] 8) | buf[2]; if (id 0x002035) return 1; return 0; }注意这里读寄存器之前通常要先发送一条进入读ID模式的命令。不同IC的ID地址不一样这些信息在屏厂规格书里都有说明。如果compare_id返回0系统会认为没有屏连接直接放弃初始化。很多时候屏幕点不亮不一定是初始化序列问题而是compare_id没通过。3.4 时序和时钟相关要不要动怎么动get_params回调会把屏幕分辨率、时序、DSI控制器参数返回给上层框架。在MTK的DTS时代很多参数已经放到了DTS里但这个回调仍然存在用于配合框架注册时钟树。如果屏幕出现奇怪的闪屏、局部花屏除初始化序列外优先检查get_params里返回的lane数量、pclk、porch参数是否和DTS一致。两边不一致的情况下框架层会用get_params的参数来配置时钟DTS里的反而成了摆设就容易出现“明明DTS改对了屏幕还是花”的情况。4. 点亮屏幕实操拿一块新屏手把手跑通流程理论说了一大堆接下来进入实战环节。假设现在你手里有一块此前没有人点亮过的MIPI DSI屏硬件连接没大问题我们要从头把它点亮。这个流程我走过很多遍每步都有可复用的经验。4.1 硬件确认上电顺序和复位时序不能错拿到新屏第一件事不是写代码而是用万用表量电压。MIPI DSI屏一般需要多路电压主供电VCI通常2.8V或3.3V、IO供电VDDIO常见1.8V、显示供电VSP/VSN正负电压用于驱动液晶面板。逐项确认这些电压在kick-off之后按时序稳定输出。有些屏对电压上电顺序非常苛刻要求VCI先于VDDIO或者VSP/VSN要有几百毫秒的延时。这些细节屏厂都会画在上电时序图里前期不确认到位后面点不亮的时候排查会非常痛苦。复位脚时序也是一个常见坑点。标准流程是拉低复位脚持续至少10ms。拉高复位脚等待120ms以上。再发送初始化序列。这个时长要求来自屏幕驱动IC的数据手册。别等到系统起来后才发初始化命令强烈建议在LK阶段就完成复位和基础初始化这样开机logo能正常显示也不容易遇到一些框架侧的时序竞争问题。4.2 按模板创建或者修改LCM驱动在drivers/misc/mediatek/lcm/目录下新建一个驱动文件复用平台上已有的相似屏幕驱动作为模板这样做的好处是结构体、注册函数这些框架代码不会出错。把屏厂提供的初始化序列转换成LCM驱动需要的数据结构。你自己维护一个转换脚本很多屏厂给的是Excel表格或者纯文本形式手动复制特别容易漏行错一个字节就可能点不亮。转换时注意几个关键点CMD_TYPE有的平台需要区分标准命令和长命令参数大于2个字节。每条命令参数个数不要数错DSI协议对参数个数要求很严。delay指令记得去掉或者转成驱动里的延时调用不能直接塞进DSI命令流。4.3 编译烧录和日志确认MTK平台编译Kernel镜像的命令各项目有些差异老一些的平台直接./mk -oTARGET_BUILD_VARIANTuserdebug 项目名 n新的平台用source build/envsetup.sh lunch那套流程。编译LK命令一般是单独编译bootloader镜像。把新编译的boot和bootloader镜像烧进去开机后重点关注以下日志[LCM] lcm_compare_id: id 0x002035, match! [LCM] lcm_init: start [LCM] lcm_init: end [DSI] dsi_clk 150MHz, lane 4如果看到lcm_compare_id匹配成功、lcm_init正常结束、DSI时钟和lane数量打印无误屏幕大概率就能亮。如果日志里没有出现匹配成功的字段先查命令模式是否正确切换。有些比较老旧的屏需要先发送特定的page切换命令才能读ID这时compare_id会读到全FF或全0。4.4 屏幕显示但不正常的微调策略第一次点亮之后屏幕往往不是一次就能完美显示。常见的几个需要微调的现象花屏且颜色杂乱大概率是pclk偏高试着每次降低2MHz到5MHz。屏幕发白或对比度差初始化序列漏了VSP/VSN相关电压配置命令或者屏幕进入的显示模式不对。有残影或重影检查gate控制信号时序调节垂直前后沿参数。屏幕闪烁背光PWM频率过低或者初始化序列中的背光控制命令配置不当。微调的时候一次只动一个参数记录下来每次改动的效果。这样做可以少走很多弯路。5. 常见问题排查与避坑速查下面这些是从多个项目里沉淀出来的高频问题整理成表格方便你直接对照排查。现象可能原因排查与解决办法屏幕完全不亮主板无反应屏幕供电异常用万用表量VCI、VDDIO、VSP/VSN是否正常对比上电时序图屏幕完全不亮背光正常初始化序列未下发或compare_id不匹配查日志确认compare_id和lcm_init是否执行开机logo不亮系统起来后屏幕正常LK和Kernel驱动不同步同步更新LK阶段的LCM驱动had 屏幕亮但显示全白或全黑无图像DSI命令/数据模式切换错误确认命令末尾是否有退出命令模式是否正常切到video模式花屏DSI时钟频率不对降低pclk每次微调1-2MHz观察变化屏幕滚动条纹背光频率和刷新率不匹配调高PWM频率到1kHz以上或调整刷新率颜色明显偏绿偏红初始化序列遗漏gamma命令和屏厂核对初始化序列逐条比差异屏幕休眠后唤醒不显示resume做了但初始化未重发确认resume回调里重新执行完整初始化序列亮度调节无效或过暗backlight设备树配置不对或CABC被打开检查背光PWM节点、CABC寄存器配置图像撕裂垂直同步信号配置不对检查vertical sync pulse和back porch参数排查时谨记第一优先级永远是看日志第二优先级是确认compare_id是否匹配成功第三优先级才是调整时序和时钟参数。5.1 一个典型的“点不亮”排查实录有一次主板回来后屏幕效验ID能读到初始化也执行了但屏就是不亮。反复看日志都正常最后用示波器量了DSI时钟和lane数据发现数据线的电压摆幅不对只有标准值的一半左右。查了一圈原来是DSI PHY的供电电压配置低了调高后屏幕立刻亮了。这类问题软件框架和日志都查不出来只能靠示波器实测。5.2 触控与显示关联问题MTK项目中屏的匹配不仅仅是LCM驱动触控驱动也会监听屏的ID或者复位信号。如果换屏后触控驱动匹配失败触控完全失效。遇到这种情况一般需要同步升级触控驱动里的屏ID匹配表或reset逻辑不要只盯着一块屏幕看。6. 最后分享几个调试习惯如果你想长期和MTK平台的LCD打交道有几个习惯建议从一开始就养成。一个是把屏厂给的初始化序列和自己转换后的代码做成一一对应的可读文本注释里标明寄存器用途。这样后续排查问题时能快速定位到具体命令而不是面对一堆十六进制数字发呆。一个是在驱动里临时加log时要在交付前移除或关闭。MTK平台日志中LCM的打印频率如果太高会影响开机速度严重的还会造成显示丢帧。还有一个是建立自己的配置基线。每调试成功一款屏就把DTS参数、LCM驱动做备份顺便在代码中记录屏型号和硬件版本。同一个项目后续要派生新机型时往往只要把老配置复制一份改参数就行了效率提升非常明显。我个人实际调试过程中最舒服的工作流其实是先花两个小时把平台代码的历史提交记录看一遍看看老工程师们改过哪些坑。很多看似诡异的问题前人早就踩过了留下过注释和提交记录。这个习惯帮我避免了很多重复劳动也让我理解了MTK这套框架本身的不少设计意图。希望这篇文章也能帮你少踩几个坑把屏幕点亮这个看似玄学的过程变成一个真正有章可循的工程任务。
返回列表