六、触摸驱动
前面我们已经学了hdmi、edp、mipi等各种显示屏的驱动,相信大家都已经点亮屏幕并进入了Android或者Linux等界面,界面能显示了但是光看着不能触摸是不是感觉还差点意思,人机交互重点就在于交互,所以接下来我们来掌握触摸驱动调试。
视频教程
【新手必学Linux-触摸驱动】立创·泰山派RK3566 Linux开发板训练营第六课
准备
触摸驱动怎么学
整个触摸包含了GPIO驱动、中断驱动、i2c驱动、input驱动等,是一个复杂的驱动集合,这些驱动我们以后在驱动教程中在和大家一起去探讨,这一节我们就基于触摸进行分析,有用到相关驱动就浅学一下,主要目的还是把触摸跑起来先会用会调,目前我们常用的Goodix、FocalTech等触摸驱动内核中已经现成的了,所以我们理解框架并掌握调试技巧以后基本上可以把屏幕驱动起来。
学习目标
- 了解i2c触摸屏引脚线序
- 掌握不同引脚线序的转接板绘制与焊接
- 看懂触摸屏手册,分辨率、时序、i2c地址、寄存器等
- 掌握触摸的设备树修改方法
- kernel中打开并配置驱动
- 理解触摸驱动大体框架做到会修改与调试
- 逻辑分析仪使用
硬件
其实吧触摸屏种类有很多我碰到过的有i2c、spi、usb等,其中i2c使用的频率最高大部分的触摸都是i2c的所以我们这里只讲i2c的触摸屏幕,另外如果你觉得下面的这些调试都太麻烦了你可以选择usb触摸无需调试。
连接方式
泰山派通过6p 0.5mm的fpc引出触摸接口,屏幕端跟据大家选的触摸常见不同对应的座子规格以及线序相应都会不同,跟据我们的经验以及问了很多厂家,触摸大家都是6根线但就这里6根线大家都不按照标准来,完全按照心情我猜应该有720种不同的线序,所以碰到线序不同的时候我们需要画个转接板,如果你量大也可以让厂家定制开模费几千的样子。
触摸接口位置
触摸座子在泰山派背面红框框出来的位置
原理图
PCB
线序
RK3566 | FPC引脚 | 功能 |
---|---|---|
GPIO1_A1_u | TP_RST | 触摸复位引脚 |
—— | VCC_3V3 | 3V3电源引脚 |
—— | GND | 地 |
GPIO1_A0_u | TP_INT_A | 触摸中断引脚 |
GPIO0_B4_u | I2C1_SDA_TP | i2c数据线 |
GPIO0_B3_u | I2C1_SCL_TP | i2c时钟线 |
屏幕资料
照顾新手小伙伴,这里以为前面我们调试的11寸屏幕带触摸总成为例,3.1寸的后面以项目方式去讲,如果你用的是其他的屏幕也没关系方法原理很简单稍微灵活应变一下就行。我前面采购的是总成,如果也采购了却只买了带mipi 31pin屏的话可以在单独卖个电容触摸屏,触摸链接(点击跳转)(只是采购回来测试,不负责售后以及相关责任,如果你有更好的屏幕推荐也可以联系我们)
数据手册:
【电容触控芯片GT9271 (IC规格书)Datasheet_20140904(金鼎泰).pdf】下载
📌 在 下载中心
->百度网盘(立创·泰山派开发板资料)
->第09章.【立创·泰山派】项目案例
->【智能小手机】项目资料
->6.触摸驱动
->触摸资料
->屏幕资料
->电容触控芯片GT9271 (IC规格书)Datasheet_20140904(金鼎泰).pdf
。
参考驱动:
【GT9xx_Driver_for_Android_V2.4_2014112801.rar】下载
📌 在 下载中心
->百度网盘(立创·泰山派开发板资料)
->第09章.【立创·泰山派】项目案例
->【智能小手机】项目资料
->6.触摸驱动
->触摸资料
->屏幕资料
->GT9xx_Driver_for_Android_V2.4_2014112801.rar
。
配置文件:
【GT9271_Config_20220925_172459.cfg】下载
📌 在 下载中心
->百度网盘(立创·泰山派开发板资料)
->第09章.【立创·泰山派】项目案例
->【智能小手机】项目资料
->6.触摸驱动
->触摸资料
->屏幕资料
->GT9271_Config_20220925_172459.cfg
。
工程图用AutoCAD打开、
【FH-101054-V5(6PIN) 全贴合工程图-1028(2)(1).dwg】下载
📌 在 下载中心
->百度网盘(立创·泰山派开发板资料)
->第09章.【立创·泰山派】项目案例
->【智能小手机】项目资料
->6.触摸驱动
->触摸资料
->屏幕资料
->FH-101054-V5(6PIN) 全贴合工程图-1028(2)(1).dwg
。
【FH-101054-V5(6PIN) 全贴合工程图-1028.pdf】下载
📌 在 下载中心
->百度网盘(立创·泰山派开发板资料)
->第09章.【立创·泰山派】项目案例
->【智能小手机】项目资料
->6.触摸驱动
->触摸资料
->屏幕资料
->FH-101054-V5(6PIN) 全贴合工程图-1028.pdf
。
如何画转接板
前面说了很多厂家的线序都是不一样的我们根本没办法直接接起来就用,因此中间需要加一个转接板,用来交换线序或者匹配不同接口座子,画起来非常简单,当然如果你手法好能够飞线那就当我没说过。
触摸链接座
屏幕厂家送了一更线,这根线两头座子还不一样,一头是6PIN,MOLEX 53261-0671 (1.25W-TO-B CONN)
接屏幕,另外一头是PH 2mm 1x6P
,所以我们转接板就要用PH 2mm 1x6P座子。
触摸链接线序
在触摸屏排线上面,或者FH-101054-V5(6PIN) 全贴合工程图-1028.pdf
文档中都有线序说明
使用JLCEDA专业版画转接板(文字很抽象,看直播视频演示)
打开jlceda
https://pro.lceda.cn/editor
新建工程
工程命名
打开原理图
shift+f搜素我们要的器件
C262262 FPC 6PinSMD P=0.5mm,卧贴 下接
C157920 PH 6Pin弯插,P=2mm
左边是泰山派接口,右边是触摸屏接口通过放置网路标签把他们连接起来
检查DRC,无错误无警告
更新到PCB
把器件摆好,鼠标选中器件,空格可以旋转器件
放置一个板框
放置单路布线把图片中的虚线全部连接起来
现在除了GND(地网络)以外其他全部已经连接,地网络我们通过铺铜来连接
放置和板框一样大小,把网络设置成GND,先铺顶层
在铺底面
铺铜以后还剩下一个网络没有连接,这是因为器件引脚间隙小于规则所以铺进不去,我们直接拉根线出来打个过孔,走线的时候按快捷键V放置过孔。
为了方便后面调试可以放置一些丝印,比如接口引脚线序,先选择丝印层,点击T放置字符,输入字符,改变线宽
检查DRC 没有错误没有警告
下单生产PCB(如果参加了训练营可以找开发菌发券),更具提示填写信息提交订单即可
下单采购元器件
为了防止你焊坏建议多买几个,比如我这里买10个,有十次焊接失败的机会
一键下单
板子回来焊接好以后最终效果如下,不会焊接FPC的可以看,这里转接板接泰山用的反排线FPC连接线
工程文件
工程导入方法开发板工程文件打开说明:点击跳转🚀
转接板工程文件下载
📌 在 下载中心
->百度网盘(立创·泰山派开发板资料)
->第09章.【立创·泰山派】项目案例
->【智能小手机】项目资料
->6.触摸驱动
->转接板
->泰山派10.1寸屏触摸转接板_2024-04-01.epro
。
触摸驱动
驱动位置
安卓和linux的触摸驱动位于都位于SDK/kernel/drivers/input/touchscreen
目录下这个目录下包含了非常多的常用触摸驱动我们当前的目标就是能够把这些驱动用起来理解框架后面在以此为模板自己在去写一个驱动。
通过屏幕实物上IC的丝印(有些厂家会磨掉)或者屏厂家给的资料我们可以得知,我们案例中这款触摸使用的触摸IC是经典汇顶GT9271,厂家有给触摸参考驱动GT9xx_Driver_for_Android_V2.4_2014112801.rar
,因为他很经典我们驱动已经支持了,所以我们直接在触摸驱动目录下找到汇顶相关的触摸驱动来做修改,前期大家如果不熟悉建议就买汇顶的触摸因为驱动比较通用,后面熟悉了以后可以使用其他的触摸并根据触摸厂家提供的参考代码修改移植。
汇顶相关的驱动位于SDK/kernel/drivers/input/touchscreen/gt9xx
下这个驱动包含兼容了gt9开头的大多数系列,比如gt911、gt910、gt9271等等。
我们主要关注gt9xx.c
触摸功能相关的实现、gt9xx.h头文件、Makefile配置文件,其他的.cfg、gt9xx_cfg.h
是用于多芯片兼容,gt9xx_update.c
、gt9xx_firmware.h
固件更新与goodix_tool.c
调试的。
驱动配置进内核
我们的sdk中gt9xx驱动是已经默认配置进了内核的(不感兴趣可跳过这一小节),这里我们一起简单的过一下驱动配置流程。
Makefile配置文件就是用来组织编译的,所以我们从SDK/kernel/drivers/input/touchscreen/gt9xx
目录下的Makefile开始逐步向上分析
#SPDX-License-Identifier: GPL-2.0# 使用 GPL-2.0 许可证声明
#将 goodix_gt9xx.o 目标文件添加到编译选项 obj-y 中
obj-y += goodix_gt9xx.o
#将 gt9xx.o 目标文件添加到 goodix_gt9xx-y 目标列表中,用于编译链接
goodix_gt9xx-y += gt9xx.o
#将 gt9xx_update.o 目标文件也添加到 goodix_gt9xx-y 目标列表中,用于编译链接
goodix_gt9xx-y += gt9xx_update.o
2
3
4
5
6
7
8
9
10
SDK/kernel/drivers/input/touchscreen/
Makefile
#省略
obj-$(CONFIG_TOUCHSCREEN_GT9XX) += gt9xx/
#省略
2
3
这里我们看到一个变量CONFIG_TOUCHSCREEN_GT9XX
,Makefile中就是通过这个变量值来判定是否编译gt9xx的这个变量的值有三个y、m、n
分别对应配置到内核、模块、不打开,我们可以通过暴力手段直接改,但这不是明智之举,适合用于调试阶段。
config TOUCHSCREEN_GT9XX
tristate "Goodix gt9xx support for rockchip platform"
depends on I2C && ARCH_ROCKCHIP
help
Say Y here if you have a touchscreen interface using the gt9xx
on Rockchip platform, and your board-specific initialization
code includes that in its table of IIC devices.
If unsure, say N.
2
3
4
5
6
7
8
在同Makefile
同目录下还有一个Kconfig
有了这个我们就可以使用menuconfig工具来配置。
config TOUCHSCREEN_GT9XX
tristate "Goodix gt9xx support for rockchip platform"
depends on I2C && ARCH_ROCKCHIP
help
Say Y here if you have a touchscreen interface using the gt9xx
on Rockchip platform, and your board-specific initialization
code includes that in its table of IIC devices.
If unsure, say N.
2
3
4
5
6
7
8
menuconfig
kenrnel目录下运行
make ARCH=arm64 menuconfig
依次进入目录(键盘上下键移动,回车进入)
│ -> Device Drivers │
│ -> Input device support │
│ -> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y]) │
-> Touchscreens (INPUT_TOUCHSCREEN [=y])
2
3
4
5
这里已经默认编译进入内核了,我们可以通过键盘上的m,y,n配置
我这里改成n进行测试,【注意你不要动不要动不要动不要动ESC退出就行】,我只是演示私底下还会改回来的,键盘左右按键移动到Save然后回车保存。
OK回车
退回到界面
退出所有路径直到退出menuconfig界面
因为我们刚刚保存的是.config每次编译的时候脚本都会去组合生产.config所以你的配置就会被覆盖掉,这也是很多小伙伴经常提问的为什么配置没生效,正确的方式是生成defconfig然后并覆盖到之前的arch/arm64/configs/
下代替我们使用的defconfig
make ARCH=arm64 savedefconfig
我们SDK使用的是rockchip_linux_defconfig
,所以把生成的defconfig复制过去代替rockchip_linux_defconfig
我用的是repo带git版本所有我就直接复制过去了,如果你没有git管理就先备份一个然后在代替过去,避免搞坏了回不去了。
mv defconfig arch/arm64/configs/rockchip_linux_defconfig
验证 把gt9xx.c生成的gt9xx.o删除掉在重新编译就不会在生产gt9xx.o文件了,这是因为我们上面的配置不编译gt9xx驱动模块生效了
单独编译内核不会的看编译教程SDK编译(点击跳转🚀),编译成功后没有在生成gt9xx.o
配置设备树
泰山触摸相关的设备树SDK\kernel\arch\arm64\boot\dts\rockchip\tspi-rk3566-dsi-v10.dtsi
中添加,根据GT9xx_Driver_for_Android_V2.4_2014112801dtsi\goodix-gt9xx.dtsi
参考修改而来
&i2c1 {
status = "okay";
ts@5d {
compatible = "goodix,gt9xx";
reg = <0x5d>;
tp-size = <89>;
max-x = <1280>;
max-y = <800>;
touch-gpio = <&gpio1 RK_PA0 IRQ_TYPE_LEVEL_LOW>;
reset-gpio = <&gpio1 RK_PA1 GPIO_ACTIVE_LOW>;
};
};
2
3
4
5
6
7
8
9
10
11
12
I2C1
//i2c1 节点追加
&i2c1 {
status = "okay";
/*加触摸*/
};
2
3
4
5
硬件部分通过原理图可以知道触摸接口用的是i2c1所以我们就直接在i2c1中写触摸。我们原理图中已经对数据线和时钟先进行了上拉。
触摸节点
&i2c1 {
status = "okay";
ts@5d { //触摸子节点
compatible = "goodix,gt9xx"; //这个非常重要,就是靠这个来匹配的驱动
};
};
2
3
4
5
6
gt9xx.c触摸部分代码
static struct of_device_id goodix_ts_dt_ids[] = {
{ .compatible = "goodix,gt9xx" },
{ }
};
static struct i2c_driver goodix_ts_driver = {
.probe = goodix_ts_probe,
.remove = goodix_ts_remove,
.id_table = goodix_ts_id,
.driver = {
.name = GTP_I2C_NAME,
.of_match_table = of_match_ptr(goodix_ts_dt_ids),
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
地址
&i2c1 {
status = "okay";
ts@5d { //触摸子节点
compatible = "goodix,gt9xx";
reg = <0x5d>; //触摸屏地址
};
};
2
3
4
5
6
7
因为一个i2c下可以挂载多个从设备,既然可以挂那么多设备我们要怎么和单个设备通讯呢,地址的作用就来了,我想和谁通讯我就叫谁的地址。我们怎么知道用的屏幕的地址是多少呢?
- 查找厂家要,比如这里厂家给的参考代码里面就已经包含地址了0x5d
- 看触摸屏数据手册,这其实也是找厂家要的
数据手册中有对地址进行描述这里写的是0xBA/0xBB,有些同学可能会迷糊上面说的是0x5d这里怎么变了,我们上面说的是7位地址,这里是8位包含第零位读写位,所以我不把0xBB/0xBA右移1位就可以得到0x5D
参数
&i2c1 {
status = "okay";
ts@5d {
compatible = "goodix,gt9xx";
reg = <0x5d>;
tp-size = <89>; //触摸大小
max-x = <1280>; //屏幕最大值
max-y = <800>; //屏幕最小值
};
};
2
3
4
5
6
7
8
9
10
设备树中触摸大小,x和y最大值这些参数来自于gt9xx.c源码里面,源码中会去获取这些值,根据屏幕的不同对应的也不同。
//2649行
/**************删减***************/
if (of_property_read_u32(np, "tp-size", &val)) {
dev_err(&client->dev, "no max-x defined\n");
return -EINVAL;
}
//2715行
if (of_property_read_u32(np, "max-x", &val)) {
dev_err(&client->dev, "no max-x defined\n");
return -EINVAL;
}
//2720行
/**************删减***************/
//ts->abs_x_max = val;
if (of_property_read_u32(np, "max-y", &val)) {
dev_err(&client->dev, "no max-y defined\n");
return -EINVAL;
}
/**************删减***************/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
中断引脚
去读取数据方式很多种比如我们可以通过轮询去读,但是这样的效率非常低,触摸屏已经给我们提供了一个中断引脚,下面是触摸屏数据手册中对中断引脚的描述,当屏幕被按下INT脚就会输出中断信号,我们泰山派配置了中断就可以实现中断来了以后再去读取触摸数据,提高了效率。
设备树中描述中断引脚
&i2c1 {
status = "okay";
ts@5d {
compatible = "goodix,gt9xx";
reg = <0x5d>;
tp-size = <89>;
max-x = <1280>;
max-y = <800>;
//中断引脚
touch-gpio = <&gpio1 RK_PA0 IRQ_TYPE_LEVEL_LOW>;
};
};
2
3
4
5
6
7
8
9
10
11
12
gt9xx.c中会去获取touch-gpio
引脚
//2711行
ts->irq_pin = of_get_named_gpio_flags(np, "touch-gpio", 0, (enum of_gpio_flags *)(&ts->irq_flags));
2
从原理图中可以知道中断引脚使用的时候GPIO1_A0所以对应&gpio1 RK_PA0
我们选择低电平触发IRQ_TYPE_LEVEL_LOW
复位引脚
&i2c1 {
status = "okay";
ts@5d {
compatible = "goodix,gt9xx";
reg = <0x5d>;
tp-size = <89>;
max-x = <1280>;
max-y = <800>;
touch-gpio = <&gpio1 RK_PA0 IRQ_TYPE_LEVEL_LOW>;
//复位引脚
reset-gpio = <&gpio1 RK_PA1 GPIO_ACTIVE_LOW>;
};
};
2
3
4
5
6
7
8
9
10
11
12
13
在gt9xx.c中会去获取引脚
//2712行
ts->rst_pin = of_get_named_gpio_flags(np, "reset-gpio", 0, &rst_flags);
2
在原理图中使用了GPIO1_A1所以对应的就是gpio1 RK_PA1,
低电平有效GPIO_ACTIVE_LOW
触摸测试
【泰山派10.1寸触摸补丁.zip】下载
📌 在 下载中心
->百度网盘(立创·泰山派开发板资料)
->第09章.【立创·泰山派】项目案例
->【智能小手机】项目资料
->6.触摸驱动
->代码补丁
->泰山派10.1寸触摸补丁.zip
。
- 打补丁看这里:
- 单独编译内核与下载固件:
实验效果
触摸驱动解析
想就gt9xx.c进行一下驱动解析的,但gt9xx.c里面做了很多兼容很多宏导致代码看起来很乱很复杂,索性我们就一起从头开始写一个触摸驱动,第一步:写一个模拟触摸驱动,并定时上报触摸数据,第二步:接上上面的gt9xx屏幕去读屏幕里面的数据进行上报。
新建触摸驱动
在SDK/kernel/drivers/input/touchscreen
下新建个目录这里我们就命名叫my_touch
,当然你在其他目录先新建也是可以的,关键是你的makefile文件中去指定。
kernel/drivers/input/touchscreen$ mkdir my_touch/
切换到my_touch目录
cd my_touch
在my_touch目录下创建我们的触摸驱动my_touch.c
后面我们的模拟驱动就写在这个里面
touch my_touch.c
往my_touch.c
里填写驱动代码
#include <linux/kernel.h>
#include <linux/hrtimer.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/input/mt.h>
#include <linux/random.h>
#if 1
#define MY_DEBUG(fmt,arg...) printk("MY_TOUCH:%s %d "fmt"",__FUNCTION__,__LINE__,##arg);
#else
#define MY_DEBUG(fmt,arg...)
#endif
struct input_dev *input_dev;
static struct timer_list my_timer;
void my_timer_callback(struct timer_list *timer)
{
unsigned int x, y; // 定义无符号整型变量 x 和 y
static bool isDown = false; // 定义静态布尔变量 isDown,并初始化为 false
// 生成随机数 x,取模得到的值在 [0, 1279] 范围内
get_random_bytes(&x, sizeof(x));
x %= 1280;
// 生成随机数 y,取模得到的值在 [0, 1279] 范围内
get_random_bytes(&y, sizeof(y));
y %= 800;
// 打印调试信息,包括 isDown 的值、x 和 y 的值
MY_DEBUG("isDown:%d x:%d y:%d!\n", isDown, x, y);
// 设定输入设备的触摸槽位
input_mt_slot(input_dev, 0);
// 报告输入设备的触摸槽位状态,MT_TOOL_FINGER 表示手指状态,isDown 表示是否按下
input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, isDown);
// 翻转 isDown 的值模仿手抬起和按下
isDown = !isDown;
// 报告输入设备的绝对位置信息:x、y 坐标,触摸面积,触摸宽度
input_report_abs(input_dev, ABS_MT_POSITION_X, x);
input_report_abs(input_dev, ABS_MT_POSITION_Y, y);
// 报告输入设备的指针仿真信息
input_mt_report_pointer_emulation(input_dev, true);
// 同步输入事件
input_sync(input_dev);
// 重新设置定时器,2 秒后再次触发
mod_timer(timer, jiffies + msecs_to_jiffies(200));
}
static int my_touch_ts_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int ret;
// 打印调试信息
MY_DEBUG("locat");
// 分配输入设备对象
input_dev = devm_input_allocate_device(&client->dev);
if (!input_dev) {
dev_err(&client->dev, "Failed to allocate input device.\n");
return -ENOMEM;
}
// 设置输入设备的名称和总线类型
input_dev->name = "my touch screen";
input_dev->id.bustype = BUS_I2C;
/*设置触摸 x 和 y 的最大值*/
// 设置输入设备的绝对位置参数
input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, 1280, 0, 0);
input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, 800, 0, 0);
// 初始化多点触摸设备的槽位
ret = input_mt_init_slots(input_dev, 5, INPUT_MT_DIRECT);
if (ret) {
dev_err(&client->dev, "Input mt init error\n");
return ret;
}
// 注册输入设备
ret = input_register_device(input_dev);
if (ret)
return ret;
// 初始化定时器
timer_setup(&my_timer, my_timer_callback, 0);
// 设置定时器,5 秒后第一次触发
mod_timer(&my_timer, jiffies + msecs_to_jiffies(5000));
return 0;
}
static int my_touch_ts_remove(struct i2c_client *client)
{
MY_DEBUG("locat");
return 0;
}
static const struct of_device_id my_touch_of_match[] = {
{ .compatible = "my,touch", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_touch_of_match);
static struct i2c_driver my_touch_ts_driver = {
.probe = my_touch_ts_probe,
.remove = my_touch_ts_remove,
.driver = {
.name = "my-touch",
.of_match_table = of_match_ptr(my_touch_of_match),
},
};
static int __init my_ts_init(void)
{
MY_DEBUG("locat");
return i2c_add_driver(&my_touch_ts_driver);
}
static void __exit my_ts_exit(void)
{
MY_DEBUG("locat");
i2c_del_driver(&my_touch_ts_driver);
}
module_init(my_ts_init);
module_exit(my_ts_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("My touch driver");
MODULE_AUTHOR("wucaicheng@qq.com");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
my_touch.c
这个文件需要被编译,所以我们还需要创建一个Makefile文件,并填入下面内容
#如果编译成.ko的话就选obj-m,如果编译到内核就obj-y
obj-y += my_touch.o
2
touchscreen
目录下的Makefile中也需要指定my_touch目录
#obj-n 把gt9xx给屏蔽掉避免后面影响
obj-n += gt9xx/
#obj-y 编译我们的my_touch
obj-y += my_touch/
2
3
4
接着还需要在设备树中添加我们的模拟触摸驱动,在SDK\kernel\arch\arm64\boot\dts\rockchip\tspi-rk3566-dsi-v10.dtsi &i2c1
中添加
myts@5d {
compatible = "my,touch";
reg = <0x5d>;
tp-size = <89>;
max-x = <1280>;
max-y = <800>;
touch-gpio = <&gpio1 RK_PA0 IRQ_TYPE_LEVEL_LOW>;
reset-gpio = <&gpio1 RK_PA1 GPIO_ACTIVE_HIGH>;
};
2
3
4
5
6
7
8
9
补丁
【代码补丁】下载
📌 在 下载中心
->百度网盘(立创·泰山派开发板资料)
->第09章.【立创·泰山派】项目案例
->【智能小手机】项目资料
->6.触摸驱动
->代码补丁
->my_touch_patch.zip
。
验证
编译进入内核
前面我们my_touch目录下的Makefile中是obj-y所以是编入到内核的,我们只需要重新编译内核并单独烧录内核就可以生效。单独编译和烧录内核的方法我们前面介绍了很多次了所以不在演示
编译成模块
编译进内核并单独烧入Boot带emmc操作起来很方便,如果没有emmc的可以生成模块ko并把ko传到泰山派上加载,编译成模块需要把my_touch目录下Makefile改成obj-m
#如果编译成.ko的话就选obj-m,如果编译到内核就obj-y
obj-m += my_touch.o
2
单独编译驱动没有报错成功会生成my_touch.ko
kernel$ make ARCH=arm64 -C . M=./drivers/input/touchscreen/my_touch
make
命令用于构建 Linux 内核或内核模块。ARCH=arm64
指定了目标架构为 64 位 ARM 架构-C
. 指定了内核源代码目录为当前目录M=./drivers/input/touchscreen/my_touch/
指定了我们触摸也就是要构建的内核模块所在的相对路径为./drivers/input/touchscreen/my_touch/
。
加载my_touch.ko
通过adb把my_touch.ko push到泰山派里面去
adb root && adb remount && adb push Z:\tspi\Android11_20231007\PublicVersion\kernel\drivers\input\touchscreen\my_touch\my_touch.ko /vendor/lib/modules
adb root
: 这个命令会尝试获取 Android 设备的 root 权限。在有些设备上,访问/vendor/lib/modules
目录需要 root 权限。adb remount
: 这个命令会重新挂载文件系统,通常用于将文件系统从只读模式切换为可读写模式。因为你要往/vendor/lib/modules
目录写入文件,所以需要将其挂载为可读写。adb push Z:\tspi\Android11_20231007\PublicVersion\kernel\drivers\input\touchscreen\my_touch\my_touch.ko /vendor/lib/modules: 这个命令的格式是 adb push <本地路径> <目标路径>
,它会将本地路径下的文件推送到目标路径。在这里,my_touch.ko
是你要推送的文件,它位于Z:\tspi\Android11_20231007\PublicVersion\kernel\drivers\input\touchscreen\my_touch\
这个本地路径下。它将被推送到 Android 设备的/vendor/lib/modules
目录中。
adb进入shell
安装驱动
rk3566_tspi:/ # insmod /vendor/lib/modules/my_touch.ko
查看驱动是否安装成功
rk3566_tspi:/ # lsmod
Module Size Used by
my_touch 16384 0
bcmdhd 1175552 0
2
3
4
查看日志可以看到我们模拟的驱动在上报数据了
效果
显示屏随机上报触摸点
驱动框架
上面在没有接触摸的情况下我们一起创建了一个my_touch.c驱动实现了随机胡乱上报触摸点,但是虽然代码非常简单可能有些新入门的小伙伴会犯迷糊,所以下面我们就一起来逐句分析。首先我们先了解一下整体的框架,所以我把不相干的都删掉了,就留下了一个最简单的框架,我们通过打印信息看看他是如何执行的。
/*
* 有些同学会好奇我怎么知道包含哪些头文件,其实我也不是一个个去找到的,
* 我直接把gt9xx那里复制过来的,后面缺少什么就在加什么
* 这里是驱动所依赖的头文件,它们提供了驱动编写所需的各种函数和宏定义。
*/
#include <linux/kernel.h> // 内核常用宏和函数
#include <linux/hrtimer.h> // 高精度定时器
#include <linux/i2c.h> // I2C总线支持
#include <linux/input.h> // 输入设备支持
#include <linux/module.h> // 模块支持
#include <linux/delay.h> // 延时函数
#include <linux/proc_fs.h> // /proc文件系统支持
#include <linux/string.h> // 字符串操作
#include <linux/uaccess.h> // 用户空间访问支持
#include <linux/vmalloc.h> // 虚拟内存分配
#include <linux/interrupt.h> // 中断处理
#include <linux/io.h> // IO操作
#include <linux/of_gpio.h> // Open Firmware GPIO支持
#include <linux/gpio.h> // GPIO操作
#include <linux/slab.h> // 内存分配(如kmalloc和kfree)
/*
* 这里我简单的封装了一个打印函数,回打印对应的函数和行号,方便定位消息
* MY_DEBUG宏用于调试,当#if 1为真时,它将使用printk打印调试信息,包括函数名和行号。
*/
#if 1
#define MY_DEBUG(fmt,arg...) printk("MY_TOUCH:%s %d "fmt"",__FUNCTION__,__LINE__,##arg);
#else
#define MY_DEBUG(fmt,arg...) // 当#if 0时,MY_DEBUG不执行任何操作
#endif
/*
* 探针函数
* 这是I2C驱动的核心函数之一,当I2C总线上有新的设备被识别时,这个函数会被调用。
*/
static int my_touch_ts_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
MY_DEBUG("probe"); // 打印调试信息
return 0; // 返回0表示成功
}
/*
* 移除函数
* 当I2C设备从总线上移除时,这个函数会被调用。
*/
static int my_touch_ts_remove(struct i2c_client *client)
{
MY_DEBUG("remove"); // 打印调试信息
return 0; // 返回0表示成功
}
/*
* 设备匹配表
* 用于匹配设备树中的设备节点。
*/
static const struct of_device_id my_touch_of_match[] = {
{ .compatible = "my,touch", }, // 兼容的设备树节点名称
{ /* sentinel */ } // 列表结束标志
};
MODULE_DEVICE_TABLE(of, my_touch_of_match); // 注册设备匹配表
/*
* I2C驱动结构体
* 定义了I2C驱动的基本信息。
*/
static struct i2c_driver my_touch_ts_driver = {
.probe = my_touch_ts_probe, // 探针函数 ,用于探测和初始化I2C设备
.remove = my_touch_ts_remove, // 移除函数 ,用于清理和卸载I2C设备
.driver = {
.name = "my-touch", // 驱动名称
.of_match_table = of_match_ptr(my_touch_of_match), // 设备树匹配表
},
};
/*
* 模块初始化函数
* 当模块加载时,这个函数会被调用。
*/
static int __init my_ts_init(void)
{
MY_DEBUG("init"); // 打印调试信息
return i2c_add_driver(&my_touch_ts_driver); // 注册I2C驱动
}
/*
* 模块退出函数
* 当模块卸载时,这个函数会被调用。
*/
static void __exit my_ts_exit(void)
{
MY_DEBUG("exit"); // 打印调试信息
i2c_del_driver(&my_touch_ts_driver); // 注销I2C驱动
}
/*
* 使用module_init和module_exit宏来指定模块的初始化和退出函数。
* 当模块被加载时,my_ts_init函数会被调用;当模块被卸载时,my_ts_exit函数会被调用。
*/
module_init(my_ts_init); // 模块初始化时调用my_ts_init函数
module_exit(my_ts_exit); // 模块退出时调用my_ts_exit函数
/*
* 模块的许可证、描述和作者信息
* 这些信息会被内核用来标识和管理模块。
*/
MODULE_LICENSE("GPL"); // 模块使用GNU通用公共许可证
MODULE_DESCRIPTION("My touch driver"); // 模块描述
MODULE_AUTHOR("wucaicheng@qq.com"); // 模块作者
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
加载驱动流程
module_init(my_ts_init);
my_ts_init
i2c_del_driver(&my_touch_ts_driver); // 注销I2C驱动
static const struct of_device_id my_touch_of_match[] = {
{ .compatible = "my,touch", }, // 兼容的设备树节点名称
{ /* sentinel */ } // 列表结束标志
};
static struct i2c_driver my_touch_ts_driver = {
.probe = my_touch_ts_probe,
.remove = my_touch_ts_remove,
.driver = {
// 驱动名称,加载后在/sys/bus/i2c/drivers/下可见为my-touch
.name = "my-touch",
.of_match_table = of_match_ptr(my_touch_of_match),
},
};
my_touch_ts_probe //当"my,touch"和设备树中匹配成功以后就执行此函数
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
按照前面步骤编译并安装驱动打印日志,可以看到驱动被安装后执行的函数
卸载驱动流程
module_exit(my_ts_exit);
my_ts_exit
my_touch_ts_remove
2
3
卸载驱动并打印日志,可以看到驱动被卸载后执行的函数
上面框架是通用的,你可以在touchscrren或者其他驱动下面随机找个基本上都是这个框架有差别的地方可能会是i2c_add_driver
如果他不是i2c驱动就是注册的其他的
初始化触摸
上面框架我们搞定了接下来我们来模拟一个触摸上报触摸事件,其实这里涉及到input子系统但是没有关系,大家理解就是是调用api就行,套路就是申请驱动、配置参数、调用相关api上报数据,就和我们stm32的外设初始一样一样的。
分配输入设备
要用触摸不可能凭空就用吧,我们需要先进调用devm_input_allocate_device
分配输入设备
input_dev = devm_input_allocate_device(&client->dev);
if (!input_dev) {
dev_err(&client->dev, "Failed to allocate input device.\n");
return -ENOMEM;
}
2
3
4
5
指定名字与设备类型
刚分配出来的没有名字,所以我们是不是要给取个名字,并指定一下设备类型
input_dev->name = "my touch screen";
input_dev->id.bustype = BUS_I2C;
2
在泰山派系统中可以通用cat /proc/bus/input/devices
或者getevent
(安卓)等查看到"my touch screen"
设置触摸参数
设置一下我们屏幕的x和y的最大值和最小值,很多小伙伴在群里反馈触摸的范围很多但是反馈到屏幕上就一点点就是这个地方设置错误了,这个值一定要和你的屏幕大小是一样的。
input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, 1280, 0, 0);
input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, 800, 0, 0);
2
input_dev
:struct input_dev
结构的指针,表示要设置参数的输入设备。struct input_dev
包含了输入设备的各种属性和状态信息。axis
:表示要设置参数的轴。在你的示例中,ABS_MT_POSITION_X
表示设置 X 轴的绝对坐标参数。min
:表示轴的最小值,设置了 X 轴的最小值为0。max
:表示轴的最大值,设置了 X 轴的最大值为1280。fuzz
:表示轴的模糊度。模糊度用于指定轴值的偏差范围,设置了 X 轴的模糊度为0,表示轴值的偏差范围为0。flat
:表示轴的平坦度。平坦度用于指定轴值的线性度,设置了 X 轴的平坦度为0,表示轴值的线性度为0。
初始多点触摸
gt9xx这个款屏幕是支持多个点同时触摸的,大家平时手机截屏不要是几个手指头一起按下的这就是多点触摸,所以我们这里在通过input_mt_init_slots
初始多点触摸。
ret = input_mt_init_slots(input_dev, 5, INPUT_MT_DIRECT);
if (ret) {
dev_err(&client->dev, "Input mt init error\n");
return ret;
}
2
3
4
5
input_dev
: struct input_dev 结构的指针,表示要设置参数的输入设备。struct input_dev
包含了输入设备的各种属性和状态信息。num_slots
:这里设置成5个点,flags
:INPUT_MT_DIRECT为触摸设备,当然还可选其他值比如INPUT_MT_POINTER表示指针设备
注册输入设备
最后我们来注册输入设备
ret = input_register_device(input_dev);
if (ret)
return ret;
2
3
注销输入设备
有注册当然就会伴随的有注销,上面留了个坑没有注销函数,所以你rmmod的时候系统回崩溃
static int my_touch_ts_remove(struct i2c_client *client)
{
MY_DEBUG("locat");
input_unregister_device(input_dev);
return 0;
}
2
3
4
5
6
上报触摸数据
选择那个点上报
前面我们说了这个一个多点触控,触摸屏同时会支持多个点触摸比如gt9271就支持10个点同时触摸,所以我们上报的时候也需要选择,当前上报的是第几个点的数据。
input_mt_slot(input_dev, 0);
状态
接下来我们要告诉系统,我们是按下触摸还是抬起触摸。
//按下
input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, true);
//松开
input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, false);
2
3
4
input_dev
:struct input_dev
结构的指针,表示要设置参数的输入设备。struct input_dev
包含了输入设备的各种属性和状态信息。tool_type
:表示触摸点的工具类型。MT_TOOL_FINGER
这里表示手指头。active
:这是一个布尔值参数,用于指示触摸点的状态。如果active
为true
,表示触摸点处于活跃状态;如果active
为false
,表示触摸点处于非活跃状态。
坐标
如果是按下我们就上传坐标信息
input_report_abs(input_dev, ABS_MT_POSITION_X, x);
input_report_abs(input_dev, ABS_MT_POSITION_Y, y);
2
启用指针仿真
该模式允许将多点触控事件转换为鼠标或指针事件
input_mt_report_pointer_emulation(input_dev, true);
同步事件
确保之前通过input_report_abs
、input_mt_report_slot_state
等函数报告的所有事件都被同步到输入子系统,并且作为一个完整的事件集进行处理。没有调用input_sync之前,这些事件是挂起的,不会被系统或用户空间的应用程序看到。
input_sync(input_dev);
定时器
初始化
因为没有实际的触摸屏所以我们通过一个定时器模仿触摸按下
//定时器回调函数
void my_timer_callback(struct timer_list *timer){
//为了实现重复
mod_timer(timer, jiffies + msecs_to_jiffies(200));
}
2
3
4
5
// 初始化定时器
timer_setup(&my_timer, my_timer_callback, 0);
2
修改定时触发时间这里是5s后回触发并调用回调函数
// 设置定时器,5 秒后第一次触发
mod_timer(&my_timer, jiffies + msecs_to_jiffies(5000));
2
删除
del_timer_sync(&my_timer);
实现自己的触摸驱动
#include "linux/stddef.h"
#include <linux/kernel.h>
#include <linux/hrtimer.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/input/mt.h>
#include <linux/random.h>
#define MY_SWAP(x, y) do{\
typeof(x) z = x;\
x = y;\
y = z;\
}while (0)
#if 1
#define MY_DEBUG(fmt,arg...) printk("MY_TOUCH:%s %d "fmt"",__FUNCTION__,__LINE__,##arg);
#else
#define MY_DEBUG(fmt,arg...)
#endif
struct my_touch_dev {
struct i2c_client *client;
struct input_dev *input_dev;
int rst_pin;
int irq_pin;
u32 abs_x_max;
u32 abs_y_max;
int irq;
};
s32 my_touch_i2c_read(struct i2c_client *client,u8 *addr,u8 addr_len, u8 *buf, s32 len)
{
struct i2c_msg msgs[2];
s32 ret=-1;
msgs[0].flags = !I2C_M_RD;
msgs[0].addr = client->addr;
msgs[0].len = addr_len;
msgs[0].buf = &addr[0];
msgs[1].flags = I2C_M_RD;
msgs[1].addr = client->addr;
msgs[1].len = len;
msgs[1].buf = &buf[0];
ret = i2c_transfer(client->adapter, msgs, 2);
if(ret == 2)return 0;
if(addr_len == 2){
MY_DEBUG("I2C Read: 0x%04X, %d bytes failed, errcode: %d! Process reset.", (((u16)(addr[0] << 8)) | addr[1]), len, ret);
}else {
MY_DEBUG("I2C Read: 0x%02X, %d bytes failed, errcode: %d! Process reset.", addr[0], len, ret);
}
return -1;
}
s32 my_touch_i2c_write(struct i2c_client *client, u8 *addr, u8 addr_len, u8 *buf,s32 len)
{
struct i2c_msg msg;
s32 ret = -1;
u8 *temp_buf;
msg.flags = !I2C_M_RD;
msg.addr = client->addr;
msg.len = len+addr_len;
temp_buf= kzalloc(msg.len, GFP_KERNEL);
if (!temp_buf){
goto error;
}
// 装填地址
memcpy(temp_buf, addr, addr_len);
// 装填数据
memcpy(temp_buf + addr_len, buf, len);
msg.buf = temp_buf;
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret == 1) {
kfree(temp_buf);
return 0;
}
error:
if(addr_len == 2){
MY_DEBUG("I2C Read: 0x%04X, %d bytes failed, errcode: %d! Process reset.", (((u16)(addr[0] << 8)) | addr[1]), len, ret);
}else {
MY_DEBUG("I2C Read: 0x%02X, %d bytes failed, errcode: %d! Process reset.", addr[0], len, ret);
}
if (temp_buf)
kfree(temp_buf);
return -1;
}
static irqreturn_t my_touch_irq_handler(int irq, void *dev_id)
{
s32 ret = -1;
struct my_touch_dev *ts = dev_id;
u8 addr[2] = {0x81,0x4E};
u8 clear_buf[1] = {0};
u8 point_data[1+8*1]={0};//1个状态位置+10个触摸点,一个点是8个数据组成
u8 touch_num = 0;
u8 buf_stats = 0;
u8 *coor_data;
int id,input_x,input_y,input_w;
MY_DEBUG("irq");
ret = my_touch_i2c_read(ts->client, addr,sizeof(addr), point_data, sizeof(point_data));
if (ret < 0){
MY_DEBUG("I2C write end_cmd error!");
}
touch_num = point_data[0]&0x0f;
buf_stats = point_data[0]&0x80>>7;
MY_DEBUG("0x814E=:%0x,touch_num:%d,buf_stats:%d",point_data[0],touch_num,buf_stats);
//获取
if (touch_num){
coor_data = &point_data[1];
id = coor_data[0] & 0x0F;
input_x = coor_data[1] | (coor_data[2] << 8);
input_y = coor_data[3] | (coor_data[4] << 8);
input_w = coor_data[5] | (coor_data[6] << 8);
MY_DEBUG("id:%d,x:%d,y:%d,w:%d",id,input_x,input_y,input_w);
// // 设定输入设备的触摸槽位
input_mt_slot(ts->input_dev, 0);
// 报告输入设备的触摸槽位状态,MT_TOOL_FINGER 表示手指状态,isDown 表示是否按下
input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
// 翻转 isDown 的值模仿手抬起和按下
MY_SWAP(input_x, input_y);
// 报告输入设备的绝对位置信息:x、y 坐标,触摸面积,触摸宽度
input_report_abs(ts->input_dev, ABS_MT_POSITION_X, 800-input_x);
input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, input_y);
}else {
input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, false);
}
// 清除寄存器,要不然回反复触发
ret = my_touch_i2c_write(ts->client, addr,sizeof(addr), clear_buf, sizeof(clear_buf));
if (ret < 0){
MY_DEBUG("I2C write end_cmd error!");
}
// 报告输入设备的指针仿真信息
input_mt_report_pointer_emulation(ts->input_dev, true);
// 同步输入事件
input_sync(ts->input_dev);
return IRQ_HANDLED;
}
s32 gt9271_read_version(struct i2c_client *client)
{
s32 ret = -1;
u8 addr[2] = {0x81,0x40};
u8 buf[6] = {0};
ret = my_touch_i2c_read(client, addr,sizeof(addr), buf, sizeof(buf));
if (ret < 0){
MY_DEBUG("GTP read version failed");
return ret;
}
if (buf[5] == 0x00){
MY_DEBUG("IC Version: %c%c%c_%02x%02x", buf[0], buf[1], buf[2], buf[5], buf[4]);
}
else{
MY_DEBUG("IC Version: %c%c%c%c_%02x%02x", buf[0], buf[1], buf[2], buf[3], buf[5], buf[4]);
}
return ret;
}
static int my_touch_ts_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int ret;
struct my_touch_dev *ts;
struct device_node *np = client->dev.of_node;
// 打印调试信息
MY_DEBUG("locat");
// ts = kzalloc(sizeof(*ts), GFP_KERNEL);
ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
if (ts == NULL){
dev_err(&client->dev, "Alloc GFP_KERNEL memory failed.");
return -ENOMEM;
}
ts->client = client;
i2c_set_clientdata(client, ts);
if (of_property_read_u32(np, "max-x", &ts->abs_x_max)) {
dev_err(&client->dev, "no max-x defined\n");
return -EINVAL;
}
MY_DEBUG("abs_x_max:%d",ts->abs_x_max);
if (of_property_read_u32(np, "max-y", &ts->abs_y_max)) {
dev_err(&client->dev, "no max-y defined\n");
return -EINVAL;
}
MY_DEBUG("abs_x_max:%d",ts->abs_y_max);
//找复位gpio
ts->rst_pin = of_get_named_gpio(np, "reset-gpio", 0);
//申请复位gpio
ret = devm_gpio_request(&client->dev,ts->rst_pin,"my touch touch gpio");
if (ret < 0){
dev_err(&client->dev, "gpio request failed.");
return -ENOMEM;
}
//找中断引进
ts->irq_pin = of_get_named_gpio(np, "touch-gpio", 0);
/* 申请使用管脚 */
ret = devm_gpio_request_one(&client->dev, ts->irq_pin,
GPIOF_IN, "my touch touch gpio");
if (ret < 0)
return ret;
gpio_direction_output(ts->rst_pin,0);
msleep(20);
gpio_direction_output(ts->irq_pin,0);
msleep(2);
gpio_direction_output(ts->rst_pin,1);
msleep(6);
gpio_direction_output(ts->irq_pin, 0);
gpio_direction_output(ts->irq_pin, 0);
msleep(50);
//申请中断
ts->irq = gpio_to_irq(ts->irq_pin);
if(ts->irq){
ret = devm_request_threaded_irq(&(client->dev), ts->irq, NULL,
my_touch_irq_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT ,
client->name, ts);
if (ret != 0) {
MY_DEBUG("Cannot allocate ts INT!ERRNO:%d\n", ret);
return ret;
}
}
// 分配输入设备对象
ts->input_dev = devm_input_allocate_device(&client->dev);
if (!ts->input_dev) {
dev_err(&client->dev, "Failed to allocate input device.\n");
return -ENOMEM;
}
// 设置输入设备的名称和总线类型
ts->input_dev->name = "my touch screen";
ts->input_dev->id.bustype = BUS_I2C;
/*设置触摸 x 和 y 的最大值*/
// 设置输入设备的绝对位置参数
input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, 800, 0, 0);
input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, 0, 1280, 0, 0);
// 初始化多点触摸设备的槽位
ret = input_mt_init_slots(ts->input_dev, 5, INPUT_MT_DIRECT);
if (ret) {
dev_err(&client->dev, "Input mt init error\n");
return ret;
}
// 注册输入设备
ret = input_register_device(ts->input_dev);
if (ret)
return ret;
gt9271_read_version(client);
return 0;
}
static int my_touch_ts_remove(struct i2c_client *client)
{
struct my_touch_dev *ts = i2c_get_clientdata(client);
MY_DEBUG("locat");
input_unregister_device(ts->input_dev);
return 0;
}
static const struct of_device_id my_touch_of_match[] = {
{ .compatible = "my,touch", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_touch_of_match);
static struct i2c_driver my_touch_ts_driver = {
.probe = my_touch_ts_probe,
.remove = my_touch_ts_remove,
.driver = {
.name = "my-touch",
.of_match_table = of_match_ptr(my_touch_of_match),
},
};
static int __init my_ts_init(void)
{
MY_DEBUG("locat");
return i2c_add_driver(&my_touch_ts_driver);
}
static void __exit my_ts_exit(void)
{
MY_DEBUG("locat");
i2c_del_driver(&my_touch_ts_driver);
}
module_init(my_ts_init);
module_exit(my_ts_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("My touch driver");
MODULE_AUTHOR("wucaicheng@qq.com");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
补丁
【my_touch_gt9271.zip】补丁下载
📌 在 下载中心
->百度网盘(立创·泰山派开发板资料)
->第09章.【立创·泰山派】项目案例
->【智能小手机】项目资料
->6.触摸驱动
->代码补丁
->my_touch_gt9271.zip
。
课后作业
[] 上面代码只实现了单点触摸,请基于此实现一个多点触摸
[] 如果你的屏幕和我不一样清常识把驱动适配到你的触摸上
调试方法
逻辑分析仪
i2c-tool
i2cdetect
i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- UU -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
2
3
4
5
6
7
8
9
10
getevent
#getevent
add device 1: /dev/input/event7
name: "my touch screen"
add device 2: /dev/input/event0
name: "fe700030.pwm"
add device 3: /dev/input/event5
name: "SEM USB Keyboard System Control"
add device 4: /dev/input/event3
name: "SEM USB Keyboard"
add device 5: /dev/input/event2
name: "adc-keys"
add device 6: /dev/input/event4
name: "SEM USB Keyboard Consumer Control"
add device 7: /dev/input/event6
name: " USB OPTICAL MOUSE"
add device 8: /dev/input/event1
name: "rk805 pwrkey"
/dev/input/event7: 0003 0039 00000017
/dev/input/event7: 0003 0035 00000220
/dev/input/event7: 0003 0036 00000275
/dev/input/event7: 0001 014a 00000001
/dev/input/event7: 0003 0000 00000220
/dev/input/event7: 0003 0001 00000275
/dev/input/event7: 0000 0000 00000000
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24