您的位置:首页 > 运维架构 > Linux

Linux设备驱动模型之platform总线深入浅出(加入设备树)

2017-06-25 17:03 417 查看
在Linux2.6以后的设备驱动模型中,需关心总线,设备和驱动这三种实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。

对于依附在USB、PCI、I2C、SPI等物理总线来 这些都不是问题。但是在嵌入式系统里面,在Soc系统中集成的独立外设控制器,挂接在Soc内存空间的外设等却不依附在此类总线。基于这一背景,Linux发明了一种总线,称为platform。

相对于USB、PCI、I2C、SPI等物理总线来说,platform总线是一种虚拟、抽象出来的总线,实际中并不存在这样的总线。

platform总线相关代码
4000
: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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux