本篇文章基于高通845与865模块,详细讲解外设MIPI转LVDS芯片lt9211的调试过程。
本文主要分两部分,第一部分为kernel阶段的初始化及点亮屏幕,第二部分为根据客户需求开机就点亮屏幕,即在UEFI阶段就初始化芯片。但是调试步骤都是一样的,一加驱动二上电三配屏。
一、kernel阶段的点亮过程
在之前的文章中介绍过LCD的点亮以及调压芯片的调试,点亮LVDS其实就是结合了这两者的调试步骤:
(1)阅读原理图与芯片数据手册并添加驱动
拿到调试任务,首先需要了解与明确调试芯片的功能、原理图以及上电时序,只有通过一定的上电规则以及信号输入,才能得到想要的输出信号,而这其中的信号转换需要靠驱动读写寄存器来操作实现,大部分时候供应商会提供驱动程序。
a.原理图
观察原理图,其实就三大部分,上面和左边的MIPI A B输入,右边的LVDS信号输出再加上下面的一些GPIO,其他的晶振先不用管。而我们需要操作的就是下面的一些GPIO。
b.数据手册
仔细寻找数据手册的有用信息:上图为上电规则与其他的芯片工作参数。
以及支持的分辨率和刷新率等信息,剩下的时序操作可以在文档最后找到或者问供应商及硬件都可以。(其实芯片对上电的时序要求不是特别高,有时候你时序错了也能调通,但是为了规范以及保险,需要严格按照时序要求上电)
c.驱动的添加
因为9211的调试比较成熟,这里不介绍具体怎么根据寄存器读写来写驱动程序,对于新手来说直接添加已有的驱动程序,并在Makefile中加入语句编译即可。
调试时可以直接obj-y。
添加好驱动后,前期的准备工作已经差不多了,接下来是上电细节及调试方法。
(2)设备树配置及上电
熟悉项目文件,找到对应设备树文件配置9211,寻找设备树的方法可以是询问同事可以参考高通文档也可以在Ubuntu搜索关键词定位。
根节点的名称根据所用I2C的GPIO来决定:在原理图中找到芯片I2C的两个引脚,看连接模块的哪几个GPIO,最后根据之前确定se组方法配置一下,格式都是一样的,只需要改se之后的数字就行,在根节点里面配置状态为ok,表示允许解析。接下来配置9211节点的内容:一定需要配置的有:compatible和reg,compatible值和驱动中的compatible匹配,reg为芯片的i2c地址,参考数据手册可得。然后配置GPIO,为了提高可读性可以将电压值来区分GPIO,而其他一些功能性GPIO可以直接用英文缩写来命名(这些GPIO的名称可以自己定),&tlmm表示普通的GPIO,中间的参数为GPIO值,第三个参数无特殊要求为0x0。其余的一些参数如interrupts根据不同需求添加,这些参数的含义可以在高通官网的文档及bindings中找到。
配置好设备树之后去驱动代码进行解析与上电:
首先还是需要看一下驱动中的结构体:
这是9211芯片的结构体,其中需要添加一下刚刚在设备树添加的一些GPIO来存储待会需要解析的GPIO值(图中已经添加好了,可以根据名称匹配一下,看不懂的请巩固C语言结构体知识),其余的一些edid buf,i2c设备之类的目前先不用管,建议在此之前熟悉安卓I2C注册流程。添加好GPIO后接下来找到解析设备树的地方:
还是一样,probe函数进来先定义局部设备树信息pdata,代表目前你调试的芯片的所有信息都需要存储在pdata里面。之后分配设备内存,接下来就是打出方框的解析设备树了,找到具体函数如下:
在前几篇文章也讲过如何解析设备树,这里简单再描述一下:一般驱动中都会有这个函数,只需要修改of_get_named_gpio_flags这个函数的参数就行了,比如解析irq-gpio,pdata->irq_gpio为你一开始在结构体中添加的中断脚,而of_get_named_gpio_flags把设备树名为“irq-gpio”的GPIO的值赋给pdata->irq_gpio,下面的gpio-reset也是一样,我觉得在这里理解了赋值操作应该也进一步明白设备树为什么那样配,以及结构体里为什么要加GPIO脚了。
在所有需要解析的GPIO脚解析玩之后,就要进行上电操作了,一般驱动中也会有这个函数,只不过和解析设备树一样需要改一下上电的引脚,可以搜索关键词gpio_direction_output来定位:
以给3v3的引脚上电为例,在gpio_config函数中先判断是打开on还是关闭off,再判断解析的GPIO值是否有效,是否可申请,如果都可以就进行拉高并延时,其他1.8v脚也一样。唯独需要注意的还是中断脚,因为中断来自模块,所以需要配置为输入模式:
至此,上电的相关操作已经结束,上电的目的主要是让I2C通,进行寄存器读写操作初始化芯片,I2C调试通后任务已经完成一大半了,但是通常都会在这卡住,如何解决之后会写一篇I2C调试攻略。
驱动中其他的代码都是寄存器读写操作,可以对照着数据手册理解每个寄存器的含义,了解如何读写寄存器等操作。
(3)屏幕MIPI参数的配置
相比LCD的MIPI参数,LVDS的MIPI参数很简单而且可以直接用之前配的现成的,这里不再介绍MIPI参数的含义,下面给出部分参数截图:
这些是基本的参数配置。
接下来还是和配置LCD一样,利用高通文档的时序并在相应文档配置,将设备树中的default显示选项改为9211的屏幕,最后添加一些背光配置。另外还需要在BP配置好xml参数以及屏幕选择的逻辑代码,逻辑代码看具体情况修改,在LCD点亮一篇中已经介绍过,下面简单给出一些AP及BP的配置截图过一下配置流程:
a.kernel选择9211屏幕显示
这里的dsi-display-active就代表会给9211这个芯片传送MIPI信号,和默认选择9211显示是一样的效果。
b.添加背光控制及供电控制
c.添加时序
d.添加相应的pinctrl
如果在背光添加那部分有引用到一些pinctrl就需要加上,这里不需要添加,可以去pinctrl中搜索对应的脚看是否配好。
这边没有给出具体配置文件的位置,一是因为在LCD点亮已经提过如何找对应文档,二是不同模块的文件位置不固定,具体需要到时候参考高通的文档找到对应位置。
e.BP的一些配置修改:
在MDPPlatformLib.c文件中添加xml参数,这里和之前的LCD不一样的,需要注意了。
其他的逻辑代码请自行阅读该文件的程序添加,在BP还需要释放权限的也需要释放,这里不再赘述。至此9211的kernel初始化就完成了。如遇问题,先看I2C有没有通,再去寻找其他原因。
二、UEFI的9211初始化调试过程
这是我最近调试的比较困难的功能,因为第一BP的初始化是我第一次调试,第二因为AB BP接口不一样需要自己配置功能函数去读写寄存器,第三就是接触BP文件夹确实不多,不熟悉文件结构以及调试步骤。在这里如果初始化成功,在开机前屏幕接好机器,一开机就会显示动画。
这一部分不再讨论上电时序,屏幕配置方面的问题了,这里最重要的问题的I2C需要调通。在阅读了很多文档以及同事的帮助下,总结以下BP侧调通I2C的步骤:
(1)确定se组,打通UEFI的I2C注册过程
以我调试的865平台的se14项目为例,配置文件方面需要释放以下设备:
因为是se14,而se组序号0为第一组,所以se14是第15组设备。如果此se组没有权限请先去trustzone释放权限并配置为I2C。
位置为QCS8250_LA1.1_BP\boot_images\QcomPkg\SocPkg\8250\Settings\I2C\core下的i2c_devcfg.c和i2c_devcfg.xml作出以下修改:
.c文件中打开se14的宏,激活se14设备,如果不确定配置的设备数字是几,可以往下翻代码查看该文件是从设备0开始还是1开始。
xml文件中取消第15个设备的注释,意义与上一步操作一样,该文件是从设备1开始,所以取消了设备15的注释。
在位置为QCS8250_LA1.1_BP\boot_images\QcomPkg\SocPkg\8250\Settings\I2C\loader下的两个文件也作出同样的修改,因为一个项目只会运行loader和core其中一个文件夹的文件,全都改可以保证修改生效,之后确认了可以将无用的改回。
(2)添加协议以及依赖
因为关于显示的操作都会在MDPPlatformLib.c中操作,而在BP和AP不一样需要靠.inf文件来包含协议与依赖,比如我们需要添加I2C协议,则需要在MDPPlatformLib.c所在文件夹的.inf文件中添加I2C的依赖:
添加好之后代表在MDPPlatformLib.c文件中可以执行I2C一些操作。
(3)确定编译顺序
这是我这回调试遇到的最大的坑,因为编译顺序的问题I2C始终不通,当display的编译与调用在I2C之后时才能保证display中的I2C操作可以正常运行。在以下文件确定:
QCS8250_LA1.1_BP\boot_images\QcomPkg\SocPkg\8250\Common\Apriori.fdf.inc
搜索关键词I2C并观察行号,再搜索display:
要确保display(或者说你需要调I2C的程序)在I2C的后面编译或调用,才能正常运行I2C。
在Common文件夹下的Core.fdf.也需要注意顺序,为了确保不出错,请在Common文件夹下搜索跟i2c和display有关的文件,把他们的顺序都改对。
(4)添加I2C接口并移植驱动
最后就是在MDPPlayformLib.c的文件中移植驱动了,一般来说需要先加上I2C的一些接口来验证是否能正常使用I2C,代码如下:
#include <Protocol/EFII2C.h> //先添加I2C库
//初始化I2C
static i2c_slave_config cfg; //定义I2C设备
static void* pI2cHandle = NULL;
i2c_status i2c_init(UINT32 SlaveAddr, UINT32 I2cFreq) //初始化I2C设备
{
i2c_status i2cstatus = I2C_SUCCESS;
cfg.bus_frequency_khz = I2cFreq;
cfg.slave_address = SlaveAddr;
cfg.mode = I2C;
cfg.slave_max_clock_stretch_us = 500;
cfg.core_configuration1 = 0;
cfg.core_configuration2 = 0;
// cfg.slave_address_type = I2C_07_BIT_SLAVE_ADDRESS;
// EFI_TLMM_PROTOCOL *TLMMProtocol = NULL;
DEBUG((EFI_D_ERROR, "LJH i2c_init+++++++++++++++++++++++++++++++++++++++++++++++++ \n"));
i2cstatus = i2c_open((i2c_instance)(I2C_INSTANCE_015), &pI2cHandle);
if (I2C_SUCCESS != i2cstatus)
{
DEBUG((EFI_D_ERROR, "Failed to initialize I2C %d\n", i2cstatus));
}
return i2cstatus;
}
//I2C读函数
unsigned char lt9211_read(unsigned int addr)
{
uint32 bRead = 0;
unsigned char getdata = 0;
i2c_status i2cstatus = I2C_SUCCESS;
unsigned char rdbuf[1] = { 0 };
gBS->Stall(600000);
i2cstatus = i2c_read(pI2cHandle, &cfg, addr, 1, rdbuf, 1, &bRead, 2500);
if (I2C_SUCCESS != i2cstatus)
{
DEBUG((EFI_D_ERROR, "Read addr:0x%X error\n", addr));
}
gBS->Stall(600000);
getdata = rdbuf[0];
DEBUG((EFI_D_ERROR, "[Qhs][0x%x]rdbuf[0] & 0x00ff is 0x%x\n\n", addr, rdbuf[0]));
return getdata;
}
//I2C写函数
unsigned char lt9211_write(unsigned char addr, unsigned char reg_data)
{
uint32 bWrote = 0;
i2c_status i2cstatus = I2C_SUCCESS;
unsigned char wdbuf[1] = { 0 };
wdbuf[0] = reg_data;
i2cstatus = i2c_write(pI2cHandle, &cfg, addr, 1, wdbuf, 1, &bWrote, 2500);
if (I2C_SUCCESS != i2cstatus)
{
DEBUG((EFI_D_ERROR, "Write addr:0x%X data:0x%X error\n", addr, reg_data));
}
return bWrote;
}
i2c_status i2c_deinit()
{
return i2c_close(pI2cHandle);
}
//初始化9211的主函数
int uefi_lt9211_init(void)
{
//uint8 Read_DPCD010A = 0x00;
DEBUG((EFI_D_ERROR, "LJH lt9211 init start\n"));
i2c_init(0x5a, 100);
//lt9211_check_chipid();
LT9211_start();
i2c_deinit();
DEBUG((EFI_D_ERROR, "LJH LT9211 init end\n"));
return 0;
}
首先先看定义的两个量,一个是i2c设备一个是类似flag的pichandle量,按照这样的格式写就可。然后我们看主函数的执行过程,I2C的流程可以看出来显示初始化I2C,传进去芯片地址以及I2C速率,然后再执行9211芯片的寄存器读写操作,最后是关闭I2C。需要注意的是在i2c_init函数中i2cstatus = i2c_open((i2c_instance)(I2C_INSTANCE_015), &pI2cHandle);这一句话的INSTANCE_015是根据不同se组改变的,这里的序号从1开始所以是15,其余的读写操作函数可以直接移植,这样就写好BP的I2C接口了。
关于移植9211驱动到BP其实很简单,照着AP的驱动把读写寄存器操作一个一个写就行了,下面举个例子:
在AP的peobe函数中,我首先要进行寄存器读写操作的函数是check chip id,直接找到这个函数看他的内容:
发现它只是进行了一次读写取出来chip id,这一个功能函数有两个作用,第一是确定id是否正确,第二是判断I2C是否通了,那么在BP下已经有了接口,直接照着写就行了:
如上图,AP的移植过来就是这样,只需要改个函数内容就可以,然后把AP进行的所以寄存器读写都照这样的方式写就可以,需要注意读写寄存器操作在BP也需要分步来写,AP执行check id的函数BP也需要建一个函数check id。
当所有的寄存器操作都移植结束之后,需要在GPIO上电的函数中加入上电操作,这里不再展示上电操作,具体做法参考GPIO那篇文档。
最后在合适的地方调用9211init的主函数,这里的合适需要自己把控,比如需要在拉高GPIO之后调用才不会出错等等情况。
至此,9211在UEFI的初始化调试也结束了,希望各位自己操作试试。
三、总结
本篇文章介绍9211芯片的调试步骤,在AP和BP的调试步骤有很大的差别,而且难度也不相同,需要特别注意的是AP的时序计算以及上电时序,在BP调试时需要注意编译顺序以及寄存器读写的移植需要细心。其余的通过自己亲手调试都会有更进一步理解。