DeviceDriver(十四):多点触摸(MT协议,Input子系统)

Input子系统框架参考:

https://blog.csdn.net/qq_34968572/article/details/89875957

电阻式多点触摸驱动参考:

https://blog.csdn.net/qq_34968572/article/details/90695776

一:电容触摸屏知识点

1、电容触摸屏是I2C接口,需要触摸IC,因此框架为I2C设备驱动框架。

2、通过中断引脚(INT)向Linux内核上报触摸信息,因此需要用到Linux中断驱动框架,坐标上报在中断服务函数中完成。

3、触摸屏的坐标信息,屏幕按下和抬起信息都属于Linux和input子系统,因此向Linux内核上报触摸屏坐标信息就得用到input子系统。

二:多点触摸(MT)协议

1、MT协议被分为两种类型,TypeA和TypeB

TypeA:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据(较少使用)

TypeB:适用于有硬件追踪并且能区分触摸点的触摸设备,此类型的设备通过一个slot更新某一个触摸点的信息。

触摸点的信息通过一系列的ABS_MT事件上报给Linux内核,只有ABS_MT事件是用于多点触摸的:

#define ABS_MT_SLOT		0x2f	/* MT slot being modified */
#define ABS_MT_TOUCH_MAJOR	0x30	/* Major axis of touching ellipse */
#define ABS_MT_TOUCH_MINOR	0x31	/* Minor axis (omit if circular) */
#define ABS_MT_WIDTH_MAJOR	0x32	/* Major axis of approaching ellipse */
#define ABS_MT_WIDTH_MINOR	0x33	/* Minor axis (omit if circular) */
#define ABS_MT_ORIENTATION	0x34	/* Ellipse orientation */
#define ABS_MT_POSITION_X	0x35	/* Center X touch position */
#define ABS_MT_POSITION_Y	0x36	/* Center Y touch position */
#define ABS_MT_TOOL_TYPE	0x37	/* Type of touching device */
#define ABS_MT_BLOB_ID		0x38	/* Group a set of packets as a blob */
#define ABS_MT_TRACKING_ID	0x39	/* Unique ID of initiated contact */
#define ABS_MT_PRESSURE		0x3a	/* Pressure on contact area */
#define ABS_MT_DISTANCE		0x3b	/* Contact hover distance */
#define ABS_MT_TOOL_X		0x3c	/* Center X tool position */
#define ABS_MT_TOOL_Y		0x3d	/* Center Y tool position */

       其中最常用的ABS_MT_POSITION_X和ABS_MT_POSITION_Y用来上报触摸点的(X,Y)坐标信息,ABS_MT_SLOT用来上报触摸点ID,对于TypeB类型设备需要用到ABS_MT_TRACKING_ID事件来区分触摸点。

2、上报数据

TypeA:void input_mt_sync(struct input_dev *dev)

该函数会触发SYN_MT_REPORT事件,此事件会通知接受者获取当前触摸数据,并且准备接受下一个触摸点数据。

TypeB:void input_mt_slot(struct input_dev *dev, int slot)

该函数第二个参数slot用于指定当前上报的触摸点信息,并触发ABS_MT_SLOT事件,此事件会告诉接收者当前正在更新的是哪个触摸点(slot)的数据。

       两种类型的设备最终都要调用input_sync()函数来标识多点触摸信息传输完成,告诉接收者处理之前累积的信息,并且准备好下一次接收。TypeB设备驱动需要给每个识别出来的触摸点分配一个slot,后面使用这个slot来上报触摸点信息,可以通过slot的ABS_MT_TRACKING_ID来新增,替换或删除触摸点,ID为-1表示slot未使用,一个不存在的ID表示一个新加的触摸点,一个ID如果不存在了就表示删除了。一旦检测到某个slot关联的触摸点ID发生了变化,驱动就应该改变这个slot的ABS_TM_TRACKING_ID,使这个slot失效,如果硬件设备追踪到了比他正在上报的还要多的触摸点,那么驱动就应该发送BTN_TOOL_*TAP消息,并且调用input_mt_report_pointer_emulation()函数,将此函数的第二个参数use_count设置为false。

3、上报时序

TypeA:

(1)通过ABS_MT_POSITION_X或者ABS_MT_POSITION_Y上报坐标数据,通过input_report_abs实现;

(2)上报SYN_MT_REPORT事件,通过input_my_sync实现;

(3)上报SYN_REPORT事件,通过input_sync实现。

每上报完一轮触摸点信息就调用一次input_sync函数,也就是发送一个SYN_REPORT。

static irqreturn_t xxx_handler(int irq, void *dev_id)
{
    ret = xxx_read_data(ts);

    for (i = 0; i < MAX_FINGERS; i++)
    {
        input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, finger[i].t);
        input_report_abs(input_dev, ABS_MT_POSITION_X, finger[i].x);   
        input_report_abs(input_dev, ABS_MT_POSITION_Y, finger[i].y);     
        input_mt_sync(input_dev);
    }
    input_sync(input_dev);
}

TypeB:

(1)上报ABS_MT_SLOT事件,也就是触摸点对应的SLOT,每次上报一个触摸点坐标前先使用input_mt_slot函数上报当前触摸点slot,其实就是触摸点ID,需要由触摸IC提供。

(2)根据TypeB要求,每个SLOT必须关联一个ABS_MT_TRACKING_ID,通过修改SLOT关联的ABS_MT_TRACKING_ID来完成对触摸点的添加,替换和删除。具体用到的函数为input_mt_report_slot_state,如果是添加新的触摸点,此函数第三个参数active要设置为true,linux会自动分配一个ABS_MT_TRACKING_ID值。

(3)上报触摸点X和Y坐标值,使用函数input_report_abs来完成。

(4)上传所有触摸点坐标后就可以发送SYN_REPORT事件,使用input_sync函数

static void xxx_report_events(struct input_dev *input, const struct touchdata *touchdata)
{
    bool touch;

    for (i = 0; i < MAX_TOUCHES; i++)  
    {
        input_mt_slot(input, i);
        input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);
        input_report_abs(input, ABS_MT_POSITION_X, x);
        input_report_abs(input, ABS_MT_POSITION_Y, y);
    }
    input_mt_report_pointer_emulation(input, false);
    input_sync(input);
}

三:多点触摸API函数

1、input_mt_init_slots:用于初始化MT的输入slots。

int input_mt_init_slots( struct input_dev *dev, unsigned int num_slots, unsigned int flags)

2、input_mt_slot:用于TypeB类型产生ABS_MT_SLOT事件,告诉内核当前上报的是哪个触摸点的坐标数据。

void input_mt_slot(struct input_dev *dev, int slot)

3、input_mt_report_slot_state:用于TypeB类型产生ABS_MT_TRACKING_ID和ABS_MT_TOOL_TYPE事件。

void input_mt_report_slot_state( struct input_dev *dev, unsigned int tool_type,bool active)

4、input_report_abs:用于TypeA和TypeB类型上报触摸点坐标信息。

void input_report_abs( struct input_dev *dev,unsigned int code,int value)

5、input_mt_report_pointer_emulation:如果追踪到的触摸点数量多于当前上报的数量,驱动程序使用BTN_TOOL_TAP事件来通知用户空间当前追踪到的触摸点总数量,然后调用该函数并将ues_count参数设置为false。否则为true,表示当前的触摸点数量。

void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)

四:多点触摸驱动示例

1、修改设备树文件

(1)添加FT5426触摸芯片IO,共4个IO(复位,中断,I2C2的SCL,SDA)

复位IO和中断IO是普通的GPIO:

pinctrl_tsc: tscgrp {
	fsl, pin = <
		MX6UL_PAD_SNVS_TAMPER9__GPIO5_IO09  0x10B0
		MX6UL_PAD_GPIO1_IO09__GPIO1_IO09    0xF080
	>;
};

I2C2的IO:

pinctrl_i2c2: i2c2grp {
	fsl,pins = <
		MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
		MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
	>;
};

(2)添加FT5426节点

&i2c2 {
	clock_frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c2>;
	status = "okay";
    ... ...
	ft5426: ft5426@38 {
		compatible = "edt, edt-ft5426";
		reg = <0x38>;
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_tsc>;
		interrupt-parent = <&gpio1>;
		interrupts = <9 0>;
		reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
		interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
	};
};

2、驱动

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAX_SUPPORT_POINTS 5 /* 5 点触摸 */
#define TOUCH_EVENT_DOWN 0x00 /* 按下 */
#define TOUCH_EVENT_UP 0x01 /* 抬起 */
#define TOUCH_EVENT_ON 0x02 /* 接触 */
#define TOUCH_EVENT_RESERVED 0x03 /* 保留 */

/* FT5X06 寄存器相关宏定义 */
#define FT5X06_TD_STATUS_REG 0X02 /* 状态寄存器地址 */
#define FT5x06_DEVICE_MODE_REG 0X00 /* 模式寄存器 */
#define FT5426_IDG_MODE_REG 0XA4 /* 中断模式 */
#define FT5X06_READLEN 29 /* 要读取的寄存器个数 */


struct tsc_dev {
    struct device_node *nd;
    int irq_pin, reset_pin;
    int irqnum;
    void *private_data;
    struct input_dev *input;
    struct i2c_client *client;
};

static struct tsc_dev tsc;


static int tsc_reset(struct i2c_client *client, struct tsc_dev *dev)
{
    int ret = 0;

    if(gpio_is_valid(dev->reset_pin)) {
        ret = devm_gpio_request_one(&client->dev, dev->reset_pin, GPIOF_OUT_INIT_LOW, "edt-ft5x06 reset");
    
        if(ret) {
            return ret;
        }

        msleep(5);
        gpio_set_value(dev->reset_pin, 1);
        msleep(300);
    }
    return 0;
}

static int tsc_read_regs(struct tsc_dev *dev, u8 reg, void *val, int len)
{
    int ret;
    struct i2c_msg msg[2];
    struct i2c_client *client = (struct i2c_client *)dev->client;

    /* msg[0]为发送要读取的首地址 */
	msg[0].addr = client->addr;			/* ft5x06地址 */
	msg[0].flags = 0;					/* 标记为发送数据 */
	msg[0].buf = ®					/* 读取的首地址 */
	msg[0].len = 1;						/* reg长度*/

	/* msg[1]读取数据 */
	msg[1].addr = client->addr;			/* ft5x06地址 */
	msg[1].flags = I2C_M_RD;			/* 标记为读取数据*/
	msg[1].buf = val;					/* 读取数据缓冲区 */
	msg[1].len = len;					/* 要读取的数据长度*/

    ret = i2c_transfer(client->adapter, msg, 2);
    if(ret == 2){
        ret = 0;
    } else {
        ret = -EREMOTEIO;
    }
    return ret;
}

static s32 tsc_write_regs(struct tsc_dev *dev, u8 reg, u8 *buf, u8 len)
{
    u8 b[256];
    struct i2c_msg msg;
    struct i2c_client *client = (struct i2c_client *)dev->client;

    b[0] = reg;
    memcpy(&b[1], buf, len);

    msg.addr = client->addr;
    msg.flags = 0;

    msg.buf = b;
    msg.len = len + 1;

    return i2c_transfer(client->adapter, &msg, 1);
}

static void tsc_write_reg(struct tsc_dev *dev, u8 reg, u8 data)
{
    u8 buf = 0;
    buf = data;
    tsc_write_regs(dev, reg, &buf, 1);
}

static irqreturn_t tsc_handler(int irq, void *dev_id)
{
    struct tsc_dev *multidata = dev_id;

    uint8_t rdbuf[29];
    int i, type, x, y, id;
    int offset, tplen;
    int ret;
    bool down;

    offset = 1;  /* 偏移 1,也就是 0X02+1=0x03,从 0X03 开始是触摸值 */
    tplen = 6;   /* 一个触摸点有 6 个寄存器来保存触摸值 */

    memset(rdbuf, 0, sizeof(rdbuf));

    /* 读取 FT5X06 触摸点坐标从 0X02 寄存器开始,连续读取 29 个寄存器 */
    ret = tsc_read_regs(multidata, FT5X06_TD_STATUS_REG, rdbuf, FT5X06_READLEN);
    if(ret)
        goto fail;
    /* 上报每一个触摸点坐标 */
    for(i = 0; i < MAX_SUPPORT_POINTS; i++)
    {
        uint8_t *buf = &rdbuf[i * tplen + offset];

        /* 以第一个触摸点为例,寄存器 TOUCH1_XH(地址 0X03),各位描述如下:
         * bit7:6 Event flag 0:按下 1:释放 2:接触 3:没有事件
         * bit5:4 保留
         * bit3:0 X 轴触摸点的 11~8 位。
         */
        type = buf[0] >> 6;   /* 获取触摸类型 */
        if(type == TOUCH_EVENT_RESERVED)
            continue;

        /* 我们所使用的触摸屏和 FT5X06 是反过来的 */
        x = ((buf[2] << 8) | buf[3]) & 0x0fff;
        y = ((buf[0] << 8) | buf[1]) & 0x0fff;

        /* 以第一个触摸点为例,寄存器 TOUCH1_YH(地址 0X05),各位描述如下:
         * bit7:4 Touch ID 触摸 ID,表示是哪个触摸点
         * bit3:0 Y 轴触摸点的 11~8 位。
         */
        id = (buf[2] >> 4) & 0x0f;
        down = type != TOUCH_EVENT_UP;

        input_mt_slot(multidata->input, id);
        input_mt_report_slot_state(multidata->input, MT_TOOL_FINGER, down);
        if(!down)
            continue;
        input_report_abs(multidata->input, ABS_MT_POSITION_X, x);
        input_report_abs(multidata->input, ABS_MT_POSITION_Y, y);
    }
    input_mt_report_pointer_emulation(multidata->input, true);
    input_sync(multidata->input);

fail:
    return IRQ_HANDLED;
}

static int tsc_irq(struct i2c_client *client, struct tsc_dev *dev)
{
    int ret = 0;
    
    /* 1,申请中断 GPIO */
    if(gpio_is_valid(dev->irq_pin))
    {
        ret = devm_gpio_request_one(&client->dev, dev->irq_pin, GPIOF_IN, "edt-ft5x06 irq");
        if(ret)
        {
            dev_err(&client->dev, "Failed to request GPIO %d, error %d\n", dev->irq_pin, ret);
            return ret;
        }
    }

    /* 2,申请中断,client->irq 就是 IO 中断, */
    ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, tsc_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, client->name, &tsc);
    if(ret)
    {
        dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
        return ret;
    }

    return 0;
}

static int tsc_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret = 0;

    tsc.client = client;
    /* 1,获取设备树中的中断和复位引脚 */
    tsc.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
    tsc.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);

    /* 2,复位 FT5x06 */
    ret = tsc_reset(client, &tsc);
    if(ret < 0) {
        goto fail;
    }
    /* 3,初始化中断 */
    ret = tsc_irq(client, &tsc);    
    if(ret < 0) {
        goto fail;
    }
    /* 4,初始化 FT5X06 */
	tsc_write_reg(&tsc, FT5x06_DEVICE_MODE_REG, 0);
    tsc_write_reg(&tsc, FT5426_IDG_MODE_REG, 1);
    /* 5,input 设备注册 */
    tsc.input = devm_input_allocate_device(&client->dev);
    if(!tsc.input) {
        ret = -ENOMEM;
        goto fail;
    }
    tsc.input->name  = client->name;
    tsc.input->id.bustype = BUS_I2C;
    tsc.input->dev.parent = &client->dev;

    __set_bit(EV_KEY, tsc.input->evbit);
    __set_bit(EV_ABS, tsc.input->evbit);
    __set_bit(BTN_TOUCH, tsc.input->keybit);

    input_set_abs_params(tsc.input, ABS_X, 0, 1024, 0, 0);
    input_set_abs_params(tsc.input, ABS_Y, 0, 600, 0, 0);
    input_set_abs_params(tsc.input, ABS_MT_POSITION_X, 0, 1024, 0, 0);
    input_set_abs_params(tsc.input, ABS_MT_POSITION_Y, 0, 600, 0, 0);
    ret = input_mt_init_slots(tsc.input, MAX_SUPPORT_POINTS, 0);
    if(ret){
        goto fail;
    }

    ret = input_register_device(tsc.input);
    if(ret){
        goto fail;
    }
    return 0;

fail:
    return ret;
}

static int tsc_remove(struct i2c_client *client)
{
    input_unregister_device(tsc.input);
    return 0;
}

static const struct i2c_device_id ft5x06_ts_id[] = {
    {"edt-ft5206", 0, },
    {"edt-ft5426", 0, },
    {/* */},
};

static const struct of_device_id ft5x06_of_match[] = {
    {.compatible = "edt,edt-ft5206",},
    {.compatible = "edt,edt-ft5426",},
    {/* */}
};

static struct i2c_driver tsc_driver = {
    .driver = {
        .owner = THIS_MODULE,
        .name = "edt_ft5x06",
        .of_match_table = of_match_ptr(ft5x06_of_match),
    },
    .id_table = ft5x06_ts_id,
    .probe = tsc_probe,
    .remove = tsc_remove,
};

static int tsc_init(void)
{
    int ret = 0;

    ret = i2c_add_driver(&tsc_driver);
    return ret;
}

static void tsc_exit(void)
{
    i2c_del_driver(&tsc_driver);
}

module_init(tsc_init);
module_exit(tsc_exit);
MODULE_LICENSE("GPL");


 

你可能感兴趣的:(I.Mx6Ull)