自用学习笔记记录整理

自用笔记整理



一、SPI协议

1. 简介

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

标准的SPI四根线如下:
CS:片选信号线。
SCK:串行时钟
MOSI:主输出从输入
MISO:主输入从输出

2.四种工作模式

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

①、CPOL=0,串行时钟空闲状态为低电平。
②、CPOL=1,串行时钟空闲状态为高电平。
③、CPHA=0,串行时钟的第一个跳变沿(上升沿或下降沿)采集数据。
④、CPHA=1,串行时钟的第二个跳变沿(上升沿或下降沿)采集数据。

3.传输机制

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

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

4.DMA和FIFO

传输 32bytes 以下使用 FIFO,传输 32bytes 以上使用 DMA。DMA 可以自动发起多次传输,一次最大 256K 。

二、SPI驱动

1.架构分析

在这里插入图片描述

  1. SPI 控制器驱动程序
    SPI 控制器不用关心设备的具体功能,它只负责把上层协议驱动准备好的数据按 SPI 总线的时序要求发送给 SPI 设备,同时把从设备收到的数据返回给上层的协议驱动,因此,内核把 SPI 控制器的驱动程序独立出来。
    SPI 控制器驱动负责控制具体的控制器硬件,诸如 DMA 和中断操作等等,因为多个上层的协议驱动可能会通过控制器请求数据传输操作,所以,SPI 控制器驱动同时也要负责对这些请求进行队列管理,保证先进先出的原则。

  2. SPI 通用接口封装层
    为了简化 SPI 驱动程序的编程工作,同时也为了降低【协议驱动程序】和【控制器驱动程序】的耦合程度,内核把控制器驱动和协议驱动的一些通用操作封装成标准的接口,加上一些通用的逻辑处理操作,组成了 SPI 通用接口封装层。
    这样的好处是,对于控制器驱动程序,只要实现标准的接口回调 API,并把它注册到通用接口层即可,无需直接和协议层驱动程序进行交互。而对于协议层驱动来说,只需通过通用接口层提供的 API 即可完成设备和驱动的注册,并通过通用接口层的 API 完成数据的传输,无需关注 SPI 控制器驱动的实现细节。

  3. SPI 协议驱动程序
    SPI 设备的具体功能是由 SPI 协议驱动程序完成的,SPI 协议驱动程序了解设备的功能和通信数据的协议格式。向下,协议驱动通过通用接口层和控制器交换数据,向上,协议驱动通常会根据设备具体的功能和内核的其它子系统进行交互。
    例如,和 MTD 层交互以便把 SPI 接口的存储设备实现为某个文件系统,和 TTY 子系统交互把 SPI 设备实现为一个 TTY 设备,和网络子系统交互以便把一个 SPI 设备实现为一个网络设备。如果是一个专有的 SPI 设备,我们也可以按设备的协议要求,实现自己的专有协议驱动。

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

2.代码分析

1.SPI 通用接口层

  • SPI 通用接口层把具体的 SPI 设备的协议驱动和 SPI 控制器驱动连接在一起。
  • 负责 SPI 系统与 Linux 设备模型相关的初始化工作。
  • 为协议驱动和控制器驱动提供一系列的标准接口 API 及其数据结构。
  • SPI 设备、SPI 协议驱动、SPI 控制器的数据抽象
  • 协助数据传输而定义的数据结构

代码如下(示例):

static int __init spi_init(void)
{
	int	status;
	
	buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL); 	
	if (!buf) {
		status = -ENOMEM;
		goto err0;
	}

	status = bus_register(&spi_bus_type);
	if (status < 0)
		goto err1;

	status = class_register(&spi_master_class);
	if (status < 0)
		goto err2;

	if (IS_ENABLED(CONFIG_SPI_SLAVE)) {
		status = class_register(&spi_slave_class);
		if (status < 0)
			goto err3;
	}

	if (IS_ENABLED(CONFIG_OF_DYNAMIC))
		WARN_ON(of_reconfig_notifier_register(&spi_of_notifier));
	if (IS_ENABLED(CONFIG_ACPI))
		WARN_ON(acpi_reconfig_notifier_register(&spi_acpi_notifier));

	return 0;

err3:
	class_unregister(&spi_master_class);
err2:
	bus_unregister(&spi_bus_type);
err1:
	kfree(buf);
	buf = NULL;
err0:
	return status;
}

2.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。
在这里插入图片描述

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

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

init flow
init flow
spi_register_master的调用
spi_register_master的调用
队列化的工作机制及过程
队列化的工作机制及过程1
队列化的工作机制及过程2
当协议驱动程序通过 spi_async 发起一个 message 请求时,队列化和工作线程被激活,触发一些列的操作,最终完成 message 的传输操作。
spi_sync 与 spi_async 类似,只是有一个等待过程。

3.相关数据结构

spi_master

struct spi_master {
	struct device dev;	//Linux 驱动模型中的设备
 	struct list_head list;	//此 spi_master 设备在全局 spi_master 链表中的节点
 	s16   bus_num;	//此 spi_master 编号
 	u16   num_chipselect;	//此 spi_master 支持的片选信号数量
 	u16   dma_alignment;	//dma 地址对齐
 	u16   mode_bits;	//此 spi_master 支持传输的 mode
 	u32   bits_per_word_mask;
 	
 	/* limits on transfer speed */
 	u32   min_speed_hz;
 	u32   max_speed_hz;

 	/* other constraints relevant to this driver */
	u16   flags;
	
	 /* lock and mutex for SPI bus locking */
	spinlock_t  bus_lock_spinlock;//总线自旋锁
	struct mutex  bus_lock_mutex;//总线互斥锁
	
	  
	bool   bus_lock_flag;	//总线是否处于 lock 状态
	
	 
	int (*setup)(struct spi_device *spi);	 //准备传输,设置传输的参数
	void (*set_cs)(struct spi_device *spi, bool enable);
	int (*set_cs_timing)(struct spi_device *spi);
	
	int (*prepare_message)(struct spi_controller *ctlr,
			       struct spi_message *message);
  	//传输数据
	int (*transfer_one)(struct spi_controller *ctlr, struct spi_device *spi,
			    struct spi_transfer *transfer);
	  // 设备 release 时的清除工作
	void (*cleanup)(struct spi_device *spi);
	
	bool (*can_dma)(struct spi_master *master,
	        		struct spi_device *spi,
	        		struct spi_transfer *xfer);
	
	bool   queued;//是否采用系统的序列化传输
	struct kthread_worker kworker;//序列化传输时的线程 worker
	struct task_struct *kworker_task;//序列化传输的线程
	struct kthread_work pump_messages;//序列化传输时的处理函数
	spinlock_t  queue_lock;//序列化传输时的queue_lock
	struct list_head queue;//序列化传输时的 msg 队列头
	struct spi_message *cur_msg;//序列化传输时当前的 msg
	bool   idling;
	bool   busy;//序列化传输时线程是否处于busy状态
	bool   running;//序列化传输时线程是否在运行
	bool   rt;//是否实时传输
	  ......
	
	int (*prepare_transfer_hardware)(struct spi_master *master);
	
	  //一个 msg 的传输实现
	int (*transfer_one_message)(struct spi_master *master,
	        	struct spi_message *mesg);
	  ......
	
	 /* gpio chip select */
	 int   *cs_gpios;
	  ......
};

参考链接

公众号:嵌入式Linux系统开发 https://mp.weixin.qq.com/s/ZTMFIltanYFQzey4FbVcsw

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值