【嵌入式环境下linux内核及驱动学习笔记-(19)LCD驱动框架2-FrameBuffer】

目录

  • 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(蓝)的顺序排布的。
【嵌入式环境下linux内核及驱动学习笔记-(19)LCD驱动框架2-FrameBuffer】_第1张图片

1.4 用户态下操作屏显

1.4.1 用文件I / O 操作屏显

在Linux环境下,可以通过framebuffer设备文件(/dev/fb0等)来操作LCD屏幕。具体步骤如下:

  1. 打开framebuffer设备文件:
int fbfd = open("/dev/fb0", O_RDWR);
  1. 通过ioctl()来获取framebuffer参数,如屏分辨率,像素格式等:
struct fb_var_screeninfo vinfo;
ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);
  1. 通过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);
  1. 可以通过fbp指针来直接操作显存,实现画点,画线,填充等功能。
  2. 通过ioctl(FBIOPUT_VSCREENINFO)来修改vinfo参数,实现Resolution修改,色深变化等功能。
  3. unmap显存:
munmap(fbp, vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8);
  1. 关闭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,主要步骤如下:

  1. 打开/dev/fb0获取文件描述符fd
  2. 通过ioctl获取屏参数,如分辨率、色深等,计算映射大小length
  3. mmap映射:
void *fbp = mmap(0, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 
  • 这里我们要读写显存,所以权限为PROT_READ | PROT_WRITE
  • 映射类型为MAP_SHARED,因为多个进程可能要同时访问屏幕
  • 偏移量offset为0,从文件开始映射
  1. fbp就是映射到用户空间的显存指针,可以直接操作它来刷屏
  2. 用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 

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

【嵌入式环境下linux内核及驱动学习笔记-(19)LCD驱动框架2-FrameBuffer】_第2张图片

\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。

下面这图是另一个角度来描述分层的概念。更宏观一点,有助于参考:

【嵌入式环境下linux内核及驱动学习笔记-(19)LCD驱动框架2-FrameBuffer】_第3张图片

2.2 fbmem.c分析

\qquad fbmem.c实质也是一个驱动模块,是在linux系统启动时自动加载的。对于驱动模块,我们分为两个方面来分析,一是驱动的启动入口__init函数,启动时做了什么。二是fbmem提供了什么接口和能力,为LCD的驱动模块提供了哪些能力。

2.2.1 fbmem的入口分析

对于驱动程序首先从其入口的__init 函数分析起,这个__init 入口函数是在该驱动被加载时首先运行的。源码如下:

【嵌入式环境下linux内核及驱动学习笔记-(19)LCD驱动框架2-FrameBuffer】_第4张图片

上图可以看出,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是两个不同的结构体变量,分别用于不同的用途,源码中定义如下:
【嵌入式环境下linux内核及驱动学习笔记-(19)LCD驱动框架2-FrameBuffer】_第5张图片

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这个内存文件系统中的文件的函数。它的声明位于头文件中。以下是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表示可读写文件)。这些模式定义在头文件中。

- 
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 在这个特定的结构体变量中,定义了以下几个函数指针:

  1. .owner:指向fb设备对应的内核模块的指针。
  2. .open:打开设备的回调函数,用于处理打开设备的操作。
  3. .read:读取设备的回调函数,用于处理从设备中读取数据的操作。
  4. .llseek:寻址设备的回调函数,用于在设备中寻址位置的操作。
  5. .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()用于注册字符设备驱动程序。它的声明位于头文件中。以下是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目录下创建和注册一个新的设备类。它的声明位于头文件中。以下是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文件中,显得比较分散,这里一一列出,阅读时,注意节点的层次。

【嵌入式环境下linux内核及驱动学习笔记-(19)LCD驱动框架2-FrameBuffer】_第6张图片

以上的树节点需要一一对照,没有的要一一补上。这里不再细说。因为以上内容,要细说,可以单独再开一篇了。

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设备的某些属性。
标志位定义在头文件中,如:

#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 
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位数。
    【嵌入式环境下linux内核及驱动学习笔记-(19)LCD驱动框架2-FrameBuffer】_第7张图片

虚拟画面的尺寸
虚拟画面一般可设为可视画面的两倍,这种结构被称之为“双缓冲机制”,这样做的好处是可以一边显示,一边缓冲下一幅画面 。

  • 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 
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 
#define module_platform_driver(__platform_driver) \
	module_driver(__platform_driver, platform_driver_register, \
			platform_driver_unregister)
#include 

#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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#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

你可能感兴趣的:(Linux内核与驱动,linux,笔记,lcd驱动,FameBuffer框架)