linux驱动篇-LCD

前言

在嵌入式行业,有很多从业者。我们工作的主旋律是拿开源代码,拿厂家代码,完成产品的功能,提升产品的性能,进而解决各种各样的问题。或者是维护一个模块或方向,一搞就是好几年。

时间长了,中年润发现我们对从零开始编写驱动、应用、算法、系统、协议、文件系统等缺乏经验。没有该有的广度和深度。中年润也是这样,工作了很多年,都是针对某个问题点修修补补或者某个模块的局部删删改改。很少有机会去独自从零开始编写一整套完整的代码。

当然,这种现状对于企业来说是比较正常的,可以降低风险。但是对于员工本身,如果缺乏必要的规划,很容易工作多年却还是停留在单点的层面,而丧失了提升到较高层面的机会。随着时间的增长很容易丧失竞争力。

另外,根据中年润的经验,绝大多数公司对于0-5年经验从业者的定位主要是积极的问题解决者。而对于5-10经验从业者的定位主要是积极的系统规划者和引领者。在这种行业规则下,中年润认为,每个从业者都应该问自己一句,“5年后,我是否具备系统化把控软件的能力呢?”。

当前的这种行业现状,如果我们不做出一点改变,是没有办法突破的。有些东西,仅仅知道是不够的,还需要深思熟虑的思考和必要的训练,简单来说就是要知行合一。

 

也许有读者会有疑惑?这不就是重复造轮子么?我们确实是在重复造轮子,因为别人会造轮子那是别人的能力,我们自己会造轮子是我们自己的能力。在行业中,有太多的定制化需求是因为轮子本身有原生性缺陷,我们无法直接使用,或者需要对其进行改进,或者需要抽取开源代码的主体思想和框架,根据公司的需要定制自己的各项功能。设想,如果我们具备这种能力,必然会促使我们在行业中脱颖而出,而不是工作很多年一直在底层搬砖。底层搬砖没什么不好,问题是当有更廉价更激情的劳动力涌进来的时候,我们这些老的搬砖民工也就失去了价值。我们不会天天重复造轮子,我们需要通过造几个轮子使得自己具备造轮子的能力,从而更好的适应这个环境,适应这个世界。

 

针对当前行业现状,中年润经过深思熟虑,想为大家做点实实在在的事情,希望能够帮助大家在巩固基础的同时提升系统化把控软件的能力。当然,中年润的水平也有限,有些观点也只是一家之谈,希望大家独立思考,谨慎采用,如果写的有错误或者不对的地方还请读者们批评斧正,我们一起共同进步。

在这里简单介绍下中年润,中年润现在就职于一家大型国际化公司,工作经验6年,硕士毕业。曾经担任过组内的项目主管,项目经理,也曾经组建过新团队,带领大家冲锋陷阵。在工作中,有做的不错的地方,也有失误的地方,有激情的时刻,也有失落的时刻。现在偏安一隅,专心搞技术,目前个人规划的技术方向是嵌入式和AI基础设施建设,以及嵌入式和AI的融合发展。

 

最后,说了这么多,中年润希望,在未来的日子里和未知的领域里,你我同行,为我们的美好生活而努力奋斗。

 

总体目标

本篇文章的目标是介绍如何从自顶向下从零编写linux下的LCD驱动。着力从总体思路,需求端,分析端,实现端,详尽描述一个完整需求的开发流程,是中年润多年经验的提炼,希望读者能够有所收获。最后的实战目标,请读者尽量完成,这样读者才能形成自己的思路。

本示例采用arm920架构,天祥电子生产的tx2440a开发板,核心为三星的s3c2440。Linux版本为2.6.31,是已经移植好的版本。编译器为arm920t-eabi-4.1.2.tar。

 

总体思路

总体思路是严格遵循需求的开发流程来,不遗漏任何思考环节。读者在阅读时请先跟中年润的思路走一遍,然后再抛弃中年润的思路,按照自己的思路走一遍,如果遇到困难请先自己思考,实在不会再来参考中年润的思路和实现。

 

中年润在写代码的的总体思路如下:

需求描述—能够详细完整的描述一个需求。

需求分析—根据需求描述,提取可供实现的功能,需要有定量或者定性的指标。(从宏观上确定需要什么功能)。

需求分解—根据需求分析,考虑要实现需求所需要做的工作(根据宏观确定的功能,拆分成小的可单独实现的功能)。

编写思路—根据需求分解从总体上描述应该如何编写代码,(解决怎么在宏观上实现)。

详细步骤—根据编写思路,落实具体步骤,(解决怎么在微观上实现)。

编写框架—根据编写思路,实现总体框架(实现编写思路里主体框架,细节内容留在具体代码里编写)。

具体代码—根据编写框架,编写每一个函数里所需要实现的小功能,主要是实现驱动代码,测试代码。

Makefile—用来编译驱动代码。

目录结构—用来说明当完成编码后的结果。

测试步骤—说明如何对驱动进行测试,主要是加载驱动模块,执行测试代码。

执行结果—观察执行结果是否符合预期。

结果总结—回顾本节的思路,知识点,api,结构体。

实战目标—说明如何根据本文档训练。

 

需求描述

编写LCD 驱动,要求如下:

1能够执行echo hello > /dev/tty1,显示hello字符;

2能够执行cat lcd.ko > /dev/fb0,显示花屏;

3能够用按键执行ls命令并在lcd屏幕上显示。(需要结合按键模拟l,s,enter驱动)

 

需求分析

要分析出需要实现哪些功能,经过分析,可以得出需要实现以下功能。

1能够控制soc上的LCD控制器;

2能够通过控制LCD控制器让LCD屏显示图像;

3能够将LCD屏作为终端输出设备,显示终端输入的结果;

4需要利用linux提供的framebuffer模块相关api。

 

需求分解

根据需求分析的结果,分解出需要实现的功能:

1需要向内核注册和卸载LCD驱动;

2需要配置LCD相关寄存器(需要和LCD屏时序进行匹配);

3配置脚本使得LCD屏能够作为终端输入输出设备。

 

编写思路

LCD相关的具体硬件原理和linux机制,中年润会专门出两篇文章来分析和讲解,敬请读者关注。

1需要向内核注册和卸载LCD驱动

1.0编写代码框架,头文件,出入口函数,声明LICENSE

1.1需要注册fb驱动

1.1.1需要申请struct fb_info结构体

1.1.2需要填充struct fb_info结构体

1.1.3需要注册struct fb_info结构体

1.2需要卸载fb驱动

 

2需要配置LCD控制器

2.1构造LCD控制器寄存器数据结构

2.2配置LCD相关GPIO

2.3配置LCD相关寄存器

2.4设置显存

2.5启动LCD控制器及LCD屏幕

 

3配置脚本使得LCD屏能够作为终端输入输出设备

 

详细步骤

1向内核注册和卸载LCD驱动

1.1注册fb驱动

1.1.0编写代码框架,头文件,出入口函数,声明LICENSE

1.1.1申请struct fb_info结构体

1.1.2填充struct fb_info结构体

1.1.2.1设置固定参数fix字段

设置名字

设置帧缓冲的长度(单位,byte)

设置帧缓冲的类型

设置帧缓冲的可视化

设置lcd一行的长度(单位,byte)

设置帧缓冲内存的起始地址(可不设置)

1.1.2.2设置可变参数var字段

设置x方向分辨率

设置y方向分辨率

设置虚拟分辨率

设置虚拟分辨率和实际分辨率偏移值(可不设置)

设置每个像素占多少位

设置灰度值

设置red、green、blue字段,设置颜色的偏移和长度

设置激活字段

设置宽度和高度字段,为LCD物理尺寸,可不设置

1.1.2.3设置操作函数fbops字段

填充owner、fb_setcolreg、fb_fillrect、fb_copyarea、fb_imageblit字段

1.1.2.4其他设置

设置调色板

设置显存的虚拟地址(可留在2.4设置)

设置显存大小(单位,byte)

1.1.3注册struct fb_info结构体

 

