Linux设备驱动——LCD驱动程序

和输入子系统一样LCD内核也有写好的框架,在fbmem.c中

fbmem.c中做了字符设备驱动都会做的事情

1.分配设置注册file_opration结构体

2.构建好了open,read,write,等函数

但是这些所以的函数都会调用到一个fb_info结构体中的数据,这个结构体中是LCD硬件相关的信息,需要其他文件来设置这个结构体,这样层次就出来了,fbmem.c中代码是稳定的通用的,也就是软件相关层,而不同的LCD通过不同的文件来设置fb_info结构体,这是硬件相关层

软件层和硬件层是怎样联系起来的呢??硬件层构造好fb_info结构体后会调用函数register_framebuffer()注册fb_info结构体,register_framebuffer函数会把fb_info结构体放到一个数组里,然后再以这个数组的下标为次设备号创建设备。软件层会在这个数组中根据打开设备文件的次设备号找到对应的fb_info结构体,这样联系就建立起来了


现在来写一个s3c_lcd.c

1.分配一个fb_info结构体

2.设置fb_info结构体

3.硬件相关操作

4.分配显存

5.注册


先来看看第一步,分配fb_info结构体的函数在fbmem.c中有写,调用它

	s3c_lcd_info = framebuffer_alloc(0,NULL);

第二步,fb_info结构体中有很多项,不用都设置的,只设置其中一部分就行,需要设置的有

</pre><pre name="code" class="cpp">struct fb_info {
	struct fb_var_screeninfo var;	/* Current var */
	struct fb_fix_screeninfo fix;	/* Current fix */

	struct fb_ops *fbops;
	
	char __iomem *screen_base;	/* Virtual address */
	unsigned long screen_size;	/* Amount of ioremapped VRAM or 0 */ 
	
	void *pseudo_palette;		/* Fake palette of 16 colors */ 
};



其中,var和fix是液晶屏的参数,

screen_base是显存的虚拟地址

screen_size是显存大小

fbops是关于对显存的一些操作函数,可以参考根据其他文件来写

pseudo_palette是假调色板,大概是需要兼容以前的东西,所以要有一个假调色板,这里的代码也可以参考其他文件来写

看一下操作函数

	s3c_lcd_info->fbops 				= &s3c_lcd_fb_ops;
static struct fb_ops s3c_lcd_fb_ops={
	
	.fb_setcolreg	= s3c_lcdfb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
};

一般来说操作函数只需要设置这四个就行了,第一个是关于假调色板,后面三个看着名字眼熟,原来装载LCD驱动程序之前需要先装载三个模块的名字和这个差不多,猜想通过这里驱动会调用到那三个模块里的内容来操作显存。写到这里想到,每次装载LCD驱动程序都要先装载那三个模块,能不能把那三个模块融合到lcd.ko中呢?修改Makefile应该能做到吧,


第三步,显存因为要物理地址连续,所以有专门的分配显存的函数,看以看看别人用什么函数分配显存的

	s3c_lcd_info->screen_base = dma_alloc_writecombine(NULL, s3c_lcd_info->fix.smem_len, &s3c_lcd_info->fix.smem_start, GFP_KERNEL);

第四步,硬件相关的操作分为两部分

1.设置相关引脚用与LCD,这个简单,把相关的引脚iomap然后设只寄存器用于LCD就行了

2.设置LCD控制寄存器,LCD控制寄存器有很多,但也不需要全部设置,只设置下面几个寄存器就行了

lcdcon1,主要是关于LCD的一些参数的设置,要看LCD手册

lcdcon2,时间参数设置,要对照LCD手册和2440手册计算出需要设置的数值

lcdcon3,时间参数设置

lcdcon4,时间参数设置

lcdcon5,关于信号反转的设置,LCD手册上的信号和2440手册上的信号有可能是相反的,这时要设置信号反转


lcdsaddr1

lcdsaddr2

lcdsaddr3,这三个寄存器是关于显存的一些设置


第五步,注册fb_info结构体,把fb_info结构体告诉fbmen.c,这样就能用起来了


需要注意的地方,

LCD的横纵坐标,一开始我把x分辨率写成320,把y分辨率写成240,结果刚好写反了

一开始把lcdcon1的某一位该设成16bpp的设成24bpp了,结果屏幕分成四个同样的小块,找了好半天才找出毛病


测试

echo hello > /dev/tty1      



#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/clk.h>

#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/div64.h>

#include <asm/mach/map.h>
#include <asm/arch/regs-lcd.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/fb.h>

static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
			     unsigned int green, unsigned int blue,
			     unsigned int transp, struct fb_info *info);


static volatile unsigned long *gpbcon;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile unsigned long *gpbdat;
static volatile struct lcd_regs * lcd_con;
static u32 pseudo_palette[16];
static struct fb_info * s3c_lcd_info;


				 
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 struct fb_ops s3c_lcd_fb_ops={
	.owner		= THIS_MODULE,
	
	.fb_setcolreg	= s3c_lcdfb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
};


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;

	/* 用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;
}





static int lcd_init(void)
{
	/*1.分配一个fb_info结构体*/
	
	s3c_lcd_info = framebuffer_alloc(0,NULL);
	
	
	
	
	/*2.设置*/
	
	strcpy(s3c_lcd_info->fix.id,"lcd");
	
	s3c_lcd_info->fix.smem_len 	= 240*320*16/8;
	s3c_lcd_info->fix.type 		= FB_TYPE_PACKED_PIXELS;
	s3c_lcd_info->fix.visual 		= FB_VISUAL_TRUECOLOR;
	s3c_lcd_info->fix.line_length 	= 240*16/8;

	s3c_lcd_info->var.xres 			= 240;
	s3c_lcd_info->var.yres 			= 320;
	s3c_lcd_info->var.xres_virtual 		= 240;
	s3c_lcd_info->var.yres_virtual 		= 320;
	s3c_lcd_info->var.bits_per_pixel 	= 16;

	s3c_lcd_info->var.red.length		=5;
	s3c_lcd_info->var.red.offset		=11;
	
	s3c_lcd_info->var.green.length		=6;
	s3c_lcd_info->var.green.offset		=5;
	
	s3c_lcd_info->var.blue.length		=5;
	s3c_lcd_info->var.blue.offset		=0;

	s3c_lcd_info->var.activate			= FB_ACTIVATE_NOW;

	s3c_lcd_info->fbops 				= &s3c_lcd_fb_ops;
	
	
	s3c_lcd_info->pseudo_palette = pseudo_palette;
	//s3c_lcd->screen_base  = ;  /* 显存的虚拟地址 */ 
	s3c_lcd_info->screen_size   = 240*324*16/8;

	/*3.硬件相关操作*/
	gpbcon = ioremap(0x56000010,8);
	gpbdat = gpbcon + 1;
	*gpbcon &= ~(3<<0);	
	*gpbcon |= (1<<0);
	*gpbdat &= ~1;     /* 输出低电平 */
	
	gpccon = ioremap(0x56000020,4);
	*gpccon = 0xaaaaaaaa;
	
	gpdcon = ioremap(0x56000030,4);
	*gpdcon = 0xaaaaaaaa;
	
	gpgcon = ioremap(0x56000060,4);
	*gpgcon |= (3<<8);

	lcd_con =  ioremap(0X4D000000,sizeof(struct lcd_regs));

	/*
	*[27:18]   0
	*[17:8]     4
	*             VCLK = HCLK / [(CLKVAL+1) x 2]     
	*		VCLK=10MHz   HCLK=100MHz
	*		10 = 100 / [(CLKVAL+1) x 2]
	*		CLKVAL=4
	   [6:5]    11 
	   [4:1]    1100
	   [0]       0
	*/
	lcd_con->lcdcon1 = (4<<8)|(3<<5)|(12<<1);
	/*
		VBPD  	[31:24]   3
		LINEVAL 	[23:14]  	319
		VFPD	[13:6] 	1
		VSPW	[5:0]  	0
	*/
	lcd_con->lcdcon2 = (1<<6)|(319<<14)|(3<<24)| (0<<0);
	/*
		HBPD	[25:19]   16
		HOZVAL 	[18:8] 	239
		HFPD	[7:0]	10
	*/

	lcd_con->lcdcon3 = (16<<19)|(239<<8)|(10<<0);
	/*
		HSPW 	[7:0] 	4
	*/

	lcd_con->lcdcon4 = 4;
	
	/* 信号的极性 
	 * bit[11]: 1=565 format
	 * bit[10]: 0 = The video data is fetched at VCLK falling edge
	 * bit[9] : 1 = HSYNC信号要反转,即低电平有效 
	 * bit[8] : 1 = VSYNC信号要反转,即低电平有效 
	 * bit[6] : 0 = VDEN不用反转
	 * bit[3] : 0 = PWREN输出0
	 * bit[1] : 0 = BSWP
	 * bit[0] : 1 = HWSWP 2440手册P413
	 */
	lcd_con->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);

	/*4.分配显存*/
	s3c_lcd_info->screen_base = dma_alloc_writecombine(NULL, s3c_lcd_info->fix.smem_len, &s3c_lcd_info->fix.smem_start, GFP_KERNEL);
	
	lcd_con->lcdsaddr1  = (s3c_lcd_info->fix.smem_start >> 1) & ~(3<<30);
	lcd_con->lcdsaddr2  = ((s3c_lcd_info->fix.smem_start + s3c_lcd_info->fix.smem_len) >> 1) & 0x1fffff;
	lcd_con->lcdsaddr3  = (240*16/16);  /* 一行的长度(单位: 2字节) */	
	
	//s3c_lcd->fix.smem_start = xxx;  /* 显存的物理地址 */
	/* 启动LCD */
	lcd_con->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
	lcd_con->lcdcon5 |= (1<<3); /* 使能LCD本身 */
	*gpbdat |= 1;     /* 输出高电平, 使能背光 */		
	
	/*5.注册*/
	register_framebuffer(s3c_lcd_info);
	
	return 0;
}

static void lcd_exit(void)
{
	unregister_framebuffer(s3c_lcd_info);
	lcd_con->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
	*gpbdat &= ~1;     /* 关闭背光 */
	dma_free_writecombine(NULL, s3c_lcd_info->fix.smem_len, s3c_lcd_info->screen_base, s3c_lcd_info->fix.smem_start);
	iounmap(lcd_con);
	iounmap(gpbcon);
	iounmap(gpccon);
	iounmap(gpdcon);
	iounmap(gpgcon);
	framebuffer_release(s3c_lcd_info);

}

module_init(lcd_init);
module_exit(lcd_exit);

MODULE_LICENSE("GPL");



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值