正点原子 Linux 驱动开发:多点电容触摸屏实验,gt9147 触摸芯片

正点原子 imx6ull 开发板,4.3寸屏

复制代码
&i2c2 {
	clock_frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c2>;
	status = "okay";

	/* 实际是 4.3寸 触摸屏 */
	gt9147: gt9147@14 {
		compatible = "goodix,gt9147","goodix,gt9";
		reg = <0X14>;
		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>;
		status = "okay";
	};

pinctrl_tsc_reset: tsc_rest {
					fsl,pins = <
							MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09   0x10B0	/* 复位引脚 */
					>;
				};

pinctrl_tsc: tscgrp {
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x79	 /* 中断引脚 */
			>;
		};

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/gpio/consumer.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/debugfs.h>
#include <linux/delay.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/i2c.h>
#include <asm/unaligned.h>

#define GT_CTRL_REG 	        0X8040  /* GT9147控制寄存器         */
#define GT_MODSW_REG 	        0X804D  /* GT9147模式切换寄存器        */
#define GT_9xx_CFGS_REG 	    0X8047  /* GT9147配置起始地址寄存器    */
#define GT_1xx_CFGS_REG 	    0X8050  /* GT1151配置起始地址寄存器    */
#define GT_CHECK_REG 	        0X80FF  /* GT9147校验和寄存器       */
#define GT_PID_REG 		        0X8140  /* GT9147产品ID寄存器       */

#define GT_GSTID_REG 	        0X814E  /* GT9147当前检测到的触摸情况 */
#define GT_TP1_REG 		        0X814F  /* 第一个触摸点数据地址 */
#define GT_TP2_REG 		        0X8157	/* 第二个触摸点数据地址 */
#define GT_TP3_REG 		        0X815F  /* 第三个触摸点数据地址 */
#define GT_TP4_REG 		        0X8167  /* 第四个触摸点数据地址  */
#define GT_TP5_REG 		        0X816F	/* 第五个触摸点数据地址   */
#define MAX_SUPPORT_POINTS      5       /* 最多5点电容触摸 */

struct gt9147_dev {
	int irq_pin,reset_pin;					/* 中断和复位IO		*/
	int irqnum;								/* 中断号    		*/
	int irqtype;							/* 中断类型         */
	int max_x;								/* 最大横坐标   	*/
	int max_y; 								/* 最大纵坐标		*/
	void *private_data;						/* 私有数据 		*/
	struct input_dev *input;				/* input结构体 		*/
	struct i2c_client *client;				/* I2C客户端 		*/

};
struct gt9147_dev gt9147;

const u8 irq_table[] = {IRQ_TYPE_EDGE_RISING, IRQ_TYPE_EDGE_FALLING, IRQ_TYPE_LEVEL_LOW, IRQ_TYPE_LEVEL_HIGH};  /* 触发方式 */

/*
 * @description     : 复位GT9147
 * @param - client 	: 要操作的i2c
 * @param - multidev: 自定义的multitouch设备
 * @return          : 0,成功;其他负值,失败
 */
static int gt9147_ts_reset(struct i2c_client *client, struct gt9147_dev *dev)
{
	int ret = 0;

    /* 申请复位IO*/
	if (gpio_is_valid(dev->reset_pin)) {  		
		/* 申请复位IO,并且默认输出高电平 */
		ret = devm_gpio_request_one(&client->dev,	
					dev->reset_pin, GPIOF_OUT_INIT_HIGH,
					"gt9147 reset");
		if (ret) {
			return ret;
		}
	}

    /* 申请中断IO*/
	if (gpio_is_valid(dev->irq_pin)) {  		
		/* 申请复位IO,并且默认输出高电平 */
		ret = devm_gpio_request_one(&client->dev,	
					dev->irq_pin, GPIOF_OUT_INIT_HIGH,
					"gt9147 int");
		if (ret) {
			return ret;
		}
	}

    /* 4、初始化GT9147,要严格按照GT9147时序要求 */
    gpio_set_value(dev->reset_pin, 0); /* 复位GT9147 */
    msleep(10);
    gpio_set_value(dev->reset_pin, 1); /* 停止复位GT9147 */
    msleep(10);
    gpio_set_value(dev->irq_pin, 0);    /* 拉低INT引脚 */
    msleep(50);
    gpio_direction_input(dev->irq_pin); /* INT引脚设置为输入 */

	return 0;
}

/*
 * @description	: 从GT9147读取多个寄存器数据
 * @param - dev:  GT9147设备
 * @param - reg:  要读取的寄存器首地址
 * @param - buf:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
static int gt9147_read_regs(struct gt9147_dev *dev, u16 reg, u8 *buf, int len)
{
	int ret;
    u8 regdata[2];
	struct i2c_msg msg[2];
	struct i2c_client *client = (struct i2c_client *)dev->client;
    
    /* GT9147寄存器长度为2个字节 */
    regdata[0] = reg >> 8;
    regdata[1] = reg & 0xFF;

	/* msg[0]为发送要读取的首地址 */
	msg[0].addr = client->addr;			/* ft5x06地址 */
	msg[0].flags = !I2C_M_RD;			/* 标记为发送数据 */
	msg[0].buf = &regdata[0];			/* 读取的首地址 */
	msg[0].len = 2;						/* reg长度*/

	/* msg[1]读取数据 */
	msg[1].addr = client->addr;			/* ft5x06地址 */
	msg[1].flags = I2C_M_RD;			/* 标记为读取数据*/
	msg[1].buf = buf;					/* 读取数据缓冲区 */
	msg[1].len = len;					/* 要读取的数据长度*/

	ret = i2c_transfer(client->adapter, msg, 2);
	if(ret == 2) {
		ret = 0;
	} else {
		ret = -EREMOTEIO;
	}
	return ret;
}

/*
 * @description	: 向GT9147多个寄存器写入数据
 * @param - dev:  GT9147设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 */
static s32 gt9147_write_regs(struct gt9147_dev *dev, u16 reg, u8 *buf, u8 len)
{
	u8 b[256];
	struct i2c_msg msg;
	struct i2c_client *client = (struct i2c_client *)dev->client;
	
	b[0] = reg >> 8;			/* 寄存器首地址低8位 */
    b[1] = reg & 0XFF;			/* 寄存器首地址高8位 */
	memcpy(&b[2],buf,len);		/* 将要写入的数据拷贝到数组b里面 */

	msg.addr = client->addr;	/* gt9147地址 */
	msg.flags = 0;				/* 标记为写数据 */

	msg.buf = b;				/* 要写入的数据缓冲区 */
	msg.len = len + 2;			/* 要写入的数据长度 */

	return i2c_transfer(client->adapter, &msg, 1);
}

#if 1
static irqreturn_t gt9147_irq_handler(int irq, void *dev_id)
{
    struct gt9147_dev *dev = dev_id;
    u8 data, clear_val = 0;
    u8 temp_buf[5];		// 每个触摸点的原始数据缓存
    int i, ret, touch_num, x, y, id;
    static u8 last_touch_mask = 0; 	// 用于记录上一次触摸的手指(槽位)状态
    u8 current_touch_mask = 0;		// 本次触摸状态
	static int last_points = -1;	// 调试

	/* 读取触摸状态寄存器 , 最高位 0x80 表示触摸数据有效 */
    ret = gt9147_read_regs(dev, GT_GSTID_REG, &data, 1);
    if (ret < 0 )
		return IRQ_HANDLED;
	if(!(data & 0x80))
		goto exit_clear;	// 清除中断

    touch_num = data & 0x0f;	// 获取触摸点数量

	/* 遍历每个触摸点 */
    for (i = 0; i < touch_num; i++) {
        gt9147_read_regs(dev, GT_TP1_REG + i * 8, temp_buf, 5);
        id = temp_buf[0] & 0x0F;		// 手指 ID(0~9),用于 MT 槽位
        x  = temp_buf[1] | (temp_buf[2] << 8);	// 触摸坐标(低字节 + 高字节)
        y  = temp_buf[3] | (temp_buf[4] << 8);

		/* 报告触摸事件 */
        if (id < MAX_SUPPORT_POINTS) {
            input_mt_slot(dev->input, id); // 选择对应槽位, 使用硬件 id 作为槽位
            input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, true);// 报告手指按下状态,标记按下
            input_report_abs(dev->input, ABS_MT_POSITION_X, x);		// 上报坐标
            input_report_abs(dev->input, ABS_MT_POSITION_Y, y);
            current_touch_mask |= (1 << id);
            
            if (i == 0) { // 兼容单点 , 老驱动或单点应用使用
                input_report_abs(dev->input, ABS_X, x);
                input_report_abs(dev->input, ABS_Y, y);
                input_report_key(dev->input, BTN_TOUCH, 1);
            }
        }
    }

    // 遍历所有槽位 , 释放已经松开的槽位
    for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
		/* 如果上一次触摸有,而本次没有,标记松开 */
        if ((last_touch_mask & (1 << i)) && !(current_touch_mask & (1 << i))) {
            input_mt_slot(dev->input, i);
            input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, false);	// 标记松开
        }
    }
    last_touch_mask = current_touch_mask;	// 更新

	/* 单点触摸松开处理: 如果没有触摸点,释放单点触摸按钮 */
    if (touch_num == 0) 
		input_report_key(dev->input, BTN_TOUCH, 0);

	/* 完成多点报告和同步 */
    input_mt_report_pointer_emulation(dev->input, true);	// 兼容老单点应用
    input_sync(dev->input);		// 通知内核所有输入事件已就绪

	/* 用于调试,避免频繁打印 */
	if (touch_num != last_points) {
//		printk("gt9147 touch: points = %d\n", touch_num);
		last_points = touch_num;
	}