1.2卸载fb驱动

1.2.1卸载fb驱动

1.2.2关闭lcd本身

1.2.3释放申请的帧缓冲区

1.2.4取消寄存器映射

1.2.5释放申请的fb_info结构体

 

2配置LCD控制器

2.1构造LCD控制器寄存器数据结构

2.2配置LCD相关GPIO

2.2.1映射物理地址为虚拟地址

2.2.2配置gpio用于LCD控制

2.3配置LCD相关寄存器

2.3.1映射物理地址为虚拟地址

2.3.2设置LCD控制器寄存器LCDCON1-LCDCON5

2.3.2.1设置LCDCON1来控制VCLK,vm触发频率,显示模式,像素格式等

2.3.2.2设置LCDCON2来控制垂直方向的时间参数

2.3.2.3设置LCDCON3来控制水平方向的时间参数

2.3.2.4设置LCDCON4来控制HSYNC同步信号的脉冲宽度

2.3.2.5设置LCDCON5来控制极性参数

2.4设置显存

2.4.1分配显存

2.4.2设置帧缓冲相关寄存器

2.4.2.1设置LCDSADDR1中的LCDBANK,LCDBASEU寄存器(说明缓冲区的开始地址)

2.4.2.2设置LCDSADDR2中的LCDBASEL寄存器(说明缓冲区的结束地址)

2.4.2.3设置LCDSADDR3中的PAGEWIDTH寄存器(说明虚拟屏幕页的宽度,单位,半字)

2.5启动LCD控制器及LCD本身

2.5.1使能LCD控制器

2.5.2使能LCD本身

 

3配置脚本使得LCD屏能够作为终端输入输出设备

3.1配置/etc/inittab

 

编写框架

/* 本文件是依照lcd 驱动<编写思路>章节编写,本文件
  * 的目的是编写代码框架,不做具体细节的编写
  */
/* 本头文件是linux2.6.31内核所提供的,其他版本按需调整 */
 
/* 1.0编写代码框架,头文件,出入口函数,声明LICENSE */
#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>
 
struct fb_info *s3c2440_lcd_info = NULL;
 
/* 1.1需要注册fb驱动 */
static int lcd_init()
{
       /* 1.1.1需要申请struct fb_info结构体 */
       s3c2440_lcd_info = framebuffer_alloc(0, NULL);
 
       /* 1.1.2需要填充struct fb_info结构体 */
 
 
       /*
         * 2需要配置LCD控制器
         * 2.1构造LCD控制器寄存器数据结构
         * 2.2配置LCD相关GPIO
         * 2.3配置LCD相关寄存器
         * 2.4设置显存
	    * 2.5启动LCD控制器及LCD屏幕
         */
 
 
       /* 1.1.3需要注册struct fb_info结构体 */
       register_framebuffer(s3c2440_lcd_info);
       return 0;
}
 
/* 1.2需要卸载fb驱动 */
static void lcd_exit()
{
       unregister_framebuffer(s3c2440_lcd_info);
}
 
/* 1.0编写代码框架,头文件,出入口函数,声明LICENSE */
 
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");

驱动代码

/* 本文件是依照lcd 驱动<详细步骤>章节编写,本文件
  * 的目的是编写代具体代码,不介绍框架
  */
 
/* 本头文件是linux2.6.31内核所提供的,其他版本按需调整 */
/* 本文件参考的lcd 芯片手册为BL43014_SPEC */
 
/* 1.1.0编写代码框架,头文件,出入口函数,声明LICENSE */
#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>
 
/* 2.1构造LCD控制器寄存器数据结构 */
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;
};
 
static u32 s3c2440_pseudo_palette[16];
static struct fb_info *s3c2440_lcd_info = NULL;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile struct lcd_regs *plcd_regs;
 
/* from pxafb.c */
/* 将三原色的数值转换成可被设置的值 */
static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
       chan &= 0xffff;
       chan >>= 16 - bf->length;
       return chan << bf->offset;
}
 
