博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
lcd驱动框架
阅读量:6969 次
发布时间:2019-06-27

本文共 17655 字,大约阅读时间需要 58 分钟。

目录


title: lcd驱动框架

tags: linux
date: 2018/12/3 15:43:23
toc: true
---

lcd驱动框架

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

框图

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

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

完整的程序流程图在

mark

mark

程序分析

入口

mark

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

static int __initfbmem_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/fb0 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 intfb_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_tfb_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创建类下的设备

intregister_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.cstatic 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.cstruct 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:分配出来的内存参数,标志定义在
    ,常用标志如下: //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/videoinsmod cfbcopyarea.ko  && insmod cfbfillrect.ko && insmod cfbimgblt.koinsmod cfbfillrect.koinsmod 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 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*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

你可能感兴趣的文章
解决Flask局域网内访问不了的问题
查看>>
PHP获取今天、昨天、明天的日期
查看>>
[转载]DLL劫持生成器 源码开放(纯WINDOWS SDK)+ 实例分析
查看>>
在eclipse上Checkstyle的安装和使用
查看>>
控制流程完整性:给大家介绍一种“另类”的Javascript反分析技术
查看>>
vertica系列:数据的导入导出
查看>>
centos7如何添加开机启动服务/脚本
查看>>
Android OpenSL ES 开发:OpenSL ES利用SoundTouch实现PCM音频的变速和变调
查看>>
kafka学习指南(总结版)
查看>>
C#事件-使用事件需要的步骤
查看>>
[PWA] Cache JSON Data in a React PWA with Workbox, and Display it while Offline
查看>>
C++之List的用法,reference,参考,手册,man,帮助,
查看>>
yijing64
查看>>
The Stanford NLP (Natural Language Processing) Group
查看>>
ECSHOP-搜索引擎收录利器快速提升收录
查看>>
html页面实现指定位置的跳转
查看>>
c++学习笔记5
查看>>
【转】依赖抽象,不要依赖具体
查看>>
Creating a Swap Partition
查看>>
点击表格获取表格行或列索引
查看>>