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.c
、geometry.c
、font.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總結
- fbmem.c是一個通用的文件
- 供上層應用來調用:根據上層
open
等函數來打開對應的設備節點 - 在當層中:通過找到該設備的次設備號,通過設備號在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內核已經幫我們做了),我們只需要編寫我們具體驅動文件。
驅動文件編寫:{
- 設置相關函數與結構體:
init、platform_driver、probe.....
- 分配一個 fb_info 結構體:framebuffer_alloc
- 註冊:register_framebuffer
- 硬件相關的操作
}