读书人

Linux上的LCD驱动(二)

发布时间: 2012-12-29 10:28:09 作者: rapoo

Linux下的LCD驱动(二)

版权所有,转载请说明转自 http://my.csdn.net/weiqing1981127

3.3 LCD文件层

帧缓冲设备作为一个字符设备,其文件操作函数就定义在文件层fbmem.c中

static const struct file_operations fb_fops = {

.owner = THIS_MODULE,

.read = fb_read, //读

.write = fb_write, //写

.unlocked_ioctl = fb_ioctl, //控制

#ifdef CONFIG_COMPAT

.compat_ioctl = fb_compat_ioctl,

#endif

.mmap = fb_mmap, //映射

.open = fb_open, //打开

.release = fb_release, //释放

#ifdef HAVE_ARCH_FB_UNMAPPED_AREA

.get_unmapped_area = get_fb_unmapped_area,

#endif

#ifdef CONFIG_FB_DEFERRED_IO

.fsync = fb_deferred_io_fsync,

#endif

};

帧缓冲设备驱动的文件操作接口已经在fbmem.c中被统一实现了,一般不需要驱动工程师编写了。对于这个fbmem.c文件,它一方面在我们的帧缓冲文件层为用户提供了访问接口函数,一方面还设计了一些供内核其他函数调用的接口函数,例如register_framebuffer、fb_set_var和fb_blank等。

用户空间对帧设备的操作主要包括open、close、ioctl和mmap实现,下面我们主要看看ioctl和mmap的实现

static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)

{

struct inode *inode = file->f_path.dentry->d_inode;

int fbidx = iminor(inode); //获得索引号

struct fb_info *info = registered_fb[fbidx]; //获取fb_info结构体

return do_fb_ioctl(info, cmd, arg); //调用二次ioctl函数

}

static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,

unsigned long arg)

{

struct fb_ops *fb;

struct fb_var_screeninfo var;

struct fb_fix_screeninfo fix;

struct fb_con2fbmap con2fb;

struct fb_cmap cmap_from;

struct fb_cmap_user cmap;

struct fb_event event;

void __user *argp = (void __user *)arg;

long ret = 0;

switch (cmd) {

case FBIOGET_VSCREENINFO: //获得可变屏幕参数

if (!lock_fb_info(info))

return -ENODEV;

var = info->var;

unlock_fb_info(info);

ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;

break;

case FBIOPUT_VSCREENINFO: //设置可变屏幕参数

if (copy_from_user(&var, argp, sizeof(var)))

return -EFAULT;

if (!lock_fb_info(info))

return -ENODEV;

acquire_console_sem();

info->flags |= FBINFO_MISC_USEREVENT;

ret = fb_set_var(info, &var);

info->flags &= ~FBINFO_MISC_USEREVENT;

release_console_sem();

unlock_fb_info(info);

if (!ret && copy_to_user(argp, &var, sizeof(var)))

ret = -EFAULT;

break;

case FBIOGET_FSCREENINFO: //获得固定屏幕参数

if (!lock_fb_info(info))

return -ENODEV;

fix = info->fix;

unlock_fb_info(info);

ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;

break;

case FBIOPUTCMAP: //设置颜色表

if (copy_from_user(&cmap, argp, sizeof(cmap)))

return -EFAULT;

ret = fb_set_user_cmap(&cmap, info);

break;

case FBIOGETCMAP: //获得颜色表

if (copy_from_user(&cmap, argp, sizeof(cmap)))

return -EFAULT;

if (!lock_fb_info(info))

return -ENODEV;

cmap_from = info->cmap;

unlock_fb_info(info);

ret = fb_cmap_to_user(&cmap_from, &cmap);

break;

case FBIOPAN_DISPLAY:

if (copy_from_user(&var, argp, sizeof(var)))

return -EFAULT;

if (!lock_fb_info(info))

return -ENODEV;

acquire_console_sem();

ret = fb_pan_display(info, &var);

release_console_sem();

unlock_fb_info(info);

if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))

return -EFAULT;

break;

case FBIO_CURSOR:

ret = -EINVAL;

break;

case FBIOGET_CON2FBMAP:

if (copy_from_user(&con2fb, argp, sizeof(con2fb)))

return -EFAULT;

if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)

return -EINVAL;

con2fb.framebuffer = -1;

event.data = &con2fb;

if (!lock_fb_info(info))

return -ENODEV;

event.info = info;

fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event);

unlock_fb_info(info);

ret = copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0;

break;

case FBIOPUT_CON2FBMAP:

if (copy_from_user(&con2fb, argp, sizeof(con2fb)))

return -EFAULT;

if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)

return -EINVAL;

if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)

return -EINVAL;

if (!registered_fb[con2fb.framebuffer])

request_module("fb%d", con2fb.framebuffer);

if (!registered_fb[con2fb.framebuffer]) {

ret = -EINVAL;

break;

}

event.data = &con2fb;

if (!lock_fb_info(info))

return -ENODEV;

event.info = info;

ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event);

unlock_fb_info(info);

break;

case FBIOBLANK:

if (!lock_fb_info(info))

return -ENODEV;

acquire_console_sem();

info->flags |= FBINFO_MISC_USEREVENT;

