Linux驱动:LCD驱动框架分析
来源:互联网 发布:华为nova2下载软件 编辑:程序博客网 时间:2024/06/09 23:02
一直想花时间来整理一下Linux内核LCD驱动,却一直都忙着做其他事情去了,这些天特意抽出时间来整理之前落下的笔记,故事就这样开始了。LCD驱动也是字符设备驱动的一种,框架上相对于字符设备驱动稍微复杂一点点,真的就是一点点,难点在对LCD硬件的配置上。
开发平台:TQ210,S5PV210处理器
内核版本:linux-3.10.46
LCD型号:AT070TN92,7英寸,TFT屏,分辨率800x480x3(RGB),24位真彩色
一、框架分析
上图说明:①内核装载LCD驱动模块:设置并注册fb_info结构,初始化LCD硬件。②APP打开LCD设备,获取设备文件,根据设备文件进行读写显存。③在内核中,根据主设备号和次设备号定位一个fb_info结构,如果应用层的系统调用是读操作则调用fb_ops中对应的操作函数,写操作也是一样。
主要数据结构分析:
1 struct fb_info { 2 int node; //用作次设备号索引 3 int flags; 4 struct mutex lock; //用于open/release/ioctl函数的锁 5 struct fb_var_screeninfo var; //可变参数,重点 6 struct fb_fix_screeninfo fix; //固定参数,重点 7 struct fb_monspecs monspecs; //显示器标准 8 struct work_struct queue; //帧缓冲区队列 9 struct fb_pixmap pixmap; //图像硬件映射10 struct fb_pixmap sprite; //光标硬件映射11 struct fb_cmap cmap; //当前颜色表12 struct list_head modelist; //模式链表13 struct fb_videomode *mode; //当前video模式14 15 char __iomem *screen_base; //显存基地址16 unsigned long screen_size; //显存大小17 void *pseudo_palette; //伪16色颜色表18 #define FBINFO_STATE_RUNNING 019 #define FBINFO_STATE_SUSPENDED 120 u32 state; //硬件状态,如挂起21 void *fbcon_par; //用作私有数据区22 void *par; //info->par指向了额外多申请内存空间的首地址23 };
1 struct fb_ops { 2 struct module *owner; 3 int (*fb_open)(struct fb_info *info, int user); 4 int (*fb_release)(struct fb_info *info, int user); 5 ssize_t (*fb_read)(struct fb_info *info, char __user *buf,size_t count, loff_t *ppos); 6 ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,size_t count, loff_t *ppos); 7 8 /* 检测可变参数,并调整到支持的值 */ 9 int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);10 11 /* 根据 info->var 设置 video 模式 */12 int (*fb_set_par)(struct fb_info *info);13 14 /* set color register */15 int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,unsigned blue, unsigned transp, struct fb_info *info);16 /* set color registers in batch */17 int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);18 /* blank display */19 int (*fb_blank)(int blank, struct fb_info *info);20 /* pan display */21 int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);22 23 /* Draws a rectangle */24 void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);25 /* Copy data from area to another */26 void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);27 /* Draws a image to the display */28 void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);29 30 ......31 32 /* perform fb specific ioctl (optional) */33 int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,34 unsigned long arg);35 /* perform fb specific mmap */36 int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);37 38 ......39 };
主要操作代码分析:
1 fb_open 2 { 3 int fbidx = iminor(inode); //获取次设备号 4 struct fb_info *info; 5 info = get_fb_info(fbidx); 6 struct fb_info *fb_info; 7 fb_info = registered_fb[fbidx];//根据次设备号从已注册的fb_info数组中获取响应的结构 8 return fb_info; 9 ......10 /* 11 * 从registered_fb[]数组项里找到fb_info结构体后,将其保存到 12 * struct file结构中的私有信息成员,难道这是为了以后在某些情况方便找到并调用??先放着...13 * 回过来发现:这样做是为了验证在read、write、ioctl等系统调用中获得的fb_info结构和open获得的是否一样14 */ 15 file->private_data = info;16 //info->fbops->fb_open无定义,这是值得思考的问题!17 if (info->fbops->fb_open) {18 res = info->fbops->fb_open(info,1);19 if (res)20 module_put(info->fbops->owner);21 }22 ......23 }
1 fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) 2 { 3 struct fb_info *info = file_fb_info(file); 4 struct inode *inode = file_inode(file); 5 int fbidx = iminor(inode); 6 //也是根据次设备号来获取fb_info结构 7 struct fb_info *info = registered_fb[fbidx]; 8 9 if (info != file->private_data)10 info = NULL;11 return info;12 //无定义13 if (info->fbops->fb_read)14 return info->fbops->fb_read(info, buf, count, ppos);15 //获得显存的大小16 total_size = info->screen_size;17 //如果应用层要读的数据count比实际最大的显存还要大,修改count值为最大显存值18 if (count >= total_size)19 count = total_size;20 //分配显存,最大只能是一页PAGE_SIZE=4KB21 buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,GFP_KERNEL);22 //要读的源地址:显存虚拟基地址+偏移23 src = (u8 __iomem *) (info->screen_base + p);24 while (count) {25 c = (count > PAGE_SIZE) ? PAGE_SIZE : count;26 //读的目的地址27 dst = buffer;28 //读操作:拷贝数据29 fb_memcpy_fromfb(dst, src, c);30 dst += c;31 src += c;32 33 if (copy_to_user(buf, buffer, c)) {34 err = -EFAULT;35 break;36 }37 *ppos += c;38 buf += c;39 cnt += c;40 count -= c;41 }42 kfree(buffer); //释放buffer,只起到临时中转站的作用43 }
1 /* 2 * 函数功能:将内核空间分配的物理显存空间映射到用户空间中 3 * 用户空间就能访问这段内存空间了 4 */ 5 static int fb_mmap(struct file *file, struct vm_area_struct * vma) 6 { 7 struct fb_info *info = file_fb_info(file); 8 struct fb_ops *fb; 9 unsigned long mmio_pgoff;10 unsigned long start;11 u32 len;12 13 if (!info)14 return -ENODEV;15 fb = info->fbops;16 if (!fb)17 return -ENODEV;18 mutex_lock(&info->mm_lock);19 //如果fb_info->fbops->fb_mmap存在就调用该函数,实际中没有!20 if (fb->fb_mmap) {21 int res;22 res = fb->fb_mmap(info, vma);23 mutex_unlock(&info->mm_lock);24 return res;25 }26 /*27 * fb缓冲内存的开始位置(物理地址)28 * info->fix.smem_start这个地址是在哪里被设置的?29 * 在驱动程序xxx_lcd_init()函数中:30 * clb_fbinfo->screen_base = dma_alloc_writecombine(NULL,clb_fbinfo->fix.smem_len, (u32*)&(clb_fbinfo->fix.smem_start), GFP_KERNEL);31 * dma_alloc_writecombine函数返回的是内核虚拟起始地址,同时第3个参数fix.smem_start会被设置成对应的物理起始地址。32 * 内核中操作这个分配的空间只能操作虚拟的地址空间!!!33 * dma_alloc_writecombine函数的调用只是把物理显存映射到内核空间,并没有映射到用户空间,因此用户在操作物理显存之前要先把34 * 物理显存空间映射到用户可见的用户空间中来,这就是该函数的意义所在。35 */36 start = info->fix.smem_start; 37 //帧缓冲长度38 len = info->fix.smem_len;39 mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;40 if (vma->vm_pgoff >= mmio_pgoff) {41 if (info->var.accel_flags) {42 mutex_unlock(&info->mm_lock);43 return -EINVAL;44 }45 46 vma->vm_pgoff -= mmio_pgoff;47 start = info->fix.mmio_start;48 len = info->fix.mmio_len;49 }50 mutex_unlock(&info->mm_lock);51 52 vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);53 fb_pgprotect(file, vma, start);54 //映射物理内存到用户空间虚拟地址55 return vm_iomap_memory(vma, start, len);56 }
问题思考:
问1.什么叫帧缓冲区,他有哪些特性指标?
答1.对于应用层来说,显示图像到LCD设备就相当于往“一块内存”中写入数据,获取LCD设备上的图像就相当于拷贝“这块内存”中的数据。因此,LCD就和“一块内存”一样,专业一点术语叫帧缓冲区,它和普通的内存不太一样,除了可以“读写”操作之外还可以进行其他操作和功能设置,特性指标就是LCD的特性指标。在内核中,一个LCD显示器就相当于一个帧缓冲设备,对应一个fb_info结构。
问2.为什么要通过 registered_fb[] 数组来找到对应的 fb_info 结构体?
答2.通过对上边这几个函数的剖析发现,不管是fb_read、fb_write、fb_ioctl、fb_mmap系统调用,都是通过次设备号在已注册的fb_info结构数组中找到匹配的那一个结构之后,判断其中的fbops结构中的操作函数是否有定义,有的话就优先调用该函数,没有就使用往下的方案策略。这样的好处就是多个相同的LCD设备可以使用同一套代码,减少代码的重复性,同时对于需要特殊定义的函数又可以方便实现重定义。
问3.这个数组在哪里被注册?
答3.在register_framebuffer()函数中被注册 register_framebuffer(struct fb_info *fb_info) ret = do_register_framebuffer(fb_info); ...... registered_fb[i] = fb_info; ......
问4.fb_mmap()函数在什么场合使用?
答4.在用户空间中通过mmap()函数来进行系统调用,该函数执行成功返回的是指向被映射的帧缓冲区的指针,这样用户直接可以通过该指针来读写缓冲区。
问5.在用户程序中调用write函数和直接使用mmap函数返回的fbp指针有什么不一样?
答5.用户空间使用fbp指针操作的地址是用户空间和物理显存空间直接映射的关系,而使用write是将用户中的数据拷贝到内核空间,然后再将这些数据写到内核中已映射的虚拟地址空间中;write是操作整个fb,而fbp只操作一个像素点。
二、驱动代码编写
1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/errno.h> 4 #include <linux/string.h> 5 #include <linux/mm.h> 6 #include <linux/slab.h> 7 #include <linux/delay.h> 8 #include <linux/fb.h> 9 #include <linux/init.h> 10 #include <linux/dma-mapping.h> 11 #include <linux/interrupt.h> 12 #include <linux/platform_device.h> 13 #include <linux/clk.h> 14 #include <linux/workqueue.h> 15 16 #include <asm/io.h> 17 #include <asm/div64.h> 18 #include <asm/uaccess.h> 19 20 #include <asm/mach/map.h> 21 #include <mach/regs-gpio.h> 22 #include <linux/fb.h> 23 24 #define VSPW 9 //4 25 #define VBPD 13 //17 26 #define LINEVAL 479 27 #define VFPD 21 //26 28 29 #define HSPW 19 //4 30 #define HBPD 25 //40 31 #define HOZVAL 799 32 #define HFPD 209 //214 33 34 #define LeftTopX 0 35 #define LeftTopY 0 36 #define RightBotX 799 37 #define RightBotY 479 38 39 static struct fb_info *clb_fbinfo; 40 41 /* LCD GPIO Pins */ 42 static long unsigned long *gpf0con; 43 static long unsigned long *gpf1con; 44 static long unsigned long *gpf2con; 45 static long unsigned long *gpf3con; 46 static long unsigned long *gpd0con; 47 static long unsigned long *gpd0dat; 48 static long unsigned long *display_control; 49 50 /* LCD Controler Pins */ 51 struct s5pv210_lcd_regs{ 52 volatile unsigned long vidcon0; 53 volatile unsigned long vidcon1; 54 volatile unsigned long vidcon2; 55 volatile unsigned long vidcon3; 56 57 volatile unsigned long vidtcon0; 58 volatile unsigned long vidtcon1; 59 volatile unsigned long vidtcon2; 60 volatile unsigned long vidtcon3; 61 62 volatile unsigned long wincon0; 63 volatile unsigned long wincon1; 64 volatile unsigned long wincon2; 65 volatile unsigned long wincon3; 66 volatile unsigned long wincon4; 67 68 volatile unsigned long shadowcon; 69 volatile unsigned long reserve1[2]; 70 71 volatile unsigned long vidosd0a; 72 volatile unsigned long vidosd0b; 73 volatile unsigned long vidosd0c; 74 }; 75 76 struct clk *lcd_clk; 77 static struct s5pv210_lcd_regs *lcd_regs; 78 79 static long unsigned long *vidw00add0b0; 80 static long unsigned long *vidw00add1b0; 81 82 static u32 pseudo_palette[16]; 83 84 /* from pxafb.c */ 85 static unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf) 86 { 87 chan &= 0xffff; 88 chan >>= 16 - bf->length; 89 return chan << bf->offset; 90 } 91 92 static int clb210_lcdfb_setcolreg(unsigned regno, 93 unsigned red, unsigned green, unsigned blue, 94 unsigned transp, struct fb_info *info) 95 { 96 unsigned int val; 97 98 if (regno > 16) 99 return 1;100 101 /* 用red,green,blue三原色构造出val */102 val = chan_to_field(red, &info->var.red);103 val |= chan_to_field(green, &info->var.green);104 val |= chan_to_field(blue, &info->var.blue);105 106 pseudo_palette[regno] = val;107 108 return 0; 109 }110 111 //帧缓冲操作函数112 static struct fb_ops clb210_lcdfb_ops = 113 {114 .owner = THIS_MODULE,115 .fb_setcolreg = clb210_lcdfb_setcolreg, //设置color寄存器和调色板116 //下面这3个函数是通用的117 .fb_fillrect = cfb_fillrect, //画一个矩形118 .fb_copyarea = cfb_copyarea, //数据拷贝119 .fb_imageblit = cfb_imageblit, //图像填充120 };121 122 static int __init clb210_lcd_init(void)123 {124 /* 1.分配一个fb_info */125 clb_fbinfo = framebuffer_alloc(0 , NULL);126 127 /* 2. 设置 */128 /* 2.1 设置固定的参数 */129 strcpy(clb_fbinfo->fix.id, "clb210_lcd");130 clb_fbinfo->fix.smem_len = 800 * 480 * 32/8;131 clb_fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;132 clb_fbinfo->fix.visual = FB_VISUAL_TRUECOLOR;133 clb_fbinfo->fix.line_length = 800 * 32/8;134 135 /* 2.2 设置可变的参数 */136 clb_fbinfo->var.xres = 800;137 clb_fbinfo->var.yres = 480;138 clb_fbinfo->var.xres_virtual = 800;139 clb_fbinfo->var.yres_virtual = 480;140 clb_fbinfo->var.bits_per_pixel = 32;141 142 /*RGB:888*/143 clb_fbinfo->var.red.offset = 16;144 clb_fbinfo->var.red.length = 8;145 146 clb_fbinfo->var.green.offset = 8;147 clb_fbinfo->var.green.length = 8;148 149 clb_fbinfo->var.blue.offset = 0;150 clb_fbinfo->var.blue.length = 8;151 152 clb_fbinfo->var.activate = FB_ACTIVATE_NOW ;153 154 /* 2.3 设置操作函数 */155 clb_fbinfo->fbops = &clb210_lcdfb_ops;156 157 /* 2.4 其他的设置 */158 /* 2.4.1 设置显存的大小 */159 clb_fbinfo->screen_size = 800 * 480 * 32/8; 160 161 /* 2.4.2 设置调色板 */162 clb_fbinfo->pseudo_palette = pseudo_palette;163 164 /* 2.4.3 设置显存的虚拟起始地址 */165 clb_fbinfo->screen_base = dma_alloc_writecombine(NULL,166 clb_fbinfo->fix.smem_len, (u32*)&(clb_fbinfo->fix.smem_start), GFP_KERNEL);167 168 169 /* 3. 硬件相关的操作 */170 /* 3.1 获取lcd时钟,使能时钟 */171 lcd_clk = clk_get(NULL, "lcd");172 if (!lcd_clk || IS_ERR(lcd_clk)) {173 printk(KERN_INFO "failed to get lcd clock source\n");174 }175 clk_enable(lcd_clk);176 177 /* 3.2 配置GPIO用于LCD */178 gpf0con = ioremap(0xE0200120, 4);179 gpf1con = ioremap(0xE0200140, 4);180 gpf2con = ioremap(0xE0200160, 4);181 gpf3con = ioremap(0xE0200180, 4);182 gpd0con = ioremap(0xE02000A0, 4);183 gpd0dat = ioremap(0xE02000A4, 4);184 185 gpd0con = ioremap(0xE02000A0, 4); 186 gpd0dat = ioremap(0xE02000A4, 4); 187 188 vidcon0 = ioremap(0xF8000000, 4); 189 vidcon1 = ioremap(0xF8000004, 4); 190 vidtcon0 = ioremap(0xF8000010, 4); 191 vidtcon1 = ioremap(0xF8000014, 4); 192 vidtcon2 = ioremap(0xF8000018, 4); 193 wincon0 = ioremap(0xF8000020, 4); 194 vidosd0a = ioremap(0xF8000040, 4); 195 vidosd0b = ioremap(0xF8000044, 4); 196 vidosd0c = ioremap(0xF8000048, 4); 197 vidw00add0b0 = ioremap(0xF80000A0, 4); 198 vidw00add1b0 = ioremap(0xF80000D0, 4); 199 shodowcon = ioremap(0xF8000034, 4); 200 201 display_control = ioremap(0xe0107008, 4);202 203 /* 设置相关GPIO引脚用于LCD */204 *gpf0con = 0x22222222;205 *gpf1con = 0x22222222;206 *gpf2con = 0x22222222;207 *gpf3con = 0x22222222;208 209 /* 使能LCD本身 */210 *gpd0con |= 1<<4;211 *gpd0dat |= 1<<1;212 213 /* 显示路径的选择, 0b10: RGB=FIMD I80=FIMD ITU=FIMD */214 *display_control = 2<<0;215 216 /* 3.3 映射LCD控制器对应寄存器 */ 217 lcd_regs = ioremap(0xF8000000, sizeof(struct s5pv210_lcd_regs)); 218 vidw00add0b0 = ioremap(0xF80000A0, 4);219 vidw00add1b0 = ioremap(0xF80000D0, 4);220 221 lcd_regs->vidcon0 &= ~((3<<26) | (1<<18) | (0xff<<6) | (1<<2)); 222 lcd_regs->vidcon0 |= ((5<<6) | (1<<4) );223 224 lcd_regs->vidcon1 &= ~(1<<7); /* 在vclk的下降沿获取数据 */225 lcd_regs->vidcon1 |= ((1<<6) | (1<<5)); /* HSYNC极性反转, VSYNC极性反转 */226 227 lcd_regs->vidtcon0 = (VBPD << 16) | (VFPD << 8) | (VSPW << 0);228 lcd_regs->vidtcon1 = (HBPD << 16) | (HFPD << 8) | (HSPW << 0);229 lcd_regs->vidtcon2 = (LINEVAL << 11) | (HOZVAL << 0);230 lcd_regs->wincon0 &= ~(0xf << 2);231 lcd_regs->wincon0 |= (0xB<<2)|(1<<15);232 lcd_regs->vidosd0a = (LeftTopX<<11) | (LeftTopY << 0);233 lcd_regs->vidosd0b = (RightBotX<<11) | (RightBotY << 0);234 lcd_regs->vidosd0c = (LINEVAL + 1) * (HOZVAL + 1);235 236 *vidw00add0b0 = clb_fbinfo->fix.smem_start; 237 *vidw00add1b0 = clb_fbinfo->fix.smem_start + clb_fbinfo->fix.smem_len; 238 239 lcd_regs->shadowcon = 0x1; /* 使能通道0 */ 240 lcd_regs->vidcon0 |= 0x3; /* 开启总控制器 */ 241 lcd_regs->wincon0 |= 1; /* 开启窗口0 */242 243 244 /*4.注册*/245 register_framebuffer(clb_fbinfo);246 247 return 0;248 }249 static void __exit clb210_lcd_exit(void)250 {251 unregister_framebuffer(clb_fbinfo);252 dma_free_writecombine(NULL, clb_fbinfo->fix.smem_len, clb_fbinfo->screen_base, clb_fbinfo->fix.smem_start);253 iounmap(gpf0con);254 iounmap(gpf1con);255 iounmap(gpf2con);256 iounmap(gpf3con);257 iounmap(gpd0con);258 iounmap(gpd0dat);259 iounmap(display_control);260 iounmap(lcd_regs);261 iounmap(vidw00add0b0);262 iounmap(vidw00add1b0);263 framebuffer_release(clb_fbinfo);264 }265 266 module_init(clb210_lcd_init);267 module_exit(clb210_lcd_exit);268 269 MODULE_LICENSE("GPL");270 MODULE_AUTHOR("clb");271 MODULE_DESCRIPTION("Lcd driver for clb210 board");
这份代码没有基于platform设备驱动来编写,在内核源码中的demo就是基于platform驱动模型来搭建的,主要内容其实一样。
- Linux驱动:LCD驱动框架分析
- Linux LCD驱动分析
- linux lcd驱动分析一
- linux lcd驱动分析二
- linux lcd驱动分析三
- linux lcd驱动分析四
- linux lcd驱动分析五
- Linux的LCD驱动分析
- LCD(二) linux驱动分析
- LCD(二) linux驱动分析
- LCD(二) linux驱动分析
- linux驱动LCD对对程序之层次分析与框架
- Linux驱动-LCD驱动
- LCD设备驱动框架分析(数据结构)
- lcd驱动框架
- Linux-2.6.20的LCD驱动分析
- Linux-2.6.20的LCD驱动分析
- Linux-2.6.20的LCD驱动分析
- 综合实验2-约瑟夫环
- 【Jenkins教程四】基于Role-based Authorization Strategy的用户权限管理
- Java正则表达式 随笔
- 比赛中使用文件输入输出
- 深度学习-学习笔记--深度学习中的epochs,batch_size,iterations详解
- Linux驱动:LCD驱动框架分析
- bos项目day04 区域一键上传功能:参数分隔符和赋值符号使用规则
- css超链接中的下划线设置
- 脏读、幻读、不可重复读
- 【C++】判断是不是2、3、4的幂数
- 转载自博客园不思蜀的转载文章:最大子序列和算法分析
- 171120-函数程序练习【连续第二十八天】
- mybatis入门之简单操作
- 通俗易懂的依赖注入