多SPI子设备Linux内核驱动实现:设备树与API选型问询
单Linux内核驱动控制双SPI子设备的实现方案、选型及规范用法
一、可行实现方案
方案1:同一SPI控制器下双设备节点+Probe阶段关联(你提到的方案)
核心逻辑是将两个SPI子设备作为同一控制器下的独立节点,通过id属性标记归属同一物理设备,驱动在probe流程中收集匹配的设备实例,待同组设备全部就绪后再完成完整初始化。
- 优势:完全符合Linux设备树分层设计规范,无需修改SPI控制器驱动,仅需实现子设备驱动即可
- 适配场景:两个SPI子设备功能独立但需协同工作,共享GPIO/中断等外设资源
方案2:SPI多片选复用(备选)
若两个子设备可共用同一SPI总线时序,仅片选信号不同,可将其配置为同一spi_device下的多片选设备,但要求控制器支持多CS输出。此方案不适用于功能差异大、通信时序不同的子设备。
二、推荐选型
SPI控制器选型
- 优先选择支持多片选(CS)输出的硬件SPI控制器(如NXP i.MX系列SPI、Rockchip SPI),满足同一总线挂载多设备的需求
- 若采用GPIO模拟SPI(
spi-gpio),需确保GPIO资源充足,且中断线连接到支持边缘触发的中断控制器
GPIO/中断选型
- 中断请求线需选用支持**边缘触发(上升/下降沿)**的GPIO,避免电平触发导致的中断风暴
- 辅助GPIO建议选用带可配置拉电阻的引脚,提升信号稳定性
三、规范设备树布局
需将两个子设备节点挂载到目标SPI控制器节点下,完整示例如下:
&spi1 { /* 目标SPI控制器节点 */ pinctrl-names = "default"; pinctrl-0 = <&spi1_pins>; status = "okay"; spi-max-frequency = <10000000>; /* 总线最大通信频率 */ bar_a@0 { compatible = "foo,bar-function-a-1.0"; reg = <0>; /* 片选地址,对应CS0 */ id = "primary"; /* 同组设备关联ID */ cs-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>; /* 片选GPIO */ interrupt-parent = <&gpio1>; interrupts = <15 IRQ_TYPE_EDGE_RISING>; /* 中断配置 */ aux-gpios = <&gpio1 12 GPIO_ACTIVE_HIGH>; /* 自定义辅助GPIO */ }; bar_b@1 { compatible = "foo,bar-function-b-1.0"; reg = <1>; /* 片选地址,对应CS1 */ id = "primary"; /* 同一关联ID */ cs-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>; /* 片选GPIO */ interrupt-parent = <&gpio1>; interrupts = <15 IRQ_TYPE_EDGE_RISING>; /* 共享中断线 */ }; };
- 关键属性说明:
compatible:驱动匹配的唯一标识,需与驱动中spi_driver的of_match_table完全对应id:用于标记同组设备,驱动通过此属性关联两个spi_device实例reg:SPI片选地址,同一控制器下必须唯一cs-gpios:指定片选GPIO,若控制器自带硬件CS可省略
四、内核API规范用法
1. 核心结构体定义
定义设备组管理结构体,用于收集同组的两个SPI设备及共享资源:
#include <linux/spi/spi.h> #include <linux/gpio/consumer.h> #include <linux/interrupt.h> #include <linux/list.h> /* 设备组管理结构体 */ struct bar_device_group { struct list_head list; char id[32]; /* 同组设备ID */ struct spi_device *dev_a; /* 功能A的SPI设备 */ struct spi_device *dev_b; /* 功能B的SPI设备 */ struct gpio_desc *aux_gpio; /* 辅助GPIO */ int irq; /* 中断号 */ struct mutex lock; /* 保护设备组资源 */ }; /* 全局设备组链表 */ static LIST_HEAD(bar_groups); static DEFINE_MUTEX(group_lock);
2. SPI驱动匹配与Probe函数
/* 设备树匹配表 */ static const struct of_device_id bar_spi_of_match[] = { { .compatible = "foo,bar-function-a-1.0" }, { .compatible = "foo,bar-function-b-1.0" }, { /* 哨兵节点 */ } }; MODULE_DEVICE_TABLE(of, bar_spi_of_match); /* 检查同组设备是否全部就绪 */ static bool bar_group_ready(struct bar_device_group *group) { return (group->dev_a && group->dev_b); } static int bar_spi_probe(struct spi_device *spi) { struct bar_device_group *group = NULL; const char *id; int ret; /* 读取设备树的id属性 */ ret = of_property_read_string(spi->dev.of_node, "id", &id); if (ret) { dev_err(&spi->dev, "Failed to read id property\n"); return ret; } mutex_lock(&group_lock); /* 查找或创建对应id的设备组 */ list_for_each_entry(group, &bar_groups, list) { if (!strcmp(group->id, id)) { break; } } if (!group) { group = devm_kzalloc(&spi->dev, sizeof(*group), GFP_KERNEL); if (!group) { mutex_unlock(&group_lock); return -ENOMEM; } strscpy(group->id, id, sizeof(group->id)); mutex_init(&group->lock); INIT_LIST_HEAD(&group->list); list_add(&group->list, &bar_groups); } mutex_unlock(&group_lock); /* 根据compatible字符串分配设备指针 */ if (!strcmp(spi->modalias, "foo,bar-function-a-1.0")) { group->dev_a = spi; } else if (!strcmp(spi->modalias, "foo,bar-function-b-1.0")) { group->dev_b = spi; } mutex_lock(&group->lock); /* 同组设备全部就绪后完成初始化 */ if (bar_group_ready(group)) { /* 获取辅助GPIO */ group->aux_gpio = devm_gpiod_get(&group->dev_a->dev, "aux", GPIOD_OUT_HIGH); if (IS_ERR(group->aux_gpio)) { ret = PTR_ERR(group->aux_gpio); dev_err(&group->dev_a->dev, "Failed to get aux GPIO\n"); goto err_unlock; } /* 申请中断 */ group->irq = gpiod_to_irq(group->aux_gpio); ret = devm_request_irq(&group->dev_a->dev, group->irq, bar_irq_handler, IRQF_TRIGGER_RISING, "bar_irq", group); if (ret) { dev_err(&group->dev_a->dev, "Failed to request IRQ\n"); goto err_unlock; } /* 配置SPI通信参数 */ spi->mode = SPI_MODE_0; spi->bits_per_word = 8; ret = spi_setup(spi); if (ret) { dev_err(&spi->dev, "Failed to setup SPI\n"); goto err_unlock; } /* 注册字符设备(示例) */ ret = bar_register_chrdev(group); if (ret) { dev_err(&spi->dev, "Failed to register chrdev\n"); goto err_unlock; } } mutex_unlock(&group->lock); return 0; err_unlock: mutex_unlock(&group->lock); return ret; } static void bar_spi_remove(struct spi_device *spi) { struct bar_device_group *group = NULL; const char *id; of_property_read_string(spi->dev.of_node, "id", &id); mutex_lock(&group_lock); list_for_each_entry(group, &bar_groups, list) { if (!strcmp(group->id, id)) { if (spi == group->dev_a) group->dev_a = NULL; else if (spi == group->dev_b) group->dev_b = NULL; /* 同组设备全部移除后清理资源 */ if (!group->dev_a && !group->dev_b) { list_del(&group->list); devm_kfree(&spi->dev, group); } break; } } mutex_unlock(&group_lock); } /* SPI驱动结构体 */ static struct spi_driver bar_spi_driver = { .driver = { .name = "bar-spi-driver", .of_match_table = bar_spi_of_match, }, .probe = bar_spi_probe, .remove = bar_spi_remove, }; module_spi_driver(bar_spi_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Dual SPI sub-device driver for custom bar device"); MODULE_AUTHOR("Custom Driver Developer");
3. 关键API说明
spi_driver:注册SPI子设备驱动,通过of_match_table匹配设备树节点devm_kzalloc:使用设备管理内存分配,自动在设备移除时释放spi_setup:配置SPI设备的通信参数(模式、位宽、频率等)devm_request_irq:申请中断,自动在设备移除时释放gpiod_get:从设备树获取GPIO资源,符合Linux GPIO子系统规范
内容的提问来源于stack exchange,提问作者apriori
相关产品推荐
相关产品推荐

