跳到主要内容

Linux 设备树(DTS)简介

The device tree is a data structure for describing hardware, which originated from Open Firmware. The data structure can hold any kind of data as internally it is a tree of named nodes and properties. Nodes contain properties and child nodes, while properties are name-value pairs.

设备树(device tree)是一种用于描述硬件的数据结构,其起源于 Open Firmware 项目。它是由命名节点和属性组成的树,数据结构可以保存任何类型的数据。节点又包含属性和子节点,而属性是名称-值(name-value)对。

More specifically, it is a description of hardware that is readable by an operating system so that the operating system doesn't need to hard code details of the machine.

更具体地说,它是一份对操作系统可读的硬件描述,因此操作系统不需要对计算机的详细信息进行硬编码。

为什么引入 DTS

DTS 即 Device Tree Source 设备树源码,Device Tree 是一种描述硬件的数据结构,它起源于 OpenFirmware (OF)。 在 Linux 2.6 中,ARM 架构的板极硬件细节过多地被硬编码在 arch/arm/plat-xxx 和 arch/arm/mach-xxx,比如板上的platform设备、 resource、i2c_board_info、spi_board_info以及各种硬件的platform_data,这些板级细节代码对内核来讲只不过是垃圾代码。 而采用 Device Tree 后,许多硬件的细节可以直接透过它传递给 Linux,而不再需要在 kernel 中进行大量的冗余编码。

每次正式的 Linux kernel release 之后都会有两周的 merge window,在这个窗口期间,kernel 各个部分的维护者都会提交各自的 patch, 将自己测试稳定的代码请求并入 kernel main line。每到这个时候,Linus 就会比较繁忙,他需要从各个内核维护者的分支上取得最新代码并 merge 到自己的 kernel source tree 中。Tony Lindgren,内核 OMAP development tree 的维护者,发送了一个邮件给 Linus,请求提交 OMAP 平台代码修改,并给出了一些细节描述:

  1. 简单介绍本次改动;
  2. 关于如何解决 merge conficts。有些 git mergetool 就可以处理,不能处理的,给出了详细介绍和解决方案。

一切都很平常,也给出了足够的信息,然而,正是这个 pull request 引发了一场针对 ARM linux 的内核代码的争论。 我相信 Linus 一定是对 ARM 相关的代码早就不爽了,ARM 的 merge 工作量较大倒在其次,主要是他认为 ARM 很多的代码都是垃圾, 代码里面有若干愚蠢的 table,而多个人在维护这个 table,从而导致了冲突。因此,在处理完 OMAP 的 pull request 之后(Linus 并非针对 OMAP 平台, 只是 Tony Lindgren 撞在枪口上了),他发出了怒吼:

Gaah.Guys, this whole ARM thing is a f*cking pain in the ass.

之后经过一些讨论,对 ARM 平台的相关 code 做出如下相关规范调整,这个也正是引入 DTS 的原因。

  1. ARM 的核心代码仍然保存在 arch/arm 目录下;
  2. ARM SoC core architecture code 保存在 arch/arm 目录下;
  3. ARM SOC 的周边外设模块的驱动保存在 drivers 目录下;
  4. ARM SOC 的特定代码在 arch/arm/mach-xxx 目录下;
  5. ARM SOC board specific 的代码被移除,由 DeviceTree 机制来负责传递硬件拓扑和硬件资源信息。

本质上,Device Tree 改变了原来用 hardcode 方式将 HW 配置信息嵌入到内核代码的方法,改用 bootloader 传递一个 DB 的形式。

如果我们认为 kernel 是一个 black box,那么其输入参数应该包括:

  1. 识别 platform 的信息
  2. runtime 的配置参数
  3. 设备的拓扑结构以及特性

对于嵌入式系统,在系统启动阶段,bootloader 会加载内核并将控制权转交给内核,此外,还需要把上述的三个参数信息传递给 kernel,以便 kernel 可以有较大的灵活性。在 linux kernel 中,Device Tree 的设计目标就是如此。

DTS 基础知识

DTS 的加载过程

如果要使用 Device Tree,首先用户要了解自己的硬件配置和系统运行参数,并把这些信息组织成 Device Tree source file。 通过 DTC(Device Tree Compiler),可以将这些适合人类阅读的 Device Tree source file 变成适合机器处理的 Device Tree binary file(有一个更好听的名字,DTB,device tree blob)。

在系统启动的时候,boot program(例如 firmware、bootloader)可以将保存在 flash 中的 DTB 拷贝到内存(当然也可以通过其他方式,例如可以通过 bootloader 的交互式命令加载 DTB,或者 firmware 可以探测到 device 的信息,组织成 DTB 保存在内存中),并把 DTB 的起始地址传递给 client program(例如 OS kernel,bootloader 或者其他特殊功能的程序)。 对于计算机系统(computer system),一般是 firmware->bootloader->OS,对于嵌入式系统,一般是 bootloader->OS。

DTS 的描述信息

Device Tree 由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性, 其实就是成对出现的 name 和 value。在 Device Tree 中,可描述的信息包括(原先这些信息大多被 hard code 到 kernel 中):

  • CPU 的数量和类别
  • 内存基地址和大小
  • 总线和桥
  • 外设连接
  • 中断控制器和中断使用情况
  • GPIO 控制器和 GPIO 使用情况
  • Clock 控制器和 Clock 使用情况

它基本上就是画一棵电路板上 CPU、总线、设备组成的树,Bootloader 会将这棵树传递给内核,然后内核可以识别这棵树, 并根据它展开出 Linux 内核中的 platform_device、i2c_client、spi_device 等设备,而这些设备用到的内存、IRQ 等资源, 也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。

是否 Device Tree 要描述系统中的所有硬件信息?答案是否定的。基本上,那些可以动态探测到的设备是不需要描述的,例如 USB device。不过对于 SOC 上的 usb hostcontroller,它是无法动态识别的,需要在 device tree 中描述。同样的道理,在 computer system 中,PCI device 可以被动态探测到,不需要在 device tree 中描述,但是 PCI bridge 如果不能被探测,那么就需要描述之。

.dts 文件是一种 ASCII 文本格式的 Device Tree 描述,此文本格式非常人性化,适合人类的阅读习惯。 基本上,在 ARM Linux 中,一个 .dts 文件对应一个 ARM 的 machine,一般放置在内核的 arch/arm/boot/dts/ 目录。 由于一个 SoC 可能对应多个 machine(一个 SoC 可以对应多个产品和电路板),势必这些 .dts 文件需包含许多共同的部分, Linux 内核为了简化,把 SoC 公用的部分或者多个 machine 共同的部分一般提炼为 .dtsi,类似于 C 语言的头文件。 其他的 machine 对应的 .dts 就 include 这个 .dtsi。譬如,对于 RK3288 而言,rk3288.dtsi 就被 rk3288-chrome.dts 所引用,rk3288-chrome.dts 有如下一行:#include "rk3288.dtsi",对于 rtd1195,在 rtd-119x-nas.dts 中就包含了 /include/ "rtd-119x.dtsi"。当然,和 C 语言的头文件类似,.dtsi 也可以 include 其他的 .dtsi,譬如几乎所有的 ARM SoC 的 .dtsi 都引用了 skeleton.dtsi,即 #include "skeleton.dtsi" 或者 /include/ "skeleton.dtsi"

正常情况下所有的 dts 文件以及 dtsi 文件都含有一个根节点 "/",这样 include 之后就会造成有很多个根节点?按理说 device tree 既然是一个树,那么其只能有一个根节点,所有其他的节点都是派生于根节点的 child node。其实 Device Tree Compiler 会对 DTS 的 node 进行合并,最终生成的 DTB 中只有一个 root node。

device tree 的基本单元是 node。这些 node 被组织成树状结构,除了 root node,每个 node 都只有一个 parent。 一个 device tree 文件中只能有一个根节点(root node)。每个 node 中包含了若干的 property/value 来描述该 node 的一些特性。 每个 node 用节点名字(node name)标识,节点名字的格式是 node-name@unit-address。如果该 node 没有 reg 属性(后面会描述这个 property),那么该节点名字中必须不能包括 @unit-address。unit-address 的具体格式是和设备挂在那个 bus 上相关。例如对于 cpu, 其 unit-address 就是从 0 开始编址,以此加一。而具体的设备,例如以太网控制器,其 unit-address 就是寄存器地址。根节点的名字是确定的,必须是 "/"。在一个树状结构的 device tree 中,如何引用一个 node 呢?要想唯一指定一个 node 必须使用 full path,例如 /node-name-1/node-name-2/node-name-N

