1.gpio_key.c介绍
1.1 功能介绍
Linux内核下的 drivers/input/keyboard/gpio_keys.c实现了一个体系结构无关的GPIO按键驱动,使用此按键驱动,只需在设备树gpio-key节点添加需要的按键子节点即可。驱动的实现非常简单,但是较适合于实现独立式按键驱动。
1.2 架构介绍
gpio-keys是基于input架构实现的一个通用GPIO按键驱动。该驱动基于platform_driver架构,实现了驱动和设备分离,符合Linux设备驱动模型的思想。本文以自己的理解介绍gpio_keys.c驱动实现原理及代码技巧。
2.设备树
设备树节点转化成device_node.gpio_keys.
c与其他驱动一样采用platform总线架构,在设备树获取硬件相关属性。
Show me the code:
从上述代码可看到,gpio_keys.c节点内定义两个按键节点: “key_power”、”key_headset”。每个按键节点包括一个gpio所用到的所有硬件属性。拿第一个设备节点解析:
key_power {
label = "Power Key"; //按键描述性名称
linux,code =<116>; //键值,即中断触发上报的键值与内核定义的保持一致。
gpios = <&pmic_eic 1="">; //按键gpio &pmic_eic: gpio组由dtsi定义 1: gpio号 1:有效电平
debounce-interval =<5>; //去抖间隔 单位ms
gpio-key,wakeup; //可唤醒系统
gpio-key,level-trigger; //中断触发方式level-trigger: 条件触发 edge-trigger: 边缘触发
};
3.数据结构
优秀的代码必然离不开优秀的数据结构,gpio_key.c之所以能够做到完全脱离板级芯片,实现通用与其数据结构的建立不无关系。以下列举使用到的数据结构:
device_node //设备树节点结构体
device //设备类型
platform_device //platform总线设备
platform_data
input_dev
gpio_keys_platform_data
注:其中device_node、device 、platform_device 、三个结构体,是在驱动编程中经常被用到较为重要的数据结构。
以我自己的理解来解释这三种结构体的关系:
device_node : 用于采集一个设备树节点信息的结构体。因此需要具备设备树属性的成员,例如:硬件设备名(const char *name)、属性指针(struct property *property)、父节点(struct device_node *parent)、子节点(struct device_node *child )等。
device : 指相同类型设备的一种集合结构体。包含了这种类型的所有信息,例如:设备类型(const struct device_type *type;)、该设备基于的总线类型(struct bus_type *bus)、具体的设备树节点信息(struct device_node *of_node)等。
platform_device: 基于platform总线的设备结构体。即挂在虚拟platform总线架构的设备,其包含的成员:设备结构体(struct device dev)、虚拟platform设备名(const char *name)等。
列举以下这三种最常见的结构体,之间的关系:
device_node = device->of_node
device = platform_device->dev
platform_device = of_find_device_by_node(device_node)
由上述关系可知道,只要有一个参数已知,其余两个参数都可以获取到。列举出他们建的关系:
platform_device --> device --> device_node --> platform_device
4.驱动实现
4.1获取设备树属性
按道理,能成功进入probe,就说明设备树platform_device和驱动platform_driver匹配成功,传进来的platform_device *pdev就包含设备树硬件信息。
依然是熟悉的配方!设备节点拿属性。
先从传来的参数里拿设备:
struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
struct device *dev = &pdev->dev;
然后判断有没有数据, pdata若没数据,我就继续拿:
error = gpio_keys_get_devtree_pdata(dev, &alt_pdata);
pdata = &alt_pdata;
看一下 gpio_keys_get_devtree_pdata 里是怎么拿的:
注:容易进入误区的一点,驱动设备树of _xx API并不都是直接从设备树里直接拿到属性的,而是先将设备树节点属性值转化为device_node数据结构体成员值,然后驱动通过of_xx API从device_node结构体中获取到相应属性的值。因此device_node的转化就非常重要了!
通过上述代码,发现还是从传参struct device dev里拿struct device_node node。
(1) 先拿设备节点: node = dev->of_node;(也可以通过of_find_node_by_path(); 或of_find_compatible_node() 拿设备节点。)
然后拿父节点属性值 “input-name”,对应设备树
(2) 再通过of_xx API 拿node对应的属性值。
这里,源码通过遍历检测node下存在的子节点,实现对每一个按键属性初始化。贴出遍历循环宏。
(3) 在遍历里拿每个gpio默认电平。
即设备树中gpios = <&pmic_eic 1 1> 第三个参数。此属性还有另一种写法例如gpios = <&pmic 1 GPIO_ACTIVE_HIGH>;意义是一样的。
of_get_gpio_flags(pp, 0, &flags); 返回gpios项里的第三个有效电平参数。
(4) 在遍历里获取中断号
(5) 然后通过of_property_read_u32 可在device_node拿到指定属性名的属性值。
注:其实在节点属性值获取时,根据属性值的格式都可以选择of_property_xx函数获取,代码里所谓的特定属性获取API大都是对of_property_xx的一层封装,例如
附上驱动遍历获取设备树节点属性值代码:
4.2使用input架构
(1) 申请input设备
input = input_allocate_device();
(2) 填充input结构体成员
(3) 设置GPIO按键
主要负责申请GPIO管脚,设置状态,输出方向,中断申请等
API: gpio_keys_setup_key(pdev, input, bdata, button);
(4) 注册input设备
input_register_device(input);
4.3上报按键事件
按键状态发生变化时,会触发中断,在中断子服务函数中,先通过消抖参数值判断是否消抖,如果消抖就启用定时器上报,若无需消抖就就直接上报按键事件。
中断子服务函数: