DeviceDriver (14): Multi-touch (protocolo MT, subsistema de entrada)

Referencia del marco del subsistema de entrada:

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

Referencia de unidad resistiva multitáctil:

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

Uno: Puntos de conocimiento de la pantalla táctil capacitiva

1. La pantalla táctil capacitiva es una interfaz I2C y requiere un IC táctil, por lo que el marco es un marco de controlador de dispositivo I2C.

2. La información táctil se envía al kernel de Linux a través del pin de interrupción (INT), por lo que se necesita el marco del controlador de interrupciones de Linux y el informe de coordenadas se realiza en la función de servicio de interrupción.

3. La información de coordenadas de la pantalla táctil y la información de presión y elevación de la pantalla pertenecen al subsistema de entrada y Linux, por lo que el subsistema de entrada debe utilizarse para informar la información de coordenadas de la pantalla táctil al kernel de Linux.

Dos: protocolo multitáctil (MT)

1. El protocolo MT se divide en dos tipos, TypeA y TypeB

Tipo A: adecuado para puntos de contacto que no se pueden distinguir ni rastrear. Este tipo de dispositivo informa datos sin procesar (menos utilizado)

Tipo B: Adecuado para dispositivos táctiles que tienen seguimiento de hardware y pueden distinguir puntos táctiles. Este tipo de dispositivo actualiza la información de un determinado punto táctil a través de una ranura.

La información del punto de contacto se envía al kernel de Linux a través de una serie de eventos ABS_MT. Solo el evento ABS_MT se utiliza para multitáctiles:

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

       Entre ellos, los ABS_MT_POSITION_X y ABS_MT_POSITION_Y más utilizados se utilizan para informar la información de coordenadas (X, Y) del punto de contacto, y ABS_MT_SLOT se utiliza para informar el ID del punto de contacto. Para los dispositivos de tipo B, el evento ABS_MT_TRACKING_ID debe utilizarse para distinguir los puntos de contacto.

2. Informe de datos

TipoA: vacío input_mt_sync (struct input_dev * dev)

Esta función activará el evento SYN_MT_REPORT, que notificará al receptor para obtener los datos táctiles actuales y se preparará para aceptar los datos del siguiente punto táctil.

TipoB: vacío input_mt_slot (struct input_dev * dev, int slot)

El segundo intervalo de parámetro de esta función se utiliza para especificar la información del punto de contacto reportado actualmente y activar el evento ABS_MT_SLOT, que le dirá al receptor qué datos de punto de contacto (intervalo) se están actualizando actualmente.

       Ambos tipos de dispositivos deben eventualmente llamar a la función input_sync () para marcar la finalización de la transmisión de información multitáctil, decirle al receptor que procese la información acumulada antes y estar listo para la siguiente recepción. El controlador de dispositivo TypeB debe asignar una ranura a cada punto de contacto reconocido y luego usar esta ranura para informar la información del punto de contacto. Puede agregar, reemplazar o eliminar puntos de contacto a través del ABS_MT_TRACKING_ID de la ranura. El ID de -1 significa la ranura no se utiliza. Una ID que no existe indica un punto de contacto recién agregado, y una ID que no existe indica que se ha eliminado. Una vez que detecta que el ID de punto de contacto asociado con una ranura ha cambiado, el controlador debe cambiar el ABS_TM_TRACKING_ID de la ranura para invalidar la ranura. Si el dispositivo de hardware rastrea más puntos de contacto de los que informa, entonces el controlador debe enviar el BTN_TOOL_ * TAP message, y llame a la función input_mt_report_pointer_emulation (), y establezca el segundo parámetro use_count de esta función en falso.

3. Secuencia de informes

Escribe un:

(1) Los datos de coordenadas se informan a través de ABS_MT_POSITION_X o ABS_MT_POSITION_Y, realizados a través de input_report_abs;

(2) Informar el evento SYN_MT_REPORT a través de input_my_sync;

(3) El evento SYN_REPORT se informa a través de input_sync.

La función input_sync se llama cada vez que se informa una ronda de información de punto de contacto, es decir, se envía un 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);
}

TipoB:

(1) Informe el evento ABS_MT_SLOT, que es la ranura correspondiente al punto de contacto. Utilice la función input_mt_slot para informar la ranura del punto de contacto actual antes de informar las coordenadas de un punto de contacto cada vez, que en realidad es el ID del punto de contacto y debe ser proporcionado por el toque IC.

(2) De acuerdo con los requisitos de TypeB, cada SLOT debe estar asociado con un ABS_MT_TRACKING_ID, y la adición, reemplazo y eliminación de puntos de contacto se puede completar modificando el ABS_MT_TRACKING_ID asociado con el SLOT. La función específica utilizada es input_mt_report_slot_state. Si está agregando un nuevo punto de contacto, el tercer parámetro activo de esta función debe establecerse en verdadero, y Linux asignará automáticamente un valor ABS_MT_TRACKING_ID.

(3) Para informar las coordenadas X e Y del punto de contacto, use la función input_report_abs para completar.

(4) Después de cargar todas las coordenadas del punto de contacto, puede enviar el evento SYN_REPORT, usando la función 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);
}

Tres: función API multitáctil

1. input_mt_init_slots: ranuras de entrada utilizadas para inicializar MT.

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

2. input_mt_slot: se utiliza para generar el evento ABS_MT_SLOT de tipo TypeB, que le indica al kernel qué datos de coordenadas del punto de contacto se informan actualmente.

void input_mt_slot(struct input_dev *dev, int slot)

3. input_mt_report_slot_state: Usado para el tipo TypeB para generar eventos ABS_MT_TRACKING_ID y ABS_MT_TOOL_TYPE.

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

4. input_report_abs: usado para TypeA y TypeB para reportar información de coordenadas de puntos de contacto.

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

5. input_mt_report_pointer_emulation: si el número de puntos de contacto rastreados es mayor que el número reportado actualmente, el conductor usa el evento BTN_TOOL_TAP para notificar al espacio de usuario el número total de puntos de contacto actualmente rastreados, y luego llama a esta función y establece el parámetro ues_count a falso. De lo contrario, es cierto, lo que indica el número actual de puntos de contacto.

void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)

Cuatro: ejemplo de unidad multitáctil

1. Modifique el archivo del árbol de dispositivos

(1) Agregue E / S del chip táctil FT5426, un total de 4 E / S (reinicio, interrupción, I2C2 SCL, SDA)

Reset IO e interrupt IO son GPIO ordinarios:

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) Agregar nodo 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. Conducir

#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");


 

Supongo que te gusta

Origin blog.csdn.net/qq_34968572/article/details/104988890
Recomendado
Clasificación