Edit online

驱动文件移植解析

5 Dec 2024
Read time: 18 minute(s)

本节描述了 GT911 触摸屏驱动的初始化和配置示例,包括 I2C 通信、GPIO 引脚配置以及触摸事件的处理。

主要功能模块简介
  1. I2C 通信初始化:

    • gt911_client.bus 用于存储 I2C 总线设备。查找并打开I2C总线设备:
      gt911_client.bus = (struct rt_i2c_bus_device *)rt_device_find(cfg->dev_name);
      if (gt911_client.bus == RT_NULL) {
          LOG_E("Can't find %s device", cfg->dev_name);
          return -RT_ERROR;
      }
      if (rt_device_open((rt_device_t)gt911_client.bus, RT_DEVICE_FLAG_RDWR) != RT_EOK) {
          LOG_E("open %s device failed", cfg->dev_name);
          return -RT_ERROR;
      }
    • gt911_client.client_addr 设置从机设备地址。
      gt911_client.client_addr = GT911_ADDRESS_HIGH;
      
  2. GPIO引脚配置:

    rt_gt911_gpio_cfg() 函数配置复位引脚和中断引脚。

    配置复位引脚和中断引脚为输入模式,并设置中断模式:
    static int rt_gt911_gpio_cfg() {
        unsigned int g, p;
        long pin;
    
        pin = drv_pin_get(AIC_TOUCH_PANEL_RST_PIN);
        g = GPIO_GROUP(pin);
        p = GPIO_GROUP_PIN(pin);
        hal_gpio_direction_input(g, p);
    
        pin = drv_pin_get(AIC_TOUCH_PANEL_INT_PIN);
        g = GPIO_GROUP(pin);
        p = GPIO_GROUP_PIN(pin);
        hal_gpio_direction_input(g, p);
        hal_gpio_set_irq_mode(g, p, 0);
    
        return 0;
    }
    
  3. 触摸设备注册:

    rt_hw_gt911_init() 函数完成触摸设备的初始化和注册。

    分配内存并初始化触摸设备结构体,然后注册该设备:
    static int rt_hw_gt911_init(const char *name, struct rt_touch_config *cfg) {
        touch_device = (struct rt_touch_device *)rt_malloc(sizeof(struct rt_touch_device));
        if (touch_device == RT_NULL) {
            LOG_E("touch device malloc fail");
            return -RT_ERROR;
        }
        rt_memset((void *)touch_device, 0, sizeof(struct rt_touch_device));
        touch_device->info.type = RT_TOUCH_TYPE_CAPACITANCE;
        touch_device->info.vendor = RT_TOUCH_VENDOR_GT;
        rt_memcpy(&touch_device->config, cfg, sizeof(struct rt_touch_config));
        touch_device->ops = &gt911_touch_ops;
        if (RT_EOK != rt_hw_touch_register(touch_device, name, RT_DEVICE_FLAG_INT_RX, RT_NULL)) {
            LOG_E("touch device gt911 init failed !!!");
            return -RT_ERROR;
        }
        LOG_I("touch device gt911 init success");
        return RT_EOK;
    }
    
  4. 触摸事件处理:

    • gt911_read_point() 读取触摸点信息:
      static rt_err_t gt911_read_point(struct rt_touch_device *touch, void *buf, rt_size_t read_num)
    • gt911_control() 控制 TP 设备,如获取ID、校准等:
      
      static rt_err_t gt911_control(struct rt_touch_device *touch, int cmd, void *arg)
  5. 设备自动加载:

    使用宏 INIT_DEVICE_EXPORT(rt_hw_gt911_port) 实现驱动在系统启动时自动加载:
    INIT_DEVICE_EXPORT(rt_hw_gt911_port);
    
完整代码示例
#include <rtthread.h>
#include <rtdevice.h>

#include <string.h>

#define DBG_TAG "gt911"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>

#include "gt911.h"

//dev结构体定义,其他TP也需要定义这个变量
static struct rt_i2c_client gt911_client;

//这个是gt911特有的数组用于更改固件的,没这需求的可以不定义
static rt_uint8_t GT911_CFG_TBL[] = {
    0x6b, 0x00, 0x04, 0x58, 0x02, 0x05, 0x0d, 0x00, 0x01, 0x0f, 0x28, 0x0f,
    0x50, 0x32, 0x03, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x8a, 0x2a, 0x0c, 0x45, 0x47, 0x0c, 0x08, 0x00, 0x00,
    0x00, 0x40, 0x03, 0x2c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x64, 0x32,
    0x00, 0x00, 0x00, 0x28, 0x64, 0x94, 0xd5, 0x02, 0x07, 0x00, 0x00, 0x04,
    0x95, 0x2c, 0x00, 0x8b, 0x34, 0x00, 0x82, 0x3f, 0x00, 0x7d, 0x4c, 0x00,
    0x7a, 0x5b, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x18, 0x16, 0x14, 0x12, 0x10, 0x0e, 0x0c, 0x0a,
    0x08, 0x06, 0x04, 0x02, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x18,
    0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x24, 0x13, 0x12, 0x10, 0x0f,
    0x0a, 0x08, 0x06, 0x04, 0x02, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x79, 0x01,
};

/*
 * I2C写函数,主要传参有三个:dev,要写入的数据,写入数据的长度
 * 这个函数适用于所有的TP,编写的时候只需要更换函数名字,跟TP对应上即可
 */
static rt_err_t gt911_write_reg(struct rt_i2c_client *dev, rt_uint8_t *data,
                                rt_uint8_t len)
{
    struct rt_i2c_msg msgs;
    //从机的设备地址,在初始化的时候会进行赋值
    msgs.addr = dev->client_addr;
    //I2C读写标志,这里是写标志
    msgs.flags = RT_I2C_WR;
    //要发送的数据
    msgs.buf = data;
    //数据长度
    msgs.len = len;

    if (rt_i2c_transfer(dev->bus, &msgs, 1) == 1) {
        return RT_EOK;
    } else {
        return -RT_ERROR;
    }
}

/*
 * I2C读函数,主要传参有四个:dev,要读取的寄存器地址,接受数据的buf,读取的长度
 * 这个函数适用于所有的TP,编写的时候只需要更换函数名字,跟TP对应上即可
 */
static rt_err_t gt911_read_regs(struct rt_i2c_client *dev, rt_uint8_t *reg,
                                rt_uint8_t *data, rt_uint8_t len)
{
    struct rt_i2c_msg msgs[2];

    msgs[0].addr = dev->client_addr;
    msgs[0].flags = RT_I2C_WR;
    /*
     * 要读取得寄存器地址,目前市面上有两种常见得TP,一种是16位寄存器地址的,一种是8位的
     * 16位的寄存器地址需要先发送高8位,再发送低8位,分成两个buf
     * 8位的直接发送即可
     */
    msgs[0].buf = reg;
    // 这里的长度对应上面的16位或者8位,16位长度为2,8位长度为1
    msgs[0].len = GT911_REGITER_LEN;

    msgs[1].addr = dev->client_addr;
    msgs[1].flags = RT_I2C_RD;
    msgs[1].buf = data;
    msgs[1].len = len;

    if (rt_i2c_transfer(dev->bus, msgs, 2) == 2) {
        return RT_EOK;
    } else {
        return -RT_ERROR;
    }
}

/*
 * 这个函数主要用于获取TP的ID,有些TP并没有具体获取ID的寄存器公开,可以不添加
 */
static rt_err_t gt911_get_product_id(struct rt_i2c_client *dev,
                                     rt_uint8_t *data, rt_uint8_t len)
{
    rt_uint8_t reg[2];
    // 这里就是将16位的寄存器地址拆开高8位低8位两个buf进行传输
    reg[0] = (rt_uint8_t)(GT911_PRODUCT_ID >> 8);
    reg[1] = (rt_uint8_t)(GT911_PRODUCT_ID & 0xff);

    if (gt911_read_regs(dev, reg, data, len) != RT_EOK) {
        LOG_E("read id failed");
        return -RT_ERROR;
    }
    return RT_EOK;
}

/*
 * 这个函数主要用于获取TP的参数,包括:xy的坐标范围最大值,触摸点的个数
 */
static rt_err_t gt911_get_info(struct rt_i2c_client *dev,
                               struct rt_touch_info *info)
{
    rt_uint8_t reg[2];
    rt_uint8_t out_info[7];
    rt_uint8_t out_len = 7;

    reg[0] = (rt_uint8_t)(GT911_CONFIG_REG >> 8);
    reg[1] = (rt_uint8_t)(GT911_CONFIG_REG & 0xFF);

