LV10-01-LCD驱动-04-Linux下的LCD驱动
接下来了解一下Linux中自带的lcd驱动?若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
PC端开发环境 | Windows | Windows11 |
Ubuntu | Ubuntu20.04.2的64位版本 | |
VMware® Workstation 17 Pro | 17.6.0 build-24238078 | |
终端软件 | MobaXterm(Professional Edition v23.0 Build 5042 (license)) | |
Win32DiskImager | Win32DiskImager v1.0 | |
Linux开发板环境 | Linux开发板 | 正点原子 i.MX6ULL Linux 阿尔法开发板 |
uboot | NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04 | |
linux内核 | linux-4.19.71(NXP官方提供) |
点击查看本文参考资料
分类 | 网址 | 说明 |
官方网站 | https://www.arm.com/ | ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档 |
https://www.nxp.com.cn/ | NXP官方网站 | |
https://www.nxpic.org.cn/ | NXP 官方社区 | |
https://u-boot.readthedocs.io/en/latest/ | u-boot官网 | |
https://www.kernel.org/ | linux内核官网 |
点击查看相关文件下载
分类 | 网址 | 说明 |
NXP | https://github.com/nxp-imx | NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库 |
nxp-imx/linux-imx/releases/tag/v4.19.71 | NXP linux内核仓库tags中的v4.19.71 | |
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 | NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0 | |
I.MX6ULL | i.MX 6ULL Applications Processors for Industrial Products | I.MX6ULL 芯片手册(datasheet,可以在线查看) |
i.MX 6ULL Applications ProcessorReference Manual | I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网) | |
Source Code | https://elixir.bootlin.com/linux/latest/source | linux kernel源码 |
kernel/git/stable/linux.git - Linux kernel stable tree | linux kernel源码(官网,tag 4.19.71) | |
https://elixir.bootlin.com/u-boot/latest/source | uboot源码 |
一、Framebuffer 设备
1. 裸机LCD驱动回顾
先来回顾一下裸机的时候 LCD 驱动是怎么编写的,裸机 LCD 驱动编写流程如下:
①、初始化 I.MX6U 的 eLCDIF 控制器,重点是 LCD 屏幕宽(width)、高(height)、 hspw、hbp、 hfp、 vspw、 vbp 和 vfp 等信息。
②、初始化 LCD 像素时钟。
③、设置 RGBLCD 显存。
④、应用程序直接通过操作显存来操作 LCD,实现在 LCD 上显示字符、图片等信息。
2. Framebuffer
2.1 fb简介
在 Linux 中应用程序最终也是通过操作 RGB LCD 的显存来实现在 LCD 上显示字符、图片等信息。在裸机中我们可以随意的分配显存,但是在 Linux 系统中内存的管理很严格,显存是需要申请的,不是想用就能用的。而且因为虚拟内存的存在,驱动程序设置的显存和应用程序访问的显存要是同一片物理内存。
为了解决上述问题, Framebuffer 诞生了, Framebuffer 翻译过来就是帧缓冲,简称 fb。它是一种机制,将系统中所有跟显示有关的硬件以及软件集合起来,虚拟出一个 fb 设备,当我们编写好 LCD 驱动以后会生成一个名为/dev/fbX(X=0~n)
的设备,应用程序通过访问/dev/fbX
这个设备就可以访问 LCD。
图中的/dev/fb0 就是 LCD 对应的设备文件, /dev/fb0 是个字符设备,因此肯定有file_operations 操作集。
2.2 fb子系统
前面我们知道Framebuffer子系统为用户空间操作显示设备提供了统一的接口,屏蔽了底层硬件之间的差异,用户只需要操作一块 内存缓冲区即可把需要的图像显示到LCD设备上。Framebuffer子系统主要分为两个部分,如下图所示:

