前言

Linux的源码中本身已经抽象出了LCD驱动的公共部分代码——drivers/video/fbmem.c,对于驱动开发人员来讲,只需要理解这部分的代码并会调用其提供的接口即可。驱动开发人员需要做的就是针对具体的SOC和LCD,设置对应的LCD参数和寄存器值即可。

至于fbmem.c的流程已经有很多文章介绍过了,我这里就不具体介绍了,可以参考一下这篇文章:Linux Framebuffer驱动剖析之二—驱动框架、接口实现和使用。下面我就具体介绍一下怎么针对具体的SOC和LCD进行编程。

正文

这里直接给出一个LCD驱动程序编写的流程吧:

1. 分配 一个fb_info结构体:framebuffer_alloc2. 设置
2.1 设置固定的参数
2.2 设置可变的参数
2.3 设置操作函数:和我们自己的fb_ops联系起来
2.4 其他设置:比如设置调色板3. 硬件相关的设置
3.1 配置GPIO用于LCD
3.2 根据LCD手册设置LCD控制器,比如VCLK的频率等
3.3 分配显存(framebuffer),并把地址告诉LCD控制器4. 注册:register_framebuffer()

根据上面的步骤,下面我们直接给出代码再来解释:

#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>static struct fb_info *s3c_lcd;static volatile unsigned long *gpbcon;
static volatile unsigned long *gpbdat;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;struct lcd_regs {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 lpcsel;
};struct lcd_regs *lcd_regs;
static u32 pseudo_palette[16];static inline unsigned int chan_to_field(unsigned int chan, const struct fb_bitfield *bf)
{chan &= 0xffff;chan >>= 16 - bf->length; /*对应到s3c_lcd->var.red.length*/return chan << bf->offset; /*对应到s3c_lcd->var.red.offset*/
}static int s3c_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)return 1;val  = chan_to_field(red, &info->var.red);val |= chan_to_field(green, &info->var.green);val |= chan_to_field(blue, &info->var.blue);pseudo_palette[regno] = val;return 0;
}static struct fb_ops s3c_fb_ops = {.owner     = THIS_MODULE,.fb_setcolreg    = s3c_lcdfb_setcolreg,.fb_fillrect = cfb_fillrect,.fb_copyarea    = cfb_copyarea,.fb_imageblit   = cfb_imageblit,
};static void lcd_init(void)
{/*1. 分配 一个fb_info结构体*/s3c_lcd = framebuffer_alloc(0, NULL);if (!s3c_lcd)return -ENOMEM;printk("framebuffer alloc success\n");/*2. 设置*//*2.1 设置固定的参数*/strcpy(s3c_lcd->fix.id, "mylcd");s3c_lcd->fix.smem_len = 480 * 272 * 16 / 8; /*RGB565*/s3c_lcd->fix.type     = FB_TYPE_PACKED_PIXELS;s3c_lcd->fix.visual   = FB_VISUAL_TRUECOLOR;s3c_lcd->fix.line_length = 480 * 2; /*一开始写成了320,造成了Segmentation fault*//*2.2 设置可变的参数*/s3c_lcd->var.xres = 480;s3c_lcd->var.yres = 272;s3c_lcd->var.xres_virtual = 480;s3c_lcd->var.yres_virtual = 272;s3c_lcd->var.bits_per_pixel = 16;/*RGB565*/s3c_lcd->var.red.offset   = 11;s3c_lcd->var.red.length   = 5;s3c_lcd->var.green.offset = 5;s3c_lcd->var.green.length = 6;s3c_lcd->var.blue.offset  = 0;s3c_lcd->var.blue.length  = 5;s3c_lcd->var.activate = FB_ACTIVATE_NOW;/*2.3 设置操作函数*/s3c_lcd->fbops = &s3c_fb_ops;/*2.4 其他设置*/s3c_lcd->pseudo_palette = pseudo_palette;//s3c_lcd->screen_base = ; /*显存的虚拟地址*/s3c_lcd->screen_size = 480 * 272 * 16 / 8;printk("start ot ioremap\n");/*3. 硬件相关的设置*//*3.1 配置GPIO用于LCD*/gpbcon = ioremap(0x56000010, 8);gpbdat = gpbcon + 1;gpccon = ioremap(0x56000020, 4);gpdcon = ioremap(0x56000030, 4);gpgcon = ioremap(0x56000060, 4);*gpdcon = 0xaaaaaaaa; /*设置为output : 01 = Output*/*gpccon = 0xaaaaaaaa;*gpbcon &= ~(3<<0);*gpbcon |= 1; /*output*/*gpbdat &= ~1; /*输出低电平,关闭背光灯*/*gpgcon &= ~(3<<8);*gpgcon |= (3<<8);   //GPG4  : 11 = LCD_PWRDN/*3.2 根据LCD手册设置LCD控制器,比如VCLK的频率等*/lcd_regs = ioremap(0X4D000000, sizeof(struct lcd_regs));printk("after ioremap the register\n");/* bit[17:8] :    VCLK = HCLK / [(CLKVAL+1) x 2]*     (10MHz)100ns  = 100MHz / [(CLKVAL+1) x 2]*                 CLKVAL = 4* bit[6:5]  : 11 = TFT LCD panel* bit[4:1]  : 1100 = 16 bpp for TFT* bit[0]     : 0 = Disable the video output and the LCD control signal*/lcd_regs->lcdcon1 = (4<<8) | (3<<5) | (0xc<<1);/*垂直方向的时间参数*bit[31:24] : VBPD, VSYNC之后过多长时间才能发出第一行数据*bit[23:14] : LINEVAL, 多少行数据*bit[13:6] : VFPD, 发出最后一行数据后,过多久发出VSYNC*bit[5:0] : VSPW, VSYNC的脉冲宽度*/lcd_regs->lcdcon2 = (1<<24) | (271<<14) | (1<<6) | (9);/*水平方向的时间参数*bit[25:19] : HBPD, HSYNC之后过多长时间才能发出第一个像素数据*bit[18:8] : HOZVAL, 多少列数据*bit[7:0] : HFPD, 发出最后一个像素后,过多久发出HSYNC*LCDCON4的bit[7:0] : HSPW, HSYNC的脉冲宽度*/lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1);lcd_regs->lcdcon4 = (40);/*信号的极性*bit[11] : 1 = 5:6:5 format*bit[10] : 0 = 低电平有效*bit[9]   : 1 = HSYNC信号要反转,即低电平有效*bit[8]   : 1 = VSYNC信号要反转,即低电平有效*bit[3]   : 0 = PWREN输出0*bit[1]   : 0 = BSWP*bit[0]   : 1 = HWSWP  2440芯片手册P413*/lcd_regs->lcdcon5 = (1<<11) | (1<<9) | (1<<8) | (1<<0);/*3.3 分配显存(framebuffer),并把地址告诉LCD控制器*/s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len)>>1) & (0x1fffff);lcd_regs->lcdsaddr3 = 480 * 16 / 16; /* 一行的长度(单位是:2字节)*///s3c_lcd->fix.smem_start = xxx;/* 启动LCD */lcd_regs->lcdcon5 |= (1<<3); /*使能LCD本身*/lcd_regs->lcdcon1 |= (1<<0); /*使能LCD控制器*/*gpbdat |= 1; /* 输出高电平,使能背光 */printk("after set the lcdconx\n");/*4. 注册*/register_framebuffer(s3c_lcd);return 0;
}static void lcd_exit(void)
{unregister_framebuffer(s3c_lcd);lcd_regs->lcdcon1 &= ~(1<<0); /*关掉LCD本身*/*gpbdat &= ~1; /* 关掉背光 */dma_free_writecombine(NULL , s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);iounmap(lcd_regs);iounmap(gpccon);iounmap(gpdcon);iounmap(gpgcon);iounmap(gpbcon);framebuffer_release(s3c_lcd);
}module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");

