【f1c200s/f1c100s】不带中断引脚采用扫描的方式实现通用gpio-keys

【f1c200s/f1c100s】不带中断引脚采用扫描的方式实现通用gpio-keys

发现问题

最近画了一块f1c200s/f1c100s开发板,暂且叫它mango-pi。原理图和PCB可以参考mago-pi
由于引脚资源有限,将按键输入引脚设计到了PA0和PA1引脚。
在这里插入图片描述
直接使能内核自带的gpio-key功能后,内核输出不能请求irq中断。查看gpio_keys.c源码发现在文件开始就说了这个驱动只针对能够产生中断的引脚。
在这里插入图片描述
后面查阅f1c200s参考手册发现PA端口居然不带GPIO中断!!!!!PD、PE、PF都有,就没有PA。所以这块板子不能使用内核自带的gpio-keys功能。
在这里插入图片描述

解决问题

既然内核自带的gpio-keys只支持带有gpio中断的引脚,那就只有自己尝试自己编写驱动了。
我希望这个驱动是通用的:

  1. 采用设备树与驱动分离的方式进行编写
  2. 只需在设备树中添加节点而不用修改源码即可增加按键输入
  3. 可方便移植到其他处理器

思路

  1. 仿照gpio-keys编写设备树节点
  2. 驱动代码根据compatible属性访问设备树节点
  3. 依次访问节点数量以获取按键数量和属性
  4. 注册input子系统
  5. 采用内核定时器定时访问按键IO事件
  6. 上报按键输入事件

具体实现方法

设备树

在设备树根节点下添加如下节点:
这里的compatible 属性设置为gpio-keys-scan,驱动代码将会去匹配这个属性。其余属性和gpio-keys相同。如果还有其他不支持中断的按键,只需要继续添加key3、key4节点即可。

	gpio-keys {
		compatible = "gpio-keys-scan";
        #address-cells = <1>;
        #size-cells = <1>;
        status = "okay";
        autorepeat; //支持连按
		key1 {
			label = "key1";
			linux,code = <1>; //模拟ESC按键
			gpios = <&pio 0 0 GPIO_ACTIVE_LOW>;//PA0
		};
		key2 {
			label = "key2";
			linux,code = <28>;//模拟ENTER按键
			gpios = <&pio 0 1 GPIO_ACTIVE_LOW>;//PA1
		};
	};

驱动代码

驱动里面尝试使用了一些原来没使用过的函数,其中关于内核定时器函数网上大部分教程是基于linux3.x版本的,而本次使用的linux5.2的内核,导致一些功能的使用方法有所改变,做了很多尝试和摸索才编写出这样的代码(说到底还是有点菜)。

代码包含详细注释如下:

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Driver for keys on GPIO lines capable of not generating interrupts.
 *
 * Copyright 2022 Lorenzo
 * Copyright 2022, 2022 Lorenzo <liangtaozhong@gmail.com>
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/gpio_keys.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/spinlock.h>
#include <dt-bindings/input/gpio-keys.h>
#include <linux/fs.h>
#include <linux/timer.h>
#include <linux/cdev.h>
#include <linux/errno.h>
#include <linux/of_gpio.h>
#include <linux/input.h>
#include <linux/semaphore.h>

#define KEYINPUT_NAME "keyinput"
#define DELAY_MS 50 //定时器间隔扫描的时间
static int nbuttons;//按键数量
static bool rep;//是否支持连按

struct key_dev {
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *nd;
    struct timer_list timer;
    struct input_dev *inputdev;
};
//每个按键的属性
struct key_desc {
    int gpio;
    u32 code;
    const char *label;
};

static struct key_desc* key = NULL;
static struct key_dev key_dev;

//定时扫描函数
void timer_func(struct timer_list* timer) {
    int i = 0;
    struct key_desc *pkey = NULL;
    pkey = key;
    for (i = 0; i < nbuttons; ++i, ++pkey) { //遍历按键
        if (gpio_get_value(pkey->gpio) == 0) { //如果有按键按下
            mdelay(10); //延时10ms
            if (gpio_get_value(pkey->gpio) == 0) {//再次检测是否按下
                input_report_key(key_dev.inputdev, pkey->code, 1); //上报输入事件
                input_sync(key_dev.inputdev);
            } 
        } else {
            input_report_key(key_dev.inputdev, pkey->code, 0);
            input_sync(key_dev.inputdev);
        }
    }
    mod_timer(&key_dev.timer, jiffies + msecs_to_jiffies(DELAY_MS)); //重新打开定时器
}


static const struct of_device_id gpio_keys_of_match[] = {
    {.compatible = "gpio-keys-scan"}, //匹配设备树的compatible属性
    { },
};

