Linux的LCD驱动

在Linux的LCD驱动中有一个很重要的概念:帧缓冲(framebuffer)。

一、帧缓冲(framebuffer)

帧缓冲(framebuffer)是Linux系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。帧缓冲设备为标准字符设备,主设备号为29,对应于/dev/fb*设备文件。

二、帧缓冲设备驱动的分析

drivers\video\fbmem.c文件中对应的是framebuffer的代码,先看看它的初始化代码:

static int __init
fbmem_init(void)
{
    create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);

    if (register_chrdev(FB_MAJOR,"fb",&fb_fops))//注册一个字符设备驱动
        printk("unable to get major %d for fb devs\n", FB_MAJOR);

    fb_class = class_create(THIS_MODULE, "graphics");//创建一个类,但并没有创建设备
    if (IS_ERR(fb_class)) {
        printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
        fb_class = NULL;
    }
    return 0;
}

从代码可见,framebuffer注册的是一个字符设备驱动,并提供了一个file_operations结构,再注意后面,它只创建了一个类,并没有创建设备。我们在看看它的file_operations结构:

static const struct file_operations fb_fops = {
    .owner =    THIS_MODULE,
    .read =     fb_read,
    .write =    fb_write,
    .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
};

它提供了/dev/fb*设备的open,read,write等函数。当我们在应用程序中打开/dev/fb*设备时,系统将调用fb_open这个函数。当我们在应用程序中往read这个设备时,系统将调用fb_read这个函数,下面让我们来看看这两个函数:
fb_open函数:

static int
fb_open(struct inode *inode, struct file *file)
{
    int fbidx = iminor(inode);
    struct fb_info *info;
    int res = 0;

    if (fbidx >= FB_MAX)
        return -ENODEV;
#ifdef CONFIG_KMOD
    if (!(info = registered_fb[fbidx]))
        try_to_load(fbidx);
#endif /* CONFIG_KMOD */
    if (!(info = registered_fb[fbidx]))//根据次设备号从registered_fb数组中选择一个fb_info结构
        return -ENODEV;
    if (!try_module_get(info->fbops->owner))
        return -ENODEV;
    file->private_data = info;
    if (info->fbops->fb_open) {
        res = info->fbops->fb_open(info,1);
        if (res)
            module_put(info->fbops->owner);
    }
    return res;
}

fb_read函数:

static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    unsigned long p = *ppos;
    struct inode *inode = file->f_path.dentry->d_inode;
    int fbidx = iminor(inode);
    struct fb_info *info = registered_fb[fbidx];//根据次设备号从registered_fb数组中选择一个fb_info结构
    u32 *buffer, *dst;
    u32 __iomem *src;
    int c, i, cnt = 0, err = 0;
    unsigned long total_size;

    if (!info || ! info->screen_base)
        return -ENODEV;

    if (info->state != FBINFO_STATE_RUNNING)
        return -EPERM;

    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);

    while (count) {
        c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;
        dst = buffer;
        for (i = c >> 2; i--; )
            *dst++ = fb_readl(src++);
        if (c & 3) {
            u8 *dst8 = (u8 *) dst;
            u8 __iomem *src8 = (u8 __iomem *) src;

            for (i = c & 3; i--;)
                *dst8++ = fb_readb(src8++);

            src = (u32 __iomem *) src8;
        }

        if (copy_to_user(buf, buffer, c)) {
            err = -EFAULT;
            break;
        }
        *ppos += c;
        buf += c;
        cnt += c;
        count -= c;
    }

    kfree(buffer);

    return (err) ? err : cnt;
}

观察这两个函数发现他们有一个共性:根据次设备号从registered_fb数组中选择一个fb_info结构,然后判断这个结构里是否有对应的函数,有则执行。那么registered_fb数组由谁来设置呢?它是由int register_framebuffer(struct fb_info *fb_info)这个函数设置的,它通过寻找registered_fb数组中的一个空缺项,使这个空缺项指向我们传递的fb_info结构,并通过这项的下标创建设备节点。这个函数由LCD驱动调用。所以说framebuffer为底层显示设备提供一个接口。

三、LCD驱动

上面说到,framebuffer为底层显示设备提供一个接口,也就是为我们的LCD提供了一个接口,LCD只需要调用int register_framebuffer(struct fb_info *fb_info)函数向上注册一个fb_info结构,我们就能通过设备节点/dev/fb*中的某个设备节点就能访问我们的设备。在注册之前需要将fb_info结构填充,看看fb_info结构体:

struct fb_info {
    int node;/*  次设备号从这里获得的*/
    int flags;
    struct fb_var_screeninfo var;   /* 可变参数,非常重要 */
    struct fb_fix_screeninfo fix;   /* 固定参数,非常重要  */
    struct fb_monspecs monspecs;    /* Current Monitor specs */
    struct work_struct queue;   /* Framebuffer event queue */
    struct fb_pixmap pixmap;    /* Image hardware mapper */
    struct fb_pixmap sprite;    /* Cursor hardware mapper */
    struct fb_cmap cmap;        /* Current cmap */
    struct list_head modelist;      /* mode list */
    struct fb_videomode *mode;  /* current mode */
    。。。。。。
    struct fb_ops *fbops;       /* 非常重要 */
    struct device *device;      /* This is the parent */
    struct device *dev;     /* This is this fb device */
    int class_flag;                    /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
    struct fb_tile_ops *tileops;    /* Tile Blitting */
#endif
    char __iomem *screen_base;  /* 显存的基地址(虚拟地址),非常重要*/
    unsigned long screen_size;  /* 显存的大小*/ 
    void *pseudo_palette;       /* 16个颜色假的调色板*/ 
#define FBINFO_STATE_RUNNING    0
#define FBINFO_STATE_SUSPENDED  1
    u32 state;          /* Hardware state i.e suspend */
    void *fbcon_par;                /* fbcon use-only private area */
    /* From here on everything is device dependent */
    void *par;  /* 用来存放私有数据*/ 
};

这个结构中重要的都用文字说明了,其中有三个个结构:fb_var_screeninfo、fb_fix_screeninfo和fb_ops。再来看看它们:
fb_var_screeninfo:这个结构体主要用来设置LCD屏幕的参数,如分辨率、像素比特数等,LCD驱动程序里面硬件相关的设置很多都涉及这个结构体。

struct fb_var_screeninfo {
    __u32 xres;         /* 分辨率,一行有多少个像素点*/
    __u32 yres;         /* 分辨率,有多少行*/
    __u32 xres_virtual;     /* 虚拟分辨率,一行有多少个像素点*/
    __u32 yres_virtual;     /* 虚拟分辨率,有多少行*/
    __u32 xoffset;          /* offset from virtual to visible */
    __u32 yoffset;          /* resolution           */

    __u32 bits_per_pixel;       /* guess what,每个像素点有多少位*/
    __u32 grayscale;        /* != 0 Graylevels instead of colors */

    struct fb_bitfield red;     /* bitfield in fb mem if true color, */
    struct fb_bitfield green;   /* else only length is significant */
    struct fb_bitfield blue;
    struct fb_bitfield transp;  /* transparency         */  

    __u32 nonstd;           /* != 0 Non standard pixel format */

    __u32 activate;         /* see FB_ACTIVATE_*        */

    __u32 height;           /* height of picture in mm    */
    __u32 width;            /* width of picture in mm     */

    __u32 accel_flags;      /* (OBSOLETE) see fb_info.flags */

    /* Timing: All values in pixclocks, except pixclock (of course) */
    __u32 pixclock;         /* pixel clock in ps (pico seconds) */
    __u32 left_margin;      /* time from sync to picture    */
    __u32 right_margin;     /* time from picture to sync    */
    __u32 upper_margin;     /* time from sync to picture    */
    __u32 lower_margin;
    __u32 hsync_len;        /* length of horizontal sync    */
    __u32 vsync_len;        /* length of vertical sync  */
    __u32 sync;         /* see FB_SYNC_*        */
    __u32 vmode;            /* see FB_VMODE_*       */
    __u32 rotate;           /* angle we rotate counter clockwise */
    __u32 reserved[5];      /* Reserved for future compatibility */
};

fb_fix_screeninfo:

struct fb_fix_screeninfo {
char id[16];/* 驱动名字就保存在这里*/
unsigned long smem_start;       /* fb缓冲区的基地址,这是一个物理地址 */
__u32 smem_len;       /* fb缓冲区的长度 */
__u32 type;              /* FB_TYPE_类型*/
__u32 type_aux;/* Interleave for interleaved Planes */
__u32 visual;/* FB_VISUAL_类型*/ 
__u16 xpanstep;/* 一般设置为0  */
__u16 ypanstep;/* 一般设置为0  */
__u16 ywrapstep;/* 一般设置为0  */
__u32 line_length;/* 屏幕一行有多个字节    */
unsigned long mmio_start;/* Start of Memory Mapped I/O   */
/* (physical address) */
__u32 mmio_len;/* Length of Memory Mapped I/O  */
__u32 accel;  /* Indicate to driver which */
/*  specific chip/card we have*/
__u16 reserved[3];/* Reserved for future compatibility */
};

fb_ops:考虑到fb_ops结构体里面的函数指针成员太多,这里仅简单列举几个比较常见的,具体的请参考源代码。(在include/linux/fb.h文件里定义)

struct fb_ops{
struct module *owner;  /* 模块计数 */
int (*fb_open)(struct fb_info *info, int user);           /* 打开函数,第一个参数为fb_info */
int (*fb_release)(struct fb_info *info, int user);
。。。。。。
/* fb_check_var函数用来检查可变参数,并调整修改可变参数  */
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
。。。。。。
/*fb_setcolreg函数就是用来设置fb_info里面的pseudo_palette调色板成员 */
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
   unsigned blue, unsigned transp, 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);
。。。。。。
/* 有了fb_ioctl应用层可以通过ioctl系统调用来设置屏幕参数等 */
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
unsigned long arg);
。。。。。。
/* 有了fb_mmap应用层可以通过mmap系统调用来读写帧缓冲区内存 */
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
。。。
};

根据LCD的datasheet配置完fb_info结构之后,我们需要初始化硬件相关的东西,才能向上注册我们的fb_info结构,因为我们一旦注册,就可能会有进程访问我们的设备,如果我们的设备还没有初始化,那么进程的访问注定是失败的。
初始化设备我们需要做的就是配置LCD控制寄存器。在设置控制寄存器时需要映射物理地址。
下面是一个LCD驱动程序的例子,基于JZ2440v2版:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/clk.h>

#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/div64.h>

#include <asm/mach/map.h>
#include <asm/arch/regs-lcd.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/fb.h>

static int S3C_lcd_setcolreg(unsigned regno, unsigned red, unsigned green,
                unsigned blue, unsigned transp, struct fb_info *info);
struct s3c_lcd_regs{
    unsigned long lcdcon1;
    unsigned long lcdcon2;
    unsigned long lcdcon3;
    unsigned long lcdcon4;
    unsigned long lcdcon5;
    unsigned long lcdsaddr1;
    unsigned long lcdsaddr2;
    unsigned long lcdsaddr3;
    unsigned long redlut;
    unsigned long greenlut;
    unsigned long bluelut;
    unsigned long reserved[8];
    unsigned long dithmode;
    unsigned long tpal;
    unsigned long lcdintpnd;
    unsigned long lcdsrcpnd;
    unsigned long lcdintmask;
    unsigned long tconsel;
};

static struct fb_ops S3C_lcd_ops = {
    .owner      = THIS_MODULE,
    .fb_setcolreg   = S3C_lcd_setcolreg,
    .fb_fillrect    = cfb_fillrect,
    .fb_copyarea    = cfb_copyarea,
    .fb_imageblit   = cfb_imageblit,
};

static volatile unsigned long *gpbcon;
static volatile unsigned long *gpbdat;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static struct s3c_lcd_regs *lcd_regs;

static unsigned long pseudo_palette[16];

