1.电容触摸屏简介
- 电容屏只需要手指轻触即可,而电阻屏是需要手指
给予一定的压力才有反应,而且电容屏不需要校准。 - 如果要做人机交互设备的开发,多点电容触摸屏基本是不可能绕过去的。
2. 驱动器件
- 正点原子ATK-7016 这款屏幕其实是由 TFT LCD+触摸屏组合起来的。底下是 LCD 面板,上面是触摸面板,将两个封装到一起就成了带有触摸屏的 LCD 屏幕
- 电容触摸屏也是需要一个驱动 IC的,驱动 IC 一般会提供一个 I2C 接口给主控制器主控制器可以通过 I2C 接口来读取驱动 IC里面的触摸坐标数据
- ATK-7016、ATK-7084这两款屏幕使用的触摸控制 IC 是 FT5426。
1.FT5426 这款驱动 IC 采用 15*28 的驱动结构,也就是 15 个感应通道,28 个驱动通道, 2.最多支持5点电容触摸。 - ATK-7016的电容触摸屏部分有4个 IO用于连接主控制器: SCL、 SDA、RST 和 INT。
1.SCL 和 SDA 是 I2C 引脚, 2.RST 是复位引脚,INT 是中断引脚。 一般通过 INT 引脚来通知主控制器有触摸点按下,然后在INT 中断服务函数中读取触摸数据。也可以不使用中断功能,采用轮询的方式不断查询是否有触摸点按下。 - 和所有的 I2C 器件一样,FT5426 也是通过读写寄存器来完成初始化和触摸坐标数据读取的主要工作就是读写FT5426 的寄存器
- FT5426 的 I2C 设备地址为 0X38,FT5426 的寄存器有很多,本章我们只用到了其中的一部分:
- 触摸屏与单片机接触引脚如下:
1.触摸屏连接着I.MX6U的I2C2, 2.INT引脚连接着I.MX6U的GPIO1_IO9, 3.RST 引脚连接着 I.MX6U 的 SNVS_TAMPER9。
裸机驱动
- 同样用的是和前面I2C驱动的一样的是,主机驱动和设备驱动
- 主机驱动就是配置SOC的代码
主机驱动bsp_i2c.h
#ifndef _BSP_I2C_H
#define _BSP_I2C_H
#include "imx6ul.h"
#define I2C_STATUS_OK (0)
#define I2C_STATUS_BUSY (1)
#define I2C_STATUS_IDLE (2)
#define I2C_STATUS_NAK (3)
#define I2C_STATUS_ARBITRATIONLOST (4)
#define I2C_STATUS_TIMEOUT (5)
#define I2C_STATUS_ADDRNAK (6)
enum i2c_direction
{
kI2C_Write = 0x0,
kI2C_Read = 0x1,
} ;
struct i2c_transfer
{
unsigned char slaveAddress;
enum i2c_direction direction;
unsigned int subaddress;
unsigned char subaddressSize;
unsigned char *volatile data;
volatile unsigned int dataSize;
};
void i2c_init(I2C_Type *base);
unsigned char i2c_master_start(I2C_Type *base, unsigned char address, enum i2c_direction direction);
unsigned char i2c_master_repeated_start(I2C_Type *base, unsigned char address, enum i2c_direction direction);
unsigned char i2c_check_and_clear_error(I2C_Type *base, unsigned int status);
unsigned char i2c_master_stop(I2C_Type *base);
void i2c_master_write(I2C_Type *base, const unsigned char *buf, unsigned int size);
void i2c_master_read(I2C_Type *base, unsigned char *buf, unsigned int size);
unsigned char i2c_master_transfer(I2C_Type *base, struct i2c_transfer *xfer);
#endif
主机驱动bsp_i2c.c
#include "bsp_i2c.h"
#include "bsp_delay.h"
#include "stdio.h"
void i2c_init(I2C_Type *base)
{
base->I2CR &= ~(1 << 7);
base->IFDR = 0X15 << 0;
base->I2CR |= (1<<7);
}
unsigned char i2c_master_repeated_start(I2C_Type *base, unsigned char address, enum i2c_direction direction)
{
if(base->I2SR & (1 << 5) && (((base->I2CR) & (1 << 5)) == 0))
return 1;
base->I2CR |= (1 << 4) | (1 << 2);
base->I2DR = ((unsigned int)address << 1) | ((direction == kI2C_Read)? 1 : 0);
return 0;
}
unsigned char i2c_master_start(I2C_Type *base, unsigned char address, enum i2c_direction direction)
{
if(base->I2SR & (1 << 5))
return 1;
base->I2CR |= (1 << 5) | (1 << 4);
base->I2DR = ((unsigned int)address << 1) | ((direction == kI2C_Read)? 1 : 0);
return 0;
}
unsigned char i2c_check_and_clear_error(I2C_Type *base, unsigned int status)
{
if(status & (1<<4))
{
base->I2SR &= ~(1<<4);
base->I2CR &= ~(1 << 7);
base->I2CR |= (1 << 7);
return I2C_STATUS_ARBITRATIONLOST;
}
else if(status & (1 << 0))
{
return I2C_STATUS_NAK;
}
return I2C_STATUS_OK;
}
unsigned char i2c_master_stop(I2C_Type *base)
{
unsigned short timeout = 0xffff;
base->I2CR &= ~((1 << 5) | (1 << 4) | (1 << 3));
while((base->I2SR & (1 << 5)))
{
timeout--;
if(timeout == 0)
return I2C_STATUS_TIMEOUT;
}
return I2C_STATUS_OK;
}
void i2c_master_write(I2C_Type *base, const unsigned char *buf, unsigned int size)
{
while(!(base->I2SR & (1 << 7)));
base->I2SR &= ~(1 << 1);
base->I2CR |= 1 << 4;
while(size--)
{
base->I2DR = *buf++;
while(!(base->I2SR & (1 << 1)));
base->I2SR &= ~(1 << 1);
if(i2c_check_and_clear_error(base, base->I2SR))
break;
}
base->I2SR &= ~(1 << 1);
i2c_master_stop(base);
}
void i2c_master_read(I2C_Type *base, unsigned char *buf, unsigned int size)
{
volatile uint8_t dummy = 0;
dummy++;
while(!(base->I2SR & (1 << 7)));
base->I2SR &= ~(1 << 1);
base->I2CR &= ~((1 << 4) | (1 << 3));
if(size == 1)
base->I2CR |= (1 << 3);
dummy = base->I2DR;
while(size--)
{
while(!(base->I2SR & (1 << 1)));
base->I2SR &= ~(1 << 1);
if(size == 0)
{
i2c_master_stop(base);
}
if(size == 1)
{
base->I2CR |= (1 << 3);
}
*buf++ = base->I2DR;
}
}
unsigned char i2c_master_transfer(I2C_Type *base, struct i2c_transfer *xfer)
{
unsigned char ret = 0;
enum i2c_direction direction = xfer->direction;
base->I2SR &= ~((1 << 1) | (1 << 4));
while(!((base->I2SR >> 7) & 0X1)){};
if ((xfer->subaddressSize > 0) && (xfer->direction == kI2C_Read))
{
direction = kI2C_Write;
}
ret = i2c_master_start(base, xfer->slaveAddress, direction);
if(ret)
{
return ret;
}
while(!(base->I2SR & (1 << 1))){};
ret = i2c_check_and_clear_error(base, base->I2SR);
if(ret)
{
i2c_master_stop(base);
return ret;
}
if(xfer->subaddressSize)
{
do
{
base->I2SR &= ~(1 << 1);
xfer->subaddressSize--;
base->I2DR = ((xfer->subaddress) >> (8 * xfer->subaddressSize));
while(!(base->I2SR & (1 << 1)));
ret = i2c_check_and_clear_error(base, base->I2SR);
if(ret)
{
i2c_master_stop(base);
return ret;
}
} while ((xfer->subaddressSize > 0) && (ret == I2C_STATUS_OK));
if(xfer->direction == kI2C_Read)
{
base->I2SR &= ~(1 << 1);
i2c_master_repeated_start(base, xfer->slaveAddress, kI2C_Read);
while(!(base->I2SR & (1 << 1))){};
ret = i2c_check_and_clear_error(base, base->I2SR);
if(ret)
{
ret = I2C_STATUS_ADDRNAK;
i2c_master_stop(base);
return ret;
}
}
}
if ((xfer->direction == kI2C_Write) && (xfer->dataSize > 0))
{
i2c_master_write(base, xfer->data, xfer->dataSize);
}
if ((xfer->direction == kI2C_Read) && (xfer->dataSize > 0))
{
i2c_master_read(base, xfer->data, xfer->dataSize);
}
return 0;
}
这就是驱动的分离和分层思想,主机驱动具有通用性,对于我们只要篇编写设备驱动就行,这里主要就是传感器FT5426的编写
设备驱动bsp_ft5xx5.h
此文件里面描写都是设备的信息,和寄存器打交道的读写函数
#ifndef _FT5XX6_H
#define _FT5XX6_H
#include "imx6ul.h"
#include "bsp_gpio.h"
#define FT5426_ADDR 0X38
#define FT5426_DEVICE_MODE 0X00
#define FT5426_IDGLIB_VERSION 0XA1
#define FT5426_IDG_MODE 0XA4
#define FT5426_TD_STATUS 0X02
#define FT5426_TOUCH1_XH 0X03
#define FT5426_XYCOORDREG_NUM 30
#define FT5426_INIT_FINISHED 1
#define FT5426_INIT_NOTFINISHED 0
#define FT5426_TOUCH_EVENT_DOWN 0x00
#define FT5426_TOUCH_EVENT_UP 0x01
#define FT5426_TOUCH_EVENT_ON 0x02
#define FT5426_TOUCH_EVENT_RESERVED 0x03
struct ft5426_dev_struc
{
unsigned char initfalg;
unsigned char intflag;
unsigned char point_num;
unsigned short x[5];
unsigned short y[5];
};
extern struct ft5426_dev_struc ft5426_dev;
void ft5426_init(void);
void gpio1_io9_irqhandler(void);
unsigned char ft5426_write_byte(unsigned char addr,unsigned char reg, unsigned char data);
unsigned char ft5426_read_byte(unsigned char addr,unsigned char reg);
void ft5426_read_len(unsigned char addr,unsigned char reg,unsigned char len,unsigned char *buf);
void ft5426_read_tpnum(void);
void ft5426_read_tpcoord(void);
#endif
设备驱动bsp_ft5xx5.c
这里用的是I2C2
#include "bsp_ft5xx6.h"
#include "bsp_i2c.h"
#include "bsp_int.h"
#include "bsp_delay.h"
#include "stdio.h"
struct ft5426_dev_struc ft5426_dev;
void ft5426_init(void)
{
unsigned char reg_value[2];
ft5426_dev.initfalg = FT5426_INIT_NOTFINISHED;
int i;
for( i = 0; i < 5; i++ )
{
ft5426_dev.x[i] = 0;
ft5426_dev.y[i] = 0;
}
ft5426_dev.point_num = 0;
IOMUXC_SetPinMux(IOMUXC_UART5_TX_DATA_I2C2_SCL,1);
IOMUXC_SetPinMux(IOMUXC_UART5_RX_DATA_I2C2_SDA,1);
IOMUXC_SetPinConfig(IOMUXC_UART5_TX_DATA_I2C2_SCL,0x70B0);
IOMUXC_SetPinConfig(IOMUXC_UART5_RX_DATA_I2C2_SDA,0X70B0);
gpio_pin_config_t ctintpin_config;
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO09_GPIO1_IO09,0);
IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER9_GPIO5_IO09,0);
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO09_GPIO1_IO09,0xF080);
IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER9_GPIO5_IO09,0X10B0);
ctintpin_config.direction = kGPIO_DigitalInput;
ctintpin_config.interruptMode = kGPIO_IntRisingOrFallingEdge;
gpio_init(GPIO1, 9, &ctintpin_config);
GIC_EnableIRQ(GPIO1_Combined_0_15_IRQn);
system_register_irqhandler(GPIO1_Combined_0_15_IRQn, (system_irq_handler_t)gpio1_io9_irqhandler, NULL);
gpio_enableint(GPIO1, 9);
ctintpin_config.direction=kGPIO_DigitalOutput;
ctintpin_config.interruptMode=kGPIO_NoIntmode;
ctintpin_config.outputLogic=1;
gpio_init(GPIO5, 9, &ctintpin_config);
i2c_init(I2C2);
gpio_pinwrite(GPIO5, 9, 0);
delayms(20);
gpio_pinwrite(GPIO5, 9, 1);
delayms(20);
ft5426_write_byte(FT5426_ADDR, FT5426_DEVICE_MODE, 0);
ft5426_write_byte(FT5426_ADDR, FT5426_IDG_MODE, 1);
ft5426_read_len(FT5426_ADDR, FT5426_IDGLIB_VERSION, 2, reg_value);
printf("Touch Frimware Version:%#X\r\n", ((unsigned short)reg_value[0] << 8) + reg_value[1]);
ft5426_dev.initfalg = FT5426_INIT_FINISHED;
ft5426_dev.intflag = 0;
}
void gpio1_io9_irqhandler(void)
{
if(ft5426_dev.initfalg == FT5426_INIT_FINISHED)
{
ft5426_read_tpcoord();
}
gpio_clearintflags(GPIO1, 9);
}
unsigned char ft5426_write_byte(unsigned char addr,unsigned char reg, unsigned char data)
{
unsigned char status=0;
unsigned char writedata=data;
struct i2c_transfer masterXfer;
masterXfer.slaveAddress = addr;
masterXfer.direction = kI2C_Write;
masterXfer.subaddress = reg;
masterXfer.subaddressSize = 1;
masterXfer.data = &writedata;
masterXfer.dataSize = 1;
if(i2c_master_transfer(I2C2, &masterXfer))
status=1;
return status;
}
unsigned char ft5426_read_byte(unsigned char addr,unsigned char reg)
{
unsigned char val=0;
struct i2c_transfer masterXfer;
masterXfer.slaveAddress = addr;
masterXfer.direction = kI2C_Read;
masterXfer.subaddress = reg;
masterXfer.subaddressSize = 1;
masterXfer.data = &val;
masterXfer.dataSize = 1;
i2c_master_transfer(I2C2, &masterXfer);
return val;
}
void ft5426_read_len(unsigned char addr,unsigned char reg,unsigned char len,unsigned char *buf)
{
struct i2c_transfer masterXfer;
masterXfer.slaveAddress = addr;
masterXfer.direction = kI2C_Read;
masterXfer.subaddress = reg;
masterXfer.subaddressSize = 1;
masterXfer.data = buf;
masterXfer.dataSize = len;
i2c_master_transfer(I2C2, &masterXfer);
}
void ft5426_read_tpnum(void)
{
ft5426_dev.point_num = ft5426_read_byte(FT5426_ADDR, FT5426_TD_STATUS);
}
void ft5426_read_tpcoord(void)
{
unsigned char i = 0;
unsigned char type = 0;
unsigned char pointbuf[FT5426_XYCOORDREG_NUM];
ft5426_dev.point_num = ft5426_read_byte(FT5426_ADDR, FT5426_TD_STATUS);
ft5426_read_len(FT5426_ADDR, FT5426_TOUCH1_XH, FT5426_XYCOORDREG_NUM, pointbuf);
for(i = 0; i < ft5426_dev.point_num ; i++)
{
unsigned char *buf = &pointbuf[i * 6];
ft5426_dev.x[i] = ((buf[2] << 8) | buf[3]) & 0x0fff;
ft5426_dev.y[i] = ((buf[0] << 8) | buf[1]) & 0x0fff;
type = buf[0] >> 6;
if(type == FT5426_TOUCH_EVENT_DOWN || type == FT5426_TOUCH_EVENT_ON )
{
}
else {
}
}
}
linux驱动
1.介绍
- 看到这里,我们可以得出电容触摸屏驱动其实大框架就是 IIC设备驱动
- 在此基础上,介绍了触摸屏的四个引脚,除去IIC的SCL和SDA,还有INT引脚和RST引脚.
- INT(中断引脚))向linux内核上报触摸信息,因此需要用到linux中断驱动框架。坐标的上报在中断服务函数中完成。
- 触摸屏的坐标信息、屏幕按下和抬起信息都属于 linux的 input子系统,因此向 linux内核上报触摸屏坐标信息就得使用 input子系统。这篇文章介绍了input子系统简述
- 在input系统框架上我们知道(按键、鼠标、键盘、触摸屏等都属于输入设备,linux内核为此专门做了一个叫做input子系统的框架来处理输入事件。输入设备本质上还是字符设备,只是在此基础上套上了input框架,用户只需要负责上报输入事件,比如按键值、坐标等信息)但是对于多点触摸的上报事件我们需要引入input 子系统下的多点电容触摸协议(MT)
1.MT协议。 MT 协议被分为两种类型, Type A和TypeB。 1)Type A:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据 2)适用于有硬件追踪并能区分触摸点的触摸设备,此类型设备通过 slot更新某一个触摸点的信息,FT5426就属于此类型,一般的多点电容触摸屏 IC都有此能力。
2.我们这里使用TypeB。我们用MT协议的根本目的就是为了上报事件给内核(这里适用于多点的触摸的ABS_MT事件)
#define ABS_MT_SLOT 0x2f
#define ABS_MT_TOUCH_MAJOR 0x30
#define ABS_MT_TOUCH_MINOR 0x31
#define ABS_MT_WIDTH_MAJOR 0x32
#define ABS_MT_ORIENTATION 0x34
#define ABS_MT_POSITION_X 0x35
#define ABS_MT_POSITION_Y 0x36
#define ABS_MT_TOOL_TYPE 0x37
#define ABS_MT_BLOB_ID 0x38
#define ABS_MT_TRACKING_ID 0x39
#define ABS_MT_PRESSURE 0x3a
#define ABS_MT_DISTANCE 0x3b
#define ABS_MT_TOOL_X 0x3c
#define ABS_MT_TOOL_Y 0x3d
void input_mt_sync(struct input_dev *dev)
void input_mt_slot(struct input_dev *dev, int slot)
3.原理:Type B 设备驱动需要给每个识别出来的触摸点分配一个 slot,后面使用这个 slot 来上报触摸点信息。(有点像信号和槽机制)。但是你既然多点触摸,肯定每个点都有个先后顺序,下面针对TypeB的来介绍
ABS_MT_SLOT 0
ABS_MT_TRACKING_ID 45
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
当一个触摸点移除以后,同样需要通过 SLOT 关联的ABS_MT_TRACKING_ID 来处理
ABS_MT_TRACKING_ID -1
SYN_REPORT
4.总结一下, MT协议隶属于linux的 input 子系统,驱动通过大量的 ABS_MT 事件向 linux 内核上报多点触摸坐标数据。根据触摸 IC的不同,分为Type A和Type B两种类型,不同的类型其上报时序不同,目前使用最多的是 Type B 类型
5.linux 下的多点触摸协议其实就是通过不同的事件来上报触摸 点坐标信息,这些事件都是通过 Linux 内核提供的对应 API 函数实现的
int input_mt_init_slots( struct input_dev *dev, unsigned int num_slots, unsigned int flags)
void input_mt_slot(struct input_dev *dev, int slot)
void input_mt_report_slot_state( struct input_dev *dev, unsigned int tool_type,bool active)
void input_report_abs( struct input_dev *dev,unsigned int code, int value)
void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
面的那么多都是整个框架中所涉及到的未知知识 多点电容触摸驱动的编写框架用到的知识点和框架: 1.多点电容触摸芯片的接口,一般都为I2C接口,因此驱动主框架肯定是 I2C。 2.linux里面一般都是通过中断来上报触摸点坐标信息,因此需要用到中断框架 3.多点电容触摸属于 input子系统,因此还要用到 input子系统框架。 4.在中断处理程序中按照 linux的MT协议上报坐标信息(此协议是多点触摸专用)
多点电容触摸驱动编写框架以及步骤如下:
- 1.I2C驱动框架(中间的probe函数里面初始化触摸 IC,中断和input子系统)
当设备树中触摸 IC的设备节点和驱动匹配以后probe函数会执行
static const struct i2c_device_id xxx_ts_id[] = {
{ "xxx", 0, },
{ }
};
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "xxx", },
{ }
};
static struct i2c_driver ft5x06_ts_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "edt_ft5x06",
.of_match_table = of_match_ptr(xxx_of_match),
},
.id_table = xxx_ts_id,
.probe = xxx_ts_probe,
.remove = xxx_ts_remove,
};
static int __init xxx_init(void)
{
int ret = 0;
ret = i2c_add_driver(&xxx_ts_driver);
return ret;
}
static void __exit xxx_exit(void)
{
i2c_del_driver(&ft5x06_ts_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
static int xxx_ts_probe(struct i2c_client *client, const struct
i2c_device_id *id)
{
struct input_dev *input;
......
devm_request_threaded_irq(&client->dev, client->irq, NULL,
xxx_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
client->name, &xxx);
......
input = devm_input_allocate_device(&client->dev);
input->name = client->name;
input->id.bustype = BUS_I2C;
input->dev.parent = &client->dev;
......
__set_bit(EV_ABS, input->evbit);
__set_bit(BTN_TOUCH, input->keybit);
input_set_abs_params(input, ABS_X, 0, width, 0, 0);
input_set_abs_params(input, ABS_Y, 0, height, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_X,0, width, 0, 0)
input_set_abs_params(input, ABS_MT_POSITION_Y,0, height, 0, 0
input_mt_init_slots(input, MAX_SUPPORT_POINTS, 0);
......
input_register_device(input);
......
}
- 3.上报坐标信息(Type B类型)
最后就是在中断服务程序中上报读取到的坐标信息
static irqreturn_t xxx_handler(int irq, void *dev_id)
{
int num;
int x[n], y[n];
......
for (i = 0; i < num; i++) {
input_mt_slot(input, id);
input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
input_report_abs(input, ABS_MT_POSITION_X, x[i]);
input_report_abs(input, ABS_MT_POSITION_Y, y[i]);
}
......
input_sync(input);
......
return IRQ_HANDLED;
}
2. 实例应用
1.修改设备树
修改IO 4个 IO,一个复位 IO、一个中断IO、I2C2的 SCL 和SDA。复位 IO 和中断 IO是普通的GPIO,因此这两个IO可以放到同一个节点下去描述,I2C2的SCL和 SDA 属于 I2C2,因此这两个要放到同一个节点下去描述
- 首先是复位 IO 和中断 IO,imx6ull-alientek-emmc.dts 文件里面默认有个名为“pinctrl_tsc”的节点,如果被删除了的话就自行创建,在此节点下添加触摸屏的中断引脚信息
pinctrl_tsc: tscgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0xF080
>;
};
触摸屏复位引脚使用的是 SNVS_TAMPER9,因此复位引脚信息要添加到 iomuxc_snvs 节点下,在 iomuxc_snvs 节点新建一个名为 pinctrl_tsc_reset 的子节点
pinctrl_tsc_reset: tsc_reset {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0
>;
};
- 继续添加 I2C2 的 SCL 和 SDA 这两个 IO 信息.x6ull-alientek-emmc.dts 里面默认就已经添加了 I2C2 的 IO 信息,这是 NXP 官方添加的,所以不需要我们去修改.
pinctrl_i2c2: i2c2grp {
fsl,pins = <
MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
>;
};
注意:要防止引脚冲突
添加设备节点 需要向 I2C2 节点下添加一个子节点,此子节点用于描述 FT5426,添加完成以后的 I2C2 节点
&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
&pinctrl_tsc_reset >;
interrupt-parent = <&gpio1>;
interrupts = <9 0>;
reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
};
};
2.编写设备驱动
总线驱动官方已经写好过了,我们只需要写设备驱动。
#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
#define TOUCH_EVENT_DOWN 0x00
#define TOUCH_EVENT_UP 0x01
#define TOUCH_EVENT_ON 0x02
#define TOUCH_EVENT_RESERVED 0x03
#define FT5X06_TD_STATUS_REG 0X02
#define FT5x06_DEVICE_MODE_REG 0X00
#define FT5426_IDG_MODE_REG 0XA4
#define FT5X06_READLEN 29
struct ft5x06_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 ft5x06_dev ft5x06;
static int ft5x06_ts_reset(struct i2c_client *client, struct ft5x06_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 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].addr = client->addr;
msg[0].flags = 0;
msg[0].buf = ®
msg[0].len = 1;
msg[1].addr = client->addr;
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);
msg.addr = client->addr;
msg.flags = 0;
msg.buf = b;
msg.len = len + 1;
return i2c_transfer(client->adapter, &msg, 1);
}
static void ft5x06_write_reg(struct ft5x06_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
ft5x06_write_regs(dev, reg, &buf, 1);
}
static irqreturn_t ft5x06_handler(int irq, void *dev_id)
{
struct ft5x06_dev *multidata = dev_id;
u8 rdbuf[29];
int i, type, x, y, id;
int offset, tplen;
int ret;
bool down;
offset = 1;
tplen = 6;
memset(rdbuf, 0, sizeof(rdbuf));
ret = ft5x06_read_regs(multidata, FT5X06_TD_STATUS_REG, rdbuf, FT5X06_READLEN);
if (ret) {
goto fail;
}
for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
u8 *buf = &rdbuf[i * tplen + offset];
type = buf[0] >> 6;
if (type == TOUCH_EVENT_RESERVED)
continue;
x = ((buf[2] << 8) | buf[3]) & 0x0fff;
y = ((buf[0] << 8) | buf[1]) & 0x0fff;
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 ft5x06_ts_irq(struct i2c_client *client, struct ft5x06_dev *dev)
{
int ret = 0;
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;
}
}
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;
}
static int ft5x06_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
ft5x06.client = client;
ft5x06.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
ft5x06.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);
ret = ft5x06_ts_reset(client, &ft5x06);
if(ret < 0) {
goto fail;
}
ret = ft5x06_ts_irq(client, &ft5x06);
if(ret < 0) {
goto fail;
}
ft5x06_write_reg(&ft5x06, FT5x06_DEVICE_MODE_REG, 0);
ft5x06_write_reg(&ft5x06, FT5426_IDG_MODE_REG, 1);
ft5x06.input = devm_input_allocate_device(&client->dev);
if (!ft5x06.input) {
ret = -ENOMEM;
goto fail;
}
ft5x06.input->name = client->name;
ft5x06.input->id.bustype = BUS_I2C;
ft5x06.input->dev.parent = &client->dev;
__set_bit(EV_KEY, ft5x06.input->evbit);
__set_bit(EV_ABS, ft5x06.input->evbit);
__set_bit(BTN_TOUCH, ft5x06.input->keybit);
input_set_abs_params(ft5x06.input, ABS_X, 0, 1024, 0, 0);
input_set_abs_params(ft5x06.input, ABS_Y, 0, 600, 0, 0);
input_set_abs_params(ft5x06.input, ABS_MT_POSITION_X,0, 1024, 0, 0);
input_set_abs_params(ft5x06.input, ABS_MT_POSITION_Y,0, 600, 0, 0);
ret = input_mt_init_slots(ft5x06.input, MAX_SUPPORT_POINTS, 0);
if (ret) {
goto fail;
}
ret = input_register_device(ft5x06.input);
if (ret)
goto fail;
return 0;
fail:
return ret;
}
static int ft5x06_ts_remove(struct i2c_client *client)
{
input_unregister_device(ft5x06.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 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("zuozhongkai");
3. 驱动测试
1.手动加载加载驱动
- 将上一小节编译出来 ft5x06.ko 拷贝到
rootfs/lib/modules/4.1.15目录中,启动开发板,进入到目录 lib/modules/4.1.15中
depmod
modprobe ft5x06.ko
hexdump /dev/input/event2
2.将驱动添加到内核
- 前面我们一直将触摸驱动编译为模块,每次系统启动以后在手动加载驱动模块,这样很不方便。当我们把驱动调试成功以后一般都会将其编译到内核中,这样内核启动以后就会自动加载驱动,不需要我们再手动 modprobe 了
1、将驱动文件放到合适的位置,在内核源码中找个合适的位置将 ft5x06.c放进去,ft5x06.c是个触摸屏驱动,因此我们需要查找一下 linux 内核里面触摸屏驱动放到了哪个目录下。linux 内核里面将触摸屏驱动放到了 drivers/input/touchscreen 目录下,因此我们要将 ft5x06.c拷贝到此目录下。
2.修改对应的Makefile 修改完成以后重新编译 linux 内核,然后用新的 zImage 启动开发板。
hexdump /dev/input/event1
|