第二期驅動篇——1.1 LCD驅動編寫—Linux內核中LCD驅動框架分析

Linux內核中LCD驅動框架分析

  • 硬件平臺:韋東山嵌入式Linxu開發板(S3C2440.v3)
  • 軟件平臺:運行於VMware Workstation 12 Player下UbuntuLTS16.04_x64 系統
  • 參考資料:《嵌入式Linux應用開發手冊》、《嵌入式Linux應用開發手冊第2版》
  • 開發環境:Linux 2.6.22.6 內核、arm-linux-gcc-3.4.5-glibc-2.3.6工具鏈


一、前言

  在016 LCD(2)-編寫程序這篇博客中,編寫了裸機LCD驅動程序,在這一節中,將在Linux系統下編寫LCD驅動程序。

二、框架

1、回顧裸板LCD驅動

在這裏插入圖片描述

  • 第一層:測試菜單層lcd_test.c
  • 第二層:根據目的劃分三個C文件:framebuffer.cgeometry.cfont.c
  • 第三層:LCD參數設置lcd.c考慮到拓展性,如果需要用到不同尺寸的lcd時,需要修改太多東西,所以在這一層下分了一個小層,用來放各種尺寸lcd的參數設置文件lcd_4_3.c,通過在lcd.c調用對應的文件。
  • 第四層:LCD控制器設置lcd_controller.c考慮到拓展性,如果用到不同芯片驅動的lcd時,需要修改太多東西,所以在這一層下分了一個小層,用來放各種芯片的lcd控制器設置文件s3c2440_lcd_controller.c,通過在lcd_controller.c調用對應的文件。

2、Linux下LCD驅動框架

   由於在Linux系統下,系統中的/drivers/video/fbmen.c文件已經做好了相當於裸板驅動框架的第二層的工作,我們只需要設置相關的硬件操作。下面先對fbmen.c中的函數進行分析瞭解其大致的框架

fbmen.c文件解析:

2.1 fbmem_init函數解析

  • 函數原型:
static int __init
fbmem_init(void)
{
	create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);

	if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
		printk("unable to get major %d for fb devs\n", FB_MAJOR);

	fb_class = class_create(THIS_MODULE, "graphics");
	if (IS_ERR(fb_class)) {
		printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
		fb_class = NULL;
	}
	return 0;
}
  • 實現功能:
    ①、register_chrdev(FB_MAJOR,"fb",&fb_fops):對構建的file_operation結構體fb_fops進行註冊
       通過繼續代碼追蹤可以發現結構體fb_fops的原型中的read、write、open等函數可以發現:沒有直接操作硬件設備的寄存器或IO口,而是根據struct fb_info *info這個結構體變量來進行函數的操作
static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
	/*..............*/
	struct inode *inode = file->f_path.dentry->d_inode;
	int fbidx = iminor(inode);
	struct fb_info *info = registered_fb[fbidx];

	if (!info || ! info->screen_base)
		return -ENODEV;

	if (info->state != FBINFO_STATE_RUNNING)
		return -EPERM;

	if (info->fbops->fb_read)
		return info->fbops->fb_read(info, buf, count, ppos);
		
	/*..............*/
}

②、class_create(THIS_MODULE, "graphics");創建一個類。

  • 提問:根據之前的經驗,在創建類之後會創建類的設備節點爲什麼這裏沒有呢?
  • 回答:因爲fbmem.c通用的文件,相當於在裸板LCD驅動中lcd.c、lcdcontroller.c根據下層lcd_4.3.c、s3c2440_controlller.c註冊設備節點給上層的應用來使用。所以fbmem.c還需要根據一個文件的參數來進行註冊設備節點、調用下層的具體函數等。

2.2 fb_open函數解析

  • 函數原型:
static int
fb_open(struct inode *inode, struct file *file)
{
	int fbidx = iminor(inode);
	struct fb_info *info;
	int res = 0;

	if (fbidx >= FB_MAX)
		return -ENODEV;
#ifdef CONFIG_KMOD
	if (!(info = registered_fb[fbidx]))
		try_to_load(fbidx);
#endif /* CONFIG_KMOD */
	if (!(info = registered_fb[fbidx]))
		return -ENODEV;
	if (!try_module_get(info->fbops->owner))
		return -ENODEV;
	file->private_data = info;
	if (info->fbops->fb_open) {
		res = info->fbops->fb_open(info,1);
		if (res)
			module_put(info->fbops->owner);
	}
	return res;
}
  • 功能:
    ①、int fbidx = iminor(inode);找到該設備的次設備號
    ②、struct fb_info *info = registered_fb[fbidx];:根據次設備號作爲索引,在數組中找到對應項賦值給info
    繼續追蹤registered_fb的原型:發現硬件設置中的參數保存fb_info結構體中
struct fb_info {
	int node;
	int flags;
	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 */
	/*.........*/
};

struct fb_var_screeninfo {
	__u32 xres;			/* visible resolution		*/
	__u32 yres;
	__u32 xres_virtual;		/* virtual resolution		*/
	__u32 yres_virtual;
	__u32 xoffset;			/* offset from virtual to visible */
	__u32 yoffset;			/* resolution			*/
	/*...........................*/
};

2.3 fbmem.c總結

在這裏插入圖片描述

  1. fbmem.c是一個通用的文件
  2. 供上層應用來調用:根據上層open等函數來打開對應的設備節點
  3. 在當層中:通過找到該設備的次設備號,通過設備號在register_fb數組中找到該設備的硬件設置函數來進行硬件初始化調用下層具體函數

下面我們在看看Linux系統中已經寫好的硬件設置文件s3c2410fb.c,進一步分析。

s3c2410fb.c文件解析:

2.4 s3c2410fb_init函數解析

  • 函數原型:
int __devinit s3c2410fb_init(void)
{
	return platform_driver_register(&s3c2410fb_driver);
}
  • 功能:
    ①、調用platform_driver_register()平臺設備註冊函數。這個時候就可以指導內核在管理LCD驅動時,採用的是platform總線機制,註冊平臺驅動程序。

2.5 struct platform_driver s3c2410fb_driver結構體解析

  • 函數原型:
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,
	},
};
  • 功能:
    ①、存儲各類操作函數的地址
    ②、特別之處:與我們之前編寫的結構體不同,這裏沒有open、read函數。
    因爲此時採用的是platform總線機制,優點:在於platform機制將設備本身的資源註冊進內核,由內核統一管理,在驅動程序中用使用這些資源時,通過platform device提供的標準接口進行申請並使用
    對於LCD驅動,這個標準接口就是剛纔所介紹的fbmem.c文件。

2.6 s3c2410fb_probe函數解析

  • 函數原型:

static int __init s3c2410fb_probe(struct platform_device *pdev)
{
	struct s3c2410fb_info *info;
	struct fb_info	   *fbinfo;
	struct s3c2410fb_hw *mregs;
	int ret;
	int irq;
	int i;
	u32 lcdcon1;

	mach_info = pdev->dev.platform_data;
	if (mach_info == NULL) {
		dev_err(&pdev->dev,"no platform data for lcd, cannot attach\n");
		return -EINVAL;
	}

	mregs = &mach_info->regs;

	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(&pdev->dev, "no irq for device\n");
		return -ENOENT;
	}

	fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
	if (!fbinfo) {
		return -ENOMEM;
	}


	info = fbinfo->par;
	info->fb = fbinfo;
	info->dev = &pdev->dev;

	platform_set_drvdata(pdev, fbinfo);

	dprintk("devinit\n");

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

	memcpy(&info->regs, &mach_info->regs, sizeof(info->regs));

	/* Stop the video and unset ENVID if set */
	info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
	lcdcon1 = readl(S3C2410_LCDCON1);
	writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);

	info->mach_info		    = pdev->dev.platform_data;

	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.height	    = mach_info->height;
	fbinfo->var.width	    = mach_info->width;
	fbinfo->var.accel_flags     = 0;
	fbinfo->var.vmode	    = FB_VMODE_NONINTERLACED;

	fbinfo->fbops		    = &s3c2410fb_ops;
	fbinfo->flags		    = FBINFO_FLAG_DEFAULT;
	fbinfo->pseudo_palette      = &info->pseudo_pal;

	fbinfo->var.xres	    = mach_info->xres.defval;
	fbinfo->var.xres_virtual    = mach_info->xres.defval;
	fbinfo->var.yres	    = mach_info->yres.defval;
	fbinfo->var.yres_virtual    = mach_info->yres.defval;
	fbinfo->var.bits_per_pixel  = mach_info->bpp.defval;

	fbinfo->var.upper_margin    = S3C2410_LCDCON2_GET_VBPD(mregs->lcdcon2) + 1;
	fbinfo->var.lower_margin    = S3C2410_LCDCON2_GET_VFPD(mregs->lcdcon2) + 1;
	fbinfo->var.vsync_len	    = S3C2410_LCDCON2_GET_VSPW(mregs->lcdcon2) + 1;

	fbinfo->var.left_margin	    = S3C2410_LCDCON3_GET_HFPD(mregs->lcdcon3) + 1;
	fbinfo->var.right_margin    = S3C2410_LCDCON3_GET_HBPD(mregs->lcdcon3) + 1;
	fbinfo->var.hsync_len	    = S3C2410_LCDCON4_GET_HSPW(mregs->lcdcon4) + 1;

	fbinfo->var.red.offset      = 11;
	fbinfo->var.green.offset    = 5;
	fbinfo->var.blue.offset     = 0;
	fbinfo->var.transp.offset   = 0;
	fbinfo->var.red.length      = 5;
	fbinfo->var.green.length    = 6;
	fbinfo->var.blue.length     = 5;
	fbinfo->var.transp.length   = 0;
	fbinfo->fix.smem_len        =	mach_info->xres.max *
					mach_info->yres.max *
					mach_info->bpp.max / 8;

	/*.......*/

	ret = s3c2410fb_init_registers(info);

	ret = s3c2410fb_check_var(&fbinfo->var, fbinfo);

	ret = register_framebuffer(fbinfo);
	/*.......*/

}
  • 功能:
    ①、fbinfo = framebuffer_alloc():根據s3c2410fb_info結構體的大小分配內存空間給fbinfo
    ②、此時的具體硬件設置存放fbinfo結構體指針所指向的內存空間
    ③、ret = register_framebuffer(fbinfo);:把具體的硬件設置註冊到數組中去。

2.7 register_framebuffer函數解析

register_framebuffer()函數位於fbmen.c文件中

  • 函數原型:

int
register_framebuffer(struct fb_info *fb_info)
{
	int i;
	struct fb_event event;
	struct fb_videomode mode;

	if (num_registered_fb == FB_MAX)
		return -ENXIO;
	num_registered_fb++;
	
	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])
			break;
	fb_info->node = i;

	fb_info->dev = device_create(fb_class, fb_info->device,
				     MKDEV(FB_MAJOR, i), "fb%d", i);

	/*...............*/
}
  • 功能:
    ①、在數組中尋找空的項,把該項的索引號保存fbinfo中。
for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])
			break;
	fb_info->node = i;

②、註冊類的設備節點,這個時候有了具體的設備,纔會註冊設備節點。

	fb_info->dev = device_create(fb_class, fb_info->device,
				     MKDEV(FB_MAJOR, i), "fb%d", i);

三、總結

對於platform總線機制下的的LCD驅動的大致框架如下圖:
在這裏插入圖片描述
可以總結出來,platform總線機制下的的LCD驅動的編寫:不像編寫裸板LCD時是抽出那麼多函數來形成一個拓展性很好的框架(因爲Linux內核已經幫我們做了),我們只需要編寫我們具體驅動文件。

驅動文件編寫:

  1. 設置相關函數與結構體init、platform_driver、probe.....
  2. 分配一個 fb_info 結構體:framebuffer_alloc
  3. 註冊:register_framebuffer
  4. 硬件相關的操作
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章