Linux LCD驱动程序

    LCD操作原理

在Linux系统中通过Framebuffer(简称为fb,又叫显存)驱动程序来控制LCD.
Frame是帧的意思,buffer是缓冲的意思,这意味着Framebuffer就是一块内存,里面保存着一帧图像。Framebuffer中保存着一帧图像的每一个像素颜色值,假设LCD的分辨率是1024x768,每一个像素的颜色用32位来表示,那么Framebuffer的大小就是:1024x768x32/8=3145728字节。
简单介绍LCD的操作原理:
① 驱动程序设置好LCD控制器:
根据LCD的参数设置LCD控制器的时序、信号极性;
根据LCD分辨率、BPP分配Framebuffer。
② APP使用ioctl获得LCD分辨率、BPP
③ APP通过mmap映射Framebuffer,在Framebuffer中写入数据

假设需要设置LCD中坐标(x,y)处像素的颜色,首要要找到这个像素对应的内存,然后根据它的BPP值设置颜色。假设fb_base是APP执行mmap后得到的Framebuffer地址,如下图所示:

 可以用以下公式算出(x,y)坐标处像素对应的Framebuffer地址:

(x,y)像素起始地址=fb_base+(xres*bpp/8)*y + x*bpp/8

最后一个要解决的问题就是像素的颜色怎么表示?它是用RGB三原色(红、绿、蓝)来表示的,在不同的BPP格式中,用不同的位来分别表示R、G、B,如下图所示:

对于32BPP,一般只设置其中的低24位,高8位表示透明度,一般的LCD都不支持。
对于24BPP,硬件上为了方便处理,在Framebuffer中也是用32位来表示,效果跟32BPP是一样的。
对于16BPP,常用的是RGB565;很少的场合会用到RGB555,这可以通过ioctl读取驱动程序中的RGB位偏移来确定使用哪一种格式。

上面指的是一个像素在framebuffer中的存放格式!比如一个rgb888像素 在framebuffer中是使用4字节来存放 但是其高8位是无效位。


    
问题来了:
1.Frambuffer在哪里?

可能是芯片的内存 也 可能是和lcd屏幕集成在一起 这得看这款LCD的接口模式


2.谁把Frambuffer中的数据发给LCD?
LCD控制器会周而复始的把Framebuffer中的RGB数据发送给LCD


LCD的接口模式

 1.8080接口模式

 这种接口将LCD控制器 显存 LCD屏幕 集成在为一个芯片。所以这种接口的模块 我们重点是需要阅读数据手册,因为每个厂家的芯片内部实现可能不一样。
这种接口就和我们在学习32的时候使用到OLED屏幕一样.

 2.TFT-RGB接口模式

这种接口将LCD控制器集成在ARM芯片内部,这种接口的好处就是可以使用内存来自己分配Frambuffer的大小,可以支持更高分辨率的屏幕而不用担心显存.

所以这种接口的重点就在于分配Frambuffer和初始化LCD控制器。

搞清楚上图中接口的作用!

 假设上图是一个LCD屏幕,屏幕中一个一个密密麻麻的黑点称之为像素点,每一行有若干个点,试想下有一个电子枪,电子枪位于某一个像素点的背后,然后向这个像素发射红,绿,蓝三种原色,这三种颜色不同比例的组合成任意一种颜色。电子枪在像素点的背后,一边移动一边发出各种颜色的光,电子枪从左往右移动,到右边边缘之后就跳到下一行的行首,继续从左往右移动,如此往复,一直移动到屏幕右下角的像素点,最后就跳回原点。

**​ 问题1:电子枪如何移动?**

​ 答: 有一条像素时钟信号线(DCLK),连接屏幕,每来一个像素时钟信号(DCLK),电子枪就移动一个像素。

**​ 问题2:电子枪打出的颜色该如何确定?**

​ 答:有三组红,绿,蓝信号线(RGB),连接屏幕,由这三组信号线(RGB)确定颜色

**​ 问题3:电子枪移动到LCD屏幕右边边缘时,如何得知需要跳到下一行的行首?**

​ 答:有一条水平同步信号线(HSYNC),连接屏幕,当接收到水平同步信号(HSYNC),电子枪就跳到下一行的最左边

**​ 问题4:电子枪如何得知需要跳到原点?**

​ 答:有一条垂直同步信号线(VSYNC),连接屏幕,当接收到垂直同步信号线(VSYNC),电子枪就由屏幕右下脚跳到左上角(原点)

​ 问题5:电子枪如何得知三组信号线(RGB)确定的颜色就是它是需要的呢?

​ 答:有一条RGB数据使能信号线(DE),连接屏幕,当接收到数据使能信号线(DE),电子枪就知道这时由这三组信号线(RGB)确定的颜色是有效的,可以发射到该像素点。

1.DCLK(像素时钟信号)

> 简单说就是:每来一个像素时钟信号(DCLK),就移动一个像素。所以DCLK的单位可以认为是像素!

像素时钟信号都有以下两个方面的作用:
(1)指挥RGB信号按顺序传输。像素时钟信号就像指挥员指挥队伍时发出的口令“一、二,一、二……”,数字RGB信号在像素时钟信号的作用下,按照一定的顺序,由驱动板传输到LCD面板中,使各电路按照一定的节拍协调地工作。
(2)确保数据传输的正确性。无论是驱动板电路,还是LCD面板电路,在读取数字RGB信号时,都是在像素时钟的作用与控制下进行的,各电路只有在像素时钟的下降沿(或上升沿)到来时才对数字RGB数据进行读取,以确保读取数据的正确性。图所示为像素时钟与数字RGB信号之间的对应关系示意图(1024×768液晶面板)。


 2.RGB三组线

(R[0:7] ,G[0:7],B[0:7] 也就是data线)
三组信号线组成,分别代表R(红色),G(绿色),B(蓝色),这三组信号中的每一组都会有8根信号,三组共同组成24根线来控制颜色数据。
> **LCD的RGB三组引脚上,数据是直接来自Framebuffer的吗?** 对于使用真彩色的LCD控制器,RGB引脚上的数据确实是来自Framebuffer;

> 但是对于使用调色板的LCD控制器,Framebuffer中的数据只是用来取出调色板中的颜色,调色板中的数据会被放到RGB引脚上去。

3.HSYNC(水平同步信号)

简单说就是当写完一行像素就回到行首


4.VSYNC(垂直同步信号)

简单说就是当移动到右下角的像素时就移回到左上角像素


 5.DE(data enable)

数据使能线。


fb_info

 我们需要知道对于每一个硬件设备(LCD)都需要一个 fb_info结构体来描述

它包含了这个硬件设备的基本信息以及操作函数 也就是说不同的fb_info对应不同的硬件设备

 fb_info结构体:

重点关注 var 、fix和 fbops 这三个参数。

 - var这个结构体保存:分辨率、BPP(多少位表示一个像素)、红绿蓝的位置等等信息 
 - fix这个结构体保存 :显存的起始地址、显存的长度等等信息
  - fbops这个结构体保存:这个Framebuffer对应的操作函数

重点注意的参数:

var (可变参数)中:

 fix(固定参数中):

 

从smem_len 和 xres 和 yres 就可以知道驱动程序支持的最大framebuffer数量,我们可以根据这个最大framebuffer数量在应用程序 使用ioctl来设置yres_virtual 也就是启用多少个framebuffer!!!!!

内核是如何来管理 不同的硬件设备对应的fb_info结构体的呢?

通过register_fb这个指针数组 每个成员都是一个指向fb_info结构体的指针

 我们在给每一个硬件设备编写驱动程序的时候 都会注册一个 fb_info结构体 来描述这个硬件设备的信息

使用register_farmebuffer()来注册fb_info

 - register_framebuffer ( ):

int
register_framebuffer(struct fb_info *fb_info)
{
	int ret;

	mutex_lock(&registration_lock);
	ret = do_register_framebuffer(fb_info);
	mutex_unlock(&registration_lock);

	return ret;
}
static int do_register_framebuffer(struct fb_info *fb_info)
{
	int i, ret;
	struct fb_event event;
	struct fb_videomode mode;

	if (fb_check_foreignness(fb_info))
		return -ENOSYS;

	ret = do_remove_conflicting_framebuffers(fb_info->apertures,
						 fb_info->fix.id,
						 fb_is_primary_device(fb_info));
	if (ret)
		return ret;

	if (num_registered_fb == FB_MAX)
		return -ENXIO;

	num_registered_fb++;
	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])
			break;
	fb_info->node = i;
	atomic_set(&fb_info->count, 1);
	mutex_init(&fb_info->lock);
	mutex_init(&fb_info->mm_lock);

	fb_info->dev = device_create(fb_class, fb_info->device,
				     MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
	if (IS_ERR(fb_info->dev)) {
		/* Not fatal */
		printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
		fb_info->dev = NULL;
	} else
		fb_init_device(fb_info);

	if (fb_info->pixmap.addr == NULL) {
		fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
		if (fb_info->pixmap.addr) {
			fb_info->pixmap.size = FBPIXMAPSIZE;
			fb_info->pixmap.buf_align = 1;
			fb_info->pixmap.scan_align = 1;
			fb_info->pixmap.access_align = 32;
			fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
		}
	}	
	fb_info->pixmap.offset = 0;

	if (!fb_info->pixmap.blit_x)
		fb_info->pixmap.blit_x = ~(u32)0;

	if (!fb_info->pixmap.blit_y)
		fb_info->pixmap.blit_y = ~(u32)0;

	if (!fb_info->modelist.prev || !fb_info->modelist.next)
		INIT_LIST_HEAD(&fb_info->modelist);

	if (fb_info->skip_vt_switch)
		pm_vt_switch_required(fb_info->dev, false);
	else
		pm_vt_switch_required(fb_info->dev, true);

	fb_var_to_videomode(&mode, &fb_info->var);
	fb_add_videomode(&mode, &fb_info->modelist);
	registered_fb[i] = fb_info;

	event.info = fb_info;
	if (!lockless_register_fb)
		console_lock();
	if (!lock_fb_info(fb_info)) {
		if (!lockless_register_fb)
			console_unlock();
		return -ENODEV;
	}

	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
	unlock_fb_info(fb_info);
	if (!lockless_register_fb)
		console_unlock();
	return 0;
}

