zoukankan      html  css  js  c++  java
  • 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
    ", FB_MAJOR);
    
    	fb_class = class_create(THIS_MODULE, "graphics");
    	if (IS_ERR(fb_class)) {
    		printk(KERN_WARNING "Unable to create fb class; errno = %ld
    ", 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
    ", 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

    //driversvideos3c2410fb.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,
    	},
    };
    
    //archarmplat-s3c24xxdevs.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
    ");
    	}
    }
    
    

    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
    ");
                  return -EINVAL;
           }
           mregs = &mach_info->regs;
    
    
           irq = platform_get_irq(pdev, 0);
           if (irq < 0) {
                  dev_err(&pdev->dev, "no irq for device
    ");
                  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
    ", ret);
                  goto free_video_memory;
           }
    ... ...
    return ret;
    }
        
    

    小结

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

    程序设计

    参照driversvideos3c2410fb.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对应的文件

      # 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");
    
    
    
  • 相关阅读:
    【网易官方】极客战记(codecombat)攻略-地牢-矮人骚乱
    Digital Twin——IoT的下一个浪潮
    PPT |《Kubernetes的兴起》
    视频课程 | Kubernetes的兴起
    干货 | 京东云原生容器—SpringCloud实践(一)
    干货 | 独创分布式网络负载均衡最佳实践
    视频课程 | 云原生下的Serverless浅谈
    ubuntu不能联网的问题
    boost库在windows上的安装
    python tkinter
  • 原文地址:https://www.cnblogs.com/zongzi10010/p/10075638.html
Copyright © 2011-2022 走看看