    if (gt911_read_regs(dev, reg, out_info, out_len) != RT_EOK) {
        LOG_E("read info failed");
        return -RT_ERROR;
    }
    /*
     * 这里是对获取到的数据进行组合得到参数信息
     * 有些TP对这些寄存器并不开放,因此我们也可以通过直接赋值的方法设置参数
     * 设置的参数要跟TP自带的固件对应上才能正常使用,具体操作如下
     */
    info->range_x = (out_info[2] << 8) | out_info[1];
    //info->range_x = AIC_TOUCH_X_COORDINATE_RANGE;
    //这里的AIC_TOUCH_X_COORDINATE_RANGE就是公用参数那里可以配置的,所以不同TP都可通用
    info->range_y = (out_info[4] << 8) | out_info[3];
    //info->range_y = AIC_TOUCH_Y_COORDINATE_RANGE;
    //这里的AIC_TOUCH_Y_COORDINATE_RANGE也是公用参数那里可以配置的,所以不同TP都可通用
    info->point_num = out_info[5] & 0x0f;
    //info->point_num = 5;
    if (info->point_num > GT911_MAX_TOUCH) {
        info->point_num = GT911_MAX_TOUCH;
        rt_kprintf("Warning,tp support more than 5 points, limited to 5 points\n");
    }

    return RT_EOK;
}

/*
 * 这里的静态变量主要是为了存储坐标点的数据,然后作逻辑判断
 * GT911_MAX_TOUCH是最大的触摸点个数也就是上面info->point_num定义的5,所以数组赋值了5个-1作为初始值
 * 不同的触摸个数这个数组大小不一样
 */
static int16_t pre_x[GT911_MAX_TOUCH] = { -1, -1, -1, -1, -1 };
static int16_t pre_y[GT911_MAX_TOUCH] = { -1, -1, -1, -1, -1 };
//这个pre_w是触摸面积的定义,有些TP并没有相关描述,所以不是必须的,但xy是必须要有
static int16_t pre_w[GT911_MAX_TOUCH] = { -1, -1, -1, -1, -1 };
static rt_uint8_t s_tp_dowm[GT911_MAX_TOUCH] = {0};
static struct rt_touch_data *read_data = RT_NULL;

/*
 * 这个是抬起事件上报函数,修改函数明即可使用
 * 如果定义上面的静态数组的时候没有定义pre_w则下面代码关于pre_w的要删除
 */
static void gt911_touch_up(void *buf, int8_t id)
{
    read_data = (struct rt_touch_data *)buf;

    if (s_tp_dowm[id] == 1) {
        s_tp_dowm[id] = 0;
        read_data[id].event = RT_TOUCH_EVENT_UP;
    } else {
        read_data[id].event = RT_TOUCH_EVENT_NONE;
    }

    read_data[id].timestamp = rt_touch_get_ts();
    //下面的这行可以删除
    read_data[id].width = pre_w[id];
    read_data[id].x_coordinate = pre_x[id];
    read_data[id].y_coordinate = pre_y[id];
    read_data[id].track_id = id;

    pre_x[id] = -1; /* last point is none */
    pre_y[id] = -1;
    //下面的这行可以删除
    pre_w[id] = -1;
}

/*
 * 这个是移动和按下事件函数,修改函数明即可使用
 * 如果定义上面的静态数组的时候没有定义pre_w则下面代码关于pre_w的要删除
 * 包括函数传参里面的int16_t w也要删除
 */
static void gt911_touch_down(void *buf, int8_t id, int16_t x, int16_t y,
                             int16_t w)
{
    read_data = (struct rt_touch_data *)buf;

    if (s_tp_dowm[id] == 1) {
        read_data[id].event = RT_TOUCH_EVENT_MOVE;
    } else {
        read_data[id].event = RT_TOUCH_EVENT_DOWN;
        s_tp_dowm[id] = 1;
    }

    read_data[id].timestamp = rt_touch_get_ts();
    //下面的这行可以删除
    read_data[id].width = w;
    read_data[id].x_coordinate = x;
    read_data[id].y_coordinate = y;
    read_data[id].track_id = id;

    pre_x[id] = x; /* save last point */
    pre_y[id] = y;
    //下面的这行可以删除
    pre_w[id] = w;
}

/*
 * 这个读取坐标点数据的函数,必须实现
 */
static rt_size_t gt911_read_point(struct rt_touch_device *touch, void *buf,
                                  rt_size_t read_num)
{
    rt_uint8_t point_status = 0;
    rt_uint8_t touch_num = 0;
    rt_uint8_t write_buf[3];
    rt_uint8_t cmd[2];
    //要读取的数据的长度,一般是读取坐标数量*每一个坐标信息的buf长度+一些其他信息
    rt_uint8_t read_buf[8 * GT911_MAX_TOUCH] = { 0 };
    rt_uint8_t read_index;
    int8_t read_id = 0;
    int16_t input_x = 0;
    int16_t input_y = 0;
    int16_t input_w = 0;
    //存储上一次触摸点个数
    static rt_uint8_t pre_touch = 0;
    //存储上一次坐标ID的数组
    static int8_t pre_id[GT911_MAX_TOUCH] = { 0 };
    //这里是对传参过来的buf清零,避免buf里面已有数据对坐标读取产生影响
    rt_memset(buf, 0, sizeof(struct rt_touch_data) * read_num);

    /* point status register */
    cmd[0] = (rt_uint8_t)((GT911_READ_STATUS >> 8) & 0xFF);
    cmd[1] = (rt_uint8_t)(GT911_READ_STATUS & 0xFF);

    if (gt911_read_regs(&gt911_client, cmd, &point_status, 1) != RT_EOK) {
        LOG_D("read point failed\n");
        read_num = 0;
        goto exit_;
    }

    if (point_status == 0) /* no data */
    {
        read_num = 0;
        goto exit_;
    }

    if ((point_status & 0x80) == 0) /* data is not ready */
    {
        read_num = 0;
        goto exit_;
    }
    //触摸点个数赋值
    touch_num = point_status & 0x0f; /* get point num */

    if (touch_num > GT911_MAX_TOUCH) /* point num is not correct */
    {
        read_num = 0;
        goto exit_;
    }

    cmd[0] = (rt_uint8_t)((GT911_POINT1_REG >> 8) & 0xFF);
    cmd[1] = (rt_uint8_t)(GT911_POINT1_REG & 0xFF);

    //读取坐标数据
    if (gt911_read_regs(&gt911_client, cmd, read_buf,
                        read_num * GT911_POINT_INFO_NUM) != RT_EOK) {
        LOG_D("read point failed\n");
        read_num = 0;
        goto exit_;
    }
    /*
     * 下面是对坐标事件的处理,一般可以直接照抄,更换一下信息即可
     */
    if (pre_touch > touch_num) /* point up */
    {
        for (read_index = 0; read_index < pre_touch; read_index++) {
            rt_uint8_t j;

            for (j = 0; j < touch_num; j++) /* this time touch num */
            {
                //坐标ID这里是需要更换的,要按照规格书来赋值
                read_id = read_buf[j * 8] & 0x0F;

                if (pre_id[read_index] == read_id) /* this id is not free */
                    break;

                if (j >= touch_num - 1) {
                    rt_uint8_t up_id;
                    up_id = pre_id[read_index];
                    gt911_touch_up(buf, up_id);
                }
            }
        }
    }

    if (touch_num) /* point down */
    {
        rt_uint8_t off_set;

        for (read_index = 0; read_index < touch_num; read_index++) {
            //这个offset是一个坐标信息的偏移,8代表一个坐标要读取8个byte,具体是多少按照规格书来
            off_set = read_index * 8;
            //坐标ID这里是需要更换的,要按照规格书来赋值
            read_id = read_buf[off_set] & 0x0f;
            pre_id[read_index] = read_id;
            //下面的xyw也是需要按照规格书重新组合buf
            input_x =
                read_buf[off_set + 1] | (read_buf[off_set + 2] << 8); /* x */
            input_y =
                read_buf[off_set + 3] | (read_buf[off_set + 4] << 8); /* y */
            input_w =
                read_buf[off_set + 5] | (read_buf[off_set + 6] << 8); /* size */

            gt911_touch_down(buf, read_id, input_x, input_y, input_w);
        }
    } else if (pre_touch) {
        for (read_index = 0; read_index < pre_touch; read_index++) {
            gt911_touch_up(buf, pre_id[read_index]);
        }
    }
    //将当前的触摸点个数赋值给pre_touch
    pre_touch = touch_num;

exit_:
    //这里是gt911特有的每次读取完数据要发送0x00完成同步,没有这个要求的不需要实现
    write_buf[0] = (rt_uint8_t)((GT911_READ_STATUS >> 8) & 0xFF);
    write_buf[1] = (rt_uint8_t)(GT911_READ_STATUS & 0xFF);
    write_buf[2] = 0x00;
    gt911_write_reg(&gt911_client, write_buf, 3);
    //返回触摸点个数
    return read_num;
}

/*
 * TP的控制函数,一般来说只需要实现RT_TOUCH_CTRL_GET_INFO,其他的可以不管
 */
static rt_err_t gt911_control(struct rt_touch_device *touch, int cmd, void *arg)
{
    if (cmd == RT_TOUCH_CTRL_GET_ID) {
        return gt911_get_product_id(&gt911_client, arg, 6);
    }

    if (cmd == RT_TOUCH_CTRL_GET_INFO) {
        return gt911_get_info(&gt911_client, arg);
    }

    rt_uint8_t buf[4];
    rt_uint8_t i = 0;
    rt_uint8_t *config;

    config =
        (rt_uint8_t *)rt_calloc(1, sizeof(GT911_CFG_TBL) + GT911_REGITER_LEN);
    if (config == RT_NULL) {
        LOG_D("malloc config memory failed\n");
        return -RT_ERROR;
    }

    config[0] = (rt_uint8_t)((GT911_CONFIG_REG >> 8) & 0xFF);
    config[1] = (rt_uint8_t)(GT911_CONFIG_REG & 0xFF);

    memcpy(&config[2], GT911_CFG_TBL, sizeof(GT911_CFG_TBL));

    switch (cmd) {
        case RT_TOUCH_CTRL_SET_X_RANGE: {
            rt_uint16_t x_range;

            x_range = *(rt_uint16_t *)arg;
            config[4] = (rt_uint8_t)(x_range >> 8);
            config[3] = (rt_uint8_t)(x_range & 0xff);

            GT911_CFG_TBL[2] = config[4];
            GT911_CFG_TBL[1] = config[3];
            break;
        }
        case RT_TOUCH_CTRL_SET_Y_RANGE: {
            rt_uint16_t y_range;

            y_range = *(rt_uint16_t *)arg;
            config[6] = (rt_uint8_t)(y_range >> 8);
            config[5] = (rt_uint8_t)(y_range & 0xff);

            GT911_CFG_TBL[4] = config[6];
            GT911_CFG_TBL[3] = config[5];
            break;
        }
        case RT_TOUCH_CTRL_SET_X_TO_Y: {
            config[8] ^= (1 << 3);
            break;
        }
        case RT_TOUCH_CTRL_SET_MODE: {
            rt_uint16_t trig_type;
            trig_type = *(rt_uint16_t *)arg;

            switch (trig_type) {
                case RT_DEVICE_FLAG_INT_RX:
                    config[8] &= 0xFC;
                    break;
                case RT_DEVICE_FLAG_RDONLY:
                    config[8] &= 0xFC;
                    config[8] |= 0x02;
                    break;
                default:
                    break;
            }
            break;
        }
        default: {
            break;
        }
    }

    if (gt911_write_reg(&gt911_client, config,
                        sizeof(GT911_CFG_TBL) + GT911_ADDR_LEN) != RT_EOK) {
        LOG_D("send config failed");
        return -1;
    }

    buf[0] = (rt_uint8_t)((GT911_CHECK_SUM >> 8) & 0xFF);
    buf[1] = (rt_uint8_t)(GT911_CHECK_SUM & 0xFF);
    buf[2] = 0;

    for (i = GT911_ADDR_LEN; i < sizeof(GT911_CFG_TBL) + GT911_ADDR_LEN; i++) {
        buf[GT911_ADDR_LEN] += config[i];
    }

    buf[2] = (~buf[2]) + 1;
    buf[3] = 1;

    gt911_write_reg(&gt911_client, buf, 4);
    rt_free(config);

    return RT_EOK;
}

/*
 * rt_touch_ops,主要是完成对读坐标和控制TP的函数注册
 */
static struct rt_touch_ops gt911_touch_ops = {
    .touch_readpoint = gt911_read_point,
    .touch_control = gt911_control,
};

/*
 * 这个函数主要实现对TP的初始操作
 */
static int rt_hw_gt911_init(const char *name, struct rt_touch_config *cfg)
{
    struct rt_touch_device *touch_device = RT_NULL;

    touch_device =
        (struct rt_touch_device *)rt_malloc(sizeof(struct rt_touch_device));
    if (touch_device == RT_NULL) {
        LOG_E("touch device malloc fail");
        return -RT_ERROR;
    }
    rt_memset((void *)touch_device, 0, sizeof(struct rt_touch_device));
    //这里是对TP一些上下电的操作,具体根据规格书来
    /* hw init*/
    // rst output 0
    rt_pin_mode(*(rt_uint8_t *)cfg->user_data, PIN_MODE_OUTPUT);
    rt_pin_write(*(rt_uint8_t *)cfg->user_data, PIN_LOW);
    rt_thread_delay(10);

    // irq output 0
    rt_pin_mode(cfg->irq_pin.pin, PIN_MODE_OUTPUT);
    rt_pin_write(cfg->irq_pin.pin, PIN_LOW);

    rt_thread_delay(2);
    // rst output 1
    rt_pin_mode(*(rt_uint8_t *)cfg->user_data, PIN_MODE_OUTPUT);
    rt_pin_write(*(rt_uint8_t *)cfg->user_data, PIN_HIGH);

    rt_thread_delay(5);
    // rst input
    rt_pin_mode(*(rt_uint8_t *)cfg->user_data, PIN_MODE_INPUT);

    //irq output 0
    rt_pin_mode(cfg->irq_pin.pin, PIN_MODE_OUTPUT);
    rt_pin_write(cfg->irq_pin.pin, PIN_LOW);

    rt_thread_delay(50);
    //最后必须要对IRQ引脚设置回输入模式,不然会影响中断信号的获取
    rt_pin_mode(cfg->irq_pin.pin, PIN_MODE_INPUT);

    gt911_client.bus =
        (struct rt_i2c_bus_device *)rt_device_find(cfg->dev_name);

    if (gt911_client.bus == RT_NULL) {
        LOG_E("Can't find %s device", cfg->dev_name);
        return -RT_ERROR;
    }
    //打开设备中断模式用RT_DEVICE_FLAG_RDWR
    if (rt_device_open((rt_device_t)gt911_client.bus, RT_DEVICE_FLAG_RDWR) !=
        RT_EOK) {
        LOG_E("open %s device failed", cfg->dev_name);
        return -RT_ERROR;
    }
    //从机设备地址赋值
    gt911_client.client_addr = GT911_ADDRESS_HIGH;

    /* register touch device */
    //赋值芯片的型号和类型
    touch_device->info.type = RT_TOUCH_TYPE_CAPACITANCE;
    touch_device->info.vendor = RT_TOUCH_VENDOR_GT;
    rt_memcpy(&touch_device->config, cfg, sizeof(struct rt_touch_config));
    touch_device->ops = &gt911_touch_ops;

    if (RT_EOK != rt_hw_touch_register(touch_device, name, RT_DEVICE_FLAG_INT_RX, RT_NULL)) {
        LOG_E("touch device gt911 init failed !!!");
        return -RT_ERROR;
    }

    LOG_I("touch device gt911 init success");
    return RT_EOK;
}

/*
 * 这个函数主要实现对GPIO的初始化,这个函数只需修改函数名即可复用
 */
static int rt_gt911_gpio_cfg()
{
    unsigned int g, p;
    long pin;

    // AIC_TOUCH_PANEL_RST_PIN是公用参数里面的复位引脚
    pin = drv_pin_get(AIC_TOUCH_PANEL_RST_PIN);
    g = GPIO_GROUP(pin);
    p = GPIO_GROUP_PIN(pin);
    hal_gpio_direction_input(g, p);

    // AIC_TOUCH_PANEL_INT_PIN是公用参数里面的中断引脚
    pin = drv_pin_get(AIC_TOUCH_PANEL_INT_PIN);
    g = GPIO_GROUP(pin);
    p = GPIO_GROUP_PIN(pin);
    hal_gpio_direction_input(g, p);
    hal_gpio_set_irq_mode(g, p, 0);

    return 0;
}

/*
 * 这个函数主要注册整个设备,改个名字即可复用
 */
static int rt_hw_gt911_port(void)
{
    struct rt_touch_config cfg;
    rt_uint8_t rst_pin;

    rt_gt911_gpio_cfg();

    rst_pin = drv_pin_get(AIC_TOUCH_PANEL_RST_PIN);
    cfg.dev_name = AIC_TOUCH_PANEL_I2C_CHAN;
    cfg.irq_pin.pin = drv_pin_get(AIC_TOUCH_PANEL_INT_PIN);
    cfg.irq_pin.mode = PIN_MODE_INPUT;
    cfg.user_data = &rst_pin;
#ifdef AIC_PM_DEMO
    rt_pm_set_pin_wakeup_source(cfg.irq_pin.pin);
#endif

    // AIC_TOUCH_PANEL_NAME这个是公用参数里面设备名字,也可以直接复用
    rt_hw_gt911_init(AIC_TOUCH_PANEL_NAME, &cfg);

    return 0;
}
//通过宏 ``INIT_DEVICE_EXPORT``,驱动在系统启动时自动加载,即可轻松适配不同型号的触控芯片
INIT_DEVICE_EXPORT(rt_hw_gt911_port);