- 核心层: 主要实现字符设备的创建,为不同的显示设备提供文件通用处理接口;同时创建graphics设备类,占据主设备号29。
- 硬件设备层: 主要提供显示设备的时序、显存、像素格式等硬件信息,实现显示设备的私有文件接口,并创建显示设备文件/dev/fbx(x=0~n)暴露给用户空间。 硬件设备层的代码需要驱动开发人员根据具体的显示设备提供给内核。
3. fb的file_operations
fb的file_operations操作集定义在fbmem.c - drivers/video/fbdev/core/fbmem.c:
1 | static const struct file_operations fb_fops = { |
这个就和前面学习的字符设备是一样的。
4. fb_info
Linux 内核将所有的 Framebuffer 抽象为一个叫做 fb_info 的结构体, fb_info 结构体包含了 Framebuffer 设备的完整属性和操作集合,因此每一个 Framebuffer 设备都必须有一个 fb_info。
1 | struct fb_info { |
fb_info 结构体的成员变量很多,我们重点关注 var、 fix、 fbops、 screen_base、 screen_size和 pseudo_palette。
二、LCD驱动分析
1. 设备树
我们先来看一下NXP 官方的设备树,官方的evk板已经添加了 LCD 设备节点,只是此节点的 LCD 屏幕信息是针对 NXP 官方 EVK 开发板所使用的 4.3 寸 480*272 编写的,alpha开发板上面的lcd引脚什么的和官方eck板一致,我们可以参考官方的板子先来学习一下。
1.1 lcdif: lcdif@21c8000
我们打开imx6ul.dtsi - arch/arm/boot/dts/imx6ul.dtsi,找到这个lcdif: lcdif@21c8000节点:
1 | lcdif: lcdif@21c8000 { |
这个就是imx6ull中的lcd控制器的节点信息,可以看到地址是0x021c8000,我们之前裸机学习的时候知道,eLCDIF 控制器的起始地址就是这个:

这里的 lcdif 节点信息是所有使用 I.MX6ULL 芯片的板子所共有的,并不是完整的 lcdif 节点信息。像屏幕参数这些需要根据不同的硬件平台去添加,比如向 imx6ull-alpha-emmc.dts 中的 lcdif 节点添加其他的属性信息。
1.2 pinctrl子系统信息
1.2.1 pinctrl_lcdif_dat: lcdifdatgrp
我们打开imx6ul-14x14-evk.dtsi - arch/arm/boot/dts/imx6ul-14x14-evk.dtsi找到以下内容:
1 | pinctrl_lcdif_dat: lcdifdatgrp { |
这里就是LCD用到的数据线所有的GPIO的配置。
1.2.2 pinctrl_lcdif_ctrl: lcdifctrlgrp
还有一些LCD的信号相关的引脚在这里:pinctrl_lcdif_ctrl: lcdifctrlgrp
1 | pinctrl_lcdif_ctrl: lcdifctrlgrp { |
1.3 GPIO子系统信息
上面使用了pinctrl子系统来设置了用到的GPIO的复用功能,GPIO的电气属性(工作模式,驱动能力)这些,但像gpio输出高低电平这些,是还需要gpio子系统来进行配置,不过nxp官方的设备树中并没有相关的了。这里知道肯可能会有就是了。
1.4 屏幕参数节点&lcdif
上面已经找到了pinctrl和gpio子系统的信息,接下来就是找到应用的地方,我们找到&lcdif:
1 | &lcdif { |
这里就是lcd控制器配置的地方,这里就会使用对应pinctrl子系统中的配置。
1.5 时钟配置
1.5.1 &lcdif的两个时钟属性
我们来看一下上面的时钟配置,前面找到&lcdif的时候,里面有两行:
1 | &lcdif { |
可以参考一下clock-bindings.txt ,这里大概就是在说当平台需要初始化默认的父时钟和时钟频率时,可通过设备树节点中的以下属性配置:
assigned-clocks
:需配置的时钟列表(phandle + 时钟标识符)assigned-clock-parents
:指定父时钟列表(phandle + 时钟标识符对)assigned-clock-rates
:指定时钟频率列表(单位:Hz)
例如:
1 | uart@a000 { |
在这个例子中,<&pll 2>时钟被设置为时钟<&clkcon 0>的父时钟,<&pll 2>时钟被分配了一个460800 Hz的频率值。
对于这个lcdif节点来说,就是把<&clks IMX6UL_CLK_LCDIF_PRE_SEL>
的父时钟设置为<&clks IMX6UL_CLK_PLL5_VIDEO_DIV>
。
1.5.2 &clk节点
我们来看一下前面的&clk节点,这个节点定义在imx6ul.dtsi:
1 | clks: ccm@20c4000 { |
看这个reg属性,可以知道这个节点起始地址是0x020c4000,这里就是我们的CCM时钟控制器的起始地址:

1.5.3 总结
这里详细的就不去深入了,知道这里是设置了一下时钟源就行了。
2. 驱动框架分析
前面我们知道lcdif: lcdif@21c8000节点的 compatible 属性值为“fsl,imx6ul-lcdif”和“fsl,imx28-lcdif” ,搜索就可以发现,驱动是这个文件:mxsfb.c - drivers/video/fbdev/mxsfb.c。点开看一眼就会发现,这个其实也是个平台设备驱动。
2.1 mxsfb_driver
既然是个平台设备驱动,那按惯例,肯定先看这个mxsfb_driver平台设备驱动结构体的定义:
1 | static struct platform_driver mxsfb_driver = { |
这里的设备树匹配表为:mxsfb_dt_ids:
1 | static const struct of_device_id mxsfb_dt_ids[] = { |
所以这里就可以完成匹配啦。
2.2 mxsfb_driver.mxsfb_probe
匹配上之后,肯定就是调用函数mxsfb_driver.mxsfb_probe了,这个函数主要工作如下:
①、申请 fb_info。
②、初始化 fb_info 结构体中的各个成员变量,时间参数信息都会存放在这个结构体中。
③、初始化 eLCDIF 控制器,这里会用到fb_info 中的一些参数,例如前面着重学习的时间参数。
④、使用 register_framebuffer() 函数向 Linux 内核注册初始化好的 fb_info 。
详细的可以去看驱动,这里就不深入分析了。
三、LCD驱动移植
上面的设备树其实是不完整的,里面少了一些参数,我们移植的时候加进去。
1. 设备树修改
1.1 LCD 屏幕 IO 配置
首先要检查一下设备树中 LCD 所使用的 IO 配置,这个其实 NXP 都已经给我们写好了,不需要修改,不过我们还是要看一下。