当前位置: 首页 > news >正文

IMX6ULL的Linux内核移植

在前面的博客中,我们已经分别介绍了U-Boot 移植U-Boot 启动流程分析以及Linux 内核启动流程分析

在这些内容发布后,也有一些读者提出了几个比较常见的问题:为什么 U-Boot 源码和 Linux 内核源码中都有设备树 DTS 文件?系统启动时到底使用的是哪一个设备树?Linux 内核在编译时,又是如何根据配置去编译各个目录下驱动程序的?

这些问题都和后续的 Linux 内核移植密切相关。因此,在正式介绍 Linux 移植之前,我们先对这些基础问题做一个统一说明,方便后面理解内核移植、设备树适配以及驱动裁剪的整体流程。

1、为什么 U-Boot 和 Linux 内核源码中都有 DTS?启动时到底使用哪一个?

U-Boot 和 Linux 内核源码中都有设备树 DTS 文件,但它们的作用不同。

①U-Boot 中的设备树主要是给U-Boot 自己使用的。在编译 U-Boot 时,DTS 会被编译进镜像中,最终生成u-boot-dtb.imx。U-Boot 运行时,会解析自己镜像中的设备树,并根据设备树信息匹配、probe 对应的驱动,用来初始化板子的基础硬件。

②而 Linux 内核使用的设备树,一般是由Linux 源码中的 DTS 编译生成的 DTB 文件。这个 DTB 会由 U-Boot 单独加载到内存中,然后传递给 Linux 内核使用。

这里需要注意:zImagedtb是两个不同的文件。编译 Linux 内核时,内核镜像和设备树是分开生成的,启动时也是分开加载的。例如:

setenv kernel_addr_r 0x80800000 setenv fdt_addr_r 0x83000000

其中,kernel_addr_rzImage的加载地址,fdt_addr_rdtb的加载地址。U-Boot 会分别加载它们,然后通过bootz启动 Linux 内核,并把设备树地址传给内核。

2、Linux 内核编译时,是如何决定编译哪些驱动的?

Linux 内核编译驱动的方式,和前面介绍 U-Boot 编译时的逻辑类似,都是通过Kconfig + Makefile来决定的。以内核默认配置文件为例:

arch/arm/configs/imx_v7_angus_defconfig

执行配置命令后,Kconfig 会根据这个defconfig生成最终的.config文件。.config中记录了哪些驱动需要编译、哪些不编译,以及哪些驱动需要编译成模块。随后,内核编译系统会根据.config生成:

include/config/auto.conf

真正编译时,各级目录下的 Makefile 会读取这些配置项,决定是否进入对应目录,以及是否编译对应的驱动源码。

目录

IMX6ULL的Linux内核移植

Linux内核移植总结

Linux 内核移植补充

第一阶段:i2c_adapter 与 i2c_client 初始化

第二阶段:I2C 读取数据与 input 子系统上报数据

总结

触摸屏适配


IMX6ULL的Linux内核移植

通过上面的解答,再结合之前对Linux 内核编译Linux 内核启动流程的分析,大家应该可以发现:Linux 内核移植中最重要的两个文件就是defconfig 配置文件设备树 DTS 文件

第一个是内核配置文件:

arch/arm/configs/imx_v7_angus_defconfig

它主要决定 Linux 内核编译时需要开启哪些功能、编译哪些驱动。

第二个是设备树文件:

arch/arm/boot/dts/imx6ull-14x14-angus.dts

它主要用来描述开发板上的硬件资源,例如串口、网卡、MMC、I2C、SPI、GPIO 等外设。

Ubuntu 端:编译并放入 TFTP 目录

make 100ask_imx6ull_defconfig //取决于你使用什么**deconfig文件,这只是一个例子 make -j4 zImage dtbs sudo cp arch/arm/boot/zImage /home/book/tftpboot/ sudo cp arch/arm/boot/dts/imx6ull-14x14-angus.dtb /home/book/tftpboot/ 检查文件: ls -l /home/book/tftpboot/zImage /home/book/tftpboot/imx6ull-14x14-angus.dtb

板子端:在 U-Boot 中下载并启动 Linux

setenv ipaddr 192.168.1.88 setenv serverip 192.168.1.66 setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw' 更新 u-boot-dtb.imx 后,在 U-Boot 中先用较小的数据块测试: setenv tftpblocksize 512 setenv tftptimeoutcountmax 100 tftp 0x80800000 zImage 如果可以下载成功,再测试默认大块传输: tftp 0x80800000 zImage tftp 0x83000000 imx6ull-14x14-angus.dtb bootz 0x80800000 - 0x83000000

Linux内核移植总结

本次工作主要是通过U-Boot 网络启动方式来验证移植后的 Linux 内核。

首先,需要调通板卡网口,使开发板能够与 Ubuntu 虚拟机中的 TFTP 服务器正常通信。随后,在 U-Boot 中通过tftp命令,将 Linux 内核镜像zImage下载到内存地址0x80800000,再将与 ANGUS 板硬件匹配的设备树文件imx6ull-14x14-angus.dtb下载到0x83000000。最后执行:

bootz 0x80800000 - 0x83000000

启动 Linux 内核。

在当前板级默认环境中,image默认设置为zImagefindfdt会根据ANGUS 14X14板卡信息,自动设置默认设备树文件名为:

imx6ull-14x14-angus.dtb

因此,执行默认网络启动流程时,U-Boot 会加载zImageimx6ull-14x14-angus.dtb这两个文件。

需要注意的是,文件名本身并不是强制固定的。我们也可以手动执行tftp命令,或者修改imagefdt_file等环境变量,来指定其他内核镜像或设备树文件。

真正必须满足的是:内核镜像格式能够被bootz启动,设备树内容必须与当前开发板硬件匹配

Linux 内核移植补充

在完成 Linux 内核基本移植后,系统已经能够正常启动,但在后续功能验证过程中发现,触摸屏暂时无法正常工作。因此,本文将进一步补充触摸屏的适配过程,主要分析触摸屏驱动、设备树节点、I2C 总线、中断引脚以及复位引脚等相关配置,最终实现触摸屏在开发板上的正常使用。

在本次触摸屏适配过程中,可以先从设备树分析触摸屏的挂载关系。触摸屏属于 I2C 设备,其设备树节点如下:

&i2c2 { gt9xx@5d { compatible = "goodix,gt9xx"; reg = <0x5d>; interrupts = <5 IRQ_TYPE_EDGE_FALLING>; }; };

同时,设备树中配置了如下 I2C 别名:i2c1 = &i2c2;

因此需要注意,硬件上使用的是 I2C2 控制器,但在 Linux 系统中会被注册为 i2c-1 总线。

对应关系如下:

① 硬件控制器:I2C2 ② Linux 总线编号:i2c-1 ③ 触摸芯片:GT9XX ④ I2C 从机地址:0x5d

第一阶段:i2c_adapter 与 i2c_client 初始化

Linux 启动时,首先由 i2c-imx 控制器驱动初始化 I2C2,并向 I2C core 注册对应的 i2c_adapter。由于设备树别名中将 i2c1 指向了 &i2c2,所以该控制器在系统中表现为 i2c-1。adapter 注册完成后,I2C core 会继续解析 &i2c2 节点下的子节点。根据 gt9xx@5d 这个设备树节点,内核会创建一个 i2c_client 设备。其中,client->adapter 指向 i2c-1 对应的 i2c_adapter,client->addr 保存触摸芯片的 I2C 地址 0x5d。

因此,在内核中可以理解为:i2c-1 总线上存在一颗地址为 0x5d 的 Goodix GT9XX 触摸屏设备。后续 Goodix 触摸屏驱动加载后,会根据 compatible = "goodix,gt9xx" 与该设备进行匹配。如果匹配成功,就会调用驱动的 probe 函数,并通过 i2c_client 与触摸芯片进行通信。

设备树中的 &i2c2 // 描述 i.MX6ULL 的 I2C2 控制器已启用 | +-- i2c_imx_probe() // i.MX6ULL I2C 控制器驱动的 probe // 初始化 I2C2 寄存器、时钟、控制器中断和传输函数 | +-- 建立 struct i2c_adapter | // adapter 表示“这一条可以收发 I2C 数据的总线” | // | // adapter.nr = 1 | // 对外显示为 i2c-1 | +-- i2c_add_numbered_adapter(&i2c_imx->adapter) // 把 i2c-1 这条总线注册到 I2C core | +-- /dev/i2c-1 | // 若 CONFIG_I2C_CHARDEV=y | // 这是整条总线的用户态原始访问入口 | +-- of_i2c_register_devices(adapter) // adapter 注册后,遍历它下面的设备树子节点 | +-- 找到 gt9xx@5d // 内核知道 i2c-1 上声明了一颗地址 0x5d 的设备 | +-- of_i2c_register_device() | // 读取 compatible 和 reg | +-- i2c_new_device(adapter, info) // 创建 struct i2c_client | +-- client->adapter = adapter | // 该设备挂在哪条 I2C 软件总线对象上 | // 即 i2c-1 | +-- client->addr = 0x5d | // 访问该设备时使用的从机地址 | +-- client->dev.of_node = gt9xx@5d | // 保存设备树配置来源 | +-- device_register(&client->dev) // 把该从设备加入 Linux 设备模型 | +-- /sys/bus/i2c/devices/1-005d // 表示 i2c-1 上的 0x5d 设备

需要注意的是,/sys 和 /dev 并不是同一个概念。

/sys 是 Linux 设备模型在用户空间的展示,用来查看内核中已经注册的设备、设备挂在哪条总线上、绑定了哪个驱动以及设备相关属性。

/dev 是字符设备或块设备提供给应用程序的访问入口。应用程序一般通过 open()、read()、write()、ioctl() 等系统调用访问 /dev 下的设备节点。

有时我们会感觉 /sys 和 /dev 表示的是同一个设备,是因为字符设备驱动通常会同时完成两件事:

第一,通过 cdev_add() 建立主设备号、次设备号与 file_operations 的对应关系。

第二,通过 device_create() 将带有设备号的设备接口注册到 Linux 设备模型中。

这样,该设备会出现在 /sys/class/... 目录下;如果系统启用了 devtmpfs,或者使用 udev、mdev 处理设备事件,还会自动生成对应的 /dev/... 节点。例如字符设备 charled 可能同时存在:

/sys/class/charled/charled
/dev/charled

其中,/sys/class/charled/charled 表示该设备在 Linux 设备模型中的描述,而 /dev/charled 是应用程序真正访问驱动的入口。

但是,总线上的硬件设备不能简单地和 /dev 节点一一对应。

① 以 I2C 设备为例,I2C 从设备在内核中通常由 struct i2c_client 表示,可以在 /sys/bus/i2c/devices/... 中查看。例如触摸屏 GT9XX 挂载在 i2c-1 总线上,地址为 0x5d,系统中可能会看到:/sys/bus/i2c/devices/1-005d。这表示 i2c-1 总线上存在一个地址为 0x5d 的 I2C 从设备,但并不代表系统一定会生成 /dev/gt9xx。对于 GT9XX 触摸屏来说,驱动通常不会直接导出 /dev/gt9xx,而是通过 input 子系统上报触摸事件,最终可能表现为:/dev/input/eventX。

②SPI 设备也是类似的。SPI 从设备可以在 /sys/bus/spi/devices/... 中查看,只有绑定了 spidev 这类向用户空间导出字符设备接口的驱动时,才会出现:/dev/spidevB.C。

③SDIO 设备也不一定有统一的 /dev/sdio... 节点。例如 SDIO Wi-Fi 通常注册为网络接口 wlan0,而 SD 卡或 eMMC 这类存储设备则会导出为:/dev/mmcblkN。

因此,可以简单理解为:

/sys:描述内核设备模型中的设备、总线、驱动和属性。
/dev:提供应用程序访问字符设备或块设备的文件接口。

总线设备注册后,通常先体现在 /sys/bus/.../devices/ 中;只有当驱动进一步向用户空间导出字符设备、块设备、input 设备或网络接口时,用户程序才会看到对应的 /dev 节点或其他访问形式。所以,/sys 中能看到某个设备,只能说明它已经进入 Linux 设备模型;至于 /dev 中是否存在对应节点,要看驱动是否向用户空间导出了访问接口。

第二阶段:I2C 读取数据与 input 子系统上报数据

当 GT9XX 驱动与设备树创建的 i2c_client 匹配成功后,内核会调用驱动的 gtp_probe() 函数。gtp_probe() 是触摸屏驱动的一次性初始化函数,主要负责完成触摸芯片上电、硬件复位、I2C 通信检测、输入设备注册以及中断申请等工作。

gtp_probe() 首先读取设备树中的复位 GPIO、中断 GPIO、坐标范围和相关配置参数,然后给触摸芯片上电,并通过 RESET 和 INT 引脚完成 GT9XX 的硬件复位。复位完成后,驱动会利用 i2c_client 中保存的 I2C adapter 和从机地址,通过 I2C 访问 GT9XX 寄存器,以确认芯片是否能够正常通信,并进一步读取芯片 ID、固件版本和面板配置信息。

初始化过程中,驱动还会注册 goodix-ts 输入设备,并申请触摸中断处理函数。初始化成功后,当用户触摸屏幕时,GT9XX 会产生中断,驱动进入 gtp_irq_handler(),通过 I2C 读取本次触摸的坐标数据,然后调用 input_report_abs() 和 input_sync() 将触摸事件上报给 input 子系统。

input 子系统最终通过 evdev 接口生成 /dev/input/eventX 节点,应用程序读取该节点即可获得标准的触摸事件。

gtp_probe() // GT9XX 设备初始化总入口 | +-> gtp_parse_dt() | // 读取设备树中的 GPIO、中断类型、坐标范围和配置开关 | | | +-> gtp_parse_dt_coords() | // 读取 touchscreen-size-x/y、max-id、压力范围 | +-> gtp_power_init() | // 获取触摸屏电源 regulator | +-> gtp_power_on() | // 给 GT9XX 芯片上电 | +-> gtp_pinctrl_init() | // 获取复位脚和中断脚的引脚状态 | +-> gtp_request_io_port() | // 申请 reset-gpios=GPIO5_IO02、irq-gpios=GPIO1_IO05 | +-> gtp_reset_guitar() | // 操作 RESET/INT 引脚,使 GT9XX 芯片硬件复位 | | | +-> RESET 拉低 -> 延时 -> RESET 拉高 | // 让触摸芯片重新启动 | +-> gtp_i2c_test() | // 读取触摸芯片寄存器,验证 I2C 通信是否正常 | | | +-> 若连续失败 | | | +-> 打印 "Failed communicate with IC use I2C" | // 优先检查地址、SCL/SDA、复位脚和供电 | +-> gtp_get_fw_info() | // 读取芯片产品 ID、固件版本、sensor_id | +-> gtp_init_panel() | // 初始化触摸面板配置 | | | +-> 当前 DTS: goodix,driver-send-cfg = <0> | | | +-> 仅读取芯片内部已有配置 | // 不会使用 DTS 中 goodix,cfg-group0/1/2 下发新参数 | +-> gtp_request_input_dev() | // 创建 Linux input 设备 goodix-ts | | | +-> input_mt_init_slots() | | // 建立多点触摸 slot,当前使用 Type-B 协议 | | | +-> input_set_abs_params() | | // 设置 X/Y/压力/触点 ID 的上报范围 | | | +-> input_register_device() | // 注册 /dev/input/eventX 设备 | +-> gtp_request_irq() | // 申请 GPIO1_IO05 的触摸中断 | | | +-> request_threaded_irq(..., gtp_irq_handler, ...) | // 中断触发后进入线程化处理函数 | +-> gtp_work_control_enable(true) // 初始化完成,允许开始处理触摸事件

总结

首先,I2C 控制器驱动会注册一个i2c_adapter,用来表示一条 I2C 总线。然后,内核根据设备树中的 gt9xx 节点创建 i2c_client,用来表示挂载在这条 I2C 总线上的 GT9XX 触摸芯片。GT9XX 驱动通过这个i2c_client 与触摸芯片进行 I2C 通信,读取触摸坐标、触点数量和触摸状态等数据。读取到触摸数据后,驱动会将这些数据上报给 input 子系统。input 子系统再生成 /dev/input/eventX 节点,用户空间应用程序最终通过读取该节点获取触摸事件。

简单来说就是:注册 i2c_adapter → 创建 i2c_client → I2C 读取触摸数据 → input 子系统上报 → 用户读取 /dev/input/eventX。

触摸屏适配

通过前面的分析可以知道,GT9XX 触摸屏是一个 I2C 输入设备。因此,触摸屏适配的主要工作就是将 GT9XX 驱动移植到当前 Linux 内核中,并通过 Kconfig、Makefile 和设备树完成驱动的编译、加载和匹配。

GT9XX 触摸屏驱动移植步骤如下:

1. 修改默认配置文件

在arch/arm/configs/imx_v7_angus_defconfig文件中添加:

CONFIG_TOUCHSCREEN_GT9XX=y CONFIG_TOUCHSCREEN_GT9XX_UPDATE=y CONFIG_TOUCHSCREEN_GT9XX_TOOL=y

这样执行 make imx_v7_angus_defconfig 后,生成的 .config 中就会包含 GT9XX 触摸屏相关配置。

2. 拷贝 GT9XX 驱动源码

将已有的 gt9xx 驱动目录拷贝到当前内核源码中: cp -rfd ~/100ask_imx6ull-sdk/Linux-4.9.88/drivers/input/touchscreen/gt9xx/ ./drivers/input/touchscreen/ 拷贝后目录为: drivers/input/touchscreen/gt9xx/

3. 修改 Kconfig

在drivers/input/touchscreen/Kconfig文件中添加:

source "drivers/input/touchscreen/gt9xx/Kconfig"

这一步的作用是让内核 Kconfig 系统能够解析 gt9xx 驱动自己的 Kconfig 文件,从而识别 CONFIG_TOUCHSCREEN_GT9XX 等配置项。如果不添加这句,make defconfig 时即使在 defconfig 中写了 CONFIG_TOUCHSCREEN_GT9XX=y,Kconfig 也可能因为找不到该配置项定义而无法正确生成到 .config 中。

