之前学习过输入子系统的相关概念,这里我们就学习一下如何编写按键驱动。
本次要实现的是让开发板的5个按键代表键盘中的L、S、回车键等。
首先看看input_dev结构体:
struct input_dev {
void *private;
const char *name; //设备名字
const char *phys; //文件路径,比如 input/buttons
const char *uniq;
struct input_id id;
unsigned long evbit[NBITS(EV_MAX)]; //表示支持哪类事件,常用有以下几种事件(可以多选)
//EV_SYN 同步事件,当使用input_event()函数后,就要使用这个上报个同步事件
//EV_KEY 键盘事件
//EV_REL (relative)相对坐标事件,比如鼠标
//EV_ABS (absolute)绝对坐标事件,比如摇杆、触摸屏感应
//EV_MSC 其他事件,功能
//EV_LED LED灯事件
//EV_SND (sound)声音事件
//EV_REP 重复键盘按键事件
//(内部会定义一个定时器,若有键盘按键事件一直按下/松开,就重复定时,时间一到就上报事件)
//EV_FF 受力事件
//EV_PWR 电源事件
//EV_FF_STATUS 受力状态事件
unsigned long keybit[NBITS(KEY_MAX)]; //存放支持的键盘按键值
//键盘变量定义在:include/linux/input.h, 比如: KEY_L(按键L)
unsigned long relbit[NBITS(REL_MAX)]; //存放支持的相对坐标值
unsigned long absbit[NBITS(ABS_MAX)]; //存放支持的绝对坐标值
unsigned long mscbit[NBITS(MSC_MAX)]; //存放支持的其它事件,也就是功能
unsigned long ledbit[NBITS(LED_MAX)]; //存放支持的各种状态LED
unsigned long sndbit[NBITS(SND_MAX)]; //存放支持的各种声音
unsigned long ffbit[NBITS(FF_MAX)]; //存放支持的受力设备
unsigned long swbit[NBITS(SW_MAX)]; //存放支持的开关功能
... ...
这里需要较为注意的是evbit和keybit数组。
其中evbit表示支持哪类事件,比如按键需要支持键盘事件EV_KEY以及重复键盘事件EV_REP。
而keybit指的是支持的按键值,这里我们要支持的是KEY_L、KEY_S、KEY_ENTER、KEY_LEFTSHIFT、KEY_ESC
为了让设置这两个数组,需要用到setbit()函数
set_bit(nr,p); //设置某个结构体成员p里面的某位等于nr,支持这个功能
set_bit(EV_KEY, button_dev->evbit);
set_bit(KEY_L, button_dev->keybit);
接下来开始注册设备
(1)注册设备
/*1. 分配一个input_dev结构体 */
button_dev = input_allocate_device();
/* 2.设置 能产生按键类事件*/
/* 哪类事件 */
set_bit(EV_KEY, button_dev->evbit);
/* 重复类事件 */
set_bit(EV_REP, button_dev->evbit);
/* 能产生哪些事件L,S,ENTER,SHIFT */
set_bit(KEY_L, button_dev->keybit);
/* 3.注册 */
input_register_device(button_dev);
首先通过函数input_allocate_device()分配input_dev结构体,然后设置相关的事件,最后通过input_register_device()函数注册即可。
通过之前学习可知,调用input_register_device()函数后会自动寻找是否有与之匹配的驱动,若是有则connect。
(2)上报事件
当检测到有按键按下则通过input_event()函数上报事件。
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value); //上报事件
// input_dev *dev :要上报哪个input_dev驱动设备的事件
// type : 要上报哪类事件, 比如按键事件,则填入: EV_KEY
// code: 对应的事件里支持的哪个变量,比如按下按键L则填入: KEY_L
//value:对应的变量里的数值,比如松开按键则填入1,松开按键则填入0
同时要调用input_sync()函数通知系统,这样系统才能知道。
static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0); //就是上报同步事件,告诉内核:input_event()事件执行完毕
}
(3)注销设备
input_unregister_device(struct input_dev *dev); //卸载/sys/class/input目录下的input_dev这个类设备, 一般在驱动出口函数写
input_free_device(struct input_dev *dev); //释放input_dev这个结构体, 一般在驱动出口函数写
通过以上两个函数实现。
学习到这里开始编写代码。
#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 <asm/irq.h>
#include <asm/io.h>
#include <mach/gpio.h>
static struct input_dev * button_dev;
static struct pin_desc *irq_pd = NULL;
static struct timer_list buttons_timer;
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies + HZ/100);
return IRQ_RETVAL (IRQ_HANDLED);
}
struct pin_desc{
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[5] = {
{IRQ_EINT(0), "s1", S3C64XX_GPN(0), KEY_L},
{IRQ_EINT(1), "s2", S3C64XX_GPN(1), KEY_S},
{IRQ_EINT(2), "s3", S3C64XX_GPN(2), KEY_ENTER},
{IRQ_EINT(3), "s4", S3C64XX_GPN(3), KEY_LEFTSHIFT},
{IRQ_EINT(4), "s5", S3C64XX_GPN(4), KEY_ESC},
};
static void buttons_timer_function(unsigned long data)
{
struct pin_desc * pindesc = irq_pd;
unsigned int pinval;
if (!pindesc)
return;
pinval = gpio_get_value(pindesc->pin);
if (pinval)
{
/* 松开的 *//* 最后一个参数0表示松开1表示按下 */
input_event(button_dev, EV_KEY, pindesc->key_val, 0);
input_sync(button_dev);
}
else
{
/* 按下的 */
input_event(button_dev, EV_KEY, pindesc->key_val, 1);
input_sync(button_dev);
}
}
static int button_init(void)
{
int i = 0;
/*1. 分配一个input_dev结构体 */
button_dev = input_allocate_device();
/* 2.设置 能产生按键类事件*/
/* 哪类事件 */
set_bit(EV_KEY, button_dev->evbit);
/* 重复类事件 */
set_bit(EV_REP, button_dev->evbit);
/* 能产生哪些事件L,S,ENTER,SHIFT */
for (i = 0; i < 5; i++)
{
set_bit(pins_desc[i].key_val, button_dev->keybit);
}
/* 3.注册 */
input_register_device(button_dev);
/* 4.硬件相关的操作 */
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
buttons_timer.expires = jiffies + HZ/100;
add_timer(&buttons_timer);
for (i = 0; i < 5; i++)
{
request_irq(pins_desc[i].irq, buttons_irq, IRQF_SAMPLE_RANDOM | IRQ_TYPE_EDGE_BOTH |IRQF_SHARED, pins_desc[i].name, &pins_desc[i]);
}
return 0;
}
static void button_exit(void)
{
int i;
for (i = 0; i < 5; i++)
{
free_irq(pins_desc[i].irq, &pins_desc[i]);
}
del_timer(&buttons_timer);
input_unregister_device(button_dev);
input_free_device(button_dev);
}
module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");
将程序编译并且insmod之后,执行cat /dev/tty1,然后按一下按键,会发现你按的按键值显示在屏幕上。
不过要注意是当你按下代表ENTER的那个键的时候你之前输入的值才会显示在屏幕上。