SPI驱动
SPI
SPI 控制器驱动程序
SPI 主机驱动就是 SOC 的 SPI 控制器驱动,SPI 控制器不用关心设备的具体功能,它只负责把上层协议驱动准备好的数据按 SPI 总线的时序要求发送给 SPI 设备,同时把从设备收到的数据返回给上层的协议驱动,因此,内核把 SPI 控制器的驱动程序独立出来。
SPI 控制器驱动负责控制具体的控制器硬件,诸如 DMA 和中断操作等等,因为多个上层的协议驱动可能会通过控制器请求数据传输操作,所以,SPI 控制器驱动同时也要负责对这些请求进行队列管理,保证先进先出的原则。
申请必要的硬件资源,比如中断、DMA 通道、DMA 内存缓冲区等等
配置 SPI 控制器的工作模式和参数,使之可以和相应的设备进行正确的数据交换
向通用接口层提供接口,使得上层的协议驱动可以通过通用接口层访问控制器驱动
配合通用接口层,完成数据消息队列的排队和处理,直到消息队列变空为止
SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册spi_master。
1 | spi_alloc_master 函数:申请 spi_master。 |
SPI 通用接口封装层
为了简化 SPI 驱动程序的编程工作,同时也为了降低【协议驱动程序】和【控制器驱动程序】的耦合程度,内核把控制器驱动和协议驱动的一些通用操作封装成标准的接口,加上一些通用的逻辑处理操作,组成了 SPI 通用接口封装层。
这样的好处是,对于控制器驱动程序,只要实现标准的接口回调 API,并把它注册到通用接口层即可,无需直接和协议层驱动程序进行交互。而对于协议层驱动来说,只需通过通用接口层提供的 API 即可完成设备和驱动的注册,并通过通用接口层的 API 完成数据的传输,无需关注 SPI 控制器驱动的实现细节。
SPI 通用接口层把具体的 SPI 设备的协议驱动和 SPI 控制器驱动连接在一起。
负责 SPI 系统与 Linux 设备模型相关的初始化工作。
为协议驱动和控制器驱动提供一系列的标准接口 API 及其数据结构。
SPI 设备、SPI 协议驱动、SPI 控制器的数据抽象
协助数据传输而定义的数据结构
1 | //重要数据结构 |
SPI 协议驱动程序
SPI 设备的具体功能是由 SPI 协议驱动程序完成的,SPI 协议驱动程序了解设备的功能和通信数据的协议格式。向下,协议驱动通过通用接口层和控制器交换数据,向上,协议驱动通常会根据设备具体的功能和内核的其它子系统进行交互。
例如,和 MTD 层交互以便把 SPI 接口的存储设备实现为某个文件系统,和 TTY 子系统交互把 SPI 设备实现为一个 TTY 设备,和网络子系统交互以便把一个 SPI 设备实现为一个网络设备。如果是一个专有的 SPI 设备,我们也可以按设备的协议要求,实现自己的专有协议驱动。
SPI 通用设备驱动程序
考虑到连接在 SPI 控制器上的设备的可变性,在内核没有配备相应的协议驱动程序,对于这种情况,内核为我们准备了通用的 SPI 设备驱动程序,该通用设备驱动程序向用户空间提供了控制 SPI 控制的控制接口,具体的协议控制和数据传输工作交由用户空间根据具体的设备来完成,在这种方式中,只能采用同步的方式和 SPI 设备进行通信,所以通常用于一些数据量较少的简单 SPI 设备。
在Linux内核中spi的函数存放位置
1 | kernel-4.15/drivers/spi/spi.c Linux 提供的通用接口封装层驱动 |
SPI 数据传输可以有两种方式:同步方式和异步方式。
同步方式:数据传输的发起者必须等待本次传输的结束,期间不能做其它事情,用代码来解释就是,调用传输的函数后,直到数据传输完成,函数才会返回。
异步方式:数据传输的发起者无需等待传输的结束,数据传输期间还可以做其它事情,用代码来解释就是,调用传输的函数后,函数会立刻返回而不用等待数据传输完成,我们只需设置一个回调函数,传输完成后,该回调函数会被调用以通知发起者数据传送已经完成。
spi_async函数是发起一个异步传输的API,它会把spi_message结构挂在spi_master的queue字段下,然后启动专门为spi传输准备的内核工作线程,由该工作线程来实际处理message的传输工作,因为是异步操作,所以该函数会立刻返回,不会等待传输的完成,这时,协议驱动程序(可能是另一个协议驱动程序)可以再次调用该API,发起另一个message传输请求,结果就是,当工作线程被唤醒时,spi_master下面可能已经挂了多个待处理的spi_message结构,工作线程会按先进先出的原则来逐个处理这些message请求,每个message传送完成后,对应spi_message结构的complete回调函数就会被调用,以通知协议驱动程序准备下一帧数据。这就是spi_message的队列化。工作线程唤醒时,spi_master、spi_message和spi_transfer之间的关系可以用下图来描述:
SPI驱动结构
spi_driver 注册示例程序如下:
1 | /* probe 函数 */ |
设备驱动的编写流程
SPI使用的API函数
1 | 603 struct spi_transfer { |
spi_transfer 需要组织成 spi_message, spi_message 也是一个结构体 。
1 | struct spi_message { |
在使用spi_message之前需要对其进行初始化, spi_message初始化函数为spi_message_init,
1 | void spi_message_init(struct spi_message *m) |
spi_message 初始化完成以后需要将 spi_transfer 添加到 spi_message 队列中,这里我们要用到 spi_message_add_tail 函数。
1 | void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m) |
spi_message 准备好以后就可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待 SPI 数据传输完成,同步传输函数为 spi_sync,函数原型如下:
1 | int spi_sync(struct spi_device *spi, struct spi_message *message) |
异步传输不会阻塞的等到 SPI 数据传输完成,异步传输需要设置 spi_message 中的 complete成员变量, complete 是一个回调函数,当 SPI 异步传输完成以后此函数就会被调用。 SPI 异步传输函数为 spi_async。
1 | int spi_async(struct spi_device *spi, struct spi_message *message) |
同步传输通过 SPI 进行 n 个字节的数据发送和接收的示例代码
1 | /* SPI 多字节发送 */ |