\qquad Linux是工作在保护模式下,Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。
\qquad Framebuffer机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过Framebuffer的读写直接对显存进行操作。用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。
\qquad 显示画面的输出,实际是通过往显存里面写像素数据来实现的。由于显存实际是处于内核态的物理内存,所以下一步要把这块物理内存映射到用户态,这样应用程序就可以直接操作这块物理内存了。
\qquad 内存的映像是在使用时,由使用者通过mmap命令实现的。
\qquad 我们有了显存之后,要如何才能将画面数据写入显存了?
\qquad 假设我们当前的环境, xres_virtual、yres_virtual分别为800,960;bpp(像素深度)为32位;所以每个像素用一个int来表示,虚拟屏幕尺寸为800*960像素。
\qquad 显存中,数据排布的顺序就是按照虚拟屏幕中像素数据从上到下,从左到右的数据来排布。而每一个像素数据则按照A(透明度)、R(红)、G(绿)、B(蓝)的顺序排布的。
在Linux环境下,可以通过framebuffer设备文件(/dev/fb0等)来操作LCD屏幕。具体步骤如下:
int fbfd = open("/dev/fb0", O_RDWR);
struct fb_var_screeninfo vinfo;
ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);
void *fbp = mmap(0 , vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8 ,
PROT_READ | PROT_WRIT E, MAP_SHARED, fbfd, 0);
munmap(fbp, vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8);
close(fbfd);
总之,通过framebuffer设备,我们可以获得屏幕信息,映射显存,直接操作显存来刷屏,这就是不使用GUI的原生LCD屏幕操作方法。
\qquad mmap是Linux系统调用,用于映射设备(如文件)的权限到进程的地址空间。对于framebuffer设备,我们可以通过mmap来映射LCD屏幕的显存到进程的用户空间,然后就可以直接操作显存来刷新屏幕。
mmap的原型如下:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数说明:
对于framebuffer,主要步骤如下:
void *fbp = mmap(0, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
munmap(fbp, length);
mmap的优点是可以直接操作物理内存,速度快;优点是会占用内存空间,并且映射和unmap也需要时间。
所以简单来说,mmap实现的是文件(物理内存)到进程虚拟地址空间的映射,我们可以通过虚拟地址直接操作文件(物理内存)。
\qquad ioctl()系统调用用于在用户空间和驱动空间之间传递信息。对于framebuffer设备,我们可以通过ioctl来获取和修改LCD屏的相关参数。主要的ioctl命令如下:
用法:
#include
struct fb_var_screeninfo vinfo;
ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);
用法:
ioctl(fbfd, FBIOPUT_VSCREENINFO, &vinfo);
struct fb_fix_screeninfo finfo;
ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo);
int blank_mode = FB_BLANK_UNBLANK; //开屏
ioctl(fbfd, FBIOBLANK, blank_mode);
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);
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是将显卡置于图形模式下的。
在测试前,可以先在系统中查看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状态
\qquad 上图是Linux帧缓冲设备驱动结构是一个四层结构。从下到上分别为硬件(LCD控制器)、设备驱动(需要使用者开发)、fbmem层(系统已完成)、应用层(需要使用者开发应用程序)。而与驱动框架密切相关的就是中间这两层fbmem层和设备驱动层。
\qquad fb的结构由内核中的fb框架实现一部分(上图中的fbmem.c),然后再由设备驱动本身实现一部分(图中的xxxfb.c)。设备驱动本身就是一个普通的platform总线驱动 。
下面这图是另一个角度来描述分层的概念。更宏观一点,有助于参考:
\qquad fbmem.c实质也是一个驱动模块,是在linux系统启动时自动加载的。对于驱动模块,我们分为两个方面来分析,一是驱动的启动入口__init函数,启动时做了什么。二是fbmem提供了什么接口和能力,为LCD的驱动模块提供了哪些能力。
对于驱动程序首先从其入口的__init 函数分析起,这个__init 入口函数是在该驱动被加载时首先运行的。源码如下:
上图可以看出,fbmem启动时只做了几个简单的工作:
\qquad fbmem在启动初始化阶段只做了一些很简单的共性的工作。重要的是创建了主设备号29,并向用户层提供了两个操作接口。
fbmem的接口,有对上(用户层)的接口,也有对(LCD控制器)向下的接口。
提供了用户层操作的接口函数:
向上的接口有两个,一个是与用户操作相关的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]中。
fbmem.c向lcd的驱动层提供了一个重要的注册接口register_framebuffer(),该函数主要功能是将其参数info指向的帧缓冲设备信息添加到内核的帧缓冲设备列表中并进行注册。
\qquad 具体来说,register_framebuffer()函数执行以下操作:
\qquad 在Linux内核中,函数proc_create()是用于创建/proc这个内存文件系统中的文件的函数。它的声明位于
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()函数,它们提供了更丰富的功能和更好的安全性。
原型:
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 在这个特定的结构体变量中,定义了以下几个函数指针:
\qquad 在Linux内核3.14中,函数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()函数,它们提供更灵活的设备号管理方式。此外,还可以使用字符设备框架中的设备模型进行设备注册和管理。
在Linux内核3.14中,函数class_create()用于在/sys/class目录下创建和注册一个新的设备类。它的声明位于
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目录下创建与设备类同名的子目录,用于存放该类的具体设备实例。同时,该函数会创建和注册相关的属性文件,用于获取和设置设备类的属性。
对于设备驱动开发者来说,使用设备类是一种将相关设备进行组织和分类的有效方式,可以更好地管理和控制设备。
在Linux 3.14中,register_framebuffer()函数用于注册一个帧缓冲设备,并将其添加到内核的帧缓冲设备列表中。
函数原型:
int register_framebuffer(struct fb_info *info);
参数:
返回值:
功能:
\qquad register_framebuffer()函数的主要功能是将其参数info指向的帧缓冲设备信息添加到内核的帧缓冲设备列表中并进行注册。
\qquad LCD驱动是platform平台驱动。框架难度不大,麻烦的是LCD有大量的硬件配置需要设置。
\qquad LCD驱动的核心能力在lcd_probe()函数,而初始化的 lcd_init()函数则是简单调用platform_driver_register()注册平台设备。本驱动采用platform的设备树匹配模式。驱动模块在切尔西成功则去调用lcd_probe()函数,完成驱动的主要能力。
这里主要参考了这编文章来详细说明LCD 驱动,由于都是用了exynos4412内核,因此参数的设置有很多相同,同时更正了文章中的一些错误以及增加了fs4412主板上的相应适配的内容。
\qquad 由于驱动采用的是设备树匹配。因此,驱动的第一步是正确设置设备树相关的节点。
由于exynos4412的设备树节点分布在几个不同的dtsi文件中,显得比较分散,这里一一列出,阅读时,注意节点的层次。
以上的树节点需要一一对照,没有的要一一补上。这里不再细说。因为以上内容,要细说,可以单独再开一篇了。
这个结构体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一样的强制控制台输出
在头文件include/uapi/linux/fb.h 中定义
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
};
**比较重要的可变参数有: **
虚拟画面的尺寸
虚拟画面一般可设为可视画面的两倍,这种结构被称之为“双缓冲机制”,这样做的好处是可以一边显示,一边缓冲下一幅画面 。
而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 这里比较常用的是:
(详见2.6.1)
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;
}
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);
};
#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);
在FrameBuffer框架下,LCD驱动的编写也是程式化了。
\qquad 首先,LCD驱动是一个标准的platform平台总线驱动,因此其驱动的总体结构就确定下来了,而匹配模式可以采用的是多种匹配方式,本文采用的是设备树匹配。
\qquad 其次,其probe函数是最重要的初始化函数,其具本步骤如下:
这里先给个初步的概念,关键是直接对照去阅读调试后的源码,里面已按上述步骤,又做了相对较细的注释。
//{% 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控制器的寄存器操作。
#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