jz2440_输入子系统驱动程序

输入子系统框架

对于输入子系统框架的分析可以参考下面这篇文章:Linux内核输入子系统框架

输入子系统驱动程序

在了解了输入子系统的框架后,就可以编写一个输入子系统的驱动程序。

我们需要写两个程序,一个是 dev 设备程序。负责与硬件进行交互;另一个是 handler 程序,负责完成与应用程序的交互。

对于 dev 设备程序:

  1. 我们需要先分配一个 input_dev 结构体;
  2. 对 input_dev 进行设置;
  3. 注册 input_dev 到内核中;
  4. 硬件相关的操作;

对于输入子系统驱动的编写,我们可以参考 gpio_keys.c 文件的实现。

对于 handler 程序:由于我们实现的是输入子系统,而内核中已经用现成的输入系统的 handler 程序。包括: evdev.c 文件中实现的事件驱动程序、keyboard.c 中实现的按键驱动程序等等,因此我们只需要实现 dev 程序即可。

实现 dev 程序

在实现 dev 驱动程序之前,我们需要对 input_dev 结构体进行分析:

struct input_dev {
	/* ...省略... */
	unsigned long evbit[NBITS(EV_MAX)];		/* 表示能产生哪类事件 */
	unsigned long keybit[NBITS(KEY_MAX)];	/* 表示能产生哪些按键 */
	unsigned long relbit[NBITS(REL_MAX)];	/* 表示能产生哪些相对位移事件 */
	unsigned long absbit[NBITS(ABS_MAX)];	/* 表示能产生哪些绝对位移事件 */
	unsigned long mscbit[NBITS(MSC_MAX)];
	unsigned long ledbit[NBITS(LED_MAX)];
	unsigned long sndbit[NBITS(SND_MAX)];
	unsigned long ffbit[NBITS(FF_MAX)];
	unsigned long swbit[NBITS(SW_MAX)];
	/* ...省略... */
}

上面是设备能产生的事件类型,事件分析参考 Linux内核输入子系统事件分析_Bin Watson的博客-CSDN博客 这篇文章。

init 函数

定义全局数据结构:

struct pin_desc 
{
	int irq;				/* 中断号 */
	char *name;				/* 按键的名字 */
	unsigned int  pin;		/* 中断的引脚 */
	unsigned char code;		/* 按键的值 */
};

定义全局变量:

static struct pin_desc pins_desc[4] = {
	{IRQ_EINT0,  "s2", S3C2410_GPF0,  KEY_L},			/* 将按键s2,定义为L */
	{IRQ_EINT2,  "s3", S3C2410_GPF2,  KEY_S},			/* 将按键s3,定义为S */
	{IRQ_EINT11, "s4", S3C2410_GPG3,  KEY_ENTER},		/* 将按键s4,定义为ENTER */
	{IRQ_EINT19, "s5", S3C2410_GPG11, KEY_LEFTSHIFT},	/* 将按键s5,定义为LEFTSHIFT */
};
static struct input_dev *s3c2440_buttons_dev; 		/* 输入设备 */
static struct timer_list s3c2440_buttons_timer;		/* 定时器,防抖动用 */
static struct timer_data s3c2440_timer_data;		/* timer_handler函数使用的参数 */

初始化函数的实现(参考了 gpio_keys.c):

