Level 6 总结与扩展:逐函数拆解 GPIO 按键驱动 本文是 level-6-按键.md 的延伸,以 Amlogic gpio_keypad.c(557 行)为样本,按照代码的物理顺序,逐函数解释它干了什么。
读完本文你会:能自己读懂一个陌生驱动,知道每个函数在做什么。
驱动源文件:common/common_drivers/drivers/input/keyboard/gpio_keypad.c
代码总览(完整源码) 以下是 gpio_keypad.c 的完整代码。每个函数后面跟着逐行解释。
// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2019 Amlogic, Inc. All rights reserved. */ #include <linux/module.h> #include <linux/init.h> #include <linux/types.h> #include <linux/string.h> #include <linux/kstrtox.h> #include <linux/input.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/gpio/consumer.h> #include <linux/interrupt.h> #include <linux/slab.h> #include <linux/amlogic/pm.h> #include <linux/irq.h> #include <linux/amlogic/power_domain.h> #include <linux/amlogic/gpiolib.h> #define DEFAULT_SCAN_PERION 20 #define DEFAULT_POLL_MODE 0 #define KEY_JITTER_COUNT 1 struct pin_desc { int current_status; struct gpio_desc *desc; int irq_num; u32 code; u32 key_type; const char *name; int count; bool ignore; }; struct gpio_keypad { int key_size; int use_irq;/* 1:irq mode ; 0:polling mode */ int scan_period; struct pin_desc *key; struct pin_desc *current_key; struct timer_list polling_timer; struct input_dev *input_dev; struct class kp_class; struct class resetkey_class; unsigned int reset_count; }; static irqreturn_t gpio_irq_handler(int irq, void *data) { struct gpio_keypad *keypad; keypad = (struct gpio_keypad *)data; mod_timer(&keypad->polling_timer, jiffies + msecs_to_jiffies(20)); return IRQ_HANDLED; } static void report_key_code(struct gpio_keypad *keypad, int gpio_val) { struct pin_desc *key = keypad->current_key; if (key->count >= KEY_JITTER_COUNT) { key->current_status = gpio_val; if (key->current_status) { input_event(keypad->input_dev, key->key_type, key->code, 0); dev_dbg(&keypad->input_dev->dev, "key %d up.\n", key->code); } else { input_event(keypad->input_dev, key->key_type, key->code, 1); // Count key press if (strcmp(key->name, "bluetooth") == 0) { keypad->reset_count++; } dev_dbg(&keypad->input_dev->dev, "key %d down.\n", key->code); } input_sync(keypad->input_dev); key->count = 0; } } static void polling_timer_handler(struct timer_list *t) { int i; int gpio_val; int is_pressing = 0; struct gpio_keypad *keypad = from_timer(keypad, t, polling_timer); if (keypad->use_irq) {/* irq mode */ for (i = 0; i < keypad->key_size; i++) { gpio_val = gpiod_get_value(keypad->key[i].desc); gpio_val |= keypad->key[i].ignore; if (keypad->key[i].current_status != gpio_val) { keypad->key[i].count++; keypad->current_key = &keypad->key[i]; report_key_code(keypad, gpio_val); } else { keypad->key[i].count = 0; } if (gpio_val == 0 || keypad->key[i].current_status == 0) is_pressing = 1; } if (is_pressing) mod_timer(&keypad->polling_timer, jiffies + msecs_to_jiffies(keypad->scan_period)); } else {/* polling mode */ for (i = 0; i < keypad->key_size; i++) { gpio_val = gpiod_get_value(keypad->key[i].desc); gpio_val |= keypad->key[i].ignore; if (keypad->key[i].current_status != gpio_val) { keypad->key[i].count++; keypad->current_key = &keypad->key[i]; report_key_code(keypad, gpio_val); } else { keypad->key[i].count = 0; } } mod_timer(&keypad->polling_timer, jiffies + msecs_to_jiffies(keypad->scan_period)); } } static ssize_t table_show(struct class *cls, struct class_attribute *attr, char *buf) { struct gpio_keypad *keypad = container_of(cls, struct gpio_keypad, kp_class); int i; int len = 0; for (i = 0; i < keypad->key_size; i++) { len += sprintf(buf + len, "[%d]: name = %-21s status = %-5d\n", i, keypad->key[i].name, keypad->key[i].current_status); } return len; } static ssize_t ignore_show(struct class *cls, struct class_attribute *attr, char *buf) { struct gpio_keypad *keypad = container_of(cls, struct gpio_keypad, kp_class); int index; int len = 0; for (index = 0; index < keypad->key_size; index++) { len += sysfs_emit_at(buf, len, "%s,%d: %c\n", keypad->key[index].name, keypad->key[index].code, keypad->key[index].ignore ? 'Y' : 'N'); } return len; } static ssize_t ignore_store(struct class *cls, struct class_attribute *attr, const char *buf, size_t count) { struct gpio_keypad *keypad = container_of(cls, struct gpio_keypad, kp_class); struct device *dev = keypad->input_dev->dev.parent; char nbuf[128]; char tmp[32]; int index; char *value = nbuf; char *name; bool ignore; bool found; if (count > sizeof(nbuf)) { dev_err(dev, "data is too long\n"); return -EINVAL; } memcpy(nbuf, buf, count); strreplace(nbuf, '\n', '\0'); name = strsep(&value, " "); if (!name) goto err; if (!strcmp("all", name)) { for (index = 0; index < keypad->key_size; index++) keypad->key[index].ignore = true; dev_dbg(dev, "ignore all keys\n"); } else if (!strcmp("none", name)) { for (index = 0; index < keypad->key_size; index++) keypad->key[index].ignore = false; dev_dbg(dev, "enable all keys\n"); } else { if (!value) goto err; if (kstrtobool(value, &ignore)) { dev_err(dev, "invalid '%s', please use Y/N instead\n", value); return -EINVAL; } /* search key */ found = false; for (index = 0; index < keypad->key_size; index++) { snprintf(tmp, sizeof(tmp), "%s,%d", keypad->key[index].name, keypad->key[index].code); if (!strcmp(tmp, name)) { found = true; break; } } if (!found) { dev_err(dev, "invalid key '%s'\n", name); return -EINVAL; } keypad->key[index].ignore = ignore; dev_dbg(dev, "ignore %s: %s\n", name, value); } return count; err: dev_err(dev, "the correct format is: [name],[code] [ignore]\n"); return -EINVAL; } static CLASS_ATTR_RO(table); static CLASS_ATTR_RW(ignore); static struct attribute *meson_gpiokey_attrs[] = { &class_attr_table.attr, &class_attr_ignore.attr, NULL }; ATTRIBUTE_GROUPS(meson_gpiokey); // Add show function for reset count static ssize_t count_show(struct class *cls, struct class_attribute *attr, char *buf) { struct gpio_keypad *keypad = container_of(cls, struct gpio_keypad, resetkey_class); return sprintf(buf, "%u\n", keypad->reset_count); } static CLASS_ATTR_RO(count); static struct attribute *resetkey_attrs[] = { &class_attr_count.attr, NULL }; ATTRIBUTE_GROUPS(resetkey); static int meson_gpio_kp_probe(struct platform_device *pdev) { struct gpio_desc *desc; int ret, i; struct input_dev *input_dev; struct gpio_keypad *keypad; struct irq_desc *d; unsigned int number; int wakeup_source = 0; int register_flag = 0; if (!(pdev->dev.of_node)) { dev_err(&pdev->dev, "pdev->dev.of_node == NULL!\n"); return -EINVAL; } keypad = devm_kzalloc(&pdev->dev, sizeof(struct gpio_keypad), GFP_KERNEL); if (!keypad) return -EINVAL; ret = of_property_read_u32(pdev->dev.of_node, "detect_mode", &keypad->use_irq); if (ret) /* The default mode is polling. */ keypad->use_irq = DEFAULT_POLL_MODE; ret = of_property_read_u32(pdev->dev.of_node, "scan_period", &keypad->scan_period); if (ret) /* he default scan period is 20. */ keypad->scan_period = DEFAULT_SCAN_PERION; if (of_property_read_bool(pdev->dev.of_node, "wakeup-source")) wakeup_source = 1; ret = of_property_read_u32(pdev->dev.of_node, "key_num", &keypad->key_size); if (ret) { dev_err(&pdev->dev, "failed to get key_num!\n"); return -EINVAL; } keypad->key = devm_kzalloc(&pdev->dev, (keypad->key_size) * sizeof(*keypad->key), GFP_KERNEL); if (!(keypad->key)) return -EINVAL; for (i = 0; i < keypad->key_size; i++) { /* get all gpio desc. */ desc = devm_gpiod_get_index(&pdev->dev, "key", i, GPIOD_IN); if (IS_ERR_OR_NULL(desc)) return -EINVAL; keypad->key[i].desc = desc; /* The gpio default is high level. */ keypad->key[i].current_status = 1; ret = of_property_read_u32_index(pdev->dev.of_node, "key_code", i, &keypad->key[i].code); if (ret < 0) { dev_err(&pdev->dev, "find key_code=%d finished\n", i); return -EINVAL; } ret = of_property_read_u32_index(pdev->dev.of_node, "key_type", i, &keypad->key[i].key_type); if (ret) keypad->key[i].key_type = EV_KEY; ret = of_property_read_string_index(pdev->dev.of_node, "key_name", i, &keypad->key[i].name); if (ret < 0) { dev_err(&pdev->dev, "find key_name=%d finished\n", i); return -EINVAL; } gpiod_direction_input(keypad->key[i].desc); gpiod_set_pull(keypad->key[i].desc, GPIOD_PULL_UP); } keypad->kp_class.name = "gpio_keypad"; keypad->kp_class.owner = THIS_MODULE; keypad->kp_class.class_groups = meson_gpiokey_groups; ret = class_register(&keypad->kp_class); if (ret) { dev_err(&pdev->dev, "fail to create gpio keypad class.\n"); return -EINVAL; } // After registering kp_class, add resetkey class registration keypad->resetkey_class.name = "resetkey"; keypad->resetkey_class.owner = THIS_MODULE; keypad->resetkey_class.class_groups = resetkey_groups; keypad->reset_count = 0; ret = class_register(&keypad->resetkey_class); if (ret) { dev_err(&pdev->dev, "fail to create resetkey class.\n"); class_unregister(&keypad->kp_class); return -EINVAL; } /* input */ input_dev = input_allocate_device(); if (!input_dev) return -EINVAL; for (i = 0; i < keypad->key_size; i++) { input_set_capability(input_dev, keypad->key[i].key_type, keypad->key[i].code); dev_dbg(&pdev->dev, "%s key(%d) type(0x%x) registered.\n", keypad->key[i].name, keypad->key[i].code, keypad->key[i].key_type); } input_dev->name = "gpio_keypad"; input_dev->phys = "gpio_keypad/input0"; input_dev->dev.parent = &pdev->dev; input_dev->id.bustype = BUS_ISA; input_dev->id.vendor = 0x0001; input_dev->id.product = 0x0001; input_dev->id.version = 0x0100; input_dev->rep[REP_DELAY] = 0xffffffff; input_dev->rep[REP_PERIOD] = 0xffffffff; input_dev->keycodesize = sizeof(unsigned short); input_dev->keycodemax = 0x1ff; keypad->input_dev = input_dev; ret = input_register_device(keypad->input_dev); if (ret < 0) { input_free_device(keypad->input_dev); return -EINVAL; } platform_set_drvdata(pdev, keypad); timer_setup(&keypad->polling_timer, polling_timer_handler, 0); if (keypad->use_irq) { for (i = 0; i < keypad->key_size; i++) { keypad->key[i].count = 0; keypad->key[i].irq_num = gpiod_to_irq(keypad->key[i].desc); ret = devm_request_irq(&pdev->dev, keypad->key[i].irq_num, gpio_irq_handler, IRQF_TRIGGER_FALLING, "gpio_keypad", keypad); if (ret) { dev_err(&pdev->dev, "Requesting irq failed!\n"); input_free_device(keypad->input_dev); del_timer(&keypad->polling_timer); return -EINVAL; } if (keypad->key[i].code == KEY_POWER) { d = irq_to_desc(keypad->key[i].irq_num); if (d) { number = d->irq_data.parent_data->hwirq - 32; pwr_ctrl_irq_set(number, 1, 0); register_flag = 1; } } } } else { mod_timer(&keypad->polling_timer, jiffies + msecs_to_jiffies(keypad->scan_period)); } if (wakeup_source) { if (register_flag) dev_dbg(&pdev->dev, "succeed to register wakeup source!\n"); else dev_err(&pdev->dev, "failed to register wakeup source!\n"); } return 0; } static int meson_gpio_kp_remove(struct platform_device *pdev) { struct gpio_keypad *keypad; keypad = platform_get_drvdata(pdev); class_unregister(&keypad->resetkey_class); class_unregister(&keypad->kp_class); input_unregister_device(keypad->input_dev); input_free_device(keypad->input_dev); del_timer(&keypad->polling_timer); return 0; } static const struct of_device_id key_dt_match[] = { { .compatible = "amlogic, gpio_keypad", }, {} }; static int meson_gpio_kp_suspend(struct device *dev) { struct gpio_keypad *pdata; pdata = (struct gpio_keypad *)dev_get_drvdata(dev); if (!pdata->use_irq) del_timer(&pdata->polling_timer); return 0; } static int meson_gpio_kp_resume(struct device *dev) { int i; struct gpio_keypad *pdata; pdata = (struct gpio_keypad *)dev_get_drvdata(dev); if (!pdata->use_irq) mod_timer(&pdata->polling_timer, jiffies + msecs_to_jiffies(5)); if (get_resume_method() == POWER_KEY_WAKEUP) { for (i = 0; i < pdata->key_size; i++) { if (pdata->key[i].code == KEY_POWER) { dev_dbg(dev, "gpio keypad wakeup\n"); input_report_key(pdata->input_dev, KEY_POWER, 1); input_sync(pdata->input_dev); input_report_key(pdata->input_dev, KEY_POWER, 0); input_sync(pdata->input_dev); break; } } } return 0; } static int meson_gpio_kp_restore(struct device *dev) { struct gpio_keypad *keypad = dev_get_drvdata(dev); struct gpio_desc *desc; int index; for (index = 0; index < keypad->key_size; index++) { desc = devm_gpiod_get_index(dev, "key", index, GPIOD_IN); if (IS_ERR_OR_NULL(desc)) { dev_err(dev, "failed to request to gpio while restore\n"); return -EINVAL; } gpiod_direction_input(desc); gpiod_set_pull(desc, GPIOD_PULL_UP); keypad->key[index].desc = desc; } return meson_gpio_kp_resume(dev); } static int meson_gpio_kp_freeze(struct device *dev) { struct gpio_keypad *keypad = dev_get_drvdata(dev); int index; int ret; ret = meson_gpio_kp_suspend(dev); if (ret) return ret; for (index = 0; index < keypad->key_size; index++) devm_gpiod_put(dev, keypad->key[index].desc); return 0; } static const struct dev_pm_ops meson_gpio_kp_pm_ops = { .suspend = meson_gpio_kp_suspend, .resume = meson_gpio_kp_resume, .freeze = meson_gpio_kp_freeze, .thaw = meson_gpio_kp_resume, .poweroff = meson_gpio_kp_suspend, .restore = meson_gpio_kp_restore, }; static struct platform_driver meson_gpio_kp_driver = { .probe = meson_gpio_kp_probe, .remove = meson_gpio_kp_remove, .driver = { .name = "gpio-keypad", .pm = &meson_gpio_kp_pm_ops, .of_match_table = key_dt_match, }, }; int __init meson_gpio_kp_init(void) { return platform_driver_register(&meson_gpio_kp_driver); } void __exit meson_gpio_kp_exit(void) { platform_driver_unregister(&meson_gpio_kp_driver); } 逐函数解释 以下按代码物理顺序,逐个函数解释它干了什么。
...