mini2440驱动分析之LCD

来源:互联网 发布:淘宝全网数据分析工具 编辑:程序博客网 时间:2024/04/28 07:37
        mini2440集成了lcd控制器的接口,板子上接的lcd硬件是统宝240*320,TFT型lcd。lcd驱动对应的文件为s3c2410fb.c。要读懂这个驱动必须了解linux platform子系统的知识。因为这个驱动是以platform驱动的形式注册到内核。而且还需要frambuffer驱动的知识,因为这个驱动还是frambuffer接口的。lcd驱动在模块初始化的时候,调用platform注册函数将自己注册到内核,利用linux设备模型核心的机制调用platform_bus总线的match函数找到相应的设备,然后由linux设备模型核心调用s3c2410fb.c中的s3c2410fb_probe ,进行硬件相关初始化,并初始化frambuffer结构。然后注册到frambuffer核心。lcd的功能实现通过frambuffer核心来完成。s3c2410fb.c的功能实现都是配合frambuffer核心的。下面详细分析lcd驱动的实现。
程序基本结构
 1.模块初始化-->向platform核心注册自己
 2.实现linux设备模型必须的probe函数-->向frambuffer核心注册自己(最重要)
                       resume函数-->系统在由挂起恢复的时候调用
                       suspand-->系统在挂起的时候调用 
                       remove--> 驱动程序注销自己的时候调用                  
 3.frambuffer驱动模型fb_ops各函数的实现-->实现fb驱动的ioctl命令需要的函数

 4.其他函数-->由2.3.中的函数调用,帮助其实现功能。

一. 相关数据结构
  1. struct fb_info 结构
[cpp] view plaincopy
  1. struct fb_info {  
  2.     int node;  
  3.     int flags;  
  4.     struct mutex lock;      /* Lock for open/release/ioctl funcs */  
  5.     struct mutex mm_lock;       /* Lock for fb_mmap and smem_* fields */  
  6.     struct fb_var_screeninfo var;   /* Current var */  
  7.     struct fb_fix_screeninfo fix;   /* Current fix */  
  8.     struct fb_monspecs monspecs;    /* Current Monitor specs */  
  9.     struct work_struct queue;   /* Framebuffer event queue */  
  10.     struct fb_pixmap pixmap;    /* Image hardware mapper */  
  11.     struct fb_pixmap sprite;    /* Cursor hardware mapper */  
  12.     struct fb_cmap cmap;        /* Current cmap */  
  13.     struct list_head modelist;      /* mode list */  
  14.     struct fb_videomode *mode;  /* current mode */  
  15.   
  16. #ifdef CONFIG_FB_BACKLIGHT  
  17.     /* assigned backlight device */  
  18.     /* set before framebuffer registration,  
  19.        remove after unregister */  
  20.     struct backlight_device *bl_dev;  
  21.   
  22.     /* Backlight level curve */  
  23.     struct mutex bl_curve_mutex;      
  24.     u8 bl_curve[FB_BACKLIGHT_LEVELS];  
  25. #endif  
  26. #ifdef CONFIG_FB_DEFERRED_IO  
  27.     struct delayed_work deferred_work;  
  28.     struct fb_deferred_io *fbdefio;  
  29. #endif  
  30.   
  31.     struct fb_ops *fbops;  
  32.     struct device *device;      /* This is the parent */  
  33.     struct device *dev;     /* This is this fb device */     
  34.     int class_flag;                    /* private sysfs flags */  
  35. #ifdef CONFIG_FB_TILEBLITTING  
  36.     struct fb_tile_ops *tileops;    /* Tile Blitting */  
  37. #endif  
  38.     char __iomem *screen_base;  /* Virtual address */  
  39.     unsigned long screen_size;  /* Amount of ioremapped VRAM or 0 */   
  40.     void *pseudo_palette;       /* Fake palette of 16 colors */   
  41. #define FBINFO_STATE_RUNNING    0  
  42. #define FBINFO_STATE_SUSPENDED  1  
  43.     u32 state;          /* Hardware state i.e suspend */  
  44.     void *fbcon_par;                /* fbcon use-only private area */  
  45.     /* From here on everything is device dependent */  
  46.     void *par;  
  47.     /* we need the PCI or similiar aperture base/size not 
  48.        smem_start/size as smem_start may just be an object 
  49.        allocated inside the aperture so may not actually overlap */  
  50.     resource_size_t aperture_base;  
  51.     resource_size_t aperture_size;  
  52. };  
    这个结构是frambuffer驱动的基本数据结构,里面包含了帧缓存设备的所有信息,每一个注册成frambuffer接口的设备都应该声明并初始化这样一个结构。register_framebuffer 函数的参数就是这样一个结构,fb_info在mini2440lcd驱动中是在s3c24xxfb_probe函数中分配并初始化的。其中struct fb_var_screeninfo结构包含了lcd显示中可以改变的信息,结构如下:
[cpp] view plaincopy
  1. struct fb_var_screeninfo {  
  2.     __u32 xres;         /* 视口水平分辨率      */  
  3.     __u32 yres;  
  4.     __u32 xres_virtual;     /* 虚拟屏幕水平分辨率        */  
  5.     __u32 yres_virtual;  
  6.     __u32 xoffset;          /* 视口与虚拟屏幕水平分辨率偏移 */  
  7.     __u32 yoffset;            
  8.   
  9.   
  10.     __u32 bits_per_pixel;       /* 像素的位数            */  
  11.     __u32 grayscale;        /* 灰度标志,如果为1代表是灰度 */  
  12.   
  13.   
  14.     struct fb_bitfield red;     /* 如果是真彩色,这个是颜色位,如果不是那么只有结构的大小重要,其他表示的信息无关紧要 */  
  15.     struct fb_bitfield green;     
  16.     struct fb_bitfield blue;  
  17.     struct fb_bitfield transp;  /* 透明度      */    
  18.   
  19.   
  20.     __u32 nonstd;           /* 非标准颜色表示标志位 */  
  21.     __u32 activate;         /* 参照 FB_ACTIVATE_*     */  
  22.     __u32 height;           /* 在内存地址空间的长度    */  
  23.     __u32 width;            /* 在内存地址空间的宽度     */  
  24.   
  25.   
  26.     __u32 accel_flags;      /* (不用了) 参照 fb_info.flags */  
  27.   
  28.   
  29.     /* 时序: 以下所有的值单位都是pixclock, 当然除了pixclock */  
  30.     __u32 pixclock;         /* 每秒像素值 */  
  31.     __u32 left_margin;      /* 从sync信号到显示真正的像素的时钟个数 */  
  32.     __u32 right_margin;     /* 从真正显示像素到sync信号的时钟个数  */  
  33.     __u32 upper_margin;     /* 上面两个是针对列像素的,这个针对行的   */  
  34.     __u32 lower_margin;  
  35.     __u32 hsync_len;        /* 水平sync信号的长度  */  
  36.     __u32 vsync_len;        /* 垂直sync信号的长度  */  
  37.     __u32 sync;         /* 参照 FB_SYNC_*     */  
  38.     __u32 vmode;            /* 参照 FB_VMODE_*        */  
  39.     __u32 rotate;           /* angle we rotate counter clockwise */   
  40.     __u32 reserved[5];      /* 保留 */  
  41. };  
  fb_fix_screeninfo包含了lcd显示中不可改变的信息,结构如下:
[cpp] view plaincopy
  1. struct fb_fix_screeninfo {  
  2.     char id[16];            /* 身份表示符,例如 "TT Builtin" */  
  3.     unsigned long smem_start;   /* frame buffer内存的开始地址 */  
  4.                     /* (物理地址) */  
  5.     __u32 smem_len;         /* frame buffer内存地址的长度 */  
  6.     __u32 type;         /* 参照 FB_TYPE_*     */  
  7.     __u32 type_aux;         /* Interleave for interleaved Planes */  
  8.     __u32 visual;           /* 参照 FB_VISUAL_*       */   
  9.     __u16 xpanstep;         /* zero if no hardware panning  */  
  10.     __u16 ypanstep;         /* zero if no hardware panning  */  
  11.     __u16 ywrapstep;        /* zero if no hardware ywrap    */  
  12.     __u32 line_length;      /* 每行的长度,单位字节    */  
  13.     unsigned long mmio_start;   /* I/O 内存的开始地址   */  
  14.                     /* (物理地址) */  
  15.     __u32 mmio_len;         /* I/O内存的长度  */  
  16.     __u32 accel;            /* 对驱动程序的标示:是哪个设备*/  
  17.     __u16 reserved[3];      /* 保留 */  
  18. };  
  其中倒数第三个成员par是设备自定义数据结构。在mini2440lcd驱动中为s3c2410fb_info,结构如下:
[cpp] view plaincopy
  1. struct s3c2410fb_info {  
  2.     struct device       *dev;  
  3.     struct clk      *clk;  
  4.   
  5.     struct resource     *mem; //io内存物理地址也就是寄存器的地址  
  6.     void __iomem        *io;  //用ioremap映射的io虚拟地址  
  7.     void __iomem        *irq_base; //中断控制器寄存器对应的虚拟地址  
  8.   
  9.     enum s3c_drv_type   drv_type;  
  10.     struct s3c2410fb_hw regs;  
  11.   
  12.     unsigned long       clk_rate;  
  13.     unsigned int        palette_ready;  
  14.   
  15. #ifdef CONFIG_CPU_FREQ  
  16.     struct notifier_block   freq_transition;  
  17. #endif  
  18.   
  19.     /* keep these registers in case we need to re-write palette */  
  20.     u32         palette_buffer[256];  
  21.     u32         pseudo_pal[16];  
  22. };  
  这个结构是和硬件相关的,包括寄存器的物理地址,虚拟地址和调色板的一些信息。这个结构也是在s3c24xxfb_probe中分配并初始化。
  2. static struct fb_ops 结构
  在mini2440lcd驱动中,fb_ops的初始化代码如下:
[cpp] view plaincopy
  1. static struct fb_ops s3c2410fb_ops = {  
  2.     .owner      = THIS_MODULE,  
  3.     .fb_check_var   = s3c2410fb_check_var,  
  4.     .fb_set_par = s3c2410fb_set_par,  
  5.     .fb_blank   = s3c2410fb_blank,  
  6.     .fb_setcolreg   = s3c2410fb_setcolreg,  
  7.     .fb_fillrect    = cfb_fillrect,  
  8.     .fb_copyarea    = cfb_copyarea,  
  9.     .fb_imageblit   = cfb_imageblit,  
  10. };  
  这些函数是驱动程序必须实现的,他们实现的功能对应frambuffer核心的Ioctl系统调用,当应用程序调用ioctl系统调用的时候,他们会被直接或间接的调用。其中:

s3c2410fb_check_var 和s3c2410fb_set_par会由fb_set_var调用,对应Ioctl的FBIOPUT_VSCREENINFO命令

s3c2410fb_blank ,对应ioctl的FBIOBLANK命令,其他几个函数也是类似。

  3. struct s3c2410fb_mach_info 结构
[cpp] view plaincopy
  1. struct s3c2410fb_mach_info {  
  2.     struct s3c2410fb_display *displays; /* attached diplays info */  
  3.     unsigned num_displays;          /* number of defined displays */  
  4.     unsigned default_display;  
  5.     /* GPIOs */  
  6.     unsigned long   gpcup;  
  7.     unsigned long   gpcup_mask;  
  8.     unsigned long   gpccon;  
  9.     unsigned long   gpccon_mask;  
  10.     unsigned long   gpdup;  
  11.     unsigned long   gpdup_mask;  
  12.     unsigned long   gpdcon;  
  13.     unsigned long   gpdcon_mask;  
  14.   
  15.     /* lpc3600 control register */  
  16.     unsigned long   lpcsel;  
  17. };  
  这个结构包括一个s3c2410fb_display结构体,其他的域是GPIO寄存器的信息。mini2440lcd驱动中定义并初始化了这样一个结构体:
[cpp] view plaincopy
  1. static struct s3c2410fb_mach_info mini2440_fb_info __initdata = {  
  2.     .displays   = &mini2440_lcd_cfg,  
  3.     .num_displays   = 1,  
  4.     .default_display = 0,  
  5.   
  6.     .gpccon =       0xaa955699,  
  7.     .gpccon_mask =  0xffc003cc,  
  8.     .gpcup =        0x0000ffff,  
  9.     .gpcup_mask =   0xffffffff,  
  10.   
  11.     .gpdcon =       0xaa95aaa1,  
  12.     .gpdcon_mask =  0xffc0fff0,  
  13.     .gpdup =        0x0000faff,  
  14.     .gpdup_mask =   0xffffffff,  
  15.   
  16.     .lpcsel     = 0xf82,  
  17. };  
  这里初始化了结构中的所有成员,s3c2410fb_display结构初始化成mini2440_lcd_cfg,这个结构的初始化是在/arch/arm/mach-s3c2440/mach-mini2440.c这个文件中。这里设置了s3c2440 lcd控制器对应的GPIO寄存器的初始值,在s3c2410fb_init_registers函数中将这些值写到相应的寄存器中。
  4. s3c2410fb_display 结构
[cpp] view plaincopy
  1. struct s3c2410fb_display {  
  2.     /* LCD type */  
  3.     unsigned type;  
  4.   
  5.     /* Screen size */  
  6.     unsigned short width;  
  7.     unsigned short height;  
  8.   
  9.     /* Screen info */  
  10.     unsigned short xres;  
  11.     unsigned short yres;  
  12.     unsigned short bpp;  
  13.   
  14.     unsigned pixclock;      /* pixclock in picoseconds */  
  15.     unsigned short left_margin;  /* value in pixels (TFT) or HCLKs (STN) */  
  16.     unsigned short right_margin; /* value in pixels (TFT) or HCLKs (STN) */  
  17.     unsigned short hsync_len;    /* value in pixels (TFT) or HCLKs (STN) */  
  18.     unsigned short upper_margin;    /* value in lines (TFT) or 0 (STN) */  
  19.     unsigned short lower_margin;    /* value in lines (TFT) or 0 (STN) */  
  20.     unsigned short vsync_len;   /* value in lines (TFT) or 0 (STN) */  
  21.   
  22.     /* lcd configuration registers */  
  23.     unsigned long   lcdcon5;  
  24. };  
  这个结构体非常重要,他包括了一个lcd显示的所有必须的配置信息。程序就是用这个结构体初始化fb_info结构中的fb_var_screeninfo相关成员的。最后这些值都会写进lcd控制器的相应寄存器中。如上分析,这个结构在mini2440lcd驱动中被初始化成了mini2440_lcd_cfg,他定义在/arch/arm/mach-s3c2440/mach-mini2440.c,如下所示:
