开发环境
主 机: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
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
$ 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配置为
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 #include6 #include 7 #include 8 #include string.h> 9 #include 10 #include 11 #include 12 #include 13 #include 14 #include 15 #include 16 #include 17 #include 18 #include 19 #include 20 21 #include 22 #include 23 #include 24 25 #include 26 #include 27 #include 28 #include 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< 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