如果我們的系統要用GUI(圖形界面介面),這時LCD設備驅動程式就應該編寫成frambuffer介面,而不是像之前那樣只編寫操作底層的LCD控制器介面。 什麼是frambuffer設備? frambuffer設備層是對圖像設備的一種抽象,它代表了視頻硬體的幀緩存,使得應用程式通過定義好的介面就可以訪 ...
如果我們的系統要用GUI(圖形界面介面),這時LCD設備驅動程式就應該編寫成frambuffer介面,而不是像之前那樣只編寫操作底層的LCD控制器介面。
什麼是frambuffer設備?
frambuffer設備層是對圖像設備的一種抽象,它代表了視頻硬體的幀緩存,使得應用程式通過定義好的介面就可以訪問硬體。所以應用程式不需要考慮底層的(寄存器級)的操作。應用程式對設備文件的訪問一般在/dev目錄,如 /dev/fb*。
內核中的frambuffer在drivers/video/fbmem.c(fb: frame buffer)
1.我們進入fbmem.c找到它的入口函數:
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)) //(1)創建字元設備 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; }
(1)創建字元設備"fb", FB_MAJOR=29,主設備號為29,我們cat /proc/devices 也能找到這個字元設備:
和我們之前的驅動程式一樣,但是沒有使用創建設備節點,為什麼?
因為需要註冊了LCD驅動後,才會有設備節點,所以這裡的代碼沒有 ,後面會分析哪裡有。
2.我們來看看註冊的file_operations結構體fb_fops的.open函數和.read函數,應用層是如何打開驅動、讀取驅動數據
2.1 fb_open函數如下:
static int fb_open(struct inode *inode, struct file *file) { int fbidx = iminor(inode); //獲取設備節點的次設備號 struct fb_info *info; //定義fb_info結構體 int res = 0; ... ... if (!(info = registered_fb[fbidx])) //(1) info= registered_fb[fbidx],獲取此設備號的lcd驅動信息 try_to_load(fbidx); ... ... if (info->fbops->fb_open) { res = info->fbops->fb_open(info,1); //調用registered_fb[fbidx]->fbops->fb_open if (res) module_put(info->fbops->owner); } return res; }
(1) registered_fb[fbidx] 這個數組也是fb_info結構體,其中fbidx等於次設備號id,顯然這個數組就是保存我們各個lcd驅動的信息
2.2 fb_read函數如下:
static ssize_t fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { unsigned long p = *ppos; struct inode *inode = file->f_path.dentry->d_inode; int fbidx = iminor(inode); //獲取次設備號 struct fb_info *info = registered_fb[fbidx]; //獲取次設備號的lcd驅動的信息 u32 *buffer, *dst; u32 __iomem *src; int c, i, cnt = 0, err = 0; unsigned long total_size; ... ... if (info->fbops->fb_read) return info->fbops->fb_read(info, buf, count, ppos); total_size = info->screen_size; //獲取屏幕長度
... ... buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,GFP_KERNEL); //分配緩衝區 if (!buffer) return -ENOMEM; src = (u32 __iomem *) (info->screen_base + p); //獲取顯存物理基地址 if (info->fbops->fb_sync) info->fbops->fb_sync(info); while (count) { c = (count > PAGE_SIZE) ? PAGE_SIZE : count; //獲取頁地址 dst = buffer; /*因為src是32位,一個src等於4個位元組,所以頁地址c >> 2*/ for (i = c >> 2; i--; ) *dst++ = fb_readl(src++); //讀取顯存每個像素點數據,放到dst地址上 if (c & 3) { u8 *dst8 = (u8 *) dst; u8 __iomem *src8 = (u8 __iomem *) src; for (i = c & 3; i--;) *dst8++ = fb_readb(src8++); src = (u32 __iomem *) src8; } if (copy_to_user(buf, buffer, c)) { //上傳數據,長度等於頁地址大小 err = -EFAULT; break; } *ppos += c; buf += c; cnt += c; count -= c; } kfree(buffer); return (err) ? err : cnt; }
從.open和.write函數中可以發現,都依賴於fb_info幀緩衝信息結構體,它從registered_fb[fbidx]數組中得到,這個數組保存我們各個lcd驅動的信息
3.我們來找找這個數組在哪裡被註冊,位於register_framebuffer():
int register_framebuffer(struct fb_info *fb_info) { ... ... for (i = 0 ; i < FB_MAX; i++) //查找空的數組 if (!registered_fb[i]) break; fb_info->node = i; ... ... /*創建設備節點,名稱為fdi,主設備號為29,次設備號為i */ fb_info->dev = device_create(fb_class, fb_info->device,MKDEV(FB_MAJOR, i), "fb%d", i); ... ... registered_fb[i] = fb_info; ... ... }
得出這個register_framebuffer()除了註冊fb_info,還創建了設備節點
所以要註冊驅動時就調用這個,如下圖所示:
4.我們來看看/drivers/video/s3c2410fb.c 中又是怎麼實現驅動的
4.1先找到入口出口函數:
int __devinit s3c2410fb_init(void) { return platform_driver_register(&s3c2410fb_driver); } static void __exit s3c2410fb_cleanup(void) { platform_driver_unregister(&s3c2410fb_driver); }
發現是註冊、註銷設備平臺drv
4.2 來看看s3c2410fb_driver 如何定義的
static struct platform_driver s3c2410fb_driver = { .probe = s3c2410fb_probe, //檢測函數,註冊設備 .remove = s3c2410fb_remove, //刪除設備 .suspend = s3c2410fb_suspend, //休眠 .resume = s3c2410fb_resume, //喚醒 .driver = { .name = "s3c2410-lcd", //drv名字 .owner = THIS_MODULE, }, };
和我們上節分析的platform機制一樣,當與設備匹配成功,就進入probe函數,初始化驅動設備
4.3 來看看.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; //獲取LCD設備信息(長寬、類型等) 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); //1.分配一個fb_info結構體 if (!fbinfo) { return -ENOMEM; } /*2.設置fb_info*/ info = fbinfo->par; info->fb = fbinfo; info->dev = &pdev->dev; ... ... /*3.硬體相關的操作,設置中斷,LCD時鐘頻率,顯存地址, 配置引腳... ...*/ ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info); //設置中斷 info->clk = clk_get(NULL, "lcd"); //獲取時鐘 clk_enable(info->clk); //使能時鐘 ret = s3c2410fb_map_video_memory(info); //顯存地址 ret = s3c2410fb_init_registers(info); //設置寄存器,配置引腳 ... ...
ret = register_framebuffer(fbinfo); //4.註冊一個fb_info結構體 if (ret < 0) { printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret); goto free_video_memory; } ... ... return ret; }
4.4 顯然要寫個LCD驅動程式,需要以下4步:
1) 分配一個fb_info結構體: framebuffer_alloc();
2) 設置fb_info
3) 硬體相關的操作(設置中斷,LCD時鐘頻率,顯存地址, 配置引腳... ...)
4 註冊fb_info: register_framebuffer()
下節就開始如何來寫LCD驅動
(LCD驅動詳解入口地址: http://www.cnblogs.com/lifexy/p/7604011.html)