ret = fb_blank(info, arg);

info->flags &= ~FBINFO_MISC_USEREVENT;

release_console_sem();

unlock_fb_info(info);

break;

default:

if (!lock_fb_info(info))

return -ENODEV;

fb = info->fbops;

if (fb->fb_ioctl)

ret = fb->fb_ioctl(info, cmd, arg);

else

ret = -ENOTTY;

unlock_fb_info(info);

}

return ret;

}

下面是fb_mmap函数

static int fb_mmap(struct file *file, struct vm_area_struct * vma)

{

int fbidx = iminor(file->f_path.dentry->d_inode);

struct fb_info *info = registered_fb[fbidx];

struct fb_ops *fb = info->fbops;

unsigned long off;

unsigned long start;

u32 len;

if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))

return -EINVAL;

off = vma->vm_pgoff << PAGE_SHIFT;

if (!fb)

return -ENODEV;

mutex_lock(&info->mm_lock);

if (fb->fb_mmap) {

int res;

res = fb->fb_mmap(info, vma);

mutex_unlock(&info->mm_lock);

return res;

}

start = info->fix.smem_start; //帧缓冲区内存

len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);

if (off >= len) { //内存映射的I/O

off -= len;

if (info->var.accel_flags) {

mutex_unlock(&info->mm_lock);

return -EINVAL;

}

start = info->fix.mmio_start;

len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len);

}

mutex_unlock(&info->mm_lock);

start &= PAGE_MASK;

if ((vma->vm_end - vma->vm_start + off) > len)

return -EINVAL;

off += start;

vma->vm_pgoff = off >> PAGE_SHIFT;

//这是一个I/O映射,告诉maydump跳过此VMA

vma->vm_flags |= VM_IO | VM_RESERVED;

fb_pgprotect(file, vma, off);

if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,

vma->vm_end - vma->vm_start, vma->vm_page_prot))

return -EAGAIN;

return 0;

}

好了,我们已经分析完了帧缓冲驱动的文件层和设备层代码了。

总结下,帧缓冲设备为用户提供file_operations结构体,其实现定义在fbmem.c中;特定的帧缓冲设备fb_info结构体成员的注册,尤其是fb_ops中成员的实现则是由s3c2410fb.c实现,fb_ops中的成员将最终操作lcd控制器硬件寄存器。那fbmem.c和s3c2410fb.c怎么相连的呢?其实是通过fb_info结构体,在s3c2410fb.c中调用register_framebuffer注册fb_info时,其实是把fb_info注册到一个叫struct fb_info *registered_fb[FB_MAX]这样的一个数组中的,那么我们在fbmem.c中就可以通过次设备号作为registered_fb数组的索引号查找到相应的fb_info,从而能够调用fb_info中实现的fb_ops的。

四.LCD驱动测试

对于我们的Mini2440开发板,使用的是X35型号的LCD,根据X35LCD屏的说明文档,进行移植LCD驱动,并编译成内核,烧写到开发板中。

实验环境:内核linux2.6.32.2,arm-linux-gcc交叉编译器,mini2440开发板。

内核配置:配置时候我们需要选中fbmem.c、s3c2410.c文件以及X35LCD型号

具体测试代码如下

#include <unistd.h>

#include <stdlib.h>

#include <stdio.h>

#include <fcntl.h>

#include <linux/fb.h>

#include <sys/mman.h>

int main()

{

int fbfd=0;

struct fb_var_screeninfo vinfo;

unsigned long screensize=0;

char *fbp=0;

int x=0,y=0,i=0;

fbfd=open("/dev/fb0",O_RDWR); //打开帧缓冲设备

if(!fbfd){

printf("error\n");

exit(1);

}

if(ioctl(fbfd,FBIOGET_VSCREENINFO,&vinfo)){ //获取屏幕可变参数

printf("error\n");

exit(1);

}

//打印屏幕可变参数

printf("%dx%d,%dbpp\n",vinfo.xres,vinfo.yres,vinfo.bits_per_pixel);

screensize=vinfo.xres*vinfo.yres*2; //缓冲区字节大小

fbp=(char *)mmap(0,screensize,PROT_READ|PROT_WRITE,MAP_SHARED,fbfd,0);//映射

if((int)fbp==-1){

printf("error\n");

exit(4);

}

for(i=0;i<3;i++){ //画图

for(y=i*(vinfo.yres/3);y<(i+1)*(vinfo.yres/3);y++){

for(x=0;x<vinfo.xres;x++){

long location=x*2+y*vinfo.xres*2;

int r=0,g=0,b=0;

unsigned short rgb;

if (i==0)

r=((x*1.0)/vinfo.xres)*32;

if (i==1)

g=((x*1.0)/vinfo.xres)*64;

if (i==2)

b=((x*1.0)/vinfo.xres)*32;

rgb=(r<<11)|(g<<5)|b;

*((unsigned short *)(fbp+location))=rgb;

}

}

}

munmap(fbp,screensize);

close(fbfd);

return 0;

}

虚拟机下编译arm-linux-gcc lcd.c -o lcd

在超级终端下运行:./lcd

可以见到:屏幕上有三道颜色

读书人网 >UNIXLINUX

热点推荐