嵌入式Linux之我行——S3C2440上LCD驱动(FrameBuffer)实例开发讲解(二).doc_第1页
嵌入式Linux之我行——S3C2440上LCD驱动(FrameBuffer)实例开发讲解(二).doc_第2页
嵌入式Linux之我行——S3C2440上LCD驱动(FrameBuffer)实例开发讲解(二).doc_第3页
嵌入式Linux之我行——S3C2440上LCD驱动(FrameBuffer)实例开发讲解(二).doc_第4页
嵌入式Linux之我行——S3C2440上LCD驱动(FrameBuffer)实例开发讲解(二).doc_第5页
免费预览已结束,剩余32页可下载查看

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

嵌入式Linux之我行S3C2440上LCD驱动(FrameBuffer)实例开发讲解(二) 嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。 开发环境 主 机:VMWare-Fedora 9 开发板:Mini2440-64MB Nand, Kernel: 编译器:arm-linux-gcc-4.3.2上接:S3C2440上LCD驱动(FrameBuffer)实例开发详解(一)四、帧缓冲(FrameBuffer)设备驱动实例代码:、建立驱动文件:my2440_lcd.c,依就是驱动程序的最基本结构:FrameBuffer驱动的初始化和卸载部分及其他,如下:#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*FrameBuffer设备名称*/static chardriver_name = my2440_lcd;/*定义一个结构体用来维护驱动程序中各函数中用到的变量先别看结构体要定义这些成员,到各函数使用的地方就明白了*/struct my2440fb_varint lcd_irq_no; /*保存LCD中断号*/struct clk *lcd_clock;/*保存从平台时钟队列中获取的LCD时钟*/struct resource *lcd_mem;/*LCD的IO空间*/void _iomem *lcd_base;/*LCD的IO空间映射到虚拟地址*/struct device *dev;struct s3c2410fb_hwregs;/*表示5个LCD配置寄存器,s3c2410fb_hw定义在mach-s3c2410/include/mach/fb.h中*/*定义一个数组来充当调色板。据数据手册描述,TFT屏色位模式为8BPP时,调色板(颜色表)的长度为256,调色板起始地址为0x4D000400*/u32palette_buffer256;u32pseudo_pal16;unsigned int palette_ready;/*标识调色板是否准备好了*/;/*用做清空调色板(颜色表)*/#define PALETTE_BUFF_CLEAR (0x80000000)/*LCD平台驱动结构体,平台驱动结构体定义在platform_device.h中,该结构体成员接口函数在第步中实现*/static struct platform_driver lcd_fb_driver = .probe= lcd_fb_probe, /*FrameBuffer设备探测*/.remove= _devexit_p(lcd_fb_remove),/*FrameBuffer设备移除*/.suspend= lcd_fb_suspend, /*FrameBuffer设备挂起*/.resume= lcd_fb_resume,/*FrameBuffer设备恢复*/.driver= /*注意这里的名称一定要和系统中定义平台设备的地方一致,这样才能把平台设备与该平台设备的驱动关联起来*/.name= s3c2410-lcd,.owner= THIS_MODULE,;static int _init lcd_init(void)/*在Linux中,帧缓冲设备被看做是平台设备,所以这里注册平台设备*/return platform_driver_register(&lcd_fb_driver);static void _exit lcd_exit(void)/*注销平台设备*/platform_driver_unregister(&lcd_fb_driver);module_init(lcd_init);module_exit(lcd_exit);MODULE_LICENSE(GPL);MODULE_AUTHOR(Huang Gang);MODULE_DESCRIPTION(My2440 LCD FrameBuffer Driver);、LCD平台设备各接口函数的实现:/*LCD FrameBuffer设备探测的实现,注意这里使用一个_devinit宏,到lcd_fb_remove接口函数实现的地方讲解*/static int _devinit lcd_fb_probe(struct platform_device *pdev)int i;int ret;struct resource*res;/*用来保存从LCD平台设备中获取的LCD资源*/struct fb_info*fbinfo;/*FrameBuffer驱动所对应的fb_info结构体*/struct s3c2410fb_mach_info*mach_info;/*保存从内核中获取的平台设备数据*/struct my2440fb_var*fbvar;/*上面定义的驱动程序全局变量结构体*/struct s3c2410fb_display*display;/*LCD屏的配置信息结构体,该结构体定义在mach-s3c2410/include/mach/fb.h中*/*获取LCD硬件相关信息数据,在前面讲过内核使用s3c24xx_fb_set_platdata函数将LCD的硬件相关信息保存到 了LCD平台数据中,所以这里我们就从平台数据中取出来在驱动中使用*/mach_info = pdev-dev.platform_data;if(mach_info = NULL)/*判断获取数据是否成功*/dev_err(&pdev-dev, no platform data for lcdn);return -EINVAL;/*获得在内核中定义的FrameBuffer平台设备的LCD配置信息结构体数据*/display = mach_info-displays + mach_info-default_display; /*给fb_info分配空间,大小为my2440fb_var结构的内存,framebuffer_alloc定义在fb.h中在fbsysfs.c中实现*/fbinfo = framebuffer_alloc(sizeof(struct my2440fb_var), &pdev-dev);if(!fbinfo)dev_err(&pdev-dev, framebuffer alloc of registers failedn);ret = -ENOMEM;goto err_noirq;platform_set_drvdata(pdev, fbinfo);/*重新将LCD平台设备数据设置为fbinfo,好在后面的一些函数中来使用*/*这里的用途其实就是将fb_info的成员par(注意是一个void类型的指针)指向这里的私有变量结构体fbvar, 目的是到其他接口函数中再取出fb_info的成员par,从而能继续使用这里的私有变量*/fbvar = fbinfo-par;fbvar-dev = &pdev-dev;/*在系统定义的LCD平台设备资源中获取LCD中断号,platform_get_irq定义在platform_device.h中*/fbvar-lcd_irq_no = platform_get_irq(pdev, 0);if(fbvar-lcd_irq_no dev, no lcd irq for platformn);return -ENOENT;/*获取LCD平台设备所使用的IO端口资源,注意这个IORESOURCE_MEM标志和LCD平台设备定义中的一致*/res = platform_get_resource(pdev, IORESOURCE_MEM, 0);if(res = NULL)/*判断获取资源是否成功*/dev_err(&pdev-dev, failed to get memory region resourcen);return -ENOENT;/*申请LCD IO端口所占用的IO空间(注意理解IO空间和内存空间的区别),request_mem_region定义在ioport.h中*/fbvar-lcd_mem = request_mem_region(res-start, res-end - res-start + 1, pdev-name);if(fbvar-lcd_mem = NULL)/*判断申请IO空间是否成功*/dev_err(&pdev-dev, failed to reserve memory regionn);return -ENOENT;/*将LCD的IO端口占用的这段IO空间映射到内存的虚拟地址,ioremap定义在io.h中 注意:IO空间要映射后才能使用,以后对虚拟地址的操作就是对IO空间的操作*/fbvar-lcd_base = ioremap(res-start, res-end - res-start + 1);if(fbvar-lcd_base = NULL)/*判断映射虚拟地址是否成功*/dev_err(&pdev-dev, ioremap() of registers failedn);ret = -EINVAL;goto err_nomem;/*从平台时钟队列中获取LCD的时钟,这里为什么要取得这个时钟,从LCD屏的时序图上看,各种控制信号的延迟 都跟LCD的时钟有关。系统的一些时钟定义在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/fbvar-lcd_clock = clk_get(NULL, lcd);if(!fbvar-lcd_clock)/*判断获取时钟是否成功*/dev_err(&pdev-dev, failed to find lcd clock sourcen);ret = -ENOENT;goto err_nomap;/*时钟获取后要使能后才可以使用,clk_enable定义在arch/arm/plat-s3c/clock.c中*/clk_enable(fbvar-lcd_clock);/*申请LCD中断服务,上面获取的中断号lcd_fb_irq,使用快速中断方式:IRQF_DISABLED 中断服务程序为:lcd_fb_irq,将LCD平台设备pdev做参数传递过去了*/ret = request_irq(fbvar-lcd_irq_no, lcd_fb_irq, IRQF_DISABLED, pdev-name, fbvar);if(ret)/*判断申请中断服务是否成功*/dev_err(&pdev-dev, IRQ%d error %dn, fbvar-lcd_irq_no, ret);ret = -EBUSY;goto err_noclk;/*好了,以上是对要使用的资源进行了获取和设置。下面就开始初始化填充fb_info结构体*/*首先初始化fb_info中代表LCD固定参数的结构体fb_fix_screeninfo*/*像素值与显示内存的映射关系有5种,定义在fb.h中。现在采用FB_TYPE_PACKED_PIXELS方式,在该方式下,像素值与内存直接对应,比如在显示内存某单元写入一个1时,该单元对应的像素值也将是1,这使得应用层把显示内存映射到用户空间变得非常方便。Linux中当LCD为TFT屏时,显示驱动管理显示内存就是基于这种方式*/strcpy(fbinfo-fix.id, driver_name);/*字符串形式的标识符*/fbinfo-fix.type = FB_TYPE_PACKED_PIXELS;fbinfo-fix.type_aux = 0;/*以下这些根据fb_fix_screeninfo定义中的描述,当没有硬件是都设为0*/fbinfo-fix.xpanstep = 0;fbinfo-fix.ypanstep = 0;fbinfo-fix.ywrapstep= 0;fbinfo-fix.accel = FB_ACCEL_NONE; /*接着,再初始化fb_info中代表LCD可变参数的结构体fb_var_screeninfo*/fbinfo-var.nonstd= 0;fbinfo-var.activate= FB_ACTIVATE_NOW;fbinfo-var.accel_flags= 0;fbinfo-var.vmode= FB_VMODE_NONINTERLACED;fbinfo-var.xres= display-xres;fbinfo-var.yres= display-yres;fbinfo-var.bits_per_pixel= display-bpp; /*指定对底层硬件操作的函数指针, 因内容较多故其定义在第步中再讲*/fbinfo-fbops= &my2440fb_ops; fbinfo-flags = FBINFO_FLAG_DEFAULT; fbinfo-pseudo_palette = &fbvar-pseudo_pal; /*初始化色调色板(颜色表)为空*/for(i = 0; i palette_bufferi = PALETTE_BUFF_CLEAR;for (i = 0; i num_displays; i+) /*fb缓存的长度*/*计算FrameBuffer缓存的最大大小,这里右移3位(即除以8)是因为色位模式BPP是以位为单位*/unsigned long smem_len = (mach_info-displaysi.xres * mach_info-displaysi.yres * mach_info-displaysi.bpp) 3;if(fbinfo-fix.smem_len fix.smem_len = smem_len;/*初始化LCD控制器之前要延迟一段时间*/msleep(1);/*初始化完fb_info后,开始对LCD各寄存器进行初始化,其定义在后面讲到*/my2440fb_init_registers(fbinfo);/*初始化完寄存器后,开始检查fb_info中的可变参数,其定义在后面讲到*/my2440fb_check_var(fbinfo);/*申请帧缓冲设备fb_info的显示缓冲区空间,其定义在后面讲到*/ret = my2440fb_map_video_memory(fbinfo);if (ret) dev_err(&pdev-dev, failed to allocate video RAM: %dn, ret);ret = -ENOMEM;goto err_nofb;/*最后,注册这个帧缓冲设备fb_info到系统中, register_framebuffer定义在fb.h中在fbmem.c中实现*/ret = register_framebuffer(fbinfo);if (ret dev, failed to register framebuffer device: %dn, ret);goto err_video_nomem;/*对设备文件系统的支持(对设备文件系统的理解请参阅:嵌入式Linux之我行设备文件系统剖析与使用) 创建frambuffer设备文件,device_create_file定义在linux/device.h中*/ret = device_create_file(&pdev-dev, &dev_attr_debug);if (ret) dev_err(&pdev-dev, failed to add debug attributen);return 0;/*以下是上面错误处理的跳转点*/err_nomem:release_resource(fbvar-lcd_mem);kfree(fbvar-lcd_mem);err_nomap:iounmap(fbvar-lcd_base);err_noclk:clk_disable(fbvar-lcd_clock);clk_put(fbvar-lcd_clock);err_noirq:free_irq(fbvar-lcd_irq_no, fbvar);err_nofb:platform_set_drvdata(pdev, NULL);framebuffer_release(fbinfo);err_video_nomem:my2440fb_unmap_video_memory(fbinfo);return ret;/*LCD中断服务程序*/static irqreturn_t lcd_fb_irq(int irq, void *dev_id)struct my2440fb_var*fbvar = dev_id;void _iomem *lcd_irq_base;unsigned long lcdirq;/*LCD中断挂起寄存器基地址*/lcd_irq_base = fbvar-lcd_base + S3C2410_LCDINTBASE;/*读取LCD中断挂起寄存器的值*/lcdirq = readl(lcd_irq_base + S3C24XX_LCDINTPND);/*判断是否为中断挂起状态*/if(lcdirq & S3C2410_LCDINT_FRSYNC)/*填充调色板*/if (fbvar-palette_ready)my2440fb_write_palette(fbvar);/*设置帧已插入中断请求*/writel(S3C2410_LCDINT_FRSYNC, lcd_irq_base + S3C24XX_LCDINTPND);writel(S3C2410_LCDINT_FRSYNC, lcd_irq_base + S3C24XX_LCDSRCPND);return IRQ_HANDLED;/*填充调色板*/static void my2440fb_write_palette(struct my2440fb_var *fbvar)unsigned int i;void _iomem *regs = fbvar-lcd_base;fbvar-palette_ready = 0;for (i = 0; i palette_bufferi;if (ent = PALETTE_BUFF_CLEAR)continue;writel(ent, regs + S3C2410_TFTPAL(i);if (readw(regs + S3C2410_TFTPAL(i) = ent)fbvar-palette_bufferi = PALETTE_BUFF_CLEAR;elsefbvar-palette_ready = 1;/*LCD各寄存器进行初始化*/static int my2440fb_init_registers(struct fb_info *fbinfo)unsigned long flags;void _iomem *tpal;void _iomem *lpcsel;/*从lcd_fb_probe探测函数设置的私有变量结构体中再获得LCD相关信息的数据*/struct my2440fb_var*fbvar = fbinfo-par;struct s3c2410fb_mach_info *mach_info = fbvar-dev-platform_data;/*获得临时调色板寄存器基地址,S3C2410_TPAL宏定义在mach-s3c2410/include/mach/regs-lcd.h中。注意对于lpcsel这是一个针对三星TFT屏的一个专用寄存器,如果用的不是三星的TFT屏应该不用管它。*/tpal = fbvar-lcd_base + S3C2410_TPAL;lpcsel = fbvar-lcd_base + S3C2410_LPCSEL;/*在修改下面寄存器值之前先屏蔽中断,将中断状态保存到flags中*/local_irq_save(flags);/*这里就是在上一篇章中讲到的把IO端口C和D配置成LCD模式*/modify_gpio(S3C2410_GPCUP, mach_info-gpcup, mach_info-gpcup_mask);modify_gpio(S3C2410_GPCCON, mach_info-gpccon, mach_info-gpccon_mask);modify_gpio(S3C2410_GPDUP, mach_info-gpdup, mach_info-gpdup_mask);modify_gpio(S3C2410_GPDCON, mach_info-gpdcon, mach_info-gpdcon_mask);/*恢复被屏蔽的中断*/local_irq_restore(flags);writel(0x00, tpal);/*临时调色板寄存器使能禁止*/writel(mach_info-lpcsel, lpcsel);/*在上一篇中讲到过,它是三星TFT屏的一个寄存器,这里可以不管*/return 0;/*该函数实现修改GPIO端口的值,注意第三个参数mask的作用是将要设置的寄存器值先清零*/static inline void modify_gpio(void _iomem *reg, unsigned long set, unsigned long mask)unsigned long tmp;tmp = readl(reg) & mask;writel(tmp | set, reg);/*检查fb_info中的可变参数*/static int my2440fb_check_var(struct fb_info *fbinfo)unsigned i;/*从lcd_fb_probe探测函数设置的平台数据中再获得LCD相关信息的数据*/struct fb_var_screeninfo *var = &fbinfo-var;/*fb_info中的可变参数*/struct my2440fb_var*fbvar = fbinfo-par;/*在lcd_fb_probe探测函数中设置的私有结构体数据*/struct s3c2410fb_mach_info *mach_info = fbvar-dev-platform_data;/*LCD的配置结构体数据,这个配置结构体的赋值在上一篇章的3. 帧缓冲设备作为平台设备中*/struct s3c2410fb_display *display = NULL;struct s3c2410fb_display *default_display = mach_info-displays + mach_info-default_display;int type = default_display-type;/*LCD的类型,看上一篇章的3. 帧缓冲设备作为平台设备中的type赋值是TFT类型*/*验证X/Y解析度*/if (var-yres = default_display-yres & var-xres = default_display-xres & var-bits_per_pixel = default_display-bpp)display = default_display;elsefor (i = 0; i num_displays; i+)if (type = mach_info-displaysi.type & var-yres = mach_info-displaysi.yres & var-xres = mach_info-displaysi.xres & var-bits_per_pixel = mach_info-displaysi.bpp) display = mach_info-displays + i;break;if (!display) return -EINVAL;/*配置LCD配置寄存器1中的5-6位(配置成TFT类型)和配置LCD配置寄存器5*/fbvar-regs.lcdcon1 = display-type;fbvar-regs.lcdcon5 = display-lcdcon5;/* 设置屏幕的虚拟解析像素和高度宽度 */var-xres_virtual = display-xres;var-yres_virtual = display-yres;var-height = display-height;var-width = display-width;/* 设置时钟像素,行、帧切换值,水平同步、垂直同步长度值 */var-pixclock = display-pixclock;var-left_margin = display-left_margin;var-right_margin = display-right_margin;var-upper_margin = display-upper_margin;var-lower_margin = display-lower_margin;var-vsync_len = display-vsync_len;var-hsync_len = display-hsync_len;/*设置透明度*/var-transp.offset = 0;var-transp.length = 0;/*根据色位模式(BPP)来设置可变参数中R、G、B的颜色位域。对于这些参数值的设置请参考CPU数据手册中显示缓冲区与显示点对应关系图,例如在上一篇章中我就画出了8BPP和16BPP时的对应关系图*/switch (var-bits_per_pixel) case 1:case 2:case 4:var-red.offset= 0;var-red.length= var-bits_per_pixel;var-green = var-red;var-blue = var-red;break;case 8:/* 8 bpp 332 */if (display-type != S3C2410_LCDCON1_TFT) var-red.length= 3;var-red.offset= 5;var-green.length= 3;var-green.offset= 2;var-blue.length= 2;var-blue.offset= 0;elsevar-red.offset= 0;var-red.length= 8;var-green = var-red;var-blue = var-red;break;case 12:/* 12 bpp 444 */var-red.length = 4;var-red.offset = 8;var-green.length = 4;var-green.offset = 4;var-blue.length = 4;var-blue.offset = 0;break;case 16:/* 16 bpp */if (display-lcdcon5 & S3C2410_LCDCON5_FRM565) /* 565 format */var-red.offset= 11;var-green.offset= 5;var-blue.offset = 0;var-red.length= 5;var-green.length= 6;var-blue.length = 5; else /* 5551 format */var-red.offset= 11;var-green.offset= 6;var-blue.offset = 1;var-red.length= 5;var-green.length= 5;var-blue.length = 5;break;case 32:/* 24 bpp 888 and 8 dummy */var-red.length= 8;var-red.offset= 16;var-green.length = 8;var-green.offset = 8;var-blue.length = 8;var-blue.offset = 0;break;return 0;/*申请帧缓冲设备fb_info的显示缓冲区空间*/static int _init my2440fb_map_video_memory(struct fb_info *fbinfo)dma_addr_t map_dma;/*用于保存DMA缓冲区总线地址*/struct my2440fb_var*fbvar = fbinfo-par;/*获得在lcd_fb_probe探测函数中设置的私有结构体数据*/unsigned map_size = PAGE_ALIGN(fbinfo-fix.smem_len);/*获得FrameBuffer缓存的大小, PAGE_ALIGN定义在mm.h中*/*将分配的一个写合并DMA缓存区设置为LCD屏幕的虚拟地址(对于DMA请参考DMA相关知识)dma_alloc_writecombine定义在arch/arm/mm/dma-mapping.c中*/fbinfo-screen_base = dma_alloc_writecombine(fbvar-dev, map_size, &map_dma, GFP_KERNEL);if (fbinfo-screen_base) /*设置这片DMA缓存区的内容为空*/memset(fbinfo-screen_base, 0x00, map_size);/*将DMA缓冲区总线地址设成fb_info不可变参数中framebuffer缓存的开始位置*/fbinfo-fix.smem_start = map_dma;return fbinfo-screen_base ? 0 : -ENOMEM;/*释放帧缓冲设备fb_info的显示缓冲区空间*/static inline void my2440fb_unmap_video_memory(struct fb_info *fbinfo)struct my2440fb_var*fbvar = fbinfo-par;unsigned map_size = PAGE_ALIGN(fbinfo-fix.smem_len);/*跟申请DMA的地方想对应*/dma_free_writecombine(fbvar-dev, map_size, fbinfo-screen_base, fbinfo-fix.smem_start);/*LCD FrameBuffer设备移除的实现,注意这里使用一个_devexit宏,和lcd_fb_probe接口函数相对应。在Linux内核中,使用了大量不同的宏来标记具有不同作用的函数和数据结构,这些宏在include/linux/init.h 头文件中定义,编译器通过这些宏可以把代码优化放到合适的内存位置,以减少内存占用和提高内核效率。_devinit、_devexit就是这些宏之一,在probe()和remove()函数中应该使用_devinit和_devexit宏。又当remove()函数使用了_devexit宏时,则在驱动结构体中一定要使用_devexit_p宏来引用remove(),所以在第步中就用_devexit_p来引用lcd_fb_remove接口函

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论