mini2440驅動分析之LCD

mini2440驅動分析之LCD
        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 結構
struct fb_info {
	int node;
	int flags;
	struct mutex lock;		/* Lock for open/release/ioctl funcs */
	struct mutex mm_lock;		/* Lock for fb_mmap and smem_* fields */
	struct fb_var_screeninfo var;	/* Current var */
	struct fb_fix_screeninfo fix;	/* Current fix */
	struct fb_monspecs monspecs;	/* Current Monitor specs */
	struct work_struct queue;	/* Framebuffer event queue */
	struct fb_pixmap pixmap;	/* Image hardware mapper */
	struct fb_pixmap sprite;	/* Cursor hardware mapper */
	struct fb_cmap cmap;		/* Current cmap */
	struct list_head modelist;      /* mode list */
	struct fb_videomode *mode;	/* current mode */

#ifdef CONFIG_FB_BACKLIGHT
	/* assigned backlight device */
	/* set before framebuffer registration, 
	   remove after unregister */
	struct backlight_device *bl_dev;

	/* Backlight level curve */
	struct mutex bl_curve_mutex;	
	u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
#ifdef CONFIG_FB_DEFERRED_IO
	struct delayed_work deferred_work;
	struct fb_deferred_io *fbdefio;
#endif

	struct fb_ops *fbops;
	struct device *device;		/* This is the parent */
	struct device *dev;		/* This is this fb device */   
	int class_flag;                    /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
	struct fb_tile_ops *tileops;    /* Tile Blitting */
#endif
	char __iomem *screen_base;	/* Virtual address */
	unsigned long screen_size;	/* Amount of ioremapped VRAM or 0 */ 
	void *pseudo_palette;		/* Fake palette of 16 colors */ 
#define FBINFO_STATE_RUNNING	0
#define FBINFO_STATE_SUSPENDED	1
	u32 state;			/* Hardware state i.e suspend */
	void *fbcon_par;                /* fbcon use-only private area */
	/* From here on everything is device dependent */
	void *par;
	/* we need the PCI or similiar aperture base/size not
	   smem_start/size as smem_start may just be an object
	   allocated inside the aperture so may not actually overlap */
	resource_size_t aperture_base;
	resource_size_t aperture_size;
};
    這個結構是frambuffer驅動的基本數據結構,裏面包含了幀緩存設備的所有信息,每一個註冊成frambuffer接口的設備都應該聲明並初始化這樣一個結構。register_framebuffer 函數的參數就是這樣一個結構,fb_info在mini2440lcd驅動中是在s3c24xxfb_probe函數中分配並初始化的。其中struct fb_var_screeninfo結構包含了lcd顯示中可以改變的信息,結構如下:
struct fb_var_screeninfo {
	__u32 xres;			/* 視口水平分辨率		*/
	__u32 yres;
	__u32 xres_virtual;		/* 虛擬屏幕水平分辨率		*/
	__u32 yres_virtual;
	__u32 xoffset;			/* 視口與虛擬屏幕水平分辨率偏移 */
	__u32 yoffset;			


	__u32 bits_per_pixel;		/* 像素的位數			*/
	__u32 grayscale;		/* 灰度標誌,如果爲1代表是灰度 */


	struct fb_bitfield red;		/* 如果是真彩色,這個是顏色位,如果不是那麼只有結構的大小重要,其他表示的信息無關緊要 */
	struct fb_bitfield green;	
	struct fb_bitfield blue;
	struct fb_bitfield transp;	/* 透明度		*/	


	__u32 nonstd;			/* 非標準顏色表示標誌位 */
	__u32 activate;			/* 參照 FB_ACTIVATE_*		*/
	__u32 height;			/* 在內存地址空間的長度    */
	__u32 width;			/* 在內存地址空間的寬度     */


	__u32 accel_flags;		/* (不用了) 參照 fb_info.flags */


	/* 時序: 以下所有的值單位都是pixclock, 當然除了pixclock */
	__u32 pixclock;			/* 每秒像素值 */
	__u32 left_margin;		/* 從sync信號到顯示真正的像素的時鐘個數	*/
	__u32 right_margin;		/* 從真正顯示像素到sync信號的時鐘個數	*/
	__u32 upper_margin;		/* 上面兩個是針對列像素的,這個針對行的	*/
	__u32 lower_margin;
	__u32 hsync_len;		/* 水平sync信號的長度	*/
	__u32 vsync_len;		/* 垂直sync信號的長度	*/
	__u32 sync;			/* 參照 FB_SYNC_*		*/
	__u32 vmode;			/* 參照 FB_VMODE_*		*/
	__u32 rotate;			/* angle we rotate counter clockwise */ 
	__u32 reserved[5];		/* 保留 */
};
  fb_fix_screeninfo包含了lcd顯示中不可改變的信息,結構如下:
struct fb_fix_screeninfo {
	char id[16];			/* 身份表示符,例如 "TT Builtin" */
	unsigned long smem_start;	/* frame buffer內存的開始地址 */
					/* (物理地址) */
	__u32 smem_len;			/* frame buffer內存地址的長度 */
	__u32 type;			/* 參照 FB_TYPE_*		*/
	__u32 type_aux;			/* Interleave for interleaved Planes */
	__u32 visual;			/* 參照 FB_VISUAL_*		*/ 
	__u16 xpanstep;			/* zero if no hardware panning  */
	__u16 ypanstep;			/* zero if no hardware panning  */
	__u16 ywrapstep;		/* zero if no hardware ywrap    */
	__u32 line_length;		/* 每行的長度,單位字節    */
	unsigned long mmio_start;	/* I/O 內存的開始地址   */
					/* (物理地址) */
	__u32 mmio_len;			/* I/O內存的長度  */
	__u32 accel;			/* 對驅動程序的標示:是哪個設備*/
	__u16 reserved[3];		/* 保留 */
};
  其中倒數第三個成員par是設備自定義數據結構。在mini2440lcd驅動中爲s3c2410fb_info,結構如下:
struct s3c2410fb_info {
	struct device		*dev;
	struct clk		*clk;

	struct resource		*mem; //io內存物理地址也就是寄存器的地址
	void __iomem		*io;  //用ioremap映射的io虛擬地址
	void __iomem		*irq_base; //中斷控制器寄存器對應的虛擬地址

	enum s3c_drv_type	drv_type;
	struct s3c2410fb_hw	regs;

	unsigned long		clk_rate;
	unsigned int		palette_ready;

#ifdef CONFIG_CPU_FREQ
	struct notifier_block	freq_transition;
#endif

	/* keep these registers in case we need to re-write palette */
	u32			palette_buffer[256];
	u32			pseudo_pal[16];
};
  這個結構是和硬件相關的,包括寄存器的物理地址,虛擬地址和調色板的一些信息。這個結構也是在s3c24xxfb_probe中分配並初始化。
  2. static struct fb_ops 結構
  在mini2440lcd驅動中,fb_ops的初始化代碼如下:
static struct fb_ops s3c2410fb_ops = {
	.owner		= THIS_MODULE,
	.fb_check_var	= s3c2410fb_check_var,
	.fb_set_par	= s3c2410fb_set_par,
	.fb_blank	= s3c2410fb_blank,
	.fb_setcolreg	= s3c2410fb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
};
  這些函數是驅動程序必須實現的,他們實現的功能對應frambuffer核心的Ioctl系統調用,當應用程序調用ioctl系統調用的時候,他們會被直接或間接的調用。其中:

s3c2410fb_check_var 和s3c2410fb_set_par會由fb_set_var調用,對應Ioctl的FBIOPUT_VSCREENINFO命令

s3c2410fb_blank ,對應ioctl的FBIOBLANK命令,其他幾個函數也是類似。

  3. struct s3c2410fb_mach_info 結構
struct s3c2410fb_mach_info {
	struct s3c2410fb_display *displays;	/* attached diplays info */
	unsigned num_displays;			/* number of defined displays */
	unsigned default_display;
	/* GPIOs */
	unsigned long	gpcup;
	unsigned long	gpcup_mask;
	unsigned long	gpccon;
	unsigned long	gpccon_mask;
	unsigned long	gpdup;
	unsigned long	gpdup_mask;
	unsigned long	gpdcon;
	unsigned long	gpdcon_mask;

	/* lpc3600 control register */
	unsigned long	lpcsel;
};
  這個結構包括一個s3c2410fb_display結構體,其他的域是GPIO寄存器的信息。mini2440lcd驅動中定義並初始化了這樣一個結構體:
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,
};
  這裏初始化了結構中的所有成員,s3c2410fb_display結構初始化成mini2440_lcd_cfg,這個結構的初始化是在/arch/arm/mach-s3c2440/mach-mini2440.c這個文件中。這裏設置了s3c2440 lcd控制器對應的GPIO寄存器的初始值,在s3c2410fb_init_registers函數中將這些值寫到相應的寄存器中。
  4. s3c2410fb_display 結構