exit_clear:
    clear_val = 0;
    gt9147_write_regs(dev, GT_GSTID_REG, &clear_val, 1);
    return IRQ_HANDLED;	// 写 0 到状态寄存器,清除中断标志 ,通知内核 中断处理完成
}
#else
static irqreturn_t gt9147_irq_handler(int irq, void *dev_id)
{
    struct gt9147_dev *dev = dev_id;
    u8 data;
    u8 clear_val = 0;
    int i, ret;
    int touch_num;
    u8 temp_buf[5];
    int id, x, y;

    /* 1. 读取状态寄存器 */
    ret = gt9147_read_regs(dev, GT_GSTID_REG, &data, 1);
    if (ret < 0)
        return IRQ_HANDLED;

    /* 判断数据是否有效(Bit7) */
    if (!(data & 0x80))
        goto exit_clear;

    touch_num = data & 0x0f;

    /* 2. 遍历所有 slot(固定 0~4) */
    for (i = 0; i < MAX_SUPPORT_POINTS; i++) {

        if (i < touch_num) {
            /* 读取触摸点数据 */
            gt9147_read_regs(dev, GT_TP1_REG + i * 8, temp_buf, 5);

            id = temp_buf[0] & 0x0F;
            x  = temp_buf[1] | (temp_buf[2] << 8);
            y  = temp_buf[3] | (temp_buf[4] << 8);

            /* ⭐ slot 用 i,不是 id */
            input_mt_slot(dev->input, i);
            input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, true);

            input_report_abs(dev->input, ABS_MT_POSITION_X, x);
            input_report_abs(dev->input, ABS_MT_POSITION_Y, y);
            input_report_abs(dev->input, ABS_MT_TRACKING_ID, id);

            /* 单点兼容 */
            if (i == 0) {
                input_report_abs(dev->input, ABS_X, x);
                input_report_abs(dev->input, ABS_Y, y);
                input_report_key(dev->input, BTN_TOUCH, 1);
            }

        } else {
            /* 释放 slot */
            input_mt_slot(dev->input, i);
            input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, false);

            if (i == 0)
                input_report_key(dev->input, BTN_TOUCH, 0);
        }
    }

    /* 同步 */
    input_mt_report_pointer_emulation(dev->input, true);
    input_sync(dev->input);

    printk("gt9147 touch: points = %d\n", touch_num);

exit_clear:
    /* 清中断 */
    clear_val = 0;
    gt9147_write_regs(dev, GT_GSTID_REG, &clear_val, 1);

    return IRQ_HANDLED;
}
#endif

/*
 * @description     : GT9147中断初始化
 * @param - client 	: 要操作的i2c
 * @param - multidev: 自定义的multitouch设备
 * @return          : 0,成功;其他负值,失败
 */
static int gt9147_ts_irq(struct i2c_client *client, struct gt9147_dev *dev)
{
	int ret = 0;
	/* 2,申请中断,client->irq就是IO中断,  IRQF_ONESHOT -> 线程没执行完之前,禁止再次触发中断 */
	ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
					gt9147_irq_handler, irq_table[dev->irqtype] | IRQF_ONESHOT,
					client->name, &gt9147);		// &gt9147 传给 handler
	if (ret) {
		dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
		return ret;
	}

	return 0;
}

