手把手教你写Linux设备驱动---input子系统(二)--按键驱动实现(一)(基于友善之臂4412开发板)

在上一节里,我们用一个应用程序实现了鼠标的控制,并控制鼠标用相对位移不断的画一个正方形,感觉非常有意思,这一节,我们将通过一个简单按键实例来真正的实现一个input设备驱动程序。

http://blog.csdn.net/morixinguan/article/details/69808832

在写Input驱动之前,我们要了解下这个结构体,在此,我们要包含相应的头文件:

#include <linux/input.h>

我们在这个头文件中找到了以下结构体,它就是input设备的核心:

 //用来和应用交互的input设备
struct input_dev {
	//输入设备的名称
	//在/proc/bus/input/devices中产生
	const char *name;
	//硬件相关
	const char *phys;
	const char *uniq;
	struct input_id id;

	unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
	//支持事件,位图里每一位代表一个事件
	//#define EV_MAX			0x1f
	//#define EV_CNT			(EV_MAX+1)
	unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
	//支持的按键#define KEY_MAX			0x2ff
	//#define KEY_CNT			(KEY_MAX+1)
	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)];
	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

	unsigned int hint_events_per_packet;

	unsigned int keycodemax;
	unsigned int keycodesize;
	void *keycode;

	int (*setkeycode)(struct input_dev *dev,
			  const struct input_keymap_entry *ke,
			  unsigned int *old_keycode);
	int (*getkeycode)(struct input_dev *dev,
			  struct input_keymap_entry *ke);

	struct ff_device *ff;

	unsigned int repeat_key;
	struct timer_list timer;

	int rep[REP_CNT];

	struct input_mt_slot *mt;
	int mtsize;
	int slot;
	int trkid;

	struct input_absinfo *absinfo;

	unsigned long key[BITS_TO_LONGS(KEY_CNT)];
	unsigned long led[BITS_TO_LONGS(LED_CNT)];
	unsigned long snd[BITS_TO_LONGS(SND_CNT)];
	unsigned long sw[BITS_TO_LONGS(SW_CNT)];
	//文件操作
	int (*open)(struct input_dev *dev);
	void (*close)(struct input_dev *dev);
	int (*flush)(struct input_dev *dev, struct file *file);
	int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);

	struct input_handle __rcu *grab;

	spinlock_t event_lock;
	struct mutex mutex;

	unsigned int users;
	bool going_away;

	bool sync;
	//继承于dev
	struct device dev;

	struct list_head	h_list;
	struct list_head	node;
};

我们还好看到input_id这个成员,它也是一个结构体如下:

struct input_id {
	//总线类型
	__u16 bustype;
	//与厂商有关
	__u16 vendor;
	//产品
	__u16 product;
	//版本
	__u16 version;
};

写驱动之前,我们还要了解一些最基本的API函数:
(1)static inline void set_bit(int nr, unsigned long *addr)

这个函数主要用来设置位,也就是设置对应的事件,因为上面的keybit,absbit,relbit等等都表示对应事件的位,我们需要哪个事件就需要去设置对应的位,所以需要这个函数:

(2)struct input_dev *input_allocate_device(void);

    void input_free_device(struct input_dev *dev);

input_allocate_device这个函数主要用来申请一个input设备,一旦申请成功了,我们就可以去填充上面这个struct input_dev这个结构体了,反之,申请不成功就要释放这个input设备,用input_free_device这个函数,这两个函数总是成对出现的。

(3)int __must_check input_register_device(struct input_dev *);

    void input_unregister_device(struct input_dev *);

     input_register_device这个函数用来注册一个input设备,反之则是注销一个input设备,用input_unregister_device这个函数。

(4)

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
	input_event(dev, EV_KEY, code, !!value);
}

static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
{
	input_event(dev, EV_REL, code, value);
}

static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
	input_event(dev, EV_ABS, code, value);
}

当然我们还会看到以上这些函数,当然还有其它的,也是类似的,在input.h中可以找到。这些是用来上报键值给上层的。
当然我们上报为具体的事件以后,我们还需要上报一个同步事件,防止底层乱报数据,也就是提高数据上报的准确性。

我们会用到下面这个函数:

static inline void input_sync(struct input_dev *dev)
{
	input_event(dev, EV_SYN, SYN_REPORT, 0);
}

暂时我们需要知道的就这么多,接下来开始写代码:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/timer.h>  /*timer*/
#include <asm/uaccess.h>  /*jiffies*/
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/input.h>

struct input_dev *dev ;

