SPI 硬件+Linux驱动详解

SPI硬件基础

1、SPI  hardware

SPI:Serial Perripheral Interface,串行外围设备接口,由 Motorola 公司提出,是一种高速、全双工、同步通信总线。SPI 以主从方式工作,通常是有一个主设备和一个或多个从设备,无应答机制。

本文我们讲解标准的 4 线 SPI,四根线如下:

、CS/SS,Slave Select/Chip Select,片选信号线,用于选择需要进行通信的从设备。

、SCK,Serial Clock,串行时钟,和 I2C 的 SCL 一样,为 SPI 通信提供时钟。

、MOSI/SDO,Master Out Slave In/Serial Data Output,主输出从输入。

、MISO/SDI,Master In Slave Out/Serial Data Input,主输入从输出。

SPI 硬件+Linux驱动详解

2、SPI 四种工作模式

SPI 有四种工作模式,通过时钟极性(CPOL)和时钟相位(CPHA)的搭配来得到四种工作模式:

SPI 硬件+Linux驱动详解

示例波形图如下:

SPI 硬件+Linux驱动详解

SPI 是全双工的,所以读写时序可以一起完成。

3、SPI 传输机制

SPI 硬件+Linux驱动详解

从图可以看出,主机和从机都有一个串行移位寄存器,主机通过向它的 SPI 串行寄存器写入一个字节来发起一次传输。寄存器通过 MOSI 信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过 MISO 信号线返回给主机。这样,两个移位寄存器中的内容就被交换。

外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。

SPI 硬件+Linux驱动详解

SPI 硬件+Linux驱动详解

虽然 SPI 四线制支持读写同时进行,但实际上我们很多时候并不需要又读又写,见以下两种情况(参考 BMA223 数据手册):

注意:如下三幅图示均为 CPOL=1,CPHA=1

1、主机向从机写数据

SPI 硬件+Linux驱动详解

主机发送先发送 8 bits,第一个 bit 为 0 代表这次主机是想写数据到从机,AD6~AD0 表示要写的寄存器地址。然后,主机就会一直写下去。在这期间 SDO 一直没用,一直是高阻态,算是一直读到1。

2、主机从从机读数据

SPI 硬件+Linux驱动详解

这种情况下,主机先发送 8 bits,第一位为 1 代表这次是读,然后 AD6 ~ AD0 是想要读的寄存器地址,然后 SDO 开始返回数据。

4、SPI timing diagram

SPI 硬件+Linux驱动详解

注意:真实的波形图如上,高低电平并不是到达最高点才算,0.3Vdd 以下为低电平,0.7Vdd 以上为高电平,计算信号时间长度的时候需要注意这个微小的时间,硬件设计必须注意信号质量风险,软件开发人员也要会看波形图。

这里的参数,一般 spi 驱动不需要设置,但是半导体厂商提供的 spi 控制器驱动中,可以修改这些参数。我们写 SPI 驱动时候,可以根据从设备的要求来修改这些参数。

5、DMA 与 FIFO

不同平台对于 SPI FIFO 和 DMA 的 buffer size 设置不同:

SPI 硬件+Linux驱动详解

传输 32bytes 以下使用 FIFO,传输 32bytes 以上使用 DMA。

DMA 可以自动发起多次传输,一次最大 256K 。

6、I2C 与 SPI 对比

I2C 和 SPI 的速率如下:

SPI 速率:几十 MHz 甚至上百 MHz,速度取决于 CPU 的 SPI 控制器和时钟 clock

STM32F103 的 SPI 最高支持 18MHz,imx6ull 的 SPI 最高支持 52MHz,其他芯片一般用不到更高的,因为速度越快波形质量越不好,越容易出问题。

具体采用多大速率还和外设有关,比如 EEPROM 的 W25Q128 的 SPI 最高支持 80MHz,ICM20608 传感器的 SPI 最高支持8MHz。一般用在 flash 上的速度会较快。

7、扩展

SPI 协议其实是包括:Standard SPI、Dual SPI 和 Queued SPI 三种协议接口。

Dual SPI 还是四线制,只是传输线可以变为同方向,速度是 Standard SPI 的两倍。

Queued SPI 是六线制,多了两根数据线,传输速度是 Standard SPI 的四倍。

SPILinux驱动

1、SPI 驱动源文件目录

Linux common spi driver

spi 控制器驱动,IC 厂商提供,不同厂商命名不同

dts

