Linux SPI设备分析2(基于Linux6.6)---SPI总线介绍
一、SPI 总线模块概述
SPI(Serial Peripheral Interface)总线是一种常见的同步串行通信协议,广泛用于微控制器与外设之间的高速数据传输。与其他串行通信协议(如 I2C)相比,SPI 通常具有更高的传输速率,并且支持全双工通信。SPI 总线是由一组信号线和一个主控制器(Master)与多个从设备(Slave)组成的。
在 Linux 内核中,SPI 总线模块(SPI bus driver)负责与 SPI 总线的硬件进行交互,管理数据的发送和接收,并提供接口供上层设备驱动使用。SPI 总线模块通常与 SPI 控制器硬件紧密结合,提供底层的通信支持。
1.1、SPI 总线的工作原理
SPI 是一种主从式(Master-Slave)通信协议,通信的基本流程如下:
- MOSI(Master Out Slave In):主设备发送数据到从设备。
- MISO(Master In Slave Out):从设备发送数据到主设备。
- SCK(Serial Clock):由主设备产生的时钟信号,用于同步数据传输。
- CS(Chip Select):也称为片选信号。主设备通过此信号选择与哪一个从设备进行通信,通常每个从设备有独立的 CS 信号。
数据传输时,主设备生成时钟信号,通过 MOSI 线将数据发送到从设备。从设备则通过 MISO 线将数据返回给主设备。整个数据传输过程是同步进行的,即数据的发送与接收都需要依赖时钟信号的同步。
1.2、SPI 总线模块的基本组成
在 Linux 内核中,SPI 总线模块主要由以下几个部分组成:
1.SPI 控制器(spi_controller)
SPI 控制器是实现 SPI 总线通信的硬件模块,它管理 SPI 总线的时序、数据传输和总线上的设备操作。每个 SPI 控制器驱动需要提供一组操作接口来与设备交互,包括数据的传输、设置时钟频率等。
spi_controller 结构体通常包含以下几个重要字段:
setup: 配置 SPI 总线的传输参数,例如设置 SPI 模式、时钟频率等。transfer: 执行 SPI 数据传输操作。transfer_one: 执行单次 SPI 传输操作。
示例代码:
struct spi_controller {
struct device dev;
int (*setup)(struct spi_device *spi); // 配置 SPI 设置
int (*transfer)(struct spi_device *spi, struct spi_transfer *xfer); // 执行数据传输
// 其他 SPI 总线控制器相关字段
};
2.SPI 设备(spi_device)
SPI 设备是连接到 SPI 总线上的硬件外设。spi_device 结构体包含了有关 SPI 设备的所有信息,例如设备的最大传输速度、片选号、SPI 模式等。每个 SPI 设备需要注册到 SPI 总线中,以便进行数据交换。
示例代码:
struct spi_device {
struct device dev;
struct spi_controller *controller; // 关联的 SPI 控制器
unsigned int max_speed_hz; // 最大传输速度
unsigned int chip_select; // 片选编号
unsigned int mode; // SPI 模式
// 其他 SPI 设备相关字段
};
3.SPI 总线(spi_bus)
SPI 总线由一个或多个 SPI 设备共享,通过不同的片选线(CS)来区分。Linux 中的 SPI 总线是通过 spi_bus 对象来表示的。这个对象包含了多个 SPI 设备以及它们的配置和传输操作。
4.SPI 传输(spi_transfer)
spi_transfer 结构表示一次 SPI 数据传输的详细信息。它描述了数据的来源(TX)和去向(RX)、传输的长度、时钟频率、延迟等参数。
struct spi_transfer {
unsigned long tx_buf; // 发送数据缓冲区
unsigned long rx_buf; // 接收数据缓冲区
unsigned len; // 传输数据的长度
unsigned speed_hz; // 传输时的时钟频率
unsigned cs_change; // 是否改变片选信号
unsigned delay_usecs; // 数据传输的延迟
};
1.3、SPI 总线的操作流程
SPI 总线的工作流程主要分为以下几个步骤:
1.初始化 SPI 总线
SPI 总线通常由一个 SPI 控制器驱动进行初始化。初始化时,SPI 控制器驱动会为 SPI 总线分配资源,设置 SPI 控制器的相关参数(如时钟频率、模式等),并注册到内核中。
static int spi_bus_init(struct spi_controller *controller)
{
// 初始化 SPI 总线控制器的硬件设置
controller->setup = spi_controller_setup;
controller->transfer = spi_controller_transfer;
return 0;
}
2.设备探测(Probe)
当一个新的 SPI 设备连接到总线时,SPI 总线驱动会调用设备的 probe 函数进行初始化。在 probe 函数中,SPI 设备会被配置为适当的 SPI 模式和传输参数,然后添加到 SPI 总线中。
static int spi_device_probe(struct spi_device *spi)
{
// 设置设备的 SPI 参数并初始化
spi->max_speed_hz = 1000000;
spi->mode = SPI_MODE_0;
return 0;
}
3.数据传输(Data Transfer)
SPI 总线的核心功能是数据传输。当设备进行数据传输时,SPI 总线驱动会调用相应的 transfer 或 transfer_one 函数来执行数据交换。这些函数会根据 SPI 设备的配置(如时钟、模式)来控制数据传输。
static int spi_transfer_data(struct spi_device *spi, struct spi_transfer *transfer)
{
// 使用 SPI 控制器进行数据传输
return spi_controller_transfer(spi, transfer);
}
4.设备移除(Remove)
当 SPI 设备从总线移除时,SPI 总线驱动会调用设备的 remove 函数进行资源清理,并将设备从总线中注销。
static int spi_device_remove(struct spi_device *spi)
{
// 清理 SPI 设备的资源
return 0;
}
1.4、SPI 总线模块开发中的注意事项
- 时钟频率与数据速率:SPI 通信速率受限于硬件控制器的时钟频率。在开发时,应该确保 SPI 总线和从设备都能支持所设定的最大速度。
- SPI 模式配置:SPI 协议有 4 种基本模式(CPOL 和 CPHA 配置),不同的设备可能需要不同的模式。驱动程序需要配置正确的模式。
- DMA 支持:对于高速传输,使用 DMA(直接存储器访问)可以提高数据传输效率,减少 CPU 占用。
- 片选管理:当多个设备共享同一 SPI 总线时,必须正确管理片选线,以确保每次通信仅与一个从设备进行。
二、SPI总线的注册
spi模块也是基于LINUX设备-总线-驱动模型进行开发的,因此其spi总线,也是需要注册到LINUX系统的总线模块的,调用的接口为是bus_register,看下spi总线都定义了哪些内容,如下是spi_bus_type的定义:
drivers/spi/spi.c
struct bus_type spi_bus_type = {
.name = "spi",
.dev_groups = spi_dev_groups,
.match = spi_match_device,
.uevent = spi_uevent,
.probe = spi_probe,
.remove = spi_remove,
.shutdown = spi_shutdown,
};
EXPORT_SYMBOL_GPL(spi_bus_type);
分析一下spi总线实现的成员变量:
- 定义了该总线的名称为spi;
- 定义了spi总线上注册设备时在sysfs中所需要创建的默认属性文件(即注册到spi总线上的设备,均需要在其设备目录下创建默认的属性文件),spi_dev_attr的定义如下,即每一个注册在spi总线上的spi设备,在挂载的sysfs文件系统对应目录下均会创建modalias的属性文件,该文件只有读权限,主要读取spi设备的名称(格式为“spi:spi->modalias”)
static struct device_attribute spi_dev_attrs[] = {
__ATTR_RO(modalias),
__ATTR_NULL,
};
- 该spi的match接口为spi_match_device,该接口主要用于spi设备与spi驱动的匹配检测;
- spi_uevent主要是在spi设备与spi驱动完成绑定时,发送给应用层的uevent条目,此处主要是增加“MODALIAS=spi:spi->modalias”的uevent条目。
- spi_pm为spi总线模块电源管理相关的接口(这块我还不熟悉,暂时就不展开了)
2.1、spi_match_device接口分析
该接口主要进行spi设备与驱动的匹配检测,该接口目前支持基于OF模块的匹配、基于spi设备与驱动名称的匹配检测、acpi模块的匹配检测。主要功能如下:
- 若LINUX系统支持OF,则调用of_driver_match_device进行OF部分的匹配接口,这是使用LINUX设备-总线-驱动模型中device类型变量与device_driver类型变量的成员间的匹配(device->of_node、device_driver->of_match_table这两个成员变量完成匹配);
- 若该spi设备或驱动支持acpi,则调用acpi_driver_match_device,进行acpi模块的匹配;
- 否则则调用spi_match_id,进行设备与驱动的匹配(这主要是将spi 设备的modalias变量与spi驱动的id_table中的参数进行比较)
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
const struct spi_device *spi = to_spi_device(dev);
const struct spi_driver *sdrv = to_spi_driver(drv);
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI */
if (acpi_driver_match_device(dev, drv))
return 1;
if (sdrv->id_table)
return !!spi_match_id(sdrv->id_table, spi);
return strcmp(spi->modalias, drv->name) == 0;
}
2.2、spi总线注册
当调用bus_register完成spi总线的注册后,spi总线与LINUX系统bus_kset、已注册到LINUX系统的总线以及spi总线与sysfs中的各结构体的关联图如下所示:
- 针对spi总线而言,其对应kobject在sysfs系统下对应一个sysfs_dirent类型的变量,通过该类型的变量即可在sysfs目录下创建一个目录(针对spi总线而言即为/sys/bus/spi),而spi总线对应kobject其kobj_type中的sysfs_ops则指向了总线的处理接口,通过这些接口即可对spi总线的属性文件进行读写操作(即可向/sys/bus/spi/下的普通文件进行读写操作)。
- 而注册到LINUX系统上的总线,通过其kset->kobject成员中的list变量,即完成了各注册的总线的关联。

2514

被折叠的 条评论
为什么被折叠?