4. 修改 Makefile

在drivers/input/touchscreen/Makefile文件中添加:

obj-$(CONFIG_TOUCHSCREEN_GT9XX) += gt9xx/

这一步是告诉 Kbuild:如果 .config 中启用了 CONFIG_TOUCHSCREEN_GT9XX,就进入 gt9xx 目录编译触摸屏驱动。当 .config 中存在:CONFIG_TOUCHSCREEN_GT9XX=y。

Kbuild 会将:obj-$(CONFIG_TOUCHSCREEN_GT9XX) += gt9xx/,展开为:obj-y += gt9xx/,于是 Kbuild 会进入:drivers/input/touchscreen/gt9xx/。继续读取该目录下的 Makefile,并编译 GT9XX 触摸屏驱动。

整体流程可以概括为:

Kconfig 引入 gt9xx 配置项

defconfig 默认选中 CONFIG_TOUCHSCREEN_GT9XX

make defconfig 生成 .config

Kbuild 读取 Makefile

obj-$(CONFIG_TOUCHSCREEN_GT9XX) 展开为 obj-y

进入 gt9xx 目录编译驱动

GT9XX 驱动被编译进内核

完成 GT9XX 触摸屏驱动的移植和配置后,重新编译 Linux 内核,并按照上一篇博客中介绍的内核移植流程,将新生成的内核镜像和设备树文件更新到开发板中进行验证。

make distclean make imx_v7_angus_defconfig make zImage -j4 make dtbs
http://www.jsqmd.com/news/894197/

相关文章:

  • 【C++进阶】vector 类从入门到精通:核心接口与内存机制实战指南
  • 【职场】关于职场“老实人“,你不知道的10个真相
  • AI精准农业杂草管理系统:YOLO11n与Jetson Orin的实践
  • 【AI Agent 开发实战·第01讲】从“缸中之脑”到“全能助手”:为什么我们需要 AI Agent?它与 ChatGPT 有什么本质区别?
  • 2026年主流种公猪基因厂家地址及核心实力评测:美系公猪哪个品牌好、蓝耳伪狂双阴性正规猪精厂家、顶王金猪、黑猪精哪个品牌好选择指南 - 优质品牌商家
  • 禾墩文化传播智慧二维码系统解析
  • 如何用AutoGen快速搭建Multi-Agent协作系统?实战指南
  • A-11-AI能做什么?盘点2026年AI的100种用法
  • 告别手写Shader!ShaderGraph可视化制作卡通风格水体(URP管线配置避坑)
  • 【求职】关于“跳槽“,你不知道的10个真相
  • 重磅!Erupt 1.14.3 发布:多个 AI 智能体在你的后台开始“组团打工“了
  • 从‘小费’到‘泰坦尼克’:用Seaborn的boxplot快速探索3个经典数据集的秘密与异常
  • Air1601 LCD 显示开发全解析
  • 扫地机器人行业 企业篇-追觅科技
  • 别再花钱找淘宝了!保姆级教程:Win10系统下AMEsim、Matlab、Visual Studio三件套一站式安装避坑指南
  • 2026年IPO资料可以用AI自动制作吗:投行文档自动化选型对比与落地清单 - 观域传媒
  • 别再右键属性了!Edge/Chrome/Firefox浏览器安装路径的3种隐藏查看法(含命令行版)
  • UE4开发者必看:解决Nvidia Ansel提示‘必须支持的游戏’错误,保姆级排查指南
  • 扫地机器人行业 企业篇-小米/米家
  • cmux:专为 AI 编程 Agent 打造的 macOS 终端神器
  • Node js 服务中集成 Taotoken 实现异步聊天补全的完整示例
  • Unity ShaderGraph实战:用Input节点5分钟搞定一个动态水面材质(附完整节点图)
  • 赋予网络物理直觉:一种多模态融合和物理敏感注意力的离心泵故障诊断(完善中......)
  • 8051中断优化:ONEREGBANK指令原理与实践
  • 课堂复刻|个人经验分享:Spring Boot整合MyBatis
  • 别再被鱼眼照片搞懵了!用OpenCV+Python手把手教你搞定相机畸变矫正(附完整代码)
  • UVa 297 Quadtrees
  • Cortex-M4外部Flash断点调试问题解决方案
  • 从开发者角度观察Taotoken平台模型更新与路由优化的及时性体验
  • 2026年5月更新指南:武安靠谱的单招机构企业选择策略解析 - 2026年企业资讯