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\n");
 }
}

以下是驱动解析:

/* 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\n", saddr1);
 dprintk("LCDSADDR2 = 0x%08lx\n", saddr2);
 dprintk("LCDSADDR3 = 0x%08lx\n", 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\n", 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)\n",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\n",
   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\n",
   var->bits_per_pixel);
 }
 /* update X/Y info */
 dprintk("setting horz: lft=%d, rt=%d, sync=%d\n",
  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\n",
   var->bits_per_pixel);
 }
 /* update X/Y info */
 dprintk("setting vert: up=%d, low=%d, sync=%d\n",
  var->upper_margin,var->lower_margin,var->vsync_len);

 dprintk("setting horz: lft=%d, rt=%d, sync=%d\n",
  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\n", __func__,var->xres);
 dprintk("%s: var->yres  = %d\n", __func__,var->yres);
 dprintk("%s: var->bpp   = %d\n", __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:\n");
 dprintk("lcdcon[1] = 0x%08lx\n", fbi->regs.lcdcon1);
 dprintk("lcdcon[2] = 0x%08lx\n", fbi->regs.lcdcon2);
 dprintk("lcdcon[3] = 0x%08lx\n", fbi->regs.lcdcon3);
 dprintk("lcdcon[4] = 0x%08lx\n", fbi->regs.lcdcon4);
 dprintk("lcdcon[5] = 0x%08lx\n", 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\n",
     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)\n", 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\n");
  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\n", 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\n", 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\n",
   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\n",
   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\n", mach_info->lpcsel);
 writel(mach_info->lpcsel, lpcsel);

 dprintk("replacing TPAL %08x\n", 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\n");
  return-EINVAL;
 }

 if(mach_info->default_display >= mach_info->num_displays){
  dev_err(&pdev->dev,"default is %d but only %d displays\n",
   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\n");
  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\n");
  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\n");
  ret =-ENOENT;
  goto dealloc_fb;
 }

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

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

 dprintk("devinit\n");

 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\n", 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\n");
  ret =-ENOENT;
  goto release_irq;
 }

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

 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\n", ret);
  ret =-ENOMEM;
  goto release_clock;
 }

 dprintk("got video memory\n");

 //屏幕尺寸及颜色位数填写
 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\n");
  goto free_video_memory;
 }

 ret = register_framebuffer(fbinfo);
 if(ret <0){
  printk(KERN_ERR "Failed to register framebuffer device: %d\n",
   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\n");
 }

 printk(KERN_INFO "fb%d: %s frame buffer device\n",
  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\n");
        exit(1);
    }

    if (ioctl(fp,FBIOGET_FSCREENINFO,&finfo)){
        printf("Error reading fixed information\n");
        exit(2);
    }
    if (ioctl(fp,FBIOGET_VSCREENINFO,&vinfo)){
        printf("Error reading variable information\n");
        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.\n");
         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 驱动

linux lcd 驱动,布布扣,bubuko.com

linux lcd 驱动

上一篇:解决myeclipse中struts2 bug问题包的替换问题


下一篇:《linux 内核完全剖析》 chapter 5 Linux内核体系结构