DTS那些事

+++++++++++++++++++++++++++++++++++++

(1)设备节点怎么修改?怎么移植?

ARM架构下dts文件存放于arch/arm/boot/dts/目录下。
那Build内核的时候是如何决定哪些dts需要被编译呢? 答案在arch/arm/boot/dts/xxxx/Makefile中,

ifeq ($(CONFIG_OF),y)
#include $(srctree)/arch/arm/boot/dts/qcom/Makefile.board
dtb-$(CONFIG_ARCH_MSM8974) += msm8974-v1-cdp.dtb \
msm8974-v1-fluid.dtb \
msm8974-v1-liquid.dtb \
msm8974-v1-mtp.dtb \
msm8974-v1-rumi.dtb \

使用DTC可以使dts文件与dtb文件相互转换。
编译dts,生成dtb
dtc -I dts -O dtb -o output.dtb file-a.dts file-b.dts
反编译dtb,生成dts
dtc -I dtb -O dts -o output.dts file-z.dtb

+++++++++++++++++++++++++++++

(2) dts的基本语法,怎么描述节点,有哪些重要属性项?有哪些特殊操作符?有哪些特殊节点?

Node节点。
在DTS中使用一对花括号”node-name{}”来定义;左括号左边的字符串,就是节点名称。
Property属性。
在Node中使用”property-name=value”字符串来定义;
按value可以表述的数据类型,可以分为:
• 字符串:string-prop = “a string”;
• 字符串列表:string-list = “hi”,“str”,“list”;
• 整型数据:cell-prop = <0xaa>;
• 二进制数据:binary-prop = [aa bb f8];
• 混合数据:mixed-prop = “str”,<0x123>,[ff dd];
• 布尔类型:bool-porp;
• 引用类型:ref-prop = <&other-node>
可以看到,
devicetree中使用不同的符号包裹数据来表示不同的数据类型
布尔类型的Property只有key没有value.

常见的Property及意义
Property 类型 释义
compatible = “string ”;// 用于device与driver匹配
reg = < integer >; // 表示设备的地址空间
#address-cells = ; //设置子节点reg属性中地址数据的cell数
#size-cells =< integer >; //设置子节点reg属性中地址长度的cell数
interrupt-parent = &reference; // 指定该设备的中断连接到的int controller

绝大多数Key是由SoC厂商或者外设厂商自定义的。
有关这些Key的意义以及写法介绍,内核要求厂商在Documents/devicetree/bindings/目录下提供文档介绍,如果大家在阅读或编写dts文件时遇到问题,应该首先到bindings目录下查阅对应的介绍文档。
有关属性中Key的命名原则有一个约定俗成的做法:
SoC厂商或其他外设厂商应使用 vendor,prop的形式。
属性Compatible也应遵守"vendor,model"的形式。 例如qcom,qcom,clk-rates;goodix,irq-gpio

compatible”属性通常用来device和driver的适配。
例如:

compatible = “fsl,mpc8641”, “ns16550”; 

在这个例子中,device首先尝试去适配”fsl,mpc8641”driver,如果失败再去尝试适配”ns16550”driver。

phandle”属性通用一个唯一的id来标识一个Node,在property可以使用这个id来引用Node。
定义一个LABEL,只需要在node的前面放置“LABEL:”即可。
定义一个“label:”来引用Node,在编译时系统会自动为node生成一个phandle属性。

引用一个标签时,只需要“&LABEL”即可。
使用”&”来引用“label”,即是引用phandle。
另外,也可以使用alias为标签定义别名,如:alias { i2c_0 = &i2c0};,这样在引用的地方就不用再写&号了,alias+label+reference是非常常用的做法。

+++++++++++++++++++++++++++++++++++

(3)寻址方案怎么描述?

reg
#address-cells
#size-cells

reg意为region,区域。
格式为:

reg = <address1length1 [address2 length2] [address3 length3]>;

“reg”属性解析出”address,length”数字,解析格式依据父节点的”#address-cells、#size-cells”定义。
“#address-cells, #size-cells”属性用来定义当前node的子node中”reg”属性的解析格式。
父类的address-cells和size-cells决定了子类的相关属性要包含多少个cell,如果子节点有特殊需求的话,可以自己再定义,这样就可以摆脱父节点的控制。