static int __init s3c2440_buttons_init(void)
{
	int i = 0, error;

	/* 1.分配一个input_dev结构体 */
	s3c2440_buttons_dev = input_allocate_device();
	if (!s3c2440_buttons_dev)
		return -ENOMEM;
	
	/* 2.设置 */
	/* 2.1 能产生按键类事件 */
	set_bit(EV_KEY, s3c2440_buttons_dev->evbit);
	/* 2.2 这类事件里的哪些事件 
	 *	   我们定义我们四个按键为:字母L, 字母S, ENTER, LEFTSHIT 
	 */
	set_bit(KEY_L, s3c2440_buttons_dev->keybit);
	set_bit(KEY_S, s3c2440_buttons_dev->keybit);
	set_bit(KEY_ENTER, s3c2440_buttons_dev->keybit);
	set_bit(KEY_LEFTSHIFT, s3c2440_buttons_dev->keybit);
	
	/* 3.注册 */
	error = input_register_device(s3c2440_buttons_dev);
	if (error) {
		printk(KERN_ERR "Unable to register %s input device\n", DEVICE_NAME);
		goto fail1;
	}

	/* 4.硬件相关的操作 */
	/* 4.1 定时器初始化工作 */
	init_timer(&s3c2440_buttons_timer);
	s3c2440_buttons_timer.data 	   = 0;
	s3c2440_buttons_timer.function = s3c2440_timer_handler;
	add_timer(&s3c2440_buttons_timer);

	/* 4.2 设置中断引脚 */
	for (i = 0; i < (sizeof(pins_desc) / sizeof(struct pin_desc)); ++i)
	{
		error = request_irq(pins_desc[i].irq, s3c2440_irq_handler, 
							IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
		if (error) {
			printk(KERN_ERR "%s: unable to claim irq %d; error %d\n", 
				DEVICE_NAME, pins_desc[i].irq, error);
			goto fail2;
		}
	}
	
	return 0;
	return 0;
	
fail2:	/* 释放所有已经设置的引脚 */
	for (i = i - 1; i >= 0; --i)	
		free_irq(pins_desc[i].irq,  &pins_desc[i]);
	del_timer(&s3c2440_buttons_timer);
fail1:	/* 释放s3c2440_buttons_dev */
	input_unregister_device(s3c2440_buttons_dev);
	input_free_device(s3c2440_buttons_dev);
	
	return error;

定时器的设置参考:jz2440_按键驱动程序的防抖机制_Bin Watson的博客-CSDN博客

设置中断引脚的设置参考:jz2440_字符设备按键驱动程序_Bin Watson的博客-CSDN博客

exit 函数

static void __exit s3c2440_buttons_exit(void)
{
	int i = 0;
	for (i = 0; i < (sizeof(pins_desc) / sizeof(struct pin_desc)); ++i)
		free_irq(pins_desc[i].irq,  &pins_desc[i]);
	
	del_timer(&s3c2440_buttons_timer);
	
	input_unregister_device(s3c2440_buttons_dev);
	input_free_device(s3c2440_buttons_dev);
}

事件发生函数

当按键被按下时,会触发中断处理函数 s3c2440_irq_handler 被调用:

static irqreturn_t s3c2440_irq_handler(int irq, void *dev_id)
{
	/* 10ms后启动定时器 */
	mod_timer(&s3c2440_buttons_timer, jiffies + (HZ/100));
	s3c2440_timer_data.irq    = irq;
	s3c2440_timer_data.dev_id = dev_id;
	
	return IRQ_HANDLED;
}

为了实现防抖的,我们将中断处理推迟到下半部,也就是在定时器的处理函数中进行处理。(定时器教程参考jz2440_按键驱动程序的防抖机制_Bin Watson的博客-CSDN博客

s3c2440_timer_handler 是定时器的处理函数,其所需要做的就是对事件进行上报,最终由 evdev.c 实现的 evdev 驱动程序来完成。s3c2440_timer_handler 的实现:

static void s3c2440_timer_handler(unsigned long data)
{
	unsigned int pinval;
	struct pin_desc *pindesc = (struct pin_desc *)s3c2440_timer_data.dev_id;
	
	if (pindesc == NULL)
		return;

	pinval = s3c2410_gpio_getpin(pindesc->pin);
	if (pinval)
	{	
        /* 按键松开 value = 0 */
		input_event(s3c2440_buttons_dev, EV_KEY, pindesc->code, 0);
		input_sync(s3c2440_buttons_dev);	/* 上报一个同步类事件,表示当前事件已经上报完了 */
	}
	else
	{
        /* 按键按下 value = 1 */
		input_event(s3c2440_buttons_dev, EV_KEY, pindesc->code, 1);
		input_sync(s3c2440_buttons_dev);
	}
}
  • pindesc:对于 init 函数中的

    request_irq(pins_desc[i].irq, s3c2440_irq_handler, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]) 中的 pins_desc[i],也就是全局数组 pins_desc[4] 的某一个元素。

  • pinval = s3c2410_gpio_getpin(pindesc->pin):用于获取引脚的值。S3C2410_GPF0、S3C2410_GPF2、S3C2410_GPF11、S3C2410_GPF19 分别连接了四个按键 “s2”、 “s3”、 “s4”、 “s5”。

    中断引脚的配置详细参考jz2440_字符设备按键驱动程序_Bin Watson的博客-CSDN博客

测试

加载设备之前:

linux > ls -l /dev/event*	# 查看event设备
crw-rw----    1 0        0         13,  64 Jan  1 00:00 /dev/event0

加载设备之后:

linux > insmod input.ko 
input: Unspecified device as /class/input/input1
linux > ls -l /dev/event*
crw-rw----    1 0        0         13,  64 Jan  1 00:00 /dev/event0
crw-rw----    1 0        0         13,  65 Jan  1 00:26 /dev/event1

挂载之后,我们的 event* 设备就变为了2个。

使用 tty1 进行测试(用到 keyboard.c 驱动程序):

# cat /dev/tty1
LS

使用 event1 进行测试(用到 evdev.c 驱动程序):

# hexdump /dev/event1
0000000 09ca 0000 808a 0004 0001 0026 0000 0000
0000010 09ca 0000 8095 0004 0000 0000 0000 0000
0000020 09ca 0000 a37f 0006 0001 0026 0001 0000
0000030 09ca 0000 a38c 0006 0000 0000 0000 0000

event1 内的数据的数据结构类型如下:

struct input_event {
	struct timeval time;
	__u16 type;
	__u16 code;
	__s32 value;
};
timetypecodevalue描述
09ca 0000 808a 0004000100260000 0000按键松开
09ca 0000 8095 0004000000000000 0000
09ca 0000 a37f 0006000100260001 0000按键按下
09ca 0000 a38c 0006000000000000 0000

重复事件

新驱动程序

添加重复事件 EV_REP 支持:

static int __init s3c2440_buttons_init(void)
{
	int i = 0, error;

	/* 1.分配一个input_dev结构体 */
	s3c2440_buttons_dev = input_allocate_device();
	if (!s3c2440_buttons_dev)
		return -ENOMEM;
	
	/* 2.设置 */
	/* 2.1 能产生按键类事件 */
	set_bit(EV_KEY, s3c2440_buttons_dev->evbit);
	set_bit(EV_REP, s3c2440_buttons_dev->evbit);	/* 当按键一直按着时,会重复生成 */
    /*...省略...*/

重复事件的实现

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
		/*...省略...*/

		case EV_KEY:

			if (code > KEY_MAX || !test_bit(code, dev->keybit) || !!test_bit(code, dev->key) == value)
				return;

			if (value == 2)
				break;

			change_bit(code, dev->key);
			
    		/* 当按键按下,并且设置了EV_REP重复事件 */
			if (test_bit(EV_REP, dev->evbit) && 
                dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && dev->timer.data && value) {
				dev->repeat_key = code;	/* 记录按键的值 */
                /* 会重置定时器 */
				mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
			}

			break;
    /*...省略...*/
}

定时器的处理函数,如下:

static void input_repeat_key(unsigned long data)
{
	struct input_dev *dev = (void *) data;

	if (!test_bit(dev->repeat_key, dev->key))
		return;

	input_event(dev, EV_KEY, dev->repeat_key, 2);	/* 上报事件 */
	input_sync(dev);

    /* 再次修改定时器 */
	if (dev->rep[REP_PERIOD])
		mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_PERIOD]));
}

完整的驱动程序

输入子系统完整的驱动程序-Linux文档类资源-CSDN文库

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值