第一部分:驱动的硬件配置部分:Sep0718处理器的lcdc控制器是带有普通的显示功能和overlay功能的,因此硬件配置上可以分成基本配置和额外的配置,基本的配置可以保证lcdc的正常运作,实现简单功能(单层的显示),额外配置主要包括对overlay的配置,从而实现多层叠加的效果:基本配置:基本配置包括:1. lcdc的基本属性配置(极性,dma burst长度, 屏幕size),在bcr和scr寄存器中;2. Lcdc的时序配置(行信号,帧信号,对比度控制),在plcr,pfcr,pccr寄存器中;3. Base层的配置(buffer起始地址,终止地址;base层的显示位置,base层的图像格式,RAW_IMAGE_WIDTH_0 DIS_IMAGE_WIDTH_0),在bbsar,bbear,btpcr,bbpcr和bcr,RAW_IMAGE_WIDTH_0 DIS_IMAGE_WIDTH_0寄存器中。 注意:在对非buffer数据地址的lcdc参数重新配置时,一定要将lcdc使能关闭后才能重新配置。因此,简化为下面的初始化代码为: /*关闭lcdc 使能寄存器*/write_reg(LCDC_ECR,0); write_reg(LCDC_BECR,0); write_reg(LCDC_W1ECR,0); write_reg(LCDC_W2ECR,0); write_reg(LCDC_CECR,0); /*此函数中将配置时序及屏幕size*/ lcdc_set(800, 480, 30);{ unsigned int parameter = 0; unsigned int LCD_PCD,H_value,V_value; H_value = X + (H_WIDTH >> 26) +(H_WAIT1 >> 8) + H_WAIT2 + 7; V_value = Y + (V_WIDTH >> 26) +(V_WAIT1 >> 8) + V_WAIT2 + 2; LCD_PCD = (sysclk / Freq) / (H_value * V_value); if(LCD_PCD & 0x1 != 0) LCD_PCD = LCD_PCD - 1; else LCD_PCD = LCD_PCD - 2; if(LCD_PCD <= 2) LCD_PCD = 2; parameter = XMAX(X) | YMAX(Y); write_reg(LCDC_SCR,parameter); parameter = 0; parameter = parameter | INT_LEVEL | HB | PIXPOL | FLMPOL | LPPOL | CLKPOL | OEPOL | PCD; write_reg(LCDC_BCR,parameter); parameter = 0; parameter = H_WIDTH | H_WAIT1 | H_WAIT2; write_reg(LCDC_PLCR,parameter); parameter = 0; parameter = V_WIDTH | V_WAIT1 | V_WAIT2; write_reg(LCDC_PFCR,parameter); parameter = 0; parameter = SCR | CC_EN | PW; write_reg(LCDC_PCCR,parameter); //the reset config of the YUV display} /*base层的配置,包括起始地址,size等*/BASE_CONFIGURE(0X40100000,800,800,0,0,799,479);{ U32 X1,X2,parameter; write_reg(LCDC_BBSAR,BBS_ADDR); write_reg(RAW_IMAGE_WIDTH_0,RAW_IMAGE_WIDTH); write_reg(DIS_IMAGE_WIDTH_0,DIS_IMAGE_WIDTH); X1 = (x1 << 16); X2 = (x2 << 16); parameter = X1 | y1; write_reg(LCDC_BTPCR,parameter); parameter = 0; parameter = X2 | y2; write_reg(LCDC_BBPCR,parameter); }lcdc_rgb_set(bpp16);{ U32 parameter; RGB_MODE = rgb_mode; parameter = read_reg(LCDC_BCR); parameter = parameter | BPIX_LAYER(RGB_MODE); write_reg(LCDC_BCR,parameter);} /*使能lcdc*/write_reg(LCDC_CECR,1);write_reg(LCDC_W2ECR,1);write_reg(LCDC_W1ECR,1);write_reg(LCDC_BECR,1);write_reg(LCDC_ECR,1); 由于lcd控制器的硬件决定,lcdc没有接受和发送的函数,需要更换显示内容时,只需要更换lcdc的buffer地址即可,改变buffer起始地址时,先将LCDC_ACSR寄存器置0,配置完成后再次置1。 第二部分:驱动的软件设计部分:1. 驱动的框架介绍:Framebuffer驱动在本质上是一个字符型驱动,通过文件/drivers/video/fbmem.c对每个驱动进行抽象,而fbmem.c就是一个典型的字符型驱动,有open,close,ioctl;用户态应用程序将通过系统调用访问到fbmem的相应接口,fbmem.c通过ioctl操作具体的fb驱动。 对于一个framebuffer驱动而言,最重要的是下面几个参数:1) fb_var_screeninfo这个结构描述了显示卡的特性:(这个结构体中很多参数和具体的液晶屏有关,因此有大量都是在液晶屏的配置文件中,用蓝色标示)struct fb_var_screeninfo{__u32 xres; /* visible resolution */ //可视区域__u32 yres;__u32 xres_virtual; /* virtual resolution */ //虚拟区域,很多场合会用到,简单的意思就是我内存中定义的区间是比较大的,但是可视的仅仅是我的一部分,比如滚屏操作……__u32 yres_virtual;__u32 xoffset; /* offset from virtual to visible resolution */ //可视区域的偏移__u32 yoffset;__u32 bits_per_pixel; /* guess what */ //每一象素的bit数,这个参数不需要自己配置,而是通过上层在调用checkvar函数传递bpp的时候赋值的。__u32 grayscale; /* != 0 Gray levels instead of colors *///等于零就成黑白//通过pixel per bpp来设定red green 和blue的位置; pixel per bpp可以通过ioctl设定struct fb_bitfield red; /* bitfield in fb mem if true color, */真彩的bit机构struct fb_bitfield green; /* else only length is significant */struct fb_bitfield blue; struct fb_bitfield transp; /* transparency */ 透明__u32 nonstd; /* != 0 Non standard pixel format */ 不是标准格式__u32 activate; /* see FB_ACTIVATE_* */ __u32 height; /* height of picture in mm */ 内存中的图像高度__u32 width; /* width of picture in mm */ 内存中的图像宽度__u32 accel_flags; /* acceleration flags (hints) */ 加速标志/* Timing: All values in pixclocks, except pixclock (of course) */时序-_-这些部分就是显示器的显示方法了,和具体的液晶显示屏有关,在驱动中一般放在具体液晶屏的配置文件__u32 pixclock; /* pixel clock in ps (pico seconds) */__u32 left_margin; /* time from sync to picture */__u32 right_margin; /* time from picture to sync */__u32 upper_margin; /* time from sync to picture */__u32 lower_margin;__u32 hsync_len; /* length of horizontal sync */ 水平可视区域__u32 vsync_len; /* length of vertical sync */ 垂直可视区域__u32 sync; /* see FB_SYNC_* */__u32 vmode; /* see FB_VMODE_* */__u32 reserved[6]; /* Reserved for future compatibility */ 备用-以后开发};2) fb_fix_screeninfon这个结构在显卡被设定模式后创建,它描述显示卡的属性,并且系统运行时不能被修改;比如FrameBuffer内存的起始地址。它依赖于被设定的模式,当一个模式被设定后,内存信息由显示卡硬件给出,内存的位置等信息就不可以修改。struct fb_fix_screeninfo {char id[16]; /* identification string eg "TT Builtin" */IDunsigned long smem_start; /* Start of frame buffer mem */ 内存起始物理地址,也就是dma**__u32 smem_len; /* Length of frame buffer mem */ 内存大小,这个会根据是不是双buffer,是不是virtual,有变化。__u32 type; /* see FB_TYPE_* */ __u32 type_aux; /* Interleave for interleaved Planes */插入区域?__u32 visual; /* see FB_VISUAL_* */__u16 xpanstep; /* zero if no hardware panning */没有硬件设备就为零__u16 ypanstep; /* zero if no hardware panning */设置成1就是标示我们可以在相应的方向移动显示,非常重要,android中使用了__u16 ywrapstep; /* zero if no hardware ywrap */__u32 line_length; /* length of a line in bytes */ 一行的字节表示unsigned long mmio_start; /* Start of Memory Mapped I/O */内存映射的I/O起始/* (physical address) */ __u32 mmio_len; /* Length of Memory Mapped I/O */ I/O的大小__u32 accel; /* Type of acceleration available */ 可用的加速类型__u16 reserved[3]; /* Reserved for future compatibility */};3)struct fb_ops s3cfb_ops = { .owner = THIS_MODULE, .fb_check_var = s3cfb_check_var, // 在上面的小节中提到对于一个LCD屏来说内核提供了两组数据结构来描述它,一组是可变属性(fb_var_screeninfo描述),另一组是不变属性(fb_fix_screeninfo描述)。对于可变属性,应该防止在操作的过程中出现超出法定范围的情况,因此内核应该可以调用相关函数来检测、并将这些属性固定在法定的范围内 .fb_set_par = s3cfb_set_par, //用户应用程序可通过ioctl对FIX参数进行重新配置,因此在进行这个之前首先会调用check_var保证参数在支持范围内。参数可改变的列表见var结构体,实际应用中主要是改变屏幕bpp和行的长度。 .fb_blank = s3cfb_blank//理论是关闭屏幕的操作(分别包括横向,纵向,整个屏),在实际中一般分为屏和背光都开,屏开背光关闭,屏和背光都关闭。 .fb_pan_display = s3cfb_pan_display,// FBIOPAN_DISPLAY在linux的注释里是“平移显示”的意思。怎么理解呢?就是按照y坐标平移显示缓存中的内容。调用FBIOPAN_DISPLAY时,会传一个y坐标偏移量yoffset给驱动,然后驱动会把当前显存的指针偏移 “yoffset X 屏幕宽度 X 位色字节数” 个字节,这样就好像实现了图像的y坐标平移,也就是“平移显示”。当这个yoffset等于屏幕高度的时候,就实现了显存的切换。 .fb_setcolreg = s3cfb_setcolreg, .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, .fb_imageblit = cfb_imageblit, .fb_cursor = soft_cursor, .fb_ioctl = s3cfb_ioctl, };因此sep0718fb.c驱动所做的主要工作就是完成这三个大结构的配置填充,并且使之和第一部分的硬件配置挂钩在一起。 2. 代码接口:a) 重要结构体说明:综上,framebuffer驱动主要涉及两方面,一方面涉及到framebuffer的参数配置,一方面涉及到lcdc硬件的配置,因此我设计了两个主要的结构体。typedef struct {struct fb_info fb;struct device *dev;struct clk *clk;struct resource *mem;void __iomem *io;unsigned int win_id;unsigned int max_bpp;unsigned int max_xres;unsigned int max_yres;/* raw memory addresses */dma_addr_t map_dma_f1; /* physical */u_char * map_cpu_f1; /* virtual */unsigned int map_size_f1; /* addresses of pieces placed in raw buffer */dma_addr_t screen_dma_f1; /* physical address of frame buffer */u_char * screen_cpu_f1; /* virtual address of frame buffer */unsigned int lcd_offset_x;unsigned int lcd_offset_y;unsigned int pseudo_pal[16];} sep0718fb_info_t;sep0718fb_info_t是关于framebuffer参数配置的结构体,里面主要包含了fb_info结构体(这个结构体中包含了上面提到的三个重要的参数var,fix,fb_ops)。 typedef struct {/* Screen size */int width;int height; /* Screen info */int xres;int yres; /* Virtual Screen info */int xres_virtual;int yres_virtual;int xoffset;int yoffset; /* OSD Screen size (overlay 1)*/int osd_width;int osd_height; /* OSD Screen info */int osd_xres;int osd_yres; /* OSD Screen info */int osd_xres_virtual;int osd_yres_virtual; int bpp;int bytes_per_pixel;unsigned long pixclock; /* lcd configuration registers */struct sep0718fb_hw regs;int hsync_len;int left_margin;int right_margin;int vsync_len;int upper_margin;int lower_margin;int sync; int cmap_grayscale:1;int cmap_inverse:1;int cmap_static:1;int unused:29; /* backlight info */int backlight_min;int backlight_max;int backlight_default; int vs_offset;int brightness;int palette_win;int backlight_level;int backlight_power;int lcd_power;}sep0718_lcdc_info_t;sep0718_lcdc_info_t是关于0718 lcdc硬件的结构体。里面包含了0718的寄存器映射列表,用于和sep0718fb_info_t值交互的一些参数。 3. 驱动详细说明:Sep0718fb驱动所采用的架构是基于platform的形式。Probe函数是驱动的初始化函数,remove是驱动的卸载函数,suspend和resume是驱动的挂起和恢复函数,在电源管理的时候会用到。 Probe函数是整个系统的核心,由于之前提到由于lcdc的使用流程更多的是对寄存器的配置,因此probe对于整个驱动非常重要。Probe主要实现了两件事情,完成了对硬件的初始化以及对framebuffer结构的申请初始化注册。硬件的初始化分布在probe中的两个地方:1. if (index == 0) sep0718fb_init_hw(&sepfb_info[index]);这里完成了lcdc的最基本的配置,比如时序,极性,分辨率等,因此这部分的内容是跟具体的屏幕有关的,因此这个函数是需要针对不同的屏幕实现的,具体的代码在fb800_480.c.2. ret = sep0718fb_init_registers(&sepfb_info[index]);由于我们的lcdc是overlay多层架构的,因此将针对不同的层分别进行配置。 对framebuffer结构体的初始化主要分布在probe中的两个函数:sep0718fb_init_fbinfo(&sepfb_info[index], driver_name, index);//完成了对大部分fb参数的配置 /* Initialize video memory */ ret = sep0718fb_map_video_memory(&sepfb_info[index]);//主要完成了对缓冲区的配置。 struct fb_ops sep0718fb_ops是整个驱动在初始化结束后会涉及的内容,由于在probe中会将sep0718fb_ops结构体注册到内核中。因此在驱动完成初始化后,其实就是这个结构体在起作用,因此下面讲一下这个结构体所做的工作。在这个结构体中具体是驱动设计到的函数有:fb_check_var: 在上面的小节中提到对于一个LCD屏来说内核提供了两组数据结构来描述它,一组是可变属性(fb_var_screeninfo描述),另一组是不变属性(fb_fix_screeninfo描述)。对于可变属性,应该防止在操作的过程中出现超出法定范围的情况,因此内核应该可以调用相关函数来检测、并将这些属性固定在法定的范围内。我们在这里的实现主要是针对bits_per_pixel的。 fb_set_var:用户应用程序可通过ioctl对FIX参数进行重新配置,因此在进行这个之前首先会调用check_var保证参数在支持范围内。参数可改变的列表见var结构体,实际应用中主要是改变屏幕bpp和行的长度。这里需要注意的一个地方时在set_var函数的内部我们是通过active函数来使寄存器重新配置的,对寄存器参数进行重新配置时一定要对lcdc先关闭,然后配完了再使能。 fb_pan_display:FBIOPAN_DISPLAY在linux的注释里是“平移显示”的意思。怎么理解呢?就是按照y坐标平移显示缓存中的内容。调用FBIOPAN_DISPLAY时,会传一个y坐标偏移量yoffset给驱动,然后驱动会把当前显存的指针偏移 “yoffset X 屏幕宽度 X 位色字节数” 个字节,这样就好像实现了图像的y坐标平移,也就是“平移显示”。当这个yoffset等于屏幕高度的时候,就实现了显存的切换。由于android中的要求是对y方向平移显示,所以我们在这里的实现也是平移显示的。注意是y平移还是x平移是在sep0718fb_init_fbinfo初始化函数中的 finfo->fb.fix.ypanstep = 1;所决定的。 fb_ioctl:ioctl函数主要是为驱动实现一些framebuffer架构没有包含的一些特殊的特性,用户应用程序可以通过ioctl来操作这些特殊的操作。Sep0718的overlay,alpha blending, color key等功能就是通过此处实现的。第三部分:驱动的内核配置及代码分布:1: 文件位置说明(具体文件,kconfig,makefile)整个驱动主要分为3个文件,均在/drivers/video/sep0718目录:sep0718_fb.c是整个framebuffer的最主要的组成,包含了fb的所有操作,硬件配置;fb800_480.c是具体液晶屏的配置文件;sep0718_fb.h是fb的头文件。 由于驱动是platform结构,因此还有一部分关于device设备描述的代码在/arch/arm/mach_sep0718/board.c中。 相应kconfig代码位置:/drivers/video/kconfig line245-295comment "SEP0718 Frame buffer hardware drivers config"depends on FB config FB_SEP0718tristate "SEP0718 frame buffer support "depends on FB && ARCH_SEP0718select FB_CFB_FILLRECTselect FB_CFB_COPYAREAselect FB_CFB_IMAGEBLIThelp Frame buffer driver for SEP0718 based boards. choice prompt "SEP0718 LCDC TYPE"depends on FB_SEP0718default FB_SEP0718_800_480 config FB_SEP0718_800_480bool "800*480 lcd support "depends on FB_SEP0718 endchoice choice prompt "SEP0718 LCDC COLOR TYPE"depends on FB_SEP0718default FB_SEP0718_BPP_16 config FB_SEP0718_BPP_16bool "16 bpp"depends on FB_SEP0718 config FB_SEP0718_BPP_18bool "18 bpp unpacked"depends on FB_SEP0718 config FB_SEP0718_BPP_24bool "24 bpp unpacked"depends on FB_SEP0718 endchoice config FB_SEP0718_NUM int "Number of Framebuffers" depends on FB_SEP0718 default "1" config FB_SEP0718_VIRTUAL_SCREENbool "sep0718 virtual screen support"depends on FB_SEP0718 comment "********************"depends on FB 相应的makefile的位置,drivers/video/sep0718/makefile:obj-$(CONFIG_FB_SEP0718) += sep0718_fb.oobj-$(CONFIG_FB_SEP0718_800_480) += fb800_480.o 通过这种架构的实现,以后增加不同型号液晶屏,只需直接增加一个液晶屏的配置文件,可以完全拷贝fb800_480.c的代码,只需对文件开始处的宏定义按照所用的屏进行修改即可(当然也需要按照fb800_480.c, 对kconfig和makefile部分进行修改,即按照红色部分进行增加)。 2: make menuconfig选择:简单驱动选择路径:一次选上framebuffer驱动,控制台输出,bootuplogo