在do_regitser_framebuffer()这个函数中我们可以注意到两句代码:

	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])
			break;

 以及

	registered_fb[i] = fb_info;

从这两段代码可以看出:

我们调用register_framebuffer(my_fb_info)其实就是把我们要注册的fb_info结构体放入全局数组 registered_fb :

 从代码也可以看出 它是一个个看数组成员是否已经使用 如果没有被使用就使用,而不是使用对应的数组下标为次设备号的成员,我猜这是因为 我们的硬件设备对应的次设备号在 注册的时候会按顺序注册 然后再调用register_framebuffer() 这样子一般来说就会将我们注册的fb_info 放入到数组下标为次设备号的成员中了,所以在后面会判断一些file->private_data 和 registered_fb[minor]是否相同

也就是说内核通过 registered_fb数组来管理 每个硬件设备的 fb_info结构体,每次注册fb_info结构体 都会将其放入到 registered_fb数组中去。

一般来说硬件设备的设备节点的次设备号 就对应 其fb_info在 registered_fb数组的哪一个下标成员中


LCD驱动程序基本框架

Framebuffer驱动程序框架

分为上下两层:

  • fbmem.c:承上启下
    • 实现、注册cdev结构体 和 file_operations结构体 
    • 把APP的read、write等调用向下转发到具体的硬件驱动程序xxx_fb.c,以使用具体的xxx_fb_read、xxx_fb_write函数
  • xxx_fb.c:硬件相关的驱动程序
    • 实现、注册fb_info结构体
    • 实现硬件操作

因为每个lcd屏幕硬件架构都不一样 ,fbmem.c文件不可能适用于全部的lcd屏幕 具体的lcd需要对应具体的驱动文件  也就是说fbmem.c就是起一个中转的作用 让我们应用程序访问到具体的LCD屏幕对应的 xxx_fb.c 驱动程序

面试如果问 LCD驱动程序的框架 就从这开始说 上下两层 然后展开了讲!

1.fbmem.c代码分析!

我们先来看看fbmem.c的代码(最好去看fbmem.c的源码!)

首先先看入口函数:

 

 ****我们可以看出在入口函数调用了:

register_chrdev(FB_MAJOR, "fb", &fb_fops)

也就是说 将主设备号 29 下的所有次设备号范围 内的设备节点 都使用一个cdev结构体来描述。(也就是说所有硬件设备(lcd)的主设备号都为29,和我们之前在学习输入子系统时一样所有输入设备的主设备号都为13 输入设备的设备节点都在/dev/input/下面) 这个cdev结构体对应的file_operations结构体是:

 fb_ops

所以 我们应用程序 访问主设备号29下的设备节点 也就是fbx节点 比如fb0 、fb1 通过其文件描述符

调用read、write接口 就会调用到 fb_ops 这个file_operations 中的fb_read、fb_write 。

我们以应用程序调用open、ioctl、read为例子,就会调用到fbmem.c中的fb_fops中的 fb_open、fb_read函数、fb_ioctl函数为例子:

重点看下面这部分!

首先

在do_regitser_framebuffer()这个函数中我们可以注意到两句代码:

	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])
			break;

 以及

	registered_fb[i] = fb_info;

从这两段代码可以看出:

我们调用register_framebuffer(my_fb_info)其实就是把我们要注册的fb_info结构体放入全局数组 registered_fb :

 从代码也可以看出 它是一个个看数组成员是否已经使用 如果没有被使用就使用,而不是使用对应的数组下标为次设备号的成员,我猜这是因为 我们的硬件设备对应的次设备号在 注册的时候会按顺序注册 然后再调用register_framebuffer() 这样子一般来说就会将我们注册的fb_info 放入到数组下标为次设备号的成员中了

1.fb_open:

应用程序调用open 打开设备节点 就会调用到fbmem.c中的 fb_open函数 然后返回一个文件描述符:

fb_open(struct inode *inode, struct file *file)
__acquires(&info->lock)
__releases(&info->lock)
{
	int fbidx = iminor(inode);
	struct fb_info *info;
	int res = 0;

	info = get_fb_info(fbidx);
	if (!info) {
		request_module("fb%d", fbidx);
		info = get_fb_info(fbidx);/* 获取fb_info */
		if (!info)
			return -ENODEV;
	}
	if (IS_ERR(info))
		return PTR_ERR(info);

	mutex_lock(&info->lock);
	if (!try_module_get(info->fbops->owner)) {
		res = -ENODEV;
		goto out;
	}
	file->private_data = info;
	if (info->fbops->fb_open) {
		res = info->fbops->fb_open(info,1);
		if (res)
			module_put(info->fbops->owner);
	}
#ifdef CONFIG_FB_DEFERRED_IO
	if (info->fbdefio)
		fb_deferred_io_open(info, inode, file);
#endif
out:
	mutex_unlock(&info->lock);
	if (res)
		put_fb_info(info);
	return res;
}
  • 2
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值