举例说明:
1、如果node”soc”中”#address-cells=<1>”、”#size-cells=<1>”,那么子node”serial”中”reg”属性的解析为“addr1 = 0x0000, size1 = 0x0100, addr2 = 0x0000, size2 = 0x0200”:

 soc {
        #address-cells = <1>;
        #size-cells = <1>;
        serial {
            reg = <0x0 0x100 0x0 0x200>;
        }
    }

2、如果node”soc”中”#address-cells=<2>”、”#size-cells=<2>”,那么子node”serial”中”reg”属性的解析为“addr1 = 0x00000100, size1 = 0x00000200”:

soc {
    #address-cells = <2>;
    #size-cells = <2>;
    serial {
        reg = <0x0 0x100 0x0 0x200>;
    }
}

3、如果node”soc”中”#address-cells=<2>”、”#size-cells=<0>”,那么子node”serial”中”reg”属性的解析为“addr1 = 0x00000100, addr2 = 0x00000200”:

soc {
    #address-cells = <2>;
    #size-cells = <0>;
    serial {
        reg = <0x0 0x100 0x0 0x200>;
    }
}

又例如:

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

位于0x10115000的SPI设备申请地址空间,起始地址为0x10115000,长度为0x1000,即属于这个SPI设备的地址范围是0x10115000~0x10116000。

实际应用中,有另外一种情况,就是通过外部芯片片选激活模块。例如,挂载在外部总线上,需要通过片选线工作的一些模块:

external-bus{
    #address-cells = <2>
    #size-cells = <1>;
 
   ethernet@0,0 {
        compatible = "smc,smc91c111";
        reg = <0 0 0x1000>;
    };
 
   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>;
        };
    };
 
   flash@2,0 {
        compatible ="samsung,k8f1315ebm", "cfi-flash";
        reg = <2 0 0x4000000>;
    };
};

external-bus使用两个cell来描述总线地址,一个是片选序号,另一个是片选序号上的偏移量。而地址空间长度依然用一个cell来描述。所以以上的子设备们都需要3个cell来描述地址空间属性——片选、偏移量、地址长度。在上个例子中,有一个例外,就是i2c控制器模块下的rtc模块。因为I2C设备只是被分配在一个地址上,不需要其他任何空间,所以只需要一个address的cell就可以描述完整,不需要size-cells。

+++++++++++++++++++++++++++++
根节点始终描述CPU的地址空间视图。根节点的子节点已经在使用CPU的地址域,因此不需要任 何显式映射。
不是根的直接子节点的节点不使用CPU的地址域。为了获得内存映射地址,设备树必须指定如何 将地址从一个域转换为另一个域。

ranges property用于此目的。
当需要描述的设备不是本总线的设备时,就需要描述一个从设备地址空间到CPU地址空间的映射关系,这里就需要用到ranges属性。external-bus 的 ranges 属性定义了经过 external-bus 桥后的地址范围如何映射到 CPU 的 memory 区域。

#address-cells= <1>;
#size-cells= <1>;
...
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
};

ranges属性为一个地址转换表。
表中的每一行都包含了子地址、父地址、在自地址空间内的区域大小。他们的大小(包含的cell)分别由子节点的address-cells的值、父节点的address-cells的值和子节点的size-cells来决定。

ranges是一个地址翻译列表。范围表中的每个条目都是一个元组,其中包含子地址,父地址和
子地址空间中区域的大小。
每个字段的大小取决于子节点的 #address-cells 值,
父节点的 #address-cells 值

子节点的 #size-cells 值。

对于我们示例中的外部总线,
子地址是2个单
元,父地址是1个单元,大小也是1个单元。
ranges范围映射如下:

芯片片选为0的偏移为0,其映射到地址范围0x10100000…0x1010ffff
芯片片选为1的偏移为0,其映射到地址范围0x10160000…0x1016ffff
芯片片选为2的偏移为0,其映射到地址范围0x30000000…0x30ffffff

如果父地址空间和子地址空间相同,则节点可以改为添加空 ranges 属性。空范围属性的 存在意味着子地址空间中的地址以1:1映射到父地址空间。

以第一行为例:

• 0 0 两个cell,由子节点external-bus的address-cells=<2>决定;

• 0x10100000 一个cell,由父节点的address-cells=<1>决定;

• 0x10000 一个cell,由子节点external-bus的size-cells=<1>决定。
最终第一行说明的意思就是:片选0,偏移0(选中了网卡),被映射到CPU地址空间的0x10100000~0x10110000中,地址长度为0x10000。

