DeviceDriver (14): Multi-touch (MT protocol, Input subsystem)

Input subsystem framework reference:

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

Resistive multi-touch drive reference:

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

One: Capacitive touch screen knowledge points

1. The capacitive touch screen is an I2C interface and requires a touch IC, so the frame is an I2C device driver frame.

2. The touch information is reported to the Linux kernel through the interrupt pin (INT), so the Linux interrupt driver framework is required, and the coordinate reporting is completed in the interrupt service function.

3. The coordinate information of the touch screen, and the screen press and lift information belong to the Linux and input subsystems. Therefore, the input subsystem must be used to report the touch screen coordinate information to the Linux kernel.

Two: Multi-touch (MT) protocol

1. The MT protocol is divided into two types, TypeA and TypeB

TypeA: Suitable for touch points that cannot be distinguished or tracked. This type of device reports raw data (less used)

TypeB: Suitable for touch devices that have hardware tracking and can distinguish touch points. This type of device updates the information of a certain touch point through a slot.

The touch point information is reported to the Linux kernel through a series of ABS_MT events. Only the ABS_MT event is used for multi-touch:

#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 */

       Among them, the most commonly used ABS_MT_POSITION_X and ABS_MT_POSITION_Y are used to report the (X, Y) coordinate information of the touch point, and ABS_MT_SLOT is used to report the touch point ID. For TypeB devices, the ABS_MT_TRACKING_ID event needs to be used to distinguish touch points.

2. Report data

TypeA:void input_mt_sync(struct input_dev *dev)

This function will trigger the SYN_MT_REPORT event, which will notify the receiver to obtain the current touch data and prepare to accept the next touch point data.

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

The second parameter slot of this function is used to specify the currently reported touch point information and trigger the ABS_MT_SLOT event, which will tell the receiver which touch point (slot) data is currently being updated.

       Both types of devices must eventually call the input_sync() function to mark the completion of the multi-touch information transmission, tell the receiver to process the accumulated information before, and be ready for the next reception. The TypeB device driver needs to assign a slot to each recognized touch point, and then use this slot to report touch point information. You can add, replace or delete touch points through the ABS_MT_TRACKING_ID of the slot. The ID of -1 means the slot is not used. An ID that does not exist indicates a newly added touch point, and an ID that does not exist indicates that it has been deleted. Once it detects that the touch point ID associated with a slot has changed, the driver should change the slot’s ABS_TM_TRACKING_ID to make the slot invalid. If the hardware device traces more touch points than it is reporting, then the driver should Send the BTN_TOOL_*TAP message, and call the input_mt_report_pointer_emulation() function, and set the second parameter use_count of this function to false.

3. Reporting sequence

TypeA:

(1) Coordinate data is reported through ABS_MT_POSITION_X or ABS_MT_POSITION_Y, which is realized through input_report_abs;

(2) Report the SYN_MT_REPORT event through input_my_sync;

(3) The SYN_REPORT event is reported through input_sync.

The input_sync function is called once after each round of touch point information is reported, that is, a SYN_REPORT is sent.

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) Report the ABS_MT_SLOT event, which is the slot corresponding to the touch point. Use the input_mt_slot function to report the current touch point slot before reporting the coordinates of a touch point each time, which is actually the touch point ID and needs to be provided by the touch IC.

(2) According to TypeB requirements, each SLOT must be associated with an ABS_MT_TRACKING_ID, and the addition, replacement and deletion of touch points can be completed by modifying the ABS_MT_TRACKING_ID associated with the SLOT. The specific function used is input_mt_report_slot_state. If you are adding a new touch point, the third parameter active of this function should be set to true, and linux will automatically assign an ABS_MT_TRACKING_ID value.

(3) To report the X and Y coordinates of the touch point, use the function input_report_abs to complete.

(4) After uploading all the touch point coordinates, you can send the SYN_REPORT event, using the input_sync function

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);
}

Three: Multi-touch API function

1. input_mt_init_slots: input slots used to initialize MT.

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

2. input_mt_slot: used for TypeB type to generate ABS_MT_SLOT event to tell the kernel which touch point coordinate data is currently reported.

void input_mt_slot(struct input_dev *dev, int slot)

3. input_mt_report_slot_state: used for TypeB type to generate ABS_MT_TRACKING_ID and ABS_MT_TOOL_TYPE events.

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

4. input_report_abs: used for TypeA and TypeB to report touch point coordinate information.

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

5. input_mt_report_pointer_emulation: If the number of touch points tracked is more than the number currently reported, the driver uses the BTN_TOOL_TAP event to notify the user space of the total number of touch points currently tracked, and then calls this function and sets the ues_count parameter to false. Otherwise, it is true, indicating the current number of touch points.

void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)

Four: Multi-touch drive example

1. Modify the device tree file

(1) Add FT5426 touch chip IO, a total of 4 IOs (reset, interrupt, I2C2 SCL, SDA)

Reset IO and interrupt IO are ordinary 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) Add FT5426 node

&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. Drive

#include <linux/module.h>
#include <linux/ratelimit.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/input/mt.h>
#include <linux/input/touchscreen.h>
#include <linux/input/edt-ft5x06.h>
#include <linux/i2c.h>

#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 = &reg;					/* 读取的首地址 */
	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");


 

Guess you like

Origin blog.csdn.net/qq_34968572/article/details/104988890