第五节:触摸驱动


   前面我们已经学了hdmi、edp、mipi等各种显示屏的驱动,相信大家都已经点亮屏幕并进入了Android或者Linux等界面,界面能显示了但是光看着不能触摸是不是感觉还差点意思,人机交互重点就在于交互,所以接下来我们来掌握触摸驱动调试。

第一部分 准备

一、触摸驱动怎么学

   整个触摸驱动非常大,包含了GPIO驱动、中断驱动、i2c驱动、input驱动等,是一个复杂的驱动集合,这些驱动我们以后在驱动教程中在和大家一起去探讨,这一节我们就基于触摸进行分析,有用到相关驱动就浅学一下,主要目的还是把触摸跑起来先会用会调,目前我们常用的Goodix、FocalTech等触摸驱动内核中已经现成的了,所以我们理解框架并掌握调试技巧以后基本上可以把屏幕驱动起来。(现在就是先懂框架)

二、学习目标

  • 了解i2c触摸屏引脚线序
  • 掌握不同引脚线序的转接板绘制与焊接
  • 看懂触摸屏手册,分辨率、时序、i2c地址、寄存器等
  • 掌握触摸的设备树修改方法
  • kernel中打开并配置驱动(现在内核中有很多驱动,都找到匹配的驱动)
  • 理解触摸驱动大体框架做到会修改与调试
  • 逻辑分析仪使用

第二部分 硬件

   触摸屏种类有很多,我碰到过的有i2c、spi、usb等,其中i2c使用的频率最高,大部分的触摸都是i2c的,所以我们这里只讲i2c的触摸屏幕,另外如果你觉得下面的这些调试都太麻烦了,你可以选择usb触摸,无需调试。

一、连接方式

   泰山派通过6pin 0.5mm的fpc引出触摸接口,屏幕端跟据大家选的触摸,不同对应的座子规格以及线序相应都会不同。
   跟据我们的经验以及问了很多厂家,触摸大家都是6根线,但就这里6根线大家都不按照标准来,完全按照心情,我猜应该有720种不同的线序,所以碰到线序不同的时候,我们需要画个转接板,如果你量大,也可以让厂家定制,开模费几千的样子。

1、触摸接口位置

   触摸座子(座子是下接的)在泰山派背面,红框框出来的位置。
在这里插入图片描述

2、原理图

在这里插入图片描述
I2C1_SDA_TP
I2C1_SCL_TP
接了主控芯片的GPIO0_B4和GPIO0_B5
已经上拉
RST和INT,随便哪个引脚都可以,可以在原理图中搜索,看看接了芯片的哪个引脚。

3、PCB

在这里插入图片描述
立创EDA画线时,顶层拖出来一根线,按“V”自动打孔,线换到底层。
转接板,就是保证泰山派mipi_tp与屏幕tp线序相同。
不用太纠结,画图的时候对应一下就行。
注意最终要PCB中的线序相同。
若不小心线序整反了,FPC线也有“同向”变为“反向”。
(反了的时候,在原理图中,剪切,粘贴,镜像即可)
触摸板是I2C,不需要等长。
可以在转接板上将触摸端子的6根线用2.54端子引出来,方便用逻辑分析仪调试。
2.54端子最好在两个TP端子的中间,这样连线好一点。

4、线序

在这里插入图片描述

二、屏幕资料

   照顾新手小伙伴,这里以为前面我们调试的11寸屏幕带触摸总成为例,3.1寸的屏幕,后面以项目方式去讲,如果你用的是其他的屏幕也没关系方法原理很简单稍微灵活应变一下就行。
   我前面采购的是总成,如果也采购了,却只买了带mipi 31pin屏的话,可以再单独买个电容触摸屏。
屏幕链接。
在这里插入图片描述

三、如何画转接板

   前面说了很多厂家的线序都是不一样的,我们根本没办法直接接起来就用,因此中间需要加一个转接板,用来交换线序或者匹配不同接口座子,画起来非常简单,当然如果你手法好能够飞线那就当我没说过。
   画转接板的具体步骤不再赘述。
   需要注意线序的对应。
   PCB上差分走线等长,是否有必要进行阻抗匹配。

第三部分 触摸驱动

这些都是10.1寸的mipi,3.1寸的mipi比较难点。
拿到一款陌生的屏幕,先看自身板子是否支持屏幕的驱动,若支持,调通I2C后用就行。
例如,若屏幕触摸芯片是gt系列,就可以调通I2C后使用。
若板子自身不支持,再去写自己的touch.c。
下面的框架是最基本的,只是能用触摸驱动,可以新加代码增加触摸唤醒等功能。
还需要更健全的机制去维护触摸,防止用户用着触摸突然不能用了。
现在仅仅是能用触摸就行。
嵌入式就是兴趣,爽,不爽谁玩?

泰山派可以跑图像识别,但是docker有问题,解决后会合并文档

一、驱动位置

   安卓和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配置文件,其他的.cfggt9xx_cfg.h是用于多芯片兼容,gt9xx_update.cgt9xx_firmware.h固件更新与goodix_tool.c调试的。

SDK/kernel/drivers/input/touchscreen/gt9xx下有一个makefile文件,去看看,是组织编译的。

gt9xx.cgt9xx.h中有触摸的驱动代码,makefile是将gt9xx.cgt9xx.h糅合进kernel的工具。

二、将驱动配置进内核!!!

   泰山派的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

SDK/kernel/drivers/input/touchscreen/
Makefile

#省略
obj-$(CONFIG_TOUCHSCREEN_GT9XX)     += gt9xx/
#省略

这里我们看到一个变量CONFIG_TOUCHSCREEN_GT9XX,Makefile中就是通过这个变量值,来判定是否编译gt9xx的。这个变量的值有三个ymn分别对应配置到内核、模块。不打开,我们可以通过暴力手段直接改,但这不是明智之举,适合用于调试阶段。

-m 将文件编译为模块,是一个.ko文件,可以动态加载它。(需要手动安装驱动,卸载驱动)
-y 将文件编译到系统中,下载固件,驱动就可以下载到泰山派中

obj-y     += gt9xx/
obj-m     += gt9xx/
obj-n     += gt9xx/

在同Makefile同目录下还有一个Kconfig,有了这个,我们就可以使用menuconfig工具来配置Makefile

Kconfig中有GT9XX的描述

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.

1、menuconfig

kenrnel目录下运行
进入menuconfig

make ARCH=arm64 menuconfig

依次进入目录(键盘上下键移动,回车进入)

使用反斜杠“/”,除号,或者“?”下面的
使用反斜杠可以搜索
这里搜索gt9xx
然后进入
在这里插入图片描述
再按“1”。进入
在这里插入图片描述
再下面看教程即可


  │     -> Device Drivers                                                             │
  │       -> Input device support                                                     │
  │         -> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])     │
             -> Touchscreens (INPUT_TOUCHSCREEN [=y])

可以看到下面红框前有星号,所以这里已经默认编译进入内核了(默认支持gt9xx,太常用了),我们可以通过键盘上的m,y,n配置

按y,就是加星号
按n,就是去掉星号

在这里插入图片描述
我这里改成n进行测试,【注意你不要动不要动不要动不要动ESC,退出就行】,我只是演示私底下还会改回来的,键盘左右按键,移动到Save,然后回车保存。
在这里插入图片描述
OK回车
在这里插入图片描述
退回到界面
在这里插入图片描述
退出所有路径(从exit中退出),直到退出menuconfig界面。
在这里插入图片描述
因为我们刚刚保存的是.config,每次编译的时候,脚本都会去组合生产.config,所以你的配置就会被覆盖掉,这也是很多小伙伴经常提问的为什么配置没生效,正确的方式是“生成defconfig”,然后并覆盖到之前arch/arm64/configs/下的defconfig

kernel目录下,输入下面的命令生成defconfig

make ARCH=arm64 savedefconfig

在这里插入图片描述
泰山派SDK使用的是rockchip_linux_defconfig,所以把生成的defconfig复制过去代替rockchip_linux_defconfig,我用的是repogit版本,所以我就直接复制过去了,如果你没有git管理,就先备份一个,然后再代替过去,避免搞坏了回不去了。
输入下面的命令,移动

mv defconfig arch/arm64/configs/rockchip_linux_defconfig

验证。

gt9xx.c生成的gt9xx.o删除掉,再重新编译就不会在生产gt9xx.o文件了。
这是因为我们上面的配置,不编译gt9xx驱动模块生效了。
在这里插入图片描述
单独编译内核不会的看编译教程(SDK编译部分),编译成功后没有再生成gt9xx.o

将单独编译内核指令,在服务器中录制为一个宏。
编译内核指令,生成一个boot.img
简单理解:boot负责启动,kernel是内核。
第一次编译完成后,再编译内核很快。

在这里插入图片描述

可以再次进入manuconfig,按y打开gt9xx,重新配置出来gt9xx.o

闲话:
如何学习?
掌握方法+做笔记

三、配置设备树

泰山派触摸相关的设备树SDK\kernel\arch\arm64\boot\dts\rockchip\tspi-rk3566-dsi-v10.dtsi中添加下面的代码,根据GT9xx_Driver_for_Android_V2.4_2014112801dtsi\goodix-gt9xx.dtsi参考修改而来。

mipi tp用到了I2C1
所以添加I2C1的代码

1.02

I2C是七位地址
7 6 5 4 3 2 1 0
一到七位 地址位(实际用的)
零位是读写位
从地址0xBA/0xBB,区别就是第零位
不要第零位(读写标志)时,将地址右移,得到0x5d,设备从地址
所以下面写入地址0x5d,reg = <0x5d>;

&i2c1 {
    status = "okay";
    ts@5d {
    	/*匹配到gt9xx驱动*/
        compatible = "goodix,gt9xx";
        /*因为是i2c所以有设备地址*/
        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>;
    };
};

1、I2C1

//i2c1 节点追加
&i2c1 {
    status = "okay";
    /*加触摸*/
};

硬件部分,通过原理图,可以知道触摸接口用的是i2c1,所以我们就直接在i2c1中写触摸。
我们原理图中,已经对数据线和时钟先进行了上拉。
在这里插入图片描述

2、触摸节点

&i2c1 {
    status = "okay";
    ts@5d { //触摸子节点
        compatible = "goodix,gt9xx"; //这个非常重要,就是靠这个来匹配的驱动
    };
};

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),
    },
};

3、地址

&i2c1 {
    status = "okay";
    ts@5d { //触摸子节点
        compatible = "goodix,gt9xx";
        reg = <0x5d>; //触摸屏地址
    };
};

因为一个i2c下可以挂载多个从设备,既然可以挂那么多设备,我们要怎么和单个设备通讯呢,地址的作用就来了,我想和谁通讯,我就叫谁的地址。
我们怎么知道用的屏幕的地址是多少呢?

  • 查找厂家要,比如这里厂家给的参考代码里面就已经包含地址了0x5d
    在这里插入图片描述
  • 看触摸屏数据手册,这其实也是找厂家要的
  • 在这里插入图片描述
    数据手册中有对地址进行描述,这里写的是0xBA/0xBB,有些同学可能会迷糊,上面说的是0x5d,这里怎么变了?
    我们上面说的是7位地址,这里是8位,包含第零位读写位,所以把0xBB/0xBA右移1位,就可以得到0x5D。
    在这里插入图片描述

4、参数

&i2c1 {
    status = "okay";
    ts@5d {
        compatible = "goodix,gt9xx";
        reg = <0x5d>;
        tp-size = <89>; //触摸大小
        max-x = <1280>; //屏幕最大值
        max-y = <800>;  //屏幕最小值
     };
};

设备树中触摸大小,xy最大值,这些参数来自于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;
    }
     /**************删减***************/

5、中断引脚

