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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章