1. 输入设备概述
输入设备是向计算机输入数据和信息的设备。像键盘、鼠标、触摸屏等设备,都属于输入设备。
2. 输入设备驱动
在内核***/include/linux/input.h中有个重要的结构体:struct input_dev 。每一个input_dev对象就是一个输入设备。驱动程序就是完成input_dev对象的成员填充和input_dev对象的注册。
重要成员:
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //事件
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //按键映射
unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; //相对坐标
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; //绝对坐标
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; //led
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; //beep
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
不同的输入设备要使用上述对应的成员,比如按键输入就要操作 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
3. 重要函数
- struct input_dev __must_check *input_allocate_device(void); //创建输入设备对象
- void input_free_device(struct input_dev *dev); //释放设备对象
- int __must_check input_register_device(struct input_dev *); //注册设备
- void input_unregister_device(struct input_dev *); //注销设备
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value);
} //发送input_event
static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0);
} //发送哨兵报告,即全0的event
4. qt210按键驱动实例
此驱动程序用qt210开发板上的4个按键模拟键盘“L”、“S”、“ENTER”、“BACKSPACE”。驱动中定时器用于滤波,防止按键按下时多次触发中断。
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/init.h>
- #include <linux/input.h> //输入设备结构体(struct input_dev)
- #include <linux/timer.h> //定时器
- #include <linux/interrupt.h> //函数request_irq()
- // arch/arm/mach-s5pv210/include/mach/regs-gpio.h
- #include <mach/regs-gpio.h> //GPIO寄存器虚拟地址的基地址(S5P_VA_GPIO)
- // arch/arm/mach-s5pv210/include/mach/irqs.h
- // arch/arm/plat-samsung/include/plat/irqs.h
- #include <mach/irqs.h> //中断号
- #define GPH2CON (*(volatile u32*)(S5P_VA_GPIO + 0x0c40))
- #define GPH2DAT (*(volatile u32*)(S5P_VA_GPIO + 0x0c44))
- #define GPH2PUD (*(volatile u32*)(S5P_VA_GPIO + 0x0c48))
- #define GPH3CON (*(volatile u32*)(S5P_VA_GPIO + 0x0c60))
- #define GPH3DAT (*(volatile u8*)(S5P_VA_GPIO + 0x0c64))
- /*按键结构体*/
- struct key_info {
- int irqno;
- const charchar *name;
- unsigned int code;
- };
- /*qt210用到的按键的信息*/
- static struct key_info key[4] = {
- [0] = {IRQ_EINT(20), "COL4", KEY_L},
- [1] = {IRQ_EINT(21), "COL5", KEY_S},
- [2] = {IRQ_EINT(22), "COL6", KEY_BACKSPACE},
- [3] = {IRQ_EINT(23), "COL7", KEY_ENTER},
- };
- /*模块结构体*/
- struct ldm_info {
- struct input_dev *pdev;
- struct timer_list timer;
- };
- static struct ldm_info ldm;
- /*定时器触发函数*/
- static void timer_handler(unsigned long data)
- {
- struct key_info *p = (struct key_info *)data;
- //检查到底是哪个按键,根据不同的按键,发送相应的input_event报告
- input_report_key(ldm.pdev, p->code, !(GPH2DAT & (1 << (p-key+4)))); //按下时value为1,抬起时value为0
- //发送哨兵报告
- input_sync(ldm.pdev);
- }
- /*中断处理函数*/
- static irqreturn_t key_isr(int irqno, voidvoid *arg)
- {
- ldm.timer.data = (unsigned long)arg;
- mod_timer(&ldm.timer, jiffies + HZ / 10);
- return IRQ_HANDLED;
- }
- /*按键相关的硬件初始化和中断注册*/
- static int key_init(void)
- {
- int ret = 0;
- //col4-7,GPH2_4-7设为输入,内部上拉
- GPH2CON &= ~(0xffff << 16);
- GPH2PUD = (GPH2PUD & ~(0xff << 8)) | 0b10101010 << 8;
- //row1,GPH3_1设为输出
- GPH3CON = (GPH3CON & ~(0xf << 4)) | 1 << 4;
- //中断注册,成功后可以通过cat /proc/interrupts来查看当前所有注册的中断信息
- ssize_t i = 0;
- for (i = 0; i < ARRAY_SIZE(key); ++i) {
- if ((ret = request_irq(key[i].irqno, key_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, key[i].name, (void*)(key + i))) < 0) {
- printk("request irq %s failed\n", key[i].name);
- goto err_request_irq;
- }
- }
- return 0;
- err_request_irq:
- for (i = i - 1; i >= 0; --i) {
- free_irq(key[i].irqno, (void*)(key + i));
- }
- return ret;
- }
- static int __init ldm_init(void)
- {
- printk("%s\n", __FUNCTION__);
- int ret = 0;
- //1 创建输入设备对象
- ldm.pdev = input_allocate_device();
- if (!ldm.pdev) {
- printk("input_allocate_device failed\n");
- ret = -ENOMEM;
- goto err_input_allocate_device;
- }
- //2 填充input_dev的成员
- //2.1 注册输入设备类型,支持按键类型的输入,支持连发
- ldm.pdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
- //2.2 注册键盘映射的键位
- ldm.pdev->keybit[BIT_WORD(KEY_BACKSPACE)] = BIT_MASK(KEY_BACKSPACE);
- ldm.pdev->keybit[BIT_WORD(KEY_ENTER)] |= BIT_MASK(KEY_ENTER);
- ldm.pdev->keybit[BIT_WORD(KEY_L)] |= BIT_MASK(KEY_L);
- ldm.pdev->keybit[BIT_WORD(KEY_S)] |= BIT_MASK(KEY_S);
- //3 硬件初始化
- ret = key_init();
- if (ret < 0) {
- printk("key_init failed\n");
- goto err_key_init;
- }
- init_timer(&ldm.timer);
- ldm.timer.function = timer_handler;
- //5 注册设备
- ret = input_register_device(ldm.pdev);
- if (ret < 0) {
- printk("input_register_device failed\n");
- goto err_input_register_device;
- }
- return 0;
- err_input_register_device:
- err_key_init:
- input_free_device(ldm.pdev);
- err_input_allocate_device:
- return ret;
- }
- static void __exit ldm_exit(void)
- {
- printk("%s\n", __FUNCTION__);
- input_unregister_device(ldm.pdev);
- ssize_t i = 0;
- for (i = ARRAY_SIZE(key) - 1; i >= 0; --i) {
- free_irq(key[i].irqno, (void*)(key + i));
- }
- input_free_device(ldm.pdev);
- }
- module_init(ldm_init);
- module_exit(ldm_exit);
- MODULE_LICENSE("GPL");
- //将该输入设备设置为当前默认的输入设备
- //cat /proc/console //显示当前默认的终端设备
- //cat /proc/bus/input/devices //查看当前注册的所有输入设备的信息
- //exec 0</dev/ttyX //将目标终端设备重定向成默认终端设备