电容触摸寄存器
触点最多5个
触摸屏实现由 IIC驱动、中断驱动、 input子系统组成
触摸屏类型Type A和 Type B
Type A:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据 (此类型在实际使
用中非常少!
Type B:适用于有硬件追踪并能区分触摸点的触摸设备,此类型设备通过 slot更新某一个
触摸点的信息, FT5426就属于此类型,一般的多点电容触摸屏 IC都有此能力。
Type B和 Type A相比最大的区别
就是 Type B可以区分出触摸点, 因此可以减少发送到用户空间的数据。
Type A触摸点数据上报时序
ABS_MT_POSITION_X x[0] //第一个点x
ABS_MT_POSITION_Y y[0] //第一个点y
SYN_MT_REPORT //报告前一个 可以读取,后一个可以开始,使用 input_mt_sync触发此事件
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_MT_REPORT // input_event(dev, EV_SYN, SYN_MT_REPORT, 0);
SYN_REPORT
Type A设备,内核驱动 需要一次性将触摸屏上所有的触摸点信息全部上报
,每个触摸点的信息在本次上报事件流中的顺序不重要,因为事件的过滤和手指(触摸点 )跟踪是在内核空间处理的。
Type B触摸点数据上报时序
ABS_MT_SLOT 0 //表示要上报第一个ID,input_mt_solt
ABS_MT_TRACKING_ID 45 //自动分配ID,给ID赋值-1,表示删除此点
ABS_MT_POSITION_X x[0]
ABS_MT_POSITION_Y y[0]
ABS_MT_SLOT 1
ABS_MT_TRACKING_ID 46
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_REPORT
Type B使用 slot协议区分具体的触摸点, slot需要用到 ABS_MT_TRACKING_ID消息,这个 ID需要硬件提供,或者通过原始数据计算出来。
Type B设备驱动需要给每个识别出来的触摸点分配一个 slot,后面使用这个 slot来上报触摸点信息。可以通过 slot的 ABS_MT_TRACKING_ID来新增、替换或删除触摸点。一个非负数的 ID表示一个有效的触摸点, -1这个 ID表示未使用 slot。一个以前不存在的 ID表示这是一个新加的触摸点,一个 ID如果再也不存在了就表示删除了。
有些设备识别或追踪的 触摸点信息要比他上报的多,这些设备驱动应该给硬件上报的每个触摸点分配一个 Type B的 slot。一旦检测到某一个 slot关联的触摸点 ID发生了变化,驱动就应该改变这个 slot的 ABS_MT_TRACKING_ID,使这个 slot失效。如果硬件设备追踪到了比他正在上报的还要多的触摸点,那么驱动程序应该发送 BTN_TOOL_*TAP消息
多点电容驱动框架
- 接口采用I2C传输数据
- 使用中断采集触摸点数据
- 需要从触摸屏寄存器读取数据,使用input子系统
- typeB类型读取数据,MT协议上报数据(MT属于input子系统)
设备树填写
FT5426触摸芯片用到了 4个 IO,一个复位 IO、一个中断 IO、 I2C2的 SCL和 SDA
中断
&iomuxc下
/*touch */
pinctrl_tsc: tscgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0xF080 /* TSC_INT */
>;
};
复位
&iomuxc_snvs下
/* MT RET*/
pinctrl_tsc_reset: tsc_reset{
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0
>;
};
I2C SCL,SDA
pinctrl_i2c2: i2c2grp {
fsl,pins = <
MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
>;
};
I2C下的设备FT5426
位于 &i2c2{ }中
&i2c2 {
clock_frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c2>;
status = "okay";
/* touch FT5426 */
ft5426: ft5426@38 {
compatible = "edt,edt-ft5426";
reg = <0x38>; /* 器件地址 */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_tsc /* 中断节点 */
&pinctrl_tsc_reset >; /* 复位节点 */
interrupt-parent = <&gpio1>; /* GPIO组为 GPIO1 */
interrupts = <9 0>; /* GPIO1组的 IOI09,0为触发方式(上升沿,下降沿等,没查到 */
reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
};
.........
.........
};
驱动编写
1. I2C驱动编写
#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>
/* ft5426 设备结构体*/
struct ft5426_dev
{
void *private_data;
};
struct ft5426_dev ft5426;
static int ft5x06_read_regs(struct ft5x06_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 ft5x06_write_regs(struct ft5x06_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); /* 将要写入的数据拷贝到数组b里面 */
msg.addr = client->addr; /* ft5x06地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 1; /* 要写入的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
static int ft5x06_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
printk("ft5426_probe!\r\n");
return 0;
}
static int ft5x06_ts_remove(struct i2c_client *client)
{
return 0;
}
/*
* 传统驱动匹配表
*/
static const struct i2c_device_id ft5x06_ts_id[] = {
{ "edt-ft5206", 0, },
{ "edt-ft5426", 0, },
{ /* sentinel */ }
};
/*
* 设备树匹配表
*/
static const struct of_device_id ft5x06_of_match[] = {
{ .compatible = "edt,edt-ft5206", },
{ .compatible = "edt,edt-ft5426", },
{ /* sentinel */ }
};
/* i2c驱动结构体 */
static struct i2c_driver ft5x06_ts_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "edt_ft5x06",
.of_match_table = of_match_ptr(ft5x06_of_match),
},
.id_table = ft5x06_ts_id,
.probe = ft5x06_ts_probe,
.remove = ft5x06_ts_remove,
};
/*设备入口函数*/
static int __init ft5x06_init(void)
{
int ret=0;
ret=i2c_add_driver(&ft5x06_ts_driver);
return ret;
}
/*设备出口函数*/
static void __exit ft5x06_exit(void)
{
i2c_del_driver(&ft5x06_ts_driver);
}
module_init(ft5x06_init);
module_exit(ft5x06_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LIU");
2. 中断和复位
- 设备结构体
struct ft5426_dev
{
struct device_node *nd; /* 设备节点 */
int irq_pin,reset_pin; /* 中断和复位IO */
int irqnum; /* 中断号 */
void *private_data; /* 私有数据 */
struct i2c_client *client; /* I2C客户端 */
};
- probe 函数
复位初始化
/* 初始化复位*/
static int ft5x06_ts_reset(struct i2c_client *client, struct ft5x06_dev *dev)
{
int ret = 0;
if (gpio_is_valid(dev->reset_pin)) { /* 检查IO是否有效 */
/* 申请复位IO,并且默认输出低电平 */
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 irqreturn_t ft5x06_handler(int irq, void *dev_id)
{
printk("ft5x06_handler\r\n");
return IRQ_HANDLED;
}
中断初始化
static int ft5x06_ts_irq(struct i2c_client *client, struct ft5x06_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,
ft5x06_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
client->name, &ft5x06);
if (ret) {
dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
return ret;
}
return 0;
}