“ranges”属性用来做当前node和父node之间的地址映射,
格式为
(child-bus-address, parentbus-address, length)。
其中
child-bus-address的解析长度受当前node的#address-cells属性控制;parentbus-address的解析长度受父node的#address-cells属性控制;
length的解析长度受当前node的#size-cells属性控制。

soc {
    compatible = “simple-bus”;
    #address-cells = <1>;
    #size-cells = <1>;
    ranges = <0x0 0xe0000000 0x00100000>;
    serial {
	    device_type = “serial”;
	    compatible = “ns16550”;
	    reg = <0x4600 0x100>;
	    clock-frequency = <0>;
	    interrupts = <0xA 0x8>;
	    interrupt-parent = <&ipic>;
    };
};

The soc node specifies a ranges property of
<0x0 0xe0000000 0x00100000>;
This property value specifies that for an 1024KB range of address space, a child node addressed at physical 0x0 maps to a parent address of physical 0xe0000000. With this mapping, the serial device node can be addressed by a load or store at address 0xe0004600, an offset of 0x4600 (specified in reg) plus the 0xe0000000 mapping specified in ranges.

++++++++++++++++++++++++++++++++++++
CPU寻址
在讨论寻址时,CPU节点代表了最简单的情况。为每个CPU分配一个唯一的ID,并且没有与CPU
ID相关联的大小。

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

在 cpus 节点中, #address-cells 设置为1,并 #size-cells 设置为0.这意味着子 reg 值是单个 uint32,表示没有大小字段的地址。在这种情况下,两个cpus被分配地址0和1. #size-cells 对 于cpu节点是0,因为每个cpu仅被分配一个地址。您还会注意到该 reg 值与节点名称中的值匹配。按照惯例,如果节点具有 reg 属性,则节点名 称必须包含unit-address,这是 reg 属性中的第一个地址值。

++++++++++++++++++++++++++++++++++
内存映射设备
与在cpu节点中的单个地址值不同,内存映射设备被分配了一系列地址空间,内存设备的所有操作都被映射到该地址空间中。 #size-cells 用于表示每个子 reg 元组中长度字段的大小。
在以下示例中,每个地址值为1个单元(32 位),每个长度值也为1个单元,这在32位系统上是典型的。
对于#address-cells和#size-cells, 64位机器可以使用值2来获得设备树中的64位寻址。

/ DTS-V1 /;
/ { 
    #address-cells = <1>;
    #size-cells = <1>;
    ...

    serial @ 101f0000 {
        compatible =“arm,pl011”; 
        reg = <0x101f0000 0x1000>;
    };
    serial @ 101f2000 {
        compatible =“arm,pl011”; 
        reg = <0x101f2000 0x1000>;
    };
    gpio @ 101f3000 {
        compatible =“arm,pl061”; 
        reg = <	0x101f3000 0x1000
        		0x101f4000 0x0010>;
    };
    interrupt-controller@ 10140000 {
        compatible =“arm,pl190”; reg = <0x10140000 0x1000>;
    };
    spi@10115000 {
        compatible = "arm,pl022";
        reg = <0x10115000 0x1000 >;
    };
    ......
};

为每个设备分配一个基址,并为其分配区域的大小。
本例中的GPIO设备地址分配了两个地址范围;
0x101f3000 … 0x101f3fff和0x101f4000…0x101f400f。

一些设备存在于具有不同寻址方案的总线上。
例如,可以使用分立的芯片选择线将器件连接到外部总线。
由于每个父节点为其子节点定义寻址域,因此可以选择地址映射以最好地描述系统。
下面的代码显示了连接到外部总线的设备的地址分配,芯片选择号编码到地址中。

   external-bus{
        #address-cells = <2>;
        #size-cells = <1>;
        ethernet @ 0,0 {
            compatible =“smc,smc91c111”; 
            reg = <0 0 0x1000>;
        };
        i2c @ 1,0 {
            compatible =“acme,a1234-i2c-bus”; 
            reg = <1 0 0x1000>;
            rtc @ 58 {
                compatible =“maxim,ds1338”;
            };
        };
        flash @ 2,0 {
            compatible =“samsung,k8f1315ebm”,“cfi-flash”; 
            reg = <2 0 0x4000000>;
        };
    };

