一、说明
spi通信协议的原理、硬件之类的,请参考其他博主的文章,网上很多大佬都写得比较详细,通俗易懂。Linux下的spi框架的使用部分,可以参考其他的博主文章,也可以参考笔者之前写的文章。linux驱动系列学习之spi子系统(五)
本文介绍的是Linux下的spi框架,更多的集中在对框架的分析、运行逻辑的介绍。
本文使用的Linux内核源码时Linux5.4.31版本。
二、spi框架
1. 整体结构
介绍spi框架之前,先来看一张整体图。图1是spi框架的整体结构图。
图1
我们使用的spi_register_driver在图1的右下角。使用这个函数去注册spi驱动,.of_match_table 里面写着设备树上的compatible属性,用于match函数里面进行匹配过程。spi_register_driver里面,添加几个函数,spi_bus_type里面的spi_match_device用于和设备树上的节点进行匹配,匹配完成之后会调用spi_drv_probe函数,spi_drv_probe函数调用sdrv->probe即驱动里面注册的probe函数。具体的可以参考博主写的platform总线:Linux驱动系列学习之platform。这里不做具体分析。
2.结构重要的结构体
spi框架里面有几个比较重要的结构体struct spi_controller、struct spi_driver、struct spi_device、struct spi_board_info、struct spi_transfer、spi_message等,spi框架就是围绕这几个结构体进行开发、管理,下面分别介绍。
2.1 struct spi_controller
Soc里面有不少spi外设,如spi0、spi1、spi2,对每一个spi外设都需要进行抽象和管理。 struct spi_controller用于描述一个spi外设。结构体主要部分如下:
struct spi_controller:描述spi控制器,对应与spi总线的主机
dev:spi_controller 是一个 device,所以包含了一个 device 的实例,设备模型使用
list:链接到全局的 spi_controller list
bus_num:spi bus 的编号,比如某 SoC有3个 SPI 控制,那么这个结构描述的是第几个
num_chipselect:片选数量,决定该控制器下面挂接多少个SPI设备,从设备的片选号不能大于这个数量
mode_bits:SPI 控制器支持的 slave 的模式
min_speed_hz/max_speed_hz:最大最小速率
slave:是否是 slave
(*setup):主要设置SPI控制器和工作方式、clock等
(*transfer):添加消息到队列的方法。这个函数不可睡眠。它的职责是安排发生的传送并且调用注册的回 调函 complete()。这个不同的控制器要具体实现,传输数据最后都要调用这个函数
(*cleanup):在spidev_release函数中被调用,spidev_release被登记为spi dev的release函数
struct kthread_worker kworker; //这些和内核的数据使用有关,用到再说
struct task_struct *kworker_task;
struct kthread_work pump_messages; //内核线程
spinlock_t queue_lock; //队列使用用到锁
struct list_head queue;
struct spi_message *cur_msg;
cs_gpiods; 片选
transfer //三者必须有一个,用于使用spi控制器发送数据
transfer_one
transfer_one_message
struct dma_chan *dma_tx; //dma部分
struct dma_chan *dma_rx;
}
简单的说,就是将芯片的Soc各种资源,用数据描述处理,使用了哪些东西。包括队列(发送消息用到)、锁(互斥访问)、spi的模式、速率等等。值得注意的是,在实现spi发送数据的接口时,如今的内核提供三种方式:transfer 、transfer_one 、transfer_one_message,必须要有一个被实现,以便使用spi_message发送信息使用(后面会介绍到)。
2.2 struct spi_driver
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
struct device_driver driver;
};
struct spi_driver就是我们平常使用spi驱动框架注册时用到的,很简单,probe函数是驱动的入口函数,放一个博主之前写的驱动的struct spi_driver样例。和平常驱动类似,不多介绍。
图2
2.3 struct spi_device
struct spi_device:spi从设备
{
dev:device 结构,设备模型使用
controller:这个 spi device 挂在那个 SPI Controller 下
max_speed_hz:通讯时钟最大频率
chip_select:片选号,每个 master 支持多个 spi_device
mode:SPI device 的模式,时钟极性和时钟相位
bits_per_word:每个通信字的字长的比特数,默认是 8
irq:使用到的中断号
modalias:设备驱动的名字
}
struct spi_device是对spi期间进行描述,需要用到哪些,包括spi器件通信时使用到的通信协议配置--时钟、位数、中断、极性、相位等等。
2.4 struct spi_board_info
struct spi_board_info用于描述板机spi设备信息,和spi_device里面的信息类似,该结构体常用与不支持设备树的内核代码,支持设备树的内核代码里面,在加载内核时,会自动读取内核,生成spi_device信息,从而与spi_driver完成匹配。
2.5 struct spi_transfer
spi_transfer“读写缓冲对,包含读、写缓冲区。将spi_transfer中的链表transfer_list链接到spi_message中的transfers,再以spi_message形势向底层发送数据。每个spi_transfer都可以对传输的一些参数进行设置,使得master controller按照它要求的参数进行数据发送。
struct spi_transfer{
tx_buf:发送缓冲区,要写入设备的数据(必须是dma_safe),或者为NULL
rx_buf:接收缓冲区,要读取的数据缓冲(必须是dma_safe),或者为NULL
len:缓冲区长度,tx和rx的大小(字节数)。这里不是指它的和,而是各自的长度,它们总是相等的
tx_dma:如果spi_message.is_dma_mapped是真,这个是tx的dma地址
rx_dma:如果spi_message.is_dma_mapped是真,这个是rx的dma地址
cs_change:1 :当前spi_transfer发送完成之后重新片选。影响此次传输之后的片选。指示本次transfer结束之后是否要重新片选并调用setup改变设置。这个标志可以减少系统开销
bits_per_word:每个字长的比特数,0代表使用spi_device中的默认值 8
delay_usecs:发送完成一个spi_transfer后延时时间,此次传输结束和片选改变之间的延时,之后就会启动另一个传输或者结束整个消息
speed_hz:通信时钟。如果是0,使用默认值
transfer_list:用于链接到spi_message,用来连接的双向链接节点
}
2.6 struct spi_message
struct spi_message,用于发送一次完成的传输,里面包含多个spi_transfer,其用于cs信号从高电平到低电平(选择spi器件)时,spi主机发送一个spi_message,之后cs拉高。
struct spi_message:{
transfer:这个 mesage 含的 transfer 链表
spi:传输的目标设备
is_dma_mapped:spi_transfer 中 tx_dma 和 rx_dma 是否已经 mapped
complete:数据传输完成的回调函数
context:提供给complete的可选参数
actual_length:spi_message已经传输了的字节数
status:出错与否,错误时返回 errorcode
queue 、state:供controller驱动内部使用
}
3 spi流程
3.1 芯片底层初始化
从图1上看,spi框架也是基于platform总线,在spi框架最底层,是芯片厂家写的驱动,包括对spi外设的配置(时钟、极性、相位等),使用
platform_driver_register(stm32_spi_driver)
进行注册,在对应的probe函数(stm32_spi_probe)中,分配一个master(就是controller,老版本叫做master)。
图3
有意思的是,这个函数里面会多分配一些内存,用于给芯片厂家使用,用于芯片厂家自定义的结构体,如
master = spi_alloc_master(&pdev->dev, sizeof(struct stm32_spi));
platform_set_drvdata(pdev, master);
spi = spi_master_get_devdata(master);
struct stm32_spi是st公司定义的结构体,用于管理自己的数据,经过这两部分操作之后,直接给spi分配好了内存,并指向。spi就是struct stm32_spi *spi。剩下的就是初始化用到的IO、中断、dma、自旋锁、完成量、配置spi等等,值得一提的是stm32_spi_transfer_one这个部分,spi控制器发送数据具体实现函数。spi_register_master函数,就是spi_register_controller。
3.2 注册controller
这一部分主要是初始化用到的自旋锁、队列、互斥锁、完成量等,将3.1初始化的部分,和后面spi driver用到的部分结合起来。spi_register_controller做的事情如图4:
图4
在spi框架里面,所有的spi controller会连接到一个链表spi_controller_list,如图5
图5
board_list是用与spi的device信息链表。
图6
在spi_controller_initialize_queue中,添加spi_transfer_one_message,初始化队列spi_init_queue。
图7
这里是初始化了一个内核线程,配置内核线程SCHED_FIFO,并将spi_pump_messages作为线程入口函数,这个函数就是用于真正发送spi数据包,里面调用了transfer_one_message函数。
图8
transfer_one_message中发送数据,调用的是stm32_spi_transfer_one,具体流程如图9.
图9
3.3 spi驱动
这里就是我们写的驱动了,在spi驱动中,spi_register_driver注册一个spi_driver,配置一下结构体。即可非常方便的使用spi框架管理。spi驱动发送数据时,需要初始化spi_message和spi_transfer,流程如图10,最后会在内核线程的spi_pump_messages中完成发送任务。
图10
3.4 spi框架初始化
spi框架基本流程已经介绍的差不多了,还有一个就是spi框架初始化,使用字符框架写驱动的同学应该很清楚,就是注册类、设备这些。贴一下代码把,不做介绍了。使用
postcore_initcall(spi_init);
初始化spi框架。
3.5 spi中断部分
linux的中断分为上下部,spi中断由芯片厂商已经写好了,在图3部分会进行注册中断。截取stm32芯片的irq注册部分。
spi->cfg->irq_handler_event和spi->cfg->irq_handler_thread就是spi中断的入口了,分成两部分,中断上下文。irq_handler_event是用于中断部分,对芯片的寄存器进行操作,若是返回IRQ_WAKE_THREAD,内核会自动地调用irq_handler_thread部分,完成中断。里面的具体内容就不说了,有兴趣的自己看看源码。
中断是一个比较复杂的部分,有时间另外更新。
三、总结
相比较Linux其他的子系统,spi框架算是一个比较简单的框架了,用到的核心结构体也没几个,代码量大概在5k行左右。本文只是介绍了spi框架的基本部分,还有一些细节未曾介绍到,对Linux上的数据结构、互斥量、信号量等也未曾详细介绍,这个部分属于操作系统原理的知识了,感兴趣的同学请参考其他的博文。
spi框架总体山可以看作是platform类的继承,在底层匹配机制上依然延续了platform总线,并对它做了一些扩展。其他的Linux子系统也是基于platform总线,并且有许多相通之处,下一个驱动框架将会晚不少时间,因为太复杂了,博主估计要看很久才能理顺。文章来源:https://www.toymoban.com/news/detail-778391.html
正是在分层分离、面向对象思想的指导下,Linux产生了大量的驱动子系统,由全世界的各位大佬完成了基本的操作,简化了驱动的开发,让驱动开发只需要完成一些简单工作,如配置xxx_driver结构体、使用xxx_register注册等,就可以完成一个稳定性高、效率高的代码,同时不容易出现各种问题如死锁。看完spi框架代码,不得不对前人的工作感慨,向各位大佬致敬并学习!文章来源地址https://www.toymoban.com/news/detail-778391.html
到了这里,关于linux驱动系列学习之spi框架源码分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!