Day04_总线驱动

1.Platform虚拟总线驱动

核心价值

作用 实现机制 优势
统一管理片上资源 虚拟总线抽象,无需物理连接 简化SoC设备管理
设备与驱动解耦 platform_device描述资源,platform_driver实现逻辑 提高代码复用性和可移植性
自动匹配 总线通过名称或ID表匹配设备与驱动 减少手动绑定代码
安全资源访问 内核统一分配资源(内存、中断) 避免硬件冲突

扩展思考:在分布式系统(如OpenHarmony)中,虚拟总线概念进一步演化为"软总线",实现跨设备的服务发现与通信

相关结构体

struct platform_driver{} -结构体

cpp 复制代码
#include <linux/platform_device.h>

struct platform_driver {
    int (*probe)(struct platform_device *);
    int (*remove)(struct platform_device *);
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;
    const struct platform_device_id *id_table;
    bool prevent_deferred_probe;
};

platform_driver是 Linux 内核中用于管理片上资源(如 SoC 外设控制器)的核心结构体,属于 Platform 总线驱动模型的一部分。Platform 总线是一种虚拟总线,用于连接 CPU 直接寻址的设备(如 GPIO、定时器等),实现驱动与设备的分离与动态匹配

关键成员功能说明:
成员 功能说明 是否必需
probe 驱动与设备匹配成功后调用,负责初始化硬件、注册字符设备/中断/DMA 等资源。 必需
remove 驱动卸载或设备移除时调用,用于释放资源(如内存、中断)。 必需
driver 内嵌的 device_driver 结构体,包含驱动名称(.name)、模块所有者(.owner)等基础信息。 必需
id_table 支持多设备匹配的标识表,用于非设备树场景。当设备名与表中某项匹配时,触发 probe 可选
of_match_table 设备树匹配表(位于 driver.of_match_table),通过设备节点的 compatible 属性匹配驱动(如 { .compatible = "xxx-gpio" })。 设备树场景必需
shutdown/suspend/resume 设备关机、休眠和唤醒的回调函数,用于电源管理。 可选

注:

  • driver.owner 通常设为 THIS_MODULE,但现代内核(5.x+)已自动填充,无需显式赋值。
  • driver.name 是传统匹配方式的关键,需与 platform_device.name 一致。
platform_driver 的注册与注销

驱动需通过以下接口注册到 Platform 总线:

cpp 复制代码
// 注册驱动
int platform_driver_register(struct platform_driver *drv);

// 注销驱动
void platform_driver_unregister(struct platform_driver *drv);
典型驱动框架示例:
cpp 复制代码
#include <linux/platform_device.h>
#include <linux/module.h>

static int my_probe(struct platform_device *pdev) {
    printk("Device matched! Resource: MEM 0x%x, IRQ %d\n",
           pdev->resource[0].start, pdev->resource[1].start);
    // 初始化硬件、注册字符设备等
    return 0;
}

static int my_remove(struct platform_device *pdev) {
    // 释放资源
    return 0;
}

static const struct of_device_id my_of_match[] = {
    { .compatible = "my-device" },  // 设备树匹配标识
    { }
};
MODULE_DEVICE_TABLE(of, my_of_match);

static struct platform_driver my_driver = {
    .driver = {
        .name = "my_driver",
        .of_match_table = my_of_match,
    },
    .probe = my_probe,
    .remove = my_remove,
};

module_platform_driver(my_driver);  // 自动生成 init/exit 函数
MODULE_LICENSE("GPL");
设备匹配机制

Platform 总线通过以下方式匹配设备与驱动:

  1. 设备树(DT)优先 设备节点的 compatible 属性与 driver.of_match_table 匹配(如 "my-device")。
  2. 传统名称匹配 若未使用设备树,则比较 platform_device.namedriver.name(如 "my_driver")。
  3. ID 表扩展匹配 通过 id_table 支持多设备名匹配(如 {"deviceA"}, {"deviceB"})。
资源获取与操作
  • 硬件资源访问 : 在 probe 函数中,通过 platform_device 获取内存、中断等资源:
cpp 复制代码
struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
int irq = platform_get_irq(pdev, 0);
  • 设备树参数解析: 使用of_property_read_u32() 等接口读取设备树中的自定义属性(如寄存器地址)。
应用场景与优势
  1. 统一管理片上外设 用于 GPIO、ADC、PWM 等非标准总线设备,避免为每个外设实现独立总线驱动。
  2. 驱动与硬件解耦 设备资源(地址、中断)通过设备树或 platform_device 描述,驱动无需硬编码,提升可移植性。
  3. 支持热插拔与电源管理 通过 suspend/resume 实现低功耗模式,shutdown 确保安全断电。

总结

platform_driver 是 Linux 驱动开发的核心抽象层,通过分离设备描述与驱动逻辑,显著提升代码复用性和可维护性。开发者需重点关注:

  1. 匹配机制:设备树兼容性 > 名称匹配 > ID 表。
  2. 生命周期管理 :资源初始化在 probe 中完成,清理在 remove 中实现。
  3. 资源获取 :利用 platform_get_resource() 等接口安全访问硬件资源。

结构体关系

cpp 复制代码
struct platform_driver{}  //device的platform包装
		struct platform_device_id{}  //非设备树设备,id信息匹配结构体
		struct device_drive{}  //设备驱动核心结构体
				struct of_device_id{}  //设备树设备,compatible匹配结构体
				struct bus_type{}  //设备驱动归属总线类型结构体


struct platform_device{}  //device_drive的platform包装
		struct device{}  //设备核心结构体
				struct device_node{}  //设备树节点信息结构体
				struct device_type{}  //...
				struct bus_type{}
				struct device_drive{}
						struct of_device_id{}
						struct bus_type{}

在 Linux 设备驱动模型中,这些结构体共同构成了设备管理的核心框架,其关系可概括为 "总线-设备-驱动"三层模型,同时融合了设备树(Device Tree)的支持。以下是各结构体的作用及相互关系分析:

一、核心设备模型结构体

  1. struct device
    • 作用:表示系统中任何硬件或虚拟设备的通用抽象,是设备模型的基类。
    • 关键字段
      • parent:指向父设备(如总线控制器)。
      • bus:设备所属的总线类型(如 platform_bus_type)。
      • driver:绑定到该设备的驱动(device_driver)。
      • of_node:关联的设备树节点(device_node)。
    • 关系
      • platform_devicepci_device 等继承(通过内嵌 struct device 成员)。
  2. struct device_driver
    • 作用:描述设备驱动的通用行为,与总线无关。
    • 关键字段
      • name:驱动名称(用于匹配设备)。
      • bus:所属总线类型。
      • probe/remove:设备初始化/卸载的回调函数。
      • of_match_table:设备树匹配表(of_device_id 数组)。
    • 关系
      • platform_driver 继承(通过内嵌 struct device_driver driver 成员)。
  3. struct bus_type
    • 作用 :定义总线类型(如 platform_bus_type),管理设备与驱动的匹配规则。
    • 关键字段
      • name:总线名称(如 "platform")。
      • match():设备与驱动的匹配函数(如按名称或设备树匹配)。
      • dev_attrs/drv_attrs:总线上设备/驱动的默认属性。
    • 关系
      • 平台总线 platform_bus_type 是其实例,用于管理 platform_deviceplatform_driver

二、平台设备相关结构体

  1. struct platform_device
    • 作用 :描述 SoC 集成的静态设备(如 GPIO 控制器),继承自 device
    • 关键字段
      • name:设备名称(与驱动匹配)。
      • resource:设备资源数组(内存地址、IRQ 等)。
      • dev.of_node:关联的设备树节点。
    • 资源获取
      • 驱动通过 platform_get_resource() 获取资源。
  2. struct platform_driver
    • 作用 :管理 platform_device 的驱动,继承自 device_driver
    • 关键字段
      • probe()/remove():设备初始化和卸载函数。
      • driver.name:驱动名称(与设备名称匹配)。
      • id_table:支持设备的 ID 表(platform_device_id 数组)。
    • 匹配流程
      • 通过总线 match() 函数比较设备名称或设备树属性。
  3. struct platform_device_id
    • 作用:定义驱动支持的设备 ID 表,用于非设备树匹配。
    • 示例
cpp 复制代码
static const struct platform_device_id my_id_table[] = {
    { "led-ip", 0 },  // 匹配名为 "led-ip" 的设备
    { }
};

三、设备树(Device Tree)相关结构体

  1. struct device_node
    • 作用:设备树在内核中的表示,描述设备的硬件属性(地址、中断等)。
    • 关键字段
      • name:节点名称。
      • compatible:驱动匹配的关键属性。
    • 关系
      • device 通过 dev.of_node 关联。
  2. struct of_device_id
    • 作用 :驱动定义的设备树匹配表,通过 compatible 属性匹配设备节点。
    • 示例
cpp 复制代码
static const struct of_device_id my_driver_match[] = {
    { .compatible = "vendor,my-device" },  // 匹配设备树中的 compatible 属性
    { }
};
  • 使用场景
    • platform_driver.driver.of_match_table 中注册。

四、其他辅助结构体

  1. struct device_type
    • 作用 :定义设备的类型(如 "uart""i2c"),用于分类管理设备。
    • 关系
      • device 引用(dev.type),影响设备在 /sys 中的属性。
结构体关系总结
结构体 依赖关系 核心功能
bus_type 管理 devicedevice_driver 定义总线匹配规则(如 platform_bus_type
device platform_device 继承 设备的通用抽象(父设备、资源等)
device_driver platform_driver 继承 驱动的通用行为(proberemove
platform_device 内嵌 device;关联 device_node SoC 设备的资源描述(内存、IRQ)
platform_driver 内嵌 device_driver;包含 of_device_idplatform_device_id 平台设备的驱动逻辑
of_device_id platform_driver.driver.of_match_table 引用 设备树驱动的匹配表
platform_device_id platform_driver.id_table 引用 非设备树驱动的匹配表
device_node device.of_node 引用 设备树节点的内核表示
关键交互流程
  1. 设备注册
    • platform_device 通过 platform_device_register() 注册到平台总线。
    • 设备树节点在启动时自动转换为 platform_device(通过 of_platform_populate())。
  2. 驱动注册
    • platform_driver 通过 platform_driver_register() 注册,其内嵌的 device_driver 关联到 platform_bus_type
    • 总线调用 match() 比较设备与驱动的名称或设备树 compatible 属性。
  3. 驱动绑定
    • 匹配成功后,总线调用驱动的 probe() 函数初始化设备(如获取资源、注册字符设备)。
    • 资源通过 platform_get_resource()platform_device.resource 中提取。
总结:Linux设备模型的核心设计思想

这些结构体通过分层抽象实现硬件与驱动的解耦:

  1. 统一接口 devicedevice_driver 作为基类,为不同总线(如 PCI、Platform)提供一致的操作接口。
  2. 动态匹配 总线(bus_type)通过 match() 实现设备与驱动的运行时绑定,支持设备树动态配置。
  3. 资源标准化 platform_device 集中管理硬件资源(内存、IRQ),驱动通过标准 API(如 platform_get_resource())访问。
  4. 设备树整合 device_nodeof_device_id 将硬件描述从驱动代码剥离,提升跨平台兼容性。

下图展示了主要结构体的关系:

cpp 复制代码
graph TD
    bus_type[bus_type] -->|管理| device(device)
    bus_type -->|管理| device_driver(device_driver)
    device_driver -->|内嵌于| platform_driver
    device -->|内嵌于| platform_device
    platform_device -->|关联| device_node
    platform_driver -->|包含| of_device_id
    platform_driver -->|包含| platform_device_id
    device_node -->|描述硬件| of_device_id

1.of_match_table的赋值规范

在 Linux 内核的platform_driver驱动框架中,of_device_id匹配表(即bus_devID[])的尾部需要一个空结构体{},这是内核设计中的强制性规范,主要作用是为匹配表的遍历提供安全终止标志。以下是具体原因和设计逻辑:

1. 空结构体的核心作用:标记数组结束
  • 终止遍历 :Linux 内核在遍历 of_device_id 数组时,通过检查每个元素的 .compatible 字段是否为 NULL 来判断是否到达数组末尾。空结构体 {} 会被初始化为全零(包括 .compatible = NULL),因此内核遇到它时会停止遍历,避免越界访问。
  • 防内存越界:若未添加空结构体,内核可能继续读取数组外的内存,导致未定义行为(如崩溃或数据错误)。
2. 内核匹配流程依赖此设计

platform 总线匹配设备与驱动的过程中,of_match_table 的匹配逻辑如下:

cpp 复制代码
// 简化版内核匹配逻辑(drivers/base/platform.c)
static int platform_match(struct device *dev, struct device_driver *drv) {
    // ...
    const struct of_device_id *matches = drv->of_match_table;
    for (; matches->compatible[0]; matches++) { // 遍历直到遇到 NULL
        if (of_driver_match_device(dev, drv))
            return 1;
    }
    return 0;
}
  • 循环终止条件matches->compatible[0]'\\0'(即 NULL)时退出循环。
  • 空结构体的必要性 :若缺少 {},循环无法终止,导致内核崩溃。
3. 实际开发中的注意事项
  1. 语法规范

    空结构体必须作为数组的最后一个元素,例如:

cpp 复制代码
c

static const struct of_device_id bus_devID[] = {
    { .compatible = "mytest" }, // 有效匹配项
    { }                          // 必须的终止标记
};

❌ 错误示例:若省略{},部分内核版本会编译报错,或运行时触发警告。

  1. 设备树兼容性匹配
    • 设备树节点的 compatible 属性(如 "mytest")需与驱动中的定义完全一致(包括大小写)。
    • 多设备支持时,可扩展数组:
cpp 复制代码
{ .compatible = "vendor,device-a" },
{ .compatible = "vendor,device-b" },
{ } // 仍需要终止标记
  1. 模块化支持

    通过 MODULE_DEVICE_TABLE(of, bus_devID) 导出匹配表,使驱动可动态加载时被内核识别

相关函数

2.module_platform_driver()-一键注册宏

cpp 复制代码
#include <linux/platform_device.h>

#define module_platform_driver(__platform_driver) \
    module_driver(__platform_driver, platform_driver_register, \
            platform_driver_unregister)
            
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
    return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
    __unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
宏展开过程
cpp 复制代码
static int __init my_driver_init(void) {
    return platform_driver_register(&my_driver); // 注册驱动
}
module_init(my_driver_init);

static void __exit my_driver_exit(void) {
    platform_driver_unregister(&my_driver);    // 注销驱动
}
module_exit(my_driver_exit);
  • ## 运算符 :连接符号(如 my_driver + _initmy_driver_init)。
  • __VA_ARGS__:支持可变参数传递(通常为空)
底层注册逻辑

platform_driver_register() 将驱动绑定到平台总线(platform_bus_type),触发设备匹配流程:

  • 匹配成功时调用驱动的 probe() 函数;
  • 卸载时调用 remove() 函数
典型驱动代码结构
cpp 复制代码
#include <linux/platform_device.h>

static int my_probe(struct platform_device *pdev) {
    /* 初始化硬件(如配置寄存器、申请中断) */
    return 0;
}

static int my_remove(struct platform_device *pdev) {
    /* 释放资源(如注销设备、释放内存) */
    return 0;
}

static const struct of_device_id my_of_match[] = {
    { .compatible = "vendor,my-device" }, // 设备树匹配标识
    {}
};

static struct platform_driver my_driver = {
    .probe = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "my_device",          // 驱动名称(传统匹配)
        .of_match_table = my_of_match, // 设备树匹配表
    },
};

module_platform_driver(my_driver); // 一行代码完成注册/注销
工作流程
cpp 复制代码
graph TD
    A[insmod driver.ko] --> B[自动调用 my_driver_init]
    B --> C[注册驱动到平台总线]
    C --> D{设备与驱动匹配}
    D -->|成功| E[执行 my_probe 初始化设备]
    F[rmmod driver.ko] --> G[调用 my_driver_exit]
    G --> H[注销驱动并执行 my_remove]

3.platform_driver_register()

cpp 复制代码
#include <linux/platform_device.h>

int platform_driver_register(struct platform_driver *drv);

platform_driver_register() 是 Linux 内核中用于注册平台驱动(platform driver)的核心函数,属于 platform 总线机制的关键部分。该机制通过虚拟总线实现驱动与设备的分离,提升代码可移植性和可维护性。

功能:

platform_driver 结构体注册到内核的 platform 虚拟总线,触发驱动与设备的匹配流程。匹配成功后,调用驱动的 probe() 函数初始化设备

  • 参数drv 指向自定义的 platform_driver 结构体。
  • 返回值 :成功返回 0,失败返回负数错误码
platform_driver 结构体解析

platform_driver 是驱动注册的核心数据结构,需开发者自定义并初始化其成员:

cpp 复制代码
struct platform_driver {
    int (*probe)(struct platform_device *);     // 设备匹配成功后调用的初始化函数
    int (*remove)(struct platform_device *);    // 设备移除或驱动卸载时的清理函数
    void (*shutdown)(struct platform_device *); // 设备断电时调用
    int (*suspend)(struct platform_device *, pm_message_t); // 设备休眠时调用
    int (*resume)(struct platform_device *);     // 设备唤醒时调用
    struct device_driver driver;                // 内嵌的标准驱动结构
    const struct platform_device_id *id_table;  // 支持多设备的ID匹配表
};
关键成员说明:
  1. proberemove
    • probe:负责设备初始化(如申请资源、注册字符设备等),是驱动的核心逻辑入口。
    • remove:释放资源(如注销设备、释放内存等),与 probe 逻辑对应。
  2. struct device_driver driver
    • name:驱动名称,用于与传统设备(非设备树)匹配。
    • of_match_table :设备树匹配表(类型为 of_device_id),通过 compatible 属性匹配设备树节点。 示例:
cpp 复制代码
static const struct of_device_id my_driver_match[] = {
    { .compatible = "vendor,my-device" }, // 设备树节点的 compatible 值
    {}
};
  1. id_table

    支持驱动匹配多个设备名称(如 "device_v1", "device_v2"),用于非设备树场景

函数内部机制
  1. 注册流程

    • 设置 driver.bus = &platform_bus_type,绑定到平台总线。
    • probe/remove 等函数指针挂载到总线的默认实现(如 platform_drv_probe)。
    • 调用 driver_register() 将驱动加入内核总线系统。
  2. 匹配机制

    当驱动与设备注册后,总线通过 platform_match() 函数进行匹配,优先级如下:

    • 设备树 :检查设备树节点的 compatible 是否与 of_match_table 匹配。
    • ID 表 :检查设备名是否在 id_table 中。
    • 名称匹配 :比较设备与驱动的 name 字段是否一致。 ✅ 匹配成功后,调用驱动的 probe() 函数。
使用示例
1. 驱动代码框架
cpp 复制代码
#include <linux/platform_device.h>

static int my_probe(struct platform_device *pdev) {
    // 1. 获取设备资源(内存、中断等)
    // 2. 初始化硬件(如配置GPIO、寄存器)
    // 3. 注册字符设备/网络设备等
    return 0;
}

static int my_remove(struct platform_device *pdev) {
    // 释放资源,注销设备
    return 0;
}

static const struct of_device_id my_of_match[] = {
    { .compatible = "vendor,my-device" },
    {}
};

static struct platform_driver my_driver = {
    .driver = {
        .name = "my_driver",
        .of_match_table = my_of_match,
    },
    .probe = my_probe,
    .remove = my_remove,
};

module_platform_driver(my_driver); // 简化注册/注销
2. 设备树节点示例
cpp 复制代码
my_device {
    compatible = "vendor,my-device";
    reg = <0x10000000 0x1000>; // 内存资源
    interrupts = <0 45 IRQ_TYPE_LEVEL_HIGH>; // 中断资源
};

4.platform_driver_unregister()

cpp 复制代码
#include <linux/platform_device.h>

void platform_driver_unregister(struct platform_driver *drv);

platform_driver_unregister 是 Linux 内核中用于注销平台设备驱动的核心函数,其功能是安全移除已注册的驱动并释放资源。

功能概述

  1. 核心作用。调用后,驱动不再响应匹配的设备请求,并触发资源清理。

    用于从平台总线(platform_bus_type)中注销指定的 platform_driver 驱动结构体

  2. 调用时机

    通常在驱动模块卸载函数(module_exit)中调用,与 platform_driver_register 配对使用

IIC总线驱动

相关结构体

1.struct i2c_driver{}-结构体

cpp 复制代码
#include <linux/i2c.h>

struct i2c_driver {
    int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);    // 当IIC总线驱动的驱动信息和设备信息匹配成功后,需要执行的函数
    int (*remove)(struct i2c_client *client);    // 当外接的使用IIC总线驱动的设备移出当前环境后,需要执行的函数
    struct device_driver driver;                 // 保存的是当前使用IIC总线驱动的驱动信息结构体  
    const struct i2c_device_id *id_table;        // 用于IIC总线驱动信息和设备信息匹配的结构体(通过IIC总线驱动的match函数进行匹配)
};

关键成员详解

以下是 i2c_driver 的核心成员及其作用:

成员 类型/说明 是否必需
probe int (*)(struct i2c_client *, const struct i2c_device_id *) ✅ 是
设备匹配成功后调用,负责初始化硬件、注册字符设备等。
remove int (*)(struct i2c_client *) ✅ 是
驱动卸载或设备移除时调用,释放资源(内存、中断等)。
driver struct device_driver ✅ 是
标准驱动模型结构体,需初始化 .name(驱动名称)和 .of_match_table(设备树匹配表)。
id_table const struct i2c_device_id * ✅ 是
支持的设备 ID 表,用于非设备树场景的匹配(如 {"at24c08", 0})。
shutdown void (*)(struct i2c_client *) ❌ 可选
设备断电时调用(如系统关机)。
suspend/resume 电源管理函数(休眠/唤醒)。 ❌ 可选
detect int (*)(struct i2c_client *, struct i2c_board_info *) ❌ 可选
自动检测设备时调用(较少使用)。
  • proberemove 是驱动必须实现的函数。
  • id_tabledriver.of_match_table 至少需初始化一个(设备树优先)

两种驱动模式对比

Linux I2C 驱动有两种编写模式,需避免混用成员初始化:

模式 特点 初始化成员
Legacy Model 需手动创建 i2c_client,硬编码设备地址(如 i2c_new_device())。 attach_adapterdetach_client
Standard Model 由内核自动创建 i2c_client,通过设备树或 id_table 匹配设备。 proberemoveid_table

注意:若同时初始化两种模式的成员(如 attach_adapter 和 probe),内核会报错

使用示例

驱动定义与注册

cpp 复制代码
#include <linux/i2c.h>

static int my_probe(struct i2c_client *client, const struct i2c_device_id *id) {
    // 初始化设备:申请资源、注册字符设备等
    return 0;
}

static int my_remove(struct i2c_client *client) {
    // 释放资源
    return 0;
}

static const struct i2c_device_id my_id_table[] = {
    { "at24c08", 0 },  // 设备名与地址
    {}
};

static const struct of_device_id my_of_match[] = {
    { .compatible = "vendor,eeprom" },  // 设备树匹配标识
    {}
};

struct i2c_driver my_driver = {
    .driver = {
        .name = "my_eeprom",
        .of_match_table = my_of_match,
    },
    .probe = my_probe,
    .remove = my_remove,
    .id_table = my_id_table,
};

module_i2c_driver(my_driver);  // 注册驱动

数据传输

在 probe中可通过 i2c_transfer读写设备:

cpp 复制代码
static int read_data(struct i2c_client *client, u8 reg, u8 *buf, int len) {
    struct i2c_msg msg[2] = {
        { // 写寄存器地址
            .addr = client->addr,
            .flags = 0,
            .len = 1,
            .buf = &reg,
        },
        { // 读数据
            .addr = client->addr,
            .flags = I2C_M_RD,
            .len = len,
            .buf = buf,
        }
    };
    return i2c_transfer(client->adapter, msg, 2);
}

在 Linux I2C 架构中的位置

i2c_driver 属于 设备驱动层,与以下组件交互:

  • i2c_client:表示物理 I2C 设备(地址、名称等)。
  • i2c_adapter :描述 I2C 控制器(适配器),提供 master_xfer() 硬件操作函数。
  • I2C 核心层 :通过 i2c_add_driver() 注册驱动,触发总线匹配(调用 probe

注意事项

  1. 匹配优先级 :设备树 > id_table > 驱动名称。
  2. 资源释放remove 必须逆向回滚 probe 中的操作。
  3. 并发处理 :I2C 消息(i2c_msg)需正确处理多设备并发访问

2.struct i2c_client{}-结构体

cpp 复制代码
#include <linux/i2c.h>

struct i2c_client {
    unsigned short flags;        // 标志位(如地址位数、协议特性)
    unsigned short addr;         // 设备地址(7位或10位)
    char name[I2C_NAME_SIZE];    // 设备名称(用于匹配驱动)
    struct i2c_adapter *adapter; // 所属总线适配器
    struct i2c_driver *driver;   // 绑定的设备驱动
    struct device dev;            // 内嵌的设备模型结构体
    int irq;                      // 设备中断号
    struct list_head detected;    // 已探测设备链表
};

i2c_client 代表一个物理 I²C 设备(如传感器、EEPROM),由内核在设备初始化时动态创建。它作为设备驱动的操作入口,封装了设备的硬件信息(地址、中断等)和所属总线适配器(i2c_adapter

关键成员详解

addr

  • 设备的 I²C 从机地址(7位或10位),主机通过此地址访问设备。
  • 地址组成
    • 读写位 :第7位(0=写,1=读),例如地址 0x50 写操作时为 0xA00x50 << 1 | 0)。
    • 地址类型I2C_M_TEN 标志表示10位地址(默认为7位)。

adapter

  • 指向所属的 i2c_adapter(总线控制器),通过 adapter->algo->master_xfer() 实现底层数据传输。
  • 例如:适配器 i2c-0 控制总线时序,设备通过其收发数据。

name

  • 设备名称(如 "mpu6050"),与 i2c_driverid_table 或设备树 compatible 属性匹配。

irq

  • 设备的中断号,用于触发事件(如数据就绪)。需在驱动中注册中断处理函数。

driver

  • 指向管理该设备的 i2c_driver,在匹配成功后由内核赋值。驱动通过 probe() 初始化设备

在 I²C 子系统中的角色

  1. 设备抽象
    • 将物理设备(如 MPU6050 陀螺仪)抽象为内核对象,提供统一的访问接口。
  2. 驱动匹配
    • i2c_driver 通过名称或设备树匹配,触发 probe() 初始化设备。
  3. 总线管理
    • 通过 adapter 关联总线控制器,确保数据传输由硬件适配器处理

i2c_client 的创建方式

方法 描述 适用场景 示例
设备树(首选) 在设备树节点定义 compatiblereg(地址),内核解析生成 i2c_client 新内核(≥3.0) reg = <0x69>; compatible="invensense,mpu6050"
i2c_board_info 静态定义设备信息,通过 i2c_register_board_info() 注册。 旧内核或无设备树平台 I2C_BOARD_INFO("24c08", 0x50)
i2c_new_device() 动态创建设备,需手动指定地址和适配器。 运行时动态添加设备 i2c_new_device(adapter, &info)

与 i2c_driver 的交互流程

  1. 匹配阶段
    • 内核根据设备树 compatiblei2c_driver->id_table 匹配设备与驱动。
  2. 初始化阶段
    • 匹配成功后调用 i2c_driver->probe(),传入 i2c_client 指针,驱动可初始化硬件(如配置寄存器)。
  3. 数据传输
    • 驱动通过 i2c_transfer()i2c_smbus_* 接口发送数据,最终由 adapter->algo->master_xfer() 实现。
  4. 资源释放
    • 设备移除时调用 i2c_driver->remove() 释放资源(如注销中断)
cpp 复制代码
graph TD
    i2c_client[i2c_client] --> |依附于适配器|i2c_adapter(i2c_adapter)
    i2c_client --> |绑定驱动|i2c_driver(i2c_driver)
    i2c_driver --> |初始化设备|probe(probe)
    i2c_adapter --> |实现传输逻辑|i2c_algorithm(i2c_algorithm)
    i2c_algorithm --> |硬件时序控制|master_xfer(master_xfer)

3.struct i2c_adapter{}-结构体

cpp 复制代码
#include <linux/i2c.h>

struct i2c_adapter {
    struct module *owner;                  // 所属模块(用于引用计数)
    unsigned int class;                    // 允许探测的设备类别
    const struct i2c_algorithm *algo;      // 总线访问算法(关键操作函数集)
    void *algo_data;                       // 算法私有数据
    struct rt_mutex bus_lock;              // 总线访问互斥锁(防止并发冲突)
    int timeout;                           // 传输超时时间(单位:jiffies)
    int retries;                           // 传输失败重试次数
    struct device dev;                     // 关联的底层设备结构体
    int nr;                                // 适配器编号(如 i2c-0 中的 0)
    char name[48];                         // 适配器名称(如 "i2c-imx")
    struct list_head userspace_clients;    // 用户空间创建的客户端链表
    const struct i2c_adapter_quirks *quirks; // 适配器特殊行为/限制描述
    struct irq_domain *host_notify_domain; // 主机通知中断域(I2C v3.0 特性)
    struct regulator *bus_regulator;       // 总线电源调节器(可选)
};

4.struct i2c_msg{}-结构体

cpp 复制代码
struct i2c_msg {
	__u16 addr;	/* slave address			*/
	__u16 flags;  //读写标志位
	__u16 len;		/* msg length				*/
	__u8 *buf;		/* pointer to msg data			*/
};

#define I2C_M_RD		0x0001	/* read data, from slave to master */
					/* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN		0x0010	/* this is a ten bit chip address */
#define I2C_M_DMA_SAFE		0x0200	/* the buffer of this message is DMA safe */
					/* makes only sense in kernelspace */
					/* userspace buffers are copied anyway */
#define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
#define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_NOSTART */
#define I2C_M_STOP		0x8000	/* if I2C_FUNC_PROTOCOL_MANGLING */

结构体定义与作用

核心定位

  • 物理适配器抽象 :每个 i2c_adapter 对应一个硬件 I2C 控制器(如 SoC 中的 I2C0、I2C1),控制总线的信号时序(如 START、STOP、ACK)。
  • 通信桥梁 :为挂载在该总线上的 I2C 设备(i2c_client)提供数据传输通道。

在 I2C 架构中的位置

  • 总线驱动层:属于 I2C 子系统三层架构中的第二层(适配器层),向上对接 I2C 核心层,向下控制硬件控制器

关键成员详解

类型/说明 成员 作用
const struct i2c_algorithm * algo 核心通信方法 :指向 i2c_algorithm,提供底层传输函数(如 master_xfer)。
struct device dev 设备模型接口:关联的物理设备信息(如设备树节点、父设备)。
char[48] name 适配器名称:标识总线(如 "i2c-0"),用于调试和日志。
int nr 总线编号:动态或静态分配的适配器 ID(如 0、1)。
int timeout 传输超时 :以 jiffies 为单位的等待时间。
int retries 重试次数:传输失败后的重试机制。
struct rt_mutex bus_lock 总线锁:保护多设备并发访问的互斥锁。
struct i2c_bus_recovery_info * bus_recovery_info 总线恢复机制:处理总线锁死(如 SCL/SDA 线拉低)的硬件操作函数。

说明

  • algo 是核心成员 :通过 master_xfer 实现实际数据传输(如 i2c_transfer 最终调用此函数)。
  • bus_recovery_info 为可选:部分厂商(如 NXP、ST)实现,瑞芯微可能未支持。
i2c_algorithm 的关系

i2c_adapter 依赖 i2c_algorithm 实现具体通信逻辑,二者关系如下:

  1. 绑定方式
cpp 复制代码
struct i2c_adapter my_adapter = {
    .algo = &my_algorithm,  // 指向自定义算法
};
  1. 算法实现i2c_algorithm 必须实现以下函数:
    • master_xfer:执行 I2C 消息传输(如读写从设备寄存器)。
    • functionality:返回适配器支持的功能(如 I2C_FUNC_I2C、I2C_FUNC_SMBUS_EMUL)。
在 Linux I2C 子系统中的角色
组件 关联点
i2c_client 通过 i2c_client->adapter 绑定到适配器,实现设备与总线的关联。
i2c_driver 驱动通过 i2c_add_driver 注册后,由 I2C 核心层匹配适配器上的设备。
I2C 核心层 调用 i2c_add_adapter() 注册适配器,将其加入全局链表。

5.注册与注销方法

注册适配器

cpp 复制代码
// 动态分配总线号
int i2c_add_adapter(struct i2c_adapter *adap);

// 静态指定总线号(如适配器编号需固定)
int i2c_add_numbered_adapter(struct i2c_adapter *adap);

返回值 :成功返回 0,失败返回错误码(如 -ENOMEM

注销适配器

cpp 复制代码
void i2c_del_adapter(struct i2c_adapter *adap);

使用示例(适配器初始化)

cpp 复制代码
#include <linux/i2c.h>

static const struct i2c_algorithm my_algo = {
    .master_xfer = my_xfer_function,   // 实现硬件传输函数
    .functionality = my_func_check,    // 返回支持的功能
};

struct i2c_adapter my_adapter = {
    .owner = THIS_MODULE,
    .name = "my_i2c_adapter",
    .algo = &my_algo,                  // 绑定算法
    .timeout = HZ,                     // 超时时间(1秒)
    .retries = 3,                      // 重试3次
};

static int __init my_adapter_init(void)
{
    return i2c_add_adapter(&my_adapter); // 动态注册
}
module_init(my_adapter_init);

static void __exit my_adapter_exit(void)
{
    i2c_del_adapter(&my_adapter);       // 注销
}
module_exit(my_adapter_exit);

注意事项

  1. 硬件依赖master_xfer 需根据控制器手册实现(如操作寄存器生成 START/STOP 信号)。
  2. 并发控制 : 多设备访问时需通过 bus_lock 避免冲突。
  3. 设备树支持 : 适配器通常在设备树中描述(如 compatible = "fsl,imx6ul-i2c"),驱动需匹配并初始化。

总结

i2c_adapter 是 Linux I2C 总线驱动的核心载体,其核心职责包括:

  1. 抽象硬件控制器:管理物理总线资源(如时钟、中断)。
  2. 绑定通信算法 :通过 i2c_algorithm 实现数据传输。
  3. 连接设备与驱动 :作为枢纽关联 i2c_clienti2c_driver。 开发者需重点关注 algo 的实现与注册流程,并结合设备树完成适配器驱动开发。

相关函数

6.module_i2c_driver()-一键注册宏

cpp 复制代码
#define module_i2c_driver(__i2c_driver) \
    module_driver(__i2c_driver, i2c_add_driver, \
            i2c_del_driver)

#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
    return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
    __unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);

用于iic设备驱动的一键注册

7.i2c_transfer()

cpp 复制代码
#include <linux/i2c.h>

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);

i2c_transfer() 是 Linux 内核 I2C 子系统的核心函数,用于实现 I2C 总线上的数据传输。它支持多消息连续传输,适用于读写寄存器、传感器数据交互等场景。

  • 核心作用

    通过指定的 I2C 适配器(i2c_adapter)执行一组 I2C 消息(i2c_msg),实现主设备与从设备间的同步数据传输

    • 同步执行:函数阻塞直至所有消息传输完成或出错。
    • 多消息支持:支持连续发送、接收或混合操作(如"写-读"序列)。
  • 在 I2C 架构中的位置

    位于 I2C 核心层 ,调用适配器的 master_xfer 方法(如 i2c_imx_xfer),最终由硬件控制器完成物理传输

参数说明

类型 参数 说明
struct i2c_adapter * adap I2C 适配器对象,代表物理总线控制器(如 i2c0)。
struct i2c_msg * msgs 消息结构体数组指针,每个元素描述一个独立传输操作(写/读)。
int num 消息数量(msgs 数组长度)。

i2c_msg结构体关键字段

字段 说明
addr 从设备地址(7位或10位)。
flags 传输标志: • 0:写操作 • I2C_M_RD:读操作。
len 数据长度(字节数)。
buf 数据缓冲区指针(写操作时为发送数据,读操作时为接收数据存储位置)。

返回值

  • 成功 :返回实际传输的消息数量(应与 num 相等)。
  • 失败 :返回负值错误码(如 EIO 表示总线错误,EINVAL 表示无效参数)

IIC在设备树节点解析

cpp 复制代码
// 在stm32mp151.dtsi文件中最初编写了IIC1外设控制器的设备树节点信息
i2c1: i2c@40012000 {                                     
    compatible = "st,stm32mp15-i2c";         // iic1外设控制器节点的compatible值               
    reg = <0x40012000 0x400>;                // 当前iic1外设控制器的寄存器属性:起始地址 可操作的地址大小            
    interrupt-names = "event", "error";      // 当前iic1外设控制器支持的中断模式:外部时间中断触发iic外设控制器、iic外设控制器使用错误            
    interrupts-extended = <&exti 21 IRQ_TYPE_LEVEL_HIGH>,    // iic1支持的外部中断的中断信息
                  <&intc GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&rcc I2C1_K>;         // iic1外设控制器时钟属性                     
    resets = <&rcc I2C1_R>;         // iic1外设控制器复位属性                     
    #address-cells = <1>;           // 当前iic1节点的子节点用于描述地址信息需要1个32位数                     
    #size-cells = <0>;              // 当前iic1节点的子节点用于描述地址大小需要0个32位数                     
    dmas = <&dmamux1 33 0x400 0x80000001>,   // iic1外设控制器支持的DMA属性            
           <&dmamux1 34 0x400 0x80000001>;               
    dma-names = "rx", "tx";                  // iic1外设控制器支持的DMA传输模式:接收 发送            
    power-domains = <&pd_core>;                          
    st,syscfg-fmp = <&syscfg 0x4 0x1>;                   
    wakeup-source;                                       
    i2c-analog-filter;                                   
    status = "disabled";                     // iic1外设控制器当前的状态属性:未使能状态            
};                                                       


在stm32mp15xx-fsmp1x.dtsi文件中引用并修改了iic5外设控制器节点信息
&i2c5 {                                           
    pinctrl-names = "default", "sleep";      // iic5外设控制器两种引脚工作模式:默认工作模式(正常使用模式)、低功耗模式     
    pinctrl-0 = <&i2c5_pins_a>;              // iic5外设控制器在正常使用模式下使用的引脚复用组信息(a组)     
    pinctrl-1 = <&i2c5_sleep_pins_a>;        // iic5外设控制器在低功耗模式下使用的引脚复用组信息(a组)     
    i2c-scl-rising-time-ns = <100>;          // iic5外设控制器支持的时钟线上升沿时间:100ns     
    i2c-scl-falling-time-ns = <7>;           // iic5外设控制器支持的时钟线下降沿时间:7ns     
    clock-frequency = <100000>;              // iic5外设控制器支持的时钟频率:100000Hz     
    /* spare dmas for other usage */              
    /delete-property/dmas;                   // 删除iic5支持的DMA属性     
    /delete-property/dma-names;              // 删除iic5支持的DMA属性模式     
    status = "okay";                         // 修改后的iic5外设控制器状态属性:处于使能状态     
                                                  
    hdmi-transmitter@39 {                    // 使用iic5外设控制器的实际外设节点信息     
        compatible = "sil,sii9022";          // hdmi-transmitter外设节点的compatible值     
        reg = <0x39>;                        // 描述hdmi-transmitter外设的地址信息(7位从机地址)     
        iovcc-supply = <&v3v3_hdmi>;              
        cvcc12-supply = <&v1v2_hdmi>;             
        reset-gpios = <&gpioa 13 GPIO_ACTIVE_LOW>;
        interrupts = <14 IRQ_TYPE_EDGE_FALLING>;  
        interrupt-parent = <&gpioa>;              
        #sound-dai-cells = <0>;                   
        status = "okay";                      // hdmi-transmitter外设的状态属性:处于使能状态    

在stm32mp15-pinctrl.dtsi文件中描述引脚复用组信息
// iic5正常工作模式下使用的引脚复用a组
i2c5_pins_a: i2c5-0 {                                           
    pins {                                                      
        pinmux = <STM32_PINMUX('A', 11, AF4)>, /* I2C5_SCL */   // 将PA11引脚复用为I2C5_SCL的功能
             <STM32_PINMUX('A', 12, AF4)>; /* I2C5_SDA */       // 将PA12引脚复用为I2C5_SDA的功能
        bias-disable;                                           
        drive-open-drain;                                       
        slew-rate = <0>;                                        
    };                                                          
};                                                              

// iic5低功耗模式下使用的引脚复用a组                                                                                                                                 
i2c5_sleep_pins_a: i2c5-sleep-0 {                               
    pins {                                                      
        pinmux = <STM32_PINMUX('A', 11, ANALOG)>, /* I2C5_SCL */
             <STM32_PINMUX('A', 12, ANALOG)>; /* I2C5_SDA */    


对于FSMP1A开发板套件而言,我们需要引用并修改IIC1外设控制器节点,并添加使用IIC1外设控制器的实际外设节点信息
在stm32mp157a-fsmp1a.dts文件中编写以下内容:(用于引用并修改了别的文件中写好的节点信息,需要编写在根节点外)
&i2c1 {                                
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&i2c1_pins_b>;        
    pinctrl-1 = <&i2c1_sleep_pins_b>;  
    i2c-scl-rising-time-ns = <100>;    
    i2c-scl-falling-time-ns = <7>;     
    clock-frequency = <100000>;        
    /* spare dmas for other usage */   
    /delete-property/dmas;             
    /delete-property/dma-names;        
    status = "okay";                   

    mysi7006@40 {                      
        compatible = "hqyj,si7006";    
        reg = <0x40>;                  
        status = "okay";               
    };                                 
};                                     

编写玩设备树文件后,需要在内核源码顶层目录下make dtbs编译设备树文件,将生成的设备树镜像文件拷贝到tftpboot文件夹中

SPI总线驱动

相关结构体

8.struct spi_driver{}-结构体

cpp 复制代码
#include <linux/spi/spi.h>

struct spi_driver {
    const struct spi_device_id *id_table;      // 支持的设备 ID 列表(用于匹配)
    int (*probe)(struct spi_device *spi);     // 设备探测函数(匹配时调用)
    int (*remove)(struct spi_device *spi);     // 设备移除函数(卸载时调用)
    void (*shutdown)(struct spi_device *spi);  // 设备关闭函数(关机时调用)
    struct device_driver driver;               // 内嵌的设备驱动基类
};

spi_driver 是 Linux 内核中描述 SPI 设备驱动的核心结构体,用于管理挂载在 SPI 总线上的从设备(如传感器、Flash 存储器等)。其作用类似于 I²C 子系统中的 i2c_driver,负责设备的初始化、数据传输和资源管理。

关键成员作用

  1. id_table
    • 指向 spi_device_id 数组,定义驱动支持的设备名称(如 {"mpu6050", 0}),用于与传统匹配方式绑定设备。
    • 若使用设备树(Device Tree),则通过 driver.of_match_table 指定 compatible 属性匹配(如 {"invensense,mpu6050"})。
  2. probe()
    • 当驱动与设备匹配成功后调用,负责初始化硬件(如配置寄存器、申请中断、注册字符设备等)。
    • 与 I²C 驱动不同,SPI 驱动可在运行时动态调整总线参数(如时钟频率、SPI 模式)。
  3. remove()
    • 设备卸载时调用,释放资源(如注销中断、销毁设备节点、释放内存)。
  4. driver
    • 内嵌的 device_driver 结构体,需设置以下字段:
      • .name:驱动名称(如 "icm20607")。
      • .owner:模块所有者(通常为 THIS_MODULE)。
      • .of_match_table:设备树匹配表(类型为 of_device_id

9.struct spi_device{}-结构体

spi_device是 Linux 内核中描述 SPI(Serial Peripheral Interface)从设备的核心结构体,用于抽象物理 SPI 设备(如传感器、Flash 存储器等)的硬件属性和配置信息。其作用类似于 I²C 子系统中的 i2c_client,但针对 SPI 总线特性设计

核心定义与作用

spi_device 定义在 include/linux/spi/spi.h 中,主要作用包括:

  • 硬件抽象:封装 SPI 设备的物理参数(如时钟频率、片选引脚、传输模式等)。
  • 连接控制器 :通过 spi_controller(原 spi_master)关联到具体的 SPI 总线控制器,实现底层硬件操作。
  • 驱动匹配 :与 spi_driver 绑定,触发设备初始化(probe)和资源释放(remove

关键成员详解

以下是 spi_device 的核心字段及其功能:

类型 成员 作用
struct device dev 内嵌的通用设备模型基类,用于设备注册、sysfs 接口管理等。
struct spi_controller* controller 指向关联的 SPI 控制器(如 SoC 的 SPI 硬件模块),管理底层时序和传输。
u32 max_speed_hz SPI 通信的最大时钟频率(单位:Hz),驱动可动态调整。
u8 chip_select 片选引脚编号(CS),用于选择总线上的特定设备。
u16 mode SPI 传输模式标志位(见下表详解)。
u8 bits_per_word 每个数据字的位数(默认为 8)。
int irq 设备的中断号(若支持中断)。
int cs_gpio 片选引脚对应的 GPIO 编号(若使用 GPIO 模拟片选)。
char[SPI_NAME_SIZE] modalias 设备别名(如 "mpu6050"),用于匹配驱动。

SPI 模式标志位(mode 字段)

SPI 模式由 CPOL(时钟极性)和 CPHA(时钟相位)组合定义,常见模式如下

标志位 含义
SPI_MODE_0 0x00 CPOL=0, CPHA=0(标准模式)
SPI_MODE_1 0x01 CPOL=0, CPHA=1
SPI_MODE_2 0x02 CPOL=1, CPHA=0
SPI_MODE_3 0x03 CPOL=1, CPHA=1
SPI_CS_HIGH 0x04 片选高电平有效(默认低电平)
SPI_LSB_FIRST 0x08 数据低位先传输(默认高位先传输)
SPI_3WIRE 0x10 三线模式(共享数据线)
SPI_LOOP 0x20 回环测试模式

10.struct spi_controller{}-结构体

相关函数

11.module_spi_driver()-一键注册宏

cpp 复制代码
#define module_spi_driver(__spi_driver) \
    module_driver(__spi_driver, spi_register_driver, \
            spi_unregister_driver)
            
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
    return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
    __unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);

// 一键注册宏包含了
//    1、入口函数、入口函数的声明、入口函数中实现将驱动信息加载到内核空间中
//    2、出口函数、出口函数的声明、出口函数中实现将驱动信息从内核空间中移除

12.spi_write()

cpp 复制代码
#include <linux/spi/spi.h>

int spi_write(struct spi_device *spi, const void *buf, size_t len);

spi_write 是 Linux SPI 子系统中的核心函数,用于实现同步数据写入操作。其作用是将数据通过 SPI 总线发送到指定的从设备(Slave Device)。

  • spi : 指向目标 SPI 设备的 spi_device 结构体指针,包含设备地址、总线参数等。
  • buf: 待发送数据的缓冲区指针。
  • len: 发送数据的字节长度。
  • 返回值 :成功返回 0,失败返回负的错误码(如 EIO 表示硬件错误)

核心功能

  • buf 中的数据通过 SPI 总线同步写入到指定设备。
  • 调用时会阻塞当前线程直至传输完成(可睡眠上下文)

Linux 内核中的实现机制

spi_write 在 Linux 内核中通过组合 spi_transferspi_message 实现:

cpp 复制代码
static inline int spi_write(struct spi_device *spi, const void *buf, size_t len) {
    struct spi_transfer t = {
        .tx_buf = buf,  // 发送缓冲区
        .len = len,     // 数据长度
    };
    struct spi_message m;
    spi_message_init(&m);             // 初始化消息
    spi_message_add_tail(&t, &m);    // 添加传输段
    return spi_sync(spi, &m);         // 同步执行传输
}
  • spi_transfer: 描述单次数据传输(方向、缓冲区、长度)。
  • spi_message : 管理多个 spi_transfer,支持复杂传输序列(如先写后读)。
  • spi_sync: 同步执行消息队列,阻塞等待传输完成。

参数配置与传输控制

SPI 模式与时钟

  • 通过 spi_setup(spi) 配置设备参数(模式、时钟频率)。
  • 示例:设置模式为 SPI_MODE_3(CPOL=1, CPHA=1):
cpp 复制代码
spi->mode = SPI_MODE_3;
spi->max_speed_hz = 10000000; // 10 MHz
spi_setup(spi);

片选信号(CS)管理

  • 驱动需手动控制 GPIO 模拟片选:
cpp 复制代码
gpio_direction_output(cs_pin, 0); // 拉低 CS 开始传输
spi_write(spi_dev, data, len);
gpio_set_value(cs_pin, 1);        // 拉高 CS 结束传输

错误示例:未保持 CS 持续低电平导致传输中断

SPI的设备树节点解析

cpp 复制代码
在stm32mp151.dtsi中最初编写的SPI4外设控制器节点信息
spi4: spi@44005000 {                              
    #address-cells = <1>;              // 当前设备树节点的子节点描述地址需要使用1个32位数           
    #size-cells = <0>;                 // 当前设备树节点的子节点描述地址大小需要使用0个32位数           
    compatible = "st,stm32h7-spi";     // spi4设备树节点的compatible值           
    reg = <0x44005000 0x400>;          // spi4设备树节点的起始地址和可操作的地址大小           
    interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;    // spi4设备树节点支持的中断属性
    clocks = <&rcc SPI4_K>;            // spi4设备树节点的时钟属性           
    resets = <&rcc SPI4_R>;            // spi4设备树节点的复位属性            
    dmas = <&dmamux1 83 0x400 0x01>,   // spi4设备树节点支持的DMA属性            
           <&dmamux1 84 0x400 0x01>;              
    dma-names = "rx", "tx";            // spi4设备树节点支持的DMA工作模式(发送和接收)           
    power-domains = <&pd_core>;                   
    status = "disabled";               // 当前spi4设备树节点的状态:处于未使能状态           
};                                                


在参考板设备树文件stm32mp15xx-dkx.dtsi文件中引用并更改了spi4节点信息
&spi4 {                                
    pinctrl-names = "default", "sleep";    // 当前spi4外设控制器的工作模式(default:正常工作模式、sleep:低功耗模式)
    pinctrl-0 = <&spi4_pins_b>;            // 在正常工作模式下,spi4使用的引脚复用组(b组)
    pinctrl-1 = <&spi4_sleep_pins_b>;      // 在低功耗模式下,spi4使用的引脚复用组(b组)
    status = "disabled";                   // 当前spi4的状态:处于未使能状态
};                                     

在stm32mp15-pinctrl.dtsi文件中定义了引脚服用组信息
spi4_pins_b: spi4-1 {                                           
    pins1 {                                                     
        pinmux = <STM32_PINMUX('E', 12, AF5)>, /* SPI4_SCK */   
             <STM32_PINMUX('E', 14, AF5)>; /* SPI4_MOSI */      
        bias-disable;                                           
        drive-push-pull;                                        
        slew-rate = <1>;                                        
    };                                                          
                                                                
    pins2 {                                                     
        pinmux = <STM32_PINMUX('E', 13, AF5)>; /* SPI4_MISO */  
        bias-disable;                                           
    };                                                          
};                                                              
                                                                
spi4_sleep_pins_b: spi4-sleep-1 {                               
    pins {                                                      
        pinmux = <STM32_PINMUX('E', 12, ANALOG)>, /* SPI4_SCK */
             <STM32_PINMUX('E', 13, ANALOG)>, /* SPI4_MISO */   
             <STM32_PINMUX('E', 14, ANALOG)>; /* SPI4_MOSI */   
    };                                                          
};                                                              

// 问题:没有查看到片选线的引脚信息
// 解决:进入~/FSMP1A/linux-5.10.10/Documentation/devicetree/bindings/spi,存在关于spi设备树编写的相关规则
查看spi-bus.txt文件即可


// 在stm32mp157a-fsmp1a.dts文件下引用并更改spi4节点信息,并加上spi4节点下的外设节点信息(在根节点外引用)

&spi4 {
    pinctrl-names = "default", "sleep";    // 当前spi4外设控制器的工作模式(default:正常工作模式、sleep:低功耗模式)
    pinctrl-0 = <&spi4_pins_b>;            // 在正常工作模式下,spi4使用的引脚复用组(b组)
    pinctrl-1 = <&spi4_sleep_pins_b>;      // 在低功耗模式下,spi4使用的引脚复用组(b组)
    cs-gpios = <&gpioe 11 0>;           // 片选线配置的相关属性
    status = "okay";                   // 当前spi4的状态:处于未使能状态                                              

    mym74hc595@0 {
        compatible = "hqyj,m74hc595";
        spi-max-frequency = <1000000>;
        reg = <0x0>;
    };
};

// 编写完设备树文件后,需要到内核源码顶层目录下make dtbs编译并生成新的设备树镜像文件,并将新的设备树镜像文件拷贝到tftpboot下
相关推荐
阿华学长单片机设计4 小时前
【开源】基于STM32的新疆地区棉花智能种植系统
stm32·嵌入式硬件·开源
一枝小雨8 小时前
STM32启动流程解析:从BootROM到BootLoader
stm32·单片机·嵌入式·arm·bootloader·boot rom
充哥单片机设计9 小时前
【STM32项目开源】基于STM32的智能养殖场环境监测系统
stm32·单片机·嵌入式硬件
New农民工9 小时前
STM32ADC模式和DMA设置
stm32·单片机·嵌入式硬件
辰哥单片机设计11 小时前
UV紫外线消毒灯详解(STM32)
stm32·uv
何处归途.11 小时前
stm32内部flash
stm32·单片机·嵌入式硬件
充哥单片机设计11 小时前
【STM32项目开源】基于STM32的智能鱼缸养殖系统
stm32·单片机·嵌入式硬件
Hello_Embed13 小时前
STM32 智能垃圾桶项目笔记(四):PWM 回顾与舵机(SG90)控制实现
笔记·stm32·单片机·学习·嵌入式软件
小冷Hello15 小时前
【stm32】CAN分析仪+TJA1050+单片机通信不上,波特率等等都没问题,usb扩展坞的供电问题,绝了
stm32·单片机·嵌入式硬件