[cpp] view plaincopy
  1. static struct s3c2410fb_display mini2440_lcd_cfg __initdata = {  
  2.   
  3. #if !defined (LCD_CON5)  
  4.     .lcdcon5    = S3C2410_LCDCON5_FRM565 |  
  5.               S3C2410_LCDCON5_INVVLINE |  
  6.               S3C2410_LCDCON5_INVVFRAME |  
  7.               S3C2410_LCDCON5_PWREN |  
  8.               S3C2410_LCDCON5_HWSWP,  
  9. #else  
  10.     .lcdcon5    = LCD_CON5,  
  11. #endif  
  12.     .type       = S3C2410_LCDCON1_TFT,  
  13.     .width      = LCD_WIDTH,  
  14.     .height     = LCD_HEIGHT,  
  15.     .pixclock   = LCD_PIXCLOCK,  
  16.     .xres       = LCD_WIDTH,  
  17.     .yres       = LCD_HEIGHT,  
  18.     .bpp        = 16,  
  19.     .left_margin    = LCD_LEFT_MARGIN + 1,  
  20.     .right_margin   = LCD_RIGHT_MARGIN + 1,  
  21.     .hsync_len  = LCD_HSYNC_LEN + 1,  
  22.     .upper_margin   = LCD_UPPER_MARGIN + 1,  
  23.     .lower_margin   = LCD_LOWER_MARGIN + 1,  
  24.     .vsync_len  = LCD_VSYNC_LEN + 1,  
  25. };  
二. 模块初始化
  1.s3c2410fb.c是内核的一个模块,在模块初始化函数中只是简单的调用了platform_driver_register把自己注册成为platform驱动。初始化函数如下:
[cpp] view plaincopy
  1. int __init s3c2410fb_init(void)  
  2. {  
  3.     int ret = platform_driver_register(&s3c2410fb_driver);  
  4.   
  5.     if (ret == 0)  
  6.         ret = platform_driver_register(&s3c2412fb_driver);  
  7.   
  8.     return ret;  
  9. }  
  platform_driver_register是platform类型驱动的注册函数,他传入一个platform_driver结构体。mini2440lcd驱动初始化了这样一个结构体。如下:
[cpp] view plaincopy
  1. static struct platform_driver s3c2410fb_driver = {  
  2.     .probe      = s3c2410fb_probe,  
  3.     .remove     = s3c2410fb_remove,  
  4.     .suspend    = s3c2410fb_suspend,  
  5.     .resume     = s3c2410fb_resume,  
  6.     .driver     = {  
  7.         .name   = "s3c2410-lcd",  
  8.         .owner  = THIS_MODULE,  
  9.     },  
  10. };  
  可以看出这里初始化了相应的函数,以及设备名称和拥有模块。其中 name="s3c2410-lcd"这个很重要,他是platform核心寻找相应platform设备的的依据。初始化的各个函数都需要lcd驱动程序编写。
三. linux设备模型相关函数
  1. 对应上面的platform_driver初始化用的函数:
s3c2410fb_probe      
s3c2410fb_remove
s3c2410fb_suspend,
s3c2410fb_resume,
    其中s3c2410fb_probe函数是调用platform_driver_register时,由platform_bus的match函数找到合适的lcd设备成功后调用的函数,完成初始化工作。下面重点分析这个函数。
  2. s3c2410fb_probe 函数分析
    这个函数只有一条语句就是调用s3c24xxfb_probe,下面是s3c24xxfb_probe函数,这个是lcd驱动最关键的函数。
