您的位置:首页 > 编程语言

LCD驱动之编写代码

2017-02-10 17:11 316 查看
我使用的开发板是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,

4000
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”“回车”之后,会显示目录下的内容。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: