3、设备树进阶:中断控制器、GPIO控制器、时钟控制器、DMA控制器绑定
在掌握了设备树基础语法和标准属性之后,本章深入探讨嵌入式系统中最关键的四个控制器类设备绑定:中断控制器(Interrupt Controller)、GPIO控制器(GPIO Controller)、时钟控制器(Clock Controller)和DMA控制器(DMA Controller)。这些控制器是SoC内部基础设施的核心,其设备树绑定的正确性直接影响整个系统的中断响应、引脚控制、时钟管理和数据搬运能力。
*-cells属性描述消费者设备如何引用这些资源。理解这种“提供者-消费者”模式是掌握设备树进阶绑定的关键。
3.1 中断控制器绑定
中断控制器是系统中负责接收外设中断请求并转发给CPU的硬件模块。在设备树中,中断控制器作为中断资源的提供者,通过标准属性描述其能力,并允许消费者设备通过interrupts属性引用中断线。
3.1.1 中断控制器节点属性
| 属性 | 类型 | 描述 |
|---|---|---|
interrupt-controller |
空属性 | 标识该节点是一个中断控制器 |
#interrupt-cells |
u32 | 指定消费者设备interrupts属性中每个中断描述符所需的cell数 |
interrupt-parent |
phandle | 指向该中断控制器自身的中断父节点(级联场景) |
以ARM通用中断控制器(GIC)为例,其典型的设备树绑定如下:
gic: interrupt-controller@f0001000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0xf0001000 0x1000>,
<0xf0000100 0x100>;
};
对于#interrupt-cells = <3>的GIC,每个中断描述符的三个cell分别表示:中断类型(0=SPI共享外设中断,1=PPI私有外设中断)、中断号、触发类型和极性标志。
3.1.2 消费者设备中断引用
消费者设备通过interrupts属性引用中断控制器提供的中断资源:
uart0: serial@f0002000 {
compatible = "ns16550a";
reg = <0xf0002000 0x100>;
interrupts = <0 33 4>; /* SPI, 中断号33, 高电平触发 */
interrupt-parent = <&gic>;
};
interrupt-parent明确指定其中断父节点。如果省略,内核会使用根节点interrupt-parent的默认值。
3.1.3 中断控制器级联
在复杂SoC中,多个中断控制器可能以级联方式连接。下图展示了一个典型的两级中断控制器拓扑:
级联场景下的设备树描述:
gpio_controller: gpio@f0003000 {
compatible = "vendor,gpio-controller";
reg = <0xf0003000 0x100>;
interrupt-controller;
#interrupt-cells = <2>;
interrupt-parent = <&gic>;
interrupts = <0 50 4>; /* 连接到GIC的中断线 */
};
i2c_controller: i2c@f0004000 {
compatible = "vendor,i2c-controller";
reg = <0xf0004000 0x100>;
interrupt-controller;
#interrupt-cells = <1>;
interrupt-parent = <&gic>;
interrupts = <0 51 4>;
};
3.2 GPIO控制器绑定
GPIO控制器管理SoC的通用输入输出引脚,提供引脚复用、方向控制和电平读写功能。在设备树中,GPIO控制器既是GPIO资源的提供者,也可能作为中断控制器提供引脚中断能力。
3.2.1 GPIO控制器节点属性
| 属性 | 类型 | 描述 |
|---|---|---|
gpio-controller |
空属性 | 标识该节点是一个GPIO控制器 |
#gpio-cells |
u32 | 指定消费者设备gpios属性中每个GPIO描述符所需的cell数 |
gpio-ranges |
prop-encoded-array | 描述GPIO控制器与引脚控制器之间的映射关系 |
典型的GPIO控制器节点:
gpio0: gpio@f0005000 {
compatible = "vendor,generic-gpio";
reg = <0xf0005000 0x200>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>;
};
对于#gpio-cells = <2>的GPIO控制器,两个cell分别表示:GPIO编号(相对于该控制器的偏移)和GPIO标志(如高有效/低有效、开漏等)。
3.2.2 消费者设备GPIO引用
消费者设备通过gpios或cs-gpios等属性引用GPIO资源:
led_red {
compatible = "gpio-leds";
gpios = <&gpio0 15 0>; /* GPIO0_15, 高有效 */
label = "red-led";
};
spi0: spi@f0006000 {
compatible = "vendor,spi-controller";
reg = <0xf0006000 0x100>;
cs-gpios = <&gpio0 10 0>,
<&gpio0 11 0>;
};
gpio0 15表示GPIO控制器0的第15号引脚,而不是SoC的第15号引脚。全局引脚映射由gpio-ranges属性在GPIO控制器和pinctrl子系统之间建立。
3.2.3 GPIO与中断的关联
许多GPIO控制器同时提供引脚中断功能。消费者设备可以通过interrupts属性引用GPIO控制器的中断能力:
key_button {
compatible = "gpio-keys";
gpios = <&gpio0 20 1>; /* GPIO0_20, 低有效 */
interrupts = <20 2>; /* GPIO0_20, 下降沿触发 */
interrupt-parent = <&gpio0>;
label = "power-key";
};
3.3 时钟控制器绑定
时钟控制器负责生成和分配SoC内部各模块所需的时钟信号。设备树通过时钟绑定描述时钟的提供者、消费者以及时钟频率、相位等属性。
3.3.1 时钟控制器节点属性
| 属性 | 类型 | 描述 |
|---|---|---|
#clock-cells |
u32 | 指定消费者设备clocks属性中每个时钟描述符所需的cell数 |
clock-output-names |
stringlist | 可选的时钟输出名称列表 |
clock-frequency |
u32 | 固定频率时钟的输出频率(单位Hz) |
时钟控制器节点示例:
clkc: clock-controller@f0007000 {
compatible = "vendor,clock-controller";
reg = <0xf0007000 0x1000>;
#clock-cells = <1>;
clock-output-names = "cpu_clk", "bus_clk", "uart_clk";
};
3.3.2 消费者设备时钟引用
消费者设备通过clocks属性引用时钟资源,并通过clock-names属性为每个时钟指定名称:
uart1: serial@f0008000 {
compatible = "ns16550a";
reg = <0xf0008000 0x100>;
clocks = <&clkc 2>; /* 引用clkc的第2个输出(uart_clk) */
clock-names = "baud_clk";
};
#clock-cells = <1>时,消费者使用一个整数索引来指定所需的时钟输出。索引0通常对应第一个时钟输出,索引1对应第二个,以此类推。驱动开发者需要查阅SoC手册确认每个索引对应的实际时钟。
3.3.3 时钟树结构
下图展示了一个典型的SoC时钟树结构,包含多个时钟控制器和消费者设备:
3.4 DMA控制器绑定
DMA(Direct Memory Access)控制器允许外设直接访问系统内存,无需CPU干预。设备树中的DMA绑定描述了DMA控制器的通道资源、传输能力和消费者设备的DMA请求映射。
3.4.1 DMA控制器节点属性
| 属性 | 类型 | 描述 |
|---|---|---|
dma-controller |
空属性 | 标识该节点是一个DMA控制器 |
#dma-cells |
u32 | 指定消费者设备dmas属性中每个DMA描述符所需的cell数 |
dma-channels |
u32 | DMA控制器支持的通道数量 |
dma-requests |
u32 | DMA控制器支持的请求线数量 |
DMA控制器节点示例:
dma0: dma-controller@f0009000 {
compatible = "vendor,dma-controller";
reg = <0xf0009000 0x1000>;
dma-controller;
#dma-cells = <2>;
dma-channels = <8>;
dma-requests = <16>;
interrupt-parent = <&gic>;
interrupts = <0 60 4>,
<0 61 4>,
<0 62 4>,
<0 63 4>; /* 4个中断线对应不同通道组 */
};
对于#dma-cells = <2>的DMA控制器,两个cell通常表示:DMA通道编号和DMA传输类型或优先级标志。
3.4.2 消费者设备DMA引用
消费者设备通过dmas属性引用DMA通道,并通过dma-names属性为每个DMA通道指定名称:
spi1: spi@f000a000 {
compatible = "vendor,spi-controller";
reg = <0xf000a000 0x100>;
dmas = <&dma0 2 0>, /* 使用dma0的通道2,类型0 */
<&dma0 3 0>; /* 使用dma0的通道3,类型0 */
dma-names = "tx", "rx";
};
dma-names属性可以明确区分每个DMA通道的用途,使驱动代码更具可读性。内核DMA引擎API通过dma_request_slave_channel(dev, "tx")这样的名称匹配来获取通道。
3.4.3 DMA请求映射
在复杂SoC中,多个外设共享DMA控制器的请求线。设备树通过dma-requests属性和消费者设备的dmas属性共同建立外设到DMA请求线的映射关系:
/* DMA控制器定义16条请求线 */
dma1: dma-controller@f000b000 {
compatible = "vendor,dma-controller";
reg = <0xf000b000 0x1000>;
dma-controller;
#dma-cells = <3>;
dma-channels = <4>;
dma-requests = <16>;
};
/* 外设A使用请求线5 */
uart2: serial@f000c000 {
compatible = "ns16550a";
reg = <0xf000c000 0x100>;
dmas = <&dma1 0 5 0>; /* 通道0, 请求线5, 标志0 */
dma-names = "rx";
};
/* 外设B使用请求线7 */
i2c1: i2c@f000d000 {
compatible = "vendor,i2c-controller";
reg = <0xf000d000 0x100>;
dmas = <&dma1 1 7 0>; /* 通道1, 请求线7, 标志0 */
dma-names = "tx";
};
3.5 四种控制器绑定对比
下表总结了中断控制器、GPIO控制器、时钟控制器和DMA控制器在设备树绑定中的核心异同:
| 控制器类型 | 提供者标识属性 | cells属性 | 消费者引用属性 | 典型cell数 | cell含义 |
|---|---|---|---|---|---|
| 中断控制器 | interrupt-controller |
#interrupt-cells |
interrupts |
1~3 | 中断类型、中断号、触发标志 |
| GPIO控制器 | gpio-controller |
#gpio-cells |
gpios |
2 | 引脚编号、有效电平标志 |
| 时钟控制器 | (无专用标识) | #clock-cells |
clocks |
0~1 | 时钟输出索引(0表示固定时钟) |
| DMA控制器 | dma-controller |
#dma-cells |
dmas |
2~3 | 通道编号、请求线编号、传输标志 |
3.6 综合示例:多控制器协同
以下是一个完整的设备树片段,展示了中断控制器、GPIO控制器、时钟控制器和DMA控制器在同一个SoC中的协同工作:
/ {
#address-cells = <1>;
#size-cells = <1>;
interrupt-parent = <&gic>;
gic: interrupt-controller@f0001000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0xf0001000 0x1000>,
<0xf0000100 0x100>;
};
clkc: clock-controller@f0007000 {
compatible = "vendor,clock-controller";
#clock-cells = <1>;
reg = <0xf0007000 0x1000>;
};
gpio0: gpio@f0005000 {
compatible = "vendor,generic-gpio";
#gpio-cells = <2>;
gpio-controller;
#interrupt-cells = <2>;
interrupt-controller;
reg = <0xf0005000 0x200>;
gpio-ranges = <&pinctrl 0 0 32>;
};
dma0: dma-controller@f0009000 {
compatible = "vendor,dma-controller";
#dma-cells = <2>;
dma-controller;
dma-channels = <8>;
reg = <0xf0009000 0x1000>;
interrupts = <0 60 4>;
};
uart0: serial@f0002000 {
compatible = "ns16550a";
reg = <0xf0002000 0x100>;
interrupts = <0 33 4>;
clocks = <&clkc 2>;
clock-names = "baud_clk";
dmas = <&dma0 0 0>,
<&dma0 1 0>;
dma-names = "tx", "rx";
};
led_red {
compatible = "gpio-leds";
gpios = <&gpio0 15 0>;
label = "red-led";
};
};
interrupts)、时钟控制器(clocks)和DMA控制器(dmas)。每个资源引用都遵循“提供者-消费者”模式,通过phandle指向对应的控制器节点,并通过cell值指定具体的资源索引。这种分层解耦的设计使得硬件描述高度模块化,便于移植和复用。
3.7 常见绑定错误与调试
在实际开发中,控制器绑定错误是设备树调试中最常见的问题。以下是几种典型错误及其排查方法:
| 错误类型 | 现象 | 排查方法 |
|---|---|---|
缺少interrupt-parent |
中断无法触发,驱动probe失败 | 检查消费者节点是否显式指定interrupt-parent,或根节点是否有默认值 |
#*-cells数量不匹配 |
内核解析设备树时报of_parse_phandle_with_args错误 |
核对提供者节点的#*-cells值与消费者节点中提供的cell数量是否一致 |
| GPIO编号越界 | GPIO操作无效或影响其他引脚 | 确认GPIO编号小于控制器
← 上一章
📖 返回目录
下一章 →
|