[cpp] view plaincopy
  1. static int __init s3c24xxfb_probe(struct platform_device *pdev,  
  2.                   enum s3c_drv_type drv_type)  
  3. {  
  4.     struct s3c2410fb_info *info;  
  5.     struct s3c2410fb_display *display;  
  6.     struct fb_info *fbinfo;  
  7.     struct s3c2410fb_mach_info *mach_info;  
  8.     struct resource *res;  
  9.     int ret;  
  10.     int irq;  
  11.     int i;  
  12.     int size;  
  13.     u32 lcdcon1;  
  14.   
  15.     mach_info = pdev->dev.platform_data;  
  16.         //在/arch/arm/mach-s3c2440/mach-mini2440.c的mini2440_machine_init函数中,调用s3c24xx_fb_set_platdata(&mini2440_fb_info)  
  17.         //将mini2440_fb_info赋值给pdev->dev.paltform_data,所以这里得到的是mini2440_fb_info  
  18.     if (mach_info == NULL) {  
  19.         dev_err(&pdev->dev,  
  20.             "no platform data for lcd, cannot attach\n");  
  21.         return -EINVAL;  
  22.     }  
  23.   
  24.     if (mach_info->default_display >= mach_info->num_displays) {  
  25.         dev_err(&pdev->dev, "default is %d but only %d displays\n",  
  26.             mach_info->default_display, mach_info->num_displays);  
  27.         return -EINVAL;  
  28.     }  
  29.   
  30.     display = mach_info->displays + mach_info->default_display;  
  31.         //mach_info->displays = 0,mach_info->default_display = mini2440_lcd_cfg  
  32.         //所以display = mini2440_lcd_cfg  
  33.     irq = platform_get_irq(pdev, 0);  
  34.         //pdev是platfoem_device结构,这个函数是从platform_device占用的资源里取出irq号  
  35.     if (irq < 0) {  
  36.         dev_err(&pdev->dev, "no irq for device\n");  
  37.         return -ENOENT;  
  38.     }  
  39.   
  40.     fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);  
  41.         //framebuffer_alloc所做的事就是分配一个fb_info结构体,因为这个结构体最后有个通用指针*par,这个是设备自定义结构,在这里是s3c24fb_info  
  42.         //所以分配内存的时候在fb_info结构的大小基础上必须加上s3c2410fb_info结构的大小,这样才是这里的fb_info真正的大小  
  43.     if (!fbinfo)  
  44.         return -ENOMEM;  
  45.   
  46.     platform_set_drvdata(pdev, fbinfo);  
  47.   
  48.     info = fbinfo->par; //将info(s3c2410fb_info结构)指向新分配的fbinfo的par位置  
  49.     info->dev = &pdev->dev;  
  50.     info->drv_type = drv_type;  
  51.   
  52.     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  
  53.         //得到lcd控制器io内存的物理地址  
  54.     if (res == NULL) {  
  55.         dev_err(&pdev->dev, "failed to get memory registers\n");  
  56.         ret = -ENXIO;  
  57.         goto dealloc_fb;  
  58.     }  
  59.   
  60.     size = (res->end - res->start) + 1;  
  61.     info->mem = request_mem_region(res->start, size, pdev->name);  
  62.         //向内核请求所用的io内存,这里主要防止其他模块竞争,如果其他模块占用这块内存,函数就会返回NULL  
  63.     if (info->mem == NULL) {  
  64.         dev_err(&pdev->dev, "failed to get memory region\n");  
  65.         ret = -ENOENT;  
  66.         goto dealloc_fb;  
  67.     }  
  68.   
  69.     info->io = ioremap(res->start, size);  
  70.         //将物理内存映射成虚拟地址,以供内核使用  
  71.     if (info->io == NULL) {  
  72.         dev_err(&pdev->dev, "ioremap() of registers failed\n");  
  73.         ret = -ENXIO;  
  74.         goto release_mem;  
  75.     }  
  76.   
  77.     info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);  
  78.         // irq_base是lcd中断控制器寄存器对应的虚拟地址  
  79.     dprintk("devinit\n");  
  80.   
  81.     strcpy(fbinfo->fix.id, driver_name);  
  82.   
  83.     /* Stop the video */  
  84.     lcdcon1 = readl(info->io + S3C2410_LCDCON1);  
  85.     writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);  
  86.   
  87.     fbinfo->fix.type     = FB_TYPE_PACKED_PIXELS;  
  88.     fbinfo->fix.type_aux     = 0;  
  89.     fbinfo->fix.xpanstep     = 0;  
  90.     fbinfo->fix.ypanstep     = 0;  
  91.     fbinfo->fix.ywrapstep        = 0;  
  92.     fbinfo->fix.accel        = FB_ACCEL_NONE;  
  93.         //以上初始化fb_fix_screeninfo结构  
  94.     fbinfo->var.nonstd       = 0;  
  95.     fbinfo->var.activate     = FB_ACTIVATE_NOW;  
  96.     fbinfo->var.accel_flags     = 0;  
  97.     fbinfo->var.vmode        = FB_VMODE_NONINTERLACED;  
  98.         //以上初始化fb_var_screeninfo结构  
  99.     fbinfo->fbops            = &s3c2410fb_ops;  
  100.         // 这里将我们实现的函数与frambuffer核心的操作联系上  
  101.     fbinfo->flags            = FBINFO_FLAG_DEFAULT;  
  102.     fbinfo->pseudo_palette      = &info->pseudo_pal;  
  103.   
  104.     for (i = 0; i < 256; i++)  
  105.         info->palette_buffer[i] = PALETTE_BUFF_CLEAR;  
  106.   
  107.     ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);  
  108.         //注册中断处理函数,一般的lcd操作基本不需要中断  
  109.     if (ret) {  
  110.         dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);  
  111.         ret = -EBUSY;  
  112.         goto release_regs;  
  113.     }  
  114.   
  115.     info->clk = clk_get(NULL, "lcd");  
  116.     if (!info->clk || IS_ERR(info->clk)) {  
  117.         printk(KERN_ERR "failed to get lcd clock source\n");  
  118.         ret = -ENOENT;  
  119.         goto release_irq;  
  120.     }  
  121.   
  122.     clk_enable(info->clk);  
  123.         //以上操作使能lcd时钟  
  124.     dprintk("got and enabled clock\n");  
  125.   
  126.     msleep(1);  
  127.   
  128.     info->clk_rate = clk_get_rate(info->clk);  
  129.     /* find maximum required memory size for display */  
  130.     for (i = 0; i < mach_info->num_displays; i++) {  
  131.         unsigned long smem_len = mach_info->displays[i].xres; // = 240  
  132.   
  133.         smem_len *= mach_info->displays[i].yres; // = 320  
  134.         smem_len *= mach_info->displays[i].bpp;  // = 16  
  135.         smem_len >>= 3;                          //将位的个数转换成字节个数  
  136.         if (fbinfo->fix.smem_len < smem_len)  
  137.             fbinfo->fix.smem_len = smem_len;  
  138.     }  
  139.   
  140.     /* Initialize video memory */  
  141.     ret = s3c2410fb_map_video_memory(fbinfo);  
  142.         //这个函数主要功能就是分配一块内存,大小为上面计算的smem_len,并且将分配的内存的物理地址赋值给fbinfo->fix.smem_start  
  143.         //将虚拟地址赋值给fbinfo->screen_base  
  144.     if (ret) {  
  145.         printk(KERN_ERR "Failed to allocate video RAM: %d\n", ret);  
  146.         ret = -ENOMEM;  
  147.         goto release_clock;  
  148.     }  
  149.   
  150.     dprintk("got video memory\n");  
  151.   
  152.     fbinfo->var.xres = display->xres;  
  153.     fbinfo->var.yres = display->yres;  
  154.     fbinfo->var.bits_per_pixel = display->bpp;  
  155.         //这三个初始化很重要,对于下面的s3c2410fb_check_var尤其重要  
  156.     s3c2410fb_init_registers(fbinfo);  
  157.         //初始化lcd控制器的GPIO接口控制寄存器  
  158.   
  159.     s3c2410fb_check_var(&fbinfo->var, fbinfo);  
  160.         //这个函数根据fbinfo->var的xres,yres和bits_per_pixel选择相应的s3c2410fb_display结构,并将这个结构的各个域的值赋值给  
  161.         //fbinfo->var的相应成员。因为mini2440lcd驱动只有一个s3c2410fb_display结构就是mini2440_lcd_cfg,所以赋值的就是mini2440_lcd_cfg  
  162.     ret = s3c2410fb_cpufreq_register(info);  
  163.     if (ret < 0) {  
  164.         dev_err(&pdev->dev, "Failed to register cpufreq\n");  
  165.         goto free_video_memory;  
  166.     }  
  167.   
  168.     ret = register_framebuffer(fbinfo);  
  169.         //将fbinfo结构注册到frambuffer核心  
  170.     if (ret < 0) {  
  171.         printk(KERN_ERR "Failed to register framebuffer device: %d\n",  
  172.             ret);  
  173.         goto free_cpufreq;  
  174.     }  
  175.   
  176.     /* create device files */  
  177.     ret = device_create_file(&pdev->dev, &dev_attr_debug);  
  178.     if (ret) {  
  179.         printk(KERN_ERR "failed to add debug attribute\n");  
  180.     }  
  181.   
  182.     printk(KERN_INFO "fb%d: %s frame buffer device\n",  
  183.         fbinfo->node, fbinfo->fix.id);  
  184.   
  185.     return 0;  
  186.   
  187. free_cpufreq:  
  188.     s3c2410fb_cpufreq_deregister(info);  
  189. free_video_memory:  
  190.     s3c2410fb_unmap_video_memory(fbinfo);  
  191. release_clock:  
  192.     clk_disable(info->clk);  
  193.     clk_put(info->clk);  
  194. release_irq:  
  195.     free_irq(irq, info);  
  196. release_regs:  
  197.     iounmap(info->io);  
  198. release_mem:  
  199.     release_resource(info->mem);  
  200.     kfree(info->mem);  
  201. dealloc_fb:  
  202.     platform_set_drvdata(pdev, NULL);  
  203.     framebuffer_release(fbinfo);  
  204.     return ret;  
  205. }   
  从上面分析可以看出,这个函数主要做了下面几件事:
  (1) 从platform_device中获得s3c2410fb_mach_info结构体赋值给mach_info。这就得到了lcd控制器的所有初始配置。
  (2) 从mach_info中获得s3c2410fb_display结构体赋值给display。这样就得到了显示相关的初始配置。
  (3) 分配一个fb_info结构体fbinfo和一个s3c2410fb_info结构体info,并且将info指向fbinfo->par
  (4) 由pdev中所用的资源初始化info结构,主要初始化io内存,并映射虚拟地址。
  (5) 关闭lcd显示
  (6) 初始化fbinfo->fix,fbinfo->var 的部分域(不依赖配置信息的部分)
  (7) 初始化fbinfo->fbops为s3c2410fb_ops
  (8) 注册中断处理程序s3c2410fb_irq
  (9) 使能lcd时钟
  (10)为lcd设备分配显存,显存开始地址赋值给fbinfo->screen_base
  (11)初始化lcd控制器的io接口控制寄存器
  (12)用display中的值初始化fbinfo->var中相应的值(与显示配置相关的部分)
  (13)将fbinfo结构注册到frambuffer核心
四. frambuffer驱动模型fb_ops各函数的实现
  由s3c2410fb_ops结构可以看出,mini2440lcd驱动主要实现了下面几个函数:
s3c2410fb_check_var
s3c2410fb_set_par
s3c2410fb_blank
s3c2410fb_setcolreg
cfb_fillrect
cfb_copyarea
cfb_imageblit
  其中最重要的是s3c2410fb_set_par,这个函数根据fbinfo的值初始化了底层的lcd控制器,重点分析这个函数。他由fb_set_var调用,对应则frambufer核心ioctl中的FBIOPUT_VSCREENINFO命令。其他的函数也是为了完成lcd的相关功能而编写的,与具体实现的功能有关。s3c2410fb_set_par函数定义如下:
[cpp] view plaincopy
  1. static int s3c2410fb_set_par(struct fb_info *info)  
  2. {  
  3.     struct fb_var_screeninfo *var = &info->var;  
  4.   
  5.     switch (var->bits_per_pixel) {  
  6.     case 32:  
  7.     case 16:  
  8.     case 12:  
  9.         info->fix.visual = FB_VISUAL_TRUECOLOR;  
  10.         break;  
  11.     case 1:  
  12.         info->fix.visual = FB_VISUAL_MONO01;  
  13.         break;  
  14.     default:  
  15.         info->fix.visual = FB_VISUAL_PSEUDOCOLOR;  
  16.         break;  
  17.     }  
  18.   
  19.     info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8;  
  20.     /* activate this new configuration */  
  21.     s3c2410fb_activate_var(info);  
  22.     return 0;  
  23. }  
  可以看出,这个函数除了根据fbinfo的像素位来赋值fix.visual外,主要是调用了s3c2410fb_activate_var函数:
