我使用的开发板是mini2440,所以有些步骤可能不同。
首先先写出一个框架,包括入口函数、出口函数以及修饰。只写函数声明就可以了,代码稍后一步一步填上去。
函数的大部分功能实现都在入口init函数中,可以确定构造这个函数需要完成:
1. 分配一个fb_info结构体
2. 设置
3. 硬件相关的操作
4. 注册
首先是分配分配一个fb_info结构体
s3c_lcd = framebuffer_alloc(0, NULL);
第一个参数指的是分配的私有空间大小。所分配的空间并不一定是结构体的大小,而是指结构体的大小与私有空间大小之和。
接下来再说简单的,第四步注册很简单
register_framebuffer(s3c_lcd);
再然后是2.设置。
仔细看一下fb_info结构体,可以发现里面分了很多个结构体。接下来把对这一步的操作划分成一下几个部分:
2.1 设置固定的参数
2.2 设置可变的参数
2.3 设置操作函数
2.4 其他的设置
2.1固定的参数定义在fb_fix_screeninfo中,包括:
//名字
strcpy(s3c_lcd->fix.id, "mylcd");
// MINI2440的LCD位宽是24,但是2440里会分配4字节即32位(浪费1字节)
s3c_lcd->fix.smem_len = 320*240*32/8;
//类型,这里选择默认值
s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS;
//我们是TFT屏,所以选择真彩色
s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR;
//一行的长度大小,320位*4字节
s3c_lcd->fix.line_length = 320*4;
里面还有一个s3c_lcd->fix.smem_start 由于还没有分配显存,所以留到后面再设置。其他的可以不设置。
接下来是2.2可变参数。可变参数在fb_var_screeninfo中定义,包括:
s3c_lcd->var.xres = 320;//x方向的分辨率
s3c_lcd->var.yres = 240;//y方向的分辨率
s3c_lcd->var.xres_virtual = 320;//x方向的虚拟分辨率
s3c_lcd->var.yres_virtual = 240;//y方向的虚拟分辨率
s3c_lcd->var.bits_per_pixel = 32;//每个像素用32位
/* RGB:888 */
s3c_lcd->var.red.offset = 16;//偏移
s3c_lcd->var.red.length = 8;//长度
s3c_lcd->var.green.offset = 8;
s3c_lcd->var.green.length = 8;
s3c_lcd->var.blue.offset = 0;
s3c_lcd->var.blue.length = 8;
//立刻生效
s3c_lcd->var.activate = FB_ACTIVATE_NOW;
其他的可以不用改。
2.3设置操作函数
s3c_lcd->fbops = &s3c_lcdfb_ops;
自然还要加上一个file_operations结构,
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,
};
其中 cfb_fillrect,cfb_copyarea,cfb_imageblit,是所有LCD驱动程序公用的,所以一定不能少,到后面还要把这三个函数编成模块。s3c_lcdfb_setcolreg是设置假调色板,具体如何设置一会再说。
2.4接下来是其他的设置。
s3c_lcd->pseudo_palette = pseudo_palette;
//显存的大小
s3c_lcd->screen_size = 320*240*32/8;
调色板的作用是什么呢?如果framebuffer采用8bpp而不是32bpp,但LCD控制器硬件决定必须采用32位,这时候我们就可以采用调色板来达到8bpp到32bpp的转换了。调色板是显示控制器中的一块内存,它里面放的是24位的像素数据。如果framebuffer里面存放的是一个8位的索引值,通过这个索引值再到调色板中找到真正的32位的像素数据,再显示在LCD上。
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;
}
其中chan_to_field()的作用是分离出red、green、blue原色。
再往下是3.硬件的设置,可以分成一下几个步骤:
3.1 配置GPIO用于LCD
3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等
3.3 分配显存(framebuffer), 并把地址告诉LCD控制器
先说3.1配置GPIO用于LCD,看原理图可以看到,GPIOC管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND,GPIOD管脚用于VD[23:8],GPG4用作LCD_PWREN
gpccon = ioremap(0x56000020, 4);
gpdcon = ioremap(0x56000030, 4);
gpgcon = ioremap(0x56000060, 4);
*gpccon = 0xaaaaaaaa;
*gpdcon = 0xaaaaaaaa;
*gpgcon |= (3<<8);
mini2440的背光电路和lcd电源使能共用LCD_PWREN,其他的开发板可能还要设置背光引脚。
接下来是3.2 根据LCD手册设置LCD控制器,由于寄存器很多,所以我们可以把这些寄存器放到一个数组里面,然后一起remap()
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;
};
然后再
lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
接下来就需要打开2440手册,lcd手册了。在2440手册找到寄存器中各位的含义,然后根据lcd手册时序图算出应该的取值,再设置寄存器。
然后是3.3 分配显存。需要用到的函数是dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp),第一个参数是设备,选择NULL,第二个参数是长度,选择fix.smem_len,第三个参数是物理地址,也就是fix.smem_start,我们在固定参数中没有设置的物理地址就在这里被设置了。第四个参数是flag,选择GFP_KERNEL。这个函数的返回值是分配内存的虚拟地址,即s3c_lcd->screen_base。
如何把地址告诉LCD控制器呢,需要往lcdsaddr1,lcdsaddr2,lcdsaddr3里写入。lcdsaddr1中存放显存的起始地址(物理),lcdsaddr2中存放显存的结束地址,lcdsaddr3中定义了一行的长度,单位是2字节
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 = (320*32/16);
接下来就是使能LCD和LCD控制器
lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身: LCD_PWREN */
到此为止,LCD驱动程序基本上就写完了。
接下来是测试。
1.首先要去掉内核自带的驱动程序
make menuconfig
-> Device Drivers
-> Graphics support
S3C2410 LCD framebuffer support
2.make uImage
make modules
3.使用新的uImage启动开发板:
4.安装驱动
insmod cfbcopyarea.ko
insmod cfbfillrect.ko
insmod cfbimgblt.ko
insmod lcd.ko
5.开始测试
①输入echo hello > /dev/tty1 就可以在LCD上看见hello
修改 /etc/inittab,加上一句“tty1::askfirst:-/bin/sh“,然后重启开发板,依次装载
insmod cfbcopyarea.ko
insmod cfbfillrect.ko
insmod cfbimgblt.ko
insmod lcd.ko
insmod buttons.ko
这个时候,由于在tty1这个设备上也启动了一个shell,所以在用按键按下“l”“s”“回车”之后,会显示目录下的内容。