mini2440简单的lcd显示驱动程序

这几天学习了一下lcd显示原理和Linux内核的framebuffer类型的设备驱动框架,值得学习的是内核中对驱动的分层概念,fbmem.c中实现了对framebuffer的共性的抽象,lcd驱动其实就是设置lcd控制器的工作方式,然后对显存进行读写,对显存的操作其实不管什么lcd,都是差不多的,所以这些相似的操作都抽取到了内核的fbmem.c里面,实现驱动的时候只需要填充fb_info结构体,把lcd硬件相关的参数填入该结构体中,然后用register_framebuffer函数进行注册,该函数会根据自动生成设备节点,就是/dev/fb0, /dev/fb1等等,对于显存的读写函数就用内核已经实现的默认的cfb_fillrect, cfb_copyarea, cfb_imageblit即可,其实不用自己去做实现,对于简单的lcd显示操作而言已经足够了。值得注意的是lcd控制芯片的信号极性和2440的lcd控制器的信号极性有可能是反相的,在设置lcd控制器工作模式的时候要注意,另外注意mini2440里面gpb1是控制lcd亮度的,要设置成pwm引脚,看清楚lcd控制芯片的时序图,搞清楚时序里面哪一段时间是多长,保险起见,全部用手册上面的typical的建议数值就行了,根据这些数值对LCDCONn中的字段进行设置,其他的就不多说了,需要注意的地方都在注释里面了。


驱动代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 

#include 
#include 
#include 
#include 

static struct fb_info *lcd_fb_info;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile unsigned long *gpgdat;
static volatile unsigned long *gpbcon;
static volatile unsigned long *gpbdat;


static struct lcd_regs_addr{
	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 volatile struct lcd_regs_addr *lcd_regs;
static u32 pseudo_palette[16]; //假的调色板



static inline unsigned int chan_to_field(unsigned int chan, const struct fb_bitfield *bf)
{
	chan &= 0xffff;
	chan >>= 16 - bf->length;
	return chan << bf->offset;
}

//设置调色板的函数
static int grh_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);
	val |= chan_to_field(green, &info->var.green);
	val |= chan_to_field(blue, &info->var.blue);
	pseudo_palette[regno] = val;
	return 0;
}

static struct fb_ops lcd_ops = {
	.owner = THIS_MODULE,
	.fb_setcolreg = grh_lcdfb_setcolreg,
	.fb_fillrect = cfb_fillrect,
	.fb_copyarea = cfb_copyarea,
	.fb_imageblit = cfb_imageblit,
};

