linux驱动系列学习之spi框架源码分析

一、说明

        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样例。和平常驱动类似,不多介绍。

扫描二维码关注公众号,回复: 14649840 查看本文章

图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总线,并且有许多相通之处,下一个驱动框架将会晚不少时间,因为太复杂了,博主估计要看很久才能理顺。

        正是在分层分离、面向对象思想的指导下,Linux产生了大量的驱动子系统,由全世界的各位大佬完成了基本的操作,简化了驱动的开发,让驱动开发只需要完成一些简单工作,如配置xxx_driver结构体、使用xxx_register注册等,就可以完成一个稳定性高、效率高的代码,同时不容易出现各种问题如死锁。看完spi框架代码,不得不对前人的工作感慨,向各位大佬致敬并学习!

猜你喜欢

转载自blog.csdn.net/zichuanning520/article/details/129736376
今日推荐