【嵌入式环境下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(蓝)的顺序排布的。
在这里插入图片描述

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

  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()用于注册字符设备驱动程序。它的声明位于<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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/77060.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法通关村第三关【黄金】| 数组元素出现次数问题

1.数字出现的次数超过数组长度的一半 方法一、使用Map键值对来记录每个元素出现的次数&#xff0c;返回次数大于一半的 class Solution {public int majorityElement(int[] nums) {Map<Integer,Integer> map new HashMap<>();for(int i 0;i<nums.length;i){m…

深度学习入门-3-计算机视觉-图像分类

1.概述 图像分类是根据图像的语义信息对不同类别图像进行区分&#xff0c;是计算机视觉的核心&#xff0c;是物体检测、图像分割、物体跟踪、行为分析、人脸识别等其他高层次视觉任务的基础。图像分类在许多领域都有着广泛的应用&#xff0c;如&#xff1a;安防领域的人脸识别…

开源低代码平台Openblocks

网友 HankMeng 想看低代码工具&#xff0c;正好手上有一个&#xff1b; 什么是 Openblocks &#xff1f; Openblocks 是一个开发人员友好的开源低代码平台&#xff0c;可在几分钟内构建内部应用程序。 传统上&#xff0c;构建内部应用程序需要复杂的前端和后端交互&#xff0c;…

百度屏蔽词有哪些?其中就有移民关键词指数被屏蔽?

我是百收网SEO&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 今日tombkeeper消息爆料&#xff1a;百度指数已经屏蔽“移民”等关键词指数。 大家好&#xff0c;我是百收网SEO商学院的狂潮微课老师&#xff0c;今天我们来讲解第 12 节课关键词优化难度分析…

Markdown编辑器 Mac版Typora功能介绍

Typora mac是一款跨平台的Markdown编辑器&#xff0c;支持Windows、MacOS和Linux操作系统。它具有实时预览功能&#xff0c;能够自动将Markdown文本转换为漂亮的排版效果&#xff0c;让用户专注于写作内容而不必关心格式调整。 Typora Mac版除了支持常见的Markdown语法外&#…

国资委79号文解读:国央企OA办公系统信创替代落地实践与标杆案例

国资委79号文解读&#xff1a;国央企OA办公系统信创替代落地实践与标杆案例 2022年9月底&#xff0c;国资委下发了重要的国资发79号文件&#xff0c;全面指导并要求国央企落实信息化系统的信创国产化改造。其中&#xff0c;明确要求所有中央企业在2022年11月底前将安可替代总体…

Unity Spine帧事件

SpinePro中添加事件帧 首先 选中右上角的层级树 然后选择事件选项 最后在右下角看到 新建 点击它 新建一个事件 点击左上角的设置按钮 弹出编辑窗口 编辑窗口 在右上角 动画栏 可以切换对应的动画 点坐边的那个小灰点来切换 亮点代表当前动画 选中帧 添加事件 点击对应事件…

[机器学习]特征工程:特征降维

特征降维 1、简介 特征降维是指通过减少特征空间中的维度&#xff0c;将高维数据映射到一个低维子空间的过程。 在机器学习和数据分析中&#xff0c;特征降维可以帮助减少数据的复杂性、降低计算成本、提高模型性能和可解释性&#xff0c;以及解决维度灾难等问题。特征降维通…

Datawhale Django后端开发入门 TASK02 Admin管理员、外键的使用

1.Admin管理员的使用 先放一张成功的截图&#xff0c;记得自己创建时的账号和密码呀&#xff0c;如果忘了的话可以也是再重新创建管理员账号和密码的 &#xff0c;这个页面接下来就不用操作了,就要开始重要的 post 步骤。 二、外键的使用 我认为比较难的&#xff08;很不好操作…

【Spring 】了解Spring AOP

目录 一、什么是Spring AOP 二、AOP的使用场景 三、AOP组成 四、Spring AOP的实现 1、添加Spring AOP依赖 2、定义切面和切点 3、定义相关通知 五、 AOP的实现原理 1、什么是动态代理 2、 JDK代理和CGLIB代理的区别 一、什么是Spring AOP AOP&#xff08;Aspect Ori…

opencv-进阶05 手写数字识别原理及示例

前面我们仅仅取了两个特征维度进行说明。在实际应用中&#xff0c;可能存在着更多特征维度需要计算。 下面以手写数字识别为例进行简单的介绍。 假设我们要让程序识别图 20-2 中上方的数字&#xff08;当然&#xff0c;你一眼就知道是“8”&#xff0c;但是现在要让计算机识别…

SharkTeam:Worldcoin运营数据及业务安全分析

Worldcoin的白皮书中声明&#xff0c;Worldcoin旨在构建一个连接全球人类的新型数字经济系统&#xff0c;由OpenAI创始人Sam Altman于2020年发起。通过区块链技术在Web3世界中实现更加公平、开放和包容的经济体系&#xff0c;并将所有权赋予每个人。并且希望让全世界每一个人都…

【iMessage频發软件苹果群发技术开源原创】当 APNs 发送通知到一个离线设备时,APNs 会把通知存储起来(一定的时间内),当设备上线时再递送给设备。

推荐内容IMESSGAE相关 作者✈️IMEAE推荐内容iMessage苹果推软件 *** 点击即可查看作者要求内容信息作者✈️IMEAE推荐内容1.家庭推内容 *** 点击即可查看作者要求内容信息作者✈️IMEAE推荐内容2.相册推 *** 点击即可查看作者要求内容信息作者✈️IMEAE推荐内容3.日历推 *** …

ATTCK覆盖度97.1%!360终端安全管理系统获赛可达认证

近日&#xff0c;国际知名第三方网络安全检测服务机构——赛可达实验室&#xff08;SKD Labs&#xff09;发布最新测试报告&#xff0c;360终端安全管理系统以ATT&CK V12框架攻击技术覆盖面377个、覆盖度97.1%&#xff0c;勒索病毒、挖矿病毒检出率100%&#xff0c;误报率0…

数据分析 | 随机森林如何确定参数空间的搜索范围

1. 随机森林超参数 极其重要的三个超参数是必须要调整的&#xff0c;一般再加上两到三个其他超参数进行优化即可。 2. 学习曲线确定n_estimators搜索范围 首先导入必要的库&#xff0c;使用sklearn自带的房价预测数据集&#xff1a; import numpy as np import pandas as pd f…

Java数字化智慧工地管理云平台源码(人工智能、物联网、大数据)

智慧工地优势&#xff1a;"智慧工地”将施工企业现场视频管理、建筑起重机械安全监控、现场从业人员管理、物料管理、进度管理、扬尘噪声监测等现场设备有机、高效、科学、规范的结合起来真正实现工程项目业务流与现场各类监控源数据流的有效结合与深度配合&#xff0c;实…

css3-grid:grid 布局 / 基础使用

一、理解 grid 二、理解 css grid 布局 CSS Grid布局是一个二维的布局系统&#xff0c;它允许我们通过定义网格和网格中每个元素的位置和尺寸来进行页面布局。CSS Grid是一个非常强大的布局系统&#xff0c;它不仅可以用于构建网格布局&#xff0c;还可以用于定位元素&#xf…

解锁编程的新契机:深入探讨Kotlin Symbol Processor (KSP)的编写

解锁编程的新契机&#xff1a;深入探讨Kotlin Symbol Processor (KSP)的编写 1. 引言 随着软件开发领域的不断发展&#xff0c;新的工具和技术不断涌现&#xff0c;以满足开发者在构建高效、可维护和创新性的代码方面的需求。Kotlin Symbol Processor&#xff08;KSP&#xf…

Actuator微服务信息完善-Eureka—SpringCloud(版)微服务学习教程(11)

一、Actuator是什么&#xff1f; Actuator是Springboot提供的用来对应用系统进行自省和监控的功能模块&#xff0c;借助于Actuator开发者可以很方便地对应用系统某些监控指标进行查看、统计等。 在Springboot中使用Actuator监控非常简单&#xff0c;只需要在工程POM文件中引入…

VMware虚拟安装Ubuntu,然后切换Ubuntu内核版本

无论你选择哪种方法&#xff0c;一旦进入 GRUB 引导菜单&#xff0c;你应该能够选择需要的内核版本并启动系统。 打开终端&#xff1a;你可以通过按下 Ctrl Alt T 快捷键来打开终端。 使用 sudo&#xff1a;切换内核需要管理员权限&#xff0c;因此你需要使用 sudo 命令。首…