Linux驱动之触摸屏(4)-多点触控
4.1 概述
此驱动支持it7260触摸屏控制器,最多支持三点触摸,已在CPU: s5pc110、linux-2.6.32.9、android-2.2上测试通过。原本以为三天就能搞定,最后还是用了一个礼拜才弄完。水平有限,可能存在一些bug,请及时反馈给我(cjok.liao@gmail.com)。
触摸屏驱动主要分为两个部分:
I2C驱动部分:主要负责将设备挂接到I2C总线上,实现数据传输;
输入子系统部分:负责把获取到的数据上报到用户空间。
中断下半部采用延迟的工作队列,完成数据的解析和上报工作。
其他都要参考控制器的数据手册来完成,比如像数据包的解析,数据传输协议(标准的I2C协议)。
4.2 驱动解析
/*
* multi touch screen driver for it7260
* base on multi-touch protocol A
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <linux/i2c.h>
#include <linux/timer.h>
/* buffer address */
#define CMD_BUF 0x20 /* command buffer (write only) */
#define SYS_CMD_BUF 0x40 /* systerm command buffer (write only) */
#define QUERY_BUF 0x80 /* query buffer (read only) */
#define CMD_RSP_BUF 0xA0 /* command response buffer (read only) */
#define SYS_CMD_RSP_BUF 0xC0 /* systerm command response buffer (read only) */
#define POINT_INFO_BUF 0xE0 /* point information buffer (read only) */
/* 构造一个触摸屏设备结构体 */
struct it7260_ts_priv {
struct i2c_client *client; /* I2C 设备 */
struct input_dev *input; /* 输入设备结构体 */
struct delayed_work work; /* 延迟工作队列 */
struct mutex mutex; /* 互斥体 */
int irq; /* 中断 */
};
/**
* 发送和接受函数,虽然内核中提供了i2c_master_recv和i2c_master_send,
* 但是这两个函数只适合单个msg的情况
*/
/**
* i2c_master_read_it7260 - issue two I2C message in master receive mode
* @client: handler to slave device
* @buf_index: buffer address
* @buf_data: where to store data read from slave
* @len_data: the bytes of buf_data to read
*
* returns negative errno, or else the number of bytes read
*/
static int i2c_master_read_it7260(struct i2c_client *client,
unsigned char buf_index, unsigned char *buf_data,
unsigned short len_data)
{
int ret;
struct i2c_msg msgs[2] = {
{
.addr = client->addr,
.flags = I2C_M_NOSTART,
.len = 1,
.buf = &buf_index,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = len_data,
.buf = buf_data,
}
};
ret = i2c_transfer(client->adapter, msgs, 2);
return (ret == 2) ? len_data : ret;
}
/**
* i2c_master_write_it7260 - issue a single I2C message in master transmit mode
* @client: handler to slave device
* @buf_index: buffer address
* @buf_data: data that wile be write to the slave
* @len_data: the bytes of buf_data to write
*
* returns negative errno, or else the number of bytes written
*/
static int i2c_master_write_it7260(struct i2c_client *client,
unsigned char buf_index, unsigned char const *buf_data,
unsigned short len_data)
{
unsigned char buf[2];
int ret;
struct i2c_msg msgs[1] = {
{
.addr = client->addr,
.flags = 0, /* default write flag */
.len = len_data + 1,
.buf = buf,
}
};
buf[0] = buf_index;
memcpy(&buf[1], buf_data, len_data);
ret = i2c_transfer(client->adapter, msgs, 1);
return (ret == 1) ? sizeof(buf) : ret;
}
/**
* 延迟工作,当产生中断时调用,负责从I2C总线上读取数据,然后按照数
* 据手册上的进行解析,然后进行上报。
*/
static void it7260_ts_poscheck(struct work_struct *work)
{
struct it7260_ts_priv *priv = container_of(work,
struct it7260_ts_priv, work.work);
unsigned char buf[14];
unsigned short xpos[3] = {0}, ypos[3] = {0};
unsigned char event[3] = {0};
unsigned char query = 0;
int ret, i;
mutex_lock(&priv->mutex);
i2c_master_read_it7260(priv->client, QUERY_BUF, &query, 1);
if (!(query & 0x80)) {
dev_err(&priv->client->dev, "no finger touch\n");
goto out;
}
memset(&buf, 0, sizeof(buf));
ret = i2c_master_read_it7260(priv->client, POINT_INFO_BUF, buf, 14);
if (ret != 14) {
dev_err(&priv->client->dev, "failed to read point info buffer\n");
goto out;
}
/* touch key */
if (buf[0] == 0x41) {
dev_info(&priv->client->dev, "the key number %d\n", buf[1]);
if (buf[1] == 0x04)
input_report_key(priv->input, KEY_HOME, !!buf[2]);
else if (buf[1] == 0x03)
input_report_key(priv->input, KEY_MENU, !!buf[2]);
else if (buf[1] == 0x02)
input_report_key(priv->input, KEY_BACK, !!buf[2]);
else if (buf[1] == 0x01)
input_report_key(priv->input, KEY_POWER, !!buf[2]);
else
goto out;
input_sync(priv->input);
goto out;
}
/* finger 0 */
if (buf[0] & 0x01) {
xpos[0] = ((buf[3] & 0x0F) << 8) | buf[2];
ypos[0] = ((buf[3] & 0xF0) << 4) | buf[4];
event[0] = buf[5] & 0x0F;
}
/* finger 1 */
if (buf[0] & 0x02) {
xpos[1] = ((buf[7] & 0x0F) << 8) | buf[6];
ypos[1] = ((buf[7] & 0xF0) << 4) | buf[8];
event[1] = buf[9] & 0x0F;
}
/* finger 2 */
if (buf[0] & 0x04) {
xpos[2] = ((buf[11] & 0x0F) << 8) | buf[10];
ypos[2] = ((buf[11] & 0xF0) << 4) | buf[12];
event[2] = buf[13] & 0x0F;
}
for (i = 0; i < 3; i++) {
input_report_abs(priv->input, ABS_MT_POSITION_X, ypos[i]);
input_report_abs(priv->input, ABS_MT_POSITION_Y, xpos[i]);
input_report_abs(priv->input, ABS_MT_TOUCH_MAJOR, !!event[i]);
input_report_abs(priv->input, ABS_MT_WIDTH_MAJOR, 0);
input_mt_sync(priv->input);
dev_info(&priv->client->dev, "finger %d > xpos = %d, \
ypos = %d, event = %d\n", i, ypos[i], xpos[i], event[i]);
}
input_sync(priv->input);
out:
mutex_unlock(&priv->mutex);
enable_irq(priv->irq);
}
/* 中断服务子程序,产生中断后,延迟(HZ/20)个tick后调度工作 */
static irqreturn_t it7260_ts_isr(int irq, void *dev_id)
{
struct it7260_ts_priv *priv = dev_id;
disable_irq_nosync(irq);
schedule_delayed_work(&priv->work, HZ / 20);
return IRQ_HANDLED;
}
/**
* it7260_identify_capsensor - identify capacitance sensor model
*
* returns error -1, or else suc 0
*/
static int it7260_identify_capsensor(struct i2c_client *client)
{
unsigned char buf[10];
unsigned char query = 0;
do {
i2c_master_read_it7260(client, QUERY_BUF, &query, 1);
} while (query & 0x01);
/* 0x00: the command of identify cap sensor */
buf[0] = 0x00;
i2c_master_write_it7260(client, CMD_BUF, buf, 1);
do {
i2c_master_read_it7260(client, QUERY_BUF, &query, 1);
} while (query & 0x01);
memset(&buf, 0, sizeof(buf));
i2c_master_read_it7260(client, CMD_RSP_BUF, buf, 10);
dev_err(&client->dev, "len = %d, %c%c%c\n", buf[0], buf[1], buf[2], buf[3]);
if (buf[1] != 'I' || buf[2] != 'T' || buf[3] != 'E')
return -1;
return 0;
}
/* probe函数,在i2c设备和i2c驱动匹配时会调用此函数来完成相应的工作 */
static int it7260_ts_probe(struct i2c_client *client,
const struct i2c_device_id *idp)
{
struct it7260_ts_priv *priv;
struct input_dev *input;
int error;
/* 识别此设备型号是否为it7260 */
error = it7260_identify_capsensor(client);
if (error) {
dev_err(&client->dev, "cannot identify the touch screen\n");
goto err0;
}
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
dev_err(&client->dev, "failed to allocate driver data\n");
error = -ENOMEM;
goto err0;
}
/* 初始化mutex */
mutex_init(&priv->mutex);
dev_set_drvdata(&client->dev, priv);
/* 分配一个input设备 */
input = input_allocate_device();
if (!input) {
dev_err(&client->dev, "failed to allocate input device\n");
error = -ENOMEM;
goto err1;
}
/* 设置input设备所支持的事件类型 */
input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
input_set_capability(input, EV_KEY, KEY_MENU);
input_set_capability(input, EV_KEY, KEY_BACK);
input_set_capability(input, EV_KEY, KEY_HOME);
input_set_capability(input, EV_KEY, KEY_POWER);
input_set_abs_params(input, ABS_MT_POSITION_X, 0, 600, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_Y, 0, 1024, 0, 0);
input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 15, 0, 0);
input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 2, 0, 0);
input->name = "it7260 touch screen";
input->phys = "I2C";
input->id.bustype = BUS_I2C;
input_set_drvdata(input, priv);
priv->client = client;
priv->input = input;
/* 初始化延迟工作队列 */
INIT_DELAYED_WORK(&priv->work, it7260_ts_poscheck);
priv->irq = client->irq;
/* 向输入子系统注册此input设备 */
error = input_register_device(input);
if (error) {
dev_err(&client->dev, "failed to register input device\n");
goto err1;
}
/* 注册中断,低电平触发 */
error = request_irq(priv->irq, it7260_ts_isr, IRQF_TRIGGER_LOW,
client->name, priv);
if (error) {
dev_err(&client->dev, "unable to request touchscreen IRQ\n");
goto err2;
}
device_init_wakeup(&client->dev, 1);
return 0;
err2:
input_unregister_device(input);
input = NULL;
err1:
input_free_device(input);
kfree(priv);
err0:
dev_set_drvdata(&client->dev, NULL);
return error;
}
/* 当没有使用此设备时调用移除函数进行注销 */
static int __devexit it7260_ts_remove(struct i2c_client *client)
{
struct it7260_ts_priv *priv = dev_get_drvdata(&client->dev);
free_irq(priv->irq, priv);
input_unregister_device(priv->input);
kfree(priv);
dev_set_drvdata(&client->dev, NULL);
return 0;
}
/* 电源管理函数 */
static int it7260_ts_suspend(struct i2c_client *client, pm_message_t mesg)
{
int ret = -1;
u8 suspend_cmd[] = {0x04, 0x00, 0x02};
struct it7260_ts_priv *priv = i2c_get_clientdata(client);
if (device_may_wakeup(&client->dev)) {
enable_irq_wake(priv->irq);
if (sizeof(suspend_cmd) == i2c_master_write_it7260(client,
CMD_BUF, suspend_cmd, 3))
ret = 0;
}
return ret;
}
static int it7260_ts_resume(struct i2c_client *client)
{
int ret = -1;
unsigned char query;
struct it7260_ts_priv *priv = i2c_get_clientdata(client);
if (device_may_wakeup(&client->dev)) {
i2c_master_read_it7260(client, QUERY_BUF, &query, 1);
disable_irq_wake(priv->irq);
ret = 0;
}
return ret;
}
/* 驱动支持的设备列表,用来匹配 */
static const struct i2c_device_id it7260_ts_id[] = {
{"IT7260", 0},
{} /* should not omitted */
};
MODULE_DEVICE_TABLE(i2c, it7260_ts_id);
static struct i2c_driver it7260_ts_driver = {
.driver = {
.name = "IT7260-ts",
},
.probe = it7260_ts_probe,
.remove = __devexit_p(it7260_ts_remove),
.suspend = it7260_ts_suspend,
.resume = it7260_ts_resume,
.id_table = it7260_ts_id,
};
/* 模块加载函数 */
static int __init it7260_ts_init(void)
{
return i2c_add_driver(&it7260_ts_driver);
}
/* 模块卸载函数 */
static void __exit it7260_ts_exit(void)
{
i2c_del_driver(&it7260_ts_driver);
}
module_init(it7260_ts_init);
module_exit(it7260_ts_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("CJOK <cjok.liao@gmail.com>");
MODULE_DESCRIPTION("it7260 touchscreen driver");
完整的源码可以通过git来下载:git clone git://github.com/cjok/it7260.git