去读取数据方式很多种,比如我们可以通过轮询去读,但是这样的效率非常低,触摸屏已经给我们提供了一个中断引脚,下面是触摸屏数据手册中对中断引脚的描述,当屏幕被按下,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>;
     };
};

gt9xx.c中会去获取touch-gpio引脚

//2711行
ts->irq_pin = of_get_named_gpio_flags(np, "touch-gpio", 0, (enum of_gpio_flags *)(&ts->irq_flags));

从原理图中可以知道,中断引脚使用的是GPIO1_A0所以对应&gpio1 RK_PA0
在这里插入图片描述我们选择低电平触发IRQ_TYPE_LEVEL_LOW

6、复位引脚

&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>;
    };
};

gt9xx.c中会去获取引脚

//2712行
ts->rst_pin = of_get_named_gpio_flags(np, "reset-gpio", 0, &rst_flags);

在原理图中使用了GPIO1_A1,所以对应的就是gpio1 RK_PA1
在这里插入图片描述
低电平有效GPIO_ACTIVE_LOW

四、触摸测试

1、将泰山派10.1寸触摸补丁下载
2、打补丁
3、单独编译内核
4、下载固件
5、查看实验效果即可

五、触摸驱动解析(手写)

位置:自己写驱动 1:30
本来想就gt9xx.c进行一下驱动解析的,但gt9xx.c里面做了很多兼容,很多宏,导致代码看起来很乱很复杂,索性我们就一起从头开始写一个触摸驱动。
第一步:写一个模拟触摸驱动,并定时上报触摸数据。
第二步:接上上面的gt9xx屏幕,去读屏幕里面的数据进行上报。

1、新建触摸驱动(先建一个驱动的框架)

SDK/kernel/drivers/input/touchscreen下新建个目录,这里我们就命名叫my_touch,当然你在其他目录新建也是可以的,关键是你的makefile文件中去指定。

kernel/drivers/input/touchscreen$ mkdir my_touch/

samba建立当然也行

切换到my_touch目录

cd my_touch

my_touch目录下创建我们的触摸驱动my_touch.c后面我们的模拟驱动就写在这个里面。

touch my_touch.c 

my_touch.c里填写驱动代码

在gt9xx.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");

my_touch.c中,可以精炼出75行的模板(通用的框架) 在第四小节 驱动框架中讲
上面是诸多函数
下面是函数的应用,有5个函数
1、module_init 驱动初始化
2、module_exit 驱动退出

my_touch.c 这个文件需要被编译,所以我们还需要创建一个Makefile文件,并填入下面内容

#如果编译成.ko的话就选obj-m,如果编译到内核就obj-y
obj-y        += my_touch.o

在这里插入图片描述
touchscreen目录下的Makefile中也需要指定my_touch目录

# obj-n 把gt9xx给屏蔽掉避免后面影响
obj-n                               += gt9xx/
# obj-y 编译我们的my_touch
obj-y                               += my_touch/

在这里插入图片描述

完成makefile后
编译内核,查看是否生成my_touch.o
然后再写my_touch.c

接着,还需要在设备树中添加我们的模拟触 摸驱动,在SDK\kernel\arch\arm64\boot\dts\rockchip\tspi-rk3566-dsi-v10.dtsi&i2c1中添加

注意是模拟触摸驱动
烧录boot后,显示屏的效果是“乱跳”,“乱点”。

    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、补丁

上面的文件,已生成补丁,放在“11寸mipi”文件夹中。

3、验证

a、编译进入内核

前面我们my_touch目录下的Makefile中是obj-y所以是编入到内核的,我们只需要重新编译内核并单独烧录内核就可以生效。单独编译和烧录内核的方法我们前面介绍了很多次了,所以不再演示。

b、编译成模块

编译进内核并单独烧入Bootemmc操作起来很方便,如果没有emmc,可以生成模块.ko,并把.ko传到泰山派上加载,编译成模块需要把my_touch目录下Makefile改成obj-m

#如果编译成.ko的话就选obj-m,如果编译到内核就obj-y
obj-m        += my_touch.o

单独编译驱动,没有报错,成功会生成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/

