ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RK3399 Android 7.1平台GSL5680触摸驱动编译调试全解析

RK3399 Android 7.1平台GSL5680触摸驱动编译调试全解析 做RK3399的Android方案最绕不开的就是各种各样的外设驱动移植。触摸屏尤其典型因为方案商给过来的驱动代码质量参差不齐尤其是GSL5680这种出货量很大的电容触摸IC厂商驱动版本经常是好几年前写的跟Android 7.1 BSP里自带的Linux 4.4内核一碰就会冒出一大串编译错误。这次文章就是编译GSL5680 touch驱动的完整调试笔记从报错现象、根因分析、修复过程到避坑经验尽量写详细。如果你正在做RK3399 Android 7.1的BSP集成或者要移植其他GSL系列的触摸驱动这篇文章的思路可以直接套用。1. 先说清楚这台设备、这颗芯片、这套代码的背景1.1 RK3399与Android 7.1的BSP构成RK3399是瑞芯微推出的一颗双Cortex-A72加四Cortex-A53的六核平台做盒子、一体机、商业显示、自助终端非常多。Android 7.1的官方BSP里内核版本固定在Linux 4.4配套的交叉编译器一般是aarch64-linux-android-4.9编译系统是老的Android Makefile体系不是后来Android 8.0以后的Soong。这个组合很经典网上资料也多但要注意正因为经典很多方案商给的驱动代码也停留在那个年代代码风格甚至都能看出是老底子翻出来的。GSL5680是思立微Silead的一颗电容触摸控制芯片通过I2C接口和主控通信常见I2C地址是0x40或者0x41。它本身只是一个控制器真正决定触摸精度和手势功能的是芯片内部固件所以驱动和固件要配套这一条后面还要细说。很多工程师拿到这种驱动第一反应是赶紧把代码放进去编译结果被一堆报错淹没其实正确思路是先搞清楚这套代码处在什么年代、依赖了哪些老接口再动手改。1.2 驱动代码从哪里来、要放到哪里编译方案商给GSL5680的驱动一般是打包好的一个目录里面有gsl_ts_driver.c、gsl_ts_core.c、gsl_ts.h这一堆文件甚至还有一份厂商自己的Makefile。很多人第一次拿到手习惯性先执行那份Makefile结果发现根本编不过因为厂商自己写的Makefile通常是给PC上某个老工具链准备的跟Android的内核编译体系完全不是一回事。Android内核编译用的是Kbuild体系由顶层Kconfig、各级Makefile、.config和Kbuild文件共同驱动。正确的做法是把驱动源码放到kernel/drivers/input/touchscreen目录下然后在Kconfig里加一条配置项在Makefile里加一个obj-$(CONFIG_xxx)规则最后再通过menuconfig或者直接修改.config把它打开。如果只是把文件复制进去却忘了改配置编译也能通过因为驱动压根没参与编译等你烧完系统发现触摸完全没反应再回头排查就是白白浪费时间。1.3 本次报错的现象我这次遇到的报错是在执行内核编译命令之后gcc开始编译gsl目录下的源文件时报出来的典型输出长这样drivers/input/touchscreen/gsl5680/gsl_ts_driver.c: In function gsl_ts_probe: drivers/input/touchscreen/gsl5680/gsl_ts_driver.c:328:18: error: struct input_dev has no member named private input-private NULL;类似的还有drivers/input/touchscreen/gsl5680/gsl_ts_core.c:202:4: error: implicit declaration of function set_irq_type表面上看是两个不同的错误一个是结构体成员不存在一个是函数隐式声明但根因是同一条厂商驱动代码依据的是老内核对input子系统和中断子系统的接口设计放到Linux 4.4里接口已经变了。2. 第一层问题内核API差异引发的编译失败2.1 input_dev的private成员去哪了Linux内核的input子系统里struct input_dev用来表示一个输入设备。很老的内核版本中这个结构体里有一个叫private的成员用来保存一些驱动私有的数据结构。后来内核开发者认为这种裸指针放在公共结构体里不优雅改成了input_set_drvdata和input_get_drvdata这一组接口函数结构体成员就被移除了。所以老驱动里出现这种代码input-private ts;在Linux 4.4上就会直接报错。修复方法也不是把它删掉就完事要看后续代码里有没有用input-private取回ts的地方。比较干净的做法是把赋值改成input_set_drvdata(input, ts);对应的读取处改成struct gsl_ts *ts input_get_drvdata(input);如果驱动里根本没用ts指针或者这个input设备没有需要反馈的私有数据那直接删掉也问题不大但为了后续功能扩展我建议还是用新的接口函数维护好驱动私有数据的存取。这里我多说一句改这种错误的时候不要只盯着报错那一行一定要全局搜索一下private这个成员在整个文件里出现了几次赋值和读取是一对漏改任何一边编译是过了运行时空指针就是早晚的事。2.2 中断初始化接口的新旧差异第二个错误是implicit declaration of function set_irq_type。set_irq_type这个函数在很老的内核里存在用来设置中断触发类型比如上升沿、下降沿、双边沿。后来内核推荐使用irq_set_irq_type而且在实际工程中触摸驱动一般不用手动设置中断类型而是在request_irq时通过IRQF_TRIGGER_*标志位来指定。GSL5680这类驱动在probe阶段的一般套路是client-irq gpio_to_irq(gpio_num); if (client-irq) set_irq_type(client-irq, IRQ_TYPE_EDGE_FALLING); ret request_irq(client-irq, gsl_ts_irq_handler, IRQF_TRIGGER_FALLING, gsl_ts, ts);在Linux 4.4上可以改成client-irq gpio_to_irq(gpio_num); ret request_irq(client-irq, gsl_ts_irq_handler, IRQF_TRIGGER_FALLING, gsl_ts, ts); if (ret) dev_err(client-dev, request irq failed: %d\n, ret);也就是说触发方式统一交给request_irq的flags参数处理。还有一种更现代的方式是使用request_threaded_irq把线程化中断和handler分开但触摸屏驱动对中断延迟要求不高用普通request_irq也够。关键是不要在probe里混用两套API否则不仅编译报错真跑起来中断触发行为也可能和你预期不一致。2.3 GPIO操作接口的兼容处理GSL系列驱动里还会频繁用到gpio_request、gpio_direction_output、gpio_direction_input、gpio_set_value这套老接口。在Linux 4.4上这些接口还在但如果你用的是设备树方式管理板级资源老驱动里那种直接在probe里gpio_request的写法在新平台也能编过真正的坑在于GPIO号从哪来。老平台上内核通过板级文件mach-xxx.c定义GPIO号驱动里写死某个数字gpio_request(1021, gsl_ts_reset)。到了RK3399的设备树体系里GPIO号是一个由控制器基数和引脚偏移计算出来的整数而且不同脚位编号规则还不一样写死基本等于撞运气。推荐做法是在设备树里加两个属性比如i2c4 { status okay; gsl568040 { compatible silead,gsl5680; reg 0x40; reset-gpios gpio1 RK_PB3 GPIO_ACTIVE_LOW; irq-gpios gpio1 RK_PB4 GPIO_ACTIVE_LOW; }; };驱动里用of_get_named_gpio或者更推荐的gpiod_get接口去取再配合devm_gpio_request_one做资源管理。不过这一步改起来需要同时改设备树和驱动工作量比改API名称大建议先确认BSP本身的默认GPIO管理方式是设备树还是板级代码。像RK3399 Android 7.1 SDK默认就是设备树那还是老老实实适配设备树方式省得后面启动时报GPIO冲突。3. 第二层问题Kconfig与Makefile才是隐藏的大坑3.1 为什么厂商自己的Makefile是个陷阱拿到GSL5680驱动时目录里那份Makefile非常容易迷惑人。它一般是这样的obj-m : gsl_ts.o gsl_ts-objs : gsl_ts_driver.o gsl_ts_core.o KERNEL_DIR ? /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: $(MAKE) -C $(KERNEL_DIR) M$(PWD) modules这份Makefile的设计目标是给PC Ubuntu调试用KERNEL_DIR直接指向宿主机内核跟你RK3399的目标内核完全是两码事。如果你照着执行很可能编出一堆x86架构的.ko烧到板子上一加载就是invalid module format。更隐蔽的是有些版本带了Makefile但漏了Kbuild文件直接make modules会报错反而把你引向错误的方向。正确姿势是放弃厂商Makefile把驱动融进目标内核的Kbuild体系。具体做法是先在kernel/drivers/input/touchscreen/下新建一个目录比如gsl5680/把源码放进去然后修改上一级Kconfig和Makefile。3.2 Kconfig和Makefile的合入步骤以Linux 4.4内核为例Kconfig里添加config TOUCHSCREEN_GSL5680 tristate Silead GSL5680 touchscreen support depends on I2C help Say Y here if you have a Silead GSL5680 touchscreen connected to your system.Makefile里添加obj-$(CONFIG_TOUCHSCREEN_GSL5680) gsl5680/然后gsl5680目录下放一个自己的Makefileobj-$(CONFIG_TOUCHSCREEN_GSL5680) gsl_ts.o gsl_ts-objs : gsl_ts_driver.o gsl_ts_core.o这里的gsl_ts-objs必须对应你实际放进去的源文件名如果漏了某个.o编译时会出现undefined symbol但报错位置又在链接阶段容易让人误判成源码逻辑问题。配置打开有两种方式。一种是直接改.config在文件里加CONFIG_TOUCHSCREEN_GSL5680y注意内核顶层.config是编译时生成的如果你直接改要确保后续make不会重新生成配置覆盖它最稳妥还是用make menuconfig在Device Drivers - Input device support - Touchscreens下面找到Silead GSL5680这一项按Y或M。如果编成模块还要记得把生成的.ko打包进系统镜像Android 7.1一般放在out/target/product/rk3399/system/lib/modules/或者对应rootfs里放错位置照样加载失败。3.3 如何确认驱动真的编进去了这一步特别重要。很多时候你改了Kconfig以为驱动编进去了结果make之后发现没有任何反应或者报错信息根本没有gsl相关文件。验证手段就一条看编译日志。关键行是这样的CC drivers/input/touchscreen/gsl5680/gsl_ts_driver.o CC drivers/input/touchscreen/gsl5680/gsl_ts_core.o LD drivers/input/touchscreen/gsl5680/gsl_ts.o对于编进内核的方式最终会看到gsl5680目录生成built-in.o对于模块方式会看到gsl_ts.ko。如果日志里这两行都没有说明你的CONFIG项没打开或者Kconfig/Makefile的路径写错了。还有一个更快的办法编译完成后直接grepgrep -i gsl out/target/product/rk3399/obj/KERNEL_OBJ/.config如果输出里没有CONFIG_TOUCHSCREEN_GSL5680那问题肯定还在配置环节不用急着去改源码。4. 第三层问题Android编译环境的那些暗坑4.1 交叉工具链和32位库RK3399 Android 7.1 SDK对编译环境的依赖比较敏感。建议用Ubuntu 16.04 x64SDK自带的交叉编译器在prebuilts/gcc/linux-x86/aarch64/下如果缺32位运行库执行make时会报cannot execute binary file: Exec format error这个报错很典型。我见过不少新手在这里卡了一下午以为是自己代码问题其实是宿主机环境问题。解决方法是在Ubuntu上安装对应32位库sudo dpkg --add-architecture i386 sudo apt-get update sudo apt-get install libstdc6:i386 libgcc1:i386 zlib1g:i386 libncurses5:i386如果是Ubuntu 14.04包名略有差异但思路一样。还有一点不要图方便直接在root用户下编译有些SDK脚本对root做了检查直接make会报错误建议用普通用户并且保证源码目录属主正确。4.2 并行编译和增量编译的假错误Android编译默认可以加-j参数并行但并行度过高会导致两类问题。第一类是内存不足编内核时每个编译单元都消耗内存-j32在某些8G内存的机器上会频繁触发OOM然后报出一些莫名其妙的错误比如找不到头文件、语法错误等其实都是内存不足导致的假象。第二类是竞争条件虽然老版本Makefile经过很多轮修复但偶尔还是会出现两个目标同时写同一个文件的情况表现就是同一个文件在连续两次编译中报错位置不一样。遇到这种情况先不要慌先看报错是不是可复现。重新单编一次内核模块如果错误消失了多半就是并行编译的锅。我在RK3399平台上习惯用-j8机器好一点可以-j16很少用到-j32。增量编译也有坑。你修改了驱动源码但编译系统可能因为时间戳或者依赖关系没有正确重建导致你的修改没有生效。这时候强制重建目标文件touch kernel/drivers/input/touchscreen/gsl5680/*.c make bootimage -j8或者更暴力一点直接把obj目录下的对应.o文件删掉再编。不要动不动就make clean那会浪费大量时间。4.3 用make V1看清真实编译命令排查编译异常时强烈建议打开详细日志make bootimage V1 -j8V1会把你每一步的完整命令都打出来包括编译器路径、包含的头文件路径、宏定义、优化选项。看到报错后可以从日志里确认编译器是不是用的aarch64交叉编译器、头文件搜索路径是不是指向了kernel/include、有没有多出不该有的宏定义。很多时候某些错误是因为编译命令里混入了宿主机路径导致的比如头文件路径包含/usr/include这种问题在日志里一眼就能看出来。还有一点Android 7.1的make bootimage最终会把内核打包进boot.img而boot.img的生成逻辑在device/rockchip/common/下跟驱动源码本身关系不大。如果改的是驱动主要看内核Image和dtb有没有生成成功最后打包的环节很少出问题。5. 常见问题速查表与我的排查经验5.1 编译错误速查表我整理了这次调试中碰到以及平时常遇到的GSL5680相关问题直接作成一个速查表方便大家遇到类似错误时对照报错信息或现象可能原因处理办法struct input_dev has no member named private老驱动使用input_dev私有成员新版内核已移除改用input_set_drvdata / input_get_drvdataimplicit declaration of function set_irq_type旧中断接口新内核改名或建议用flags改成irq_set_irq_type或将触发方式放request_irq flagsundefined reference to xxx 出现在链接阶段Kbuild里gsl_ts-objs漏了源文件检查gsl5680目录的Makefile配置.config里没有CONFIG_TOUCHSCREEN_GSL5680Kconfig未合入或配置未打开补Kconfig后make menuconfig选中编译后根本没有gsl相关日志Makefile/Kconfig路径不对或CONFIG未打开用grep查.config看CC日志cannot execute binary file: Exec format error宿主机缺32位库或工具链架构不对安装lib32库确认使用SDK自带工具链同一文件两次编译报错位置不同并行编译竞争或内存不足减小-j数量或先编kernel再编bootimg驱动编进去了但触摸无反应固件缺失 / I2C地址错 / 中断GPIO不对查固件加载日志核对设备树和dmesg这个表虽然不是GSL5680专属但对于任何老驱动移植到新内核的场景基本都适用。5.2 排查编译问题的高效套路踩过几次坑之后我总结了一套顺序固定的排查套路效率高很多第一步先确定报错来自哪个编译阶段。CC阶段的问题通常是源码语法或API差异LD阶段的问题多半是符号缺失打包阶段的问题则要查rootfs或镜像脚本。不同阶段的问题排查方法完全不同先判断再动手不要上来就改代码。第二步看报错列表里的第一条error而不是后面的warning或者重复报错。编译器有时候会被同一个错误搞出一堆连带错误真正的原因往往只有前两三条。第三步确认改动真的生效。用git status或git diff看改动用grep确认CONFIG置位再用时间戳确认.o文件重新生成了。这个习惯能帮你省掉一大半我明明改了为什么没效果的困惑。第四步小范围单编。在Android环境里可以单独编内核部分而不是每次都make整个系统比如make kernel -j8这样反馈速度极快改一下源码几十秒就能看到下一轮报错。5.3 从编译通过到触摸点亮还差了哪些事编译通过只是一个开始真正把触摸点亮后面还有几道关。GSL5680驱动在probe阶段会通过I2C向芯片写入固件固件一般是一个二进制数组直接放在头文件里或者从外部文件加载。如果固件数据损坏或者驱动里配置的分辨率参数和实际模组不一致就算驱动注册成功点击屏幕也不会有坐标上报。此时先看dmesgdmesg | grep -i gsl正常情况能看到gsl_ts_probe、firmware load ok类似的日志。然后看输入设备节点cat /proc/bus/input/devices确认是否有gsl_ts这个设备。最后用getevent抓事件getevent手指点击屏幕如果终端弹出type、code、value的事件流说明触摸已经通了接下来就是校准和调参的事。所以嵌入式调试的核心思维是把大目标拆成编译、加载、设备创建、事件上报、坐标校准这几个小节点按节点逐项验证才不会陷入盲目改代码的循环。这次调试最花时间的其实不是报错本身而是前前后后浪费在驱动到底编没编进去和为什么改完没生效这两件事上。后来我养成了一个习惯拿到任何方案商的驱动代码第一件事不是看业务逻辑而是先确定它的Makefile和内核版本依赖把所有跟Kbuild、设备树、老接口相关的部分提前梳理清楚再动手改代码。编译报错不可怕可怕的是报错信息本身会诱导你陷入细节忘了真正该把力气花在接口适配和配置验证上。如果你也正在被GSL5680或者其他触摸驱动的编译问题折磨可以从Kconfig配置和API版本差异入手检查一遍大概率能找到问题所在。等编译过了记得留时间验证固件和I2C地址别以为编过去就是稳了。
返回列表