LCD驅動程式
學習目標:熟悉TFT LCD的概念,分層驅動工作原理和程式編寫。
一、LCD 概念
1. 顯示器資料組織格式
1)一幅影象成為一幀,每幀由多行組成,每行由多個畫素組成。每個畫素的顏色由若干位表示,對於256色LCD,每個畫素由8位表示,稱為8BPP。
2)顯示器呈Z字行的路線進行掃描顯示,使用HSYNC、VSYNC控制掃描和跳轉的路徑;
2、操作過程
1)設定LCD的HSYNC、VSYNC\VCLK等訊號的引數,並將幀記憶體的地址告訴LCD控制器,塔克自動的發起DMA傳輸,從幀記憶體中得到影象資料,出現在資料匯流排VD[23:0]上。我們只需要將顯示的影象資料寫入幀記憶體中即可。
2)影象資料的儲存:
例如:由三原色組建的256色(8BPP)顯示模式,使用8位資料表示一個畫素的顏色。但特殊的是,這8位資料用於表示在調色盤中的索引值。這裡的調色盤使用256*16的記憶體,即使用16BPP的顯示格式來表示對應各個索引值的顏色。因此,最終在LCD顯示的仍為16BPP的資料。
記憶體資料和畫素對應的關係為:
其中,P1、P2...為一個個的畫素。
畫素在調色盤中的資料存放模式16BPP分為兩種格式:5:6:5和5:5:5:1.即:
二、LCD驅動
1、幀緩衝裝置
frambuffer裝置層是對顯示裝置的一種抽象。其中,幀緩衝是Linux為顯示裝置提供的一個介面,它把一些顯示裝置描述成一個緩衝區,允許應用程式通過FrameBuffer定義好的介面訪問這些圖形裝置,從而不用去關心具體的硬體細節。對於幀緩衝裝置而言,只要在顯示緩衝區與顯示點對應的區域寫入顏色值,對應的顏色就會自動的在螢幕上顯示。
2、LCD作為一種幀緩衝裝置,也是一種標準的字元型裝置,對應於檔案系統下/dev/fb%d裝置檔案。
3、驅動結構
首先分析一下driver/video/fbmem.c
1)進入__init fbmem_init(入口函式),主要建立了字元裝置“fb”和類,利用cat 命令檢視(cat /proc/devices),可看到該目錄下的fb,主裝置號為29。由於還沒有註冊LCD驅動,所以沒有裝置節點,
1 static int __init fbmem_init(void) 2 { 3 create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);4 //建立字元裝置"fb" 5 if (register_chrdev(FB_MAJOR,"fb",&fb_fops)) 6 printk("unable to get major %d for fb devs\n", FB_MAJOR); 7 8 fb_class = class_create(THIS_MODULE, "graphics"); //建立類graphics" 9 if (IS_ERR(fb_class)) { 10 printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class)); 11 fb_class = NULL; 12 } 13 return 0; 14 }
2)fb_fops結構體及open函式
1 static const struct file_operations fb_fops = { 2 .owner = THIS_MODULE, 3 .read = fb_read, 4 .write = fb_write, 5 .ioctl = fb_ioctl, 6 #ifdef CONFIG_COMPAT 7 .compat_ioctl = fb_compat_ioctl, 8 #endif 9 .mmap = fb_mmap, 10 .open = fb_open, 11 .release = fb_release, 12 #ifdef HAVE_ARCH_FB_UNMAPPED_AREA 13 .get_unmapped_area = get_fb_unmapped_area, 14 #endif 15 #ifdef CONFIG_FB_DEFERRED_IO 16 .fsync = fb_deferred_io_fsync, 17 #endif 18 };
-->fb_open函式:
1 static int fb_open(struct inode *inode, struct file *file)
2 {
3 int fbidx = iminor(inode); //取出裝置次裝置號
4 struct fb_info *info; //定義一個fb_info結構體
5 int res = 0;
6
7 if (fbidx >= FB_MAX)
8 return -ENODEV;
9 #ifdef CONFIG_KMOD
10 if (!(info = registered_fb[fbidx])) // 在次裝置裡面得到fb_info結構資訊(lcd的驅動資訊)賦值給info
11 try_to_load(fbidx);
12 #endif /* CONFIG_KMOD */
13 if (!(info = registered_fb[fbidx]))
14 return -ENODEV;
15 if (!try_module_get(info->fbops->owner))
16 return -ENODEV;
17 file->private_data = info;
18 if (info->fbops->fb_open) { //如果該裝置info結構體有open函式,就執行registered_fb[fbidx]->fbops->fb_open
19 res = info->fbops->fb_open(info,1);
20 if (res)
21 module_put(info->fbops->owner);
22 }
23 return res;
24 }
由於fb裝置(幀緩衝裝置)主裝置號固定,不同裝置以次裝置號進行區分,執行該裝置open函式時,最終指向的是對應裝置的open函式。
接下來看一下fb_read函式:
1 static ssize_t
2 fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
3 {
4 unsigned long p = *ppos;
5 struct inode *inode = file->f_path.dentry->d_inode;
6 int fbidx = iminor(inode); //取出裝置次裝置號
7 struct fb_info *info = registered_fb[fbidx]; //定義並獲取裝置的fb_info結構體
8 u32 *buffer, *dst;
9 u32 __iomem *src;
10 int c, i, cnt = 0, err = 0;
11 unsigned long total_size;
12
13 if (!info || ! info->screen_base)
14 return -ENODEV;
15
16 if (info->state != FBINFO_STATE_RUNNING)
17 return -EPERM;
18
19 if (info->fbops->fb_read)
20 return info->fbops->fb_read(info, buf, count, ppos); //如果該裝置fb_info結構體有read函式,就執行registered_fb[fbidx]->fbops->fb_read
21
22 total_size = info->screen_size;
23
24 if (total_size == 0)
25 total_size = info->fix.smem_len;
26
27 if (p >= total_size)
28 return 0;
29
30 if (count >= total_size)
31 count = total_size;
32
33 if (count + p > total_size)
34 count = total_size - p;
35
36 buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
37 GFP_KERNEL);
38 if (!buffer)
39 return -ENOMEM;
40
41 src = (u32 __iomem *) (info->screen_base + p);
42
43 if (info->fbops->fb_sync)
44 info->fbops->fb_sync(info);
45
46 while (count) {
47 c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
48 dst = buffer;
49 for (i = c >> 2; i--; )
50 *dst++ = fb_readl(src++);
51 if (c & 3) {
52 u8 *dst8 = (u8 *) dst;
53 u8 __iomem *src8 = (u8 __iomem *) src;
54
55 for (i = c & 3; i--;)
56 *dst8++ = fb_readb(src8++);
57
58 src = (u32 __iomem *) src8;
59 }
60
61 if (copy_to_user(buf, buffer, c)) {
62 err = -EFAULT;
63 break;
64 }
65 *ppos += c;
66 buf += c;
67 cnt += c;
68 count -= c;
69 }
71 kfree(buffer);
73 return (err) ? err : cnt;
74 }
由以上程式可知,read的呼叫和open類似。都依賴於對應裝置的fb_info結構體info,在程式中是由registered_fb[fbidx]陣列獲取的。
3)最後,看一下registered_fb[fbidx]陣列的定義,位於register_framebuffer函式中。
1 int register_framebuffer(struct fb_info *fb_info) 2 { 3 int i; 4 struct fb_event event; 5 struct fb_videomode mode; 6 7 if (num_registered_fb == FB_MAX) 8 return -ENXIO; 9 num_registered_fb++; 10 for (i = 0 ; i < FB_MAX; i++) 11 if (!registered_fb[i]) 12 break; 13 fb_info->node = i; 14 15 fb_info->dev = device_create(fb_class, fb_info->device,MKDEV(FB_MAJOR, i), "fb%d", i);//建立裝置節點,名稱為fbi,主裝置號為FB_MAJOR 29,次裝置號為i 17 if (IS_ERR(fb_info->dev)) { 18 /* Not fatal */ 19 printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev)); 20 fb_info->dev = NULL; 21 } else 22 fb_init_device(fb_info); 23 24 if (fb_info->pixmap.addr == NULL) { 25 fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL); 26 if (fb_info->pixmap.addr) { 27 fb_info->pixmap.size = FBPIXMAPSIZE; 28 fb_info->pixmap.buf_align = 1; 29 fb_info->pixmap.scan_align = 1; 30 fb_info->pixmap.access_align = 32; 31 fb_info->pixmap.flags = FB_PIXMAP_DEFAULT; 32 } 33 } 34 fb_info->pixmap.offset = 0; 35 36 if (!fb_info->pixmap.blit_x) 37 fb_info->pixmap.blit_x = ~(u32)0; 38 39 if (!fb_info->pixmap.blit_y) 40 fb_info->pixmap.blit_y = ~(u32)0; 41 42 if (!fb_info->modelist.prev || !fb_info->modelist.next) 43 INIT_LIST_HEAD(&fb_info->modelist); 44 45 fb_var_to_videomode(&mode, &fb_info->var); 46 fb_add_videomode(&mode, &fb_info->modelist); 47 registered_fb[i] = fb_info; //賦值到registered_fb[i]陣列中 48 49 event.info = fb_info; 50 fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event); 51 return 0; 52 }
接下來看一下fb硬體驅動程式,以/drivers/video/s3c2410fb.c為例。
1)驅動入口
1 static struct platform_driver s3c2410fb_driver = { 2 .probe = s3c2410fb_probe, 3 .remove = s3c2410fb_remove, 4 .suspend = s3c2410fb_suspend, 5 .resume = s3c2410fb_resume, 6 .driver = { 7 .name = "s3c2410-lcd", 8 .owner = THIS_MODULE, 9 }, 10 }; 11 12 int __devinit s3c2410fb_init(void) 13 { 14 return platform_driver_register(&s3c2410fb_driver); 15 } 16 17 static void __exit s3c2410fb_cleanup(void) 18 { 19 platform_driver_unregister(&s3c2410fb_driver); 20 }
2)當平臺裝置的驅動和裝置匹配後,會直接呼叫prob函式。
1 static int __init s3c2410fb_probe(struct platform_device *pdev) 2 { 3 struct s3c2410fb_info *info; 4 struct fb_info *fbinfo; 5 struct s3c2410fb_hw *mregs; 6 int ret; 7 int irq; 8 int i; 9 u32 lcdcon1; 10 11 mach_info = pdev->dev.platform_data; //獲取lcd裝置資訊 12 if (mach_info == NULL) { 13 dev_err(&pdev->dev,"no platform data for lcd, cannot attach\n"); 14 return -EINVAL; 15 } 25 fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev); //分配fb_info結構體 26 if (!fbinfo) { 27 return -ENOMEM; 28 }
//設定fb_info結構體
31 info = fbinfo->par; 32 info->fb = fbinfo; 33 info->dev = &pdev->dev; 34 35 platform_set_drvdata(pdev, fbinfo); 37 dprintk("devinit\n"); 39 strcpy(fbinfo->fix.id, driver_name); 41 memcpy(&info->regs, &mach_info->regs, sizeof(info->regs)); 43 /* Stop the video and unset ENVID if set */ 44 info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID; 45 lcdcon1 = readl(S3C2410_LCDCON1); 46 writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1); 47 48 info->mach_info = pdev->dev.platform_data; 49 50 fbinfo->fix.type = FB_TYPE_PACKED_PIXELS; 51 fbinfo->fix.type_aux = 0; 52 fbinfo->fix.xpanstep = 0; 53 fbinfo->fix.ypanstep = 0; 54 fbinfo->fix.ywrapstep = 0; 55 fbinfo->fix.accel = FB_ACCEL_NONE;
64 fbinfo->fbops = &s3c2410fb_ops; 65 fbinfo->flags = FBINFO_FLAG_DEFAULT; 66 fbinfo->pseudo_palette = &info->pseudo_pal; 67 73 74 fbinfo->var.upper_margin = S3C2410_LCDCON2_GET_VBPD(mregs->lcdcon2) + 1; 75 fbinfo->var.lower_margin = S3C2410_LCDCON2_GET_VFPD(mregs->lcdcon2) + 1; 76 fbinfo->var.vsync_len = S3C2410_LCDCON2_GET_VSPW(mregs->lcdcon2) + 1; 77 78 fbinfo->var.left_margin = S3C2410_LCDCON3_GET_HFPD(mregs->lcdcon3) + 1; 79 fbinfo->var.right_margin = S3C2410_LCDCON3_GET_HBPD(mregs->lcdcon3) + 1; 80 fbinfo->var.hsync_len = S3C2410_LCDCON4_GET_HSPW(mregs->lcdcon4) + 1; 81 90 fbinfo->fix.smem_len = mach_info->xres.max * 91 mach_info->yres.max * 92 mach_info->bpp.max / 8; 93 94 for (i = 0; i < 256; i++) 95 info->palette_buffer[i] = PALETTE_BUFF_CLEAR; 96 97 if (!request_mem_region((unsigned long)S3C24XX_VA_LCD, SZ_1M, "s3c2410-lcd")) { 98 ret = -EBUSY; 99 goto dealloc_fb; 100 } 103 dprintk("got LCD region\n"); 104 //硬體相關的操作,中斷、時鐘.... 105 ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info); 106 if (ret) { 107 dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret); 108 ret = -EBUSY; 109 goto release_mem; 110 } 111 112 info->clk = clk_get(NULL, "lcd"); 113 if (!info->clk || IS_ERR(info->clk)) { 114 printk(KERN_ERR "failed to get lcd clock source\n"); 115 ret = -ENOENT; 116 goto release_irq; 117 } 118 119 clk_enable(info->clk); 120 dprintk("got and enabled clock\n"); 121 122 msleep(1); 123 124 /* Initialize video memory */ 125 ret = s3c2410fb_map_video_memory(info); 126 if (ret) { 127 printk( KERN_ERR "Failed to allocate video RAM: %d\n", ret); 128 ret = -ENOMEM; 129 goto release_clock; 130 } 137 ret = register_framebuffer(fbinfo);//註冊fb_info結構體 138 if (ret < 0) { 139 printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret); 140 goto free_video_memory; 141 } 142 143 /* create device files */ 144 device_create_file(&pdev->dev, &dev_attr_debug); 145 146 printk(KERN_INFO "fb%d: %s frame buffer device\n", 147 fbinfo->node, fbinfo->fix.id); 148 149 return 0; 150 151 free_video_memory: 152 s3c2410fb_unmap_video_memory(info); 153 release_clock: 154 clk_disable(info->clk); 155 clk_put(info->clk); 156 release_irq: 157 free_irq(irq,info); 158 release_mem: 159 release_mem_region((unsigned long)S3C24XX_VA_LCD, S3C24XX_SZ_LCD); 160 dealloc_fb: 161 framebuffer_release(fbinfo); 162 return ret; 163 }
小結:
根據驅動結構和程式原始碼分析可知,lcd驅動程式需要完成以下幾部分:
1)分配一個fb_info結構體:由函式framebuffer_alloc() 完成 ;
2)設定fb_info結構體;
3)註冊fb_info:register_framebuffer();
4)硬體相關的操作