/* set color register */
/* 设置调色板中的颜色 */
int s3c2440_lcd_setcolreg(unsigned regno, unsigned red, unsigned green,
                     unsigned blue, unsigned 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);
 
       s3c2440_pseudo_palette[regno] = val;
       return 0;
}
 
static struct fb_ops s3c2440_lcd_ops = {
       .owner = THIS_MODULE,
       .fb_setcolreg = s3c2440_lcd_setcolreg,//设置颜色寄存器
       .fb_fillrect = cfb_fillrect,//填充一个矩形
       .fb_copyarea = cfb_copyarea,//拷贝一个区域的数据到另一个区域
       .fb_imageblit = cfb_imageblit,//向显示屏画一幅图像
};
 
 
/* 1.1注册fb驱动 */
static int lcd_init()
{
       /* 1.1.1需要申请struct fb_info结构体 */
       s3c2440_lcd_info = framebuffer_alloc(0, NULL);
 
       /* 1.1.2需要填充struct fb_info结构体 */
       /* 1.1.2.1设置固定参数fix字段 */
       /* 设置名字 */
       strcpy(s3c2440_lcd_info->fix.id,"s3c2440_lcd");
       /* 设置帧缓冲长度 */
       s3c2440_lcd_info->fix.smem_len = 480 * 272 * 16 / 8;
       /* 设置帧缓冲内存的起始地址(可不设置) */
       //s3c2440_lcd_info->fix.smem_start = ;
       /* 设置帧缓冲的类型 */
       s3c2440_lcd_info->fix.type = FB_TYPE_PACKED_PIXELS;
       /* 设置帧缓冲的可视化 */
       s3c2440_lcd_info->fix.visual = FB_VISUAL_TRUECOLOR;
       /* 设置lcd一行的长度(单位,byte) */
       s3c2440_lcd_info->fix.line_length = 480 * 2;
 
 
       /* 1.1.2.2设置可变参数var字段 */
       /* 设置x方向分辨率 */
       s3c2440_lcd_info->var.xres = 480;
       /* 设置y方向分辨率 */
       s3c2440_lcd_info->var.yres = 272;
       /* 设置虚拟分辨率 */
       s3c2440_lcd_info->var.xres_virtual = 480;
       s3c2440_lcd_info->var.yres_virtual = 272;
       /* 设置每个像素占多少位 */
       s3c2440_lcd_info->var.bits_per_pixel = 16;
 
       /* 设置red、green、blue字段,设置颜色的偏移和长度 */
       s3c2440_lcd_info->var.red.offset = 11;
       s3c2440_lcd_info->var.red.length = 5;
       s3c2440_lcd_info->var.green.offset = 5;
       s3c2440_lcd_info->var.green.length = 6;
       s3c2440_lcd_info->var.blue.offset = 0;
       s3c2440_lcd_info->var.blue.length = 5;
 
       /* 设置激活字段 */
       s3c2440_lcd_info->var.activate = FB_ACTIVATE_NOW;
      
       /* 设置宽度和高度字段,为LCD物理尺寸,可不设置 */
       s3c2440_lcd_info->var.width = 480;
       s3c2440_lcd_info->var.height = 272;
        
         
       /* 1.1.2.3设置操作函数fbops字段 */
       s3c2440_lcd_info->fbops = &s3c2440_lcd_ops;
 
       /* 1.1.2.4其他设置 */
       /* 设置调色板 */
       s3c2440_lcd_info->pseudo_palette = s3c2440_pseudo_palette;
       /* 设置显存的虚拟地址(可留在2.4设置) */
       //s3c2440_lcd_info->screen_base = ;
       /* 设置显存大小(单位,byte) */
       s3c2440_lcd_info->screen_size = 480 * 272 * 16 / 8;
      
       /* 主体思路
         * 2需要配置LCD控制器
         * 2.1构造LCD控制器寄存器数据结构
         * 2.2配置LCD相关GPIO
         * 2.3配置LCD相关寄存器
         * 2.4设置显存
         * 2.5启动LCD控制器及LCD本身
         */
 
       /* 2配置LCD控制器 */
       /* 2.1构造LCD控制器寄存器数据结构 */
       /* 2.2配置LCD相关GPIO */
       /* 2.2.1映射物理地址为虚拟地址 */
       gpccon = ioremap(0x56000020,4);
       gpdcon = ioremap(0x56000030,4);
       gpgcon = ioremap(0x56000060,4);
 
       /* 2.2.2配置gpio用于LCD控制 */
       /* 根据s3c2440手册配置 */
       /* config vd0 - vd7,lcd_lpcrevb,lcd_lpcrev,lcd_lpcode,vm,vframe,vline,vclk,lend*/
       *gpccon  = 0xaaaaaaaa;
       /* config vd8 - vd23 */
       *gpdcon  = 0xaaaaaaaa;
       /* config lcd_pwrdn */
       *gpgcon &= (~(3<<8))|(3<<8);
      
       /* 2.3配置LCD相关寄存器 */
       /* 2.3.1映射物理地址为虚拟地址 */
       plcd_regs = ioremap(0X4d000000,sizeof(struct lcd_regs));
       /* 2.3.2设置LCD控制器寄存器LCDCON1-LCDCON5 */
 
#if 1
       /* 2.3.2.1设置LCDCON1来控制VCLK,vm触发频率,显示模式,像素格式等*/
       /* configed by myself */
       /* CLKVAL bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2]
         *            10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2]
         *            CLKVAL = 4
         * MMODE bit[7]:0=each frame, toggle rate of VM
         * PNRMODE bit[6:5]: 0b11, TFT LCD
         * BPPMODE bit[4:1]: 0b1100, 16 bpp for TFT
         * ENVID bit[0]  : 0 = Disable the video output and the LCD control signal.
         */
       /* CLKVAL     CLKVAL_TFT,determine VCLK
         * MMODE  MVAL_USED,determine rates of VM
         * PNRMODE      dispaly mode
         * BPPMODE      bits per pixel
         * ENVID   LCD video output and the logic enable/disable
         * (CLKVAL<<8)|(MMODE<<7)|(PNRMODE<<5)|(BPPMODE<<1)|(ENVID<<0)
         */
      
       plcd_regs->lcdcon1 = (4<<8) | (0<<7) | (3<<5) | (0x0c<<1);
 
       /* 2.3.2.2设置LCDCON2来控制垂直方向的时间参数*/
       /* 垂直方向的时间参数
       * VBPD bit[31:24]:  VSYNC之后再过多长时间才能发出第1行数据
       *    LCD手册,Tvbp = 3,VBPD = 3 - 1 = 2
       * LINEVAL bit[23:14]: 多少行
       *    LCD 手册272, LINEVAL=272-1=271
       * VFPD bit[13:6] :  发出最后一行数据之后,再过多长时间才发出VSYNC
       *   LCD手册,Tvfp = 2,VFPD = 2 - 1 = 1
       * VSPW bit[5:0]  : VSYNC信号的脉冲宽度,
        *    LCD手册Tvwh=1, 所以VSPW=1-1=0
       */  
       /* VBPD 垂直同步信号的后肩
         * VFPD 垂直同步信号的前肩
         * VSPW 垂直同步信号的脉宽
         * (VBPD<<24)|(LINEVAL<<14)|(VFPD<<6)|(VSPW)
         */
 
       plcd_regs->lcdcon2 = (2<<24) | (271<<14) | (1<<6) | 0;
 
       /* 2.3.2.3设置LCDCON3来控制水平方向的时间参数*/
       /* 水平方向的时间参数
       * HBPD bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据
       *   LCD手册,Thbp = 36, HBPD= 36 -1 =35
       * HOZVAL bit[18:8]: 多少列
       *    LCD手册 480, 所以HOZVAL=480-1=479
       * HFPD bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,
       * 再过多长时间才发出HSYNC
       *   LCD手册,Thfb = 4, 所以HFPD= 4 - 1= 3
       */  
       /* HBPD 垂直同步信号的后肩
         * HFPD 垂直同步信号的前肩
         * HSPW 垂直同步信号的脉宽
         * (HBPD<<19)|(HOZVAL<<8)|(HFPD);
         */
      
       plcd_regs->lcdcon3 = (35<<19) | (479<<8) | (3<<0);
 
       /* 2.3.2.4设置LCDCON4来控制HSYNC同步信号的脉冲宽度*/
       /* 水平方向的同步信号
       * HSPW bit[7:0]    :  HSYNC信号的脉冲宽度,
        *   LCD手册Thwh = 1, 所以HSPW= 1 - 1 = 0
       */
       /* MVAL        VM 信号触发率
         * HSPW             垂直同步信号的脉宽
         * (MVAL<<8)|(HSPW)
         */
        
       plcd_regs->lcdcon4 = 0;
 
       /* 2.3.2.5设置LCDCON5来控制极性参数*/
       /* 信号的极性
        * bit[11]: 1=565 format
       * bit[10]: 0 = The video data is fetched at VCLK falling edge
       * bit[9] : 1 = HSYNC信号要反转,即低电平有效
        * bit[8] : 1 = VSYNC信号要反转,即低电平有效
        * bit[6] : 0 = VDEN不用反转
       * bit[3] : 0 = PWREN输出0
       * bit[1] : 0 = BSWP
       * bit[0] : 1 = HWSWP 2440手册P427
       */
 
       plcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) |
                                                 (0<<7) | (0<<6)| (1<<3) | (0<<1) |(1<<0);
