Bootstrap

lcd驱动框架


title: lcd驱动框架
tags: linux
date: 2018/12/3 15:43:23
toc: true
---

lcd驱动框架

参考文档 cnblog 鱼树笔记 韦老师2期视频

框图

LCD设备驱动程序应该编写成frambuffer接口, frambuffer设备层是对图像设备的一种抽象,它代表了视频硬件的帧缓存,使得应用程序通过定义好的接口就可以访问硬件。应用程序不需要考虑底层的(寄存器级)的操作。

这里的lcd驱动框架,也可以理解是fb总线下面挂接了lcd设备,默认的是一种总线-平台-设备模型.

完整的程序流程图在这里

mark

mark

程序分析

入口

mark

函数入口在drivers/video/fbmem.c中的fbmem_init

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;
}
  1. create_proc_read_entry/proc下也会有fb文件

    # cat /proc/fb
    0 s3c2410fb
  2. register_chrdev注册驱动告知内核,主设备号是FB_MAJOR=29

  3. class_create注册了一个类graphics,具体的设备文件并不在这里创建

    # ls /sys/class/graphics/
    fb0    fbcon   # 这个并不是在这里创建的

打开open

mark

这里注册的是字符设备驱动,结构是默认的file_operations=fb_fops,从open=fb_open入手分析.

  1. 可以看到这里有一个新的结构fb_info,这个结构存储在以次设备号为索引的数组registered_fb[]中,这里次设备号最大为32,应该就是支持最多32个fb设备了,这里的fb_info应该就是管理结构了.
  2. 也就是根据次设备号在registered_fb中寻找对应的fb_info中的fb_ops中的open
  3. 注册了一个字符设备驱动fb_fops结构,open=fb_fops.open > registered_fb[次设备号].fb_ops.open
static int
fb_open(struct inode *inode, struct file *file)
{
    int fbidx = iminor(inode);
    struct fb_info *info;               //这个是fb信息结构
    int res = 0;

    if (fbidx >= FB_MAX)
        return -ENODEV;
    if (!(info = registered_fb[fbidx]))
        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); //registered_fb[fbidx]->fbops->fb_open
        if (res)
            module_put(info->fbops->owner);
    }
    return res;
}

read

已经抽象出read的算法部分,根据lcd的参数读取具体的frambuf

static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    unsigned long p = *ppos;
    // 从全局数组中按照次设备号获取具体的 fb_info 结构
    struct inode *inode = file->f_path.dentry->d_inode;
    int fbidx = iminor(inode);
    //调用 registered_fb[次设备号].fb_read
    struct fb_info *info = registered_fb[fbidx];
    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;

    // 如果自定义了驱动层的read,则调用自定义的read,否则执行默认的
    if (info->fbops->fb_read)
        return info->fbops->fb_read(info, buf, count, ppos);
    
    //total_size = 屏幕大小
    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;
    //获得需要读取的地址=基地址+offset
    src = (u32 __iomem *) (info->screen_base + p);

    
    //这个应该用于等待同步如果有自定义的话
    if (info->fbops->fb_sync)
        info->fbops->fb_sync(info);

    //读取数据到buf
    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

mark

从上述可得知,具体的操作实际上是在registered_fb这个全局数组中的,肯定有函数来注册初始化它.si中搜索register很快找到,这里会寻找到空的数组元素,并使用device_create创建类下的设备

int
register_framebuffer(struct fb_info *fb_info)
{
    int i;
    struct fb_event event;
    struct fb_videomode mode;

    
    // 寻找一个空的registered_fb[] ,可以发现fb_info->node 也就是索引也是设备号
    if (num_registered_fb == FB_MAX)
        return -ENXIO;
    num_registered_fb++;
    for (i = 0 ; i < FB_MAX; i++)
        if (!registered_fb[i])
            break;
    fb_info->node = i;
    
    // 在类下创建设备文件,名字为fb次设备号
    fb_info->dev = device_create(fb_class, fb_info->device,
                     MKDEV(FB_MAJOR, i), "fb%d", i);
    if (IS_ERR(fb_info->dev)) {
        /* Not fatal */
        printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
        fb_info->dev = NULL;
    } else
        fb_init_device(fb_info);

    if (fb_info->pixmap.addr == NULL) {
        fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
        if (fb_info->pixmap.addr) {
            fb_info->pixmap.size = FBPIXMAPSIZE;
            fb_info->pixmap.buf_align = 1;
            fb_info->pixmap.scan_align = 1;
            fb_info->pixmap.access_align = 32;
            fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
        }
    }   
    fb_info->pixmap.offset = 0;

    if (!fb_info->pixmap.blit_x)
        fb_info->pixmap.blit_x = ~(u32)0;

    if (!fb_info->pixmap.blit_y)
        fb_info->pixmap.blit_y = ~(u32)0;

    if (!fb_info->modelist.prev || !fb_info->modelist.next)
        INIT_LIST_HEAD(&fb_info->modelist);

    fb_var_to_videomode(&mode, &fb_info->var);
    fb_add_videomode(&mode, &fb_info->modelist);
    registered_fb[i] = fb_info;

    event.info = fb_info;
    fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
    return 0;
}

