多点触摸屏驱动
=========================

多点触摸屏我们并不陌生，也就是电容屏，现在的手机、pad都是多点触摸屏。很多应用场景单点触摸屏已经不能适应了，比如手机游戏就需要同时触摸多个点。当然这些都不是我们要关心的，我们只需要弄清楚他在linux中驱动的实现方法。

input子系统中的多点触摸屏
------------------------------

多点触摸屏也是输入设备，前面我们讲的linux中input子系统中，也有队多点触摸屏的支持。回想一下input子系统，input子系统中，通过input_handler帮我们实现了fops中的方法，我们需要做的就是结合具体硬件，有数据时通过接口发送数据。实现一个输入设备驱动，步骤大致如下：

1.构造input_device

2.设置input_device参数

3.向内核注册input_device

4.当硬件接收到数据时通过input_event上报数据

结合上我们这里的硬件设备多点触摸屏，大致步骤如下：

1) 分配input_dev结构体

+-----------------------------------------------------------------------+
| struct input_dev \*ts_dev;                                            |
|                                                                       |
| ts_dev = input_allocate_device();                                     |
+-----------------------------------------------------------------------+

2) 设置input_dev结构体

设置能够产生的事件类型：

+-----------------------------------------------------------------------+
| set_bit(EV_SYN, ts_dev->evbit);//设置按键事件                         |
|                                                                       |
| set_bit(EV_ABS, ts_dev->evbit);//设置滑动事件                         |
+-----------------------------------------------------------------------+

设置这类事件类型中能产生的具体事件：

+-----------------------------------------------------------------------+
| set_bit(ABS_MT_TRACKING_ID, ts_dev->absbit);                          |
|                                                                       |
| 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, MTP_MAX_ID, 0,    |
| 0);                                                                   |
|                                                                       |
| input_set_abs_params(ts_dev, ABS_MT_POSITION_X, 0, MTP_MAX_X, 0, 0);  |
|                                                                       |
| input_set_abs_params(ts_dev, ABS_MT_POSITION_Y, 0, MTP_MAX_Y, 0, 0);  |
+-----------------------------------------------------------------------+

3) 注册input_dev结构体

+-----------------------------------------------------------------------+
| input_register_device(ts_dev);                                        |
+-----------------------------------------------------------------------+

4) 硬件操作

注册中断：

+-----------------------------------------------------------------------+
| request_irq();                                                        |
+-----------------------------------------------------------------------+

上报事件：

+-----------------------------------------------------------------------+
| input_report_abs(ts_dev, ABS_MT_POSITION_X, mtp_events[i].x);         |
|                                                                       |
| input_report_abs(ts_dev, ABS_MT_POSITION_Y, mtp_events[i].y);         |
|                                                                       |
| input_report_abs(ts_dev, ABS_MT_TRACKING_ID, mtp_events[i].id);       |
|                                                                       |
| input_mt_sync(ts_dev);                                                |
+-----------------------------------------------------------------------+

多点触摸屏与SoC的接口
--------------------------

触摸屏一般会通过IIC或者SPI连接到SoC，软件上把屏幕看成是一个平面坐标系，当我们触摸到屏幕上某个位置，触摸屏就会通过IIC或SPI发送这个点的坐标到SoC，SoC如何处理这个坐标这就是后话了。对于多点触摸屏来说也只是一次发送多个点而已。

当存在”多个”的概念时，就必然多了一个需要关心的属性，那就是多个之间的关系。这里我们上报也就有了两种模式，一是不关心触点间的关系直接上报位置，二是上位置的同时加上触电关系先后顺序等。

只上报触点位置：

+-----------------------------------------------------------------------+
| ABS_MT_POSITION_X x0                                                  |
|                                                                       |
| ABS_MT_POSITION_Y y0                                                  |
|                                                                       |
| SYN_MT_REPORT                                                         |
|                                                                       |
| ABS_MT_POSITION_X x1                                                  |
|                                                                       |
| ABS_MT_POSITION_Y y1                                                  |
|                                                                       |
| SYN_MT_REPORT                                                         |
+-----------------------------------------------------------------------+

上报触点位置和关系：

+-----------------------------------------------------------------------+
| ABS_MT_TRACKING_ID 0                                                  |
|                                                                       |
| ABS_MT_POSITION_X x0                                                  |
|                                                                       |
| ABS_MT_POSITION_Y y0                                                  |
|                                                                       |
| ABS_MT_TRACKING_ID 1                                                  |
|                                                                       |
| ABS_MT_POSITION_X x1                                                  |
|                                                                       |
| ABS_MT_POSITION_Y y1                                                  |
|                                                                       |
| SYN_MT_REPORT                                                         |
+-----------------------------------------------------------------------+

我们的开发板平台是通过IIC和多点触摸屏互联的，也就是说我们的多点触摸屏驱动会是IIC和input子系统的混合体。

IIC框架比较简单，我们就直接通过代码去看。

vivado工程与petalinux
--------------------------

ALINX的平台上是通过PL端的扩展口和触摸屏相连的，因此需要使用vivado软件配置工程，然后重新制作petalinux工程。重建vivado工程和petalinux工程我们在pwm章节中已经经历过一次了。

vivado工程配置方法步骤以及petalinux的设置可参考s4的教程，这里就不再重复说明了。有一点注意，我们这里的驱动都是当成模块来加载的，所以重新制作petalinux工程，我们只需要做其中的一步，选择Digilent
axi_dynclk Driver即可。

.. image:: images/24_media/image1.png
   
实验
---------

原理图
~~~~~~~~~~~~~

.. image:: images/24_media/image2.png

触摸屏幕的电路很简单，一是传递数据的I2C(SCL、SDA)，二是提醒数据的中断INT，三是复位信号RST。

设备树
~~~~~~~~~~~~~

把触摸屏看作是一个IIC的client，把设备树节点放在&i2c0下。设备树修改方法可参考前面的章节，别忘了要在新得petalinux工程中操作哦。

.. image:: images/24_media/image3.png
   
多点触摸屏驱动程序
~~~~~~~~~~~~~~~~~~~~~~~~~

在新的petalinux工程中新建驱动文件ax-touchscreen-drv：

.. image:: images/24_media/image4.png
   
在文件ax-touchscreen-drv.c中输入下面的代码： 

.. code:: c
   
 #include <linux/kernel.h>
 #include <linux/hrtimer.h>
 #include <linux/i2c.h>
 #include <linux/input.h>
 #include <linux/module.h>
 #include <linux/delay.h>
 #include <linux/i2c.h>
 #include <linux/proc_fs.h>
 #include <linux/string.h>
 #include <asm/uaccess.h>
 #include <linux/vmalloc.h>
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/gpio.h>
 #include <linux/irq.h>
 #include <linux/uaccess.h>
 
 #define SCREEN_MAX_X                800
 #define SCREEN_MAX_Y                480
 //***************************PART1:ON/OFF define*******************************
 #define GTP_DEBUG_ON          1
 #define GTP_DEBUG_ARRAY_ON    0
 #define GTP_DEBUG_FUNC_ON     0
 #define IIC_ADDRESS	      0x14
 struct goodix_ts_data {
     spinlock_t irq_lock;
     struct i2c_client *client;
     struct input_dev  *input_dev;
     struct hrtimer timer;
     struct work_struct  work;
     s32 irq_is_disable;
     s32 use_irq;
     u16 abs_x_max;
     u16 abs_y_max;
     u8  int_trigger_type;
 
     u8  gtp_rawdiff_mode;
 };
 
 //*************************** PART2:TODO define **********************************
 #define GTP_MAX_HEIGHT   800
 #define GTP_MAX_WIDTH    480
 #define GTP_INT_TRIGGER  0            // 0: Rising 1: Falling
 #define GTP_MAX_TOUCH         5
 //***************************PART3:OTHER define*********************************
 #define GTP_DRIVER_VERSION          "V2.4<2014/11/28>"
 #define GTP_I2C_NAME                "alinx,an071"
 #define GTP_POLL_TIME         10    
 #define GTP_ADDR_LENGTH       2
 #define GTP_CONFIG_MIN_LENGTH 186
 #define GTP_CONFIG_MAX_LENGTH 240
 #define FAIL                  0
 #define SUCCESS               1
 #define SWITCH_OFF            0
 #define SWITCH_ON             1
 
 // Registers define
 #define GTP_READ_COOR_ADDR    0x814E
 #define GTP_REG_SLEEP         0x8040
 #define GTP_REG_SENSOR_ID     0x814A
 #define GTP_REG_CONFIG_DATA   0x8047
 #define GTP_REG_VERSION       0x8140
 
 #define RESOLUTION_LOC        3
 #define TRIGGER_LOC           8
 
 // Log define
 #define GTP_INFO(fmt,arg...)           printk("<<-GTP-INFO->> "fmt"\n",##arg)
 #define GTP_ERROR(fmt,arg...)          printk("<<-GTP-ERROR->> "fmt"\n",##arg)
 #define GTP_DEBUG(fmt,arg...)          do{\
                                          if(GTP_DEBUG_ON)\
                                          printk("<<-GTP-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
                                        }while(0)
 
 #define GTP_DEBUG_FUNC()               do{\
                                          if(GTP_DEBUG_FUNC_ON)\
                                          printk("<<-GTP-FUNC->> Func:%s@Line:%d\n",__func__,__LINE__);\
                                        }while(0)
 
 static const char *goodix_ts_name = "goodix-ts";
 static const char *goodix_input_phys = "input/ts";
 static struct workqueue_struct *goodix_wq;
 struct i2c_client * i2c_connect_client = NULL; 
 
 u8 config[GTP_CONFIG_MAX_LENGTH + GTP_ADDR_LENGTH]
                 = {GTP_REG_CONFIG_DATA >> 8, GTP_REG_CONFIG_DATA & 0xff};
 
 static s8 gtp_i2c_test(struct i2c_client *client);
 
 struct edt_i2c_chip_data 
 {
     int  max_support_points;
 };
 
 s32 gtp_i2c_read(struct i2c_client *client, u8 *buf, s32 len)
 {
     struct i2c_msg msgs[2];
     s32 ret=-1;
     s32 retries = 0;
 
     GTP_DEBUG_FUNC();
 
     msgs[0].flags = !I2C_M_RD;
     msgs[0].addr  = client->addr;//client->addr;
     msgs[0].len   = GTP_ADDR_LENGTH;
     msgs[0].buf   = &buf[0];
     
     msgs[1].flags = I2C_M_RD;
     msgs[1].addr  = client->addr;//client->addr;
     msgs[1].len   = len - GTP_ADDR_LENGTH;
     msgs[1].buf   = &buf[GTP_ADDR_LENGTH];
 
     while(retries < 5)
     {
         ret = i2c_transfer(client->adapter, msgs, 2);
         if(ret == 2)break;
         retries++;
     }
     if((retries >= 5))
     {
         GTP_ERROR("I2C Read: 0x%04X, %d bytes failed, errcode: %d! Process reset.", (((u16)(buf[0] << 8)) | buf[1]), len-2, ret);
     }
     return ret;
 }
 
 s32 gtp_i2c_write(struct i2c_client *client,u8 *buf,s32 len)
 {
     struct i2c_msg msg;
     s32 ret = -1;
     s32 retries = 0;
 
     GTP_DEBUG_FUNC();
 
     msg.flags = !I2C_M_RD;
     msg.addr  = client->addr;//client->addr;
     msg.len   = len;
     msg.buf   = buf;
     //msg.scl_rate = 300 * 1000;    // for Rockchip, etc
 
     while(retries < 5)
     {
         ret = i2c_transfer(client->adapter, &msg, 1);
         if (ret == 1)break;
         retries++;
     }
     if((retries >= 5))
     {
         GTP_ERROR("I2C Write: 0x%04X, %d bytes failed, errcode: %d! Process reset.", (((u16)(buf[0] << 8)) | buf[1]), len-2, ret);
     }
     return ret;
 }
 
 void gtp_irq_disable(struct goodix_ts_data *ts)
 {
     unsigned long irqflags;
 
     GTP_DEBUG_FUNC();
 
     spin_lock_irqsave(&ts->irq_lock, irqflags);
     if (!ts->irq_is_disable)
     {
         ts->irq_is_disable = 1; 
         disable_irq_nosync(ts->client->irq);
     }
     spin_unlock_irqrestore(&ts->irq_lock, irqflags);
 }
 
 void gtp_irq_enable(struct goodix_ts_data *ts)
 {
     unsigned long irqflags = 0;
 
     GTP_DEBUG_FUNC();
     
     spin_lock_irqsave(&ts->irq_lock, irqflags);
     if (ts->irq_is_disable) 
     {
         enable_irq(ts->client->irq);
         ts->irq_is_disable = 0; 
     }
     spin_unlock_irqrestore(&ts->irq_lock, irqflags);
 }
 
 static void gtp_touch_down(struct goodix_ts_data* ts,s32 id,s32 x,s32 y,s32 w)
 {
     input_report_abs(ts->input_dev, ABS_X, x);
     input_report_abs(ts->input_dev, ABS_Y, y);
     input_event(ts->input_dev, EV_KEY, BTN_TOUCH, 1);
     input_report_abs(ts->input_dev, ABS_PRESSURE, 1);
 }
 
 static void gtp_touch_up(struct goodix_ts_data* ts, s32 id,s32 x,s32 y,s32 w)
 {
     input_report_key(ts->input_dev, BTN_TOUCH, 0);
     input_report_abs(ts->input_dev, ABS_PRESSURE, 0);
     GTP_DEBUG("ID:%d, X:%d, Y:%d, W:%d", id, x, y, w);
 }
 
 static void goodix_ts_work_func(struct work_struct *work)
 {
     u8  end_cmd[3] = {GTP_READ_COOR_ADDR >> 8, GTP_READ_COOR_ADDR & 0xFF, 0};
     u8  point_data[2 + 1 + 8 * GTP_MAX_TOUCH + 1]={GTP_READ_COOR_ADDR >> 8, GTP_READ_COOR_ADDR & 0xFF};
     u8  touch_num = 0;
     u8  finger = 0;
     static u16 pre_touch = 0;
     static u8 pre_key = 0;
 
     u8  key_value = 0;
     u8* coor_data = NULL;
     s32 input_x = 0;
     s32 input_y = 0;
     s32 input_w = 0;
     s32 id = 0;
     s32 i  = 0;
     s32 ret = -1;
     struct goodix_ts_data *ts = NULL;
 
     GTP_DEBUG_FUNC();
     ts = container_of(work, struct goodix_ts_data, work);
 
     ret = gtp_i2c_read(ts->client, point_data, 12);
     if (ret < 0)
     {
         GTP_ERROR("I2C transfer error. errno:%d\n ", ret);
         if (ts->use_irq)
         {
             gtp_irq_enable(ts);
         }
         return;
     }
     
     finger = point_data[GTP_ADDR_LENGTH];
 
     if (finger == 0x00)
     {
         if (ts->use_irq)
         {
             gtp_irq_enable(ts);
         }
         return;
     }
 
     if((finger & 0x80) == 0)
     {
         goto exit_work_func;
     }
 
     touch_num = finger & 0x0f;
     if (touch_num > GTP_MAX_TOUCH)
     {
         goto exit_work_func;
     }
 
     if (touch_num > 1)
     {
         u8 buf[8 * GTP_MAX_TOUCH] = {(GTP_READ_COOR_ADDR + 10) >> 8, (GTP_READ_COOR_ADDR + 10) & 0xff};
 
         ret = gtp_i2c_read(ts->client, buf, 2 + 8 * (touch_num - 1)); 
         memcpy(&point_data[12], &buf[2], 8 * (touch_num - 1));
     }
 
     pre_key = key_value;
 
     if (touch_num)
     {
         for (i = 0; i < touch_num; i++)
         {
             coor_data = &point_data[i * 8 + 3];
 
             id = coor_data[0] & 0x0F;
             input_x  = coor_data[1] | (coor_data[2] << 8);
             input_y  = coor_data[3] | (coor_data[4] << 8);
             input_w  = coor_data[5] | (coor_data[6] << 8);
         
             {
                 gtp_touch_down(ts, id, input_x, input_y, input_w);
             }
         }
     }
     else if (pre_touch)
     {
         {
             for (i = 0; i < pre_touch; i++)
             {
                 coor_data = &point_data[i * 8 + 3];
 
                 id = coor_data[0] & 0x0F;
                 input_x  = coor_data[1] | (coor_data[2] << 8);
                 input_y  = coor_data[3] | (coor_data[4] << 8);
                 input_w  = coor_data[5] | (coor_data[6] << 8);
                 gtp_touch_up(ts, id, input_x, input_y, input_w);
             }
         }
     }
 
     pre_touch = touch_num;
 
     input_sync(ts->input_dev);
 
 exit_work_func:
     if(!ts->gtp_rawdiff_mode)
     {
         ret = gtp_i2c_write(ts->client, end_cmd, 3);
         if (ret < 0)
         {
             GTP_INFO("I2C write end_cmd error!");
         }
     }
     if (ts->use_irq)
     {
         gtp_irq_enable(ts);
     }
 }
 
 static enum hrtimer_restart goodix_ts_timer_handler(struct hrtimer *timer)
 {
     struct goodix_ts_data *ts = container_of(timer, struct goodix_ts_data, timer);
 
     GTP_DEBUG_FUNC();
 
     queue_work(goodix_wq, &ts->work);
     hrtimer_start(&ts->timer, ktime_set(0, (GTP_POLL_TIME+6)*1000000), HRTIMER_MODE_REL);
     return HRTIMER_NORESTART;
 }
 
 static irqreturn_t goodix_ts_irq_handler(int irq, void *dev_id)
 {
     struct goodix_ts_data *ts = dev_id;
 
     GTP_DEBUG_FUNC();
  
     gtp_irq_disable(ts);
 
     queue_work(goodix_wq, &ts->work);
     
     return IRQ_HANDLED;
 }
 
 static s32 gtp_init_panel(struct goodix_ts_data *ts)
 { 
     if ((ts->abs_x_max == 0) && (ts->abs_y_max == 0))
     {
         ts->abs_x_max = (config[RESOLUTION_LOC + 1] << 8) + config[RESOLUTION_LOC];
         ts->abs_y_max = (config[RESOLUTION_LOC + 3] << 8) + config[RESOLUTION_LOC + 2];
         ts->int_trigger_type = (config[TRIGGER_LOC]) & 0x03; 
     }
 
     GTP_INFO("X_MAX: %d, Y_MAX: %d, TRIGGER: 0x%02x", ts->abs_x_max,ts->abs_y_max,ts->int_trigger_type);
 
     msleep(10);
     return 0;
 }
 
 s32 gtp_read_version(struct i2c_client *client, u16* version)
 {
     s32 ret = -1;
     u8 buf[8] = {GTP_REG_VERSION >> 8, GTP_REG_VERSION & 0xff};
 
     GTP_DEBUG_FUNC();
 
     ret = gtp_i2c_read(client, buf, sizeof(buf));
     if (ret < 0)
     {
         GTP_ERROR("GTP read version failed");
         return ret;
     }
 
     if (version)
     {
         *version = (buf[7] << 8) | buf[6];
     }
     if (buf[5] == 0x00)
     {
         GTP_INFO("IC Version: %c%c%c_%02x%02x", buf[2], buf[3], buf[4], buf[7], buf[6]);
     }
     else
     {
         GTP_INFO("IC Version: %c%c%c%c_%02x%02x", buf[2], buf[3], buf[4], buf[5], buf[7], buf[6]);
     }
     return ret;
 }
 
 static s8 gtp_i2c_test(struct i2c_client *client)
 {
     u8 test[3] = {GTP_REG_CONFIG_DATA >> 8, GTP_REG_CONFIG_DATA & 0xff};
     u8 retry = 0;
     s8 ret = -1;
   
     GTP_DEBUG_FUNC();
   
     while(retry++ < 5)
     {
         ret = gtp_i2c_read(client, test, 3);
         if (ret > 0)
         {
             return ret;
         }
         GTP_ERROR("GTP i2c test failed time %d.",retry);
         msleep(10);
     }
     return ret;
 }
 
 static s8 gtp_request_irq(struct goodix_ts_data *ts)
 {
     s32 ret = -1;
     unsigned long irq_flags;
 
     GTP_DEBUG_FUNC();
     GTP_DEBUG("INT trigger type:%x", ts->int_trigger_type);
     
     irq_flags = irq_get_trigger_type(ts->client->irq);
     if (irq_flags == IRQF_TRIGGER_NONE) irq_flags = IRQF_TRIGGER_HIGH;
     irq_flags |= IRQF_ONESHOT;
     
     ret = devm_request_threaded_irq(&ts->client->dev, ts->client->irq, NULL, goodix_ts_irq_handler, irq_flags, ts->client->name, ts);
     if (ret) 
     {
         GTP_ERROR("Request IRQ failed!ERRNO:%d.", ret);
         
         hrtimer_init(&ts->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
         ts->timer.function = goodix_ts_timer_handler;
         hrtimer_start(&ts->timer, ktime_set(1, 0), HRTIMER_MODE_REL);
         return -1;
     }
     else 
     {
         gtp_irq_disable(ts);
         ts->use_irq = 1;
         return 0;
     }
 }
 
 static s8 gtp_request_input_dev(struct goodix_ts_data *ts)
 {
     s8 ret = -1;
     
     GTP_DEBUG_FUNC();
   
     ts->input_dev = input_allocate_device();
     if (ts->input_dev == NULL)
     {
         GTP_ERROR("Failed to allocate input device.");
         return -ENOMEM;
     }
 
     __set_bit(EV_ABS, ts->input_dev->evbit);
     __set_bit(EV_KEY, ts->input_dev->evbit);
     __set_bit(EV_SYN, ts->input_dev->evbit);
     
     __set_bit(BTN_TOUCH, ts->input_dev->keybit);
     __set_bit(ABS_X, ts->input_dev->absbit);
     __set_bit(ABS_Y, ts->input_dev->absbit);
     __set_bit(ABS_PRESSURE, ts->input_dev->absbit);
     input_set_abs_params(ts->input_dev, ABS_X, 0, SCREEN_MAX_X, 0, 0);
     input_set_abs_params(ts->input_dev, ABS_Y, 0, SCREEN_MAX_Y, 0, 0);
 
     ts->input_dev->name = goodix_ts_name;
     ts->input_dev->phys = goodix_input_phys;
     ts->input_dev->id.bustype = BUS_I2C;
     ts->input_dev->dev.parent = &ts->client->dev;
     
     input_set_drvdata(ts->input_dev, ts);
     
     ret = input_register_device(ts->input_dev);
     if (ret)
     {
         GTP_ERROR("Register %s input device failed", ts->input_dev->name);
         return -ENODEV;
     }
 
     return 0;
 }
 
 static int goodix_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
 {
     s32 ret = -1;
     struct goodix_ts_data *ts;
     u16 version_info;
     
     GTP_DEBUG_FUNC();
     
     GTP_INFO("GTP Driver Version: %s", GTP_DRIVER_VERSION);
     GTP_INFO("GTP I2C Address: 0x%02x", IIC_ADDRESS);
 
     i2c_connect_client = client;
     
     if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) 
     {
         GTP_ERROR("I2C check functionality failed.");
         return -ENODEV;
     }
     ts = kzalloc(sizeof(*ts), GFP_KERNEL);
     if (ts == NULL)
     {
         GTP_ERROR("Alloc GFP_KERNEL memory failed.");
         return -ENOMEM;
     }
 
     INIT_WORK(&ts->work, goodix_ts_work_func);
     ts->client = client;
     spin_lock_init(&ts->irq_lock);
 
     i2c_set_clientdata(client, ts);   
     ts->gtp_rawdiff_mode = 0;
 
     ret = gtp_i2c_test(client);
     if (ret < 0)
     {
         GTP_ERROR("I2C communication ERROR!");
     }
 
     ret = gtp_read_version(client, &version_info);
     if (ret < 0)
     {
         GTP_ERROR("Read version failed.");
     }
     
     ret = gtp_init_panel(ts);
     if (ret < 0)
     {
         GTP_ERROR("GTP init panel failed.");
         ts->abs_x_max = GTP_MAX_WIDTH;
         ts->abs_y_max = GTP_MAX_HEIGHT;
         ts->int_trigger_type = GTP_INT_TRIGGER;
     }
  
     ret = gtp_request_input_dev(ts);
     if (ret < 0)
     {
         GTP_ERROR("GTP request input dev failed");
     }
     
     ret = gtp_request_irq(ts); 
     if (ret < 0)
     {
         GTP_INFO("GTP works in polling mode.");
     }
     else
     {
         GTP_INFO("GTP works in interrupt mode.");
     }
 
     if (ts->use_irq)
     {
         gtp_irq_enable(ts);
     }
     
     return 0;
 }
 
 static void goodix_ts_remove(struct i2c_client *client)
 {
     struct goodix_ts_data *ts = i2c_get_clientdata(client);
     
     GTP_DEBUG_FUNC();
 
     if (ts) 
     {
         if (ts->use_irq)
         {
             //free_irq(client->irq, ts);
         }
         else
         {
             hrtimer_cancel(&ts->timer);
         }
     }   
     
     GTP_INFO("GTP driver removing...");
     i2c_set_clientdata(client, NULL);
     input_unregister_device(ts->input_dev);
     kfree(ts);
 
 }
 
 static int __maybe_unused alinx_ts_suspend(struct device *dev)
 {
     struct i2c_client *client = to_i2c_client(dev);
 
     if (device_may_wakeup(dev)) enable_irq_wake(client->irq);
 
     return 0;
 }
 
 static int __maybe_unused alinx_ts_resume(struct device *dev)
 {
     struct i2c_client *client = to_i2c_client(dev);
 
     if (device_may_wakeup(dev)) disable_irq_wake(client->irq);
 
     return 0;
 }
 
 static SIMPLE_DEV_PM_OPS(alinx_ts_pm_ops, alinx_ts_suspend, alinx_ts_resume);
 
 static const struct edt_i2c_chip_data alinx_data = 
 {
     .max_support_points = 5,
 };
 
 
 static const struct of_device_id goodix_match_table[] = {
 		{.compatible = "alinx,an071", .data = &alinx_data },
 		{ },
 };
 
 static const struct i2c_device_id goodix_ts_id[] = {
     { .name = GTP_I2C_NAME, .driver_data = (long)&alinx_data },
     { }
 };
 
 static struct i2c_driver goodix_ts_driver = {
     .probe      = goodix_ts_probe,
     .remove     = goodix_ts_remove,
     .id_table   = goodix_ts_id,
     .driver = {
         .name     = GTP_I2C_NAME,
         .owner    = THIS_MODULE,
         .of_match_table = goodix_match_table,
         .pm       = &alinx_ts_pm_ops,
     },
 };
 
 static int goodix_ts_init(void)
 {
     s32 ret;
 
     GTP_DEBUG_FUNC();   
     GTP_INFO("GTP driver installing...");
     goodix_wq = create_singlethread_workqueue("goodix_wq");
     if (!goodix_wq)
     {
         GTP_ERROR("Creat workqueue failed.");
         return -ENOMEM;
     }
 
     ret = i2c_add_driver(&goodix_ts_driver);
     return ret; 
 }
 
 static void __exit goodix_ts_exit(void)
 {
     GTP_DEBUG_FUNC();
     GTP_INFO("GTP driver exited.");
     i2c_del_driver(&goodix_ts_driver);
     if (goodix_wq)
     {
         destroy_workqueue(goodix_wq);
     }
 }
 
 module_init(goodix_ts_init);
 module_exit(goodix_ts_exit);
 
 MODULE_DESCRIPTION("GTP Series Driver");
 MODULE_LICENSE("GPL");
 



驱动程序还是从入口函数开始看起。

625行goodix_ts_init()函数中初始化了一个工作队列，用于添加中断处理的内容。

638行通过i2c_add_driver添加的一个i2c驱动，之后就是i2c框架的内容，先看到注册的内容goodix_ts_driver。

goodix_ts_driver的定义在613行，主要看一下probe函数，跳转到474行goodix_ts_probe()函数实现。

499行添加一个队列项到队列中，队列项goodix_ts_work_func()实际也就是中断要处理的内容。198行的函数实现中，主要是对i2c返回的数据解析并处理，具体的定义可参考触摸屏的手册。解析后我们在gtp_touch_down、gtp_touch_up函数中做触摸对应的操作。

527行调用的gtp_request_input_dev()函数，内容是注册一个输入子系统，并且设置了点击事件滑动事件等。

代码中还有一些参数设置就不细说了。总结下来，就是通过i2c设置并获取触摸屏的信息，再通过输入子系统，把触摸行为反馈给系统，以响应其他程序的需求。

我们把21行的GTP_DEBUG_ON定义为1，编译后在板端加载驱动，点击屏幕，就能看到串口中断返回触摸坐标了。

但这还体现不出input子系统的作用，下一章我们结合lcd显示，再确认最终的测试结果。

运行测试
~~~~~~~~~~~~~~~

在加载驱动之前，需要先将AN970触摸屏插到板卡上。

.. image:: images/24_media/image5.png

测试方法步骤如下：

+-----------------------------------------------------------------------+
| mount -t nfs -o nolock 192.168.1.107:/home/alinx/work /mnt            |
|                                                                       |
| cd /mnt                                                               |
|                                                                       |
| mkdir /tmp/qt                                                         |
|                                                                       |
| mount qt_lib.img /tmp/qt                                              |
|                                                                       |
| cd /tmp/qt                                                            |
|                                                                       |
| source ./qt_env_set.sh                                                |
|                                                                       |
| cd /mnt                                                               |
|                                                                       |
| insmod ./ax-touchscreen-drv.ko                                        |
+-----------------------------------------------------------------------+

.. image:: images/24_media/image6.png
   
在加载完驱动后，点击屏幕，控制台会打印点击的坐标信息。

.. image:: images/24_media/image7.png
   