LCD驱动

fbmem.c是内核自带的LCD驱动程序,它是抽象出来的,依赖于底层提供的fb_info结构体

fbmem_init(void){    
	create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);
	if (register_chrdev(FB_MAJOR,"fb",&fb_fops))		
		printk("unable to get major %d for fb devs\n", FB_MAJOR);
	fb_class = class_create(THIS_MODULE, "graphics");
	if (IS_ERR(fb_class)) {
		printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
		fb_class = NULL;
	}
	return 0;
}

因为“fbmem.c”是通用的文件,故并不能直接使用这个 file_operations 结构中的.read 等函数。这里 fbmem.c 没有在设备类下创建设备,只有真正有硬件设备时才有必要在这个类下去创建设备。

app: open("/dev/fb0", …)
info = registered_fb[fbidx]得到这个设备节点的次设备号,从这个数组里面registered_fb[0]得到以次设备号为下标的一项,如果它有open函数就调用它,没有返回。

kernel:
fb_open
	int fbidx = iminor(inode);	//得到次设备号
		struct fb_info *info = = registered_fb[0];    

app: read("/dev/fb0", …)
假设app想读,得到次设备号,以次设备号为下标在数组registered_fb[fbidx]里得到一项,如果这个数组项里提供了fbops,就调用它的读函数。如果没有从screen_base读,src等于显存的基地址加偏移值,从里面读源(显存基地址screen_base)到数据放到目的(buffer)里。

kernel:
fb_read
	int fbidx = iminor(inode);
	struct fb_info *info = registered_fb[fbidx];	//以次设备号为下标从 registered_fd 数组中得一项赋给"info"结构。
		if (info->fbops->fb_read)
			return info->fbops->fb_read(info, buf, count, ppos);
		src = (u32 __iomem *) (info->screen_base + p);	//screen_base 是指显存的基地址。这里是读源 src 等于显存的基地址加上某个偏移值。
		dst = buffer;	
		*dst++ = fb_readl(src++);	//读源(从显存基地址+P偏移)那里读到一个数据放到目标“*dst++”里。dst是buffer,buffer是kmalloc()上面分配的空间。
		copy_to_user(buf, buffer, c)	//把数据拷贝到用户空间    	

open和read都依赖于fb_info结构体(fb是framebuffe帧缓冲区)结构体),这个结构体从registered_fb[fbidx]数组里得到的,以次设备号为下标得到一项。

问1. registered_fb数组在哪里被设置?
答1. register_framebuffer
把fb_info结构体先找到某个空项,找到空项后device_create,在类下面创建设备,之前只创建了类,真正有硬件设备才去创建设备节点。fbmem.c 提供的都是抽象出来的东西。最终都得依赖这个“registered_fb”数组里的“fb_info”结构体。

register_framebuffer(struct fb_info *fb_info){	
	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])	//先找出一个空项
			break;
	fb_info->dev = device_create(fb_class, fb_info->device,MKDEV(FB_MAJOR, i), "fb%d", i);    
}

在这里插入图片描述
怎么写LCD驱动程序?
1 分配fb_info结构体: framebuffer_alloc
2 设置fb_info结构体
3 注册fb_info结构体: register_framebuffer
4 硬件相关的操作

1 分配

framebuffer_alloc(size_t size, struct device *dev)

2 设置
fb_info结构体

struct fb_info {
	struct fb_var_screeninfo var;	    // 可变参数 
	struct fb_fix_screeninfo fix;	    // 固定参数  
	struct fb_ops *fbops;				// 操作函数
	void *pseudo_palette;	//Fake palette of 16 colors  假调色板};
固定参数
struct fb_fix_screeninfo {
	char id[16];				//名字     
	unsigned long smem_start;	//显存的物理起始地址,分配内存时再设置
	__u32 smem_len;	 			//显存长度(看液晶屏)240*320*16 
	__u32 type;					// see FB_TYPE_* 宏		默认值	 
	__u32 visual;				// see FB_VISUAL_*  TFT  真彩色
	__u32 line_length;			// 一行的长度240*16/8   
};
可变参数
struct fb_var_screeninfo {
	__u32 xres;					// x方向分辨率240     
	__u32 yres;     			// y方向分辨率320
	__u32 xres_virtual;  		// 虚拟分辨率简单设为和硬件相同  
	__u32 yres_virtual;
	__u32 bits_per_pixel;		// 每个像素位数16(2440只支持16浪费2位)
	struct fb_bitfield red;		// 红  5
	struct fb_bitfield green;	// 绿  6
	struct fb_bitfield blue;	// 蓝  5									  
	struct fb_bitfield transp;	// 透明度  0};
RGB 565
struct fb_bitfield {
	__u32 offset;		// 开始位   	
	__u32 length;		// 长度			
	__u32 msb_right;	// 最重要位在右边,我们最重要位在左边不设置*/ 
};
显存操作函数
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,
};
其它设置
s3c_lcd->pseudo_palette = pseudo_palette;	//假调色板
s3c_lcd->screen_base  = ;        			//显存的虚拟地址
s3c_lcd->screen_size  = 240*324*16/8;    	//显存大小

调色板是块内存,像数组一样,里面存放有真正的16位数据颜色。LCD以内存中8位数据为索引,在调色板中取得16位真正颜色的数据。

3 硬件操作
信号:
VSYNC:垂直同步信号
HSYNC:水平同步信号
HCLK:像素时钟信号,100MHz->10MHz
VD[0:23]:数据信号
PWREN:电源开关信号

3.1 配置GPIO用于LCD
3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等
LCDCON1像素时钟VCLK(100ns,10MHz),LCD类型TFT,BPP 16,使能LCD信号输出
LCDCON2:垂直方向的时间参数同步信号:VBPD上等待时间,LINEVAL垂直宽度,VFPD下无效时间,VSPW脉冲宽度
LCDCON3:水平方向的时间参数:HBPD左等待时间,HOZVAL垂直宽度,HFPD右无效时间
LCDCON4:水平方向的同步信号:HSPW信号脉冲宽度
LCDCON5数据格式 565,引脚信号的极性,字节交换使能(16bpp:高低字节2143,1234)
帧内存地址寄存器
帧内存与视口,帧内存很大,真正要显示的区域为视口,它处于帧内存之中,这3个寄存器用于确定帧内存的起始地址,定位视口在帧内存中的位置。
LCDADDR1:bit29:21帧内存起始地址A30:22,4M对齐。bit20:0保存视口所对应内存的起始地址,A21:1这块内存称为帧缓冲区
LCDADDR2:帧缓冲区结束地址A21:1
LCDADDR3:上一行最后和下一行开始地址差值,视口宽度
3.3 分配显存(framebuffer), 并把地址告诉LCD控制器
dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp)
第一个参数是设备NULL,第二个参数是大小,第三个参数是存放物理地址,第四个参数是标记,返回值是这块内存的虚拟地址s3c_lcd->screen_base。

4 注册
register_framebuffer(s3c_lcd);

程序

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

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

/* from pxafb.c */
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);
	
	pseudo_palette[regno] = val;
	return 0;
}
static int lcd_init(void){
	/* 1. 分配一个fb_info */
	s3c_lcd = framebuffer_alloc(0, NULL);

	/* 2. 设置 */
	/* 2.1 设置固定的参数 */
	strcpy(s3c_lcd->fix.id, "mylcd");
	s3c_lcd->fix.smem_len = 240*320*16/8;
	s3c_lcd->fix.type     = FB_TYPE_PACKED_PIXELS;
	s3c_lcd->fix.visual   = FB_VISUAL_TRUECOLOR; /* TFT */
	s3c_lcd->fix.line_length = 240*2;
	
	/* 2.2 设置可变的参数 */
	s3c_lcd->var.xres           = 240;
	s3c_lcd->var.yres           = 320;
	s3c_lcd->var.xres_virtual   = 240;
	s3c_lcd->var.yres_virtual   = 320;
	s3c_lcd->var.bits_per_pixel = 16;

	/* RGB:565 */
	s3c_lcd->var.red.offset     = 11;
	s3c_lcd->var.red.length     = 5;	
	s3c_lcd->var.green.offset   = 5;
	s3c_lcd->var.green.length   = 6;
	s3c_lcd->var.blue.offset    = 0;
	s3c_lcd->var.blue.length    = 5;
	s3c_lcd->var.activate       = FB_ACTIVATE_NOW;
		
	// 2.3 设置操作函数
	s3c_lcd->fbops              = &s3c_lcdfb_ops;
	
	/* 2.4 其他的设置 */
	s3c_lcd->pseudo_palette = pseudo_palette;
	s3c_lcd->screen_size   = 240*324*16/8;
	/* 3. 硬件相关的操作 */
	/* 3.1 配置GPIO用于LCD */
	gpbcon = ioremap(0x56000010, 8);
	gpbdat = gpbcon+1;
	gpccon = ioremap(0x56000020, 4);
	gpdcon = ioremap(0x56000030, 4);
	gpgcon = ioremap(0x56000060, 4);
    *gpccon  = 0xaaaaaaaa;   /* GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
	*gpdcon  = 0xaaaaaaaa;   /* GPIO管脚用于VD[23:8] */
	*gpbcon &= ~(3);  /* GPB0设置为输出引脚 */
	*gpbcon |= 1;
	*gpbdat &= ~1;     /* 输出低电平 */
	*gpgcon |= (3<<8); /* GPG4用作LCD_PWREN */
	
	/* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
	lcd_regs = ioremap(0x4D000000, sizeof(struct 	lcd_regs));	//映射寄存器
	lcd_regs->lcdcon1  = (4<<8) | (3<<5) | (0x0c<<1);
	//垂直方向的时间参数
	lcd_regs->lcdcon2  = (3<<24) | (319<<14) | (1<<6) | (0<<0);
	// 水平方向的时间参数
	lcd_regs->lcdcon3 = (16<<19) | (239<<8) | (10<<0);
	// 水平方向的同步信号
	lcd_regs->lcdcon4 = 4;
	// 信号的极性 
	lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
	
	/* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */
	s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);
	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  = (240*16/16);  /* 一行的长度(单位: 2字节) */	
	
	/* 启动LCD */
	lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
	lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
	*gpbdat |= 1;     /* 输出高电平, 使能背光 */		

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

module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值