您的位置:首页 > 运维架构 > Linux

Linux监控系统开发详解(三)--LCD驱动分析

2015-01-30 20:21 417 查看
3.4 LCD驱动程序分析

一个LCD驱动可以分为两层,一个是真正和硬件打交道的platform一个是抽象的fb设备,这个在我接触的驱动中貌似是linux的一个惯用的手法,先用一个platform device 和platform driver去操作底层硬件(这两个文件一般对应/arch/arm/mach-da8xx/devices.c和drives/video/da8xx_fb.c,红色应对应你自己的处理器),然后通过一种匹配,在lcd里面使用的是全局变量:struct fb_info *registered_fb [FB_MAX]来共享里面的资源的,当platform匹配成功就把映射空间等存入registered_fb[]变量以使别人能够访问,这样子再在linux/drivers/video/fbmem.c里面定义一个fb设备,通过读写fb这个设备然后间接的利用 struct fb_info *registered_fb [FB_MAX]就可以控制底层了。

3.4.1 platform设备

目的:将设备与驱动分离,通过platform总线进行连接。

3.4.1.1 platform设备

结构体structplatform_device{

const char name;/*设备名*/

u32 id;/*设备id*/

struct device dev;/*设备*/

u32 num_resource;/*设备所使用各类资源数量*/

struct resource resource;/*资源*/

}

这我现在只想说一个成员constcharname;它表示设备的名称,查看platform_driver的时候你会看到它的成员driver也有这个字段,platform总线检查这两个字段如果匹配就将platform设备和驱动关联起来。

3.4.1.2 platform驱动

structplatform_driver{

int(*probe)(struct platform_device*);

int(*remove)(struct platform_device*);



structdevice_driver driver;

};

这里主要介绍上述3个成员:

probe函数:正如字面意思(探针),当platform设备与驱动匹配后会调用此函数,我们对字符设备的注册的工作可以在这里完成。

remove函数:对字符设备的注销工作在这里完成。

driver:包含两个字段。

.name:需要与前面的platform_device中的name字 段保持一致,才能完成匹配。

.owner:一般设置为THIS_MODULE。

3.4.1.3 系统如何完成platform设备和驱动的匹配

系统为platform总线定义了一个bus_type(总线类型)的实例platform_bus_type,在此结构体中有一个成员函数:

.match,系统就是靠这个函数完成匹配的。

3.4.1.4 驱动编写

编写platform驱动要完成三方面的工作:

1.platform_device的编写。

2.platform_driver的编写。

3.设备资源和数据的定义。

3.4.1.5 在内核中添加一个platform设备

1.在驱动目录下要存在相应设备的驱动文件。

2.写数据结构 platform_device 和 platform_driver,在 include/linux/platform_device.h 文件中找到arch\arm\mach-s3c64xx\mach-mini6410.c写数据结构。

static struct platform_devicemini6410_lcd_powerdev = {

.name = "platform-lcd",

.dev.parent = &s3c_device_fb.dev,

.dev.platform_data=&mini6410_lcd_power_data,

};

3.向内核注册lcd_device。

3.4.1.6 LCD控制器与platform设备

由于LCD控制器被集成的SOC上作为一个独立的硬件模块而存在(成为platform_device),因此,LCD驱动中包含了平台驱动,这样在帧缓冲设备驱动的模块加载函数中完成的工作只是注册平台驱动,而初始化FBI结构体中固定和可变参数,LCD控制器硬件的初始化,申请帧缓冲设备的显示缓冲空间和注册真缓冲设备的工作则移交到平台驱动的探测函数中完成。

3.4.2 帧缓冲设备驱动结构

Framebuffer帧缓冲设备给用户态提供的读写接口file_operations实际由fbmem.c文件里的特定file_operatins结构对象来提供,而特定帧缓冲设备fb_info结构体的注册、注销及其中成员的维护,尤其是fb_ops中的成员函数的实现则由对应的xxxfb.c文件实现,fb_ops中的成员函数最终会操作LCD控制器的寄存器。



实现一个LCD驱动程序主要做如下两步:

分配系统内存作为显存。

根据具体硬件特性,实现fb_ops的接口。

.分配系统内存作为显存。

由于大多数LCD controller 没有自己的显存,所以需要分配一块系统内存作为显存。这块系统内存的起始地址和长度会被存放在 fb_fix_screeninfo 的smem_start和 smem_len域中。该内存应该是物理上连续的。

对于带独立显存的显卡,使用request_mem_region 和ioremap 将显卡外设内存映射到处理器虚拟地址空间。

2).实现fb_ops结构。

3.5 帧缓冲设备源码分析

3.5.3 帧缓冲设备的模块加载函数

首先找到设备驱动文件,也就是含有module_init()等函数的文件.该文件是 s3cfb.c.从入口函数,moudle_init()入手。

module_init(s3cfb_init);

//在初始化函数中就干了一件事。

int __devinit s3cfb_init(void)

{

//这里面调用了结构体的地址,通过 platform_driver_register 函数注册该设备的 过程中,它会回调.probe 函数。

Returnplatform_driver_register(&s3cfb_driver);

}

这个结构体的成员都是函数,相当于字符设备驱动中的open,read等函数。

static struct platform_drivers3cfb_driver = {

.probe = s3cfb_probe,

.remove = s3cfb_remove,

.suspend = s3cfb_suspend,

.resume = s3cfb_resume,

.driver = {

.name = "s3c-fb",

.owner = THIS_MODULE, }, };

3.5.4 platform驱的探测函数

驱动注册的probe函数。

probe函数在设备驱动注册最后收尾工作,当设备的device 和其对应的driver 在总线上完成配对之后,系统就调用platform设备的probe函数完成驱动注册最后工作。资源、中断调用函数以及其他相关工作。

platform_drv_probe()->driver->probe(),如果probe成 功则绑定该设备到该驱动.

static int __init s3cfb_probe(struct platform_device *pdev)
{
 	struct resource *res;//描述lcd的硬件控制寄存器的结构体。
	struct fb_info *fbinfo; //fb_info 为内核提供的 buffer 驱动的接口数据结构, 每个帧缓冲驱动都对应一个这样的结构。s3cfb_probe 的最终目的填充该结构,并向内核注册。
s3cfb_info_t *info; //该结构记录了 s3cfb 驱动的所有信息。
char driver_name[] = "s3cfb"; //驱动名称
int index = 0, ret, size;
fbinfo = framebuffer_alloc(sizeof(s3cfb_info_t), &pdev->dev);
if (!fbinfo)
		return -ENOMEM;
      //以下开始做正经事了,填充 fbinfo 了。
	platform_set_drvdata(pdev, fbinfo);
 	//该函数的实现非常简单,实际的操作为:pdev->dev.driver_data = fbinfo,device。
结构的 driver_data 域指向驱动程序的私有数据空间。
	info = fbinfo->par;
	info->dev = &pdev->dev;
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res == NULL) {
		dev_err(&pdev->dev, "failed to get memory registers\n");
		ret = -ENXIO;
		goto dealloc_fb;
	}
	size = (res->end - res->start) + 1;
	info->mem = request_mem_region(res->start, size, pdev->name);
	if (info->mem == NULL) {
		dev_err(&pdev->dev, "failed to get memory region\n");
		ret = -ENOENT;
		goto dealloc_fb;
	}

	info->io = ioremap(res->start, size);
	if (info->io == NULL) {
		dev_err(&pdev->dev, "ioremap() of registers failed\n");
		ret = -ENXIO;
		goto release_mem;
	}
s3cfb_pre_init();
	s3cfb_set_backlight_power(1);
	s3cfb_set_lcd_power(1);
	s3cfb_set_backlight_level(S3CFB_DEFAULT_BACKLIGHT_LEVEL);
      //该函数得到时钟源,并与硬件紧密相连。
	info->clk = clk_get(NULL, "lcd");

	if (!info->clk || IS_ERR(info->clk)) {
		printk(KERN_INFO "failed to get lcd clock source\n");
		ret =  -ENOENT;
		goto release_io;
	}
     //打开时钟
	clk_enable(info->clk);
	printk("S3C_LCD clock got enabled :: %ld.%03ld Mhz\n", 					   			PRINT_MHZ(clk_get_rate(info->clk)));
 
	s3cfb_fimd.vsync_info.count = 0;
	init_waitqueue_head(&s3cfb_fimd.vsync_info.wait_queue);

	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);

	if (res == NULL) {
		dev_err(&pdev->dev, "failed to get irq\n");
		ret = -ENXIO;
		goto release_clock;
	}
      //向内核注册中断
	ret = request_irq(res->start, s3cfb_irq, 0, "s3c-lcd", pdev);

	if (ret != 0) {
		printk("Failed to install irq (%d)\n", ret);
		goto release_clock;
	}
	msleep(5);
for (index = 0; index < S3CFB_NUM; index++) {
		s3cfb_info[index].mem = info->mem;
		s3cfb_info[index].io = info->io;
		s3cfb_info[index].clk = info->clk;
s3cfb_init_fbinfo(&s3cfb_info[index], driver_name, index);

		/* Initialize video memory */
		ret = s3cfb_map_video_memory(&s3cfb_info[index]);

		if (ret) {
			printk("Failed to allocate video RAM: %d\n", ret);
			ret = -ENOMEM;
			goto release_irq;
		}
ret = s3cfb_init_registers(&s3cfb_info[index]);
		ret = s3cfb_check_var(&s3cfb_info[index].fb.var, 	&s3cfb_info[index].fb);
if (index < 2){
			if (fb_alloc_cmap(&s3cfb_info[index].fb.cmap, 256, 0) < 0)
				goto dealloc_fb;
		} else {
			if (fb_alloc_cmap(&s3cfb_info[index].fb.cmap, 16, 0) < 0)
				goto dealloc_fb;
		}
              //向内核正式注册
		ret = register_framebuffer(&s3cfb_info[index].fb);
if (ret < 0) {
			printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret);
			goto free_video_memory;
		}
printk(KERN_INFO "fb%d: %s frame buffer device\n", s3cfb_info[index].fb.node, 	s3cfb_info[index].fb.fix.id);
	}
/* create device files */
//为该设备创建一个在 sysfs中的属性
	ret = device_create_file(&(pdev->dev), &dev_attr_backlight_power);
if (ret < 0)
		printk(KERN_WARNING "s3cfb: failed to add entries\n");
ret = device_create_file(&(pdev->dev), &dev_attr_backlight_level);
if (ret < 0)
		printk(KERN_WARNING "s3cfb: failed to add entries\n");
ret = device_create_file(&(pdev->dev), &dev_attr_lcd_power);
if (ret < 0)
		printk(KERN_WARNING "s3cfb: failed to add entries\n");
return 0;
free_video_memory:
	s3cfb_unmap_video_memory(&s3cfb_info[index]);
release_irq:
	free_irq(res->start, &info);
release_clock:
	clk_disable(info->clk);
	clk_put(info->clk);

release_io:
	iounmap(info->io);
release_mem:
	release_resource(info->mem);
	kfree(info->mem);
dealloc_fb:
	framebuffer_release(fbinfo);
	return ret;
}

在probe函数有这样一条语句ret = s3cfb_map_video_memory(&s3cfb_info[index]);

这是能将数据传送到framebuffer的关键。

3.5.5 什么时候调用PROBE函数,注册后如何找到驱动匹配的设备

do_basic_setup()->driver_init()->platform_bus_init()->...初始化platform bus(虚拟总线)设备向内核注册的时候platform_device_register()->platform_device_add()->...内核把设备挂在虚拟的platform bus下。

驱动注册的时候platform_driver_register()->driver_register()->bus_add_driver()->driver_attach()->bus_for_each_dev()对每个挂在虚拟的platform bus的设备作__driver_attach()->driver_probe_device()->drv->bus->match()==platform_match()->比较strncmp(pdev->name, drv->name, BUS_ID_SIZE),如果相符就调用。

3.5.6 platform_driver_register函数解析

设备与驱动的两种绑定方式:在设备注册时进行绑定及在驱动注册时进行绑定。以一个USB设备为例,有两种情形:

(1)先插上USB设备并挂到总线中,然后在安装USB驱动程序过程中从总线上遍 历各个设备,看驱动程序是否与其相匹配,如果匹配就将两者邦定。这就是platform_driver_register()。

(2)先安装USB驱动程序,然后当有USB设备插入时,那么就遍历总线上的各个驱动,看两者是否匹配,如果匹配就将其绑定。这就是platform_device_register()函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: