全志A133平台Linux驱动配置实战:从设备树到内核编译完整指南
1. 项目缘起:一次由“缺驱动”引发的板卡启动失败
最近在调试一块基于全志A133平台的新板子,遇到了一个典型的嵌入式开发“拦路虎”:系统启动后,某个关键外设(比如一个I2C接口的TP芯片)死活不工作。用ls /dev/一看,对应的设备节点压根没生成;用dmesg翻看内核启动日志,也只能看到一句“probe failed”或者干脆没有相关驱动加载的痕迹。问题直指内核配置——驱动没配进去。
这场景对于玩全志平台的朋友来说太熟悉了。无论是A50、A133,还是T527、H616,这些芯片功能强大、性价比高,但官方SDK的驱动配置体系对于刚上手的新人,甚至是有经验的开发者调整新外设时,都像是一座需要耐心翻越的小山。网上的资料要么是零散的代码片段,要么是过于笼统的“勾选某个选项”,真正把“为什么这么配”以及“配错了会怎样”讲清楚的内容并不多。
今天,我就结合这次给A133新增TP驱动(以Goodix GT9XX系列为例)的实际经历,把在全志Linux内核(以Linux 4.9/5.4等长期支持版本为例)中新增驱动的完整流程、背后的配置逻辑,以及那些容易踩坑的细节,系统地梳理一遍。目标很明确:让你不仅能跟着步骤“配出来”,更能理解每一步在做什么,下次遇到其他外设也能举一反三。
2. 全志平台驱动配置体系深度解析
在动手修改任何一个文件之前,我们必须先理解全志SDK中驱动配置的“游戏规则”。这不同于标准的、纯内核的make menuconfig,它是一套结合了全志硬件特性和产品快速开发需求的“增强型”配置体系。
2.1 核心配置文件:sys_config.fex与board.dts
这是全志平台驱动配置的“总开关”和“硬件描述书”,理解它们的关系至关重要。
sys_config.fex文件:这是全志沿用自Allwinner方案的经典配置脚本。它是一个文本文件,使用类似INI的格式,定义了芯片各个引脚的功能复用(Pin Mux)、电源管理、时钟以及大量外设的板级参数。例如,你要配置一个UART,不仅需要打开内核驱动,还需要在sys_config.fex里指定使用哪两个引脚作为TX和RX,并配置好波特率。
注意:在新版本的SDK(尤其是Linux 5.x内核以后)中,全志正在推动向标准设备树(Device Tree)的迁移。但大量现有项目和BSP包仍然严重依赖
sys_config.fex,而且很多底层硬件初始化(特别是boot阶段)仍由其控制。因此,它目前是不可或缺的。
board.dts文件:这是Linux内核标准的设备树源文件。它以一种结构化的数据格式描述了硬件的拓扑结构,包括CPU、内存、总线以及挂载在总线上的各种设备(如I2C设备、SPI设备)及其属性。内核在启动时会解析这个文件,并根据其中的描述来动态加载和初始化驱动。
两者的分工与协作:
sys_config.fex管“硬”的:主要负责芯片上电早期、内核启动前的硬件环境初始化,特别是引脚复用、电源和时钟的初始状态。这部分配置会被一个叫sunxi-fex的工具编译成二进制格式,并打包进最终的固件镜像。board.dts管“软”的:主要负责向Linux内核描述板上有什么设备、设备地址是什么、中断号是多少、需要哪些驱动参数等。内核驱动通过匹配设备树中的compatible属性来绑定设备。
在实际操作中,一个外设要正常工作,往往需要两者配合。例如一个I2C触摸屏:
- 在
sys_config.fex中,你需要配置对应的I2C控制器引脚(如twi2_sda,twi2_scl)为I2C功能,并可能使能相关的电源域。 - 在
board.dts中,你需要在i2c2节点下添加一个子节点,描述这个TP的I2C地址、中断引脚、compatible字符串(用于匹配驱动)以及厂商提供的具体参数(如复位脚、最大坐标等)。
2.2 驱动代码的归宿:内核配置 (Kconfig&Makefile)
设备树告诉了内核“板上有个设备”,但内核里必须有对应的驱动代码才能让它动起来。这部分就是标准的Linux内核模块管理。
Kconfig文件:定义了在make menuconfig时出现的配置选项。它决定了这个驱动是编译进内核(y)、编译为模块(m)、还是不编译(n)。全志通常会将自家芯片和外设的驱动配置放在类似drivers/input/touchscreen/Kconfig这样的路径下。Makefile文件:指明了如何编译这个驱动,即哪些源文件(.c)在哪种配置条件下会被编译。
为全志平台新增驱动,大部分情况下不是从零写驱动代码,而是确保正确的驱动代码在正确的配置下被编译,并确保设备树或sys_config.fex提供了正确的硬件信息。
2.3 配置生效的完整流程
理清了关键文件,我们来看从修改配置到驱动生效的完整数据流,这能帮你定位大部分“配置了但没生效”的问题:
- 修改
sys_config.fex-> 使用fex2bin(或SDK中的打包脚本)将其编译成sys_config.bin-> 该文件被放入boot分区(通常是boot.img的一部分),在uboot和内核早期初始化阶段被读取。 - 修改
board.dts-> 使用设备树编译器dtc将其编译成board.dtb(设备树二进制文件)-> 该文件同样被放入boot分区,由Linux内核在启动阶段解析。 - 配置内核(
make menuconfig) -> 生成.config文件 -> 根据此文件编译内核zImage和驱动模块。 - 系统启动:
- Uboot加载
sys_config.bin,完成最基础的引脚、时钟初始化。 - Uboot加载内核镜像
zImage和board.dtb到内存,并跳转到内核。 - 内核启动,解析
board.dtb,根据其中的节点遍历所有总线。 - 当内核发现一个设备节点(如
i2c2下的touchscreen@5d),它会读取其compatible属性。 - 内核在所有已编译的驱动中,寻找
of_device_id表里compatible字段与之匹配的驱动。 - 如果找到匹配的驱动,且驱动被编译进了内核(
y)或可作为模块加载,内核就会调用驱动的probe函数来初始化该设备。probe函数会从设备树节点中读取中断号、寄存器地址等参数,最终创建设备文件(如/dev/input/event0)。
- Uboot加载
一个常见的误区:只在menuconfig里勾选了驱动,但没在设备树或sys_config.fex里描述硬件,驱动probe时找不到硬件信息,就会失败。反之亦然。
3. 实战:为A133新增Goodix GT9XX触摸驱动
下面我们进入实战环节。假设我们的A133板子,I2C2接口上挂了一个Goodix GT911触摸芯片,中断接在PG12,复位脚接在PG10。
3.1 第一步:硬件连接与原理图确认
这是所有软件配置的基石,绝对不能错。
- 确认I2C总线:查原理图,确认TP的
SDA和SCL线连接到了SoC的哪一组I2C上。假设是TWI2(对应Linux内核中的i2c2)。 - 确认中断引脚:查原理图,TP的
INT脚接到了哪个GPIO。假设是PG12。记住,在Linux设备树中,中断需要配置为下降沿触发或电平触发,具体看芯片数据手册。GT911通常配置为下降沿触发(IRQ_TYPE_EDGE_FALLING)。 - 确认复位引脚:查原理图,TP的
RESET脚接到了哪个GPIO。假设是PG10。复位时序(拉低多久再拉高)通常在驱动里或设备树中指定。 - 确认I2C地址:Goodix芯片的I2C地址可能是
0x5d或0x14,具体看原理图上ADDR引脚的上拉下拉情况。假设为0x5d。
3.2 第二步:配置sys_config.fex(引脚复用与电源)
找到SDK中的sys_config.fex文件(路径可能类似device/config/chips/a133/configs/{方案名}/sys_config.fex)。
配置I2C2引脚:
[twi2] twi2_used = 1 twi2_scl = port:PE12<2><default><default><default> twi2_sda = port:PE13<2><default><default><default>twi2_used = 1表示启用TWI2控制器。twi2_scl和twi2_sda定义了引脚复用。port:PE12<2>...表示PE12引脚复用为功能2(即I2C功能)。这里的PE12和PE13需要根据你的实际原理图修改。<2>就是复用功能号,全志每个引脚的功能号是固定的,需要查《A133用户手册》的Pin Mux章节。
配置中断和复位GPIO(可选但推荐): 虽然设备树中也会配置,但在
sys_config.fex中预先配置这些GPIO的初始状态(如上拉、下拉、驱动能力)可以避免启动阶段的毛刺。通常放在[gpio_para]或其他GPIO配置段,但更常见的做法是只在设备树中配置,因为设备树描述的是内核接管后的状态。对于复位脚,为了确保开机稳定,有时会在sys_config.fex的[power]段或特定脚本里先拉高。
3.3 第三步:配置设备树board.dts
这是最关键的一步,告诉Linux内核这个设备的存在。文件路径通常为kernel/linux-4.9/arch/arm64/boot/dts/sunxi/或类似位置下的{板型}.dts。
- 找到正确的I2C控制器节点:在设备树文件中找到
i2c2节点。它可能已经被定义,也可能在i2c0,i2c1旁边,需要你添加。&i2c2 { clock-frequency = <400000>; status = "okay"; gt911@5d { compatible = "goodix,gt911"; reg = <0x5d>; interrupt-parent = <&pio>; interrupts = <12 2>; // PG12, 2 代表下降沿触发 reset-gpios = <&pio 10 1 GPIO_ACTIVE_LOW>; // PG10, 低电平有效 irq-gpios = <&pio 12 0>; // PG12 touchscreen-size-x = <800>; touchscreen-size-y = <480>; // 其他厂商特定参数,如: // goodix,cfg-data = [ ... ]; // 配置寄存器数组 }; };compatible = "goodix,gt911";:这是驱动匹配的“钥匙”。内核中Goodix驱动的of_device_id表里必须有一项是"goodix,gt911"。reg = <0x5d>;:I2C设备地址。interrupts = <12 2>;:这是中断说明。<12 2>的含义是:中断号是GPIO编号12(即PG12),触发类型是2(IRQ_TYPE_EDGE_FALLING)。这里的GPIO编号是全局编号,计算方式是组号*32 + 组内序号。对于PG12,P是G组,组内序号12,所以编号是6*32 + 12 = 204?等等,这里容易出错!实际上,全志的设备树中常用<&pio PIN IRQ_TYPE>格式,PIN就是PG12中的12。而interrupts单元格的具体含义依赖于interrupt-parent。上面例子是一种简化写法。更准确的做法是查阅SDK中类似板子的写法。一种常见的正确写法是:
或者直接使用数字:interrupts = <PIN_GPIO(6, 12) IRQ_TYPE_EDGE_FALLING>; // 假设PIN_GPIO是宏interrupts = <204 IRQ_TYPE_EDGE_FALLING>;(如果PG12的全局中断号是204)。这里是最易错点,必须参考原SDK中其他中断的写法!reset-gpios和irq-gpios:使用GPIO描述符的方式指定复位和中断引脚,更现代和推荐。<&pio 10 1>表示使用pio控制器,第10个引脚(PG10),1可能代表GPIO_ACTIVE_LOW。同样需要参考现有代码。status = "okay";:确保I2C控制器本身是启用的。
3.4 第四步:配置内核,确保驱动被编译
- 进入内核目录:
cd kernel/linux-4.9(具体路径根据你的SDK而定)。 - 执行菜单配置:
make ARCH=arm64 menuconfig(A133是64位,所以是arm64。A50是arm)。 - 找到触摸屏驱动配置:
- 使用
/键搜索GOODIX或GT9XX。 - 或者按路径导航:
Device Drivers->Input device support->Touchscreens-><*> Goodix I2C touchscreen。
- 使用
- 选择编译方式:
- 按
y将驱动编译进内核:这样驱动会直接包含在zImage里,开机自动加载,适合核心、必须的驱动。 - 按
m将驱动编译为模块:会生成一个.ko文件,需要手动insmod或配置系统自动加载。适合调试或不常用的驱动。建议:首次调试选m(模块),方便反复加载、卸载、调试,不成功也不影响内核启动。稳定后改为y。
- 按
- 保存退出:选择
Save,保存到默认的.config文件。 - 重新编译内核和模块:
如果只编译了模块,也需要执行make ARCH=arm64 -j8 make ARCH=arm64 modules -j8make modules。
3.5 第五步:集成与烧录
- 更新设备树:编译内核后,设备树源文件(
.dts)会被编译成二进制文件(.dtb)。你需要将这个新的.dtb文件替换掉SDK打包目录(如out/{方案名}/)中对应的文件。 - 更新
sys_config.bin:使用SDK提供的脚本(如pack脚本)或fex2bin工具,将修改后的sys_config.fex转换成sys_config.bin,并更新到打包目录。 - 打包固件:在SDK根目录执行
./build.sh pack(具体命令看SDK文档),它会将所有组件(uboot, kernel, dtb, sys_config.bin, rootfs)打包成一个可烧录的镜像(如sunxi.img)。 - 烧录与测试:使用PhoenixSuit、AllwinnerTech PhoenixUSBPro或其他烧录工具将镜像烧录到板子。
- 上电验证:
- 查看设备节点:
ls /dev/input/看是否有eventX出现。 - 查看内核日志:
dmesg | grep -i goodix或dmesg | grep -i touch,查看驱动probe是否成功,有无错误信息。 - 测试输入:使用
cat /dev/input/eventX(用实际的event号) 然后触摸屏幕,看是否有乱码输出。或者使用evtest工具进行更专业的测试。
- 查看设备节点:
4. 排错指南:当驱动没有按预期工作时
按照上述步骤操作,大部分情况下驱动都能工作。但如果没工作,别慌,按照以下链路系统性排查,这才是真正体现经验的地方。
4.1 排查链第一步:内核启动日志 (dmesg)
这是最重要的信息源。上电后,立刻在串口终端执行dmesg | grep -E \"(goodix|i2c|input|touch)\"。
场景A:完全没有相关日志
- 可能原因1:设备树节点未生效。检查
board.dts中i2c2节点的status是否为"okay";检查TP子节点的compatible字符串是否拼写错误;检查设备树是否被正确编译和打包。 - 可能原因2:I2C控制器引脚复用错误。检查
sys_config.fex中twi2的配置,特别是引脚号和功能号。用cat /sys/kernel/debug/pinctrl/pio/(路径可能不同)查看引脚复用状态。 - 可能原因3:驱动根本未编译。检查
.config文件,确认CONFIG_TOUCHSCREEN_GOODIX是=y或=m。如果是=m,检查/lib/modules/$(uname -r)/下是否有goodix.ko文件。
- 可能原因1:设备树节点未生效。检查
场景B:有I2C通信错误日志
- 类似
i2c i2c-2: sendbytes: error -110或gt911 2-005d: Failed to read config。 - 可能原因1:I2C地址错误。用
i2cdetect -y 2(假设是i2c-2总线)扫描,看0x5d地址是否出现(UU表示被驱动占用,5d表示设备存在但无驱动)。 - 可能原因2:电源或复位时序问题。测量TP芯片的供电电压是否正常。检查复位脚波形,驱动可能在
probe时进行了复位操作,但复位时间不足或过长。可以在设备树中调整复位延时参数,或检查硬件复位电路。 - 可能原因3:中断问题。如果驱动需要中断但中断配置错误,可能导致通信超时。检查设备树中的中断配置,用
cat /proc/interrupts查看gt911中断是否被注册以及触发次数。
- 类似
场景C:驱动probe成功,但无输入事件
- 日志显示
input: goodix_ts as /devices/platform/soc/.../input/inputX,但/dev/input/下没有事件或事件无效。 - 可能原因1:坐标轴反转或缩放。检查设备树中的
touchscreen-size-x/y是否正确,或者尝试在驱动中或用户空间(通过evdev)交换X/Y坐标。 - 可能原因2:触摸屏上报的数据格式不对。有些TP需要加载特定的固件或配置表(
cfg-data)。检查驱动文档,看是否需要通过设备树属性goodix,cfg-data传入配置数组。
- 日志显示
4.2 排查链第二步:深入系统状态检查
- 检查设备树是否被加载:
cat /proc/device-tree/可以浏览内核解析后的设备树。找到i2c2节点,看其下是否有gt911子节点,属性是否正确。 - 检查I2C总线:
i2cdetect -l列出所有I2C总线。i2cdetect -y 2扫描总线2上的设备。 - 检查GPIO状态:
cat /sys/kernel/debug/gpio查看GPIO使用情况,确认你的中断和复位GPIO没有被其他驱动占用。 - 检查模块是否加载:
lsmod | grep goodix。如果是模块,确保已加载insmod goodix.ko。
4.3 常见坑点与经验之谈
- GPIO编号的“坑”:全志平台GPIO编号方式多样,有直接数字(如
204),有Px宏(如PIN_PG12),有<bank, nr>对。务必、务必、务必参考你所用SDK中其他设备(如LED、按键)的写法,保持一致。这是新手最容易栽跟头的地方。 - 时钟与电源域:有些外设(如某些TP)可能挂在一个需要单独使能的电源域下,或者其父I2C控制器的时钟需要额外配置。如果排查了所有软配置都不行,去查芯片手册,看该外设是否有特殊的电源/时钟要求,并在
sys_config.fex的[power]或[clock]段进行配置。 - 驱动版本匹配:从GitHub或其他地方找的驱动,可能与你内核的API不兼容。优先使用SDK自带的驱动,或确保你移植的驱动适配你的内核版本(特别是设备树、GPIO、中断相关的API)。
sys_config.fex与设备树的冲突:如果同一个引脚在两个文件里配置了不同的功能,谁最后生效取决于初始化顺序,可能导致不可预知的行为。尽量保持配置一致,或者明确知道初始化流程。- 调试符号与打印:如果问题棘手,可以尝试打开驱动的调试信息。在内核配置中打开
CONFIG_DYNAMIC_DEBUG,然后在驱动加载后执行echo -n 'module goodix +p' > /sys/kernel/debug/dynamic_debug/control,这样驱动内部的dev_dbg打印就会输出到dmesg,获得更详细的运行信息。
5. 举一反三:其他类型驱动的配置思路
掌握了I2C触摸屏的配置,其他类型的驱动大同小异,核心都是“内核配置 + 硬件描述”两板斧。
UART串口:
sys_config.fex: 配置uartX段,指定TX、RX引脚及功能号。board.dts: 通常串口控制器节点已使能,主要检查status = "okay"和波特率等参数。如果使用串口作为控制台,还需修改uboot和内核的启动参数(bootargs)。- 内核配置:
Device Drivers->Character devices->Serial drivers->Allwinner SoC serial support。
SPI设备(如屏幕、Flash):
sys_config.fex: 配置spiX段,指定CLK、MOSI、MISO、CS引脚。board.dts: 在spiX节点下添加子节点,定义compatible、reg(片选号)、spi-max-frequency等。- 内核配置:启用对应的SPI控制器驱动和具体设备驱动(如
CONFIG_SPI_SPIDEV用于测试,或具体的显示屏驱动)。
GPIO-Keys(按键):
- 主要工作在设备树。在
/根节点或gpio-keys节点下定义子节点,指定gpios、linux,code(对应键盘键值)等。 - 无需在
sys_config.fex特殊配置(除非需要上拉/下拉)。 - 内核配置:
Device Drivers->Input device support->Keyboards->GPIO Buttons。
- 主要工作在设备树。在
背光/PWM:
sys_config.fex: 可能配置PWM引脚复用。board.dts: 定义backlight节点,关联到PWM控制器。- 内核配置:启用
CONFIG_PWM_SUNXI和CONFIG_BACKLIGHT_PWM。
核心思想:先确定外设的通信总线类型(I2C/SPI/UART等)或控制类型(GPIO/PWM等),然后去配置对应的控制器,最后在控制器下添加设备节点。内核配置则确保这条路径上的所有驱动(总线控制器驱动、设备驱动)都被编译。
6. 进阶:驱动配置的优化与固化
当驱动调试稳定后,我们还需要考虑如何让配置更优化、更易于维护。
1. 将驱动编译进内核 vs. 编译为模块
- 进内核(
y):启动速度快,驱动总是可用。适合系统必需的基础驱动(如eMMC、USB Host控制器)。 - 模块(
m):减小内核镜像大小,增加灵活性,可以动态加载/卸载。适合调试阶段、或非必需/可插拔的设备驱动(如某些传感器、特定型号的Wi-Fi模块)。 - 建议:基础平台驱动用
y,外设驱动根据情况用m。在产品发布时,为了启动速度和可靠性,通常会把所有必需驱动编译进内核。
2. 设备树的重用与覆盖如果你的项目有多个衍生板卡,它们大部分配置相同,只有少数外设不同(比如一个板子有TP,另一个没有),可以使用设备树覆盖(dt-overlay)或条件编译。
- 全志SDK常用方法:在
board.dts中使用#include包含一个公共的.dtsi文件,然后在板级.dts文件中只进行差异化的修改(如启用或禁用某个节点)。在打包脚本中,选择对应的.dts文件进行编译。
3. 配置的版本管理sys_config.fex和board.dts是纯文本文件,一定要纳入Git等版本控制系统。每次修改前做好备份,修改时写清注释,说明修改原因和日期。这对于团队协作和问题回溯至关重要。
4. 自动化编译脚本不要手动执行每一步编译命令。利用SDK提供的build.sh脚本,或者自己编写一个Makefile,将内核配置、编译、设备树编译、打包等步骤串联起来,实现一键编译,减少人为失误。
调试驱动配置的过程,就像在解一个多维度的谜题,硬件连接、引脚复用、设备树、内核编译环环相扣。最宝贵的经验往往来自于那些最令人头疼的失败案例。每次成功点亮一个新设备,你对整个嵌入式Linux系统的理解就会加深一层。希望这篇基于A133实战的总结,能帮你少走些弯路,更顺畅地驾驭全志平台。
