内核LCD驱动结构分析及实例分析

来源:互联网 发布:c语言flag的用法 编辑:程序博客网 时间:2024/05/23 00:43

    内核中,LCD是作为帧缓冲设备来使用的,帧缓冲设备是标准的字符设备,主设备号为29.内核LCD驱动

的结构包含了字符设备的结构和paltform设备的结构,以smdk2410为例,其主要涉及4个文件:

               linux-2.6.22.6\arch\arm\mach-s3c2410\mach-smdk2410.c

               linux-2.6.22.6\arch\arm\plat-s3c24xx\devs.c

               linux-2.6.22.6\drivers\video\s3c2410fb.c

               linux-2.6.22.6\drivers\video\fbmem.c

    一、fbmem.c里面实现了帧缓冲设备这个字符设备的文件操作集fb_fops(注意与后面的fb_ops不同),在

帧缓冲设备模块被加载的时候,fbmem.c里面的fbmem_init()函数就会被调用,

fbmem_init(void){......if (register_chrdev(FB_MAJOR,"fb",&fb_fops))......fb_class = class_create(THIS_MODULE, "graphics");......}
其中FB_MAJOR=29,注册了帧缓冲设备,创建了类,但是没有创建设备节点。若创建好设备节点的话,应用程序

就可以openreadwrite调用到fb_fops来操作帧缓冲设备了。设备节点的创建的前提是需要有一个已经设置

好了的帧缓冲设备信息结构体( struct fb_info) 来支持。那么在哪里设备这个fb_info结构体,在哪里创建设

备节点?它们都是在platform设备驱动结构的probe()函数里面进行。下面分析platform设备驱动结构。


   二S3c2410fb.c里面主要做了3件大事:

1、实现用于fb_info结构体的fb_ops成员:s3c2410fb_ops

 static struct fb_ops s3c2410fb_ops = {.owner= THIS_MODULE,.fb_check_var= s3c2410fb_check_var,.fb_set_par= s3c2410fb_set_par,.fb_blank= s3c2410fb_blank,.fb_setcolreg= s3c2410fb_setcolreg,.fb_fillrect= cfb_fillrect,.fb_copyarea= cfb_copyarea,.fb_imageblit= cfb_imageblit,};

这个结构体与paltform设备驱动没有什么直接的关系,只是platform驱动结构的probe()函数要用到它及它的

成员函数来设置fb_info

 

2、实现platform驱动结构的platform_driver结构体s3c2410_driver:

static struct platform_driver s3c2410fb_driver = {.probe= s3c2410fb_probe,.remove= s3c2410fb_remove,.suspend= s3c2410fb_suspend,.resume= s3c2410fb_resume,.driver= {.name= "s3c2410-lcd",.owner= THIS_MODULE,},};

其中的这个probe()函数,后面还会详细讨论一下,因为它里面完成了帧缓冲设备这个字符设备还没有完成的任

务,那就是前面(一)最后说的设置fb_info结构体和创建字符设备节点。


3、注册platform_driver结构体:s3c2410fb_driver:

int __devinit s3c2410fb_init(void){return platform_driver_register(&s3c2410fb_driver);}

至此,根据platform设备驱动的机制,当有与”s3c2410-lcd”同名的platform_device注册的时候,上面的probe()

就会被调用。那么这个名为s3c2410-lcdplatfrom_device在哪里被注册呢?它在mach-sec2410.c里面被注册,

不过下面先分析一下devs.c


   三、devs.c的作用是构造了s3c2410芯片的所有平台设备资源结构体struct resource和平台设备结构体

struct platform_device,所谓平台设备,就是芯片内部集成的设备,一个芯片就是一个平台,集成在这芯片上

的设备就叫平台设备,如LCD控制器,USB控制器,UARTnand等控制器和寄存器资源,对一款芯片,它上面

的硬件资源或设备资源是固定的,对这种稳定的设备的驱动,人们往往把它写成平台设备驱动,这样对于片上

设备的驱动就不用重复写了。当然把外接设备写成平台设备驱动结构也是可以的,它本质就是一个机制,

driverdevice两边注册后调用probe()函数的一种机制,我们在probe()函数里面爱干嘛就干嘛。

   摘取devs.c的部分内容,其他内容与之相似:

....../* LCD Controller */static struct resource s3c_lcd_resource[] = {[0] = {.start = S3C24XX_PA_LCD,.end   = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,.flags = IORESOURCE_MEM,},[1] = {.start = IRQ_LCD,.end   = IRQ_LCD,.flags = IORESOURCE_IRQ,}};static u64 s3c_device_lcd_dmamask = 0xffffffffUL;struct platform_device s3c_device_lcd = {.name  = "s3c2410-lcd",.id  = -1,.num_resources  = ARRAY_SIZE(s3c_lcd_resource),.resource  = s3c_lcd_resource,.dev              = {.dma_mask= &s3c_device_lcd_dmamask,.coherent_dma_mask= 0xffffffffUL}};......

devs.c里面设置了platform_device结构体,但没有注册它,上面说了,它在mach-sec2410.c里面被注册。

 

   四、mach-s3c2410.c部分代码:

......static struct platform_device *smdk2410_devices[] __initdata = {    &s3c_device_usb,    &s3c_device_lcd,    &s3c_device_wdt,    &s3c_device_i2c,    &s3c_device_iis,};......static void __init smdk2410_init(void){platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices));smdk_machine_init();}......

__init smdk2410_init()函数把smdk2410_devices[]数组里面的几个平台设备都添加到内核,

platform_add_devices()相当于各个设备分别调用platform_device_register(),包括s3c_device_lcd,由(三)

s3c_device_lcd->name =“s3c2410-lcd”,与(二)中s3c2410fb_driver->driver.name =“s3c2410-lcd”相同,则调用s3c2410fb_driver->probe,s3c2410fb_probe()函数。

   下面先粗略分析这个probe函数。

   部分源码:

static int __init s3c2410fb_probe(struct platform_device *pdev){    ......fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);    ....../* 设置fbinfo结构体 */    ......ret = register_framebuffer(fbinfo);    ......}

Probe()函数主要就是分配、设置、注册 fbinfo结构体。

   下面粗略看一下register_framebuffer()函数:

   部分源码:

register_framebuffer(struct fb_info *fb_info){    ......fb_info->dev = device_create(fb_class, fb_info->device,     MKDEV(FB_MAJOR, i), "fb%d", i);    ......registered_fb[i] = fb_info;    ......}

   Register_framebuffer()函数就创建了设备节点,这样字符设备驱动的结构就完成了。同时,把次设备号i作为数组下标,把fb_info放入数组registered_fb[i] = fb_infofb_info结构体里面的信息供应用程序使用,当应用

程序读写帧缓冲设备fb%d时,会根据次设备号从registered_fb[]数组中取出一个fb_info结构体,根据结构体里

的信息找到读写位置进行读写。


    应用程序是通过fbmem.c里面抽象好的帧缓冲设备操作函数集,结合registered_fb[]里的fb_info提供的设备信息来操作设备的,其操作函数集是fb_fops。而与之容易混淆的一个操作集是fb_ops,fb_opsfb_info结构

体的成员,它是用来操作或设置fb_info自身的其他成员的,涉及到底层硬件的操作,一般需要开发人员自己编

写,而fb_fops作为帧缓冲设备通用操作函数集,已集成在fbmem.c里面,不需要我们再编写。

 

   对于源码中的probe()函数的细节,我实在是无能力分析了,不过其主要功能就是设置fb_info结构体,然后注册。

   下面结合东山老师的源码分析一下怎么设置这个fb_info结构体吧。东山老师的驱动结构中没有platfrom驱动结构,直接分配,设置,注册fb_info结构体,清晰明了。

   东山老师的Lcd.c,我的分析都添加在注释里了:

#include <linux/kernel.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/workqueue.h>#include <linux/wait.h>#include <linux/platform_device.h>#include <linux/clk.h>

#include <linux/module.h>

#include <asm/io.h>#include <asm/uaccess.h>#include <asm/div64.h>#include <asm/mach/map.h>#include <asm/arch/regs-lcd.h>#include <asm/arch/regs-gpio.h>#include <asm/arch/fb.h>/* s3c_lcdfb_setcolreg()是fb_ops结构体的成员函数,用于设置调色板 */static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, unsigned int blue, unsigned int transp, struct fb_info *info);/* lcd_regs 结构体与LCD控制器寄存器组对应,后面通过lcd_regs 操作寄存器组,* 因为后面有lcd_regs = ioremap(0x4d000000,sizeof(struct lcd_regs)),把寄存器组首地址* 映射后得到的虚拟地址赋给 lcd_regs,以后就可以通过lcd_regs操作到寄存器了。*/struct lcd_regs {unsigned longlcdcon1;unsigned longlcdcon2;unsigned longlcdcon3;unsigned longlcdcon4;unsigned longlcdcon5; unsigned longlcdsaddr1; unsigned longlcdsaddr2; unsigned longlcdsaddr3; unsigned longredlut; unsigned longgreenlut; unsigned longbluelut; unsigned longreserved[9]; unsigned longdithmode; unsigned longtpal; unsigned longlcdintpnd; unsigned longlcdsrcpnd; unsigned longlcdintmsk; unsigned longlpcsel;}; static struct fb_ops s3c_lcdfb_ops = {.owner= THIS_MODULE,.fb_setcolreg= s3c_lcdfb_setcolreg,//设置调色板函数,即使不使用也要实现这一项.fb_fillrect= cfb_fillrect,//矩形填充,系统提供的帧缓冲设备通用函数.fb_copyarea= cfb_copyarea, //复制区域,系统提供的帧缓冲设备通用函数.fb_imageblit= cfb_imageblit, //矩形填充,系统提供的帧缓冲设备通用函数};static struct fb_info *s3c_lcd;static volatile unsigned long *gpbcon;static volatile unsigned long *gpbdat;static volatile unsigned long *gpccon;static volatile unsigned long *gpdcon;static volatile unsigned long *gpgcon;static volatile struct lcd_regs* lcd_regs;/* 伪彩色调色板,这个伪彩色调色板,原理上就是一个16色的调色板,某些场合下就是用16级灰度色来反映 事物,研究事物,使用查找得到的伪彩色的数值显示的色彩是16BPP(或32BPP)的真彩色,但它不是物体 本身真正的颜色,它没有完全反映原图的彩色,如应用在医学图像中。较好地放映物体原色要256色以上 的调色板,过大的又没必要,人眼分辨不了那么微细的光波频率*/static u32 pseudo_palette[16];/* from pxafb.c *颜色位处理函数,用于构造565格式的颜色值。* chan:色域,red、green、blue * bf:位域结构体,成员包含所占位数和偏移,即length和offset*/static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf){chan &= 0xffff;//保留chan的低16位chan >>= 16 - bf->length;//把低16位的高length位移为低length位return chan << bf->offset;//移至颜色域的对应位}static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, unsigned int blue, unsigned int transp, struct fb_info *info){unsigned int val;if (regno > 16)//位调色板已经填充完成return 1;/* 用red,green,blue三原色构造出val */val = chan_to_field(red,&info->var.red);val |= chan_to_field(green, &info->var.green);val |= chan_to_field(blue,&info->var.blue);//((u32 *)(info->pseudo_palette))[regno] = val;pseudo_palette[regno] = val;return 0;}static int lcd_init(void){/* 1. 分配一个fb_info */s3c_lcd = framebuffer_alloc(0, NULL);/* 2. 设置 *//* 2.1 设置固定的参数 */strcpy(s3c_lcd->fix.id, "mylcd");s3c_lcd->fix.smem_len = 240*320*16/8;//字节为单位s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS;//与fix.type一起使用//参考https://www.kernel.org/doc/Documentation/fb/api.txts3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR; /* TFT */s3c_lcd->fix.line_length = 240*2;//一行里有效像素长度,240*16/8,以字节为单位,/* 2.2 设置可变的参数 */s3c_lcd->var.xres = 240;s3c_lcd->var.yres = 320;s3c_lcd->var.xres_virtual = 240;s3c_lcd->var.yres_virtual = 320;s3c_lcd->var.bits_per_pixel = 16;/* RGB:565 */s3c_lcd->var.red.offset = 11;s3c_lcd->var.red.length = 5;//__u32 msb_right字段默认为零,最重要为在左边,即高位。s3c_lcd->var.green.offset = 5;s3c_lcd->var.green.length = 6;s3c_lcd->var.blue.offset = 0;s3c_lcd->var.blue.length = 5;s3c_lcd->var.activate = FB_ACTIVATE_NOW;/* 2.3 设置操作函数 */s3c_lcd->fbops = &s3c_lcdfb_ops;/* 2.4 其他的设置 */s3c_lcd->pseudo_palette = pseudo_palette; //s3c_lcd->screen_base = ; /* 显存的虚拟地址 */ /* 由下面的s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);设置,dma_alloc-writecombine()函数分配一段连续的显 存,显存的物理地址保存在参数s3c_lcd->fix.smem_start中,返回显存的虚拟地址保存在 s3c_lcd->screen_base中。*/s3c_lcd->screen_size = 240*324*16/8;//字节为单位/* 3. 硬件相关的操作 *//* 3.1 配置GPIO用于LCD */gpbcon = ioremap(0x56000010, 8);gpbdat = gpbcon+1;gpccon = ioremap(0x56000020, 4);gpdcon = ioremap(0x56000030, 4);gpgcon = ioremap(0x56000060, 4); *gpccon = 0xaaaaaaaa; /* GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */*gpdcon = 0xaaaaaaaa; /* GPIO管脚用于VD[23:8] */*gpbcon &= ~(3); /* GPB0设置为输出引脚 */*gpbcon |= 1;*gpbdat &= ~1; /* 输出低电平 */*gpgcon |= (3<<8); /* GPG4用作LCD_PWREN *//* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));/* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2], LCD手册P14 * 10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2] * CLKVAL = 4 * bit[6:5]: 0b11, TFT LCD * bit[4:1]: 0b1100, 16 bpp for TFT * bit[0] : 0 = Disable the video output and the LCD control signal. */lcd_regs->lcdcon1 = (4<<8) | (3<<5) | (0x0c<<1);#if 1/* 垂直方向的时间参数 * bit[31:24]: VBPD, VSYNC之后再过多长时间才能发出第1行数据 * LCD手册 T0-T2-T1=4 * VBPD=3 * bit[23:14]: 多少行, 320, 所以LINEVAL=320-1=319 * bit[13:6] : VFPD, 发出最后一行数据之后,再过多长时间才发出VSYNC * LCD手册T2-T5=322-320=2, 所以VFPD=2-1=1 * bit[5:0] : VSPW, VSYNC信号的脉冲宽度, LCD手册T1=1, 所以VSPW=1-1=0 */lcd_regs->lcdcon2 = (3<<24) | (319<<14) | (1<<6) | (0<<0);/* 水平方向的时间参数 * bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据 * LCD手册 T6-T7-T8=17 * HBPD=16 * bit[18:8]: 多少列, 240, 所以HOZVAL=240-1=239 * bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC * LCD手册T8-T11=251-240=11, 所以HFPD=11-1=10 */lcd_regs->lcdcon3 = (16<<19) | (239<<8) | (10<<0);/* 水平方向的同步信号 * bit[7:0]: HSPW, HSYNC信号的脉冲宽度, LCD手册T7=5, 所以HSPW=5-1=4 */lcd_regs->lcdcon4 = 4;#elselcd_regs->lcdcon2 =S3C2410_LCDCON2_VBPD(5) | \S3C2410_LCDCON2_LINEVAL(319) | \S3C2410_LCDCON2_VFPD(3) | \S3C2410_LCDCON2_VSPW(1);lcd_regs->lcdcon3 =S3C2410_LCDCON3_HBPD(10) | \S3C2410_LCDCON3_HOZVAL(239) | \S3C2410_LCDCON3_HFPD(1);lcd_regs->lcdcon4 =S3C2410_LCDCON4_MVAL(13) | \S3C2410_LCDCON4_HSPW(0);#endif/* 信号的极性 * bit[11]: 1=565 format * bit[10]: 0 = The video data is fetched at VCLK falling edge * bit[9] : 1 = HSYNC信号要反转,即低电平有效 * bit[8] : 1 = VSYNC信号要反转,即低电平有效 * bit[6] : 0 = VDEN不用反转 * bit[3] : 0 = PWREN输出0 * bit[1] : 0 = BSWP * bit[0] : 1 = HWSWP 2440手册P413 */lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);/* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len,\ &s3c_lcd->fix.smem_start, GFP_KERNEL);lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1)\ & 0x1fffff;lcd_regs->lcdsaddr3 = (240*16/16); /* 一行的长度(单位: 2字节) *///s3c_lcd->fix.smem_start = xxx; /* 显存的物理地址 *//* 启动LCD */lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */*gpbdat |= 1; /* 输出高电平, 使能背光 *//* 4. 注册 */register_framebuffer(s3c_lcd);return 0;}static void lcd_exit(void){unregister_framebuffer(s3c_lcd);lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */*gpbdat &= ~1; /* 关闭背光 */dma_free_writecombine(NULL, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, \s3c_lcd->fix.smem_start);iounmap(lcd_regs);iounmap(gpbcon);iounmap(gpccon);iounmap(gpdcon);iounmap(gpgcon);framebuffer_release(s3c_lcd);}module_init(lcd_init);module_exit(lcd_exit);MODULE_LICENSE("GPL");


参考文章:

http://blog.csdn.net/qq_22863733/article/details/78251540


欢迎转载,转载请注明出处。









原创粉丝点击