您的位置:首页 > 运维架构 > Linux

Linux驱动:LCD驱动框架分析

2017-11-20 22:10 393 查看
一直想花时间来整理一下Linux内核LCD驱动,却一直都忙着做其他事情去了,这些天特意抽出时间来整理之前落下的笔记,故事就这样开始了。LCD驱动也是字符设备驱动的一种,框架上相对于字符设备驱动稍微复杂一点点,真的就是一点点,难点在对LCD硬件的配置上。

开发平台:TQ210,S5PV210处理器

内核版本:linux-3.10.46

LCD型号:AT070TN92,7英寸,TFT屏,分辨率800x480x3(RGB),24位真彩色

一、框架分析



上图说明:①内核装载LCD驱动模块:设置并注册fb_info结构,初始化LCD硬件。②APP打开LCD设备,获取设备文件,根据设备文件进行读写显存。③在内核中,根据主设备号和次设备号定位一个fb_info结构,如果应用层的系统调用是读操作则调用fb_ops中对应的操作函数,写操作也是一样。

主要数据结构分析:



1 struct fb_info {
2     int node;                        //用作次设备号索引
3     int flags;
4     struct mutex lock;                //用于open/release/ioctl函数的锁
5     struct fb_var_screeninfo var;    //可变参数,重点
6     struct fb_fix_screeninfo fix;    //固定参数,重点
7     struct fb_monspecs monspecs;    //显示器标准
8     struct work_struct queue;        //帧缓冲区队列
9     struct fb_pixmap pixmap;        //图像硬件映射
10     struct fb_pixmap sprite;        //光标硬件映射
11     struct fb_cmap cmap;            //当前颜色表
12     struct list_head modelist;      //模式链表
13     struct fb_videomode *mode;        //当前video模式
14
15     char __iomem *screen_base;        //显存基地址
16     unsigned long screen_size;        //显存大小
17     void *pseudo_palette;            //伪16色颜色表
18 #define FBINFO_STATE_RUNNING    0
19 #define FBINFO_STATE_SUSPENDED    1
20     u32 state;                        //硬件状态,如挂起
21     void *fbcon_par;                //用作私有数据区
22     void *par;                        //info->par指向了额外多申请内存空间的首地址
23 };




1 struct fb_ops {
2     struct module *owner;
3     int (*fb_open)(struct fb_info *info, int user);
4     int (*fb_release)(struct fb_info *info, int user);
5     ssize_t (*fb_read)(struct fb_info *info, char __user *buf,size_t count, loff_t *ppos);
6     ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,size_t count, loff_t *ppos);
7
8     /* 检测可变参数,并调整到支持的值 */
9     int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
10
11     /* 根据 info->var 设置 video 模式 */
12     int (*fb_set_par)(struct fb_info *info);
13
14     /* set color register */
15     int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,unsigned blue, unsigned transp, struct fb_info *info);
16     /* set color registers in batch */
17     int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
18     /* blank display */
19     int (*fb_blank)(int blank, struct fb_info *info);
20     /* pan display */
21     int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
22
23     /* Draws a rectangle */
24     void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
25     /* Copy data from area to another */
26     void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
27     /* Draws a image to the display */
28     void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
29
30     ......
31
32     /* perform fb specific ioctl (optional) */
33     int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
34             unsigned long arg);
35     /* perform fb specific mmap */
36     int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
37
38     ......
39 };


主要操作代码分析:



1 fb_open
2 {
3     int fbidx = iminor(inode);         //获取次设备号
4     struct fb_info *info;
5     info = get_fb_info(fbidx);
6         struct fb_info *fb_info;
7         fb_info = registered_fb[fbidx];//根据次设备号从已注册的fb_info数组中获取响应的结构
8         return fb_info;
9     ......
10     /*
11      * 从registered_fb[]数组项里找到fb_info结构体后,将其保存到
12      * struct file结构中的私有信息成员,难道这是为了以后在某些情况方便找到并调用??先放着...
13      * 回过来发现:这样做是为了验证在read、write、ioctl等系统调用中获得的fb_info结构和open获得的是否一样
14      */
15     file->private_data = info;
16     //info->fbops->fb_open无定义,这是值得思考的问题!
17     if (info->fbops->fb_open) {
18         res = info->fbops->fb_open(info,1);
19         if (res)
20             module_put(info->fbops->owner);
21     }
22     ......
23 }




1 fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
2 {
3     struct fb_info *info = file_fb_info(file);
4         struct inode *inode = file_inode(file);
5         int fbidx = iminor(inode);
6         //也是根据次设备号来获取fb_info结构
7         struct fb_info *info = registered_fb[fbidx];
8
9         if (info != file->private_data)
10             info = NULL;
11         return info;
12     //无定义
13     if (info->fbops->fb_read)
14         return info->fbops->fb_read(info, buf, count, ppos);
15     //获得显存的大小
16     total_size = info->screen_size;
17     //如果应用层要读的数据count比实际最大的显存还要大,修改count值为最大显存值
18     if (count >= total_size)
19         count = total_size;
20     //分配显存,最大只能是一页PAGE_SIZE=4KB
21     buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,GFP_KERNEL);
22     //要读的源地址:显存虚拟基地址+偏移
23     src = (u8 __iomem *) (info->screen_base + p);
24     while (count) {
25         c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;
26         //读的目的地址
27         dst = buffer;
28         //读操作:拷贝数据
29         fb_memcpy_fromfb(dst, src, c);
30         dst += c;
31         src += c;
32
33         if (copy_to_user(buf, buffer, c)) {
34             err = -EFAULT;
35             break;
36         }
37         *ppos += c;
38         buf += c;
39         cnt += c;
40         count -= c;
41     }
42     kfree(buffer); //释放buffer,只起到临时中转站的作用
43 }



 fb_write



1 /*
2  * 函数功能:将内核空间分配的物理显存空间映射到用户空间中
3  * 用户空间就能访问这段内存空间了
4  */
5 static int fb_mmap(struct file *file, struct vm_area_struct * vma)
6 {
7     struct fb_info *info = file_fb_info(file);
8     struct fb_ops *fb;
9     unsigned long mmio_pgoff;
10     unsigned long start;
11     u32 len;
12
13     if (!info)
14         return -ENODEV;
15     fb = info->fbops;
16     if (!fb)
17         return -ENODEV;
18     mutex_lock(&info->mm_lock);
19     //如果fb_info->fbops->fb_mmap存在就调用该函数,实际中没有!
20     if (fb->fb_mmap) {
21         int res;
22         res = fb->fb_mmap(info, vma);
23         mutex_unlock(&info->mm_lock);
24         return res;
25     }
26     /*
27     * fb缓冲内存的开始位置(物理地址)
28     * info->fix.smem_start这个地址是在哪里被设置的?
29     * 在驱动程序xxx_lcd_init()函数中:
30     * clb_fbinfo->screen_base = dma_alloc_writecombine(NULL,clb_fbinfo->fix.smem_len, (u32*)&(clb_fbinfo->fix.smem_start), GFP_KERNEL);
31     * dma_alloc_writecombine函数返回的是内核虚拟起始地址,同时第3个参数fix.smem_start会被设置成对应的物理起始地址。
32     * 内核中操作这个分配的空间只能操作虚拟的地址空间!!!
33     * dma_alloc_writecombine函数的调用只是把物理显存映射到内核空间,并没有映射到用户空间,因此用户在操作物理显存之前要先把
34     * 物理显存空间映射到用户可见的用户空间中来,这就是该函数的意义所在。
35     */
36     start = info->fix.smem_start;
37     //帧缓冲长度
38     len = info->fix.smem_len;
39     mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;
40     if (vma->vm_pgoff >= mmio_pgoff) {
41         if (info->var.accel_flags) {
42             mutex_unlock(&info->mm_lock);
43             return -EINVAL;
44         }
45
46         vma->vm_pgoff -= mmio_pgoff;
47         start = info->fix.mmio_start;
48         len = info->fix.mmio_len;
49     }
50     mutex_unlock(&info->mm_lock);
51
52     vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
53     fb_pgprotect(file, vma, start);
54     //映射物理内存到用户空间虚拟地址
55     return vm_iomap_memory(vma, start, len);
56 }


问题思考:

问1.什么叫帧缓冲区,他有哪些特性指标?

答1.对于应用层来说,显示图像到LCD设备就相当于往“一块内存”中写入数据,获取LCD设备上的图像就相当于拷贝“这块内存”中的数据。因此,LCD就和“一块内存”一样,专业一点术语叫帧缓冲区,它和普通的内存不太一样,除了可以“读写”操作之外还可以进行其他操作和功能设置,特性指标就是LCD的特性指标。在内核中,一个LCD显示器就相当于一个帧缓冲设备,对应一个fb_info结构。

