Linux LCD驅動架構學習(二)
Linux 帧缓冲设备驱动结构
下图所示为 Linux 帧缓冲设备驱动的主要结构,帧缓冲设备提供给用户空间的file_operations 结构体由 fbmem.c 中的 file_operations 提供,而特定帧缓冲设备 fb_info结构体的注册、注销以及其中成员的维护,尤其是 fb_ops 中成员函数的实现则由对应的 xxxfb.c 文件实现,fb_ops 中的成员函数最终会操作 LCD 控制器硬件寄存器。
帧缓冲设备驱动的模块加载与卸载函数
在帧缓冲设备驱动的模块加载函数中,应该完成如下 4 个工作。
1、申请 FBI 结构体的内存空间,初始化 FBI 结构体中固定和可变的屏幕参数,即填充 FBI 中 fb_var_screeninfo var 和 struct fb_fix_screeninfo fix 成员。
2、根据具体 LCD 屏幕的特点,完成 LCD 控制器硬件的初始化。
3、申请帧缓冲设备的显示缓冲区空间。
4、注册帧缓冲设备。
在帧缓冲设备驱动的模块卸载函数中,应该完成相反的工作,包括释放 FBI 结构体内存、关闭 LCD、释放显示缓冲区以及注销帧缓冲设备。 由于 LCD 控制器经常被集成在 SoC 上作为一个独立的硬件模块而存在(成为platform_device),因此,LCD 驱动中也经常包含平台驱动,这样,在帧缓冲设备驱动的模块加载函数中完成的工作只是注册平台驱动,而初始化 FBI 结构体中的固定和可变参数、LCD 控制器硬件的初始化、申请帧缓冲设备的显示缓冲区空间和注册帧缓冲设备的工作则移交到平台驱动的探测函数中完成。
同样地,在使用平台驱动的情况下,释放 FBI 结构体内存、关闭 LCD、释放显示缓冲区以及注销帧缓冲设备的工作也移交到平台驱动的移除函数中完成。
代码清单所示为帧缓冲设备驱动的模块加载和卸载以及平台驱动的探测和移除函数中的模板。
1 /* 平台驱动结构体 */
2 static struct platform_driver xxxfb_driver =
3 {
4 .probe = xxxfb_probe, //平台驱动探测函数
5 .remove = xxxfb_remove, //平台驱动移除函数
6 .suspend = xxxfb_suspend, .resume = xxxfb_resume, .driver =
7 {
8 .name = "xxx-lcd", //驱动名
9 .owner = THIS_MODULE,
10 }
11 };
12
13 /* 平台驱动探测函数 */
14 static int _ _init xxxfb_probe(...)
15 {
16 struct fb_info *info;
17
18 /*分配 fb_info 结构体*/
19 info = framebuffer_alloc(...);
20
21 info->screen_base = framebuffer_virtual_memory;
22 info->var = xxxfb_var; //可变参数
23 info->fix = xxxfb_fix; //固定参数
24
25 /*分配显示缓冲区*/
26 alloc_dis_buffer(...);
27
28 /*初始化 LCD 控制器*/
29 lcd_init(...);
30
31 /*检查可变参数*/
32 xxxfb_check_var(&info->var, info);
33
34 /*注册 fb_info*/
35 if (register_framebuffer(info) < 0)
36 return - EINVAL;
37
38 return 0;
39 }
40
41 /* 平台驱动移除函数 */
42 static void _ _exit xxxfb_remove(...)
43 {
44 struct fb_info *info = dev_get_drv_data(dev);
45
46 if (info)
47 {
48 unregister_framebuffer(info); //注销 fb_info
49 dealloc_dis_buffer(...); //释放显示缓冲区
50 framebuffer_release(info); //注销 fb_info
51 }
52
53 return 0;
54 }
55
56 /* 帧缓冲设备驱动模块加载与卸载函数 */
57 int __devinit xxxfb_init(void)
58 {
59 return platform_driver_register(&xxxfb_driver); //注册平台设备
60 }
61
62 static void __exit xxxfb_cleanup(void)
63 {
64 platform_driver_unregister(&xxxfb_driver); //注销平台设备
65 }
66
67 module_init(xxxfb_init);
68 module_exit(xxxfb_cleanup);
上述代码中第35行、48行成对出现的register_framebuffer() 和unregister_framebuffer()分别用于注册和注销帧缓冲设备。
帧缓冲设备显示缓冲区的申请与释放
在嵌入式系统中,一种常见的方式是直接在 RAM 空间中分配一段显示缓冲区,典型结构如图所示。
在分配显示缓冲区时一定要考虑 cache 的一致性问题,因为系统往往会通过DMA 方式搬移显示数据。合适的方式是使用 dma_alloc_writecombine()函数分配一 段 writecombining 区 域 , 对 应 的 writecombining 区域由dma_free_writecombine()函数释放,如代码清单所示。
writecombining 意味着“写合并”,它允许写入的数据被合并,并临时保存在写合并缓冲区(WCB)中,直到进行一次 burst 传输而不再需要多次 single 传输。通过dma_alloc_ writecombine()分配的显示缓冲区不会出现 cache 一致性问题,这一点类似于 dma_alloc_ coherent()。
1 static int __init xxxfb_map_video_memory(struct xxxfb_info *fbi) 2 { 3 fbi->map_size = PAGE_ALIGN(fbi->fb->fix.smem_len + PAGE_SIZE); 4 fbi->map_cpu = dma_alloc_writecombine(fbi->dev, fbi->map_size, 5 &fbi->map_dma,GFP_KERNEL); //分配内存 6 7 fbi->map_size = fbi->fb->fix.smem_len; //显示缓冲区大小 8 9 if (fbi->map_cpu) 10 { 11 memset(fbi->map_cpu, 0xf0, fbi->map_size); 12 13 fbi->screen_dma = fbi->map_dma; 14 fbi->fb->screen_base = fbi->map_cpu; 15 fbi->fb->fix.smem_start = fbi->screen_dma; //赋值 fix 的smem_start 16 } 17 18 return fbi->map_cpu ? 0 : - ENOMEM; 19 } 20 21 static inline void xxxfb_unmap_video_memory(struct s3c2410fb_info *fbi) 22 { 23 //释放显示缓冲区 24 dma_free_writecombine(fbi->dev,fbi->map_size,fbi->map_cpu, fbi->map_dma); 25 }
帧缓冲设备的参数设置
定时参数
FBI 结构体可变参数var中的left_margin、 right_margin 、 upper_margin、lower_margin、hsync_len 和 vsync_len 直接查 LCD 的数据手册就可以得到,图所示为某 LCD 数据手册中直接抓图获得的定时信息。 由图可知对该 LCD 而言,var 中各参数的较合适值分别为:left_margin = 104,right_margin = 8,upper_margin = 2,lower_margin = 2,hsync_len = 2,vsync_len
= 2。在 QVGA 模式下也
可类似得到。
像素时钟
FBI 可变参数 var 中的 pixclock 意味着像素时钟,例如,如果为 28.37516 MHz,那么画 1 个像素需要 35242 ps(皮秒): 1/(28.37516E6 Hz) = 35.242E-9 s
如果屏幕的分辨率是 640×480,显示一行需要的时间是: 640*35.242E-9 s = 22.555E-6 s
每条扫描线是 640,但是水平回扫和水平同步也需要时间,假设水平回扫和同步需要 272 个像素时钟,因此,画一条扫描线完整的时间是: (640+272)*35.242E-9 s = 32.141E-6 s
可以计算出水平扫描率大约是 31kHz: 1/(32.141E-6 s) = 31.113E3 Hz
完整的屏幕有 480 线,但是垂直回扫和垂直同步也需要时间,假设垂直回扫和垂直同步需要 49 个象素时钟,因此,画一个完整的屏幕的时间是: (480+49)*32.141E-6 s = 17.002E-3 s
可以计算出垂直扫描率大约是 59kHz: 1/(17.002E-3 s) = 58.815 Hz
这意味着屏幕数据每秒钟大约刷新 59 次。
颜色位域
FBI 可变参数 var 中的 red、green 和 blue 位域的设置直接由显示缓冲区与显示点的对应关系决定,例如,对于 RGB565 模式,查表 18.4,red 占据 5 位,偏移为 11 位;green 占据 6 位,偏移为 5 位;blue 占据 5 位,偏移为 0 位,即:
fbinfo->var.red.offset = 11;
fbinfo->var.green.offset = 5;
fbinfo->var.blue.offset = 0;
fbinfo->var.transp.offset = 0;
fbinfo->var.red.length = 5;
fbinfo->var.green.length = 6;
fbinfo->var.blue.length = 5;
固定参数
FBI 固定参数 fix 中的 smem_start 指示帧缓冲设备显示缓冲区的首地址,smem_len为帧缓冲设备显示缓冲区的大小,计算公式为:
smem_len = max_xres * max_yres * max_bpp
即:帧缓冲设备显示缓冲区的大小 = 最大的 x 解析度 * 最大的 y 解析度 * 最大的 BPP。
帧缓冲设备驱动的 fb_ops 成员函数
FBI 中的 fp_ops 是使得帧缓冲设备工作所需函数的集合,它们最终与 LCD 控制器硬件打交道。
fb_check_var()用于调整可变参数,并修正为硬件所支持的值;fb_set_par()则根据屏幕参数设置具体读写 LCD 控制器的寄存器以使得 LCD 控制器进入相应的工作状态。
对于 fb_ops 中的 fb_fillrect()、fb_copyarea()和 fb_imageblit()成员函数,通常直接使用对应的通用的 cfb_fillrect()、cfb_copyarea()和 cfb_imageblit()函数即可。cfb_fillrect()函 数 定 义 在 drivers/video/cfbfillrect.c 文 件 中 , cfb_copyarea() 定 义 在drivers/video/cfbcopyarea.c 文件中,cfb_imageblit()定义在
drivers/video/cfbimgblt.c 文件中。
fb_ops 中 的 fb_setcolreg() 成 员 函 数 实 现 伪 颜 色 表 ( 针 对FB_VISUAL_TRUECOLOR、FB_ VISUAL_DIRECTCOLOR 模式)和颜色表的填充,其模板如代码清单所示。
1 static int xxxfb_setcolreg(unsigned regno, unsigned red, unsigned green, 2 unsigned blue, unsigned transp, struct fb_info *info) 3 { 4 struct xxxfb_info *fbi = info->par; 5 unsigned int val; 6 7 switch (fbi->fb->fix.visual) 8 { 9 case FB_VISUAL_TRUECOLOR: 10 /* 真彩色,设置伪颜色表 */ 11 if (regno < 16) 12 { 13 u32 *pal = fbi->fb->pseudo_palette; 14 15 val = chan_to_field(red, &fbi->fb->var.red); 16 val |= chan_to_field(green, &fbi->fb->var.green); 17 val |= chan_to_field(blue, &fbi->fb->var.blue); 18 19 pal[regno] = val; 20 } 21 break; 22 23 case FB_VISUAL_PSEUDOCOLOR: 24 if (regno < 256) 25 { 26 /* RGB565 模式 */ 27 val = ((red >> 0) &0xf800); 28 val |= ((green >> 5) &0x07e0); 29 val |= ((blue >> 11) &0x001f); 30 31 writel(val, XXX_TFTPAL(regno)); 32 schedule_palette_update(fbi, regno, val); 33 } 34 break; 35 ... 36 } 37 38 return 0; 39 }
上述代码第 11 行对 regno < 16 的判断意味着伪颜色表只有 16 个成员,实际上,它们对应 16 种控制台颜色,logo 显示也会使用伪颜色表。
LCD 设备驱动的读写、mmap 和 ioctl 函数
虽然帧缓冲设备的 file_operations 中的成员函数,即文件操作函数已经由内核在fbmem.c 文件中实现,一般不再需要驱动工程师修改,但分析这些函数对于巩固字符设备驱动的知识以及加深对帧缓冲设备驱动的理解是大有裨益的。
代码清单所示为 LCD 设备驱动的文件操作读写函数的源代码,从代码结构及习惯而言,与本书第二篇所讲解的字符设备驱动完全一致。
1 static ssize_t fb_read(struct file *file, char __user *buf, size_t count, 2 loff_t *ppos) 3 { 4 unsigned long p = *ppos; 5 struct inode *inode = file->f_dentry->d_inode; 6 int fbidx = iminor(inode); 7 struct fb_info *info = registered_fb[fbidx]; //获得 FBI 8 u32 *buffer, *dst; 9 u32 __iomem *src; 10 int c, i, cnt = 0, err = 0; 11 unsigned long total_size; 12 13 if (!info || !info->screen_base) 14 return - ENODEV; 15 16 if (info->state != FBINFO_STATE_RUNNING) 17 return - EPERM; 18 19 if (info->fbops->fb_read) //如果 fb_ops 中定义了特定的读函数 20 return info->fbops->fb_read(file, buf, count, ppos); 21 /*获得显示缓冲区总的大小*/ 22 total_size = info->screen_size; 23 24 if (total_size == 0) 25 total_size = info->fix.smem_len; 26 27 if (p >= total_size) 28 return 0; 29 /*获得有效的读长度*/ 30 if (count >= total_size) 31 count = total_size; 32 33 if (count + p > total_size) 34 count = total_size - p; 35 /*分配用于临时存放显示缓冲区数据的 buffer*/ 36 buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count, GFP_KERNEL); 37 if (!buffer) 38 return - ENOMEM; 39 40 src = (u32 _ _iomem*)(info->screen_base + p); //获得源地址 41 42 if (info->fbops->fb_sync) 43 info->fbops->fb_sync(info); 44 45 while (count) 46 { /*读取显示缓冲区中的数据并复制到分配的 buffer*/ 47 c = (count > PAGE_SIZE) ? PAGE_SIZE : count; 48 dst = buffer; 49 for (i = c >> 2; i--;) 50 *dst++ = fb_readl(src++); 51 if (c &3) 52 { 53 u8 *dst8 = (u8*)dst; 54 u8 _ _iomem *src8 = (u8 _ _iomem*)src; 55 56 for (i = c &3; i--;) 57 *dst8++ = fb_readb(src8++); 58 59 src = (u32 _ _iomem*)src8; 60 } 61 62 if (copy_to_user(buf, buffer, c)) //复制到用户空间 63 { 64 err = - EFAULT; 65 break; 66 } 67 *ppos += c; 68 buf += c; 69 cnt += c; 70 count -= c; 71 } 72 73 kfree(buffer); 74 75 return (err) ? err : cnt; 76 } 77 78 static ssize_t fb_write(struct file *file, const char _ _user *buf, size_t count, 79 loff_t *ppos) 80 { 81 unsigned long p = *ppos; 82 struct inode *inode = file->f_dentry->d_inode; 83 int fbidx = iminor(inode); 84 struct fb_info *info = registered_fb[fbidx]; 85 u32 *buffer, *src; 86 u32 _ _iomem *dst; 87 int c, i, cnt = 0, err = 0; 88 unsigned long total_size; 89 90 if (!info || !info->screen_base) 91 return - ENODEV; 92 93 if (info->state != FBINFO_STATE_RUNNING) 94 return - EPERM; 95 96 if (info->fbops->fb_write) //如果 fb_ops 中定义了特定的写函数 97 return info->fbops->fb_write(file, buf, count, ppos); 98 /*获得显示缓冲区总的大小*/ 99 total_size = info->screen_size; 100 101 if (total_size == 0) 102 total_size = info->fix.smem_len; 103 104 if (p > total_size) 105 return 0; 106 /*获得有效的写长度*/ 107 if (count >= total_size) 108 count = total_size; 109 110 if (count + p > total_size) 111 count = total_size - p; 112 /*分配用于存放用户空间传过来的显示缓冲区数据的 buffer*/ 113 buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count, GFP_KERNEL); 114 if (!buffer) 115 return - ENOMEM; 116 117 dst = (u32 _ _iomem*)(info->screen_base + p); //要写的显示缓冲区基地址 118 119 if (info->fbops->fb_sync) 120 info->fbops->fb_sync(info); 121 122 while (count) 123 { /*读取用户空间数据并复制到显示缓冲区*/ 124 c = (count > PAGE_SIZE) ? PAGE_SIZE : count; 125 src = buffer; 126 127 if (copy_from_user(src, buf, c)) 128 { 129 err = - EFAULT; 130 break; 131 } 132 133 for (i = c >> 2; i--;) 134 fb_writel(*src++, dst++); 135 136 if (c &3) 137 { 138 u8 *src8 = (u8*)src; 139 u8 _ _iomem *dst8 = (u8 _ _iomem*)dst; 140 141 for (i = c &3; i--;) 142 fb_writeb(*src8++, dst8++); 143 144 dst = (u32 _ _iomem*)dst8; 145 } 146 147 *ppos += c; 148 buf += c; 149 cnt += c; 150 count -= c; 151 } 152 153 kfree(buffer); 154 155 return (err) ? err : cnt; 156 }
file_operations 中的 mmap()函数非常关键,它将显示缓冲区映射到用户空间,从而使得用户空间可以直接操作显示缓冲区而省去一次用户空间到内核空间的内存复制过程,提高效率,其源代码如代码清单 所示。
1 static int fb_mmap(struct file *file, struct vm_area_struct *vma) 2 { 3 int fbidx = iminor(file->f_dentry->d_inode); 4 struct fb_info *info = registered_fb[fbidx]; 5 struct fb_ops *fb = info->fbops; 6 unsigned long off; 7 8 if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) 9 return - EINVAL; 10 off = vma->vm_pgoff << PAGE_SHIFT; 11 if (!fb) 12 return - ENODEV; 13 if (fb->fb_mmap) //FBI 中实现了 mmap,则调用 FBI 的 mmap 14 { 15 int res; 16 lock_kernel(); 17 res = fb->fb_mmap(info, vma); 18 unlock_kernel(); 19 return res; 20 } 21 22 /* !sparc32... */ 23 lock_kernel(); 24 25 /* 映射帧缓冲设备的显示缓冲区 */ 26 start = info->fix.smem_start; //开始地址 27 len = PAGE_ALIGN((start &~PAGE_MASK) + info->fix.smem_len); //长度 28 if (off >= len) 29 { 30 /* 内存映射的 I/O */ 31 off -= len; 32 if (info->var.accel_flags) 33 { 34 unlock_kernel(); 35 return - EINVAL; 36 } 37 start = info->fix.mmio_start; 38 len = PAGE_ALIGN((start &~PAGE_MASK) + info->fix.mmio_len); 39 } 40 unlock_kernel(); 41 start &= PAGE_MASK; 42 if ((vma->vm_end - vma->vm_start + off) > len) 43 return - EINVAL; 44 off += start; 45 vma->vm_pgoff = off >> PAGE_SHIFT; 46 /* 这是 1 个 I/O 映射 */ 47 vma->vm_flags |= VM_I/O | VM_RESERVED; 48 vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); 49 50 if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, 51 vma->vm_end -vma->vm_start, vma->vm_page_prot)) //映射 52 return - EAGAIN; 53 54 return 0; 55 }
fb_ioctl() 函数最终实现对用户I/O控制命令的执行 , 这些命令包括FBIOGET_VSCREENINFO(获得可变的屏幕参数)、FBIOPUT_VSCREENINFO(设置可变的屏幕参数)、FBIOGET _FSCREENINFO(获得固定的屏幕参数设置,注意,固定的屏幕参数不能由用户设置)、FBIOPUTCMAP(设置颜色表)、FBIOGETCMAP(获得颜色表)等。代码清单所示为帧缓冲设备 ioctl()函数的源代码。
1 static int fb_ioctl(struct inode *inode, struct file *file, unsigned int cmd, 2 unsigned long arg) 3 { 4 int fbidx = iminor(inode); 5 struct fb_info *info = registered_fb[fbidx]; 6 struct fb_ops *fb = info->fbops; 7 struct fb_var_screeninfo var; 8 struct fb_fix_screeninfo fix; 9 struct fb_con2fbmap con2fb; 10 struct fb_cmap_user cmap; 11 struct fb_event event; 12 void _ _user *argp = (void _ _user*)arg; 13 int i; 14 15 if (!fb) 16 return - ENODEV; 17 switch (cmd) 18 { 19 case FBIOGET_VSCREENINFO:// 获得可变的屏幕参数 20 return copy_to_user(argp, &info->var, sizeof(var)) ? - EFAULT: 0; 21 case FBIOPUT_VSCREENINFO: //设置可变的屏幕参数 22 if (copy_from_user(&var, argp, sizeof(var))) 23 return - EFAULT; 24 acquire_console_sem(); 25 info->flags |= FBINFO_MISC_USEREVENT; 26 i = fb_set_var(info, &var); 27 info->flags &= ~FBINFO_MISC_USEREVENT; 28 release_console_sem(); 29 if (i) 30 return i; 31 if (copy_to_user(argp, &var, sizeof(var))) 32 return - EFAULT; 33 return 0; 34 case FBIOGET_FSCREENINFO: //获得固定的屏幕参数设置 35 return copy_to_user(argp, &info->fix, sizeof(fix)) ? - EFAULT: 0; 36 case FBIOPUTCMAP: //设置颜色表 37 if (copy_from_user(&cmap, argp, sizeof(cmap))) 38 return - EFAULT; 39 return (fb_set_user_cmap(&cmap, info)); 40 case FBIOGETCMAP: //获得颜色表 41 if (copy_from_user(&cmap, argp, sizeof(cmap))) 42 return - EFAULT; 43 return fb_cmap_to_user(&info->cmap, &cmap); 44 case FBIOPAN_DISPLAY: 45 if (copy_from_user(&var, argp, sizeof(var))) 46 return - EFAULT; 47 acquire_console_sem(); 48 i = fb_pan_display(info, &var); 49 release_console_sem(); 50 if (i) 51 return i; 52 if (copy_to_user(argp, &var, sizeof(var))) 53 return - EFAULT; 54 return 0; 55 case FBIO_CURSOR: 56 return - EINVAL; 57 case FBIOGET_CON2FBMAP: 58 if (copy_from_user(&con2fb, argp, sizeof(con2fb))) 59 return - EFAULT; 60 if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES) 61 return - EINVAL; 62 con2fb.framebuffer = - 1; 63 event.info = info; 64 event.data = &con2fb; 65 notifier_call_chain(&fb_notifier_list, FB_EVENT_GET_CONSOLE_MAP, &event); 66 return copy_to_user(argp, &con2fb, sizeof(con2fb)) ? - EFAULT: 0; 67 case FBIOPUT_CON2FBMAP: 68 if (copy_from_user(&con2fb, argp, sizeof(con2fb))) 69 return - EFAULT; 70 if (con2fb.console < 0 || con2fb.console > MAX_NR_CONSOLES) 71 return - EINVAL; 72 if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX) 73 return - EINVAL; 74 #ifdef CONFIG_KMOD 75 if (!registered_fb[con2fb.framebuffer]) 76 try_to_load(con2fb.framebuffer); 77 #endif /* CONFIG_KMOD */ 78 if (!registered_fb[con2fb.framebuffer]) 79 return - EINVAL; 80 event.info = info; 81 event.data = &con2fb; 82 return notifier_call_chain(&fb_notifier_list, 83 FB_EVENT_SET_CONSOLE_MAP, &event); 84 case FBIOBLANK: 85 acquire_console_sem(); 86 info->flags |= FBINFO_MISC_USEREVENT; 87 i = fb_blank(info, arg); 88 info->flags &= ~FBINFO_MISC_USEREVENT; 89 release_console_sem(); 90 return i; 91 default: 92 if (fb->fb_ioctl == NULL) 93 return - EINVAL; 94 return fb->fb_ioctl(info, cmd, arg); 95 } 96 }