static struct fb_info *fbinfo;

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 S3C_lcd_setcolreg(unsigned regno, unsigned red, unsigned green,
                unsigned blue, unsigned 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);
    pseudo_palette[regno] = val;
    return 0;
}
static int __init lcd_init(void)
{
    /*1、创建一个fd_info结构体*/
    fbinfo = framebuffer_alloc(0,NULL);
    /*2、设置fd_info结构体*/
    /*2.1、设置固定 参数*/
    strcpy(fbinfo->fix.id, "mylcd");
    //fbinfo->fix.smem_start;/* Start of frame buffer mem  (physical address) */
    fbinfo->fix.smem_len = 480*272*16/8;
    fbinfo->fix.type     = FB_TYPE_PACKED_PIXELS;
    fbinfo->fix.visual   = FB_VISUAL_TRUECOLOR;
    fbinfo->fix.line_length = 480*16/8;
    /*2.2、设置可变参数*/
    fbinfo->var.xres = 480;
    fbinfo->var.yres = 272;
    fbinfo->var.xres_virtual = 480;
    fbinfo->var.yres_virtual = 272;
    fbinfo->var.bits_per_pixel = 16;
    //RGB:565
    fbinfo->var.red.offset = 11;
    fbinfo->var.red.length = 5;
    fbinfo->var.red.msb_right = 0;

    fbinfo->var.green.offset = 5;
    fbinfo->var.green.length = 6;
    fbinfo->var.green.msb_right = 0;

    fbinfo->var.blue.offset = 0;
    fbinfo->var.blue.length = 5;
    fbinfo->var.blue.msb_right = 0;

    fbinfo->var.activate = FB_ACTIVATE_NOW;
    /*2.2、设置其他参数*/
    fbinfo->fbops = &S3C_lcd_ops;
    fbinfo->pseudo_palette = pseudo_palette;
    //fbinfo->screen_base = ;    /*显存的虚拟地址*/

    fbinfo->screen_size = 480*272*16/8;/*显存的大小*/
    /*3、硬件相关的初始化*/
    gpbcon = ioremap(0x56000010,8);
    gpbdat = gpbcon+1;
    gpccon = ioremap(0x56000020,4);
    gpdcon = ioremap(0x56000030,4);
    gpgcon = ioremap(0x56000060,4);
    lcd_regs = ioremap(0X4D000000,sizeof(struct s3c_lcd_regs));
    /*设置背光GPB0为输出引脚*/
    *gpbcon &= ~(0x03);
    *gpbcon |= 0x01;
    *gpbdat &= ~1;

    *gpccon = 0xaaaaaaaa;
    *gpdcon = 0xaaaaaaaa;
    /*设置LCD电源引脚GPG4为LCD_PWRDN*/
    *gpgcon |= (0x3<<(2*4));
    /*设置LCD控制寄存器*/
    lcd_regs->lcdcon1 = (4<<8)|(3<<5)|(12<<1);
    lcd_regs->lcdcon2 = (1<<24)|(271<<14)|(1<<6)|(9);
    lcd_regs->lcdcon3 = (1<<19)|(479<<8)|(1<<0);
    lcd_regs->lcdcon4 = 40;
    lcd_regs->lcdcon5 = 0x00000b01;

    fbinfo->screen_base = dma_alloc_writecombine(NULL,fbinfo->fix.smem_len, &fbinfo->fix.smem_start,GFP_KERNEL);
    lcd_regs->lcdsaddr1 = (fbinfo->fix.smem_start>>1) & ~(0x3<<30);
    lcd_regs->lcdsaddr2 = ((fbinfo->fix.smem_start+fbinfo->fix.smem_len)>>1)&0x1fffff;
    lcd_regs->lcdsaddr3 = (480*16/16);

    /*使能LCD*/
    lcd_regs->lcdcon1 |= 1;
    lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
    *gpbdat |= 1;     /* 输出高电平, 使能背光 */
    /*2、注册fd_info结构体*/
    register_framebuffer(fbinfo);
    return 0;
}

static void __exit lcd_exit(void)
{
    unregister_framebuffer(fbinfo);
    lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
    *gpbdat &= ~1;     /* 关闭背光 */
    dma_free_writecombine(NULL,fbinfo->fix.smem_len,fbinfo->screen_base,fbinfo->fix.smem_start);
    iounmap(lcd_regs);
    iounmap(gpbcon);
    iounmap(gpccon);
    iounmap(gpdcon);
    iounmap(gpgcon);
    framebuffer_release(fbinfo);
}
module_init(lcd_init);
module_exit(lcd_exit);

MODULE_LICENSE("GPL");
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值