/*
 * @description     : GT9147读取固件
 * @param - client 	: 要操作的i2c
 * @param - multidev: 自定义的multitouch设备
 * @return          : 0,成功;其他负值,失败
 */
static int gt9147_read_firmware(struct i2c_client *client, struct gt9147_dev *dev)
{
	int ret = 0, version = 0;
	u16 id = 0;
	u8 data[7]={0};	/* data[0~3]  → PID(ASCII), data[4~5]  → VERSION(小端 u16) */
	char id_str[5];
	ret = gt9147_read_regs(dev, GT_PID_REG, data, 6);
	if (ret) {
		dev_err(&client->dev, "Unable to read PID.\n");
		return ret;
	}
	memcpy(id_str, data, 4);
	id_str[4] = 0;
    if (kstrtou16(id_str, 10, &id))	// 字符串形式的数字 转换成 u16
        id = 0x1001;
	version = get_unaligned_le16(&data[4]);	// 从字节流读小端16位: version = data[4] | (data[5] << 8)
	dev_info(&client->dev, "ID %d, version: %04x\n", id, version);
	switch (id) {    /* 由于不同的芯片配置寄存器地址不一样需要判断一下  */
    case 1151:
    case 1158:
    case 5663:
    case 5688:    /* 读取固件里面的配置信息  */
		printk("gt9147 read GT_1xx_CFGS_REG\r\n");
        ret = gt9147_read_regs(dev, GT_1xx_CFGS_REG, data, 7);  
		break;
    default:
		printk("gt9147 read GT_9xx_CFGS_REG\r\n");
        ret = gt9147_read_regs(dev, GT_9xx_CFGS_REG, data, 7);
		break;
    }
	if (ret) {
		dev_err(&client->dev, "Unable to read Firmware.\n");
		return ret;
	}

	dev->max_x = (data[2] << 8) + data[1];
	dev->max_y = (data[4] << 8) + data[3];
	dev->irqtype = data[6] & 0x3;

	printk("X_MAX: %d, Y_MAX: %d, TRIGGER: 0x%02x", dev->max_x, dev->max_y, dev->irqtype);

	return 0;
}

int gt9147_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    u8 data;
	int ret;
    gt9147.client = client;

 	/* 1,获取设备树中的中断和复位引脚 */
	gt9147.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
	gt9147.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);

	/* 2,复位GT9147 */
	ret = gt9147_ts_reset(client, &gt9147);
	if(ret < 0) {
		goto fail;
    }

    /* 3,初始化GT9147 */
    data = 0x02;
    gt9147_write_regs(&gt9147, GT_CTRL_REG, &data, 1); /* 软复位 */
    mdelay(100);
    data = 0x0;
    gt9147_write_regs(&gt9147, GT_CTRL_REG, &data, 1); /* 停止软复位 */
    mdelay(100);
    
    /* 4,初始化GT9147,读取固件  */
	ret = gt9147_read_firmware(client, &gt9147);
	if(ret != 0) {
		printk("Fail !!! gt9147 check !!\r\n");
		goto fail;
    }
	
    /* 5. Input 设备注册 */
	gt9147.input = devm_input_allocate_device(&client->dev);
	if (!gt9147.input)
		return -ENOMEM;

	/* 设备基本信息初始化 */
	gt9147.input->name = client->name;
	gt9147.input->id.bustype = BUS_I2C;
	gt9147.input->dev.parent = &client->dev;

	/* 声明支持的"事件类型" */
	__set_bit(EV_ABS, gt9147.input->evbit);			// 绝对坐标(触摸屏必须)
	__set_bit(EV_KEY, gt9147.input->evbit);			// 按键事件(触摸按下/抬起)
	__set_bit(BTN_TOUCH, gt9147.input->keybit);		// 这个设备有一个"触摸按键"
	__set_bit(INPUT_PROP_DIRECT, gt9147.input->propbit);	// 设备属性:直接触摸设备(屏幕)

	/* 声明支持哪些"坐标事件" */
	__set_bit(ABS_X, gt9147.input->absbit);		// X 坐标
	__set_bit(ABS_Y, gt9147.input->absbit);		// Y 坐标

	/* 多点(MT协议) */
	__set_bit(ABS_MT_POSITION_X, gt9147.input->absbit);	// 多点X
	__set_bit(ABS_MT_POSITION_Y, gt9147.input->absbit);	// 多点Y

	/* 单点(兼容用),设置坐标范围   */
	input_set_abs_params(gt9147.input, ABS_X, 0, gt9147.max_x, 0, 0);	// X: 0 ~ max_x
	input_set_abs_params(gt9147.input, ABS_Y, 0, gt9147.max_y, 0, 0);	// Y: 0 ~ max_y

	/* 设置 多点坐标范围  0 ~ max */
	input_set_abs_params(gt9147.input, ABS_MT_POSITION_X, 0, gt9147.max_x, 0, 0);
	input_set_abs_params(gt9147.input, ABS_MT_POSITION_Y, 0, gt9147.max_y, 0, 0);

	/* 标识"这是第几个手指" , 系统用这个来跟踪:手指移动 手指抬起 */
    input_set_abs_params(gt9147.input, ABS_MT_TRACKING_ID, 0, 255, 0, 0);

	/* 每个手指一个坑位 Slot0 → 手指1 , Slot1 → 手指2 */
	/* 初始化多点槽(MT Slot), MAX_SUPPORT_POINTS -> 最多支持5个手指 , INPUT_MT_DIRECT 触摸屏 */
	ret = input_mt_init_slots(gt9147.input, MAX_SUPPORT_POINTS, INPUT_MT_DIRECT);
	if (ret) {
		dev_err(&client->dev, "MT init failed: %d\n", ret);
		return ret;
	}

	/* 注册 input 设备 */
	ret = input_register_device(gt9147.input);
	if (ret)
		return ret;

    /* 6,最后初始化中断 */
	ret = gt9147_ts_irq(client, &gt9147);
	if(ret < 0) {
		goto fail;
	}
    return 0;

