imx7 lcd驱动分析 2

       任何Linux设备驱动都有两个层次,一个是偏底层硬件的SOC寄存器编程,一个是偏上层应用的Linux子系统软件接口,前者负责和硬件的交互,后者负责跟上层应用交互。Linux为了给用户提供统一的编程接口,在所有的设备驱动之上再架设一层公共接口层,如所有驱动都可以通过open、read、write来进行操作。

       我们在用户进程中是申请一块物理连续的内存块(返回地址是0-3G的进程虚拟地址空间),并将多个图像资源数据(如文字,图像等)放到这个内存中。当用户图像数据buffer和内核虚拟地址空间buffer对应的都是同一块物理内存。当资源数据拷贝到用户图像数据buffer时,即是直接拷贝到显示物理内存了。所以,framebuffer驱动最重要的功能就是给用户提供一个进程空间映射到实际的显示物理内存的接口(mmap)。它跟进程间通信的共享变量的原理是一致的。

    framebuffer支持32个显示缓存(registered_fb[32])。其在内部进行了抽象,即其向上层应用统一抽象为一个字符主设备,而不同的显示缓存即视为不同的字符从设备。对显示从设备的管理属于Framebuffer内部框架的功能。

       在linux设备驱动中,所有的显示缓存设备均由framebuffer子系统内部管理,即linux设备驱动框架只认识一个主设备号为29的framebuffer设备。应用层所有针对显示缓存(最多32个)的访问均会推送给fb_fops进行进一步分发操作。

static const struct file_operations fb_fops = {
	.owner =	THIS_MODULE,
	.read =		fb_read,
	.write =	fb_write,
	.unlocked_ioctl = fb_ioctl,
	.mmap =		fb_mmap,
	.open =		fb_open,
	.release =	fb_release,
	.llseek =	default_llseek,
};

 fb_info结构体

 fb_info结构体代表单个显示缓存从设备,在调用register_framebuffer接口之前,必须要初始化其中的重要数据成员。其定义如下:

 

 其中,fb_var_screeninfo和fb_fix_screeninfo两个结构体跟LCD硬件属性相关,fb_var_screeninfo代表可修改的LCD显示参数,如分辨率和像素比特数;fb_fix_screeninfo代表不可修改的LCD属性参数,如显示内存的物理地址和长度等。另外一个非常重要的成员是fb_ops,其是LCD底层硬件操作接口集。

 fb_ops硬件操作接口集包含很多接口,如设置可变参数fb_set_par、设置颜色寄存器fb_setcolreg、清屏接口fb_blank、画位图接口fb_imageblit、内存映射fb_mmap等等。

 fb_info结构体在调用register_framebuffer之前完成初始化。一般来说,LCD设备属于平台设备,其初始化是在平台设备驱动的probe接口完成。而LCD设备所涉及的硬件初始化则在平台设备初始化中完成。
 

fb_open接口

 当一个framebuffer设备完成初始化时,其对应的fb_info结构体会在全局数组registered_fb中记录,并且位于跟从设备号相等的位置上;而且,在/dev目录下也会生成一个/dev/fbx的设备文件。以下分析假设是访问第一个framebuffer设备:

 对于应用层open(“/dev/fb0”,…)访问该framebuffer设备来说,vfs先通过设备名(/dev/fb0)获得该设备的主设备(29)和从设备号(0)。而linux设备驱动框架则通过主设备29找到该设备对应的设备驱动接口集fb_fops。Linux驱动框架的分析过程请看《Linux字符设备驱动剖析》。接着linux设备驱动框架会调用fb_fops的fb_open,我们来跟踪一下:

这个接口很简单,即是次设备号在全局数组registered_fb中找出对应的fb_info数据结构,将其设置到file指针的私有数据中,便于之后用户访问调用其他接口如mmap、ioctl等能够直接找到对应的fb_info。最后也会调用fb_info->fb_ops->fb_open,不过这个接口一般没干啥,赋值为NULL即可。

 所以,framebuffer子系统内部驱动框架即负责通过从设备号找到对应的从设备(具体LCD操作接口所在的fb_info)。

5.     驱动框架总结

 经过上面的分析,这张图应该很容易理解了。

 

三、mmap映射

 framebuffer驱动最重要的功能就是给用户提供一个进程空间映射到实际的显示物理内存的接口(mmap)。当用户图像数据buffer和内核虚拟地址空间buffer对应的都是同一块物理内存时,资源数据拷贝到用户图像数据buffer时,即是直接拷贝到显示物理内存了。

 

 应用层mmap映射接口会经历以下层次调用:

1.     sys_mmap

 sys_mmap是虚拟文件系统层的映射实现,其会在用户进程虚拟空间(0到3G)申请一块虚拟内存,最终会调用到framebuffer子系统的fb_mmap,并向其传递vm_area_struct结构体指针,该结构体已经包括进程用户空间的内存地址信息。

 Vfs虚拟文件系统是linux系统的重要组成部分,这里不再展开,以后再分析。

2.     fb_mmap

 来跟踪一下:

 

 该接口也是找到具体的从设备对应的LCD操作接口集,并调用fb_mmap接口。

 我们选一个具体的LCD的接口实现看看:

 

 remap_pfn_range接口即是建立进程地址空间(0到3G)到实际的显示物理内存的映射。其中,lcd_mem是fb_info结构体初始化使用kzmalloc申请的,其代表内核态(3G到4G)的虚拟内存首地址。而virt_to_phys即是将虚拟地址转化为物理地址,更新vm_area_struct的数据成员,而其最终会影响进程的页表设置,进而影响MMU的设置。

 当该接口完成之后,最终向应用层返回进程空间的内存首地址(0到3G)。这样,应用层即可以直接访问这块内存,进行读写操作。其直接通过MMU来访问实际的物理地址,而不需要再经过驱动的管理。

接口使用

       如下,操作是不是很简单?不过,大家要记得,framebuffer只负责位图显示,而很多图像都是有编码格式的,如JPG等等,需要先解码,再送给framebuffer。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
#include <linux/fb.h>

static int fd_fb;
static struct fb_var_screeninfo var;    /* Current var */
static int screen_size;
static unsigned char *fb_base;
static unsigned int line_width;
static unsigned int pixel_width;

/**********************************************************************
 * 函数名称: lcd_put_pixel
 * 功能描述: 在LCD指定位置上输出指定颜色(描点)
 * 输入参数: x坐标,y坐标,颜色
 * 输出参数: 无
 * 返 回 值: 会
 * 修改日期        版本号     修改人          修改内容
 * -----------------------------------------------
 * 2020/05/12        V1.0     zh(angenao)         创建
 ***********************************************************************/ 
void lcd_put_pixel(int x, int y, unsigned int color)
{
    unsigned char *pen_8 = fb_base + y*line_width + x*pixel_width;
    unsigned short *pen_16; 
    unsigned int *pen_32;   

    unsigned int red, green, blue;  

    pen_16 = (unsigned short *)pen_8;
    pen_32 = (unsigned int *)pen_8;

    switch (var.bits_per_pixel)
    {
        case 8:
        {
            *pen_8 = color;
            break;
        }
        case 16:
        {
            /* 565 */
            red   = (color >> 16) & 0xff;
            green = (color >> 8) & 0xff;
            blue  = (color >> 0) & 0xff;
            color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
            *pen_16 = color;
            break;
        }
        case 32:
        {
            *pen_32 = color;
            break;
        }
        default:
        {
            printf("can't surport %dbpp\n", var.bits_per_pixel);
            break;
        }
    }
}



int main(int argc, char **argv)
{
    int i,j;
    fd_fb = open("/dev/fb0",O_RDWR);
    if(fd_fb < 0)
    {
        printf("can't open /dev/fb0\n");
        return -1;
    }
    if(ioctl(fd_fb,FBIOGET_VSCREENINFO,&var))
    {
        printf("can't get var\n");
        return -1;
    }

    line_width = var.xres * var.bits_per_pixel / 8;
    pixel_width = var.bits_per_pixel / 8;
    screen_size = var.xres *  var.yres * var.bits_per_pixel / 8;
    fb_base = (unsigned char *)mmap(NULL,screen_size,PROT_READ | PROT_WRITE,MAP_SHARED,fd_fb,0);
    printf("var.xres = %d, var.yres = %d,line_width = %d, pixel_width = %d ,screen_size = %d ,fb_base = %x,fb_base+screen_size=%x\n",var.xres, var.yres,line_width, pixel_width,screen_size,fb_base,fb_base+screen_size);
    if(fb_base == (unsigned char *)-1)
    {
        printf("can't mmap\n");
        return -1;
    }

     /* 清屏: 全部设为白色 */
    memset(fb_base, 0xff, screen_size);
    
    /* 随便设置出300个为红色 */
    for (i = 0; i < 300; i++)
        lcd_put_pixel(var.xres/2+i, var.yres/2, 0xFF0000);
    
    printf("size=%d\n",sizeof(unsigned int));
    munmap(fb_base,screen_size);
    close(fd_fb);
    return 0;
}

 

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值