硬件环境:tq2440 4.3 LCD
软件环境:Fedora17 arm-linux-gcc-4.3.2
内核版本:2.6.39
以韦东山老师视频为基础,加入自己的实践
1.基础知识(转载,原作者不详,感谢先):
|
|
|
|
三、帧缓冲(FrameBuffer)设备驱动结构:
1. 帧缓冲设备驱动在Linux子系统中的结构如下:
我们从上面这幅图看,帧缓冲设备在Linux中也可以看做是一个完整的子系统,大体由fbmem.c和xxxfb.c组成。向上给应用程序提供完善的设备文件操作接口(即对FrameBuffer设备进行read、write、ioctl等操作),接口在Linux提供的fbmem.c文件中实现;向下提供了硬件操作的接口,只是这些接口Linux并没有提供实现,因为这要根据具体的LCD控制器硬件进行设置,所以这就是我们要做的事情了(即xxxfb.c部分的实现)。
2. 帧缓冲相关的重要数据结构:
从帧缓冲设备驱动程序结构看,该驱动主要跟fb_info结构体有关,该结构体记录了帧缓冲设备的全部信息,包括设备的设置参数、状态以及对底层硬件操作的函数指针。在Linux中,每一个帧缓冲设备都必须对应一个fb_info,fb_info在/linux/fb.h中的定义如下:(只列出重要的一些)
|
其中,比较重要的成员有struct fb_var_screeninfo var、struct fb_fix_screeninfo fix和structfb_ops *fbops,他们也都是结构体。下面我们一个一个的来看。
fb_var_screeninfo结构体主要记录用户可以修改的控制器的参数,比如屏幕的分辨率和每个像素的比特数等,该结构体定义如下:
|
而fb_fix_screeninfo结构体又主要记录用户不可以修改的控制器的参数,比如屏幕缓冲区的物理地址和长度等,该结构体的定义如下:
|
fb_ops结构体是对底层硬件操作的函数指针,该结构体中定义了对硬件的操作有:(这里只列出了常用的操作)
|
3. 帧缓冲设备作为平台设备(本文代码中没有用到):
在S3C2440中,LCD控制器被集成在芯片的内部作为一个相对独立的单元,所以Linux把它看做是一个平台设备,故在内核代码/arch/arm/plat-s3c24xx/devs.c中定义有LCD相关的平台设备及资源,代码如下:
|
除此之外,Linux还在/arch/arm/mach-s3c2410/include/mach/fb.h中为LCD平台设备定义了一个s3c2410fb_mach_info结构体,该结构体主要是记录LCD的硬件参数信息(比如该结构体的s3c2410fb_display成员结构中就用于记录LCD的屏幕尺寸、屏幕信息、可变的屏幕参数、LCD配置寄存器等),这样在写驱动的时候就直接使用这个结构体。下面,我们来看一下内核是如果使用这个结构体的。在/arch/arm/mach-s3c2440/mach-smdk2440.c中定义有:
|
现在知道当初移植linux LCD驱动时,这些参数是怎么设置的了
注意:可能有很多朋友不知道上面红色部分的参数是做什么的,其值又是怎么设置的?其实它是跟你的开发板LCD控制器密切相关的,看了下面两幅图相信就大概知道他们是干什么用的:
上面第一幅图是开发板原理图的LCD控制器部分,第二幅图是S3c2440数据手册中IO端口C和IO端口D控制器部分。原理图中使用了GPC8-15和GPD0-15来用做LCD控制器VD0-VD23的数据端口,又分别使用GPC0、GPC1端口用做LCD控制器的LEND和VCLK信号,对于GPC2-7则是用做STN屏或者三星专业TFT屏的相关信号。然而,S3C2440的各个IO口并不是单一的功能,都是复用端口,要使用他们首先要对他们进行配置。所以上面红色部分的参数就是把GPC和GPD的部分端口配置成LCD控制功能模式。
从以上讲述的内容来看,要使LCD控制器支持其他的LCD屏,重要的是根据LCD的数据手册修改以上这些参数的值。下面,我们再看一下在驱动中是如果引用到s3c2410fb_mach_info结构体的(注意上面讲的是在内核中如何使用的)。在mach-smdk2440.c中有:
|
s3c24xx_fb_set_platdata定义在plat-s3c24xx/devs.c中:
|
这里再讲一个小知识:不知大家有没有留意,在平台设备驱动中,platform_data可以保存各自平台设备实例的数据,但这些数据的类型都是不同的,为什么都可以保存?这就要看看platform_data的定义,定义在/linux/device.h中,void *platform_data是一个void类型的指针,在Linux中void可保存任何数据类型。
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 <mach/regs-lcd.h>
- #include <mach/regs-gpio.h>
- #include <mach/fb.h>
- static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
- unsigned int green, unsigned int blue,
- unsigned int transp, struct fb_info *info);
- 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 struct fb_ops s3c_lcdfb_ops = {
- .owner = THIS_MODULE,
- .fb_setcolreg = s3c_lcdfb_setcolreg,
- .fb_fillrect = cfb_fillrect,
- .fb_copyarea = cfb_copyarea,
- .fb_imageblit = cfb_imageblit,/* linux内核自带的驱动,需要加载,后面使用的时候会讲到 */
- };
- static struct fb_info *s3c_lcd;
- static volatile unsigned long *gpccon;
- static volatile unsigned long *gpdcon;
- static volatile unsigned long *gpgcon;
- static volatile struct lcd_regs* lcd_regs;
- static u32 pseudo_palette[16];
- /* from pxafb.c */
- /* 这里使用的是linux自带的函数,直接copy的 */
- static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
- {
- chan &= 0xffff;
- chan >>= 16 - bf->length;
- return chan << bf->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;
- /* 用red,green,blue三原色构造出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;
- pseudo_palette[regno] = val;
- return 0;
- }
- static int lcd_init(void)
- {
- /* 1. 分配一个fb_info */
- s3c_lcd = framebuffer_alloc(0, NULL);
- /* 2. 设置 */
- /* 2.1 设置固定的参数 */
- strcpy(s3c_lcd->fix.id, "tq2440_480_272_lcd");
- s3c_lcd->fix.smem_len = 480*272*16/8;
- s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS;
- s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR; /* TFT */
- s3c_lcd->fix.line_length = 480*2;
- /* 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;
- /* RGB:565 */
- 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_lcdfb_ops;
- /* 2.4 其他的设置 */
- s3c_lcd->pseudo_palette = pseudo_palette;
- /*s3c_lcd->screen_base = ; 后面再设置显存的虚拟地址 */
- s3c_lcd->screen_size = 480*272*16/8;
- /* 3. 硬件相关的操作 */
- /* 3.1 配置GPIO用于LCD */
- gpccon = ioremap(0x56000020, 4);
- gpdcon = ioremap(0x56000030, 4);
- gpgcon = ioremap(0x56000060, 4);
- *gpccon = 0xaaaaaaaa; /* GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
- *gpdcon = 0xaaaaaaaa; /* GPIO管脚用于VD[23:8] */
- *gpgcon |= (3<<8); /* GPG4用作LCD_PWREN */
- /* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
- lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
- /* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2], LCD手册
- * 10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2]
- * CLKVAL = 4
- * bit[6:5]: 0b11, TFT LCD
- * bit[4:1]: 0b1100, 16 bpp for TFT
- * bit[0] : 0 = Disable the video output and the LCD control signal.
- */
- lcd_regs->lcdcon1 = (4<<8) | (3<<5) | (0x0c<<1);
- /* 垂直方向的时间参数
- * bit[31:24]: VBPD, VSYNC之后再过多长时间才能发出第1行数据
- * LCD手册 VBPD=2
- * bit[23:14]: 多少行, 272, 所以LINEVAL=272-1=271
- * bit[13:6] : VFPD, 发出最后一行数据之后,再过多长时间才发出VSYNC
- * LCD手册 VFPD=2
- * bit[5:0] : VSPW, VSYNC信号的脉冲宽度, LCD手册T1=1, 所以VSPW=1-1=0
- */
- lcd_regs->lcdcon2 = (2<<24) | (271<<14) | (2<<6) | (10<<0);
- /* 水平方向的时间参数
- * bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据
- * LCD手册 HBPD=2
- * bit[18:8]: 多少列, 480, 所以HOZVAL=480-1=479
- * bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC
- * LCD手册 HFPD=2
- */
- lcd_regs->lcdcon3 = (2<<19) | (479<<8) | (2);
- /* 水平方向的同步信号
- * bit[7:0] : HSPW, HSYNC信号的脉冲宽度, LCD手册 HSPW=41
- */
- lcd_regs->lcdcon4 = (41<<0);
- /* 信号的极性
- * 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手册P413
- */
- lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (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->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
- lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
- /* 4. 注册 */
- register_framebuffer(s3c_lcd);
- return 0;
- }
- static void lcd_exit(void)
- {
- unregister_framebuffer(s3c_lcd);
- lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
- 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);
- framebuffer_release(s3c_lcd);
- }
- module_init(lcd_init);
- module_exit(lcd_exit);
- MODULE_LICENSE("GPL");
KERN_DIR = /home/stevenking/workspace/code/linux-2.6.39
#红色部分修改为自己的内核路径
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += sk_lcd.o
1.首先先从原先内核中make menuconfig去掉原来的驱动程序,不然会有冲突
-> Device Drivers
-> Graphics support
<M> S3C2410 LCD framebuffer support
2.编译内核,下载新的内核
make zImage
make modules
3.重建根文件系统
将linux文件夹下/driver/video/下面的
cfbimgblt.ko
cfbcopyarea.ko
以及新编译的
放入/root/lib/文件夹下
重新 mkyaffs2image root root.bin,下载新的文件系统
4.启动之后:
cd /lib
insmod cfbcopyarea.ko
insmod cfbfillrect.ko
insmod cfbimgblt.ko
insmod sk_lcd.ko
echo hello > /dev/tty1
cat /proc/devices > /dev/tty1
就能在LCD上看到输出的文字了
tristate "TQ2440 LCD framebuffer support by Steven King"
depends on FB && ARCH_S3C2410
select FB_CFB_FILLRECT
select FB_CFB_COPYAREA
select FB_CFB_IMAGEBLIT
---help---
LCD driver by steven king 8/18/2013.
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>