You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 23:05:05