LCD(三)驱动分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/JerryGou/article/details/79979190

一、框架分析

①内核装载LCD驱动模块:设置并注册fb_info结构,初始化LCD硬件。

②APP打开LCD设备,获取设备文件,根据设备文件进行读写显存。

③在内核中,根据主设备号和次设备号定位一个fb_info结构,如果应用层的系统调用是读操作则调用fb_ops中对应的操作函数,写操作也是一样。

二、帧缓冲子系统(Framebuffer

    Linux下可支持多个帧缓冲设备,最多可达32个(通过内核宏定义设置),分别为/dev/fb0~/dev/fb31。帧缓冲设备为标准字符设备,主设备号为29,次设备号为0~31。

三、帧缓冲子系统数据结构

fb_info结构

包括了对帧缓冲设备属性和方法的完整描述。

每一个帧缓冲设备对应一个fb_info,fb_info在/linux/fb.h中定义。

struct fb_info {
    int node;                        //用作次设备号索引
    int flags;
    struct mutex lock;                //用于open/release/ioctl函数的锁
    struct fb_var_screeninfo var;    //可变参数,重点
    struct fb_fix_screeninfo fix;    //固定参数,重点
    struct fb_monspecs monspecs;    //显示器标准
    struct work_struct queue;        //帧缓冲区队列
    struct fb_pixmap pixmap;        //图像硬件映射
    struct fb_pixmap sprite;        //光标硬件映射
    struct fb_cmap cmap;            //当前颜色表
    struct list_head modelist;      //模式链表
    struct fb_videomode *mode;        //当前video模式

    char __iomem *screen_base;        //显存基地址
    unsigned long screen_size;        //显存大小
    void *pseudo_palette;            //伪16色颜色表
#define FBINFO_STATE_RUNNING    0
#define FBINFO_STATE_SUSPENDED    1
    u32 state;                        //硬件状态,如挂起
    void *fbcon_par;                //用作私有数据区
    void *par;                        //info->par指向了额外多申请内存空间的首地址
};

 

fb_var_screeninfo结构

 

记录可修改的控制器参数,如分辨率、像素比特数等。

 

fb_fix_screeninfo结构

记录不可修改的控制器参数,如屏幕缓冲区的物理地址、长度等。

 

fb_ops结构

对底层硬件操作的函数指针,实现对硬件的操作。

struct fb_ops {
    struct module *owner;
    int (*fb_open)(struct fb_info *info, int user);
    int (*fb_release)(struct fb_info *info, int user);
    ssize_t (*fb_read)(struct fb_info *info, char __user *buf,size_t count, loff_t *ppos);
    ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,size_t count, loff_t *ppos);

    /* 检测可变参数,并调整到支持的值 */
    int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);

    /* 根据 info->var 设置 video 模式 */
    int (*fb_set_par)(struct fb_info *info);

    /* set color register */
    int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,unsigned blue, unsigned transp, struct fb_info *info);
    /* set color registers in batch */
    int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
    /* blank display */
    int (*fb_blank)(int blank, struct fb_info *info);
    /* pan display */
    int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);

    /* Draws a rectangle */
    void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
    /* Copy data from area to another */
    void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
    /* Draws a image to the display */
    void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);

    ......

    /* perform fb specific ioctl (optional) */
    int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
            unsigned long arg);
    /* perform fb specific mmap */
    int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);

    ......
};
fb_open
{
    int fbidx = iminor(inode);         //获取次设备号
    struct fb_info *info;
    info = get_fb_info(fbidx);
        struct fb_info *fb_info;
        fb_info = registered_fb[fbidx];//根据次设备号从已注册的fb_info数组中获取响应的结构
        return fb_info;
    ......
    /* 
     * 从registered_fb[]数组项里找到fb_info结构体后,将其保存到 
     * struct file结构中的私有信息成员,难道这是为了以后在某些情况方便找到并调用??先放着...
     * 回过来发现:这样做是为了验证在read、write、ioctl等系统调用中获得的fb_info结构和open获得的是否一样
     */  
    file->private_data = info;
    //info->fbops->fb_open无定义,这是值得思考的问题!
    if (info->fbops->fb_open) {
        res = info->fbops->fb_open(info,1);
        if (res)
            module_put(info->fbops->owner);
    }
    ......
}
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    struct fb_info *info = file_fb_info(file);
        struct inode *inode = file_inode(file);
        int fbidx = iminor(inode);
        //也是根据次设备号来获取fb_info结构
        struct fb_info *info = registered_fb[fbidx];
    
        if (info != file->private_data)
            info = NULL;
        return info;
    //无定义
    if (info->fbops->fb_read)
        return info->fbops->fb_read(info, buf, count, ppos);
    //获得显存的大小
    total_size = info->screen_size;
    //如果应用层要读的数据count比实际最大的显存还要大,修改count值为最大显存值
    if (count >= total_size)
        count = total_size;
    //分配显存,最大只能是一页PAGE_SIZE=4KB
    buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,GFP_KERNEL);
    //要读的源地址:显存虚拟基地址+偏移
    src = (u8 __iomem *) (info->screen_base + p);
    while (count) {
        c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;
        //读的目的地址
        dst = buffer;
        //读操作:拷贝数据
        fb_memcpy_fromfb(dst, src, c);
        dst += c;
        src += c;

        if (copy_to_user(buf, buffer, c)) {
            err = -EFAULT;
            break;
        }
        *ppos += c;
        buf += c;
        cnt += c;
        count -= c;
    }
    kfree(buffer); //释放buffer,只起到临时中转站的作用
}
static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    unsigned long p = *ppos;
    struct fb_info *info = file_fb_info(file); //获取fb_info结构