struct s3c2410fb_display {
	/* LCD type */
	unsigned type;

	/* Screen size */
	unsigned short width;
	unsigned short height;

	/* Screen info */
	unsigned short xres;
	unsigned short yres;
	unsigned short bpp;

	unsigned pixclock;		/* pixclock in picoseconds */
	unsigned short left_margin;  /* value in pixels (TFT) or HCLKs (STN) */
	unsigned short right_margin; /* value in pixels (TFT) or HCLKs (STN) */
	unsigned short hsync_len;    /* value in pixels (TFT) or HCLKs (STN) */
	unsigned short upper_margin;	/* value in lines (TFT) or 0 (STN) */
	unsigned short lower_margin;	/* value in lines (TFT) or 0 (STN) */
	unsigned short vsync_len;	/* value in lines (TFT) or 0 (STN) */

	/* lcd configuration registers */
	unsigned long	lcdcon5;
};
  這個結構體非常重要,他包括了一個lcd顯示的所有必須的配置信息。程序就是用這個結構體初始化fb_info結構中的fb_var_screeninfo相關成員的。最後這些值都會寫進lcd控制器的相應寄存器中。如上分析,這個結構在mini2440lcd驅動中被初始化成了mini2440_lcd_cfg,他定義在/arch/arm/mach-s3c2440/mach-mini2440.c,如下所示:
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,
};
二. 模塊初始化
  1.s3c2410fb.c是內核的一個模塊,在模塊初始化函數中只是簡單的調用了platform_driver_register把自己註冊成爲platform驅動。初始化函數如下:
int __init s3c2410fb_init(void)
{
	int ret = platform_driver_register(&s3c2410fb_driver);

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

	return ret;
}
  platform_driver_register是platform類型驅動的註冊函數,他傳入一個platform_driver結構體。mini2440lcd驅動初始化了這樣一個結構體。如下:
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,
	},
};
  可以看出這裏初始化了相應的函數,以及設備名稱和擁有模塊。其中 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驅動最關鍵的函數。
static int __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;
        //在/arch/arm/mach-s3c2440/mach-mini2440.c的mini2440_machine_init函數中,調用s3c24xx_fb_set_platdata(&mini2440_fb_info)
        //將mini2440_fb_info賦值給pdev->dev.paltform_data,所以這裏得到的是mini2440_fb_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;
	}

	display = mach_info->displays + mach_info->default_display;
        //mach_info->displays = 0,mach_info->default_display = mini2440_lcd_cfg
        //所以display = mini2440_lcd_cfg
	irq = platform_get_irq(pdev, 0);
        //pdev是platfoem_device結構,這個函數是從platform_device佔用的資源裏取出irq號
	if (irq < 0) {
		dev_err(&pdev->dev, "no irq for device\n");
		return -ENOENT;
	}

	fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
        //framebuffer_alloc所做的事就是分配一個fb_info結構體,因爲這個結構體最後有個通用指針*par,這個是設備自定義結構,在這裏是s3c24fb_info
        //所以分配內存的時候在fb_info結構的大小基礎上必須加上s3c2410fb_info結構的大小,這樣纔是這裏的fb_info真正的大小
	if (!fbinfo)
		return -ENOMEM;

	platform_set_drvdata(pdev, fbinfo);

	info = fbinfo->par; //將info(s3c2410fb_info結構)指向新分配的fbinfo的par位置
	info->dev = &pdev->dev;
	info->drv_type = drv_type;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        //得到lcd控制器io內存的物理地址
	if (res == NULL) {
		dev_err(&pdev->dev, "failed to get memory registers\n");
		ret = -ENXIO;
		goto dealloc_fb;
	}

	size = (res->end - res->start) + 1;
	info->mem = request_mem_region(res->start, size, pdev->name);
        //向內核請求所用的io內存,這裏主要防止其他模塊競爭,如果其他模塊佔用這塊內存,函數就會返回NULL
	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);
        // irq_base是lcd中斷控制器寄存器對應的虛擬地址
	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;
        //以上初始化fb_fix_screeninfo結構
	fbinfo->var.nonstd	    = 0;
	fbinfo->var.activate	    = FB_ACTIVATE_NOW;
	fbinfo->var.accel_flags     = 0;
	fbinfo->var.vmode	    = FB_VMODE_NONINTERLACED;
        //以上初始化fb_var_screeninfo結構
	fbinfo->fbops		    = &s3c2410fb_ops;
        // 這裏將我們實現的函數與frambuffer核心的操作聯繫上
	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);
        //註冊中斷處理函數,一般的lcd操作基本不需要中斷
	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);
        //以上操作使能lcd時鐘
	dprintk("got and enabled clock\n");

	msleep(1);

	info->clk_rate = clk_get_rate(info->clk);
	/* find maximum required memory size for display */
	for (i = 0; i < mach_info->num_displays; i++) {
		unsigned long smem_len = mach_info->displays[i].xres; // = 240

		smem_len *= mach_info->displays[i].yres; // = 320
		smem_len *= mach_info->displays[i].bpp;  // = 16
		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);
        //這個函數主要功能就是分配一塊內存,大小爲上面計算的smem_len,並且將分配的內存的物理地址賦值給fbinfo->fix.smem_start
        //將虛擬地址賦值給fbinfo->screen_base
	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;
        //這三個初始化很重要,對於下面的s3c2410fb_check_var尤其重要
	s3c2410fb_init_registers(fbinfo);
        //初始化lcd控制器的GPIO接口控制寄存器

	s3c2410fb_check_var(&fbinfo->var, fbinfo);
        //這個函數根據fbinfo->var的xres,yres和bits_per_pixel選擇相應的s3c2410fb_display結構,並將這個結構的各個域的值賦值給
        //fbinfo->var的相應成員。因爲mini2440lcd驅動只有一個s3c2410fb_display結構就是mini2440_lcd_cfg,所以賦值的就是mini2440_lcd_cfg
	ret = s3c2410fb_cpufreq_register(info);
	if (ret < 0) {
		dev_err(&pdev->dev, "Failed to register cpufreq\n");
		goto free_video_memory;
	}

	ret = register_framebuffer(fbinfo);
        //將fbinfo結構註冊到frambuffer核心
	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);

	return 0;

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;
} 
  從上面分析可以看出,這個函數主要做了下面幾件事:
  (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函數定義如下:
static int s3c2410fb_set_par(struct fb_info *info)
{
	struct fb_var_screeninfo *var = &info->var;

	switch (var->bits_per_pixel) {
	case 32:
	case 16:
	case 12:
		info->fix.visual = FB_VISUAL_TRUECOLOR;
		break;
	case 1:
		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);
	return 0;
}
  可以看出,這個函數除了根據fbinfo的像素位來賦值fix.visual外,主要是調用了s3c2410fb_activate_var函數:
static void 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) {
        //判斷lcd型號,我們的lcd是TFT型lcd,所以下面語句執行
		s3c2410fb_calculate_tft_lcd_regs(info, &fbi->regs);
                //這個函數主要的功能就是將info中的lcd配置相關的值賦值給s3c2410fb_info結構的regs成員
                //這個regs是一個s3c2410fb_hw結構,這個結構就是定義了5個lcd控制寄存器lcdcon1~5
		--clkdiv;
		if (clkdiv < 0)
			clkdiv = 0;
	} else {
        //如果是STN型的lcd,那麼執行下面的函數。因爲s3c2440的lcd控制器由有幾個專門用於控制STN型lcd的寄存器,所以要單獨設置
		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);
        //設置顯存地址寄存器,設置爲我們分配的那塊內存,設置之後,lcd控制器就會在這塊內存取數據送往lcd顯示

	fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID,
        //打開視頻顯示,這樣lcd就可以正確顯示了
	writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1);
}
五. 總結
      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函數中,有如下的調用
if ( ioctl(console_fd, FBIOGET_VSCREENINFO, &vinfo) < 0 ) {
     GAL_SetError("NEWGAL>FBCON: Couldn't get console pixel format\n");
     FB_VideoQuit(this);
     return(-1);
}
然後就是設置finfo中需要改變的值,最後有下面的代碼來設置lcd控制寄存器器
if ( ioctl(console_fd, FBIOPUT_VSCREENINFO, &vinfo) < 0 ) {
      vinfo.yres_virtual = height;
      if ( ioctl(console_fd, FBIOPUT_VSCREENINFO, &vinfo) < 0 ) {
           GAL_SetError("NEWGAL>FBCON: Couldn't set console screen info");
           return(NULL);
      }
}
        關於lcd驅動還有好多知識要學,比如mmap操作。mmap是一般lcd應用程序運行的模式,他將顯存映射到用戶空間,提高系統的性能。因爲自己只是爲了熟悉一下這個驅動,所以沒有深入看下去。




  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章