转自:https://www.cnblogs.com/lifexy/p/7604011.html

在上一节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终端打印命令行

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

添加->tty1::askfirst:-/bin/sh   //将sh进程(命令行)输出到tty1里,也就是使LCD输出信息

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

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

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

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

下章学习:

18.Llinux-触摸驱动(详解)


版权声明:本文为博主原创文章,转载请标注文章来源,觉得文章不错,不妨点个赞呗~

标注出处在于:珍惜他人的码字成果,并且文章有更新或者修改出错的地方,也能方便他人查找到

 

16.Linux-LCD驱动(详解)【转】的更多相关文章

  1. linux usb 驱动详解

    linux usb 驱动详解 USB 设备驱动代码通过urb和所有的 USB 设备通讯.urb用 struct urb 结构描述(include/linux/usb.h ). urb 以一种异步的方式 ...

  2. 13.Linux键盘驱动 (详解)

    版权声明:本文为博主原创文章,未经博主允许不得转载. 在上一节分析输入子系统内的intput_handler软件处理部分后,接下来我们开始写input_dev驱动 本节目标: 实现键盘驱动,让开发板的 ...

  3. LCD驱动详解

    参考文档:<液晶屏.pdf><S3C2440用户手册><JZ2440-V3原理图>   frame buffer: 显存,用于存放LCD显示数据:frame buf ...

  4. usb驱动---linux ACM驱动详解ACA【转】

    转自:http://blog.chinaunix.net/uid-9185047-id-3404684.html DTE提供或接收数据,连接到网络中的用户端机器,主要是计算机和终端设备.与此相对地,在 ...

  5. Linux设备驱动详解 宋宝华 硬件基础

    处理器 存储器 接口与总线 I2C时序 SPI总线时序 以太网

  6. 16.Linux-LCD驱动(详解)

    在上一节LCD层次分析中,得出写个LCD驱动入口函数,需要以下4步: 1) 分配一个fb_info结构体: framebuffer_alloc(); 2) 设置fb_info 3) 设置硬件相关的操作 ...

  7. Linux /dev目录详解和Linux系统各个目录的作用

    Linux /dev目录详解(转http://blog.csdn.net/maopig/article/details/7195048) 在linux下,/dev目录是很重要的,各种设备都在下面.下面 ...

  8. Linux串口编程详解(转)

    串口本身,标准和硬件 † 串口是计算机上的串行通讯的物理接口.计算机历史上,串口曾经被广泛用于连接计算机和终端设备和各种外部设备.虽然以太网接口和USB接口也是以一个串行流进行数据传送的,但是串口连接 ...

  9. 25.Linux-Nor Flash驱动(详解)

    1.nor硬件介绍: 从原理图中我们能看到NOR FLASH有地址线,有数据线,它和我们的SDRAM接口相似,能直接读取数据,但是不能像SDRAM直接写入数据,需要有命令才行 1.1其中我们2440的 ...

随机推荐

  1. Doxygen程序注释转换说明文档

    Doxygen使用 https://www.jianshu.com/p/9464eca6aefe

  2. volatile简记

    volatile指出变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错,也就是在使用变量时必须从它的地址中重新读取.

  3. C#&.Net干货分享-构建后台自动定时任务的源码

    1.创建一个自动处理中心任务参数的类,直接源码: namespace Frame.AutoProcess{    /// <summary>    /// 委托(用于异步处理任务)    ...

  4. CSS .css边框属性(border)

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. python之大作业

    一.题目要求 获得网页中A-Z所有名字并且爬取名字详情页中的信息,如姓名,性别,,说明等,并存放到csv中(网址:http://www.thinkbabynames.com/start/0/A) 现在 ...

  6. Paper | LISTEN, ATTEND AND SPELL: A NEURAL NETWORK FOR LARGE VOCABULARY CONVERSATIONAL SPEECH RECOGNITION

    目录 1. 相关工作 2. 方法细节 2.1 收听器 2.2 注意力和拼写 本文提出了一个基于神经网络的语音识别系统List, Attend and Spell(LAS),能够将语音直接转录为文字. ...

  7. 基于socketserver实现并发的socket编程

    目录 一.基于TCP协议 1.1 server类 1.2 request类 1.3 继承关系 1.4 服务端 1.5 客户端 1.6 客户端1 二.基于UDP协议 2.1 服务端 2.2 客户端 2. ...

  8. Express中app.use()用法 详解

    app.use(path,callback)中的callback既可以是router对象又可以是函数 app.get(path,callback)中的callback只能是函数 当一个路由有好多个子路 ...

  9. json递归查询

    主体: class json_search(): '''递归查询依赖key''' def search_key(self,data,key): self.data = data self.key_va ...

  10. Kafka生产消费API JAVA实现

    Maven依赖: <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka- ...