使用F1C200S从零制作掌机之按键input驱动

按键input驱动程序

#include <linux/types.h> 
#include <linux/kernel.h> 
#include <linux/delay.h> 
#include <linux/ide.h> 
#include <linux/init.h>
#include <linux/module.h> 
#include <linux/errno.h> 
#include <linux/gpio.h> 
#include <linux/cdev.h> 
#include <linux/device.h> 
#include <asm/mach/map.h> 
#include <asm/uaccess.h> 
#include <asm/io.h> 
#include <linux/of.h> 
#include <linux/of_address.h>
#include <linux/platform_device.h> 
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h> 
#include <linux/input.h> 

#define KEYINPUT_CNT    1      /* 设备号个数 */ 
#define KEYINPUT_NAME   "keyinput"  /* 名字 */ 
#define KEY_NUM    1      /* 按键数量 */

/* 中断 IO 描述结构体 */
struct irq_keydesc {
	int gpio; /* gpio */
	int irqnum; /* 中断号 */
	unsigned char value; /* 按键对应的键值 */
	char name[10]; /* 名字 */
	irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};

/* key_dev 设备结构体 */ 
struct key_dev{ 
	dev_t devid; /* 设备号 */ 
	struct cdev cdev; /* cdev */ 
	struct class *class; /* 类 */
	struct device *device; /* 设备 */ 
	int major; /* 主设备号 */ 
	int minor; /* 次设备号 */ 
	struct device_node *nd; /* 设备节点 */
	struct timer_list timer; /* 定义一个定时器 */ 
	struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
	unsigned char curkeynum; /* 当前的按键号 */
	struct input_dev *inputdev; /* input 结构体 */
}; 

struct key_dev keydev; /* key 设备 */

static irqreturn_t key0_irq_handler(int irqe, void *dev_id)
{
	struct key_dev *dev = (struct key_dev *)dev_id;

	// printk("key irq handler!\n");
	dev->curkeynum = 0;

	// 这里用定时器防抖
	// mod_timer() 会重新注册定时器到内核,而不管定时器函数是否被运行过。
	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));

	return IRQ_HANDLED;
}

/* 定时器回调函数 */ 
void timer_function(struct timer_list* timer)//回调函数
{
	unsigned char num;
	struct irq_keydesc *keydesc;

	// printk("timer %lu\r\n", timer->expires);
	num = keydev.curkeynum;
	keydesc = &keydev.irqkeydesc[num];

	if (gpio_get_value(keydesc->gpio) == 0) { /* key 按下 */
		/* 上报按键值 */ 
		//input_event(keydev.inputdev, EV_KEY, keydesc->value, 1); 
		input_report_key(keydev.inputdev, keydesc->value, 1);/*1,按下*/ 
		input_sync(keydev.inputdev);
	} else {   /* 释放 */
		//input_event(keydev.inputdev, EV_KEY, keydesc->value, 0); 
		input_report_key(keydev.inputdev, keydesc->value, 0);
		input_sync(keydev.inputdev);
	}
}