1、分配 一个fb_info结构体

直接就调用framebuffer_alloc()函数来分配一个struct fb_info结构体就好了,没什么好讲的

2、设置LCD的的固定和可变参数

这两个结构体的参数,根据注释和已有的LCD驱动代码,我们是可以一个一个进行填充的。这里重点讲一下s3c_lcd->fix.smem_len(framebuffer大小),根据我们的4.3寸的LCD手册,像素是480*272的,且像素的格式是RGB565,,也就是16bit,所以framebuffer的大小就应该是480*272*16/8(单位:byte)。同理,s3c_lcd->fix.line_length代表每列的长度,单位是byte,因为像素为480*272,每个像素的大小为2byte,所以每列长度为480*2(单位:byte)。

固定参数结构体:
struct fb_fix_screeninfo {char id[16];          /* identification string eg "TT Builtin" */unsigned long smem_start;  /* Start of frame buffer mem *//* (physical address) */__u32 smem_len;          /* Length of frame buffer mem */__u32 type;         /* see FB_TYPE_*        */__u32 type_aux;           /* Interleave for interleaved Planes */__u32 visual;            /* see FB_VISUAL_*      */ __u16 xpanstep;          /* zero if no hardware panning  */__u16 ypanstep;           /* zero if no hardware panning  */__u16 ywrapstep;      /* zero if no hardware ywrap    */__u32 line_length;        /* length of a line in bytes    */unsigned long mmio_start; /* Start of Memory Mapped I/O   *//* (physical address) */__u32 mmio_len;           /* Length of Memory Mapped I/O  */__u32 accel;          /* Indicate to driver which *//*  specific chip/card we have    */__u16 reserved[3];        /* Reserved for future compatibility */
};

s3c_lcd->var.bits_per_pixel顾名思义就是每个像素有多少bit,我们这里是RGB565,也就是16 bit。假如每个像素格式如下:

R R R R R G G G G G G B B B B B

很明显,R的offset为从最低位(右起)偏移11位,占5bit,所以可变参数的设置就为:

s3c_lcd->var.red.offset   = 11;
s3c_lcd->var.red.length   = 5;

同理,G和B的设置也就很容易了。

可变参数结构体:
struct fb_var_screeninfo {__u32 xres;           /* visible resolution       */__u32 yres;__u32 xres_virtual;        /* virtual resolution       */__u32 yres_virtual;__u32 xoffset;         /* offset from virtual to visible */__u32 yoffset;          /* resolution           */__u32 bits_per_pixel;     /* guess what           */__u32 grayscale;      /* != 0 Graylevels instead of colors */struct fb_bitfield red;     /* bitfield in fb mem if true color, */struct fb_bitfield green;    /* else only length is significant */struct fb_bitfield blue;struct fb_bitfield transp; /* transparency         */  __u32 nonstd;           /* != 0 Non standard pixel format */__u32 activate;            /* see FB_ACTIVATE_*        */__u32 height;         /* height of picture in mm    */__u32 width;            /* width of picture in mm     */__u32 accel_flags;      /* (OBSOLETE) see fb_info.flags *//* Timing: All values in pixclocks, except pixclock (of course) */__u32 pixclock;         /* pixel clock in ps (pico seconds) */__u32 left_margin;        /* time from sync to picture    */__u32 right_margin;       /* time from picture to sync    */__u32 upper_margin;       /* time from sync to picture    */__u32 lower_margin;__u32 hsync_len;       /* length of horizontal sync    */__u32 vsync_len;      /* length of vertical sync  */__u32 sync;           /* see FB_SYNC_*        */__u32 vmode;          /* see FB_VMODE_*       */__u32 rotate;         /* angle we rotate counter clockwise */__u32 reserved[5];       /* Reserved for future compatibility */
};

3、操作函数的设置

我是直接参考现有的LCD驱动:drivers/video/s3c2410fb.c。唯一需要我们自己改动的就是调色板的函数,后面再说

4、 假的调色板

为什么这里要说是假的调色板呢?因为我这里的LCD驱动程序实际上并没用到调色板这个东西,但是为了兼容,也模仿程序加了一个。至于什么是调色板,可以参考一下我以前的文章:S3C2440芯片的LCD控制器,里面有一小节降到了调色板的概念。

看一下设置调色板的函数:

static int s3c_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)return 1;val  = chan_to_field(red, &info->var.red);val |= chan_to_field(green, &info->var.green);val |= chan_to_field(blue, &info->var.blue);pseudo_palette[regno] = val;return 0;
}

函数的参数中有红蓝绿3种颜色,然后经过chan_to_field()函数的“调色”后,赋值给val,然后就放到“调色板“pseudo_palette中。我们再看一下chan_to_field函数中是怎么”调色“的。

