12.1SPI驱动框架

news2024/12/24 1:59:31

SPI硬件基础

总线拓扑结构

在这里插入图片描述

引脚含义

DO(MOSI):Master Output, Slave Input, SPI主控用来发出数据,SPI从设备用来接收数据
DI(MISO) :Master Input, Slave Output, SPI主控用来发出数据,SPI从设备用来接收数据
SCK: Serial Clock,时钟
CS:Chip Select,芯片选择引脚

SPI模式

在SPI协议中,有两个值来确定SPI的模式,分别是:
CPOL(时钟极性)表示SPI CLK的初始电平,0为电平,1为高电平
CPHA (时钟相位)即第一个还是第二个时钟沿采样数据,0为第一个时钟沿,1为第二个时钟沿

CPOL	CPHA	模式	含义
0		0		0		SPI CLK初始电平为低电平,在第一个时钟沿采样数据
0		1		1		SPI CLK初始电平为低电平,在第二个时钟沿采样数据
1		0		2		SPI CLK初始电平为高电平,在第一个时钟沿采样数据
1		1		3		SPI CLK初始电平为高电平,在第二个时钟沿采样数据

此外部分SPI控制器还可以配置数据流顺序,分别是MSB(高位在前)、LSB(低位在前)

SPI 驱动框架组成

SPI驱动框架包括以下几个部分:

  1. SPI 核心;管理 SPI 控制器驱动驱动、 SPI 设备驱动、 SPI 设备,此部分由linux提供
  2. SPI 控制器驱动:用于驱 SOC 上的SPI控制器,此部分由主控芯片厂家提供
  3. SPI 设备:用于描述 SPI 设备信息和此设备对SPI总线的配置,一般在设备树中编写。
  4. SPI 设备驱动:用于驱动 SPI 总线上的设备,一移植厂家驱动或自己编写
  5. spidev :一个通用的SPI设备驱动,提供一种用户空间访问SPI总线的功能(需要设备树支持)

SPI 控制器驱动

对象 struct spi_controller 表示一个 SPI 控制器,其核心成员如下:

	//表示继承于struct device,其中的of_node需要指定,否则无法解析设备树
	struct device dev;
	//总线编号
	s16 bus_num;
	//片选数量,从设备的片选号不能大于这个数量
	u16 num_chipselect;
	//控制器所支持的模式
	u32 mode_bits;
	//最小传输速率
	u32 min_speed_hz;
	//最大传输速率
	u32 max_speed_hz;
	//标志,表示控制器的一些特性
	u16 flags;
	//指示此控制器是否时从设备
	bool slave;
	//配置SPI控制器
	int (*setup)(struct spi_device *spi);
	//设置CS的时序
	void (*set_cs_timing)(struct spi_device *spi, u8 setup_clk_cycles, 
		u8 hold_clk_cycles, u8 inactive_clk_cycles);
	//SPI数据包传输
	int (*transfer)(struct spi_device *spi, struct spi_message *mesg);
	int (*transfer_one)(struct spi_controller *ctlr, struct spi_device *spi, struct spi_transfer *transfer);
	//维护 queue 的自旋锁
	spinlock_t queue_lock;
	//管理 SPI 控制器需要传输的 spi_message,一个 spi_message 表示一系列需要传输的数据
	struct list_head queue;
	//SPI 控制器正在传输的 spi_message
	struct spi_message *cur_msg;
	//片选引脚编号列表
	int *cs_gpios;
	//片选引脚描述符列表
	struct gpio_desc **cs_gpiods;
	//是否使用gpio描述符接口控制控制片选引脚
	bool use_gpio_descriptors;

注册、注销SPI控制器驱动

SPI 控制器驱动的核心就是完成对 struct spi_controller 的分配和初始化,然后将其添加到系统中,如下是分配 SPI 控制器并向系统添加和删除 SPI 控制器驱动的函数:

	//分配SPI控制器
	struct spi_controller *spi_alloc_master(struct device *host, unsigned int size)
	//释放SPI控制器
	void spi_controller_put(struct spi_controller *ctlr)
	#define spi_master_put(_ctlr) spi_controller_put(_ctlr)
	//注册SPI控制器
	int spi_register_master(struct spi_controller *ctlr)
	int spi_register_controller(struct spi_controller *ctlr)
	int devm_spi_register_master(struct device *dev, struct spi_controller *ctlr);
	int devm_spi_register_controller(struct device *dev, struct spi_controller *ctlr);
	//注销SPI控制器
	void spi_unregister_master(struct spi_controller *ctlr)

SPI 设备和驱动

SPI 设备用 struct spi_device 表示,它用于描述一个 SPI 设备, SPI 设备的驱动用 struct spi_driver 表示,它用于描述 SPI 设备的驱动。
struct spi_device 的核心成员如下:

	//继承的device对象
	struct device dev;
	//所属控制器
	struct spi_controller *controller;
	//最大总线频率
	u32 max_speed_hz;
	//片选号,与控制器的片选列表对应
	u8 chip_select;
	//总线模式
	u32 mode;
	//片选引脚,不使用时为-ENOENT
	int cs_gpio;
	//驱动程序的名称或别名
	char modalias[SPI_NAME_SIZE];
	//强制匹配字符串
	const char *driver_override;

struct spi_driver 的核心成员如下:

	//ID匹配表
	const struct spi_device_id *id_table;
	//设备和驱动匹配成功执行
	int (*probe)(struct spi_device *spi);
	//设备或驱动卸载执行
	int (*remove)(struct spi_device *spi);
	//继承的device_driver
	struct device_driver driver;

注册/注销 SPI 设备和驱动

可以使用如下函数注册/注销 SPI 设备:

	//注册PSI设备
	int spi_add_device(struct spi_device *spi);
	struct spi_device *spi_new_device(struct spi_controller *, struct spi_board_info *);
	//注销SPI设备
	void spi_unregister_device(struct spi_device *spi);

可以使用如下函数注册/注销 SPI 设备驱动:

	//注册SPI设备驱动
	int spi_register_driver(struct spi_driver *sdrv)
	//注销SPI设备驱动
	void spi_unregister_driver(struct spi_driver *sdrv)

SPI 设备和驱动匹配过程

SPI 注销基于总线设备驱动模型框架,在向 SPI 添加设备或驱动时会执行 SPI 总线的设备去匹配函数,其函数内容如下:

	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);

		//采用driver_override进行强制匹配
		if (spi->driver_override)
			return strcmp(spi->driver_override, drv->name) == 0;

		//设备树匹配
		if (of_driver_match_device(dev, drv))
			return 1;

		//ACPI匹配
		if (acpi_driver_match_device(dev, drv))
			return 1;

		//id_table匹配
		if (sdrv->id_table)
			return !!spi_match_id(sdrv->id_table, spi);

		//采用驱动程序的名称匹配
		return strcmp(spi->modalias, drv->name) == 0;
	}

SPI 控制器注册过程

构造并初始化一个 struct spi_controller 对象
	调用 spi_register_controller 注册 SPI 控制器
		使用 spi_controller_check_ops 检查必要参数是否配置正确
		通过 idr_alloc 将 SPI 控制器放入到一个 idr 对象中(如果 bus_num 无效会尝试从设备树中获取)
		初始化 struct spi_controller 各种资源
		从设备树中解析片选引脚,这里根据配置选择gpio编号模式和gpio描述符模式,若选择个屁哦编号模式在调用 spi_register_controller 函数前对相应引脚进行请求操作
		使用 device_add 将 struct spi_controller 的 dev 添加到内核
		若未提供 transfer 函数,但是提供了 transfer_one 函数或者 transfer_one_message 函数则调用 spi_controller_initialize_queue 函数使能队列参数模式
			将 struct spi_controller 对象的 transfer 设置为 spi_queued_transfer
			初始化 SPI 队列模式相关资源
		将 struct spi_controller 放入 spi_controller_list 链表中
		调用 of_register_spi_devices 解析 SPI 设备树
			遍历 spi 控制器的设备树子节点,并调用 of_register_spi_device 完成一个子节点的解析
				使用 spi_alloc_device 分配一个 SPI 设备
				使用 of_modalias_node 读取设备树的 compatible 来配置 SPI 设备的 modalias 参数
				使用 of_spi_parse_dt 解析设备树,并根据设备树配置 SPI 设备
				使用 spi_add_device 将设备注册到 SPI 总线
					检查片选是否合法
					调用 spi_dev_set_name ,利用总线名称和片选编号配置设备名称
					调用 bus_for_each_dev 遍历 SPI 设备,检查有设备是否存再片选相同问题(通过回调检查)
					绑定片选引脚
					调用 spi_setup 设置 SPI 设备
					调用 device_add 将设备加到系统(如果指定了总线会注册到对应总线,并进行设备驱动匹配,这里应该是 SPI 总线)

SPI 驱动注册过程

构造并初始化 struct spi_driver 对象
	调用 spi_register_driver  注册 SPI 驱动
		再次配置 struct spi_driver 对象,主要是内部 driver 成员的 bus 、 probe 、 remove 等
		调用 driver_register 注册驱动,这里指定的 bus 为 spi_bus_type 所以会注册到 spi_bus_type 总线中,然后执行设备驱动匹配操作

SPI 驱动传输数据

SPI 驱动采用 struct spi_transfer 描述一个数据包,其核心成员如下:

	//发送缓存
	const void *tx_buf;
	//接收缓存
	void *rx_buf;
	//缓存长度
	unsigned len;
	//此次传输完成后是否重新获取片选并配置
	unsigned cs_change;
	//片选无效时间
	u16 cs_change_delay;
	//cs_change_delay的单位,由us、ns、clk3种选择
	u8 cs_change_delay_unit;
	//字长
	u8 bits_per_word;
	//每个字传输完成后延迟多少us
	u8 word_delay_usecs;
	//每个字传输完成后延迟多少个时钟周期
	u16 word_delay;
	//在此传输后更改片选状态前之前延迟多少us
	u16 delay_usecs;
	//时钟速率
	u32 speed_hz;
	//spi_transfer链表节点
	struct list_head transfer_list;

struct spi_transfer 描述的是单个数据包,在 SPI 框架中还需要利用 struct spi_message 将一个或多个数据包组合成一个 message 后才能进行发送, struct spi_message 的核心成员如下:

	//要传输的数据包链表
	struct list_head transfers;
	//所属的 SPI 设备,其中包含了 SPI 设备的各种信息,如SPI控制器、片选、模式、时钟速率等
	struct spi_device *spi;
	//传输完成回调函数,用于异步传输
	void (*complete)(void *context);
	//用于再 SPI 控制器中形成一个队列,方便控制器管理需要发送的数据
	struct list_head queue;

SPI 传输数据相关的函数如下:

	//初始化一个SPI message
	void spi_message_init(struct spi_message *m)
	//向SPI message中添加一个数据包
	void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
	//启动SPI,进行同步传输
	int spi_sync(struct spi_device *spi, struct spi_message *message)
	//启动SPI,进行异步传输,传输完成后调用void (*complete)(void *context)函数
	int spi_async(struct spi_device *spi, struct spi_message *message)

SPI 传输数据的流程

以 spi_sync 为例, SPI 驱动框架提供了两种 spi_message 传输方案,分别如下:

  1. 阻塞模式
    在注册 SPI 控制器时如果提供了 transfer 函数即工作在阻塞模式。
