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子系统。
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);
}
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");