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,此时父地址单元应省略。

中断映射(interrupt-map)数据流 PCIe设备 #interrupt-cells = 1 interrupt-map 子地址+子中断 → 父地址+父中断 interrupt-map-mask 过滤 GIC中断控制器 #interrupt-cells = 3 映射组格式(每5个单元) 1. 子地址(child address):根据父总线 #address-cells 决定 2. 子中断(child interrupt):根据子节点 #interrupt-cells 决定 3. 父控制器phandle:指向目标中断控制器节点 4. 父地址(parent address):根据父控制器 #address-cells 决定 5. 父中断(parent interrupt):根据父控制器 #interrupt-cells 决定

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 状态将引脚设置为输入或下拉,以降低漏电流。

pinctrl 状态切换与引脚配置流程 UART设备 pinctrl-names = "default","sleep" pinctrl-0 uart0_default TX/RX 功能1, 上拉 pinctrl-1 uart0_sleep TX/RX GPIO输入, 下拉 pinctrl控制器 引脚复用寄存器 电气特性寄存器 驱动运行时通过 pinctrl_pm_select_default_state() / pinctrl_pm_select_sleep_state() 切换 系统休眠时自动切换到 sleep 状态,唤醒后恢复 default 状态

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 失败或运行时异常。