fail:
	return ret;
}

/*
 * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
 * @param - client 	: i2c设备
 * @return          : 0,成功;其他负值,失败
 */
int gt9147_remove(struct i2c_client *client)
{
	/* 使用 dev_info 会自动处理换行,且信息更完整 */
    dev_info(&client->dev, "gt9147 driver removed\n");
	
    input_unregister_device(gt9147.input);
    return 0;
}

/*
 *  传统驱动匹配表
 */ 
const struct i2c_device_id gt9147_id_table[] = {
	{ "goodix,gt9147", 0},
    { /* sentinel */ }
};

/*
 * 设备树匹配表 
 */
const struct of_device_id gt9147_of_match_table[] = {
    {.compatible = "goodix,gt9147" },
    { /* sentinel */ }
};

/* i2c驱动结构体 */	
struct i2c_driver gt9147_i2c_driver = {
    .driver = {
        .name  = "gt9147",
        .owner = THIS_MODULE,
        .of_match_table = gt9147_of_match_table,
    },
    .id_table = gt9147_id_table,
    .probe  = gt9147_probe,
    .remove = gt9147_remove,
};

module_i2c_driver(gt9147_i2c_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
相关推荐
senijusene2 小时前
IMX6ULL ADC 驱动开发解析:
驱动开发·嵌入式硬件
航Hang*2 小时前
第2章:进阶Linux系统——第10节:Linux 系统编程与 Shell 脚本全解笔记(GCC+Make+Vim+Shell Script)
linux·运维·服务器·学习·vim·apache·vmware
孙同学_2 小时前
【Linux篇】应用层协议HTTP
linux·运维·http
DeadPool loves Star2 小时前
新版VSCode登录Old Linux
linux·ide·vscode
我爱学习好爱好爱2 小时前
Ansible Loop循环 循环遍历的属性 Notify和Handlers
linux·运维·ansible
charlie1145141912 小时前
嵌入式Linux驱动开发——模块参数与内核调试:让模块“活“起来的魔法
linux·驱动开发·学习·c
xin_yao_xin2 小时前
Linux下项目开机自启服务
linux·运维·服务器
陳10302 小时前
Linux:入门开发工具--Git和GUN调试器
linux·运维·git
DeepHacking2 小时前
Ubuntu 上安装 ComfyUI(NVIDIA GPU / Conda / CUDA 12.1)
linux·ubuntu·conda