基于Android系统的多点触摸屏(MultiTouchScreen)驱动
理论:
输入子系统由来
在Linux中, 应用层对于输入设备(鼠标、键盘、触摸屏等)的操作无非都是open、read、write、ioctl,然后调用驱动层的xxx_open、xxx_read、xxx_write、xxx_ioctl去操作具体的硬件输入设备。如果按照传统的思路,每个输入设备都按照这个套路写这些open、read等,是不是太过于累赘了。所以Linux就定义了一套标准,来标准化这些输入设备驱动,这个标准就叫做输入子系统。通过这个标准,写驱动的人不在重复的写xxx_open、xxx_read等通用代码,而只用完成各个输入设备不同的部分(注册不同的输入设备, 上报不同的输入事件,操作不同的硬件等)。
输入子系统框架
在Linux中,输入子系统是由输入子系统设备驱动层、输入子系统核心层(Input Core)和输入子系统事件处理层(Event Handler)组成。其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。所以这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。下面用图形来描述一下这三者的关系吧!(转)
Linux输入子系统的结构体:
应用空间:
open(“/dev/input/eventx”)
内核空间:
输入子系统设备驱动
向输入系统核心注册一个具体类型的输入设备,检测输入事件的发生,并上报输入事件到输入子系统事件处理层。
输入子系统核心
提供各个输入设备的注册接口,设备驱动上报事件到事件处理层的桥梁。
输入子系统事件处理层
接收设备驱动上报的输入事件,提供应用层访问设备统一的接口(open, read等),将事件通过接口传递到用户空间。
底层硬件:
鼠标 键盘 触摸屏等
通过输入系统的这个框架发现,我们需要做的事情就是对于不同的输入设备,写出对应的设备驱动,并向输入系统核心注册这个输入设备当检测到有输入事件发生,将输入事件上报到输入子系统事件处理层就OK了。
具体操作:
首先,对触摸屏数据的采集是采用i2c接口或者spi接口,本例子以i2c接口为例,那么首先要熟悉i2c驱动的框架。
1. 注册一个i2c_driver结构体,去匹配i2c_device机构体(i2c_device可以通过添加board_info,设备树等方式创建,这里不做详细介绍)
/* 分配/设置i2c_driver */
static struct i2c_driver mts_driver = {
.driver = {
.name = "my_mts",
.owner = THIS_MODULE,
},
.id_table = mts_id_table,
.probe = mts_probe,
.remove = mts_remove,
};
static const struct i2c_device_id mts_id_table[] = {
{ "my_mts", 0 },
{}
};
/* 注册i2c_driver */
i2c_add_driver(&mts_driver);
2. 在i2c的probe函数中分配、设置、注册一个input_dev结构体。
static int mts_probe(struct i2c_client *client, const struct i2c_device_id *id) {
ts_client = client; //记录i2c_client
/* 2.1分配input_dev结构体 */
struct input_dev * ts_dev = input_allocate_device();
/* 2.2设置这个input_dev结构体 */
//设置这个输入设备能产生哪类事件 -- 能产生同步事件EV_SYN 和 绝对坐标事件EV_ABS
set_bit(EV_SYN, ts_dev->evbit);
set_bit(EV_ABS, ts_dev->evbit);
//能产生这类事件中的哪些具体事件 -- X轴坐标ABS_MT_POSITION_X Y轴坐标ABS_MT_POSITION_Y 和触点的ID ABS_MT_TRACKING_ID
set_bit(ABS_MT_TRACKING_ID, ts_dev->absbit); //对于多点触摸屏,每一个触点都有一个ABS_MT_TRACKING_ID
set_bit(ABS_MT_POSITION_X, ts_dev->absbit);
set_bit(ABS_MT_POSITION_Y, ts_dev->absbit);
//这些事件的范围
//支持多少个触点,具体支持多少个,由具体硬件手册决定
input_set_abs_params(ts_dev, ABS_MT_TRACKING_ID, 0, MTS_MAX_ID, 0, 0);
//X轴和Y轴的最大值,由硬件手册看触摸屏分辨率决定
input_set_abs_params(ts_dev, ABS_MT_POSITION_X, 0, MTS_MAX_X, 0, 0);
input_set_abs_params(ts_dev, ABS_MT_POSITION_Y, 0, MTS_MAX_Y, 0, 0);
//Android系统会根据这个name来找相应的配置文件,如果仅仅是Linux驱动则可有可无
//#define FT5X0X_NAME ft5x0x_ts
ts_dev->name = FT5X0X_NAME;
/* 2.3注册这个输入设备 */
input_register_device(ts_dev);
3. 硬件相关的操作:
//初始化一个工作队列,中断下半部使用
INIT_WORK(&mts_work, mts_work_func);
//注册触摸屏对应的外部中断,中断号可以通过gpio_to_irq获取,如果是3.x以后的内核需要通过设备树获取
request_irq(irq, mts_interrupt, IRQ_TYPE_EDGE_FALLING, "my_mts", NULL);
---------------------
4. 实现中断处理函数,并使用中断的底半部机制去处理中断(上报ABS事件)
//中断处理函数
irqreturn_t mts_interrupt(int irqno, void * dev_id)
{
/* 通过i2c读取中断数据即坐标
* 但是i2c是低速设备,所以用中断下半部处理
* */
schedule_work(&mts_work); //调度中断下半部
return IRQ_HANDLED;
}
1
2
3
4
5
6
7
8
9
10
//中断的底半部,来读取中断数据(坐标)
static void mts_work_func(void * t)
{
int i;
int ret;
/* 读取i2c设备,获得触点数据,并上报 */
/* 读数据 */
ret = mts_read_data();
if (ret < 0)
return;
/* 如果没有触点只上报同步事件 */
if (!ts.ts_points) {
input_mt_sync(ts.ts_dev);
input_sync(ts.ts_dev);
return ;
}
for (i = 0; i < ts_points; i++){ /* 将每一个触点进行上报 */
input_report_abs(ts_dev, ABS_MT_POSITION_X, ts_events[i].x); //第i个触点的x坐标
input_report_abs(ts_dev, ABS_MT_POSITION_Y, ts_events[i].y); //第i个触点的y坐标
input_report_abs(ts_dev, ABS_MT_TRACKING_ID, ts_events[i].id); //第i个触点的ID
/* 表示一个触点上报完成 */
input_mt_sync(ts_dev);
}
/* 表示某一时刻一次上报事件结束 */
input_sync(ts_dev);
}
---------------------
//通过i2c读取中断数据
static int mts_read_data(void) {
u8 buf[32] = { 0 };
int ret;
ret = i2c_read_data(ts_client, buf, 31);
if (ret < 0) {
return ret;
}
ts_points= buf[2] & (0xf); //根据驱动IC手册,读取触点的个数
if (!ts_points) { //如果触点个数为0,则只上报同步事件
input_mt_sync(ts_dev);
input_sync(ts_dev);
return 1;
}
//读取每一个触点的坐标,id,并保存到ts_event中,具体如何获取buf中数据的格式,参考具体datasheet
switch (ts_points) {
case 5:
ts_events[4].x = (s16)(buf[0x1b] & 0x0F)<<8 | (s16)buf[0x1c];
ts_events[4].y = (s16)(buf[0x1d] & 0x0F)<<8 | (s16)buf[0x1e];
ts_events[4].id= (s16)(buf[0x1d] >> 4);
case 4:
ts_events[3].x = (s16)(buf[0x15] & 0x0F)<<8 | (s16)buf[0x16];
ts_events[3].y = (s16)(buf[0x17] & 0x0F)<<8 | (s16)buf[0x18];
ts_events[3].id= (s16)(buf[0x17] >> 4);
case 3:
ts_events[2].x = (s16)(buf[0x0f] & 0x0F)<<8 | (s16)buf[0x10];
ts_events[2].y = (s16)(buf[0x11] & 0x0F)<<8 | (s16)buf[0x12];
ts_events[2].id= (s16)(buf[0x11] >> 4);
case 2:
ts_events[1].x = (s16)(buf[0x09] & 0x0F)<<8 | (s16)buf[0x0a];
ts_events[1].y = (s16)(buf[0x0b] & 0x0F)<<8 | (s16)buf[0x0c];
ts_events[1].id= (s16)(buf[0x0b] >> 4);
case 1:
ts_events[0].x = (s16)(buf[0x03] & 0x0F)<<8 | (s16)buf[0x04];
ts_events[0].y = (s16)(buf[0x05] & 0x0F)<<8 | (s16)buf[0x06];
ts_events[0].id= (s16)(buf[0x05] >> 4);
break;
case 0: //没有触点
return 0;
default:
return -1;
}
return 0;
}
//保存数据的结构体
struct tiny4412_ts_event {
int x;
int y;
int id;
};
struct tiny4412_ts_event ts_events[16];
---------------------
//i2c读取数据
static int i2c_read_data(struct i2c_client * client, char *rxdata, int length) {
int ret;
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = 0,
.len = 1,
.buf = rxdata,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = length,
.buf = rxdata,
},
};
ret = i2c_transfer(client->adapter, msgs, 2);
if (ret < 0)
pr_err("%s: i2c read error: %d\n", __func__, ret);
return ret;
}
实验:
为Android创建配置文件(idc文件),配置文件的名字和注册的input_dev的name一致 比如ft5x0x_ts.idc并放入Android文件系统的/system/usr/idc/,里面的内容为:touch.deviceType = touchScreen //表示是触摸屏设备,参考官方文档https://source.android.com/devices/input/touch-devices。
将my_mts.c放入linux-3.0.86/drivers/input/touchscreen/目录,然后修改Makefile,将原有的触摸屏驱动去掉,把新的编译进去,最后make生成zImage
烧写新的zImage,重启设备,触摸屏幕,OK。