在九、Linux驱动之输入子系统分析中我们分析了输入子系统的框架,接下来我以按键模拟键盘的方式来编写输入子系统代码。
1. 分析设备
本人使用的是JZ2440v3开发板,该开发板CPU使用的是S3C2440A,按键与CPU连接如下:
可以看到4个按键分别连接到2440的GPF0、GPF2、GPG3、GPG11引脚上面,4个按键接上拉电阻,可知按键按下时引脚输入低电平,按键松开时引脚输入高电平。
再定位到S3C2440A芯片手册:
从上面资料可知信息:
1. 想要控制开发板上面的4个按键,需要控制2440的GPF0、GPF2、GPG3、GPG11三个引脚为Input模式,所以要配置GPFCON寄存器的位[5:4]=00,[1:0]=00,配置GPGCON寄存器的位[23:22]=00,[7:6]=00。
2. 想要控制4个按键还需要控制GPFDAT、GPGDAT寄存器对应位。
3. GPFCON寄存器的物理地址为0x56000050,GPFDAT寄存器的物理地址为0x56000054。GPGCON寄存器的物理地址为0x56000060,GPGDAT寄存器的物理地址为0x56000064。
2. 编写代码
驱动程序button_input.c代码如下:
/* 参考drivers\input\keyboard\gpio_keys.c */
#include <linux/module.h>
#include <linux/version.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/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
struct pin_desc{
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[4] = {
{IRQ_EINT0, "S2", S3C2410_GPF0, KEY_L},
{IRQ_EINT2, "S3", S3C2410_GPF2, KEY_S},
{IRQ_EINT11, "S4", S3C2410_GPG3, KEY_ENTER},
{IRQ_EINT19, "S5", S3C2410_GPG11, KEY_LEFTSHIFT},
};
static struct input_dev *buttons_dev;
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
/* 10ms后启动定时器 */
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies+HZ/100);
return IRQ_RETVAL(IRQ_HANDLED);
}
static void buttons_timer_function(unsigned long data)
{
struct pin_desc * pindesc = irq_pd;
unsigned int pinval;
if (!pindesc)
return;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if (pinval)
{
/* 松开 : 最后一个参数: 0-松开, 1-按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 0); //上报事件
input_sync(buttons_dev); //报告完毕,同步事件
}
else
{
/* 按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 1); //上报事件
input_sync(buttons_dev); //报告完毕,同步事件
}
}
static int buttons_init(void)
{
int i,j;
int err;
/* 1. 分配一个input_dev结构体 */
buttons_dev = input_allocate_device();
/* 2. 设置 */
/* 2.1 能产生哪类事件 */
set_bit(EV_KEY, buttons_dev->evbit); //能产生按键类事件
set_bit(EV_REP, buttons_dev->evbit); //能产生重复事件
/* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */
set_bit(KEY_L, buttons_dev->keybit);
set_bit(KEY_S, buttons_dev->keybit);
set_bit(KEY_ENTER, buttons_dev->keybit);
set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);
/* 3. 注册 */
input_register_device(buttons_dev);
/* 4. 硬件相关的操作 */
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
add_timer(&buttons_timer);
for (i = 0; i < 4; i++)
{
err = request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
if(err < 0)
{
for(j = 0; j < i; j++)
free_irq(pins_desc[j].irq, &pins_desc[j]);
}
}
return err;
}
static void buttons_exit(void)
{
int i;
for (i = 0; i < 4; i++)
{
free_irq(pins_desc[i].irq, &pins_desc[i]);
}
del_timer(&buttons_timer);
input_unregister_device(buttons_dev);
input_free_device(buttons_dev);
}
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_AUTHOR("LVZHENHAI");
MODULE_LICENSE("GPL");
Makefile代码如下:
KERN_DIR = /work/system/linux-2.6.22.6 //内核目录
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += button_input.o
3. 测试
内核:linux-2.6.22.6
编译器:arm-linux-gcc-3.4.5
环境:ubuntu9.10
在开发板目录上执行:
ls /dev/event*
可以看到已经存在一个input设备。将button_input.c、Makefile三个文件放入网络文件系统内,在ubuntu该目录下执行:
make
再次在开发板目录上执行:
insmod button_input.ko
ls /dev/event*
新出现的/dev/event1设备便是我们创建的input模拟键盘设备了。下面有两种方法测试该驱动。
方法一:
在开发板目录上执行:
cat /dev/tty1
此命令表示显示输出tty1。按下开发板的S2、S3,开发板lLCD上就能看到“ls”被打印出来了。然后按下S4,此时串口终端结果如下:
按键虚拟键盘键值正确被系统接收到了,说明我们的驱动成功了。
PS:使用命令:exec 0</dev/tty1 (将标准输入改为tty1)
方法二:
在开发板目录上执行:
hexdump /dev/event1
然后按下S2键串口终端输出如下:
其中2、3列表示秒,4、5列表示微妙,6列表示类,7列表示code,8、9列表示value。
4. 程序说明
在buttons_init初始化函数中分配一个input_dev结构体,设置并注册它。当按键按下或松开时,中断发生,调用中断函数buttons_irq(),该函数里在按键事件10ms后调用buttons_timer_function定时器函数,在该函数里面针对中断获取到的不同键值调用input_event函数上报。可见我们的程序里并没有创建设备节点那一套操作,是由系统替我们完成的。
5. 知识点
5.1 编写输入子系统驱动我们需要做什么?
(1) 分配一个input_dev结构体。
(2) 设置input_dev结构体(1.能产生哪类事件,2.能产生这类操作里的哪些事件)。
(3) 注册这个input_dev结构体。
(4) 获得事件时调用input_event上报事件和input_sync同步事件(表示上报结束)。
5.2 input设备支持的事件类型
EV_SYN 0x00 //同步事件
EV_KEY 0x01 //按键事件 EV_REL 0x02 相对坐标(如:鼠标移动,报告相对最后一次位置的偏移)
EV_ABS 0x03 //绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置)
EV_MSC 0x04 //其它
EV_SW 0x05 //开关
EV_LED 0x11 //按键/设备灯
EV_SND 0x12 //声音/警报
EV_REP 0x14 //重复
EV_FF 0x15 //力反馈
EV_PWR 0x16 //电源
EV_FF_STATUS 0x17 //力反馈状态
EV_MAX 0x1f //事件类型最大个数和提供位掩码支持