注册

mark

搜索这个注册函数register_framebuffer,可以看到在s3c2410fb_probe中调用,再搜索则有如下,.看到了类型是platform_driver,这就是平台platform框架程序了,可以搜索"s3c2410-lcd"来查找它的设备文件也就是来查看资源.s3c24xx_fb_set_platdata可以为这个资源文件再分配私有数据s3c2410fb_mach_info

//drivers\video\s3c2410fb.c
static struct platform_driver s3c2410fb_driver = {
    .probe      = s3c2410fb_probe,
    .remove     = s3c2410fb_remove,
    .suspend    = s3c2410fb_suspend,
    .resume     = s3c2410fb_resume,
    .driver     = {
        .name   = "s3c2410-lcd",
        .owner  = THIS_MODULE,
    },
};

//arch\arm\plat-s3c24xx\devs.c
struct platform_device s3c_device_lcd = {
    .name         = "s3c2410-lcd",
    .id       = -1,
    .num_resources    = ARRAY_SIZE(s3c_lcd_resource),
    .resource     = s3c_lcd_resource,
    .dev              = {
        .dma_mask       = &s3c_device_lcd_dmamask,
        .coherent_dma_mask  = 0xffffffffUL
    }
};
void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)
{
    struct s3c2410fb_mach_info *npd;

    npd = kmalloc(sizeof(*npd), GFP_KERNEL);
    if (npd) {
        memcpy(npd, pd, sizeof(*npd));
        s3c_device_lcd.dev.platform_data = npd;
    } else {
        printk(KERN_ERR "no memory for LCD platform data\n");
    }
}

s3c2410fb_probe函数就是模块加载的第一个程序,这里肯定会对硬件要进行操作的.

static int __init s3c2410fb_probe(struct platform_device *pdev)
{
       struct s3c2410fb_info *info;
       struct fb_info     *fbinfo;
       struct s3c2410fb_hw *mregs;
       int ret;
       int irq;
       int i;
       u32 lcdcon1;
 
       mach_info = pdev->dev.platform_data;     //获取LCD设备信息(长宽、类型等)

       if (mach_info == NULL) {
              dev_err(&pdev->dev,"no platform data for lcd, cannot attach\n");
              return -EINVAL;
       }
       mregs = &mach_info->regs;


       irq = platform_get_irq(pdev, 0);
       if (irq < 0) {
              dev_err(&pdev->dev, "no irq for device\n");
              return -ENOENT;
       }

 

       fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev); //1.分配一个fb_info结构体
       if (!fbinfo) {
              return -ENOMEM;
       }

 

     /*2.设置fb_info*/
       info = fbinfo->par;
       info->fb = fbinfo;
       info->dev = &pdev->dev;
       ... ...

    /*3.硬件相关的操作,设置中断,LCD时钟频率,显存地址, 配置引脚... ...*/
       ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info); //设置中断
       info->clk = clk_get(NULL, "lcd");                    //获取时钟
       clk_enable(info->clk);                                  //使能时钟
       ret = s3c2410fb_map_video_memory(info);               //显存地址  
       ret = s3c2410fb_init_registers(info);                //设置寄存器,配置引脚
       ... ...

       ret = register_framebuffer(fbinfo);        //4.注册一个fb_info结构体
       if (ret < 0) {
              printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret);
              goto free_video_memory;
       }
... ...
return ret;
}
    

小结

也就是说,fbmem.c已经抽象出读写的操作,能够根据提供的基地址,页面大小来读写内部ram类型frambuf,我们需要写驱动来操作硬件,并告知其具体lcd的信息

程序设计

参照drivers\video\s3c2410fb.c来设计这个fb总线下的platform平台驱动,我们这里不使用platform设计,而是直接写驱动.参考s3c2410fb_probe来进行初始化设置

  • 入口
  1. 分配一个fb_info,使用framebuffer_alloc,具体的参数设置可以参考s3c2410fb_probe,其中mach_infobast_init>s3c24xx_fb_set_platdata(&bast_lcd_info)
    1. 设置固定的参数fb_info-> fix
    2. 设置可变的参数fb_info-> var
    3. 设置具体的文件操作指针fb_info->fbops
  2. 设置GPIO引脚

  3. 分配显存fb_info>screen_base,这里使用dma_alloc_writecombine,这里注意函数返回值是虚拟地址,有个参数*handle返回实际物理地址,这个物理地址需要设置到lcd的寄存器

    void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);  //分配DMA缓存区给显存
    //返回值为:申请到的DMA缓冲区的虚拟地址,若为NULL,表示分配失败,则需要使用dma_free_writecombine()释放内存,避免内存泄漏
    //参数如下: 
    
    //*dev:指针,这里填0,表示这个申请的缓冲区里没有内容
    
    //size:分配的地址大小(字节单位)
    
    //*handle:申请到的物理起始地址
    
    //gfp:分配出来的内存参数,标志定义在<linux/gfp.h>,常用标志如下:
        //GFP_ATOMIC    用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠.
        //GFP_KERNEL    内核内存的正常分配. 可能睡眠.
        //GFP_USER      用来为用户空间页来分配内存; 它可能睡眠.
  4. 注册fb_info结构体,register_framebuffer

  • 出口
  1. 卸载内核的fb_info,unregister_framebuffer
  2. 释放申请的显存dma_free_writecombine
  3. 寄存器操作以及ioremap
  4. 释放fb_info的内存framebuffer_release
  • fb_ops结构注册,这里需要我们实现设置调色板的功能

    static struct fb_ops my_lcdfb_ops = {
          .owner           = THIS_MODULE,
          .fb_setcolreg  = my_lcdfb_setcolreg,//调用my_lcdfb_setcolreg()函数,来设置调色板fb_info-> pseudo_palette
          .fb_fillrect       = cfb_fillrect,     //填充矩形
          .fb_copyarea   = cfb_copyarea,     //复制数据
          .fb_imageblit  = cfb_imageblit,    //绘画图形,
    };
    //这里颜色表示为565 red green blue
      my_lcd->var.red.offset      =     11; //红色的最低bit
      my_lcd->var.red.length      =     5;  //红色的长度
      my_lcd->var.green.offset  =       5;
      my_lcd->var.green.length  =       6;
      my_lcd->var.blue.offset     =     0;
      my_lcd->var.blue.length     =     5;
    
    
    //填充颜色到16位数据中
    static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
    {
    /*内核中的单色都是16位,默认从左到右排列,比如G颜色[0x1f],那么chan就等于0XF800*/
           chan       &= 0xffff;
           chan       >>= 16 - bf->length;    //右移,将数据靠到位0上
           return chan << bf->offset;    //左移一定偏移值,放入16色数据中对应的位置
    }
    
    static int my_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)                //调色板数组不能大于15
                  return 1;
    
           /* 用red,green,blue三个颜色值构造出16色数据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;     //放到调色板数组中
           return 0;
    }
    

测试

  1. 去掉内核的2410lcd模块Device Drivers>Graphics support 编译为模块(M选项),为了使用以下三个

    static struct fb_ops my_lcdfb_ops = {
    ...
          .fb_fillrect       = cfb_fillrect, //填充矩形
          .fb_copyarea   = cfb_copyarea,     //复制数据
          .fb_imageblit  = cfb_imageblit,    //绘画图形,
    };
  2. make uImage

  3. make modules得到需要的模块

    /drivers/video
    insmod cfbcopyarea.ko  && insmod cfbfillrect.ko && insmod cfbimgblt.ko
    insmod cfbfillrect.ko
    insmod cfbimgblt.ko
  4. uboot使用nfs烧写指定内核nfs 0x30008000 192.168.137.222:/work/nfs_root/u-boot.bin

  5. 启动bootm 30000000

方式一操作fb0

使用cat lcd.ko>/dev/fb0这个直接写设备文件,可以看到lcd花屏

方式二操作tty

显示文件,这里第一次测试需要使用自带的那个qt的文件系统,但是我做完方式三后发现精简的文件系统也可以,后来重试了也可以,没明白为什么

echo 123> /dev/tty1     
cat Makefile>/dev/tty1

方式三操作终端

  1. 使用lcd显示sh,修改/etc/inittab,添加tty1::askfirst:-/bin/sh,重启后加载lcd驱动,当加载lcd.ko就能发现 LCD 上有提示输入回车激活终端

     mount -o nolock,rsize=1024,wsize=1024  172.16.45.222:/home/book/stu  /mnt
     insmod cfbcopyarea.ko  && insmod cfbfillrect.ko && insmod cfbimgblt.ko
     insmod lcd.ko
  2. 再使用输入子系统中的按键驱动insmod button.ko,按下按键ent11也就是代表enter会激活 LCD 的终端

  3. 这个时候可以使用按键输入ls,激活命令

  4. 在同时在串口中输入ps可以看到有两个sh

      767 0          3096 S   -sh
      768 0          3096 S   -sh
  5. 查看sh对应的文件
    shell # ls /proc/768/fd -l lrwx------ 1 0 0 64 Jan 1 00:48 0 -> /dev/tty1 lrwx------ 1 0 0 64 Jan 1 00:48 1 -> /dev/tty1 lrwx------ 1 0 0 64 Jan 1 00:48 10 -> /dev/tty lrwx------ 1 0 0 64 Jan 1 00:48 2 -> /dev/tty1

完整程序

#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>

/*LCD :  480*272     */
#define   LCD_xres     480        //LCD 行分辨率
#define   LCD_yres     272          //LCD列分辨率
 

