我使用的开发板是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.ko5.开始测试 
 ①输入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”“回车”之后,会显示目录下的内容。