#endif
 
#if 0
       /* supported by vendor */
       plcd_regs->lcdcon1 = (4<<8) | (0<<7) | (3<<5) | (0x0c<<1);
       plcd_regs->lcdcon2 = (2<<24) | (271<<14) | (4<<6) | 8;
       plcd_regs->lcdcon3 = (10<<19) | (479<<8) | (19<<0);
       plcd_regs->lcdcon4 = (13<<8) | 30;
       plcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) |
                                                 (0<<7) | (0<<6)| (1<<3) | (0<<1) |(1<<0);
#endif    
      
       /* 2.4设置显存 */
       /* 2.4.1分配显存 */
       s3c2440_lcd_info->screen_base = dma_alloc_writecombine(NULL,
                                      s3c2440_lcd_info->fix.smem_len,
                                      &s3c2440_lcd_info->fix.smem_start,GFP_KERNEL);
       /* 2.4.2设置帧缓冲相关寄存器 */
       /* 2.4.2.1设置LCDSADDR1中的LCDBANK,LCDBASEU寄存器(说明缓冲区的开始地址) */
       /* 取缓冲区开始地址的1-30bit */
       plcd_regs->lcdsaddr1 = (s3c2440_lcd_info->fix.smem_start >> 1) & ~(3<<30);
       /* 2.4.2.2设置LCDSADDR2中的LCDBASEL寄存器(说明缓冲区的结束地址) */
       /* 取缓冲区结束地址的1-21bit */
       plcd_regs->lcdsaddr2 = ((s3c2440_lcd_info->fix.smem_start +
              s3c2440_lcd_info->fix.smem_len) >> 1) & 0x1fffff;
       /* 2.4.2.3设置LCDSADDR3中的PAGEWIDTH寄存器(说明虚拟屏幕页的宽度,单位,半字) */
       plcd_regs->lcdsaddr3  = (480*16/16);  /* 一行的长度(单位: 2字节) */   
 
       /* 2.5启动LCD控制器及LCD本身 */
       /* 2.5.1使能LCD控制器 */
       plcd_regs->lcdcon1 |= (1<<0);
       /* 2.5.2使能LCD本身 */
       plcd_regs->lcdcon5 |= (1<<3);
      
       /* 1.1.3需要注册struct fb_info结构体 */
       register_framebuffer(s3c2440_lcd_info);
       return 0;
}
 
