对于驱动开发者来说,对按键 触摸屏 鼠标等设备分别进行文件操作显得很繁琐,他们具有一些相同的规律,即内核负责记录数据,应用负责读取数据,因此,内核开发者为了简化驱动开发者的工作,特地创造了输入子系统。
输入子系统分为两层,一个是驱动子系统,一个是文件操作子系统。驱动子系统依旧由驱动开发者完成,当发生一个事件时,驱动向子系统核心发送一个事件报告,子系统核心将这个报告交给文件操作子系统,由后者将具体的事件封装成一个input_event结构体,并传递给应用程序,这样应用程序就知道硬件发生了什么。
以触摸屏驱动程序为例,大致介绍输入子系统的使用。
触摸屏驱动程序
大致看一下一个输入设备结构体包含哪些成员,其余成员暂且不介绍
接下来,看看注册函数
在进行一番比较后,如果设备和处理函数匹配,则连接。input_handler结构体如下,该结构体负责处理对应input_dev的输入事件。
事件结构体如下
下面以input_event(ts_dev, EV_ABS, ABS_X, x)具体分析input_handle_event函数
继续调用input_handle_abs_event函数
如下是关于ABS_X的定义以及结构体input_absinfo
最后一步,在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中断后将事件上报给输入子系统。
输入子系统分为两层,一个是驱动子系统,一个是文件操作子系统。驱动子系统依旧由驱动开发者完成,当发生一个事件时,驱动向子系统核心发送一个事件报告,子系统核心将这个报告交给文件操作子系统,由后者将具体的事件封装成一个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中断后将事件上报给输入子系统。