在上一节中,分析了内核中的LCD的驱动框架,不要被内核中复杂的调用过程把我们搞迷糊了,总结起来就是以下几点
1、分配fb_info结构体
2、设置fb_info结构体
3、注册fb_info结构体
是不是很简单,软件层面的fbmem.c不需要我们去管,当然,如果你想要去深入分析的话,剩下的硬件层面的代码就做了那么三件事情,这一节,来编写具体的内核LCD驱动,遵循上面的编程流程
先来分配一个结构体,然后去设置它,在设置的时候要注意,fb_info结构体中有一个成员是固定的,就是表示这个设备的一些常用的参数,比如ID、name、类型等,还有一些是可变的,就是每一个不同类型的设备都可能不一样的
变量
static struct fb_info *s3c_lcd;
static volatile unsigned long *gpbcon;
static volatile unsigned long *gpbdat;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpcdat;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static u32 pseudo_palette[16]; //调色板
typedef 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 int lcd_init(void)
{
/* 1、分配一个fb_info结构体 */
s3c_lcd = framebuffer_alloc(0,NULL);
/* 2、设置 */
/* 设置固定的参数fix */
strcpy(s3c_lcd->fix.id,"mylcd"); //设备名
s3c_lcd->fix.smem_len = 240*320*16/8; //frame buffer的长度,每一个像素是16bit 2个字节,总共240*320个像素
s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS; //类型,LCD设备
s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR; //真彩色
s3c_lcd->fix.line_length = 240*2; //一行数据的长度(Byte),一行240个像素点
/* 设置可变的参数 */
s3c_lcd->var.xres = 240; //x方向的像素
s3c_lcd->var.yres = 320; //y方向的像素
s3c_lcd->var.xres_virtual = 240; //虚拟的分辨率,这里设置为和真实的一样
s3c_lcd->var.yres_virtual = 320;
s3c_lcd->var.bits_per_pixel = 16; //5 6 5 格式
/* RGB 565 */
s3c_lcd->var.red.offset = 11; //red的偏移
s3c_lcd->var.red.length = 5; //red的数据长度
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;
/* 设置操作函数 */
s3c_lcd->fbops = &s3c_lcdfb_ops;
/* 其他设置 */
s3c_lcd->pseudo_palette = pseudo_palette; //调色板 (必须有)
s3c_lcd->screen_size = 240*320*2; //屏幕的大小
/* 3、硬件相关操作 不再多说,和裸机编程中的是一样的*/
/* GPIO */
gpbcon = ioremap(0x56000010, 8);
gpbdat = gpbcon + 1;
gpccon = ioremap(0x56000020, 4);
gpdcon = ioremap(0x56000030, 4);
gpgcon = ioremap(0x56000060, 4);
*gpccon = 0xaaaaaaaa;
*gpdcon = 0xaaaaaaaa;
*gpbcon &= ~(3); //gpb0 输出引脚
*gpbcon |= 1;
*gpbdat &= ~1;
*gpgcon |= (3 << 8); //GPG4 POWER ENABLE
/* LCD控制器 */
lcd_regs = ioremap(0X4D000000, sizeof(struct lcd_regs));
/*
17:8 CLKVAL VCLK = HCLK / [(CLKVAL+1) x 2]
VCLK = 100M / [(CLKVAL+1) * 2]
VCLK = 100ns = 10M (收据手册P14)
CLKVAL = 10 / 2 - 1 = 4
[6:5] 11 = TFT LCD panel
[4:1] 1100 = 16 bpp for TFT
[0] 0 = Disable 1 = Enable
*/
lcd_regs->lcdcon1 = (4 << 8) | (3 << 5) | (0xc << 1);
/*
[31:24] VBPD VBPD+1:VSYNC信号有效之后过多久,第一行数据有效,T0-T2-T1 =4,VBPD = 3
[23:14] LINEVAL LINEVAL+1是一行有效数据,T5 LINEVAL = T5 - 1 = 319
[13:6] VFPD VFPD+1:一帧数据显示完之后再过多久发出垂直同步信号 T2-T5=2,VFPD = 1
[5:0] VSPW VSPW+1:垂直同步信号持续的时间,VSPW = T1 - 1 = 0
*/
lcd_regs->lcdcon2 = (3 << 24) | (319 << 14) | (1 << 6) | (0 << 0);
/*
[25:19] HBPD + 1:水平同步信号有效之后再过多久第一个像素有效 T6-T8-T7=17 HBPD=16
[18:8] HOZVAL + 1:水平数据持续的时间 HOZVAL = T11 - 1 = 240 -1 = 239
[7:0] HFPD + 1:一行数据显示完之后再过多久发出水平同步信号,T8-T11=251-240=11
HFPD = 10
*/
lcd_regs->lcdcon3 = (16 << 19) | (239 << 8) | (10 << 0);
/*
[7:0] HSPW + 1:水平同步信号持续的时间 HSPW = T7 -1 = 4
*/
lcd_regs->lcdcon4 = (4 << 0);
/*
[11] 1 = 5:6:5 Format
[10] 0 在VCLK下降沿采集数据 1上升沿,使用下降沿
[9] HSYNC信号极性 1反转,需要反转
[8] VSYNC信号极性 1反转,需要反转
[7] VD信号极性,1反转,不需要反转
[6] VDEN信号极性 1反转,不需要反转
[5] PWREN信号极性 1反转
[4] LEND
[3] LCD_PWREN output signal enable/disable 1 = Enable
[2] LEND output signal enable/disable 1 = Enable
[1] BSWP
[0] HWSWP 这两位是数据是否交换,我们不交换
*/
lcd_regs->lcdcon5 = (1 << 11) | (0 << 10) | (1 << 9) | (1 << 8) | (1 << 0);
/* 显存 返回分配的地址空间的虚拟地址 */
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 = 240;
/* 启动LCD */
lcd_regs->lcdcon1 |= 1; //lcd本身
lcd_regs->lcdcon5 |= (1 << 3);
*gpbdat |= 1; //背光
/* 4、注册 */
register_framebuffer(s3c_lcd);
}
static void lcd_exit(void)
{
unregister_framebuffer(s3c_lcd);
lcd_regs->lcdcon1 &= ~1; //lcd本身
*gpbdat &= ~1; //背光
dma_free_writecombine(NULL,s3c_lcd->fix.smem_len, s3c_lcd->screen_base,s3c_lcd->fix.smem_start);
iounmap(lcd_regs);
iounmap(gpbcon);
iounmap(gpbcon);
iounmap(gpdcon);
iounmap(gpgcon);
framebuffer_release(s3c_lcd);
}
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");
上面对LCD控制器的基本设置就已经完成了,LCD的时序部分请看数据手册**“液晶屏”**,具体的对比如下
fb_info的设置中有一项函数的设置
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,
};
这里这个调色板的函数必须设置,简单介绍一下调色板的概念
framebuffer(显存)就是在内存中划分的一块内存空间,里边存放的是LCD的显示数据,LCD控制器获取显存中的数据然后通过RGB数据线把数据发送给LCD设备进行显示,我们这款LCD的分辨率是240320,每一个像素的格式是565,那么显存的大小就是24032016/8个字节,引入调色板的概念就是为了减少显存所占内存空间的大小,每一个像素分配一个字节,那么显存所占的总大小就是240320*1个字节,那么,每一个像素在显存中的这个字节并不是真实的数据,而是作为索引,从调色板中获取真实的565格式的数据发给LCD控制器。
在这里的设置的调色板是一个假的调色板,不是很重要
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;
val = chan_to_field(red, &info->var.red); //把RGB合并为一个565的数据,根据每一个颜色的位起始和长度
val |= chan_to_field(green, &info->var.green);
val |= chan_to_field(blue, &info->var.blue);
pseudo_palette[regno] = val; //放入调色板
return 0;
}
结构体中的其他三个函数没有设置,因为这三个函数是其他文件里的函数,等会要加载的
程序已经编写完了,接下来就来测试。
测试
make menuconfig 去掉原来的驱动程序
make uImage
make modules 编译模块,包括LCD所依赖的模块
使用新内核启动
挂载网络文件系统 已经制作好了一个根文件系统备用 /work/nfs_root/fs_mini_mdev_my
mv arch/arm/boot/uImage /work/nfs_root/fs_mini_mdev_my/uImage_nolcd
mv drivers/video/cfg*.ko /work/nfs_root/fs_mini_mdev_my
mount -t nfs -o intr,nolock,rsize=1024,wsize=1024 192.168.1.101:/work/nfs_root/fs_mini_mdev_my /mnt
启动之后
insmod cfbcopyarea.ko
insmod cfbfillrect.ko
insmod cfbimgblt.ko
insmod lcd.ko
cat lcd.ko > /dev/fb0 出现花屏
echo hello > /dev/tty1 出现hello
问题
insmod lcd.ko出现段错误,根据网上的解决方案,配置内核去掉FRAMEBUFFER_CONSOLE,然后装载lcd.ko,确实不再出现段错误,但是,LCD屏幕显示不了hello,试过多次,无果,
于是尝试另一种方法,把lcd.c编译进内核中,原来内核中使用的LCD的驱动文件是drivers/video/s3c2410fb.c,
在drivers/video/Makefile中去掉s3c2410ffb.c的编译,添加lcd.c的编译条件
在同目录下的Kconfig中添加配置
选择编译,这个时候要记得选择配置内核LCD支持,因为驱动文件以及被换成了我们自己的,不用再以模块的形式加载,
同时选择上面去掉的FRAMEBUFFER_CONSOLE,编译
使用新内核,启动,即可看到效果