在这里插入图片描述

c、加载my_touch.ko

通过adb把my_touch.ko文件 “push”到泰山派里面去

用了两个shell,一个push,一个进入adbshell,执行安装和卸载的操作

下面adb push命令紧接着就是.ko文件所在目录,目标路径

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 目录中。

在这里插入图片描述

若已经存在my_touch.ko
需要删除
adb shell进入tspi的adb
删除时,若提示“只读文件”
需要root权限
shell中(tspi adb外面) 运行adb root就行,或者adb root && adb remount

adb进入shell
在这里插入图片描述
安装触摸的驱动

rk3566_tspi:/ # insmod /vendor/lib/modules/my_touch.ko

如果在/vendor/lib/modules/这个路径下,直接执行insmod my_touch.ko 即可

查看驱动是否安装成功

rk3566_tspi:/ # lsmod
Module                  Size  Used by
my_touch               16384  0
bcmdhd               1175552  0

在这里插入图片描述
查看日志(串口TXRX查看)
可以看到我们模拟的驱动在上报数据了

/vendor/lib/modules/这个路径下,执行dmesg | grep "MY_TOUCH".
会在adb shell 中得到执行过程中,打印的信息

在这里插入图片描述

d、效果

可以看到,在触摸屏随机上报触摸点。

e、卸载my_touch.ko

执行

rk3566_tspi:/ # rmmod /vendor/lib/modules/my_touch.ko

如果在/vendor/lib/modules/这个路径下,直接执行rmmod my_touch.ko 即可
insmod安装
rmmod卸载

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驱动  
}  
  /*下面5个函数 是使用*/