/* 1.2需要卸载fb驱动 */
static void lcd_exit()
{
       /* 1.2.1卸载fb驱动 */
       unregister_framebuffer(s3c2440_lcd_info);
       /* 1.2.2关闭lcd本身 */
       plcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
       /* 1.2.3释放申请的帧缓冲区 */
       dma_free_writecombine(NULL, s3c2440_lcd_info->fix.smem_len,
              s3c2440_lcd_info->screen_base, s3c2440_lcd_info->fix.smem_start);
       /* 1.2.4取消寄存器映射 */
       iounmap(plcd_regs);
       iounmap(gpccon);
       iounmap(gpdcon);
       iounmap(gpgcon);
       /* 1.2.5释放申请的fb_info结构体 */
       framebuffer_release(s3c2440_lcd_info);
}
 
/* 1.0编写代码框架,头文件,出入口函数,声明LICENSE */
 
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");

测试代码

本示例无需测试代码,可以通过相关命令测试。

 

Makefile

KERN_DIR = /home/linux/tools/linux-2.6.31_TX2440A

all:

       make -C $(KERN_DIR) M=`pwd` modules

clean:

       make -C $(KERN_DIR) M=`pwd` modules clean

       rm -rf modules.order

obj-m += lcd.o

目录结构

代码编写完成的目录结构如下所示。

