当前位置: 首页 > 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_tablereg = <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函数来完成的。


http://www.mrgr.cn/news/95778.html

相关文章:

  • Unity 实现一个简易可拓展性的对话系统
  • 深度解读DeepSeek:开源周(Open Source Week)技术解读
  • 从零开始的LeetCode刷题日记:128. 最长连续序列
  • Spring Boot 整合 Nacos 注册中心终极指南
  • CentOS 7 更换 yum 源(阿里云)+ 扩展 epel 源
  • Jackson实现JSON数据的合并
  • vivo 湖仓架构的性能提升之旅
  • AI本地部署之dify
  • Redis 服务搭建
  • DeepSeek面试——模型架构和主要创新点
  • 《TCP/IP网络编程》学习笔记 | Chapter 21:异步通知 I/O 模型
  • springboot使用netty做TCP客户端
  • python面试高频考点(深度学习大模型方向)
  • 鸿蒙进行视频上传,使用 request.uploadFile方法
  • 大模型应用(Java)2025/3/24
  • LeetCode热题100JS(69/100)第十三天|34|33|153|4|20
  • 2025-3-24 leetcode刷题情况(动态规划——01背包)
  • 【HTML5游戏开发教程】零基础入门合成大西瓜游戏实战 | JS物理引擎+Canvas动画+完整源码详解
  • stm32-IIC
  • 运动仿真——phased.Platform