项目介绍 :多点电容式触摸屏控制芯片Guitar,采用投射式电容检测原理,由15个驱动通道与10个感应通道组成触摸检测网络,通过内置模拟放大电路、数字运算模块,及高性能MPU得到实时准确的触 摸信息,并通过I2C传输给主控芯片。实现“所点 即所得”的非凡用户体验。 Guitar可同时识别5个触摸点位的实时准确 位置,移动轨迹及触摸力度。并可根据主控需要,读取相应点数的触摸信息。本项目主要通过i2c子系统及input子系统完成驱 动与硬件的命令和信息交互,掌握常见触摸屏的工作原理 。
l三个部分对多点触控进行支持
1)在注册input子系统时需要添加对多点触控支持,代码如下(ps原有的设置不能少):
input_set_abs_params(ts->input_dev, ABS_MT_PRESSURE, 0, 255, 0, 0); //设置多点触控模式下的压力值的范围为0-255
input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); //
input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, SCREEN_MAX_HEIGHT, 0, 0);
input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, 0, SCREEN_MAX_WIDTH, 0, 0);
2)需要通过算法实现对多点信息的记录,这部分头文件已经支持
a,基本数据结构
struct point_node
{
unsigned int num; //触点编号
unsigned int state; //触点状态,state?down:up
};
struct point_queue //触点信息结构体
{
int length; //触点数目
struct point_node pointer[MAX_FINGER_NUM]; //触点数组
};
b,数据操作接口(实现过程请查阅goodix_touch_v2.2)
//删除触点信息
static void del_point(struct point_queue *point_list)
//添加触点信息
static int add_point(struct point_queue *point_list, int num)
//将某个触点的状态修改为抬起(up)
static int set_up_point(struct point_queue *point_list, int num)
c,处理思路(data[32]为从控制芯片获取到的数据)
1,定义一个全局的struct point_queue变量finger_list
定义了无符号8bit变量的静态局部变量 finger_bit 用于存储上一次的触点标志(触摸屏控制芯片中的0x00寄存器的值,用于判断触点数)
2,仅保留data[1]的低5位数据(因为芯片仅支持最多5点触控),然后将新的标志和旧的做比较,方法如下:
finger = finger_bit^data[1];//'^'是异或运算,如果标志在前后发生变化,那么finger上对应就为1
finger_bit=0 && data[1]=0 表示没有动作,如果仅finger_bit=0,那么当前标志没变,剩下一种情况就是标志发生变化,就需要去对finger_list进行处理(使用接口)
根据finger低5位的数值去处理数据,循环操作,当finger的某位为1时表示有变化,如果finger_bit上对应的位上为1那么表示抬起,调用set_up_point,反之为按下,调用add_point.
3需要注意,处理完后用finger_bit去保存data[1],然后在上报玩信息后清空finger_list。
3)多点信息的上报,在原来的上报过程中加入一段多点上报,在驱动中,输入子系统中用mt(multi-touch)协议来处理多点触控:
if(finger_list.length > 0 && finger_list.pointer[0].state == FLAG_DOWN)
{
input_report_abs(goodix->input_dev, ABS_X, (800-pos[0][0]));
input_report_abs(goodix->input_dev, ABS_Y, pos[0][1]);
}
if(finger_list.length > 0)
{
input_report_abs(goodix->input_dev, ABS_PRESSURE, pressure[0]);
input_report_key(goodix->input_dev, BTN_TOUCH, finger_list.pointer[0].state);
}
/* 加上下面的循环即可支持多点上报 */
for(count = 0; count < finger_list.length; count++)
{
input_report_abs(goodix->input_dev, ABS_MT_POSITION_X, (800 -pos[count][0]));
input_report_abs(goodix->input_dev, ABS_MT_POSITION_Y, pos[count][1]);
input_report_abs(goodix->input_dev, ABS_MT_PRESSURE, pressure[count]);
input_report_abs(goodix->input_dev, ABS_MT_TOUCH_MAJOR, pressure[count]);
input_mt_sync(goodix->input_dev);
}
input_sync(goodix->input_dev);
del_point(&finger_list);
4)、单点和多点上报互不影响,因为用户读取单点和多点是分开读取的,所以即使上报了单点坐标,再多点上报时重复上报第一个坐标不会重复读取。
5)、无论单点还是多点上报结束后都要input_sync()同步,而多点时input_mt_sync()是在每一个点完成后要同步一次。
code:
ABS_X 0x00 0
ABS_Y 0x01 1
ABS_PRESSURE 0x18 24
ABS_MT_TOUCH_MAJOR 0x30 48
ABS_MT_POSITION_X 0x35 53
ABS_MT_POSITION_Y 0x36 54
ABS_MT_PRESSURE 0x3a 58
BTN_TOUCH 0x14a 330
goodix_touch_v3.0.h
#ifndef __GOODIX_TOUCH_V3_H__
#define __GOODIX_TOUCH_V3_H__
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/earlysuspend.h>
#include <linux/hrtimer.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/irq.h>
#define GOODIX_I2C_NAME "Goodix-TS"
#define TOUCH_MAX_HEIGHT 7680
#define TOUCH_MAX_WIDTH 5120
#if 0
#define SCREEN_MAX_HEIGHT 480
#define SCREEN_MAX_WIDTH 272
#else
#define SCREEN_MAX_HEIGHT 800
#define SCREEN_MAX_WIDTH 480
#endif
#define MAX_FINGER_NUM 5 //最大支持手指数(<=5)
#define FLAG_UP 0
#define FLAG_DOWN 1
/* need modify if platform changed */
#define INT_PORT S5P_EXT_INT0(4) //Int IO port;GPH0CON[4]/EXT_INT[4]
#define INT_FUNC gpio_to_irq(INT_PORT)
/* need modify if platform changed */
#define SHUTDOWN_PORT S5P_EXT_INT1(3) //SHUTDOWN管脚号 GPH1CON[3]/EXT_INT[11]
typedef int (*pos_t)[2];
struct goodix_priv {
struct i2c_client *cli;
struct input_dev *input_dev;
struct work_struct work;
void *privata_data;
};
struct point_node
{
unsigned int num;
unsigned int state;
};
struct point_queue
{
int length;
struct point_node pointer[MAX_FINGER_NUM];
};
struct point_queue finger_list; //record the fingers list
static void del_point(struct point_queue *point_list)
{
int count = point_list->length-1;
int position;
for(; count >= 0; count--) //note: must search from tail to head
if(point_list->pointer[count].state == FLAG_UP)
{
if(point_list->length == 0 )
return ;
position = count;
for(; position < MAX_FINGER_NUM -1; position++)
point_list->pointer[position] = point_list->pointer[position+1];
point_list->length--;
}
}
static int add_point(struct point_queue *point_list, int num)
{
if(point_list->length >= MAX_FINGER_NUM || num < 0 )
return -1;
point_list->pointer[point_list->length].num = num;
point_list->pointer[point_list->length].state = FLAG_DOWN;
point_list->length++;
return 0;
}
static int set_up_point(struct point_queue *point_list, int num)
{
int count = 0;
if(point_list->length <= 0 || num < 0 || num > MAX_FINGER_NUM)
return -1; //no data
for(; count < point_list->length; count++)
if(point_list->pointer[count].num == num)
{
point_list->pointer[count].state = FLAG_UP;
return 0;
}
else continue;
return -1;
}
static int goodix_analysis_data(uint8_t *point_data, struct goodix_priv *goodix, pos_t pos, unsigned int *pressure)
{
static uint8_t finger_bit=0;
uint8_t finger=0;
int position =0;
int count = 0, read_position = 0;
int check_sum = 0;
//The bit indicate which fingers pressed down
switch(point_data[1]& 0x1f)
{
case 0:
case 1:
for(count=1; count<8; count++)
check_sum += (int)point_data[count];
if((check_sum%256) != point_data[8])
goto XFER_ERROR;
break;
case 2:
case 3:
for(count=1; count<13;count++)
check_sum += (int)point_data[count];
if((check_sum%256) != point_data[13])
goto XFER_ERROR;
break;
default: //(point_data[1]& 0x1f) > 3
for(count=1; count<34;count++)
check_sum += (int)point_data[count];
if((check_sum%256) != point_data[34])
goto XFER_ERROR;
}
point_data[1]&=0x1f;
finger = finger_bit^point_data[1];
if(finger == 0 && point_data[1] == 0)
goto NO_ACTION; //no fingers and no action
else if(finger == 0) //the same as last time
goto BIT_NO_CHANGE;
//check which point(s) DOWN or UP
for(position = 0; (finger !=0)&& (position < MAX_FINGER_NUM); position++)
{
if((finger&0x01) == 1) //current bit is 1?
{ //NO.postion finger is change
if(((finger_bit>>position)&0x01)==1) //NO.postion finger is UP
set_up_point(&finger_list, position);
else
add_point(&finger_list, position);
}
finger>>=1;
}
BIT_NO_CHANGE:
for(count = 0; count < finger_list.length; count++)
{
if(finger_list.pointer[count].state == FLAG_UP)
{
pos[count][0] = pos[count][1] = 0;
pressure[count] = 0;
continue;
}
if(finger_list.pointer[count].num < 3)
read_position = finger_list.pointer[count].num*5 + 3;
else if (finger_list.pointer[count].num == 4)
read_position = 29;
if(finger_list.pointer[count].num != 3)
{
pos[count][0] = (unsigned int) (point_data[read_position]<<8) + (unsigned int)( point_data[read_position+1]);
pos[count][1] = (unsigned int)(point_data[read_position+2]<<8) + (unsigned int) (point_data[read_position+3]);
pressure[count] = (unsigned int) (point_data[read_position+4]);
}
else
{
pos[count][0] = (unsigned int) (point_data[18]<<8) + (unsigned int)( point_data[25]);
pos[count][1] = (unsigned int)(point_data[26]<<8) + (unsigned int) (point_data[27]);
pressure[count] = (unsigned int) (point_data[28]);
}
#ifdef GOODIX_TS_DEBUG
if(count == 3)
dev_info(&(goodix->cli->dev), "Original data: Count:%d Index:%d X:%d Y:%d\n",count, finger_list.pointer[count].num, pos[count][0], pos[count][1]);
#endif
pos[count][0] = (TOUCH_MAX_WIDTH - pos[count][0])*SCREEN_MAX_WIDTH/TOUCH_MAX_WIDTH;//y,
pos[count][1]= pos[count][1]*SCREEN_MAX_HEIGHT/TOUCH_MAX_HEIGHT ; //x,
swap(pos[count][0] ,pos[count][1]);
#ifdef GOODIX_TS_DEBUG
if(count == 3)
dev_info(&(goodix->cli->dev), " Coordinate: Count:%d Index:%d X:%d Y:%d\n",count, finger_list.pointer[count].num, pos[count][0], pos[count][1]);
#endif
}
finger_bit=point_data[1];
return 0;
XFER_ERROR:
NO_ACTION:
return -1;
}
#endif
goodix_touch_v3.0.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/earlysuspend.h>
#include <linux/hrtimer.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/irq.h>
#include "driverapp.h"
#define debug_goodix(fmt, arg...) printk(KERN_INFO fmt, ##arg);
/* other cpt touch can use this workqueue */
static struct workqueue_struct *goodix_wq;
static const struct i2c_device_id goodix_ts_id[] = {
{ GOODIX_I2C_NAME, 0 },
{ }
};
static int i2c_write_bytes(struct i2c_client *client,uint8_t *data,int len)
{
/* just one i2c_msg OK. */
struct i2c_msg msg;
msg.addr = client->addr;
msg.flags = !I2C_M_RD;
msg.len = len;
msg.buf = data;
return 1-i2c_transfer(client->adapter,&msg,1);
}
static int i2c_read_bytes(struct i2c_client *client, uint8_t *data, int len)
{
/* u need two i2c_msg */
struct i2c_msg msg[2];
msg[0].addr = client->addr;
msg[0].flags = !I2C_M_RD;
msg[0].len = 1;
msg[0].buf = data;
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = len-1;
msg[1].buf = data+1;
return 2-i2c_transfer(client->adapter,msg,2);
}
static int goodix_read_panel(struct goodix_priv *goodix, uint8_t *data, int size)
{
return i2c_read_bytes(goodix->cli, data, size);
}
static int goodix_init_panel(struct goodix_priv *goodix)
{
uint8_t config_info[54]={0x30, 0x19,0x05,0x05,0x28,0x02,0x14,0x14,0x10,0x32,0xF8,0x14,0x00,0x1E,0x00,0xED,0xCB,
0xA9,0x87,0x65,0x43,0x21,0x01,0x00,0x00,0x35,0x2E,0x4D,0xC0,0x20,0x01,0x01,0x83,
0x50,0x3C,0x1E,0x28,0x00,0x33,0x2C,0x01,0xEC,0x00,0x3C,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x01};
return i2c_write_bytes(goodix->cli, config_info, sizeof(config_info));
}
static void goodix_ts_work_func(struct work_struct *work)
{
uint8_t point_data[35]={ 0 };
int ret = 0;
int count;
int pos[MAX_FINGER_NUM][2];
unsigned int pressure[MAX_FINGER_NUM];
disable_irq(INT_FUNC);
struct goodix_priv *goodix = container_of(work, struct goodix_priv, work);
ret = goodix_read_panel(goodix, point_data, sizeof(point_data));
ret = goodix_analysis_data(point_data, goodix, pos, pressure);
if(finger_list.length > 0 && finger_list.pointer[0].state == FLAG_DOWN)
{
input_report_abs(goodix->input_dev, ABS_X, (800-pos[0][0]));
input_report_abs(goodix->input_dev, ABS_Y, pos[0][1]);
}
if(finger_list.length > 0)
{
input_report_abs(goodix->input_dev, ABS_PRESSURE, pressure[0]);
input_report_key(goodix->input_dev, BTN_TOUCH, finger_list.pointer[0].state);
}
printk("%d: len = %d,up_down = %d \n",__LINE__,finger_list.length,finger_list.pointer[0].state);
for(count = 0; count < finger_list.length; count++)
{/* 多点 */
input_report_abs(goodix->input_dev, ABS_MT_POSITION_X, (800 -pos[count][0]));
input_report_abs(goodix->input_dev, ABS_MT_POSITION_Y, pos[count][1]);
input_report_abs(goodix->input_dev, ABS_MT_PRESSURE, pressure[count]);
input_report_abs(goodix->input_dev, ABS_MT_TOUCH_MAJOR, pressure[count]);
input_mt_sync(goodix->input_dev);
}
input_sync(goodix->input_dev);
del_point(&finger_list);
enable_irq(INT_FUNC);
return ;
}
static irqreturn_t goodix_ts_irq_handler(int irq, void *dev_id)
{
struct goodix_priv *ts = dev_id;
queue_work(goodix_wq, &ts->work);
return IRQ_HANDLED;
}
static int goodix_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct goodix_priv *goodix;
int ret;
int retry = 5;
/*allocate memory for goodix — kmalloc*/
goodix = (struct goodix_priv *)kzalloc(sizeof(*goodix),GFP_KERNEL);
ret = gpio_request(SHUTDOWN_PORT, "GOODIX_TS SHUTDOWN PORT");
if (ret < 0) {
dev_err(&client->dev, "Failed to request GPIO:%d, ERRNO:%d\n",(int)SHUTDOWN_PORT,ret);
goto err_check_functionality_failed;
}
/* set gpio output & 0; goodix step into normal mode */
gpio_direction_output(SHUTDOWN_PORT, 0);
ret = i2c_check_functionality(client->adapter, I2C_FUNC_I2C);
if (ret == 0) {
dev_err(&client->dev, "Soc no I2C function, ERRNO:%d\n", ret);
goto err_check_functionality_failed;
}
msleep(16);//step into normal mode, need 16ms
dev_info(&client->dev, "chip goodix work...\t i2c connecttion work...\t now we request irq and input_dev\n");
/*init goodix variable*/
i2c_set_clientdata(client,goodix);
goodix->cli = client;
/* init work of workqueue*/
INIT_WORK(&(goodix->work),goodix_ts_work_func);
/* input dev init and register */
goodix->input_dev = input_allocate_device();
if(IS_ERR(goodix->input_dev)){
debug_goodix("allocate input device fail!\n");
ret = PTR_ERR(goodix->input_dev);
goto err_check_functionality_failed;
}
goodix->input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) ;
set_bit(BTN_TOUCH, goodix->input_dev->keybit);
set_bit(ABS_X, goodix->input_dev->absbit);
set_bit(ABS_Y, goodix->input_dev->absbit);
set_bit(ABS_PRESSURE, goodix->input_dev->absbit);
input_set_abs_params(goodix->input_dev, ABS_X, 0, SCREEN_MAX_HEIGHT, 0, 0);
input_set_abs_params(goodix->input_dev, ABS_Y, 0, SCREEN_MAX_WIDTH, 0, 0);
input_set_abs_params(goodix->input_dev, ABS_PRESSURE, 0 , 255, 0, 0);
/* 多点 */
input_set_abs_params(goodix->input_dev, ABS_MT_PRESSURE, 0, 255, 0, 0);
input_set_abs_params(goodix->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
input_set_abs_params(goodix->input_dev, ABS_MT_POSITION_X, 0, SCREEN_MAX_HEIGHT, 0, 0);
input_set_abs_params(goodix->input_dev, ABS_MT_POSITION_Y, 0, SCREEN_MAX_WIDTH, 0, 0);
finger_list.length = 0;
ret = input_register_device(goodix->input_dev);
if(ret){
debug_goodix("input device register fail\n");
ret = -EFAULT;
goto input_err;
}
/* request_irq */
ret = request_irq(INT_FUNC,goodix_ts_irq_handler,IRQ_TYPE_EDGE_FALLING,"touchscreen",goodix);
if(ret){
debug_goodix("request irq fail!\n");
goto irq_err;
}
/* init hardware*/
while(retry--)
ret = goodix_init_panel(goodix);
if(ret){
debug_goodix("init hardware fail\n");
goto hardware_err;
}
return 0;
hardware_err:
free_irq(INT_FUNC,goodix);
irq_err:
input_unregister_device(goodix->input_dev);
input_err:
input_free_device(goodix->input_dev);
err_check_functionality_failed:
kfree(goodix);
return ret;
}
static int goodix_ts_remove(struct i2c_client *client)
{
struct goodix_priv *goodix;
goodix = (struct goodix_priv *)i2c_get_clientdata(client);
free_irq(INT_FUNC,goodix);
input_unregister_device(goodix->input_dev);
input_free_device(goodix->input_dev);
kfree(goodix);
gpio_free(SHUTDOWN_PORT);
return 0;
}
static struct i2c_driver goodix_ts_driver = {
.probe = goodix_ts_probe,
.remove = goodix_ts_remove,
.id_table = goodix_ts_id,
.class = I2C_CLASS_HWMON,
.driver = {
.name = GOODIX_I2C_NAME,
.owner = THIS_MODULE,
},
};
static int __devinit cpt_ts_init(void)
{
int ret;
printk(KERN_INFO "Touchscreen driver of guitar is installing...\n");
goodix_wq = create_workqueue("goodix_wq");//success means not null
if (!goodix_wq) {
printk(KERN_ALERT "creat workqueue faiked\n");
return -ENOMEM;
}
ret=i2c_add_driver(&goodix_ts_driver);
return ret;
}
static void __devexit cpt_ts_exit(void)
{
printk(KERN_INFO "Touchscreen driver of guitar is exiting...\n");
i2c_del_driver(&goodix_ts_driver);
if (goodix_wq)
destroy_workqueue(goodix_wq);
return ;
}
module_init(cpt_ts_init);
module_exit(cpt_ts_exit);
MODULE_DESCRIPTION("Goodix Touchscreen Driver");
MODULE_LICENSE("GPL");
goodix_dev.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/irq.h>
static struct i2c_board_info dev_test_info = {
I2C_BOARD_INFO("Goodix-TS",0x55),
};
static struct i2c_client *dev_test_client;
static int dev_test_dev_init(void)
{
struct i2c_adapter *i2c_adap = NULL;
i2c_adap = i2c_get_adapter(0);
dev_test_client = i2c_new_device(i2c_adap,&dev_test_info);
return 0;
}
static void dev_test_dev_exit(void)
{
i2c_unregister_device(dev_test_client);
}
module_init(dev_test_dev_init);
module_exit(dev_test_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("sunplusedu");