2440 LCD嵌入式驱动用到了Linux platform driver 机制。一个十分明显的优势在于 platform 机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时通过 platform device 提供的标准接口进行申请并使用。这样提高了驱动和资源管理的独立性,并且拥有较好的可移植性和安全性 ( 这些标准接口是安全的 ) Platform 机制的本身使用并不复杂,由两部分组成: platform_device 和 platfrom_driver 。 通过 Platform 机制开发发底层驱动的大致流程为 :
定义 platform_device -> 注册 platform_device-> 定义 platform_driver-> 注册 platform_driver 。
plat层定义device, 如下面文件定义了 s3c_device_lcd
/linux/linux-2.6.29/arch/arm/plat-s3c24xx/Devs.c
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 } } |
驱动层即s3c2410fb.c定义platform_driver 如下面文件定义了s3c2410fb_driver: /linux/linux-2.6.29/drivers/video/S3c2410fb.c
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, } , } ; |
因为内核配置是针对s3c2440芯片的, s3c2440自带lcd控制器,因此内核已经知道有s3c_device_lcd这个device存在, 驱动为device服务,driver通过 platform_driver_register(&s3c2410fb_driver) 告诉内核驱动的存在,内核根据 driver.name 找到 device, 然后把device的信息通过 platform_device *pdev 这个参数传递给driver下挂着的各个功能函数,从而使驱动完成使命.
驱动函数s3c24xxfb_probe 所引用到的参数 mach_info, 即pdev->dev.platform_data 在 mach-mini2440.c里定义如下
static struct s3c2410fb_mach_info mini2440_fb_info __initdata = { . displays = & mini2440_lcd_cfg, . num_displays = 1, . default_display = 0, . gpccon = 0xaa955699, . gpccon_mask = 0xffc003cc, . gpcup = 0x0000ffff, . gpcup_mask = 0xffffffff, . gpdcon = 0xaa95aaa1, . gpdcon_mask = 0xffc0fff0, . gpdup = 0x0000faff, . gpdup_mask = 0xffffffff, . lpcsel = 0xf82, } ; |
驱动根据 lcd device 信息建立一块buffer, 然后把这块buffer作为一个设备通过 ret = register_buffer(fbinfo) 注册到linux设备管理系统中, linux在 /dev 下生成一个叫 fb 的节点, GUI就对这个 fb 节点操作.
下面我们从platform_device s3c_device_lcd定义开始分析。
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 } } |
其中
.name "s3c2410-lcd"是2440LCD驱动的设备名称。它的作用上面已经说明了。是在设备驱动匹配的时候用的。
.id = -1 id表示设备编号,id的值为-1表示只有一个这样的设备。
.resource = s3c_lcd_resource 该结构中比较重要的一个成员就是resource, Linux设计了这个通用的数据结构来描述各种I/O资源(如:I/O端口、外设内存、DMA和IRQ等)。它的定义如
// include/linux/ioport.h: struct resource { const char * name; unsigned long start, end; unsigned long flags; struct resource * parent, * sibling, * child; } ; |
struct resource 是linux对挂接在4G总线空间上的设备实体的管理方式。
一个独立的挂接在cpu总线上的设备单元,一般都需要一段线性的地址空间来描述设备自身,linux是怎么管理所有的这些外部"物理地址范围段",进而给用户和linux自身一个比较好的观察4G总线上挂接的一个个设备实体的简洁、统一级联视图的呢?
linux采用struct resource结构体来描述一个挂接在cpu总线上的设备实体(32位cpu的总线地址范围是0~4G):
resource- > start 描述设备实体在cpu总线上的线性起始物理地址; resource- > end 描述设备实体在cpu总线上的线性结尾物理地址; resource- > name 描述这个设备实体的名称, 这个名字开发人员可以随意起, 但最好贴切; resource- > flag 描述这个设备实体的一些共性和特性的标志位; |
只需要了解一个设备实体的以上4项,linux就能够知晓这个挂接在cpu总线的上的设备实体的基本使用情况,也就是 [resource->start, resource->end]这段物理地址现在是空闲着呢,还是被什么设备占用着呢?
linux 会坚决避免将一个已经被一个设备实体使用的总线物理地址区间段[resource->start, resource->end],再分配给另一个后来的也需要这个区间段或者区间段内部分地址的设备实体,进而避免设备之间出现对同一总线物理地址段 的重复引用,而造成对唯一物理地址的设备实体二义性.
以上的4个属性仅仅用来描述一个设备实体自身,或者是设备实体可以用来自治的单元,但是这不 是linux所想的,linux需要管理4G物理总线的所有空间,所以挂接到总线上的形形色色的各种设备实体,这就需要链在一起,因此resource结 构体提供了另外3个成员:指针parent、sibling和 child:分别为指向父亲、兄弟和子资源的指针,它们的设置是为了以一种树的形式来管理各种I/O资源,以root source为例,root->child(*pchild)指向root所有孩子中地址空间最小的一个;pchild->sibling是 兄弟链表的开头,指向比自己地址空间大的兄弟。
属性flags是一个unsigned long类型的32位标志值,用以描述资源的属性。比如:资源的类型、是否只读、是否可缓存,以及是否已被占用等。下面是一部分常用属性标志位的定义
2440的s3c_lcd_resource结构定义。
路径 /opt/FriendlyARM/mini2440/linux-2.6.29/arch/arm/plat-s3c24xx/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, } } ; |
其中S3C24XX_PA_LCD定义在 /opt/FriendlyARM/mini2440/linux-2.6.29/arch/arm/mach-s3c2400/include/mach/map.h
# define S3C2400_PA_LCD ( 0x14A00000) |
S3C24XX_SZ_LCD定义在 /opt/FriendlyARM/mini2440/linux2.6.29/arch/arm/include/asm/sizes.h:
# define S3C24XX_SZ_LCD SZ_1M # define SZ_1M 0x00100000 |
由上可知LCD占用的资源包括两类,一类是MEM类型,一类是IRQ类型。MEME类型资源对应的物理地址范围是 0x14A00000 - 0x14AFffff;
下面在/opt/FriendlyARM/mini2440/linux-2.6.29/arch/arm/mach-s3c2410/mach-smdk2410.c 中调用platform_add_devices()来向系统中添加该设备了,首先来看它的定义
static struct platform_device * smdk2410_devices[ ] __initdata = { & s3c_device_usb, & s3c_device_lcd, & s3c_device_wdt, & s3c_device_i2c0, & s3c_device_iis, } ; static void __init smdk2410_init( void ) { s3c_i2c0_set_platdata( NULL ) ; platform_add_devices( smdk2410_devices, ARRAY_SIZE( smdk2410_devices) ) ; smdk_machine_init( ) ; } |
其中platform_add_devices()->platform_driver_register。
int platform_device_register( struct platform_device * pdev) { device_initialize( & pdev- > dev) ; return platform_device_add( pdev) ; } |
其中device_initialize函数分析在 http://w.xue163.com/html/20091219/3169506.html
这里暂不分析。
platform_device_add函数
int platform_device_add( struct platform_device * pdev) { int i, ret = 0; if ( ! pdev) /*验证指针的有效性 */ return - EINVAL; if ( ! pdev- > dev. parent) /* 都说总线有两个链表,一个是设备链表(通过device 内嵌)一个是驱动链表(通过device_driver内嵌)这里如果pdev->dev.parent为0,说明设备链表还没有设备,因此处理办 法是将platform_bus作为设备链表的开始,一直感觉platform_bus和platform_bus_type很难区分,不过在这里清楚了 platform_bus是一个设备,platform_bus_type才是真正的总线*/ pdev- > dev. parent = & platform_bus; /*device 的父结点*/ pdev- > dev. bus = & platform_bus_type; /*device 要挂接在platform_bus_type这个总线上拉,看到了,设备和总线是这么勾搭上滴,很直接,很干脆*/ if ( pdev- > id ! = - 1) snprintf( pdev- > dev. bus_id, BUS_ID_SIZE, "%s.%d" , pdev- > name, pdev- > id) ; /*这个如果看不懂,可以参考LINUX的格式化输出的相关资料*/ else strlcpy( pdev- > dev. bus_id, pdev- > name, BUS_ID_SIZE) ; for ( i = 0; i < pdev- > num_resources; i+ + ) { struct resource * p, * r = & pdev- > resource[ i] ; if ( r- > name = = NULL ) /*name一般为NULL*/ r- > name = pdev- > dev. bus_id; /*资源的名称赋值为pdev->dev.bus_id,如果一个platform_device有多个resource 则出现同名现象*/ p = r- > parent; if ( ! p) { /*父资源为0,说明不是从一个大的资源里面切割出来的*/ if ( r- > flags & IORESOURCE_MEM) p = & iomem_resource; else if ( r- > flags & IORESOURCE_IO) p = & ioport_resource; } if ( p & & insert_resource( p, r) ) { /*如果从父资源里面切割失败,则进行如下处理*/ printk( KERN_ERR "%s: failed to claim resource %d/n" , pdev- > dev. bus_id, i) ; ret = - EBUSY; goto failed; } } pr_debug( "Registering platform device '%s'. Parent at %s/n" , pdev- > dev. bus_id, pdev- > dev. parent- > bus_id) ; ret = device_add( & pdev- > dev) ; /*资源也分配好了,准备工作也做足,终于可以把设备添加到设备链表里面了*/ if ( ret = = 0) return ret; failed: /*失败处理*/ while ( - - i > = 0) if ( pdev- > resource[ i] . flags & ( IORESOURCE_MEM| IORESOURCE_IO) ) release_resource( & pdev- > resource[ i] ) ; return ret; } EXPORT_SYMBOL_GPL( platform_device_add) ; |
至此和
platform_device相关的部分分析完毕。下面分析platform_driver