【驱动】第4课、LCD驱动之学习笔记

开发环境

主   机:VMWare--Ubuntu-16.04.2-x64-100ask
开发板:Mini2440--256M NandFlash, 2M NorFlash, 64M SDRAM, LCD-TD35;
    bootlorder:u-boot1.16, Kernel:2.6.22.6;
编译器:arm-linux-gcc-3.4.5


 目录

1、LCD驱动程序分析方法

2、本课程LCD驱动源码程序的缺陷

3、本LCD驱动程序与裸机LCD程序框架的异同

4、LCD驱动程序设计扩展猜想

5、笔记

6、LCD驱动学习难点是以下三个函数的使用

7、问题:在bootloader阶段、内核空间、用户空间对指针指向的空间是如何定义和引用的

  其一:郝斌C语言课程关于指针常见错误的笔记

  其二:谭浩强《C程序设计》第八章_善于利用指针

  7.2 内核空间指针和指针变量的引用的方法?或者说对内核空间内存单元的操作方法

  总结

8、寄存器位运算详解

9.测试


1、驱动程序分析,一般从入口函数(xx_init()函数)或设备的 file_operations 结构体入手,一路走下去,

重点分析对象: 1>赋值语句; 2>函数调用; 3>
不特别在意的东西: 1>参数的判断的语句; 2>看不懂的语句;

2、本LCD驱动程序的缺陷:对于LCD的参数的使用是直接查看LCD手册,然后填写到驱动的帧缓冲器结构体、硬件寄存器的配置、
对于不同的LCD操作不具有可移植性,

3、本LCD驱动程序与裸机LCD程序框架的异同:
相同:
不同: 1)本LCD驱动多了帧缓冲器信息结构体struct fb_info *s3c_lcd 的初始化和配置;
驱动程序中,根据s3c_lcd = framebuffer_alloc(0, NULL);分配帧缓冲器的各项信息,
I: 内容上类同于裸板LCD结构体的定义,但是本质和功能完全不同,该驱动是帧缓冲器信息结构体s3c_lcd,裸板是某款LCD的各项时间参数结构体lcd_3_5_params等;
帧缓冲器信息结构体s3c_lcd作用:定义可供内核调用某款LCD的使用的LCD帧缓冲器;
LCD参数结构体lcd_3_5_params作用:给LCD控制器的初始化配置提供数据;
II:FB_BASE显存地址的分配方式不同:
驱动上是>> /* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */
s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);
裸板上是>> #define LCD_3_5_FB_BASE 0x33b00000 //lcd_3.5.c文件;

2)本LCD驱动少了裸板对于LCD结构体的定义、具体某一款LCD的定义初始化;

4、LCD驱动程序设计扩展猜想:可否把裸板的分离分层程序和该章的框架结合起来?甚至把前两章学习的分离、分层结合起来?
编写一个具备良好移植兼容性的LCD驱动模块。

5、笔记:
1)对于寄存器或者映射内存,需要遵循先定义后使用的原则:
gpbcon = ioremap(0x56000010, 8);
lcdcon1 = (4<<8) | (3<<5) | (0x0c<<1);

2)硬件寄存器的设置上,本驱动和我的mini2440的寄存器配置的数据有什么不同?
I:GPIO口——
II:LCD控制器——

3)LCD背光灯、LCD本身、触摸屏是三个独立的系统,只不过集成到一个LCD屏幕上工作而已。
4)裸板LCD的主要工作是配置LCD及其控制器,并实现分离分层,然后是关于测试LCD的应用程序:在帧缓冲器中填充数据以完成不同的图案或字符的输出;
驱动LCD的主要工作是配置帧缓冲器结构体struct fb_info *s3c_lcd ,然后是LCD相关寄存器的配置。
5)驱动中调色板(假调色板)终究没有用到?只是在fb_ops结构体中定义好元素.fb_setcolreg(即对应LCD的调色板配置函数s3c_lcdfb_setcolreg),
以兼容以往的程序?

6、LCD驱动学习难点是以下三个函数的使用:
I. s3c_lcdfb = framebuffer_alloc(0, NULL);
功能:分配一个帧缓冲器,即分配一段连续的物理内存并 set to zero 给帧缓冲器结构体指针 s3c_lcdfb使用。
问题1:以前分配一个结构体时,直接定义然后初始化结构体内的元素即可。这次的xx_fb结构体,定义时(或之后),
需要在初始化之前使用专用的函数分配一段内存给结构体xx_fb。是因为这个结构体太大了吗?
以前裸机全局变量是直接在.bss段分配内存给其使用,由于.bss段在程序有效数据的最后面,只要总的内存还够,就可以直接
顺序分配内存空间给全局变量。如此,既有问题2:
问题2:整个linux系统(bootlorder, linux内核, fs根文件系统)中,关于内存是如何管理、使用和分配的?具体到驱动程序的全局变量和应用程序
的全局变量的内存又是怎样分配的?
答:百度查到的博文有说,内存管理系统可以分为两部分,分别是内核空间内存管理和用户空间内存管理。具体以后再看。
问题3:framebuffer_alloc()函数的使用是因为在其之前只定义了帧缓冲器结构体指针 s3c_lcdfb的原因还是 因为结构体太大需要用专用函数
分配一段内存给该结构体使用?对于结构体的定义是怎么样的?
补充:以前裸机的全局变量,若是一般全局变量分配到.data段,const全局变量(常变量)分配到.rodata段(只读数据段),初值为0/无初值的全局变量
分配到.bss段。这些段在内存中的地址分配如下:
SECTIONS{
. = 0x30000000; /*SDRAM在CPU统一编址中的基地址*/
__code_start = .;
. = ALIGN(4);
.text : {*(.text);}
. = ALIGN(4);
.rodata : {*(.rodata);}
. = ALIGN(4);
.data : {*(.data);}
. = ALIGN(4);
.bss : {
_bss_start = . ;
*(.bss) *(.COMMON);
}
. = ALIGN(4);
_end = . ;
}
注意:kzalloc申请过程做了初始化工作并在其后的过程中给指针的成员device做了赋值工作。如果暂时不要给device成员赋值,参数可以写NULL。
II. s3c_lcdfb->screen_base = dma_alloc_writecombine(NULL, s3c_lcdfb->fix.smem_len, s3c_lcdfb->fix.smem_start, GFP_KERNEL); /* 3.3 分配显存,并把显存物理地址告诉LCD控制器 */
III. register_framebuffer(s3c_lcdfb); /* 4.注册帧缓冲器设备 */

--------------------------------------------------------------------------------------
7、问题:在bootloader阶段、内核空间、用户空间对指针指向的空间是如何定义和引用的?
答:我的困惑源于对各层级指针操作仍存在错误的模糊认识!关于bootloader空间、内核空间、用户空间指针的用法如下。

7.0 <1>旧问题:内核中,对于结构体变量的是如何定义和分配内存的?
<2>定义结构体指针变量时,有没有同时分配存储空间啊?
<3>看到结构体的数组定义好以后就直接可以用了。但是结构体指针在链表中还要malloc()申请空间。这是为什么啊?
答:<1>两种方法,一种是静态分配内存(即直接定义):struct Student stu1; 第二种是通过该结构体指针动态分配一段内存空间:struct Student *pst2=malloc(sizeof(struct Student));
<2>定义结构体指针变量 p,并不代表定义了结构体指针指向的变量 *p,系统分配的内存是给指针变量p用的,对于指针p指向的空间未定义不分配内存空间;
<3>结构体数组struct Student pstu3[]={{...}, {...}};即同时定义了两个结构体变量pstu3[0], pstu3[1], 只不过他们是作为数组pstu3的元素而存在的。
链表一般是动态分配内存空间,可随时插入和删除节点,作为指针域的指针,需要动态分配一个节点的内存空间作为其指向的插入的节点。

7.1 在用户空间中指针变量的引用的方法,详解如下:
其一:郝斌C语言课程关于指针常见错误的笔记
<0>指针就是地址,地址就是指针; 指针不是指针变量,指针变量不是指针。
<1>int *p; #不代表定义了一个叫做*p的变量!
<2>所有的变量、内存单元都遵循先定义再使用的规则,否则就会编译报错或程序崩溃!
例:int main(void){
int *p;
int i = 5;
// *p = i; //不屏蔽则程序运行崩溃!
printf("p = %#x\n", p); //打印:p = 0xcccccccc
// printf("*p = %d\n", *p); //不屏蔽则程序运行崩溃!
return 0;
}
/*在VC++6.0中的运行结果:
p = 0xcccccccc
Press any key to continue
*/
语句*p = i; (//不屏蔽则程序运行崩溃!)的详解:
p须先有指向,然后才能有一个值(地址)赋给它; 若p无指向,则p为空/不可知的垃圾值,因为不知道p的值是多少,所以不知*p
到底代表的是哪一个变量。而*p=i=5,就是把5赋给了一个不知道地址的单元。归根到底是因为用户程序只能访问属于本程序的内存单元,
对于不属于本程序的内存单元,指针p无权访问。
<3>为指针变量指向的内存单元分配内存空间的方法(目前只有这两种方法,其他alloc与malloc类同):
a.静态分配指针p指向的内存空间:int *p; int i; p=&i; *p=5;
b.动态分配指针p指向的内存空间:int *p = (int *)malloc(sizeof(int)); *p=5; //其中(int *)可以省略,系统会自动转换到需要的指针类型;
说明:内存的动态分配主要应用于建立程序中的动态数据结构(如链表)中。
<4>给指针变量赋值的方法只有一种: int *p; int i; p=&i; ?应该还有另外一种,如上malloc。

其二:谭浩强《C程序设计》第八章_善于利用指针
8.1 指针是什么
为了说清楚什么是指针,必须先弄清楚数据在内存中是如何存储的,又是如何读取的。
如果在程序中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配内存单元。编译系统根据程序中定义的变量类型,分配一定长度的空间。
由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。
在程序中一般是通过变量名来引用变量的值,例如:printf("%d\n", i);
上面实际上,是通过变量名i找到存储单元的地址,从而对存储单元进行存取操作的。程序经过编译以后已经将变量名转换为变量的地址,对变量值的存取都是
通过地址进行的。 (对变量操作的本质>>>>>>>>>对内存操作)。
将数值3送到变量i中,有两种表达方法:
<1>直接访问:将3直接送到变量i所标志的单元中,例如“i=3;”; “scanf("%d", &i);”。
<2>间接访问:将3送到变量pi所指向的单元(即变量i的存储单元),例如“*pi=3;”, 其中*pi表pi指向的对象。
8.2 指针变量
存放地址的变量是指针变量,它用来指向另一个对象(如变量、数组、函数等)。
8.2.1 定义指针变量
类型名 * 指针变量名;
8.2.1 引用指针变量
<1>给指针变量赋值。如:p=&a; //把a的地址赋给指针变量p;
<2>引用指针变量指向的变量。如果已经执行"p=&a;",即指针变量p指向了整型变量a,则:printf("%d",*p);其作用是以整数形式输出指针变量p所指向的变量的值,即变量a的值。如果有赋值语句:*p=1;表示把整数1赋给p当前所指向的变量。
<3>引用指针变量的值。如:printf("%#x", p);
注意:要熟练掌握两个有关的运算符:<1>& 取地址运算符。<2>* 指针运算符。
8.8 动态内存分配与指向它的指针变量
8.8.1 什么是内存的动态分配
除了静态内存分配(一般变量),C语言还允许建立内存动态分配区域,以存放一些临时用的数据,这些数据不必在程序的声明部分定义,也不必等到函数结束
时才释放,而是需要时随时开辟,不需要时随时释放。由于未在声明部分定义它们为变量或数组,因此不能通过变量名或者数组名去引用这些数据,只能通过指针来引用。
8.8.2 怎样建立内存的动态分配
对内存的动态分配是通过系统提供的库函数来实现的,主要有malloc, calloc, free, realloc等函数。
内存的动态分配主要应用于建立程序中的动态数据结构(如链表)中。
【知识归纳】
指针的定义:一个从0开始的非负整数的内存单元编号。
指针变量的使用:先定义(int i;int *p;),然后赋初值(p=&i),再然后才可以对其指向的内存单元进行读/写等操作。

7.2 内核空间指针和指针变量的引用的方法?或者说对内核空间内存单元的操作方法?
分两类:一类是对硬件地址的直接引用(*(volatile unsigned int *)(0x48000000)) ,但只有读/写两种操作方式,这是在bottloader(和内核?)阶段使用的,
用户空间所不具备的应用方式。用户空间属于应用层,通过内核方可访问硬件(包括各种设备的地址)。
这个对硬件地址的直接引用的层级没有系统概念,因为系统是我们自己定义编写的。
二类是与用户空间的方法一样:定义指针变量静态分配内存,然后赋初值再引用,p=&a;*p=3; 或动态分配内存int *p=malloc(sizeof(int));*p=5;可以有读/写/算数加减(包括自加减)等运算操作。
【总结】
造成这种模糊认知是因为arm学习中,CPU对GPIO接口、协议类接口、内存类接口控制器(不包括Nandflash)进行统一编址,
对寄存器的操作(包括读/写数据)是直接用寄存器的地址(即指针)的指针运算符* Addr(例如:GPACON)作为对象进行读/写操作,而非借助
某一对象,或者说这个对象已经回归到某一具体(已知地址的)设备或内存单元。
#define __REG(x) (*(volatile unsigned int *)(x))
#define BWSCON __REG(0x48000000) //Memory Controllers:Bus width & wait status control
#define GPACON __REG(0x56000000) //I/O port:Port A control
在裸机和内核中,对于没有赋初值的变量一般自动写0,指针变量写NULL。这时,对指针变量 p 操作:*p=3;是不现实的。

----------------------------------------------------------------------------------------------------------------
8、寄存器位运算详解
<1>lcdsaddr1的设置如下,哪种方法正确?
LCDSADDR1位定义:
LCDBANK [29:21]
这些位表明系统存储器中视频缓冲器的 bank 位置的 A[30:22]。即使当
移动视口时也不能改变 LCDBANK 的值。LCD 帧缓冲器应该在 4MB
连续区域内,以保证当移动视口时不会改变 LCDBANK 的值。因此应
该谨慎使用 malloc()函数。
默认值:0x00
LCDBASEU [20:0]
对于双扫描 LCD:这些位表明递增地址计数器的开始地址的 A[21:1],
它是用于双扫描 LCD 的递增帧存储器或单扫描 LCD 的帧存储器。
对于单扫描 LCD:这些位表明 LCD 帧缓冲器的开始地址的 A[21:1]。
默认值:0x000000
LCD裸板程序该寄存器的配置:
addr = plcdparams->fb_base<<1;
addr >>= 2;
LCDSADDR1 = addr; //帧缓冲器的开始地址;
方法1:plcd_regs->lcdsaddr1 = ((s3c_lcdfb->fix.smem_start<<1)>>2);
方法2:lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
问题:哪种可以达到正确配置要求?
答:方法2是经过LCD驱动上机验证可行的,对于方法1是裸板LCD程序使用的方法,但是对于long型数据的直接多次位操作并赋予
寄存器尚待商榷。
验证代码:
#include <stdio.h>
int main(void)
{ unsigned int addr1 = 0xffffffff;
unsigned int lcdsaddr1 = ((addr1<<1)>>2);
unsigned int lcdsaddr2 = ((0xffffffff<<1)>>2);
printf("lcdsaddr1 = %#x, lcdsaddr2 = %#x\n", lcdsaddr1, lcdsaddr2);
return 0;
}
/*在VC++6.0中的运行结果:
lcdsaddr1 = 0x3fffffff, lcdsaddr2 = 0x3fffffff
Press any key to continue
*/
VC++6.0和gcc编译器对于这类位操作的规则是否一致?为什么不在直接在arm平台测试该应用程序呢?##########
若一致,即可说明方法2也是可行的。本次LCD驱动测试使用方法2,观察LCD运行是否正常。
现象:可使得LCD正常工作!以上两种方法均可运行!


9.测试
目的:配置一个没有LCD模块的内核,用新内核去挂接到开发板0x30000000(即SDRAM)进行测试:
# nfs 30000000 192.168.1.105:/work/nfs_root/uImage_mlcd
挂接(服务器上的)网络根文件系统到(开发板根文件系统的)/mnt,从flash上启动根文件系统:
# mount -t nfs -o nolock,vers=2 192.168.1.105:/work/nfs_root/fs_second /mnt
说明:经在单板测试,若单板上的根文件系统原本就是挂接的服务器上的根文件系统(例如:fs_second),则不需再用mount命令挂接网络根文件
系统到开发板了!可直接进行驱动模块加载等试,执行命令:
# echo hello > /dev/tty1
均可在单板LCD上打印出字符串“hello”!

操作步骤:
9.1 编译一个新的没有LCD模块的内核--uImage_nolcd
$ cd /home/book/workbook/mini2440/systems/linux-2.6.22.6/
$ make menuconfig //配置内核,去掉原来的LCD驱动程序;
-> Device Drivers
-> Graphics support
<M> S3C2410 LCD framebuffer support
$ make uImage //编译生成新内核;
$ cp arch/arm/boot/uImage /work/nfs_root/uImage_nolcd
$ make modules //编译模块,是为了把fb_ops结构体的3个cfb_xx函数对应的cfb_xx.c源文件编译成.ko文件(模块),供稍后测试时使用。

9.2 用新内核启动开发板,在倒数计时结束前按下“空格”键,进入uboot菜单>
OpenJTAG> print
...(打印的内核信息..)
ipaddr=192.168.7.17
...(打印的内核信息..)
OpenJTAG> set ipaddr 192.168.1.17
OpenJTAG> save
OpenJTAG> nfs 30000000 192.168.1.105:/work/nfs_root/uImage_mlcd //uImage_mlcd是内核菜单配置时,LCD_fb配置为<M>,即模块,可以事后加载使用;
OpenJTAG> bootm 30000000 //启动新内核uImage_mlcd;
<启动内核...>
# mount -t nfs -o nolock,vers=2 192.168.1.105:/work/nfs_root/fs_second /mnt
# cd /mnt
# ls
bin driver_test lib mnt sbin usr
dev etc linuxrc proc sys

9.3 重新配置单板根文件系统的/etc/inittab文件,使得单板上的Linux系统拥有串口0终端和单板按键-LCD两个控制台
修改inittab文件:
# vi /etc/inittab
#/etc/inittab
#console::askfirst:-/bin/sh
::sysinit:/etc/init.d/rcS
s3c2410_serial0::askfirst:-/bin/sh
tty1::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
查看:
# cat /etc/inittab
#/etc/inittab
#console::askfirst:-/bin/sh
::sysinit:/etc/init.d/rcS
s3c2410_serial0::askfirst:-/bin/sh
tty1::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
# reboot //重启系统;
OpenJTAG> nfs 30000000 192.168.1.105:/work/nfs_root/uImage_mlcd
OpenJTAG> bootm 30000000 //启动新内核uImage_mlcd;
<启动内核...>
# mount -t nfs -o nolock,vers=2 192.168.1.105:/work/nfs_root/fs_second /mnt
# cd /mnt
# insmod cfbcopyarea.ko
# insmod cfbimgblt.ko
# insmod cfbfillrect.ko
# insmod lcd_6.ko
Console: switching to colour frame buffer device 30x40
# insmod input_keys2.ko
input: Unspecified device as /class/input/input1
<此时,LCD屏幕显示提示信息...以下都是单板按键--LCD屏幕控制台的LCD屏幕的显示信息...>
Please press Enter to activate
starting pid 768, tty '/dev/tty1': '/bin/sh'
# ls
bin dev etc driver_test ...

问题:刚加载好了LCD模块,LCD可以正常使用,但是过一会不用就黑屏了,不能再显示写入的字符串了,为什么?
答:Linux下的LCD驱动默认在无操作之后10分钟后会自动关闭屏幕。
问题:怎么唤醒LCD屏幕呢?
答:若加载了单板的输入子系统,可直接敲击单板上的指令“按键”,即可自动唤醒LCD屏幕进行指令操作。
在中断执行命令# echo wakakak > /dev/tty1 虽然仍然可以发送字符到单板LCD屏幕,但是不能唤醒LCD屏幕,需要用单板按键唤醒!

10、源码

  1 /*
  2 2018-12-14
  3 File: lcd_2.c
  4 */
  5 #include <linux/module.h>
  6 #include <linux/kernel.h>
  7 #include <linux/errno.h>
  8 #include <linux/string.h>
  9 #include <linux/mm.h>
 10 #include <linux/slab.h>
 11 #include <linux/delay.h>
 12 #include <linux/fb.h>
 13 #include <linux/init.h>
 14 #include <linux/dma-mapping.h>
 15 #include <linux/interrupt.h>
 16 #include <linux/workqueue.h>
 17 #include <linux/wait.h>
 18 #include <linux/platform_device.h>
 19 #include <linux/clk.h>
 20 
 21 #include <asm/io.h>
 22 #include <asm/uaccess.h>
 23 #include <asm/div64.h>
 24 
 25 #include <asm/mach/map.h>
 26 #include <asm/arch/regs-lcd.h>
 27 #include <asm/arch/regs-gpio.h>
 28 #include <asm/arch/fb.h>
 29 MODULE_LICENSE("GPL");
 30 
 31 /* 函数声明 */
 32 static int s3c_lcdfb_setcolreg(unsigned regno, unsigned red, unsigned green,
 33                 unsigned blue, unsigned transp, struct fb_info *info);    
 34 
 35 struct lcd_regs {
 36     unsigned long lcdcon1;
 37     unsigned long lcdcon2;
 38     unsigned long lcdcon3;
 39     unsigned long lcdcon4;
 40     unsigned long lcdcon5;
 41     unsigned long lcdsaddr1;
 42     unsigned long lcdsaddr2;
 43     unsigned long lcdsaddr3;
 44     unsigned long redlut;
 45     unsigned long greenlut;
 46     unsigned long bluelut;
 47     unsigned long reserved[8];
 48     unsigned long dithmode;
 49     unsigned long tpal;
 50     unsigned long lcdintpnd;
 51     unsigned long lcdsrcpnd;
 52     unsigned long lcdintmsk;
 53     unsigned long tconsel;
 54 };
 55 
 56 static struct fb_ops s3c_lcdfb_ops = {
 57     .owner = THIS_MODULE,
 58     /* set color register */
 59     .fb_setcolreg = s3c_lcdfb_setcolreg,
 60     /* Draws a rectangle */
 61     .fb_fillrect = cfb_fillrect,        /* void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect); */
 62     /* Copy data from area to another*/
 63     .fb_copyarea = cfb_copyarea,        /* void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);*/
 64     /* Draws a image to the display */
 65     .fb_imageblit = cfb_imageblit,        /*void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);*/
 66 };
 67 static struct fb_info *s3c_lcdfb;    /* 定义一个帧缓冲器结构体 */
 68 static volatile struct lcd_regs *plcd_regs;
 69 static volatile unsigned long *gpbcon;
 70 static volatile unsigned long *gpbdat;
 71 static volatile unsigned long *gpccon;
 72 static volatile unsigned long *gpdcon;
 73 static volatile unsigned long *gpgcon;
 74 static u32 pseudo_palette[16];
 75 
 76 /* 像素格式转换 */
 77 static inline unsigned int chan_to_field(u_int chan, struct fb_bitfield * bf)
 78 {
 79     chan &= 0xffff;                /* 保留该color的低16位 */
 80     chan >>= 16 - bf->length;    /* 裁剪到设定像素数据格式的该color长度 */
 81     return chan<<bf->offset;
 82 }
 83 /* 设置调色板 */
 84 static int s3c_lcdfb_setcolreg(unsigned regno, unsigned red, unsigned green,
 85                 unsigned blue, unsigned transp, struct fb_info *info)
 86 {
 87     unsigned int val;
 88     if(regno > 16)
 89         return 1;    /* unknown tybe */
 90     val = chan_to_field(red, &info->var.red);
 91     val |= chan_to_field(green, &info->var.green);
 92     val |= chan_to_field(blue, &info->var.blue);
 93     pseudo_palette[regno] = val;
 94     return 0;
 95 }
 96 
 97 static int lcd_init(void)
 98 {
 99     int ret = 0;
100     
101     /* 1.分配一个帧缓冲器 */
102     s3c_lcdfb = framebuffer_alloc(0, NULL);        /* sucess: return info; err: return NULL; */
103     if(!s3c_lcdfb)
104     {
105         printk(KERN_ERR "Unable to alloc s3c_lcdfb!\n");
106         goto err_fail1;
107     }
108     /* 2.设置帧缓冲器结构体 */
109     /* 2.1设置固定参数 */
110     strcpy(s3c_lcdfb->fix.id, "mylcd");    
111     //s3c_lcdfb->fix.smem_start     = ;        /* 帧缓冲器的起始地址(物理地址) */
112     s3c_lcdfb->fix.smem_len        = 320*240*16/8;            /* 帧缓冲器的长度 */
113     s3c_lcdfb->fix.type            = FB_TYPE_PACKED_PIXELS;
114     s3c_lcdfb->fix.visual        = FB_VISUAL_TRUECOLOR;    /* TFT真彩 */
115     s3c_lcdfb->fix.line_length    = 240*16/8;                /* length of a line in bytes */
116     /* 2.2设置可变参数 */
117     s3c_lcdfb->var.xres            = 240;        /* 可见分辨率 */
118     s3c_lcdfb->var.yres         = 320;
119     s3c_lcdfb->var.xres_virtual    = 240;        /* 虚拟分辨率 */
120     s3c_lcdfb->var.yres_virtual    = 320;
121     s3c_lcdfb->var.bits_per_pixel    = 16;    /* 16bpp */
122     /* RGB = 565 */
123     s3c_lcdfb->var.red.offset    = 11;        /* 开始位域偏移量 */
124     s3c_lcdfb->var.red.length    = 5;        /* 位域长度 */
125     s3c_lcdfb->var.green.offset = 5;    
126     s3c_lcdfb->var.green.length = 6;
127     s3c_lcdfb->var.blue.offset    = 0;
128     s3c_lcdfb->var.blue.length    = 5;
129     s3c_lcdfb->var.activate        = FB_ACTIVATE_NOW;
130     
131     /* 2.3设置操作函数 */
132     s3c_lcdfb->fbops    = &s3c_lcdfb_ops;
133     /* 2.4设置其余参数 */
134     //s3c_lcdfb->screen_base         = ;    /* 屏幕开始的虚拟地址 */
135     s3c_lcdfb->screen_size         = 320*240*16/8; /* Amount of ioremapped VRAM or 0 */ 
136     s3c_lcdfb->pseudo_palette    = pseudo_palette;
137     /* 3.设置硬件(LCD相关寄存器) */
138     /* 3.1设置用于LCD的GPIO引脚 */
139     /* 映射寄存器地址到虚拟内存 */
140     gpbcon = ioremap(0x56000010, 8);
141     gpbdat = gpbcon + 1;
142     gpccon = ioremap(0x56000020, 4);
143     gpdcon = ioremap(0x56000030, 4);
144     gpgcon = ioremap(0x56000060, 4);
145     /* LCD背光灯LED+-, 硬件自动连接到电源正负极,不管? 
146      * GPB1: 配置为输出引脚,高电平:背光供电;  低电平:不供电 
147      * GPBCON &= ~(3<<2);
148      * GPBCON |=  (1<<2);
149      */
150     *gpbcon &= ~(3<<2);
151     *gpbcon |=  (1<<2);
152     *gpbdat &= ~(1<<1);        /* 低电平,先不提供背光 */
153     /* S3C2440-LCD连接引脚状态控制 */
154     *gpccon = 0xaaaaaaaa;     /* GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
155     *gpdcon = 0xaaaaaaaa;    /* GPIO管脚用于VD[23:8] */
156     /* GPG4: 配置为 LCD_PWREN 功能引脚 */
157     *gpgcon |= (3<<8);
158     
159     /* 3.2设置LCD控制器 */
160     plcd_regs = ioremap(0X4D000000, sizeof(struct lcd_regs));
161     /* LCDCON1位定义:
162      * CLKVAL   [17:8] 决定 VCLK 的频率和 CLKVAL[9:0]。TFT:VCLK = HCLK / [(CLKVAL + 1) × 2] (CLKVAL≥0);
163      * PNRMODE  [6:5] 11 = TFT LCD 面板;
164      * BPPMODE  [4:1] 选择 BPP(位每像素)模式:  1100 = TFT 的 16 bpp;  
165      * ENVID    [0]     0 = 禁止视频输出和 LCD 控制信号;  1 = 允许视频输出和 LCD 控制信号;
166      *
167      * //int CLKVAL = (double)HCLK/plcdparams->time_seq.vclk/2 - 1 + 0.5;
168      * //int CLKVAL = 5;        // jz2440  的4.3寸TFT屏幕;
169      * int CLKVAL = 7;            // mini2440的3.5寸TFT屏幕;
170      *  int bppmode =     plcdparams->bpp == 8?  0xb :\
171                     plcdparams->bpp == 16? 0xc :\
172                     0xd;    //0xd: 32/24bpp;
173      * LCDCON1 =  (CLKVAL<<8) | (3<<5) | (bppmode<<1);
174       */
175      plcd_regs->lcdcon1 =  (7<<8) | (3<<5) | (0xc<<1);
176     /* LCDCON2位定义:
177      * VBPD  [31:24]TFT:tvb-1, 垂直后沿为帧开始时,垂直同步周期后的的无效行数。0x00
178      * LINEVAL  [23:14]  xres-1 ,TFT/STN:此位决定了 LCD 面板的垂直尺寸。  0000000000
179      * VFPD  [13:6]TFT: tvf-1, 垂直前沿为帧结束时,垂直同步周期前的的无效行数。00000000
180      * VSPW  [5:0]TFT:  tvp-1, 通过计算无效行数垂直同步脉冲宽度决定 VSYNC 脉冲的高电平宽度。000000
181      
182      * LCDCON2 =     (plcdparams->time_seq.tvb - 1<<24) |\
183                 (plcdparams->time_seq.tvf - 1<<6)  |\
184                 (plcdparams->time_seq.tvp - 1<<0)  |\
185                 (plcdparams->yres - 1<<14);
186      */
187     plcd_regs->lcdcon2 =     (1<<24) | (319<<14) |(8<<6) | (1<<0);                
188     /* LCDCON3位定义:
189      * HBPD(TFT)[25:19] TFT:thb-1, 水平后沿为 HSYNC 的下降沿与有效数据的开始之间的 VCLK 周期数。0000000
190      * HOZVAL     [18:8]  TFT/STN:yres-1, 此位决定了 LCD 面板的水平尺寸。必须决定 HOZVAL 来满足 1 行的总字节为 4n 字节。
191      *             如果单色模式中 LCD 的 x 尺寸为 120个点,但不能支持 x=120,因为 1 行是由 16 字节(2n)所组成。
192      *            LCD面板驱动器将舍弃额外的 8 个点。00000000000
193      * HFPD(TFT)[7:0]   TFT:thf-1, 水平后沿为有效数据的结束与 HSYNC 的上升沿之间的 VCLK 周期数。0X00
194     
195      * LCDCON3 =     (plcdparams->time_seq.thb - 1<<19) |\
196                   (plcdparams->xres- 1<<8)             |\
197                   (plcdparams->time_seq.thf - 1<<0);
198       */
199      plcd_regs->lcdcon3 =     (34<<19) | (239<<8) | (39<<0);
200     /*
201      * LCDCON4位定义:
202      * HSPW(TFT)[7:0]TFT:通过计算 VCLK 的数水平同步脉冲宽度决定 HSYNC 脉冲的高电平宽度  0X00
203      
204      * LCDCON4 = (plcdparams->time_seq.thp - 1<<0);
205      */
206     plcd_regs->lcdcon4 = (4<<0);
207     /*
208      * LCDCON5位定义:
209      * FRM565     [11]TFT:此位选择 16 bpp 输出视频数据的格式, 0 = 5:5:5:1 格式  1 = 5:6:5 格式;   0
210      * INVVCLK    [10]STN/TFT:此位控制 VCLK 有效沿的极性,  0 = VCLK 下降沿取视频数据  1 = VCLK 上升沿取视频数据; 0
211      * INVVLINE   [9]STN/TFT:此位表明 VLINE/HSYNC 脉冲极性, 0 = 正常  1 = 反转;    0,
212      * INVVFRAME  [8]STN/TFT:此位表明 VFRAME/VSYNC 脉冲极性, 0 = 正常  1 = 反转;    0,
213      * INVVD      [7]STN/TFT:此位表明 VD(视频数据)脉冲极性, 0 = 正常  1 = 反转 VD;    0
214      * INVVDEN    [6]TFT:此位表明 VDEN 信号极性, 0 = 正常  1 = 反转;    0
215      * INVPWREN   [5]STN/TFT:此位表明 PWREN 信号极性, 0 = 正常  1 = 反转;    0
216      * INVLEND    [4]TFT:此位表明 LEND 信号极性, 0 = 正常  1 = 反转;    0
217      * PWREN      [3]STN/TFT:LCD_PWREN 输出信号使能/禁止, 0 = 禁止 PWREN 信号  1 = 允许 PWREN 信号;    0
218      * ENLEND     [2]TFT:LEND 输出信号使能/禁止, 0 = 禁止 LEND 信号  1 = 允许 LEND 信号;    0
219      * 
220      * BPP24BL    [12]TFT:此位决定 24 bpp 视频存储器的顺序, 0 = LSB 有效  1 = MSB 有效;    0
221      * BSWP       [1]STN/TFT:字节交换控制位, 0 = 交换禁止  1 = 交换使能;    0
222      * HWSWP      [0]STN/TFT:半字节交换控制位, 0 = 交换禁止  1 = 交换使能,    0
223      *
224      * 存储器数据格式(TFT)
225      * pixels_data_format =     plcdparams->bpp == 32 ? (0<<0) : \
226                                 plcdparams->bpp == 16 ? (1<<0) : \
227                                 (1<<1);        //8bpp;
228        LCDCON5 =     (plcdparams->pin_pol.vclk<<10) |\
229                     (plcdparams->pin_pol.hsync<<9) |\
230                     (plcdparams->pin_pol.vsync<<8) |\
231                     (plcdparams->pin_pol.rgb<<7)   |\
232                     (plcdparams->pin_pol.de<<6)    |\
233                     (plcdparams->pin_pol.pwren<<5) |\
234                     (1<<11) | pixels_data_format;
235       */
236      plcd_regs->lcdcon5 = (1<<11) | (0<<10) |(1<<9) | (1<<8) | (0<<7) |\
237                           (0<<6)  |    (0<<5) |(0<<1) | (1<<0);
238 
239     /* 3.3 配置显存 */
240     s3c_lcdfb->screen_base = dma_alloc_writecombine(NULL, s3c_lcdfb->fix.smem_len, (dma_addr_t *)&s3c_lcdfb->fix.smem_start, GFP_KERNEL);    /* 屏幕开始的虚拟地址 */
241     /* sucess: return info; err: return NULL; */
242     if(!s3c_lcdfb->screen_base)
243     {
244         printk(KERN_ERR "Unable to alloc the framebuffer of s3c_lcdfb->screen_base!\n");
245         goto err_fail2;
246     }
247     /*
248      * LCDSADDR1位定义:
249         LCDBANK  [29:21]
250         这些位表明系统存储器中视频缓冲器的 bank 位置的 A[30:22]。即使当
251         移动视口时也不能改变 LCDBANK 的值。LCD 帧缓冲器应该在 4MB
252         连续区域内,以保证当移动视口时不会改变 LCDBANK 的值。因此应
253         该谨慎使用 malloc()函数。
254         0x00
255         LCDBASEU  [20:0]
256         对于双扫描 LCD:这些位表明递增地址计数器的开始地址的 A[21:1],
257         它是用于双扫描 LCD 的递增帧存储器或单扫描 LCD 的帧存储器。
258         对于单扫描 LCD:这些位表明 LCD 帧缓冲器的开始地址的 A[21:1]。
259         0x000000
260     
261         addr = plcdparams->fb_base<<1;
262         addr >>= 2;
263         LCDSADDR1 = addr;        //帧缓冲器的开始地址;
264      */
265     plcd_regs->lcdsaddr1 = ((s3c_lcdfb->fix.smem_start<<1)>>2);
266     /*
267     LCDSADDR2  位  描述  初始状态
268      LCDBASEL  [20:0]
269      对于单扫描 LCD:这些位表明 LCD 帧缓冲器的结束地址的 A[21:1]。
270      LCDBASEL = ((帧结束地址) >> 1) + 1
271      = LCDBASEU + (PAGEWIDTH+OFFSIZE) × (LINEVAL+1);
272      0x0000
273     
274     //addr = plcdparams->fb_base + plcdparams->xres*plcdparams->yres*plcdparams->bpp/8;
275     //LCDSADDR2 = (addr>>1) + 1;        //帧缓冲器的结束地址;
276 
277     addr = plcdparams->fb_base + plcdparams->xres*plcdparams->yres*plcdparams->bpp/8;
278     addr >>= 1;
279     //addr &= 0x1fffff;    //有没有都一样!
280     LCDSADDR2 = addr; 
281     */
282     plcd_regs->lcdsaddr2 = ((s3c_lcdfb->fix.smem_start + s3c_lcdfb->fix.smem_len)>>1) & 0x1fffff;
283     plcd_regs->lcdsaddr3 = 240*16/16;    /* 虚拟屏的页宽度(单位: 半字节) */
284     /* 3.4启动LCD */
285     /* GPB0: 输出高电平,给LCD提供背光灯LED+-
286      * GPBDAT |= (1<<1);
287      */
288     *gpbdat |= (1<<1);
289     /* LCDCON1位定义:
290      * ENVID [0] 使能LCD信号输出:  0 = 禁止视频输出和 LCD 控制信号  1 = 允许视频输出和 LCD 控制信号 
291      * LCDCON1 |= (1<<0);
292      */
293     plcd_regs->lcdcon1 |= (1<<0);
294     /* LCDCON5位定义:
295      * PWREN [3] STN/TFT:LCD_PWREN 输出信号使能/禁止, 0 = 禁止 PWREN 信号  1 = 允许 PWREN 信号;    0 
296      * LCDCON5 |= (1<<3);
297      */
298     plcd_regs->lcdcon5 |= (1<<3);
299 
300     /* 4.注册帧缓冲器 */
301     ret = register_framebuffer(s3c_lcdfb);
302     if(ret < 0)
303     {
304         printk(KERN_ERR "Unable to register the s3c_lcdfb framebuffer device!\n");
305         goto err_fail3;
306     }
307     
308     return 0;
309  err_fail3:
310     unregister_framebuffer(s3c_lcdfb);
311     *gpbdat &= ~(1<<1);
312     plcd_regs->lcdcon1 &= ~(1<<0);
313     plcd_regs->lcdcon5 &= ~(1<<3);
314  err_fail2:
315     dma_free_writecombine(NULL, s3c_lcdfb->fix.smem_len, s3c_lcdfb->screen_base, s3c_lcdfb->fix.smem_start);
316     iounmap(plcd_regs);
317     iounmap(gpbcon);
318     iounmap(gpccon);
319     iounmap(gpdcon);
320     iounmap(gpgcon);
321  err_fail1:
322     framebuffer_release(s3c_lcdfb);
323     return ret;
324 }
325 
326 static void lcd_exit(void)
327 {
328     /* 1.注销帧缓冲器 */
329     unregister_framebuffer(s3c_lcdfb);
330     /* 2.禁止LCD使能 */
331     /* GPB0: 输出高电平,给LCD提供背光灯LED+-
332      * GPBDAT &= ~(1<<1);
333      */
334     *gpbdat &= ~(1<<1);
335     /* LCDCON1位定义:
336      * ENVID [0] 使能LCD信号输出:  0 = 禁止视频输出和 LCD 控制信号  1 = 允许视频输出和 LCD 控制信号 
337      * LCDCON1 &= ~(1<<0);
338      */
339     plcd_regs->lcdcon1 &= ~(1<<0);
340     /* LCDCON5位定义:
341      * PWREN [3] STN/TFT:LCD_PWREN 输出信号使能/禁止, 0 = 禁止 PWREN 信号  1 = 允许 PWREN 信号;    0     
342      * LCDCON5 &= ~(1<<3);
343      */
344     plcd_regs->lcdcon5 &= ~(1<<3);
345 
346     /* 3.释放显存 */
347     dma_free_writecombine(NULL, s3c_lcdfb->fix.smem_len, s3c_lcdfb->screen_base, s3c_lcdfb->fix.smem_start);
348     /* 4.释放寄存器映射的内存 */
349     iounmap(plcd_regs);
350     iounmap(gpbcon);
351     iounmap(gpccon);
352     iounmap(gpdcon);
353     iounmap(gpgcon);
354     /* 5.收回分配给帧缓冲器的空间 */
355     framebuffer_release(s3c_lcdfb);
356 }
357 
358 module_init(lcd_init);
359 module_exit(lcd_exit);

Makefile

 1 ifneq ($(KERNELRELEASE),)
 2     obj-m := lcd_6.o
 3 else
 4     KERN_DIR ?= /home/book/workbook/mini2440/systems/linux-2.6.22.6
 5 
 6     PWD = $(shell pwd)
 7 all:
 8     $(MAKE) -C $(KERN_DIR) M=$(PWD) modules
 9 clean:
10     $(MAKE) -C $(KERN_DIR) M=$(PWD) modules clean
11     rm -rf modules.order
12 
13 endif

猜你喜欢

转载自www.cnblogs.com/xiaohujian/p/10158716.html