/************************************************************
    函数跟进分析:
    static struct fb_info *file_fb_info(struct file *file)
    {
        struct inode *inode = file_inode(file); 
        int fbidx = iminor(inode);                   //获取次设备号
        struct fb_info *info = registered_fb[fbidx]; //根据次设备号获取相应的fb_info结构
    
        if (info != file->private_data)
            info = NULL;
        return info;  //返回fb_info结构
    }
************************************************************/
    u8 *buffer, *src;
    u8 __iomem *dst;
    int c, cnt = 0, err = 0;
    unsigned long total_size;
    //获取fb_info失败或者fb_info结构中没有设置显存基址,返回
    if (!info || !info->screen_base)
        return -ENODEV;

    if (info->state != FBINFO_STATE_RUNNING)
        return -EPERM;
    //如果帧缓冲操作函数结构中有重定义fb_write函数,优先使用!实际上没有。
    if (info->fbops->fb_write)
        return info->fbops->fb_write(info, buf, count, ppos);
    //获取显存大小
    total_size = info->screen_size;

    if (total_size == 0)
        total_size = info->fix.smem_len;
    //如果写偏移位置p比整个显存还要大,出错返回。
    if (p > total_size)
        return -EFBIG;

    if (count > total_size) {
        err = -EFBIG;
        count = total_size;
    }

    if (count + p > total_size) {
        if (!err)
            err = -ENOSPC;

        count = total_size - p;
    }
    //内核空间分配临时帧缓冲区
    buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,GFP_KERNEL);
    if (!buffer)
        return -ENOMEM;
    //计算写目的地址(虚拟地址:内核空间中能够操作的也就是虚拟地址)
    dst = (u8 __iomem *) (info->screen_base + p);

    if (info->fbops->fb_sync)
        info->fbops->fb_sync(info);

    while (count) {
        c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
        //源地址
        src = buffer;

        if (copy_from_user(src, buf, c)) {
            err = -EFAULT;
            break;
        }
        // 从内存buffer拷贝数据到帧缓冲区
        fb_memcpy_tofb(dst, src, c);
        dst += c;
        src += c;
        *ppos += c;
        buf += c;
        cnt += c;
        count -= c;
    }

    kfree(buffer);
    return (cnt) ? cnt : err;
}
/* 
 * 函数功能:将内核空间分配的物理显存空间映射到用户空间中 
 * 用户空间就能访问这段内存空间了
 */  
