3、设备树语法精讲(下):中断控制器、中断映射、GPIO控制器、时钟、pinctrl、电源管理
本章继续深入设备树的核心语法,重点解析中断子系统、GPIO控制器、时钟框架、引脚控制(pinctrl)以及电源管理域(power-domains)的绑定规范。这些节点是驱动与硬件交互的桥梁,理解其映射关系是移植适配的关键。
3.1 中断控制器(interrupt-controller)与中断映射(interrupt-map)
中断控制器节点负责将硬件中断源路由到CPU核心。设备树通过 interrupt-controller 属性标记节点为中断控制器,并使用 #interrupt-cells 指定中断描述符的单元数。
3.1.1 中断控制器节点属性
| 属性 | 类型 | 描述 |
|---|---|---|
interrupt-controller |
空属性 | 标识该节点为中断控制器 |
#interrupt-cells |
u32 | 指定中断描述符所需的单元数(通常为1、2或3) |
interrupt-parent |
phandle | 指向父中断控制器(用于级联) |
典型ARM GIC(通用中断控制器)节点示例:
gic: interrupt-controller@f1000 {
compatible = "arm,cortex-a15-gic";
reg = <0x0 0xf1000 0x0 0x1000>,
<0x0 0xf2000 0x0 0x2000>;
interrupt-controller;
#interrupt-cells = <3>;
};
其中 #interrupt-cells = <3> 表示每个中断描述符包含3个单元:中断类型(0=SPI,1=PPI)、中断号、触发标志。
3.1.2 中断映射(interrupt-map)
当系统中存在多个中断控制器或需要将PCIe、总线设备的中断重新路由时,使用 interrupt-map 属性建立从设备中断到父中断控制器的映射关系。该属性通常与 interrupt-map-mask 配合使用。
映射规则: interrupt-map 是一个数组,每5个单元为一组:子地址, 子中断, 父控制器phandle, 父地址, 父中断。
PCIe中断映射示例:
pcie@10000000 {
compatible = "pci-host-ecam-generic";
reg = <0x0 0x10000000 0x0 0x10000000>;
#address-cells = <3>;
#size-cells = <2>;
#interrupt-cells = <1>;
interrupt-map-mask = <0xf800 0x0 0x0 0x7>;
interrupt-map = <0x0000 0x0 0x0 0x1 &gic 0x0 0x0 0x84 0x4>,
<0x0000 0x0 0x0 0x2 &gic 0x0 0x0 0x85 0x4>,
<0x0000 0x0 0x0 0x3 &gic 0x0 0x0 0x86 0x4>,
<0x0000 0x0 0x0 0x4 &gic 0x0 0x0 0x87 0x4>;
};
避坑提示: 中断映射中的父地址单元数由父中断控制器的 #address-cells 决定。若父节点未定义 #address-cells,则默认为0,此时父地址单元应省略。
3.2 GPIO控制器
GPIO控制器节点提供通用输入输出引脚的管理能力。设备树通过 gpio-controller 属性标识节点,并使用 #gpio-cells 定义GPIO描述符的格式。
3.2.1 GPIO控制器节点属性
| 属性 | 类型 | 描述 |
|---|---|---|
gpio-controller |
空属性 | 标识该节点为GPIO控制器 |
#gpio-cells |
u32 | 指定GPIO描述符单元数(通常为2) |
gpio-ranges |
数组 | 将GPIO引脚映射到pinctrl引脚范围 |
标准GPIO控制器节点示例:
gpio0: gpio@ff0000 {
compatible = "snps,dw-apb-gpio";
reg = <0x0 0xff0000 0x0 0x1000>;
gpio-controller;
#gpio-cells = <2>;
ngpios = <32>;
};
设备引用GPIO的方式:
led {
compatible = "gpio-leds";
led-red {
gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
label = "red-led";
};
};
其中 GPIO_ACTIVE_LOW 为0,GPIO_ACTIVE_HIGH 为1,定义在 dt-bindings/gpio/gpio.h 中。
常见错误: 忘记在GPIO控制器节点中添加 gpio-controller 属性,或 #gpio-cells 值与实际使用不匹配(如使用3个单元但只声明2个),会导致内核无法解析GPIO请求。
3.3 时钟(clocks)
时钟子系统通过设备树描述时钟源、时钟控制器以及设备与时钟的依赖关系。核心属性包括 clocks、clock-names 和 assigned-clocks。
3.3.1 时钟控制器节点
clk: clock-controller@f10000 {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24000000>;
clock-output-names = "osc24M";
};
对于多输出时钟控制器(如PLL),#clock-cells 通常为1,通过索引区分不同时钟输出:
pll: clock-controller@f20000 {
compatible = "rockchip,rk3399-pll";
reg = <0x0 0xf20000 0x0 0x1000>;
#clock-cells = <1>;
clocks = <&clk 0>;
clock-names = "ref";
};
3.3.2 设备时钟绑定
设备节点通过 clocks 属性列出所有依赖的时钟,clock-names 提供可读名称:
uart0: serial@ff1800 {
compatible = "snps,dw-apb-uart";
reg = <0x0 0xff1800 0x0 0x100>;
clocks = <&clk 0>, <&clk 1>;
clock-names = "baudclk", "apb_pclk";
};
关键点: 驱动通过 clk_get(dev, "baudclk") 获取指定名称的时钟句柄。若 clock-names 缺失,驱动只能按索引顺序获取,容易出错。
3.4 pinctrl(引脚控制)
pinctrl子系统负责引脚复用、电气特性配置。设备树通过 pinctrl-names 和 pinctrl-0、pinctrl-1 等属性将设备与引脚状态绑定。
3.4.1 pinctrl节点结构
pinctrl: pinctrl@ff0200 {
compatible = "rockchip,rk3399-pinctrl";
reg = <0x0 0xff0200 0x0 0x100>;
gpio-controller;
#gpio-cells = <2>;
#address-cells = <2>;
#size-cells = <2>;
uart0_default: uart0-default {
uart0_tx {
rockchip,pins = <2 RK_PB4 RK_FUNC_1 &pcfg_pull_up>;
};
uart0_rx {
rockchip,pins = <2 RK_PB5 RK_FUNC_1 &pcfg_pull_up>;
};
};
};
3.4.2 设备引用pinctrl状态
uart0: serial@ff1800 {
compatible = "snps,dw-apb-uart";
reg = <0x0 0xff1800 0x0 0x100>;
pinctrl-names = "default", "sleep";
pinctrl-0 = <&uart0_default>;
pinctrl-1 = <&uart0_sleep>;
};
| 属性 | 描述 |
|---|---|
pinctrl-names |
定义状态名称列表,如 "default"、"sleep"、"idle" |
pinctrl-0 |
对应第一个状态(default)的引脚配置列表 |
pinctrl-1 |
对应第二个状态(sleep)的引脚配置列表 |
最佳实践: 每个设备至少定义 default 状态。对于低功耗场景,定义 sleep 状态将引脚设置为输入或下拉,以降低漏电流。
3.5 电源管理(power-domains)
电源管理域(power domain)用于控制SoC内部不同模块的电源开关和电压调节。设备树通过 power-domains 属性将设备关联到对应的电源域控制器。
3.5.1 电源域控制器节点
power: power-controller@f30000 {
compatible = "rockchip,rk3399-power-controller";
reg = <0x0 0xf30000 0x0 0x1000>;
#power-domain-cells = <1>;
#address-cells = <1>;
#size-cells = <0>;
pd_gpu: power-domain@1 {
reg = <1>;
clocks = <&clk 3>;
pm_qos = <&qos_gpu>;
};
pd_vpu: power-domain@2 {
reg = <2>;
clocks = <&clk 4>;
};
};
3.5.2 设备绑定电源域
gpu: gpu@f40000 {
compatible = "arm,mali-midgard";
reg = <0x0 0xf40000 0x0 0x4000>;
power-domains = <&power 1>;
clocks = <&clk 3>;
};
| 属性 | 描述 |
|---|---|
#power-domain-cells |
指定电源域描述符单元数(通常为1,表示域索引) |
power-domains |
设备所属的电源域列表,每个条目包含控制器phandle和索引 |
domain-idle-states |
可选的电源域空闲状态描述(用于深度睡眠) |
电源域级联: 一个电源域可以包含子域,形成树形结构。父域关闭前必须确保所有子域已关闭。设备树通过 power-domains 属性在子域节点中引用父域来实现层级关系。
3.5.3 电源域与性能状态(Performance State)
对于支持DVFS(动态电压频率调整)的电源域,设备树可描述性能状态表:
pd_cpu: power-domain@0 {
reg = <0>;
#power-domain-cells = <0>;
operating-points-v2 = <&cpu_opp_table>;
};
性能状态表定义在单独的OPP节点中:
cpu_opp_table: opp-table {
compatible = "operating-points-v2";
opp00 {
opp-hz = /bits/ 64 <600000000>;
opp-microvolt = <900000>;
};
opp01 {
opp-hz = /bits/ 64 <1200000000>;
opp-microvolt = <1100000>;
};
};
兼容性注意: 不同SoC厂商的电源域控制器绑定差异较大。Rockchip使用 rockchip,rk3399-power-controller,而TI AM335x使用 ti,am3xxx-power-controller。移植时务必参考对应厂商的绑定文档。
3.6 综合案例:多控制器协同配置
实际设备往往同时依赖中断、GPIO、时钟、pinctrl和电源域。以下展示一个完整的I2C控制器节点配置:
i2c0: i2c@ff1000 {
compatible = "snps,designware-i2c";
reg = <0x0 0xff1000 0x0 0x100>;
interrupts = <0 56 4>;
clocks = <&clk 5>;
clock-names = "i2c";
pinctrl-names = "default";
pinctrl-0 = <&i2c0_default>;
power-domains = <&power 3>;
#address-cells = <1>;
#size-cells = <0>;
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <16>;
};
};
该节点展示了多控制器协同的典型模式:
- 中断: 通过
interrupts指定SPI中断号56,高电平触发 - 时钟: 依赖索引为5的时钟,驱动通过名称 "i2c" 获取
- pinctrl: 默认状态下使用
i2c0_default引脚配置 - 电源域: 关联到索引为3的电源域,确保上电顺序正确
移植检查清单: 在为新板卡编写设备树时,逐项确认每个设备节点的中断、时钟、pinctrl和电源域是否完整。遗漏任何一项都可能导致驱动 probe 失败或运行时异常。