DTS 的组成结构

/ {  
node1 {
a-string-property = "A string";
a-string-list-property = "first string", "second string";
a-byte-data-property = [0x01 0x23 0x34 0x56];
child-node1 {
first-child-property;
second-child-property = <1>;
a-string-property = "Hello, world";
};
child-node2 {
};
};
node2 {
an-empty-property;
a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
child-node1 {
};
};
};

上述 .dts 文件并没有什么真实的用途,但它基本表征了一个 Device Tree 源文件的结构:

  • 1个 root 结点 "/";
  • root 结点下面含一系列子结点,本例中为 "node1" 和 "node2";
  • 结点 "node1" 下又含有一系列子结点,本例中为 "child-node1" 和 "child-node2";
  • 各结点都有一系列属性。
    • 这些属性可能为空,如 "an-empty-property";
    • 可能为字符串,如 "a-string-property";
    • 可能为字符串数组,如 "a-string-list-property";
    • 可能为 Cells(由 u32 整数组成),如 "second-child-property";
    • 可能为二进制数,如 "a-byte-data-property"。

下面以一个最简单的 machine 为例来看如何写一个 .dts 文件。假设此 machine 的配置如下:

  • 1个双核 ARM Cortex-A9 32 位处理器;
  • ARM 的 local bus 上的内存映射区域分布了2个串口(分别位于 0x101F1000 和 0x101F2000)、 GPIO 控制器(位于 0x101F3000)、SPI 控制器(位于 0x10115000)、中断控制器(位于 0x10140000)和一个 external bus 桥;
  • External bus 桥上又连接了 SMC SMC91111 Ethernet(位于 0x10100000)、I2C 控制器(位于 0x10160000)、64MB NOR Flash(位于 0x30000000);
  • External bus 桥上连接的 I2C 控制器所对应的 I2C 总线上又连接了 Maxim DS1338 实时钟(I2C 地址为 0x58)。

其对应的 .dts 文件为:

/ {  
compatible = "acme,coyotes-revenge";
#address-cells = <1>;
#size-cells = <1>;
interrupt-parent = <&intc>;

cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0>;
};
cpu@1 {
compatible = "arm,cortex-a9";
reg = <1>;
};
};

serial@101f0000 {
compatible = "arm,pl011";
reg = <0x101f0000 0x1000 >;
interrupts = < 1 0 >;
};

serial@101f2000 {
compatible = "arm,pl011";
reg = <0x101f2000 0x1000 >;
interrupts = < 2 0 >;
};

gpio@101f3000 {
compatible = "arm,pl061";
reg = <0x101f3000 0x1000
0x101f4000 0x0010>;
interrupts = < 3 0 >;
};

intc: interrupt-controller@10140000 {
compatible = "arm,pl190";
reg = <0x10140000 0x1000 >;
interrupt-controller;
#interrupt-cells = <2>;
};

spi@10115000 {
compatible = "arm,pl022";
reg = <0x10115000 0x1000 >;
interrupts = < 4 0 >;
};

external-bus {
#address-cells = <2>
#size-cells = <1>;
ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet
1 0 0x10160000 0x10000 // Chipselect 2, i2c controller
2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash

ethernet@0,0 {
compatible = "smc,smc91c111";
reg = <0 0 0x1000>;
interrupts = < 5 2 >;
};

i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
#address-cells = <1>;
#size-cells = <0>;
reg = <1 0 0x1000>;
rtc@58 {
compatible = "maxim,ds1338";
reg = <58>;
interrupts = < 7 3 >;
};
};

flash@2,0 {
compatible = "samsung,k8f1315ebm", "cfi-flash";
reg = <2 0 0x4000000>;
};
};
};

上述.dts文件中,root 结点 "/" 的 compatible 属性 compatible = "acme,coyotes-revenge"; 定义了系统的名称。Linux 内核透过根节点 "/" 的 compatible 属性即可判断它启动的是什么 machine。

在 .dts 文件的每个设备,都有一个 compatible 属性,compatible 属性用户驱动和设备的绑定。compatible 属性是一个字符串的列表,列表中的第一个字符串表征了节点代表的确切设备,其后的字符串表征可兼容的其他设备。可以说前面的是特指,后面的则涵盖更广的范围。

