一.设备树
&i2c1 {status = "okay";myft5x06: my-ft5x06@38 {compatible = "my-ft5x06";reg = <0x38>;reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;interrupt-parent = <&gpio3>;interrupts-gpio = <&gpio3 RK_PA5 GPIO_ACTIVE_LOW>;interrupts = <RK_PA5 IRQ_TYPE_LEVEL_LOW>;pinctrl-names = "default";pinctrl-0 = <&myft5x06_pins>;};
};
reset-gpios = ;: 定义了设备的复位引脚,连接到 GPIO0 的 RK_PB6 引脚,电平为低电平有效。
interrupt-parent = ;: 指定中断的父节点为 GPIO3。
interrupts-gpio = ;: 定义了设备的中断引脚,连 接到 GPIO3 的 RK_PA5 引脚,电平为低电平触发。
interrupts = ;: 进一步描述了中断的触发方式,为电 平低电平触发。
pinctrl-names = "default"; pinctrl-0 = ;: 指定了设备使用的默认针脚配 置。
这里指定的 pinctrl 节点名为 myft5x06_pins,所以还需要对 pinctrl 节点进行追加,追加内 容如下所示:
&pinctrl {myft5x06 {myft5x06_pins: myft5x06-pins {rockchip,pins =<0 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>, <0 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>;};};
};
二.FT5X06 驱动
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/input.h>
#include <linux/workqueue.h>// 声明工作队列要用到的函数
void ft5x06_func(struct work_struct *work);// 声明一个工作结构体
static DECLARE_WORK(ft5x06_work, ft5x06_func);// 定义 ft5x06 设备的 GPIO 描述符
// 用于保存 reset 和 interrupt 引脚的 GPIO 描述符
struct gpio_desc *reset_gpio, *irq_gpio;// 保存 ft5x06 设备的 i2c 客户端对象指针
struct i2c_client *ft5x06_client;// 保存 ft5x06 输入设备的dev指针
struct input_dev *ft5x06_input_dev;// 读取 ft5x06 设备寄存器的函数
int ft5x06_read_reg(u8 reg_addr)
{u8 data;// 定义两个 i2c_msg 结构体,分别表示写操作和读操作struct i2c_msg msgs[2] = {[0] = {.addr = ft5x06_client->addr, // 设备地址.flags = 0, // 写操作.len = sizeof(reg_addr),.buf = ®_addr, // 写入要读取的寄存器地址},[1] = {.addr = ft5x06_client->addr,.flags = I2C_M_RD, // 读操作.len = sizeof(data),.buf = &data, // 读取到的数据存储位置},};// 使用 i2c_transfer 函数进行 i2c 总线读取操作// 如果读取失败,返回 -EIO 错误码if (i2c_transfer(ft5x06_client->adapter, msgs, ARRAY_SIZE(msgs)) != ARRAY_SIZE(msgs)){return -EIO;}return data; // 返回读取到的寄存器值
}// 向 ft5x06 设备写入寄存器的函数
void ft5x06_write_reg(u8 reg_addr, u8 *data, u16 len)
{u8 buff[256];struct i2c_msg msgs[] = {[0] = {.addr = ft5x06_client->addr, // 设备地址.flags = 0, // 写操作.len = len + 1, // 数据长度 + 寄存器地址长度.buf = buff, // 数据缓冲区},};buff[0] = reg_addr; // 写入寄存器地址memcpy(&buff[1], data, len); // 写入数据// 使用 i2c_transfer 函数进行 i2c 总线写入操作// 如果写入失败,直接返回if (i2c_transfer(ft5x06_client->adapter, msgs, ARRAY_SIZE(msgs)) != ARRAY_SIZE(msgs)) {return;}
}// 工作函数
void ft5x06_func(struct work_struct *work)
{int TOUCH1_XH, TOUCH1_XL, x;int TOUCH1_YH, TOUCH1_YL, y;int TD_STATUS;// 从寄存器中读取触摸坐标数据TOUCH1_XH = ft5x06_read_reg(0x03);TOUCH1_XL = ft5x06_read_reg(0x04);x = ((TOUCH1_XH << 8) | TOUCH1_XL) & 0xfff;TOUCH1_YH = ft5x06_read_reg(0x05);TOUCH1_YL = ft5x06_read_reg(0x06);y = ((TOUCH1_YH << 8) | TOUCH1_YL) & 0xfff;// 读取触摸状态寄存器TD_STATUS = ft5x06_read_reg(0x02);TD_STATUS = TD_STATUS & 0xf;// 根据触摸状态更新输入设备if (TD_STATUS == 0) {// 触摸释放input_report_key(ft5x06_input_dev, BTN_TOUCH, 0);input_sync(ft5x06_input_dev);} else {// 触摸按下input_report_key(ft5x06_input_dev, BTN_TOUCH, 1);input_report_abs(ft5x06_input_dev, ABS_X, x);input_report_abs(ft5x06_input_dev, ABS_Y, y);input_sync(ft5x06_input_dev);}
}// ft5x06 中断处理函数
irqreturn_t ft5x06_handler(int irq, void *args)
{// 将 ft5x06_work 工作结构体提交到工作队列中schedule_work(&ft5x06_work);// 表示中断已经被处理return IRQ_RETVAL(IRQ_HANDLED);
}// ft5x06 设备的初始化函数
int ft5x06_probe(struct i2c_client *client, const struct i2c_device_id *id)
{int ret;printk("This is ft5x06 probe\n");ft5x06_client = client; // 保存 i2c 客户端对象指针// 获取 reset GPIO 描述符// gpiod_get_optional 函数尝试获取设备树中名为 "reset" 的 GPIO 描述符,// 如果获取失败则返回 NULLreset_gpio = gpiod_get_optional(&client->dev, "reset", 0);if (!reset_gpio) {printk("gpiod_get_optional reset gpio is error\n");return -1;}// 获取 irq GPIO 描述符// gpiod_get_optional 函数尝试获取设备树中名为 "interrupts" 的 GPIO 描述符,// 如果获取失败则返回 NULLirq_gpio = gpiod_get_optional(&client->dev, "interrupts", 0);if (!irq_gpio) {printk("gpiod_get_optional irq gpio is error\n");return -1;}// 设置 reset GPIO 为输出,并拉低 5ms 后拉高// 这是一个复位操作,用于初始化 ft5x06 设备gpiod_direction_output(reset_gpio, 0);msleep(5);gpiod_direction_output(reset_gpio, 1);// 请求中断,设置为下降沿触发,单次触发// 当 ft5x06 设备产生中断时,会调用 ft5x06_handler 函数处理ret = request_irq(client->irq, ft5x06_handler,IRQ_TYPE_EDGE_FALLING | IRQF_ONESHOT, "ft5x06 irq", NULL);if (ret < 0) {printk("request irq is error\n");return -2;}// 分配一个 input 设备ft5x06_input_dev = input_allocate_device();if (ft5x06_input_dev == NULL) {printk("input allocate device is error\n");return -2;}// 设置 input 设备的名称ft5x06_input_dev->name = "ft5x06_dev";// 设置 input 设备支持的事件类型和事件set_bit(EV_KEY, ft5x06_input_dev->evbit);set_bit(BTN_TOUCH, ft5x06_input_dev->keybit);set_bit(EV_ABS, ft5x06_input_dev->evbit);set_bit(ABS_X, ft5x06_input_dev->absbit);set_bit(ABS_Y, ft5x06_input_dev->absbit);// 设置 input 设备的绝对坐标范围input_set_abs_params(ft5x06_input_dev, ABS_X, 0, 800, 0, 0);input_set_abs_params(ft5x06_input_dev, ABS_Y, 0, 1280, 0, 0);// 注册 input 设备ret = input_register_device(ft5x06_input_dev);if (ret < 0) {printk("input register device is error\n");goto error_0;}return 0;error_0:input_free_device(ft5x06_input_dev);free_irq(client->irq, NULL);gpiod_put(reset_gpio);gpiod_put(irq_gpio);return ret;
}// ft5x06 设备的移除函数
// 参数 client 是 i2c 客户端对象指针
int ft5x06_remove(struct i2c_client *client)
{// 释放中断free_irq(client->irq, NULL);// 释放 GPIO 资源gpiod_put(reset_gpio);gpiod_put(irq_gpio);return 0;
}// 定义 i2c_device_id 结构体数组,用于标识 ft5x06 设备
static const struct i2c_device_id ft5x06_id[] = {{ "my-ft5x06", 0 },{ }
};// 定义 i2c_driver 结构体,描述 ft5x06 设备驱动
static struct i2c_driver ft5x06_driver = {.driver = {.name = "my-ft5x06",.owner = THIS_MODULE,},.probe = ft5x06_probe,.remove = ft5x06_remove,.id_table = ft5x06_id,
};// 驱动初始化函数
static int __init ft5x06_driver_init(void)
{int ret;// 注册 I2C 设备驱动ret = i2c_add_driver(&ft5x06_driver);if (ret < 0) {printk("i2c_add_driver is error\n");return ret;}return 0;
}// 驱动退出函数
static void ft5x06_driver_exit(void)
{/* 释放中断 */free_irq(ft5x06_client->irq, NULL);/* 注销输入设备 */input_unregister_device(ft5x06_input_dev);/* 释放 GPIO */gpiod_put(reset_gpio);gpiod_put(irq_gpio);/* 删除 I2C 驱动 */i2c_del_driver(&ft5x06_driver);
}module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);
MODULE_LICENSE("GPL");
三.报点分析
点击屏幕的左上角:然后hexdump /dev/input/event4
首先对得到的数据进行分组,分组之后的数据如下所示:
c282 6666 0000 0000 36d3 000c 0000 0000 0001 014a 0001 0000
c282 6666 0000 0000 36d3 000c 0000 0000 0003 0000 0001 0000
c282 6666 0000 0000 36d3 000c 0000 0000 0003 0001 0005 0000
c282 6666 0000 0000 36d3 000c 0000 0000 0000 0000 0000 0000
c282 6666 0000 0000 ca15 000d 0000 0000 0001 014a 0000 0000
c282 6666 0000 0000 ca15 000d 0000 0000 0000 0000 0000 0000
这 6 组数据具体代表的含义如下所示:
四.触摸测试程序
#include <fcntl.h>
#include <linux/input.h>
#include <unistd.h>
#include <stdio.h>int main(int argc, char *argv[]) {int fd;struct input_event event;// 打开输入设备事件流fd = open("/dev/input/event4", O_RDONLY);if (fd < 0) {perror("open");return -1;}while (1) {// 读取一个事件if (read(fd, &event, sizeof(event)) != sizeof(event)) {perror("read");close(fd);return -1;}// 判断事件类型是否为绝对坐标事件if (event.type == EV_ABS) {// 判断事件编码是否为X轴坐标if (event.code == ABS_X) {printf("x = %d\n", event.value);}// 判断事件编码是否为Y轴坐标else if (event.code == ABS_Y) {printf("y = %d\n", event.value);}}}return 0;
}