内核LCD驱动结构分析及实例分析

本文介绍S3C2410芯片的LCD驱动实现过程,涵盖帧缓冲设备初始化、平台设备注册及探针函数分析。深入探讨了fb_info结构体的配置与注册流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    内核中,LCD是作为帧缓冲设备来使用的,帧缓冲设备是标准的字符设备,主设备号为29。内核LCD驱动的结构包含了字符设备的结构和paltform设备的结构,以smdk2410为例,其主要涉及4个文件:

               linux-2.6.22.6\arch\arm\mach-s3c2410\mach-smdk2410.c

               linux-2.6.22.6\arch\arm\plat-s3c24xx\devs.c

               linux-2.6.22.6\drivers\video\s3c2410fb.c

               linux-2.6.22.6\drivers\video\fbmem.c

    一、fbmem.c里面实现了帧缓冲设备这个字符设备的文件操作集fb_fops(注意与后面的fb_ops不同),在帧缓冲设备模块被加载的时候,fbmem.c里面的fbmem_init()函数就会被调用,

fbmem_init(void)
{
	......
	if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
	......
fb_class = class_create(THIS_MODULE, "graphics");
	......
}

其中FB_MAJOR=29,注册了帧缓冲设备,创建了类,但是没有创建设备节点。若创建好设备节点的话,应用程序就可以open,read,write调用到fb_fops来操作帧缓冲设备了。设备节点的创建的前提是需要有一个已经设置好了的帧缓冲设备信息结构体( struct fb_info) 来支持。那么在哪里设备这个fb_info结构体,在哪里创建设备节点?它们都是在platform设备驱动结构的probe()函数里面进行。下面分析platform设备驱动结构。

    二、S3c2410fb.c里面主要做了3件大事:

1、实现用于fb_info结构体的fb_ops成员:s3c2410fb_ops

 static struct fb_ops s3c2410fb_ops = {
	.owner		= THIS_MODULE,
	.fb_check_var	= s3c2410fb_check_var,
	.fb_set_par	= s3c2410fb_set_par,
	.fb_blank		= s3c2410fb_blank,
	.fb_setcolreg	= s3c2410fb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
};

这个结构体与paltform设备驱动没有什么直接的关系,只是platform驱动结构的probe()函数要用到它及它的成员函数来设置fb_info。

2、实现platform驱动结构的platform_driver结构体s3c2410_driver:

static struct platform_driver s3c2410fb_driver = {
	.probe		= s3c2410fb_probe,
	.remove		= s3c2410fb_remove,
	.suspend	= s3c2410fb_suspend,
	.resume		= s3c2410fb_resume,
	.driver		= {
		.name	= "s3c2410-lcd",
		.owner	= THIS_MODULE,
	},
};

其中的这个probe()函数,后面还会详细讨论一下,因为它里面完成了帧缓冲设备这个字符设备还没有完成的任务,那就是前面(一)最后说的设置fb_info结构体和创建字符设备节点。

3、注册platform_driver结构体:s3c2410fb_driver:

int __devinit s3c2410fb_init(void)
{
	return platform_driver_register(&s3c2410fb_driver);
}

至此,根据platform设备驱动的机制,当有与”s3c2410-lcd”同名的platform_device注册的时候,上面的probe()就会被调用。那么这个名为s3c2410-lcd的platfrom_device在哪里被注册呢?它在mach-s3c2410.c里面被注册,不过下面先分析一下devs.c。

    三、devs.c的作用是构造了s3c2410芯片的所有平台设备资源结构体struct resource和平台设备结构体struct platform_device,所谓平台设备,就是芯片内部集成的设备,一个芯片就是一个平台,集成在这芯片上的设备就叫平台设备,如LCD控制器,USB控制器,UART,nand等控制器和寄存器资源,对一款芯片,它上面的硬件资源或设备资源是固定的,对这种稳定的设备的驱动,人们往往把它写成平台设备驱动,这样对于片上设备的驱动就不用重复写了。当然把外接设备写成平台设备驱动结构也是可以的,它本质就是一个机制,driver和device两边注册后调用probe()函数的一种机制,我们在probe()函数里面爱干嘛就干嘛。

   摘取devs.c的部分内容,其他内容与之相似:

......

/* LCD Controller */

static struct resource s3c_lcd_resource[] = {
	[0] = {
		.start = S3C24XX_PA_LCD,
		.end   = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = IRQ_LCD,
		.end   = IRQ_LCD,
		.flags = IORESOURCE_IRQ,
	}

};

static u64 s3c_device_lcd_dmamask = 0xffffffffUL;

struct platform_device s3c_device_lcd = {
	.name		  = "s3c2410-lcd",
	.id		  = -1,
	.num_resources	  = ARRAY_SIZE(s3c_lcd_resource),
	.resource	  = s3c_lcd_resource,
	.dev              = {
		.dma_mask		= &s3c_device_lcd_dmamask,
		.coherent_dma_mask	= 0xffffffffUL
	}
};

......

devs.c里面设置了platform_device结构体,但没有注册它,上面说了,它在mach-sec2410.c里面被注册。

   四、mach-s3c2410.c部分代码:

......
static struct platform_device *smdk2410_devices[] __initdata = {
	    &s3c_device_usb,
	    &s3c_device_lcd,
	    &s3c_device_wdt,
	    &s3c_device_i2c,
	    &s3c_device_iis,
};
......

static void __init smdk2410_init(void)
{
	platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices));
	smdk_machine_init();
}
......

__init smdk2410_init()函数把smdk2410_devices[]数组里面的几个平台设备都添加到内核,platform_add_devices()相当于各个设备分别调用platform_device_register(),包括s3c_device_lcd,由(三)知s3c_device_lcd->name =“s3c2410-lcd”,与(二)中s3c2410fb_driver->driver.name =“s3c2410-lcd”相同,则调用s3c2410fb_driver->probe,即s3c2410fb_probe()函数。

   下面先粗略分析这个probe函数。

   部分源码:

static int __init s3c2410fb_probe(struct platform_device *pdev)
{
	
    ......
	fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
    ......
	/* 设置fbinfo结构体 */
    ......
	ret = register_framebuffer(fbinfo);
    ......
}

Probe()函数主要就是分配、设置、注册 fbinfo结构体。

   下面粗略看一下register_framebuffer()函数:

   部分源码:

register_framebuffer(struct fb_info *fb_info)
{
    ......
	fb_info->dev = device_create(fb_class, fb_info->device,
				     MKDEV(FB_MAJOR, i), "fb%d", i);
	
    ......
	registered_fb[i] = fb_info;
    ......
	
}

   Register_framebuffer()函数就创建了设备节点,这样字符设备驱动的结构就完成了。同时,把次设备号i作为数组下标,把fb_info放入数组registered_fb[i] = fb_info,fb_info结构体里面的信息供应用程序使用,当应用程序读写帧缓冲设备fb%d时,会根据次设备号从registered_fb[]数组中取出一个fb_info结构体,根据结构体里的信息找到读写位置进行读写。

    应用程序是通过fbmem.c里面抽象好的帧缓冲设备操作函数集,结合registered_fb[]里的fb_info提供的设备信息来操作设备的,其操作函数集是fb_fops。而与之容易混淆的一个操作集是fb_ops,fb_ops是fb_info结构体的成员,它是用来操作或设置fb_info自身的其他成员的,涉及到底层硬件的操作,一般需要开发人员自己编写,而fb_fops作为帧缓冲设备通用操作函数集,已集成在fbmem.c里面,不需要我们再编写。

   对于源码中的probe()函数的细节,我实在是无能力分析了,不过其主要功能就是设置fb_info结构体,然后注册。

   下面结合东山老师的源码分析一下怎么设置这个fb_info结构体吧。东山老师的驱动结构中没有platfrom驱动结构,直接分配,设置,注册fb_info结构体,清晰明了。

   东山老师的Lcd.c,我的分析都添加在注释里了:

#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 <linux/module.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>
/* s3c_lcdfb_setcolreg()是fb_ops结构体的成员函数,用于设置调色板 */
static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
			     unsigned int green, unsigned int blue,
			     unsigned int transp, struct fb_info *info);

/* lcd_regs 结构体与LCD控制器寄存器组对应,后面通过lcd_regs 操作寄存器组,
* 因为后面有lcd_regs = ioremap(0x4d000000,sizeof(struct lcd_regs)),把寄存器组首地址
* 映射后得到的虚拟地址赋给 lcd_regs,以后就可以通过lcd_regs操作到寄存器了。
*/
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;
/* 伪彩色调色板,这个伪彩色调色板,原理上就是一个16色的调色板,某些场合下就是用16级灰度色来反映
   事物,研究事物,使用查找得到的伪彩色的数值显示的色彩是16BPP(或32BPP)的真彩色,但它不是物体
   本身真正的颜色,它没有完全反映原图的彩色,如应用在医学图像中。较好地放映物体原色要256色以上
   的调色板,过大的又没必要,人眼分辨不了那么微细的光波频率
*/
static u32 pseudo_palette[16];


/* from pxafb.c 
*颜色位处理函数,用于构造565格式的颜色值。
* chan:色域,red、green、blue 
* bf:位域结构体,成员包含所占位数和偏移,即length和offset
*/
static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
	chan &= 0xffff;		//保留chan的低16位
	chan >>= 16 - bf->length;	//把低16位的高length位移为低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 = 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;//与fix.type一起使用
//参考https://www.kernel.org/doc/Documentation/fb/api.txt
	s3c_lcd->fix.visual   = FB_VISUAL_TRUECOLOR; /* TFT */
	s3c_lcd->fix.line_length = 240*2;	//一行里有效像素长度,240*16/8,以字节为单位,
	
	/* 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;
	//__u32 msb_right字段默认为零,最重要为在左边,即高位。
	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_base  = ;  /* 显存的虚拟地址 */ 
/*     由下面的s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, 
       &s3c_lcd->fix.smem_start, GFP_KERNEL);设置,dma_alloc-writecombine()函数分配一段连续的显
       存,显存的物理地址保存在参数s3c_lcd->fix.smem_start中,返回显存的虚拟地址保存在
       s3c_lcd->screen_base中。

*/
	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));

	/* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2], LCD手册P14
	 *            10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2]
	 *            CLKVAL = 4
	 * bit[6:5]: 0b11, TFT LCD
	 * bit[4:1]: 0b1100, 16 bpp for TFT
	 * bit[0]  : 0 = Disable the video output and the LCD control signal.
	 */
	lcd_regs->lcdcon1  = (4<<8) | (3<<5) | (0x0c<<1);

#if 1
	/* 垂直方向的时间参数
	 * bit[31:24]: VBPD, VSYNC之后再过多长时间才能发出第1行数据
	 *             LCD手册 T0-T2-T1=4
	 *             VBPD=3
	 * bit[23:14]: 多少行, 320, 所以LINEVAL=320-1=319
	 * bit[13:6] : VFPD, 发出最后一行数据之后,再过多长时间才发出VSYNC
	 *             LCD手册T2-T5=322-320=2, 所以VFPD=2-1=1
	 * bit[5:0]  : VSPW, VSYNC信号的脉冲宽度, LCD手册T1=1, 所以VSPW=1-1=0
	 */
	lcd_regs->lcdcon2  = (3<<24) | (319<<14) | (1<<6) | (0<<0);


	/* 水平方向的时间参数
	 * bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据
	 *             LCD手册 T6-T7-T8=17
	 *             HBPD=16
	 * bit[18:8]: 多少列, 240, 所以HOZVAL=240-1=239
	 * bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC
	 *             LCD手册T8-T11=251-240=11, 所以HFPD=11-1=10
	 */
	lcd_regs->lcdcon3 = (16<<19) | (239<<8) | (10<<0);

	/* 水平方向的同步信号
	 * bit[7:0]	: HSPW, HSYNC信号的脉冲宽度, LCD手册T7=5, 所以HSPW=5-1=4
	 */	
	lcd_regs->lcdcon4 = 4;

#else
lcd_regs->lcdcon2 =	S3C2410_LCDCON2_VBPD(5) | \
		S3C2410_LCDCON2_LINEVAL(319) | \
		S3C2410_LCDCON2_VFPD(3) | \
		S3C2410_LCDCON2_VSPW(1);

lcd_regs->lcdcon3 =	S3C2410_LCDCON3_HBPD(10) | \
		S3C2410_LCDCON3_HOZVAL(239) | \
		S3C2410_LCDCON3_HFPD(1);

lcd_regs->lcdcon4 =	S3C2410_LCDCON4_MVAL(13) | \
		S3C2410_LCDCON4_HSPW(0);

#endif
	/* 信号的极性 
	 * 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_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字节) */	
	
	//s3c_lcd->fix.smem_start = xxx;  /* 显存的物理地址 */
	/* 启动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");

参考文章:

http://blog.youkuaiyun.com/qq_22863733/article/details/78251540

欢迎转载,转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值