您的位置:首页 > 运维架构 > Linux

linux驱动之输入子系统

2015-07-06 20:35 531 查看
对于驱动开发者来说,对按键 触摸屏 鼠标等设备分别进行文件操作显得很繁琐,他们具有一些相同的规律,即内核负责记录数据,应用负责读取数据,因此,内核开发者为了简化驱动开发者的工作,特地创造了输入子系统。

输入子系统分为两层,一个是驱动子系统,一个是文件操作子系统。驱动子系统依旧由驱动开发者完成,当发生一个事件时,驱动向子系统核心发送一个事件报告,子系统核心将这个报告交给文件操作子系统,由后者将具体的事件封装成一个input_event结构体,并传递给应用程序,这样应用程序就知道硬件发生了什么。

以触摸屏驱动程序为例,大致介绍输入子系统的使用。

触摸屏驱动程序

#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/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/delay.h>

struct input_dev *ts_dev;
struct timer_list ts_timer;

struct adc_regs {
	unsigned long adccon;
	unsigned long adctsc;
	unsigned long adcdly;
	unsigned long adcdat0;
	unsigned long adcdat1;
	unsigned long adcupdn;
	unsigned long adcclrint;
	unsigned long reserved;
	unsigned long adcclrintpndnup;
};

static struct adc_regs *adc_regs;

//等待按下
static void wait_for_pen_down(void)
{
	adc_regs->adctsc = 0xd3;
}

//等待抬起
static void wait_for_pen_up(void)
{
	adc_regs->adctsc = 0x1d3;
}

//xy坐标自动转换
static void measure_xy_mode(void)
{
	adc_regs->adctsc = ((1<<7) | (1<<6) | (1<<4) | (1<<3) | (1<<2));
}

//启动adc转换
static void start_adc(void)
{
	adc_regs->adccon |= (1<<0);
}

//触摸中断
irqreturn_t ts_irq(int irq, void *dev_id)
{
	unsigned long data0, data1;
	int down;

	data0 = adc_regs->adcdat0;
	data1 = adc_regs->adcdat1;
	down = (!(data0 & (1 << 15))) && (!(data1 & (1 << 15)));//只有xy都没有值时down才会为0,表示松开
	if(!down)
	{
		wait_for_pen_down();//等待按下
		input_event(ts_dev, EV_ABS, ABS_PRESSURE, 0);
		input_event(ts_dev, EV_KEY, BTN_TOUCH, 0);
		input_sync(ts_dev);//报告失败事件
	}
	else
	{
		measure_xy_mode();//xy自动转换
		start_adc();//开启adc转换
	}
//	adc_regs->adcclrint = 0;//清除adc中断标志
	adc_regs->adcclrintpndnup = 0;//清除触摸中断标志

	return IRQ_HANDLED;
}
//adc中断
irqreturn_t adc_irq(int irq, void *dev_id)
{
	mod_timer(&ts_timer, jiffies + HZ / 100);//10ms
	wait_for_pen_up();//等待抬起

	adc_regs->adcclrint = 0;//清除adc中断标志
//	adc_regs->adcclrintpndnup = 0;//清除触摸中断标志

	return IRQ_HANDLED;
}
//定时器中断
void ts_timer_function(unsigned long data)
{
	int x, y;
	unsigned long data0, data1;
	int down;

	data0 = adc_regs->adcdat0;
	data1 = adc_regs->adcdat1;
	down = (!(data0 & (1 << 15))) && (!(data1 & (1 << 15)));//只有xy都没有值时down才会为0,表示松开
	if(!down)
	{
		wait_for_pen_down();//等待按下
		input_event(ts_dev, EV_ABS, ABS_PRESSURE, 0);
		input_event(ts_dev, EV_KEY, BTN_TOUCH, 0);
		input_sync(ts_dev);//报告失败事件
	}
	else
	{
		x = data0 & 0xfff;
		y = data1 & 0xfff;
		input_event(ts_dev, EV_ABS, ABS_X, x);
		input_event(ts_dev, EV_ABS, ABS_Y, y);
		input_event(ts_dev, EV_ABS, ABS_PRESSURE, 1);
		input_event(ts_dev, EV_KEY, BTN_TOUCH, 1);
		input_sync(ts_dev);

		measure_xy_mode();//xy自动转换
		start_adc();//开启adc转换
	}
}

int ts_init(void)
{
	struct clk *clk;

	ts_dev = input_allocate_device();//分配输入结构体
	
	set_bit(EV_KEY, ts_dev->evbit);//key事件
	set_bit(EV_ABS, ts_dev->evbit);//绝对地址
	set_bit(BTN_TOUCH, ts_dev->keybit);//触摸
	
	input_set_abs_params(ts_dev, ABS_X, 0, 0xfff, 0, 0);//设置x坐标的范围
	input_set_abs_params(ts_dev, ABS_Y, 0, 0xfff, 0, 0);
	input_set_abs_params(ts_dev, ABS_PRESSURE, 0, 1, 0, 0);

	input_register_device(ts_dev);//注册设备

	adc_regs = ioremap(0x7e00b000, sizeof(struct adc_regs));//虚拟地址映射

	clk = clk_get(NULL, "adc");
	clk_enable(clk);

	adc_regs->adccon = (1 << 16) | (1 << 14) | (65 << 6);
	adc_regs->adcdly = 0xffff;

	adc_regs->adcclrintpndnup = 0;//清除触摸中断标志,该标志为1时进入中断,必须软件清零

	request_irq(IRQ_TC, ts_irq, IRQF_SHARED, "ts", NULL);//申请触摸中断
	request_irq(IRQ_ADC, adc_irq, IRQF_SHARED, "adc", NULL);//adc转换完成中断

	init_timer(&ts_timer);
	ts_timer.function = ts_timer_function;
	add_timer(&ts_timer);

	wait_for_pen_down();//等待按下

	return 0;
}

