Linux内核下的drivers/input/keyboard/gpio_keys.c实现了一个体系无关的GPIO按键驱动,使用此按键驱动,只需要在设备树gpio-key节点添加需要的按键子节点即可,适合于实现独立式按键驱动。
gpio-keys是基于input架构实现的一个通用gpio按键驱动,该驱动基于platform_driver架构,实现了驱动和设备分离,符合linux设备驱动模型的思想。
首先大致看下代码实现搞清楚结构体之间的关系,然后根据结构体之前的关系再看代码细节。
struct gpio_keys_drvdata:
struct gpio_keys_drvdata {
const struct gpio_keys_platform_data *pdata;
struct input_dev *input;
struct mutex disable_lock;
unsigned short *keymap;
struct gpio_button_data data[];
};
struct gpio_keys_platform_data:
struct gpio_keys_platform_data {
const 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_button_data:
struct gpio_button_data {
const struct gpio_keys_button *button;
struct input_dev *input;
struct gpio_desc *gpiod;
unsigned short *code;
struct timer_list release_timer;
unsigned int release_delay; /* in msecs, for IRQ-only buttons */
struct delayed_work work;
unsigned int software_debounce; /* in msecs, for GPIO-driven buttons */
unsigned int irq;
unsigned int wakeup_trigger_type;
spinlock_t lock;
bool disabled;
bool key_pressed;
bool suspended;
};
struct gpio_keys_button:
struct gpio_keys_button {
unsigned int code;
int gpio;
int active_low;
const char *desc;
unsigned int type;
int wakeup;
int wakeup_event_action;
int debounce_interval;
bool can_disable;
int value;
unsigned int irq;
};
以Android volumn up key为例。
gpio_keys {
compatible = "gpio-keys";
label = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&key_vol_up_default &google_key_default>;
vol_up {
label = "volume_up";
gpios = <&pm7325_gpios 6 GPIO_ACTIVE_LOW>;
linux,input-type = <1>;
linux,code = ;
gpio-key,wakeup;
debounce-interval = <15>;
linux,can-disable;
};
google_key {
label = "google_key";
gpios = <&tlmm 41 GPIO_ACTIVE_LOW>;
linux,input-type = <1>;
linux,code = ;
gpio-key,wakeup;
debounce-interval = <15>;
linux,can-disable;
};
... ... ...
};
probe函数
static int gpio_keys_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
struct fwnode_handle *child = NULL;
struct gpio_keys_drvdata *ddata;
struct input_dev *input;
int i, error;
int wakeup = 0;
if (!pdata) {
//解析设备树配置
pdata = gpio_keys_get_devtree_pdata(dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
}
//给driver data分配内存,struct_size用来计算gpio_keys_drvdata结构体和nbuttons个gpio_button_data 所占内存大小
ddata = devm_kzalloc(dev, struct_size(ddata, data, pdata->nbuttons),
GFP_KERNEL);
if (!ddata) {
dev_err(dev, "failed to allocate state\n");
return -ENOMEM;
}
dev_err(dev, "william debug gpio keys driver\n");
ddata->keymap = devm_kcalloc(dev,
pdata->nbuttons, sizeof(ddata->keymap[0]),
GFP_KERNEL);
if (!ddata->keymap)
return -ENOMEM;
//分配input设备
input = devm_input_allocate_device(dev);
if (!input) {
dev_err(dev, "failed to allocate input device\n");
return -ENOMEM;
}
ddata->pdata = pdata;
ddata->input = input;
mutex_init(&ddata->disable_lock);
platform_set_drvdata(pdev, ddata);
input_set_drvdata(input, ddata);
input->name = pdata->name ? : pdev->name;
input->phys = "gpio-keys/input0";
input->dev.parent = dev;
input->open = gpio_keys_open;
input->close = gpio_keys_close;
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
input->keycode = ddata->keymap;
input->keycodesize = sizeof(ddata->keymap[0]);
input->keycodemax = pdata->nbuttons;
/* Enable auto repeat feature of Linux input subsystem */
if (pdata->rep)
__set_bit(EV_REP, input->evbit);
//在这个循环里面根据每一个按键的设置申请中断检测按键
for (i = 0; i < pdata->nbuttons; i++) {
const struct gpio_keys_button *button = &pdata->buttons[i];
//获取每个按键节点child
if (!dev_get_platdata(dev)) {
child = device_get_next_child_node(dev, child);
if (!child) {
dev_err(dev,
"missing child device node for entry %d\n",
i);
return -EINVAL;
}
}
error = gpio_keys_setup_key(pdev, input, ddata,
button, i, child);
if (error) {
fwnode_handle_put(child);
return error;
}
if (button->wakeup)
wakeup = 1;
}
fwnode_handle_put(child);
//注册输入设备
error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device, error: %d\n",
error);
return error;
}
device_init_wakeup(dev, wakeup);
return 0;
}
button设置函数gpio_keys_setup_key
static int gpio_keys_setup_key(struct platform_device *pdev,
struct input_dev *input,
struct gpio_keys_drvdata *ddata,
const struct gpio_keys_button *button,
int idx,
struct fwnode_handle *child)
{
const char *desc = button->desc ? button->desc : "gpio_keys";
struct device *dev = &pdev->dev;
//每一个bdata跟button对应,见以上结构体之间的关系
struct gpio_button_data *bdata = &ddata->data[idx];
irq_handler_t isr;
unsigned long irqflags;
int irq;
int error;
bdata->input = input;
bdata->button = button;
spin_lock_init(&bdata->lock);
if (child) {
//如果child节点不空,使用此函数获取gpio description
bdata->gpiod = devm_fwnode_gpiod_get(dev, child,
NULL, GPIOD_IN, desc);
if (IS_ERR(bdata->gpiod)) {
error = PTR_ERR(bdata->gpiod);
if (error == -ENOENT) {
/*
* GPIO is optional, we may be dealing with
* purely interrupt-driven setup.
*/
bdata->gpiod = NULL;
} else {
if (error != -EPROBE_DEFER)
dev_err(dev, "failed to get gpio: %d\n",
error);
return error;
}
}
} else if (gpio_is_valid(button->gpio)) {
/*
* Legacy GPIO number, so request the GPIO here and
* convert it to descriptor.
*/
unsigned flags = GPIOF_IN;
if (button->active_low)
flags |= GPIOF_ACTIVE_LOW;
error = devm_gpio_request_one(dev, button->gpio, flags, desc);
if (error < 0) {
dev_err(dev, "Failed to request GPIO %d, error %d\n",
button->gpio, error);
return error;
}
//将gpio转成gpio description,为了使用新的gpio控制接口,gpiod接口
bdata->gpiod = gpio_to_desc(button->gpio);
if (!bdata->gpiod)
return -EINVAL;
}
if (bdata->gpiod) {
//GPIO_ACTIVE_LOW表示在低电平时触发某种操作,而GPIO_ACTIVE_HIGH表示在高电平时触发相同的操作,将逻辑电平与物理电平区分开
bool active_low = gpiod_is_active_low(bdata->gpiod);
if (button->debounce_interval) {
error = gpiod_set_debounce(bdata->gpiod,
button->debounce_interval * 1000);
/* use timer if gpiolib doesn't provide debounce */
if (error < 0)
//如果对应的gpio chip没有实现debounce的实现,使用software debounce
bdata->software_debounce =
button->debounce_interval;
}
if (button->irq) {
bdata->irq = button->irq;
} else {
irq = gpiod_to_irq(bdata->gpiod);
if (irq < 0) {
error = irq;
dev_err(dev,
"Unable to get irq number for GPIO %d, error %d\n",
button->gpio, error);
return error;
}
bdata->irq = irq;
}
//初始化一个delayed work,作为终端的下半部
INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);
//中断服务函数,中断上半部
isr = gpio_keys_gpio_isr;
//触发中断的电平条件,上升沿或者下降沿触发
irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
switch (button->wakeup_event_action) {
case EV_ACT_ASSERTED:
bdata->wakeup_trigger_type = active_low ?
IRQ_TYPE_EDGE_FALLING : IRQ_TYPE_EDGE_RISING;
break;
case EV_ACT_DEASSERTED:
bdata->wakeup_trigger_type = active_low ?
IRQ_TYPE_EDGE_RISING : IRQ_TYPE_EDGE_FALLING;
break;
case EV_ACT_ANY:
default:
/*
* For other cases, we are OK letting suspend/resume
* not reconfigure the trigger type.
*/
break;
}
} else {
if (!button->irq) {
dev_err(dev, "Found button without gpio or irq\n");
return -EINVAL;
}
bdata->irq = button->irq;
if (button->type && button->type != EV_KEY) {
dev_err(dev, "Only EV_KEY allowed for IRQ buttons.\n");
return -EINVAL;
}
bdata->release_delay = button->debounce_interval;
timer_setup(&bdata->release_timer, gpio_keys_irq_timer, 0);
isr = gpio_keys_irq_isr;
irqflags = 0;
/*
* For IRQ buttons, there is no interrupt for release.
* So we don't need to reconfigure the trigger type for wakeup.
*/
}
bdata->code = &ddata->keymap[idx];
*bdata->code = button->code;
//设置该输入设备的能力,支持上报的事件类型
input_set_capability(input, button->type ?: EV_KEY, *bdata->code);
/*
* Install custom action to cancel release timer and
* workqueue item.
*/
//用做软件防抖?当中断下半部触发之后,如果在debounce time时间之内,gpio口电平有变化,会执行gpio_keys_quiesce_key把上报键值的work cancel掉
error = devm_add_action(dev, gpio_keys_quiesce_key, bdata);
if (error) {
dev_err(dev, "failed to register quiesce action, error: %d\n",
error);
return error;
}
/*
* If platform has specified that the button can be disabled,
* we don't want it to share the interrupt line.
*/
if (!button->can_disable)
irqflags |= IRQF_SHARED;
//申请中断,中断服务函数isr
error = devm_request_any_context_irq(dev, bdata->irq, isr, irqflags,
desc, bdata);
if (error < 0) {
dev_err(dev, "Unable to claim irq %d; error %d\n",
bdata->irq, error);
return error;
}
return 0;
}
中断服务函数isr
static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;
BUG_ON(irq != bdata->irq);
if (bdata->button->wakeup) {
const struct gpio_keys_button *button = bdata->button;
//保持系统awake状态
pm_stay_awake(bdata->input->dev.parent);
if (bdata->suspended &&
(button->type == 0 || button->type == EV_KEY)) {
/*
* Simulate wakeup key press in case the key has
* already released by the time we got interrupt
* handler to run.
*/
input_report_key(bdata->input, button->code, 1);
}
}
//在防抖时间software_debounce之后,调度执行delayed work,在work中上报input event
mod_delayed_work(system_wq,
&bdata->work,
msecs_to_jiffies(bdata->software_debounce));
return IRQ_HANDLED;
}
退出delayed work的函数
static void gpio_keys_quiesce_key(void *data)
{
struct gpio_button_data *bdata = data;
if (bdata->gpiod)
cancel_delayed_work_sync(&bdata->work);
else
del_timer_sync(&bdata->release_timer);
}
volumn up:
[ 611.497258] /dev/input/event0: EV_KEY KEY_VOLUMEUP DOWN
[ 611.497258] /dev/input/event0: EV_SYN SYN_REPORT 00000000 rate 0
[ 611.643337] /dev/input/event0: EV_KEY KEY_VOLUMEUP UP
[ 611.643337] /dev/input/event0: EV_SYN SYN_REPORT 00000000 rate 6
google key:
[ 731.598789] /dev/input/event0: EV_KEY KEY_SEARCH DOWN
[ 731.598789] /dev/input/event0: EV_SYN SYN_REPORT 00000000 rate 0
[ 731.779700] /dev/input/event0: EV_KEY KEY_SEARCH UP
[ 731.779700] /dev/input/event0: EV_SYN SYN_REPORT 00000000 rate 5