static int fb_mmap(struct file *file, struct vm_area_struct * vma)
{
    struct fb_info *info = file_fb_info(file);
    struct fb_ops *fb;
    unsigned long mmio_pgoff;
    unsigned long start;
    u32 len;

    if (!info)
        return -ENODEV;
    fb = info->fbops;
    if (!fb)
        return -ENODEV;
    mutex_lock(&info->mm_lock);
    //如果fb_info->fbops->fb_mmap存在就调用该函数,实际中没有!
    if (fb->fb_mmap) {
        int res;
        res = fb->fb_mmap(info, vma);
        mutex_unlock(&info->mm_lock);
        return res;
    }
    /*
    * fb缓冲内存的开始位置(物理地址)
    * info->fix.smem_start这个地址是在哪里被设置的?
    * 在驱动程序xxx_lcd_init()函数中:
    * clb_fbinfo->screen_base = dma_alloc_writecombine(NULL,clb_fbinfo->fix.smem_len, 
    * (u32*)&(clb_fbinfo->fix.smem_start), GFP_KERNEL);
    * dma_alloc_writecombine函数返回的是内核虚拟起始地址,同时第3个参数fix.smem_start会被设置成对应的物理起始地址。
    * 内核中操作这个分配的空间只能操作虚拟的地址空间!!!
    * dma_alloc_writecombine函数的调用只是把物理显存映射到内核空间,并没有映射到用户空间,因此用户在操作物理显存之前要先把
    * 物理显存空间映射到用户可见的用户空间中来,这就是该函数的意义所在。
    */
    start = info->fix.smem_start; 
    //帧缓冲长度
    len = info->fix.smem_len;
    mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;
    if (vma->vm_pgoff >= mmio_pgoff) {
        if (info->var.accel_flags) {
            mutex_unlock(&info->mm_lock);
            return -EINVAL;
        }

        vma->vm_pgoff -= mmio_pgoff;
        start = info->fix.mmio_start;
        len = info->fix.mmio_len;
    }
    mutex_unlock(&info->mm_lock);

    vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
    fb_pgprotect(file, vma, start);
    //映射物理内存到用户空间虚拟地址
    return vm_iomap_memory(vma, start, len);
}

问题思考:

问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只操作一个像素点。

四、驱动代码

#include <linux/module.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/clk.h>
#include <linux/string.h>

static struct fb_info *lcd_info;
unsigned long pseudo_palette[16];

/*LCD GPIO Pins*/
volatile unsigned long* gpf0con;
volatile unsigned long* gpf1con;
volatile unsigned long* gpf2con;
volatile unsigned long* gpf3con;
volatile unsigned long* gpd0con;
volatile unsigned long* gpd0dat;
volatile unsigned long* display_control;

/*LCD Controler Pins*/
struct S5PV210_lcd_regs{
    volatile unsigned long vidcon0;
    volatile unsigned long vidcon1;
    volatile unsigned long vidcon2;
    volatile unsigned long vidcon3;
    
    volatile unsigned long vidtcon0;
    volatile unsigned long vidtcon1;
    volatile unsigned long vidtcon2;
    volatile unsigned long vidtcon3;
    
    volatile unsigned long wincon0;
    volatile unsigned long wincon1;
    volatile unsigned long wincon2;
    volatile unsigned long wincon3;
    volatile unsigned long wincon4;
    
    volatile unsigned long shadowcon;
    volatile unsigned long reserve1[2];
    
    volatile unsigned long vidosd0a;
    volatile unsigned long vidosd0b;
    volatile unsigned long vidosd0c;

	
};
volatile unsigned long* vidw00add0b0;
volatile unsigned long* vidw00add1b0;

struct clk *lcd_clk;
struct S5PV210_lcd_regs *tq210_lcd_regs;

static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
	chan &= 0xffff;
	chan >>= 16 - bf->length;
	return chan << bf->offset;
}

static int tq210_lcdfb_setcolreg(unsigned int regno, unsigned int red,
			     unsigned int green, unsigned int blue,
			     unsigned int transp, struct fb_info *info)
{
	unsigned int val;
	
	if (regno > 16)
		return 1;

	/* 用red,green,blue三原色构造出val */
	val  = chan_to_field(red,	&info->var.red);
	val |= chan_to_field(green, &info->var.green);
	val |= chan_to_field(blue,	&info->var.blue);
	
	//((u32 *)(info->pseudo_palette))[regno] = val;
	pseudo_palette[regno] = val;
	return 0;
}


//帧缓冲操作函数
static struct fb_ops tq210_lcd_fbops = {
	.owner		= THIS_MODULE,
	.fb_setcolreg	= tq210_lcdfb_setcolreg,//设置color寄存器和调色板
	//下面这3个函数是通用的
	.fb_fillrect	= cfb_fillrect,//画一个矩形
	.fb_copyarea	= cfb_copyarea,//数据拷贝
	.fb_imageblit	= cfb_imageblit,//图像填充
};

static int __init lcd_init(void)
{
	int ret;
	//1. 分配fb_info
	lcd_info = framebuffer_alloc(0, NULL);
	if(lcd_info == NULL){
		printk(KERN_ERR "alloc framebuffer failed!\n");
		return -ENOMEM;
	}

	//2. 配置fb_info参数
	//2.1 fix固定参数
	strcpy(lcd_info->fix.id, "TQ210_lcd");
	lcd_info->fix.smem_len 			   =   800 * 480 * 32/8; //缓冲区大小(全屏字节数),
采用32bpp表示32位表示一个像素点
	lcd_info->fix.type 				   =   FB_TYPE_PACKED_PIXELS;
	lcd_info->fix.visual 			   =   FB_VISUAL_TRUECOLOR; //TFT 真彩色
	lcd_info->fix.line_length 			   =   800 * 4; //行字节数,一行800个点

	//2.2 var可变参数
   	lcd_info->var.xres				   =   800;//X轴(行)的实际像素
   	lcd_info->var.yres				   =   480;//y轴(列)实际像素
   	lcd_info->var.xres_virtual		   =   800;//虚拟屏,设置虚拟像素和实际像素一样
   	lcd_info->var.yres_virtual		   =   480;
   	lcd_info->var.xoffset			   =   0;//实际像素和虚拟像素偏移值为0
   	lcd_info->var.yoffset			   =   0;
   	lcd_info->var.bits_per_pixel 	   =   32;//每个像素点有32个位组成(4个字节)
	/*RGB:888*/
	lcd_info->var.red.offset 		   =   16;//red在16位域中偏移值为11
   	lcd_info->var.red.length 		   =   8;
   	lcd_info->var.red.msb_right		   =   0;
   	lcd_info->var.green.offset		   =   8;//red在16位域中偏移值为11
   	lcd_info->var.green.length		   =   8;
   	lcd_info->var.green.msb_right	   =   0;
   	lcd_info->var.blue.offset		   =   0;//red在16位域中偏移值为11
   	lcd_info->var.blue.length		   =   8;
   	lcd_info->var.blue.msb_right 	   =   0;
	lcd_info->var.activate			   =   FB_ACTIVATE_NOW;

	//2.3 设置其他参数
	//2.3.1 显存大小
	lcd_info->screen_size 			   =   800 * 480 * 4; //和显存一样大小设置
	//2.3.2 调色板
	lcd_info->pseudo_palette 		   =   pseudo_palette;
	//2.3.3 显存操作函数
	lcd_info->fbops 				   =   &tq210_lcd_fbops;
	//2.3.4 设置显存的虚拟起始地址
	lcd_info->screen_base = dma_alloc_writecombine(NULL,  lcd_info->screen_size , 
                    (dma_addr_t *)&lcd_info->fix.smem_start, GFP_KERNEL); 

	//3. 硬件相关操作
	//3.1获取lcd时钟,使能时钟
	lcd_clk = clk_get(NULL, "lcd");
	if(!lcd_clk || IS_ERR(lcd_clk)){
		printk(KERN_INFO "failed to get clock source\n");
	}
	clk_enable(lcd_clk);
	
	//3.2 配置GPIO用于LCD
	gpf0con = ioremap(0xE0200120, 4);
    gpf1con = ioremap(0xE0200140, 4);
    gpf2con = ioremap(0xE0200160, 4);
    gpf3con	= ioremap(0xE0200180, 4);
    gpd0con = ioremap(0xE02000A0, 4);
    gpd0dat = ioremap(0xE02000A4, 4);
    
	
    tq210_lcd_regs = ioremap(0xF8000000, sizeof(struct S5PV210_lcd_regs)); 

	vidw00add0b0 = ioremap(0xF80000A0, 4);
	vidw00add1b0 = ioremap(0xF80000D0, 4);
	
	display_control = ioremap(0xe0107008, 4);

	//设置相关GPIO引脚用于LCD
	*gpf0con = 0x22222222;
	*gpf1con = 0x22222222;
	*gpf2con = 0x22222222;
	*gpf3con = 0x22222222;

	//使能LCD本身,LCD_PWM引脚 XpwmTOUT0 GPD0_0 背光
	*gpd0con &= ~0x0F;
	*gpd0con |= 0x01;
	*gpd0dat |= 1<<0; //0打开 1关闭
	
	//显示路径的选择, 0b10: RGB=FIMD I80=FIMD ITU=FIMD
	/*RGB接口和i80接口的区别:
	在嵌入式的主流 LCD屏中主要支持两大类的硬件接口,一种是常见的RGB接口,另外一种是MCU接口.
	MCU接口最早是针对单片机的领域在使用,因此得名.后在中低端手机大量使用,其主要特点是价格便宜的.
	MCU-LCD接口的标准术语是Interface 80,因此在很多文档中用I80 来指MCU-LCD屏。
	MCU-LCD屏它与RGB-LCD屏主要区别在于显存的位置.
	而MCU-LCD的设计之初只要考虑单片机的 内存较小,因此都是把显存内置在LCD模块内部.然后软件通过专
	门显示命令来更新显存,因此MCU屏往往不能做得很大.同时显示更新速度也比RGB-LCD慢.
	
	RGB屏只需显存组织好数据。启动显示后。LCD-DMA会自动把显存通过RGB接口送到LCM。
	而MCU屏则需要发送画点的命令来修改MCU内部RAM。(即不能直接MCU屏RAM)
	所以RGB显示速度明显比MCU快,而且播放视频方面,MCU-LCD也比较慢.
	ITU接口是给摄像头使用的。*/
	*display_control  = 2<<0;

	//3.3映射LCD控制器对应寄存器 
	tq210_lcd_regs->vidcon0 = (4<<6)|(1<<4);
	tq210_lcd_regs->vidcon1 = (1<<6)|(1<<5)|(1<<4);

	tq210_lcd_regs->vidtcon0 = (17<<16)|(26<<8)|(4<<0);
	tq210_lcd_regs->vidtcon1 = (40<<16)|(214<<8)|(4<<0);
	tq210_lcd_regs->vidtcon2 = (479<<11)|(799<<0);

	tq210_lcd_regs->wincon0 &= ~(0xf<<2);
	tq210_lcd_regs->wincon0 |= (0xb<<2);

	tq210_lcd_regs->vidosd0a = (0<<11)|(0<<0);
	tq210_lcd_regs->vidosd0b = (799<<11)|(479<<0);
	tq210_lcd_regs->vidosd0c = 480*800;
	
	*vidw00add0b0 = (volatile unsigned long)lcd_info->fix.smem_start;
	*vidw00add1b0 = (volatile unsigned long)lcd_info->fix.smem_start + lcd_info->fix.smem_len;

	tq210_lcd_regs->shadowcon = 0x01;//使能通道0
	tq210_lcd_regs->vidcon0  |= 0x3;  //开启总控制器   
    tq210_lcd_regs->wincon0 |= 1;     //开启窗口0

	//4. 注册fb_info
	ret = register_framebuffer(lcd_info);
	return ret;
}

static void __exit lcd_exit(void)
{
	unregister_framebuffer(lcd_info);
	dma_free_writecombine(NULL, lcd_info->fix.smem_len, 
		(void*)lcd_info->screen_base, (dma_addr_t)lcd_info->fix.smem_start);

	iounmap(vidw00add1b0);
	iounmap(vidw00add0b0);
	iounmap(tq210_lcd_regs);
	iounmap(gpd0dat);
	iounmap(gpd0con);
	iounmap(gpf3con);
	iounmap(gpf2con);
	iounmap(gpf1con);
	iounmap(gpf0con);
	framebuffer_release(lcd_info);
}

module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jerry.Gou");
MODULE_DESCRIPTION("TQ210 lcd driver");
阅读更多

没有更多推荐了,返回首页