從 Linux 2.6 起引入了一套新的驅動管理和註冊機制 :Platform_device 和 Platform_driver 。node
Linux 中大部分的設備驅動,均可以使用這套機制 , 設備用 Platform_device 表示,驅動用 Platform_driver 進行註冊。linux
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 。spa
一、platform_device註冊過程:debug
首先要確認的就是設備的資源信息,例如設備的地址,中斷號等。orm
在 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; // 做爲 IO PORT資源分配
}
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;
}
二、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驅動
}
int 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) {
printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",
__func__, drv->name);
}
error = add_bind_files(drv);
if (error) {
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;}