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

来源:互联网 发布:tensorflow安装 32位 编辑:程序博客网 时间:2024/06/05 21:51

在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;}
原创粉丝点击