static int key_probe(struct platform_device *pdev) {
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    struct device_node *next;
    int i = 0;
    int ret;
    nbuttons = of_get_child_count(np); //获取gpio_keys节点中子节点数目,其实就是定义的按键数目
    printk("having %d buttons\r\n", nbuttons);
    if (!nbuttons)
        return ERR_PTR(-ENODEV);
    //动态分配存储按键信息的内存,这个函数使用后不用手动释放,驱动卸载时会自动释放
    key = devm_kzalloc(dev, nbuttons * sizeof(struct key_desc), 
        GFP_KERNEL);
    if (key == NULL) {
        return -ENOMEM; 
    }
    struct key_desc* pkey = key;
	//检查是否支持连按
    rep = of_property_read_bool(np, "autorepeat");
    printk("rep : %d\n", rep);
	//遍历子节点即每个按键信息
    for_each_child_of_node(np, next) {
        
        of_property_read_string(next, "label", &(pkey->label));//获取label属性
        of_property_read_u32(next, "linux,code", &(pkey->code));//获取模拟按键值属性
        printk("name: %s, label: %s, code: %d\n", next->name, 
            pkey->label, pkey->code);
        pkey->gpio = of_get_named_gpio(next, "gpios", 0);//获取gpio信息
        if (pkey->gpio < 0) {
            printk("can't request gpio: %s\r\n", pkey->label);
            return -ENODEV;
        }
        gpio_request(pkey->gpio, pkey->label);
        gpio_direction_input(pkey->gpio);//设置gpio方向为输入
        pkey++;
    }

    key_dev.inputdev = input_allocate_device(); 
    if (NULL == key_dev.inputdev)
    {
            printk(KERN_ERR "input_allocate_device error");
            return -1;
    }

    key_dev.inputdev->name = KEYINPUT_NAME;
    key_dev.inputdev->evbit[0] = BIT_MASK(EV_KEY);  //设置输入事件为按键
    if (rep) {
        key_dev.inputdev->evbit[0] = BIT_MASK(EV_REP); //设置是否支持连按
    }

    for (i = 0, pkey = key; i < nbuttons; ++i, ++pkey) {
        input_set_capability(key_dev.inputdev, EV_KEY, pkey->code); //设置每个按键模拟的按键值
    }

    ret = input_register_device(key_dev.inputdev);//注册input子系统
    //注册失败了就要释放gpio
    if (ret) {
        printk("register input device failed\n");
        for (i = 0, pkey = key; i < nbuttons; ++i, ++pkey) {
            gpio_free(pkey->gpio);
        }
        input_unregister_device(key_dev.inputdev);
        return ret;
    }
    /*
     * Use timer to scan all buttons
     */
    timer_setup(&key_dev.timer, timer_func, 0);//打开定时器
    mod_timer(&key_dev.timer, jiffies + msecs_to_jiffies(DELAY_MS));
    return 0;
}

static int key_remove(struct platform_device *dev) {
    input_unregister_device(key_dev.inputdev);
    input_free_device(key_dev.inputdev);
    del_timer_sync(&key_dev.timer);

    return 0;
}

//设置平台驱动的属性
static struct platform_driver gpio_keys_scan_driver = {
    .driver = {
        .name = "scan-keys",
        .of_match_table = gpio_keys_of_match,
    },
    .probe = key_probe,
    .remove = key_remove,
};

static int  __init keydriver_init(void) {
    return platform_driver_register(&gpio_keys_scan_driver);
}

static void __exit keydriver_exit(void) {
    platform_driver_unregister(&gpio_keys_scan_driver);
}


module_init(keydriver_init);
module_exit(keydriver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Lorenzo <liangtaozhong@gmail.com>");
MODULE_DESCRIPTION("Keyboard driver for GPIOs");
MODULE_ALIAS("platform:gpio-keys-scan");

将驱动添加进内核

将驱动文件放在gpio_keys.c的同级目录,也就是drivers/input/keyboard/,重命名为gpio_keys_scan.c。

  1. 增加menuconfig配置选项
    drivers/input/keyboard/Kconfig文件末尾中添加如下代码:
    config KEYBOARD_GPIO_SCAN
    	tristate "Scan GPIO Buttons"
    	depends on GPIOLIB || COMPILE_TEST
    
    这样就可以在menuconfig中keyboards目录中看到我们新增的选项了:
    在这里插入图片描述
  2. 修改Makefile
    前面只是把选项添加到了menuconfig中,选中该选项后,menuconfig仅仅会生成含有该选项配置文件,并不会生成相应的Makefile文件,因此还需要修改Makefile文件。
    drivers/input/keyboard/Makefile末尾增加obj-$(CONFIG_KEYBOARD_GPIO_SCAN) += gpio_keys_scan.o
    在这里插入图片描述

meunconfig生成的配置文件中含有CONFIG_KEYBOARD_GPIO_SCAN=y选项,在Makefile文件中展开为obj-y+=gpio_keys_scan.o,也就是将该驱动文件直接编译进内核。现在编译内核,不出意外驱动将编译进内核,在开机时就会加载该驱动。

现象

开机时,该驱动开始加载,内核输出如下:
在这里插入图片描述
找到两个按键,并且注册为input0。
在进入系统后利用evtest工具进行测试:
请添加图片描述
OK!

参考资料

[1] 内核代码:gpio_keys.c
[2] 内核绑定文档:gpio-keys.txt
[3] 内核参考文档

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Liangtao`

请作者喝杯咖啡吧~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值