[cpp] view plaincopy
  1. static void s3c2410fb_activate_var(struct fb_info *info)  
  2. {  
  3.     struct s3c2410fb_info *fbi = info->par;  
  4.     void __iomem *regs = fbi->io;  
  5.     int type = fbi->regs.lcdcon1 & S3C2410_LCDCON1_TFT;  
  6.     struct fb_var_screeninfo *var = &info->var;  
  7.     int clkdiv;  
  8.   
  9.     clkdiv = DIV_ROUND_UP(s3c2410fb_calc_pixclk(fbi, var->pixclock), 2);  
  10.   
  11.     dprintk("%s: var->xres  = %d\n", __func__, var->xres);  
  12.     dprintk("%s: var->yres  = %d\n", __func__, var->yres);  
  13.     dprintk("%s: var->bpp   = %d\n", __func__, var->bits_per_pixel);  
  14.   
  15.     if (type == S3C2410_LCDCON1_TFT) {  
  16.         //判断lcd型号,我们的lcd是TFT型lcd,所以下面语句执行  
  17.         s3c2410fb_calculate_tft_lcd_regs(info, &fbi->regs);  
  18.                 //这个函数主要的功能就是将info中的lcd配置相关的值赋值给s3c2410fb_info结构的regs成员  
  19.                 //这个regs是一个s3c2410fb_hw结构,这个结构就是定义了5个lcd控制寄存器lcdcon1~5  
  20.         --clkdiv;  
  21.         if (clkdiv < 0)  
  22.             clkdiv = 0;  
  23.     } else {  
  24.         //如果是STN型的lcd,那么执行下面的函数。因为s3c2440的lcd控制器由有几个专门用于控制STN型lcd的寄存器,所以要单独设置  
  25.         s3c2410fb_calculate_stn_lcd_regs(info, &fbi->regs);  
  26.         if (clkdiv < 2)  
  27.             clkdiv = 2;  
  28.     }  
  29.   
  30.     fbi->regs.lcdcon1 |=  S3C2410_LCDCON1_CLKVAL(clkdiv);  
  31.     /* write new registers */  
  32.     dprintk("new register set:\n");  
  33.     dprintk("lcdcon[1] = 0x%08lx\n", fbi->regs.lcdcon1);  
  34.     dprintk("lcdcon[2] = 0x%08lx\n", fbi->regs.lcdcon2);  
  35.     dprintk("lcdcon[3] = 0x%08lx\n", fbi->regs.lcdcon3);  
  36.     dprintk("lcdcon[4] = 0x%08lx\n", fbi->regs.lcdcon4);  
  37.     dprintk("lcdcon[5] = 0x%08lx\n", fbi->regs.lcdcon5);  
  38.   
  39.     writel(fbi->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID,  
  40.         regs + S3C2410_LCDCON1);  
  41.     writel(fbi->regs.lcdcon2, regs + S3C2410_LCDCON2);  
  42.     writel(fbi->regs.lcdcon3, regs + S3C2410_LCDCON3);  
  43.     writel(fbi->regs.lcdcon4, regs + S3C2410_LCDCON4);  
  44.     writel(fbi->regs.lcdcon5, regs + S3C2410_LCDCON5);  
  45.         //将配置值写入五个寄存器  
  46.     /* set lcd address pointers */  
  47.     s3c2410fb_set_lcdaddr(info);  
  48.         //设置显存地址寄存器,设置为我们分配的那块内存,设置之后,lcd控制器就会在这块内存取数据送往lcd显示  
  49.   
  50.     fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID,  
  51.         //打开视频显示,这样lcd就可以正确显示了  
  52.     writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1);  
  53. }  
五. 总结
      mini2440lcd驱动分别涉及到了platform和frambuffer核心,利用这两个核心实现其功能。刚开始分析这个驱动的时候,总是感觉很乱没有重点。我想这个主要是和自己对frambuffer和lcd显示还不熟悉的原因。看了几天过后才逐渐有点眉目。最让人迷惑的就是如何设置lcd控制器的寄存器问题。我以前认为这个应该在probe函数中设置,一般来说这个函数检测设备状态,初始化设备,然后设备就绪,应用程序就可以操作了。但是在s3c2410fb_probe中只是设置了相关的GPIO接口寄存器,根本没有设置lcd控制寄存器。后来发现是在s3c2410fb_set_par函数中设置的。这个函数对应用户空间ioctl的FBIOPUT_VSCREENINFO。也就是说应用程序必须调用ioctl(fd,FBIOPUT_VSCREENINFO,struct fb_var_screeninfo *var)才能设置正确的lcd状态,但是这个命令有一个参数是fb_var_screeninfo结构,也就意味这应用程序必须填充这样一个结构,才可以调用ioctl。这样一来内核初始化的默认配置信息就没用了。唯一的办法是先调用ioctl(fd,FBIOGET_VSCREENINFO,struct fb_var_screeninfo *var)获得这个结构,然后修改之后在调用ioctl(fd,FBIOPUT_VSCREENINFO,struct fb_var_screeninfo *var)将修改的值写入。我在MiniGUI的源码中验证了这个推论,在MiniGUI的fbcon图形引擎中的FB_SetVideoMode函数中,有如下的调用
[cpp] view plaincopy
  1. if ( ioctl(console_fd, FBIOGET_VSCREENINFO, &vinfo) < 0 ) {  
  2.      GAL_SetError("NEWGAL>FBCON: Couldn't get console pixel format\n");  
  3.      FB_VideoQuit(this);  
  4.      return(-1);  
  5. }  
然后就是设置finfo中需要改变的值,最后有下面的代码来设置lcd控制寄存器器
[cpp] view plaincopy
  1. if ( ioctl(console_fd, FBIOPUT_VSCREENINFO, &vinfo) < 0 ) {  
  2.       vinfo.yres_virtual = height;  
  3.       if ( ioctl(console_fd, FBIOPUT_VSCREENINFO, &vinfo) < 0 ) {  
  4.            GAL_SetError("NEWGAL>FBCON: Couldn't set console screen info");  
  5.            return(NULL);  
  6.       }  
  7. }  

        关于lcd驱动还有好多知识要学,比如mmap操作。mmap是一般lcd应用程序运行的模式,他将显存映射到用户空间,提高系统的性能。因为自己只是为了熟悉一下这个驱动,所以没有深入看下去。

原创粉丝点击