static int keyio_init(void)
{
	int ret = 0;
	unsigned char i = 0;

	keydev.nd = of_find_node_by_path("/my-key");
	if (keydev.nd== NULL) {
		printk("my_key node can not found!\r\n");
		return -EINVAL;
	}

	/* 提取 GPIO */
	for (i=0; i<KEY_NUM; i++) {
		keydev.irqkeydesc[i].gpio = of_get_named_gpio(keydev.nd, "key-gpios", i);
		if (keydev.irqkeydesc[i].gpio < 0) {
			printk("can't get key%d\r\n", i);
			return -EINVAL;
		}
	}

	/* 初始化 key 所使用的 IO,并且设置成中断模式 */
	for (i=0; i<KEY_NUM; i++) {
		memset(keydev.irqkeydesc[i].name, 0, sizeof(keydev.irqkeydesc[i].name)); 
		sprintf(keydev.irqkeydesc[i].name, "KEY%d", i); 
		gpio_request(keydev.irqkeydesc[i].gpio, keydev.irqkeydesc[i].name);
		gpio_direction_input(keydev.irqkeydesc[i].gpio); 
		keydev.irqkeydesc[i].irqnum = gpio_to_irq(keydev.irqkeydesc[i].gpio);
		printk("key%d:gpio=%d, irqnum=%d\r\n", i, keydev.irqkeydesc[i].gpio, keydev.irqkeydesc[i].irqnum);
	}

	/* 初始化 timer,设置定时器处理函数,还未设置周期,所有不会激活定时器 */ 
	timer_setup(&keydev.timer, timer_function, 0);

	/* 申请 input_dev */ 
	keydev.inputdev = input_allocate_device(); 
	keydev.inputdev->name = KEYINPUT_NAME; 
#if 0 
	/* 初始化 input_dev,设置产生哪些事件 */ 
	__set_bit(EV_KEY, keydev.inputdev->evbit); /*按键事件 */ 
	__set_bit(EV_REP, keydev.inputdev->evbit); /* 重复事件 */ 

	/* 初始化 input_dev,设置产生哪些按键 */ 
	__set_bit(KEY_0, keydev.inputdev->keybit); 
#endif 

#if 0 
	keydev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); 
	keydev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0); 
#endif 
	keydev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); 
	input_set_capability(keydev.inputdev, EV_KEY, KEY_0); 

	/* 注册输入设备 */ 
	ret = input_register_device(keydev.inputdev); 
	if (ret) { 
		printk("register input device failed!\r\n"); 
		return ret; 
	}

	/* 申请中断 */
	keydev.irqkeydesc[0].handler = key0_irq_handler;
	keydev.irqkeydesc[0].value = KEY_0;

	for (i=0; i<KEY_NUM; i++) {
		ret = request_irq(keydev.irqkeydesc[i].irqnum, keydev.irqkeydesc[i].handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, keydev.irqkeydesc[i].name, &keydev);
		if(ret < 0){
			printk("irq %d request failed!\r\n", keydev.irqkeydesc[i].irqnum);
			return -EFAULT;
		}
	}

	return 0;
}

static int __init mykey_init(void) 
{
    int ret;
	
	/* 获取设备树中的属性数据 */
	ret = keyio_init();   /* 初始化按键 IO */
	if (ret < 0) {
		return ret;
	}

    return 0;
}

static void __exit mykey_exit(void) 
{ 
	unsigned int i = 0;

	/* 删除定时器 */ 
 	del_timer_sync(&keydev.timer);

	/* 注销字符设备 */ 
	cdev_del(&keydev.cdev);/* 删除 cdev */ 
	unregister_chrdev_region(keydev.devid, KEYINPUT_CNT); 

	device_destroy(keydev.class, keydev.devid); 
	class_destroy(keydev.class); 
	
	/* 释放中断 */
	for (i=0; i<KEY_NUM; i++) {
		free_irq(keydev.irqkeydesc[i].irqnum, &keydev);
		gpio_free(keydev.irqkeydesc[i].gpio);
	}

	/* 释放 input_dev */ 
	input_unregister_device(keydev.inputdev); 
	input_free_device(keydev.inputdev);
} 

module_init(mykey_init); 
module_exit(mykey_exit); 
MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("wangxinchen");

测试 APP

#include "stdio.h" 
#include "unistd.h" 
#include "sys/types.h" 
#include "sys/stat.h" 
#include "sys/ioctl.h" 
#include "fcntl.h" 
#include "stdlib.h" 
#include "string.h" 
#include <poll.h> 
#include <sys/select.h> 
#include <sys/time.h> 
#include <signal.h> 
#include <fcntl.h> 
#include <linux/input.h> 

// ./keyinputApp /dev/input/eventx

static struct input_event inputevent;

int main(int argc, char *argv[]) 
{ 
    int fd; 
    int err = 0; 
    char *filename; 

    filename = argv[1]; 

    if(argc != 2) { 
        printf("Error Usage!\r\n"); 
        return -1; 
    } 

    fd = open(filename, O_RDWR); 
    if (fd < 0) { 
        printf("Can't open file %s\r\n", filename); 
        return -1; 
    }
    while (1) { 
        err = read(fd, &inputevent, sizeof(inputevent)); 
        if (err > 0) { /* 读取数据成功 */ 
            switch (inputevent.type) { 
                case EV_KEY: 
                    if (inputevent.code < BTN_MISC) { /* 键盘键值 */ 
                        printf("key %d %s\r\n", inputevent.code, inputevent.value ? "press" : "release"); 
                    } else { 
                        printf("button %d %s\r\n", inputevent.code, inputevent.value ? "press" : "release"); 
                    } 
                    break; 

                 /* 其他类型的事件,自行处理 */ 
                case EV_REL: 
                    break; 
                case EV_ABS: 
                    break; 
                case EV_MSC: 
                    break; 
                case EV_SW: 
                    break; 
            } 
        } else { 
            printf("读取数据失败\r\n"); 
        } 
    }
    return 0; 
}

测试

[root@buildroot: /lib/modules/5.2/key_input]$ls /dev/input
ls: /dev/input: No such file or directory

[root@buildroot: /lib/modules/5.2/key_input]$insmod keyinpuDriver.ko
[   27.602575] keyinpuDriver: loading out-of-tree module taints kernel.
[   27.610443] gpio[131] label = KEY0
[   27.613944] key0:gpio=131, irqnum=57
[   27.618216] input: keyinput as /devices/virtual/input/input0

[root@buildroot: /lib/modules/5.2/key_input]$ls /dev/input
event0

[root@buildroot: /lib/modules/5.2/key_input]$hexdump /dev/input/event0
0000000 002e 0000 f8c3 000c 0001 000b 0001 0000
0000010 002e 0000 f8c3 000c 0000 0000 0000 0000
0000020 002f 0000 007e 0000 0001 000b 0000 0000
0000030 002f 0000 007e 0000 0000 0000 0000 0000
0000040 002f 0000 7644 0009 0001 000b 0001 0000
0000050 002f 0000 7644 0009 0000 0000 0000 0000
0000060 002f 0000 aaa0 000c 0001 000b 0000 0000
0000070 002f 0000 aaa0 000c 0000 0000 0000 0000
0000080 0030 0000 ccd5 0005 0001 000b 0001 0000
0000090 0030 0000 ccd5 0005 0000 0000 0000 0000
00000a0 0030 0000 8be8 0008 0001 000b 0000 0000
00000b0 0030 0000 8be8 0008 0000 0000 0000 0000
00000c0 0031 0000 0dad 0003 0001 000b 0001 0000
00000d0 0031 0000 0dad 0003 0000 0000 0000 0000
00000e0 0031 0000 ccc8 0005 0001 000b 0000 0000
00000f0 0031 0000 ccc8 0005 0000 0000 0000 0000
^C
[root@buildroot: /lib/modules/5.2/key_input]$cd test/
[root@buildroot: /lib/modules/5.2/key_input/test]$
[root@buildroot: /lib/modules/5.2/key_input/test]$./
Makefile       keyinputApp    keyinputApp.c  keyinputApp.o

[root@buildroot: /lib/modules/5.2/key_input/test]$./keyinputApp /dev/input/event0
key 11 press
key 11 release
key 11 press
key 11 release
key 11 press
key 11 release
key 11 press
key 11 release

分析三种设置事件和事件值的方法

#define BITS_TO_LONGS(nr)	DIV_ROUND_UP(nr, BITS_PER_TYPE(long))
#define DIV_ROUND_UP(n, d)	(((n) + (d) - 1) / (d))
#define BITS_TO_LONGS(nr)   (((n) + (32) - 1) / (32))