static irqreturn_t irq_fuction(int irq, void *dev_id)
{
	struct input_dev *keydev ;
	#if  0
	if(in_interrupt()){
	     printk("%s in interrupt handle!\n",__FUNCTION__);
	}
	#endif
	keydev = dev_id ; //上报键值
	input_report_key(keydev, KEY_HOME,  \
				!gpio_get_value(EXYNOS4_GPX3(2)));
	input_sync(keydev); //上报一个同步事件
	printk("irq:%d\n",irq);
	return IRQ_HANDLED ;
}

static int __init tiny4412_Key_irq_test_init(void)
{
	int err = 0 ;
	int irq_num1 = 0;
	int ret ;
	struct input_id id ;
	dev = input_allocate_device();
	if(IS_ERR_OR_NULL(dev)){
	    ret = -ENOMEM ;
	    goto ERR_alloc;
	}
	//对input_id的成员进行初始化
	dev->name = "tiny4412_home_key" ;
	dev->phys = "YYX_create_key" ;
	dev->uniq = "20170410" ;
	dev->id.bustype = BUS_HOST ;
	dev->id.vendor = ID_PRODUCT ;
	dev->id.version = ID_VENDOR ;
	set_bit(EV_SYN,dev->evbit);	//设置为同步事件,这个宏可以在input.h中找到
	set_bit(EV_KEY,dev->evbit);	//因为是按键,所以要设置成按键事件
	set_bit(KEY_HOME,dev->keybit);	//设置这个按键表示为KEY_HOME这个键,到时用来上报
	ret = input_register_device(dev); //注册input设备
	if(IS_ERR_VALUE(ret))
	    goto ERR_input_reg ;
	irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2));//申请中断号,并注册中断,下降沿触发,设备就是input设备
	err = request_irq(irq_num1,irq_fuction,IRQF_TRIGGER_FALLING,"tiny4412_key1",dev);
	if(err != 0)
	     goto free_irq_flag ;
	//以下除了return 0 都为出错处理
	return 0 ;
	ERR_input_reg:
	input_unregister_device(dev);
	free_irq_flag:
	free_irq(irq_num1,(void *)"key1");
	ERR_alloc:
	return ret ;
}

static void __exit tiny4412_Key_irq_test_exit(void)
{	//为了简单,我这里不需要验证exit的功能,只要在开机init成功就可以了,日后再完善
	int irq_num1 ;
	printk("irq_key exit\n");
	irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2));
	free_irq(irq_num1,dev);
}

