简介:tp28xx_kdrv_tp9930.tar.gz 驱动源码包,面向 Linux 嵌入式与物联网设备开发者,专为 TP28xx 系列(如 TP2828、TP2831)芯片及 TP9930 模块设计,可用作视频采集、触摸外设等场景的底层驱动开发基础,解决硬件初始化、寄存器读写、数据传输与中断处理等关键问题。整个压缩包体积仅 76KB,共 14 个文件;其中 9 个 C 源文件是驱动主体,分别负责设备初始化、IOCTL 命令处理和数据收发逻辑;2 个头文件提供寄存器映射、数据结构与公共接口定义;3 个 Makefile 则便于按需选择编译目标并生成内核模块。开发者既可以通过阅读代码掌握 Linux 内核模块的编写、IOCTL 交互、Makefile 交叉编译等核心技能,也可以利用包内测试验证相关文件快速观察驱动运行效果;对比不同芯片的驱动实现,还能快速理解同类芯片的差异与移植要点。系统集成人员则可以直接复用现成的构建脚本与头文件,针对具体硬件裁剪 C 文件进行移植,显著缩短项目适配周期。目前已有 1429 人学习下载,适合具备一定 Linux 驱动基础、正在从事相关芯片驱动开发或调试的中高级开发人员参考。 拿到一个命名为tp28xx_kdrv_tp9930.tar.gz的文件,不少人的第一反应是双击解压,然后对着里面一堆源码发懵。这个命名其实把关键信息全写在脸上了:tp28xx是触摸屏控制器芯片的系列型号,kdrv是 kernel driver 的缩写,tp9930是具体的驱动版本或芯片型号,最后的tar.gz是 Linux 下最常见的源码打包压缩格式。换句话说,这是一个用于某款触摸屏芯片的 Linux 内核驱动源码包,大概率来自芯片原厂或方案商的 BSP 发布包。
这篇文章就从这个文件名出发,完整走一遍“拿到驱动包 → 解压 → 看懂结构 → 交叉编译 → 加载测试”的全流程,同时把 tar.gz 解压命令、内核模块编译环境、常见报错处理这些实操细节一并讲透。不管你是做嵌入式 BSP 的新人,还是被厂商驱动包折磨过的老手,这篇都能给你一些可以直接套用的经验。
1. 文件名里藏着的秘密:tp28xx_kdrv_tp9930.tar.gz 到底是个什么包
先别急着解压,花两分钟拆解一下文件名,能帮你少走很多弯路。tp28xx通常代表触摸屏芯片的系列,比如某家厂商的 TP2800/TP2802/TP2806 这一族设备。kdrv基本可以确定是 kernel driver 的缩写,说明这是一份内核驱动源码,而不是用户态的 lib 库或者固件镜像。tp9930则可能是该系列下的具体型号,也可能是驱动版本号——具体是哪种,得解压后看源码里的宏定义和设备树 compatible 字段才能确认。tar.gz表明这是一个被 gzip 压缩的 tar 归档文件,在 Linux/Unix 生态里相当于 Windows 下的.zip但更常见于源码发布。
这类驱动包一般来自三种渠道:芯片原厂直接提供、方案商随 BSP 一起发布、或者在某些开发板论坛上被二次转存。无论哪种来源,拿到手之后第一件事不是解压,而是先校验文件的完整性。用file命令看一下文件真实格式,用tar -tzf列一下压缩包内有哪些内容,能提前发现文件是否损坏、是不是伪装成 tar.gz 的其它格式,以及里面是不是藏了不该有的东西。
$ file tp28xx_kdrv_tp9930.tar.gz tp28xx_kdrv_tp9930.tar.gz: gzip compressed data, last modified: ..., from Unix, original size ...如果输出不是gzip compressed data,那说明文件名和后缀对不上,可能是用其它压缩算法打包后改了名。这种时候不要强行用 tar 去解,先识别真实格式再决定解包工具。
2. 解压前的环境检查:内核源码树、交叉编译链和 rootfs 缺失会连环翻车
驱动包本身只是个代码压缩包,真正的坑在解压之后的编译环境。嵌入式 Linux 驱动编译和普通 Linux 用户态程序完全不同,它必须依赖目标平台的内核源码树(kernel source tree),因为驱动模块本质上是要嵌进内核编译体系里的。所以解压之前,先把交叉编译链、内核源码路径、以及 rootfs 里对应的/lib/modules/版本号这三样东西备齐。
先说内核源码树。驱动编译依赖KERNELDIR,这个变量指向的内核源码目录必须和最终运行该驱动的目标系统内核版本一致,否则模块加载时会出现version magic不匹配。具体来说,内核源码目录下得有:
include/generated/autoconf.h include/config/auto.conf Module.symvers其中Module.symvers尤其重要,它记录了内核导出的符号信息。如果这个文件缺失,驱动即使编译通过,也极可能在 insmod 时因为Unknown symbol而失败。实操中强烈建议在使用前先执行一次make modules_prepare,确保内核源码树处于可编译模块的完整状态。
再说交叉编译链。驱动必须用目标架构的编译器来编,比如 ARM 平台用arm-linux-gnueabihf-gcc,AArch64 平台用aarch64-linux-gnu-gcc。很多新手拿到 x86 机器上编出的.ko文件,往 ARM 板子上一拷就报告Exec format error,这就是架构不对。检查编译器是否可用,可以用:
$ aarch64-linux-gnu-gcc --version然后要确认目标板内核的ARCH和CROSS_COMPILE环境变量是否已正确导出:
export ARCH=arm64 export CROSS_COMPILE=aarch64-linux-gnu-这两行的作用,是告诉内核 Makefile 当前在为哪个架构构建、使用哪个前缀的编译器。如果漏了ARCH=arm64,kbuild 系统可能默认按 x86 来配置,最后编出来的模块当然没法用。这块是根因式问题,排查起来也简单——看.ko文件格式即可,用file命令,看到ELF 64-bit LSB relocatable, ARM aarch64就对了。
3. tar.gz 解压的正确姿势:从基础命令到避坑经验
既然文件名带着tar.gz,那就绕不开解压这步。基础命令大家都知道:tar -xzf tp28xx_kdrv_tp9930.tar.gz,但实际工程里我更建议用tar -xzvf把解压过程完整打出来,方便确认哪些文件被解到了哪里,尤其当包内文件很多时,这样可以第一时间发现是否有文件覆盖、权限错误等问题。
$ tar -xzvf tp28xx_kdrv_tp9930.tar.gz tp28xx_kdrv_tp9930/ tp28xx_kdrv_tp9930/Makefile tp28xx_kdrv_tp9930/src/tp9930.c tp28xx_kdrv_tp9930/src/tp9930.h tp28xx_kdrv_tp9930/dts/tp28xx.dtsi tp28xx_kdrv_tp9930/README.md ...这里说几个容易踩的细节:
解压路径污染:如果包内的文件是散落在根目录的
Makefile、*.c文件,没有统一目录包裹,那么解压时会直接覆盖当前目录下的同名文件。所以解压前先ls一下包里顶层有哪些条目,如果发现不是单一顶层目录,就先建一个临时目录再进去解压。绝对路径解压风险:极少数恶意或损坏的归档里会记录
/tmp/xxx这样的绝对路径,tar默认会按包内路径解压,可能把文件写到系统目录里去。用tar -tzf先查看包内路径,如果看到以/开头的条目,就要用--no-absolute-names选项强制剥掉绝对路径。权限和所有权:
tar.gz包内保存了文件权限和时间戳。如果当前用户是普通用户,解压出来的文件owner会变成当前用户。如果是 root 操作且包内有设备节点之类特殊文件,需要注意安全问题。嵌入式开发中一般用普通用户解压,再切换权限。通配符解压指定文件:不需要全部解压时,比如我只想看
README.md,可以tar -xzf tp28xx_kdrv_tp9930.tar.gz tp28xx_kdrv_tp9930/README.md。这个技巧在处理大型 BSP 包时很实用。
顺带提一下,e2fsprogs 这类文件系统的源码包也是同样的 tar.gz 格式,官方发布的e2fsprogs-1.46.6.tar.gz里同样是标准的configure、Makefile.in自动构建体系,但驱动包通常不走 configure,而是走内核 kbuild 体系。所以如果你把驱动包当成普通软件包,直接./configure && make必然失败——它的 Makefile 是给内核模块系统用的,不是 GNU autotools 那套。
4. 深入源码:Makefile 和 driver 入口函数决定驱动怎么编、怎么挂
解压完成后,先用tree或find看一下整体结构。一个规范的触摸屏驱动包通常包含以下部分:
tp28xx_kdrv_tp9930/ ├── Makefile ├── README.md ├── src/ │ ├── tp9930.c │ ├── tp9930.h │ └── tp28xx_core.c ├── dts/ │ └── tp28xx-tp9930.dtsi ├── patches/ └── tools/ └── calibration先看Makefile,它决定了编译入口。典型的驱动 Makefile 长这样:
obj-m += tp9930_drv.o tp9930_drv-objs := src/tp9930.o src/tp28xx_core.o KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean这里的obj-m表示目标模块被编译成可加载模块(.ko),而obj-y则是编入内核。嵌入式系统上为了灵活部署,一般都用obj-m。KERNELDIR ?=是覆盖入口,如果你已经下载并配置好目标内核源码,编译时直接手动指定:
make KERNELDIR=/home/user/linux-imx-5.10 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-如果你的主板 BSP 源码里已经有现成的编译脚本,比如build.sh,那就按脚本走。很多厂商的驱动包会在README.md里写清楚默认适用的内核版本和编译方法。最稳的做法是:先读 README,再改 Makefile。这比盲目尝试节约半天时间。
再看驱动源码入口。Linux 触摸屏驱动本质是一个 input 子系统设备驱动,代码中必须实现probe、remove以及i2c_driver或platform_driver结构体。tp9930.c中大概率会看到:
static const struct of_device_id tp9930_of_match[] = { { .compatible = "tp28xx,tp9930", }, { } }; MODULE_DEVICE_TABLE(of, tp9930_of_match); static struct i2c_driver tp9930_i2c_driver = { .driver = { .name = "tp9930", .of_match_table = tp9930_of_match, }, .probe = tp9930_probe, .remove = tp9930_remove, .id_table = tp9930_id_table, }; module_i2c_driver(tp9930_i2c_driver);compatible字符串是设备树匹配的核心。你在设备树里写的节点 compatible 必须和这里一致,比如:
&i2c2 { tp9930@38 { compatible = "tp28xx,tp9930"; reg = <0x38>; interrupt-parent = <&gpio1>; interrupts = <13 IRQ_TYPE_EDGE_FALLING>; reset-gpio = <&gpio5 3 GPIO_ACTIVE_LOW>; }; };如果设备树里的 compatible 和驱动里的不匹配,probe函数永远进不去,模块加载后cat /proc/bus/input/devices里看不到任何设备。这算是我见过最多的“驱动加载成功但没反应”的原因之一。
5. 编译驱动的一次完整实践:从 make 到 insmod 的详细过程
准备好内核源码、交叉编译链,也确认了 Makefile 没问题之后,就可以正式编译了。以 ARM64 平台为例,完整步骤如下。
5.1 确认环境变量,统一架构
export ARCH=arm64 export CROSS_COMPILE=aarch64-linux-gnu- export KERNELDIR=/path/to/your/kernel/source注意,KERNELDIR必须是已经配置过的内核目录,至少包含.config和Module.symvers。如果是全新的内核源码,先make defconfig和make modules_prepare。对于开发板厂商的 BSP,通常内核已经默认构建过,直接用即可。
5.2 执行编译
cd tp28xx_kdrv_tp9930 make -j8-j8是并行编译,如果内核源码目录的缓存不佳,建议先用-j1试一杯,确保不至于因为并行任务太多导致依赖错乱。编译结束的末尾,你会看到:
CC [M] src/tp9930.o CC [M] src/tp28xx_core.o LD [M] tp9930_drv.ko此时当前目录下生成了tp9930_drv.ko。用file验证一下:
$ file tp9930_drv.ko tp9930_drv.ko: ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), BuildID[sha1]=..., not stripped看到ARM aarch64就说明架构对了。
5.3 交叉检查模块依赖和内核版本
$ modinfo tp9930_drv.ko filename: tp9930_drv.ko vermagic: 5.10.72-g0f5e remotec depends:vermagic必须和目标板的uname -r一致,或者至少前缀一致的 localversion 一致,否则 insmod 时会提示version magic '5.10.72-g0f5e' should be '5.10.72'。这种问题的常见解法是让内核源码的.config里的CONFIG_LOCALVERSION和目标板保持一致,或者用modprobe --force强行加载——但后者能不用就不用,容易出现莫名问题。
5.4 部署到目标板并加载
把tp9930_drv.ko拷贝到目标板,可以用 NFS、adb push 或者 SD 卡拷贝。在目标板上执行:
insmod tp9930_drv.ko加载成功后,dmesg | tail应该能看到驱动probe时的打印,比如:
[tp9930] tp9930_probe: i2c addr=0x38, irq=42 [tp9930] input: tp9930 Touchscreen as /devices/platform/soc/.../input/input3然后cat /proc/bus/input/devices查看是否生成了event节点。如果没有,先检查 I2C 总线地址、中断引脚是否配置正确。
6. 编译和加载阶段的高频报错:根因分析与处理思路
这部分是真正的经验区。触摸屏驱动编译出错,90% 不是代码问题,而是环境问题。以下是我实际踩过的几类坑,按出现频率排序。
6.1 implicit declaration of function xxx
报错示例:
src/tp9930.c:120:2: error: implicit declaration of function ‘gpiod_set_value_cansleep’这类错误通常是因为内核版本不同导致 API 变化。比如gpio_*系列函数在 4.x 内核之后逐渐被gpiod_*取代,而老驱动没有跟着改。遇到类似问题,不要自己硬改,先看内核源码里对应头文件有哪些导出接口,再决定用哪个宏过度。很多厂商驱动会在源码里自带compat.h之类的兼容层,检查一下 Makefile 里是否漏编了某个 compat 源文件。
6.2 error: ‘struct i2c_client’ has no member named ‘irq’
这是新版内核把irq字段从i2c_client中移除,改为通过i2c_client->irq仍然存在?实际上新版内核中i2c_client的irq字段还在,但某些 6.x 内核取消了对旧 API 的默认支持。遇到这类 API 变更问题,去查Documentation/driver-api或者内核源码头文件中的注释,比直接去网上搜更靠谱。
6.3 编译时报错找不到linux/input/mt.h
src/tp28xx_core.c:8:10: fatal error: linux/input/mt.h: No such file or directory这通常意味着你的内核源码树没有配置CONFIG_INPUT_MT,或者内核源码不完整。检查.config:
grep INPUT_MT /path/to/kernel/.config如果没配置,需要重新make menuconfig打开多点触控支持,或者直接把内核源码更新到完整版。
6.4 insmod 时 Unknown symbol
tp9930_drv: Unknown symbol input_allocate_polled_device (err 0)这表示驱动里用到了内核未导出的符号,通常是Module.symvers缺失或不匹配导致的。解决办法是重新编译内核模块目标,并确认Module.symvers里有对应符号。也有可能是驱动依赖其它模块(比如触摸屏框架模块),此时需要先加载依赖模块。
7. 设备树调试与触摸屏校准的几个实战技巧
驱动能在系统里看到 event 节点,只代表内核态基本通了,触摸屏能不能正常上报坐标又是另一件事。
第一件要做的事是用evtest测试原始事件。目标板执行evtest,选择触摸屏事件节点,按压屏幕,看是否产生BTN_TOUCH、ABS_MT_POSITION_X、ABS_MT_POSITION_Y等事件。如果没有任何事件,检查中断是否有触发——在dmesg里看是否有中断风暴或 I2C 读超时,一般是设备树中interrupts配错了触发类型(边沿还是电平)。
第二件是坐标方向、范围不对。这通常是驱动读取到的寄存器参数和实际面板参数不匹配。驱动代码里一般会有x_max、y_max、swap_xy、invert_x等初始化参数,看看这些值是否和屏幕分辨率一致。如果用的是设备树配置,检查touchscreen-size-x、touchscreen-size-y属性是否写正确。
第三件,也是很多人忽略的:触摸屏的电源时序。有些 TP 驱动依赖的一路 LDO 必须在 I2C 通信之前就绪,如果设备树里power-supply或reset-gpio的顺序不对,芯片在 probe 阶段会 NAK,导致驱动枚举失败。此时dmesg里通常有i2c transfer error,而排查方向不是代码,而是硬件上电时序。可以尝试在设备树里增加reset-gpio的GPIO_ACTIVE_LOW → 延时 → 释放动作,或者修改驱动 probe 里 reset 引脚的时序逻辑。
校准方面,如果系统使用了tslib或者内核input直接上报,一般不需要校准;但如果是老式的电阻屏且驱动里没有自带校准参数,可以用 tslib 的ts_calibrate生成校验文件。电容屏驱动通常直接从触摸 IC 的寄存器里拿到坐标,只需要保证设备树里的touchscreen-inverted-x、touchscreen-inverted-y、touchscreen-swapped-x-y属性设置正确。
8. 从解压到量产:驱动落地后的 checklist
最后分享一份我每次调试完驱动都会过一遍的检查清单,这基本上是把一个驱动包从压缩文件变成可量产固件的全过程要点。
- 包内文件是否完整:
README、Makefile、源码、设备树 dtsi、补丁文件是否齐全,缺了什么早期发现比后期排查代价小得多。 - 编译链版本是否和 BSP 一致:
aarch64-linux-gnu-gcc --version记录版本,后面驱动一旦出现异常便于回溯。 - 内核源码树是否干净:如果之前编译被中断过,先
make clean再modules_prepare,避免残存的.o文件干扰。 - 设备树节点是否与驱动 compatible 一致:用
dtc -I fs /proc/device-tree或者查看目标板/sys/firmware/devicetree/base/下对应节点,和源码里的of_match_table比对。 - 模块加载是否干净:
lsmod看看依赖模块是否已加载,dmesg无 error。 - 输入事件是否正常:
evtest里坐标是否平滑、有无跳点;cat /sys/class/input/eventX/device/device/...留意是否有中断错误计数。 - 掉电重启后是否稳定:驱动如果依赖电源时序,冷启动和热重启的表现可能有差异,多做几次 cycle 测试。
我在实际项目里,曾经因为厂商驱动包的 Makefile 里默认KERNELDIR指向的是 Ubuntu 本机的/lib/modules/$(uname -r)/build,导致我忘记了指定ARCH,结果在 x86 主机上编出了 x86 的.ko,拷到 ARM 板子上直接白费了半天。从那以后,每次拿到类似tp28xx_kdrv_tp9930.tar.gz的包,都会先花 10 分钟做环境自查,而不是急着解压编译。
最后再分享一个小经验:如果你对某个驱动包的来历、适用内核版本不放心,可以优先看看包里有没有patches/目录。厂商通常会把适配不同内核版本的补丁按顺序放进去。手动执行git apply或者patch -p1 <之前,先看补丁头部的--- a/xxx和+++ b/xxx路径,确认补丁是基于什么版本做的适配。很多老驱动报错,就是因为缺了关键的 patch,而不是源码本身有问题。这也是为什么我总强调不要只盯着.c文件——补丁往往藏着真正的解决方案。
本文还有配套的精品资源,点击获取