1. 程式人生 > >Linux裝置驅動模型之platform匯流排深入淺出(加入裝置樹)

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