目录
Framebuffer设备
LCD驱动简析
LCD驱动程序编写
LCD屏幕参数节点信息修改
LCD 屏幕背光节点信息
使能Linux logo显示
设置LCD作为终端控制台
Framebuffer设备
先来回顾一下裸机的时候LCD驱动是怎么编写的,裸机LCD驱动编写流程如下:
1.初始化I.MX6U的eLCDIF控制器,重点是LCD屏幕宽(width)、高(height)、hspw、hbp、hfp、vspw、vbp和vfp 等信息。
2.初始化LCD像素时钟。
3.设置RGBLCD显存。
4.应用程序直接通过操作显存来操作LCD,实现在LCD上显示字符、图片等信息。
在Linux中应用程序最终也是通过操作RGB LCD的显存来实现在LCD上显示字符、图片等信息。在裸机中我们可以随意的分配显存,但是在Linux系统中内存的管理很严格,显存是需要申请的,不是你想用就能用的。而且因为虚拟内存的存在,驱动程序设置的显存和应用程序访问的显存要是同一片物理内存。
为了解决上述问题, Framebuffer诞生了, Framebuffer翻译过来就是帧缓冲,简称fb,因此大家在以后的Linux学习中见到"Framebuffer"或者“fb”的话第一反应应该想到RGBLCD或者显示设备。fb是一种机制,将系统中所有跟显示有关的硬件以及软件集合起来,虚拟出一个 fb 设备,当我们编写好LCD 驱动以后会生成一个名为/dev/fbX(X=0-n)的设备,应用程序通过访问/dev/fbX这个设备就可以访问LCD。NXP官方的Linux内核默认已经开启了LCD驱动,因此我们是可以看到/dev/fb0这样一个设备,如图所示:
图中的/dev/fb0就是LCD对应的设备文件,/dev/fb0是个字符设备,因此肯定有
file-operations操作集,fb的fileoperations操作集定义在drivers/video/fbdev/core/fbmem.c文件中,如下所示:
LCD驱动简析
LCD裸机例程主要分两部分:
1.获取LCD的屏幕参数。
2.根据屏幕参数信息来初始化eLCDIF接口控制器。不同分辨率的LCD屏幕其eLCDIF控制器驱动代码都是一样的,只需要修改好对应的屏幕参数即可。屏幕参数信息属于屏幕设备信息内容,这些肯定是要放到设备树中的,因此本实验的主要工作就是修改设备树,NXP官方的设备树已经添加了LCD设备节点,只是此节点的LCD屏幕信息是针对NXP官方EVK开发板所使用的4.3寸480*272编写的,需要将其改为我们所使用的屏幕参数。简单看一下NXP 官方编写的Linux下的LCD驱动,打开imx6ull.dtsi,然后找到Icdif节点内容,如下所示:
示例代码59.1.2.1中的Iedif节点信息是所有使用I.MX6ULL芯片的板子所共有的,并不是完整的ledif节点信息。像屏幕参数这些需要根据不同的硬件平台去添加,比如向imx6ullalientek-emmc.dts中的lcdif节点添加其他的属性信息。从示例代码可以看出lcdif节点的compatible属性值为"fsl,imx6ul-ledif"和"fsl,imx28-ledif",因此在Linux源码中搜索这两个字符串即可找到I.MX6ULL的LCD驱动文件,这个文件为drivers/video/fbdev/mxsfb.c, mxsfb.c就是 I.MX6ULL的LCD驱动文件,在此文件中找到如下内容:
从示例代码可以看出,这是一个标准的platform驱动,当驱动和设备匹配以后mxsfb_probe 函数就会执行。在看mxsfb_probe函数之前我们先简单了解一下Linux下Framebuffer驱动的编写流程, Linux内核将所有的Framebuffer抽象为一个叫做fb_info的结构体,fb_info结构体包含了Framebuffer设备的完整属性和操作集合,因此每一个Framebuffer设备都必须有一个fb_info。换言之就是, LCD的驱动就是构建fb_info,并且向系统注册fb_info的过程。fb_info 结构体定义在include/linux/fb.h文件里面,内容如下(省略掉条件编译):
fb_info结构体的成员变量很多,我们重点关注var、fix、fbops、 screen_base、screen_size和pseudo_palette。
mxsfb_probe函数的主要工作内容为:
1.申请fb_info。
2.初始化 fb_info 结构体中的各个成员变量。
3.初始化eLCDIF控制器。
4.使用register_framebuffer函数向Linux内核注册初始化好的fb_info。register_framebuffer函数原型如下:
fb_info:需要上报的。
返回值: 0,成功;负值,失败。
接下来我们简单看一下mxsfb_probe函数,函数内容如下(有缩减):
第1374行, host结构体指针变量,表示I.MX6ULL的LCD的主控接口, mxsfb_info结构体是NXP定义的针对I.MX系列SOC的Framebuffer设备结构体。也就是前面一直说的设备结构体,此结构体包含了I.MX系列SOC的Framebuffer设备详细信息,比如时钟、eLCDIF控制器寄存器基地址、fb_info等。
第1395行,从设备树中获取eLCDIF接口控制器的寄存器首地址,设备树中Icdif节点已经设置了eLCDIF寄存器首地址为0X021C8000,因此res=0X021C8000。
第1401行,给host申请内存,host为mxsfb_info类型结构体指针。
第1407行,给fb_info申请内存,也就是申请fb_info。
第1413~1414行,设置host的fbinfo成员变量为fb_info,设置fb_info的par成员变量为host。通过这一步就将前面申请的host和fb_info联系在了一起。
第1416行,申请中断,中断服务函数为mxsfb_irq_handler。
第1425行,对从设备树中获取到的寄存器首地址(res)进行内存映射,得到虚拟地址,并保存到 host的base成员变量。因此通过访问host的base成员即可访问I.MX6UULL的整个eLCDIF寄存器。其实在mxsfb.c中已经定义了eLCDIF各个寄存器相比于基地址的偏移值,如下所示:
继续回到示例代码中的mxsfb_probe函数,第1462行,给fb_info中的pseudo_palette.申请内存。
第1473行,调用mxsfb_init_fbinfo函数初始化fb_info,重点是fb_info的var、fix、 fbops,screen_base和screen_size。其中fbops是Framebuffer设备的操作集, NXP 提供的fbops为mxsfb_ops,内容如下:
关于mxsfb_ops里面的各个操作函数这里就不去详解的介绍了。mxsfb_init_fbinfo函数通过调用mxsfb_init_fbinfo_dt函数从设备树中获取到LCD的各个参数信息。最后, mxsfb_init_fbinfo函数会调用mxsfb_map_videomem函数申请LCD的帧缓冲内存(也就是显存)。
第1489-1490行,设置eLCDIF控制器的相应寄存器。
第1494 行,最后调用register_framebuffer函数向Linux内核注册fb_info。mxsfb.c文件很大,还有一些其他的重要函数,比如mxsfb_remove、mxsfb_shutdown等,这里就简单的介绍了一下mxsfb_probe 函数,至于其他的函数大家自行查阅。
LCD驱动程序编写
需要做的就是按照所使用的LCD来修改设备树。重点要注意三个地方:
1.LCD 所使用的 IO 配置。
2.LCD屏幕节点修改,修改相应的属性值,换成我们所使用的LCD屏幕参数。
3.LCD 背光节点信息修改,要根据实际所使用的背光IO来修改相应的设备节点信息。接下来依次来看一下上面这两个节点改如何去修改:
LCD屏幕IO配置
首先要检查一下设备树中LCD所使用的IO配置,这个其实NXP都已经给我们写好了,不需要修改,不过还是要看一下。打开imx6ull-alientek-emmc.dts文件,在iomuxc节点中找到如下内容:
第2-27行,子节点pinctrl_ledif_dat,为RGB LCD的24根数据线配置项。
第30~36行,子节点pinctrl_ledif_ctrl,RGB LCD的4根控制线配置项,包括CLK、ENABLE、VSYNC和HSYNC。
第37~40行,子节点pinctrl_pwml,LCD背光PWM引脚配置项。这个引脚要根据实际情况设置,这里建议大家在以后的学习或工作中,LCD的背光IO尽量和半导体厂商的官方开发板一致。注意示例代码中默认将LCD的电气属性都设置为 0X79,这里将其都改为0X49,也就是将LCD相关IO的驱动能力改为R0/1,也就是降低 LCD相关IO的驱动能力。因为前面已经说了,正点原子的ALPHA开发板上的LCD接口用了三个SGM3157模拟开关,为了防止模拟开关影响到网络,因此这里需要降低LCD数据线的驱动能力,如果你所使用的板子没有用到模拟开关那么就不需要将0X79改为0X49。
LCD屏幕参数节点信息修改
继续在imx6ull-alientek-emmc.dts文件中找到ledif节点,节点内容如下所示:
第3行,pinctrl-0属性,LCD所使用的IO信息,这里用到了pinctrl_ledif_dat、pinctrl_ledif_ctrl和pinctrl_ledif_reset这三个IO相关的节点,前两个在示例代码中已经讲解了。pinctrl_lcdif_reset是LCD复位IO信息节点,正点原子的I.MX6U-ALPHA开发板的LCD 没有用到复位IO,因此pinctrl_ledif_reset可以删除掉。
第6行,display属性,指定LCD属性信息所在的子节点,这里为display0,下面就是display0子节点内容。
第9-32行, display0子节点,描述LCD的参数信息,第10行的bits-per-pixel属性用于指明一个像素占用的bit数,默认为16bit。将LCD配置为RGB888模式,因此一个像素点占用24bit, bits-per-pixel属性要改为24。
第11行的 bus-width 属性用于设置数据线宽度,因为要配置为RGB888模式,因此bus-width也要设置为24。
第13~30行,这几行非常重要!因为这几行设置了LCD 的时序参数信息,NXP官方的EVK开发板使用了一个4.3寸的480*272屏幕,因此这里默认是按照NXP官方的那个屏幕参数设置的。每一个属性的含义后面的注释已经写的很详细了,大家自己去看就行了,这些时序参数就是重点要修改的,需要根据自己所使用的屏幕去修改。
这里以正点原子的 ATK7016(7寸1024*600)屏幕为例,将imx6ull-alientek-emmc.dts文件中的ledif节点改为如下内容:
第3行,设置LCD屏幕所使用的IO,删除掉原来的pinctrl_lcdif reset,因为没有用到屏幕复位 10,其他的IO不变。
第9行,使用RGB888模式,所以一个像素点是24bit。
第15-23行,ATK7016屏幕时序参数,根据自己所使用的屏幕修改即可。
LCD 屏幕背光节点信息
正点原子的LCD接口背光控制IO连接到了I.MX6U的GPIO1_IO08引脚上, GPIO1_IO08复用为PWM1_OUT,通过PWM信号来控制LCD屏幕背光的亮度,正点原子IMX6U-ALPHA开发板的LCD背光引脚和NXP官方EVK开发板的背光引脚一样,因此背光的设备树节点是不需要修改的,但是考虑到其他同学可能使用别的开发板或者屏幕,LCD背光引脚和NXP官方 EVK开发板可能不同,因此还是来看一下如何在设备树中添加背光节点信息。
首先是GPIO1_1008这个IO的配置,在imx6ull-alientek-emme.dts中找到如下内容:
pinctrl_pwm1节点就是GPIO1_1O08的配置节点,从第3行可以看出,设置GPIO1_1008这个IO复用为PWM1_OUT,并且设置电气属性值为 0x110b0。
LCD背光要用到PWM1,因此也要设置PWM1节点,在imx6ull.dtsi文件中找到如下内容:
imx6ull.dtsi文件中的pwml节点信息大家不要修改,如果要修改pwm1节点内容的话请在imx6ull-alientek-emmc.dts文件中修改。在整个Linux源码文件中搜索compatible属性的这两个值即可找到imx6ull的pwm 驱动文件,imx6ull的PWM驱动文件为
drivers/pwm/pwm-imx.c,这里我们就不详细的去分析这个文件了。
继续在imx6ull-alientek-emme.dts文件中找到向pwm1追加的内容,如下所示:
第3行,设置pwml所使用的IO为pinctrl_pwm1,也就是示例代码所定义的GPIO1_IO08 这个IO。
第4行,将status设置为okay。如果背光用的其他pwm通道,比如pwm2,那么就需要仿照示例代码的内容,向pwm2节点追加相应的内容。pwm和相关的IO已经准备好了,但是Linux系统怎么知道PWM1_OUT就是控制LCD 背光的呢?因此还需要一个节点来将LCD 背光和 PWM1_OUT连接起来。这个节点就是backlight , backlight节点描述可以参考Documentation/devicetree/indings/video/backlight/pwm-backlight.txt这个文档,此文档详细讲解了backlight 节点该如何去创建,这里大概总结一下:
1.节点名称要为“backlight”。
2.节点的compatible属性值要为"pwm-backlight”,因此可以通过在Linux内核中搜索“pwm-backlight”来查找PWM背光控制驱动程序,这个驱动程序文件为
drivers/video/backlight/pwm_bl.c,感兴趣的可以去看一下这个驱动程序。
3.pwms属性用于描述背光所使用的PWM以及PWM频率,比如要使用的pwm1,pwm频率设置为200Hz(NXP官方推荐设置)。
4.brightness-levels属性描述亮度级别,范围为0-255, 0表示PWM占空比为0%,也就是亮度最低,255表示100%占空比,也就是亮度最高。至于设置几级亮度,大家可以自行填写此属性。
5.default-brightness-level 属性为默认亮度级别。
根据上述5点设置backlight节点,这个NXP已经给设置好了,大家在imx6ull-alientekemmc.dts 文件中找到如下内容:
第3行,设置背光使用pwml, PWM频率为200Hz。
第4行,设置背8级背光(0-7),分别为0、4、8、16、32、64、128、255,对应占空比为0%、1.57%、3.13%、6.27%、12.55%、25.1%、50.19%、100%,如果嫌少的话可以自行添加一些其他的背光等级值。
第5行,设置默认背光等级为6,也就是50.19%的亮度。
使能Linux logo显示
Linux内核启动的时候可以选择显示小企鹅 logo,只要这个小企鹅logo显示没问题那么我们的LCD驱动基本就工作正常了。这个logo显示是要配置的,不过Linux内核一般都会默认开启logo显示,但是奔着学习的目的,我们还是来看一下如何使能Linuxlogo显示。打开Linux内核图形化配置界面,按下路径找到对应的配置项:
图中这三个选项分别对应黑白、16位、24位色彩格式的logo,把这三个都选中,都编译进Linux内核里面。设置好以后保存退出,重新编译Linux内核,编译完成以后使用新编译出来的imx6ull-alientek-emmc.dtb和zImage镜像启动系统,如果LCD驱动工作正常的话就会在LCD屏幕左上角出现一个彩色的小企鹅logo,屏幕背景色为黑色,如图所示:
设置LCD作为终端控制台
一直使用SecureCRT作为Linux开发板终端,开发板通过串口和SecureCRT进行通信。现在已经驱动起来LCD了,所以可以设置LCD作为终端,也就是开发板使用自己的显示设备作为自己的终端,然后接上键盘就可以直接在开发板上敲命令了,将LCD设置为终端控制台的方法如下:
设置uboot中的bootargs
重启开发板,进入Linux命令行,重新设bootargs参数的console内容,命令如下所示:
注意红色字体部分设置console,这里我们设置了两遍console,第一次设置console-tty1,也就是设置LCD屏幕为控制台,第二遍又设置console=ttymxc0,115200,也就是设置串口也作为控制台。相当于我们打开了两个console,一个是 LCD,一个是串口,重启开发板就会发现LCD 和串口都会显示Linux启动log信息。但是此时我们还不能使用LCD作为终端进行交互,因为的设置还未完成。
修改/etc/inittab文件
打开开发板根文件系统中的/etc/inittab文件,在里面加入下面这一行:
添加完成以后的/etc/inittab 文件内容如图所示:
修改完成以后保存/etc/inittab并退出,然后重启开发板,重启以后开发板LCD屏幕最后一行会显示下面一行语句:
至此,拥有了两套终端,一个是基于串口的SecureCRT,一个就是我们开发板的LCD屏幕,但是为了方便调试。