以上文件对应如下 SPI 驱动软件架构:

SPI 硬件+Linux驱动详解

SPI 控制器驱动程序

SPI 控制器不用关心设备的具体功能,它只负责把上层协议驱动准备好的数据按 SPI 总线的时序要求发送给 SPI 设备,同时把从设备收到的数据返回给上层的协议驱动,因此,内核把 SPI 控制器的驱动程序独立出来。

SPI 控制器驱动负责控制具体的控制器硬件,诸如 DMA 和中断操作等等,因为多个上层的协议驱动可能会通过控制器请求数据传输操作,所以,SPI 控制器驱动同时也要负责对这些请求进行队列管理,保证先进先出的原则。

SPI 通用接口封装层

为了简化 SPI 驱动程序的编程工作,同时也为了降低【协议驱动程序】和【控制器驱动程序】的耦合程度,内核把控制器驱动和协议驱动的一些通用操作封装成标准的接口,加上一些通用的逻辑处理操作,组成了 SPI 通用接口封装层。

这样的好处是,对于控制器驱动程序,只要实现标准的接口回调 API,并把它注册到通用接口层即可,无需直接和协议层驱动程序进行交互。而对于协议层驱动来说,只需通过通用接口层提供的 API 即可完成设备和驱动的注册,并通过通用接口层的 API 完成数据的传输,无需关注 SPI 控制器驱动的实现细节。

SPI 协议驱动程序

SPI 设备的具体功能是由 SPI 协议驱动程序完成的,SPI 协议驱动程序了解设备的功能和通信数据的协议格式。向下,协议驱动通过通用接口层和控制器交换数据,向上,协议驱动通常会根据设备具体的功能和内核的其它子系统进行交互。

例如,和 MTD 层交互以便把 SPI 接口的存储设备实现为某个文件系统,和 TTY 子系统交互把 SPI 设备实现为一个 TTY 设备,和网络子系统交互以便把一个 SPI 设备实现为一个网络设备。如果是一个专有的 SPI 设备,我们也可以按设备的协议要求,实现自己的专有协议驱动。

SPI 通用设备驱动程序

考虑到连接在 SPI 控制器上的设备的可变性,在内核没有配备相应的协议驱动程序,对于这种情况,内核为我们准备了通用的 SPI 设备驱动程序,该通用设备驱动程序向用户空间提供了控制 SPI 控制的控制接口,具体的协议控制和数据传输工作交由用户空间根据具体的设备来完成,在这种方式中,只能采用同步的方式和 SPI 设备进行通信,所以通常用于一些数据量较少的简单 SPI 设备。

2、SPI 通用接口层

SPI 通用接口层把具体的 SPI 设备的协议驱动和 SPI 控制器驱动连接在一起。

负责 SPI 系统与 Linux 设备模型相关的初始化工作。

为协议驱动和控制器驱动提供一系列的标准接口 API 及其数据结构。

SPI 设备、SPI 协议驱动、SPI 控制器的数据抽象

协助数据传输而定义的数据结构

kernel-4.14/drivers/spi/spi.c

在这里创建了 SPI 总线,创建 /sys/bus/spi 节点和 /sys/class/spi_master 节点。

重要数据结构:

重要 API

接下来详细解析结构体和API,只讲解重点部分,完整解析请参考官方文档

只有熟悉每个结构体存储的是什么东西,才能真正搞懂 SPI 模块。

spi_master/spi_controller:描述一个 spi 主机设备

spi_device:描述一个 spi 从机设备

spi_driver:描述一个 spi 设备驱动

spi_board_info:描述一个 spi 从机设备板级信息,无设备树时使用

spi_transfer:描述 spi 传输的具体数据

spi_message:描述一次 spi 传输的信息

队列化

SPI 数据传输可以有两种方式:同步方式和异步方式。

同步方式:数据传输的发起者必须等待本次传输的结束,期间不能做其它事情,用代码来解释就是,调用传输的函数后,直到数据传输完成,函数才会返回。

异步方式:数据传输的发起者无需等待传输的结束,数据传输期间还可以做其它事情,用代码来解释就是,调用传输的函数后,函数会立刻返回而不用等待数据传输完成,我们只需设置一个回调函数,传输完成后,该回调函数会被调用以通知发起者数据传送已经完成。

同步方式简单易用,很适合处理那些少量数据的单次传输。但是对于数据量大、次数多的传输来说,异步方式就显得更加合适。