如在 arch/arm/boot/dts/vexpress-v2m.dtsi 中的 Flash 结点:

flash@0,00000000 {  
compatible = "arm,vexpress-flash", "cfi-flash";
reg = <0 0x00000000 0x04000000>,
<1 0x00000000 0x04000000>;
bank-width = <4>;
};

compatible 属性的第2个字符串 "cfi-flash" 明显比第1个字符串 "arm,vexpress-flash" 涵盖的范围更广。接下来根节点 "/" 的 cpus 子节点下面又包含2个 cpu 子节点,描述了此 machine 上的2个 CPU,并且二者的 compatible 属性为 "arm,cortex-a9"。

注意:cpus 和 cpus 的2个 cpu 子节点的命名,它们遵循的组织形式为:[@]<> 中的内容是必选项,[] 中的则为可选项。name 是一个 ASCII 字符串,用于描述结点对应的设备类型,如 3com Ethernet 适配器对应的节点名宜为 ethernet,而不是 3com509。如果一个节点描述的设备有地址,则应该给出 @unit-address。多个相同类型设备节点的 name 可以一样,只要 unit-address 不同即可,如本例中含有 cpu@0cpu@1 以及 serial@101f0000serial@101f2000 这样的同名结点。 设备的 unit-address 地址也经常在其对应结点的 reg 属性中给出。reg 的组织形式为 reg = <address1 length1 [address2 length2] [address3 length3] ... >,其中的每一组 address length 表明了设备使用的一个地址范围。address为1个或多个32位的整型(即 cell),而 length 则为 cell 的列表或者为空 (若 #size-cells = 0)。address 和 length 字段是可变长的,父节点的 #address-cells#size-cells 分别决定了子节点的 reg 属性的 address 和 length 字段的长度。

在本例中,根节点的 #address-cells = <1>;#size-cells =<1>; 决定了 serial、gpio、spi 等结点的 address 和 length 字段的长度分别为1。cpus 节点的 #address-cells= <1>;#size-cells =<0>; 决定了2个 cpu 子结点的 address 为1,而 length 为空, 于是形成了2个 cpu 的 reg =<0>;reg =<1>;。external-bus 节点的 #address-cells= <2>#size-cells =<1>; 决定了其下的 ethernet、i2c、flash 的 reg 字段形如 reg = <0 00x1000>;reg = <1 00x1000>;reg = <2 00x4000000>;。 其中,address字段长度为0,开始的第一个 cell(0、1、2)是对应的片选,第2个 cell(0,0,0)是相对该片选的基地址, 第3个 cell(0x1000、0x1000、0x4000000)为 length。特别要留意的是 i2c 结点中定义的 #address-cells = <1>;#size-cells =<0>; 又作用到了 I2C 总线上连接的 RTC,它的 address 字段为 0x58,是设备的 I2C 地址。

根节点的子节点描述的是 CPU 的视图,因此 root 子结点的 address 区域就直接位于 CPU 的 memory 区域。但是, 经过总线桥后的 address 往往需要经过转换才能对应的 CPU 的 memory 映射。external-bus 的 ranges 属性定义了经过 external-bus 桥后的地址范围如何映射到 CPU 的 memory 区域。

ranges = <0 0  0x10100000   0x10000         // Chipselect 1, Ethernet  
1 0 0x10160000 0x10000 // Chipselect 2, i2c controller
2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash
  • ranges 是地址转换表,其中的每个项目是一个子地址、父地址以及在子地址空间的大小的映射。
  • 映射表中的子地址、 父地址分别采用子地址空间的 #address-cells 和父地址空间的 #address-cells 大小。
  • 对于本例而言,子地址空间的 #address-cells 为2, 父地址空间的 #address-cells 值为1,因此 0 0 0x10100000 0x10000 的前2个 cell 为 external-bus 后片选0上偏移0,第3个 cell 表示 external-bus 后片选0上偏移0的地址空间被映射到 CPU 的 0x10100000 位置,第4个 cell 表示映射的大小为 0x10000。ranges 的后面2个项目的含义可以类推。

Device Tree 中还可以中断连接信息,对于中断控制器而言,它提供如下属性:

  • interrupt-controller – 这个属性为空,中断控制器应该加上此属性表明自己的身份;
  • #interrupt-cells – 与 #address-cells 和 #size-cells 相似,它表明连接此中断控制器的设备的interrupts 属性的 cell 大小。在整个 Device Tree 中,与中断相关的属性还包括:
    • interrupt-parent - 设备结点透过它来指定它所依附的中断控制器的 phandle,当结点没有指定 interrupt-parent 时,则从父级结点继承。对于本例而言,root 结点指定了 interrupt-parent= <&intc>; 其对应于 intc: interrupt-controller@10140000,而root结点的子结点并未指定interrupt-parent,因此它们都继承了 intc,即位于 0x10140000 的中断控制器。
    • interrupts - 用到了中断的设备结点透过它指定中断号、触发方法等,具体这个属性含有多少个cell,由它依附的中断控制器结点的 #interrupt-cells 属性决定。而具体每个 cell 又是什么含义,一般由驱动的实现决定,而且也会在Device Tree的binding文档中说明

譬如,对于 ARM GIC 中断控制器而言,#interrupt-cells 为 3,它 3 个 cell 的具体含义

kernel/Documentation/devicetree/bindings/arm/gic.txt 就有如下文字说明:

The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI interrupts.

The 2st cell contains the interrupt number for the interrupt type.
SPI interrupts are in the range [0-987]. PPI interrupts are in the range [0-15].

The 3rd cell is the flags, encoded as follows:
bits[3:0] trigger type and level flags.
1 = low-to-high edge triggered
2 = high-to-low edge triggered
4 = active high level-sensitive
8 = active low level-sensitive
bits[15:8] PPI interrupt cpu mask. Each bit corresponds to each of the 8 possible cpus attached to the GIC. A bit set to '1' indicated the interrupt is wired to that CPU. Only valid for PPI interrupts.

PPI(Private peripheral interrupt)SPI(Shared peripheral interrupt)

一个设备还可能用到多个中断号。对于 ARM GIC 而言,若某设备使用了 SPI 的 168、169 号2个中断,而言都是高电平触发, 则该设备结点的 interrupts 属性可定义为:interrupts =<0 168 4>, <0 169 4>;

dts 引起 BSP 和 driver 的变更

没有使用 dts 之前的 BSP 和 driver

static void usbcp_key_release(struct device *dev)
{
return ;
}

static struct platform_device usbcp_key_dev = {
.name = "usbcopy_key",
.id = -1,
.dev = {
.release = usbcp_key_release,
},
};
static struct platform_driver usbcp_key_driver = {
.driver = {
.name = "usbcopy_key",
.owner = THIS_MODULE,
},
.probe = usbcp_key_probe,
.remove = usbcp_key_remove,
};

使用 dts 之后的 driver

static const struct of_device_id usbcp_key_table[] = {
{.compatible = "Realtek,rtk-gpio-ctl-irq-mux"},
{},
};

static struct platform_driver usbcp_key_driver = {
.driver = {
.name = "usbcopy_key",
.of_match_table = usbcp_key_table,
.owner = THIS_MODULE,
},
.probe = usbcp_key_probe,
.remove = usbcp_key_remove,
};
rtk_gpio_ctl_mlk {
compatible = "Realtek,rtk-gpio-ctl-irq-mux";
gpios = <&rtk_iso_gpio 8 0 1>;
};

针对上面的dts,注意一下几点:

  1. rtk_gpio_ctl_mlk 这个是 node 的名字,自己可以随便定义,当然最好是见名知意,可以通过驱动程序打印当前使用的设备树节点 printk(“now dts node name is %s\n”,pdev->dev.of_node->name);
  2. compatible 选项是用来和驱动程序中的 of_match_table 指针所指向的 of_device_id 结构里的 compatible 字段匹配的,只有 dts 里的 compatible 字段的名字和驱动程序中 of_device_id 里的 compatible 字段的名字一样,驱动程序才能进入 probe 函数。
  3. 对于 gpios 这个字段,首先 &rtk_iso_gpio 指明了这个 gpio 是连接到的是 rtk_iso_gpio,接着那个8是 gpio number 偏移量,它是以 rtk_iso_gpiobase 为基准的,紧接着那个0说明目前配置的 gpio number 是设置成输入 input,如果是1就是设置成输出 output。最后一个字段1是指定这个 gpio 默认为高电平,如果是0则是指定这个 gpio 默认为低电平。
  4. 如果驱动里面只是利用 compatible 字段进行匹配进入 probe 函数,那么 gpios 可以不需要,但是如果驱动程序里面是采用设备树相关的方法进行操作获取 gpio number,那么 gpios 这个字段必须使用。gpios 这个字段是由 of_get_gpio_flags 函数默认指定的 name。

获取 gpio number 的函数如下:

of_get_named_gpio_flags()
of_get_gpio_flags()

注册 i2c_board_info,指定 IRQ 等板级信息。

static struct i2c_board_info __initdata afeb9260_i2c_devices[] = {
{
I2C_BOARD_INFO("tlv320aic23", 0x1a),
}, {
I2C_BOARD_INFO("fm3130", 0x68),
}, {
I2C_BOARD_INFO("24c64", 0x50),
}
}

之类的 i2c_board_info 代码,目前不再需要出现,现在只需要把 tlv320aic23、fm3130、24c64 这些设备结点填充作为相应的 I2C controller 结点的子结点即可,类似于前面的

i2c@1,0 {
compatible = "acme,a1234-i2c-bus";

rtc@58 {
compatible = "maxim,ds1338";
reg = <58>;
interrupts = < 7 3 >;
};
};

Device Tree 中的 I2C client 会透过 I2C host 驱动的 probe() 函数中调用 of_i2c_register_devices(&i2c_dev->adapter); 被自动展开。

常见的 DTS 函数

Linux 内核中目前 DTS 相关的函数都是以 of_ 前缀开头的,它们的实现位于内核源码的 drivers/of 下面

void __iomem *of_iomap(struct device_node *node, int index)

通过设备结点直接进行设备内存区间的 ioremap(),index是内存段的索引。若设备结点的 reg 属性有多段,可通过 index 标示要 ioremap 的是哪一段,只有1段的情况,index 为0。采用 Device Tree 后,大量的设备驱动通过 of_iomap() 进行映射,而不再通过传统的 ioremap。

int of_get_named_gpio_flags(struct device_node *np, const char *propname, int index, enum of_gpio_flags *flags)

static inline int of_get_gpio_flags(structdevice_node *np, int index, enum of_gpio_flags *flags)
{
return of_get_named_gpio_flags(np, "gpios", index,flags);
}

从设备树中读取相关 GPIO 的配置编号和标志,返回值为 gpio number

DTC (device tree compiler)

将 .dts 编译为 .dtb 的工具。DTC 的源代码位于内核的 scripts/dtc 目录,在 Linux 内核使能了 Device Tree 的情况下,编译内核的时候主机工具 dtc 会被编译出来,对应 scripts/dtc/Makefile 中的 hostprogs-y := dtc 这一 hostprogs 编译 target。 在 Linux 内核的 arch/arm/boot/dts/Makefile 中,描述了当某种 SoC 被选中后,哪些 .dtb 文件会被编译出来,如与 VEXPRESS 对应的 .dtb 包括:

dtb-$(CONFIG_ARCH_VEXPRESS) += vexpress-v2p-ca5s.dtb \
vexpress-v2p-ca9.dtb \
vexpress-v2p-ca15-tc1.dtb \
vexpress-v2p-ca15_a7.dtb \
xenvm-4.2.dtb

在 Linux 下,我们可以单独编译 Device Tree 文件。当我们在 Linux 内核下运行 make dtbs 时,若我们之前选择了 ARCH_VEXPRESS, 上述 .dtb 都会由对应的 .dts 编译出来。因为 arch/arm/Makefile 中含有一个 dtbs 编译 target 项目。


pinctrl 子节点格式规范,格式框架如下:

pinctrl_自定义名字: 自定义名字 {
fsl,pins = <
引脚复用宏定义 PAD(引脚)属性
引脚复用宏定义 PAD(引脚)属性
>;
};
	pinctrl_i2c4: i2c4grp {
fsl,pins = <
MX8MQ_IOMUXC_SAI3_RXFS_GPIO4_IO28 0x19
MX8MQ_IOMUXC_SAI3_RXC_GPIO4_IO29 0x19
>;
};