/*  
 * 使用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");    // 模块作者

加载驱动的流程

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"和设备树中匹配成功以后就执行此函数

按照前面步骤编译并安装驱动打印日志,可以看到驱动被安装后执行的函数
在这里插入图片描述
卸载驱动流程

module_exit(my_ts_exit);
    my_ts_exit
        my_touch_ts_remove

卸载驱动并打印日志,可以看到驱动被卸载后执行的函数
在这里插入图片描述
上面框架是通用的,你可以在touchscrren或者其他驱动下面,随机找个,基本上都是这个框架,有差别的地方可能会是i2c_add_driver,如果他不是i2c驱动,就是注册的其他的。

5、初始化触摸(往框架中填写代码)

上面框架我们搞定了,接下来我们来模拟一个触摸上报触摸事件,其实这里涉及到input子系统,但是没有关系,大家理解就是调用api就行,套路就是申请驱动、配置参数、调用相关api上报数据,就和我们stm32的外设初始一样一样的。

多看看gt9xx的驱动

a、分配输入设备

要用触摸不可能凭空就用吧,我们需要先调用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;
    }
b、指定名字与设备类型

刚分配出来的没有名字,所以我们是不是要给取个名字,并指定一下设备类型

    input_dev->name = "my touch screen";
    input_dev->id.bustype = BUS_I2C;

在泰山派系统中,可以通用cat /proc/bus/input/devices或者getevent(安卓)等查看到"my touch screen"
在这里插入图片描述

c、设置触摸参数

设置一下我们屏幕的xy的最大值和最小值,很多小伙伴在群里反馈触摸的范围很多,但是反馈到屏幕上就一点点,就是这个地方设置错误了,这个值一定要和你的屏幕大小是一样的。

    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);
  • 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。
d、初始多点触摸

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;
    }
  • input_devstruct input_dev 结构的指针,表示要设置参数的输入设备。struct input_dev 包含了输入设备的各种属性和状态信息。
  • num_slots:这里设置成5个点。
  • flagsINPUT_MT_DIRECT为触摸设备,当然还可选其他值比如INPUT_MT_POINTER表示指针设备。
e、注册输入设备

最后我们来注册输入设备

    ret = input_register_device(input_dev);
    if (ret)
        return ret;
f、注销输入设备

有注册当然就会伴随的有注销,上面留了个坑,没有注销函数,所以你rmmod的时候,系统会崩溃。

static int my_touch_ts_remove(struct i2c_client *client)
{
    MY_DEBUG("locat");
    input_unregister_device(input_dev);
    return 0;
}

6、上报触摸数据(往框架中填写代码)

a、选择那个点上报

前面我们说了这个一个多点触控,触摸屏同时会支持多个点触摸比如gt9271就支持10个点同时触摸,所以我们上报的时候也需要选择,当前上报的是第几个点的数据。

input_mt_slot(input_dev, 0);
b、状态

接下来我们要告诉系统,我们是按下触摸还是抬起触摸。

//按下
input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, true);
//松开
input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, false);
  • input_dev: struct input_dev 结构的指针,表示要设置参数的输入设备。struct input_dev 包含了输入设备的各种属性和状态信息。
  • tool_type:表示触摸点的工具类型。MT_TOOL_FINGER 这里表示手指头。
  • active:这是一个布尔值参数,用于指示触摸点的状态。如果 activetrue,表示触摸点处于活跃状态;如果 activefalse,表示触摸点处于非活跃状态。
c、坐标

如果是按下,我们就上传坐标信息

    input_report_abs(input_dev, ABS_MT_POSITION_X, x);
    input_report_abs(input_dev, ABS_MT_POSITION_Y, y);
d、启用指针仿真

该模式允许将“多点触控事件”转换为“鼠标或指针事件”。

input_mt_report_pointer_emulation(input_dev, true);
e、同步事件

确保之前通过input_report_absinput_mt_report_slot_state等函数报告的所有事件都被同步到输入子系统,并且作为一个完整的事件集进行处理。没有调用input_sync之前,这些事件是挂起的,不会被系统或用户空间的应用程序看到。

input_sync(input_dev);
f、定时器
<1> 初始化

因为没有实际的触摸屏,所以我们通过一个定时器模仿触摸按下

//定时器回调函数
void my_timer_callback(struct timer_list *timer){
    //为了实现重复
    mod_timer(timer, jiffies + msecs_to_jiffies(200));
}
    // 初始化定时器
    timer_setup(&my_timer, my_timer_callback, 0);

修改定时触发时间,这里是5s后会触发并调用回调函数。

    // 设置定时器,5 秒后第一次触发
mod_timer(&my_timer, jiffies + msecs_to_jiffies(5000));
<2>删除
del_timer_sync(&my_timer);

7、小节

自己写屏幕驱动时,需要重点注意的地方。
1、按照数据手册,找复位序列的地方,屏幕不一样,复位信号也不一样,复位成功,触摸屏幕就会触发中断。
2,读版本号,注册输入设备后,需要读I2C的版本号,来确定I2C是否正常。
要注意,不同触摸寄存器地址不一样,有些是1个8位寄存器,有些是两个8位寄存器。
不同屏幕的触摸数据地址不同,数据按下后读到的状态值也不同。
处理数据的方式也不同,但是大同小异。
不同屏幕触发中断的信号不同,有的上升沿,有的下降沿。

六、实现自己的触摸驱动

#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");

对应的补丁,已经放到“11寸mipi中”

a、课后作业

  • 上面代码只实现了单点触摸,请基于此,实现一个多点触摸
  • 如果你的屏幕和我不一样,请尝试把驱动适配到你的触摸上

七、调试方法

当I2C没有芯片手册时怎么写,需要抓别人的包。(使用逻辑分析仪)
画触摸转接板的时候,可以将各线引出2.54排针,方便逻辑分析仪接入。

1、逻辑分析仪

打开逻辑分析仪的软件,并开始
adb shell中 安装my_touch.ko
然后在屏幕上触摸
逻辑分析仪中会有变化
在这里插入图片描述
在这里插入图片描述

2、i2c-tool

不知道i2c地址的时候可以使用

a、i2cdetect

adb shell中输入i2cdetect -y 1使用
1 代表I2C1

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: -- -- -- -- -- -- -- --

I2C1地址是5d

3、getevent

安卓下使用,adb shell下使用,get事件

# 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
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值