static inline unsigned int chan_to_field(unsigned int chan, const struct fb_bitfield *bf)
{chan &= 0xffff;chan >>= 16 - bf->length; /*对应到s3c_lcd->var.red.length*/return chan << bf->offset; /*对应到s3c_lcd->var.red.offset*/
}

其实也很简单,一个像素点的格式是RGB565,在设置fb_info结构体的可变参数时,我们曾经设置过RGB 3色的长度和偏移,这里就是从chan(表示R、G或者B的一种)得到一个像素点中R或者G或者B的值。最后将RGB拼凑成val放到pseudo_palette中。

5、配置GPIO用于LCD

每个板子LCD相关的GPIO都不一样,所以设置肯定各不相同,不过道理是一样的,根据硬件原理图,将LCD所用到的GPIO都ioremap一下,然后就根据芯片手册赋相关的值,使其成为输出还是输入引脚。

6、配置LCD控制器

一般来说,开发板是通过LCD控制器来控制LCD扫描的频率、垂直方向和水平方向的各种时间参数,所以我们还需要根据芯片手册来设置LCD控制器的各个寄存器的值,达到我们的要求。由于LCD控制器有很多寄存器,这里我就不一一列举了,下面示范一下怎么通过读芯片手册,来设置LCD控制器的寄存器。(下面的设置是根据3.5寸的LCD屏幕手册来设置的,上面的代码参数设置是4.3寸的屏幕,所以数值会有差异

下面看一下寄存器LCDCON2(垂直方向参数)的各个bit的设置

S3C2440芯片手册中的时序图:

下面是LCD手册展示的时序图:

LCD参考手册中的时序的具体参考值如下:

所以结合LCD参考手册中的时序图和具体的时序值,我们可以反推出LCDCON2中各个bit的值:

VBPD + 1 = T0 - T2 - T0 = 327 - 322 - 1 = 4,所以 VBPD = 3

LINEVAL + 1 = T5 = 320,所以LINEVAL = 319

VFPD + 1 = T2 - T5 = 2,所以VFPD = 1

VSPW + 1 = T1 = 1,所以VSPW = 0

******************************************************************************************************************

下面看一下设置水平方向参数的寄存器LCDCON3和LCDCON4:

下面是S3C2440芯片手册中的水平方向参数的时序图:

LCD手册中的水平参数的时序图:

LCD手册中水平方向参数的具体值:

所以结合LCD手册,我们可以得到LCDCON3寄存器各个bit的值:

HBPD + 1 = T6 - T8 - T7 = 273 - 251 - 5 = 17,所以HBPD = 16

HOZVAL + 1 = T11 = 240,所以HOZVAL = 239

HFPD + 1 = T8 - T11 = 251 - 240 = 11,所以HFPD = 10

HSPW + 1 = T7 = 5.所以HSPW = 4

7、分配显存

一般来说,配置高点的电子设备都有单独的显存来做显示的工作,不过我的开发板配置较低,需要从内存中直接划分一块出来做显存用。Linux已经为我们提供了一些接口函数,比如这里我们可以直接调用dma_alloc_writecombine()来分配一块由我们指定大小的内存,如果分配成功就会返回物理地址和虚拟地址的首地址。这个函数的作用可以参考一下这篇文章:、、dma_alloc_writecombine 和mmap函数。然后就是通过设置LCDADDRx寄存器,将显存的起始地址和大小告诉LCD控制器。

8、注册framebuffer

其实在Linux的整个LCD框架下编程,我们需要做的工作,也就是设置一些硬件相关的参数,这些工作都做完后,就用register_framebuffer()函数直接将设置好的fb_info结构体注册进系统。

Linux的LCD驱动相关推荐

  1. linux系统LCD驱动(三):mtk lcd驱动lcm的加载以及初始化

    上一篇博文(linux系统LCD驱动(二):mtk lcd驱动fb_info初始化)https://blog.csdn.net/Ian22l/article/details/105929192 提到m ...

  2. Linux下LCD驱动的详解

    看了不少人写的LCD驱动解释,看之前很懵逼,看之后还是很懵逼.都是放一大堆内核代码,我当时就想吐槽,能写就写,写不明白放一大堆代码是啥意思.后来,实在没办法,只能去看内核代码,结果,真香,原来别人放一 ...

  3. Linux LCD 驱动实验

    目录 Linux 下LCD 驱动简析 1 Framebuffer 设备 LCD 驱动简析 硬件原理图分析 LCD 驱动程序编写 运行测试 LCD 屏幕基本测试 设置LCD 作为终端控制台 LCD 背光 ...

  4. Linux下LCD编程

    Linux下的帧缓冲lcd应用编程 (2009-12-16 22:25)一键转载 分类: xserver 原文地址:http://www.dzkf.cn/html/qianrushixitong/20 ...

  5. Linux驱动之LCD驱动编写

    在Linux驱动之内核自带的S3C2440的LCD驱动分析这篇博客中已经分析了编写LCD驱动的步骤,接下来就按照这个步骤来字尝试字节编写LCD驱动.用的LCD屏幕为tft屏,每个像素点为16bit.对 ...

  6. linux驱动编写(lcd驱动)

    [ 声明:版权所有,欢迎转载,请勿用于商业用途. 联系信箱:feixiaoxing @163.com] 有些嵌入式设备是不需要lcd的,比如路由器.但是,还有些设备是需要lcd显示内容的,比如游戏机. ...

  7. Linux LCD 驱动

    裸机 LCD 驱动编写流程如下: ①.初始化 I.MX6U 的 eLCDIF 控制器,重点是 LCD 屏幕宽(width).高(height). hspw. hbp. hfp. vspw. vbp 和 ...

  8. 七 linux LCD驱动代分析

    LCD驱动分析 原文地址: http://blog.csdn.net/woshidahuaidan2011/article/details/52054795 1.对LCD驱动添加设备信息 对lcd驱动 ...

  9. linux的LCD的驱动编写

    Framebuffer设备 ​ Framebuffer 翻译过来就是帧缓冲,简称 fb,因 此大家在以后的 Linux 学习中见到"Framebuffer"或者"fb&q ...

  10. linux在开发板LCD上显,W35型LCD驱动移植 - linux-2.6.32在mini2440开发板上移植_Linux编程_Linux公社-Linux系统门户网站...

    编者注:本移植主要步骤还是按照手册来,里面讲解了一些有用的基础知识.但书册上提供了集中屏幕的方案,我们这里主要就用一种,也就是开发板自带的W35型号.液晶驱动的源程序在src/drivers/vide ...

最新文章

  1. 树套树 ----- P1975 [国家集训队]排队(树状数组套权值线段树求动态逆序对)
  2. 基于深度学习的目标检测技术的演进:从R-CNN到Faster R-CNN
  3. c中的指针和直接引用结构体的编译后的差异
  4. php微信墙开发,Node.js如何开发微信墙
  5. 经典中的品味:第一章 C++的Hello,World!
  6. 十问十答 BSD 许可证
  7. Linux 内核宏 time_after解析
  8. MATLAB GUI的CreateFcn如何创建
  9. 855计算机应用基础,2017年曲阜师范大学信息技术与传播学院855计算机应用基础考研导师圈点必考题汇编...
  10. 赛锐信息:SAP设计ERP主路线
  11. Gitter - 高颜值GitHub小程序客户端诞生记 1
  12. Dell R720服务器安装Ubuntu 16.04 Server 版步骤
  13. 在浏览器地址栏输入url的后的过程
  14. JS iframe 跨域
  15. Kotlin 常用API汇总
  16. 云路php解密网站源码_云路PHP解密-免费PHP文件解密工具
  17. Eclipse中,使用Darkest主题,static方法在main中不是斜体的解决办法
  18. springboot项目+多个启动类部署到linux服务器上
  19. 使用用AI制作logo图标教程
  20. x265中的lookahead

热门文章

  1. W25Q64Flash芯片
  2. 使用鸿蒙原生做游戏适配问题
  3. 用excel做logistic回归分析_利用SPSS进行Logistic回归分析
  4. KVM虚拟化技术基础框架
  5. python时间序列分析——基于混沌和数据分形理论的特征构建
  6. QTTabBar功能是灰色,无法启用的解决办法
  7. ubuntu下使用dos2unix
  8. aliez歌词_【aLIEz】附平假名歌词(完整)
  9. 通过GPS测试跑步速度可行性验证
  10. php 判断访问类型,基于php判断客户端类型