module_init(tiny4412_Key_irq_test_init);
module_exit(tiny4412_Key_irq_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("YYX");
MODULE_DESCRIPTION("Exynos4 KEY Driver");

编译好,下载到开发板,用cat /proc/bus/input/devices可以查看到以下信息:

我们看到,Bus,vendor,Product,version就是刚刚我们那个input_id里的结构体成员,name,phys,Uniq就是我们input_dev里的成员,Sysfs的这个input节点是由内核分配的,在/dev/input/event4

我们可以用cat /dev/input/event4,按下按键会打印信息,不过这个并不是我们想要看到的结果,我们可以简单的写一个按键事件,因为我是在android系统上做测试,还没有去写这个程序,有兴趣的同学可以自己去写,然后测测,是否读event4这个事件会有值返回,这其实是在中断处理函数打印的中断irq的值。

这节到此为止,往后我们学习完i2c等等驱动以后,会教大家如何来写触摸屏,重力传感器等复杂的驱动程序,敬请期待。

时间: 2024-11-02 22:06:53

手把手教你写Linux设备驱动---input子系统(二)--按键驱动实现(一)(基于友善之臂4412开发板)的相关文章

手把手教你写Linux设备驱动---input子系统(一)--input事件应用程序的读写实现(基于友善之臂4412开发板)

     这节,我们来说下input子系统,什么是input子系统? input子系统就是输入子系统.      输入子系统是 Linux内核用于管理各种输入设备 (键盘,鼠标,遥控杆,书写板等等 )的部分,用户通过输入子系统进行内核,命令行,图形接口之间的交换.输入子系统在内核里实现,因为设备经常要通过特定的硬件接口被访问 (例如串口, ps/2, usb等等 ),这些硬件接口由内核保护和管理.内核给用户导出一套固定的硬件无关的 input API,供用户空间程序使用. 输入子系统分为三块:

手把手教你写Linux设备驱动---input子系统(三)--电容屏事件坐标读取(基于友善之臂4412开发板)

前面我们学习了鼠标是如何如何通过应用程序来读取事件和坐标值的,后面也写了一个简单的input系统的按键驱动程序. 博文如下,讲的内容非常清楚,给小白来入手当然是非常容易的: http://blog.csdn.net/morixinguan/article/details/69808832 这节,我们来学习一下触摸屏事件获取,然后上一个基于4412开发板ft5x0x型号的x,y坐标值读取,后面我们将从零开始实现这款触摸屏的驱动程序: 首先,我们要明白一个概念,触摸屏在input系统中是一类什么事件

手把手教你写Linux设备驱动---中断(一)(基于友善之臂4412开发板)

今天,我们要来实现一个基于tiny4412开发板上的最简本的按键中断驱动程序,那么,写这个程序之前,我们先来了解下Linux中断的基本知识. 在Linux内核中,每一个能够发出中断请求的硬件设备控制器都有一条名为IRQ的输出线.所有现在存在的IRQ线都与一个名为可编程中断控制器的硬件电路的输入引脚相连,我们可以来看下4412上与板子上相连的按键. 下面这张电路图,也就是4412板子上按键的电路图和CPU的连接关系图: 我们明显可以看到,4个按键分别接在GPX3这几个引脚上,对应着引脚,接下来我们

手把手教你写Linux设备驱动---定时器(一)(基于友善之臂4412开发板)

这个专题我们来说下Linux中的定时器. 在Linux内核中,有这样的一个定时器,叫做内核定时器,内核定时器用于控制某个函数,也就是定时器将要处理的函数在未来的某个特定的时间内执行.内核定时器注册的处理函数只执行一次,即不是循环执行的. 如果对延迟的精度要求不高的话,最简单的实现方法如下---忙等待: Unsigned long j = jiffies + jit_delay * HZ; While(jiffies < j) { -- } 下面来说下具体的参数代表的含义: jiffies:全局变

手把手教你写Linux设备驱动---中断(二)--tasklet实现(基于友善之臂4412开发板)

上节:http://blog.csdn.net/morixinguan/article/details/68958185 在上一节博文中,教会了大家如何来写一个Linux设备的中断程序,实现也非常简单,我们来回顾一下具体的操作流程,只要遵循以下几个步骤即可实现最简单的中断处理程序: 使用中断相关的API和定义时要包含以下头文件: #include <linux/interrupt.h> 然后写中断需要以下步骤 1.申请中断号 使用gpio_to_irq函数,可以从返回值获取到对应的中断号 2.

手把手教你从零实现Linux misc设备驱动二(基于友善之臂4412开发板)

上一节,我教大家实现了一个最简单的MISC设备驱动,那么这节,我们将用一个实例来驱动蜂鸣器,这里为了方便,我就不再写应用程序进行测试,直接在驱动里调用open函数,这个程序是在Android系统里跑起来,后面我会教大家如何在Android下写应用测试程序. 我们参考以前写的蜂鸣器驱动程序,将它移植到我们这个程序里,让它成为一个MISC设备. 参考以前写的文章: http://blog.csdn.net/morixinguan/article/details/50628588 接下来,看看代码:

手把手教你从零实现Linux misc设备驱动一(基于友善之臂4412开发板)

关于如何来写一个misc设备,在前面有篇文章已经介绍了大致的流程,现在就让我们来实现一个最简单的misc设备驱动. http://blog.csdn.net/morixinguan/article/details/52700146 关于前面的字符设备有以下四篇文章,可以做参考: http://blog.csdn.net/morixinguan/article/details/55002774 http://blog.csdn.net/morixinguan/article/details/550

手把手教你写Linux设备驱动---input子系统(四)--电容屏驱动ft5x06编写(一)(基于友善之臂4412开发板)

这一节,我们将从零开始写tiny4412的触摸屏驱动ft5x06,写这节博客之前,先了解下需要什么知识: 1.i2c驱动相关的知识 2.输入子系统 3.中断 4.工作队列 关于i2c驱动相关的知识,在后期的博文里会专门写几篇博文来进行总结,这里就不再说i2c相关的知识,我们先知道怎么用就行了. 首先,我在ts.h构造了一个ts_info_st结构体,用来存放触摸屏的中断线,x坐标,y坐标,压力值. 用ts_st构造了该触摸屏的设备结构体. 我们还是直接看点实际的东西,上代码: ts.h #ifn

手把手教你写Linux设备驱动---中断(三)--workqueue实现(基于友善之臂4412开发板)

上节,我们讲到如何来实现tasklet小任务机制 http://blog.csdn.net/morixinguan/article/details/69666935 这节,我们来实现一下中断下半部的工作队列: 在写这个demo之前,我们要了解一下工作队列的相关数据结构还有API. 需要包含的头文件: #include <linux/workqueue.h> 基本的数据结构: //工作队列结构 struct work_struct { atomic_long_t data; //链表处理 str