12.LCD驱动

LCD相关知识

 

0.1硬件

板载的lcd是24数据线的,但是这里接了5+6+5,低位连地,因为2440支持16、24bpp显示

上图流程:

  1. 电子枪移到下一个像素,“VCLK”把LCD想象成里面有很多像素,像素一行一行的排下来,后面有一个电子枪把颜色喷打到像素点上,喷完一个像素点后,电子枪移动到下一个像素电上喷打颜色,它之所以知道移动到下一个像素点,是因为有一个“时钟”,每来一个 “时钟”,每来一个“时钟”,就从左向右移动一个像素,然后打出颜色。
  2. 颜色的数据来源:RGB

VDxx(vedio data视频数据)从R0-B4一共16条线上为颜色(有些线没有使用,本来是VD0~VD23上图只使用了16条),电子枪是从这里得到颜色数据后打到像素点上

  1. 移动到LCD最右边,行(水平方向)同步信号-HSYNC,当电子枪从最左边喷打颜色到最右边后,就要转到下一个行像素点上,接收到HSYNC同步信号,会从行最右边跳转到下一行的最左边,接着再从数据线上得到颜色喷打到行像素上。
  2. 垂直方向同步信号:VSYNC(或称为帧同步信号VFRAME)

当喷打到最后一行的最右边,就会跳转到最上一行的最左边。

  1. 使用显示器时会发现有4条黑框(里面没有喷打颜色)VM(VDENGPC4)

VDEN:vedio data enable当这个VDEN使能有效时,喷枪才会打出颜色,只移动而喷打颜色时就会出现黑框

  1. 喷枪从“线”上得到颜色,颜色是从显存framebuffer上得到,分配显存—>将地址告诉LCD控制器—>LCD从显存取出一个颜色发送到“线”上—>LCD控制器控制VDEN、VCLK、VFRAME等把颜色喷打到像素上

0.2LCD时序原理

从喷枪图可以看出,LCD实际范围是240*320,但是喷枪实际走过的的范围是

(240+ HSPW+1 HBPD+1+HFPD+1) * (320+VSPW+1 VBPD+1+ HFPD+1)比LCD的要大

从时序图可以得出我们需要设置以下的参数

HSPW

HBPD

HFPD

VSPW

VBPD

HFPD+

VCLK

CPU三星S3C2440A芯片手册418

0.3.framebuffer数据格式   

对于16bpp来说我们可以如下设置

如果是8bpp就是如下

0.4lcd控制器

fbmem.c <--- 底层硬件驱动提供

1.Fbmem.c 分析:

1,先看“入口函数”:

int __init fbmem_init(void)

-->register_chrdev(FB_MAJOR,"fb",&fb_fops) 注册"file_operations”结构体“fb_fops”。

1.主设备号:

2.File_operations 结构体:

因为“fbmem.c”是通用的文件,故并不能直接使用这个 file_operations 结构

中的.read 等函数。

int __init fbmem_init(void)

-->register_chrdev(FB_MAJOR,"fb",&fb_fops)

-->fb_class = class_create(THIS_MODULE, "graphics");//创建类(是额外代码自动创建设备节点。)这里 fbmem.c 没有在设备类下创建设备,只有真正有硬件设备时才有必要在这个类下去创建设备。,在“register_framebuffer()”中可以看到创建设备

“fb_info->dev = device_create(fb_class,fb_info->device,MKDEV(FB_MAJOR, i), "fb%d", i);”

假设

3.app: open

open("/dev/fb0", ...) 假设 APP 打开一个主设备号: 29, 次设备号:

0 的设备时,最终会找到 file_operations fb_fops 结构中的“.open =

fb_open,”函数(其中用到: registered_fb[iminor(inode)]数组)。

--------------------------------------------------------------

Int fb_open(struct inode *inode, struct file *file)

-->int fbidx = iminor(inode); //iminor(inode)得到这个设备节点的“次设备号”。

-->struct fb_info *info; //帧缓冲区结构体。

-->info = registered_fb[fbidx];//即 struct fb_info *info =registered_fb[fbidx];

假设“次设备号”为 0.即:

struct fb_info *info = registered_fb[iminor(inode)] =registered_fb[0]。从这个 registered_fb[]数组里得到“以次设备号为 0 为下标”的一项。

-->info->fbops->fb_open //若这个 info = registered_fd[0] 的“fbops”有“fb_open”函数时就:

-->res = info->fbops->fb_open(info,1);

