Platform: RK3399
OS: Android 7.1
Kernel: v4.4.103
Board: Firefly-AIO-3399C
TSC2007 的 Datasheet 给出了它的一个典型应用电路:
我们自己打了几块调试的小板,大概如下图这样:
然后是接到开发板上准备调试 :
一、内核驱动
tsc2007的驱动源码在内核中是已经存在的,其路径如下:
kernel/drivers/input/touchscreen/tsc2007.c
所以,我们只需要在kernel目录下,通过 make menuconfig 命令来配置内核并重新编译即可;或者,在调试阶段,也可以用一种简单粗暴的方式——直接改Makefile(kernel/drivers/input/touchscreen/Makefile):
#
# Makefile for the touchscreen drivers.
#
...
#obj-$(CONFIG_TOUCHSCREEN_TSC2007) += tsc2007.o
obj-y += tsc2007.o
...
这里可以简单看一下 tsc2007 的设备结构体在驱动文件中的定义:
struct tsc2007 {
struct input_dev *input;
char phys[32];
struct i2c_client *client;
u16 model;
u16 x_plate_ohms;
u16 max_rt;
unsigned long poll_period; /* in jiffies */
int fuzzx;
int fuzzy;
int fuzzz;
unsigned gpio;
int irq;
wait_queue_head_t wait;
bool stopped;
int (*get_pendown_state)(struct device *);
void (*clear_penirq)(void);
};
二、设备树
内核的配置搞定了,接下来就是修改设备树文件,来把相关的硬件信息传给驱动。之前在Firefly的开发板上接了一LVDS接口的LCD屏幕,使用的是以下的dts:
kernel/arch/arm64/boot/dts/rockchip/rk3399-firefly-aioc-lvds-HSX101H40C.dts
在此基础上做修改。
那么,tsc2007的设备树节点应该怎么写呢?
首先,去看bindings。
打开 kernel/Documentation/devicetree/bindings/input/touchscreen/tsc2007.txt,可看到它给出了一个example:
Example:
&i2c1 {
/* ... */
tsc2007@49 {
compatible = "ti,tsc2007";
reg = <0x49>;
interrupt-parent = <&gpio4>;
interrupts = <0x0 0x8>;
gpios = <&gpio4 0 0>;
ti,x-plate-ohms = <180>;
};
/* ... */
};
对设备节点中各个属性的解释如下:
Required properties:
- compatible: must be “ti,tsc2007”.
- reg: I2C address of the chip.
- ti,x-plate-ohms: X-plate resistance in ohms.
Optional properties:
- gpios: the interrupt gpio the chip is connected to (trough the penirq pin).
The penirq pin goes to low when the panel is touched. (see GPIO binding[1] for more details).- interrupt-parent: the phandle for the gpio controller (see interrupt binding[0]).
- interrupts: (gpio) interrupt to which the chip is connected (see interrupt binding[0]).
- ti,max-rt: maximum pressure.
- ti,fuzzx: specifies the absolute input fuzz x value. If set, it will permit noise in the data up to ± the value given to the fuzz parameter, that is used to filter noise from the event stream.
- ti,fuzzy: specifies the absolute input fuzz y value.
- ti,fuzzz: specifies the absolute input fuzz z value.
- ti,poll-period: how much time to wait (in milliseconds) before reading again the values from the tsc2007.
– [0]: Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
–[1]: Documentation/devicetree/bindings/gpio/gpio.txt
这里,列举几个主要的:
- I2C从设备地址 —— reg
tsc2007的7位设备地址是由A0, A1两个引脚的连接方式确定的,在 Datasheet 有给出说明。
7位地址中,D7~D5为高3位,所以地址高位为100,即为4。
由于我把A1, A0两个引脚接地,所以 D4~D1为1000,即为8。
因此,这7位地址应该是0x48:
reg = <0x48>;
- 中断引脚 —— gpios
tsc2007的中断引脚(PENIRQ)在触摸屏被触摸时会拉至低电平,以此来产生一个中断信号给主机,这个引脚我把它接到了AIO-3399C引出来的一个GPIO——GPIO2_B4上,这个GPIO的位置,可以参看这里:http://wiki.t-firefly.com/AIO-3399C/driver_gpio.html 。
gpios = <&gpio2 12 GPIO_ACTIVE_LOW>;
- 中断控制器 —— interrupt-parent
指定中断引脚对应的中断控制器。在设备树中可以作为 interrupt-parent 的节点,都带有 interrupt-controller这个属性,由于我们使用GPIO2_B4这个IO,所以这个 interrupt-parent 设为 gpio2。
interrupt-parent = <&gpio2>;
- 中断号及其触发方式 —— interrupts
我们在 gpio2 下申请一个中断,interrupts这个属性有2个cell:第一个cell就是引脚号,这里GPIO_B4的number就是12;第二个cell,用来表示中断的触发方式,可以使用诸如 IRQ_TYPE_LEVEL_LOW,IRQ_TYPE_EDGE_FALLING,IRQ_TYPE_EDGE_BOTH 这样的宏来表示。这里我们设置为下降沿触发。
interrupts = <12 IRQ_TYPE_EDGE_FALLING>;
在AIO-3399C 开发板上,引出了 I2C4 这个I2C接口,这个接口也是开发板原本的电容触摸屏(gslx680)的接口,AIO-3399C 开发板的I2C接口可参看这里:http://wiki.t-firefly.com/AIO-3399C/driver_i2c.html 。现在我们在 I2C4 这个接口下使用tsc2007,最终,dts文件的修改如下:
...
&i2c4 {
status = "okay";
gslx680: gslx680@41 {
compatible = "gslX680";
reg = <0x41>;
screen_max_x = <800>;
screen_max_y = <1280>;
touch-gpio = <&gpio4 28 IRQ_TYPE_LEVEL_LOW>;
reset-gpio = <&gpio4 21 GPIO_ACTIVE_HIGH>;
flip-x = <1>;
flip-y = <0>;
swap-xy = <0>;
gsl,fw = <1>;
status = "disabled";
};
// tsc2007 - Yumin
tsc2007@48 {
compatible = "ti,tsc2007";
reg = <0x48>;
interrupt-parent = <&gpio2>;
interrupts = <12 IRQ_TYPE_EDGE_FALLING>;
gpios = <&gpio2 12 GPIO_ACTIVE_LOW>; //GPIO2_B4
ti,x-plate-ohms = <180>;
status = "okay";
};
};
...
三、重新编译内核
做完上述的工作之后,应该就可以重新编译内核了,但是这里有一点需要说明一下,就是需要把RK的 camsys 驱动从内核中去掉,具体是修改 kernel/drivers/media/Makefile,把对应的行注释掉。
...
# Do not compile camsys - Yumin
# obj-$(CONFIG_CAMSYS_DRV) += video/rk_camsys/
...
这么做的原因是:之前在调试一个电池模块的充电插入检测的时候,有使用 GPIO2_B4 这个IO口,结果发现 Android 系统需要延迟几分钟才广播充电状态,并且,在使用USB摄像头热拔插的时候,会出现系统崩溃。最终排查的结果是和 camsys 这个驱动有关系,于是把它从内核中直接去掉,终于可以正常使用。所以在这里为了避免踩坑,就把它先去掉吧。
好了,重新编译内核吧,在开发板的SDK根目录下执行相关的命令即可:
$ . ./build/envsetup.sh
$ lunch
$ ./FFTools/make.sh -k -j32
再把所有的镜像打包到 rockdev/Image-rk3399_firefly_aioc_lvds_box 目录去:
$ ./mkimage.sh
按理说,完成了上面工作之后,应该就可以正常使用触摸屏了。但是,当我把镜像烧录到机器中,启动之后,发现我在触摸屏上滑动手指,屏幕上有鼠标的光标跟着移动,或者,可以这样表述:“触摸屏操作类似鼠标一样”,“触摸屏操作类似触摸板”。百思不得其解,在网上查找了很多资料,都没有得到正确的答案,最早的甚至能看到2013年的帖子(居然没有答案!):
后来最终在android的官网上找到了答案。
四、Android 输入设备配置文件
对于 输入设备配置文件(input device configuration files),安卓的官方文档为:https://source.android.com/devices/input/input-device-configuration-files
这里摘抄一段吧:
输入设备配置文件(
.idc
文件)包含特定设备的配置属性,这些属性会影响输入设备的行为。输入设备配置文件通常并非标准外围设备(例如 HID 键盘和鼠标)所必需的,因为默认的系统行为通常可确保它们开箱即用。另一方面,内置的嵌入式设备(尤其是触摸屏)几乎总是需要输入设备配置文件来指定其行为。
基本原理
根据关联的 Linux 内核输入设备驱动程序报告的事件类型和属性,Android 会自动检测和配置大多数输入设备功能。
例如,如果输入设备支持
EV_REL
事件类型、代码REL_X
和REL_Y
以及EV_KEY
事件类型和BTN_MOUSE
,则 Android 会将输入设备归类为鼠标。鼠标的默认行为是在屏幕上显示光标,光标跟踪鼠标的移动并在鼠标被点击时模拟触摸操作。虽然可以通过不同的方式配置鼠标,但是默认行为通常足以用于标准的鼠标外围设备。某些类别的输入设备更加模糊。例如,多点触摸屏和触摸板都至少支持
EV_ABS
事件类型以及代码ABS_MT_POSITION_X
和ABS_MT_POSITION_Y
。不过,这些设备的主要用途千差万别,无法总能自动确定。此外,还需要其他信息才能了解触摸设备报告的压力和大小信息。因此,触摸设备(尤其是内置触摸屏)通常都需要 IDC 文件。位置
输入设备配置文件由 USB 供应商、产品(及可选版本)ID 或输入设备名称定位。
按顺序查阅以下路径。
/odm/usr/idc/Vendor_XXXX_Product_XXXX_Version_XXXX.idc
/vendor/usr/idc/Vendor_XXXX_Product_XXXX_Version_XXXX.idc
/system/usr/idc/Vendor_XXXX_Product_XXXX_Version_XXXX.idc
/data/system/devices/idc/Vendor_XXXX_Product_XXXX_Version_XXXX.idc
/odm/usr/idc/Vendor_XXXX_Product_XXXX.idc
/vendor/usr/idc/Vendor_XXXX_Product_XXXX.idc
/system/usr/idc/Vendor_XXXX_Product_XXXX.idc
/data/system/devices/idc/Vendor_XXXX_Product_XXXX.idc
/odm/usr/idc/DEVICE_NAME.idc
/vendor/usr/idc/DEVICE_NAME.idc
/system/usr/idc/DEVICE_NAME.idc
/data/system/devices/idc/DEVICE_NAME.idc
当构建包含设备名称的文件路径时,设备名称中除“0-9”、“a-z”、“A-Z”、“-”或“_" 之外的所有字符将替换为“_”。
“触摸设备(尤其是内置触摸屏)通常都需要 IDC 文件”,嗯,这句话说得好!
这里我打算在android文件系统以 /system/usr/idc/DEVICE_NAME.idc 这种形式来作为 TSC2007 的 IDC 文件。
那么这个DEVICE_NAME又是什么呢?回过头看一看我们的tsc2007.c这个驱动文件吧:
static int tsc2007_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
...
input_dev->name = "TSC2007 Touchscreen";//输入设备名称
input_dev->phys = ts->phys;
input_dev->id.bustype = BUS_I2C;
input_dev->open = tsc2007_open;
input_dev->close = tsc2007_close;
...
err = input_register_device(input_dev); //注册输入设备
if (err) {
dev_err(&client->dev,
"Failed to register input device: %d\n", err);
return err;
}
...
}
可见在probe的过程中,向输入子系统注册输入设备的时候,设备名称为 “TSC2007 Touchscreen” ,所以最终我们的IDC在android文件系统下应该是:/system/usr/idc/TSC2007_Touchscreen.idc
1. 我们应该在源码的哪里创建这个IDC文件,并拷贝到设备的文件系统呢?
如果你对安卓系统的编译过程有所了解的话,应该知道,通常一个厂商的某个具体产品的makefile,都会放在device目录下,并且被命名为 xxxx.mk,它利用编译系统中已有的全局变量、函数等来完成所需要的功能,如:编译结束后,复制指定文件到设备系统中、设置系统属性等。
于是找到device/rockchip/rk3399/rk3399_firefly_aioc_lvds_box.mk,在这个 makefile 中,可以通过指定 PRODUCT_COPY_FILES 这个变量来实现在编译过程中拷贝文件到设备系统。可以参考这篇文章:
rk3399_firefly_aioc_lvds_box.mk 如下:
$(call inherit-product, $(LOCAL_PATH)/rk3399.mk)
$(call inherit-product, device/rockchip/common/tv/tv_base.mk)
PRODUCT_CHARACTERISTICS := box
PRODUCT_BRAND := rockchip
PRODUCT_MANUFACTURER := rockchip
PRODUCT_NAME := rk3399_firefly_aioc_lvds_box
PRODUCT_DEVICE := rk3399_firefly_aioc_lvds_box
PRODUCT_MODEL := AIO-3399C
PRODUCT_FIREFLY_NAME := LVDS
PRODUCT_AAPT_CONFIG := normal tvdpi hdpi
PRODUCT_AAPT_PREF_CONFIG := tvdpi
# enable mtp default
BOARD_USB_ALLOW_DEFAULT_MTP := true
# debug-logs
ifneq ($(TARGET_BUILD_VARIANT),user)
MIXIN_DEBUG_LOGS := true
endif
#for drm widevine
BUILD_WITH_WIDEVINE := true
看到 device/rockchip/rk3399/rk3399_firefly_aioc_lvds_box/ 这个目录,那么就在这里创建TSC2007_Touchscreen.idc 吧!
因此在 rk3399_firefly_aioc_lvds_box.mk 的末尾加上 PRODUCT_COPY_FILES 的配置:
PRODUCT_COPY_FILES += \ device/rockchip/rk3399/rk3399_firefly_aioc_lvds_box/TSC2007_Touchscreen.idc:system/usr/idc/TSC2007_Touchscreen.idc
完成这个修改之后,则在下次编译系统的时候会先把 device/rockchip/rk3399/rk3399_firefly_aioc_lvds_box中的TSC2007_Touchscreen.idc 复制到 out/target/product/rk3399_firefly_aioc_lvds_box/system/usr/idc/ 目录中,然后再从这里打包到 system.img 镜像。
2. 完成TSC2007_Touchscreen.idc
接下来,就是把 TSC2007_Touchscreen.idc 写出来了。在网上找了一些触摸屏的IDC文件的写法,直接copy了一个过来用,如下:
# Basic Parameters
touch.deviceType = touchScreen
#touch.orientationAware = 1
# Touch Size
touch.touchSize.calibration = default
# Tool Size
touch.toolSize.calibration = default
# Pressure
touch.pressure.calibration = default
# Size
touch.size.calibration = default
# Orientation
touch.orientation.calibration = none
这里最重要的当然应该是这一句啦:
touch.deviceType = touchScreen
当然,Android的官方文档里,也有对于触摸设备的详细的说明:https://source.android.com/devices/input/touch-devices
好了,到这里总算把所有的东西都搞定了,前面说过,配置完 rk3399_firefly_aioc_lvds_box.mk 之后只有在下一次编译的时候,才会在 system.img 中放入 TSC2007_Touchscreen.idc 文件,可是,我不想等上几分钟的编译过程,怎么办呢?
很简单,直接手动复制 TSC2007_Touchscreen.idc 到 out/target/product/rk3399_firefly_aioc_lvds_box/system/usr/idc/ 目录中,然后回到SDK的根目录,执行打包命令打包所有镜像:
$ ./mkimage.sh
或者可以直接打包 system.img (在 out/target/product/rk3399_firefly_aioc_lvds_box/ 下生成 system.img ):
$ make snod
把新的 system.img 烧录到机器上,重启,再试一下在触摸屏上滑动手指,nice,终于不再出现鼠标光标了!