×
嵌入式 > 嵌入式开发 > 详情

Linux设备驱动模型之platform总线深入浅出

发布时间:2020-05-16 发布时间:
|

在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;//设备驱动结构};

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

例如在\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 }};

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\n", 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\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;}

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

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\n"); 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\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;}

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

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

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


『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
ARM入门篇之(一)概念