简明过程:

kernel:

fb_open

int fbidx = iminor(inode);

struct fb_info *info = = registered_fb[0];

//fb 是 frame buffer 帧缓冲区

----------------------------------------------------

4.App:read()

想知道内存上有什么内容。看“file_operations fb_fops”结

构中的“.read = fb_read”

ssize_t fb_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)

-->struct fb_info *info = registered_fb[iminor(inode)];以次设备号为下

标从 registered_fd 数组中得一项赋给"info"结构。

-->info->fbops->fb_read //若这个 info 数组项中提供了“fbops”结构的

“fb_read”读函数时:就调用此读函数。

-->return info->fbops->fb_read(info, buf, count, ppos);

-->若没有提供"info"项,接着就:

total_size = info->screen_size;(屏幕大小)等。

-->buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,GFP_KERNEL);//分配一个缓冲区。

-->src = (u32 __iomem *) (info->screen_base + p);

//screen_base 是指显存的基地址。这里是读源 src 等于显存的基地址加上某个偏移值。

-->*dst++ = fb_readl(src++);

//读源(从显存基地址+P 偏移)那里读到一个数据放到目标“*dst++”里。 dst 是 buffer, buffer 是 kmalloc()上面分配的空间。

-->copy_to_user(buf, buffer, c); //把数据拷贝到用户空间。

总结:

就是说有"if (info->fbops->fb_read)"函数时就从读函数中读

“info->fbops->fb_read(info, buf, count, ppos);”;若没有则从“src = (u32 __iomem *) (info->screen_base + p);”里读(从screen_base 显存基地址加一个 P 偏移处)。从这个“src”源处读到用 kmalloc()分配的一个目标地址“dst”中(*dst++ = fb_readl(src++);)。最后“copy_to_user(buf, buffer, c)”把读到的数据拷贝到用户空间。

简明过程:

kernel:

5.小结

从上面知道“.open”和“.read”都依赖一个结构体 fb_info 结构体。这个结

构体是从数组“registered_fd[]”经“次设备号”为下标得到一项为 fd_info结构体。

“struct fb_info *info = registered_fb[fbidx]”。registered_fb 这个 fb_info 结构数组的定义:

Fbmem.c 提供的都是抽象出来的东西。最终都得依赖这个“registered_fb”数

组里的“fb_info”结构体。搜索源代码找“registered_fb”这个 fb_info 结构数组的出处。

1,“registered_fd” bf_inof 结构数组的定义:

struct fb_info *registered_fb[FB_MAX] __read_mostly;

2,“registered_fd” bf_inof 结构数组的设置:

Int register_framebuffer(struct fb_info *fb_info); //注册framebuffer。

-->registered_fb[i] = fb_info;

这样分层之后, APP 就知道读写函数里面的形参是什么含义了。 Ioctl()要传什

么参数就固定下来了。

分析“register_framebuffer()”:

Int register_framebuffer(struct fb_info *fb_info)

-->if (!registered_fb[i]) //先找出一个空项。

-->fb_info->dev = device_create(fb_class, fb_info->device,

MKDEV(FB_MAJOR, i), "fb%d", i);

在“fd_class”类下面创建设备。只有真正有硬件设备时才有必要在这个类下去创建设备。这样 mdev 或 udev 才能去自动创建设备节点。 Fbmem.c 只是抽象出来的 LCD 驱动框架程序,并不能支持具体的驱动。它需要依赖底层的某个驱 动程序给它注册一个“fb_info”结构体(由“register_framebuffer()来注册”)。搜索内核会发现有各种 LCD 驱动程序调用这个“register_framebuffer()”(如 6832fb.c、 amifb.c、 atmel_lcdfb.c、还有 2410 的如 s3fb.c、

s3c2410fb.c 等)。所以想要用"fbmem.c"这一套代码时,就要按上图的框架来写代码,要自已定义底层的硬件驱动程序(如上面内核中有 s3c2410fb.c 这个LCD 底层驱动程序。)

问 1. registered_fb 在哪里被设置?

答 1. register_framebuffer

2.分析层底硬件驱动程序

1.分析过程

如: inux/drivers/video/s3c2410fb.c(从入口函数看起)

__devinit s3c2410fb_init(void)

-->platform_driver_register(&s3c2410fb_driver);注册一个平台驱动(总线设备驱动模型时关心.probe 函数)

分配一个"fb_info"结构。接着就开始设置这个 fb_info 结构体.

-->

2.总结

总结上面的过程:抽象出驱动程序:怎么写 LCD 驱动程序?

1. 分配一个 fb_info 结构体: framebuffer_alloc

2. 设置

3. 注册: register_framebuffer

4. 硬件相关的操作要得到 LCD 的分辨率等信息: 从上往下分析(fbmem.c 开始)

Fbmem.c: 看“.ioctl”。

int __init fbmem_init(void)

-->register_chrdev(FB_MAJOR,"fb",&fb_fops) //看 fb_ops 结构

(file_operations)。

-->

struct file_operations fb_fops = {

.. .. ..

.ioctl = fb_ioctl, //看 fb_ioctrl()中获得什么内容.

.. .. ..

}

-->

//有各个 cmd

int fb_ioctl(struct inode *inode, struct file *file, unsigned intcmd,unsigned long arg)

-->以次设备号为下标,在 registered_fb[]数组中得到 fb_info 结构体变量"info"。

int fbidx = iminor(inode);

struct fb_info *info = registered_fb[fbidx];

--> 将这个 info 结构中的 var 成员拷贝回用户空间。

switch (cmd) {

case FBIOGET_VSCREENINFO: //GET 获得.V(var)可变的.SCREEN 屏幕.INFO 信息.

return copy_to_user(argp, &info->var,

sizeof(var)) ? -EFAULT : 0;

---------------------------------------------------------------------

看 info->var 这个成员中有什么内容:

分析 fbmem.c 中的 LCD 分辨率也证实了 fbmem.c 是抽象出来的内容,最终它得

依赖具体的底层设备驱动提供的“fb_info”结构体。

3.LCD 硬件操作步骤

在写驱动之前,先看硬件的具体操作:

参考以前的 LCD 裸机程序笔记。

上图流程:

  1. 电子枪移到下一个像素,“VCLK”把LCD想象成里面有很多像素,像素一行一行的排下来,后面有一个电子枪把颜色喷打到像素点上,喷完一个像素点后,电子枪移动到下一个像素电上喷打颜色,它之所以知道移动到下一个像素点,是因为有一个“时钟”,每来一个 “时钟”,每来一个“时钟”,就从左向右移动一个像素,然后打出颜色。
  2. 颜色的数据来源:RGB

VDxx(vedio data视频数据)从R0-B4一共16条线上为颜色(有些线没有使用,本来是VD0~VD23上图只使用了16条),电子枪是从这里得到颜色数据后打到像素点上

  1. 移动到LCD最右边,行(水平方向)同步信号-HSYNC,当电子枪从最左边喷打颜色到最右边后,就要转到下一个行像素点上,接收到HSYNC同步信号,会从行最右边跳转到下一行的最左边,接着再从数据线上得到颜色喷打到行像素上。
  2. 垂直方向同步信号:VSYNC(或称为帧同步信号VFRAME)

当喷打到最后一行的最右边,就会跳转到最上一行的最左边。

  1. 使用显示器时会发现有4条黑框(里面没有喷打颜色)VM(VDENGPC4)

VDEN:vedio data enable当这个VDEN使能有效时,喷枪才会打出颜色,只移动而喷打颜色时就会出现黑框

  1. 喷枪从“线”上得到颜色,颜色是从显存framebuffer上得到,分配显存—>将地址告诉LCD控制器—>LCD从显存取出一个颜色发送到“线”上—>LCD控制器控制VDEN、VCLK、VFRAME等把颜色喷打到像素上

 

 

设置 LCD 控制器时,不同的 LCD 的"VCLK"(频率)可能不会一样,所以要

调整 LCD 控制器来设置频率的快和慢,使其发出合适的时钟(若 1 秒一次,则

喷打颜色到像素点时都看的清晰太慢了。若 VCLK 为 1GHz,可能又反应不过

来。所以要调 LCD 控制器使其发出合适的时钟)。

发出使命的“时钟”要看 LCD 屏的硬件手册。

 

硬件操作过程:

1, 根据 LCD 手册设置 LCD 控制器。(时钟参数等)

2, 分配显存,并将显存的地址告诉 LCD 控制器。

还要说明颜色格式。一个像素用多少个字节来表示等。

  1. 之后就是 2440 相关的操作:配置引脚用于 LCD。芯片的管脚可以配置成输入输出,还可以配置为 LCD 引脚。

1.LCD 硬件参数设置:

1,头文件:

#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 <asm/io.h>
#include <asm/uaccess.h>
#include <asm/div64.h>
#include <asm/mach/map.h>
#include <asm/arch/regs-lcd.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/fb.h>

2,入口、出口函数以及修饰它们的框架:

1.1,先写入口函数:

static int lcd_init(void)

{

return 0;

}

1.2,出口函数;

static void lcd_exit()

{ }

1.3,修饰入口中与出口函数.

module_init(lcd_init);

module_exit(lcd_exit);

MODULE_LICENSE("GPL");

static int lcd_init(void)

{  }

2.,分配一个 fb_info 结构体.

static struct fb_info *s3c_lcd; //定义一个 fb_info 结构体变量 s3c_lcd.

 

3.,设置 fb_info 结构体。

 

1.分析

struct fb_info *framebuffer_alloc(size_t size, struct device *dev)

-->传入了一个“size”大小,本来 fb_info 结构体的大小如下:

int fb_info_size = sizeof(struct fb_info);

-->然后传入的 size 大小与原本定义的“fb_info”结构体大小相加:分配这一段 p 空间。

p = kzalloc(fb_info_size + size, GFP_KERNEL);

-->然后 par 指向额外分配的那段 size 空间。

info->par = p + fb_info_size;我们不需要这段额外空间就不设置。就把size=0.

 

s3c_lcd = framebuffer_alloc(0, NULL);//可能内存不足要判断返回值.

分析: struct fb_info *framebuffer_alloc(size_t size, struct device*dev)原型:

参 1 为大小。在定义 fb_info 结构时,结构体的大小是固定了的。内核中经常有这个取巧的方法,本来一个结构体原本分配的大小,而内核紧接着再分配了一段大小给这个结构体。定义时的结构体里面有一个指针,指向这个内核又分配的一段空间,这个空间里放“私有数据”。

2.设置

设置“fb_info”结构体:

  • 先看 fb_info 结构的具体成员: fix,var,fbops 等。

②,设置固定的参数:

fb_info 结构定义中的“struct fb_fix_screeninfo fix;”

固定参数的内容:

a,__u32 smem_len; 设置显存长度。

显存长度设置查看 LCD 手册“F:\embedded\第二期:深入驱动\源码_文档_图片_原理图_芯片手册\原理图及芯片手册\JZ2440v2\芯片手册/液晶屏.pdf”

分辨率:

颜色:

RGB 红绿蓝每个像素占 6bit,实际上在 2410 里面, RGB 只能是 R-5 位, G-6

位, B-5 位,不能是 666,一色里丢弃 1 位没关系。这是因为 2440 不支持 18

位,只支持 16 位或其他如 24 位等。但这里浪费了 2 位也没关系。

则显存的长度:分辨率*颜色位数---240*320*16(位)

b,__u32 type:看 FB_TYPE_*宏

此实例中是用“FB_TYPE_PACKED_PIXELS”。是个默认值。(一般看不懂时就使用默认值)。默认值就是可以支持大部分类的 LCD。

c,__u32 type_aux:附加的类型

若是平板要用到这个附加的类型。但此实例中不用设置。

d,__u32 visual:

查看宏“FB_VISUAL_*”

我们是 TFT 真彩色屏:

e,__u16 xpanstep;

f,__u32 line_length;

最终固定信息设置结果为:

只有一个“显存”的起始地址没有设置,在之后分配显存的时候再去设置它。

 

③,设置可变的参数:

fb_info 结构定义中的“struct fb_var_screeninfo var;”

可变信息的结构定义:

A, __u32 xres:X 方向的分辨率。

B, __u32 xres_virtual:虚拟分辨率。

买回的 LCD 分辨率固定死了,但还能在 PC 桌面上右键设置虚拟分辨率。__u32 yres_virtual:这里 x,y 方向的虚拟分辨率都设置成和实际的分辨率一样。

C, __u32 xoffset:虚拟和实际分辨率之间的偏移值(差值)。

__u32 yoffset:

上面设置了 x, y 方向上的实际和虚拟的分辨率是相同的,所以这两个项设置为

0 即可。 因为是 0 则此项可以不设置。 p = kzalloc(fb_info_size + size,GFP_KERNEL);分配空间默认也填 0

D,__u32 bits_per_pixel:每个像素用多少位。

(此项重要)。这里从 LCD 手册知道是 16 位。

。。。。。。。

发布了241 篇原创文章 · 获赞 28 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_34738528/article/details/105049485