一,gpio_keys.c介绍
Linux内核下的drivers/input/keyboard/gpio_keys.c实现了一个体系无关的GPIO按键驱动,使用此按键驱动,只需要在设备树gpio-key节点添加需要的按键子节点即可,适合于实现独立式按键驱动。
gpio-keys是基于input架构实现的一个通用gpio按键驱动,该驱动基于platform_driver架构,实现了驱动和设备分离,符合linux设备驱动模型的思想。
二,主要结构体及其关系
首先大致看下代码实现搞清楚结构体之间的关系,然后根据结构体之前的关系再看代码细节。
1,主要的结构体
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;
};
2,结构体之间的关系
三,关键代码分析
以Android volumn up key为例。
1,设备树配置
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 = <KEY_VOLUMEUP>;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 = <KEY_SEARCH>;gpio-key,wakeup;debounce-interval = <15>;linux,can-disable;};... ... ...
};
2,
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];//获取每个按键节点childif (!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 descriptionbdata->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 debouncebdata->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;//申请中断,中断服务函数isrerror = 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 eventmod_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);elsedel_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