目录
- 1、 Frmebuffer(帧缓冲)操作介绍
- 1.1 显示设备的抽象
- 1.2 内存映像
- 1.3 输出画面数据
- 1.4 用户态下操作屏显
- 1.4.1 用文件I / O 操作屏显
- 1.4.2 mmap() 函数
- 1.4.3 ioctl()函数
- 1.4.5 用命令操作屏
- 1.4.6 测试程序
- 2、Framebuffer总体框架
- 2.1 框架要点
- 2.2 fbmem.c分析
- 2.2.1 fbmem的入口分析
- 2.2.2 fbmem的接口功能
- 2.2.2.1 向上的接口
- 2.2.2.2 向下的接口
- 2.2.3 相关函数详解
- 2.2.3.1 proc_create()函数
- 2.2.3.2 fb_proc_fops结构体变量
- 2.2.3.3. register_chrdev()函数
- 2.2.3.4 class_create()函数
- 2.2.3.5 register_framebuffer()函数
- 2.3 LCD驱动分析(设备树匹配的platform平台驱动)
- 2.3.1 设备树相关内容
- 2.3.2 数据结构及函数
- struct fb_info
- struct fb_var_screeninfo 和 struct fb_fix_screeninfo
- struct fb_var_screeninfo
- struct fb_fix_screeninfo
- struct fb_videomode结构体
- fb_var_to_videomode()与 fb_videomode_to_var()函数
- struct fb_ops
- 宏 module_platform_driver
- 2.3.3 LCD驱动的结构与步骤
- 3、驱动程序实例
- 4、测试LCD显示的应用程序
- 写在结尾
- 参考
1、 Frmebuffer(帧缓冲)操作介绍
1.1 显示设备的抽象
\qquad Linux是工作在保护模式下,Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。
\qquad Framebuffer机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过Framebuffer的读写直接对显存进行操作。用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。
1.2 内存映像
\qquad
显示画面的输出,实际是通过往显存里面写像素数据来实现的。由于显存实际是处于内核态的物理内存,所以下一步要把这块物理内存映射到用户态,这样应用程序就可以直接操作这块物理内存了。
\qquad
内存的映像是在使用时,由使用者通过mmap命令实现的。
1.3 输出画面数据
\qquad
我们有了显存之后,要如何才能将画面数据写入显存了?
\qquad
假设我们当前的环境, xres_virtual、yres_virtual分别为800,960;bpp(像素深度)为32位;所以每个像素用一个int来表示,虚拟屏幕尺寸为800*960像素。
\qquad
显存中,数据排布的顺序就是按照虚拟屏幕中像素数据从上到下,从左到右的数据来排布。而每一个像素数据则按照A(透明度)、R(红)、G(绿)、B(蓝)的顺序排布的。
1.4 用户态下操作屏显
1.4.1 用文件I / O 操作屏显
在Linux环境下,可以通过framebuffer设备文件(/dev/fb0等)来操作LCD屏幕。具体步骤如下:
- 打开framebuffer设备文件:
int fbfd = open("/dev/fb0", O_RDWR);
- 通过ioctl()来获取framebuffer参数,如屏分辨率,像素格式等:
struct fb_var_screeninfo vinfo;
ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);
- 通过mmap()来映射LCD屏幕的显存到用户空间,得到一个指针fbp:
void *fbp = mmap(0 , vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8 ,
PROT_READ | PROT_WRIT E, MAP_SHARED, fbfd, 0);
- 可以通过fbp指针来直接操作显存,实现画点,画线,填充等功能。
- 通过ioctl(FBIOPUT_VSCREENINFO)来修改vinfo参数,实现Resolution修改,色深变化等功能。
- unmap显存:
munmap(fbp, vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8);
- 关闭framebuffer设备文件:
close(fbfd);
总之,通过framebuffer设备,我们可以获得屏幕信息,映射显存,直接操作显存来刷屏,这就是不使用GUI的原生LCD屏幕操作方法。
1.4.2 mmap() 函数
\qquad
mmap是Linux系统调用,用于映射设备(如文件)的权限到进程的地址空间。对于framebuffer设备,我们可以通过mmap来映射LCD屏幕的显存到进程的用户空间,然后就可以直接操作显存来刷新屏幕。
mmap的原型如下:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数说明:
- addr: 要映射的内存块的起始地址,一般设为NULL让系统选择地址
- length: 要映射的内存块的大小
- prot: 设定内存块的访问权限,如PROT_READ、PROT_WRITE、PROT_EXEC等
- flags: 设定映射类型,如MAP_SHARED、MAP_PRIVATE等
- fd: 要映射的文件描述符
- offset: 文件映射的偏移量
对于framebuffer,主要步骤如下:
- 打开/dev/fb0获取文件描述符fd
- 通过ioctl获取屏参数,如分辨率、色深等,计算映射大小length
- mmap映射:
void *fbp = mmap(0, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
- 这里我们要读写显存,所以权限为PROT_READ | PROT_WRITE
- 映射类型为MAP_SHARED,因为多个进程可能要同时访问屏幕
- 偏移量offset为0,从文件开始映射
- fbp就是映射到用户空间的显存指针,可以直接操作它来刷屏
- 用munmap释放映射:
munmap(fbp, length);
mmap的优点是可以直接操作物理内存,速度快;优点是会占用内存空间,并且映射和unmap也需要时间。
所以简单来说,mmap实现的是文件(物理内存)到进程虚拟地址空间的映射,我们可以通过虚拟地址直接操作文件(物理内存)。
1.4.3 ioctl()函数
\qquad ioctl()系统调用用于在用户空间和驱动空间之间传递信息。对于framebuffer设备,我们可以通过ioctl来获取和修改LCD屏的相关参数。主要的ioctl命令如下:
- 1、 FBIOGET_VSCREENINFO: 获取可变屏幕参数,如分辨率、色彩模式等.
从字面上可以理解为“fb ioctl, get variable screen info”:获取应用程序可改变的参数(如设定的分辨率)
用法:
#include <linux/fb.h>
struct fb_var_screeninfo vinfo;
ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);
用法:
ioctl(fbfd, FBIOPUT_VSCREENINFO, &vinfo);
- 3、 FBIOGET_FSCREENINFO:获取固定屏参数,对应的数据结构是fb_fix_screeninfo,包括帧缓冲区大小等信息。
从字面上理解“fb ioctl, get fixed screen info”:获取固定的参数(如屏幕的分辨率)
用法:
struct fb_fix_screeninfo finfo;
ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo);
- 4、 FBIOBLANK: 设置屏幕的blanking参数,用于开启或关闭屏幕显示。
用法:
int blank_mode = FB_BLANK_UNBLANK; //开屏
ioctl(fbfd, FBIOBLANK, blank_mode);
- 5、 FBIOGETCMAP: 获取颜色映射表
用法:
struct fb_cmap cmap;
ioctl(fbfd, FBIOGETCMAP, &cmap);
-
6、 FBIOPUTCMAP: 设置颜色映射表
**用法:**同 FBIOGETCMAP -
7、 FBIOGET_CON2FBMAP: 获取虚拟控制台到framebuffer的映射
用法:
__u32 console_fb_map[MAX_NR_CONSOLES];
ioctl(fbfd, FBIOGET_CON2FBMAP, console_fb_map);
-
8、 FBIOPUT_CON2FBMAP: 设置虚拟控制台到framebuffer的映射
**用法:**同FBIOGET_CON2FBMAP -
9、. FBIOGET_VBLANK: 获取垂直空白中断相关参数
用法:
struct fb_vblank vblank;
ioctl(fbfd, FBIOGET_VBLANK, &vblank);
1.4.5 用命令操作屏
framebuffer的设备文件一般是/dev/fb0、/dev/fb1
等等。
可以用命令: dd if=/dev/zero of=/dev/fb
清空屏幕.
如果显示模式是1024x768-8 位色,用命令:
dd if=/dev/zero of=/dev/fb0 bs=1024 count=768
清空屏幕;
用命令: dd if=/dev/fb0 of=fbfile
可以将fb中的内容保存下来;
可以重新写回屏幕: dd if=fbfile of=/dev/fb
;
在使用Framebuffer时,Linux是将显卡置于图形模式下的。
1.4.6 测试程序
在测试前,可以先在系统中查看lcd参数
输入 cat /sys/class/graphics/fb0/modes即可查看分辨率
输入cat /dev/urandom > /dev/fb0即可知晓fb是否能正常工作
fb_test.c
该程序在用户态打开fb0,然后,用ioctl函数读出fb可变参数与固定参数。然后用两个函数在framebuff上绘制背影和线条
编译正确后,需要切换到tty模式运行测试程序:
1、按ctrl+alt+F1 进入tty模式
2、登录,进入工作目录内
3、运行./fb_test.efl
4、运行结束后,按ctrl+alt+F7退出,回到x server状态
2、Framebuffer总体框架
2.1 框架要点
- Framebuffer(帧缓冲)是Linux系统为显示设备提供的一个接口,它将显示缓冲区抽象,并屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。
-
- framebuffer帧缓冲(简称fb)是一个platform类型设备,设备文件位于/dev/fbn。(n或以是0,1,…)
-
- framebuffer向应用层提供一个统一标准接口的显示设备。不论最终输出是通过hdmi还是lcd控制器,可以认为所有的GUI都是向fb输出画面的。
- 对于帧缓冲设备而言,只要在显示缓冲区中与显示点对应的区域内写入着色值,对应的着色会自动在屏幕上显示。
-
- 实际上是frambuffer就是linux内核驱动申请的一片内存空间,然后lcd内有一片sram,cpu内部有个lcd控制器,它有个单独的dma用来将frambuffer中的数据拷贝到lcd的sram中去,拷贝到lcd的sram中的数据就会显示在lcd上,具体数据的内容是由应用程序控制的。
-
- LCD驱动和framebuffer驱动没有必然的联系,它只是驱动LCD正常工作的,比如有信号传过来,那么LCD驱动负责把信号转成显示屏上的内容,至于什么内容,怎么显示,它根本不关心也不知道。
-
- 对于现代LCD,有一种“多屏叠加”的机制,即一个LCD设备可以有多个独立虚拟屏幕,以达到画面叠加的效果。所以fb与LCD不是一对一的关系,在常见的情况下,一个LCD对应了fb0~fb4。像QT这种GUI会默认把画面输出到fb0
\qquad
上图是Linux帧缓冲设备驱动结构是一个四层结构。从下到上分别为硬件(LCD控制器)、设备驱动(需要使用者开发)、fbmem层(系统已完成)、应用层(需要使用者开发应用程序)。而与驱动框架密切相关的就是中间这两层fbmem层和设备驱动层。
\qquad
fb的结构由内核中的fb框架实现一部分(上图中的fbmem.c),然后再由设备驱动本身实现一部分(图中的xxxfb.c)。设备驱动本身就是一个普通的platform总线驱动 。
- f b m e m 层 \color{red}{fbmem层} fbmem层主要完成了“上传下达”的任务:
-
- 1、向下获buffer地址。
-
- 2、创建设备类(graphics)。
-
- 3、注册字符设备主设备号。
-
- 4、向上提供操作函数接口。
- 驱动设备层主要完成了“驱动”的任务:
-
- 1、创建buffer。
-
- 2、构建设备模型(完成数据结构fb_info填写)
-
- 3、设置LCD控制器的寄存器。
-
- 4、实现设备的操作接口fb_ops。
下面这图是另一个角度来描述分层的概念。更宏观一点,有助于参考:
2.2 fbmem.c分析
\qquad fbmem.c实质也是一个驱动模块,是在linux系统启动时自动加载的。对于驱动模块,我们分为两个方面来分析,一是驱动的启动入口__init函数,启动时做了什么。二是fbmem提供了什么接口和能力,为LCD的驱动模块提供了哪些能力。
2.2.1 fbmem的入口分析
对于驱动程序首先从其入口的__init 函数分析起,这个__init 入口函数是在该驱动被加载时首先运行的。源码如下:
上图可以看出,fbmem启动时只做了几个简单的工作:
- 1、创建了/proc/fb文件,并关联了struct file_operations fb_proc_fops结构体,用于绑定/proc中操作fb文件的相关函数。
- 2、创建了一个主设备号为29的主设备(注意,这里只是建立了主设备,没有具体的fb设备),并关联了fb_fops,用于向用户层使用/dev/fbx设备提供接口。
- 3、创建了一个设备类,生成一个类文件/sys/class/graphics
(具体的上面的三个函数 可以看下面的4.2.3节。)
\qquad fbmem在启动初始化阶段只做了一些很简单的共性的工作。重要的是创建了主设备号29,并向用户层提供了两个操作接口。
2.2.2 fbmem的接口功能
fbmem的接口,有对上(用户层)的接口,也有对(LCD控制器)向下的接口。
2.2.2.1 向上的接口
提供了用户层操作的接口函数:
向上的接口有两个,一个是与用户操作相关的fb_fops这个结构体变量。提供了默认的open,read,ioctrl等操作接口。因此下面主要讨论的是这个接口。另一个向上接口是对内存文件系统/proc中的fb进行操作的接口。
在fbmem.c中,fb_fops和fb_proc_fops是两个不同的结构体变量,分别用于不同的用途,源码中定义如下:
fb_fops作用 | fb_proc_fops作用 |
---|---|
\qquad fb_fops是用于定义fb设备在文件系统中的普通文件操作的结构体变量,它定义了打开、读取、写入、寻址等操作的回调函数。这个结构体变量被用于处理用户对fb设备文件的操作,例如通过open()函数打开设备文件、通过read()函数读取设备文件的内容等。 | \qquad 而fb_proc_fops是用于定义fb设备在/proc文件系统中的文件操作的结构体变量。在Linux内核中,/proc文件系统是一种特殊的文件系统,用于提供内核和进程的运行时信息。/proc/fb是一个特殊的文件,它提供了有关帧缓冲设备的一些信息,如设备名称、设备类型、设备大小等。 |
\qquad 可以看到,fb_fops和fb_proc_fops是用于不同的文件系统和不同的操作类型的结构体变量。它们分别定义了对应于不同文件系统中的fb设备的文件操作。这样设计的目的是为了能够根据不同的文件系统和操作类型,提供不同的文件操作处理方式,以满足不同的需求和场景。
提供了用户层操作的数据接口:
struct fb_info *registered_fb[FB_MAX]
\qquad
struct fb_info *registered_fb[FB_MAX]是一个全局数组,用于存储已注册的帧缓冲设备的帧缓冲信息结构体。
\qquad
在Linux的帧缓冲设备框架中,struct fb_info结构体代表了一个帧缓冲设备的信息。该结构体包含了帧缓冲设备的各种属性和状态,如设备名称、设备类型、设备大小、显存地址、像素格式等。
\qquad
registered_fb数组的作用是用于管理已注册的帧缓冲设备。当一个帧缓冲设备被成功注册时(在帧缓冲设备框架中,通过register_framebuffer()函数将一个帧缓冲设备注册到内核中,并将其struct fb_info结构体存储在registered_fb数组中的一个位置上。),内核就可以通过访问registered_fb数组来获取已注册的帧缓冲设备的信息。
当需要访问已注册的帧缓冲设备时,可以通过遍历registered_fb数组来获取相应的帧缓冲信息。
\qquad
registered_fb数组的大小是由宏FB_MAX定义的,它表示系统中最大支持的帧缓冲设备数量。通过限制registered_fb数组的大小,可以限制系统中可以注册的帧缓冲设备的数量。
\qquad
在实际过程中,不同的帧缓冲设备的fb_info结构体是存入以次设备号做为下标的registered_fb数组中。例如/dev/fb0的fb_info信息是存在registered_fb[0]中。
2.2.2.2 向下的接口
fbmem.c向lcd的驱动层提供了一个重要的注册接口register_framebuffer(),该函数主要功能是将其参数info指向的帧缓冲设备信息添加到内核的帧缓冲设备列表中并进行注册。
\qquad
具体来说,register_framebuffer()函数执行以下操作:
- 1.检查帧缓冲设备信息的有效性,比如检查是否提供了必需的回调函数(如刷新屏幕函数fb_ops->fb_pan_display、设置显示模式函数fb_ops->fb_set_par等)。
- 2.为帧缓冲设备信息分配内存,并将设备信息的指针存储在内核中的帧缓冲设备列表(registered_fb[])的一个位置上。
- 3.调用设备驱动程序的初始化函数(如果存在的话),完成设备的初始化工作。
- 4.根据设备信息中指定的显存地址和大小等信息,为帧缓冲设备分配显存空间。
- 5.注册帧缓冲设备到内核的帧缓冲子系统,使得应用程序可以使用对应设备的帧缓冲操作接口。 通过registered_fb[]数组,内核可以追踪已注册的帧缓冲设备,并提供对它们进行管理、控制和访问的功能。 需要注意的是,注册帧缓冲设备需要具有相应的权限,通常需要在内核初始化期间或者使用特权用户执行才能成功注册。
2.2.3 相关函数详解
2.2.3.1 proc_create()函数
\qquad 在Linux内核中,函数proc_create()是用于创建/proc这个内存文件系统中的文件的函数。它的声明位于<linux/proc_fs.h>头文件中。以下是proc_create()函数的声明:
struct proc_dir_entry *proc_create(const char *name,
umode_t mode,
struct proc_dir_entry *parent,
const struct file_operations *proc_fops);
函数的参数:
-
1. name:要创建的文件的名称。这应该是一个唯一的字符串,作为文件在/proc目录下的名称。
- 2. mode:文件的访问权限模式(例如,S_IRUGO | S_IWUGO表示可读写文件)。这些模式定义在<linux/stat.h>头文件中。
-
3. parent:指向父目录的指针,即/proc中包含新文件的目录。
-
4. proc_fops:指向struct file_operations结构体的指针,它包含文件在被读/写时将调用的回调函数。该结构体包含了一个文件的各种操作函数,例如读取(read())、写入(write())和关闭(release())。
函数的功能:
\qquad
创建一个新的文件,并将其添加到/proc文件系统中。创建的文件将在通过parent参数指定的目录中可见。通过设置适当的回调函数来响应文件的读取和写入操作。
通过proc_create()函数创建的文件将在/proc中出现为一个虚拟的文件,并且可以通过相应的读写操作进行处理。这样,用户空间程序可以通过读取/proc文件系统来获取内核状态和信息。
需要注意的是,proc_create()函数已经被标记为弃用,并不推荐在新的内核代码中使用。代替的推荐方法是使用proc_create_data()函数或更高级的proc_create_single_data()函数,它们提供了更丰富的功能和更好的安全性。
2.2.3.2 fb_proc_fops结构体变量
原型:
static const struct file_operations fb_proc_fops = {
.owner = THIS_MODULE,
.open = proc_fb_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
\qquad
在Linux内核版本3.14中,fbmem.c文件中定义了一个名为fb_proc_fops的结构体变量,其类型为struct file_operations。
\qquad
这个结构体变量用于定义对应于在/proc文件系统中的fb设备请求的文件操作。
\qquad
在这个特定的结构体变量中,定义了以下几个函数指针:
- .owner:指向fb设备对应的内核模块的指针。
- .open:打开设备的回调函数,用于处理打开设备的操作。
- .read:读取设备的回调函数,用于处理从设备中读取数据的操作。
- .llseek:寻址设备的回调函数,用于在设备中寻址位置的操作。
- .release:释放设备的回调函数,用于处理释放设备的操作。
\qquad 其中,THIS_MODULE宏作为.owner参数,表示当前fb设备模块是这个文件操作的所有者。其他的回调函数则是指向相应的处理函数。
\qquad 这个结构体变量fb_proc_fops在fbmem.c文件中的作用是定义了对应于fb设备在/proc文件系统中的操作。当用户在/proc文件系统中访问fb设备时,内核将按照这个结构体变量中指定的函数来处理相应的操作,例如打开、读取、寻址和释放等。 通过这个结构体变量,fb设备模块可以响应相关的文件操作,以对用户的请求做出适当的响应。这种机制允许用户通过文件系统的方式与设备进行交互,提供了一种统一的接口来管理和控制fb设备。
2.2.3.3. register_chrdev()函数
\qquad 在Linux内核3.14中,函数register_chrdev()用于注册字符设备驱动程序。它的声明位于<linux/fs.h>头文件中。以下是register_chrdev()函数的声明:
int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);
参数解释如下:
-
1. major:设备的主设备号。如果指定为0,则会由内核动态分配可用的主设备号。
-
2. name:设备的名称。这个名称将作为设备的标识,可以在/dev目录下找到相应的设备文件。
-
3. fops:指向struct file_operations结构体的指针,其中包含了为设备定义的各种操作函数。
返回值
\qquad
表示注册结果,返回一个负值表示注册失败,返回一个非负值表示注册成功,并返回已分配或动态分配的主设备号。
功能:
\qquad
register_chrdev()函数的功能是向内核注册字符设备驱动程序。注册后,操作系统将分配一个设备文件并将其与驱动程序关联起来,这将使其可以与用户程序进行通信。
字符设备驱动程序通常涉及到文件操作,例如打开、关闭、读取和写入。通过提供适当的回调函数,例如open()、release()、read()和write(),驱动程序可以响应这些操作。
需要注意
\qquad
register_chrdev()函数已经被标记为弃用,并不推荐在新的内核代码中使用。代替的推荐方法是使用register_chrdev_region()函数或更高级的alloc_chrdev_region()函数,它们提供更灵活的设备号管理方式。此外,还可以使用字符设备框架中的设备模型进行设备注册和管理。
2.2.3.4 class_create()函数
在Linux内核3.14中,函数class_create()用于在/sys/class目录下创建和注册一个新的设备类。它的声明位于<linux/device.h>头文件中。以下是class_create()函数的声明:
struct class *class_create(struct module *owner, const char *name);
参数解释如下:
1、owner:指向拥有该类的内核模块的指针。通常使用THIS_MODULE宏作为参数,表示当前模块是该类的所有。
2、name:设备类的名称。这个名称将作为设备类的标识,会出现在/sys/class目录下。
返回值:
\qquad
是一个指向struct class结构体的指针,代表创建的设备类。如果创建失败,将返回一个错误指针。
函数的功能:
\qquad
是创建并注册一个新的设备类。设备类是用于管理一组相关设备的集合。通过创建设备类,可以将一组具有相似功能或属性的设备进行分组,并在/sys/class目录下创建相应的子目录。
创建设备类后,可以使用device_create()函数在设备类下创建具体的设备实例,并将其与相应的设备文件进行关联。
需要注意的是,class_create()函数在创建并注册设备类时,还会自动在/sys/class目录下创建与设备类同名的子目录,用于存放该类的具体设备实例。同时,该函数会创建和注册相关的属性文件,用于获取和设置设备类的属性。
对于设备驱动开发者来说,使用设备类是一种将相关设备进行组织和分类的有效方式,可以更好地管理和控制设备。
2.2.3.5 register_framebuffer()函数
在Linux 3.14中,register_framebuffer()函数用于注册一个帧缓冲设备,并将其添加到内核的帧缓冲设备列表中。
函数原型:
int register_framebuffer(struct fb_info *info);
参数:
- info:指向帧缓冲设备信息的指针,类型为struct fb_info。帧缓冲设备信息结构体struct fb_info包含了与该设备相关的各种信息,例如显存地址、显存大小、像素格式、分辨率等。
返回值:
- 成功时,函数返回值为0,表示设备注册成功。
- 失败时,函数返回值为负数,表示设备注册失败。
功能:
\qquad
register_framebuffer()函数的主要功能是将其参数info指向的帧缓冲设备信息添加到内核的帧缓冲设备列表中并进行注册。
2.3 LCD驱动分析(设备树匹配的platform平台驱动)
\qquad
LCD驱动是platform平台驱动。框架难度不大,麻烦的是LCD有大量的硬件配置需要设置。
\qquad
LCD驱动的核心能力在lcd_probe()函数,而初始化的 lcd_init()函数则是简单调用platform_driver_register()注册平台设备。本驱动采用platform的设备树匹配模式。驱动模块在切尔西成功则去调用lcd_probe()函数,完成驱动的主要能力。
这里主要参考了这编文章来详细说明LCD 驱动,由于都是用了exynos4412内核,因此参数的设置有很多相同,同时更正了文章中的一些错误以及增加了fs4412主板上的相应适配的内容。
2.3.1 设备树相关内容
\qquad 由于驱动采用的是设备树匹配。因此,驱动的第一步是正确设置设备树相关的节点。
由于exynos4412的设备树节点分布在几个不同的dtsi文件中,显得比较分散,这里一一列出,阅读时,注意节点的层次。
以上的树节点需要一一对照,没有的要一一补上。这里不再细说。因为以上内容,要细说,可以单独再开一篇了。
2.3.2 数据结构及函数
struct fb_info
这个结构体fb_info定义了Linux内核中关于帧缓冲设备(Framebuffer)的所有信息。
头文件/include/linux/fb.h
struct fb_info {
atomic_t count; 这个原子计数器记录了当前打开此帧缓冲设备的进程数
int node; numenode节点编号。对于支持NUMA的系统来说,这个值定义了帧缓冲设备所在的节点。
int flags; 一些标志位,定义此帧缓冲设备的一些属性
int fbcon_rotate_hint; 一个提示值,默认情况下为-1,由驱动器设置为FB_ROTATE_*值,如果它知道lcd没有垂直安装,fbcon应该旋转进行补偿。
struct mutex lock; 两个互斥锁,lock用于open/release/ioctl操作
struct mutex mm_lock; mm_lock用于fb_mmap和smem_*字段的访问。
struct fb_var_screeninfo var; 可变参数结构体
struct fb_fix_screeninfo fix; LCD固定参数结构体
struct fb_monspecs monspecs; LCD显示器规格描述了当前显示器的规格信息,如制造商、型号等。
struct work_struct queue; 帧缓冲事件队列一个工作队列,用于在中断上下文中排队和调度非中断上下文的Framebuffer事件。
struct fb_pixmap pixmap; 图像硬件mapper, pixmap用于硬件上映射的图像,
struct fb_pixmap sprite; 光标硬件mapper ,sprite用于光标的硬件映射。
struct fb_cmap cmap; 当前的颜色表cmap用于描述帧缓冲设备的当前色彩映射表。
struct list_head modelist; /* mode list */
struct fb_videomode *mode; 当前的显示模式*/
#if IS_ENABLED(CONFIG_FB_BACKLIGHT)
struct backlight_device *bl_dev ; 帧缓冲区注册前设置的指定背光设备,注销后删除
struct mutex bl_curve_mutex; 背光水平曲线
u8 bl_curve[FB_BACKLIGHT_LEVELS]; 背光调整
#endif
#ifdef CONFIG_FB_DEFERRED_IO
struct delayed_work deferred_work;
struct fb_deferred_io *fbdefio;
#endif
struct fb_ops *fbops; 对底层硬件操作的函数指针
struct device *device; 父设备指针
struct device *dev; 本fb设备
int class_flag; /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; 图块Blitting(位图)
#endif
union {
char __iomem *screen_base; 虚拟基地址
char *screen_buffer; “联合体,这两个互相覆盖”
};
unsigned long screen_size; LCD IO映射的虚拟内存大小,重新映射的VRAM数量或0
void *pseudo_palette; 伪16色颜色表
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; LCD的挂起或恢复状态,值为上面这两个宏之一
void *fbcon_par; /* fbcon use-only private area */
从这里开始,往下,一切都依赖于设备
void *par;
我们需要PCI或类似的光圈基础/大小,而不是smem_start/size,因为smem_start可能只是分配在光圈内部的对象,因此实际上可能不会重叠
struct apertures_struct {
unsigned int count;
struct aperture {
resource_size_t base;
resource_size_t size;
} ranges[0];
} *apertures;
bool skip_vt_switch; /* no VT switch on suspend/resume required */
};
在fb_info结构体中,flags字段是一个整数,它包含一些标志位,用于表示Framebuffer设备的某些属性。
标志位定义在<linux/fb.h>头文件中,如:
#define FBINFO_MODULE 0x0001 /* Low-level driver is a module *//*低级驱动器是一个模块*/
#define FBINFO_HWACCEL_DISABLED 0x0002 /*设置FBINFO_HWACCEL_DISABLED时:*硬件加速被关闭。所需函数(copyrea()、fillrect()和imageblit())的软件实现 取而代之;加速发动机应处于静止状态*/
#define FBINFO_VIRTFB 0x0004 /*FB是系统RAM,而不是设备*/
#define FBINFO_PARTIAL_PAN_OK 0x0040 /*otw只使用pan进行双重缓冲*/
#define FBINFO_READS_FAST 0x0080 /* soft-copy 比渲染快 /*硬件支持的操作*/
/*语义:当设置了一个位时,表示硬件加速了操作。即使未设置位,所需的功能仍将工作。*如果没有设置标志位,则可选功能甚至可能不存在。*/
#define FBINFO_HWACCEL_NONE 0x0000 表示帧缓冲设备不支持硬件加速。
#define FBINFO_HWACCEL_COPYAREA 0x0100 表示帧缓冲设备支持copyarea硬件加速,用于高效地复制区域。
#define FBINFO_HWACCEL_FILLRECT 0x0200 表示帧缓冲设备支持fillrect硬件加速,用于高效地填充矩形区域。
#define FBINFO_HWACCEL_IMAGEBLIT 0x0400 表示帧缓冲设备支持imageblit硬件加速,用于高效地传输图像数据。
#define FBINFO_HWACCEL_ROTATE 0x0800 表示帧缓冲设备支持旋转操作的硬件加速。这是可选的功能。
#define FBINFO_HWACCEL_XPAN 0x1000 /* optional */表示帧缓冲设备支持水平平移的硬件加速。这是可选的功能。
#define FBINFO_HWACCEL_YPAN 0x2000 /* optional */表示帧缓冲设备支持垂直平移的硬件加速。这是可选的功能。
#define FBINFO_HWACCEL_YWRAP 0x4000 /* optional */表示帧缓冲设备支持垂直循环滚动的硬件加速。这是可选的功能。
#define FBINFO_MISC_USEREVENT 0x10000 /* event request来自用户空间*/表示帧缓冲设备接收到的事件请求来自用户空间。
#define FBINFO_MISC_TILEBLITTING 0x20000 /* use tile blitting */表示帧缓冲设备使用瓦片传输进行加速。
#define FBINFO_MISC_ALWAYS_SETPAR 0x40000 此标志用于指示在每次切换控制台时都调用set_par函数。这可以确保在依赖于正确的硬件状态或更改该状态的任何函数之前,set_par函数始终被调用。但如果set_par函数执行较慢,会导致控制台切换的延迟增加。
#define FBINFO_MISC_FIRMWARE 0x80000 /*其中fb是一个固件驱动程序,可以用合适的驱动程序替换*/该标志表示帧缓冲驱动程序是一个固件驱动程序,并可被适当的驱动程序替换。
*/
#define FBINFO_FOREIGN_ENDIAN 0x100000 /*主机和GPU端序不同。*/表示主机和GPU的字节序不同。
#define FBINFO_BE_MATH 0x100000 大端序。这与上面的标志相同,但含义不同,由fb子系统根据FOREIGN_ENDIAN标志和主机端序设置。驱动不应使用此标志。
#define FBINFO_CAN_FORCE_OUTPUT 0x200000 向VT层报告此fb驱动程序可以接受像oopes一样的强制控制台输出
struct fb_var_screeninfo 和 struct fb_fix_screeninfo
在头文件include/uapi/linux/fb.h 中定义
struct fb_var_screeninfo
fb_var_screeninfo结构体主要记录用户可以修改的控制器的参数,比如屏幕的分辨率和每个像素的比特数等,该结构体定义如下:
#include <uapi/linux/fb.h>
struct fb_var_screeninfo {
__u32 xres; 可见屏幕一行有多少个像素点visible resolution
__u32 yres; 可见屏幕一列有多少个像素点
__u32 xres_virtual; 虚拟屏幕一行有多少个像素点。虚拟屏幕是在硬件上模拟的屏幕,可以比可见屏幕更大。
__u32 yres_virtual; 虚拟屏幕一列有多少个像素点。和xres_virtual一样,这个成员存储虚拟屏幕的垂直分辨率。
__u32 xoffset; 虚拟屏幕到可见屏幕之间的行偏移。当虚拟屏幕大于可见屏幕时,这个成员指定了虚拟
屏幕相对于可见屏幕的水平偏移。
__u32 yoffset; 虚拟屏幕到可见屏幕之间的列偏移。和xoffset一样,这个成员指定了虚拟屏幕相对于可
见屏幕的垂直偏移。
__u32 bits_per_pixel; 每个像素的位数,即BPP(Bits Per Pixel)。这个成员存储屏幕中每个像素的位数,
用于确定图像的颜色深度。
__u32 grayscale; 非0时,表示图像为灰度图像。当grayscale为0时,表示图像为彩色图像。
struct fb_bitfield red; 表示帧缓冲中的红、绿、蓝位域。这些位域用于存储真彩色图像的每个像素的颜色值。
struct fb_bitfield green;
struct fb_bitfield blue;
struct fb_bitfield transp; 透明度位域。如果图像支持透明度,这个位域用于存储每个像素的透明度值。
__u32 nonstd; 非0时,表示非标准像素格式。这个成员用于区分非标准的像素格式。
__u32 activate; 用于指定激活显示的方式,可以是FB_ACTIVATE_NOW(立即激活)或
FB_ACTIVATE_FORCE(强制激活)。看fb.h中的 FB_ACTIVATE_*宏
__u32 height; 图像的高度,以毫米为单位。这个成员用于描述图像在屏幕上的实际高度。
__u32 width; 图像的宽度,以毫米为单位。和height一样,这个成员用于描述图像在屏幕上的实际宽度。
__u32 accel_flags; /* (OBSOLETE) see fb_info.flags */
/* Timing: All values in pixclocks, except pixclock (of course) */
定时:除了pixclock本身外,其他的都以像素时钟为单位
__u32 pixclock; 像素时钟,这个成员存储像素时钟的频率,用于计算图像的时序信息。
__u32 left_margin; 行切换,从同步到绘图之间的延迟 time from sync to picture
__u32 right_margin; 行切换,从绘图到同步之间的延迟 time from picture to sync
__u32 upper_margin; 帧切换,从同步到绘图之间的延迟 time from sync to picture
__u32 lower_margin; 帧切换,从绘图到同步之间的延迟
__u32 hsync_len; 水平同步的长度。它们表示同步信号的持续时间。
__u32 vsync_len; 垂直同步的长度。它们表示同步信号的持续时间。
__u32 sync; 示同步的方式,可以是FB_SYNC_HOR_HIGH_ACT(水平同步信号为高电平活动)
或FB_SYNC_VERT_HIGH_ACT(垂直同步信号为高电平活动)等。看fb.h中的 FB_SYNC_*宏
__u32 vmode; 显示模式。它指定了显示器的模式,可以是FB_VMODE_NONINTERLACED(非隔行模式)
或FB_VMODE_INTERLACED(隔行模式)等。参考fb.h中的 FB_VMODE_*宏
__u32 rotate; 以逆时针方向旋转的角度。它表示图像在屏幕上显示时的旋转角度。
__u32 colorspace; 基于FOURCC的模式的颜色空间。它指定了基于FOURCC的模式的颜色空间,例如RGB或YUV。
__u32 reserved[4]; 保留Reserved for future compatibility
};
**比较重要的可变参数有: **
- xres、yres:可视画面的x、y轴分辨率(应用层改不了)
- xres_virtual、yres_virtual:虚拟画面(即fb)x、y轴分辨率
- xoffset、yoffset:可视画面相对于虚拟画面的x、y轴偏移量
- bits_per_pixel:像素深度,每个像数的bit位数。
虚拟画面的尺寸
虚拟画面一般可设为可视画面的两倍,这种结构被称之为“双缓冲机制”,这样做的好处是可以一边显示,一边缓冲下一幅画面 。
- activate: 激活设定参数的方式。在fb.h中定义的宏。这些宏在设置帧缓冲设备的属性时,用于激活或改变属性的不同标志。下面是每个宏的具体含义:
-
- FB_ACTIVATE_NOW(0):立即设置属性的值(或在垂直空白期设置)。
-
- FB_ACTIVATE_NXTOPEN(1):在下一次打开设备时激活属性。
-
- FB_ACTIVATE_TEST(2):不设置属性的值,将不可能的值四舍五入。
-
- FB_ACTIVATE_MASK(15):用于掩码操作的值。
-
- FB_ACTIVATE_VBL(16):在下一个垂直空白期激活属性的值。
-
- FB_CHANGE_CMAP_VBL(32):在下一个垂直空白期更改颜色映射表。
-
- FB_ACTIVATE_ALL(64):更改此帧缓冲设备上的所有虚拟控制台。
-
- FB_ACTIVATE_FORCE(128):即使没有更改,也强制应用属性的设置。
-
- FB_ACTIVATE_INV_MODE(256):使当前视频模式无效。
-
- 这些标志可以用来指定在设置帧缓冲设备属性时应采取的行动。可以根据需要组合使用这些标志。
struct fb_fix_screeninfo
而fb_fix_screeninfo结构体又主要记录用户不可以修改的控制器的参数,比如屏幕缓冲区的物理地址和长度等,该结构体的定义如下:
struct fb_fix_screeninfo {
char id[16]; 字符串形式的标示符 identification string eg "TT Builtin"
unsigned long smem_start; fb缓存的开始位置 Start of frame buffer mem (physical address)
__u32 smem_len; fb缓存的长度 Length of frame buffer mem
__u32 type; 看FB_TYPE_*
__u32 type_aux; 分界 Interleave for interleaved Planes
__u32 visual; 看 FB_VISUAL_*
__u16 xpanstep; 如果没有硬件panning就赋值为0 zero if no hardware panning
__u16 ypanstep; 如果没有硬件panning就赋值为0 zero if no hardware panning
__u16 ywrapstep; 如果没有硬件ywrap就赋值为0 zero if no hardware ywrap
__u32 line_length; 一行的字节数 length of a line in bytes
unsigned long mmio_start; 内存映射IO的开始位置 /* Start of Memory Mapped I/O */
/* (physical address) */
__u32 mmio_len; 内存映射IO的长度/* Length of Memory Mapped I/O */
__u32 accel; /* Indicate to driver which */
/* specific chip/card we have */
__u16 capabilities; /* see FB_CAP_* */
__u16 reserved[2]; /* Reserved for future compatibility */
};
\qquad 这里比较常用的是:
- smem_len : framebuff的长度
- type : 类型,在fb.h中有如下的宏定义。
-
- #define FB_TYPE_PACKED_PIXELS 0 /* Packed Pixels 表示使用“Packed Pixels”格式的像素数据。在此格式中,像素按照紧密排列的方式存储,每个像素占用固定数量的位或字节。这是一种常见的视频和图形数据存储格式。*/
-
- #define FB_TYPE_PLANES 1 /* Non interleaved planes 表示使用“非交错平面”格式的像素数据。在此格式中,像素数据被分为多个独立的平面,每个平面存储一种颜色分量。这种像素排列方式在某些情况下可以提供更高的灵活性和图像质量。*/
-
- #define FB_TYPE_INTERLEAVED_PLANES 2 /* Interleaved planes 表示使用“交错平面”格式的像素数据。在此格式中,像素数据以交错方式存储在不同的平面中。这种像素排列方式可以提供更高的内存访问效率。*/
-
- #define FB_TYPE_TEXT 3 /* Text/attributes 表示使用“文本/属性”格式的像素数据。在此格式中,像素数据用于显示文本字符和相关属性(如前景色、背景色、样式等)。 */
-
- #define FB_TYPE_VGA_PLANES 4 /* EGA/VGA planes 表示使用“EGA/VGA平面”格式的像素数据。这种格式与 FB_TYPE_PLANES 类似,但特定于 VGA 图形适配器。 */
-
- #define FB_TYPE_FOURCC 5 /* Type identified by a V4L2 FOURCC 表示使用由 V4L2(Video for Linux 2)定义的 FOURCC(Four-Character Code)标识的像素数据类型。这种类型标识可以用于表示各种特定格式和压缩算法的像素数据。*/
- visual : 可视化模式或像素模式,在fb.h中有如下的宏定义
-
- #define FB_VISUAL_MONO01 0 /* Monochr. 1=Black 0=White 表示单色模式,像素值为0表示白色,像素值为1表示黑色。 */
-
- #define FB_VISUAL_MONO10 1 /* Monochr. 1=White 0=Black 表示单色模式,像素值为0表示黑色,像素值为1表示白色。 */
-
- #define FB_VISUAL_TRUECOLOR 2 /* True color 表示真彩色模式, 可以支持高分辨率图像显示。在此模式中,每个像素由红、绿、蓝三个颜色分量组成,可以使用更广泛的颜色范围,以呈现更真实的图像。*/
-
- #define FB_VISUAL_PSEUDOCOLOR 3 /* Pseudo color (like atari) 表示伪彩色模式,类似于Atari ST的显示模式。在此模式中,每个像素的颜色值是通过颜色映射表(调色板)查找来确定的,显卡上的颜色映射表支持有限的颜色范围。*/
-
- #define FB_VISUAL_DIRECTCOLOR 4 /* Direct color 表示直接彩色模式,像素的颜色将由红、绿、蓝三个颜色分量组成,但与真彩色不同,颜色分量的数量可能不同,且混合方式可能不同。此模式在显示彩色图像时提供了更高的位深度和精度。 */
-
- #define FB_VISUAL_STATIC_PSEUDOCOLOR 5 /* Pseudo color readonly 表示静态伪彩色模式,类似于伪彩色模式,但只读,即无法更改像素的颜色映射表。 */
-
- #define FB_VISUAL_FOURCC 6 /* Visual identified by a V4L2 FOURCC 表示被V4L2(Video for Linux 2)标识的特定像素格式。它允许使用V4L2 FOURCC(四字符码)来标识特定的像素编解码器和压缩算法,以支持多种图像格式。 */
struct fb_videomode结构体
(详见2.6.1)
fb_var_to_videomode()与 fb_videomode_to_var()函数
void fb_var_to_videomode(struct fb_videomode *mode,
const struct fb_var_screeninfo *var)
{
u32 pixclock, hfreq, htotal, vtotal;
mode->name = NULL;
mode->xres = var->xres;
mode->yres = var->yres;
mode->pixclock = var->pixclock;
mode->hsync_len = var->hsync_len;
mode->vsync_len = var->vsync_len;
mode->left_margin = var->left_margin;
mode->right_margin = var->right_margin;
mode->upper_margin = var->upper_margin;
mode->lower_margin = var->lower_margin;
mode->sync = var->sync;
mode->vmode = var->vmode & FB_VMODE_MASK;
mode->flag = FB_MODE_IS_FROM_VAR;
mode->refresh = 0;
if (!var->pixclock)
return;
pixclock = PICOS2KHZ(var->pixclock) * 1000;
htotal = var->xres + var->right_margin + var->hsync_len +
var->left_margin;
vtotal = var->yres + var->lower_margin + var->vsync_len +
var->upper_margin;
if (var->vmode & FB_VMODE_INTERLACED)
vtotal /= 2;
if (var->vmode & FB_VMODE_DOUBLE)
vtotal *= 2;
hfreq = pixclock/htotal;
mode->refresh = hfreq/vtotal;
}
void fb_videomode_to_var(struct fb_var_screeninfo *var,
const struct fb_videomode *mode)
{
var->xres = mode->xres;
var->yres = mode->yres;
var->xres_virtual = mode->xres;
var->yres_virtual = mode->yres;
var->xoffset = 0;
var->yoffset = 0;
var->pixclock = mode->pixclock;
var->left_margin = mode->left_margin;
var->right_margin = mode->right_margin;
var->upper_margin = mode->upper_margin;
var->lower_margin = mode->lower_margin;
var->hsync_len = mode->hsync_len;
var->vsync_len = mode->vsync_len;
var->sync = mode->sync;
var->vmode = mode->vmode & FB_VMODE_MASK;
}
struct fb_ops
fb_ops结构体是对底层硬件操作的函数指针,该结构体中定义了对硬件的操作有:
#include <linux/fb.h>
struct fb_ops {
/* open/release and usage marking */
struct module *owner;
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);
对于具有奇怪非线性布局或不适用于正常内存映射访问的帧缓冲区
ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
size_t count, loff_t *ppos);
ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
size_t count, loff_t *ppos);
检查可变参数并进行设置/* checks var and eventually tweaks it to something supported,DO NOT MODIFY PAR */
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
根据设置的值进行更新,使之有效/* set the video mode according to info->var */
int (*fb_set_par)(struct fb_info *info);
设置颜色寄存器/* set color register */
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info);
/* set color registers in batch */
int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
显示空白/* blank display */
int (*fb_blank)(int blank, struct fb_info *info);
/* pan display */
int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
矩形填充/* Draws a rectangle */
void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
复制数据/* Copy data from area to another */
void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
图形填充/* Draws a image to the display */
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
/* Draws cursor */
int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);
/* Rotates the display */
void (*fb_rotate)(struct fb_info *info, int angle);
/* wait for blit idle, optional */
int (*fb_sync)(struct fb_info *info);
/* perform fb specific ioctl (optional) */
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
unsigned long arg);
/* Handle 32bit compat ioctl (optional) */
int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
unsigned long arg);
/* perform fb specific mmap */
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
/* get capability given var */
void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
struct fb_var_screeninfo *var);
/* teardown any resources to do with this framebuffer */
void (*fb_destroy)(struct fb_info *info);
/* called at KDB enter and leave time to prepare the console */
int (*fb_debug_enter)(struct fb_info *info);
int (*fb_debug_leave)(struct fb_info *info);
};
宏 module_platform_driver
#include <linux/platform_device.h>
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
#include <linux/device.h>
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
实例:
module_platform_driver(s3c_fb_driver)
#define module_driver(__driver, __register, __unregister, ...) \
static int __init s3c_fb_driver_init(void) \
{ \
return platform_driver_register(&(s3c_fb_driver) , ##__VA_ARGS__); \
} \
module_init(s3c_fb_driver_init); \
static void __exit s3c_fb_driver_exit(void) \
{ \
platform_driver_unregister(&(s3c_fb_driver) , ##__VA_ARGS__); \
} \
module_exit(s3c_fb_driver_exit);
2.3.3 LCD驱动的结构与步骤
在FrameBuffer框架下,LCD驱动的编写也是程式化了。
\qquad
首先,LCD驱动是一个标准的platform平台总线驱动,因此其驱动的总体结构就确定下来了,而匹配模式可以采用的是多种匹配方式,本文采用的是设备树匹配。
\qquad
其次,其probe函数是最重要的初始化函数,其具本步骤如下:
- 1、分配一个fb_info
- 2、设置fb_info数据结构
-
- 2.1 设置 fix 固定的参数
-
- 2.2 设置 var 可变的参数
-
- 2.3 设置操作函数
-
- 2.4 其他的设置
- 3、 硬件相关的操作
-
- 3.1 配置GPIO用于LCD
-
- 3.2 根据LCD手册设置LCD控制器
-
- 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器
- 4、注册
这里先给个初步的概念,关键是直接对照去阅读调试后的源码,里面已按上述步骤,又做了相对较细的注释。
3、驱动程序实例
//{% codeblock lang:c [lcd_drv.c] https://github.com/hceng/learn/blob/master/tiny4412/02_lcd_drv/lcd_drv.c %}
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/div64.h>
#include <asm/mach/map.h>
#include <linux/fb.h>
#include <asm/types.h>
#define VIDCON0 0x00
#define VIDCON1 0x04
#define VIDTCON0 0x10
#define VIDTCON1 0x14
#define VIDTCON2 0x18
#define WINCON0 0x20
#define VIDOSD0C 0x48
#define SHADOWCON 0x34
#define WINCHMAP2 0x3c
#define VIDOSD0A 0x40
#define VIDOSD0B 0x44
#define VIDW00ADD0B0 0xA0
#define VIDW00ADD1B0 0xD0
#define CLK_SRC_LCD0 0x234
#define CLK_SRC_MASK_LCD 0x334
#define CLK_DIV_LCD 0x534
#define CLK_GATE_IP_LCD 0x934
#define LCDBLK_CFG 0x00
#define LCDBLK_CFG2 0x04
#define LCD_LENTH 1024 //800
#define LCD_WIDTH 600 //480
#define BITS_PER_PIXEL 32
/**********调试用到的变量****/
int j;
static struct resource *res_debug;
/**********/
static struct fb_info *fs4412_lcd;
static volatile void __iomem *lcd_regs_base;
static volatile void __iomem *lcdblk_regs_base;
static volatile void __iomem *lcd0_configuration;//Configures power mode of LCD0.0x10020000+0x3C80
static volatile void __iomem *clk_regs_base;
static u32 pseudo_palette[16];
static struct resource *res0, *res1, *res2, *res3;
/* from pxafb.c */
static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf){
chan &= 0xFFFF;//保留低16位
chan >>= 16 - bf->length;//保留高bf->length位
return chan << bf->offset;//返回保留的位,且在原位置
}
static int cfb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, unsigned int blue, unsigned int transp, struct fb_info *info){
unsigned int color = 0;
uint32_t *p;
color = chan_to_field(red, &info->var.red);
color |= chan_to_field(green, &info->var.green);
color |= chan_to_field(blue, &info->var.blue);p = info->pseudo_palette;
p[regno] = color;
return 0;
}
static struct fb_ops fs4412_lcdfb_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = cfb_setcolreg, //设置调色板,实现伪颜色表
.fb_fillrect = cfb_fillrect, //填充矩形
.fb_copyarea = cfb_copyarea, //数据复制
.fb_imageblit = cfb_imageblit, //图形填充
};
static int lcd_probe(struct platform_device *pdev){
int ret;
unsigned int temp;
/* 1. 分配一个fb_info */
fs4412_lcd = framebuffer_alloc(0, NULL); //不要额外空间设置私有数据
if(!fs4412_lcd) {
return -ENOMEM;
}
/* 2. 设置 */
/* 2.1 设置 fix 固定的参数 */
strcpy(fs4412_lcd->fix.id, "s702"); //设置fix名称
fs4412_lcd->fix.smem_len = LCD_LENTH*LCD_WIDTH*BITS_PER_PIXEL/8; //显存的长度=分辨率*每象素字节数
fs4412_lcd->fix.type = FB_TYPE_PACKED_PIXELS; //类型:填充式像素(常用在TFT屏幕)
fs4412_lcd->fix.visual = FB_VISUAL_TRUECOLOR; //TFT 真彩色
fs4412_lcd->fix.line_length = LCD_LENTH*BITS_PER_PIXEL/8; //每行的长度,以字节为单位
/* 2.2 设置 var 可变的参数 */
fs4412_lcd->var.xres = LCD_LENTH; //x方向分辨率
fs4412_lcd->var.yres = LCD_WIDTH; //y方向分辨率
fs4412_lcd->var.xres_virtual = LCD_LENTH; //x方向虚拟分辨率
fs4412_lcd->var.yres_virtual = LCD_WIDTH; //y方向虚拟分辨率
fs4412_lcd->var.xoffset = 0 ; //x方向真实值和虚拟值得差值0
fs4412_lcd->var.yoffset = 0; //y方向真实值和虚拟值得差值
fs4412_lcd->var.bits_per_pixel = BITS_PER_PIXEL; //每个像素占多少位RGB:888
fs4412_lcd->var.red.length = 8;
fs4412_lcd->var.red.offset = 16; //红
fs4412_lcd->var.green.length = 8;
fs4412_lcd->var.green.offset = 8; //绿
fs4412_lcd->var.blue.length = 8;
fs4412_lcd->var.blue.offset = 0; //蓝
fs4412_lcd->var.pixclock = 65000000; //65MHZ
fs4412_lcd->var.left_margin = 140; //HBP
fs4412_lcd->var.right_margin = 160; //HFP
fs4412_lcd->var.upper_margin = 20; //VBP
fs4412_lcd->var.lower_margin = 12; //VFP
fs4412_lcd->var.hsync_len = 20;
fs4412_lcd->var.vsync_len = 3;
fs4412_lcd->var.sync = ~FB_SYNC_HOR_HIGH_ACT | ~FB_SYNC_VERT_HIGH_ACT;
fs4412_lcd->var.vmode = FB_VMODE_NONINTERLACED;
fs4412_lcd->var.activate = FB_ACTIVATE_NOW; //使设置的值立即生效
/* 2.3 设置操作函数 */
fs4412_lcd->fbops = &fs4412_lcdfb_ops; //绑定操作函数
/* 2.4 其他的设置 */
fs4412_lcd->pseudo_palette = pseudo_palette; //存放调色板所调颜色的数组
fs4412_lcd->screen_size = LCD_LENTH * LCD_WIDTH * BITS_PER_PIXEL / 8; //显存大小
/* 3. 硬件相关的操作 */
/* 3.1 配置GPIO用于LCD */
//在设备树中,将 GPF0_0-GPF0_7、GPF1_0-GPF1_7、GPF2_0-GPF2_7、GPF3_0-GPF3_3
//配置为了复用第二功能(LCD),禁止内部上拉,驱动强度配置设置为0;
/* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
//寄存器映射
/**********打印出所有resource*/
for (j=0; j<pdev->num_resources;j++){
res_debug = pdev->resource+j;
printk("debug: resournces[%d],start:[%X], end:[%X],name:[%s],flags:[%X],parent[%p],sibling[%p],child[%p]\n",j,res_debug->start,res_debug->end,res_debug->name,(unsigned int)res_debug->flags,res_debug->parent,res_debug->sibling,res_debug->child);
}
/**********/
res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res0 == NULL){
printk("debug: lcd_driver.c->lcd_probe() platform_get_resource A error.\n");
return -EINVAL;
}
lcd_regs_base = devm_ioremap_resource(&pdev->dev, res0);
if (lcd_regs_base == NULL){
printk("debug : lcd_driver.c -> lcd_probe() devm_ioremap_resource A error.\n");
return -EINVAL;
}
res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (res1 == NULL){
printk("debug: lcd_driver.c->lcd_probe() platform_get_resource B error.\n");
return -EINVAL;
}
lcdblk_regs_base = devm_ioremap_resource(&pdev->dev, res1);
if (lcdblk_regs_base == NULL){
printk("debug : lcd_driver.c -> lcd_probe() devm_ioremap_resource B error.\n");
return -EINVAL;
}
res2 = platform_get_resource(pdev, IORESOURCE_MEM, 2);
if (res2 == NULL){
printk("debug: lcd_driver.c->lcd_probe() platform_get_resource C error.\n");
return -EINVAL;
}
/*devm_ioremap()和devm_ioremap_resource()区别:
devm_ioremap()可以重复map相同的地址空间,devm_ioremap_resource()不可以。
一般SoC的中,各个硬件模块各自的memory region都有严格的划分(比如说USB host的地址空间绝对不会和flash host冲突),所以一般的driver使用devm_ioremap()和devm_ioremap_resource()都行。
但这里,应该系统已经映射过一次了,所以使用devm_ioremap_resource()会报错。*/
lcd0_configuration = devm_ioremap(&pdev->dev, res2->start, resource_size(res2));
if (lcd0_configuration == NULL){
printk("debug : lcd_driver.c -> lcd_probe() devm_ioremap_resource C error.\n");
return -EINVAL;
}
*(unsigned long *)lcd0_configuration = 7; //Reset Value = 0x00000007 power on
res3 = platform_get_resource(pdev, IORESOURCE_MEM, 3);
if (res3 == NULL){
printk("debug: lcd_driver.c->lcd_probe() platform_get_resource D error.\n");
return -EINVAL;
}
//clk_regs_base = devm_ioremap_resource(&pdev->dev, res3);
clk_regs_base = devm_ioremap(&pdev->dev, res3->start, resource_size(res3));
if (clk_regs_base == NULL){
printk("debug : lcd_driver.c -> lcd_probe() devm_ioremap_resource D error.\n");
return -EINVAL;
}
//时钟源选择\使能时钟
//Selects clock source for LCD_BLK
//FIMD0_SEL:bit[3:0]=0110=SCLKMPLL_USER_T=800M
temp = readl(clk_regs_base + CLK_SRC_LCD0);
temp &= ~(0x0F<<0);
temp |= (0x3<<1);
writel(temp, clk_regs_base + CLK_SRC_LCD0);
//Clock source mask for LCD_BLK
//FIMD0_MASK:Mask output clock of MUXFIMD0 (1=Unmask)
temp = readl(clk_regs_base + CLK_SRC_MASK_LCD);
temp |= (0x01<<0);
writel(temp, clk_regs_base + CLK_SRC_MASK_LCD);
//设置LCD_BLK的时钟分频
//SCLK_FIMD0 = MOUTFIMD0/(FIMD0_RATIO + 1),分频比 1/1
temp = readl(clk_regs_base + CLK_DIV_LCD);
temp &= ~(0x0F<<0);
writel(temp, clk_regs_base + CLK_DIV_LCD);
//Controls IP clock gating for LCD_BLK 时钟使能
//CLK_FIMD0:Gating all clocks for FIMD0 (1=Pass)
temp = readl(clk_regs_base + CLK_GATE_IP_LCD);
temp |= (0x01<<0);
writel(temp, clk_regs_base + CLK_GATE_IP_LCD);
//背光控制
//FIMDBYPASS_LBLK0:FIMD of LBLK0 Bypass Selection (1=FIMD Bypass)
temp = readl(lcdblk_regs_base + LCDBLK_CFG);
temp |= (0x01<<1);
writel(temp, lcdblk_regs_base + LCDBLK_CFG);
//PWM设置
//MIE0_DISPON:MIE0_DISPON: PWM output control (1=PWM outpupt enable)
temp = readl(lcdblk_regs_base + LCDBLK_CFG2);
temp |= (0x01<<0);
writel(temp, lcdblk_regs_base + LCDBLK_CFG2);
mdelay(1000);
//VIDCON0的VCLK时钟设置
//LCD时钟: VCLK=FIMD*SCLK/(CLKVAL+1), where CLKVAL>=1
//800/(19+1) == 40M<80M
temp = readl(lcd_regs_base + VIDCON0);
temp |= (19<<6);
//temp |= (3<<6);
writel(temp, lcd_regs_base + VIDCON0);
/**
* VIDCON1:
* [5]:IVSYNC ===> 1 : Inverted(反转)
* [6]:IHSYNC ===> 1 : Inverted(反转)
* [7]:IVCLK ===> 1 : Fetches video data at VCLK rising edge (上降沿触发)
* [10:9]:FIXVCLK ====> 01 : VCLK running
* */
temp = readl(lcd_regs_base + VIDCON1);
temp |= (1 << 9) | (1 << 7) | (1 << 5) | (1 << 6);
writel(temp, lcd_regs_base + VIDCON1);
/**
* VIDTCON0:
* * [23:16]: VBPD+1=tvb-tvpw=23-11=12 --> VBPD=11
* * [15:8] : VFPD+1=tvfp=22 --> VFPD=21
* * [7:0] : VSPW+1=tvpw=1~20(暂取11) --> VSPW=10
* */
temp = readl(lcd_regs_base + VIDTCON0);
//temp |= (11 << 16) | (21 << 8) | (10 << 0);
temp |= (20 << 16) | (12 << 8) | (3 << 0);
writel(temp, lcd_regs_base + VIDTCON0);
/** VIDTCON1:
* * [23:16]: HBPD+1=thb-hpw=46-21=25 --> HBPD=24
* * [15:8] : HFPD+1=thfp=210 --> HFPD=209
* * [7:0] : HSPW+1=hpw=1~40(暂取21) --> HSPW=20
* */
temp = readl(lcd_regs_base + VIDTCON1);
//temp |= (24 << 16) | (209 << 8) | (20 << 0);
temp |= (140 << 16) | (160 << 8) | (20 << 0);
writel(temp, lcd_regs_base + VIDTCON1);
/**VIDTCON2
* HOZVAL = (Horizontal display size) - 1
* LINEVAL = (Vertical display size) - 1.
* * Horizontal(水平) display size : 800
* * Vertical(垂直) display size : 480*/
temp = ((LCD_WIDTH-1) << 11) | (LCD_LENTH << 0);
writel(temp, lcd_regs_base + VIDTCON2);
/**
* WINCON0:
* * [15]:Specifies Word swap control bit. 1 = Enables swap 低位像素存放在低字节
* * [5:2]: Selects Bits Per Pixel (BPP) mode for Window image : 1101 ===> Unpacked 25 BPP (non-palletized A:1-R:8-G:8-B:8)
* * [0]:Enables/disables video output 1 = Enables
* */
temp = readl(lcd_regs_base + WINCON0);
temp &= ~(0x0F << 2);
temp |= (0X01 << 15) | (0x0D << 2) | (0x01<<0);
writel(temp, lcd_regs_base + WINCON0);
//SHADOWCON
//Enables Channel 0.
temp = readl(lcd_regs_base + SHADOWCON);
writel(temp | 0x01, lcd_regs_base + SHADOWCON);
//WINCHMAP2
//Selects Channel 0
temp = readl(lcd_regs_base + WINCHMAP2);
temp &= ~(7 << 16);
temp |= (0x01 << 16);//CH0FISEL:Selects Channel 0's channel.001 = Window 0
temp &= ~(7 << 0);
temp |= (0x01 << 0);//W0FISEL:Selects Window 0's channel.001 = Channel 0
writel(temp, lcd_regs_base + WINCHMAP2);
//VIDOSD0A VIDOSD0B VIDOSD0C
//设置OSD显示大小
//Window Size For example. Height * Width (number of word)
temp = (LCD_LENTH * LCD_WIDTH) >> 1;
writel(temp, lcd_regs_base + VIDOSD0C);
/** bit0-10 : 指定OSD图像左上像素的垂直屏幕坐标
* * bit11-21: 指定OSD图像左上像素的水平屏幕坐标*/
writel(0, lcd_regs_base + VIDOSD0A);
/** bit0-10 : 指定OSD图像右下像素的垂直屏幕坐标
* * bit11-21: 指定OSD图像右下像素的水平屏幕坐标*/
writel(((LCD_LENTH-1) << 11) | (LCD_WIDTH-1), lcd_regs_base + VIDOSD0B);
//VIDCON0
//Display On: ENVID and ENVID_F are set to "1".
temp = readl(lcd_regs_base + VIDCON0);
writel(temp | (0x01<<1) | (0x01<<0), lcd_regs_base + VIDCON0);
/* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */
// fs4412_lcd->screen_base 显存虚拟地址
// fs4412_lcd->fix.smem_len 显存大小,前面计算的
// fs4412_lcd->fix.smem_start 显存物理地址
fs4412_lcd->screen_base = dma_alloc_writecombine(NULL, fs4412_lcd->fix.smem_len, (dma_addr_t *)&fs4412_lcd->fix.smem_start, GFP_KERNEL);
//显存起始地址
writel(fs4412_lcd->fix.smem_start, lcd_regs_base + VIDW00ADD0B0);
//显存结束地址
writel(fs4412_lcd->fix.smem_start + fs4412_lcd->fix.smem_len, lcd_regs_base + VIDW00ADD1B0);
/* 4. 注册 */
ret = register_framebuffer(fs4412_lcd);
return ret;
}
static int lcd_remove(struct platform_device *pdev){
//Direct Off: ENVID and ENVID_F are set to “0” simultaneously.
unsigned int temp;
temp = readl(lcd_regs_base + VIDCON0);
temp &= ~(0x01<<1 | 0x01<<0);
writel(temp, lcd_regs_base + VIDCON0);
unregister_framebuffer(fs4412_lcd);
dma_free_writecombine(NULL, fs4412_lcd->fix.smem_len, fs4412_lcd->screen_base, fs4412_lcd->fix.smem_start);
framebuffer_release(fs4412_lcd);
return 0;
}
static const struct of_device_id lcd_dt_ids[] = {
{.compatible = "samsung,exynos4210-fimd"},
{},
};
MODULE_DEVICE_TABLE(of, lcd_dt_ids);
static struct platform_driver lcd_driver = {
.driver={
.name = "mylcd",
.of_match_table = of_match_ptr(lcd_dt_ids),
},
.probe = lcd_probe,
.remove = lcd_remove,
};
static int lcd_init(void) {
int ret;
ret = platform_driver_register(&lcd_driver);
return ret;
}
static void lcd_exit(void){
printk("enter %s\n", __func__);
platform_driver_unregister(&lcd_driver);
}
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");
\qquad 以上内容。细节很多,特别是在probe函数中的寄存器赋值部份还有相当多的细节,但在这里是讲述驱动框架的,因此另开一篇来讲解exynos4412的LCD控制器的寄存器操作。
4、测试LCD显示的应用程序
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <unistd.h>
#define FBDEVICE "/dev/fb0"
void draw_back(unsigned int *pfb, unsigned int width, unsigned int height, unsigned int color);
void draw_line(unsigned int *pfb, unsigned int width, unsigned int height);
int main(void)
{
int fd = -1;
int ret = -1;
unsigned int *pfb = NULL;
struct fb_fix_screeninfo finfo;
struct fb_var_screeninfo vinfo;
fd = open(FBDEVICE, O_RDWR);
if (fd < 0)
{
perror("open");
return -1;
}
printf("open %s success \n", FBDEVICE);
/*获取fb信息*/
ret = ioctl(fd, FBIOGET_FSCREENINFO, &finfo);
if (ret < 0)
{
perror("ioctl");
return -1;
}
ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
if (ret < 0)
{
perror("ioctl");
return -1;
}
/*建立mmap映射*/
pfb = mmap(NULL, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (NULL == pfb)
{
perror("mmap");
return -1;
}
printf("pfb :0x%x \n",(unsigned int ) pfb);
draw_back(pfb, vinfo.xres_virtual, vinfo.yres_virtual, 0xffff0000);
draw_line(pfb, vinfo.xres_virtual, vinfo.yres_virtual);
close(fd);
return 0;
}
void draw_back(unsigned int *pfb, unsigned int width, unsigned int height, unsigned int color)
{
unsigned int x, y;
for (y = 0; y < height; y++)
{
for (x = 0; x < width; x++)
{
*(pfb + y * width + x) = color;
}
}
}
void draw_line(unsigned int *pfb, unsigned int width, unsigned int height)
{
unsigned int x, y;
for (x = 50; x < width - 50; x++)
{
*(pfb + 50 * width + x) = 0xffffff00;
}
for (y = 50; y < height -50; y++)
{
*(pfb + y * width + 50) = 0xffffff00;
}
}
写在结尾
\qquad
本篇与上一篇是linux下的LCD驱动框架-FrameBuffer框架的完整笔记,框架本身是简单的,但由于涉及到大量LCD的显示原理,LCD控制器的配置,寄存器的配置,时序的分辨等。而这些又与大量的数据结构相对应。在理清上述内容后,又需要在开发板上进行验证,因此这两篇实际写了一个多月。
\qquad
内容又多又杂,难免有诸多遗漏与不足,因此,在以后如有发现缺漏,我将会随时进行修改。
参考
https://blog.csdn.net/qq_28992301/article/details/52727050
https://www.cnblogs.com/armlinux/archive/2011/01/14/2396864.html
https://zhuanlan.zhihu.com/p/598132318
http://www.51hei.com/bbs/dpj-43162-1.html
https://www.ngui.cc/zz/1632478.html?action=onClick