多点触摸屏驱动#

多点触摸屏我们并不陌生,也就是电容屏,现在的手机、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();

  1. 设置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);

  1. 注册input_dev结构体

input_register_device(ts_dev);

  1. 硬件操作

注册中断:

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即可。

../_images/image1278.png

实验#

原理图#

../_images/image2192.png

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

设备树#

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

../_images/image3166.png

多点触摸屏驱动程序#

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

../_images/image4139.png

在文件ax-touchscreen-drv.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触摸屏插到板卡上。

../_images/image5107.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

../_images/image697.png

在加载完驱动后,点击屏幕,控制台会打印点击的坐标信息。

../_images/image788.png