问2.为什么要通过 registered_fb[] 数组来找到对应的 fb_info 结构体?

答2.通过对上边这几个函数的剖析发现,不管是fb_read、fb_write、fb_ioctl、fb_mmap系统调用,都是通过次设备号在已注册的fb_info结构数组中找到匹配的那一个结构之后,判断其中的fbops结构中的操作函数是否有定义,有的话就优先调用该函数,没有就使用往下的方案策略。这样的好处就是多个相同的LCD设备可以使用同一套代码,减少代码的重复性,同时对于需要特殊定义的函数又可以方便实现重定义。

问3.这个数组在哪里被注册?

答3.在register_framebuffer()函数中被注册 register_framebuffer(struct fb_info *fb_info) ret = do_register_framebuffer(fb_info); ...... registered_fb[i] = fb_info; ......

问4.fb_mmap()函数在什么场合使用?

答4.在用户空间中通过mmap()函数来进行系统调用,该函数执行成功返回的是指向被映射的帧缓冲区的指针,这样用户直接可以通过该指针来读写缓冲区。

问5.在用户程序中调用write函数和直接使用mmap函数返回的fbp指针有什么不一样?

答5.用户空间使用fbp指针操作的地址是用户空间和物理显存空间直接映射的关系,而使用write是将用户中的数据拷贝到内核空间,然后再将这些数据写到内核中已映射的虚拟地址空间中;write是操作整个fb,而fbp只操作一个像素点。

二、驱动代码编写



