从Linux 2.6 起引入了一套新的驱动管理和注册机制:Platform_device 和Platform_driver 。
Linux 中大部分的设备驱动,都可以使用这套机制, 设备用Platform_device 表示,驱动用Platform_driver 进行注册。
Linux platform driver 机制和传统的device driver 机制( 通过driver_register 函数进行注册) 相比,一个十分明显的优势在于platform 机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时通过platform device 提供的标准接口进行申请并使用。这样提高了驱动和资源管理的独立性,并且拥有较好的可移植性和安全性( 这些标准接口是安全的) 。
Platform 机制的本身使用并不复杂,由两部分组成:platform_device 和platfrom_driver 。
通过Platform 机制开发发底层驱动的大致流程为: 定义 platform_add_devices 注册 platform_device, 定义 platform_add_driver 注册 platform_driver 。
1、platform_device注册过程:
首先要确认的就是设备的资源信息,例如设备的地址,中断号等。
在2.6 内核中platform 设备用结构体platform_device 来描述,该结构体定义在kernel/include/linux/platform_device.h 中,
struct platform_device { const char * name; int id; struct device dev; u32 num_resources; struct resource * resource; struct platform_device_id *id_entry; };
该结构一个重要的元素是resource ,该元素存入了最为重要的设备资源信息,定义在kernel/include/linux/ioport.h 中
struct resource { resource_size_t start; resource_size_t end; const char *name; unsigned long flags; struct resource *parent, *sibling, *child; };
下面举s3c6410 平台的i2c 驱动作为例子来说明:
static struct platform_device *smdk6410_devices[] __initdata = { #ifdef CONFIG_SMDK6410_SD_CH0 &s3c_device_hsmmc0, #endif #ifdef CONFIG_SMDK6410_SD_CH1 &s3c_device_hsmmc1, #endif &s3c_device_i2c0, &s3c_device_i2c1, &s3c_device_fb, &s3c_device_usb, &s3c_device_usb_hsotg, &smdk6410_lcd_powerdev, &smdk6410_smsc911x, };
把一个或几个设备资源放在一起,便于集中管理,其中IIC设备platform_device如下:
struct platform_device s3c_device_i2c0 = { .name = "s3c2410-i2c", #ifdef CONFIG_S3C_DEV_I2C1 .id = 0, #else .id = -1, #endif .num_resources = ARRAY_SIZE(s3c_i2c_resource), .resource = s3c_i2c_resource, };
具体resource如下:
static struct resource s3c_i2c_resource[] = { [0] = { .start = S3C_PA_IIC, .end = S3C_PA_IIC + SZ_4K - 1, .flags = IORESOURCE_MEM, }, [1] = { .start = IRQ_IIC, .end = IRQ_IIC, .flags = IORESOURCE_IRQ, }, };
这里定义了两组resource ,它描述了一个I2C 设备的资源,第1 组描述了这个I2C 设备所占用的总线地址范围,IORESOURCE_MEM 表示第1 组描述的是内存类型的资源信息,第2 组描述了这个I2C 设备的中断号,IORESOURCE_IRQ 表示第2 组描述的是中断资源信息。设备驱动会根据flags 来获取相应的资源信息。
定义好了platform_device 结构体后就可以调用函数platform_add_devices 向系统中添加该设备了,之后可以调用platform_driver_register() 进行设备注册。
s3c6410-i2c的platform_device是在系统启动时,在mach-smdk6410.c里的smdk6410_machine_init()函数里进行注册的,这个函数申明为arch_initcall的函数调用,arch_initcall的优先级高于module_init。所以会在Platform驱动注册之前调用。(详细参考imach-smdk6410.c)
static void __init smdk6410_machine_init(void) { s3c_i2c0_set_platdata(NULL); s3c_i2c1_set_platdata(NULL); s3c_fb_set_platdata(&smdk6410_lcd_pdata); gpio_request(S3C64XX_GPN(5), "LCD power"); gpio_request(S3C64XX_GPF(13), "LCD power"); gpio_request(S3C64XX_GPF(15), "LCD power"); i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0)); i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1)); platform_add_devices(smdk6410_devices, ARRAY_SIZE(smdk6410_devices)); //添加多设备 } int platform_add_devices(struct platform_device **devs, int num) { int i, ret = 0; for (i = 0; i < num; i++) { ret = platform_device_register(devs[i]); if (ret) { while (--i >= 0) platform_device_unregister(devs[i]); break; } } return ret; } int platform_device_register(struct platform_device *pdev) { device_initialize(&pdev->dev); return platform_device_add(pdev); } int platform_device_add(struct platform_device *pdev) { int i, ret = 0; if (!pdev) return -EINVAL; if (!pdev->dev.parent) pdev->dev.parent = &platform_bus; pdev->dev.bus = &platform_bus_type; if (pdev->id != -1) dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);//如果有id 表示有多个同类设备用pdev->name和pdev->id标识该设备 else dev_set_name(&pdev->dev, "%s", pdev->name); //否则,只用pdev->name标识该设备 for (i = 0; i < pdev->num_resources; i++) { struct resource *p, *r = &pdev->resource[i]; if (r->name == NULL) r->name = dev_name(&pdev->dev); p = r->parent; if (!p) { if (resource_type(r) == IORESOURCE_MEM) p = &iomem_resource;// 作为IOMEM资源分配 else if (resource_type(r) == IORESOURCE_IO) p = &ioport_resource;// 作为IOPORT资源分配 } if (p && insert_resource(p, r)) {//将新的resource插入内核resource tree printk(KERN_ERR "%s: failed to claim resource %d/n", dev_name(&pdev->dev), i); ret = -EBUSY; goto failed; } } pr_debug("Registering platform device '%s'. Parent at %s/n", dev_name(&pdev->dev), dev_name(pdev->dev.parent)); ret = device_add(&pdev->dev);//添加设备到设备树 if (ret == 0) return ret; failed: while (--i >= 0) { struct resource *r = &pdev->resource[i]; unsigned long type = resource_type(r); if (type == IORESOURCE_MEM || type == IORESOURCE_IO) release_resource(r); } return ret; }
2. platform_driver注册过程:
在platform_device注册完成后,就可以进行platform_driver注册了,在驱动初始化函数中调用函数platform_driver_register() 注册platform_driver ,需要注意的是 s3c_device_i2c 结构中name 元素和s3c6410_i2c_driver 结构中driver.name 必须是相同的,这样在platform_driver_register() 注册时会对所有已注册的所有platform_device 中的name 和当前注册的platform_driver 的driver.name 进行比较,只有找到相同的名称的platfomr_device 才能注册成功。
static struct platform_driver s3c24xx_i2c_driver = { .probe = s3c24xx_i2c_probe, .remove = s3c24xx_i2c_remove, .suspend_late = s3c24xx_i2c_suspend_late, .resume = s3c24xx_i2c_resume, .id_table = s3c24xx_driver_ids, .driver = { .owner = THIS_MODULE, .name = "s3c-i2c", }, }; static int __init i2c_adap_s3c_init(void) { return platform_driver_register(&s3c24xx_i2c_driver);//注册IIC驱动 } nt platform_driver_register(struct platform_driver *drv) { drv->driver.bus = &platform_bus_type; if (drv->probe) drv->driver.probe = platform_drv_probe; if (drv->remove) drv->driver.remove = platform_drv_remove; if (drv->shutdown) drv->driver.shutdown = platform_drv_shutdown; return driver_register(&drv->driver); } int driver_register(struct device_driver *drv) { int ret; struct device_driver *other; BUG_ON(!drv->bus->p); if ((drv->bus->probe && drv->probe) || (drv->bus->remove && drv->remove) || (drv->bus->shutdown && drv->shutdown)) printk(KERN_WARNING "Driver '%s' needs updating - please use " "bus_type methods/n", drv->name); other = driver_find(drv->name, drv->bus);//检查Driver是否已经存在 if (other) { put_driver(other); printk(KERN_ERR "Error: Driver '%s' is already registered, " "aborting.../n", drv->name); return -EBUSY; } //若不存在,则添加驱动到驱动树。 ret = bus_add_driver(drv); if (ret) return ret; ret = driver_add_groups(drv, drv->groups); if (ret) bus_remove_driver(drv); return ret; } int bus_add_driver(struct device_driver *drv) { struct bus_type *bus; struct driver_private *priv; int error = 0; bus = bus_get(drv->bus); if (!bus) return -EINVAL; pr_debug("bus: '%s': add driver %s/n", bus->name, drv->name); priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) { error = -ENOMEM; goto out_put_bus; } klist_init(&priv->klist_devices, NULL, NULL); priv->driver = drv; drv->p = priv; priv->kobj.kset = bus->p->drivers_kset; error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, "%s", drv->name); if (error) goto out_unregister; if (drv->bus->p->drivers_autoprobe) { error = driver_attach(drv); if (error) goto out_unregister; } klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); module_add_driver(drv->owner, drv); error = driver_create_file(drv, &driver_attr_uevent); if (error) { printk(KERN_ERR "%s: uevent attr (%s) failed/n", __func__, drv->name); } error = driver_add_attrs(bus, drv); if (error) { /* How the hell do we get out of this pickle? Give up */ printk(KERN_ERR "%s: driver_add_attrs(%s) failed/n", __func__, drv->name); } error = add_bind_files(drv); if (error) { /* Ditto */ printk(KERN_ERR "%s: add_bind_files(%s) failed/n", __func__, drv->name); } kobject_uevent(&priv->kobj, KOBJ_ADD); return 0; out_unregister: kfree(drv->p); drv->p = NULL; kobject_put(&priv->kobj); out_put_bus: bus_put(bus); return error; }