4. SPI控制器节点:SPI控制器在设备树中的描述

SPI控制器节点是设备树中描述SoC内部SPI主机(Master)硬件模块的核心节点。它向内核提供控制器的寄存器基址、中断号、时钟源、DMA通道以及总线特性等关键资源信息。驱动通过解析该节点,完成控制器初始化、时钟使能、中断注册和SPI总线注册。

4.1 控制器节点的基本结构

一个典型的SPI控制器节点位于SoC的soc总线节点下,其compatible属性用于匹配驱动,reg描述寄存器地址空间,interrupts指定中断资源,clocks提供时钟句柄。以下是一个基于ARM架构的SPI控制器节点示例:

spi@ff110000 {
    compatible = "arm,pl022", "arm,primecell";
    reg = <0xff110000 0x1000>;
    interrupts = <0 39 4>;
    clocks = <&clkc 15>, <&clkc 16>;
    clock-names = "sspclk", "apb_pclk";
    #address-cells = <1>;
    #size-cells = <0>;
    num-cs = <4>;
    cs-gpios = <&gpio0 10 GPIO_ACTIVE_LOW>,
               <&gpio0 11 GPIO_ACTIVE_LOW>,
               <&gpio0 12 GPIO_ACTIVE_LOW>,
               <&gpio0 13 GPIO_ACTIVE_LOW>;
    status = "okay";

    spi-flash@0 {
        compatible = "jedec,spi-nor";
        reg = <0>;
        spi-max-frequency = <50000000>;
    };
};
关键点:控制器节点必须声明 #address-cells 和 #size-cells,子节点(SPI设备)的 reg 属性才能被正确解析为片选号。通常 #address-cells = <1>,#size-cells = <0>。

4.2 核心属性详解

属性名 类型 说明 示例值
compatible string list 驱动匹配字符串,通常包含SoC特定名和通用名 "arm,pl022", "arm,primecell"
reg u32 list 寄存器基地址和长度,格式由父节点的 #address-cells 和 #size-cells 决定 <0xff110000 0x1000>
interrupts u32 list 中断号、触发类型(依赖中断控制器编码) <0 39 4>
clocks phandle list 时钟句柄列表,配合 clock-names 使用 <&clkc 15>, <&clkc 16>
clock-names string list 时钟名称,驱动通过名称获取对应时钟 "sspclk", "apb_pclk"
#address-cells u32 子节点 reg 中地址字段的单元数,SPI子节点通常为1 <1>
#size-cells u32 子节点 reg 中长度字段的单元数,SPI子节点通常为0 <0>
num-cs u32 控制器支持的片选线数量 <4>
cs-gpios phandle list 片选GPIO列表,用于GPIO模拟片选 <&gpio0 10 ...>
status string 设备状态,"okay" 启用,"disabled" 禁用 "okay"

4.3 资源属性与驱动映射

驱动在probe阶段通过设备树API获取上述属性,完成硬件资源申请。以下为典型的资源映射关系:

  • compatible → 驱动匹配:内核通过 of_match_table 匹配节点,调用对应probe函数。
  • reg → 寄存器映射:驱动调用 devm_platform_ioremap_resource() 将物理地址映射为虚拟地址。
  • interrupts → 中断注册:使用 platform_get_irq() 获取中断号,调用 devm_request_irq() 注册中断处理函数。
  • clocks → 时钟使能:通过 devm_clk_get() 获取时钟句柄,clk_prepare_enable() 使能时钟。
  • cs-gpios → 片选GPIO:使用 of_get_named_gpio_flags() 解析GPIO,通过GPIO子系统控制片选。