对于 SPI 控制器来说,要支持异步方式必须要考虑以下两种状况:

对于同一个数据传输的发起者,既然异步方式无需等待数据传输完成即可返回,返回后,该发起者可以立刻又发起一个 message,而这时上一个message还没有处理完。

对于另外一个不同的发起者来说,也有可能同时发起一次message传输请求。

队列化正是为了为了解决以上的问题,所谓队列化,是指把等待传输的 message 放入一个等待队列中,发起一个传输操作,其实就是把对应的 message 按先后顺序放入一个等待队列中,系统会在不断检测队列中是否有等待传输的 message,如果有就不停地调度数据传输内核线程,逐个取出队列中的 message 进行处理,直到队列变空为止。SPI 通用接口层为我们实现了队列化的基本框架。

SPI 硬件+Linux驱动详解

spi_message 就是一次 SPI 数据交换的原子操作,不可打断。

3、SPI 控制器驱动层

SPI 控制器驱动层负责最底层的数据收发,主要有以下功能:

申请必要的硬件资源,比如中断、DMA 通道、DMA 内存缓冲区等等

配置 SPI 控制器的工作模式和参数,使之可以和相应的设备进行正确的数据交换

向通用接口层提供接口,使得上层的协议驱动可以通过通用接口层访问控制器驱动

配合通用接口层,完成数据消息队列的排队和处理,直到消息队列变空为止

SPI 主机驱动就是 SOC 的 SPI 控制器驱动。Linux 内核使用 spi_master/spi_controller 表示 SPI 主机驱动,spi_master 是个结构体,定义在 include/linux/spi/spi.h 文件中。

SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册 spi_master。

API 如下:

SPI 主机驱动的加载

以 MTK 为例,源码来自于小米开源项目

小米每做一个项目,都会把 kernel 部分开源,因为需要遵循 Linux GPL 开源协议。

【设备】声明在设备树中

SPI 硬件+Linux驱动详解

【驱动】

kernel-4.14/drivers/spi/spi-mt65xx.c

SPI 硬件+Linux驱动详解

SPI 硬件+Linux驱动详解

匹配以后,probe 函数执行,申请 spi_master,初始化 spi_master,最后向 Linux 内核注册 spi_master。

SPI 硬件+Linux驱动详解

4、软件流程

SPI 硬件+Linux驱动详解

看懂该图,对 SPI 驱动框架就有完整的了解了。

1、2、3 按顺执行,首先有 spi 总线的注册,然后是 spi 控制器驱动加载,然后是设备驱动加载。

区别在于,spi 控制器驱动加载时,是靠 platform 总线匹配设备(控制器)与驱动。spi 设备驱动加载时,是靠 spi 总线匹配设备(外设IC)与驱动。

init flow

SPI 硬件+Linux驱动详解

spi_register_master 的调用序列图

SPI 硬件+Linux驱动详解

队列化的工作机制及过程

SPI 硬件+Linux驱动详解

SPI 硬件+Linux驱动详解

当协议驱动程序通过 spi_async 发起一个 message 请求时,队列化和工作线程被激活,触发一些列的操作,最终完成 message 的传输操作。

spi_sync 与 spi_async 类似,只是有一个等待过程。

5、SPI 设备驱动

【设备】声明在设备树中

SPI 硬件+Linux驱动详解

SPI 硬件+Linux驱动详解

注意:设备的声明,slave device node 应该包含在你所要挂载的 &spi node 下,将 device 绑定在 master 上。然后通过 pinctrl 方式指定 GPIO,并在驱动中操作 pinctrl 句柄。

【驱动】demo

Linux 内核使用 spi_driver 结构体来表示 spi 设备驱动,我们在编写 SPI 设备驱动的时候需要实现 spi_driver。spi_driver 结构体定义在 include/linux/spi/spi.h 文件中。

在驱动入口函数中调用 spi_register_driver 来注册 spi_driver。

在驱动出口函数中调用 spi_unregister_driver 来注销 spi_driver。

spi 读写数据demo

除了 init、exit、probe、remove、read、write 函数外,其他的函数看需求实现,这几个是最基本的。

6、总结

Linux 是 总线、设备、驱动 的框架,理解了这个框架,就能理解所有的模块驱动框架。

SPI 驱动比 I2C 驱动还是简单很多的。

end

一口Linux

来源:一口Linux

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年4月13日
下一篇 2022年4月13日

相关推荐