一、基本开发环境和触摸芯片接口
1、基本开发环境
PC机:Ubuntu12.04 64bit
GCC工具链条:arm-none-linux-gnueabi (gcc version 4.5.1 (ctng-1.8.1-FA))
开发板:友善之臂Tiny4412
板载系统:Android5.0.2
板载系统内核:Linux-3.0.86
2、触摸芯片接口
从上图中可以看出,触摸芯片和开发板之间通过三条线链接:其中两条是用于IIC数据传输,另外一条是用于中断引脚。
二、驱动程序的编写
1、驱动框架和前期准备
由上面可知驱动触摸芯片和主机之间是通过IIC接口链接的,所以需要使用IIC驱动框架:总线、设备、驱动模型;又考虑到触摸屏最终是通过输入子系统的形式来上报输入事件,所以还需要使用输入子系统驱动框架。所以从驱动程序的总体框架来说:要实现IIC驱动框架和输入子系统驱动框架。
为了更好地编写驱动程序,在开始之前,先定义一些宏来表示驱动程序使用的常量,以及定义一些全局变量或者结构体来更好地维护和方便驱动的开发,这一部分代码的实现如下:
/* 定义触摸驱动的名字 */
#define TINY4412_TS_NAME "ft5x0x_ts"
#define TINY4412_TS_MAX_X 800 // x轴最大分辨率
#define TINY4412_TS_MAX_Y 480 // y轴最大分辨率
#define TINY4412_TS_MAX_ID 10 // 由硬件来决定
/* 定义一个结构体用来描述触摸点的信息 */
struct yl_tiny4412_ts_event {
int x; // 获得的触摸点的x坐标
int y; // 获得的触摸点的y坐标
int id; // 获得触摸点的id,用来表示对应的触摸点
};
/* 定义一个全局结构体存放相关成员,更好的方便驱动程序的编写 */
struct yl_tiny4412_ts_config
{
int gpio; // 定义触摸屏外部中断的GPIO口
int touch_points; // 表示当前同时有几个触摸点或者说当前是几点触摸
struct i2c_client *i2c_client; // 用于存放i2c_client指针变量
struct input_dev *input_dev; // 定义一个input_dev结构体指针变量
struct work_struct work_queue; // 定义工作队列,用来处理和触摸相关的事件
struct yl_tiny4412_ts_event ts_event[10]; // 定义一个描述触摸点的数组
};
/* 定义一个 yl_tiny4412_ts_config 结构体的全局变量 */
static struct yl_tiny4412_ts_config yl_tiny4412_ts;
2、IIC框架部分的具体实现
由于IIC驱动程序采用总线、设备、驱动模型来进行实现,所以要自己来实现设备端和驱动端相关的代码。但是在内核中已经实现了设备相关的代码,如下所示:
static struct i2c_board_info i2c_devs1[] __initdata = {
#ifdef CONFIG_TOUCHSCREEN_FT5X0X
{
I2C_BOARD_INFO("ft5x0x_ts", (0x70 >> 1)),
.platform_data = &ft5x0x_pdata,
},
#endif
{
I2C_BOARD_INFO("wm8994", 0x1a),
.platform_data = &wm8994_platform_data,
},
};
所以这里只需要实现IIC驱动端,相关的代码即可。定义一个i2c_driver的结构体变量,具体实现如下:
/* 定义一个id表,用于i2c驱动和设备的匹配 */
static const struct i2c_device_id tiny4412_ts_id[] = {
{ TINY4412_TS_NAME, 0 },
{ }
};
/* 定义一个i2c_driver的实例 */
static struct i2c_driver tiny4412_ts_driver = {
.probe = tiny4412_ts_probe,
.remove = __devexit_p(tiny4412_ts_remove),
.id_table = tiny4412_ts_id,
.driver = {
.name = TINY4412_TS_NAME,
.owner = THIS_MODULE,
},
};
给它添加了一个id_table来匹配设备端代码,在驱动的入口和出口处来注册iic驱动端,代码实现如下:
/* 驱动的入口函数 */
static int __init tiny4412_ts_init(void)
{
yl_tiny4412_ts.gpio = EXYNOS4_GPX1(6); // 获得和触摸屏外部中断相关的GPIO口
return i2c_add_driver(&tiny4412_ts_driver);
}
/* 驱动的出口函数 */
static void __exit tiny4412_ts_exit(void)
{
i2c_del_driver(&tiny4412_ts_driver);
}
因为在内核中有同名的平台设备,所以这时候平台驱动的probe函数将会被调用。
3、input输入子系统部分实现
可以在iic驱动程序的probe函数当中添加和输入子系统相关的代码,这部分的代码实现如下:
/* 当驱动和设备匹配成功后会调用probe函数 */
static int tiny4412_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
yl_tiny4412_ts.i2c_client = client;
/* 1、分配一个input_dev的结构体指针变量 */
yl_tiny4412_ts.input_dev= input_allocate_device();
if (!(yl_tiny4412_ts.input_dev)) {
printk("tiny4412_ts_probe->input_allocate_device error!\n");
ret = -ENOMEM;
goto out1;
}
/* 2、设置这个结构体变量 */
/* 2.1 能产生哪类事件 */
set_bit(EV_SYN, yl_tiny4412_ts.input_dev->evbit); // 同步事件
set_bit(EV_ABS, yl_tiny4412_ts.input_dev->evbit); // 绝对位移类事件
/* 2.2 能产生该类事件中哪些事件 */
set_bit(ABS_MT_TRACKING_ID, yl_tiny4412_ts.input_dev->absbit);
set_bit(ABS_MT_POSITION_X, yl_tiny4412_ts.input_dev->absbit);
set_bit(ABS_MT_POSITION_Y, yl_tiny4412_ts.input_dev->absbit);
/* 2.3 确定产生的这些事件的范围 */
input_set_abs_params(yl_tiny4412_ts.input_dev, ABS_MT_TRACKING_ID, 0, TINY4412_TS_MAX_ID, 0, 0);
input_set_abs_params(yl_tiny4412_ts.input_dev, ABS_MT_POSITION_X, 0, TINY4412_TS_MAX_X, 0, 0);
input_set_abs_params(yl_tiny4412_ts.input_dev, ABS_MT_POSITION_Y, 0, TINY4412_TS_MAX_Y, 0, 0);
/* 2.4 其他设置 */
set_bit(INPUT_PROP_DIRECT, yl_tiny4412_ts.input_dev->propbit);
yl_tiny4412_ts.input_dev->name = TINY4412_TS_NAME; // Android系统会根据它找到配置文件
/* 3、注册这个input_dev结构体指针变量 */
ret = input_register_device(yl_tiny4412_ts.input_dev);
if (ret)
{
printk("tiny4412_ts_probe->input_register_device error!\n");
ret = -ENODEV;
goto out2;
}
/* 4、硬件相关的操作 */
INIT_WORK(&yl_tiny4412_ts.work_queue, tiny4412_work_queue_func); // 初始化工作队列
ret = request_irq(gpio_to_irq(yl_tiny4412_ts.gpio), tiny4412_ts_interrupt,
IRQ_TYPE_EDGE_FALLING /*IRQF_TRIGGER_FALLING*/, "tiny4412_ts", NULL);
if (ret)
{
printk("tiny4412_ts_probe->request_irq error!\n");
ret = -EIO;
goto out3;
}
return 0; // 一切正常则返回0
out3:
input_unregister_device(yl_tiny4412_ts.input_dev);
out2:
input_free_device(yl_tiny4412_ts.input_dev);
out1:
return ret;
}
这一部分代码主要是实现input输入子系统的相关代码,注册和多点触摸相关的输入事件,之后要初始化了一个工作队列用来处理输入事件的上报操作,最后实现了和触摸屏相关的外部中断的注册通过它来检测触摸屏是按下还是松开。
当触摸屏被按下或者松开时,会触发对应的外部中断,这时候外部中断函数会被调用,外部中断函数实现如下:
/* 触摸屏触发时的中断处理程序 */
static irqreturn_t tiny4412_ts_interrupt(int irq, void *dev_id)
{
/* 通过工作队列唤醒内核线程来处理触摸相关事件 */
schedule_work(&yl_tiny4412_ts.work_queue);
return IRQ_HANDLED;
}
从上面可以看出,中断处理程序只是唤醒工作队列,让工作队列来处理具体和触摸相关的事件,这是因为IIC接口传输速度慢,会导致延时,而中断本身需要快速响应,所以直接在中断函数中处理触摸输入事件,会影响整个系统的性能。下面来看一下工作队列处理函数的具体实现:
/* 工作队列处理 */
static void tiny4412_work_queue_func(struct work_struct *work)
{
int i;
int ret;
/* 读取触摸屏数据 */
ret = tiny4412_ts_ft5x0x_read_data();
if (ret < 0) /* 触摸屏返回数据失败,直接返回 */
return;
/* 如果触摸屏松开 */
if (!yl_tiny4412_ts.touch_points) {
input_mt_sync(yl_tiny4412_ts.input_dev);
input_sync(yl_tiny4412_ts.input_dev);
return;
}
/* 如果触摸屏按下 */
for (i = 0; i < yl_tiny4412_ts.touch_points; i++) { /* 每一个点 */
input_report_abs(yl_tiny4412_ts.input_dev, ABS_MT_POSITION_X, yl_tiny4412_ts.ts_event[i].x);
input_report_abs(yl_tiny4412_ts.input_dev, ABS_MT_POSITION_Y, yl_tiny4412_ts.ts_event[i].y);
input_report_abs(yl_tiny4412_ts.input_dev, ABS_MT_TRACKING_ID, yl_tiny4412_ts.ts_event[i].id);
input_mt_sync(yl_tiny4412_ts.input_dev);
}
input_sync(yl_tiny4412_ts.input_dev);
}
可以看出它通过调用tiny4412_ts_ft5x0x_read_data()这个函数来获取触摸点相关数据,然后来对触摸点事件进行上报,下面来看一看这个函数的具体实现:
static int tiny4412_ts_ft5x0x_read_data(void) {
u8 buf[32] = { 0 };
int ret;
ret = tiny4412_ts_ft5x0x_i2c_rxdata(yl_tiny4412_ts.i2c_client, buf, 31);
/* 读取触摸屏数据失败,返回负数 */
if (ret < 0) {
printk("%s: read touch data failed, %d\n", __func__, ret);
return ret;
}
/* 获得当前触摸屏的触摸点数 */
yl_tiny4412_ts.touch_points = buf[2] & 0x0f;
switch (yl_tiny4412_ts.touch_points) {
case 5:
yl_tiny4412_ts.ts_event[4].x = (s16)(buf[0x1b] & 0x0F)<<8 | (s16)buf[0x1c];
yl_tiny4412_ts.ts_event[4].y = (s16)(buf[0x1d] & 0x0F)<<8 | (s16)buf[0x1e];
yl_tiny4412_ts.ts_event[4].id = buf[0x1d]>>4;
case 4:
yl_tiny4412_ts.ts_event[3].x = (s16)(buf[0x15] & 0x0F)<<8 | (s16)buf[0x16];
yl_tiny4412_ts.ts_event[3].y = (s16)(buf[0x17] & 0x0F)<<8 | (s16)buf[0x18];
yl_tiny4412_ts.ts_event[3].id = buf[0x17]>>4;
case 3:
yl_tiny4412_ts.ts_event[2].x = (s16)(buf[0x0f] & 0x0F)<<8 | (s16)buf[0x10];
yl_tiny4412_ts.ts_event[2].y = (s16)(buf[0x11] & 0x0F)<<8 | (s16)buf[0x12];
yl_tiny4412_ts.ts_event[2].id = buf[0x11]>>4;
case 2:
yl_tiny4412_ts.ts_event[1].x = (s16)(buf[0x09] & 0x0F)<<8 | (s16)buf[0x0a];
yl_tiny4412_ts.ts_event[1].y = (s16)(buf[0x0b] & 0x0F)<<8 | (s16)buf[0x0c];
yl_tiny4412_ts.ts_event[1].id = buf[0x0b]>>4;
case 1:
yl_tiny4412_ts.ts_event[0].x = (s16)(buf[0x03] & 0x0F)<<8 | (s16)buf[0x04];
yl_tiny4412_ts.ts_event[0].y = (s16)(buf[0x05] & 0x0F)<<8 | (s16)buf[0x06];
yl_tiny4412_ts.ts_event[0].id = buf[0x05]>>4;
break;
case 0:
return 0;
default:
return -1;
}
return 0;
}
它又会调用tiny4412_ts_ft5x0x_i2c_rxdata()函数通过iic接口来取出触摸屏数据,然后首先获得触摸屏上的触摸点个数,然后通过计算发这些触摸点的数据放在事先定义好的数组中,那么tiny4412_ts_ft5x0x_i2c_rxdata()这个函数的具体实现是怎样的呢?如下所示:
static int tiny4412_ts_ft5x0x_i2c_rxdata(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;
}
从上面可以看出,它通过i2c_transfer()函数从触摸芯片当中读取触摸的原始数据。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* 定义触摸驱动的名字 */
#define TINY4412_TS_NAME "ft5x0x_ts"
#define TINY4412_TS_MAX_X 800 // x轴最大分辨率
#define TINY4412_TS_MAX_Y 480 // y轴最大分辨率
#define TINY4412_TS_MAX_ID 10 // 由硬件来决定
/* 定义一个结构体用来描述触摸点的信息 */
struct yl_tiny4412_ts_event {
int x; // 获得的触摸点的x坐标
int y; // 获得的触摸点的y坐标
int id; // 获得触摸点的id,用来表示对应的触摸点
};
/* 定义一个全局结构体存放相关成员,更好的方便驱动程序的编写 */
struct yl_tiny4412_ts_config
{
int gpio; // 定义触摸屏外部中断的GPIO口
int touch_points; // 表示当前同时有几个触摸点或者说当前是几点触摸
struct i2c_client *i2c_client; // 用于存放i2c_client指针变量
struct input_dev *input_dev; // 定义一个input_dev结构体指针变量
struct work_struct work_queue; // 定义工作队列,用来处理和触摸相关的事件
struct yl_tiny4412_ts_event ts_event[10]; // 定义一个描述触摸点的数组
};
/* 定义一个 yl_tiny4412_ts_config 结构体的全局变量 */
static struct yl_tiny4412_ts_config yl_tiny4412_ts;
static int tiny4412_ts_ft5x0x_i2c_rxdata(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;
}
static int tiny4412_ts_ft5x0x_read_data(void) {
u8 buf[32] = { 0 };
int ret;
ret = tiny4412_ts_ft5x0x_i2c_rxdata(yl_tiny4412_ts.i2c_client, buf, 31);
/* 读取触摸屏数据失败,返回负数 */
if (ret < 0) {
printk("%s: read touch data failed, %d\n", __func__, ret);
return ret;
}
/* 获得当前触摸屏的触摸点数 */
yl_tiny4412_ts.touch_points = buf[2] & 0x0f;
switch (yl_tiny4412_ts.touch_points) {
case 5:
yl_tiny4412_ts.ts_event[4].x = (s16)(buf[0x1b] & 0x0F)<<8 | (s16)buf[0x1c];
yl_tiny4412_ts.ts_event[4].y = (s16)(buf[0x1d] & 0x0F)<<8 | (s16)buf[0x1e];
yl_tiny4412_ts.ts_event[4].id = buf[0x1d]>>4;
case 4:
yl_tiny4412_ts.ts_event[3].x = (s16)(buf[0x15] & 0x0F)<<8 | (s16)buf[0x16];
yl_tiny4412_ts.ts_event[3].y = (s16)(buf[0x17] & 0x0F)<<8 | (s16)buf[0x18];
yl_tiny4412_ts.ts_event[3].id = buf[0x17]>>4;
case 3:
yl_tiny4412_ts.ts_event[2].x = (s16)(buf[0x0f] & 0x0F)<<8 | (s16)buf[0x10];
yl_tiny4412_ts.ts_event[2].y = (s16)(buf[0x11] & 0x0F)<<8 | (s16)buf[0x12];
yl_tiny4412_ts.ts_event[2].id = buf[0x11]>>4;
case 2:
yl_tiny4412_ts.ts_event[1].x = (s16)(buf[0x09] & 0x0F)<<8 | (s16)buf[0x0a];
yl_tiny4412_ts.ts_event[1].y = (s16)(buf[0x0b] & 0x0F)<<8 | (s16)buf[0x0c];
yl_tiny4412_ts.ts_event[1].id = buf[0x0b]>>4;
case 1:
yl_tiny4412_ts.ts_event[0].x = (s16)(buf[0x03] & 0x0F)<<8 | (s16)buf[0x04];
yl_tiny4412_ts.ts_event[0].y = (s16)(buf[0x05] & 0x0F)<<8 | (s16)buf[0x06];
yl_tiny4412_ts.ts_event[0].id = buf[0x05]>>4;
break;
case 0:
return 0;
default:
return -1;
}
return 0;
}
/* 工作队列处理 */
static void tiny4412_work_queue_func(struct work_struct *work)
{
int i;
int ret;
/* 读取触摸屏数据 */
ret = tiny4412_ts_ft5x0x_read_data();
if (ret < 0) /* 触摸屏返回数据失败,直接返回 */
return;
/* 如果触摸屏松开 */
if (!yl_tiny4412_ts.touch_points) {
input_mt_sync(yl_tiny4412_ts.input_dev);
input_sync(yl_tiny4412_ts.input_dev);
return;
}
/* 如果触摸屏按下 */
for (i = 0; i < yl_tiny4412_ts.touch_points; i++) { /* 每一个点 */
input_report_abs(yl_tiny4412_ts.input_dev, ABS_MT_POSITION_X, yl_tiny4412_ts.ts_event[i].x);
input_report_abs(yl_tiny4412_ts.input_dev, ABS_MT_POSITION_Y, yl_tiny4412_ts.ts_event[i].y);
input_report_abs(yl_tiny4412_ts.input_dev, ABS_MT_TRACKING_ID, yl_tiny4412_ts.ts_event[i].id);
input_mt_sync(yl_tiny4412_ts.input_dev);
}
input_sync(yl_tiny4412_ts.input_dev);
}
/* 触摸屏触发时的中断处理程序 */
static irqreturn_t tiny4412_ts_interrupt(int irq, void *dev_id)
{
/* 通过工作队列唤醒内核线程来处理触摸相关事件 */
schedule_work(&yl_tiny4412_ts.work_queue);
return IRQ_HANDLED;
}
/* 当驱动和设备匹配成功后会调用probe函数 */
static int tiny4412_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
yl_tiny4412_ts.i2c_client = client;
/* 1、分配一个input_dev的结构体指针变量 */
yl_tiny4412_ts.input_dev= input_allocate_device();
if (!(yl_tiny4412_ts.input_dev)) {
printk("tiny4412_ts_probe->input_allocate_device error!\n");
ret = -ENOMEM;
goto out1;
}
/* 2、设置这个结构体变量 */
/* 2.1 能产生哪类事件 */
set_bit(EV_SYN, yl_tiny4412_ts.input_dev->evbit); // 同步事件
set_bit(EV_ABS, yl_tiny4412_ts.input_dev->evbit); // 绝对位移类事件
/* 2.2 能产生该类事件中哪些事件 */
set_bit(ABS_MT_TRACKING_ID, yl_tiny4412_ts.input_dev->absbit);
set_bit(ABS_MT_POSITION_X, yl_tiny4412_ts.input_dev->absbit);
set_bit(ABS_MT_POSITION_Y, yl_tiny4412_ts.input_dev->absbit);
/* 2.3 确定产生的这些事件的范围 */
input_set_abs_params(yl_tiny4412_ts.input_dev, ABS_MT_TRACKING_ID, 0, TINY4412_TS_MAX_ID, 0, 0);
input_set_abs_params(yl_tiny4412_ts.input_dev, ABS_MT_POSITION_X, 0, TINY4412_TS_MAX_X, 0, 0);
input_set_abs_params(yl_tiny4412_ts.input_dev, ABS_MT_POSITION_Y, 0, TINY4412_TS_MAX_Y, 0, 0);
/* 2.4 其他设置 */
set_bit(INPUT_PROP_DIRECT, yl_tiny4412_ts.input_dev->propbit);
yl_tiny4412_ts.input_dev->name = TINY4412_TS_NAME; // Android系统会根据它找到配置文件
/* 3、注册这个input_dev结构体指针变量 */
ret = input_register_device(yl_tiny4412_ts.input_dev);
if (ret)
{
printk("tiny4412_ts_probe->input_register_device error!\n");
ret = -ENODEV;
goto out2;
}
/* 4、硬件相关的操作 */
INIT_WORK(&yl_tiny4412_ts.work_queue, tiny4412_work_queue_func); // 初始化工作队列
ret = request_irq(gpio_to_irq(yl_tiny4412_ts.gpio), tiny4412_ts_interrupt,
IRQ_TYPE_EDGE_FALLING /*IRQF_TRIGGER_FALLING*/, "tiny4412_ts", NULL);
if (ret)
{
printk("tiny4412_ts_probe->request_irq error!\n");
ret = -EIO;
goto out3;
}
return 0; // 一切正常则返回0
out3:
input_unregister_device(yl_tiny4412_ts.input_dev);
out2:
input_free_device(yl_tiny4412_ts.input_dev);
out1:
return ret;
}
/* 当驱动和设备分离时会调用remove函数 */
static int __devexit tiny4412_ts_remove(struct i2c_client *client)
{
free_irq(gpio_to_irq(yl_tiny4412_ts.gpio), NULL);
input_unregister_device(yl_tiny4412_ts.input_dev);
input_free_device(yl_tiny4412_ts.input_dev);
cancel_work_sync(&yl_tiny4412_ts.work_queue);
return 0;
}
/* 定义一个id表,用于i2c驱动和设备的匹配 */
static const struct i2c_device_id tiny4412_ts_id[] = {
{ TINY4412_TS_NAME, 0 },
{ }
};
/* 定义一个i2c_driver的实例 */
static struct i2c_driver tiny4412_ts_driver = {
.probe = tiny4412_ts_probe,
.remove = __devexit_p(tiny4412_ts_remove),
.id_table = tiny4412_ts_id,
.driver = {
.name = TINY4412_TS_NAME,
.owner = THIS_MODULE,
},
};
/* 驱动的入口函数 */
static int __init tiny4412_ts_init(void)
{
yl_tiny4412_ts.gpio = EXYNOS4_GPX1(6); // 获得和触摸屏外部中断相关的GPIO口
return i2c_add_driver(&tiny4412_ts_driver);
}
/* 驱动的出口函数 */
static void __exit tiny4412_ts_exit(void)
{
i2c_del_driver(&tiny4412_ts_driver);
}
/* 对入口函数和出口函数进行修饰 */
module_init(tiny4412_ts_init);
module_exit(tiny4412_ts_exit);
/* 给驱动添加"GPL"协议 */
MODULE_LICENSE("GPL");