/* GPIO prot   */
static unsigned long  *GPBcon;
static unsigned long  *GPCcon;
static unsigned long  *GPDcon;
static unsigned long  *GPGcon;  //GPG4:控制LCD信号 
static unsigned long  *GPBdat;   //GPB0: 控制背光


/* LCD control */ 
 struct  lcd_reg{  
              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[9];  
              unsigned long       dithmode;     
              unsigned long       tpal ;            
              unsigned long       lcdintpnd;
              unsigned long       lcdsrcpnd;
              unsigned long       lcdintmsk;
              unsigned long       tconsel;  
};

static struct lcd_reg  *lcd_reg;
 
static struct fb_info *my_lcd;    //定义一个全局变量
static u32 pseudo_palette[16];   //调色板数组,被fb_info->pseudo_palette调用

static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
/*内核中的单色都是16位,默认从左到右排列,比如G颜色[0x1f],那么chan就等于0XF800*/
       chan       &= 0xffff;
       chan       >>= 16 - bf->length;    //右移,将数据靠到位0上
       return chan << bf->offset;    //左移一定偏移值,放入16色数据中对应的位置
}

static int my_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)                //调色板数组不能大于15
              return 1;

       /* 用red,green,blue三个颜色值构造出16色数据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;     //放到调色板数组中
       return 0;
}


static struct fb_ops my_lcdfb_ops = {
      .owner           = THIS_MODULE,
      .fb_setcolreg  = my_lcdfb_setcolreg,//调用my_lcdfb_setcolreg()函数,来设置调色板fb_info-> pseudo_palette
      .fb_fillrect       = cfb_fillrect,     //填充矩形
      .fb_copyarea   = cfb_copyarea,     //复制数据
      .fb_imageblit  = cfb_imageblit,    //绘画图形,
};

static int lcd_init(void)
{
  /*1.申请一个fb_info结构体*/
  my_lcd= framebuffer_alloc(0,0); 

  /*2.设置fb_info*/
 
  /* 2.1设置固定的参数fb_info-> fix */
  /*my_lcd->fix.smem_start    物理地址后面注册MDA缓存区设置*/ 
  strcpy(my_lcd->fix.id, "mylcd");                       //名字
  my_lcd->fix.smem_len =LCD_xres*LCD_yres*2;             //地址长
  my_lcd->fix.type        =FB_TYPE_PACKED_PIXELS;
  my_lcd->fix.visual            =FB_VISUAL_TRUECOLOR;            //真彩色
  my_lcd->fix.line_length      =LCD_xres*2;               //LCD 一行的字节

  /* 2.2 设置可变的参数fb_info-> var  */
  my_lcd->var.xres        =LCD_xres;                          //可见屏X 分辨率
  my_lcd->var.yres        =LCD_yres;                          //可见屏y 分辨率
  my_lcd->var.xres_virtual     =LCD_xres;                   //虚拟屏x分辨率
  my_lcd->var.yres_virtual     =LCD_yres;                   //虚拟屏y分辨率
  my_lcd->var.xoffset           = 0;                     //虚拟到可见屏幕之间的行偏移
  my_lcd->var.yoffset           =0;                      //虚拟到可见屏幕之间的行偏移

  my_lcd->var.bits_per_pixel=16;                //像素为16BPP
  my_lcd->var.grayscale       =   0;            //灰色比例
 
  my_lcd->var.red.offset      =     11;
  my_lcd->var.red.length      =     5;
  my_lcd->var.green.offset  =       5;
  my_lcd->var.green.length  =       6;
  my_lcd->var.blue.offset     =     0;
  my_lcd->var.blue.length     =     5;

