Linux裝置驅動模型之platform匯流排深入淺出(加入裝置樹)
在Linux2.6以後的裝置驅動模型中,需關心匯流排,裝置和驅動這三種實體,匯流排將裝置和驅動繫結。在系統每註冊一個裝置的時候,會尋找與之匹配的驅動;相反,在系統每註冊一個驅動的時候,會尋找與之匹配的裝置,而匹配由匯流排完成。
對於依附在USB、PCI、I2C、SPI等物理匯流排來 這些都不是問題。但是在嵌入式系統裡面,在Soc系統中整合的獨立外設控制器,掛接在Soc記憶體空間的外設等卻不依附在此類匯流排。基於這一背景,Linux發明了一種匯流排,稱為platform。
相對於USB、PCI、I2C、SPI等物理匯流排來說,platform匯流排是一種虛擬、抽象出來的匯流排,實際中並不存在這樣的匯流排。
platform匯流排相關程式碼:driver\base\platform.c 檔案
相關結構體定義:include\linux\platform_device.h 檔案中
platform匯流排管理下最重要的兩個結構體是platform_device和platform_driver
分別表示裝置和驅動
在Linux中的定義如下
一:platform_driver
//include\linux\platform_device.h
struct platform_driver {
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;//裝置驅動結構
};
struct device_driver {
const char * name;
struct bus_type * bus;
struct kobject kobj;
struct klist klist_devices;
struct klist_node knode_bus;
struct module * owner;
const char * mod_name; /* used for built-in modules */
struct module_kobject * mkobj;
int (*probe) (struct device * dev);
int (*remove) (struct device * dev);
void (*shutdown) (struct device * dev);
int (*suspend) (struct device * dev, pm_message_t state);
int (*resume) (struct device * dev);
};
二:platform_device
struct platform_device {
const char * name;//裝置名稱
u32 id;//取-1
struct device dev;//裝置結構
u32 num_resources;// resource結構個數
struct resource * resource;//裝置資源
};
resource結構體也是描述platform_device的一個重要結構體 該元素存入了最為重要的裝置資源資訊
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
我們通常關心start,end,flags。它們分別標明瞭資源的開始值,結束值和型別,flags可以為IORESOURCE_IO,IORESOURCE_MEM,IORESOURCE_IRQ,IORESOURCE_DMA等,start,end的含義會隨著flags變更,如當flags為IORESOURCE_MEM時,start,end分別表示該platform_device佔據的記憶體的開始地址和結束地址;當flags為,IORESOURCE_IRQ時,start,end分別表示該platform_device使用的中斷號的開始值和結束值,如果只使用了1箇中斷號,開始和結束值相同。對於同種型別的資源而言,可以有多份,例如某裝置佔據了兩個記憶體區域,則可以定義兩個IORESOURCE_MEM資源。
對resource的定義也通常在BSP的板檔案中執行,而在具體的裝置驅動中通過platform_get_resource()這樣的API來獲取,此API的原型為:
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type,unsigned int num)
例如在\arch\arm\mach-at91\Board-sam9261ek.c板檔案中為DM9000網絡卡定義瞭如下的resource
static struct resource at91sam9261_dm9000_resource[] = {
[0] = {
.start = AT91_CHIPSELECT_2,
.end = AT91_CHIPSELECT_2 + 3,
.flags = IORESOURCE_MEM
},
[1] = {
.start = AT91_CHIPSELECT_2 + 0x44,
.end = AT91_CHIPSELECT_2 + 0xFF,
.flags = IORESOURCE_MEM
},
[2] = {
.start = AT91_PIN_PC11,
.end = AT91_PIN_PC11,
.flags = IORESOURCE_IRQ
}
};
在DM9000網絡卡驅動中則是通過如下辦法拿到這3份資源
db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
對於irq而言platform_get_resource()還有一個進行了封裝的變體platform_get_irq(),
api原型是
int platform_get_irq(struct platform_device *dev, unsigned int num)
{
struct resource *r = platform_get_resource(dev, IORESOURCE_IRQ, num);
return r ? r->start : -ENXIO;
}
實際上這個函式也是呼叫platform_get_resource(dev, IORESOURCE_IRQ, num)來獲取資源
裝置除了可以在BSP中定義資源以外,還可以附加一些資料資訊,因為對裝置的硬體描述除了中斷,記憶體等標準資源以外,可能還會有一些配置資訊,這些配置資訊也依賴於板,不適宜直接放置在裝置驅動上,因此,platform也提供了platform_data的支援,例如對於dm9000
static struct dm9000_plat_data dm9000_platdata = {
.flags = DM9000_PLATF_16BITONLY,
};
static struct platform_device at91sam9261_dm9000_device = {
.name = "dm9000",
.id = 0,
.num_resources = ARRAY_SIZE(at91sam9261_dm9000_resource),
.resource = at91sam9261_dm9000_resource,
.dev = {
.platform_data = &dm9000_platdata,
}
};
獲取platform_data的API是dev_get_platdata()
struct dm9000_plat_data *pdata = dev_get_platdata(&pdev->dev);
三:platform匯流排
系統為platform匯流排定義了一個bus_type的例項platform_bus_type,其定義位於drivers/base/platform.c下
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
最重要的是match函式,真是此成員函式確定了platform_device和platform_driver之間如何匹配的
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
可知有3中情況下platform_device和platform_driver匹配
1.基於裝置樹風格的匹配
2.匹配ID表(即platform_device裝置名是否出現在platform_driver的id表內)
3.匹配platform_device裝置名和驅動的名字
在4.0還有一種情況是基於ACPI風格的匹配
對於裝置驅動的開發
其設計順序為定義 platform_device -> 註冊 platform_device-> 定義 platform_driver-> 註冊 platform_driver 。
這裡選用的例子是q40kbd,在/drivers/input/serio目錄裡。這是一個鍵盤控制器驅動
這裡直接分析初始化函式
static int __init q40kbd_init(void)
{
int error;
if (!MACH_IS_Q40)
return -EIO;
/* platform匯流排驅動的註冊 */
error = platform_driver_register(&q40kbd_driver);
if (error)
return error;
/* 分配一個platform裝置 */
q40kbd_device = platform_device_alloc("q40kbd", -1);
if (!q40kbd_device)
goto err_unregister_driver;
/* platform設備註冊 */
error = platform_device_add(q40kbd_device);
if (error)
goto err_free_device;
return 0;
err_free_device:
platform_device_put(q40kbd_device);
err_unregister_driver:
platform_driver_unregister(&q40kbd_driver);
return error;
}
驅動註冊函式是 platform_driver_register()函式
int platform_driver_register(struct platform_driver *drv)
{
//把驅動的匯流排設定為platform匯流排
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;
if (drv->suspend)
drv->driver.suspend = platform_drv_suspend;
if (drv->resume)
drv->driver.resume = platform_drv_resume;
//呼叫driver_register註冊驅動
return driver_register(&drv->driver);
}
/* 初始化後呼叫bus_add_driver函式執行註冊過程 */
int driver_register(struct device_driver * drv)
{
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);
}
klist_init(&drv->klist_devices, NULL, NULL);
return bus_add_driver(drv);
}
為匯流排增加一個驅動
int bus_add_driver(struct device_driver *drv)
{
struct bus_type * bus = get_bus(drv->bus);
int error = 0;
if (!bus)
return -EINVAL;
pr_debug("bus %s: add driver %s\n", bus->name, drv->name);
/* 為kobject結構設定名字 */
error = kobject_set_name(&drv->kobj, "%s", drv->name);
if (error)
goto out_put_bus;
drv->kobj.kset = &bus->drivers;
/* 為sysfs檔案系統建立裝置的相關檔案 */
if ((error = kobject_register(&drv->kobj)))
goto out_put_bus;
if (drv->bus->drivers_autoprobe) {
/* 驅動加入匯流排的驅動列表 */
error = driver_attach(drv);
if (error)
goto out_unregister;
}
/* 在sysfs建立驅動的屬性檔案和模組 */
klist_add_tail(&drv->knode_bus, &bus->klist_drivers);
module_add_driver(drv->owner, drv);
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",
__FUNCTION__, drv->name);
}
error = add_bind_files(drv);
if (error) {
/* Ditto */
printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
__FUNCTION__, drv->name);
}
return error;
out_unregister:
kobject_unregister(&drv->kobj);
out_put_bus:
put_bus(bus);
return error;
}
執行驅動載入
int driver_attach(struct device_driver * drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
int bus_for_each_dev(struct bus_type * bus, struct device * start,
void * data, int (*fn)(struct device *, void *))
{
struct klist_iter i;
struct device * dev;
int error = 0;
if (!bus)
return -EINVAL;
/* 初始化一個klist_iter結構 目的是在雙向連結串列中定位一個成員 */
klist_iter_init_node(&bus->klist_devices, &i,
(start ? &start->knode_bus : NULL));
while ((dev = next_device(&i)) && !error)
//對每個裝置都呼叫fn函式指標 fn就是傳入的函式指標__driver_attach
error = fn(dev, data);
klist_iter_exit(&i);
return error;
}
static int __driver_attach(struct device * dev, void * data)
{
struct device_driver * drv = data;
/*
* Lock device and try to bind to it. We drop the error
* here and always return 0, because we need to keep trying
* to bind to devices and some drivers will return an error
* simply if it didn't support the device.
*
* driver_probe_device() will spit a warning if there
* is an error.
*/
if (dev->parent) /* Needed for USB */
down(&dev->parent->sem);
/* 獲取裝置的鎖 */
down(&dev->sem);
if (!dev->driver)
driver_probe_device(drv, dev);
up(&dev->sem);
if (dev->parent)
up(&dev->parent->sem);
return 0;
}
int driver_probe_device(struct device_driver * drv, struct device * dev)
{
int ret = 0;
if (!device_is_registered(dev))
return -ENODEV;
/* 呼叫匯流排配置的match函式 */
if (drv->bus->match && !drv->bus->match(dev, drv))
goto done;
pr_debug("%s: Matched Device %s with Driver %s\n",
drv->bus->name, dev->bus_id, drv->name);
/* 呼叫really_probe函式 */
ret = really_probe(dev, drv);
done:
return ret;
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = 0;
atomic_inc(&probe_count);
pr_debug("%s: Probing driver %s with device %s\n",
drv->bus->name, drv->name, dev->bus_id);
WARN_ON(!list_empty(&dev->devres_head));
dev->driver = drv;
if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__FUNCTION__, dev->bus_id);
goto probe_failed;
}
/* 呼叫匯流排的probe函式 */
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
/* 如果驅動提供了Probe函式,則呼叫驅動的probe函式 */
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
/* 裝置發現了驅動 通過sysfs建立一些檔案 和裝置做符號連結 */
driver_bound(dev);
ret = 1;
pr_debug("%s: Bound Device %s to Driver %s\n",
drv->bus->name, dev->bus_id, drv->name);
goto done;
probe_failed:
devres_release_all(dev);
driver_sysfs_remove(dev);
dev->driver = NULL;
if (ret != -ENODEV && ret != -ENXIO) {
/* driver matched but the probe failed */
printk(KERN_WARNING
"%s: probe of %s failed with error %d\n",
drv->name, dev->bus_id, ret);
}
/*
* Ignore errors returned by ->probe so that the next driver can try
* its luck.
*/
ret = 0;
done:
atomic_dec(&probe_count);
wake_up(&probe_waitqueue);
return ret;
}
driver_probe_device() 這個函式實際上做了兩件事
1.呼叫匯流排提供的match函式。如果檢查通過 說明裝置和驅動是匹配的 裝置所指的驅動指標要賦值為當前驅動
2.探測(probe) 首先呼叫匯流排提供的probe函式,如果驅動有自己的Probe函式,還要呼叫驅動的probe函式
platform匯流排的match函式在上文有介紹 三種情況均可匹配
platform匯流排的probe函式就是platform_drv_probe() 這個函式是個封裝函式 它只是簡單的呼叫了驅動的Probe函式 驅動的Probe函式就是q40kbd_probe
static int __devinit q40kbd_probe(struct platform_device *dev)
{
q40kbd_port = kzalloc(sizeof(struct serio), GFP_KERNEL);
if (!q40kbd_port)
return -ENOMEM;
q40kbd_port->id.type = SERIO_8042;
q40kbd_port->open = q40kbd_open;
q40kbd_port->close = q40kbd_close;
q40kbd_port->dev.parent = &dev->dev;
strlcpy(q40kbd_port->name, "Q40 Kbd Port", sizeof(q40kbd_port->name));
strlcpy(q40kbd_port->phys, "Q40", sizeof(q40kbd_port->phys));
//註冊serio結構變數
serio_register_port(q40kbd_port);
printk(KERN_INFO "serio: Q40 kbd registered\n");
return 0;
}
綜上所述,platform匯流排驅動註冊就是遍歷各個裝置 檢查是否和驅動匹配
接下來分析platform裝置的註冊
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;
/* 設定裝置的匯流排型別為platform_bus_type */
pdev->dev.bus = &platform_bus_type;
if (pdev->id != -1)
snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%u", pdev->name, pdev->id);
else
strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);
/* 把裝置IO埠和IO記憶體資源註冊到系統 */
for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i];
if (r->name == NULL)
r->name = pdev->dev.bus_id;
p = r->parent;
if (!p) {
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;
}
int device_add(struct device *dev)
{
struct device *parent = NULL;
char *class_name = NULL;
struct class_interface *class_intf;
int error = -EINVAL;
dev = get_device(dev);
if (!dev || !strlen(dev->bus_id))
goto Error;
pr_debug("DEV: registering device: ID = '%s'\n", dev->bus_id);
/* 獲得父裝置 */
parent = get_device(dev->parent);
error = setup_parent(dev, parent);
if (error)
goto Error;
/* first, register with generic layer. */
/* 這是kobject的名字 */
kobject_set_name(&dev->kobj, "%s", dev->bus_id);
/* 在sys目錄生成裝置目錄 */
error = kobject_add(&dev->kobj);
if (error)
goto Error;
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
/* notify clients of device entry (new way) */
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);
/* 設定uevent屬性 */
dev->uevent_attr.attr.name = "uevent";
dev->uevent_attr.attr.mode = S_IRUGO | S_IWUSR;
if (dev->driver)
dev->uevent_attr.attr.owner = dev->driver->owner;
dev->uevent_attr.store = store_uevent;
dev->uevent_attr.show = show_uevent;
error = device_create_file(dev, &dev->uevent_attr);
if (error)
goto attrError;
/* 裝置的屬性檔案 */
if (MAJOR(dev->devt)) {
struct device_attribute *attr;
attr = kzalloc(sizeof(*attr), GFP_KERNEL);
if (!attr) {
error = -ENOMEM;
goto ueventattrError;
}
attr->attr.name = "dev";
attr->attr.mode = S_IRUGO;
if (dev->driver)
attr->attr.owner = dev->driver->owner;
attr->show = show_dev;
error = device_create_file(dev, attr);
if (error) {
kfree(attr);
goto ueventattrError;
}
dev->devt_attr = attr;
}
/* 建立裝置類的符號連結 */
if (dev->class) {
sysfs_create_link(&dev->kobj, &dev->class->subsys.kobj,
"subsystem");
/* If this is not a "fake" compatible device, then create the
* symlink from the class to the device. */
if (dev->kobj.parent != &dev->class->subsys.kobj)
sysfs_create_link(&dev->class->subsys.kobj,
&dev->kobj, dev->bus_id);
if (parent) {
sysfs_create_link(&dev->kobj, &dev->parent->kobj,
"device");
#ifdef CONFIG_SYSFS_DEPRECATED
class_name = make_class_name(dev->class->name,
&dev->kobj);
if (class_name)
sysfs_create_link(&dev->parent->kobj,
&dev->kobj, class_name);
#endif
}
}
/* 裝置的能源管理 */
if ((error = device_add_attrs(dev)))
goto AttrsError;
if ((error = device_pm_add(dev)))
goto PMError;
if ((error = bus_add_device(dev)))
goto BusError;
kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_attach_device(dev);
/* 裝置加入父裝置的連結串列 */
if (parent)
klist_add_tail(&dev->knode_parent, &parent->klist_children);
if (dev->class) {
down(&dev->class->sem);
/* tie the class to the device */
list_add_tail(&dev->node, &dev->class->devices);
/* notify any interfaces that the device is here */
list_for_each_entry(class_intf, &dev->class->interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
up(&dev->class->sem);
}
Done:
kfree(class_name);
put_device(dev);
return error;
BusError:
device_pm_remove(dev);
PMError:
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->bus_notifier,
BUS_NOTIFY_DEL_DEVICE, dev);
device_remove_attrs(dev);
AttrsError:
if (dev->devt_attr) {
device_remove_file(dev, dev->devt_attr);
kfree(dev->devt_attr);
}
if (dev->class) {
sysfs_remove_link(&dev->kobj, "subsystem");
/* If this is not a "fake" compatible device, remove the
* symlink from the class to the device. */
if (dev->kobj.parent != &dev->class->subsys.kobj)
sysfs_remove_link(&dev->class->subsys.kobj,
dev->bus_id);
if (parent) {
#ifdef CONFIG_SYSFS_DEPRECATED
char *class_name = make_class_name(dev->class->name,
&dev->kobj);
if (class_name)
sysfs_remove_link(&dev->parent->kobj,
class_name);
kfree(class_name);
#endif
sysfs_remove_link(&dev->kobj, "device");
}
}
ueventattrError:
device_remove_file(dev, &dev->uevent_attr);
attrError:
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
kobject_del(&dev->kobj);
Error:
if (parent)
put_device(parent);
goto Done;
}
遍歷驅動 為裝置找到合適的驅動
int device_attach(struct device * dev)
{
int ret = 0;
down(&dev->sem);
if (dev->driver) {
ret = device_bind_driver(dev);
if (ret == 0)
ret = 1;
else {
dev->driver = NULL;
ret = 0;
}
} else {
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
}
up(&dev->sem);
return ret;
}
但是在Linux加入了裝置樹的時候 就不再需要大量的板級資訊了 譬如/arch/arm/plat-xxx和arch/arm/mach-xxx中platform的修改部分
原先的
static struct resource xxx_resource[] = {
[0] = {
.start = ...,
.end = ...,
.flags = ...,
},
[1] = {
.start = ...,
.end = ...,
.flags = ...,
},
[2] = {
.start =...,
.end = ...,
.flags = ...,
}
};
static struct platform_device xxx_device = {
.name = "xxx",
.id = -1,
.num_resources = ARRAY_SIZE(xxx_resource),
.resource = xxx_resource,
.dev = {
.platform_data = &xxx_platdata,
}
};
之類的註冊platform_device,繫結resource,即記憶體,irq等板級資訊 現在都不再需要 其中platform_device會由核心自動展開。而這些resource實際來源於.dts中裝置節點的reg,interrups屬性
再者就是platform_data這些平臺數據屬性化
比如\linux-3.4.2\arch\arm\mach-at91\Board-sam9263ek.c下用如下程式碼註冊gpio_keys裝置 通過gpio_keys_platform_data來定義platform_data
static struct gpio_keys_button ek_buttons[] = {
{ /* BP1, "leftclic" */
.code = BTN_LEFT,
.gpio = AT91_PIN_PC5,
.active_low = 1,
.desc = "left_click",
.wakeup = 1,
},
{ /* BP2, "rightclic" */
.code = BTN_RIGHT,
.gpio = AT91_PIN_PC4,
.active_low = 1,
.desc = "right_click",
.wakeup = 1,
}
};
static struct gpio_keys_platform_data ek_button_data = {
.buttons = ek_buttons,
.nbuttons = ARRAY_SIZE(ek_buttons),
};
static struct platform_device ek_button_device = {
.name = "gpio-keys",
.id = -1,
.num_resources = 0,
.dev = {
.platform_data = &ek_button_data,
}
};
裝置驅動drivers/input/keyboard/gpio_keys.c通過以下方法取得這個platform_data
struct device *dev = &pdev->dev;
const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
轉移到裝置樹後 platform_data就不再arch/arm/mach-xxx中 它需要從裝置樹中獲取,比如一個電路板上有gpio_keys 則只需要在裝置樹中新增類似arch/arm/boot/dts/exyons4210-origen.dts的程式碼
gpio_keys {
compatible = "gpio-keys";
#address-cells = <1>;
#size-cells = <0>;
up {
label = "Up";
gpios = <&gpx2 0 0 0 2>;
linux,code = <103>;
};
down {
label = "Down";
gpios = <&gpx2 1 0 0 2>;
linux,code = <108>;
};
而在驅動中通過of_開頭的讀屬性的API來讀取這些資訊 並組織處gpio_keys_platform_data結構體
gpio_keys_get_devtree_pdata(struct device *dev)
{
struct gpio_keys_platform_data *pdata;
struct gpio_keys_button *button;
struct fwnode_handle *child;
int nbuttons;
nbuttons = device_get_child_node_count(dev);
if (nbuttons == 0)
return ERR_PTR(-ENODEV);
pdata = devm_kzalloc(dev,
sizeof(*pdata) + nbuttons * sizeof(*button),
GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
button = (struct gpio_keys_button *)(pdata + 1);
pdata->buttons = button;
pdata->nbuttons = nbuttons;
pdata->rep = device_property_read_bool(dev, "autorepeat");
device_property_read_string(dev, "label", &pdata->name);
device_for_each_child_node(dev, child) {
if (is_of_node(child))
button->irq =
irq_of_parse_and_map(to_of_node(child), 0);
if (fwnode_property_read_u32(child, "linux,code",
&button->code)) {
dev_err(dev, "Button without keycode\n");
fwnode_handle_put(child);
return ERR_PTR(-EINVAL);
}
fwnode_property_read_string(child, "label", &button->desc);
if (fwnode_property_read_u32(child, "linux,input-type",
&button->type))
button->type = EV_KEY;
button->wakeup =
fwnode_property_read_bool(child, "wakeup-source") ||
/* legacy name */
fwnode_property_read_bool(child, "gpio-key,wakeup");
button->can_disable =
fwnode_property_read_bool(child, "linux,can-disable");
if (fwnode_property_read_u32(child, "debounce-interval",
&button->debounce_interval))
button->debounce_interval = 5;
button++;
}
return pdata;
}