构造 spi_message (发送过程可能会使用DMA)
	调用 spi_sync 进行传输
		再 spi_sync 中加锁后直接调用 __spi_sync (这里采用的互斥量加锁)
			调用 __spi_validate 检查 spi_message
			为 spi_message 绑定传输完成回调函数和 SPI 控制器
			检查 spi_controller 的 transfer 是否等于 spi_queued_transfer 函数,若不等于则调用 spi_async_locked 函数
				然后在 spi_async_locked 中加锁后调用 __spi_async (这里采用的自旋锁加锁)
					通过 spi_controller 控制器中的 transfer 指针调用驱动层的传输函数(应该是一个非阻塞函数,在控制器参数完成后调用 spi_message 传输完成回调函数)
			调用 wait_for_completion 等待传输完成
			获取传输结果并返回
  1. 队列模式
    在注册 SPI 控制器时不提供 transfer 函数,但提供了 transfer_one 函数,即工作在队列模式。
构造 spi_message ,因为发送过程可能会使用DMA,所以内存最好使用 kmalloc 分配
	调用 spi_sync 进行传输
		再 spi_sync 中加锁后直接调用 __spi_sync (这里采用的互斥量加锁)
			调用 __spi_validate 检查 spi_message
			为 spi_message 绑定传输完成回调函数和 SPI 控制器
			检查 spi_controller 的 transfer 是否等于 spi_queued_transfer ,等于则利用自旋锁加锁,然后调用 __spi_queued_transfer 将 spi_message 放入 spi_controller 的队列中
			调用 __spi_pump_messages 启动传输
				通过 spi_controller 的 cur_msg 判断是否正在传输,若正在传输则直接返回
				从 spi_controller 中取出的一个 spi_message ,并将其从队列中删除
				调用 spi_controller 控制器中的 transfer_one_message 传输一个 spi_message (默认的传输函数是 spi_transfer_one_message )
					在 spi_transfer_one_message 函数中多次调用 spi_controller 的 transfer_one 去完成一个 spi_message 的传输(每次调用 transfer_one 后还需要调用 spi_transfer_wait 去等待transfer_one 传输完成)
					传输完成后再调用 spi_finalize_current_message
						在 spi_finalize_current_message 中启动一个内核线程传输剩余的 spi_message ,然后执行当前 spi_message 的传输完成回调函数
			调用 wait_for_completion 函数等待 spi_message 传输完成
			获取传输结果并返回

SPI 设备树节点编写

在那条 SPI 总线下挂载设备就在那条总线的设备树节点下添加对应设备的子节点,节点命名规则 [标签:]名称[@地址],节点内容必须包含 reg 属性、 compatible 属性、 spi-max-frequency 属性, reg 属性用于描述片选索引, compatible 属性用于设备和驱动的匹配, spi-max-frequency 用于描述设备可支持的最大 SPI 总线频率,如下是在 SPI1 中添加一个 icm20608 设备节点的示例:

&spi1 {
	//描述SPI控制器引脚和片选引脚,并使能I2C控制器
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&spi1_pins_a>;
	pinctrl-1 = <&spi1_sleep_pins_a>;
	cs-gpios = <&gpioz 3 GPIO_ACTIVE_LOW>;
	status = "okay";

	//描述icm20608设备
	spidev: icm20608@0 {
		compatible = "alientek,icm20608";
	 	reg = <0>;						/* CS #0 */
	 	spi-max-frequency = <8000000>;	/* 最大时钟频率 */
		spi-cpha;						/* cpha=1 */
		spi-cpol;						/* cpol=1 */
		spi-cs-high;					/* 片选高电平有效 */
		spi-lsb-first;					/* 低位在前 */
		spi-rx-bus-width = <1>;			/* 接收数据线宽度为1B */
		spi-tx-bus-width = <1>;			/* 发送数据线宽度为1B */
	};
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1372604.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【设计模式】创建型模式之单例模式(Golang实现)

定义 一个类只允许创建一个对象或实例&#xff0c;而且自行实例化并向整个系统提供该实例&#xff0c;这个类就是一个单例类&#xff0c;它提供全局访问的方法。这种设计模式叫单例设计模式&#xff0c;简称单例模式。 单例模式的要点&#xff1a; 某个类只能有一个实例必须…

家用洗地机怎么选?家用洗地机排名

现代很多年轻人常常为家庭卫生问题而感到头痛。一整天的工作之后&#xff0c;回到家中还得花费大量时间来处理地面的清理工作&#xff0c;包括吸尘和拖地等繁琐的任务。这些任务让人感到相当烦躁&#xff0c;尤其是对于有小孩的家庭来说&#xff0c;地板上的油污和食物残渣经常…

一键修复所有dll缺失的工具,dll修复工具下载使用教程

在计算机使用过程中&#xff0c;我们经常会遇到各种软件或系统错误提示&#xff0c;其中最常见的就是“找不到指定的模块”或“无法找到某某.dll文件”。Dll是动态链接库的缩写&#xff0c;它是Windows操作系统中的重要组成部分&#xff0c;负责提供各种功能和资源给应用程序使…

自学Python,需要注意哪些?

为什么要学习Python&#xff1f; 在学习Python之前&#xff0c;你不要担心自己没基础或“脑子笨”&#xff0c;我始终认为&#xff0c;只要你想学并为之努力&#xff0c;就能学好&#xff0c;就能用Python去做很多事情。在这个喧嚣的时代&#xff0c;很多技术或概念会不断兴起…

骑砍战团MOD开发(37)-module_skin.py皮肤系统

一.脸谱代码 与地形代码类似,骑砍引擎将人物头部模型采用脸谱代码制作,以实现不同脸谱的动态拼接以及捏脸等功能。 在人物捏脸界面CtrlE可编辑脸谱代码,可配置肤色,发型,年龄等相关参数.在module_troops.py可实现不同兵种脸谱. #第12 13个参数进行脸谱参数配置 # Each troop …

C++内存管理机制(侯捷)笔记1

C内存管理机制&#xff08;侯捷&#xff09; 本文是学习笔记&#xff0c;仅供个人学习使用。如有侵权&#xff0c;请联系删除。 参考链接 Youtube: 侯捷-C内存管理机制 Github课程视频、PPT和源代码: https://github.com/ZachL1/Bilibili-plus 第一讲primitives的笔记 截至…

N卡可以点亮但是A卡无法点亮故障解决记录

目录 关键词平台说明一、故障现象二、排查过程三、根本原因四、措施3.1进入boot后更改CSM启动为下图所示。3.2更改PCIE自动为3.0 关键词 英伟达、AMD、显卡、无法点亮 平台说明 项目Value主板铭瑄 TZZ_H61ITX 2.5GCPU12400f显卡RX6600 RTX4060附加设备PCIE 延长线–显卡 一…

C#入门篇(一)

变量 顾名思义就是变化的容器&#xff0c;即可以用来存放各种不同类型数值的一个容器 折叠代码 第一步&#xff1a;#region 第二步&#xff1a;按tab键 14种数据类型 有符号的数据类型 sbyte&#xff1a;-128~127 short&#xff1a;-32768~32767 int&#xff1a;-21亿多~21亿多…

尝试OmniverseFarm的最基础操作

目标 尝试OmniverseFarm的最基础操作。本地机器作为Queue和Agent&#xff0c;同时在本地提交任务。 主要参考了官方文档&#xff1a; Farm Queue — Omniverse Farm latest documentation Farm Agent — Omniverse Farm latest documentation Farm Examples — Omniverse Far…

微机原理常考简答题总结

一&#xff0c;8086和8088这两个微处理器在结构上有什么异同&#xff1f; &#xff08;1&#xff09;共同点&#xff1a;内部均由EU、BIU组成&#xff0c;结构基本相同&#xff1b;寄存器等功能部件均为16位&#xff1b;内部数据通路为16位&#xff1b;指令系统相同。 &#x…

让测试人头疼的web自动化之验证码识别彻底解决方案

验证码识别解决方案 对于web应用程序来讲&#xff0c;处于安全性考虑&#xff0c;在登录的时候&#xff0c;都会设置验证码&#xff0c;验证码的类型种类繁多&#xff0c;有图片中辨别数字字母的&#xff0c;有点击图片中指定的文字的&#xff0c;也有算术计算结果的&#xff…

vsCode输出控制台中文乱码解决

在tasks.json里的args中添加 "-fexec-charsetGBK", // 处理mingw中文编码问题 "-finput-charsetUTF-8",// 处理mingw中文编码问题

现代操作系统复习笔记【核心考点知识+重点复习题】

文章目录 一、核心考点基础知识第一章 概述1、操作系统的基本概念、基本功能2、分时系统、批处理系统、实时系统的主要特征3、用户接口、系统调用过程4、单到与多道程序技术5、操作系统虚拟机体系结构6、CPU工作模式&#xff1b;7、部分课后习题 第二章 进程与线程1、进程的基本…

Tsmaster使用笔记整理

选择厂商 根据你所选择的CAN分析仪的厂商&#xff0c;确定你的厂商设备设置。 我一般会选择PEAK&#xff0c;和 ZLG多一点&#xff0c;其他的没有用过。除了上图中的&#xff0c;市面上的CAN分析仪还有CANanlyst、广成科技、创芯科技等&#xff0c;但它们都不能在Tsmaster上使…

如何利用 NFTScan Portfolio 功能分析钱包 NFT 持仓

随着 NFT 市场的扩大和投资者的增加&#xff0c;追踪和管理大量 NFT 资产正变得越来越复杂&#xff0c;无论是新手还是资深投资者&#xff0c;都需要借助实时的 NFT 数据作为判断依据。因此&#xff0c;一个能够全面分析 NFT 钱包持仓的工具就显得尤为重要。帮助投资者掌握自身…

Linux C/C++ 显示NIC流量统计信息

NIC流量统计信息是由操作系统维护的。当数据包通过NIC传输时&#xff0c;操作系统会更新相关的计数器。这些计数器记录了数据包的发送和接收数量、字节数等。通过读取这些计数器&#xff0c;我们可以获得关于网络流量的信息。 为什么需要这些信息? 可以使用这些信息来监控网络…

Linux的网络服务DHCP

一.了解DHCP服务 1.1 DHCP定义 DHCP&#xff08;动态主机配置协议&#xff09;是一个局域网的网络协议。指的是由服务器控制一段IP地址范围&#xff0c;客户机登录服务器时就可以自动获得服务器分配的IP地址和子网掩码。默认情况下&#xff0c;DHCP作为Windows Server的一个服…

【Python】使用tkinter设计开发Windows桌面程序记事本(2)

上一篇&#xff1a;【Python】使用tkinter设计开发Windows桌面程序记事本&#xff08;1&#xff09;-CSDN博客 下一篇&#xff1a; 作者发炎 此代码模块是继承上一篇文章的代码模块的基础上开始设计开发的。 如果不知道怎么新建"记事本项目"文件夹&#xff0c;请参…

程序员试用期转正工作总结

一、试用期工作总结 在公司的三个月试用期中&#xff0c;我完成了以下工作&#xff1a; 完成了XX个功能模块的开发&#xff0c;包括XX模块、XX模块和XX模块。参与了XX个项目的开发和上线&#xff0c;其中XX项目、XX项目和XX项目是我主导的。优化了现有系统的性能&#xff0c;特…

跟着我学Python进阶篇:02.面向对象(上)

往期文章 跟着我学Python基础篇&#xff1a;01.初露端倪 跟着我学Python基础篇&#xff1a;02.数字与字符串编程 跟着我学Python基础篇&#xff1a;03.选择结构 跟着我学Python基础篇&#xff1a;04.循环 跟着我学Python基础篇&#xff1a;05.函数 跟着我学Python基础篇&#…