linux驱动LCD框架

框架核心文件fbmem.c:
fbmem_init:
    register_chrdev(FB_MAJOR,"fb",&fb_fops)
    fb_class = class_create(THIS_MODULE, "graphics");
该函数中注册了字符设备驱动,注册了一个fops结构体fb_fops,该结构体
中有常见的字符设备驱动中该有的操作函数。
思考:现在就定义好了操作函数,那么不同的LCD硬件上的不同怎样体现
出来呢?
猜测:LCD属于协议类的接口,协议类接口有两大要素,一是传输协议本身,
而是传输所遵循的时序。(通俗来讲就是双方要想能够通话,一需要
保证统一语言,二需要保证双方适应对方的语速)。所有的LCD传输协议是
不变的,这一部分可以写成公共的操作代码,即一个具有这些操作的对象
(面向对象的方法),而时序部分的操作不同的LCD屏幕就有所不同了。
这就是个性部分,也是设备驱动部分真正需要我们操心的部分。

fb_open:
    int fbidx = iminor(inode);
    info = registered_fb[fbidx];
    if (info->fbops->fb_open) {
        res = info->fbops->fb_open(info,1);
可以看到open函数中最终调用的是registered_fb[fbidx]->fbops->fb_open(info,1)
在registered_fb数组中根据次设备号取出对应的操作对象,使用该
对象中的成员及成员函数来进行操作。
思考:上述做法的优势是非常明显的,我们需要怎样进行操作,就怎样
去设置那个操作结构体就ok了,该结构体中的成员的具体实现是留给
我们驱动开发人员的接口,具体怎样实现可以很灵活,但是实例化好这个
结构体后是在哪里将其放入registered_fb数组的呢?

根据以往的经验,应该会有一个注册函数,将我们实例化好的对象注册
到这个数组中。我们在框架代码中查找,发现了如下函数:
register_framebuffer:
    num_registered_fb++;
        for (i = 0 ; i < FB_MAX; i++)
            if (!registered_fb[i])
                break;
    fb_info->dev = device_create(fb_class, fb_info->device,
                     MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
    registered_fb[i] = fb_info;
从上面可以看出是在register_framebuffer这个函数中将fb_info这个
结构体注册到registered_fb数组中的,并创建了设备节点。
思考:那么这个函数在什么时候被调用呢?
猜测:我们驱动工程师在实现具体LCD的驱动程序时,主要就是要构建
fb_info结构体,将设置好的结构体通过该函数注册到驱动体系中,
这样具体对于驱动的操作就转嫁到fb_info结构体上来了。所以我们实现
LCD驱动时:
一、分配一个fb_info结构体
二、设置该结构体
三、register_framebuffer注册进驱动体系中
四、进行硬件相关的设置。(gpio、clock、SRF、操作时序等)


下面再来看read函数
fb_read:
    if (info->fbops->fb_read)
        return info->fbops->fb_read(info, buf, count, ppos);
    
    total_size = info->screen_size;

    if (total_size == 0)
        total_size = info->fix.smem_len;

    if (p >= total_size)
        return 0;

    if (count >= total_size)
        count = total_size;

    if (count + p > total_size)
        count = total_size - p;

    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);
从上面这段代码可以看出fb_info结构体中我们应该要设置哪些值。
由于该函数的结尾实现了读显存的函数,所以fb_info中可以不设置
fb_read成员,如果设置fb_read成员,则用该读函数,如果不设置
则用该函数中默认的代码。info->screen_size或者info->fix.smem_len
需要设置,这是显存的字节数,也是整个屏幕上像素占的字节数。
info->screen_base代表显存基地址的物理地址,需要设置,后面的读
操作需要用到该地址映射之后的基地址。info->fbops->fb_sync成员
函数可以不用设置。

fb_write函数与上面的读函数是对称的,这里不再单独说明。

下面是fb_ioctl函数
fb_ioctl:
    // 根据次设备号得到fb_info结构体
    struct fb_info *info = registered_fb[fbidx]
    // 根据用户空间传来的命令和参数以及获取的fb_info结构体来处理这些事件
    do_fb_ioctl(info, cmd, arg)
        switch (cmd) {
        case FBIOGET_VSCREENINFO:
            var = info->var;
            // 返回info->var信息
            ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
    
        case FBIOPUT_VSCREENINFO
            ret = fb_set_var(info, &var);
                // 核查fb_var_screeninfo中的数据是否有错误
                ret = info->fbops->fb_check_var(var, info);
                    ...   // 进行一系列校验
                    s3cfb_set_bitfield(var);  // 设置位域
                    s3cfb_set_alpha_info(var, win);  // 设置透明度
                    
                old_var = info->var;  // 旧的fb_var_screeninfo
                info->var = *var;    // 新的fb_var_screeninfo
                
                ret = info->fbops->fb_set_par(info);   // 设置窗口信息以及一些寄存器,显存映射(进行一些硬件SFR方面的操作)
                    s3cfb_set_win_params(fbdev, win->id)
                        // 设置WINCON寄存器
                        s3cfb_set_window_control(ctrl, id)
                        // 设置window position寄存器
                        s3cfb_set_window_position(ctrl, id)
                        // 设置window size
                        s3cfb_set_window_size(ctrl, id)
                        // 设置显存起始与结束寄存器
                        s3cfb_set_buffer_address(ctrl, id)
                        // 设置图像buffer size
                        s3cfb_set_buffer_size(ctrl, id)
                        if (id > 0) {
                            // 设置透明度方式(以像素为单位或者以整个帧为单位)
                            s3cfb_set_alpha_blending(ctrl, id);
                            // 设置浓度值
                            s3cfb_set_chroma_key(ctrl, id);
                        }
        case FBIOGET_FSCREENINFO:
            fix = info->fix
            // 将屏幕的fix信息返回到用户空间
            ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0
            
            
        case FBIOPUTCMAP:
            ret = fb_set_user_cmap(&cmap, info)
                rc = fb_set_cmap(&umap, info)
                    // 设置调色板
                    info->fbops->fb_setcolreg(start++,
                              hred, hgreen, hblue,
                              htransp, info)
            
        case FBIOGETCMAP:
            cmap_from = info->cmap
            // 获取调色板到用户空间
            ret = fb_cmap_to_user(&cmap_from, &cmap)
            
        case FBIOPAN_DISPLAY:   // 双缓冲显示时有效
            ret = fb_pan_display(info, &var)
                err = info->fbops->fb_pan_display(var, info)
                    fb->var.yoffset = var->yoffset
                    // 驱动人员开发接口(设置显存起始与结束寄存器)
                    s3cfb_set_buffer_address(fbdev, win->id)
            
        case FBIOBLANK:
            ret = info->fbops->fb_blank(blank, info)
                case FB_BLANK_UNBLANK:
                    // 驱动开发人员接口(显示原始图像,非空白格式)
                    s3cfb_win_map_off(fbdev, win->id)
                    // 驱动人员开发接口(使能video输出)
                    s3cfb_set_window(fbdev, win->id, 1)
                case FB_BLANK_NORMAL:
                    // 驱动开发人员的接口(空白格式,显示背景,这里是全黑,0像素)
                    s3cfb_win_map_on(fbdev, win->id, 0)
                    // 驱动开发人员的接口(开启video输出)
                    s3cfb_set_window(fbdev, win->id, 1);
                case FB_BLANK_POWERDOWN:
                    // 驱动开发人员的接口(关闭video传输)
                    s3cfb_set_window(fbdev, win->id, 0);
                    // 显示原始图像
                    s3cfb_win_map_off(fbdev, win->id);
            
        default:
            ret = fb->fb_ioctl(info, cmd, arg)
                case FBIO_WAITFORVSYNC:
                    // 等待垂直同步信号(一帧结束)
                    s3cfb_wait_for_vsync(fbdev)
                case S3CFB_WIN_POSITION:
                    // 驱动开发人员的接口(设置window位置)
                    s3cfb_set_window_position(fbdev, win->id)
                case S3CFB_WIN_SET_PLANE_ALPHA:
                    // 驱动开发人员的接口(设置透明度)
                    s3cfb_set_alpha_blending(fbdev, win->id)
                case S3CFB_WIN_SET_CHROMA:
                    // 驱动开发人员的接口(设置浓度值)
                    s3cfb_set_chroma_key(fbdev, win->id)
                case S3CFB_SET_VSYNC_INT:
                    // 驱动开发人员的接口(设置frame帧中断(开启或关闭))
                    s3cfb_set_vsync_interrupt(fbdev, p.vsync)
                case S3CFB_GET_CURR_FB_INFO:
                    // 将fb_info传到用户空间
                    copy_to_user((void *)arg, (struct s3cfb_next_info *) &next_fb_info, sizeof(struct s3cfb_next_info))
                case S3CFB_GET_LCD_ADDR:
                    LCDControllerBase = (volatile unsigned int *)ioremap(0xf8000000,1024);
                    // 获取framebuffer的起始地址
                    framebuffer_addr = LCDControllerBase[0xa0/4 + (win->id)*2];
                    iounmap(LCDControllerBase);
                    // 传到用户空间
                    copy_to_user((void *)arg, &framebuffer_addr, sizeof(int))
                    
上面是对核心文件fbmem.c的fb_ioctl函数的全部分析,用户空间通过ioctl
函数可以调用到内核空间的该函数,对LCD按照cmd进行相关的设置。

总结:在这个核心文件fbmem.c中主要做的两件事:一是创建了一个class,在这个class中
我们驱动开发人员可以添加节点,二是注册了一个字符设备驱动,绑定了一个
fops,该fops提供了基本的字符设备操作函数,不过该函数只是一个空壳子,
其经过一次转换最终调用的是fb_info中的操作函数,这个fb_info结构体
相当于一个操作句柄,而该结构体的定义就是内核为我们驱动开发人员
提供的开放接口,由我们自由定义,那么我们驱动开发人员要做的就是
分配并设置好该结构体,通过register_framebuffer注册该操作句柄,
并且针对我们要开发的具体设备的特性进行硬件
相关的操作就行了,并且s3cfb_fimd6x.c中内核已经为我们提供了很多
硬件的操作方法,我们主要需要操心的是为这些方法提供数据参数,而
这些数据参数就是根据不同硬件的具体特性而来的。这就是linux驱动中
的数据与程序分离的思想,程序框架之间分层的思想。(很精妙,移植起来非常方便)

猜你喜欢

转载自blog.csdn.net/Wenlong_L/article/details/81909336