在Linux2.6以后的設備驅動模型中,需關心總線,設備和驅動這三種實體,總線將設備和驅動綁定。在系統每注冊一個設備的時候,會尋找與之匹配的驅動;相反,在系統每注冊一個驅動的時候,會尋找與之匹配的設備,而匹配由總線完成。?
對于依附在USB、PCI、I2C、SPI等物理總線來 這些都不是問題。但是在嵌入式系統里面,在Soc系統中集成的獨立外設控制器,掛接在Soc內存空間的外設等卻不依附在此類總線。基于這一背景,Linux發明了一種總線,稱為platform。?
相對于USB、PCI、I2C、SPI等物理總線來說,platform總線是一種虛擬、抽象出來的總線,實際中并不存在這樣的總線。?
platform總線相關代碼:driveraseplatform.c 文件?
相關結構體定義:includelinuxplatform_device.h 文件中
platform總線管理下最重要的兩個結構體是platform_device和platform_driver?
分別表示設備和驅動?
在Linux中的定義如下?
一:platform_driver
//includelinuxplatform_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;//設備驅動結構};
1
2
3
4
5
6
7
8
9
10
11
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);};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
二:platform_device
struct platform_device { const char * name;//設備名稱 u32 id;//取-1 struct device dev;//設備結構 u32 num_resources;// resource結構個數 struct resource * resource;//設備資源};
1
2
3
4
5
6
7
resource結構體也是描述platform_device的一個重要結構體 該元素存入了最為重要的設備資源信息
struct resource { resource_size_t start; resource_size_t end; const char *name; unsigned long flags; struct resource *parent, *sibling, *child;};
1
2
3
4
5
6
7
我們通常關心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)
1
2
例如在archarmmach-at91Board-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 }};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在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);
1
2
3
對于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;}
1
2
3
4
5
實際上這個函數也是調用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, }};
1
2
3
4
5
6
7
8
9
10
11
12
13
獲取platform_data的API是dev_get_platdata()
struct dm9000_plat_data *pdata = dev_get_platdata(&pdev->dev);
1
三: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,};
1
2
3
4
5
6
7
最重要的是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);}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
可知有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;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
驅動注冊函數是 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);}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 初始化后調用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 ", drv->name); } klist_init(&drv->klist_devices, NULL, NULL); return bus_add_driver(drv);}
1
2
3
4
5
6
7
8
9
10
11
12
為總線增加一個驅動
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 ", 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 ", __FUNCTION__, drv->name); } error = add_bind_files(drv); if (error) { /* Ditto */ printk(KERN_ERR "%s: add_bind_files(%s) failed ", __FUNCTION__, drv->name); } return error;out_unregister: kobject_unregister(&drv->kobj);out_put_bus: put_bus(bus); return error;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
執行驅動加載
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;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 ", 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 ", 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 ", __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 ", 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 ", 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;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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 "); return 0;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
綜上所述,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 ", pdev->dev.bus_id, i); ret = -EBUSY; goto failed; } } pr_debug("Registering platform device '%s'. Parent at %s ", 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;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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' ", 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;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
遍歷驅動 為設備找到合適的驅動
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;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
但是在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, }};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
之類的注冊platform_device,綁定resource,即內存,irq等板級信息 現在都不再需要 其中platform_device會由內核自動展開。而這些resource實際來源于.dts中設備節點的reg,interrups屬性
再者就是platform_data這些平臺數據屬性化? 比如linux-3.4.2archarmmach-at91Board-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, }};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
設備驅動drivers/input/keyboard/gpio_keys.c通過以下方法取得這個platform_data
struct device *dev = &pdev->dev;const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
1
2
轉移到設備樹后 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>; };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
而在驅動中通過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
"); 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;}
?
評論
查看更多