16.Linux-LCD驱动(详解)

在上一节LCD层次分析中,得出写个LCD驱动入口函数,需要以下4步:

1) 分配一个fb_info结构体: framebuffer_alloc();

2) 设置fb_info

3) 设置硬件相关的操作

4) 使能LCD,并注册fb_info: register_framebuffer()

本节需要用到的函数:

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      用来为用户空间页来分配内存; 它可能睡眠.    

分配一段DMA缓存区,分配出来的内存会禁止cache缓存(因为DMA传输不需要CPU)

它和 dma_alloc_coherent ()函数相似,不过 dma_alloc_coherent ()函数是分配出来的内存会禁止cache缓存以及禁止写入缓冲区

dma_free_writecombine(dev,size,cpu_addr,handle);   //释放缓存
//cpu_addr:虚拟地址, 
//handle:物理地址

释放DMA缓冲区, dev和size参数和上面的一样

struct fb_info *framebuffer_alloc(size_t size, struct device *dev);      //申请一个fb_info结构体,
//size:额外的内存,
//*dev:指针, 这里填0,表示这个申请的结构体里没有内容
int register_framebuffer(struct fb_info *fb_info);  

                      //向内核中注册fb_info结构体,若内存不够,注册失败会返回负数

int unregister_framebuffer(struct fb_info *fb_info) ;

                      //注销内核中fb_info结构体

本节需要用到的结构体:

fb_info结构体如下:

struct fb_info {
        ... ...
       struct fb_var_screeninfo var;       //可变的参数
       struct fb_fix_screeninfo fix;        //固定的参数
       ... ...
       struct fb_ops *fbops;              //操作函数
       ... ...
       char __iomem *screen_base;        //显存虚拟起始地址
       unsigned long screen_size;          //显存虚拟地址长度
         void *pseudo_palette;                
//假的16色调色板,里面存放了16色的数据,可以通过8bpp数据来找到调色板里面的16色颜色索引值,模拟出16色颜色来,节省内存,不需要的话就指向一个不用的数组即可
       ... ...
};

其中操作函数fb_info-> fbops 结构体写法如下:

static struct fb_ops s3c_lcdfb_ops = {
         .owner                = THIS_MODULE,
         .fb_setcolreg    = my_lcdfb_setcolreg,//设置调色板fb_info-> pseudo_palette,自己构造该函数

         .fb_fillrect      = cfb_fillrect,     //填充矩形,用/drivers/video/ cfbfillrect.c里的函数即可

         .fb_copyarea    = cfb_copyarea,  //复制数据, 用/drivers/video/cfbcopyarea.c里的函数即可

         .fb_imageblit    = cfb_imageblit, //绘画图形, 用/drivers/video/imageblit.c里的函数即可
};

固定的参数fb_info-> fix 结构体如下:

struct fb_fix_screeninfo {
       char id[16];                   //id名字
       unsigned long smem_start;  //framebuffer物理起始地址                          
       __u32 smem_len;           //framebuffer长度,字节为单位
       __u32 type;                 //lcd类型,默认值0即可
       __u32 type_aux;               //附加类型,为0
       __u32 visual;                     //画面设置,常用参数如下
// FB_VISUAL_MONO01             0   单色,0:白色,1:黑色
// FB_VISUAL_MONO10             1    单色,1:白色,0:黑色
// FB_VISUAL_TRUECOLOR          2     真彩(TFT:真彩)
// FB_VISUAL_PSEUDOCOLOR     3     伪彩
// FB_VISUAL_DIRECTCOLOR        4     直彩

    __u16 xpanstep;                /*如果没有硬件panning就赋值为0 */
    __u16 ypanstep;                /*如果没有硬件panning就赋值为0 */
    __u16 ywrapstep;                 /*如果没有硬件ywrap就赋值为0 */

    __u32 line_length;                 /*一行的字节数 ,例:(RGB565)240*320,那么这里就等于240*16/8 */
    /*以下成员都可以不需要*/
    unsigned long mmio_start;        /*内存映射IO的起始地址,用于应用层直接访问寄存器,可以不需要*/                                   
       __u32 mmio_len;                   /* 内存映射IO的长度,可以不需要*/
       __u32 accel;                 
       __u16 reserved[3];        

};