├── lcd.c

├── lcd_skeleton.c

└── Makefile

Make执行完成后的目录结构体如下所示,其中cfb*.ko的生成请参考测试步骤。

├── cfbcopyarea.ko

├── cfbfillrect.ko

├── cfbimgblt.ko

├── lcd.c

├── lcd.ko

├── lcd_skeleton.c

└── Makefile

 

测试步骤

0 在linux下的makefile +180行处配置好arch为arm,cross_compile为arm-linux-(或者arm-angstrom-linux-gnueabi-)。

1在menuconfig中配置好内核源码的目标系统为s3c2440。

2 移植好的linux默认是有s3c2440 lcd模块的的,如果我们的驱动也加载进去,会调用原有的驱动。因此需要在menuconfig中去掉移植的lcd驱动。

make menuconfig

Location:                                                                                                                                                     

-> Device Drivers                                                                                                                                            

-> Graphics support                                                                                                                                       

-> Support for frame buffer devices (FB [=y])

       S3C2410 LCD framebuffer support

3 make uImage 生成uImage,并使用新的内核启动。

4 配置三个依赖函数并执行make modules 将依赖的三个函数生成模块。

Lcd驱动需要依赖三个函数,因此需要修改Kconfig(drivers/video/Kconfig)将这三个模块配置成m并编译成.ko,通过insmod命令加载到系统中。系统默认是y,会编译成.o。

  87 config FB_CFB_FILLRECT

  88     tristate

  89     depends on FB

  90     default m

  96 config FB_CFB_COPYAREA

  97     tristate

  98     depends on FB

  99     default m

105 config FB_CFB_IMAGEBLIT

106     tristate

107     depends on FB

108     default m

5 在pc上将驱动程序编译生成.ko,命令:make。

6 挂载nfs,这样就可以在开发板上看到pc端的.ko文件和测试文件。

mount -t nfs -o nolock,vers=2 192.168.0.105:/home/linux/nfs_root  /mnt/nfs

 

7 加载ko。

[root@TX2440A 10lcd]# insmod cfbfillrect.ko

[root@TX2440A 10lcd]# insmod cfbcopyarea.ko

[root@TX2440A 10lcd]# insmod cfbimgblt.ko

[root@TX2440A 10lcd]# insmod lcd.ko

 

8在开发板执行如下命令,观察现象。

echo hello > /dev/tty1

cat lcd.ko > /dev/fb0

 

特别注意:以下为另一个测试步骤,基于上述步骤和buttons驱动模拟l,s,enter按键的驱动

 

9配合buttons驱动,让按键模拟l,s,enter,并显示输出到lcd屏幕上

9.1修改/etc/inittab,新增一行,如果读者的脚本无法直接在板子上修改,请自行修改自己的根文件系统并更新。

tty1::askfirst:-/bin/sh

并重新上电。

[root@TX2440A 08button]# insmod buttons.ko

input: Unspecified device as /class/input/input1

[root@TX2440A 10lcd]# insmod cfbfillrect.ko

[root@TX2440A 10lcd]# insmod cfbcopyarea.ko

[root@TX2440A 10lcd]# insmod cfbimgblt.ko

[root@TX2440A 10lcd]# insmod lcd.ko

Console: switching to colour frame buffer device 60x34

加载完成后,会在lcd屏幕显示一行,如下所示。

Please press Enter to activate this console.

9.2按下表示enter的按键,则会激活该lcd console。

9.3按下表示l,s,enter的按键,则会像在串口上输入ls+enter命令一样,显示当前目录下的文件。

 

执行结果

8在开发板执行如下命令,观察现象。

echo hello > /dev/tty1

cat lcd.ko > /dev/fb0

 

echo hello > /dev/tty1

该命令执行后,lcd屏幕上会显示hello字符,如下图所示

cat lcd.ko > /dev/fb0

该命令执行后,lcd屏幕上会花屏,且只会花屏一半左右,如下图所示

 

9配合buttons驱动,让按键模拟l,s,enter,并显示输出到lcd屏幕上

9.1重新上电后,lcd屏幕如下图所示

9.2按下enter后如下图所示

9.3按下l+s+enter按键后如下图所示

 

结果总结

在本篇文章中,中年润跟读者分享了lcd驱动的编写思路和方法,其中贯穿始终的有几个函数和关键数据结构,它们分别是:

struct fb_ops

struct fb_info

 

framebuffer_alloc

framebuffer_release

dma_alloc_writecombine

dma_free_writecombine

register_framebuffer

unregister_framebuffer

 

请读者尽力去了解这些函数的作用,入参,返回值。

 

问题汇总

为什么echo hello > /dev/tty1时会将hello字符显示在lcd屏幕上呢?

因为tty1就是用drivers/video/fbcon.c的驱动安装的,它也会用到我们编写的lcd驱动。当我们向tty1写入数据的时候,数据自然会被输出到lcd屏幕上了。

 

实战目标

1请读者根据《需求描述》章节,独立编写需求分析和需求分解。

2请读者根据需求分析和需求分解,独立编写编写思路和详细步骤。

3请读者根据编写思路,独立写出编写框架。

4请读者根据详细步骤,独立编写驱动代码和测试代码。

5请读者根据《Makefile》章节,独立编写Makefile。

6请读者根据《测试步骤》章节,独立进行测试。

7请读者抛开上述练习,自顶向下从零开始再编写一遍驱动代码,测试代码,makefile

8如果无法独立写出7,请重复练习1-6,直到能独立写出7。

 

参考资料

《linux设备驱动开发祥解》

《TX2440开发手册及代码》

《韦东山嵌入式教程》

《鱼树驱动笔记》

 

致谢

感谢在嵌入式领域深耕多年的前辈,感谢中年润的家人,感谢读者。没有前辈们的开拓,我辈也不能站在巨人的肩膀上看世界;没有家人的鼎力支持,我们也不能集中精力完成自己的工作;没有读者的关注和支持,我们也没有充足的动力来编写和完善文章。看完中年润的文章,希望读者学到的不仅仅是如何编写代码,更进一步能够学到一种思路和一种方法。

 

中年润后续有计划按照本模板编写linux下的常见驱动,敬请读者关注。

 

联系方式

微信群:见文章最底部,因微信群有效期只有7天,感兴趣的同学可以加下。微信群里主要是为初学者答疑解惑,也可以进行技术和非技术的交流,同时也欢迎和中年润志同道合的中年人加入。

微信订阅号:自顶向下学嵌入式

公众号微信:EmbeddedAIOT

CSDN博客:chichi123137

CSDN博客网址:https://blog.csdn.net/chichi123137?utm_source=blog_pc_recommand

QQ邮箱:834759803@qq.com

QQ群:766756075

更多原创文章请关注微信公众号。另外,中年润还代理销售韦东山老师的视频教程,欢迎读者咨询。在中年润这里购买了韦东山老师的视频教程,除了能得到韦东山官方的技术支持外,还能获得中年润细致入微的技术和非技术的支持和帮助。欢迎大家选购哦。

 

 

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值