在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;
}