1、概述:
通常在Linux中,把SoC系统中集成的独立外设单元(如:I2C、IIS、RTC、看门狗等)都被当作平台设备来处理。
从Linux2.6起,引入了一套新的驱动管理和注册机制:Platform_device和Platform_driver,来管理相应设备。
Linux中大部分的设备驱动,都可以使用这套机制,设备用platform_device表示,驱动用platform_driver进行注册。
Linux platform driver机制和传统的device_driver机制相比,一个十分明显的优势在于platform机制将本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时通过platform_device提供的标准接口进行申请并使用。
这样提高了驱动和资源管理的独立性,并且拥有较好的可移植性和安全性。
platform是一个虚拟的地址总线,相比pci,usb,它主要用于描述SOC上的片上资源,比如s3c2410上集成的控制器(lcd,watchdog,rtc等),platform所描述的资源有一个共同点,就是可以在cpu的总线上直接取址。
2、platform机制分为三个步骤
1)总线注册阶段:
内核启动初始化时的main.c文件中的
kernel_init() ->do_basic_setup() -> driver_init() ->platform_bus_init() ->bus_register(&platform_bus_type),注册了一条platform总线(虚拟总线)
platform总线用于连接各类采用platform机制的设备,此阶段无需我们修改,由linux内核维护。
2)添加设备阶段:
设备注册的时候
Platform_device_register() -> platform_device_add() -> pdev->dev.bus = &platform_bus_type -> device_add(),就这样把设备给挂到虚拟的总线上。
本阶段是增加设备到plartform总线上,我们增加硬件设备,需要修改此处信息。
此部分操作一般arm/arm/mach-s3c2440/mach-smdk2440.c类似的文件中,需要我们根据硬件的实际需要修改相应代码
3)驱动注册阶段:
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),如果相符就调用really_probe(实际就是执行相应设备的platform_driver->probe(platform_device)。)
开始真正的探测,如果probe成功,则绑定设备到该驱动。
本阶段是在编写具体的驱动程序时完成,在注册驱动时获取步骤2中已经申请的资料,一般由程序开发者完成。
3、platform设备开发过程
platform机制开发的并不复杂,由两部分组成:platform_device和platfrom_driver
通过Platform机制开发发底层驱动的大致流程为:
定义 platform_device
注册 platform_device
定义 platform_driver
注册 platform_driver
以linux2.6.34平台下S3C2440为例:前两项工作主要在arch/arm/mach-s3c2440/match-smdk2440.c与arch/arm/platform-s3c24xx/devs.c中完成,后两项工作主要在编写具体的驱动程序时完成
在2.6内核中platform设备用结构体platform_device来描述,该结构体定义在kernel\include\linux\platform_device.h中,
struct platform_device
{
const char * name;
u32 id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
每个具体的驱动都对应一个这样的结构体。
Platform_device结构体描述了一个platform结构的设备,在其中包含了
一般设备的结构体:struct device dev;
设备的资源结构体:struct resource * resource;
还有设备的名字:const char * name。
(注意,这个名字一定要和后面platform_driver.driver->name相同,因为在注册具体的设备驱动时会遍历这个结构体查找相应的数据结构,后面会详细讲解)
该结构一个重要的元素是resource,该元素存入了最为重要的设备资源信息,定义在kernel\include\linux\ioport.h中,
struct resource
{
resource_size_t start; //定义资源的起始地址
resource_size_t end; //定义资源的结束地址
const char *name; //定义资源的名称
unsigned long flags; //定义资源的类型,比如MEM,IO,IRQ,DMA类型
struct resource *parent, *sibling, *child; //资源链表指针
};
主要用于定义具体设备占用的硬件资源(如:地址空间、中断号等;
其中 flags位表示该资源的类型
start和end分别表示该资源的起始地址和结束地址;
要注意的是,这里的platform_device设备的注册过程必须在相应设备驱动加载之前被调用;
即执行platform_driver_register()之前,原因是驱动注册时需要匹配内核中所有已注册的设备名。
我们以内核中对SMDK2440的支持为例观察一下整个过程:
内核启动过程中会调用用 arch/arm/mach-s3c2440/smdk2440_machine_init()函数进行板级硬件初始化
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_fb_info);
s3c_i2c0_set_platdata(NULL);
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
smdk_machine_init();
}
此函数中调用了platform_add_devices() -> platform_device_register()注册platform设备
注册顺序根据同文件夹下的
static struct platform_device *smdk2440_devices[] __initdata =
{
&s3c_device_ohci,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_iis,
&s3c_device_dm9k,
&s3c24xx_uda134x,
&s3c_device_sdi,
};
结构体进行
这些设备的初始化一般都在arch/arm/plat-s3c24xx/devs.c下
我们以s3c_device_wdt为例进行观察:
/* Watchdog */
//看门狗资源结构体
static struct resource s3c_wdt_resource[] = {
[0] = {
.start = S3C24XX_PA_WATCHDOG,
.end = S3C24XX_PA_WATCHDOG + S3C24XX_SZ_WATCHDOG - 1,
.flags = IORESOURCE_MEM, //看门狗所使用的IO口范围
},
[1] = {
.start = IRQ_WDT,
.end = IRQ_WDT,
.flags = IORESOURCE_IRQ, //看门狗所使用的中断资源
}
};
//定义了一个看门狗结构体
struct platform_device s3c_device_wdt = {
.name = "s3c2410-wdt", //驱动名称
.id = -1, //id号,-1代表自动分配
.num_resources = ARRAY_SIZE(s3c_wdt_resource),
//指定资源数量
.resource = s3c_wdt_resource, //指定资源结构体
};
platform_driver在具体的硬件设备驱动编写中完成:
同plartform_device相似,需要定义并实现以下结构体
struct platform_driver (/include/linux/Platform_device.h)
{
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
Platform_driver结构体描述了一个platform结构的驱动。
其中除了一些函数指针外,还有一个一般驱动的device_driver结构。
/*Watchdog平台驱动结构体,平台驱动结构体定义在platform_device.h中,该结构体内的接口函数需要单独实现*/
static struct platform_driver watchdog_driver =
{
.probe = watchdog_probe, /*Watchdog探测函数*/
.remove = __devexit_p(watchdog_remove),/*Watchdog移除函数*/
.shutdown = watchdog_shutdown, /*Watchdog关闭函数*/
.suspend = watchdog_suspend, /*Watchdog挂起函数*/
.resume = watchdog_resume, /*Watchdog恢复函数*/
.driver =
{
/*注意这里的名称一定要和系统中定义平台设备的地方一致,这样才能把平台设备与该平台设备的驱动关联起来*/
.name = "s3c2410-wdt",
.owner = THIS_MODULE,
},
};
static int __init watchdog_init(void)
{
/*将Watchdog注册成平台设备驱动*/
return platform_driver_register(&watchdog_driver);
}
static void __exit watchdog_exit(void)
{
/*注销Watchdog平台设备驱动*/
platform_driver_unregister(&watchdog_driver);
}
module_init(watchdog_init);
module_exit(watchdog_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("linux");
MODULE_DESCRIPTION("S3C2440 Watchdog Driver");
static int __devinit watchdog_probe(struct platform_device *pdev)
{
int ret;
int started = 0;
struct resource *res;/*定义一个资源,用来保存获取的watchdog的IO资源*/
/*在系统定义的watchdog平台设备中获取watchdog中断号platform_get_irq定义在platform_device.h中*/
wdt_irqno = platform_get_irq(pdev, 0);
/*申请Watchdog中断服务,这里使用的是快速中断:IRQF_DISABLED。中断服务程序为:wdt_irq,将Watchdog平台设备pdev做参数传递过去了*/
ret = request_irq(wdt_irqno, wdt_irq, IRQF_DISABLED, pdev->name, pdev);
/*获取watchdog平台设备所使用的IO端口资源,注意这个IORESOURCE_MEM标志和watchdog平台设备定义中的一致*/
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
/*申请watchdog的IO端口资源所占用的IO空间(要注意理解IO空间和内存空间的区别),request_mem_region定义在ioport.h中*/
wdt_mem = request_mem_region(res->start, res->end - res->start + 1, pdev->name);
/*将watchdog的IO端口占用的这段IO空间映射到内存的虚拟地址,ioremap定义在io.h中。
注意:IO空间要映射后才能使用,以后对虚拟地址的操作就是对IO空间的操作,*/
wdt_base = ioremap(res->start, res->end - res->start + 1);
return ret;
}
/*Watchdog平台驱动的设备移除接口函数的实现*/
static int __devexit wdt_remove(struct platform_device *dev)
{
/*释放获取的Watchdog平台设备的IO资源*/
release_resource(wdt_mem);
kfree(wdt_mem);
wdt_mem = NULL;
/*同watchdog_probe中中断的申请相对应,在那里申请中断,这里就释放中断*/
free_irq(wdt_irqno, dev);
wdt_irq = NULL;
/*释放获取的Watchdog平台设备的时钟*/
clk_disable(wdt_clock);
clk_put(wdt_clock);
wdt_clock = NULL;
/*释放Watchdog设备虚拟地址映射空间*/
iounmap(wdt_base);
return 0;
}
#ifdef CONFIG_PM
/*定义两个变量来分别保存挂起时的WTCON和WTDAT值,到恢复的时候使用*/
static unsigned long wtcon_save;
static unsigned long wtdat_save;
/*Watchdog平台驱动的设备挂起接口函数的实现*/
static int wdt_suspend(struct platform_device *dev, pm_message_t state)
{
/*保存挂起时的WTCON和WTDAT值*/
wtcon_save = readl(wdt_base + S3C2410_WTCON);
wtdat_save = readl(wdt_base + S3C2410_WTDAT);
/*停止看门狗定时器*/
wdt_start_or_stop(0);
return 0;
}
/*Watchdog平台驱动的设备恢复接口函数的实现*/
static int wdt_resume(struct platform_device *dev)
{
/*恢复挂起时的WTCON和WTDAT值,注意这个顺序*/
writel(wtdat_save, wdt_base + S3C2410_WTDAT);
writel(wtdat_save, wdt_base + S3C2410_WTCNT);
writel(wtcon_save, wdt_base + S3C2410_WTCON);
return 0;
}
#else /*配置内核时没选上电源管理,Watchdog平台驱动的设备挂起和恢复功能均无效,这两个函数也就无需实现了*/
#define wdt_suspend NULL
#define wdt_resume NULL
#endif