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控制器节点在设备树中的位置、核心属性以及驱动资源映射的完整链路:
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 工具直接读取寄存器地址验证映射是否正确。