external-bus 用了
2个cells定义地址值; 一个用于芯片的片选,一个用于表示相对于被选芯片的基址偏 移。
1个长度字段cell,因为只有地址的偏移部分需要具有范围。
因此,在这个例子 中,每个 reg 条目包含3个单元格;芯片的片选号,偏移量和长度。
由于地址域包含在节点及其子节点中,因此父节点可以自由定义对总线有意义的任何寻址方案。
直接父节点和子节点之外的节点通常不必关心本地寻址域,并且必须映射地址以从一个域到另一个域。

++++++++++++++++++++++++++++++++
非内存映射设备
设备未在处理器总线上的映射内存。它们可以具有地址范围,但CPU无法直接访问它们。
父设备的驱动程序将代表CPU执行间接访问。
以i2c设备为例,每个设备都分配了一个地址,但没有与之关联的长度或范围。这看起来与CPU地址分配大致相同。

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>;
    };
};

应该注意到 ranges i2c @ 1,0节点中没有ranges property。
这样做的原因是,与外部总线不同,i2c总线 上的设备不在CPU的地址域中进行内存映射。
相反,CPU通过i2c @ 1,0设备间接访问rtc @ 58设备。
缺少 ranges 属性意味着除了父级之外的任何设备都不能直接访问设备。

++++++++++++++++++++++++++++++++++++++

(5)中断怎么描述?

和中断相关的node可以分成3种:
• “Interrupt Generating Devices”,中断发生设备,这种设备可以发生中断。
• “Interrupt Controllers”,中断控制器,处理中断。
• “Interrupt Nexus”,中断联结,路由中断给中断控制器。

描述中断连接需要四个属性:

  1. interrupt-controller 一个空属性用来声明这个node接收中断信号;
  2. #interrupt-cells 这是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符;
  3. interrupt-parent 标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的;
  4. interrupts 一个中断标识符列表,表示每一个中断输出信号。具体每个 cell 又是什么含义,一般由驱动的实现决定,而且也会在 Device Tree 的 binding 文档中说明。譬如,对于 ARM GIC 中断控制器而言,#interrupt-cells 为 3,它 3 个 cell 的具体含义Documentation/devicetree/bindings/arm/gic.txt 就有如下文字说明:

The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI
interrupts.
The 2nd 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.
另外,值得注意的是,一个设备还可能用到多个中断号。对于 ARM GIC 而言,若某设备使用了 SPI 的 168、169 号 2 个中断,而言都是高电平触发,则该设备结点的 interrupts属性可定义为:

interrupts = <0 168 4>, <0 169 4>

通常,
二个cell的情况
中断号:触发类型
固定的取值如下:
1 = low-to-high edge triggered
2 = high-to-low edge triggered
4 = active high level-sensitive
8 = active low level-sensitive
三个cell的情况
中断号:触发的类型:优先级
0级是最高的,7级是最低的;其中0级的中断系统当做 FIQ处理。

“interrupts”属性用来定义设备的中断解析,
根据其”interrupt-parent”node中定义的“#interrupt-cells”来解析。
包含一个interrupt specifiers列表,每一个用于表示设备上的每个中断输出信号。
一个interrupt specifier是数据的一个或多个cells(如通过#interrupt-cells),指定哪个中断输入被设备关联 到。
大多数器件只有一个中断输出,如下例所示,但可以在器件上有多个中断输出。
interrupt specifier 的含义完全取决于其所绑定的中断控制器设备。每个中断控制器可以决定唯一定义中断输入所需的单元数(cell数量)。
以下代码为我们的Coyote Revenge示例机器添加了中断连接:

/ DTS-V1 /;
/ {
    compatible =“acme,coyotes-revenge”;
    #address-cells = <1>;
    #size-cells = <1>; 
    interrupt-parent = <&intc>;//phandle = <&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>;
            interrupts = <6 2>;
            rtc@58 {
                compatible =“maxim,ds1338”;
                reg = <58>; 
                interrupts = <7 3>;
            };
        };

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

有些事情需要注意:
机器只有一个中断控制器,中断控制器@ 10140000。

标签’intc:’已添加到中断控制器节点,标签用于将phandle分配给根节点中的interrupt-parent属性。此interrupt-parent为系统的默认值,因为除非显式覆盖它,否则所有子节点都会继承它。

每个器件都使用interrupt property 来指定不同的中断输入线(interrupt input line)。

#interrupt-cells为2,因此每个中断说明符都有2个cells。此示例使用第一个cell编码设备所使用的终端号,第二个cell用于编码终端属性标志,例如高电平触发还是低电平触发,或边沿触发亦或是电平敏感触发。对于任何给定的中断控制器,请参阅控制器的绑定文档以了解如何编码interrupt specifier。

“#interrupt-cells”属性用来规定连接到该中断控制器上的设备的”interrupts”属性的解析长度。(类似于 #address-cells 和 #size-cells )
比如#interrupt-cells=2,那根据2个cells为单位来解析”interrupts”属性。

“interrupt-parent”属性用来制定当前设备的Interrupt Controllers/Interrupt Nexus,phandle指向对应的node。包含与其连接的中断控制器的phandle。没有interrupt-parent属性的节点也可以从其父节点继承该属性。

“interrupt-controller”属性用来声明当前node为中断控制器。一个空属性,将节点声明为接收中断信号的设备。

“interrupt-map”属性用来描述interrupt nexus设备对中断的路由。解析格式为5元素序列“child unit address, child interrupt specifier, interrupt-parent, parent unit address, parent interrupt specifier”。
其中:
“child unit address”的cells长度由子节点的“#address-cells”指定;
“child interrupt specifier”的cells长度由子节点的“#interrupt-cells”指定;
“interrupt-parent”phandle指向interrupt controller的引用;
“parent unit address”的cells长度由父节点的“#address-cells”指定;
“parent interrupt specifier”的cells长度由父节点的“#interrupt-cells”指定;

举例:

  soc {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        open-pic {
            clock-frequency = <0>;
            interrupt-controller;
            #address-cells = <0>;
            #interrupt-cells = <2>;
        };
        pci {
            #interrupt-cells = <1>;
            #size-cells = <2>;
            #address-cells = <3>;
            interrupt-map-mask = <0xf800 0 0 7>;
            interrupt-map = <
            /* IDSEL 0x11 - PCI slot 1 */
            0x8800 0 0 1 &open-pic 2 1 /* INTA */
            0x8800 0 0 2 &open-pic 3 1 /* INTB */
            0x8800 0 0 3 &open-pic 4 1 /* INTC */
            0x8800 0 0 4 &open-pic 1 1 /* INTD */
            /* IDSEL 0x12 - PCI slot 2 */
            0x9000 0 0 1 &open-pic 3 1 /* INTA */
            0x9000 0 0 2 &open-pic 4 1 /* INTB */
            0x9000 0 0 3 &open-pic 1 1 /* INTC */
            0x9000 0 0 4 &open-pic 2 1 /* INTD */
            >;
        };
    };

For example, the first row of the interrupt-map table specifies the mapping for INTA of slot 1. The components of that row are shown here
child unit address: 0x8800 0 0
child interrupt specifier: 1
interrupt parent: &open-pic
parent unit address: (empty because #address-cells = <0> in the open-pic node)
parent interrupt specifier: 2 1

与遵循树的自然结构的地址范围转换不同,中断信号可以源自并终止于机器中的任何设备。与在设备树中自然表达的设备寻址不同,中断信号表示为独立于树的节点之间的链路。

+++++++++++++++++++++++++++++++++

(6)预定义的标准NODE

Root node
每个DeviceTree只有一个根节点。

/aliases node
例如:

aliases {
    serial0 = "/simple-bus@fe000000/serial@llc500";
    ethernet0 = "/simple-bus@fe000000/ethernet@31c000";
};

/memory node
例如:

  {
        #address-cells = <2>;
        #size-cells = <2>;
    
        memory@0 {
            device_type = "memory";
            reg = <0x000000000 0x00000000 0x00000000 0x80000000
            0x000000001 0x00000000 0x00000001 0x00000000>;
        };
    }

/chosen node
其中“bootargs”属性用来传递cmdline参数,
“stdout-path”属性用来指定标准输出设备,
“stdin-path”属性用来指定标准输入设备。
例如:

chosen {
        bootargs = "console=tty0 console=ttyMT0,921600n1 root=/dev/ram";
    };
    

/cpus node

**

++++++++++++++++++++++++++++++++++++++++

(7)常见节点及属性的设置与获取?

当修改或编写驱动时,常常需要修改gpio、时钟、中断等等参数,以前都是在mach-xxx中的device设置的,现在则要在节点里设置,然后驱动用特殊的API来获取。
属性的获取常常在probe函数中进行,但是获取属性之前,最重要的是,确定哪个节点触发了驱动。如果一个驱动对应多个节点,那驱动可以通过

int of_device_is_compatible(const struct device_node *device, const char *name)

来判断当前节点是否包含指定的compatible(兼容性)

gpio的设置与获取

/*imx6dl.dtsi中gpio1控制器的定义节点*/
gpio1: gpio@0209c000 {
 	compatible = "fsl,imx6q-gpio", "fsl,imx35-gpio";
	reg = <0x0209c000 0x4000>;
	 interrupts = 	<0 66 IRQ_TYPE_LEVEL_HIGH>,
             		<0 67 IRQ_TYPE_LEVEL_HIGH>;
    gpio-controller;	   
    #gpio-cells = <2>;
    interrupt-controller;
	#interrupt-cells = <2>;
};
	 
	/*imx6qdl-sabreauto.dtsi中某个设备节点*/
max7310_reset: max7310-reset {
	    compatible = "gpio-reset";
	    reset-gpios = <&gpio1 15 1>;
	    reset-delay-us = <1>;
	    #reset-cells = <0>;
};

一般来说,我们把gpio属性的名字起为xxx-gpios(xxx我们可以随便起),
这样驱动才能通过特定API从识别该属性,并转换成具体的gpio号。
该设备节点中设置了reset-gpios = <&gpio1 15 1>;这格式是什么意思呢?&gpio1 15引用了gpio1节点,故此处含义为gpio1_15这个引脚;
最后一个参数1则代表低电平有效,0则为高电平有效。
至于gpio1_15具体对应哪个引脚,在imx6的手册上都有详细描述。
其实最后一个参数(高低电平有效)不是必须的,因为gpio1节点中设置了#gpio-cells = <2>;,所以才有两个参数;
某些soc的gpio节点中会设置为#gpio-cells = <1>;,那么可以不写最后一个参数。
驱动一般通过以下接口获取上面节点中gpio的属性。
该函数第一个参数是节点,一般可以在传入probe的参数中间接获得;
第二个参数是gpio属性的名字,一定要和节点属性中的xxx-gpios相同;
最后一个是编号index,当节点中有n个同名的xxx-gpios时,可以通过它来获取特定的那个gpio,同一节点中gpio同名情况很少,我们都把index设为0。
gpio = of_get_named_gpio(node, “reset-gpios”, index);
在dts和驱动都不关心gpio名字的情况下,也可直接通过以下接口来获取gpio号,这个时候编号index就十分重要了,可以指定拿取节点的第index个gpio属性
gpio = of_get_gpio(node, index);

+++++++++++++++++++++++++++++++++

中断的设置与获取
假设某设备节点需要一个gpio中断

/*先确定中断所在的组*/
	interrupt-parent = <&gpio6>;
	 
	/*表示中断,GPIO6中的第8个IO,2为触发类型,下降沿触发*/
	interrupts = <8 2>;

而在驱动中使用 中断号 =irq_of_parse_and_map(node, index)函数返回值来得到中断号。

**
+++++++++++++++++++++++++++++++++++++++++++++++++

(8)KERNEL如何解析DTS?如何利用DTS生成设备节点?

setup_machine_fdt()
直接在dtb中解析根节点的一些属性和子节点给系统早期使用。
解析”/”节点的model”属性给machine_desc赋值;
解析”/chosen”node中的”bootargs”属性给boot_command_line;
解析”/”节点的”#size-cells”、”#address-cells”属性;
解析”/memory”node中的”reg”属性,并将memory区域加入到系统;

unflatten_device_tree()
将DTB完全解析为内核使用的的device_node、property结构:

of_platform_populate()
首先root节点下的第1级子节点创建成platform device。

对root节点下的第1级子节点,如果有”compatible”属性创建对应platform device;
如果”compatible”属性等于of_default_bus_match_table(“simple-bus”/”simple-mfd”/”arm,amba-bus”)中任意一种,继续对其子节点进行platform device创建。
因为第1级子节点会被注册成platform device,例如i2c/spi控制器,那么对应也需要注册platform driver。已i2c控制器驱动为例:

控制器首先会创建对应platform driver,把adapter注册成i2c device;
在adapter的probe过程中,会调用of_i2c_register_devices()函数遍历控制器下挂的i2c设备的DTS节点,并将其注册成i2c_client;

**
++++++++++++++++++++++++++++++++++++++++++++++

(8)新内核架构下,如何编写驱动?如何使驱动与设备节点匹配?

对于驱动本身来说,新内核下的变化倒不大;
主要是platform设备不再需要在mach-xxx中注册,而是直接以节点形式定义在设备树中。
platform设备可以直接定义在dts的根节点内,比如imx6dl-hummingboard.dts内的ir-receiver设备

/ {
    model = "SolidRun HummingBoard DL/Solo";
    compatible = "solidrun,hummingboard", "fsl,imx6dl";

    ir_recv: ir-receiver {
        compatible = "gpio-ir-receiver";
        gpios = <&gpio1 2 1>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_hummingboard_gpio1_2>;
    };

/*后面一堆代码就省略了*/

• 驱动程序将直接和设备树里的设备节点进行配对,是通过设备节点中的compatible(兼容性)来与设备节点进行配对的。具体方法是定义一个of_match_table,只要里面的compatible与设备节点里的compatible相同,那么就触发probe函数

   /*驱动中定义的of_match_table*/
    static struct of_device_id gpio_ir_recv_of_match[] = {
        { .compatible = "gpio-ir-receiver", },
        { },
    };
    
    /*of_match_table被绑定到driver结构体内*/
    static struct platform_driver gpio_ir_recv_driver = {
        .probe  = gpio_ir_recv_probe,
        .remove = gpio_ir_recv_remove,
        .driver = {
            .name   = GPIO_IR_DRIVER_NAME,
            .owner  = THIS_MODULE,
            .of_match_table = of_match_ptr(gpio_ir_recv_of_match),
        },
    };

有关设备的私有数据,新内核不再使用plat_data了,而是直接在节点中定义各种属性,然后在驱动中用特定的API获取

在新内核下,i2c适配器的驱动倒是没有变化,而i2c适配器设备体的注册,却采用了设备树的方式
下面是imx6qdl.dtsi中对i2c1适配器设备的定义和注册,里面定义了很多参数,一般来说我们是根本不用去修改这个节点的。
假设我们要修改其中的参数(比如频率),只需在项目的dts中引用该节点,并重写即可.

   i2c1: i2c@021a0000 {
        #address-cells = <1>;
        #size-cells = <0>;
        compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
        reg = <0x021a0000 0x4000>;
        interrupts = <0 36 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&clks IMX6QDL_CLK_I2C1>;
        status = "disabled";
    };

所谓的i2c设备(client),就是挂在i2c上的外设(比如各种传感器),这个需要我们自己注册,在3.x后的kernel中采用了设备树节点的方式,故这里需要分类讨论。

下面是imx6dl-hummingboard.dts中对于一个pcf8523的注册,它首先引用了imx6qdl.dtsi中的i2c1适配器,并重写了其中的status 属性为okay,如果设备对频率有要求,也可以重写clocks属性。
在i2c1节点中定义了一个pcf8523节点,只有定义在这,该节点才会以i2c设备的身份被注册,并且绑定i2c1这个适配器

 &i2c1 {
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_hummingboard_i2c1>;
        status = "okay";
        
    	rtc: pcf8523@68 {
        			compatible = "nxp,pcf8523";
       		 		reg = <0x68>;
   		 };
    };

设备树对i2c设备的注册有比较大的影响,详见前面的章节,这里不再赘述;而对于驱动程序,设备树带来的变化极小,主要是驱动和设备之间的匹配方式变了
老旧的id_table方式不再使用,取而代之的是类似的一种结构:of_match_table
这里以pcf8523驱动为例,只要驱动中的of_match_table 中的compatible 值和设备节点中的compatible 相匹配,那么probe函数就会被触发。不仅i2c是这样,platform、spi等都是这个原理

  /*定义的of_match_table*/
    static const struct of_device_id pcf8523_of_match[] = {
        { .compatible = "nxp,pcf8523" },
        { }
    };
    
    /*driver 结构体中的of_match_table*/
    static struct i2c_driver pcf8523_driver = {
        .driver = {
            .name = DRIVER_NAME,
            .owner = THIS_MODULE,
            .of_match_table = of_match_ptr(pcf8523_of_match),
        },
        .probe = pcf8523_probe,
        .id_table = pcf8523_id,
    };

i2c和spi驱动还支持一种“别名匹配”的机制,就以pcf8523为例,假设某程序员在设备树中的pcf8523设备节点中写了compatible = “pcf8523”;,显然相对于驱动id_table中的”nxp,pcf8523”,他遗漏了nxp字段,但是驱动却仍然可以匹配上,因为别名匹配对compatible中字符串里第二个字段敏感

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值