#define EV_SYN			0x00
#define EV_KEY			0x01
#define EV_REL			0x02
#define EV_ABS			0x03
#define EV_MSC			0x04
#define EV_SW			0x05
#define EV_LED			0x11
#define EV_SND			0x12
#define EV_REP			0x14
#define EV_FF			0x15
#define EV_PWR			0x16
#define EV_FF_STATUS	0x17
#define EV_MAX			0x1f
#define EV_CNT			(EV_MAX+1)

...
#define KEY_MAX			0x2ff
#define KEY_CNT			(KEY_MAX+1)
BITS_TO_LONGS(), BIT_WORD(), BIT_MASK()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

These three macros from bitops.h help some bitfield computations::

	BITS_TO_LONGS(x) - returns the length of a bitfield array in longs for
			   x bits
	BIT_WORD(x)	 - returns the index in the array in longs for bit x
	BIT_MASK(x)	 - returns the index in a long for bit x
/*********第一种设置事件和事件值的方法***********/ 
__set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */ 
__set_bit(EV_REP, inputdev->evbit); /* 重复事件 */ 
__set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值 */ 
/************************************************/ 

/*********第二种设置事件和事件值的方法***********/ 
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); 
keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0); 
/************************************************/ 

/*********第三种设置事件和事件值的方法***********/ 
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); 
input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0); 
/************************************************/
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 事件类型的位图 */ 
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 按键值的位图 */

evbit[BITS_TO_LONGS(EV_CNT)];
-> evbit[(((0x20) + (32) - 1) / (32))]
-> evbit[1]  这里用1个unsigned long保存ev信息

keybit[BITS_TO_LONGS(KEY_CNT)];
-> keybit[(((0x300) + (32) - 1) / (32))]
-> keybit[24]  这里用24个unsigned long保存key信息

Linux自带按键驱动

Linux支持配置

-> Device Drivers 
	-> Input device support 
 		-> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y]) 
 			-> Keyboards (INPUT_KEYBOARD [=y]) 
				->GPIO Buttons 

image-20240507232409342

设备树:

#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
/ {
	gpio-keys {
		compatible = "gpio-keys";
		#address-cells = <1>;
		#size-cells = <0>;
		autorepeat;
		key0 {
			label = "GPIO Key Enter";
			linux,code = <KEY_ENTER>;
			gpios = <&pio 4 3 GPIO_ACTIVE_LOW>;
		};
	};
}

测试:

[root@buildroot: ~]$hexdump /dev/input/event0
0000000 0041 0000 2867 0009 0001 001c 0001 0000
0000010 0041 0000 2867 0009 0000 0000 0000 0000
0000020 0041 0000 726b 000b 0001 001c 0000 0000
0000030 0041 0000 726b 000b 0000 0000 0000 0000
0000040 0042 0000 8c85 0008 0001 001c 0001 0000
0000050 0042 0000 8c85 0008 0000 0000 0000 0000
0000060 0042 0000 fd2d 000a 0001 001c 0000 0000
0000070 0042 0000 fd2d 000a 0000 0000 0000 0000
0000080 0043 0000 bbab 0004 0001 001c 0001 0000
0000090 0043 0000 bbab 0004 0000 0000 0000 0000
00000a0 0043 0000 2c92 0007 0001 001c 0000 0000
00000b0 0043 0000 2c92 0007 0000 0000 0000 0000
00000c0 0044 0000 27ad 0000 0001 001c 0001 0000
00000d0 0044 0000 27ad 0000 0000 0000 0000 0000
00000e0 0044 0000 e6d4 0002 0001 001c 0000 0000
00000f0 0044 0000 e6d4 0002 0000 0000 0000 0000
......

如果连续输出数据,修改GPIO_ACTIVE_LOW。触发电平的问题,导致重复触发。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值