RK3399 MIPI屏幕调试记录

发布于2019年1月14日 -

RK3399是瑞芯微Rockchip公司目前最高端的SoC,它的上一代RK3288广泛用在各个地方,例如,ASUS C100PA Chromebook Flip使用的就是RK3288。作为RK3288的升级型号,RK3399的核心数提高到了由两个A72和四个A53核心组成的六核,GPU升级为了Mali-T860。

这次的目标是给基于RK3399的某个开发板移植一款MIPI屏幕。首先简单介绍一下MIPI。MIPI是Mobile Industry Processor Interface的缩写,随着移动设备的不断发展,MIPI已经发展出多个部分,最常用的是CSI和DSI,分别对应摄像头(camera)和显示屏(display)。本文所述的内容就是这里的DSI部分。

网上有不少介绍给RK3399移植MIPI屏幕的教程,例如最出名的firefly开发板就在其官网给出了移植指南,如果移植很顺利,这篇文章就不存在了😂 下面主要记录一下调试流程,供有需要的人参考吧。

第一次尝试

在我真正动手之前,z4yx大佬教育我说,MIPI非常难调;gaoyichuan大佬则举了他调试MIPI CSI的例子,说调了一个暑假最终放弃了。究其原因,MIPI DSI的时钟是差分信号,幅度很低(100mV级别),频率却高达数十甚至数百MHz,此外,DSI有两种工作模式,分别是命令(Command)和视频(Video)模式,这两者的频率和幅度也不尽相同。因此,使用普通的示波器是无法看到DSI信号的。实践证明,z4yx大佬总是对的,使用普通的Tektronix示波器,只能看到工频干扰信号,完全没法分析。

可是我怎么会信邪呢?搞之!

由于每个厂家的引脚定义都不同,所以在开始调试之前,需要根据引脚定义制作转接板。转接板负责两件事:一是给屏幕本身提供合适的电压,二是利用LED驱动芯片驱动背光。这次调试的转接板不是我画的,也不是我焊的。我按照1脚对1脚的方法,用FPC排线接好之后,一上电,钽电容就炸了(就像这样)。显然,电源接反了,我仔细检查了一下,我拿到的这块转接板的FPC画反了。出师未捷身先死,屏幕和开发板都烧了😂

重新买了开发板、屏幕并重新焊了转接板后,屏幕的背光亮了。

作为一个有良心的厂家,Rockchip已经写好了常见设备的驱动,我们增加一个新外设的标准流程就是改设备树(Device Tree)。而开发板自带的屏幕也是DSI的,所以事实上驱动是有的。由于各种资料都讲了DSI的时序,所以这里就不再重复,只补充一下,DSI部分的clock-frequency指的是PCLK,不要弄错了。开机,啥都没有😂

移植代码

我忽然想到厂家还给了一个初始化脚本,所谓的初始化脚本,就是一个需要在上电后,给屏幕发的一组命令,至于它是什么,我没有查到资料介绍。可是我手里的这份设备树没有地方可以写,令人绝望。

我对比了一下手里的代码和Rockchip在GitHub上的代码,发现我的代码版本非常老,于是我立刻想到了两个办法:一是把内核换成Rockchip最新的,二是把DSI部分的代码替换成最新的。使用第一个方法,设备树变化太大,基本上需要重新配置一遍所有的设备,这个基本做不到。于是在z4yx大佬的建议下,我又试了第二个方法。

本以为换一下Rockchip的显示屏驱动panel-simple.c就好,然而它依赖若干变动了的结构体,这些结构体又依赖若干新的头文件,一路换下来,甚至已经换到HDMI去了,这让我想到了这幅图:

在折腾了几个小时之后,我放弃了这个潜入无底洞的做法,转而专注于修改屏幕驱动panel-simple.c。这一次我没有选择最新的代码,而是去Rockchip的仓库里找了最早加上有初始化命令的代码,然后把初始化命令相关的代码移了过来,修完了缺失的结构体后,顺利通过编译。

我满怀期待地上电,然而什么都没有发生,dmesg也没有任何异常,而使用Vysor也能看到屏幕尺寸改变了,我很绝望🤣🤣🤣

MIPI测试仪

那么,屏幕是好的吗?

我从来没见这个屏幕显示过画面,所以开始怀疑这件事,是不是我把屏幕弄坏了。由于没有合适的示波器,我也没法看数据到底哪里不对(事实上即使有,也非常不好调)。一筹莫展之际,抱着试试看的想法,我打开了万能的淘宝,还真找到了MIPI测试仪。

买回来,把时序的参数填上,再把初始化代码填进去:

原来屏幕是好的!(这是视频截图,所以画质渣)

再战DSI

仔细看一下MIPI测试仪的配置,无非就是设置好时序,然后发指令。既然设备树不好搞,我直接写在代码里发总行了吧。我把发指令的代码直接写在了panel_simple_enable函数里,这个函数会在屏幕点亮之前调用,所以应该是个发指令的好地方。于是屏幕真的有东西了!

打开可以让屏幕显示纯色的apk,可以看到它的颜色基本都是对的,但是,画面就这样诡异,充满了条纹。咨询厂家,建议修改PCLK,但怎么改都无效。厂家还建议替换初始化代码,也无效。

我又去检查了一遍MIPI测试仪的代码,发现发完指令之后,需要让屏幕进入Video模式,那我的屏幕现在是Video模式吗?我翻了RK3399的寄存器手册,但是读到的值似乎都不太对,于是还是回到了代码。

经过反复比对,我最终在DSI的驱动dw-mipi-dsi.c里找到了端倪。它的dw_mipi_dsi_encoder_enable函数是这样写的:

if (drm_panel_prepare(dsi->panel))
  dev_err(dsi->dev, "failed to prepare panel\n");
if (pdata->grf_dsi0_mode_reg)
  regmap_write(dsi->grf_regmap, pdata->grf_dsi0_mode_reg,
			   pdata->grf_dsi0_mode);
dw_mipi_dsi_phy_init(dsi);
dw_mipi_dsi_wait_for_two_frames(dsi);
dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_VID_MODE);
drm_panel_enable(dsi->panel);

它的流程是,先调用prepare函数,然后初始化DSI的phy部分,接下来设置模式为Video,最后调用enable函数。所以我在enable函数执行完命令之后,并没有进入Video模式。

这个时候回头再去看当时移植的代码,Rockchip修正后的驱动是把初始化代码写在了prepare函数里,而在dw_mipi_dsi_phy_init函数里,写了这样一句:

dsi_write(dsi, DSI_PWR_UP, POWERUP);

也就是说,如果混合两个版本的代码(修正过的panel-simple.c和修正前的dw-mipi-dsi.c),实际的执行流程是,先配置屏幕,再初始化,能用就见鬼了🤣

所以,只需要修改dw_mipi_dsi_encoder_enable的实现,调换一下初始化顺序即可:

if (pdata->grf_dsi0_mode_reg)
  regmap_write(dsi->grf_regmap, pdata->grf_dsi0_mode_reg,
			   pdata->grf_dsi0_mode);
dw_mipi_dsi_phy_init(dsi);
dw_mipi_dsi_wait_for_two_frames(dsi);
if (drm_panel_prepare(dsi->panel))
  dev_err(dsi->dev, "failed to prepare panel\n");
dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_VID_MODE);
drm_panel_enable(dsi->panel);

经过如此一番修改,屏幕终于能工作了:

总结与后续

虽然MIPI测试仪并不能帮我解决实际问题,但一个正常工作的设备确实给了我调试的信心,尽管它的代码不开源,它的工作流程还是给了我不少启发。

值得一说的后续是,在我烧了第一块板子之后,每次摸MIPI接口之前,我都会去摸一下电源的地线放放静电,然而在调完屏幕之后,这块板子的MIPI接口还是被我莫名其妙地烧坏了。在后续的设计里,我使用了EMI4183作为放ESD的器件,不过目前还没有实际测试。


题图:Untitled

作者:Mathew Schwartz