可变的参数fb_info-> var 结构体如下:

structfb_var_screeninfo{                                    
   __u32xres;                    /*可见屏幕一行有多少个像素点*/
    __u32 yres;                      /*可见屏幕一列有多少个像素点*/
    __u32 xres_virtual;         /*虚拟屏幕一行有多少个像素点 */       
    __u32  yres_virtual;       /*虚拟屏幕一列有多少个像素点*/
    __u32 xoffset;                 /*虚拟到可见屏幕之间的行偏移,若可见和虚拟的分辨率一样,就直接设为0*/
    __u32 yoffset;                 /*虚拟到可见屏幕之间的列偏移*/
    __u32  bits_per_pixel;    /*每个像素的位数即BPP,比如:RGB565则填入16*/
    __u32 grayscale;           /*非0时,指的是灰度,真彩直接填0即可*/

    struct fb_bitfield red;          //fb缓存的R位域, fb_bitfield结构体成员如下:
//__u32 offset;          区域偏移值,比如RGB565中的R,就在第11位
//__u32 length;                   区域长度,比如RGB565的R,共有5位
//__u32 msb_right;  msb_right ==0,表示数据左边最大, msb_right!=0,表示数据右边最大

    struct fb_bitfield green;    /*fb缓存的G位域*/
    struct fb_bitfield blue;       /*fb缓存的B位域*/   /*以下参数都可以不填,默认为0*/
    struct fb_bitfield transp;   /*透明度,不需要填0即可*/     
    __u32nonstd;                    /* != 0表示非标准像素格式*/
    __u32 activate;                 /*设为0即可*/
    __u32height;                     /*外设高度(单位mm),一般不需要填*/
    __u32width;                      /*外设宽度(单位mm),一般不需要填*/
    __u32 accel_flags;        /*过时的参数,不需要填*/

    /* 除了pixclock本身外,其他的都以像素时钟为 单位*/ 
    __u32pixclock;                   /*像素时钟(皮秒)*/
    __u32 left_margin;         /*行切换,从同步到绘图之间的延迟*/
    __u32right_margin;        /*行切换,从绘图到同步之间的延迟*/
    __u32upper_margin;       /*帧切换,从同步到绘图之间的延迟*/
    __u32lower_margin;       /*帧切换,从绘图到同步之间的延迟*/
    __u32hsync_len;              /*水平同步的长度*/
    __u32 vsync_len;           /*垂直同步的长度*/
    __u32 sync;
    __u32 vmode;
    __u32 rotate;
    __u32reserved[5];        /*保留*/

}

1.写驱动程序:

(驱动设置:参考自带的LCD平台驱动drivers/video/s3c2410fb.c )

(LCD控制寄存器设置:参考之前的LCD裸机驱动:http://www.cnblogs.com/lifexy/p/7144890.html)

1.1 步骤如下:

在驱动init入口函数中:

1)分配一个fb_info结构体

2)设置fb_info

  2.1)设置固定的参数fb_info-> fix

  2.2) 设置可变的参数fb_info-> var

  2.3) 设置操作函数fb_info-> fbops

  2.4) 设置fb_info 其它的成员

3)设置硬件相关的操作    

  3.1)配置LCD引脚

  3.2)根据LCD手册设置LCD控制器

  3.3)分配显存(framebuffer),把地址告诉LCD控制器和fb_info

4)开启LCD,并注册fb_info: register_framebuffer()

  4.1) 直接在init函数中开启LCD(后面讲到电源管理,再来优化)

    控制LCDCON5允许PWREN信号,

    然后控制LCDCON1输出PWREN信号,

    输出GPB0高电平来开背光,

  4.2) 注册fb_info

在驱动exit出口函数中:

1)卸载内核中的fb_info

2) 控制LCDCON1关闭PWREN信号,关背光,iounmap注销地址

3)释放DMA缓存地址dma_free_writecombine()

4)释放注册的fb_info

1.2 具体代码如下:

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

2.重新编译内核,去掉默认的LCD

make menuconfig ,进入menu菜单重新设置内核参数:

进入Device Drivers-> Graphics support:
<M> S3C2410 LCD framebuffer support          //将自带的LCD驱动设为模块, 不编进内核中

然后make uImage 编译内核

make modules 编译模块

为什么要编译模块?

因为LCD驱动相关的文件也没有编进内核,而fb_ops里的成员fb_fillrect(), fb_copyarea(), fb_imageblit()用的都是drivers/video下面的3个文件,所以需要这3个的.ko模块,如下图所示:

3.挂载驱动

将编译好的LCD驱动模块 和drivers/video里的3个.ko模块 放入nfs文件系统目录中

然后烧写内核, 先装载3个/drivers/video下编译好的模块,再来装载LCD驱动模块

挂载LCD驱动后, 如下图,可以通过  ls -l /dev/fb*   命令查看已挂载的LCD设备节点:

4.测试运行

测试有两种: 

(echo和cat命令详解入口地址: http://www.cnblogs.com/lifexy/p/7601122.html)

echo hello> /dev/tty1     // LCD上便显示hello字段

cat Makefile>/dev/tty1    // LCD上便显示Makeflie文件的内容

4.1使用上节的键盘驱动在LCD终端运行linux

vi  /etc/inittab         //修改inittab, inittab:配置文件,用于启动init进程时,读取inittab

添加->tty1::askfirst:-/bin/sh   //启动tty1的-sh进程(开机)之前,在LCD终端上打印提示enter信息

然后重启,insmod装载3个/drivers/video下编译好的模块,再来insmod装载LCD驱动模块,tty1设备便有了,就能看到提示信息:

如下图,我们insmod上一节的键盘驱动后,按下enter键,便能在LCD终端上操作linux了

(上一节的键盘驱动详解入口地址: http://www.cnblogs.com/lifexy/p/7553861.html)

按下enter键,它就启动了一个进程号772的-sh进程,如下图发现这个-sh的描述符都指向了tty1:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏岑玉海

hbase源码系列(十四)Compact和Split

先上一张图讲一下Compaction和Split的关系,这样会比较直观一些。 ? Compaction把多个MemStore flush出来的StoreFile...

2868
来自专栏皮振伟的专栏

linux、pthread、qemu 的一次 pthread create 失败的分析

qemu 因为 pthread create 失败而发生了 crash 。这种类型的问题比较少见,这里记录一下问题的分析过程以及解决思路。

1900
来自专栏Linux驱动

15.linux-LCD层次分析(详解)

如果我们的系统要用GUI(图形界面接口),这时LCD设备驱动程序就应该编写成frambuffer接口,而不是像之前那样只编写操作底层的LCD控制器接口。 什么是...

1886
来自专栏人工智能LeadAI

mongoDB的安装及基本使用

mongoDB简介 1、NoSQL数据库 数据库:进行高效的、有规则的进行数据持久化存储的软件 NoSQL数据库:Not only sql,指代非关系型数据库...

2798
来自专栏Linux驱动

30.Linux-RTC驱动分析及使用

 linux中的rtc驱动位于drivers/rtc下,里面包含了许多开发平台的RTC驱动,我们这里是以S3C24xx为主,所以它的RTC驱动为rtc-s3c....

2418
来自专栏Python攻城狮

Python数据科学(四)- 数据收集系列1.数据型态2.结构化vs半结构化vs非结构化数据3.Python IO与档案处理

◆ 定性分析: 分析: _ 知几写了很多篇文章 ◆ 定量分析: 分析:_ 知几写了107篇文章。

682
来自专栏腾讯数据库技术

Linux删除文件过程解析

2562
来自专栏不想当开发的产品不是好测试

mysql 删表引出的问题

背景 将测试环境的表同步到另外一个数据库服务器中,但有些表里面数据巨大,(其实不同步该表的数据就行,当时没想太多),几千万的数据!! 步骤 1. 既然已经把数据...

1997
来自专栏python3

大文件下载以及进度条展示和MD5校验

732
来自专栏magicsoar

windows下的C++ socket服务器(3)

int make_server_socket(int port) { WSADATA inet_WsaData;//1 WSAStar...

20910

扫码关注云+社区