gpio-keys.c
源码中的gpio-keys驱动(gpio-keys.c)按照input子系统和platform总线编写,驱动文件入口函数为platform_driver_register
,即挂载platform驱动,出口函数为platform_driver_unregister
,卸载platform驱动。
platform_driver
结构体
static struct platform_driver key_driver = {
.probe = key_probe,
.remove = key_remove,
.driver = {
.name = "key",
.of_match_table = of_key_match,
},
};
定义 .probe
入口函数和 .remove
出口函数,定义 .driver
,其中指明了查找设备树的结构体。
of_device_id
结构体
static const struct of_device_id of_key_match[] = {
{.compatible = "key",},
{},
};
通过这个结构体从设备树中找到对应的设备,其中的.compatible
属性应该与设备树中的一致。
probe 函数
platform 总线将驱动和设备分离,其中设备由设备树描述,驱动则通过 probe 函数挂载到 platform 总线上。当进入到这个函数时,说明已经通过 of_device_id 结构体的 .compatible 属性建立起 device 和 driver 之间的连接。
具体的,probe 函数做了以下工作:
- 遍历设备树的各个节点,读取相关数据
- 注册 platform driver
- 注册 input driver
- 创建 sysfs接口
- 注册 input device
为实现上述步骤,建立了四个结构体管理相关数据,具体如下
gpio_keys_button
结构体
这个结构体是描述gpio-key的相关属性的,在遍历设备树后直接将设备树的内容读到了这个结构体内,代码如下:
struct gpio_keys_button {
unsigned int code;
int gpio;
int active_low;
const char *desc;
unsigned int type;
int wakeup;
int debounce_interval;
bool can_disable;
int value;
unsigned int irq;
struct gpio_desc *gpiod;
};
结构体中的各个元素的定义及含义:
unsigned int code
描述按键对应的事件代码,例如 KEY_*,SW_*等。对应的事件在 input.h中定义,置位相关的宏即可完成定义。int gpio
按键对应的 GPIO 标签,一个标签对应一个引脚,可通过设备树更改,如果不支持 gpio 则值为 -1。在代码中,通过 of_get_gpio_flags 函数赋值 gpio 属性。int active_low
电平激励方式,如果为 true 则表示低电平有效,反之高电平有效。const char *desc
设备树中的 label 属性描述的对应信息,在代码中通过 button->desc = of_get_property(pp, “label”, NULL); 实现赋值。unsigned int type
输入事件类型,例如 EV_KEY,EV_SW,EV-ABS等,在 input.h 中定义。int wakeup
设备树中的 wakeup 属性,负责管理管脚休眠唤醒的功能。int debounce_interval
负责按键防抖的延时,单位是ms。在代码中,通过下面的语句赋值,即使能防抖则设定防抖延时时间为5ms。
if (of_property_read_u32(pp, "debounce-interval", &button->debounce_interval)) button->debounce_interval = 5;
bool can_disable
是否允许用户空间通过 sysfs 使能或禁用这个 button。在代码中,通过下面的语句赋值,两次取反的目的是为了把函数的返回值变成 bool 类型。
button->can_disable = !!of_get_property(pp, "linux,can-disable", NULL);
int value
源码注释说是 EV_ABS 的轴值,实际使用作用不详,没有赋值。unsigned int irq
描述这个子节点对应的中断,在源码中通过下面的语句赋值
button->irq = irq_of_parse_and_map(pp, 0);
struct gpio_desc *gpiod
源码注释说是 GPIO 描述符,实际使用作用不详,没有赋值。
gpio_keys_platform_data 结构体
这个结构体的作用是描述挂载到 platform 总线需要的一些参数,具体如下:
struct gpio_keys_platform_data {
struct gpio_keys_button *buttons;
int nbuttons;
unsigned int poll_interval;
unsigned int rep:1;
int (*enable)(struct device *dev);
void (*disable)(struct device *dev);
const char *name;
};
结构体中各个元素的定义:
struct gpio_keys_button *buttons
指向 gpio_keys_button 结构体的指针。由于在设备树中,一个gpio-keys可能包含多个子节点,所以在管理所有的子节点使用结构体数组(指针)表示。int nbuttons
指设备树中的一个设备节点下的设备子节点的个数,同时也是 gpio_keys_button 结构体的个数,还是实际上使用的按键的个数。unsigned int poll_interval poll
机制的轮询时间间隔(ms),在源码中没有使用,作用未知。unsigned int rep:1
使能输入子系统的自动使能,默认为1(使能)。赋值语句如下
key_data->rep = !!of_get_property(node, "autorepeat", NULL);
int (*enable)(struct device *dev)
一个函数指针,函数的参数是 device 指针,返回值是 int,指向启用设备的函数。int (*disable)(struct device *dev)
一个函数指针,函数的参数是 device 指针,返回值是 int,指向禁用设备的函数。const char *name
input设备名称。
gpio_button_data 结构体
这个结构体用来描述具体的用来 probe 的相关数据,其实就是把 gpio_keys_button 结构体进行一个转换(翻译),从而可以直接作为某些函数的参数,这个结构体仍然是描述某一个具体的子节点(某一个按键),而不是描述整个设备(全部按键)。
struct gpio_button_data {
const struct gpio_keys_button *button;
struct input_dev *input;
struct timer_list release_timer;
unsigned int release_delay;
struct delayed_work work;
unsigned int software_debounce;
unsigned int irq;
spinlock_t lock;
bool disabled;
bool key_pressed;
};
结构体中的各个元素定义如下:
const struct gpio_keys_button *button
gpio_keys_button类型的结构体指针struct input_dev *input
input 结构体指针struct timer_list release_timer
定时器结构体,描述用于防抖的定时器。unsigned int release_delay
用于防抖的定时时间,就是gpio_keys_button
结构体的debounce_interval
。在源码中赋值如下
bdata->release_delay = button->debounce_interval;
struct delayed_work work
仍然是与按键防抖相关的,一个延时工作队列。unsigned int software_debounce
仍然与按键防抖相关,也是按键防抖的定时时间,实际上在源码 中这个参数才是真正被使用的防抖定时器延时时间,其值与release_delay
和debounce_interval
相同,赋值语句如下:
if (button->debounce_interval) {
/* 按键防抖相关, 设置 debounce*/
error = gpio_set_debounce(button->gpio, button->debounce_interval * 1000);
if (error < 0) bdata->software_debounce = button->debounce_interval;
}
unsigned int irq
描述中断。spinlock_t lock
自旋锁,防止多线程时资源竞争。bool disabled
描述这个按键(一个设备子节点)是否使能。bool key_pressed
描述这个按键(一个设备子节点)是否被按下。
gpio_keys_drvdata
结构体
这个结构体用来描述 gpio-keys 驱动需要使用的数据(没有也可以,创立的原因我猜是为了作为函数的输入,其实就是上面结构体的组合,描述整个设备)
struct gpio_keys_drvdata {
const struct gpio_keys_platform_data *pdata;
struct input_dev *input;
struct mutex disable_lock;
struct gpio_button_data data[0];
};
结构体中的各个元素定义如下:
const struct gpio_keys_platform_data *pdata
显而易见,gpio_keys_platform_data
结构体。struct input_dev *input
显而易见,input 结构体指针,描述 input 子系统。struct mutex disable_lock
互斥体struct gpio_button_data data[0]
显而易见,gpio_button_data data
数组,描述多个设备子节点。
gpio_keys_get_devtree_pdata
函数,遍历设备树的各个节点,读取相关数据
函数内容如下:
static struct gpio_keys_platform_data *
gpio_keys_get_devtree_pdata(struct device *dev)
{
struct device_node *node, *pp;
struct gpio_keys_platform_data *pdata;
struct gpio_keys_button *button;
int error;
int nbuttons;
int i;
node = dev->of_node;
if (!node)
return ERR_PTR(-ENODEV);
nbuttons = of_get_child_count(node);
if (nbuttons == 0)
return ERR_PTR(-ENODEV);
pdata = devm_kzalloc(dev,
sizeof(*pdata) + nbuttons * sizeof(*button),
GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
pdata->buttons = (struct gpio_keys_button *)(pdata + 1);
pdata->nbuttons = nbuttons;
pdata->rep = !!of_get_property(node, "autorepeat", NULL);
i = 0;
for_each_child_of_node(node, pp) {
enum of_gpio_flags flags;
button = &pdata->buttons[i++];
button->gpio = of_get_gpio_flags(pp, 0, &flags);
if (button->gpio < 0) {
error = button->gpio;
if (error != -ENOENT) {
if (error != -EPROBE_DEFER)
dev_err(dev,
"Failed to get gpio flags, error: %d\n",
error);
return ERR_PTR(error);
}
} else {
button->active_low = flags & OF_GPIO_ACTIVE_LOW;
}
button->irq = irq_of_parse_and_map(pp, 0);
if (!gpio_is_valid(button->gpio) && !button->irq) {
dev_err(dev, "Found button without gpios or irqs\n");
return ERR_PTR(-EINVAL);
}
if (of_property_read_u32(pp, "linux,code", &button->code)) {
dev_err(dev, "Button without keycode: 0x%x\n",
button->gpio);
return ERR_PTR(-EINVAL);
}
button->desc = of_get_property(pp, "label", NULL);
if (of_property_read_u32(pp, "linux,input-type", &button->type))
button->type = EV_KEY;
button->wakeup = !!of_get_property(pp, "gpio-key,wakeup", NULL);
button->can_disable = !!of_get_property(pp, "linux,can-disable", NULL);
if (of_property_read_u32(pp, "debounce-interval",
&button->debounce_interval))
button->debounce_interval = 5;
}
if (pdata->nbuttons == 0)
return ERR_PTR(-EINVAL);
return pdata;
}
这个函数在 gpio_keys_probe
函数最开始的时候调用,作用是遍历根据设备节点compatible
属性找到的节点下的各个子节点,实际上就是遍历所有使用的按键。
struct device_node *node, *pp;
定义了两个设备节点 node
和pp
,其中node
是作为设备节点,pp
是该节点下的子节点。
struct gpio_keys_platform_data *pdata;
定义gpio_keys_platform_data
结构体,该结构体用来描述 platform 总线设备属性,实际上就是自定义的能够描述所有设备子节点的结构体。
struct gpio_keys_button *button;
描述设备子节点的结构体。
int error;
描述错误的变量,作为调用的某些函数的返回值,特别是包含堆或者指针操作的函数,例如kmalloc
。
int nbuttons;
设备子节点的个数、按键的个数或者 gpio_keys_button
结构体的个数(三个实际上含义是一样的)。
node = dev->of_node;
赋值设备节点。dev
是传入的参数,含义是device
结构体指针,描述device
设备。实际上只要能够进入probe
函数,就说明已经找到了设备树中对应的device。device
结构体中的of_node
是一个void类型的指针,与设备树有关,实际上就是描述找到的设备节点。
if (!node)
return ERR_PTR(-ENODEV);
如果没有找到node
,则返回 error。
nbuttons = of_get_child_count(node);
查看这个设备节点下有多少个子节点,然后存储在nbuttons
中。
if (nbuttons == 0)
return ERR_PTR(-ENODEV);
如果没有子节点,说明没有任何按键,返回 error。
pdata = devm_kzalloc(dev,
sizeof(*pdata) + nbuttons * sizeof(*button),
GFP_KERNEL);
为 gpio_keys_platform_data
结构体开辟一块空间,用来存储描述这个设备节点的数据。
if (!pdata)
return ERR_PTR(-ENOMEM);
指针操作一定要判断并返回error。
pdata->buttons = (struct gpio_keys_button *)(pdata + 1);
将pdata +1
的地址空间强制类型转换为struct gpio_keys_button
,然后另pdata->buttons
指针指向这个空间。实际上就是对上面开辟的空间进行定义,pdata + 1
指向的空间就是用来存放*button
的。
pdata->nbuttons = nbuttons;
一个赋值,没什么好说的。
pdata->rep = !!of_get_property(node, "autorepeat", NULL);
这里进行了一个设备树的查询,查询当前设备节点的autorepeat
属性,这个属性顾名思义,就是输入子系统的自动使能功能,显然这个属性是在设备节点中定义,而非设备子节点。
i = 0;
i
是数组的下标,也是遍历设备子节点的个数(第几个),从0开始。
for_each_child_of_node(node, pp)
这个宏顾名思义,就是遍历所有的设备子节点,其中node
是节点,pp
是当前正在遍历的子节点。从这行代码开始,以下都是循环体内的内容。
enum of_gpio_flags flags;
定义一个of_gpio_flags
的枚举,顾名思义,这个枚举一定和设备树有关系。这个枚举的作用应该是找到具体的GPIO的一个地址映射,或者是对GPIO激励方式的一个定义(我猜测),这里应该可以自己改写。这个枚举内仅存了一个OF_GPIO_ACTIVE_LOW
。
button = &pdata->buttons[i++];
一个赋值,后续使用button
就代表pdata->buttons
数组的第i - 1
个元素。
button->gpio = of_get_gpio_flags(pp, 0, &flags);
读设备树gpio
信息。
if (button->gpio < 0) {
error = button->gpio;
if (error != -ENOENT) {
if (error != -EPROBE_DEFER)
dev_err(dev,
"Failed to get gpio flags, error: %d\n",
error);
return ERR_PTR(error);
}
} else {
button->active_low = flags & OF_GPIO_ACTIVE_LOW;
}
如果button->gpio < 0
说明读取错误,读到了就赋值对应的激励方式。
button->irq = irq_of_parse_and_map(pp, 0);
读设备树中的中断描述。
if (!gpio_is_valid(button->gpio) && !button->irq) {
dev_err(dev, "Found button without gpios or irqs\n");
return ERR_PTR(-EINVAL);
}
当gpio_is_valid
函数返回false
且button->irq
为0,返回error。gpio_is_valid
函数顾名思义,应该是一个判断gpio
是否合法的函数,但在源码中直接返回了false
。这句话意思应该是gpio
不合法且中断也没有找到或者没有开启,就返回error。
if (of_property_read_u32(pp, "linux,code", &button->code)) {
dev_err(dev, "Button without keycode: 0x%x\n",
button->gpio);
return ERR_PTR(-EINVAL);
}
读设备树中的按键事件,存储到button->code
。
button->desc = of_get_property(pp, "label", NULL);
将lable
标签的内容存储到button->desc
。
if (of_property_read_u32(pp, "linux,input-type", &button->type))
button->type = EV_KEY;
读type
,没读到就给EV_KEY
的type
。
button->can_disable = !!of_get_property(pp, "linux,can-disable", NULL);
读使能。
if (of_property_read_u32(pp, "debounce-interval",
&button->debounce_interval))
button->debounce_interval = 5;
读防抖,如果没读到就设定5ms的防抖延时时间。
if (pdata->nbuttons == 0)
return ERR_PTR(-EINVAL);
这里退出循环了,再次判断一下子节点的数目。
return pdata;
返回,函数结束。