基于Linux的SPI驱动框架源码分析

文章目录

1、前言

​   我个人感觉学习一个驱动框架,先了解下驱动框架涉及到的数据结构体是个十分必要的东西。知道数据结构体里面每个成员的含义以及作用后,再去看源码就只跟自身C语言功力和框架中涉及到其他概念知识相关了。所以讲解SPI驱动源码之前,我不会只把里面涉及到的数据结构体成员注释贴出来就行,我还会讲里面每个成员具体的作用和为什么要定义这个成员。

  本篇博客是以I.MX6ULL为基础的SPI驱动框架分析,由于我觉得 SPI 接口底层硬件知识对整个框架的理解同样有着很大的帮助,所以本次博客也添加了对 SPI 接口底层操作函数的讲解。另外,由于本人能力有限,所以只会讲解其中关键的流程和函数,如果有什么错误的地方,欢迎大家指出。

​   最后,本篇博客我觉得不太适合没有太多基础的人看,因为本篇博客没有过多的讲解一些基础的概念知识,更多是纯粹的对源码的分析。所以本篇博客更加适合一些已经大概看过SPI驱动框架源码但还有一些细节不明白的朋友和提供一个笔记查阅的作用,为一些已经看过SPI驱动框架源码但后面害怕忘记的朋友。

2、I.MX6UL SPI接口硬件底层介绍

2.1、传输方式介绍

​   I.MX6UL SPI 驱动框架中,兼容 DMA 和非 DMA 两种方式进行数据传输。在非DMA方式下,数据的传输是由CPU完成的,每次数据传输需要CPU的介入,这种方式传输速度较慢,对CPU的占用也较高。在非DMA模式下,SPI的传输可以使用 PIO 模式或者 IRQ 模式,PIO 模式是CPU 直接进行数据读写,IRQ 模式是 CPU 通过中断的方式读取数据。而在 DMA 方式下,数据的传输是通过 DMA 控制器实现的,可以大大降低CPU 的介入,从而提高数据传输效率。在 DMA 模式下,数据传输可以使用 DMA M2M、DMA PIO、DMA IRQ 和 DMA Scatter-Gather 等多种方式。

2.1.1、非DMA模式

​   PIO 是指 Programmed I/O,即编程I/O。在PIO模式下,每个字节的数据都需要 CPU 进行读写操作。因此,PIO模式的数据传输效率较低,但是实现相对简单。在 SPI 驱动中,PIO 模式是指通过 CPU 来控制SPI总线的数据传输,即每个字节的传输都需要CPU参与,传输速率相对较慢。当数据量较小,对数据传输速率要求不高时,可以选择使用 PIO 模式。

​   IRQ 模式(Interrupt Request Mode)是指设备通过中断请求(IRQ)的方式来向 CPU 报告设备状态或者请求处理。在 SPI 通信中,使用 IRQ 模式时,当 SPI 传输完成或者出现错误时,设备会向 CPU 发送一个中断请求,CPU 响应中断请求,调用相应的中断处理函数进行处理。使用 IRQ 模式时,通信过程中,设备将数据存入缓冲区,当缓冲区已满时,设备会请求中断,让 CPU 去取走数据,这种方式可以减少CPU的轮询次数,降低CPU的负担,提高效率。

2.1.2、DMA模式

​   DMA M2M(Memory-to-Memory)传输方式是将数据从源地址复制到目的地址,通常用于内存拷贝或缓冲区填充。该传输方式由DMA控制器完成,CPU无需参与数据传输。

​   DMA PIO(Peripheral Input/Output)传输方式是将数据从外设读取到内存或将数据从内存写入外设,使用PIO(Programmed Input/Output)方式进行传输。该传输方式由DMA控制器完成,CPU无需参与数据传输。

​   DMA IRQ(Interrupt Request)传输方式是使用DMA控制器进行数据传输,并使用中断机制通知CPU传输完成。在这种传输方式下,DMA控制器读取或写入外设数据,直到完成数据传输,然后向CPU发送中断请求。

​   DMA Scatter-Gather传输方式是将多个散布的数据块按顺序组合成一个连续的数据块进行传输。该传输方式需要使用DMA引擎的Scatter-Gather功能,可以在一个DMA传输周期中完成多个传输操作。它能够在数据传输过程中更好地利用DMA带宽,降低CPU的参与度。

2.2、相关寄存器介绍

​   以下寄存器介绍将会在下一章节的底层硬件源码分析涉及到。

2.2.1、Control Register (ECSPIx_CONREG)

​   XCH(2):用于启动数据交换,该位为1表示启动数据传输。

​   SMC(3):用于设置起始模式,该位为1表示TXFIFO有数据时立即传输,该位为0表示由XCH位控制传输。

2.2.2、Config Register (ECSPIx_CONFIGREG)

​   SCLK_PHA(3-0):用于设置SPI时钟相位,该位为1表示在第二个SPI时钟边沿处进行数据采样,该位为0表示在第一个SPI时钟边沿处进行数据采样。

​ SCLK_POL(7-4):用于设置SPI时钟极性,该位为1表示SPI时钟空闲状态为高电平,该位为0表示SPI时钟空闲状态为低电平。

​   SS_CTL(11-8):主模式下,该位为1表示将传输多个SPI传输,当TXFIFO为空时,SPI传输将自动停止。该位为0表示只传输一个SPI传输。如果SMC位设置,则忽略SS_CTL。

​   SS_POL(15-12):用于设置片选信号极性,该位为1表示片选信号空闲状态为高电平,该位为0表示片选信号空闲状态为低电平。

​   SCLK_CTL(23-20):用于设置时钟信号极性,该位为1表示时钟信号空闲状态为高电平,该位为0表示时钟信号空闲状态为低电平。

2.2.3、Interrupt Control Register (ECSPIx_INTREG)

​   TEEN(0):发送缓冲区空时,产生中断请求,如果该位被设置,将使能发送缓冲区空中断。

​   RREN(3):接收缓冲区非空时,产生中断请求,如果该位被设置,将使能接收缓冲区非空中断。

​   TCEN(7):传输完成时,产生中断请求,如果该位被设置,将使能传输完成中断。

2.2.4、DMA Control Register (ECSPIx_DMAREG)

​   RX_ THRESHOLD(21-16):DMA接收中断触发阈值偏移量,指定DMA传输时接收缓冲区空间大小。

​   TX_ THRESHOLD(5-0):DMA传输中断触发阈值偏移量,指定DMA传输时发送缓冲区空间大小。

​   TEDEN(7):DMA传输使能偏移量,用于控制DMA传输的使能。

​   RXDEN(23):DMA接收使能偏移量,用于控制DMA接收的使能。

2.2.5、Status Register (ECSPIx_STATREG)

​   STAT_RR(3):接收寄存器就绪标志位,当该位为1时,表示接收缓冲区中有数据可以读取。

2.3、两种触发SPI数据传输的配置方式

2.3.1、非DMA传输配置方式

​   该方式用在非DMA传输数据中。此时需要进行一下配置:

  1. SMC标志位:将SMC标志位置为0。

  2. XCH标志位:XCH标志位用于控制SPI数据传输的起始和结束。当XCH标志位被设置为1时,表示启动SPI传输;当SPI传输完成后,XCH标志位会自动清零,表示SPI传输结束。

  3. SS_CTL标志位:SS_CTL标志位被忽略,可以设置为任意值。

2.3.2、DMA传输配置方式

​   该方式用在DMA传输数据中。此时需要进行一下配置:

  1. SMC标志位:将SMC标志位置为1。
  2. XCH标志位:XCH标志位被用于控制SPI数据交换顺序,一般情况下建议将其设置为1,以保证数据传输的正确性。
  3. SS_CTL标志位:将SS_CTL标志位设置为0,以让硬件自动控制片选信号的拉高和拉低,避免出现传输错误。

2.4、SPI 接口硬件相关操作API函数

​   在SPI驱动框架中实现SPI数据传输时最终都会涉及到I.MX6UL平台的底层函数操作,而这部分与硬件平台相关,属于SPI接口底层知识。在正式分析SPI驱动框架源码时,有必要先了解下硬件底层相关API函数的实现方式。设备树匹配成功后,在设备树匹配表中还指定额外的数据结构(imx6ul_ecspi_devtype_data),这个结构体描述 i.MX6UL SPI 控制器设备的底层操作实现方式,且这些操作方式都涉及到了I.MX6UL SPI相关寄存器的配置。该结构体如下所示:

static struct spi_imx_devtype_data imx6ul_ecspi_devtype_data = {
	.intctrl = mx51_ecspi_intctrl,					/* 用于控制中断 */
	.config = mx51_ecspi_config,					/* 用于配置 SPI 控制器的寄存器 */
	.trigger = mx51_ecspi_trigger,					/* 用于控制传输触发方式的寄存器 */
	.rx_available = mx51_ecspi_rx_available,		 /* 用于检查接收缓冲区是否有数据可读 */
	.reset = mx51_ecspi_reset,					 	/* 用于重置 SPI 控制器 */
	.devtype = IMX6UL_ECSPI,						/* 表示设备类型 */
};
2.4.1、mx51_ecspi_intctrl
static void __maybe_unused mx51_ecspi_intctrl(struct spi_imx_data *spi_imx, int enable)
{
	unsigned val = 0;

	if (enable & MXC_INT_TE)
		val |= MX51_ECSPI_INT_TEEN;					// TXFIFO空中断使能

	if (enable & MXC_INT_RR)
		val |= MX51_ECSPI_INT_RREN;					// RXFIFO非空中断使能

	if (enable & MXC_INT_TCEN)
		val |= MX51_ECSPI_INT_TCEN;					// 传输完成中断使能

	writel(val, spi_imx->base + MX51_ECSPI_INT);
}

​   该函数用来控制I.MX6UL平台的SPI控制器中断的使能和禁止,该函数有两个传参变量,spi_imx_data结构指针先不管,后面会讲解。enable参数的值来设置或清除控制寄存器中的相应位。enable参数取值可以为MXC_INT_TE、MXC_INT_RR、MXC_INT_TCEN,分别对应使能 TXFIFO空中断使能、RXFIFO非空中断使能和传输完成中断使能。当enable为0时,表示清除所有中断。

2.4.2、mx51_ecspi_config
static int __maybe_unused mx51_ecspi_config(struct spi_imx_data *spi_imx, struct spi_imx_config *config)
{
	u32 ctrl = MX51_ECSPI_CTRL_ENABLE, cfg = 0, dma = 0;						 //SPI使能
	u32 clk = config->speed_hz, delay;

	ctrl |= MX51_ECSPI_CTRL_MODE_MASK;											//设置4个spi通道为主模式
	ctrl |= mx51_ecspi_clkdiv(spi_imx->spi_clk, config->speed_hz, &clk);		   //设置SPI时钟速度
	ctrl |= MX51_ECSPI_CTRL_CS(config->cs);										//选择片选通道置位
	ctrl |= (config->bpw - 1) << MX51_ECSPI_CTRL_BL_OFFSET;					 	  //设置spi突发长度

	cfg |= MX51_ECSPI_CONFIG_SBBCTRL(config->cs);								 //SS波形选择,

	if (config->mode & SPI_CPHA)
		cfg |= MX51_ECSPI_CONFIG_SCLKPHA(config->cs);							 //时钟相位设置

	if (config->mode & SPI_CPOL) {												//时钟极性设置
		cfg |= MX51_ECSPI_CONFIG_SCLKPOL(config->cs);
		cfg |= MX51_ECSPI_CONFIG_SCLKCTL(config->cs);
	}
	if (config->mode & SPI_CS_HIGH)												//片选引脚信号极性设置
		cfg |= MX51_ECSPI_CONFIG_SSBPOL(config->cs);

	writel(ctrl, spi_imx->base + MX51_ECSPI_CTRL);
	writel(cfg, spi_imx->base + MX51_ECSPI_CONFIG);

	delay = (2 * 1000000) / clk;
	if (likely(delay < 10))	
		udelay(delay);
	else			
		usleep_range(delay, delay + 10);

	if (spi_imx->dma_is_inited) {
		if (spi_imx->devtype_data->devtype != IMX6UL_ECSPI)
			spi_imx->tx_wml = 1;
		dma = (spi_imx->rx_wml - 1) << MX51_ECSPI_DMA_RX_WML_OFFSET					//rxfifo接收阈值,大于该值,触发dma接收请求
		      | (spi_imx->tx_wml - 1) << MX51_ECSPI_DMA_TX_WML_OFFSET				//txfifo发送阈值,大于该值,触发dma发送请求
		      | (1 << MX51_ECSPI_DMA_TEDEN_OFFSET)								  //TXFIFO DMA请求使能
		      | (1 << MX51_ECSPI_DMA_RXDEN_OFFSET);								  //RXFIFO DMA请求使能
		writel(dma, spi_imx->base + MX51_ECSPI_DMA);
	}
	return 0;
}

​   该函数用来配置I.MX6UL平台的SPI控制器,它使用给定的SPI设备配置参数,初始化SPI控制器并使能SPI传输。spi_imx_data结构指针和spi_imx_config结构指针先别管。只需知道该函数会根据这两个结构指针中的某些参数来配置SPI控制。该函数的主要步骤如下:

  1. 将SPI使能控制器寄存器设置为使能状态,并设置SPI模式、时钟速度、片选信号、SPI突发长度等参数。
  2. 根据给定的SPI模式设置相应的控制器寄存器和配置寄存器的位,例如时钟相位、时钟极性、片选信号极性等。
  3. 设置延迟时间,以确保SPI控制器在配置完成后足够的时间启动并达到稳定状态。
  4. 如果DMA已经初始化,则将DMA相关寄存器的位设置为相应的值。

​   其中重点介绍下接收和发送阈值。这两个参数设置主要用在SPI DMA传输中,当SPI发送和接收数据时,会先把数据存放在TXFIFO和RXFIFO中,当TXFIFO和RXFIFO存放的数据超过所设定的阈值后,才会发起一次DMA请求。如果不设置阈值,默认为1的话,将会频繁的发起DMA请求,影响CPU运行效率。

2.4.3、mx51_ecspi_trigger
static void __maybe_unused mx51_ecspi_trigger(struct spi_imx_data *spi_imx)
{
	u32 reg = readl(spi_imx->base + MX51_ECSPI_CTRL);

	if (!spi_imx->usedma)					/* 没有使用DMA */
		reg |= MX51_ECSPI_CTRL_XCH;			/* 当SMC位为0时,如果该位置1将会触发一次SPI传输 */
	else if (!spi_imx->dma_finished && spi_imx->devtype_data->devtype == IMX6UL_ECSPI)	/* DMA还没有传输完成 */
		reg |= MX51_ECSPI_CTRL_SMC;			/* 当TXFIFO中存在数据时,将会触发一次SPI传输 */
	else								  /* DMA传输完成 */
		reg &= ~MX51_ECSPI_CTRL_SMC;		/* 当DMA传输完成后需要将改为清0,需要等在下次DMA传输完成后,再启动SPI传输 */		
	writel(reg, spi_imx->base + MX51_ECSPI_CTRL);
}

​   该函数用来触发SPI传输。该函数的实现方式与2.3中讲到的传输方式有关。在函数中,首先通过读取 MX51_ECSPI_CTRL 寄存器的值来判断当前是否需要触发 SPI 传输。如果没有使用 DMA,直接将 MX51_ECSPI_CTRL_XCH 位置1就可以触发一次 SPI 传输。如果使用了 DMA,需要根据 DMA 的状态来判断是否需要触发 SPI 传输。如果 DMA 还没有传输完成,则将 MX51_ECSPI_CTRL_SMC 位置1即可触发一次 SPI 传输;如果 DMA 已经传输完成,则需要将 MX51_ECSPI_CTRL_SMC 清0,并等待下一次 DMA 传输完成后再触发 SPI 传输。最后,通过调用 writel() 函数将修改后的 MX51_ECSPI_CTRL 寄存器的值写入到硬件寄存器中,以触发 SPI 传输。

2.4.4、mx51_ecspi_rx_available
static int __maybe_unused mx51_ecspi_rx_available(struct spi_imx_data *spi_imx)
{
	return readl(spi_imx->base + MX51_ECSPI_STAT) & MX51_ECSPI_STAT_RR;		
}

​   该函数用来检查SPI控制器中的RXFIFO是否有可用数据。它读取SPI控制器的状态寄存器,该寄存器指示RXFIFO中是否有数据。如果有数据,它返回非零值,否则返回零。

2.4.5、mx51_ecspi_reset
static void __maybe_unused mx51_ecspi_reset(struct spi_imx_data *spi_imx)
{
	while (mx51_ecspi_rx_available(spi_imx))			/* 如果RXFIFO中存在数据 */
		readl(spi_imx->base + MXC_CSPIRXDATA);			/* 读取RXFIFO中的数据 */
}

​   该函数的作用是用来重置SPI控制器。函数通过不断读取RXFIFO中的数据来清空接收缓冲区,直到RXFIFO为空为止,以达到重置的目的。

3、SPI 框架涉及到的一些其他机制介绍

​   这些东西每块都是一个大的内容,没办法用一章节的内容就讲清,所以这里我只放一些我觉得写得可以的文章链接。建议对这些机制在分析到源码这部分相关内容后再看,否则直接看可能不知道具体作用。另外对于这些机制涉及到的函数,建议只需要知道怎么用就好,不需要研究太深。

3.1、同步机制——complete

linux同步机制-complete

linux设备驱动中的completion(kernel-4.7)

3.2、工作队列——kthread_worker 和 kthread_work

内核 kthread_worker 和 kthread_work 机制

kthread_worker 和 kthread_work

3.3、DMA传输

Linux 4.0的dmaengine编程

IOMMU/SMMUV3代码分析(8)函数dma_map_sg()

Linux 4.14内核———— scatterlist介绍

3.4、SPI 控制器中的消息队列结构

在这里插入图片描述

​   该图参考自一口Linux公众号,特别强烈建议linux爱好者可以关注该公众号。

​   该图是 SPI 控制器的消息队列结构。尽管我们知道 SPI 控制器是用来传输数据,但是在 SPI 驱动框架中并没有简简单单直接发送数据就完事。它而是把要发送的数据组成了一个队列的结构,然后按照队列的顺序去发送。在这个队列结构中,它把消息分为了 message 和 transfer 两个结构,transfer 是最小的数据单元,如果我们有多个 transfer 就可以通过该结构体中的 queue 链表成员将其互相链接起来。然后把 transfer 链表的头又链接在 message 上,这样就由多个 transfer 组成了一个 message。message也可以存在过个,它们之间也可以通过 queue 链表成员将其互相链接起来,最后 SPI 控制器通过 queue 链表成员链接到 message 链表的头。这样最终就组成了一个消息队列,通过 SPI 控制器就可以一次访问里面每一个数据。

​   但是需要注意的是,在 SPI 控制器发送数据中,最小的数据组成单元是 transfer,但是写驱动程序时,发送数据的最小单元是 message,最后在 SPI 数据传输中将 message 挂接中的 transfer 依次取出发送。如果看过 SPI 设备驱动程序就知道,要发送数据时,除了需要定义一个 spi_transfer 以外,还需要定义一个spi_message,然后把 spi_transfer 挂接到 spi_message 上才能进行发送。

4、SPI 框架中的数据结构体

4.1、spi_imx_data

​   spi_imx_data 用于描述 I.MX6ULL 这款芯片特有的一些数据,这些数据都是每个平台SPI驱动不同的部分,需要驱动设计人员自己去根据自己使用的芯片去设计。它的定义在 drivers\spi\spi-imx.c 文件,如下:

struct spi_imx_data {
	struct spi_bitbang bitbang;
	{
		spinlock_t	lock = spin_lock_init(&bitbang->lock);		/* 用来保护bitbang->busy这个共享资源,因为该变量会被多个线程访问。为了避免多个线程同时调用同一个SPI控制器,所以需要保护该变量。 */
		u8			busy;										/* 表示该SPI控制器是否正在被使用 */
		u8			use_dma;									/* 表示该SPI传输是否使用DMA传输 */
		u8			flags;										/* 设置SPI传输模式的默认参数 */

		struct spi_master *master = master;				   		/* 指向SPI控制器 */

		int	 (*setup_transfer)(struct spi_device *spi, struct spi_transfer *t) = spi_imx_setupxfer;			/* SPI传输时需要的一些初始化,如时钟、传输速率等 */
		void (*chipselect)(struct spi_device *spi, int is_on)                  = spi_imx_chipselect;		/* SPI片选信号选择 */
		int	 (*txrx_bufs)(struct spi_device *spi, struct spi_transfer *t)      = spi_imx_transfer;			/* SPI数据传输 */
		u32	(*txrx_word[4])(struct spi_device *spi, unsigned nsecs, u32 word, u8 bits);						/* 用在IO模拟SPI传输的情况下,对应4种SPI传输模式 */	
	}

	struct completion xfer_done = init_completion(&spi_imx->xfer_done);		/* 传输完成同步信号,该同步信号表示该transfer的数据传输完成,并不是message的数据传输完成 */
	void __iomem *base = devm_ioremap_resource(&pdev->dev, res);	/* SPI控制器的首地址 */	
	struct clk *clk_per = devm_clk_get(&pdev->dev, "per");			/* spi控制器ipg时钟 */
	struct clk *clk_ipg = devm_clk_get(&pdev->dev, "ipg");			/* spi控制器per时钟 */
	unsigned long spi_clk = clk_get_rate(spi_imx->clk_per);			/* spi控制器per时钟速率 */

	unsigned int count = transfer->len;								/* 该变量保存了transfer中传输的数据字节长度 */
	void (*tx)(struct spi_imx_data *) = spi_imx_buf_tx_u8;			/* spi发送函数 */
	void (*rx)(struct spi_imx_data *) = spi_imx_buf_rx_u8;			/* spi接收函数 */		
	void 		*rx_buf = transfer->rx_buf;							/* 该指针变量指向了transfer中接收缓冲区 */
	const void 	*tx_buf = transfer->tx_buf;							/* 该指针变量指向了transfer中发送缓冲区 */
	unsigned int txfifo; 											/* 表示数据放入到TXFIFO中的个数,每放入一个字节该变量就加1。因为SPI传输是同时接收和发送,所以每接收到一个字节该变量就减1 */

	/* DMA */
	unsigned int dma_is_inited;										/* 表示DMA是否初始化完成 */
	unsigned int dma_finished;										/* 表示DMA是否传输完成 */
	bool usedma;													/* 表示此时SPI传输是否用到DMA传输 */
	u32 rx_wml = spi_imx->rx_config.src_maxburst;					/* 触发dma接收请求的rxfifo接收阈值 */
	u32 tx_wml = spi_imx->tx_config.dst_maxburst;					/* 触发dma发送请求的txfifo发送阈值 */
	u32 rxt_wml;													/* 源码中为看到该变量使用情况 */
	struct completion dma_rx_completion;							/* DMA接收完成的同步信号 */
	struct completion dma_tx_completion;							/* DMA发送完成的同步信号 */
	struct dma_slave_config rx_config;								
	{
		enum dma_transfer_direction direction = DMA_DEV_TO_MEM;		/* DMA传输方向:设备到内存 */
		dma_addr_t src_addr = res->start + MXC_CSPIRXDATA;			/* DMA传输源地址 */
		dma_addr_t dst_addr;		
		enum dma_slave_buswidth src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;	/* 源地址总线宽度为8bit */
		enum dma_slave_buswidth dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;	/* 目标地址总线宽度为8bit */	
		u32 src_maxburst = spi_imx_get_fifosize(spi_imx) / 2;		/* DMA传输的最大burst长度 */
		u32 dst_maxburst;										
		bool device_fc;												/* 表示设备是否使用流控 */
		unsigned int slave_id;										/* DMA从设备ID */
	}
	struct dma_slave_config tx_config;								/* 与rx_config类似 */

	const struct spi_imx_devtype_data *devtype_data = of_id->data;
	{
		void (*intctrl)(struct spi_imx_data *, int) 					= mx51_ecspi_intctrl;			/* SPI控制器的中断使能和禁止 */
		int (*config)(struct spi_imx_data *, struct spi_imx_config *) 	= mx51_ecspi_config;			/* SPI控制器参数配置,初始化SPI控制器并使能SPI传输 */
		void (*trigger)(struct spi_imx_data *) 							= mx51_ecspi_trigger;			/* 触发SPI传输 */
		int (*rx_available)(struct spi_imx_data *) 						= mx51_ecspi_rx_available;		/* 检查SPI控制器中的RXFIFO是否有可用数据 */
		void (*reset)(struct spi_imx_data *) 							= mx51_ecspi_reset;				/* 重置SPI控制器 */
		enum spi_imx_devtype devtype 									= IMX6UL_ECSPI;					/* 设备类型 */
	}
	int chipselect[0] = of_get_named_gpio(np, "cs-gpios", i);		/* 柔性数组,数组大小为片选引脚的数量 */
};
4.1.1、base、clk_per、clk_ipg、spi_clk

​   base 为 SPI 控制器的首地址,程序中涉及到 SPI 的寄存器都是以 base 为偏移量。clk_per 为 SPI 控制器 per 时钟。clk_ipg 为 SPI 控制器 ipg 时钟。spi_clk 为 SPI 控制器 per 时钟速率。以上参数都是通过设备树获取,所以对于不同的平台这些参数都不一样,需要自己去配置。

4.1.2、struct spi_bitbang bitbang
struct spi_bitbang bitbang;
{
    spinlock_t	lock = spin_lock_init(&bitbang->lock);		/* 用来保护bitbang->busy这个共享资源,因为该变量会被多个线程访问。为了避免多个线程同时调用同一个SPI控制器,所以需要保护该变量。 */
    u8			busy;										/* 表示该SPI控制器是否正在被使用 */
    u8			use_dma;									/* 表示该SPI传输是否使用DMA传输 */
    u8			flags;										/* 设置SPI传输模式的默认参数 */

    struct spi_master *master = master;				   		/* 指向SPI控制器 */

    int	 (*setup_transfer)(struct spi_device *spi, struct spi_transfer *t) = spi_imx_setupxfer;			/* SPI传输时需要的一些初始化,如时钟、传输速率等 */
    void (*chipselect)(struct spi_device *spi, int is_on)                  = spi_imx_chipselect;		/* SPI片选信号选择 */
    int	 (*txrx_bufs)(struct spi_device *spi, struct spi_transfer *t)      = spi_imx_transfer;			/* SPI数据传输 */
    u32	(*txrx_word[4])(struct spi_device *spi, unsigned nsecs, u32 word, u8 bits);						/* 用在IO模拟SPI传输的情况下,对应4种SPI传输模式 */	
}

​   解释下这个结构体的字面意思,bitbang 表示 SPI 总线的数据传输是通过软件模拟实现的,而不是使用硬件外设实现的。也就是说,SPI 总线上的数据传输是通过直接控制单个位(bit)的值来完成的。因此,struct spi_bitbang 结构体是一个软件实现的 SPI 驱动程序的接口。

​   以上是对该结构体的介绍,按照介绍意思来讲,其结构体是为模拟 IO 的 SPI 通信模式而设计、但是在 I.MX6ULL SPI 驱动框架中破坏了这结构体的设计方式,在里面参杂了硬件 SPI 通信的方法,以至于分析模拟 IO 和硬件 SPI 这两种方式的程序时会有些乱。接下来按我的理解来重新讲解。

​   struct spi_bitbang 结构体是一个抽象的结构体,它定义了SPI 控制器的基本操作,这些操作需要由底层的硬件驱动实现。因为每个平台操作 SPI 的方式不一样,所以该结构体也被放到了 struct spi_imx_data 结构体中,需要根据驱动设计者自己定义方法。该结构体分为两部分,第一部分是底层硬件驱动方法的实现,即四个回调函数,setup_transfer、chipselect、txrx_bufs、txrx_word。第二部分是 SPI 控制器的配置参数。

​   先看第一部分,setup_transfer 的作用为 SPI 传输时需要的一些初始化,如时钟、传输速率等;chipselect 的作用为 SPI 片选信号选择;txrx_bufs 的作用为 SPI 数据传输,SPI 驱动框架的传输函数最终都会调用到该回调函数;可以发现以上这些函数都涉及底层硬件驱动。还有一个回调函数 txrx_word,它的作用是使用模拟 IO 的方式传输数据,它与3.1.3中有联系,我会放到 3.1.3 中结合起来说。

​   最后看第二部分,busy、use_dma、flags 这三个配置参数。flags 设置 SPI 传输模式的默认参数,其实真正设置 SPI 传输模式的变量是spi_master 结构体中的 mode_bits,但是如果该参数没有被设置,则将会使用 flags 定义的默认参数去设置 mode_bits。busy 用来表示该 SPI控制器是否正在被使用,但是在spi_master 结构体中的 busy 也是用来表示 SPI 控制器是否正在被使用。spi_bitbang->busy 被用在模拟 IO 通信方式,spi_master->busy 被用在硬件 SPI 通信方式中。use_dma 参数用来表示是否使用了 DMA,但是在 spi_imx_data 结构体中的 usedma 也表示是否使用了 DMA,前者主要被用在模拟 IO 通信方式,后者被用在硬件 SPI 通信方式中。

4.1.3、tx、rx

​   这两个回调函数为 SPI 控制器的发送数据和接收数据的回调函数,在 I.MX6ULL 平台中它们分别被定义为 spi_imx_buf_tx_u8 和 spi_imx_buf_rx_u8 这两个函数,作用分别是发送一个字节数据和接收一个字节数据。这两个函数就实现了一个 SPI 控制器最基本的发送和接收的底层硬件驱动方法。但是这样的传输数据方法由 SPI 控制器自动产生时序。但有时候情况很特殊,在一些没有 SPI 控制器的芯片或者没有多余的 SPI 接口,就只能用模拟 IO 的方法去产生 SPI 时序。那么这两个函数就不再适用。可能有人疑问,既然这个结构体内的参数都是由驱动设计人员自己定义的,那么我们把这两个回调函数的方法改为模拟 IO 的实现方法不就行了么?这个方法的确可以,但是为了不破坏 I.MX6ULL 驱动框架的结构,毕竟大多数使用的还是硬件 SPI,所以就另外定义了 模拟 IO 的实现方法,也就是3.1.2中讲到 txrx_word 回调函数。

​   在硬件 SPI 方式中 tx、rx 是最小的发送和接收单元,那在模拟 IO 方式中,txrx_word 就是最小的接收和发送单元,因为 SPI 具有四种传输模式,所以该回调函数定义了4组,分别对应四种 SPI 传输模式。所以如果要使用模拟 IO 方式就需要驱动设计人员去定义这几个函数。但是如果真要使用 模拟 IO方法只定义 txrx_word 这四个回调函数还远远不够,还需要做一些修改。

​   在上一节中,I.MX6ULL 的 SPI 驱动框架设置了 setup_transfer、txrx_bufs 这三个回调函数,如果要使用模拟 IO 方式就需要重新定义这两个方法,因为在 txrx_bufs = spi_imx_transfer 中的最终调用的是 tx、rx 实现方法,如果你要调用 txrx_word 的实现方法就得使用驱动默认提供的函数 txrx_bufs = spi_bitbang_bufs 函数。还有 setup_transfer = spi_imx_setupxfer 中选择了 tx、rx 具体的实现方法,比如是一次传输1字节、2字节还是4字节,而在模拟 IO 中需要使用 setup_transfer = spi_bitbang_setup_transfer 去选择具体的实现方法。最后还需要设置 spi_master 结构体中的 setup 成员,该成员会在后面具体说到,这里说下作用,在 I.MX6ULL 驱动框架中它被赋值为 spi_imx_setup,该函数仅仅完成了片选引脚的选择,而如果要使用模拟 IO 方式,则需要 master->setup = spi_bitbang_setup,该函数中不仅选择的片选引脚,还根据 SPI 传输模式选择了 txrx_word 中具体的传输函数。以下是两种传输方式赋值区别对比:

/********************* 模拟IO方式 **********************/
master->setup           = spi_bitbang_setup;			/* 根据SPI传输模式选择txrx_word中的传输函数和选择片选引脚 */
bitbang->setup_transfer = spi_bitbang_setup_transfer;	/* 选择txrx_word具体的传输方法,也就是一次是发送一个字节、两个字节、还是四个字节,选择好后会赋值给cs->txrx_bufs */
bitbang->txrx_bufs      = spi_bitbang_bufs;			   /* 实现模拟 IO     传输的方法,里面会调用cs->txrx_bufs            */
cs->txrx_bufs = bitbang_txrx_8;

/******************* 硬件SPI模式 *********************/
master->setup           = spi_imx_setup;			 /* 根据spi设备更新硬件配置,如设置spi工作模式、时钟等。这里的作用是SPI片选引脚选择 */	
bitbang->setup_transfer = spi_imx_setupxfer;	     /* SPI传输时需要的一些初始化,如选择具体的传输函数、SPI控制器时钟配置等 */
bitbang->txrx_bufs      = spi_imx_transfer;			/* SPI数据传输 */
4.1.4、count 、rx_buf、tx_buf、 txfifo

​   前三个变量分别保存了 transfer 中传输的数据字节长度、transfer 中接收缓冲区、transfer 中发送缓冲区。在 3.4 中可以知道,transfer 是 SPI 传输数据时的最小单元,现在这三个变量就是取出 transfer 中的信息,然后就开始发送。

​   因为发送数据时,需要先把 tx_buf 的数据存放到 TXFIFO 中,然后通过 2.4.3 中讲到触发传输函数,最终数据才会开始发送。所以这里也透露出一点,每个 transfer 的数据长度不能大于 TXFIFO 的长度,而在 I.MX6ULL 中 TXFIFO 长度为64。txfifo 就是用来记录存放到 TXFIFO 中的字节数。

4.1.5、dma_is_inited、dma_finished、usedma

​   dma_is_inited 用来表示此时SPI传输是否用到 DMA 传输,dma_finished 用来表示 DMA 是否传输完成,usedma 用来表示此时 SPI 传输是否用到 DMA 传输。

4.1.6、rx_wml、tx_wml

​   rx_wml为触发 dma 接收请求的 rxfifo 接收阈值,tx_wml 为触发 dma 发送请求的 txfifo 发送阈值。这里就引出一个问题,如果 SPI 控制器采用 DMA 传输数据时,如果有的数据长度不满足这个阈值要求,岂不就发生不了么?的确会出现这样的情况,所以在 SPI 传输函数中就对此情况做了处理,也就是当剩余的长度不满足阈值要求时,就采用 PIO 的传输方式去传输剩下的数据。具体函数处理细节可以看 6.6 和 6.8。

4.1.7、rx_config、tx_config

​   这两个是配置 SPI DMA发送和接收通道的配置参数,具体讲解可以参考 3.3。

4.1.8、devtype_data

​ 该结构体具体成员如下:

void (*intctrl)(struct spi_imx_data *, int) 					= mx51_ecspi_intctrl;			/* SPI控制器的中断使能和禁止 */
int (*config)(struct spi_imx_data *, struct spi_imx_config *) 	= mx51_ecspi_config;			/* SPI控制器参数配置,初始化SPI控制器并使能SPI传输 */
void (*trigger)(struct spi_imx_data *) 							= mx51_ecspi_trigger;			/* 触发SPI传输 */
int (*rx_available)(struct spi_imx_data *) 						= mx51_ecspi_rx_available;		/* 检查SPI控制器中的RXFIFO是否有可用数据 */
void (*reset)(struct spi_imx_data *) 							= mx51_ecspi_reset;				/* 重置SPI控制器 */
enum spi_imx_devtype devtype 									= IMX6UL_ECSPI;					/* 设备类型 */

  可以发现这些函数在 2.4 中已经讲到,这些都是 I.MX6ULL 的底层驱动硬件函数。

4.1.9、chipselect[0]

​   这是一个柔性数组,别看这个数组个数为0,在 SPI 控制的注册过程中根据设备树中片选引脚的数量申请了空间。即说明,这个数组用来存放了该 SPI 控制器下使用到的片选引脚。后面程序会根据该数组去控制相应的片选引脚。

4.1.10、xfer_done

​   xfer_done 成员为传输完成同步信号,该同步信号表示该 transfer 的数据传输完成,并不是 message 的数据传输完成。在 SPI 驱动框架中一共涉及到了3个同步信号,分别是 4.1.10 中的 xfer_done 同步信号,表示 transfer 的数据传输完成;4.2.14 中 xfer_completion 同步信号,同样也是表示transfer的数据传输完成,但和 xfer_done 应用场景不一样,两者不能同时被使用,具体区别可见 4.2.14;4.4.3中&done 同步信号,表示 message 的数据传输完成。transfer 和 message 区别可见 3.4。

4.2、spi_master

​   spi_master 用来描述一个实际的 SPI 控制器,它的定义在 include\linux\spi\spi.h 文件,如下:

struct spi_master 
{
	struct device	dev;										/* spi控制器对应的device结构 */
	{
		struct device 		*parent  = &pdev->dev;				/* 指向spi控制器这个平台总线设备 */						
		struct device_node	*of_node = pdev->dev.of_node; 		/* spi控制器节点 */
		struct class		*class   = &spi_master_class;		/* 用来生成/sys/class/spi_master这个目录 */
		struct kobject  kobj.name    = "spi%u";					/* 用来生成/sys/class/spi_master/spi%u 这个设备文件 */
		void		 *driver_data    = spi_imx_data;			/* 私有数据,指向spi_imx_data */
	}
	struct list_head list = spi_master_list;												/* 将该spi控制器添加到spi_master_list总链表上 */

	s16	 bus_num = of_alias_get_id(master->dev.of_node, "spi");								/* SPI控制器ID号*/
	u16	 num_chipselect = of_property_read_u32(np, "fsl,spi-num-chipselects", &num_cs);	  	/* SPI控制器片选引脚的数量 */

	u16			dma_alignment;																/* 完成SPI控制器对DMA缓冲区的对齐要求 */

	u16			mode_bits =  SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;								/* spi 控制器支持的传输模式 */
	u32			bits_per_word_mask= SPI_BPW_RANGE_MASK(1, 32);								/* 传输字节数的掩码,0xFFFF */

	u32			min_speed_hz;																/* 定义spi设备最小传输速率 */											
	u32			max_speed_hz;																/* 定义spi设备最大传输速率,如果spi设备没有指定,则以该变量为默认参数 */

	u16			flags = SPI_MASTER_MUST_RX | SPI_MASTER_MUST_TX;							/* 表示该spi控制器需要进行发送和接收 */
#define SPI_MASTER_HALF_DUPLEX	BIT(0)		/* 不能做全双工 */
#define SPI_MASTER_NO_RX		BIT(1)		/* 不能进行缓冲区读取 */
#define SPI_MASTER_NO_TX		BIT(2)		/* 不能进行缓冲区写入 */
#define SPI_MASTER_MUST_RX      BIT(3)		/* 需要接收 */
#define SPI_MASTER_MUST_TX      BIT(4)		/* 需要发送 */

	spinlock_t	  bus_lock_spinlock = spin_lock_init(&master->bus_lock_spinlock);  	/* 和bus_lock_mutex作用一样,但是两者涉及到一个重要的机制,即总线的独占使用 */	
	struct mutex  bus_lock_mutex = mutex_init(&master->bus_lock_mutex);			   	/* SPI控制器的消息队列为共享资源,当多个线程都同时将message挂载到消息队列上就可能出现问题,所以这里需要互斥量保护。用在同步传输中 */
	bool		  bus_lock_flag;													/* 表示该spi总线被独占 */

	int	 (*setup)(struct spi_device *spi)    = spi_imx_setup;						/* 根据spi设备更新硬件配置,如设置spi工作模式、时钟等。这里的作用是SPI片选引脚选择 */	
	void (*cleanup)(struct spi_device *spi)  = spi_imx_cleanup;						/* cleanup函数会在spidev_release函数中被调用,spidev_release被登记为spi dev的release函数。*/
	int (*transfer)(struct spi_device *spi, struct spi_message *mesg) = spi_queued_transfer;							/* 将消息添加到SPI控制器的消息队列上 */
	bool (*can_dma)(struct spi_master *master,struct spi_device *spi, struct spi_transfer *xfer) = spi_imx_can_dma;		/* 判断SPI控制器是否可以使用DMA传输数据 */
					   				  
	bool					queued;												/* 表示内核工作线程创建是否成功,其实内核工作线程处理消息时也像一个队列,但是此队列和SPI控制器上的队列不一样 */
	struct kthread_worker	kworker = init_kthread_worker(&master->kworker);	/* 用于管理数据传输消息队列的工作队列线程 */
	struct task_struct		*kworker_task = kthread_run(kthread_worker_fn, &master->kworker, "%s", dev_name(&master->dev));		/* 为kworker创建一个内核工作线程 */
	struct kthread_work		pump_messages = init_kthread_work(&master->pump_messages, spi_pump_messages);						/* 用来具体实现数据传输的工作函数 */
	//queue_kthread_work(&master->kworker, &master->pump_messages);
	spinlock_t				queue_lock = spin_lock_init(&master->queue_lock);	/* meassge传输期间可能会出现多个spi设备同时传输message,所以需要该自旋锁保护消息队列中message传输期间不被其他线程抢占 */
	
	struct list_head		queue = INIT_LIST_HEAD(&master->queue);				/* SPI控制器的消息队列,所有等待传输的message挂在该链表下 */
	struct spi_message		*cur_msg;											/* 当前正在处理的message */
	bool					idling;												/* 该标志位为1的时候,表示SPI控制器空闲 */
	bool					busy;												/* 该标志位为1的时候,表示SPI控制器正在被使用 */
	bool					running;											/* 正在跑 */
	bool					rt;													/* 当该标志位为1的时候,为提高实时性,内核工作线程将以高优先级运行 */
	bool					auto_runtime_pm;
	bool                	cur_msg_prepared;									/* 表示当前的message已经准备好 */
	bool					cur_msg_mapped;										/* 表示当前的message已经被映射到了连续的虚拟空间中 */
	struct completion   	xfer_completion = init_completion(&master->xfer_completion);		/* 传输完成同步信号,该同步信号表示该transfer的数据传输完成,并不是message的数据传输完成*/
	size_t					max_dma_len = INT_MAX;								/* DMA最大传输长度 */												

	int (*prepare_transfer_hardware)(struct spi_master *master) 					= spi_bitbang_prepare_hardware;		/* 发起传输前被调用,这里是用来将bitbang->busy = 1,该标志位表示当前spi控制器正在被使用      */ 
	int (*unprepare_transfer_hardware)(struct spi_master *master) 					= spi_bitbang_unprepare_hardware;	/* 传输完成后被调用,这里用来将bitbang->busy = 0 */			
	int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg) = spi_bitbang_transfer_one;			/* message传输回调函数,用来发送spi控制器消息队列中的一条meassge */			  
	int (*prepare_message)(struct spi_master *master,struct spi_message *message) 	= spi_imx_prepare_message;			/* 发起传输前被调用,这里是用来SPI时钟使能 */       
	int (*unprepare_message)(struct spi_master *master,struct spi_message *message) = spi_imx_unprepare_message;		/* 传输完成后被调用,这里用来SPI时钟禁止*/	
				 

	 /* 如果用户没有定义master->transfer_one_message传输函数,则将会默认使用spi_transfer_one_message函数,而以下三个函数就是使用该函数需要定义的回调函数 */
	void (*set_cs)(struct spi_device *spi, bool enable);																/* 选择片选引脚,如果spi_device中的cs_gpio没有定义,则使用该函数实现片选引脚选择 */										
	int (*transfer_one)(struct spi_master *master, struct spi_device *spi, struct spi_transfer *transfer);				/* 用来发送spi控制器消息队列中的一条meassge */
	void (*handle_err)(struct spi_master *master, struct spi_message *message);											/* 当message传输发生错误时,该函数将会被执行 */

	int	*cs_gpios = of_get_named_gpio(np, "cs-gpios", i);						/* 指向一个保存了片选引脚的数组 */

	struct dma_chan	 *dma_tx = dma_request_slave_channel(dev, "tx");			/* spi DMA发送通道 */
	struct dma_chan	 *dma_rx = dma_request_slave_channel(dev, "rx");			/* spi DMA接收通道 */

	void	*dummy_rx;															/* 假数据,用来填充 */
	void	*dummy_tx;															/* 假数据,用来填充 */
};
4.2.1、struct device dev

​   dev 用来完成 SPI 控制器的注册,该成员只列以下几个成员,如下:

struct device	dev;										/* spi控制器对应的device结构 */
{
    struct device 		*parent  = &pdev->dev;				/* 指向spi控制器这个平台总线设备 */							
    struct device_node	*of_node = pdev->dev.of_node; 		/* spi控制器节点 */
    struct class		*class   = &spi_master_class;		/* 用来生成/sys/class/spi_master这个目录 */
    struct kobject  kobj.name    = "spi%u";					/* 用来生成/sys/class/spi_master/spi%u 这个设备文件 */
    void		 *driver_data    = spi_imx_data;			/* 私有数据,指向spi_imx_data */
}

​   其实如果驱动源码看多了的话,可以发现任何一个设备都包含 device 这个结构体,其实这个结构体就是所有设备共有的属性,我们可以去配置这些属性去完成一些工作,比如在 sysfs 文件系统下相应的目录下生成设备文件、继而在这个设备文件下生成相应的属性文件,所以我们在应用编程时,我们就可以访问这些属性文件去操作硬件。但是我们在应用编程中大部分还是用的文件 IO 的方式,也就是 read、write,而使用这样的方法也需要借助 sysfs 文件系统下设备文件在 /dev 文件目录下自动生成设备节点,生成设备节点后我们才能去进行读写操作。

​   前面说到我们定义的设备,比如 SPI 控制器,会在相应的目录下生成设备文件,而我们怎么决定在那个目录下生成呢?就是靠 dev->class。该结构体指向了一个 class 类,在本结构体中,它指向了 spi_master_class 类,而该类的名字就叫做“spi_master",所以在创建设备的时候,就会在 spi_master 目录下生成叫做 “spi%u” 的设备文件。设备文件的名字由 dev->kobj.name 决定。至于 spi_master 目录是怎么来的,是因为在内核启动过程中加载 spi 模块的时候,执行了 class_register(&spi_master_class); 函数。

​   另外说下属性文件,可以看到该结构体并没有提供什么关于属性文件的信息,事实也的确是这样,在 SPI 控制器设备文件的生成中并没有指定属性文件。但是不代表它不可以,在 IIC 驱动框架中,就通过 device 结构体下的 struct device_type *type,指定了属性文件,从而可以通过应用层访问这些属性文件获取 IIC 控制器的一些信息。

4.2.2、kworker、kworker_task、pump_messages

​   在 3.2 中已经说了这东西的工作机制,在这里我就再稍微理下思路。首先我们需要一个用于管理数据传输消息队列的工作队列线程(切记,这里的消息队列和 3.4 中说到的消息队列不是同一个,3.4 中说到的消息队列是 SPI 控制器中数据的一种组织结构,而这里说的消息队列是一个工作任务队列,也就是我们把发送消息的请求放在这个工作队列上,然后内核线程去按照这个工作队列顺序去执行任务),所以需要初始化一个 kworker 对象,该对象专门用来管理工作队列线程,但是现在仅仅只创建了一个对象,还没有具体的工作线程,所以需要通过 kworker_task 去给 kworker 对象中创建一个线程。现在线程创建好了,我们要它执行相应的任务,就需要往里面添加任务,但是任务也需要特定的结构,所以就通过 pump_messages 创建了一个任务。最后就是往工作队列线程里面添加任务了,也就是 queue_kthread_work(&master->kworker, &master->pump_messages); 函数。添加完成后,工作队列线程就会在合适的时机调度该任务去执行。

4.2.3、setup、cleanup、transfer、can_dma
int	 (*setup)(struct spi_device *spi)    = spi_imx_setup;						/* 根据spi设备更新硬件配置,如设置spi工作模式、时钟等。这里的作用是SPI片选引脚选择 */	
void (*cleanup)(struct spi_device *spi)  = spi_imx_cleanup;						/* cleanup函数会在spidev_release函数中被调用,spidev_release被登记为spi dev的release函数。*/
int (*transfer)(struct spi_device *spi, struct spi_message *mesg) = spi_queued_transfer;							/* 将消息添加到SPI控制器的消息队列上 */
bool (*can_dma)(struct spi_master *master,struct spi_device *spi, struct spi_transfer *xfer) = spi_imx_can_dma;		/* 判断SPI控制器是否可以使用DMA传输数据 */			   	

​   注释已经写的很明白,这里只稍微说下 transfer。transfer 设置的是 spi_queued_transfer 函数,该函数内部又调用了一个 __spi_queued_transfer,该函数有个传参 need_pump。当 need_pump 为 false 的时候,把 message 挂接到 SPI 控制器的消息队列上后,还需要在本线程中调用相应的函数去把该 message 取下来然后发送出去。当当 need_pump 为 true 的时候,会在内核线程中,即 4.2.2 中创建的工作队列线程中把该 message 取下来然后发送出去。其实这就是同步传输和异步传输的区别,具体可以看后续的函数讲解部分。

4.2.4、prepare_transfer_hardware、unprepare_transfer_hardware、transfer_one_message、prepare_message、unprepare_message
int (*prepare_transfer_hardware)(struct spi_master *master) 					= spi_bitbang_prepare_hardware;		/* 发起传输前被调用,这里是用来将bitbang->busy = 1,该标志位表示当前spi控制器正在被使用      */ 
int (*unprepare_transfer_hardware)(struct spi_master *master) 					= spi_bitbang_unprepare_hardware;	/* 传输完成后被调用,这里用来将bitbang->busy = 0 */			
int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg) = spi_bitbang_transfer_one;			/* message传输回调函数,用来发送spi控制器消息队列中的一条meassge */			  
int (*prepare_message)(struct spi_master *master,struct spi_message *message) 	= spi_imx_prepare_message;			/* 发起传输前被调用,这里是用来SPI时钟使能 */       
int (*unprepare_message)(struct spi_master *master,struct spi_message *message) = spi_imx_unprepare_message;		/* 传输完成后被调用,这里用来SPI时钟禁止*/	 

​   注释已经写的很明白,关于 prepare_transfer_hardware 和 unprepare_transfer_hardware 中设置的标志位 bitbang->busy 作用可以看4.1.2。这里最重要的还是 transfer_one_message,该函数设置的是 spi_bitbang_transfer_one,用来发送一条 message 的数据。在该函数内部就会将该 message 中的 transfer 取出依次发送出去,具体可以看后续的函数讲解部分。

4.2.5、set_cs、transfer_one、handle_err
/* 如果用户没有定义master->transfer_one_message传输函数,则将会默认使用spi_transfer_one_message函数,而以下三个函数就是使用该函数需要定义的回调函数 */
void (*set_cs)(struct spi_device *spi, bool enable);																/* 选择片选引脚,如果spi_device中的cs_gpio没有定义,则使用该函数实现片选引脚选择 */										
int (*transfer_one)(struct spi_master *master, struct spi_device *spi, struct spi_transfer *transfer);				/* 用来发送spi控制器消息队列中的一条meassge */
void (*handle_err)(struct spi_master *master, struct spi_message *message);											/* 当message传输发生错误时,该函数将会被执行 */

​   注释已经写的很明白,其实这三个函数就是为没有定义master->transfer_one_message传输函数准备的,但是无论有没有准备,还是得自己去写相应的实现方法。

4.2.6、mode_bits、flags、bits_per_word_mask、min_speed_hz、max_speed_hz

​   mode_bits 用来表示 SPI 控制器支持的传输模式 ,学过 SPI 通信的就知道,SPI 通信方式有四种。flags 表示该 SPI 控制器支持的功能。他的支持功能有:

#define SPI_MASTER_HALF_DUPLEX	BIT(0)		/* 不能做全双工 */
#define SPI_MASTER_NO_RX		BIT(1)		/* 不能进行缓冲区读取 */
#define SPI_MASTER_NO_TX		BIT(2)		/* 不能进行缓冲区写入 */
#define SPI_MASTER_MUST_RX      BIT(3)		/* 需要接收 */
#define SPI_MASTER_MUST_TX      BIT(4)		/* 需要发送 */

  bits_per_word_mask 是传输字节数的掩码,min_speed_hz 表示 SPI 设备最小传输速率,max_speed_hz 表示 SPI 设备最大传输速率,但是一般在解析 SPI 设备时,会在设备树中解析到相应最大的传输速率,但是如果设备树中没有定义,则将以 max_speed_hz 参数为准。

4.2.7、dma_tx、dma_rx、max_dma_len

​   这两个分别是 SPI DMA 发送通道句柄和 SPI DMA 接收通道 句柄,与 4.1.7 中结合起来,就可以设置 DMA 的发送的接收通道。max_dma_len 表示 DMA 一次最大的传输字节数。

4.2.8、bus_lock_spinlock、bus_lock_mutex、bus_lock_flag
spinlock_t	  bus_lock_spinlock = spin_lock_init(&master->bus_lock_spinlock);  	/* 和bus_lock_mutex作用一样,但是两者涉及到一个重要的机制,即总线的独占使用 */	
struct mutex  bus_lock_mutex = mutex_init(&master->bus_lock_mutex);			   	/* SP&emsp;&emsp;I控制器的消息队列为共享资源,当多个线程都同时将message挂载到消息队列上就可能出现问题,所以这里需要互斥量保护。用在同步传输中 */
bool		  bus_lock_flag;													/* 表示该spi总线被独占 */

​   注释如上所示,这三个变量主要涉及到总线独占的机制。总线独占的意思是说,当多个 SPI 设备占用同一条总线时,其中一个 SPI 设备可以采用总线独占机制,独自占有该总线,这样其它的 SPI 设备就无法使用这条总线。总线独占的代码细节在6.1中阐述。

4.2.9、dummy_rx、dummy_tx

​   这两个成员主要用来填充数据,我们知道,SPI 通信时发送和接收是同时进行的,你发送多少字节数据就会接收多少字节数据,但是如果出现发送或者接收数据时,只指定了长度而没有给 xfer->tx_buf 或者 xfer->rx_buf 赋予缓冲区,那么会使用dummy变量去填充缺失的缓冲区。比如有时候我们给 SPI 设备发送命令时,只想发送数据,而不想要接收的数据,就只给定了发送缓冲区,这样驱动就会用 dummy_rx 申请一块空间去接收数据,最后传输完毕后,驱动会自动把这块空间释放掉。这样就达到了我们只想发送数据而不想要接收数据的情况。

4.2.10、struct list_head list

​   在 Linux 内核中用了一个 spi_master_list 总链表去管理所有的 SPI 控制器,而 SPI 控制器就是通过该链表成员挂接到 spi_master_list 总链表上。

4.2.11、bus_num、num_chipselect、cs_gpios

​   这三个都是从设备树中解析出来,分别表示 SPI 控制器 ID 号和、SPI 控制器片选引脚的数量 和 指向一个保存了片选引脚的数组 。

4.2.12、cur_msg、cur_msg_prepared、cur_msg_mapped

​   cur_msg 表示当前正在处理的 message,cur_msg_prepared 表示当前的 message 已经准备好,当 master->prepare_message 执行成功过后就将 cur_msg_prepared 设置为 true。cur_msg_mapped 表示当前的message已经被映射到了连续的虚拟空间中,因为使用 DMA 传输时,需要把零散的物理地址放到连续的虚拟空间中,如果映射成功后,cur_msg_mapped 设置为 true。

4.2.13、idling、busy、running

​   running 表示 SPI 控制器是否正在运行,当内核工作队列线程创建完成后,该标志位就被设置为了 true,这样同样使用该 SPI 控制器的 SPI 设备在创建工作内核线程的时候就会失败,因为一个 SPI 控制器只需要一个内核工作队列线程。busy 表示 SPI 控制器正在被使用,当从 SPI 控制器的消息队列上取下 mesaage 进行发送时,该标志位就被设置为 true,这样同样使用该 SPI 控制器的 SPI 设备想要发送数据时就会失败。idling 表示 SPI 控制器是否空闲,这个变量将会在本次消息传输完成过后进行一些后续清理工作时被设置为 true,所以这个时候同样使用该 SPI 控制器的 SPI 设备请求发送数据就会失败,不然就会破坏清理工作这部分环节,导致一些内存无法得到释放。busy 和 idling 这两个参数具体用法可以看下 6.3 讲解。

4.2.14、rt、xfer_completion、queue

​   xfer_completion 为传输完成同步信号,该同步信号表示该 transfer 的数据传输完成,并不是 message 的数据传输完成。切记别跟 spi_imx_data 中的 xfer_done 同步信号搞混了,在 SPI 驱动框架中有一个默认的传输函数 spi_transfer_one_message,而该函数中使用到的同步信号就是 xfer_completion,但是在 I.MX6UL 中的驱动框架中并没有使用该默认的传输函数,而是使用了一个自己定义的传输函数 spi_imx_transfer,而该函数使用的同步信号就是 xfer_done。所以 xfer_completion 和 xfer_done 两个同步信号的作用是一样的,只不过应用的场景不一样。

​   4.2.2 中说到了内核工作线程,其本质其实就是一个线程,正常的参与线程调度,但是有时候我们对实时性要求高的时候,比如每次 SPI 通信需要尽快完成,就可以把 rt 标志位设置为1,这样驱动为提高实时性,把内核工作线程将以高优先级运行。queue 的话就可以看下 3.4,SPI 控制器就是通过该链表成员将所有传输的数据组织成一个队列结构。

4.2.15、queued

​   queued 表示内核工作队列线程创建是否成功,该标志位主要被用在当 SPI 控制器注销时,因为我们创建内核工作队列线程时耗费了内存资源,当我们注销 SPI 控制器时,就需要通过该标志位去判断是否有创建过内核工作队列线程,如果有就将会把这段占用的内存释放掉。

4.3、spi_device

​   spi_device 用来描述一个实际的 SPI 设备,它的定义在 include\linux\spi\spi.h 文件,如下:

struct spi_device 
{
	struct device		dev;									/* SPI控制器对应的device结构 */
	{
		struct device *parent = &master->dev;					/* 父设备为SPI控制器 */
		struct device_node	*of_node = nc; 						/* 记录spi设备节点 */
		struct bus_type	*bus = &spi_bus_type;					/* 设置该设备使用的总线 */
		struct kobject kobj.name = "%s.%u";						/* /sys/class/spi_master/spi%d/目录下的设备文件名 */	
	}
	struct spi_master	*master = master;												/* SPI设备使用的 SPI控制器 */
	u32			max_speed_hz = of_property_read_u32(nc, "spi-max-frequency", &value);	/* 通讯时钟最大频率 */
	u8			chip_select = of_property_read_u32(nc, "reg", &value);					/* 片选号,在spi中reg表示该设备的片选信号属于第几个 */
	u8			bits_per_word = 8;										 				/* 每个字长的比特数,默认是8 */
	u16			mode;				/* SPI 设备模式 */	
	
#define	SPI_CPHA	0x01			/* 指示数据是在时钟脉冲的前沿还是后沿进行采样。*/
#define	SPI_CPOL	0x02			/* 指示时钟信号的空闲状态 */

/* 表示四种SPI传输模式,由SPI_CPHA和SPI_CPOL的组合定义 */
#define	SPI_MODE_0	(0|0)			
#define	SPI_MODE_1	(0|SPI_CPHA)
#define	SPI_MODE_2	(SPI_CPOL|0)
#define	SPI_MODE_3	(SPI_CPOL|SPI_CPHA)

#define	SPI_CS_HIGH	0x04			/* 指示片选信号是活动高电平还是活动低电平 */
#define	SPI_LSB_FIRST	0x08		/* 指示是先传输最低有效位还是最高有效位 */
#define	SPI_3WIRE	0x10			/* 指示MOSI和MISO线是否组合成单个双向数据线 */
#define	SPI_LOOP	0x20			/* 指示是否启用回环模式,它可以将传输的数据直接送回接收端进行测试 */
#define	SPI_NO_CS	0x40			/* 指示只有一个设备和总线连接,无需片选信号 */
#define	SPI_READY	0x80			/* 指示从设备侧拉低该信号可以暂停SPI总线的传输*/
#define	SPI_TX_DUAL	0x100			/* 指示总线发送宽度为半字*/
#define	SPI_TX_QUAD	0x200			/* 指示总线发送宽度为字*/
#define	SPI_RX_DUAL	0x400			/* 指示总线发送宽度为半字*/
#define	SPI_RX_QUAD	0x800			/* 指示总线发送宽度为字*/

	int			irq = irq_of_parse_and_map(nc, 0);					/* SPI 设备中断号 */
	void		*controller_state;									/* 控制器状态 */
	void		*controller_data;									/* 控制器数据 */
	char		modalias[SPI_NAME_SIZE] = of_modalias_node(nc, spi->modalias, sizeof(spi->modalias);		/* 设备驱动的名字 */
	int			cs_gpio;											/* 片选引脚号 */
};
4.3.1 struct device dev

​   该成员用来在sysfs文件系统中注册 SPI 设备文件,其中 struct kobject kobj.name 就是 SPI 设备文件名,而 spi_bus_type 表示该设备属于 SPI 总线,这样 SPI 总线就可以统一管理所有的 SPI 设备。

4.3.2、controller_state、controller_data

​   controller_state 与 4.1.2 和 4.1.3 中讲到 SPI 模拟 IO 传输方式有关。如果再回看 4.1.3 中最后一部分内容, 实现模拟 IO 传输的方法调用的函数为 spi_bitbang_bufs。而 spi_bitbang_bufs 函数中又调用了 cs->txrx_bufs。这里面的 cs 是一个 struct spi_bitbang_cs 类型的结构体,而 controller_state 就指向了这个结构体。这样就可以直接通过 spi_device 中的 controller_state 成员去使用模拟 IO 传输的方法。

​   controller_data 由于驱动中并没有使用,所以没办法知道其作用,我猜测是用来对 controller_state 进行传参。

4.3.3 、max_speed_hz、chip_select、irq、modalias、cs_gpio

​   这每个成员都在开头已经注释过了,这几个成员都是通过解析设备树获得的参数。

4.3.4 、mode

​   该成员主要用来设置 SPI 设备的属性和模式,该成员主要在以下几个地方被用到:

  1. 在模拟 IO 传输中根据该成员在 bitbang->txrx_word[spi->mode & (SPI_CPOL|SPI_CPHA)] 选择相应的传输函数;
  2. 根据 spi->mode 选择片选引脚有效电平。
  3. 在 spi_imx_setupxfer 函数中根据 spi->mode 初始化 SPI 控制器。

4.3、spi_board_info

struct boardinfo {
	struct list_head		list;
	struct spi_board_info	board_info;
	{
		char		modalias[SPI_NAME_SIZE];								/* 名字 */
		const void	*platform_data;											/* 平台数据 */
		void		*controller_data;										/* 控制器数据 */
		int			irq;													/* 中断号 */
		u32			max_speed_hz;											/* 最大速率 */
		u16			bus_num;												/* spi总线编号 */
		u16			chip_select;											/* 片选 */
		u16			mode;													/* 模式 */	
	}
};

​   可以发现该结构体中的成员基本与 spi_device 一样,两者其实都是用来描述 SPI 设备信息的结构体。只不过前者用在无设备树的内核中,而后者用在有设备树的内核中。在没有设备树的内核中,需要驱动设计人员自己定义一个 struct boardinfo 类型的结构体数组,该数组保存了我们需要注册的 SPI 设备信息,最后 SPI 驱动框架在初始化中,会解析该数组注册 SPI 设备。

​   其中 list 链表成员被用来链接到 board_list 总链表上,方便遍历所有的 SPI 设备信息。

4.4、spi_message

​   spi_message 用来描述一条 SPI 消息,它和 4.5 中的 spi_transfer 一样,都是用来封装我们需要传输的数据。回顾 3.4 可知道一条 spi_message 由多个 spi_transfer 组成。我们在编写驱动时,就需要定义这两个结构体来封装我们需要传输的数据。它的定义在 include\linux\spi\spi.h 文件,如下:

struct spi_message 
{
	struct list_head	transfers;												/* spi_transfer链表队列,用来链接spi_transfer */

	struct spi_device	*spi = spi;												/* 记录该message使用的spi device */

	unsigned		is_dma_mapped:1;											/* 如果为真,此次调用提供dma和cpu虚拟地址。*/

	void			(*complete)(void *context) = spi_complete;					/* 异步调用完成后的回调函数 */
	void			*context = &done;											/* 指向创建的done同步信号,在数据传输完成后,会通过message找到该同步信号,并发送完成同步信号	 */

	unsigned		frame_length;												/* 记录该message的总长度 */
	unsigned		actual_length;												/* 记录该message已经传输完成的长度 */
	int				status;														/* 该消息的发送结果,成功被置0,否则是一个负的错误码。*/
 
	struct list_head queue;														/* 被链接到spi_master的queue链表成员下 */
	void			 *state;
};
4.4.1、transfers、queue

​   回顾 3.4 中的内容可知,这两个链表成员用来组成 SPI 控制器的消息队列结构。queue 用来挂接到 spi_master 的 queue 成员上。而 spi_transfer 的 transfer_list 成员挂接到 transfers 成员上。

4.4.2、is_dma_mapped

​   该成员表示是否使用到 DMA。如果在 SPI 传输中使用到 DMA,由于我们要发送或者接收的缓冲区往往不是连续的地址空间,所以需要将这些缓冲空间映射到一个连续的虚拟地址空间中,这样接收缓冲区和发送缓冲区都会得到一个起始的虚拟地址,而这个起始的虚拟地址就保存在 spi_transfer 中的 tx_dma 和 rx_dma成员。这样就可以通过 is_dma_mapped 成员判断是否需要使用这两个起始的虚拟地址。

4.4.3、complete、context

​   complete 是一个回调函数,context 是 complete 回调函数的一个参数,而 context 指向了一个同步信号 &done。同步信号 &done 用来表示 一条 message 传输完成。我觉得看到这里,可以把 4.1.10 、4.2.14 和这里一起结合起来看,这三者都是同步信号,看下三者有什么区别。因为同步信号&done 被保存在了 spi_message 中的 context 成员中,这样当一条 message 传输完成后,就可以调用 complete 回调函数发送 &done 同步信号。

4.4.4、frame_length、actual_length

​   frame_length 表示 message 的总长度,在一条 message 发送时,会统计里面所有的 transfer 的数据长度。actual_length 表示 message已经传输完成的长度,这样就可以通过这两个变量判断当前传输数据的进程。

4.5、spi_transfer

struct spi_transfer 
{
	const void	*tx_buf;													/* 发送缓冲区 */
	void		*rx_buf;													/* 接收缓冲区 */
	unsigned	len;														/* 缓冲区长度,tx和rx的大小(字节数)。指它们各自的大小 */

	dma_addr_t	tx_dma;														/* tx的dma地址 */
	dma_addr_t	rx_dma;														/* rx的dma地址 */
	struct sg_table tx_sg;													/* 一个包含传输缓冲区散列-聚合信息的 sg_table 结构体 */
	struct sg_table rx_sg;													/* 一个包含传输缓冲区散列-聚合信息的 sg_table 结构体 */

	unsigned	cs_change:1;												/* 当前spi_transfer发送完成之后重新片选 */
	unsigned	tx_nbits:3;													/* 传输时传输的位数 */
	unsigned	rx_nbits:3;													/* 接收时接收的位数 */
#define	SPI_NBITS_SINGLE	0x01 /* 1bit transfer */
#define	SPI_NBITS_DUAL		0x02 /* 2bits transfer */
#define	SPI_NBITS_QUAD		0x04 /* 4bits transfer */
	u8		bits_per_word;													/* 每个字长的比特数 */
	u16		delay_usecs;													/* 发送完成一个spi_transfer后的延时时间,此次传输结束和片选改变之间的延时,之后就会启动另一个传输或者结束整个消息 */
	u32		speed_hz;														/* 通信时钟 */

	struct list_head transfer_list;											/* 用于链接到spi_message,用来连接的双向链接节点 */
};

4.5.1、transfer_list

​   回顾 3.4 中的内容可知,该成员用来链接到 spi_message 中的 transfers 成员上。

4.5.2、tx_buf、rx_buf、len

​   tx_buf、rx_buf、len 分别用来表示发送缓冲区、接收缓冲区和缓冲区长度。在我们写驱动程序时,我们要发送的数据就保存在发送缓冲区中,读取到的数据就保存到接收缓冲中。len 表示缓冲区的长度,这里需要注意,在一些驱动程序中可能会看到注释说 len 是发送缓冲区和接收缓冲区的总长度,实际并不是,这里 len 分别表示发送和接收缓冲区长度为 len。

4.5.3、tx_dma、rx_dma、tx_sg、rx_sg

​   这几个变量与DMA传输有关,可以参考 3.3。

4.5.4、cs_change、tx_nbits、rx_nbits

​   cs_change 成员用来表示是否重新片选,当 cs_change 为1时,每传输一个 transfer 就会重新片选下,至于这样做的原因我也不太清楚,我猜测可能是有些芯片需要这样的一个时序要求。tx_nbits 和 rx_nbits 分别表示传输时传输的位数和接收时接收的位数。

4.5.5、bits_per_word、delay_usecs、speed_hz

​   bits_per_word 成员表示每个字长的比特数。delay_usecs 成员表示发送完成一个spi_transfer后的延时时间,这个成员我猜测也可能是为了某些芯片的时序要求设计。speed_hz 表示此次 transfer 传输使用的 SPI 时钟速度。

5、SPI 控制器注册

​   SPI 控制器的注册主要是做了两件事情,一件事情就是注册 SPI 控制器,一件事情就是注册 SPI 控制器下挂载的 SPI 设备。SPI 控制器的数量在每个芯片上是固定的,且在 Linux 内核驱动中都会默认注册全部的 SPI 控制器。但是在每个 SPI 控制器下的 SPI 设备数量是不固定的,开发人员需要根据应用场景去设置相应数量的 SPI 设备信息,最后在 SPI 控制器的注册过程中根据这些 SPI 设备信息去注册相应的 SPI 设备。

​   而设置 SPI 设备信息的方式一般有两种。第一种是无设备树的方式,这种方式需要用到 4.4 中讲到的 struct boardinfo 结构体,将我们使用的 SPI 设备信息去填充 struct boardinfo 结构体。因为我们可能会用到多个 SPI 设备,所以会用一个 struct boardinfo 结构体数组去保存。最后在内核启动中的某个环节调用 spi_register_board_info() 函数,通过 struct boardinfo 结构体数组去注册 SPI 设备。第二种方式是有设备树方式,将我们要用到 SPI 设备以设备树的形式挂接到相应的 SPI 控制器节点上,这样在 SPI 控制器注册中,就会解析该控制器下的 SPI 设备节点,从而注册 SPI 设备。

​   以下是 SPI 控制器注册流程,我会根据下面每个步骤进行说明。

在这里插入图片描述

5.1、spi_master_initialize_queue

static int spi_master_initialize_queue(struct spi_master *master)
{
	int ret;

	/* 设置 spi master 使用的 transfer 函数 */
	master->transfer = spi_queued_transfer;

	/* 如果没有设置 transfer_one_message 函数,就使用默认的 spi_transfer_one_message */								
	if (!master->transfer_one_message)									//不成立
		master->transfer_one_message = spi_transfer_one_message;

	/* 初始化 spi master 的队列 */
	ret = spi_init_queue(master);										//创建一个内核线程来处理消息传输,spi_pump_messages
	if (ret) {
		dev_err(&master->dev, "problem initializing queue\n");
		goto err_init_queue;
	}

	/* 启动 spi master 的队列 */
	master->queued = true;												//表示队列初始化成功
	ret = spi_start_queue(master);										//唤醒内核工作线程,spi_pump_messages							
	if (ret) {
		dev_err(&master->dev, "problem starting queue\n");
		goto err_start_queue;
	}

	return 0;

err_start_queue:
	/* 启动 spi master 的队列失败,销毁队列 */
	spi_destroy_queue(master);
err_init_queue:
	/* 初始化 spi master 的队列失败,返回错误码 */
	return ret;
}

​   该函数主要完成以下操作:

  1. 将 spi_queued_transfer 函数赋值给 spi_master 结构体中的 transfer 成员(该成员在4.2.3中介绍过)。spi_queued_transfer 是一个处理队列传输的函数,用于将 SPI 消息(spi_message)添加到SPI控制器的队列中进行传输。该函数在6.2中被详细讲解到。
  2. 检查是否设置了 master->transfer_one_message 回调函数,从 4.2.4 中介绍可知,该函数已经被注册过,所以该判断不成立。
  3. 调用 spi_init_queue 函数初始化内核工作队列。该函数会创建一个工作队列线程来处理消息的传输,其实就是 3.2 中涉及到的机制。
  4. 调用 spi_start_queue 函数启动队列传输。在该函数内部,其实就是调用了 queue_kthread_work 函数往工作队列线程里面添加任务,由于第三步我们已经创建了这个工作队列线程,所以在合适时间将会执行该任务。而该任务实际就是执行__spi_pump_messages()函数(该函数在6.3中详细讲到)。但是如果此时消息队列中并没有挂接数据,则实际没做什么事情。

​   另外在该函数中设置了 master->queued = true 和 master->running = true 这两个标志位,这两个标志位的作用分别在 4.2.15 和 4.2.13中讲到。

5.2、of_register_spi_device

of_register_spi_device(struct spi_master *master, struct device_node *nc)
{
	struct spi_device *spi;
	int rc;
	u32 value;

	/* Alloc an spi_device */
	spi = spi_alloc_device(master);												//分配spi device结构体,父设备为spi master 
	if (!spi) {
		dev_err(&master->dev, "spi_device alloc error for %s\n",
			nc->full_name);
		rc = -ENOMEM;
		goto err_out;
	}

	/* Select device driver */
	rc = of_modalias_node(nc, spi->modalias, sizeof(spi->modalias));
	if (rc < 0) {
		dev_err(&master->dev, "cannot find modalias for %s\n",
			nc->full_name);
		goto err_out;
	}

	/* Device address */
	rc = of_property_read_u32(nc, "reg", &value);							//在spi中reg表示该设备的片选信号属于第几个
	if (rc) {
		dev_err(&master->dev, "%s has no valid 'reg' property (%d)\n",
			nc->full_name, rc);
		goto err_out;
	}
	spi->chip_select = value;												//得到该设备属于第几个片选

	/* Mode (clock phase/polarity/etc.) */
	if (of_find_property(nc, "spi-cpha", NULL))								//设置spi传输模式
		spi->mode |= SPI_CPHA;
	if (of_find_property(nc, "spi-cpol", NULL))
		spi->mode |= SPI_CPOL;
	if (of_find_property(nc, "spi-cs-high", NULL))
		spi->mode |= SPI_CS_HIGH;
	if (of_find_property(nc, "spi-3wire", NULL))
		spi->mode |= SPI_3WIRE;
	if (of_find_property(nc, "spi-lsb-first", NULL))
		spi->mode |= SPI_LSB_FIRST;

	/* Device DUAL/QUAD mode */
	if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) {
		switch (value) {
		case 1:
			break;
		case 2:
			spi->mode |= SPI_TX_DUAL;
			break;
		case 4:
			spi->mode |= SPI_TX_QUAD;
			break;
		default:
			dev_warn(&master->dev,
				"spi-tx-bus-width %d not supported\n",
				value);
			break;
		}
	}

	if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) {
		switch (value) {
		case 1:
			break;
		case 2:
			spi->mode |= SPI_RX_DUAL;
			break;
		case 4:
			spi->mode |= SPI_RX_QUAD;
			break;
		default:
			dev_warn(&master->dev,
				"spi-rx-bus-width %d not supported\n",
				value);
			break;
		}
	}

	/* Device speed */
	rc = of_property_read_u32(nc, "spi-max-frequency", &value);								//获取该设备spi传输的最大速度
	if (rc) {
		dev_err(&master->dev, "%s has no valid 'spi-max-frequency' property (%d)\n",
			nc->full_name, rc);
		goto err_out;
	}
	spi->max_speed_hz = value;

	/* IRQ */
	spi->irq = irq_of_parse_and_map(nc, 0);													//得到该设备的中断号

	/* Store a pointer to the node in the device structure */
	of_node_get(nc);
	spi->dev.of_node = nc;																	//保存该spi设备的设备节点

	/* Register the new device */
	rc = spi_add_device(spi);
	if (rc) {
		dev_err(&master->dev, "spi_device register error %s\n",
			nc->full_name);
		goto err_out;
	}

	return spi;

err_out:
	spi_dev_put(spi);
	return ERR_PTR(rc);
}

​   该函数主要完成以下操作:

  1. 分配一个SPI设备结构体(struct spi_device)并进行初始化。
  2. 从设备树节点中读取并设置SPI设备的属性,例如设备驱动程序的模块别名、设备地址、SPI模式(时钟相位、极性等)、数据传输宽度(单线、双线、四线等)、最大传输速度等。
  3. 解析并映射设备树节点中的中断,并设置SPI设备结构体的中断属性。
  4. 注册 SPI 设备。

​   该函数其实大部分都是在根据设备树节点信息来设置 SPI 设备的属性,这样我们使用该 SPI 设备传输数据时,SPI 驱动框架才知道该怎么去传输数据。另外还有最重要的一点就是,注册了 SPI 设备。

5.3、spi_alloc_device

struct spi_device *spi_alloc_device(struct spi_master *master)
{
	struct spi_device	*spi;

	/* 获取 spi_master 对象 */
	if (!spi_master_get(master))
		return NULL;

	/* 分配 spi_device 结构体 */
	spi = kzalloc(sizeof(*spi), GFP_KERNEL);
	if (!spi) {
		spi_master_put(master);
		return NULL;
	}