1 #include <linux/module.h>
2 #include <linux/kernel.h>
3 #include <linux/errno.h>
4 #include <linux/string.h>
5 #include <linux/mm.h>
6 #include <linux/slab.h>
7 #include <linux/delay.h>
8 #include <linux/fb.h>
9 #include <linux/init.h>
10 #include <linux/dma-mapping.h>
11 #include <linux/interrupt.h>
12 #include <linux/platform_device.h>
13 #include <linux/clk.h>
14 #include <linux/workqueue.h>
15
16 #include <asm/io.h>
17 #include <asm/div64.h>
18 #include <asm/uaccess.h>
19
20 #include <asm/mach/map.h>
21 #include <mach/regs-gpio.h>
22 #include <linux/fb.h>
23
24 #define VSPW        9   //4
25 #define VBPD        13  //17
26 #define LINEVAL     479
27 #define VFPD        21  //26
28
29 #define HSPW        19    //4
30 #define HBPD        25   //40
31 #define HOZVAL      799
32 #define HFPD        209   //214
33
34 #define LeftTopX    0
35 #define LeftTopY    0
36 #define RightBotX   799
37 #define RightBotY   479
38
39 static struct fb_info *clb_fbinfo;
40
41 /* LCD GPIO Pins */
42 static long unsigned long *gpf0con;
43 static long unsigned long *gpf1con;
44 static long unsigned long *gpf2con;
45 static long unsigned long *gpf3con;
46 static long unsigned long *gpd0con;
47 static long unsigned long *gpd0dat;
48 static long unsigned long *display_control;
49
50 /* LCD Controler Pins */
51 struct s5pv210_lcd_regs{
52     volatile unsigned long vidcon0;
53     volatile unsigned long vidcon1;
54     volatile unsigned long vidcon2;
55     volatile unsigned long vidcon3;
56
57     volatile unsigned long vidtcon0;
58     volatile unsigned long vidtcon1;
59     volatile unsigned long vidtcon2;
60     volatile unsigned long vidtcon3;
61
62     volatile unsigned long wincon0;
63     volatile unsigned long wincon1;
64     volatile unsigned long wincon2;
65     volatile unsigned long wincon3;
66     volatile unsigned long wincon4;
67
68     volatile unsigned long shadowcon;
69     volatile unsigned long reserve1[2];
70
71     volatile unsigned long vidosd0a;
72     volatile unsigned long vidosd0b;
73     volatile unsigned long vidosd0c;
74 };
75
76 struct clk      *lcd_clk;
77 static struct s5pv210_lcd_regs *lcd_regs;
78
79 static long unsigned long *vidw00add0b0;
80 static long unsigned long *vidw00add1b0;
81
82 static u32  pseudo_palette[16];
83
84 /* from pxafb.c */
85 static  unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
86 {
87     chan &= 0xffff;
88     chan >>= 16 - bf->length;
89     return chan << bf->offset;
90 }
91
92 static int  clb210_lcdfb_setcolreg(unsigned regno,
93                    unsigned red, unsigned green, unsigned blue,
94                    unsigned transp, struct fb_info *info)
95 {
96     unsigned int val;
97
98     if (regno > 16)
99         return 1;
100
101     /* 用red,green,blue三原色构造出val */
102     val  = chan_to_field(red,   &info->var.red);
103     val |= chan_to_field(green, &info->var.green);
104     val |= chan_to_field(blue,  &info->var.blue);
105
106     pseudo_palette[regno] = val;
107
108     return 0;
109 }
110
111 //帧缓冲操作函数
112 static struct fb_ops clb210_lcdfb_ops =
113 {
114     .owner            = THIS_MODULE,
115     .fb_setcolreg    = clb210_lcdfb_setcolreg, //设置color寄存器和调色板
116     //下面这3个函数是通用的
117     .fb_fillrect    = cfb_fillrect,  //画一个矩形
118     .fb_copyarea    = cfb_copyarea,  //数据拷贝
119     .fb_imageblit    = cfb_imageblit, //图像填充
120 };
121
122 static int  __init clb210_lcd_init(void)
123 {
124     /* 1.分配一个fb_info */
125     clb_fbinfo = framebuffer_alloc(0 , NULL);
126
127     /* 2. 设置 */
128     /* 2.1 设置固定的参数 */
129     strcpy(clb_fbinfo->fix.id, "clb210_lcd");
130     clb_fbinfo->fix.smem_len = 800 * 480 * 32/8;
131     clb_fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
132     clb_fbinfo->fix.visual = FB_VISUAL_TRUECOLOR;
133     clb_fbinfo->fix.line_length = 800 * 32/8;
134
135     /* 2.2 设置可变的参数 */
136     clb_fbinfo->var.xres = 800;
137     clb_fbinfo->var.yres = 480;
138     clb_fbinfo->var.xres_virtual   = 800;
139     clb_fbinfo->var.yres_virtual   = 480;
140     clb_fbinfo->var.bits_per_pixel = 32;
141
142     /*RGB:888*/
143     clb_fbinfo->var.red.offset = 16;
144     clb_fbinfo->var.red.length = 8;
145
146     clb_fbinfo->var.green.offset = 8;
147     clb_fbinfo->var.green.length = 8;
148
149     clb_fbinfo->var.blue.offset = 0;
150     clb_fbinfo->var.blue.length = 8;
151
152     clb_fbinfo->var.activate = FB_ACTIVATE_NOW    ;
153
154     /* 2.3 设置操作函数 */
155     clb_fbinfo->fbops = &clb210_lcdfb_ops;
156
157     /* 2.4 其他的设置 */
158     /* 2.4.1 设置显存的大小 */
159     clb_fbinfo->screen_size =  800 * 480 * 32/8;
160
161     /* 2.4.2 设置调色板 */
162     clb_fbinfo->pseudo_palette = pseudo_palette;
163
164     /* 2.4.3 设置显存的虚拟起始地址 */
165     clb_fbinfo->screen_base = dma_alloc_writecombine(NULL,
166             clb_fbinfo->fix.smem_len, (u32*)&(clb_fbinfo->fix.smem_start), GFP_KERNEL);
167
168
169     /* 3. 硬件相关的操作 */
170     /* 3.1 获取lcd时钟,使能时钟 */
171     lcd_clk = clk_get(NULL, "lcd");
172     if (!lcd_clk || IS_ERR(lcd_clk)) {
173         printk(KERN_INFO "failed to get lcd clock source\n");
174     }
175     clk_enable(lcd_clk);
176
177     /* 3.2 配置GPIO用于LCD */
178     gpf0con = ioremap(0xE0200120, 4);
179     gpf1con = ioremap(0xE0200140, 4);
180     gpf2con = ioremap(0xE0200160, 4);
181     gpf3con = ioremap(0xE0200180, 4);
182     gpd0con = ioremap(0xE02000A0, 4);
183     gpd0dat = ioremap(0xE02000A4, 4);
184
185     gpd0con      = ioremap(0xE02000A0, 4);
186     gpd0dat      = ioremap(0xE02000A4, 4);
187
188     vidcon0      = ioremap(0xF8000000, 4);
189     vidcon1      = ioremap(0xF8000004, 4);
190     vidtcon0     = ioremap(0xF8000010, 4);
191     vidtcon1     = ioremap(0xF8000014, 4);
192     vidtcon2     = ioremap(0xF8000018, 4);
193     wincon0      = ioremap(0xF8000020, 4);
194     vidosd0a     = ioremap(0xF8000040, 4);
195     vidosd0b     = ioremap(0xF8000044, 4);
196     vidosd0c     = ioremap(0xF8000048, 4);
197     vidw00add0b0 = ioremap(0xF80000A0, 4);
198     vidw00add1b0 = ioremap(0xF80000D0, 4);
199     shodowcon    = ioremap(0xF8000034, 4);
200
201     display_control = ioremap(0xe0107008, 4);
202
203     /* 设置相关GPIO引脚用于LCD */
204     *gpf0con = 0x22222222;
205     *gpf1con = 0x22222222;
206     *gpf2con = 0x22222222;
207     *gpf3con = 0x22222222;
208
209     /* 使能LCD本身 */
210     *gpd0con |= 1<<4;
211     *gpd0dat |= 1<<1;
212
213     /* 显示路径的选择, 0b10: RGB=FIMD I80=FIMD ITU=FIMD */
214     *display_control  = 2<<0;
215
216     /* 3.3 映射LCD控制器对应寄存器 */
217     lcd_regs = ioremap(0xF8000000, sizeof(struct s5pv210_lcd_regs));
218     vidw00add0b0 = ioremap(0xF80000A0, 4);
219     vidw00add1b0 = ioremap(0xF80000D0, 4);
220
221     lcd_regs->vidcon0 &= ~((3<<26) | (1<<18) | (0xff<<6)  | (1<<2));
222     lcd_regs->vidcon0 |= ((5<<6) | (1<<4) );
223
224     lcd_regs->vidcon1 &= ~(1<<7);            /* 在vclk的下降沿获取数据 */
225     lcd_regs->vidcon1 |= ((1<<6) | (1<<5));  /* HSYNC极性反转, VSYNC极性反转 */
226
227     lcd_regs->vidtcon0 = (VBPD << 16) | (VFPD << 8) | (VSPW << 0);
228     lcd_regs->vidtcon1 = (HBPD << 16) | (HFPD << 8) | (HSPW << 0);
229     lcd_regs->vidtcon2 = (LINEVAL << 11) | (HOZVAL << 0);
230     lcd_regs->wincon0 &= ~(0xf << 2);
231     lcd_regs->wincon0 |= (0xB<<2)|(1<<15);
232     lcd_regs->vidosd0a = (LeftTopX<<11) | (LeftTopY << 0);
233     lcd_regs->vidosd0b = (RightBotX<<11) | (RightBotY << 0);
234     lcd_regs->vidosd0c = (LINEVAL + 1) * (HOZVAL + 1);
235
236     *vidw00add0b0 = clb_fbinfo->fix.smem_start;
237     *vidw00add1b0 = clb_fbinfo->fix.smem_start + clb_fbinfo->fix.smem_len;
238
239     lcd_regs->shadowcon = 0x1;  /* 使能通道0 */
240     lcd_regs->vidcon0  |= 0x3;  /* 开启总控制器 */
241     lcd_regs->wincon0 |= 1;     /* 开启窗口0 */
242
243
244     /*4.注册*/
245     register_framebuffer(clb_fbinfo);
246
247     return 0;
248 }
249 static void __exit clb210_lcd_exit(void)
250 {
251     unregister_framebuffer(clb_fbinfo);
252     dma_free_writecombine(NULL,  clb_fbinfo->fix.smem_len, clb_fbinfo->screen_base, clb_fbinfo->fix.smem_start);
253     iounmap(gpf0con);
254     iounmap(gpf1con);
255     iounmap(gpf2con);
256     iounmap(gpf3con);
257     iounmap(gpd0con);
258     iounmap(gpd0dat);

259     iounmap(display_control);
260     iounmap(lcd_regs);
261     iounmap(vidw00add0b0);
262     iounmap(vidw00add1b0);
263     framebuffer_release(clb_fbinfo);
264 }
265
266 module_init(clb210_lcd_init);
267 module_exit(clb210_lcd_exit);
268
269 MODULE_LICENSE("GPL");
270 MODULE_AUTHOR("clb");
271 MODULE_DESCRIPTION("Lcd driver for clb210 board");


这份代码没有基于platform设备驱动来编写,在内核源码中的demo就是基于platform驱动模型来搭建的,主要内容其实一样。

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: