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;
}
可见是把设备描述中的 modalias
与 id_table
的 name
进行比较。
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_table
的 name
,匹配成功,返回 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 |
ACPI | 高 | x86 / 部分 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函数来完成的。