Linux驱动开发07:【设备树】分析nanopi设备树,添加I2C子节点

2023-10-12 07:59

本文主要是介绍Linux驱动开发07:【设备树】分析nanopi设备树,添加I2C子节点,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

介绍

本节简单介绍一下设备树,然后对nanopi的设备树文件进行分析。为了真正地体会设备树的用法,我们在现有设备树的I2C节点中尝试添加一个mpu6050的子节点,然后在nanopi的文件系统中尝试访问该节点,以验证是否添加成功。下一节会修改mpu6050的I2C驱动,使之支持该节点。


设备树简介

设备树文件是一种板级描述文件,该文件由一系列的节点和子节点组成,用于描述CPU内部资源和板级外设资源。CPU内部资源是指CPU中包含的外设,如I2C控制器、SPI控制器、GPIO控制器等。板级外设是指PCB上包含的硬件资源,如LED灯、按键、I2C外设、SPI外设等。盗一张图说明一个设备树的基本组成(出处:https://blog.csdn.net/smcdef/article/details/77387975)
这里写图片描述
图中,\为根节点,而\ { ... }中就是整个文件包含的内容,可以看出根节点中包含了两个子节点:node@0和node@1,其中节点的名称可以一致,只要单元地址不一致即可。
子节点拥有有自己的属性,同时中也可以包含自己的子节点。属性的作用范围在整个节点的范围内,如果其子节点未申明,那么默认和根节点使用相同的属性。这里child-node@0就是node@0的子节点,同时子节点又拥有自己的属性。
注意node@1的前面有一个叫做node1的标签,其作用就是给该节点一个标号以供其他地方引用。这里node1就被引用到了child-node@0的属性中:a-reference-to-something = <&node1>。其中&node1叫做phandle


分析nanopi设备树

设备树文件的后缀有.dts.dtsi,其中.dtsi包含了不同型号电路板中相同的资源,可以被.dts文件包含,类似于c语言的头文件。dtc是设备树编译器(Device Tree Compiler)的缩写,一般linux内核中自带dtc.dts文件被dtc编译后会生成.dtb文件,该文件就是设备树的二进制文件,可以被内核读取并解析。

为了更清楚地说明问题,这里使用了nanopi-air的设备树为例进行讲解。作者水平有限,只介绍自己理解的部分。nanopi-air的设备树文件在arch/arm/boot/dts/目录中,其中相关的文件是:

sun8i-h3-nanopi-neo-air.dts
sun8i-h3-nanopi.dtsi
sun8i-h3.dtsi
sunxi-h3-h5.dtsi

第一个文件是对nanopi-air特定资源的描述,第二个文件是对nanopi系列特定资源的描述,第三文件是对全志h3芯片的特定描述,第四个文件是对全志h3-h5芯片通用资源的描述。可见一层一层由特定到通用。打开最后一个文件,可以看到如下层次结构:

/ {interrupt-parent = <&gic>;#address-cells = <1>;#size-cells = <1>;clocks {#address-cells = <1>;#size-cells = <1>;ranges;...};soc {compatible = "simple-bus";#address-cells = <1>;#size-cells = <1>;ranges;...pio: pinctrl@01c20800 {/* compatible is in per SoC .dtsi file */reg = <0x01c20800 0x400>;interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>,<GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>,<GIC_SPI 23 IRQ_TYPE_LEVEL_HIGH>;clocks = <&ccu CLK_BUS_PIO>, <&osc24M>, <&osc32k>;clock-names = "apb", "hosc", "losc";gpio-controller;#gpio-cells = <3>;interrupt-controller;#interrupt-cells = <3>;...};...i2c0: i2c@01c2ac00 {compatible = "allwinner,sun6i-a31-i2c";reg = <0x01c2ac00 0x400>;interrupts = <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>;clocks = <&ccu CLK_BUS_I2C0>;resets = <&ccu RST_BUS_I2C0>;pinctrl-names = "default";pinctrl-0 = <&i2c0_pins>;status = "disabled";#address-cells = <1>;#size-cells = <0>;};...r_pio: pinctrl@01f02c00 {compatible = "allwinner,sun8i-h3-r-pinctrl";reg = <0x01f02c00 0x400>;interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>;clocks = <&apb0_gates 0>, <&osc24M>, <&osc32k>;clock-names = "apb", "hosc", "losc";resets = <&apb0_reset 0>;gpio-controller;#gpio-cells = <3>;interrupt-controller;#interrupt-cells = <3>;...};...gic: interrupt-controller@01c81000 {compatible = "arm,gic-400";reg = <0x01c81000 0x1000>,<0x01c82000 0x2000>,<0x01c84000 0x2000>,<0x01c86000 0x2000>;interrupt-controller;#interrupt-cells = <3>;interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>;};};
};

首先,可以看到根节点下只有两个子节点:clocksoc,而这两个节点下又分别有许多子节点。根节点下除了子节点外的内容就是该节点的属性,这里根节点的属性是:

interrupt-parent = <&gic>;
#address-cells = <1>;
#size-cells = <1>;

第一个interrupt-parent是指中断控制器,这里<&gic>是指引用gic这个标签,我们找到这个标签,在soc的子节点下,有个中断控制器节点interrupt-controller@01c81000,所以这里表示根节点下使用这个中断控制器。根据之前提到的属性作用范围为整个节点,就是说子节点如果不特别声明,那么默认使用这个中断控制器。

剩下两个属性是指子节点的reg(寄存器)属性的address个数和size个数,这一点下面再说明。

我们重点看soc下的子节点,首先看soc的属性:

compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges;

这里出现一个新属性是compatible,和字面意思一样,表示该节点的兼容性,该属性在内核识别设备树节点时将会起到重要作用。这里该属性的值为”simple-bus”,这表示该节点是一个总线,该总线就是CPU总线,在CPU总线上挂接有各种片上外设,如内存、网络控制器、GPIO控制器、I2C控制器等。这些外设都有一个地址,CPU通过这个地址来访问外设。

内核在解析设备树时遇到”simple-bus”时,会继续解析这个节点的子节点,并将各个子节点注册为一个platform_device放到platform_bus_type中。这一点下一节再通过代码来分析。

再来看pinctrl@01c20800这个子节点,这个节点的属性如下:

reg = <0x01c20800 0x400>;
interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>,<GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>,<GIC_SPI 23 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_PIO>, <&osc24M>, <&osc32k>;
clock-names = "apb", "hosc", "losc";
gpio-controller;
#gpio-cells = <3>;
interrupt-controller;
#interrupt-cells = <3>;

注意reg属性,这个属性由两部分构成,一部分是address,另一部分是size,因为它的父节点soc#address-cells = <1>; #size-cells = <1>;,所以address和size都只有一个,可以判断这里address=0x01c20800, size=0x400,意思是这个子节点从的地址0x01c20800开始,到0x01c20c00结束。这一点可以对应全志H3芯片的数据手册找到相应的内容:
这里写图片描述
从图中可以看到这个节点是一个GPIO控制器,地址正是从0x01c20800开始的,和上面分析一致,另外该GPIO控制器控制的是PA, PC~PG总共6个bank,分别用0, 2~6表示(注意没有PB端口),每个bank拥有32个pin,这一点可以查看数据手册得知。

该节点还拥有gpio-controller属性,表示该节点是一个GPIO控制器,#gpio-cells = <3>表示使用该GPIO控制器时需要3个参数,三个参数分别表示什么内容需要查看binding文档。该文档在

Documentation/devicetree/bindings/pinctrl/allwinner,sunxi-pinctrl.txt

其中有一段话介绍了三个参数的意义:

Consumers that would want to refer to one or the other (or both) should provide through the usual *-gpios and interrupts properties a cell with 3 arguments, first the number of the bank, then the pin inside that bank, and finally the flags for the GPIO/interrupts.

第一个数字表示bank号,第二个数字表示pin编号,第三个数字表示flag,稍后我们会用到这三个参数。

再来看i2c节点,该节点名称为i2c@01c2ac00,属性如下:

compatible = "allwinner,sun6i-a31-i2c";
reg = <0x01c2ac00 0x400>;
interrupts = <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_BUS_I2C0>;
resets = <&ccu RST_BUS_I2C0>;
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins>;
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;

这是一个i2c控制器(适配器),注意compatible属性,该属性也非常重要,后期内核通过识别该字符串来加载i2c_adapter到i2c总线上。地址范围是0x01c2ac000x01c2b000

这里有个属性是status="disabled"表示该控制器是关闭状态。#address-cells = <1>; #size-cells = <0>;表示了其子节点reg属性的构成就只有一个address。


添加mpu6050子节点

分析了sunxi-h3-h5.dtsi文件,我们写一个最简单的设备树节点。直接打开sun8i-h3-nanopi-neo-air.dts文件,该文件间接包含了sunxi-h3-h5.dtsi文件。在该文件下找到&i2c这个phandle

&i2c0 {status = "okay";
};

注意这里&i2c0不是表示子节点,而是一个phandle,该phandle就是sunxi-h3-h5.dtsi文件中的i2c控制器,这里表示引用该i2c控制器,然后将其status属性改为”okay”。由于mpu6050是挂在i2c总线上的,因此需要在i2c控制器下增加子节点,如下所示增加子节点:

&i2c0 {status = "okay";imu@68 {compatible = "inv,mpu6050";reg = <0x68>;};
};

其中imu是该子节点的类型,@68表示该节点的地址是68,这里因为是i2c总线,所以表示i2c从机地址。compatible = "inv,mpu6050"用于在驱动中匹配该i2c设备,其中”inv”表示生产厂商,”mpu6050”表示器件型号。

重新编译设备树,在内核目录执行make dtbs ARCH=arm CROSS_COMPILE=arm-linux-,然后将编译出的.dtb文件拷贝到nanopi的SD卡当中,重新启动nanopi,如果设备树解析成功,则在/sys/bus/i2c/device目录下会多出一个0-0068的目录:
这里写图片描述
进入该目录中的of_node目录,然后读取其中的三个文件可以看到我们定义的属性值。
这里写图片描述
我们读取reg时得到的是”h”,它的ASCII码正是0x68

这篇关于Linux驱动开发07:【设备树】分析nanopi设备树,添加I2C子节点的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/194438

相关文章

Linux之systemV共享内存方式

《Linux之systemV共享内存方式》:本文主要介绍Linux之systemV共享内存方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、工作原理二、系统调用接口1、申请共享内存(一)key的获取(二)共享内存的申请2、将共享内存段连接到进程地址空间3、将

Python中的Walrus运算符分析示例详解

《Python中的Walrus运算符分析示例详解》Python中的Walrus运算符(:=)是Python3.8引入的一个新特性,允许在表达式中同时赋值和返回值,它的核心作用是减少重复计算,提升代码简... 目录1. 在循环中避免重复计算2. 在条件判断中同时赋值变量3. 在列表推导式或字典推导式中简化逻辑

快速修复一个Panic的Linux内核的技巧

《快速修复一个Panic的Linux内核的技巧》Linux系统中运行了不当的mkinitcpio操作导致内核文件不能正常工作,重启的时候,内核启动中止于Panic状态,该怎么解决这个问题呢?下面我们就... 感谢China编程(www.chinasem.cn)网友 鸢一雨音 的投稿写这篇文章是有原因的。为了配置完

Go语言开发实现查询IP信息的MCP服务器

《Go语言开发实现查询IP信息的MCP服务器》随着MCP的快速普及和广泛应用,MCP服务器也层出不穷,本文将详细介绍如何在Go语言中使用go-mcp库来开发一个查询IP信息的MCP... 目录前言mcp-ip-geo 服务器目录结构说明查询 IP 信息功能实现工具实现工具管理查询单个 IP 信息工具的实现服

usb接口驱动异常问题常用解决方案

《usb接口驱动异常问题常用解决方案》当遇到USB接口驱动异常时,可以通过多种方法来解决,其中主要就包括重装USB控制器、禁用USB选择性暂停设置、更新或安装新的主板驱动等... usb接口驱动异常怎么办,USB接口驱动异常是常见问题,通常由驱动损坏、系统更新冲突、硬件故障或电源管理设置导致。以下是常用解决

Linux命令之firewalld的用法

《Linux命令之firewalld的用法》:本文主要介绍Linux命令之firewalld的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux命令之firewalld1、程序包2、启动firewalld3、配置文件4、firewalld规则定义的九大

Java程序进程起来了但是不打印日志的原因分析

《Java程序进程起来了但是不打印日志的原因分析》:本文主要介绍Java程序进程起来了但是不打印日志的原因分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java程序进程起来了但是不打印日志的原因1、日志配置问题2、日志文件权限问题3、日志文件路径问题4、程序

Linux之计划任务和调度命令at/cron详解

《Linux之计划任务和调度命令at/cron详解》:本文主要介绍Linux之计划任务和调度命令at/cron的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux计划任务和调度命令at/cron一、计划任务二、命令{at}介绍三、命令语法及功能 :at

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字