驱动代码片段(SPI控制器probe):
static int spi_pl022_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct pl022 *pl022;
    struct resource *res;

    pl022 = devm_kzalloc(dev, sizeof(*pl022), GFP_KERNEL);
    if (!pl022)
        return -ENOMEM;

    // 映射寄存器
    pl022->virtbase = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(pl022->virtbase))
        return PTR_ERR(pl022->virtbase);

    // 获取中断
    pl022->irq = platform_get_irq(pdev, 0);
    if (pl022->irq < 0)
        return pl022->irq;

    // 获取时钟
    pl022->clk = devm_clk_get(dev, "sspclk");
    if (IS_ERR(pl022->clk))
        return PTR_ERR(pl022->clk);
    clk_prepare_enable(pl022->clk);

    // 注册SPI控制器
    return spi_register_controller(pl022->host);
}

4.4 SPI控制器节点结构图

下图展示了SPI控制器节点在设备树中的位置、核心属性以及驱动资源映射的完整链路:

SPI控制器节点结构及驱动资源映射 设备树节点 spi@ff110000 compatible = "arm,pl022" reg = <0xff110000 0x1000> interrupts = <0 39 4> clocks = <&clkc 15> 驱动资源映射 platform_driver probe of_match_table → 匹配compatible devm_ioremap_resource → reg platform_get_irq → interrupts devm_clk_get → clocks 内核SPI框架 spi_register_ controller() SPI总线注册 设备枚举 说明:设备树节点中的每个属性都对应驱动中的资源获取API。compatible属性是驱动匹配的入口,reg、interrupts、clocks分别映射为寄存器基址、中断号和时钟句柄,驱动通过这些资源完成SPI控制器的初始化。 属性与驱动API对照表 设备树属性 驱动API 获取资源 用途 compatible of_match_table 匹配ID表 驱动与设备绑定 reg devm_ioremap_resource 物理→虚拟地址 寄存器访问 interrupts platform_get_irq 中断号 中断处理注册

4.5 片选与GPIO扩展

SPI控制器的片选信号(Chip Select)有两种实现方式:

  • 硬件片选:由SPI控制器内部逻辑直接驱动CS引脚,无需额外GPIO。此时设备树中不需要 cs-gpios 属性。
  • GPIO片选:当控制器硬件片选数量不足或需要灵活映射时,使用GPIO模拟片选。设备树中通过 cs-gpios 属性指定每个片选对应的GPIO。
注意:使用GPIO片选时,必须确保 cs-gpios 列表长度与 num-cs 一致。驱动在传输开始前通过GPIO置低选中设备,传输结束后置高释放。GPIO片选会增加传输延迟,但对硬件设计更灵活。

4.6 时钟与中断的典型配置

SPI控制器通常需要两个时钟:

  • SPI引擎时钟(sspclk):用于SPI位时钟生成,决定SPI通信速率。
  • APB总线时钟(apb_pclk):用于控制器内部寄存器访问,通常与总线频率一致。

中断配置方面,SPI控制器通常使用一个中断线,在传输完成、FIFO溢出或错误时触发。中断触发类型在 interrupts 属性的第三个字段指定:

触发类型值 含义
0 上升沿触发
1 下降沿触发
2 高电平触发
3 低电平触发
4 边沿触发(上升沿和下降沿)

4.7 控制器节点常见错误排查

  • compatible不匹配:驱动无法probe,检查设备树中的compatible字符串是否与驱动中的 of_match_table 一致。
  • reg地址冲突:寄存器地址与其他设备重叠,导致ioremap失败,检查地址范围是否正确。
  • 中断号错误:中断号超出中断控制器范围或触发类型不匹配,导致中断无法注册。
  • 时钟未使能:驱动未获取或使能时钟,控制器寄存器读写无响应,检查clock-names是否匹配。
  • 片选GPIO配置错误:cs-gpios数量与num-cs不一致,或GPIO号无效,导致片选无法控制。
调试建议:使用 ls /sys/bus/platform/devices/ 查看设备是否注册;使用 cat /proc/interrupts 确认中断是否触发;使用 devmem2 工具直接读取寄存器地址验证映射是否正确。