当前位置: 首页 > news >正文

Linux的SPI子系统的原理和结构详解【SPI控制器(spi_master)、SPI总线(device-driver-match匹配机制)、SPI设备、SPI万能驱动`spidev.c`】

前言说明

如果前面对Plartform总线和I2C总线有详细认真的学习的话,那么理解SPI总线那就很容易了,所以可以先去回顾一下Plartform总线和I2C总线。

关于Platform总线的原理和结构,详情见下面三篇博文:
https://blog.csdn.net/wenhao_ir/article/details/145023181
https://blog.csdn.net/wenhao_ir/article/details/145018442
https://blog.csdn.net/wenhao_ir/article/details/145030037

关于I2C总线的原理和结构,详情见下面这篇博文:
https://blog.csdn.net/wenhao_ir/article/details/146405656

视频和讲义资料可以看一看

关于Linux的SPI总线的原理和结构,视频里已经讲得比较清楚了,百度网盘搜索“1-3_03_SPI总线设备驱动模型”,然后从头开始看。当然视频刚开始花了5分钟左右来回顾Plartform总线,如果赶时间的话,可以跳过,直接从第5分钟10秒开始看对SPI总线的介绍。
可以配套下面这个MD文档来观看视频:
https://pan.baidu.com/s/15Zu_9f2YnNjJix3Wd48uJw?pwd=j9f5

看完视频和讲义资料后,大致的关键点提取如下文所述。

SPI子系统的完整结构

下面幅图可以单独开个窗口查看。
在这里插入图片描述
从上面这幅图我们可以看出,SPI的硬件上分为两部分,一部是SPI控制器,另一部分是SPI设备。
SPI控制器的驱动通过Plartform总线实现、SPI设备的驱动通过SPI总线实现。
一个SPI控制器对应于一个spi_master的结构体的实例、一个SPI设备对应于一个spi_device的结构体。
在这里插入图片描述
设备文件的节点举例如下:
在这里插入图片描述
上面的截图是关于SPI子系统的设备文件的节点举例,在截图的代码中,spi3是一个SPI控制器(spi_master)、它下面有一个名叫gpio_spi@0的SPI设备。
值得说明的一点是:SPI设备节点的解析工作是由SPI控制器(spi_master)驱动的probe函数来完成的。

关于SPI控制器的驱动

SPI控制器的驱动程序走得是Plartform总线,提供SPI的底层传输能力,Linux内核中通过spi_master结构体来描述一个SPI控制器。
来源:include\linux\spi\spi.h
在这里插入图片描述
截图中用红框圈中的transfer成员函数是最关键的函数,在它里面实现了SPI控制器对数据的收发操作。

SPI总线介绍

SPI总线的结构图

下面这幅图就不用多说什么了,如果了解了Plartform总线和I2C总线,那下面这幅图就很好理解了。
在这里插入图片描述

spi_device结构体

spi_device结构体的截图如下:
来源:include\linux\spi\spi.h
在这里插入图片描述
spi_device结构体可以来自设备树、也可以来自C文件,对应于博文 https://blog.csdn.net/wenhao_ir/article/details/146417363 对“i2c_client的实现和生成”的方式二和方式三。

spi_driver结构体

来源:include\linux\spi\spi.h
在这里插入图片描述

SPI总线的match函数分析(匹配机制分析)

SPI总线的match函数会赋值给结构体实例spi_bus_type,如下图所示:
\Linux-4.9.88\drivers\spi\spi.c
在这里插入图片描述
然后我们进入函数spi_match_device来进行分析:
在这里插入图片描述
spi_match_device() 按照以下顺序尝试匹配 SPI 设备和驱动:

第1优先级的匹配——设备树(Device Tree, DT)匹配

if (of_driver_match_device(dev, drv))
    return 1;
  • 适用于 ARM 及其他基于设备树的平台(如 IMX6ULL)。
  • 如果设备树 (.dts) 中的 compatible 字符串 与驱动的 of_match_table 匹配,则匹配成功。

示例
驱动代码:

static const struct of_device_id my_spi_dt_ids[] = {
    { .compatible = "myvendor,myspi" },
    { }
};
MODULE_DEVICE_TABLE(of, my_spi_dt_ids);

static struct spi_driver my_spi_driver = {
    .driver = {
        .name = "my_spi_device",
        .of_match_table = my_spi_dt_ids,  // 设备树匹配表
    },
    .probe = my_spi_probe,
    .remove = my_spi_remove,
};

注意:在定义了my_spi_dt_ids后,要用代码MODULE_DEVICE_TABLE(of, my_spi_dt_ids);my_spi_dt_ids 导出到内核的设备表,这样才能真正的进行设备树匹配,否则my_spi_dt_ids 就只是my_spi_dt_ids ,起不到进行设备树匹配的作用。

设备树 .dts

&ecspi1 {
    my_spi_device@0 {
        compatible = "myvendor,myspi";  // 匹配 of_match_table
        reg = <0>;
        spi-max-frequency = <10000000>;
    };
};

如果 compatible 匹配 of_match_table,匹配成功,返回 1


第2优先级的匹配——ACPI 设备匹配

if (acpi_driver_match_device(dev, drv))
    return 1;
  • 适用于 x86 及支持 ACPI 的 ARM64 设备
  • ACPI 匹配表 acpi_match_table 用于 匹配 ACPI DSDT 表中的 _HID(Hardware ID)

示例
驱动代码:

static const struct acpi_device_id my_spi_acpi_ids[] = {
    { "MYSP1000", 0 },  // ACPI 设备 ID
    { }
};
MODULE_DEVICE_TABLE(acpi, my_spi_acpi_ids);

static struct spi_driver my_spi_driver = {
    .driver = {
        .name = "my_spi_device",
        .acpi_match_table = ACPI_PTR(my_spi_acpi_ids),
    },
    .probe = my_spi_probe,
    .remove = my_spi_remove,
};

注意:在定义了my_spi_acpi_ids后,要用代码MODULE_DEVICE_TABLE(acpi, my_spi_acpi_ids);my_spi_acpi_ids 导出到内核的设备表,这样才能真正的进行ACPI 匹配,否则my_spi_acpi_ids 就只是my_spi_acpi_ids ,起不到进行ACPI 匹配的作用。

ACPI DSDT 表:

Device (SPI1)
{
    Name (_HID, "MYSP1000")  // 硬件 ID
}

如果 _HID 匹配 acpi_match_table,匹配成功,返回 1


第3优先级的匹配—— id_table 设备 ID 匹配

if (sdrv->id_table)
    return !!spi_match_id(sdrv->id_table, spi);
  • 适用于 传统的 SPI 设备驱动,主要用于 没有设备树或 ACPI 的情况。
  • id_table 包含 驱动支持的 SPI 设备列表,通常用于 手动注册的 SPI 设备

其中spi_match_id函数的代码如下:
\Linux-4.9.88\drivers\spi\spi.c

static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,
						const struct spi_device *sdev)
{
	while (id->name[0]) {
		if (!strcmp(sdev->modalias, id->name))
			return id;
		id++;
	}
	return NULL;
}

可见是把设备描述中的 modaliasid_tablename进行比较。
spi_device结构体的定义前面已经给出过了,里面就有一项是 modalias
在这里插入图片描述

示例
驱动代码:

static const struct spi_device_id my_spi_id_table[] = {
    { "my_spi_device", 0 },
    { }
};
MODULE_DEVICE_TABLE(spi, my_spi_id_table);

static struct spi_driver my_spi_driver = {
    .driver = {
        .name = "my_spi_device",
    },
    .id_table = my_spi_id_table,
    .probe = my_spi_probe,
    .remove = my_spi_remove,
};

手动注册设备:

struct spi_board_info spi_device_info = {
    .modalias = "my_spi_device",  // 匹配 id_table
    .max_speed_hz = 1000000,
    .bus_num = 1,
    .chip_select = 0,
    .mode = SPI_MODE_0,
};

如果 modalias 匹配 id_tablename,匹配成功,返回 1


第4优先级的匹配—— modalias 设备名称匹配

return strcmp(spi->modalias, drv->name) == 0;
  • 适用于 SPI 设备 ID 表为空的情况,作为最后的匹配方式。
  • 设备的 modalias 必须与驱动的 name 完全一致 才能匹配。

spi_device结构体的定义前面已经给出过了,里面就有一项是 modalias
在这里插入图片描述

示例

static struct spi_driver my_spi_driver = {
    .driver = {
        .name = "my_spi_device",
    },
    .probe = my_spi_probe,
    .remove = my_spi_remove,
};

设备:

spi_device->modalias = "my_spi_device";

如果 modalias driver.name 完全一致,匹配成功,返回 1

SPI总线 匹配机制总结

匹配方式优先级适用场景匹配机制
设备树(DT)最高ARM 设备compatible 字符串匹配 of_match_table
ACPIx86 / 部分 ARM64_HID 字符串匹配 acpi_match_table
id_table手动注册设备id_table[].name 匹配 spi->modalias
modalias最低默认匹配driver.name 必须等于 spi->modalias

万能SPI驱动spidev.c的来由

在I2C子系统中,我们有了I2C的控制器驱动后,就可以直接在应用层通过I2C控制器的驱动操作各I2C设备了,而不需要先接入I2C总线的驱动,再去使用I2C的驱动。

但SPI设备不一样,SPI设备比I2C设备要复杂,比如每个SPI设备有自己的片选控制引脚、有自己的最大时钟值、有自己的传输方式(任意时刻是只读或只写还是既读又写),所以SPI设备直接使用SPI控制器的驱动是很困难的。要想使用一个SPI设备,通常都不是直接去使用SPI控制器的驱动来操作SPI设备,而是通过SPI总线来为具体的SPI设备提供驱动。

Linux内核心中有一个特殊的SPI总线驱动,它的字名叫spidev.c,它被称为SPI的万能驱动,它其实是把SPI控制器的底层传输方法进行了轻度封装,给应用层提供常用的利用SPI控制器进行数据读写的方法。由于它的封装是轻度封装,是不针对某个SPI设备的封装,所以它被称为是万能驱动。

SPI总线的设备树文件的节点举例及解析函数

在这里插入图片描述
截图就是关于SPI子系统的设备树文件的节点举例,在截图的代码中,spi3是一个SPI控制器(spi_master)、它下面有一个名叫gpio_spi@0的SPI设备。
值得说明的一点是:SPI设备节点的解析工作是由SPI控制器(spi_master)驱动的probe函数来完成的。

相关文章:

  • 前端性能优化方案总结
  • ARCGIS PRO SDK 创建右键菜单
  • 基于腾讯云HAI-CPU部署DeepSeek:搭建图书馆知识库,赋能智慧图书馆建设
  • 从技术架构和生态考虑,不是单纯的配置优化,还有哪些方式可以提高spark的计算性能
  • TCP 三次握手与四次挥手过程
  • Nordic Semiconductor 芯片(如 nRF52/nRF53 系列)的 VSCode 开发环境的步骤
  • 大型语言模型(LLM)推理框架的全面分析与选型指南(2025年版)
  • LLM之RAG实战(五十二)| 如何使用混合搜索优化RAG 检索
  • 鸿蒙进行视频上传,使用 request.uploadFile方法
  • 深入理解C#中的享元模式(Flyweight Pattern)
  • 感应电机反电动势频率与电源频率相等以及转差率的测量机制
  • 26考研——图_图的遍历(6)
  • 【C++】vector的push_back和emplace_back
  • 电动自行车/电动工具锂电池PCM方案--SH367003、SH367004、SH79F329
  • C# SerialPort 类中 Handshake 属性的作用
  • 基于springboot人脸识别的社区流调系统(源码+lw+部署文档+讲解),源码可白嫖!
  • 如何解决用户名文件夹是中文导致的识别不到路径,获取不到ssh密匙
  • 淘宝历史价格数据获取指南:API 与爬虫方案的合法性与效率对比
  • 大模型——字节跳动开源AI Agent框架Agent TARS:智能化自动化的新利器
  • 人工智能之数学基础:特征值和特征向量
  • 全国台联原会长杨国庆逝世,享年89岁
  • 美乌矿产协议预计最早于今日签署
  • 来论|受美国“保护”,日本民众要付出什么代价?
  • 国铁集团郑州局预计“五一”发送642.5万人
  • 外媒称菲方允许菲官员窜台,国台办:应停止在台湾问题上玩火
  • 国台办:台商台企有信心与国家一起打赢这场关税战