/* 2.3 设置操作函数fb_info-> fbops  */
  my_lcd->fbops                 = &my_lcdfb_ops;


  /* 2.4 设置fb_info 其它的成员  */ 
 /*my_lcd->screen_base    虚拟地址在后面注册MDA缓存区设置*/
  my_lcd->pseudo_palette   =pseudo_palette;            //保存调色板数组
  my_lcd->screen_size          =LCD_xres * LCD_yres *2;     //虚拟地址长

  /*3   设置硬件相关的操作*/
  /*3.1 配置LCD引脚*/
  GPBcon                     = ioremap(0x56000010, 8);
  GPBdat                     = GPBcon+1;
  GPCcon                     = ioremap(0x56000020, 4);
  GPDcon                  = ioremap(0x56000030, 4);
  GPGcon                  = ioremap(0x56000060, 4);

  *GPBcon            &=~(0x03<<(0*2));
  *GPBcon            |= (0x01<<(0*2));     //PGB0背光
  *GPBdat            &=~(0X1<<0);           //关背光
  *GPCcon            =0xaaaaaaaa;
  *GPDcon            =0xaaaaaaaa;
  *GPGcon            |=(0x03<<(4*2));    //GPG4:LCD信号

  /*3.2 根据LCD手册设置LCD控制器,参考之前的裸机驱动*/
  lcd_reg=ioremap(0X4D000000, sizeof( lcd_reg) );
   /*HCLK:100Mhz */
   lcd_reg->lcdcon1     = (4<<8) | (0X3<<5) |  (0x0C<<1) ;
   lcd_reg->lcdcon2     = ((3)<<24) | (271<<14) |  ((1)<<6) |((0)<<0);
   lcd_reg->lcdcon3     = ((16)<<19) | (479<<8) | ((10));
   lcd_reg->lcdcon4     = (4);
   lcd_reg->lcdcon5     = (1<<11) | (1<<9) | (1<<8) |(1<<0);     

   lcd_reg->lcdcon1     &=~(1<<0);              // 关闭PWREN信号输出
   lcd_reg->lcdcon5     &=~(1<<3);              //禁止PWREN信号

 /* 3.3  分配显存(framebuffer),把地址告诉LCD控制器和fb_info*/
   my_lcd->screen_base=dma_alloc_writecombine(0,my_lcd->fix.smem_len,  &my_lcd->fix.smem_start, GFP_KERNEL);

   /*lcd控制器的地址必须是物理地址*/
   lcd_reg->lcdsaddr1 =(my_lcd->fix.smem_start>>1)&0X3FFFFFFF;  //保存缓冲起始地址A[30:1]  
   lcd_reg->lcdsaddr2 =((my_lcd->fix.smem_start+my_lcd->screen_size)>>1)&0X1FFFFF; //保存存缓冲结束地址A[21:1]
   lcd_reg->lcdsaddr3 =LCD_xres& 0x3ff;        //OFFSIZE[21:11]:保存LCD上一行结尾和下一行开头的地址之间的差
                                //PAGEWIDTH [10:0]:保存LCD一行占的宽度(半字数为单位)

   /*4开启LCD,并注册fb_info: register_framebuffer()*/
   /*4.1 直接在init函数中开启LCD(后面讲到电源管理,再来优化)*/
  lcd_reg->lcdcon1      |=1<<0;         //输出PWREN信号
  lcd_reg->lcdcon5      |=1<<3;        //允许PWREN信号
  *GPBdat                   |=(0X1<<0);           //开背光

   /*4.2 注册fb_info*/
   register_framebuffer(my_lcd);
   return 0;
}

static int lcd_exit(void)
{
   /* 1卸载内核中的fb_info*/
     unregister_framebuffer(my_lcd);

  /*2 控制LCDCON1关闭PWREN信号,关背光,iounmap注销地址*/
   lcd_reg->lcdcon1 &=~(1<<0);              // 关闭PWREN信号输出
   lcd_reg->lcdcon5 &=~(1<<3);            //禁止PWREN信号
   *GPBdat            &=~(0X1<<4);           //关背光
   iounmap(GPBcon);
   iounmap(GPCcon);
   iounmap(GPDcon);
   iounmap(GPGcon);

  /*3.释放DMA缓存地址dma_free_writecombine()*/
  dma_free_writecombine(0,my_lcd->screen_size,my_lcd->screen_base,my_lcd->fix.smem_start);

   /*4.释放注册的fb_info*/
   framebuffer_release(my_lcd);

   return 0;
}

module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");

转载于:https://www.cnblogs.com/zongzi10010/p/10075638.html

;