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;} elsefb_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);elsepm_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_IOif (info->fbdefio)fb_deferred_io_open(info, inode, file);
#endif
out:mutex_unlock(&info->lock);if (res)put_fb_info(info);return res;
}
static struct fb_info *get_fb_info(unsigned int idx)
{struct fb_info *fb_info;if (idx >= FB_MAX)return ERR_PTR(-ENODEV);mutex_lock(&registration_lock);fb_info = registered_fb[idx];if (fb_info)atomic_inc(&fb_info->count);mutex_unlock(&registration_lock);return fb_info;
}

从源码中可见fb_open 的作用就是:

1.从 registered_fb[ ] 数组中取出 registered_fb[ fbidx]数组成员 赋值给info

2.如果info->fbops->fb_open存在 也就是对应的硬件设备的file_operations的open成员存在 就调用

3.上面代码有一行:    file->private_data = info;
就是把info 赋值给file结构体的私有数据 也就是file->private_data

这样我们后续使用到返回的文件描述符比如使用read write等打开的file结构体的私有数据就是这个硬件设备对应的fb_info了!不过我们通常还是直接从 registered_fb[ ] 数组中取来使用fb_info,但是会先和file结构体中的私有数据做个对比在使用,如果一样就使用不一样就返回NULL。

2.fb_read:

file_fb_info()函数 是通过registered_fb[minor ]来获取info 如果registered_fb[minor ]和file的私有数据中对的fb_info不一样 就返回NULL

 可见我们还是通过registered_fb[minor ]来获取这个具体设备的设备节点对应的 

 info 也就是这个描述具体设备的fb_info结构体,但是会和file->private_data做一下对比,如果不一致就返回NULL,这是因为在注册fb_info时可能会出现问题(在学习framebuffer_register()的时候会学习到),这个结构体中就有具体设备对应的file_operations结构体,也就是 info->fbobs (这是在fb_open函数中完成的赋值)。然后调用它! 这样就完成了中转 实现了可以访问到具体设备对应的file_operations的方法!

3.fb_ioctl:

我们应用程序调用ioctl 就会调用到fb_ioctl:

可见实际上调用到时do_fb_ioctl:

static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,unsigned long arg)
{struct fb_ops *fb;struct fb_var_screeninfo var;struct fb_fix_screeninfo fix;struct fb_con2fbmap con2fb;struct fb_cmap cmap_from;struct fb_cmap_user cmap;struct fb_event event;void __user *argp = (void __user *)arg;long ret = 0;switch (cmd) {case FBIOGET_VSCREENINFO:if (!lock_fb_info(info))return -ENODEV;var = info->var;unlock_fb_info(info);ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;break;case FBIOPUT_VSCREENINFO:if (copy_from_user(&var, argp, sizeof(var)))return -EFAULT;console_lock();if (!lock_fb_info(info)) {console_unlock();return -ENODEV;}info->flags |= FBINFO_MISC_USEREVENT;ret = fb_set_var(info, &var);info->flags &= ~FBINFO_MISC_USEREVENT;unlock_fb_info(info);console_unlock();if (!ret && copy_to_user(argp, &var, sizeof(var)))ret = -EFAULT;break;case FBIOGET_FSCREENINFO:if (!lock_fb_info(info))return -ENODEV;fix = info->fix;unlock_fb_info(info);ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;break;case FBIOPUTCMAP:if (copy_from_user(&cmap, argp, sizeof(cmap)))return -EFAULT;ret = fb_set_user_cmap(&cmap, info);break;case FBIOGETCMAP:if (copy_from_user(&cmap, argp, sizeof(cmap)))return -EFAULT;if (!lock_fb_info(info))return -ENODEV;cmap_from = info->cmap;unlock_fb_info(info);ret = fb_cmap_to_user(&cmap_from, &cmap);break;case FBIOPAN_DISPLAY:if (copy_from_user(&var, argp, sizeof(var)))return -EFAULT;console_lock();if (!lock_fb_info(info)) {console_unlock();return -ENODEV;}ret = fb_pan_display(info, &var);unlock_fb_info(info);console_unlock();if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))return -EFAULT;break;case FBIO_CURSOR:ret = -EINVAL;break;case FBIOGET_CON2FBMAP:if (copy_from_user(&con2fb, argp, sizeof(con2fb)))return -EFAULT;if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)return -EINVAL;con2fb.framebuffer = -1;event.data = &con2fb;if (!lock_fb_info(info))return -ENODEV;event.info = info;fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event);unlock_fb_info(info);ret = copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0;break;case FBIOPUT_CON2FBMAP:if (copy_from_user(&con2fb, argp, sizeof(con2fb)))return -EFAULT;if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)return -EINVAL;if (con2fb.framebuffer >= FB_MAX)return -EINVAL;if (!registered_fb[con2fb.framebuffer])request_module("fb%d", con2fb.framebuffer);if (!registered_fb[con2fb.framebuffer]) {ret = -EINVAL;break;}event.data = &con2fb;console_lock();if (!lock_fb_info(info)) {console_unlock();return -ENODEV;}event.info = info;ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event);unlock_fb_info(info);console_unlock();break;case FBIOBLANK:console_lock();if (!lock_fb_info(info)) {console_unlock();return -ENODEV;}info->flags |= FBINFO_MISC_USEREVENT;ret = fb_blank(info, arg);info->flags &= ~FBINFO_MISC_USEREVENT;unlock_fb_info(info);console_unlock();break;default:if (!lock_fb_info(info))return -ENODEV;fb = info->fbops;if (fb->fb_ioctl)ret = fb->fb_ioctl(info, cmd, arg);elseret = -ENOTTY;unlock_fb_info(info);}return ret;
}

可见应用程序调用ioctl 中的不同参数 会到时内核的do_fb_ioctl函数返回不同的结果。

比如应用程序调用 ioctl中的命令参数为 FBIOGET_VSCREENINFO 时

(var screen infomations)就会返回屏幕的可变参数,参数为FBIOGET_FSCREENINFO(fix screen informations)就会返回屏幕的固定参数;使用ioctl传入不同的参数是应用程序来获取当前硬件设备的信息的方式!这也是ioctl的常见用法!

***从上面可以看出 在内核中描述一个不同的LCD是通过 fb_info结构体:

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

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

fb_mem.c中使用到的套路是很常见的!要记住!

可以看出 这个套路和我们之前学习Input子系统时 老内核的实现是类似的 通过input.c 中的file_operations结构体来中转 访问到具体硬件对应的file_operations结构体中的函数。它们使用这个中转套路的原因是 使用了老接口 register_chrdev ()!使用了老接口 会将该主设备号下的所有次设备号范围的设备节点都使用一个cdev结构体来描述 也就是都使用同一个file_operations结构体 所以需要中转 去找到具体硬件对应的file_operations结构体来使用具体函数!

框架流程:

应用程序通过open 打开主设备为29的设备节点 ,然后通过文件描述符使用 ioctl获取到var fix等硬件参数 然后使用mmap映射显存 最后通过映射出来的地址读写Framebuffer

|

|

|

V

应用程序的read、write调用到 内核中的fbmem.c 中的file_operations结构体fb_ops 中的fb_read、fb_write

|

|

|

V

fb_read、fb_write 中通过 该设备节点对应的次设备号从registered_fb【】这个数组中 得到描述具体设备的fb_info结构体 info ,通过info->fbops 就可以得到具体设备对应的具体操作函数了!

如何给具体的设备编写Framebuffer驱动程序?

(可以参考内核中的 s3c2410fb.c 这个设备文件)

Frambuffer驱动程序分为上下两层 :

1.fbmem.c。这是已经给定的框架,提供file_operitions结构体,让应用程序调用。file_operitions结构体中的readwrite 这些函数起中转的作用 它们会将应用程序的请求下发到具体硬件的驱动程序(也就是xxxfb.c实现的和硬件相关的驱动程序)。
2.xxxfb.c。这是和硬件相关,在这个文件里 抽象出fb_info结构体 保存和硬件相关的资源和驱动程序。

首先fbmem.c已经是实现了的我们直接使用就可以了。那么我们只需要实现xxxfb.c就可以了。那么我们在xxxfb.c需要做什么?

1.分配fb_info

 - framebuffer_alloc ( )
     (framebuffer_alloc ( ) 可以分配一个fb_info结构体加我们传入的第一个参数size 大小的空间,size大小的空间用来保存私有数据 比如硬件信息等。fb_info的par成员指向这块空间!)

/*** framebuffer_alloc - creates a new frame buffer info structure** @size: size of driver private data, can be zero* @dev: pointer to the device for this fb, this can be NULL** Creates a new frame buffer info structure. Also reserves @size bytes* for driver private data (info->par). info->par (if any) will be* aligned to sizeof(long).** Returns the new structure, or NULL if an error occurred.**/
struct fb_info *framebuffer_alloc(size_t size, struct device *dev)
{
#define BYTES_PER_LONG (BITS_PER_LONG/8)
#define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG))int fb_info_size = sizeof(struct fb_info);struct fb_info *info;char *p;if (size)fb_info_size += PADDING;p = kzalloc(fb_info_size + size, GFP_KERNEL);if (!p)return NULL;info = (struct fb_info *) p;if (size)info->par = p + fb_info_size;info->device = dev;#ifdef CONFIG_FB_BACKLIGHTmutex_init(&info->bl_curve_mutex);
#endifreturn info;
#undef PADDING
#undef BYTES_PER_LONG
}

问题来了:

1.注意到 struct fb_info *framebuffer_alloc(size_t size, struct device *dev) 中的参数有一个size

这个size的作用是什么?framebuffer_alloc的返回值不是一个fb_info结构体指针吗?

从代码中可以看出 是在申请的fb_info后再连续申请了一段size大小的内存 然后返回fb_info

然后让fb_info->par 指向那段size大小的内存的首地址

但是看厂家提供的lcd驱动程序却不是这样使用的,而是:

可以看到host中包含fb_info ,厂家提供的驱动是使用kzalloc申请自己单板对应的host,然后将framebuffer_alloc(0,&pdev->dev )获得的fb_info 赋值给 host的fb_info成员,随后让fb_info->par = host

我们注意到framebuffer_alloc(0,&pdev->dev )的第一个参数是0 也就是只单独申请一个fb_info结构体,厂家提供的驱动这样子的用法就没有使用到framebuffer_alloc第一个参数size了,不过我也觉得这样子比较好,起码比较直观 不用去看framebuffer_alloc的源码

2.参数dev的作用是什么?

Linux struct device设备结构体__cuihua的博客-CSDN博客_device结构体这篇是我学习时遇见的struct device内容的集合,记录着所学时对此结构体的理解,内容不完善,会不断更新在学习Linux设备驱动时,经常遇见的是就是struct device结构体,他是保存设备基本信息的结构体。几乎在所有的驱动中都会遇见,意思就是他是驱动的设备结构体,所有的各种类型的结构体都继承他。在include/linux/device.h的723行有定义,其内容如下:struct...https://blog.csdn.net/weixin_42397613/article/details/105145905

作用是将我们申请的fb_info和 该设备对应的device结构体绑定起来

2.设置fb_info

一般是通过设备树获得LCD参数 然后设置fb_info

- var (我们只需要设置LCD的分辨率和BPP以及颜色的格式就可以了)
 - fix        (固定的屏幕信息)
 - fbops     (Framebuffer的操作函数)
 - 硬件相关操作

3.注册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;} elsefb_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);elsepm_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 放入到数组下标为次设备号的成员中了

自己编写最简单的框架例子:

参考内核中s3c2410fb.c

static struct fb_info *myfb_info;
dma_addr_t phy_addr;static struct fb_ops myfb_ops = {.owner        = THIS_MODULE,.fb_fillrect = cfb_fillrect,.fb_copyarea    = cfb_copyarea,    .fb_imageblit   = cfb_imageblit,
};/* 不自己编写open 、read、write成员就会使用到fbmem.c中写好的fb_open 、fb_read、fb_write函数 *//* 1.入口函数 */
int __init lcd_drv_init(void)
{int ret;int Framebuffer_len;/* 1.分配 fb_info */myfb_info = framebuffer_alloc(0, NULL);/* 2.设置 fb_info *//* a.var :主要设置分辨率、颜色格式*/myfb_info->var.xres = 1024; /* 分辨率1024*600 */myfb_info->var.yres = 600;myfb_info->var.bits_per_pixel = 16;/* BPP:RGB565 */if (myfb_info->var.bits_per_pixel == 24)Framebuffer_len = myfb_info->var.xres * myfb_info->var.yres * (myfb_info->var.bits_per_pixel / 8 + 1);elseFramebuffer_len = myfb_info->var.xres * myfb_info->var.yres * myfb_info->var.bits_per_pixel / 8;myfb_info->var.red.offset = 11;/* 红色位的起始位第11bit位 */myfb_info->var.red.length = 5; /* 红色位的长度 */myfb_info->var.red.msb_right = 0; /* 为0代表最重要的位在左边 */myfb_info->var.green.offset = 5;myfb_info->var.green.length = 6;myfb_info->var.green.msb_right = 0;myfb_info->var.blue.offset = 0;myfb_info->var.blue.length = 5;myfb_info->var.blue.msb_right = 0;/* b.fix */myfb_info->screen_base = dma_alloc_wc(NULL, Framebuffer_len, /* 显存的虚拟起始地址 */&phy_addr, GFP_KERNEL);//dma_alloc_wc这个函数会将物理地址放到参数phy_addr中,且返回虚拟地址myfb_info->fix.smem_start = phy_addr;/* 显存的物理起始地址(所以在fix的成员中)*/myfb_info->fix.smem_len = Framebuffer_len;/* 显存长度 */myfb_info->fix.type = FB_TYPE_PACKED_PIXELS;myfb_info->fix.visual = FB_VISUAL_TRUECOLOR;/* 真彩色 *//* c.fbops */myfb_info->fbops = &myfb_ops;/* 3.注册 fb_info */register_framebuffer(myfb_info);/* 4.硬件操作 */return 0;
}/* 2.出口函数 */
static void __exit lcd_drv_exit(void)
{/* 倒影式编程 */unregister_framebuffer(myfb_info);framebuffer_release(myfb_info);}module_init(lcd_drv_init);
module_exit(lcd_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zzf");
MODULE_DESCRIPTION("Framebuffer driver for me");

上面的代码有个疑惑:

为什么fb_info要保存显存的物理地址?
>    这是因为,应用程序要使用显存,需要使用物理地址然后通过mmap来映射。所以我们就可以在drv_mmap中将物理地址映射为虚拟地址返回给应用程序,应用程序调用mmap就可以使用到映射了的显存的虚拟地址

应用程序可以直接访问fb_info保存的显存的虚拟地址吗?

>     不可以,这是给在内核的驱动程序使用的。我们应用程序需要使用mmap映射然后调用到drv_mmap 使用保存在fb_info的显存的物理地址smem_start和显存长度smem_len映射成虚拟地址给应用程序使用!


重点了解一下HV模式 和 DE模式

LCD的DE模式和HV模式,以及DITHB抖动功能_【ql君】qlexcel的博客-CSDN博客_de模式首先RGB的信号线如下:然后看一下LCD的时序图:        LCD在显示可视数据之前,在行数据上有HFP、HBP、HSYNC,在列数据上有VFP、VBP、VSYNC,而不是所有的数据都是可以显示的数据,因此LCD的驱动和LCD之间需要采用某种方式来同步,比如让LCD知道现在我在发送的是HFP时序,你不要显示出来;或者通知LCD,现在开始后面的数据是可视数据,你可以开始显示了。...https://blog.csdn.net/qlexcel/article/details/86301007

现在常用DE模式

在DE模式下,LCD是不需要HS信号线的,也就是说LCD选择了DE模式,即使没有接HS信号线,LCD也能正常工作。


编写一个较为规范的LCD驱动程序

首先我们要知道我们使用到的厂家提供的内核中自带的我们这个LCD的驱动程序是:

mxsfb.c

参考内核文件:

  • drivers\video\of_display_timing.c

  • drivers\video\fbdev\mxsfb.c

下面的代码只使用了单framebuffer!

设备树文件:

修改板子对应的dts文件/* 根结点下添加  */framebuffer-mylcd{compatiple = "100ask, lcd_drv";reg = <0x021c8000 0x4000>;#这个reg作为LCD控制器寄存器的起始地址0x021c8000 大小为0x4000 用来给驱动程序获得pinctrl-names = "default";pincttrl-0 = <&mylcd_pinctrl>;# lcd对应的引脚设置backlight-gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>; #背光功能对应的GPIO设置clocks = <&clks IMX6UL_CLK_LCDIF_PIX>, #时钟 <&clks IMX6UL_CLK_LCDIF_APB>;clock-names = "pix", "axi";#LCD屏幕的硬件参数 在设备树中定义 在驱动程序中获取赋值给fb_infodisplay = <&displayA>;displayA: display {bits-per-pixel = <24>;#BPP:24位 既RGB888bus-width = <24>;#这两个是通用的属性display-timings {#display-timings下的timingA、timingx等等等等就不是通用的属性因为每个LCD可能都不一样native-mode = <&timingA>;#这里只实现了timingA 以后可以添加多个timing来适配多个LCD#timingA中包含了一些信息我们可以在驱动程序中使用函数获取#下面部分表示时钟频率分辨率 以及LCD的时钟参数timingA: timingA_1024x600 {clock-frequency = <50000000>;#时钟频率hactive = <1024>;           #分辨率vactive = <600>;hfront-porch = <160>;hback-porch = <140>;hsync-len = <20>;vback-porch = <20>;vfront-porch = <12>;vsync-len = <3>;#下面部分表示hsync-active = <0>;#水平方向同步信号低脉冲有效vsync-active = <0>;#垂直方向同步信号低脉冲有效de-active = <1>;   #DE模式下高电平有效pixelclk-active = <0>;#像素时钟下降沿有效 也就是在DCLK下降沿时读取RGB数据};};};            };/* 在&iomuxc节点下添加 mylcd_pinctrl这个节点 如下:fsl,pins这些信息是配置lcd的相关引脚使用了厂家提供的工具*/
&iomuxc {pinctrl-names = "default";pinctrl-0 = <&pinctrl_hog_1>;imx6ul-evk {mylcd_pinctrl: mylcd_pinctrl {                /*!< Function assigned for the core: Cortex-A7[ca7] */fsl,pins = <MX6UL_PAD_GPIO1_IO08__GPIO1_IO08           0x000010B0MX6UL_PAD_LCD_CLK__LCDIF_CLK               0x000010B0MX6UL_PAD_LCD_DATA00__LCDIF_DATA00         0x000010B0MX6UL_PAD_LCD_DATA01__LCDIF_DATA01         0x000010B0MX6UL_PAD_LCD_DATA02__LCDIF_DATA02         0x000010B0MX6UL_PAD_LCD_DATA03__LCDIF_DATA03         0x000010B0MX6UL_PAD_LCD_DATA04__LCDIF_DATA04         0x000010B0MX6UL_PAD_LCD_DATA05__LCDIF_DATA05         0x000010B0MX6UL_PAD_LCD_DATA06__LCDIF_DATA06         0x000010B0MX6UL_PAD_LCD_DATA07__LCDIF_DATA07         0x000010B0MX6UL_PAD_LCD_DATA08__LCDIF_DATA08         0x000010B0MX6UL_PAD_LCD_DATA09__LCDIF_DATA09         0x000010B0MX6UL_PAD_LCD_DATA10__LCDIF_DATA10         0x000010B0MX6UL_PAD_LCD_DATA11__LCDIF_DATA11         0x000010B0MX6UL_PAD_LCD_DATA12__LCDIF_DATA12         0x000010B0MX6UL_PAD_LCD_DATA13__LCDIF_DATA13         0x000010B0MX6UL_PAD_LCD_DATA14__LCDIF_DATA14         0x000010B0MX6UL_PAD_LCD_DATA15__LCDIF_DATA15         0x000010B0MX6UL_PAD_LCD_DATA16__LCDIF_DATA16         0x000010B0MX6UL_PAD_LCD_DATA17__LCDIF_DATA17         0x000010B0MX6UL_PAD_LCD_DATA18__LCDIF_DATA18         0x000010B0MX6UL_PAD_LCD_DATA19__LCDIF_DATA19         0x000010B0MX6UL_PAD_LCD_DATA20__LCDIF_DATA20         0x000010B0MX6UL_PAD_LCD_DATA21__LCDIF_DATA21         0x000010B0MX6UL_PAD_LCD_DATA22__LCDIF_DATA22         0x000010B0MX6UL_PAD_LCD_DATA23__LCDIF_DATA23         0x000010B0MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE         0x000010B0MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC           0x000010B0MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC           0x000010B0>;};

驱动程序lcd_drv.c:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/err.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/platform_device.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>
#include <linux/io.h>
#include <video/display_timing.h>
#include <video/of_display_timing.h>
#include <linux/gpio/consumer.h>#include <asm/div64.h>#include <asm/mach/map.h>/*imx6ull_lcdif是LCD控制器中的寄存器*这里定义的寄存器是和手册中一样顺序排列*的,这样子只要我们知道了起始的寄存器的的地址*我们就可以按顺序访问后面的寄存器了,这也是*常用的方法来设置寄存器!*/
struct imx6ull_lcdif {volatile unsigned int CTRL;                              volatile unsigned int CTRL_SET;                        volatile unsigned int CTRL_CLR;                         volatile unsigned int CTRL_TOG;                         volatile unsigned int CTRL1;                             volatile unsigned int CTRL1_SET;                         volatile unsigned int CTRL1_CLR;                       volatile unsigned int CTRL1_TOG;                       volatile unsigned int CTRL2;                            volatile unsigned int CTRL2_SET;                       volatile unsigned int CTRL2_CLR;                        volatile unsigned int CTRL2_TOG;                        volatile unsigned int TRANSFER_COUNT;   unsigned char RESERVED_0[12];volatile unsigned int CUR_BUF;                          unsigned char RESERVED_1[12];volatile unsigned int NEXT_BUF;                        unsigned char RESERVED_2[12];volatile unsigned int TIMING;                          unsigned char RESERVED_3[12];volatile unsigned int VDCTRL0;                         volatile unsigned int VDCTRL0_SET;                      volatile unsigned int VDCTRL0_CLR;                     volatile unsigned int VDCTRL0_TOG;                     volatile unsigned int VDCTRL1;                          unsigned char RESERVED_4[12];volatile unsigned int VDCTRL2;                          unsigned char RESERVED_5[12];volatile unsigned int VDCTRL3;                          unsigned char RESERVED_6[12];volatile unsigned int VDCTRL4;                           unsigned char RESERVED_7[12];volatile unsigned int DVICTRL0;    unsigned char RESERVED_8[12];volatile unsigned int DVICTRL1;                         unsigned char RESERVED_9[12];volatile unsigned int DVICTRL2;                        unsigned char RESERVED_10[12];volatile unsigned int DVICTRL3;                        unsigned char RESERVED_11[12];volatile unsigned int DVICTRL4;                          unsigned char RESERVED_12[12];volatile unsigned int CSC_COEFF0;  unsigned char RESERVED_13[12];volatile unsigned int CSC_COEFF1;                        unsigned char RESERVED_14[12];volatile unsigned int CSC_COEFF2;                        unsigned char RESERVED_15[12];volatile unsigned int CSC_COEFF3;                        unsigned char RESERVED_16[12];volatile unsigned int CSC_COEFF4;   unsigned char RESERVED_17[12];volatile unsigned int CSC_OFFSET;  unsigned char RESERVED_18[12];volatile unsigned int CSC_LIMIT;  unsigned char RESERVED_19[12];volatile unsigned int DATA;                              unsigned char RESERVED_20[12];volatile unsigned int BM_ERROR_STAT;                     unsigned char RESERVED_21[12];volatile unsigned int CRC_STAT;                        unsigned char RESERVED_22[12];volatile  unsigned int STAT;                             unsigned char RESERVED_23[76];volatile unsigned int THRES;                             unsigned char RESERVED_24[12];volatile unsigned int AS_CTRL;                           unsigned char RESERVED_25[12];volatile unsigned int AS_BUF;                            unsigned char RESERVED_26[12];volatile unsigned int AS_NEXT_BUF;                     unsigned char RESERVED_27[12];volatile unsigned int AS_CLRKEYLOW;                    unsigned char RESERVED_28[12];volatile unsigned int AS_CLRKEYHIGH;                   unsigned char RESERVED_29[12];volatile unsigned int SYNC_DELAY;
} ;struct lcd_regs {volatile unsigned int fb_base_phys;volatile unsigned int fb_xres;volatile unsigned int fb_yres;volatile unsigned int fb_bpp;
};static struct lcd_regs *mylcd_regs;static struct fb_info *myfb_info;static unsigned int pseudo_palette[16];static struct gpio_desc *bl_gpio;static struct clk *clk_pix;
static struct clk *clk_axi;static void lcd_controller_enable(struct imx6ull_lcdif *lcdif)
{lcdif->CTRL |= (1<<0);
}/*参数:*1.lcdif :imx6ull的LCD控制器中我们需要设置寄存器*2.dt:对应设备树中的timing0 也就是对应我们这款LCD的参数*3.lcd_bpp:在LCD上显示的像素的BPP*4.fb_bpp:在framebuffer中保存的像素的BPP*5.fb_phy:framebuffer的物理地址**功能:设置imx6ull的LCD控制器*/
static int lcd_controller_init(struct imx6ull_lcdif *lcdif, struct display_timing *dt, int lcd_bpp, int fb_bpp, unsigned int fb_phy)
{int lcd_data_bus_width;int fb_width;int vsync_pol = 0;int hsync_pol = 0;int de_pol = 0;int clk_pol = 0;if (dt->flags & DISPLAY_FLAGS_HSYNC_HIGH)hsync_pol = 1;if (dt->flags & DISPLAY_FLAGS_VSYNC_HIGH)vsync_pol = 1;if (dt->flags & DISPLAY_FLAGS_DE_HIGH)de_pol = 1;if (dt->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE)clk_pol = 1;if (lcd_bpp == 24)lcd_data_bus_width = 0x3;else if (lcd_bpp == 18)lcd_data_bus_width = 0x2;else if (lcd_bpp == 8)lcd_data_bus_width = 0x1;else if (lcd_bpp == 16)lcd_data_bus_width = 0x0;elsereturn -1;if (fb_bpp == 24 || fb_bpp == 32)fb_width = 0x3;else if (fb_bpp == 18)fb_width = 0x2;else if (fb_bpp == 8)fb_width = 0x1;else if (fb_bpp == 16)fb_width = 0x0;elsereturn -1;/* * 初始化LCD控制器的CTRL寄存器* [19]       :  1      : DOTCLK和DVI modes需要设置为1 * [17]       :  1      : 设置为1工作在DOTCLK模式* [15:14]    : 00      : 输入数据不交换(小端模式)默认就为0,不需设置* [13:12]    : 00      : CSC数据不交换(小端模式)默认就为0,不需设置* [11:10]    : 11       : 数据总线为24bit* [9:8]    根据显示屏资源文件bpp来设置:8位0x1 , 16位0x0 ,24位0x3* [5]        :  1      : 设置elcdif工作在主机模式* [1]        :  0      : 24位数据均是有效数据,默认就为0,不需设置*/   lcdif->CTRL = (0<<30) | (0<<29) | (0<<28) | (1<<19) | (1<<17) | (lcd_data_bus_width << 10) |\(fb_width << 8) | (1<<5);/** 设置ELCDIF的寄存器CTRL1* 根据bpp设置,bpp为24或32才设置* [19:16]  : 111  :表示ARGB传输格式模式下,传输24位无压缩数据,A通道不用传输)*/   if(fb_bpp == 24 || fb_bpp == 32){   lcdif->CTRL1 &= ~(0xf << 16); lcdif->CTRL1 |=  (0x7 << 16); }elselcdif->CTRL1 |= (0xf << 16); /** 设置ELCDIF的寄存器TRANSFER_COUNT寄存器* [31:16]  : 垂直方向上的像素个数  * [15:0]   : 水平方向上的像素个数*/lcdif->TRANSFER_COUNT  = (dt->vactive.typ << 16) | (dt->hactive.typ << 0);/** 设置ELCDIF的VDCTRL0寄存器* [29] 0 : VSYNC输出  ,默认为0,无需设置* [28] 1 : 在DOTCLK模式下,设置1硬件会产生使能ENABLE输出* [27] 0 : VSYNC低电平有效    ,根据屏幕配置文件将其设置为0* [26] 0 : HSYNC低电平有效 , 根据屏幕配置文件将其设置为0* [25] 1 : DOTCLK下降沿有效 ,根据屏幕配置文件将其设置为1* [24] 1 : ENABLE信号高电平有效,根据屏幕配置文件将其设置为1* [21] 1 : 帧同步周期单位,DOTCLK mode设置为1* [20] 1 : 帧同步脉冲宽度单位,DOTCLK mode设置为1* [17:0] :  vysnc脉冲宽度 */lcdif->VDCTRL0 = (1 << 28)|( vsync_pol << 27)\|( hsync_pol << 26)\|( clk_pol << 25)\|( de_pol << 24)\|(1 << 21)|(1 << 20)|( dt->vsync_len.typ << 0);/** 设置ELCDIF的VDCTRL1寄存器* 设置垂直方向的总周期:上黑框tvb+垂直同步脉冲tvp+垂直有效高度yres+下黑框tvf*/    lcdif->VDCTRL1 = dt->vback_porch.typ + dt->vsync_len.typ + dt->vactive.typ + dt->vfront_porch.typ;  /** 设置ELCDIF的VDCTRL2寄存器* [18:31]  : 水平同步信号脉冲宽度* [17: 0]   : 水平方向总周期* 设置水平方向的总周期:左黑框thb+水平同步脉冲thp+水平有效高度xres+右黑框thf*/ lcdif->VDCTRL2 = (dt->hsync_len.typ << 18) | (dt->hback_porch.typ + dt->hsync_len.typ + dt->hactive.typ + dt->hfront_porch.typ);/** 设置ELCDIF的VDCTRL3寄存器* [27:16] :水平方向上的等待时钟数 =thb + thp* [15:0]  : 垂直方向上的等待时钟数 = tvb + tvp*/ lcdif->VDCTRL3 = ((dt->hback_porch.typ + dt->hsync_len.typ) << 16) | (dt->vback_porch.typ + dt->vsync_len.typ);/** 设置ELCDIF的VDCTRL4寄存器* [18]     使用VSHYNC、HSYNC、DOTCLK模式此为置1* [17:0]  : 水平方向的宽度*/ lcdif->VDCTRL4 = (1<<18) | (dt->hactive.typ);/** 设置ELCDIF的CUR_BUF和NEXT_BUF寄存器* CUR_BUF  :  当前显存地址* NEXT_BUF :  下一帧显存地址* 方便运算,都设置为同一个显存地址*/ lcdif->CUR_BUF  =  fb_phy;lcdif->NEXT_BUF =  fb_phy;}/* from pxafb.c */
static inline unsigned int chan_to_field(unsigned int chan,struct fb_bitfield *bf)
{chan &= 0xffff;chan >>= 16 - bf->length;return chan << bf->offset;
}static int mylcd_setcolreg(unsigned regno,unsigned red, unsigned green, unsigned blue,unsigned transp, struct fb_info *info)
{unsigned int val;/* dprintk("setcol: regno=%d, rgb=%d,%d,%d\n",regno, red, green, blue); */switch (info->fix.visual) {case FB_VISUAL_TRUECOLOR:/* true-colour, use pseudo-palette */if (regno < 16) {u32 *pal = info->pseudo_palette;val  = chan_to_field(red,   &info->var.red);val |= chan_to_field(green, &info->var.green);val |= chan_to_field(blue,  &info->var.blue);pal[regno] = val;}break;default:return 1;   /* unknown type */}return 0;
}static struct fb_ops myfb_ops = {.owner       = THIS_MODULE,.fb_setcolreg    = mylcd_setcolreg,.fb_fillrect = cfb_fillrect,.fb_copyarea    = cfb_copyarea,.fb_imageblit   = cfb_imageblit,
};static int mylcd_probe(struct platform_device *pdev)
{struct device_node *display_np;/*它对应设备树中的framebuffer-mylcd节点下的display节点 单独创建一个device_node是因为我们要获取的是framebuffer-mylcd节点下的子节点 而在我们驱动程序中我们可以使用到的platform_device* pdev是framebuffer-mylcd节点对应的device_node转化而来 所以我们要通过pdev 来获取子节点*/                                      struct display_timings *timings = NULL;struct display_timing *dt = NULL;struct imx6ull_lcdif *lcdif;struct resource *res;dma_addr_t phy_addr;int ret;int width;int bits_per_pixel;
/***************************获得LCD参数 start******************************//* 从父节点(framebuffer-mylcd节点)中获取display这个子节点其中pdev->dev.of_node 就对应framebuffer-mylcd节点 这个函数通过父节点来获取子节点*/display_np = of_parse_phandle(pdev->dev.of_node, "display", 0);/* 获取一些通用属性(property就是通用的意思) *//* 对应设备树bus-width = <24>; */ret = of_property_read_u32(display_np, "bus-width", &width);/* 对应设备树bus-width = <24>; */ret = of_property_read_u32(display_np, "bits-per-pixel",&bits_per_pixel);/* 解析display节点下的display-timingstimings中就包含了  display-timings下的所有信息 */timings = of_get_display_timings(display_np);/* 因为设备树中的timings下本来应该有很多timing0 timing1等,我们当前使用的是timing0 */dt = timings->timings[timings->native_mode];
/***************************获得LCD参数 end******************************//* 设置控制背光的GPIO 输出1 让屏幕点亮 也就是让gpio1的8号引脚输出1*//* get gpio from dveice-tree */bl_gpio = gpiod_get(&pdev->dev, " backlight", 0);/* config gpio as output */gpiod_direction_output(bl_gpio, 1);/* set value :  gpiod_set_value(struct gpio_desc * desc, int value) *//*************************获得和初始化时钟 start********************************//* get clock from device tree */clk_pix = devm_clk_get(&pdev->dev, "pix");clk_axi = devm_clk_get(&pdev->dev, "axi");/* set clock rate  *///单位是hz 这里是50Mhz 不过应该是从设备树获得不应该是在这里写死//clk_set_rate(clk_pix, 50000000);clk_set_rate(clk_pix, dt->pixelclock.typ);/* clock enable */clk_prepare_enable(clk_pix);clk_prepare_enable(clk_axi);
/*************************获得和初始化时钟 end********************************//*************************分配注册设置fb_info start******************************//* 1.1 分配fb_info */myfb_info = framebuffer_alloc(0, NULL);/* 1.2 通过设备树获得的LCD参数设置fb_info *//* a. var : LCD分辨率、颜色格式 */myfb_info->var.xres_virtual = myfb_info->var.xres = dt->hactive.typ;myfb_info->var.yres_virtual = myfb_info->var.yres = dt->vactive.typ;myfb_info->var.bits_per_pixel = 16;  /* rgb565 */myfb_info->var.red.offset = 11;myfb_info->var.red.length = 5;myfb_info->var.green.offset = 5;myfb_info->var.green.length = 6;myfb_info->var.blue.offset = 0;myfb_info->var.blue.length = 5;/* b. fix */strcpy(myfb_info->fix.id, "100ask_lcd");/* 这里使用单Framebuffer所以 smem_len 就是一帧图像大小!!要注意!!!*/myfb_info->fix.smem_len = myfb_info->var.xres * myfb_info->var.yres * myfb_info->var.bits_per_pixel / 8;if (myfb_info->var.bits_per_pixel == 24)myfb_info->fix.smem_len = myfb_info->var.xres * myfb_info->var.yres * 4;/* fb的虚拟地址 */myfb_info->screen_base = dma_alloc_wc(NULL, myfb_info->fix.smem_len, &phy_addr,GFP_KERNEL);myfb_info->fix.smem_start = phy_addr;  /* fb的物理地址 */myfb_info->fix.type = FB_TYPE_PACKED_PIXELS;myfb_info->fix.visual = FB_VISUAL_TRUECOLOR;myfb_info->fix.line_length = myfb_info->var.xres * myfb_info->var.bits_per_pixel / 8;if (myfb_info->var.bits_per_pixel == 24)myfb_info->fix.line_length = myfb_info->var.xres * 4;/* c. fbops */myfb_info->fbops = &myfb_ops;myfb_info->pseudo_palette = pseudo_palette;/* 1.3 注册fb_info */register_framebuffer(myfb_info);/*************************分配注册设置fb_info end******************************//*******************************一些操作******************************************//* 从设备树中获得LCD控制器的起始地址并且映射为虚拟地址 */res = platform_get_resource(pdev, IORESOURCE_MEM, 0);lcdif = devm_ioremap_resource(&pdev->dev, res);/* 设置且使能LCD控制器 */lcd_controller_init(lcdif, dt, bits_per_pixel, 16, phy_addr);lcd_controller_enable(lcdif);gpiod_set_value(bl_gpio, 1); /* 让背光GPIO输出1 也就是点亮屏幕 */
/*******************************一些操作******************************************/return 0;
}static int mylcd_remove(struct platform_device *pdev)
{unregister_framebuffer(myfb_info);framebuffer_release(myfb_info);iounmap(mylcd_regs);return 0;
}static const struct of_device_id mylcd_of_match[] = {{ .compatible = "100ask, lcd_drv", },{ },
};static struct platform_driver mylcd_driver = {.driver = {.name = "mylcd",.of_match_table = mylcd_of_match,},.probe = mylcd_probe,.remove = mylcd_remove,
};static int __init lcd_drv_init(void)
{int ret;ret = platform_driver_register(&mylcd_driver);if (ret)return ret;return 0;
}static void __exit lcd_drv_exit(void)
{platform_driver_unregister(&mylcd_driver);
}module_init(lcd_drv_init);
module_exit(lcd_drv_exit);MODULE_AUTHOR("www.100ask.net");
MODULE_DESCRIPTION("Framebuffer driver for the linux");
MODULE_LICENSE("GPL");

上面的设备树代码把timing0 改成了 timingA  不然编译设备树的时候会报错。这是因为lcdif节点里面有了timing0 不改的话会报错:同名节点

注意到代码中获取LCD参数的时候:

创建了一个device_node结构体

struct device_node *display_np;

它对应设备树中的framebuffer-mylcd节点下的display节点 单独创建一个device_node是因为我们要获取的是 framebuffer-mylcd节点下的子节点 而在我们驱动程序中我们可以使用到的platform_device* pdev是framebuffer-mylcd节点对应的device_node转化而来 所以我们要通过pdev 来获取子节点

/* 从父节点(framebuffer-mylcd节点)中获取display这个子节点
        其中pdev->dev.of_node 就对应framebuffer-mylcd节点 这个函数通过父节点来
        获取子节点*/
    display_np = of_parse_phandle(pdev->dev.of_node, "display", 0);

也就是:

还有一个问题:

我们已经获取到设备树中的display_timings了,为什么还需要定义一个display_timing   dt?

先看设备树:

因为display_timings有个s意思就是保存有多个display_timing,比如timing0 timing1等 每一个timing对应一个lcd屏幕的信息 我们在这里使用到了timing0 所以需要定义一个display_timing结构体来代表timing0

再看timings的定义:

 可以看出display_timings 结构体中保存了一个指向 display_timing数组的指针(二重指针可以转化为数组指针)其中的数组成员就代表设备树中的许多timing 也就是代表许多不同的lcd屏幕的参数,我们在设备树中定义的timing0 就对应我们这款屏幕的参数,所以我们在代码中要获得设备树中定义的我们这款lcd屏幕参数 就要先获得display_timings 再获得对应我们这款屏幕的display_timing,这也就是为什么我们需要定义一个dt了!

又有一个注意点:

我们使用imx6ull_lcdif这个结构体来表示LCD控制器的寄存器,那我们怎么访问到这些寄存器呢?

可以我们定义imx6ull_lcdif来设置LCD控制器的寄存器 ,imx6ull_lcdif中每个寄存器的顺序都是按照手册来的,而且每个寄存器之间的地址差也得严格按照手册来,就比如手册中写到 CTRL寄存器和 CTRL1寄存器之间差了12个字节 ,所以他们之间差了3个int ,所以得严格按照手册来定义imx6ull_lcdif结构体,这样子我们只需要知道CTRL的起始地址就可以访问到所有的LCD控制器了!

同时 上面这个定义imx6ull_lcdif结构体来访问LCD控制器这个方法 也是我们常见的访问芯片控制器中的寄存器的方法 得熟悉!

总结:

我们之前编写框架的时候提到 编写lcd驱动程序的核心就是:

1.分配、设置、注册fb_info 2.硬件设置

从上面的代码可以看出 一个完整的LCD驱动程序 通常是这样的:

在设备树中:a.定义好lcd使用到的引脚 b.背光功能对应的GPIO c.时钟 d.中断 等

在驱动程序中 :

a.注册platform_driver

b.在platform_driver 的probe函数中 :

1).硬件相关设置:使用GPIO子系统函数从设备树设置背光功能对应的GPIO的输出或输入,使用时钟子系统的函数从设备树获取时钟 设置时钟的速率 使能时钟,从设备树中获取LCD参数,通过设备树获取到的参数设置芯片的LCD控制器的寄存器(比如设置Framebuffer地址设置 Framebuffer中数据格式设置 LCD时序参数设置 LCD引脚极性设置等)

2).分配、设置(通过设备树获取硬件信息)、注册fb_info;


上机实验

首先我们要知道我们使用到的厂家提供的内核中自带的我们这个LCD的驱动程序是:

mxsfb.c

嵌入式Linux上机实验_基于IMX6ULL_韦东山的博客-CSDN博客上机实验_基于IMX6ULL参考资料,GIT仓库里:芯片资料IMX6ULL\开发板配套资料\datasheet\Core_board\CPU\IMX6ULLRM.pdf《Chapter 34 Enhanced LCD Interface (eLCDIF)》IMX6ULL的LCD裸机程序IMX6ULL\source\03_LCD\05_参考的裸机源码\03_font_test内核自带的IMX6ULL LCD驱动程序驱动源码:Linux-4.9.88\drivers\https://blog.csdn.net/thisway_diy/article/details/119905729 看上面的博客!

注意我们把内核自带的lcd驱动程序在makefile注释了之后

想恢复原样就取消注释 重新编译 dtb文件和内核镜像放到 开发板的/boot/ 目录下!

把&lcdif 下的status改成disable  也就是禁止这个节点

为什么要这样做?

看韦东山老师的视频说是:因为我们要使用自己编写的节点那我们就需要把同功能的禁止。

但是:

我们前面修改了home/imx6ull/100ask_imx6ull-sdk/Linux-4.9.88/drivers/video/fbdev 的Makefile

注释掉:#obj-$(CONFIG_FB_MXS)             += mxsfb.o 这行了

在mxsfb.c也就是我们内核自带的适配自带LCD驱动程序中有:

也就是在驱动程序中获得了这个设备树节点来获取LCD参数我们看lcdif这个设备树节点下的LCD参数和我们写的其实是一样的。

但是我们都不编译mxsfb.c了 为什么还要把lcdif的status改成disabled? 不懂

建议实验完就恢复原样 使用原来的驱动程序mxsfb.c 因为原来自带的mxsfb.c这个驱动程序考虑的比较完善啥的。


单Framebuffer和多 Framebuffer

嵌入式Linux系统驱动之单Buffer的缺点与改进方法_韦东山的博客-CSDN博客单Buffer的缺点与改进方法参考资料,GIT仓库里:内核自带的LCD驱动程序IMX6ULL驱动源码:Linux-4.9.88\drivers\video\fbdev\mxsfb.cSTM32MP157的驱动程序是基于GPU的,在Linux-5.4里没有mxsfb.c,可以参考另一个:Linux-5.4\drivers\video\fbdev\goldfishfb.c在视频里基于IMX6ULL的mxsfb.c来讲解,我们把这个驱动程序也放到GIT仓库里IMX6ULL\sourcehttps://blog.csdn.net/thisway_diy/article/details/119905765

注意:驱动程序默认是不开启多framebuffer的 需要我们应用程序去设置!

我们知道我们可以往framebuffer中写入数据 然后LCD控制器会把framebuffer中的数据显示在LCD上 如果只使用一个framebuffer就会出现问题:

使用单Framebuffer的缺点

  • 如果APP速度很慢,可以看到它在LCD上缓慢绘制图案
  • 即使APP速度很高,LCD控制器不断从Framebuffer中读取数据来显示,而APP不断把数据写入Framebuffer
    • 假设APP想把LCD显示为整屏幕的蓝色、红色
    • 很大几率出现这种情况:
      • LCD控制器读取Framebuffer数据,读到一半时,在LCD上显示了半屏幕的蓝色
      • 这是APP非常高效地把整个Framebuffer的数据都改为了红色
      • LCD控制器继续读取数据,于是LCD上就会显示半屏幕蓝色、半屏幕红色
      • 人眼就会感觉到屏幕闪烁、撕裂

使用多Framebuffer来改进

上述两个缺点的根源是一致的:Framebuffer中的数据还没准备好整帧数据,就被LCD控制器使用了。
使用双buffer甚至多buffer可以解决这个问题:

  • 假设有2个Framebuffer:FB0、FB1
  • LCD控制器正在读取FB0
  • APP写FB1
  • 写好FB1后,让LCD控制器切换到FB1
  • APP写FB0
  • 写好FB0后,让LCD控制器切换到FB0

那么我们怎么分配多个FB呢,去参考内核自带的厂家提供的LCD驱动程序 mxsfb.c:

可见imx6ull的LCD驱动程序是直接分配一大块32M的内存 那问题来了 不是说要分配多个FB吗?它这直接分配一个32M的内存是什么意思?

我们再来看看 fb_info中的可变参数:

我们之前使用了fb_info的可变参数var中的 xres 和 yres 知道这是分辨率也就是一帧图像的大小

那么 xres_virtual(虚拟分辨率) 和 yres_virtual 是什么?

从图中可知对于一个大内存 我们只要知道一帧图像的 xres 和 yres 以及 xres_virtual 和 yres_virtual 就知道这个大内存是有多少个FB了!

也就是说对于直接分配的大内存也就是我们mxsfb.c中分配的32M内存这种方式分配显存 :

xres_virtual == xres

yres_virtual == yres*FB个数

注意:驱动程序默认是不开启多framebuffer的 需要我们应用程序去设置!

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

驱动程序不允许一开始就设置 yres_virtual == yres*FB个数 一开始应该是

yres_virtual == yres

需要我们应用程序设置:

var.yres_virtual = nBuffers * var.yres;
ioctl(fd_fb, FBIOPUT_VSCREENINFO, &var);

详情看韦老师的博客!

还有个问题:xoffset 、yoffset 是什么?

如果把第一个FB的第一个像素当原点 当前描绘的像素的坐标就是 (xoffset,yoffset)。

驱动程序通常是通过xoffset 、yoffset来切换FB !

通过上面的mxsfb.c的程序分析可以知道对于一个驱动程序通常会设置多个framebuffer 在fb_info的固定参数fix 中保存的 smem_len 是指多个framebuffer总和的大小!!!

从韦老师的博客中也可以看出 应用程序读写framebuffer的数据 是通过ioctl 传入不同的命令参数 然后读写映射后的地址 完成的 (ioctl 会调用到fbmem.c中的 fb_ioctl 这些命令参数可以去看fbmem.c的fb_ioctl 的源码)这也是 ioctl的最常用的用法 !!要明白!

编写使用多framebuffer的应用程序

(驱动程序直接基于内核自带的mxsfb.c)

该程序的功能:循环显示整屏幕的红、绿、蓝、黑、白。(当然也可以输出字符啥的 不过就需要用到字库了 然后再改一下功能函数就行了)

通过这个应用程序也可以知道读写framebuffer的应用程序该怎么写,在编写这个应用程序的时候应该去看fbmem.c中的fb_ioctl函数源码 这样才知道要使用到传入的命令参数,并且下面的应用程序的代码是将RGB数据写入framebuffer 其他就不需要我们管了,驱动和LCD控制器会将framebuffer中的数据显示到LCD。

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <time.h>static int fd_fb;
/* 用来获得内核中fb_info的可变参数var 和 固定参数fix*/
static struct fb_var_screeninfo var;    /* Current var */
static struct fb_fix_screeninfo fix;    /* Current fix *//*  */
static int screen_size;
static unsigned char *fb_base;
static unsigned int line_width;
static unsigned int pixel_width;/* USAGE:./multi_framebuffer_test singleFB */
void lcd_put_pixel(void *fb_base, int x, int y, unsigned int color);
void lcd_draw_screen(void *fb_base, unsigned int color);int main(int argc, char **argv)
{int i;int nFramebufferNum;int nBuffer = 1;/* 代表当前使用到的是第几个framebuffer */char *pNextBuffer;struct timespec time;time.tv_sec = 0;time.tv_nsec = 100000000;/* 0x00RRGGBB 这里是RGB888*/;unsigned int colors[] = {0x00ff0000, 0x0000ff00, 0x000000ff, 0, 0x00ffffff};/* 红             绿           蓝         黑       白  */if (argc != 2){printf("Usage: %s <singleFB/doubleFB>\n", argv[0]);return -1;}fd_fb = open("/dev/fb0", O_RDWR);if (fd_fb < 0){printf("can't open /dev/fb0\n");return -1;}/*FBIOGET_VSCREENINFO:fb_info get var screen infomation从内核驱动程序中获取fb_info的可变参数var */if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var)){printf("can't get var\n");return -1;}/* 获得固定参数fix */if (ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix)){printf("can't get fix\n");return -1;}/* framebuffer一个像素的字节数 */if (var.bits_per_pixel == 16)pixel_width = 2;elsepixel_width = 4;/* 一帧图像一行的字节数 */line_width = var.xres * pixel_width;/* 获得一帧图像的大小 */if (var.bits_per_pixel != 16)screen_size = var.xres * var.yres * 4;elsescreen_size = var.xres * var.yres * 2;/* framebuffer的数量 */nFramebufferNum = fix.smem_len / screen_size;printf("FramebufferNum = %d\n", nFramebufferNum);/* 映射所有的framebuffer *//* 所以使用fix.smem_len */fb_base = mmap(fix.smem_start, fix.smem_len, PROT_READ|PROT_WRITE, MAP_SHARED, fd_fb, 0);if (!fb_base){printf("mmap fb_base error!\n");return -2;}/* 单framebuffer情况 */if ((!strcmp("singleFB", argv[1])) || (fb_base == 1)){while (1){for (i = 0; i < sizeof(colors) / sizeof(colors[0]); i++ ){lcd_draw_screen(fb_base, colors[i]);nanosleep(&time, NULL);}}}/* 双framebuffer情况 */else if (!strcmp("doubleFB", argv[1])){/* 1.给内核的fb_info的var.yres_virtual赋值 也就是启用多少framebuffer *//* 使用更多framebuffer就应该是 var.yres_virtual = var.yres * nFramebufferNum;*/var.yres_virtual = var.yres * 2;ioctl(fd_fb, FBIOPUT_VSCREENINFO, &var);/* 2.设置写第几个framebuffer      ,然后将RGB数据写入Framebuffer */while (1){/* 下面的循环就是把 红绿蓝         黑白 的RGB数据循环写入framebuffer中 */for (i = 0; i < sizeof(colors) / sizeof(colors[0]); i++ ){/* pNextBuffer指向第nBuffer个framebuffer的起始地址 */pNextBuffer = fb_base + nBuffer * screen_size  ;/* 将RGB数据写入第nBuffer个framebuffer */lcd_draw_screen(pNextBuffer, colors[i]);/* 移动yoffset 让驱动程序知道此时该切换到下一个framebuffer*//* FBIOPAN_DISPLAY在fbmem.c的fb_ioctl源码中对应的功能就是修改yoffset切换framebuffer */var.yoffset = nBuffer * var.yres;ioctl(fd_fb, FBIOPAN_DISPLAY, &var);/* 如果是使用多Framebuffer就用这个代码 使用完最后一个Framebuffer就回到第一个Framebuffer来使用nBuffer++;if (nBuffer >= nFramebufferNum)nBuffer = 0;*//* 我们只使用双framebuffer 就直接使用下面的代码了 */nBuffer = !nBuffer;nanosleep(&time, NULL);}}}   munmap(fb_base, fix.smem_len);close(fd_fb);return 0;
}/*********************************************************************** 函数名称: lcd_put_pixel* 功能描述: 在LCD指定位置上输出指定颜色(描点函数)* 输入参数: x坐标,y坐标,颜色* 输出参数: 无* 返 回 值: 会***********************************************************************/ void lcd_put_pixel(void *fb_base, int x, int y, unsigned int color)
{/* 这里pen8 pen16 pen32分别对应BPP为8 16 32 */unsigned char *pen_8;unsigned short *pen_16;    unsigned int *pen_32;   unsigned int red, green, blue;  pen_8 = fb_base+y*line_width+x*pixel_width; pen_16 = (unsigned short *)pen_8;pen_32 = (unsigned int *)pen_8;switch (var.bits_per_pixel)/* 根据BPP来将RGB数据写入framebuffer */{case 8:{*pen_8 = color;break;}case 16:{/* 565 */red   = (color >> 16) & 0xff;green = (color >> 8) & 0xff;blue  = (color >> 0) & 0xff;color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);*pen_16 = color;break;}case 32:{*pen_32 = color;break;}default:{printf("can't surport %dbpp\n", var.bits_per_pixel);break;}}
}/* 注意这个函数是将RGB数据写入Framebuffer 然后LCD控制器会自动把Framebuffer中的RGB数据放在LCD屏幕上 */
void lcd_draw_screen(void *fb_base, unsigned int color)
{int x,y;/* 下面的代码是一列一列的描点 把x、y反过来就是一行一行的描点 */for (x = 0; x < var.xres; x++){for (y = 0; y < var.yres; y++){ lcd_put_pixel(fb_base, x, y, color);}}
}

从上面的代码我们可以看出,应用程序获取这款LCD在内核中对应的fb_info的var 和 fix 以及显存起始地址啊 大小啊 设置一些参数 都是通过ioctl来完成的!由此可见编写使用到硬件参数的应用程序 都离不开ioctl 所以要注意!


我们如果使用厂家提供的framebuffer驱动程序要注意 比如mxsfb.c中

可见不管我们注册的fb_info中设置了BPP是多少 最后通过驱动程序都变为了32获16,也就是说在Framebuffer中使用32位或者16位表示一个像素

我们上面学习的框架都是针对framebuffer的 fb_info也是针对framebuffer的结构体 LCD我们不需要管

Linux LCD驱动程序相关推荐

  1. Linux内核 LCD 驱动程序框架

    Linux 内核 LCD 驱动程序框架 1. framebuffer 简介 1.1 什么是 framebuffer 1.2 framebuffer的作用 2. framebuffer 驱动的框架 3. ...

  2. Linux系统驱动之分析内核自带的LCD驱动程序_基于IMX6ULL

    百问网技术交流群,百万嵌入式工程师聚集地: https://www.100ask.net/page/2248041 资料下载 coding无法使用浏览器打开,必须用git工具下载: git clone ...

  3. linux lcd驱动调试 echo dev/fb0,LCD驱动程序 - osc_msmij2gf的个人空间 - OSCHINA - 中文开源技术交流社区...

    LCD驱动程序 假设 app: open("/dev/fb0", ...) 主设备号: 29, 次设备号: 0 kernel: fb_open int fbidx = iminor ...

  4. Linux LCD 驱动实验

    目录 Linux 下LCD 驱动简析 1 Framebuffer 设备 LCD 驱动简析 硬件原理图分析 LCD 驱动程序编写 运行测试 LCD 屏幕基本测试 设置LCD 作为终端控制台 LCD 背光 ...

  5. LCD笔记(7)LCD驱动程序框架_配置时钟

    1. 硬件相关的操作 LCD驱动程序的核心就是: 分配fb_info 设置fb_info 注册fb_info 硬件相关的设置 硬件相关的设置又可以分为3部分: 引脚设置 时钟设置 LCD控制器设置 本 ...

  6. arm裸机与嵌入式linux驱动开发,如何编写基于ARM的裸机程序和基于Linux的驱动程序?...

    在嵌入式开发中,ADC应用比较频繁,本文主要讲解ADC的基本原理以及如何编写基于ARM的裸机程序和基于Linux的驱动程序. ARM架构:Cortex-A9Linux内核:3.14 在讲述ADC之前, ...

  7. Linux LCD 驱动

    裸机 LCD 驱动编写流程如下: ①.初始化 I.MX6U 的 eLCDIF 控制器,重点是 LCD 屏幕宽(width).高(height). hspw. hbp. hfp. vspw. vbp 和 ...

  8. 七 linux LCD驱动代分析

    LCD驱动分析 原文地址: http://blog.csdn.net/woshidahuaidan2011/article/details/52054795 1.对LCD驱动添加设备信息 对lcd驱动 ...

  9. Linux LCD屏幕驱动调参实操

    Linux LCD屏幕驱动调参实操 初探 Linux下的 LCD 驱动文中提到过, IMX6ULL的 eLCDIF接口驱动程序已经有半导体厂家NXP编写好了,并且不同分辨率LCD屏的eLCDIF接口驱 ...

最新文章

  1. 【每日一算法】单词接龙
  2. 娃哈哈信息部李钒助阵FBS2017 共探食品饮料信息化之路
  3. 机器学习的入门平台天花板,还可免费实操经典教程,确实也没谁了
  4. pytorch view(): argument 'size' (position 1) must be tuple of ints, not Tensor
  5. 笔记-高项案例题-2009年上-需求管理
  6. popen函数_PHP中16个高危函数
  7. python模仿百度云桌面_利用百度云接口实现车牌识别!人称Python调包侠!
  8. linux mysql 磁盘空间_磁盘空间满了之后MySQL会怎样
  9. flutter ios打包_使用 Travis CI 为 Flutter 项目打包 Android/iOS 应用
  10. 前排强势围观|云端落地AI,如此超级干货有哪些?
  11. php ajax 增删改查 分页,Jquery之Ajax_分页及增删改查
  12. 十 suprocess模块
  13. MATLAB中空间滤波卷积有什么作用
  14. AD7124-4/8芯片的模拟通道、基准、顺从电压、测三线制RTD电路等注意事项
  15. 一款号称最适合程序员的编程字体(JetBrains Mono)专为开发人员设计。
  16. 黑客使用含病毒的邮件 半年内盗取近20亿卢布
  17. Android View详解(三) 视图状态及重绘流程分析
  18. 计算机毕业设计 SSM+Vue前台点菜订餐平台系统 餐饮点餐订单管理系统 外卖点餐团购平台管理系统Java Vue MySQL数据库 远程调试 代码讲解
  19. web开发常见问题和解决方法
  20. 10项最佳在线免费学习工具

热门文章

  1. 运营初创业公司的几点建议
  2. 立创开源丨TDA1521/TDA2616_双声道HIFi功率放大器
  3. 如何平衡机器学习中偏差与方差
  4. Drugbank的医药数据XML解析
  5. deepin更新失败_Deepin深度应用商店和系统更新不正常的解决方法
  6. PHP自学笔记 ---李炎恢老师PHP第一季 TestGuest0.9/1.0 提交数据
  7. USB总线简介(一)
  8. Weblogic的t3协议
  9. 土地主大威德之模板方法模式
  10. 图片模糊到清晰的展示方式