Linux SPI设备分析2

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 总线驱动会调用相应的 transfertransfer_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模块的匹配检测。主要功能如下:

  1. 若LINUX系统支持OF,则调用of_driver_match_device进行OF部分的匹配接口,这是使用LINUX设备-总线-驱动模型中device类型变量与device_driver类型变量的成员间的匹配(device->of_node、device_driver->of_match_table这两个成员变量完成匹配);
  2. 若该spi设备或驱动支持acpi,则调用acpi_driver_match_device,进行acpi模块的匹配;
  3. 否则则调用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中的各结构体的关联图如下所示:

  1. 针对spi总线而言,其对应kobject在sysfs系统下对应一个sysfs_dirent类型的变量,通过该类型的变量即可在sysfs目录下创建一个目录(针对spi总线而言即为/sys/bus/spi),而spi总线对应kobject其kobj_type中的sysfs_ops则指向了总线的处理接口,通过这些接口即可对spi总线的属性文件进行读写操作(即可向/sys/bus/spi/下的普通文件进行读写操作)。
  2. 而注册到LINUX系统上的总线,通过其kset->kobject成员中的list变量,即完成了各注册的总线的关联。

975f4781bd8745d2804a2e29a8088580.png

 

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值