目录
- 一、SPI_Slave_Mode驱动程序框架
- 1. Master和Slave模式差别
- 1.1 主设备 (Master)
- 1.2 从设备 (Slave)
- 1.3 示例
- 2. SPI传输概述
- 2.1 数据组织方式
- 2.2 SPI控制器数据结构
- 3. SPI Slave Mode数据传输过程
- 4. 如何编写程序
- 4.1 设备树
- 4.2 内核相关
- 4.3 简单的示例代码
- 4.3.1 master和slave驱动示例
- 4.3.2 master和slave使用示例
- 二、SPI_Slave_Mode驱动程序源码解读(drivers/spi/spi-imx.c)
- 1. 设备树
- 2. 控制器驱动程序
- 2.1 分配一个spi_controller
- 2.2 设置spi_controller
- 2.3 注册spi_controller
- 2.4 硬件操作
- 3. 设备驱动程序
- 3.1 Master模式
- 3.2 Slave模式
一、SPI_Slave_Mode驱动程序框架
- 参考内核源码:
Linux-5.x\drivers\spi\spi-imx.c
- 注意:Linux 4.9的内核未支持SPI Slave Mode
- 参考文档:《Linux_as_an_SPI_Slave_Handouts.pdf》
1. Master和Slave模式差别
SPI(串行外设接口,Serial Peripheral Interface)是一种常用的同步串行通信协议,用于微控制器与各种外设(如传感器、存储器、显示器等)之间的通信。在 SPI 通信中,通常有两个角色:主设备(Master)和从设备(Slave)。这两者之间的区别如下:
1.1 主设备 (Master)
-
定义:主设备负责控制 SPI 通信,生成时钟信号,并管理从设备的选择。
-
功能:
- 时钟产生:主设备生成 SPI 时钟信号(SCK),并控制数据传输的速率。
- 选择从设备:通过选择线(如 Chip Select,CS)选择与其通信的特定从设备。通常在传输数据前,主设备会将 CS 线拉低以使对应的从设备准备接收数据。
- 数据发送和接收:主设备负责读取从设备发送的数据,并向从设备发送命令和数据。
-
例子:微控制器通常充当主设备,与多个传感器或存储器从设备通信。
1.2 从设备 (Slave)
-
定义:从设备被动地响应主设备的命令。它不会主动启动通信,而是等待主设备进行操作。
-
功能:
- 响应主设备:从设备在主设备的指令下工作,并在主设备发送时钟信号时提供数据。
- 数据接收和发送:从设备接收来自主设备的数据,并将其处理后,发送响应或数据回主设备。
-
例子:传感器、EEPROM、LCD 显示屏等通常作为从设备,实现数据的接收和响应。
1.3 示例
以IMX6ULL为例,Master和Slave模式的异同如下图:
-
初始化
- 使用的SPI寄存器有所不同
- 引脚有所不同:Master模式下SS、CLK引脚是输出引脚,Slave模式下这些引脚是输入引脚
-
准备数据:都需要准备好要发送的数据,填充TX FIFO
-
发起传输:Master模式下要主动发起传输,等待发送完成(等待中断)
-
使能接收中断:Slave模式下无法主动发起传输,只能被动地等待,使能接收中断即可
-
中断函数里:读取RXFIFO得到数据
-
传输完成
2. SPI传输概述
2.1 数据组织方式
使用SPI传输时,最小的传输单位是"spi_transfer",对于一个设备,可以发起多个spi_transfer,这些spi_transfer,会放入一个spi_message里。
所以,反过来,SPI传输的流程是这样的:
- 从spi_master的队列里取出每一个spi_message
- 从spi_message的队列里取出一个spi_transfer
- 处理spi_transfer
- 从spi_message的队列里取出一个spi_transfer
2.2 SPI控制器数据结构
参考内核文件:include\linux\spi\spi.h
,Linux中SPI控制器struct spi_controller
,有两套传输方法:
3. SPI Slave Mode数据传输过程
4. 如何编写程序
4.1 设备树
- SPI控制器设备树节点中,需要添加一个空属性:spi-slave
- 要模拟哪类slave设备?需要添加
slave
子节点,这是用来指定slave protocol
4.2 内核相关
-
新配置项:CONFIG_SPI_SLAVE
-
设备树的解析:增加对
spi-slave
、slave
子节点的解析 -
sysfs:新增加
/sys/devices/.../CTLR/slave
,对应SPI Slave handlers
-
新API
- spi_alloc_slave( )
- spi_slave_abort( )
- spi_controller_is_slave( )
-
硬件设置不一样
- master:SS、SCLK引脚是输出引脚
- slave:SS、SCLK引脚是输入引脚
-
传输时等待函数不一样
- master:master主动发起spi传输,它可以指定超时时间,使用函数
wait_for_completion_timeout()
进行等待 - slave:slave只能被动等待传输,它无需指定超时时间,使用函数
wait_for_completion_interruptible()
进行等待 ,使用.slave_abort()
来取消等待
- master:master主动发起spi传输,它可以指定超时时间,使用函数
4.3 简单的示例代码
4.3.1 master和slave驱动示例
4.3.2 master和slave使用示例
对于设置为spi master模式的spi控制器,下面接的是一个一个spi slave设备,我们编写各类spi slave driver,通过spi master的函数读写spi slave 设备。
对于设置为spi slave模式的spi控制器,它是作为一个spi slave设备被其他单板的spi master设备来访问,它如何接收数据、如何提供数据?我们编写对应的spi slave handler。
对于master和slave,有两个重要概念:
- SPI Slave Driver:通过SPI Master控制器跟SPI Slave设备通信
- SPI Slave Handler:通过SPI Slave控制器监听远端的SPI Master
二、SPI_Slave_Mode驱动程序源码解读(drivers/spi/spi-imx.c)
1. 设备树
下图是摘自《Linux_as_an_SPI_Slave_Handouts.pdf》:
2. 控制器驱动程序
在Linux 5.x版本中,SPI控制器的驱动程序仍然使用原理的名字:struct spi_master,但是它已经是一个宏:
#define spi_master spi_controller
这意味着它可以工作于master模式,也可以工作于slave模式。
2.1 分配一个spi_controller
2.2 设置spi_controller
工作于slave模式时,跟master模式最大的差别就是如下函数不一样:
- bitbang.txrx_bufs函数不一样
- 在IMX6ULL中,这个函数为spi_imx_transfer,里面对master、slave模式分开处理
- master模式:使用
spi_imx_pio_transfer
函数 - slave模式:使用
spi_imx_pio_transfer_slave
函数
- 增加了bitbang.master->slave_abort函数
不同厂家的SOC的SPI 驱动实现会有所不同,有的就不会用到bitbang;spi_bitbang 结构体定义了一种软件模拟SPI通信的方式,通过软件控制GPIO引脚来实现SPI通信所需的时序和信号。这种方法通常用于那些没有内置SPI硬件控制器的微控制器或系统上。
2.3 注册spi_controller
无论是master模式,还是slave模式,注册函数时一样的:
在spi_bitbang_start内部,会处理设备树中的子节点,创建并注册spi_device:
spi_bitbang_start
spi_register_master
spi_register_controller
of_register_spi_devices
对于master模式,设备树中的子节点对应真实的spi设备;但是对于slave模式,这些子节点只是用来选择对应的spi slave handler:就是使用哪个驱动来模拟spi slave设备,比如:
/* drivers/spi/spi-slave-time.c
* SPI从机处理器,接收上一条SPI消息后报告系统运行时间
*
* 该SPI从机处理器以二进制格式和网络字节顺序发送上一条SPI消息接收时间
* 的两个32位无符号整数,分别表示自系统启动以来的秒数和小数秒数(以微秒为单位)。
*
* 版权所有 (C) 2016-2017 Glider bvba
*
* 本文件受 GNU 通用公共许可证条款和条件的约束。请参阅此存档主目录中的 "COPYING" 文件以获取更多详细信息。
*
* 使用方法(假设 /dev/spidev2.0 对应远程系统上的 SPI 主设备):
*
* # spidev_test -D /dev/spidev2.0 -p dummy-8B
* spi 模式: 0x0
* 每字的位数: 8
* 最大速度: 500000 Hz (500 KHz)
* RX | 00 00 04 6d 00 09 5b bb ...
* ^^^^^ ^^^^^^^^
* 秒数 微秒数
*/
#include <linux/completion.h>
#include <linux/module.h>
#include <linux/sched/clock.h>
#include <linux/spi/spi.h>
struct spi_slave_time_priv {
struct spi_device *spi;
struct completion finished;
struct spi_transfer xfer;
struct spi_message msg;
__be32 buf[2];
};
static int spi_slave_time_submit(struct spi_slave_time_priv *priv);
/**
* @brief SPI从机时间补偿函数
*
* 当SPI从机传输完成或者发生错误时,此函数被调用以处理相应的后续操作。主要功能包括检查上一次传输状态,
* 并根据状态决定是否继续提交时间补偿处理或者终止并通知上层。
*
* @param arg 传递给此函数的参数,实际类型为struct spi_slave_time_priv*,用于访问SPI从机时间补偿相关的数据。
*/
static void spi_slave_time_complete(void *arg)
{
// 将参数转换为相应的结构体指针,以便访问私有数据
struct spi_slave_time_priv *priv = arg;
// 存储操作结果的变量
int ret;
// 检查上一次SPI从机消息的状态,如果存在错误,则不继续操作
ret = priv->msg.status;
if (ret)
goto terminate;
// 尝试提交下一次时间补偿处理,如果提交失败,则跳转到terminate块
ret = spi_slave_time_submit(priv);
if (ret)
goto terminate;
// 如果没有错误,正常返回
return;
terminate:
// 当出现错误或者传输完成时,记录日志并通知上层处理已经完成
dev_info(&priv->spi->dev, "Terminating\n");
complete(&priv->finished);
}
/**
* 提交SPI从机时间同步请求
*
* 该函数用于从SPI从机角度,向SPI主机请求时间同步。它会将本地时钟和剩余微秒封装成数据,
* 并通过SPI异步传输的方式发送给SPI主机。时间同步对于需要在分布式系统中保持时间一致性的
* 应用非常关键。
*
* @param priv 指向SPI从机时间私有数据结构的指针,包含必要的传输数据和配置信息。
* @return 返回spi_async调用的结果,如果失败则记录错误信息。
*/
static int spi_slave_time_submit(struct spi_slave_time_priv *priv)
{
// 剩余的微秒部分
u32 rem_us;
// 返回值
int ret;
// 时间戳,以纳秒为单位
u64 ts;
// 获取当前的本地时钟值,以纳秒为单位
ts = local_clock();
// 从纳秒转换到微秒,并将结果保存到rem_us
rem_us = do_div(ts, 1000000000) / 1000;
// 将纳秒和微秒部分分别转换为大端字节序,并存入缓冲区
priv->buf[0] = cpu_to_be32(ts);
priv->buf[1] = cpu_to_be32(rem_us);
// 初始化SPI消息,只包含一个传输操作
spi_message_init_with_transfers(&priv->msg, &priv->xfer, 1);
// 设置SPI消息的完成回调函数和上下文数据
priv->msg.complete = spi_slave_time_complete;
priv->msg.context = priv;
// 异步发送SPI消息
ret = spi_async(priv->spi, &priv->msg);
// 如果发送失败,记录错误信息
if (ret)
dev_err(&priv->spi->dev, "spi_async() failed %d\n", ret);
return ret;
}
/**
* spi_slave_time_probe - SPI从机时间延迟自动探测函数
* @spi: 指向SPI设备结构体的指针
*
* 本函数用于在SPI从机模式下,自动探测和调整时间延迟参数,以确保SPI通信的正确性和稳定性。
* 它通过分配私有数据空间,初始化必要的结构,然后提交一个SPI传输请求来进行时间延迟探测。
* 若探测过程出错,则返回相应的错误码。
*
* 返回: 0表示成功,负值表示遇到的相应错误。
*/
static int spi_slave_time_probe(struct spi_device *spi)
{
struct spi_slave_time_priv *priv;
int ret;
/* 分配内存用于存储私有数据,包括时间延迟探测所需的各种数据 */
priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* 初始化私有数据结构中的成员 */
priv->spi = spi;
init_completion(&priv->finished);
priv->xfer.tx_buf = priv->buf;
priv->xfer.len = sizeof(priv->buf);
/* 提交SPI传输请求,进行时间延迟探测 */
ret = spi_slave_time_submit(priv);
if (ret)
return ret;
/* 将私有数据结构与SPI设备驱动模型数据关联,以便后续使用 */
spi_set_drvdata(spi, priv);
return 0;
}
/**
* 从SPI设备中移除slave定时器功能
*
* 本函数主要用于从SPI设备中移除slave定时器功能在一些情况下,可能需要
* 中止当前的SPI传输操作,并确保所有相关的定时器处理已经完成此函数通过
* 调用spi_slave_abort来尝试中止当前的SPI操作,并通过等待完成来确保所有
* 相关的定时器处理已经结束这对于在设备移除或者重新配置时清理资源非常有用
*
* @param spi 指向SPI设备结构的指针该参数是必需的,用于标识需要进行操作的SPI设备
*
* @return 该函数返回0,表示执行成功这里不使用其他的返回值,因为当前的实现
* 不需要区分更多的错误类型
*
* 注意:该函数假定传入的spi参数是非空的,并且spi所指向的设备已经正确初始化并
* 具有slave定时器功能
*/
static int spi_slave_time_remove(struct spi_device *spi)
{
// 获取与SPI设备相关的私有数据
struct spi_slave_time_priv *priv = spi_get_drvdata(spi);
// 尝试中止当前的SPI操作
spi_slave_abort(spi);
// 等待直到所有定时器相关的操作完成
wait_for_completion(&priv->finished);
return 0;
}
static struct spi_driver spi_slave_time_driver = {
.driver = {
.name = "spi-slave-time",
},
.probe = spi_slave_time_probe,
.remove = spi_slave_time_remove,
};
module_spi_driver(spi_slave_time_driver);
MODULE_AUTHOR("Geert Uytterhoeven <geert+renesas@glider.be>");
MODULE_DESCRIPTION("SPI slave reporting uptime at previous SPI message");
MODULE_LICENSE("GPL v2");
2.4 硬件操作
3. 设备驱动程序
3.1 Master模式
3.2 Slave模式
参考代码:Linux-5.x\drivers\spi\spi-slave-time.c
本文章参考了韦东山老师驱动大全部分笔记,其余内容为自己整理总结而来。水平有限,欢迎各位在评论区指导交流!!!😁😁😁