	/* 初始化 spi_device 结构体 */
	spi->master = master;
	spi->dev.parent = &master->dev;
	spi->dev.bus = &spi_bus_type;					
	spi->dev.release = spidev_release;
	spi->cs_gpio = -ENOENT;
	device_initialize(&spi->dev);
	return spi;
}

​   该函数主要完成以下操作:

  1. 分配 spi_device 结构体 。
  2. 初始化 spi_device 结构体。

​   这里说下spi_device 结构体的初始化,其中 spi_bus_type 是 SPI 驱动框架初始化时注册的 SPI 总线,这里将它赋值给 spi->dev.bus 表明该 SPI 设备属于 SPI 总线,这样当涉及一些 SPI 总线操作时,就可以遍历该总线下所有的 SPI 设备。

​   device_initialize(&spi->dev);该函数用来初始化 spi_device 结构体,当调用该函数过后,就可以调用 device_add(&spi->dev);函数完成设备的注册。其实 device_initialize 和 device_add 这两个函数实现的功能等同于 device_create 函数实现的功能,只不过这里是拆开了使用,这种拆开的方式在内核设备注册过程中十分常见。

​   这里我说下一个我另外的猜想。在这里可以看到,在 SPI 控制器的注册过程中已经完成对各个 SPI 设备的创建,而在一些驱动外设代码中,比如 SPI 接口的 icm20608 芯片,会发现在其中又重新创建了一个 SPI 设备。那这两者有什么区别呢?我的猜测是,前者主要用在 sysfs 文件系统中,这样在应用层可以通过 SPI 设备的属性文件去操作 SPI 设备。而后者则是通过创建字符设备,用文件IO的方式去操作 SPI 设备。但是我们可以看到在这段代码的初始化中并没有涉及到 SPI 设备的属性文件,这是因为该驱动源码就并没有去设置,如果看过我写的 IIC 驱动框架源码分析的文章,就可以发现在 IIC 驱动框架中是给每个 IIC 设备都创建了属性文件。 如果要设置的话,就额外还要设置spi->dev.type成员。

5.4、spi_add_device

int spi_add_device(struct spi_device *spi)
{
	static DEFINE_MUTEX(spi_add_lock);
	struct spi_master *master = spi->master;
	struct device *dev = master->dev.parent;
	int status;

	/* 检查chip select编号是否合法 */
	if (spi->chip_select >= master->num_chipselect) {				//判断该设备的片选号是否有效
		dev_err(dev, "cs%d >= max %d\n",
			spi->chip_select,
			master->num_chipselect);
		return -EINVAL;
	}

	spi_dev_set_name(spi);				/* 根据使用的片选引脚设置设备文件名,如使用的是SPI1的片选3,则设备文件名为spi1.3 */
	mutex_lock(&spi_add_lock);

	status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check);				/* 检查SPI总线上是否已经存在该SPI设备 */
	if (status) {
		dev_err(dev, "chipselect %d already in use\n",
				spi->chip_select);
		goto done;
	}

	if (master->cs_gpios)															//成立,在spi master注册里面里面已经获取了所有的片选引脚
		spi->cs_gpio = master->cs_gpios[spi->chip_select];

	status = spi_setup(spi);														//初始化SPI设备
	if (status < 0) {
		dev_err(dev, "can't setup %s, status %d\n",
				dev_name(&spi->dev), status);
		goto done;
	}

	status = device_add(&spi->dev);													//注册SPI设备
	if (status < 0)
		dev_err(dev, "can't add %s, status %d\n",
				dev_name(&spi->dev), status);
	else
		dev_dbg(dev, "registered child %s\n", dev_name(&spi->dev));

done:
	mutex_unlock(&spi_add_lock);
	return status;
}

​   该函数主要完成以下操作:

  1. 对新的 SPI 设备的芯片选择进行验证,确保它小于主设备支持的最大芯片选择数。
  2. 为 SPI 设备设置一个唯一的名称,以便区分不同的设备,同时为了在/sys/class/spi_master/spi%d/目录下创建设备文件。
  3. 在调用设备的 setup() 函数之前,检查是否已经有其他设备使用了当前设备的芯片选择。
  4. 调用 spi_setup() 函数来对 SPI 设备进行初始化。
  5. 使用 device_add() 函数将当前 SPI 设备添加到总线上,同时创建 SPI 设备文件。

​   这里说下这个函数 bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check); 其实这个函数在内核中用的挺多的,其作用就是遍历整个总线上的 SPI 设备然后执行 spi_dev_check 函数去判断当前注册的 SPI 设备是否已经存在。注意,这里是指整个 SPI 总线,并不是指 SPI1、SPI2 这样的单独一条总线。这也是为什么在 SPI 设备初始化时都会设置 spi->dev.bus = &spi_bus_type; 这一步。

5.5、spi_match_master_to_boardinfo

static void spi_match_master_to_boardinfo(struct spi_master *master, struct spi_board_info *bi)
{
	struct spi_device *dev;
 	/* 总线编号匹配 */
	if (master->bus_num != bi->bus_num)
		return;
	/* 注册SPI设备 */
	dev = spi_new_device(master, bi);
	if (!dev)
		dev_err(master->dev.parent, "can't create new device for %s\n",
			bi->modalias);
}

​   该函数被用在无内核的设备树中,该函数的传参参数 bi,也就是提前定义好的 SPI 设备信息。如果是在有设备树的内核中,SPI 设备信息由设备树提供。该函数中的 spi_new_device 函数主要干了两件事情,第一件事情就是把 spi_board_info 类型的 SPI 设备信息转换成 spi_device 类型的 SPI 设备信息。第二件事情就是注册 SPI 设备,也就是调用 spi_add_device 函数。

6、SPI 数据传输

​   在 SPI 驱动框架中提供了两种数据传输方式:同步方式和异步方式。这里说的同步不是指硬件层次上的同步和异步,而是指软件层次上。在同步方式中,SPI 控制器发起一次传输后,必须等待传输完成后才能进行下一次的传输。而异步方式,SPI 控制器发起一次传输后,可以立刻返回进行下一次的传输。同步方式和异步方式软件实现方式大部分相同,但是有部分细节需要注意,后续讲到会说。同步方式简单易用,很适合处理那些少量数据的单次传输。但是对于数据量大、次数多的传输来说,异步方式就显得更加合适。我主要以同步传输讲解为主,后面会适当提一下异步传输和同步传输代码上的区别,实际两者代码基本类似。

​   以下是 SPI 同步传输的函数调用流程图,我会根据下面每个步骤进行说明。

在这里插入图片描述

6.1、__spi_sync(内含总线独占的机制讲解)

static int __spi_sync(struct spi_device *spi, struct spi_message *message, int bus_locked)
{
	DECLARE_COMPLETION_ONSTACK(done);						//创建一个同步信号放在内核堆栈中,如果不加ONSTACK就是静态申请,会存放在全局变量区
	int status;
	struct spi_master *master = spi->master;
	unsigned long flags;

	status = __spi_validate(spi, message);
	if (status != 0)
		return status;

	message->complete = spi_complete;						//设置调用完成后的回调函数
	message->context = &done;								//指向创建的done同步信号,在数据传输完成后,会通过message找到该同步信号,并发送完成同步信号					
	message->spi = spi;										//记录该message使用的spi device

	if (!bus_locked)
		mutex_lock(&master->bus_lock_mutex);

	if (master->transfer == spi_queued_transfer) {					//成立,在spi_register_master函数中完成注册
		spin_lock_irqsave(&master->bus_lock_spinlock, flags);

		trace_spi_message_submit(message);

		status = __spi_queued_transfer(spi, message, false);		//将该messge挂接到spi master的消息队列上,

		spin_unlock_irqrestore(&master->bus_lock_spinlock, flags);
	} else {
		status = spi_async_locked(spi, message);					
	}

	if (!bus_locked)
		mutex_unlock(&master->bus_lock_mutex);

	if (status == 0) {
		if (master->transfer == spi_queued_transfer)
			__spi_pump_messages(master, false);						//处理spi master的消息队列上的数据

		wait_for_completion(&done);									//等待传输完成
		status = message->status;
	}
	message->context = NULL;
	return status;
}

  函数的参数包括:

  • spi:指向所属SPI设备的结构体指针;
  • message:指向spi_message结构体的指针,该结构体描述了数据传输的相关信息;
  • bus_locked:用于指示总线是否已被锁定,0表示未锁定,1表示已锁定。

​   该函数主要完成以下操作:

  1. 创建同步信号 done,该信号用于实现同步等待。
  2. 初始化spi_message结构体中的一些字段,包括 message->complete,message->context 和 message->spi。message->complete是回调函数指针,用于在传输完成时通知应用程序或其他线程,这里指定为 spi_complete,表示在传输完成时调用 spi_complete 函数。message->context 是传递给回调函数的参数,在这里指定为&done,即使用DECLARE_COMPLETION_ONSTACK定义的done变量的地址。message->spi是指向spi_device的指针,用于标识该消息将传输到哪个设备上。
  3. 将一个spi message加入队列等待传输,函数会将message添加到spi->queued队列中。这里只是添加到队列上,并没有发送。
  4. 处理spi master的消息队列上的数据,从消息队列中取出消息,对于每个消息,将其传递给驱动程序以执行 SPI 传输。
  5. 等待传输完成。

​   这里面涉及到一个重要的机制,即总线的独占使用。意思就是说,当多个SPI设备共用一条SPI总线时,如果此时一个设备想独自占用该总线使用,就要用到该机制。首先看spi_bus_lock()函数。

int spi_bus_lock(struct spi_master *master)
{
	unsigned long flags;
	mutex_lock(&master->bus_lock_mutex);					/* 获取互斥锁 */

	spin_lock_irqsave(&master->bus_lock_spinlock, flags);
	master->bus_lock_flag = 1;								/* 总线独占标志位置1 */
	spin_unlock_irqrestore(&master->bus_lock_spinlock, flags);
	return 0;
}

​   这个函数做的事情其实很简单,首先获取 master->bus_lock_mutex 这个互斥锁,然后把master->bus_lock_flag标志位置1,表示该总线被独占。那么为什么这样做了后,就能实现总线独占的功能?首先看同步传输的函数 spi_sync,该函数就调用了__spi_sync函数,即我们这节讲到的函数。该函数的传递参数bus_locked为0,表示此时总线并未有被独占。那么在该函数中 mutex_lock(&master->bus_lock_mutex);这段代码就能正常的去获取互斥锁。但是在 spi_bus_lock 函数中已经获取过了bus_lock_mutex 这个互斥锁并且没有释放,那么在 _spi_sync函数中就无法获取到该互斥锁,则总线独占后同步传输无法传输数据。再来看异步传输spi_async()函数

int spi_async(struct spi_device *spi, struct spi_message *message)
{
	struct spi_master *master = spi->master;
	int ret;
	unsigned long flags;

	ret = __spi_validate(spi, message);
	if (ret != 0)
		return ret;

	spin_lock_irqsave(&master->bus_lock_spinlock, flags);

	if (master->bus_lock_flag)					/* 判断总线是否被独占 */	
		ret = -EBUSY;
	else
		ret = __spi_async(spi, message);

	spin_unlock_irqrestore(&master->bus_lock_spinlock, flags);

	return ret;
}

​   在该函数中,判断了 master->bus_lock_flag 标志位是否为1,而在spi_bus_lock()函数中已经将该标志位置1,那么spi_async函数就会直接返回,则总线独占后异步传输也无法传输数据。

​   现在我们知道总线独占后,异步传输和同步传输都无法传输数据了。但是总线独占后,有什么好处呢?这样当SPI总线下的SPI设备使用spi_bus_lock()函数后,就能独自使用该总线。但是前面也说了,当总线被独占后,同步传输和异步传输都不能传输数据,那这个SPI设备怎么去传输数据呢?其实在SPI驱动框架中就提供了一个专用于总线被独占情况下的SPI传输函数,即spi_async_locked()和spi_sync_locked()。这两个函数具体如下:

int spi_sync_locked(struct spi_device *spi, struct spi_message *message)
{
	return __spi_sync(spi, message, 1);
}

int spi_async_locked(struct spi_device *spi, struct spi_message *message)
{
	struct spi_master *master = spi->master;
	int ret;
	unsigned long flags;

	ret = __spi_validate(spi, message);
	if (ret != 0)
		return ret;

	spin_lock_irqsave(&master->bus_lock_spinlock, flags);

	ret = __spi_async(spi, message);

	spin_unlock_irqrestore(&master->bus_lock_spinlock, flags);

	return ret;

}

  可以发现这两个函数与原来的同步传输和异步传输函数非常类似,但是有些细节不一样了。比如在同步传输函数中,该函数的传递参数master->bus_locked为1,不再是0,这样在__spi_sync函数中就不再去获取bus_lock_mutex 这个互斥锁,这样那么总线被独占,也能正常传输数据。再看异步传输函数中,该函数没有了对master->bus_lock_flag 标志位的判断,也说明能正常传输数据了。这样在SPI总线被独占时,SPI设备就可以使用这两个函数去实现SPI的传输。

​   另外说明下,总线被独占并不说明只能一个SPI设备使用该总线,只要使用spi_sync_locked()和spi_async_locked()两个函数的SPI设备也都能使用SPI总线。但是这样难免会出现多个SPI设备访问一条总线的情况。在总线没有被独占时,主要是master->bus_locked这个互斥锁完成对共享资源的保护,而在总线被独占时,这时就需要master->bus_lock_spinlock自旋锁完成对共享资源的保护。这也是为什么在SPI控制器中需要同时声明这两个变量。

6.2、__spi_queued_transfer

static int __spi_queued_transfer(struct spi_device *spi, struct spi_message *msg, bool need_pump)
{
	struct spi_master *master = spi->master;						//得到该spi设备的spi控制器
	unsigned long flags;

	spin_lock_irqsave(&master->queue_lock, flags);

	if (!master->running) {											//不成立,在spi_register_master函数中设置为了true
		spin_unlock_irqrestore(&master->queue_lock, flags);
		return -ESHUTDOWN;
	}
	msg->actual_length = 0;											//传输长度清0,该值会在传输完成后被更新
	msg->status = -EINPROGRESS;

	list_add_tail(&msg->queue, &master->queue);						//将该messge挂接到队列上
	if (!master->busy && need_pump)
		queue_kthread_work(&master->kworker, &master->pump_messages);	//将工作项放入指定内核线程的工作队列中,该工作执行函数为__spi_pump_messages。

	spin_unlock_irqrestore(&master->queue_lock, flags);
	return 0;
}

  函数的参数包括:

  • spi:指向所属SPI设备的结构体指针;
  • message:指向spi_message结构体的指针,该结构体描述了数据传输的相关信息;
  • need_pump:用于是否需要启动消息泵(不知道这么翻译对不对)来处理这个spi message,true表示需要,false表示不需要。

​   该函数需要注意一个参数need_pump,这里涉及到到SPI同步传输和异步传输的区别。在异步传输中,当need_pump为true后,就会执行queue_kthread_work()函数。这样当内核线程准备好时,就会调用_spi_pump_messages()函数来处理消息队列上的数据。这也是为什么异步传输只需要把spi message挂接到spi master的消息队列就可以直接返回的原因,因为此时数据传输已经交给了内核线程处理。但是当need_pump为false后,该函数的条件不满足,就执行不了queue_kthread_work()函数。这时会在该函数执行完成后调用 _spi_pump_messages()函数来处理消息队列上的数据,而不是像异步通信方式那样,通过内核线程的方式调用 _spi_pump_messages()函数。

​   该函数主要完成以下操作:

  1. 首先获取 spi master 的锁,防止同时有多个线程同时对主设备进行操作。
  2. 检查 spi master 是否正在运行,如果不是则解锁并返回错误码。
  3. 初始化 spi message 的状态和实际传输长度。
  4. 将该 message 添加到 spi master 的消息队列中。
  5. 如果 spi master 当前未繁忙且需要启动消息泵,则将该任务添加到 spi master 的工作队列中,以便在 kworker 线程中处理。
  6. 解锁 spi master 并返回0表示成功。

6.3、__spi_pump_messages(内含同步传输和异步传输流程上的区别讲解)

​   该函数分成三部分进行分析,第一部分是参数判断,第二部分是传输完成后一些参数的恢复,第三部分是数据的传输。

static void __spi_pump_messages(struct spi_master *master, bool in_kthread)
{
	unsigned long flags;
	bool was_busy = false;
	int ret;

	spin_lock_irqsave(&master->queue_lock, flags);

	if (master->cur_msg) 														 
	{
		spin_unlock_irqrestore(&master->queue_lock, flags);
		return;
	}

	if (master->idling) 
	{													
		queue_kthread_work(&master->kworker, &master->pump_messages);			   //将work插入woker的work_list链表中
		spin_unlock_irqrestore(&master->queue_lock, flags);
		return;
	}

  函数的参数包括:

  • master:指向所属SPI控制器的结构体指针;
  • in_kthread:表示此函数是否从内核线程中调用(即作为操作系统内核的一部分运行的线程)。如果 in_kthread 为 true,则函数假定它在内核上下文中运行,并可能执行某些在非内核上下文中不安全的操作;

​   这段函数主要完成以下操作:

  1. 在 spi_master 上获取锁,以防止其他线程在此函数运行时访问它。
  2. 检查 spi_master 的消息队列中是否有任何消息。如果有消息,则释放锁并返回。
  3. 检查 spi_master是否处于空闲。如果是,则将工作添加到工作队列中以便在未来继续处理。
	if (list_empty(&master->queue) || !master->running) {						   /* 消息队列为空或者spi没有运行		*/
		if (!master->busy) 														/* 如果SPI没有正在传输数据则直接返回 */
		{
			spin_unlock_irqrestore(&master->queue_lock, flags);
			return;
		}

		if (!in_kthread) 														/* 如果工作在中断上下文 */
		{			
			queue_kthread_work(&master->kworker, &master->pump_messages);			//则将pump_messages工作队列添加到内核线程中执行,因为在中断上下文中是不允许执行睡眠函数(如等待队列)
			spin_unlock_irqrestore(&master->queue_lock, flags);
			return;
		}

		master->busy = false;													
		master->idling = true;
		spin_unlock_irqrestore(&master->queue_lock, flags);

		/* 释放掉 spi master 中的 dummy_rx 和 dummy_tx 两个缓冲区,并调用 spi_master_unprepare_transfer_hardware 函数来清理硬件状态 */
		kfree(master->dummy_rx);
		master->dummy_rx = NULL;
		kfree(master->dummy_tx);
		master->dummy_tx = NULL;
		if (master->unprepare_transfer_hardware && master->unprepare_transfer_hardware(master))			//bitbang->busy = 0;
			dev_err(&master->dev, "failed to unprepare transfer hardware\n");
		if (master->auto_runtime_pm) {
			pm_runtime_mark_last_busy(master->dev.parent);
			pm_runtime_put_autosuspend(master->dev.parent);
		}
		trace_spi_master_idle(master);

		spin_lock_irqsave(&master->queue_lock, flags);
		master->idling = false;
		spin_unlock_irqrestore(&master->queue_lock, flags);
		return;
	}

​   这段函数主要完成以下操作:

  1. 判断消息队列是否为空或者 spi 是否正在运行,如果都满足,则直接返回。
  2. 判断 spi_master 是否处于忙碌状态,如果没有,则直接返回。
  3. 如果当前工作不在内核线程中(在中断上下文),则将 __spi_pump_messages() 函数添加到内核线程的工作队列中等待执行,因为在中断上下文中不能执行睡眠函数。
  4. spi_master 忙碌标志位和空闲状态位清除。
  5. 当所有的消息处理完毕后,会将 spi_master 结构体中的 dummy_rx 和 dummy_tx 两个缓冲区释放,并且调用 spi_master_unprepare_transfer_hardware() 函数来清理硬件状态。
	master->cur_msg = list_first_entry(&master->queue, struct spi_message, queue); //拿出在spi master下挂接的message队列中的第一个message
	list_del_init(&master->cur_msg->queue);										//将拿出的message从message队列中删除
	if (master->busy)
		was_busy = true;
	else
		master->busy = true;												  //表示该spi master正在被设备使用
	spin_unlock_irqrestore(&master->queue_lock, flags);

	if (!was_busy && master->auto_runtime_pm) {
		ret = pm_runtime_get_sync(master->dev.parent);
		if (ret < 0) {
			dev_err(&master->dev, "Failed to power device: %d\n", ret);
			return;
		}
	}

	if (!was_busy)
		trace_spi_master_busy(master);

	if (!was_busy && master->prepare_transfer_hardware) {					//成立,在spi_bitbang_start函数中完成注册,且在该函数中还完成了spi master的注册						
		ret = master->prepare_transfer_hardware(master);					//bitbang->busy = 1;
		if (ret) {
			dev_err(&master->dev, "failed to prepare transfer hardware\n");

			if (master->auto_runtime_pm)
				pm_runtime_put(master->dev.parent);
			return;
		}
	}

	trace_spi_message_start(master->cur_msg);							

	if (master->prepare_message) {										  //成立,在spi_imx_probe函数中完成注册,
		ret = master->prepare_message(master, master->cur_msg);				//使能spi时钟
		if (ret) {
			dev_err(&master->dev, "failed to prepare message: %d\n", ret);
			master->cur_msg->status = ret;
			spi_finalize_current_message(master);
			return;
		}
		master->cur_msg_prepared = true;
	}

	ret = spi_map_msg(master, master->cur_msg);						//将当前message的数据映射到连续的虚拟地址上,以便DMA传输
	if (ret) {
		master->cur_msg->status = ret;
		spi_finalize_current_message(master);
		return;
	}

	ret = master->transfer_one_message(master, master->cur_msg);	 //发送数据,该函数在spi_bitbang_start函数中完成注册
	if (ret) {
		dev_err(&master->dev, "failed to transfer one message from queue\n");
		return;
	}
}

​   这段函数主要完成以下操作:

  1. 通过 list_first_entry 函数获取消息队列中的第一个 message,并将其赋值给 master->cur_msg。
  2. 通过 list_del_init 函数将 master->cur_msg 从消息队列中删除。
  3. 判断 master->busy 是否为真,若为真则将 was_busy 置为真,否则将 master->busy 置为真。设置这一步的目的是为了避免重复的执行 master->prepare_transfer_hardware 函数。如果消息队列上有多个消息,那么该函数应该执行一次就够了,不然每传输一次消息都会执行一次这个函数,这样传输效率就会变低。
  4. 调用 master->prepare_transfer_hardware函数,在新的数据传输前,做好硬件准备工作。
  5. 调用 master->prepare_message函数使能 SPI 时钟。
  6. 调用 spi_map_msg 函数将当前 message 的数据映射到连续的虚拟地址上,以便 DMA 传输。
  7. 调用 master->transfer_one_message 函数来发送数据。

​   该函数主体功能部分不难理解,即处理消息队列上的消息。但是在正式处理消息之前,有一部分关于spi master状态的判断,我觉得对于这些状态判断的理解有助于更加深刻理解spi 消息队列处理消息的机制。这部分状态的判断属于我个人的理解,有可能有错误,这里面一些标志位的判断有时候实在让我想不通在什么情况下才会出现那样的情况。

​   先简单了解下在异步传输中,消息队列处理数据的整个流程。在异步传输时,将需要传输的消息挂接到 spi 控制器的消息队列上,然后发送消息处理请求到内核线程上,这样内核线程准备好时,就会调用__spi_pump_messages()函数。如果消息队列上有多个消息,这样当本次消息处理完毕后,会在spi_finalize_current_message()函数(该函数后续会讲解)再次调用queue_kthread_work()函数发送消息处理请求到内核线程上,最后一直循环,直到消息队列上的数据全部处理完毕。

​   在__spi_pump_messages()函数中,首先就判断了master->cur_msg是否为空,如果不为空,则表示此时spi控制器正在处理数据,所以此时直接返回。但是如果就在这里返回了,怎么再次调用该函数发送此次数据呢?这就涉及到刚说的异步传输流程中的spi_finalize_current_message()函数,当一次消息处理完成后,就会再次调用queue_kthread_work()函数发送消息处理请求到内核线程。当执行到spi_finalize_current_message()函数时就说明本次消息处理已经完成了,就会把master->cur_msg=NULL,所以这是为什么在queue_kthread_work()函数里执行queue_kthread_work()函数可以接着处理消息队列后面的数据。

​   这里就能看出异步传输的一个特点,当多个线程使用同一个spi总线发送数据时,只会把数据挂接到消息队列上,至于消息队列上的数据是怎么处理的,内核线程会去处理。由于内核线程执行的顺序是先进先出,所以每个__spi_pump_messages()函数执行是不会冲突的,只有当本次消息的 _spi_pump_messages()函数执行完成后才会执行下一个 _spi_pump_messages()函数。

​   但是同步传输就不一样,同步传输是直接调用_spi_pump_messages()函数处理消息,而不是通过内核线程的方式,这样在多线程使用时就会发生一些冲突。

​   第一种情况:当第一个线程调用_spi_pump_messages()函数还没有处理完本次消息,第二个线程又开始调用 _spi_pump_messages()函数,那么由于master->cur_msg不为空,则直接返回。这样后续的流程跟异步传输差不多,当第一个线程处理本次消息后,会在spi_finalize_current_message()函数中调用queue_kthread_work()函数,通过内核线程的方式执行第二个线程挂接到消息队列上的消息。

​   第二种情况:当第一个线程调用_spi_pump_messages()函数完成本次消息处理后,会在spi_finalize_current_message()函数中调用queue_kthread_work()函数,这次使用内核线程是为了清一些 spi master 状态标志。此次调用 _spi_pump_messages()函数,list_empty(&master->queue) 条件将会成立,就会把master->busy = false,master->idling = true,表示此刻总线不忙,空闲。当总线空闲后,表明此次消息处理完成,就要释放master->dummy_rx和master->dummy_tx两个缓冲区。但是如果在释放这两个缓冲区时,第二个线程开始调用 _spi_pump_messages()函数,master->idling=ture条件成立,向内核线程发起消息请求,然后返回。之所以会这样是,在第一个线程中master->busy = false,master->idling = true,表明此次消息处理完成。如果第二个线程再次发起传输,就是一个新的消息传输请求,那么在新的消息请求开始之前,需要等待第一次消息请求的一些后续清理工作完成。所以,只要第一个消息请求后续工作请求没有完成,那么master->idling就一直为true,那么第二个线程就会一直反复执行 _spi_pump_messages()函数。

6.4、spi_map_msg

static int spi_map_msg(struct spi_master *master, struct spi_message *msg)
{
	struct spi_transfer *xfer;
	void *tmp;
	unsigned int max_tx, max_rx;

	if (master->flags & (SPI_MASTER_MUST_RX | SPI_MASTER_MUST_TX)) 			/* 如果使能了SPI DMA接收和发送 */
	{
		max_tx = 0;
		max_rx = 0;

		/* 如果出现发送数据时,只指定了长度而没有赋值数据,那么会使用dummy变量去填充缺失的数据 */
		list_for_each_entry(xfer, &msg->transfers, transfer_list) 
		{
			if ((master->flags & SPI_MASTER_MUST_TX) && !xfer->tx_buf)		
				max_tx = max(xfer->len, max_tx);
			if ((master->flags & SPI_MASTER_MUST_RX) && !xfer->rx_buf)
				max_rx = max(xfer->len, max_rx);
		}

		if (max_tx) {
			tmp = krealloc(master->dummy_tx, max_tx, GFP_KERNEL | GFP_DMA);
			if (!tmp)
				return -ENOMEM;
			master->dummy_tx = tmp;
			memset(tmp, 0, max_tx);
		}

		if (max_rx) {
			tmp = krealloc(master->dummy_rx, max_rx, GFP_KERNEL | GFP_DMA);
			if (!tmp)
				return -ENOMEM;
			master->dummy_rx = tmp;
		}

		if (max_tx || max_rx) {
			list_for_each_entry(xfer, &msg->transfers, transfer_list) 
			{
				if (!xfer->tx_buf)
					xfer->tx_buf = master->dummy_tx;
				if (!xfer->rx_buf)
					xfer->rx_buf = master->dummy_rx;
			}
		}
	}

	return __spi_map_msg(master, msg);
}

​   这段函数主要完成了两件事。第一件事情是判断该消息内有没有存在只给了数据长度却没有分配发送缓冲或者接收缓冲的transfer,如果存在,就必须使用 master->dummy_tx 和 master->dummy_rx 去申请缓冲去填补这个缺失缓冲的transfer,否则发送和接收数据的时序将会被打乱。第二件事就是将当前 message 的数据映射到连续的虚拟地址上,这部分的过程可以参考3.3中的知识点。

6.5、spi_bitbang_transfer_one


static int spi_bitbang_transfer_one(struct spi_master *master, struct spi_message *m)
{
	struct spi_bitbang	*bitbang;
	unsigned		nsecs;
	struct spi_transfer	*t = NULL;
	unsigned		cs_change;
	int			status;
	int			do_setup = -1;
	struct spi_device	*spi = m->spi;

	bitbang = spi_master_get_devdata(master);

	/* FIXME this is made-up ... the correct value is known to
	 * word-at-a-time bitbang code, and presumably chipselect()
	 * should enforce these requirements too?
	 */
	nsecs = 100;

	cs_change = 1;
	status = 0;

	list_for_each_entry(t, &m->transfers, transfer_list) 			/* 遍历该message下的所有transfer */
	{
		if (t->speed_hz || t->bits_per_word)						/*检查spi接口通信速度和字节掩码有没有设置 */			
			do_setup = 1;

		/* init (-1) or override (1) transfer params */
		if (do_setup != 0) 
		{
			if (bitbang->setup_transfer) 							//该函数在spi_probe_imx函数下注册
			{	
				status = bitbang->setup_transfer(spi, t);			//配置spi和spi FIFO dma		
				if (status < 0)
					break;
			}
			if (do_setup == -1)
				do_setup = 0;
		}

		/* set up default clock polarity, and activate chip;
		 * this implicitly updates clock and spi modes as
		 * previously recorded for this device via setup().
		 * (and also deselects any other chip that might be
		 * selected ...)
		 */
		if (cs_change) {
			bitbang->chipselect(spi, BITBANG_CS_ACTIVE);		//该设备片选引脚置为低电平
			ndelay(nsecs);
		}
		cs_change = t->cs_change;
		if (!t->tx_buf && !t->rx_buf && t->len) 				/* 不允许发送只有长度没有数据的transfer */
		{
			status = -EINVAL;
			break;
		}

		/* transfer data.  the lower level code handles any
		 * new dma mappings it needs. our caller always gave
		 * us dma-safe buffers.
		 */
		if (t->len) {
			/* REVISIT dma API still needs a designated
			 * DMA_ADDR_INVALID; ~0 might be better.
			 */
			if (!m->is_dma_mapped)
				t->rx_dma = t->tx_dma = 0;
			status = bitbang->txrx_bufs(spi, t);				/* 发送数据 */ 
		}
		if (status > 0)
			m->actual_length += status;							//记录该message已经发送成功的字节数
		if (status != t->len) {
			/* always report some kind of error */
			if (status >= 0)
				status = -EREMOTEIO;
			break;
		}
		status = 0;

		/* protocol tweaks before next transfer */
		if (t->delay_usecs)
			udelay(t->delay_usecs);

		if (cs_change &&
		    !list_is_last(&t->transfer_list, &m->transfers)) {
			/* sometimes a short mid-message deselect of the chip
			 * may be needed to terminate a mode or command
			 */
			ndelay(nsecs);
			bitbang->chipselect(spi, BITBANG_CS_INACTIVE);	//该设备片选引脚置为高电平
			ndelay(nsecs);
		}
	}

	m->status = status;

	/* normally deactivate chipselect ... unless no error and
	 * cs_change has hinted that the next message will probably
	 * be for this chip too.
	 */
	if (!(status == 0 && cs_change)) {
		ndelay(nsecs);
		bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
		ndelay(nsecs);
	}

	spi_finalize_current_message(master);				/* 发送完成,处理内核线程中下个发送请求,并发送本次传输完成的同步信号 */	
	return status;
}

​   该函数主要完成以下几个步骤:

  1. 遍历该message下的所有transfer。
  2. 配置spi和spi FIFO dma。
  3. 将设备片选引脚置为低电平,准备启动传输。
  4. 启动传输。
  5. 将设备片选引脚置为高电平。
  6. 处理内核线程中的下一个发送请求,并发送本次传输完成的同步信号。

6.6、spi_imx_transfer(内含DMA发送特殊情况的讲解)

static int spi_imx_transfer(struct spi_device *spi, struct spi_transfer *transfer)
{
	int ret;
	struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);

	/* 如果传输结构体中的buf长度小于TXFIFO,则通过PIO方式发送数据 */
	if (spi_imx->bitbang.master->can_dma && spi_imx_can_dma(spi_imx->bitbang.master, spi, transfer))
	{
		spi_imx->usedma = true;
		ret = spi_imx_dma_transfer(spi_imx, transfer);
		spi_imx->usedma = false; /* clear the dma flag */
		if (ret != -EAGAIN)
			return ret;
	}
	spi_imx->usedma = false;

	return spi_imx_pio_transfer(spi, transfer);
}

​   该函数包含了两个传输方式,一种是PIO方式,一种是DMA方式。在2.2.4中说过DMA控制器相关的寄存器标志位,其中RX_ THRESHOLD和TX_ THRESHOLD,表示DMA接收中断触发阈值偏移量和DMA发送中断触发阈值偏移量。在5.1.5中的spi_bitbang_transfer_one函数在发送数据时初始化了spi dma,这里面就完成对了这两个标志位的配置。假设现在的发送阈值是8个字节,而你发送的数据长度为5,则无法满足触发一次DMA传输的条件,则此时将会采用PIO方式传输数据。也就是说,哪怕采用了DMA传输方式,也会因为有些情况导致只能用PIO方式传输。还有一种情况是,假设你发送的数据长度为10个字节,该长度大于发送阈值,则采用DMA传输,但是当DMA发送一次数据后,剩下的2个字节将无法满足新的一次DMA传输,那么此时也会采用PIO传输方式。

6.7、spi_imx_pio_transfer(内含spi中断服务函数的讲解)

static int spi_imx_pio_transfer(struct spi_device *spi, struct spi_transfer *transfer)
{
	struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);

	spi_imx->tx_buf = transfer->tx_buf;
	spi_imx->rx_buf = transfer->rx_buf;
	spi_imx->count = transfer->len;
	spi_imx->txfifo = 0;

	reinit_completion(&spi_imx->xfer_done);						//重新初始化同步信号量

	spi_imx_push(spi_imx);										//使用spi接口发送数据到fifo中,并触发一次SPI传输

	spi_imx->devtype_data->intctrl(spi_imx, MXC_INT_TE);		//使能TXFIFO空中断

	/* 上面开启了TXFIFO空中断,当数据传输完毕直到TXFIFO为空,会进入SPI中断服务函数中传输数据,当传输完成后,会发送同步信号 */
	wait_for_completion(&spi_imx->xfer_done);					//等待SPI传输完成

	return transfer->len;
}

​   在spi_imx_push函数中,会首先将该transfer上的数据全部存放到TXFIFO中,所以采用PIO传输方式需要注意每个transfer上的数据长度不要超过TXFIFO的长度,MX6UL的TXFIFO和RXFIFO都是64个字节大小。然后就会使用2.4.3中讲到的mx51_ecspi_trigger函数触发一次SPI传输。这里采用了TXFIFO空中断的方式来判断数据是否发送成功,这样当数据发送完成后将会进入SPI中断服务函数中,在SPI中断服务函数中会发送同步信号,这样本次PIO传输就传输完成。注意这里的同步信号和最开始说的同步传输的同步信号不是同一个东西。同步传输的同步信号表示本次message的数据传输完成,而这里的同步信号表示本次transfer的数据传输完成。

​   再来看下SPI中断服务函数:

static irqreturn_t spi_imx_isr(int irq, void *dev_id)
{
	struct spi_imx_data *spi_imx = dev_id;

	/* 如果发生RXFIFO就绪中断,就表示RXFIFO中接收到数据 */
	while (spi_imx->devtype_data->rx_available(spi_imx)) 	/* 判断RXFIFO是否存在数据 */
	{
		spi_imx->rx(spi_imx);					/* 读取RXFIFO数据到spi_imx->rx_buf中存放 */
		spi_imx->txfifo--;						/*  */
	}

	/* 该情况用在DMA传输中,剩下的字节数无法满足一次DMA传输时 */
	if (spi_imx->count) 						/* 判断是否有发送的数据存在 */				
	{
		spi_imx_push(spi_imx);					/* 将transfer中的数据放入TXFIFO中 */
		return IRQ_HANDLED;
	}

	/* 如果发生TXFIFO空中断,就表示TXFIFO中数据发送完成 */
	if (spi_imx->txfifo)
	{
		spi_imx->devtype_data->intctrl(spi_imx, MXC_INT_RR);	/* 使能RXFIFO非空中断 */
		return IRQ_HANDLED;	
	}

	spi_imx->devtype_data->intctrl(spi_imx, 0);			/* 此次发送或者接收完成,关闭中断,等待下次需求时打开 */
	complete(&spi_imx->xfer_done);						/* 发送同步信号 */

	return IRQ_HANDLED;
}

​   这里提一下SPI传输的特性,SPI传输数据发送和接收是同时进行的,也就是你发送一个字节数据,同时也接收到一个字节数据。所以当进入该中断服务函数后不仅表示传输完成,也表示接收数据完成。不过这里需要注意的是,尽管发送和接收是同时进行的,但是在程序上却分了两步来做,即先将要发送的数据放到TXFIFO中,然后使能TXFIFO空中断,这样当TXFIFO中数据传输完后,就会触发中断进入到中断服务函数中。由于spi_imx->txfifo变量记录了存入TXFIFO中的字节数,所以当进入中断服务函数后spi_imx->txfifo不为0,则将使能RXFIFO非空中断。由于发送和接收数据是同时进行的,当TXFIFO中数据传输完后,RXFIFO中也接收到了同样字节数的数据,所以将再次进入中断服务函数中,此时spi_imx->devtype_data->rx_available(spi_imx)该条件成立,就会读取RXFIFO中的数据。

6.8、spi_imx_dma_transfer(内含DMA接收特殊情况的讲解)

static int spi_imx_dma_transfer(struct spi_imx_data *spi_imx, struct spi_transfer *transfer)
{
	struct dma_async_tx_descriptor *desc_tx = NULL, *desc_rx = NULL;
	int ret;
	int left = 0;
	struct spi_master *master = spi_imx->bitbang.master;
	struct sg_table *tx = &transfer->tx_sg, *rx = &transfer->rx_sg;

	if (tx) {
		desc_tx = dmaengine_prep_slave_sg(master->dma_tx,				//获取一个传输描述符
					tx->sgl, tx->nents, DMA_MEM_TO_DEV,
					DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
		if (!desc_tx)
			goto tx_nodma;

		desc_tx->callback = spi_imx_dma_tx_callback;					//dma传输完成后,将会调用该回调函数
		desc_tx->callback_param = (void *)spi_imx;
		dmaengine_submit(desc_tx);										//提交传输描述符,把传输描述符加入到DMA engine驱动的等待队列(仅仅提交描述符到DMA engine的等待队列,它不会启动DMA操作)
	}

	if (rx) {
		struct scatterlist *sgl_last = &rx->sgl[rx->nents - 1];
		unsigned int	orig_length = sgl_last->length;
		int	wml_mask = ~(spi_imx->rx_wml - 1);
		/*
		 * Adjust the transfer lenth of the last scattlist if there are
		 * some tail data, use PIO read to get the tail data since DMA
		 * sometimes miss the last tail interrupt.
		 */
		left = transfer->len % spi_imx->rx_wml;
		if (left)
			sgl_last->length = orig_length & wml_mask;

		desc_rx = dmaengine_prep_slave_sg(master->dma_rx,				//获取一个接收描述符
					rx->sgl, rx->nents, DMA_DEV_TO_MEM,
					DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
		if (!desc_rx)
			goto rx_nodma;

		desc_rx->callback = spi_imx_dma_rx_callback;					//设置dma接收完成回调函数
		desc_rx->callback_param = (void *)spi_imx;
		dmaengine_submit(desc_rx);										//提交传输描述符,把传输描述符加入到DMA engine驱动的等待队列(仅仅提交描述符到DMA engine的等待队列,它不会启动DMA操作)
	}

	reinit_completion(&spi_imx->dma_rx_completion);						//初始化接收信号量
	reinit_completion(&spi_imx->dma_tx_completion);						//初始化发送信号量

	/* Trigger the cspi module. */
	spi_imx->dma_finished = 0;
	spi_imx->devtype_data->trigger(spi_imx);							//设置spi触发模式。该函数设置spi触发模式由SPI SS波形选择

	dma_async_issue_pending(master->dma_tx);							//启动异步DMA传输,但不会立即启动传输,它会告知DMA引擎开始执行先前添加到队列中的DMA请求
	dma_async_issue_pending(master->dma_rx);							//启动异步DMA传输
	/* Wait SDMA to finish the data transfer.*/
	ret = wait_for_completion_timeout(&spi_imx->dma_tx_completion,		//等待发送成功
					  IMX_DMA_TIMEOUT(transfer->len));
	if (!ret) {
		pr_warn("%s %s: I/O Error in DMA TX:%x\n",
			dev_driver_string(&master->dev),
			dev_name(&master->dev), transfer->len);
		dmaengine_terminate_all(master->dma_tx);					
	} else {
		ret = wait_for_completion_timeout(&spi_imx->dma_rx_completion,	//等待接收成功
				IMX_DMA_TIMEOUT(transfer->len));
		if (!ret) {
			pr_warn("%s %s: I/O Error in DMA RX:%x\n",
				dev_driver_string(&master->dev),
				dev_name(&master->dev), transfer->len);
			spi_imx->devtype_data->reset(spi_imx);
			dmaengine_terminate_all(master->dma_rx);					/* 清除DMA传输完成标志位 */
		} else if (left) {												//如果剩下的数据不满足触发DMA接收的条件,将会使用spi接收的方式,将剩下的数据接收。
			/* read the tail data by PIO */
			dma_sync_sg_for_cpu(master->dma_rx->device->dev,			//CPU 访问内存之前,通过调用 dma_sync_{single,sg}_for_cpu() 来 Invalidate Cache,
					    &rx->sgl[rx->nents - 1], 1,						//这样 CPU 在后续访问时才能重新从 DDR 上加载最新的数据到 Cache 上。
					    DMA_FROM_DEVICE);
			spi_imx->rx_buf = transfer->rx_buf + (transfer->len - left);
			spi_imx_tail_pio_set(spi_imx, left);
			reinit_completion(&spi_imx->xfer_done);

			spi_imx->devtype_data->intctrl(spi_imx, MXC_INT_TCEN);		//使能发送完成中断

			ret = wait_for_completion_timeout(&spi_imx->xfer_done,		//在spi中断中完成信号同步
						IMX_DMA_TIMEOUT(transfer->len));
			if (!ret) {
				pr_warn("%s %s: I/O Error in RX tail\n",
					dev_driver_string(&master->dev),
					dev_name(&master->dev));
			}
		}
	}

	spi_imx->dma_finished = 1;
	if (spi_imx->devtype_data->devtype == IMX6UL_ECSPI)
		spi_imx->devtype_data->trigger(spi_imx);

	if (!ret)
		ret = -ETIMEDOUT;
	else if (ret > 0)
		ret = transfer->len;

	return ret;

rx_nodma:
	dmaengine_terminate_all(master->dma_tx);
tx_nodma:
	pr_warn_once("%s %s: DMA not available, falling back to PIO\n",
		     dev_driver_string(&master->dev),
		     dev_name(&master->dev));
	return -EAGAIN;
}

​   该函数使用了DMA来传输数据,里面涉及到的很多函数都与3.3中的介绍有关,建议先看下3.3中的内容,这一个函数很多东西都能搞懂。

​   这里我只提下接收数据时需要注意的一点东西。前面讲采用DMA发送时,可能遇到不满足TXFIFO发送阈值的情况而采用PIO方式传输。那么DMA接收时,同样也会遇到这样的情况,也就是接收到的数据不满足RXFIFO接收阈值。那么此时就又要采用PIO的方式来接收剩下这些数据。因为SPI传输的特性,你要接收剩下的数据,就要先发送这么多的数据,在该函数中采用的方式就是使能发送完成中断。之所以使能发送完成中断就能进入到中断服务函数就是因为前面DMA传输完成时,也将发送完成标志位置1。进入中断服务函数后,可以看到该函数里面在判断spi_imx->count是否为0,因为还有未发送完的数据,所以此参数不为0,于是就会把这些数据又放到TXFIFO中。后面的流程就跟PIO传输方式一样了。

6.9、spi_finalize_current_message

void spi_finalize_current_message(struct spi_master *master)
{
	struct spi_message *mesg;
	unsigned long flags;
	int ret;

	spin_lock_irqsave(&master->queue_lock, flags);
	mesg = master->cur_msg;
	spin_unlock_irqrestore(&master->queue_lock, flags);

	spi_unmap_msg(master, mesg);											//解除内存映射关系

	if (master->cur_msg_prepared && master->unprepare_message) {
		ret = master->unprepare_message(master, mesg);						//禁止spi时钟信号
		if (ret) {
			dev_err(&master->dev,
				"failed to unprepare message: %d\n", ret);
		}
	}

	spin_lock_irqsave(&master->queue_lock, flags);
	master->cur_msg = NULL;
	master->cur_msg_prepared = false;
	queue_kthread_work(&master->kworker, &master->pump_messages);			//处理内核线程中下个发送请求
	spin_unlock_irqrestore(&master->queue_lock, flags);

	trace_spi_message_done(mesg);

	mesg->state = NULL;
	if (mesg->complete)
		mesg->complete(mesg->context);										//发送message传输完成同步信号
}

  该函数主要完成以下几个步骤:

  1. 解除传输完成的message的内存映射。
  2. message 传输完成后的一些清理工作,如禁止spi时钟等工作。
  3. 处理内核工作线程中下个发送请求。
  4. 发送 message 传输完成同步信号。

7、最后

​   总算是把这个东西写完了,其实这东西在去年的11月份就在开始写了,没想到一直磕磕绊绊拖到现在才写好。由于分了很多次写的原因,每次写都会重新看下源码,导致每次都会都新的理解,这次写完居然发现写了快3万字。但正是因为这样,导致写的内容太多,以我目前的能力很难去把控,所以里面可能很多东西会理解不到位,写错很多东西。希望后面朋友能见谅,也希望能指出我的错误,我会积极改正的!
  最后附上我的笔记文档链接:基于Linux的SPI驱动框架分析

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力一点,幸运一点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值