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下
相关推荐
Deitymoon2 小时前
STM32——外部中断按键控制led
stm32·单片机·嵌入式硬件
czwxkn2 小时前
7STM32(stdl)flash内部闪存
stm32·单片机·嵌入式硬件
咕噜咕噜啦啦2 小时前
STlink下载程序
stm32·单片机
Deitymoon4 小时前
STM32——串口中断接收
stm32·单片机·嵌入式硬件
Deitymoon6 小时前
STM32——串口通信发送数据
stm32·单片机·嵌入式硬件
czwxkn7 小时前
8STM32(stdl)低功耗模式
stm32·单片机·嵌入式硬件
czwxkn7 小时前
9STM32(stdl)看门狗
stm32·单片机·嵌入式硬件
LCG元8 小时前
STM32实战:基于STM32F103的SPI通信驱动W25Qxx Flash存储
stm32·单片机·嵌入式硬件
iCxhust9 小时前
led_pattern = (led_pattern << 1) | (led_pattern >> 7)执行顺序
stm32·单片机·嵌入式硬件·51单片机·微机原理
Deitymoon9 小时前
STM32——串口通信发送字符串
stm32·单片机·嵌入式硬件