static int lcd_init(void){
	//分配fb_info结构体实例
	lcd_fb_info = framebuffer_alloc(0, NULL);

	printk(KERN_EMERG"lcd init function 1!\n");

	//设置fb_info结构体的内容
	strcpy(lcd_fb_info->fix.id, "grh_lcd");
	lcd_fb_info->fix.smem_len = 320*240*16/8; //显存长度
	lcd_fb_info->fix.type = FB_TYPE_PACKED_PIXELS;
	lcd_fb_info->fix.visual = FB_VISUAL_TRUECOLOR; //设置真彩色
	lcd_fb_info->fix.line_length = 240*2; //一行的数据长度

	lcd_fb_info->var.xres = 240; //设置分辨率
	lcd_fb_info->var.yres = 320;
	lcd_fb_info->var.xres_virtual = 240;
	lcd_fb_info->var.yres_virtual = 320;
	lcd_fb_info->var.bits_per_pixel = 16; //每个像素16位

	//RGB 5:6:5
	lcd_fb_info->var.red.offset= 11; lcd_fb_info->var.red.length=5;
	lcd_fb_info->var.green.offset = 5; lcd_fb_info->var.green.length = 6;
	lcd_fb_info->var.blue.offset = 0; lcd_fb_info->var.green.length = 5;
	lcd_fb_info->var.accel_flags = FB_ACTIVATE_NOW;

	
	lcd_fb_info->fbops = &lcd_ops; //设置lcd相关的操作函数
	lcd_fb_info->screen_size = 240*320*2; //显存大小

	//设置lcd相关的引脚
	gpbcon = ioremap(0x56000010, 12);
	gpccon = ioremap(0x56000020, 12);
	gpdcon = ioremap(0x56000030, 12);
	gpgcon = ioremap(0x56000060, 12);
	gpgdat = gpgcon + 1;
	gpbdat = gpbcon + 1;

	*gpbcon &= ~(1<<2); //设置gpb1为PWM输出信号,控制背光亮度
	*gpbcon |= (1<<3);

	*gpccon = 0xaaaaaaaa;
	*gpdcon = 0xaaaaaaaa;

	*gpgcon |= (3<<8);
	*gpgdat &= ~(1<<4); /*关闭电源*/
	

	printk(KERN_EMERG"lcd init function 2!\n");

	//设置lcd controler的参数
	lcd_regs = ioremap(0x4d000000, sizeof(struct lcd_regs_addr));
	/*
	lcd时钟信号频率,TFT:VCLK = 101.25M / [(CLKVAL + 1) × 2] = 6.4M CLKVAL = 7
	bit[17:8] CLKVAL = 7
	bit[6:5] PNRMODE=0b11 (tft lcd)
	bit[4:1] BPPMODE=0b1100 (tft 16bpp)
	bit[0] ENDIV=0 lcd使能信号,一开始先关闭使能
	*/
	lcd_regs->lcdcon1 = (7<<8) | (3<<5) | (12<<1);
	/*
	水平信号参数
	bit[31:24] VBPD=1
	bit[23:14] LINEVAL=319
	bit[13:6] VFPD=1
	bit[5:0] VSPW=1
	*/
	lcd_regs->lcdcon2 = (1<<24) | (319<<14) | (1<<6) | (1<<0); 
	/*
	垂直信号参数
	bit[25:19] HBPD=19
	bit[18:8] HOZVAL=239
	bit[7:0] HFPD==9
	*/
	lcd_regs->lcdcon3 = (19<<19) | (239<<8) | (9<<0);
	/*
	垂直信号参数
	bit[7:0] HSPW=9
	*/
	lcd_regs->lcdcon4 = (9<<0);
	/*
	引脚的极性设置
	bit[11] FRM565=1
	bit[10] INVVCLK=1 上升沿取数据
	bit[9] INVVLINE=1 水平同步信号反相
	bit[8] INVVFRAME=1 垂直同步信号反相
	bit[7] INVVD=0 数据信号不需要反相
	bit[6] INVVDEN=0 数据使能信号不需要反相
	bit[5] INVPWREN=0 电源使能信号不需要反相
	bit[3] PWREN=0 先不使用电源使能信号
	bit[1:0] BSWP=0 HWSWP=1 设置字节排列的顺序
	*/
	lcd_regs->lcdcon5 = (1<<11) | (1<<10) | (1<<9) | (1<<8) | (0<<7) | (0<<6) | (0<<5) | (0<<3) | (0<<1) | (1<<0);


	printk(KERN_EMERG"lcd init function 3!\n");

	//分配显存,设置显存的虚拟地址和物理地址
	lcd_fb_info->screen_base = dma_alloc_writecombine(NULL, lcd_fb_info->fix.smem_len, &(lcd_fb_info->fix.smem_start), GFP_KERNEL);


	lcd_regs->lcdsaddr1 = (lcd_fb_info->fix.smem_start>>1) & ~(3<<30);
	lcd_regs->lcdsaddr2 = ((lcd_fb_info->fix.smem_start+lcd_fb_info->fix.smem_len)>>1) & 0x1fffff;
	lcd_regs->lcdsaddr3 = 240; //一行的长度,单位是半字


	//开启lcd
	lcd_regs->lcdcon1 |= (1<<0); //使能lcd信号
	lcd_regs->lcdcon5 |= (1<<3); //使能LCD_PWREN(GPG4)信号
	*gpgdat |= (1<<4); //开启LCD_PWREN信号
	

	printk(KERN_EMERG"lcd init function 4!\n");
	//注册fb_info结构体实例
	register_framebuffer(lcd_fb_info);
	printk(KERN_EMERG"lcd init function 5!\n");
	
	return 0;
}

static void lcd_exit(void){
	unregister_framebuffer(lcd_fb_info);
	//关闭lcd
	lcd_regs->lcdcon1 &= ~(1<<0);
	*gpgdat &= ~(1<<4); //关闭背光
	iounmap(gpccon);
	iounmap(gpdcon);
	iounmap(gpgcon);
	iounmap(gpbcon);
	dma_free_writecombine(NULL, lcd_fb_info->fix.smem_len, lcd_fb_info->screen_base, lcd_fb_info->fix.smem_start);
	framebuffer_release(lcd_fb_info);
	return;
}

module_init(lcd_init);
module_exit(lcd_exit);


MODULE_AUTHOR("GRH");
MODULE_VERSION("1.0");
MODULE_DESCRIPTION("LCD DRIVER");
MODULE_LICENSE("GPL");




用户层测试代码,就是把一个像素点矩阵直接写到/dev/fb1里面去(我没有改动内核,所以启动的时候已经有一个/dev/fb0了,我的驱动对应的设备就是/dev/fb1),lcd会显示红绿蓝三个颜色块。代码如下:

#include 
#include 
#include 
#include 

unsigned short img[320][240];

int main(void){
	int fd, i, j;
	printf("size of short:%d\n", sizeof(unsigned short));

	fd = open("/dev/fb1", O_RDWR);
	if(-1 == fd){
		printf("open device error!\n");
		return -1;
	}

	for(i=0; i<320; i++){
		for(j=0; j<240; j++){
			if(i<=100)
				img[i][j] = 0b1111100000000000;
			else if(i<=200)
				img[i][j] = 0b0000011111100000;
			else
				img[i][j] = 0b0000000000011111;
		}
	}
	write(fd, img[0], 320*248*sizeof(unsigned short));
	close(fd);

	return 0;
}

效果:

mini2440简单的lcd显示驱动程序_第1张图片



你可能感兴趣的:(mini2440)