zoukankan      html  css  js  c++  java
  • Linux驱动:LCD驱动框架分析

    一直想花时间来整理一下Linux内核LCD驱动,却一直都忙着做其他事情去了,这些天特意抽出时间来整理之前落下的笔记,故事就这样开始了。LCD驱动也是字符设备驱动的一种,框架上相对于字符设备驱动稍微复杂一点点,真的就是一点点,难点在对LCD硬件的配置上。

    开发平台:TQ210,S5PV210处理器

    内核版本:linux-3.10.46

    LCD型号:AT070TN92,7英寸,TFT屏,分辨率800x480x3(RGB),24位真彩色

    一、框架分析

    上图说明:①内核装载LCD驱动模块:设置并注册fb_info结构,初始化LCD硬件。②APP打开LCD设备,获取设备文件,根据设备文件进行读写显存。③在内核中,根据主设备号和次设备号定位一个fb_info结构,如果应用层的系统调用是读操作则调用fb_ops中对应的操作函数,写操作也是一样。

    主要数据结构分析:

     1 struct fb_info {
     2     int node;                        //用作次设备号索引
     3     int flags;
     4     struct mutex lock;                //用于open/release/ioctl函数的锁
     5     struct fb_var_screeninfo var;    //可变参数,重点
     6     struct fb_fix_screeninfo fix;    //固定参数,重点
     7     struct fb_monspecs monspecs;    //显示器标准
     8     struct work_struct queue;        //帧缓冲区队列
     9     struct fb_pixmap pixmap;        //图像硬件映射
    10     struct fb_pixmap sprite;        //光标硬件映射
    11     struct fb_cmap cmap;            //当前颜色表
    12     struct list_head modelist;      //模式链表
    13     struct fb_videomode *mode;        //当前video模式
    14 
    15     char __iomem *screen_base;        //显存基地址
    16     unsigned long screen_size;        //显存大小
    17     void *pseudo_palette;            //伪16色颜色表
    18 #define FBINFO_STATE_RUNNING    0
    19 #define FBINFO_STATE_SUSPENDED    1
    20     u32 state;                        //硬件状态,如挂起
    21     void *fbcon_par;                //用作私有数据区
    22     void *par;                        //info->par指向了额外多申请内存空间的首地址
    23 };
    fb_info
     1 struct fb_ops {
     2     struct module *owner;
     3     int (*fb_open)(struct fb_info *info, int user);
     4     int (*fb_release)(struct fb_info *info, int user);
     5     ssize_t (*fb_read)(struct fb_info *info, char __user *buf,size_t count, loff_t *ppos);
     6     ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,size_t count, loff_t *ppos);
     7 
     8     /* 检测可变参数,并调整到支持的值 */
     9     int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
    10 
    11     /* 根据 info->var 设置 video 模式 */
    12     int (*fb_set_par)(struct fb_info *info);
    13 
    14     /* set color register */
    15     int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,unsigned blue, unsigned transp, struct fb_info *info);
    16     /* set color registers in batch */
    17     int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
    18     /* blank display */
    19     int (*fb_blank)(int blank, struct fb_info *info);
    20     /* pan display */
    21     int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
    22 
    23     /* Draws a rectangle */
    24     void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
    25     /* Copy data from area to another */
    26     void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
    27     /* Draws a image to the display */
    28     void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
    29 
    30     ......
    31 
    32     /* perform fb specific ioctl (optional) */
    33     int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
    34             unsigned long arg);
    35     /* perform fb specific mmap */
    36     int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
    37 
    38     ......
    39 };
    fb_ops

    主要操作代码分析:

     1 fb_open
     2 {
     3     int fbidx = iminor(inode);         //获取次设备号
     4     struct fb_info *info;
     5     info = get_fb_info(fbidx);
     6         struct fb_info *fb_info;
     7         fb_info = registered_fb[fbidx];//根据次设备号从已注册的fb_info数组中获取响应的结构
     8         return fb_info;
     9     ......
    10     /* 
    11      * 从registered_fb[]数组项里找到fb_info结构体后,将其保存到 
    12      * struct file结构中的私有信息成员,难道这是为了以后在某些情况方便找到并调用??先放着...
    13      * 回过来发现:这样做是为了验证在read、write、ioctl等系统调用中获得的fb_info结构和open获得的是否一样
    14      */  
    15     file->private_data = info;
    16     //info->fbops->fb_open无定义,这是值得思考的问题!
    17     if (info->fbops->fb_open) {
    18         res = info->fbops->fb_open(info,1);
    19         if (res)
    20             module_put(info->fbops->owner);
    21     }
    22     ......
    23 }
    fb_open
     1 fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
     2 {
     3     struct fb_info *info = file_fb_info(file);
     4         struct inode *inode = file_inode(file);
     5         int fbidx = iminor(inode);
     6         //也是根据次设备号来获取fb_info结构
     7         struct fb_info *info = registered_fb[fbidx];
     8     
     9         if (info != file->private_data)
    10             info = NULL;
    11         return info;
    12     //无定义
    13     if (info->fbops->fb_read)
    14         return info->fbops->fb_read(info, buf, count, ppos);
    15     //获得显存的大小
    16     total_size = info->screen_size;
    17     //如果应用层要读的数据count比实际最大的显存还要大,修改count值为最大显存值
    18     if (count >= total_size)
    19         count = total_size;
    20     //分配显存,最大只能是一页PAGE_SIZE=4KB
    21     buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,GFP_KERNEL);
    22     //要读的源地址:显存虚拟基地址+偏移
    23     src = (u8 __iomem *) (info->screen_base + p);
    24     while (count) {
    25         c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;
    26         //读的目的地址
    27         dst = buffer;
    28         //读操作:拷贝数据
    29         fb_memcpy_fromfb(dst, src, c);
    30         dst += c;
    31         src += c;
    32 
    33         if (copy_to_user(buf, buffer, c)) {
    34             err = -EFAULT;
    35             break;
    36         }
    37         *ppos += c;
    38         buf += c;
    39         cnt += c;
    40         count -= c;
    41     }
    42     kfree(buffer); //释放buffer,只起到临时中转站的作用
    43 }
    fb_read
    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;
    }
    fb_write
     1 /* 
     2  * 函数功能:将内核空间分配的物理显存空间映射到用户空间中 
     3  * 用户空间就能访问这段内存空间了
     4  */  
     5 static int fb_mmap(struct file *file, struct vm_area_struct * vma)
     6 {
     7     struct fb_info *info = file_fb_info(file);
     8     struct fb_ops *fb;
     9     unsigned long mmio_pgoff;
    10     unsigned long start;
    11     u32 len;
    12 
    13     if (!info)
    14         return -ENODEV;
    15     fb = info->fbops;
    16     if (!fb)
    17         return -ENODEV;
    18     mutex_lock(&info->mm_lock);
    19     //如果fb_info->fbops->fb_mmap存在就调用该函数,实际中没有!
    20     if (fb->fb_mmap) {
    21         int res;
    22         res = fb->fb_mmap(info, vma);
    23         mutex_unlock(&info->mm_lock);
    24         return res;
    25     }
    26     /*
    27     * fb缓冲内存的开始位置(物理地址)
    28     * info->fix.smem_start这个地址是在哪里被设置的?
    29     * 在驱动程序xxx_lcd_init()函数中:
    30     * clb_fbinfo->screen_base = dma_alloc_writecombine(NULL,clb_fbinfo->fix.smem_len, (u32*)&(clb_fbinfo->fix.smem_start), GFP_KERNEL);
    31     * dma_alloc_writecombine函数返回的是内核虚拟起始地址,同时第3个参数fix.smem_start会被设置成对应的物理起始地址。
    32     * 内核中操作这个分配的空间只能操作虚拟的地址空间!!!
    33     * dma_alloc_writecombine函数的调用只是把物理显存映射到内核空间,并没有映射到用户空间,因此用户在操作物理显存之前要先把
    34     * 物理显存空间映射到用户可见的用户空间中来,这就是该函数的意义所在。
    35     */
    36     start = info->fix.smem_start; 
    37     //帧缓冲长度
    38     len = info->fix.smem_len;
    39     mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;
    40     if (vma->vm_pgoff >= mmio_pgoff) {
    41         if (info->var.accel_flags) {
    42             mutex_unlock(&info->mm_lock);
    43             return -EINVAL;
    44         }
    45 
    46         vma->vm_pgoff -= mmio_pgoff;
    47         start = info->fix.mmio_start;
    48         len = info->fix.mmio_len;
    49     }
    50     mutex_unlock(&info->mm_lock);
    51 
    52     vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
    53     fb_pgprotect(file, vma, start);
    54     //映射物理内存到用户空间虚拟地址
    55     return vm_iomap_memory(vma, start, len);
    56 }
    fb_mmap

    问题思考:

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

    二、驱动代码编写

      1 #include <linux/module.h>
      2 #include <linux/kernel.h>
      3 #include <linux/errno.h>
      4 #include <linux/string.h>
      5 #include <linux/mm.h>
      6 #include <linux/slab.h>
      7 #include <linux/delay.h>
      8 #include <linux/fb.h>
      9 #include <linux/init.h>
     10 #include <linux/dma-mapping.h>
     11 #include <linux/interrupt.h>
     12 #include <linux/platform_device.h>
     13 #include <linux/clk.h>
     14 #include <linux/workqueue.h>
     15 
     16 #include <asm/io.h>
     17 #include <asm/div64.h>
     18 #include <asm/uaccess.h>
     19 
     20 #include <asm/mach/map.h>
     21 #include <mach/regs-gpio.h>
     22 #include <linux/fb.h>
     23 
     24 #define VSPW        9   //4
     25 #define VBPD        13  //17
     26 #define LINEVAL     479  
     27 #define VFPD        21  //26
     28 
     29 #define HSPW        19    //4
     30 #define HBPD        25   //40
     31 #define HOZVAL      799   
     32 #define HFPD        209   //214
     33 
     34 #define LeftTopX    0
     35 #define LeftTopY    0
     36 #define RightBotX   799
     37 #define RightBotY   479
     38 
     39 static struct fb_info *clb_fbinfo;
     40 
     41 /* LCD GPIO Pins */
     42 static long unsigned long *gpf0con;
     43 static long unsigned long *gpf1con;
     44 static long unsigned long *gpf2con;
     45 static long unsigned long *gpf3con;
     46 static long unsigned long *gpd0con;
     47 static long unsigned long *gpd0dat;
     48 static long unsigned long *display_control;
     49 
     50 /* LCD Controler Pins */
     51 struct s5pv210_lcd_regs{
     52     volatile unsigned long vidcon0;
     53     volatile unsigned long vidcon1;
     54     volatile unsigned long vidcon2;
     55     volatile unsigned long vidcon3;
     56     
     57     volatile unsigned long vidtcon0;
     58     volatile unsigned long vidtcon1;
     59     volatile unsigned long vidtcon2;
     60     volatile unsigned long vidtcon3;
     61     
     62     volatile unsigned long wincon0;
     63     volatile unsigned long wincon1;
     64     volatile unsigned long wincon2;
     65     volatile unsigned long wincon3;
     66     volatile unsigned long wincon4;
     67     
     68     volatile unsigned long shadowcon;
     69     volatile unsigned long reserve1[2];
     70     
     71     volatile unsigned long vidosd0a;
     72     volatile unsigned long vidosd0b;
     73     volatile unsigned long vidosd0c;
     74 };
     75 
     76 struct clk      *lcd_clk;
     77 static struct s5pv210_lcd_regs *lcd_regs;
     78 
     79 static long unsigned long *vidw00add0b0;
     80 static long unsigned long *vidw00add1b0;
     81 
     82 static u32  pseudo_palette[16];
     83 
     84 /* from pxafb.c */
     85 static  unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
     86 {
     87     chan &= 0xffff;
     88     chan >>= 16 - bf->length;
     89     return chan << bf->offset;
     90 }
     91 
     92 static int  clb210_lcdfb_setcolreg(unsigned regno,
     93                    unsigned red, unsigned green, unsigned blue,
     94                    unsigned transp, struct fb_info *info)
     95 {
     96     unsigned int val;
     97     
     98     if (regno > 16) 
     99         return 1;
    100     
    101     /* 用red,green,blue三原色构造出val */
    102     val  = chan_to_field(red,   &info->var.red);
    103     val |= chan_to_field(green, &info->var.green);
    104     val |= chan_to_field(blue,  &info->var.blue);
    105 
    106     pseudo_palette[regno] = val;
    107         
    108     return 0;    
    109 }
    110 
    111 //帧缓冲操作函数
    112 static struct fb_ops clb210_lcdfb_ops = 
    113 {
    114     .owner            = THIS_MODULE,
    115     .fb_setcolreg    = clb210_lcdfb_setcolreg, //设置color寄存器和调色板
    116     //下面这3个函数是通用的
    117     .fb_fillrect    = cfb_fillrect,  //画一个矩形
    118     .fb_copyarea    = cfb_copyarea,  //数据拷贝
    119     .fb_imageblit    = cfb_imageblit, //图像填充
    120 };
    121 
    122 static int  __init clb210_lcd_init(void)
    123 {
    124     /* 1.分配一个fb_info */
    125     clb_fbinfo = framebuffer_alloc(0 , NULL);
    126 
    127     /* 2. 设置 */
    128     /* 2.1 设置固定的参数 */
    129     strcpy(clb_fbinfo->fix.id, "clb210_lcd");
    130     clb_fbinfo->fix.smem_len = 800 * 480 * 32/8;
    131     clb_fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
    132     clb_fbinfo->fix.visual = FB_VISUAL_TRUECOLOR;
    133     clb_fbinfo->fix.line_length = 800 * 32/8;
    134 
    135     /* 2.2 设置可变的参数 */
    136     clb_fbinfo->var.xres = 800;
    137     clb_fbinfo->var.yres = 480;
    138     clb_fbinfo->var.xres_virtual   = 800;
    139     clb_fbinfo->var.yres_virtual   = 480;
    140     clb_fbinfo->var.bits_per_pixel = 32;
    141     
    142     /*RGB:888*/
    143     clb_fbinfo->var.red.offset = 16;
    144     clb_fbinfo->var.red.length = 8;
    145     
    146     clb_fbinfo->var.green.offset = 8;
    147     clb_fbinfo->var.green.length = 8;
    148     
    149     clb_fbinfo->var.blue.offset = 0;
    150     clb_fbinfo->var.blue.length = 8;
    151     
    152     clb_fbinfo->var.activate = FB_ACTIVATE_NOW    ;
    153 
    154     /* 2.3 设置操作函数 */
    155     clb_fbinfo->fbops = &clb210_lcdfb_ops;
    156     
    157     /* 2.4 其他的设置 */
    158     /* 2.4.1 设置显存的大小 */
    159     clb_fbinfo->screen_size =  800 * 480 * 32/8;    
    160 
    161     /* 2.4.2 设置调色板 */
    162     clb_fbinfo->pseudo_palette = pseudo_palette;
    163 
    164     /* 2.4.3 设置显存的虚拟起始地址 */
    165     clb_fbinfo->screen_base = dma_alloc_writecombine(NULL,
    166             clb_fbinfo->fix.smem_len, (u32*)&(clb_fbinfo->fix.smem_start), GFP_KERNEL);
    167     
    168 
    169     /* 3. 硬件相关的操作 */
    170     /* 3.1 获取lcd时钟,使能时钟 */
    171     lcd_clk = clk_get(NULL, "lcd");
    172     if (!lcd_clk || IS_ERR(lcd_clk)) {
    173         printk(KERN_INFO "failed to get lcd clock source
    ");
    174     }
    175     clk_enable(lcd_clk);
    176 
    177     /* 3.2 配置GPIO用于LCD */
    178     gpf0con = ioremap(0xE0200120, 4);
    179     gpf1con = ioremap(0xE0200140, 4);
    180     gpf2con = ioremap(0xE0200160, 4);
    181     gpf3con = ioremap(0xE0200180, 4);
    182     gpd0con = ioremap(0xE02000A0, 4);
    183     gpd0dat = ioremap(0xE02000A4, 4);
    184     
    185     gpd0con      = ioremap(0xE02000A0, 4);  
    186     gpd0dat      = ioremap(0xE02000A4, 4);  
    187       
    188     vidcon0      = ioremap(0xF8000000, 4);  
    189     vidcon1      = ioremap(0xF8000004, 4);  
    190     vidtcon0     = ioremap(0xF8000010, 4);  
    191     vidtcon1     = ioremap(0xF8000014, 4);  
    192     vidtcon2     = ioremap(0xF8000018, 4);  
    193     wincon0      = ioremap(0xF8000020, 4);  
    194     vidosd0a     = ioremap(0xF8000040, 4);  
    195     vidosd0b     = ioremap(0xF8000044, 4);  
    196     vidosd0c     = ioremap(0xF8000048, 4);  
    197     vidw00add0b0 = ioremap(0xF80000A0, 4);  
    198     vidw00add1b0 = ioremap(0xF80000D0, 4);  
    199     shodowcon    = ioremap(0xF8000034, 4); 
    200     
    201     display_control = ioremap(0xe0107008, 4);
    202 
    203     /* 设置相关GPIO引脚用于LCD */
    204     *gpf0con = 0x22222222;
    205     *gpf1con = 0x22222222;
    206     *gpf2con = 0x22222222;
    207     *gpf3con = 0x22222222;
    208 
    209     /* 使能LCD本身 */
    210     *gpd0con |= 1<<4;
    211     *gpd0dat |= 1<<1;
    212 
    213     /* 显示路径的选择, 0b10: RGB=FIMD I80=FIMD ITU=FIMD */
    214     *display_control  = 2<<0;
    215 
    216     /* 3.3 映射LCD控制器对应寄存器 */    
    217     lcd_regs = ioremap(0xF8000000, sizeof(struct s5pv210_lcd_regs));    
    218     vidw00add0b0 = ioremap(0xF80000A0, 4);
    219     vidw00add1b0 = ioremap(0xF80000D0, 4);
    220     
    221     lcd_regs->vidcon0 &= ~((3<<26) | (1<<18) | (0xff<<6)  | (1<<2));   
    222     lcd_regs->vidcon0 |= ((5<<6) | (1<<4) );
    223 
    224     lcd_regs->vidcon1 &= ~(1<<7);            /* 在vclk的下降沿获取数据 */
    225     lcd_regs->vidcon1 |= ((1<<6) | (1<<5));  /* HSYNC极性反转, VSYNC极性反转 */
    226 
    227     lcd_regs->vidtcon0 = (VBPD << 16) | (VFPD << 8) | (VSPW << 0);
    228     lcd_regs->vidtcon1 = (HBPD << 16) | (HFPD << 8) | (HSPW << 0);
    229     lcd_regs->vidtcon2 = (LINEVAL << 11) | (HOZVAL << 0);
    230     lcd_regs->wincon0 &= ~(0xf << 2);
    231     lcd_regs->wincon0 |= (0xB<<2)|(1<<15);
    232     lcd_regs->vidosd0a = (LeftTopX<<11) | (LeftTopY << 0);
    233     lcd_regs->vidosd0b = (RightBotX<<11) | (RightBotY << 0);
    234     lcd_regs->vidosd0c = (LINEVAL + 1) * (HOZVAL + 1);
    235 
    236     *vidw00add0b0 = clb_fbinfo->fix.smem_start;  
    237     *vidw00add1b0 = clb_fbinfo->fix.smem_start + clb_fbinfo->fix.smem_len;  
    238 
    239     lcd_regs->shadowcon = 0x1;  /* 使能通道0 */    
    240     lcd_regs->vidcon0  |= 0x3;  /* 开启总控制器 */    
    241     lcd_regs->wincon0 |= 1;     /* 开启窗口0 */
    242 
    243     
    244     /*4.注册*/
    245     register_framebuffer(clb_fbinfo);
    246 
    247     return 0;
    248 }
    249 static void __exit clb210_lcd_exit(void)
    250 {
    251     unregister_framebuffer(clb_fbinfo);
    252     dma_free_writecombine(NULL,  clb_fbinfo->fix.smem_len, clb_fbinfo->screen_base, clb_fbinfo->fix.smem_start);
    253     iounmap(gpf0con);
    254     iounmap(gpf1con);
    255     iounmap(gpf2con);
    256     iounmap(gpf3con);
    257     iounmap(gpd0con);
    258     iounmap(gpd0dat);
    
    259     iounmap(display_control);
    260     iounmap(lcd_regs);
    261     iounmap(vidw00add0b0);
    262     iounmap(vidw00add1b0);
    263     framebuffer_release(clb_fbinfo);
    264 }
    265 
    266 module_init(clb210_lcd_init);
    267 module_exit(clb210_lcd_exit);
    268 
    269 MODULE_LICENSE("GPL");
    270 MODULE_AUTHOR("clb");
    271 MODULE_DESCRIPTION("Lcd driver for clb210 board");
    lcd_drv.c

    这份代码没有基于platform设备驱动来编写,在内核源码中的demo就是基于platform驱动模型来搭建的,主要内容其实一样。

  • 相关阅读:
    iOS开发之使用 infer静态代码扫描工具
    iOS 组件化开发之使用CocoaPod制作自己的远程私有库
    WKWebView 使用的坑
    iOS 自动化打包发布(Fastlane+ Jenkins+蒲公英)
    Flutter 开发入门实践
    【读书笔记】--《编写高质量iOS与OS X代码的52个有效方法》
    iOS 10.3+ 动态修改 App 图标
    ubuntu16.04 安装 caffe cuda 相关流程
    ubuntu 休眠后窗口边缘出现花边的解决方案
    c++实现二叉树的非递归创建以及非递归先序、中序、后序遍历
  • 原文地址:https://www.cnblogs.com/lubiao/p/4850258.html
Copyright © 2011-2022 走看看