void ts_exit(void)
{
	del_timer(&ts_timer);
	free_irq(IRQ_TC, NULL);
	free_irq(IRQ_ADC, NULL);
	iounmap(adc_regs);
	input_unregister_device(ts_dev);
	input_free_device(ts_dev);
}

module_init(ts_init);
module_exit(ts_exit);
MODULE_LICENSE("GPL");
输入子系统分析

大致看一下一个输入设备结构体包含哪些成员,其余成员暂且不介绍

struct input_dev
{
	unsigned long evbit[BITS_TO_LONGS(EV_CNT)];//所支持的事件类型,包括EV_KEY按键 EV_ABS绝对地址,触摸屏 EV_REL相对地址,鼠标
	unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//对于按键事件,有哪些选项
	struct device dev;//设备结构体
	struct list_head	node;
};
首先,看一下设备分配函数

struct input_dev *input_allocate_device(void)
{
	struct input_dev *dev;
	dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
	if (dev) {
		dev->dev.type = &input_dev_type;
		dev->dev.class = &input_class;
		device_initialize(&dev->dev);
		mutex_init(&dev->mutex);
		spin_lock_init(&dev->event_lock);
		INIT_LIST_HEAD(&dev->h_list);
		INIT_LIST_HEAD(&dev->node);
		__module_get(THIS_MODULE);
	}
	return dev;
}
一般情况下,都会采用设备嵌套的结构,而分配函数也就是对input_dev->dev的初始化以及自身相关的初始化

接下来,看看注册函数

int input_register_device(struct input_dev *dev)
{
	list_add_tail(&dev->node, &input_dev_list);//将结点添加到input_dev链表
	list_for_each_entry(handler, &input_handler_list, node)//遍历链表
		input_attach_handler(dev, handler);
}
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
	const struct input_device_id *id;
	id = input_match_device(handler, dev);
	error = handler->connect(handler, dev, id);
}


在进行一番比较后,如果设备和处理函数匹配,则连接。input_handler结构体如下,该结构体负责处理对应input_dev的输入事件。

struct input_handler {
	void *private;
	void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	bool (*match)(struct input_handler *handler, struct input_dev *dev);
	int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
	void (*disconnect)(struct input_handle *handle);
	void (*start)(struct input_handle *handle);
	const struct file_operations *fops;
	int minor;
	const char *name;
	const struct input_device_id *id_table;
	struct list_head	h_list;
	struct list_head	node;
}
接下来,介绍一下输入事件

事件结构体如下

struct input_event {
	struct timeval time;
	__u16 type;//事件类型
	__u16 code;//事件的具体参数类型
	__s32 value;//参数值
};
发送事件函数如下

void input_event(struct input_dev *dev,
		 unsigned int type, unsigned int code, int value)
{
	if (is_event_supported(type, dev->evbit, EV_MAX)) {//测试是否设置了该事件,一般如下设置set_bit(EV_KEY, ts_dev->evbit);
		input_handle_event(dev, type, code, value);
	}
}


下面以input_event(ts_dev, EV_ABS, ABS_X, x)具体分析input_handle_event函数

static void input_handle_event(struct input_dev *dev,
			       unsigned int type, unsigned int code, int value)
{
	switch (type) //首先判断类型,为EV_ABS
	{
		case EV_ABS:
			if (is_event_supported(code, dev->absbit, ABS_MAX))//测试EV_ABS事件是否设置了该参数
			disposition = input_handle_abs_event(dev, code, &value);
			break;
	}
}


继续调用input_handle_abs_event函数

static int input_handle_abs_event(struct input_dev *dev,
				  unsigned int code, int *pval)
{
	if (is_mt_event && dev->slot != input_abs_get_val(dev, ABS_MT_SLOT)) {
		input_abs_set_val(dev, ABS_MT_SLOT, dev->slot);
		input_pass_event(dev, EV_ABS, ABS_MT_SLOT, dev->slot);
	}
}


如下是关于ABS_X的定义以及结构体input_absinfo

#define ABS_MT_SLOT		0x2f	/* MT slot being modified */
#define ABS_X			0x00
struct input_absinfo {
	__s32 value;
	__s32 minimum;
	__s32 maximum;
	__s32 fuzz;
	__s32 flat;
	__s32 resolution;
};


最后一步,在input_pass_event(dev, EV_ABS, ABS_MT_SLOT, dev->slot);中调用input_handler的event函数,将消息整合成input_event结构体的形式发送给用户,由用户读取信息。具体代码没有深入研究,猜测类似于read、write之类的函数。

触摸屏相关分析

如图所示,是电阻型触摸屏获取坐标值的原理。

对于触摸屏驱动来说,有两个中断,分别是IRQ_TC和IRQ_ADC,前者是触摸中断,后者是ADC转换完成中断。

当进入IRQ_TC中断时,启动adc转换,adc转换的结果x坐标、y坐标分别存放在ADCDAT0和ADCDAT1寄存器中,进入ADC中断后将事件上报给输入子系统。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: