• linux lcd 驱动


    写到后面的驱动的,不是所有驱动,都要自已去写,去写也是不现实的,以后工作中只需要移植修改就可以了。所以,学习驱动的框架,以及硬件的操作方式,在这部分驱动学习中应该着重强调,即要明确学习方法。

         帧缓冲设备为标准的字符型设备,在Linux中主设备号29,定义在/include/linux/major.h中的FB_MAJOR,次设备号定义帧缓冲的个数,最大允许有32个FrameBuffer,定义在/include/linux/fb.h中的FB_MAX,对应于文件系统下/dev /fb%d设备文件。

    1. 帧缓冲设备驱动在Linux子系统中的结构如下: 
    linux lcd 驱动 - 直到永远 - 直到永远的博客 
    我们从上面这幅图看,帧缓冲设备在Linux中也可以看做是一个完整的子系统,大体由fbmem.c和xxxfb.c组成。向上给应用程序提供完善的设备文件操作接口(即对FrameBuffer设备进行read、write、ioctl等操作),接口在Linux提供的fbmem.c文件中实现;向下提供了硬件操作的接口,只是这些接口Linux并没有提供实现,因为这要根据具体的LCD控制器硬件进行设置,所以这就是我们要做的事情了(即xxxfb.c 部分的实现)。

    从这上面可以看出,所有嵌入式linux中帧缓冲设备都是这种模式,其结构层次相当重要,如上图可见。其中,fbmem.c是内核早已实现好的与上层应用程序的接口程序,我们不需要去管,大概内容就是注册29号帧缓冲设备,以及一些file_operation的实现,这些函数接口都是与应用程序相对应的,其配制的硬件操作还要调用下层的,xxxfb.c。好,可见,我们要写驱动或者说,到时项目中移植LCD驱动时,只需要实现xxxfb.c部分。而这部分里的重点,主要是platform_driver的注册,当platform_bustype利用match匹配后,调用probe函数,也正是在这个函数中实现了,本部分程序的重要部分,fb_info等数据结构的初始化,相关寄存器的初始化,缓冲buffer的内存分配与映射,以及register_framebuffer的注册。

    其中,要我们实现的主要部分就是probe函数,fb_info结构体,调用register_framebuffer函数,以及以下结构的实现
    static struct fb_ops s3c2410fb_ops = {
     .owner  = THIS_MODULE,
     .fb_check_var = s3c2410fb_check_var,
     .fb_set_par = s3c2410fb_set_par,/*设置fb_info中的参数,主要是LCD的显示模式*/   这些部分的函数,会被fbmem.c上层调用
       .fb_blank = s3c2410fb_blank,//显示空白
     .fb_setcolreg = s3c2410fb_setcolreg,/
     .fb_fillrect = cfb_fillrect,
     .fb_copyarea = cfb_copyarea,
     .fb_imageblit = cfb_imageblit,
    };
    这里面主要是前四个函数的实现,后面的内核已帮我们实现好了。

    这里先看一下platform_device的注册过程:

    static struct resource s3c_lcd_resource[] = {//LCD设备及资源定义 文件在arch/arm/plat-s3c24xx/devs.c中
     [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
     }
    };
    接着是LCD屏幕配置信息

    //;NEC 3.5鈥滾CD 鐨勯厤缃�拰鍙傛暟璁剧疆
    #if defined(CONFIG_FB_S3C2410_N240320)   //arch/arm/mach-s3c2440/mach-mini2440.c
    #define LCD_WIDTH 240
    #define LCD_HEIGHT 320
    #define LCD_PIXCLOCK 100000
    #define LCD_RIGHT_MARGIN 36
    #define LCD_LEFT_MARGIN 19
    #define LCD_HSYNC_LEN 5
    #define LCD_UPPER_MARGIN 1
    #define LCD_LOWER_MARGIN 5
    #define LCD_VSYNC_LEN 1
    //;澶忔櫘8鈥滾CD 鐨勯厤缃�拰鍙傛暟璁剧疆
    #elif defined(CONFIG_FB_S3C2410_TFT640480)
    #define LCD_WIDTH 640
    #define LCD_HEIGHT 480
    #define LCD_PIXCLOCK 80000
    #define LCD_RIGHT_MARGIN 67
    #define LCD_LEFT_MARGIN 40
    #define LCD_HSYNC_LEN 31
    #define LCD_UPPER_MARGIN 25
    #define LCD_LOWER_MARGIN 5
    #define LCD_VSYNC_LEN 1
    //Sony 3.5鈥滾CD 鐨勯厤缃�拰鍙傛暟璁剧疆
    #elif defined(CONFIG_FB_S3C2410_X240320)
    #define LCD_WIDTH 240
    #define LCD_HEIGHT 320
    #define LCD_PIXCLOCK 170000
    #define LCD_RIGHT_MARGIN 25
    #define LCD_LEFT_MARGIN 0
    #define LCD_HSYNC_LEN 4
    #define LCD_UPPER_MARGIN 0
    #define LCD_LOWER_MARGIN 4
    #define LCD_VSYNC_LEN 9
    #define LCD_CON5 (S3C2410_LCDCON5_FRM565 | S3C2410_LCDCON5_INVVDEN | S3C2410_LCDCON5_INVVFRAME | S3C2410_LCDCON5_INVVLINE | S3C2410_LCDCON5_INVVCLK | S3C2410_LCDCON5_HWSWP ) 
    //;缁熷疂3.5鈥滾CD 鐨勯厤缃�拰鍙傛暟璁剧疆
    #elif defined(CONFIG_FB_S3C2410_T240320)
    #define LCD_WIDTH 240
    #define LCD_HEIGHT 320
    #define LCD_PIXCLOCK 146250//146250
    #define LCD_RIGHT_MARGIN 25
    #define LCD_LEFT_MARGIN 0
    #define LCD_HSYNC_LEN 4
    #define LCD_UPPER_MARGIN 1//1
    #define LCD_LOWER_MARGIN 4
    #define LCD_VSYNC_LEN 1//1
    //;缇ゅ垱7鈥滾CD 鐨勯厤缃�拰鍙傛暟璁剧疆
    #elif defined(CONFIG_FB_S3C2410_TFT800480)
    #define LCD_WIDTH 800
    #define LCD_HEIGHT 480
    #define LCD_PIXCLOCK 11463//40000
    #define LCD_RIGHT_MARGIN 67
    #define LCD_LEFT_MARGIN 40
    #define LCD_HSYNC_LEN 31
    #define LCD_UPPER_MARGIN 25
    #define LCD_LOWER_MARGIN 5
    #define LCD_VSYNC_LEN 1
    //;LCD2VGA(鍒嗚鲸鐜囦负1024x768)妯″潡鐨勯厤缃�拰鍙傛暟璁剧疆
    #elif defined(CONFIG_FB_S3C2410_VGA1024768)
    #define LCD_WIDTH 1024
    #define LCD_HEIGHT 768
    #define LCD_PIXCLOCK 80000
    #define LCD_RIGHT_MARGIN 15
    #define LCD_LEFT_MARGIN 199
    #define LCD_HSYNC_LEN 15
    #define LCD_UPPER_MARGIN 1
    #define LCD_LOWER_MARGIN 1
    #define LCD_VSYNC_LEN 1
    #define LCD_CON5 (S3C2410_LCDCON5_FRM565 | S3C2410_LCDCON5_HWSWP)
    #endif
    #if defined (LCD_WIDTH)
    static struct s3c2410fb_display mini2440_lcd_cfg __initdata = {
    #if !defined (LCD_CON5)
    .lcdcon5 = S3C2410_LCDCON5_FRM565 |
    S3C2410_LCDCON5_INVVLINE |
    S3C2410_LCDCON5_INVVFRAME |
    S3C2410_LCDCON5_PWREN |
    S3C2410_LCDCON5_HWSWP,
    #else
    .lcdcon5 = LCD_CON5,
    #endif
    .type = S3C2410_LCDCON1_TFT,
    .width = LCD_WIDTH,
    .height = LCD_HEIGHT,
    .pixclock = LCD_PIXCLOCK,
    .xres = LCD_WIDTH,
    .yres = LCD_HEIGHT,
    .bpp = 16,
    .left_margin = LCD_LEFT_MARGIN + 1,
    .right_margin = LCD_RIGHT_MARGIN + 1,
    .hsync_len = LCD_HSYNC_LEN + 1,
    .upper_margin = LCD_UPPER_MARGIN + 1,
    .lower_margin = LCD_LOWER_MARGIN + 1,
    .vsync_len = LCD_VSYNC_LEN + 1,
    };
    static struct s3c2410fb_mach_info mini2440_fb_info __initdata = {
    .displays = &mini2440_lcd_cfg,
    .num_displays = 1,
    .default_display = 0,
    .gpccon = 0xaa955699,
    .gpccon_mask = 0xffc003cc,
    .gpcup = 0x0000ffff,
    .gpcup_mask = 0xffffffff,
    .gpdcon = 0xaa95aaa1,
    .gpdcon_mask = 0xffc0fff0,
    .gpdup = 0x0000faff,
    .gpdup_mask = 0xffffffff,
    .lpcsel = 0xf82,
    };
    #endif

    接着把要注册的所有平台设备放到平台设备数组中去:

    static struct platform_device *mini2440_devices[] __initdata = {
     &s3c_device_usb,
     &s3c_device_rtc,
     &s3c_device_lcd,
     &s3c_device_wdt,
     &s3c_device_i2c0,
     &s3c_device_iis,
     &s3c_device_nand, //;鎶妌and flash 璁惧�娣诲姞鍒板紑鍙戞澘鐨勮�澶囧垪琛ㄧ粨鏋?
     &mini2440_device_eth,  //;鎶婄綉鍗″钩鍙拌�澶囨坊鍔犲埌寮?鍙戞澘鐨勮�澶囧垪琛ㄧ粨鏋?
     &s3c_device_sdi, //鎶奡D 鍗$粨鏋勮�澶囨坊鍔犲埌鐩�爣骞冲彴璁惧�闆嗕腑
      &s3c24xx_uda134x, //;娉ㄥ唽UDA1341 璁惧�骞冲彴鍒板唴鏍镐腑

    };

    最后就是平台设备的注册了:

    static void __init mini2440_machine_init(void)
    {
     #if defined (LCD_WIDTH)
      s3c24xx_fb_set_platdata(&mini2440_fb_info);//这里是将以上的屏幕信息添加到platform_data中去,见下
    #endif
     s3c_device_sdi.dev.platform_data = &mini2440_mmc_cfg;
     s3c_i2c0_set_platdata(NULL);
     s3c_device_nand.dev.platform_data = &mini2440_nand_info; 
     platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
     //smdk_machine_init();
    }

    void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)
    {
     struct s3c2410fb_mach_info *npd;

     npd = kmalloc(sizeof(*npd), GFP_KERNEL);
     if (npd) {
      memcpy(npd, pd, sizeof(*npd));
      s3c_device_lcd.dev.platform_data = npd;
     } else {
      printk(KERN_ERR "no memory for LCD platform data ");
     }
    }

    以下是驱动解析:

    /* linux/drivers/video/s3c2410fb.c
     * Copyright (c) 2004,2005 Arnaud Patard
     * Copyright (c) 2004-2008 Ben Dooks
     *
     * S3C2410 LCD Framebuffer Driver
     *
     * This file is subject to the terms and conditions of the GNU General Public
     * License.  See the file COPYING in the main directory of this archive for
     * more details.
     *
     * Driver based on skeletonfb.c, sa1100fb.c and others.
    */

    #include<linux/module.h>
    #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/platform_device.h>
    #include<linux/clk.h>
    #include<linux/cpufreq.h>

    #include<asm/io.h>
    #include<asm/div64.h>

    #include<asm/mach/map.h>
    #include<mach/regs-lcd.h>
    #include<mach/regs-gpio.h>
    #include<mach/fb.h>

    #ifdef CONFIG_PM
    #include<linux/pm.h>
    #endif

    #include"s3c2410fb.h"

    /* Debugging stuff */
    #ifdef CONFIG_FB_S3C2410_DEBUG
    staticint debug =1;
    #else
    staticint debug =0;
    #endif

    #define dprintk(msg...) if(debug){ printk(KERN_DEBUG "s3c2410fb: " msg);}

    /* useful functions */

    staticint is_s3c2412(struct s3c2410fb_info *fbi)
    {
     return(fbi->drv_type == DRV_S3C2412);
    }

    /* s3c2410fb_set_lcdaddr
     *
     * initialise lcd controller address pointers
     */
    staticvoid s3c2410fb_set_lcdaddr(struct fb_info *info)
    {
     unsignedlong saddr1, saddr2, saddr3;
     struct s3c2410fb_info *fbi = info->par;
     void __iomem *regs = fbi->io;

     saddr1  = info->fix.smem_start >>1;
     saddr2  = info->fix.smem_start;
     saddr2 += info->fix.line_length * info->var.yres;
     saddr2 >>=1;

     saddr3 = S3C2410_OFFSIZE(0)|
       S3C2410_PAGEWIDTH((info->fix.line_length /2)&0x3ff);

     dprintk("LCDSADDR1 = 0x%08lx ", saddr1);
     dprintk("LCDSADDR2 = 0x%08lx ", saddr2);
     dprintk("LCDSADDR3 = 0x%08lx ", saddr3);

     writel(saddr1, regs + S3C2410_LCDSADDR1);//写入三个地址寄存器
     writel(saddr2, regs + S3C2410_LCDSADDR2);
     writel(saddr3, regs + S3C2410_LCDSADDR3);
    }

    /* s3c2410fb_calc_pixclk()
     *
     * calculate divisor for clk->pixclk
     */
    staticunsignedint s3c2410fb_calc_pixclk(struct s3c2410fb_info *fbi,
           unsignedlong pixclk)
    {
     unsignedlong clk = fbi->clk_rate;
     unsignedlonglong div;

     /* pixclk is in picoseconds, our clock is in Hz
      *
      * Hz -> picoseconds is / 10^-12
      */

     div =(unsignedlonglong)clk * pixclk;
     div >>=12;   /* div / 2^12 */
     do_div(div,625*625UL*625);/* div / 5^12 */

     dprintk("pixclk %ld, divisor is %ld ", pixclk,(long)div);
     return div;
    }

    /*
     * s3c2410fb_check_var():
     * Get the video params out of 'var'. If a value doesn't fit, round it up,
     * if it's too big, return -EINVAL.
     *
     */
    staticint s3c2410fb_check_var(struct fb_var_screeninfo *var,
              struct fb_info *info)
    {
     struct s3c2410fb_info *fbi = info->par;
     struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data;
     struct s3c2410fb_display *display = NULL;
     struct s3c2410fb_display *default_display = mach_info->displays +
              mach_info->default_display;
     int type = default_display->type;
     unsigned i;

     dprintk("check_var(var=%p, info=%p) ",var, info);

     /* validate x/y resolution */
     /* choose default mode if possible */
     if(var->yres == default_display->yres &&
         var->xres == default_display->xres &&
         var->bits_per_pixel == default_display->bpp)
      display = default_display;
     else
      for(i =0; i < mach_info->num_displays; i++)
       if(type == mach_info->displays[i].type &&
           var->yres == mach_info->displays[i].yres &&
           var->xres == mach_info->displays[i].xres &&
           var->bits_per_pixel == mach_info->displays[i].bpp){
        display = mach_info->displays + i;
        break;
       }

     if(!display){
      dprintk("wrong resolution or depth %dx%d at %d bpp ",
       var->xres,var->yres,var->bits_per_pixel);
      return-EINVAL;
     }

     /* it is always the size as the display */
     var->xres_virtual = display->xres;
     var->yres_virtual = display->yres;
     var->height = display->height;
     var->width = display->width;

     /* copy lcd settings */
     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;

     fbi->regs.lcdcon5 = display->lcdcon5;
     /* set display type */
     fbi->regs.lcdcon1 = display->type;

     var->transp.offset =0;
     var->transp.length =0;
     /* set r/g/b positions */
     switch(var->bits_per_pixel){
     case1:
     case2:
     case4:
      var->red.offset =0;
      var->red.length =var->bits_per_pixel;
      var->green =var->red;
      var->blue =var->red;
      break;
     case8:
      if(display->type != S3C2410_LCDCON1_TFT){
       /* 8 bpp 332 */
       var->red.length  =3;
       var->red.offset  =5;
       var->green.length =3;
       var->green.offset =2;
       var->blue.length =2;
       var->blue.offset =0;
      }else{
       var->red.offset  =0;
       var->red.length  =8;
       var->green  =var->red;
       var->blue  =var->red;
      }
      break;
     case12:
      /* 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;

     default:
     case16:
      if(display->lcdcon5 & S3C2410_LCDCON5_FRM565){
       /* 16 bpp, 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{
       /* 16 bpp, 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;
     case32:
      /* 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;
     }
     return0;
    }

    /* s3c2410fb_calculate_stn_lcd_regs
     *
     * calculate register values from var settings
     */
    staticvoid s3c2410fb_calculate_stn_lcd_regs(conststruct fb_info *info,
              struct s3c2410fb_hw *regs)
    {
     conststruct s3c2410fb_info *fbi = info->par;
     conststruct fb_var_screeninfo *var=&info->var;
     int type = regs->lcdcon1 &~S3C2410_LCDCON1_TFT;
     int hs =var->xres >>2;
     unsigned wdly =(var->left_margin >>4)-1;
     unsigned wlh =(var->hsync_len >>4)-1;

     if(type != S3C2410_LCDCON1_STN4)
      hs >>=1;

     switch(var->bits_per_pixel){
     case1:
      regs->lcdcon1 |= S3C2410_LCDCON1_STN1BPP;
      break;
     case2:
      regs->lcdcon1 |= S3C2410_LCDCON1_STN2GREY;
      break;
     case4:
      regs->lcdcon1 |= S3C2410_LCDCON1_STN4GREY;
      break;
     case8:
      regs->lcdcon1 |= S3C2410_LCDCON1_STN8BPP;
      hs *=3;
      break;
     case12:
      regs->lcdcon1 |= S3C2410_LCDCON1_STN12BPP;
      hs *=3;
      break;

     default:
      /* invalid pixel depth */
      dev_err(fbi->dev,"invalid bpp %d ",
       var->bits_per_pixel);
     }
     /* update X/Y info */
     dprintk("setting horz: lft=%d, rt=%d, sync=%d ",
      var->left_margin,var->right_margin,var->hsync_len);

     regs->lcdcon2 = S3C2410_LCDCON2_LINEVAL(var->yres -1);

     if(wdly >3)
      wdly =3;

     if(wlh >3)
      wlh =3;

     regs->lcdcon3 = S3C2410_LCDCON3_WDLY(wdly)|
       S3C2410_LCDCON3_LINEBLANK(var->right_margin /8)|
       S3C2410_LCDCON3_HOZVAL(hs -1);

     regs->lcdcon4 = S3C2410_LCDCON4_WLH(wlh);
    }

    /* s3c2410fb_calculate_tft_lcd_regs
     *
     * calculate register values from var settings
     */
    staticvoid s3c2410fb_calculate_tft_lcd_regs(conststruct fb_info *info,
              struct s3c2410fb_hw *regs)
    {
     conststruct s3c2410fb_info *fbi = info->par;
     conststruct fb_var_screeninfo *var=&info->var;

     switch(var->bits_per_pixel){
     case1:
      regs->lcdcon1 |= S3C2410_LCDCON1_TFT1BPP;
      break;
     case2:
      regs->lcdcon1 |= S3C2410_LCDCON1_TFT2BPP;
      break;
     case4:
      regs->lcdcon1 |= S3C2410_LCDCON1_TFT4BPP;
      break;
     case8:
      regs->lcdcon1 |= S3C2410_LCDCON1_TFT8BPP;
      regs->lcdcon5 |= S3C2410_LCDCON5_BSWP |
         S3C2410_LCDCON5_FRM565;
      regs->lcdcon5 &=~S3C2410_LCDCON5_HWSWP;
      break;
     case16:
      regs->lcdcon1 |= S3C2410_LCDCON1_TFT16BPP;//16色模式
      regs->lcdcon5 &=~S3C2410_LCDCON5_BSWP;//关闭字节交换
      regs->lcdcon5 |= S3C2410_LCDCON5_HWSWP;//开启半字交换
      break;
     case32:
      regs->lcdcon1 |= S3C2410_LCDCON1_TFT24BPP;
      regs->lcdcon5 &=~(S3C2410_LCDCON5_BSWP |
           S3C2410_LCDCON5_HWSWP |
           S3C2410_LCDCON5_BPP24BL);
      break;
     default:
      /* invalid pixel depth */
      dev_err(fbi->dev,"invalid bpp %d ",
       var->bits_per_pixel);
     }
     /* update X/Y info */
     dprintk("setting vert: up=%d, low=%d, sync=%d ",
      var->upper_margin,var->lower_margin,var->vsync_len);

     dprintk("setting horz: lft=%d, rt=%d, sync=%d ",
      var->left_margin,var->right_margin,var->hsync_len);

     regs->lcdcon2 = S3C2410_LCDCON2_LINEVAL(var->yres -1)|
       S3C2410_LCDCON2_VBPD(var->upper_margin -1)|
       S3C2410_LCDCON2_VFPD(var->lower_margin -1)|
       S3C2410_LCDCON2_VSPW(var->vsync_len -1);

     regs->lcdcon3 = S3C2410_LCDCON3_HBPD(var->right_margin -1)|
       S3C2410_LCDCON3_HFPD(var->left_margin -1)|
       S3C2410_LCDCON3_HOZVAL(var->xres -1);

     regs->lcdcon4 = S3C2410_LCDCON4_HSPW(var->hsync_len -1);
    }

    /* s3c2410fb_activate_var
     *
     * activate (set) the controller from the given framebuffer
     * information
     */
    staticvoid s3c2410fb_activate_var(struct fb_info *info)
    {
     struct s3c2410fb_info *fbi = info->par;
     void __iomem *regs = fbi->io;
     int type = fbi->regs.lcdcon1 & S3C2410_LCDCON1_TFT;
     struct fb_var_screeninfo *var=&info->var;
     int clkdiv;

     clkdiv = DIV_ROUND_UP(s3c2410fb_calc_pixclk(fbi,var->pixclock),2);

     dprintk("%s: var->xres  = %d ", __func__,var->xres);
     dprintk("%s: var->yres  = %d ", __func__,var->yres);
     dprintk("%s: var->bpp   = %d ", __func__,var->bits_per_pixel);

     if(type == S3C2410_LCDCON1_TFT){
      s3c2410fb_calculate_tft_lcd_regs(info,&fbi->regs);
      --clkdiv;
      if(clkdiv <0)
       clkdiv =0;
     }else{
      s3c2410fb_calculate_stn_lcd_regs(info,&fbi->regs);
      if(clkdiv <2)
       clkdiv =2;
     }

     fbi->regs.lcdcon1 |=  S3C2410_LCDCON1_CLKVAL(clkdiv);

     /* write new registers */

     dprintk("new register set: ");
     dprintk("lcdcon[1] = 0x%08lx ", fbi->regs.lcdcon1);
     dprintk("lcdcon[2] = 0x%08lx ", fbi->regs.lcdcon2);
     dprintk("lcdcon[3] = 0x%08lx ", fbi->regs.lcdcon3);
     dprintk("lcdcon[4] = 0x%08lx ", fbi->regs.lcdcon4);
     dprintk("lcdcon[5] = 0x%08lx ", fbi->regs.lcdcon5);

     writel(fbi->regs.lcdcon1 &~S3C2410_LCDCON1_ENVID,
      regs + S3C2410_LCDCON1);
     writel(fbi->regs.lcdcon2, regs + S3C2410_LCDCON2);//把设置的数据,真正写入物理寄存器
     writel(fbi->regs.lcdcon3, regs + S3C2410_LCDCON3);
     writel(fbi->regs.lcdcon4, regs + S3C2410_LCDCON4);
     writel(fbi->regs.lcdcon5, regs + S3C2410_LCDCON5);

     /* set lcd address pointers */
     s3c2410fb_set_lcdaddr(info);//地址寄存器设置

     fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID,
     writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1);//开启视频功能
    }

    /*
     *      s3c2410fb_set_par - Alters the hardware state.
     *      @info: frame buffer structure that represents a single frame buffer
     *
     */
    staticint s3c2410fb_set_par(struct fb_info *info)
    {
     struct fb_var_screeninfo *var=&info->var;

     switch(var->bits_per_pixel){
     case32:
     case16:
     case12:
      info->fix.visual = FB_VISUAL_TRUECOLOR;//设置为16位真彩色
      break;
     case1:
      info->fix.visual = FB_VISUAL_MONO01;
      break;
     default:
      info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
      break;
     }

     info->fix.line_length =(var->xres_virtual *var->bits_per_pixel)/8;

     /* activate this new configuration */

     s3c2410fb_activate_var(info);
     return0;
    }

    staticvoid schedule_palette_update(struct s3c2410fb_info *fbi,
            unsignedint regno,unsignedint val)
    {
     unsignedlong flags;
     unsignedlong irqen;
     void __iomem *irq_base = fbi->irq_base;

     local_irq_save(flags);

     fbi->palette_buffer[regno]= val;

     if(!fbi->palette_ready){
      fbi->palette_ready =1;

      /* enable IRQ */
      irqen = readl(irq_base + S3C24XX_LCDINTMSK);
      irqen &=~S3C2410_LCDINT_FRSYNC;
      writel(irqen, irq_base + S3C24XX_LCDINTMSK);
     }

     local_irq_restore(flags);
    }

    /* from pxafb.c */
    staticinlineunsignedint chan_to_field(unsignedint chan,
          struct fb_bitfield *bf)
    {
     chan &=0xffff;
     chan >>=16- bf->length;
     return chan << bf->offset;
    }

    staticint s3c2410fb_setcolreg(unsigned regno,
              unsigned red,unsigned green,unsigned blue,
              unsigned transp,struct fb_info *info)
    {
     struct s3c2410fb_info *fbi = info->par;
     void __iomem *regs = fbi->io;
     unsignedint val;

     /* dprintk("setcol: regno=%d, rgb=%d,%d,%d ",
         regno, red, green, blue); */

     switch(info->fix.visual){
     case FB_VISUAL_TRUECOLOR:
      /* true-colour, use pseudo-palette */

      if(regno <16){
       u32 *pal = info->pseudo_palette;

       val  = chan_to_field(red,   &info->var.red);
       val |= chan_to_field(green,&info->var.green);
       val |= chan_to_field(blue,  &info->var.blue);

       pal[regno]= val;//假调色板
      }
      break;

     case FB_VISUAL_PSEUDOCOLOR:
      if(regno <256){
       /* currently assume RGB 5-6-5 mode */

       val  =(red   >>  0)&0xf800;
       val |=(green >>  5)&0x07e0;
       val |=(blue  >>11)&0x001f;

       writel(val, regs + S3C2410_TFTPAL(regno));//真正用到调色板
       schedule_palette_update(fbi, regno, val);
      }

      break;

     default:
      return1; /* unknown type */
     }

     return0;
    }

    /* s3c2410fb_lcd_enable
     *
     * shutdown the lcd controller
     */
    staticvoid s3c2410fb_lcd_enable(struct s3c2410fb_info *fbi,int enable)
    {
     unsignedlong flags;

     local_irq_save(flags);

     if(enable)//开关LCD显示
      fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID;
     else
      fbi->regs.lcdcon1 &=~S3C2410_LCDCON1_ENVID;

     writel(fbi->regs.lcdcon1, fbi->io + S3C2410_LCDCON1);

     local_irq_restore(flags);
    }


    /*
     *      s3c2410fb_blank
     * @blank_mode: the blank mode we want.
     * @info: frame buffer structure that represents a single frame buffer
     *
     * Blank the screen if blank_mode != 0, else unblank. Return 0 if
     * blanking succeeded, != 0 if un-/blanking failed due to e.g. a
     * video mode which doesn't support it. Implements VESA suspend
     * and powerdown modes on hardware that supports disabling hsync/vsync:
     *
     * Returns negative errno on error, or zero on success.
     *
     */
    staticint s3c2410fb_blank(int blank_mode,struct fb_info *info)
    {
     struct s3c2410fb_info *fbi = info->par;
     void __iomem *tpal_reg = fbi->io;

     dprintk("blank(mode=%d, info=%p) ", blank_mode, info);

     tpal_reg += is_s3c2412(fbi)? S3C2412_TPAL : S3C2410_TPAL;

     if(blank_mode == FB_BLANK_POWERDOWN){
      s3c2410fb_lcd_enable(fbi,0);
     }else{
      s3c2410fb_lcd_enable(fbi,1);
     }

     if(blank_mode == FB_BLANK_UNBLANK)
      writel(0x0, tpal_reg);//禁止临时调色板
     else{
      dprintk("setting TPAL to output 0x000000 ");
      writel(S3C2410_TPAL_EN, tpal_reg);//使能
     }

     return0;
    }

    staticint s3c2410fb_debug_show(struct device *dev,
        struct device_attribute *attr,char*buf)
    {
     return snprintf(buf, PAGE_SIZE,"%s ", debug ?"on":"off");
    }

    staticint s3c2410fb_debug_store(struct device *dev,
         struct device_attribute *attr,
         constchar*buf,size_t len)
    {
     if(len <1)
      return-EINVAL;

     if(strnicmp(buf,"on",2)==0||
         strnicmp(buf,"1",1)==0){
      debug =1;
      printk(KERN_DEBUG "s3c2410fb: Debug On");
     }elseif(strnicmp(buf,"off",3)==0||
         strnicmp(buf,"0",1)==0){
      debug =0;
      printk(KERN_DEBUG "s3c2410fb: Debug Off");
     }else{
      return-EINVAL;
     }

     return len;
    }

    static DEVICE_ATTR(debug,0666, s3c2410fb_debug_show, s3c2410fb_debug_store);

    staticstruct fb_ops s3c2410fb_ops ={
     .owner  = THIS_MODULE,
     .fb_check_var = s3c2410fb_check_var,
     .fb_set_par = s3c2410fb_set_par,/*设置fb_info中的参数,主要是LCD的显示模式*/
     .fb_blank = s3c2410fb_blank,//显示空白
     .fb_setcolreg = s3c2410fb_setcolreg,
     .fb_fillrect = cfb_fillrect,
     .fb_copyarea = cfb_copyarea,
     .fb_imageblit = cfb_imageblit,
    };

    /*
     * s3c2410fb_map_video_memory():
     * Allocates the DRAM memory for the frame buffer.  This buffer is
     * remapped into a non-cached, non-buffered, memory region to
     * allow palette and pixel writes to occur without flushing the
     * cache.  Once this area is remapped, all virtual memory
     * access to the video memory should occur at the new region.
     */
    staticint __init s3c2410fb_map_video_memory(struct fb_info *info)
    {
     struct s3c2410fb_info *fbi = info->par;
     dma_addr_t map_dma;//虚拟地址起始地址
     unsigned map_size = PAGE_ALIGN(info->fix.smem_len);/* to align the pointer to the (next) page boundary */

     dprintk("map_video_memory(fbi=%p) map_size %u ", fbi, map_size);

     //DMA内存分配,这里为非cache式的,即非缓冲的,保证了数据的一致和稳定
     info->screen_base = dma_alloc_writecombine(fbi->dev, map_size,
             &map_dma, GFP_KERNEL);

     if(info->screen_base){
      /* prevent initial garbage on screen */
      dprintk("map_video_memory: clear %p:%08x ",
       info->screen_base, map_size);
      //将分配的地址清零
      memset(info->screen_base,0x00, map_size);
      //将虚拟地址起始地址放于固定参数中
      info->fix.smem_start = map_dma;

      dprintk("map_video_memory: dma=%08lx cpu=%p size=%08x ",
       info->fix.smem_start, info->screen_base, map_size);
     }

     return info->screen_base ?0:-ENOMEM;
    }

    staticinlinevoid s3c2410fb_unmap_video_memory(struct fb_info *info)
    {
     struct s3c2410fb_info *fbi = info->par;

     dma_free_writecombine(fbi->dev, PAGE_ALIGN(info->fix.smem_len),
             info->screen_base, info->fix.smem_start);
    }

    staticinlinevoid modify_gpio(void __iomem *reg,
              unsignedlongset,unsignedlong mask)
    {
     unsignedlong tmp;

     tmp = readl(reg)&~mask;
     writel(tmp |set, reg);
    }

    /*
     * s3c2410fb_init_registers - Initialise all LCD-related registers
     */
    staticint s3c2410fb_init_registers(struct fb_info *info)
    {
     struct s3c2410fb_info *fbi = info->par;//获得s3c24xxfb_probe中struct s3c2410fb_info *info的值
     struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data;
     unsignedlong flags;
     void __iomem *regs = fbi->io;
     void __iomem *tpal;
     void __iomem *lpcsel;

     if(is_s3c2412(fbi)){
      tpal = regs + S3C2412_TPAL;
      lpcsel = regs + S3C2412_TCONSEL;
     }else{
      tpal = regs + S3C2410_TPAL;
      lpcsel = regs + S3C2410_LPCSEL;
     }

     /* Initialise LCD with values from haret */

     local_irq_save(flags); //关闭cpu所有中断

     /* modify the gpio(s) with interrupts set (bjd) */
     //设置多功能引脚,选为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); //恢复cpu所有中断

     dprintk("LPCSEL    = 0x%08lx ", mach_info->lpcsel);
     writel(mach_info->lpcsel, lpcsel);

     dprintk("replacing TPAL %08x ", readl(tpal));

     /* ensure temporary palette disabled */
     writel(0x00, tpal);//先关闭临时调色板

     return 0;
    }

    staticvoid s3c2410fb_write_palette(struct s3c2410fb_info *fbi)
    {
     unsignedint i;
     void __iomem *regs = fbi->io;

     fbi->palette_ready =0;

     for(i =0; i <256; i++){
      unsignedlong ent = fbi->palette_buffer[i];
      if(ent == PALETTE_BUFF_CLEAR)
       continue;

      writel(ent, regs + S3C2410_TFTPAL(i)); //写入调色板RAM

      /* it seems the only way to know exactly
       *if the palette wrote ok,is to check
       * to see if the value verifies ok
       */

      if (readw(regs + S3C2410_TFTPAL(i)) == ent)
       fbi->palette_buffer[i] = PALETTE_BUFF_CLEAR;
      else
       fbi->palette_ready = 1;   /*retry*/
     }
    }

    static irqreturn_t s3c2410fb_irq(int irq, void *dev_id)
    {
     struct s3c2410fb_info *fbi = dev_id;
     void __iomem *irq_base = fbi->irq_base;
     unsigned long lcdirq = readl(irq_base + S3C24XX_LCDINTPND);

     if (lcdirq & S3C2410_LCDINT_FRSYNC) {//帧同步中断
      if(fbi->palette_ready)//如果用的真彩色模式,感觉中断没被用到
       s3c2410fb_write_palette(fbi);

      writel(S3C2410_LCDINT_FRSYNC, irq_base + S3C24XX_LCDINTPND);//清LCD总挂起寄存器
      writel(S3C2410_LCDINT_FRSYNC, irq_base + S3C24XX_LCDSRCPND);//清LCD子挂起寄存器
     }

     return IRQ_HANDLED;
    }

    #ifdef CONFIG_CPU_FREQ

    static int s3c2410fb_cpufreq_transition(struct notifier_block *nb,
         unsignedlong val,void*data)
    {
     struct cpufreq_freqs *freqs = data;
     struct s3c2410fb_info *info;
     struct fb_info *fbinfo;
     long delta_f;

     info = container_of(nb,struct s3c2410fb_info, freq_transition);
     fbinfo = platform_get_drvdata(to_platform_device(info->dev));

     /* work out change, <0 for speed-up */
     delta_f = info->clk_rate - clk_get_rate(info->clk);

     if((val == CPUFREQ_POSTCHANGE && delta_f >0)||
         (val == CPUFREQ_PRECHANGE && delta_f <0)){
      info->clk_rate = clk_get_rate(info->clk);
      s3c2410fb_activate_var(fbinfo);
     }

     return0;
    }

    staticinlineint s3c2410fb_cpufreq_register(struct s3c2410fb_info *info)
    {
     info->freq_transition.notifier_call = s3c2410fb_cpufreq_transition;

     return cpufreq_register_notifier(&info->freq_transition,
          CPUFREQ_TRANSITION_NOTIFIER);
    }

    staticinlinevoid s3c2410fb_cpufreq_deregister(struct s3c2410fb_info *info)
    {
     cpufreq_unregister_notifier(&info->freq_transition,
            CPUFREQ_TRANSITION_NOTIFIER);
    }

    #else
    staticinlineint s3c2410fb_cpufreq_register(struct s3c2410fb_info *info)
    {
     return0;
    }

    staticinlinevoid s3c2410fb_cpufreq_deregister(struct s3c2410fb_info *info)
    {
    }
    #endif


    staticchar driver_name[]="s3c2410fb";

    staticint __init s3c24xxfb_probe(struct platform_device *pdev,
          enum s3c_drv_type drv_type)
    {
     struct s3c2410fb_info *info;//驱动相关参数信息
     struct s3c2410fb_display *display;//屏幕参数
     struct fb_info *fbinfo;//核心数据结构
     struct s3c2410fb_mach_info *mach_info;
     struct resource *res;
     int ret;
     int irq;
     int i;
     int size;
     u32 lcdcon1;

     mach_info = pdev->dev.platform_data;//取出设置好的mach_info指针
     if(mach_info == NULL){
      dev_err(&pdev->dev,
       "no platform data for lcd, cannot attach ");
      return-EINVAL;
     }

     if(mach_info->default_display >= mach_info->num_displays){
      dev_err(&pdev->dev,"default is %d but only %d displays ",
       mach_info->default_display, mach_info->num_displays);
      return-EINVAL;
     }
     //获取lcd屏幕参数
     display = mach_info->displays + mach_info->default_display;
     //获取irq中断资源,其它此函数也是调用platform_get_resource
     irq = platform_get_irq(pdev,0);
     if(irq <0){
      dev_err(&pdev->dev,"no irq for device ");
      return-ENOENT;
     }
     //分配fbinfo结构体
     fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info),&pdev->dev);
     if(!fbinfo)
      return-ENOMEM;
     //设置platform_device->device->device_private->(void *driver_data)=fbinfo
     //空类型可存任何数据结构,这里存在这的意思是后面通过私有数据结构可用到其它地方
     platform_set_drvdata(pdev, fbinfo);

     //初始info相关部分
     info = fbinfo->par;//这里很重要,后面的参数设置要用
     info->dev =&pdev->dev;
     info->drv_type = drv_type;
     //获取IO资源
     res = platform_get_resource(pdev, IORESOURCE_MEM,0);
     if(res == NULL){
      dev_err(&pdev->dev,"failed to get memory registers ");
      ret =-ENXIO;
      goto dealloc_fb;
     }
     //IO内存操作:申请->映射->访问->释放
     //操作函数:request_mem_region->ioremap->writel etc.->iounmap+release_mem_region
     //有时release_mem_region可以这样用release_resource+kfree这点,可以参看
     //release_mem_region代码实现可知
     size =(res->end- res->start)+1;
     info->mem = request_mem_region(res->start, size, pdev->name);//申请
     if(info->mem == NULL){
      dev_err(&pdev->dev,"failed to get memory region ");
      ret =-ENOENT;
      goto dealloc_fb;
     }

     info->io = ioremap(res->start, size);//映射
     if(info->io == NULL){
      dev_err(&pdev->dev,"ioremap() of registers failed ");
      ret =-ENXIO;
      goto release_mem;
     }

     info->irq_base = info->io +((drv_type == DRV_S3C2412)? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);

     dprintk("devinit ");

     strcpy(fbinfo->fix.id, driver_name);

     /* Stop the video */
     lcdcon1 = readl(info->io + S3C2410_LCDCON1);
     writel(lcdcon1 &~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);

     //初始化固定参数
     fbinfo->fix.type     = FB_TYPE_PACKED_PIXELS;
     fbinfo->fix.type_aux     =0;
     fbinfo->fix.xpanstep     =0;
     fbinfo->fix.ypanstep     =0;
     fbinfo->fix.ywrapstep     =0;
     fbinfo->fix.accel     = FB_ACCEL_NONE;
     ////初始化可变参数
     fbinfo->var.nonstd     =0;
     fbinfo->var.activate     = FB_ACTIVATE_NOW;
     fbinfo->var.accel_flags     =0;
     fbinfo->var.vmode     = FB_VMODE_NONINTERLACED;

     //操作函数集,这里才是上层fbmem.c中read/write的最终实现函数
     fbinfo->fbops      =&s3c2410fb_ops;
     fbinfo->flags      = FBINFO_FLAG_DEFAULT;
     fbinfo->pseudo_palette      =&info->pseudo_pal;

     //初始化调色板
     for(i =0; i <256; i++)
      info->palette_buffer[i]= PALETTE_BUFF_CLEAR;

     //注册中断
     ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);
     if(ret){
      dev_err(&pdev->dev,"cannot get irq %d - err %d ", irq, ret);
      ret =-EBUSY;
      goto release_regs;
     }

     //获取总线时钟
     info->clk = clk_get(NULL,"lcd");
     if(!info->clk || IS_ERR(info->clk)){
      printk(KERN_ERR "failed to get lcd clock source ");
      ret =-ENOENT;
      goto release_irq;
     }

     clk_enable(info->clk);
     dprintk("got and enabled clock ");

     msleep(1);
     //获取时钟频率,这里=HCLK=100M
     info->clk_rate = clk_get_rate(info->clk);

     /* find maximum required memory size for display */
     for(i =0; i < mach_info->num_displays; i++){
      unsignedlong smem_len = mach_info->displays[i].xres;

      smem_len *= mach_info->displays[i].yres;
      smem_len *= mach_info->displays[i].bpp;
      smem_len >>=3;//计算显存大小
      if(fbinfo->fix.smem_len < smem_len)
       fbinfo->fix.smem_len = smem_len;
     }

     /* Initialize video memory */
     //分配显存,并映射
     ret = s3c2410fb_map_video_memory(fbinfo);
     if(ret){
      printk(KERN_ERR "Failed to allocate video RAM: %d ", ret);
      ret =-ENOMEM;
      goto release_clock;
     }

     dprintk("got video memory ");

     //屏幕尺寸及颜色位数填写
     fbinfo->var.xres = display->xres;
     fbinfo->var.yres = display->yres;
     fbinfo->var.bits_per_pixel = display->bpp;

     //Initialise all LCD-related registers
     s3c2410fb_init_registers(fbinfo);

     s3c2410fb_check_var(&fbinfo->var, fbinfo);//检查并设置可变参数

     ret = s3c2410fb_cpufreq_register(info); //在s3c2440中没定义,因为CONFIG_CPU_FREQ没定义
     if(ret <0){
      dev_err(&pdev->dev,"Failed to register cpufreq ");
      goto free_video_memory;
     }

     ret = register_framebuffer(fbinfo);
     if(ret <0){
      printk(KERN_ERR "Failed to register framebuffer device: %d ",
       ret);
      goto free_cpufreq;
     }

     /* create device files */
     ret = device_create_file(&pdev->dev,&dev_attr_debug);
     if(ret){
      printk(KERN_ERR "failed to add debug attribute ");
     }

     printk(KERN_INFO "fb%d: %s frame buffer device ",
      fbinfo->node, fbinfo->fix.id);

     return0;

     free_cpufreq:
     s3c2410fb_cpufreq_deregister(info);
    free_video_memory:
     s3c2410fb_unmap_video_memory(fbinfo);
    release_clock:
     clk_disable(info->clk);
     clk_put(info->clk);
    release_irq:
     free_irq(irq, info);
    release_regs:
     iounmap(info->io);
    release_mem:
     release_resource(info->mem);
     kfree(info->mem);
    dealloc_fb:
     platform_set_drvdata(pdev, NULL);
     framebuffer_release(fbinfo);
     return ret;
    }

    staticint __init s3c2410fb_probe(struct platform_device *pdev)
    {
     return s3c24xxfb_probe(pdev, DRV_S3C2410);
    }

    staticint __init s3c2412fb_probe(struct platform_device *pdev)
    {
     return s3c24xxfb_probe(pdev, DRV_S3C2412);
    }


    /*
     *  Cleanup
     */
    staticint s3c2410fb_remove(struct platform_device *pdev)
    {
     struct fb_info *fbinfo = platform_get_drvdata(pdev);
     struct s3c2410fb_info *info = fbinfo->par;
     int irq;

     unregister_framebuffer(fbinfo);
     s3c2410fb_cpufreq_deregister(info);

     s3c2410fb_lcd_enable(info,0);
     msleep(1);

     s3c2410fb_unmap_video_memory(fbinfo);

     if(info->clk){
      clk_disable(info->clk);
      clk_put(info->clk);
      info->clk = NULL;
     }

     irq = platform_get_irq(pdev,0);
     free_irq(irq, info);

     iounmap(info->io);

     release_resource(info->mem);
     kfree(info->mem);

     platform_set_drvdata(pdev, NULL);
     framebuffer_release(fbinfo);

     return0;
    }

    #ifdef CONFIG_PM

    /* suspend and resume support for the lcd controller */
    staticint s3c2410fb_suspend(struct platform_device *dev,pm_message_t state)
    {
     struct fb_info    *fbinfo = platform_get_drvdata(dev);
     struct s3c2410fb_info *info = fbinfo->par;

     s3c2410fb_lcd_enable(info,0);

     /* sleep before disabling the clock, we need to ensure
      * the LCD DMA engine is not going to get back on the bus
      * before the clock goes off again (bjd) */

     msleep(1);
     clk_disable(info->clk);

     return0;
    }

    staticint s3c2410fb_resume(struct platform_device *dev)
    {
     struct fb_info    *fbinfo = platform_get_drvdata(dev);
     struct s3c2410fb_info *info = fbinfo->par;

     clk_enable(info->clk);
     msleep(1);

     s3c2410fb_init_registers(fbinfo);

     /* re-activate our display after resume */
     s3c2410fb_activate_var(fbinfo);
     s3c2410fb_blank(FB_BLANK_UNBLANK, fbinfo);

     return0;
    }

    #else
    #define s3c2410fb_suspend NULL
    #define s3c2410fb_resume  NULL
    #endif

    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,
     },
    };

    staticstruct platform_driver s3c2412fb_driver ={
     .probe  = s3c2412fb_probe,
     .remove  = s3c2410fb_remove,
     .suspend = s3c2410fb_suspend,
     .resume  = s3c2410fb_resume,
     .driver  ={
      .name ="s3c2412-lcd",
      .owner = THIS_MODULE,
     },
    };

    int __init s3c2410fb_init(void)
    {
     int ret = platform_driver_register(&s3c2410fb_driver);

     if(ret ==0)
      ret = platform_driver_register(&s3c2412fb_driver);

     return ret;
    }

    staticvoid __exit s3c2410fb_cleanup(void)
    {
     platform_driver_unregister(&s3c2410fb_driver);
     platform_driver_unregister(&s3c2412fb_driver);
    }

    module_init(s3c2410fb_init);
    module_exit(s3c2410fb_cleanup);

    MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>, "
           "Ben Dooks <ben-linux@fluff.org>");
    MODULE_DESCRIPTION("Framebuffer driver for the s3c2410");
    MODULE_LICENSE("GPL");
    MODULE_ALIAS("platform:s3c2410-lcd");
    MODULE_ALIAS("platform:s3c2412-lcd");

    
    
    

    测试应用程序:

    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <linux/fb.h>
    #include <sys/mman.h>

    int main () {
        int fp=0;
        struct fb_var_screeninfo vinfo;
        struct fb_fix_screeninfo finfo;
        long screensize=0;
        char *fbp = 0;
        int x = 0, y = 0;
        long location = 0;
        fp = open ("/dev/fb0",O_RDWR);

        if (fp < 0){
            printf("Error : Can not open framebuffer device ");
            exit(1);
        }

        if (ioctl(fp,FBIOGET_FSCREENINFO,&finfo)){
            printf("Error reading fixed information ");
            exit(2);
        }
        if (ioctl(fp,FBIOGET_VSCREENINFO,&vinfo)){
            printf("Error reading variable information ");
            exit(3);
        }

        screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;   //单帧画面空间
        /*这就是把fp所指的文件中从开始到screensize大小的内容给映射出来,得到一个指向这块空间的指针*/
        fbp =(char *) mmap (0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fp,0);
        if ((int) fbp == -1)
        {
             printf ("Error: failed to map framebuffer device to memory. ");
             exit (4);
        }
        /*这是你想画的点的位置坐标,(0,0)点在屏幕左上角*/
        for(x=100;x<150;x++)
       {
            for(y=100;y<150;y++)
           {
                 location = x * (vinfo.bits_per_pixel / 8) + y  *  finfo.line_length;

                 *(fbp + location) = 255;  /* 蓝色的色深 */  /*直接赋值来改变屏幕上某点的颜色*/
                 *(fbp + location + 1) = 0; /* 绿色的色深*/   /*注明:这几个赋值是针对每像素四字节来设置的,如果针对每像素2字节,*/
                 *(fbp + location + 2) = 0; /* 红色的色深*/   /*比如RGB565,则需要进行转化*/
                 *(fbp + location + 3) = 0;  /* 是否透明*/ 
             } 
        }
        munmap (fbp, screensize); /*解除映射*/
        close (fp);    /*关闭文件*/
        return 0;

    }
    通过mmap函数映射后,可直接操作设备缓冲区。

    相对应的fbmem.c中的iotcl和mmap函数很重要。

    static int
    fb_mmap(struct file *file, struct vm_area_struct * vma)
    {
     int fbidx = iminor(file->f_path.dentry->d_inode);
     struct fb_info *info = registered_fb[fbidx];
     struct fb_ops *fb = info->fbops;
     unsigned long off;
     unsigned long start;
     u32 len;

     if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
      return -EINVAL;
     off = vma->vm_pgoff << PAGE_SHIFT;
     if (!fb)
      return -ENODEV;
     mutex_lock(&info->mm_lock);
     if (fb->fb_mmap) {
      int res;
      res = fb->fb_mmap(info, vma);
      mutex_unlock(&info->mm_lock);
      return res;
     }

     /* frame buffer memory */
     start = info->fix.smem_start;
     len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);
     if (off >= len) {
      /* memory mapped io */
      off -= len;
      if (info->var.accel_flags) {
       mutex_unlock(&info->mm_lock);
       return -EINVAL;
      }
      start = info->fix.mmio_start;
      len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len);
     }
     mutex_unlock(&info->mm_lock);
     start &= PAGE_MASK;
     if ((vma->vm_end - vma->vm_start + off) > len)
      return -EINVAL;
     off += start;
     vma->vm_pgoff = off >> PAGE_SHIFT;
     /* This is an IO map - tell maydump to skip this VMA */
     vma->vm_flags |= VM_IO | VM_RESERVED;
     fb_pgprotect(file, vma, off);
     if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
            vma->vm_end - vma->vm_start, vma->vm_page_prot))//核心部分,建立页表映射
      return -EAGAIN;
     return 0;
    }

    linux lcd 驱动

    From: http://www.cnblogs.com/ganrui/
  • 相关阅读:
    Ubuntu 设置网卡固定IP
    gawk Notes(2)
    再读simpledb 之 存储的实现
    [zZ]HDFSRAID使用Erasure Code来实现HDFS的数据冗余
    初识gawk, gawk Notes(1)
    gawk notes(3)
    Shell Notes(2)
    凶残的突击面试
    Google 图片下载工具
    Shell Notes(3)
  • 原文地址:https://www.cnblogs.com/ganrui/p/3691865.html
Copyright © 2020-2023  润新知