【驱动】SPI驱动分析(三)-SPI关键数据类型

SPI数据类型

SPI控制器驱动结构体

struct spi_master抽象了控制器硬件,在SoC中的指的就是内部SPI控制器,当向SPI核心层注册一个SPI控制器时就需要提供这样的一个结构体变量。它的定义在 include/linux/spi/spi.h 文件,如下:

c 复制代码
/**
 * struct spi_master - interface to SPI master controller
 * @dev: device interface to this driver
 * @list: link with the global spi_master list
 * @bus_num: board-specific (and often SOC-specific) identifier for a
 *	given SPI controller.
 * @num_chipselect: chipselects are used to distinguish individual
 *	SPI slaves, and are numbered from zero to num_chipselects.
 *	each slave has a chipselect signal, but it's common that not
 *	every chipselect is connected to a slave.
 * @dma_alignment: SPI controller constraint on DMA buffers alignment.
 * @mode_bits: flags understood by this controller driver
 * @bits_per_word_mask: A mask indicating which values of bits_per_word are
 *	supported by the driver. Bit n indicates that a bits_per_word n+1 is
 *	supported. If set, the SPI core will reject any transfer with an
 *	unsupported bits_per_word. If not set, this value is simply ignored,
 *	and it's up to the individual driver to perform any validation.
 * @min_speed_hz: Lowest supported transfer speed
 * @max_speed_hz: Highest supported transfer speed
 * @flags: other constraints relevant to this driver
 * @bus_lock_spinlock: spinlock for SPI bus locking
 * @bus_lock_mutex: mutex for SPI bus locking
 * @bus_lock_flag: indicates that the SPI bus is locked for exclusive use
 * @setup: updates the device mode and clocking records used by a
 *	device's SPI controller; protocol code may call this.  This
 *	must fail if an unrecognized or unsupported mode is requested.
 *	It's always safe to call this unless transfers are pending on
 *	the device whose settings are being modified.
 * @transfer: adds a message to the controller's transfer queue.
 * @cleanup: frees controller-specific state
 * @can_dma: determine whether this master supports DMA
 * @queued: whether this master is providing an internal message queue
 * @kworker: thread struct for message pump
 * @kworker_task: pointer to task for message pump kworker thread
 * @pump_messages: work struct for scheduling work to the message pump
 * @queue_lock: spinlock to syncronise access to message queue
 * @queue: message queue
 * @idling: the device is entering idle state
 * @cur_msg: the currently in-flight message
 * @cur_msg_prepared: spi_prepare_message was called for the currently
 *                    in-flight message
 * @cur_msg_mapped: message has been mapped for DMA
 * @xfer_completion: used by core transfer_one_message()
 * @busy: message pump is busy
 * @running: message pump is running
 * @rt: whether this queue is set to run as a realtime task
 * @auto_runtime_pm: the core should ensure a runtime PM reference is held
 *                   while the hardware is prepared, using the parent
 *                   device for the spidev
 * @max_dma_len: Maximum length of a DMA transfer for the device.
 * @prepare_transfer_hardware: a message will soon arrive from the queue
 *	so the subsystem requests the driver to prepare the transfer hardware
 *	by issuing this call
 * @transfer_one_message: the subsystem calls the driver to transfer a single
 *	message while queuing transfers that arrive in the meantime. When the
 *	driver is finished with this message, it must call
 *	spi_finalize_current_message() so the subsystem can issue the next
 *	message
 * @unprepare_transfer_hardware: there are currently no more messages on the
 *	queue so the subsystem notifies the driver that it may relax the
 *	hardware by issuing this call
 * @set_cs: set the logic level of the chip select line.  May be called
 *          from interrupt context.
 * @prepare_message: set up the controller to transfer a single message,
 *                   for example doing DMA mapping.  Called from threaded
 *                   context.
 * @transfer_one: transfer a single spi_transfer.
 *                  - return 0 if the transfer is finished,
 *                  - return 1 if the transfer is still in progress. When
 *                    the driver is finished with this transfer it must
 *                    call spi_finalize_current_transfer() so the subsystem
 *                    can issue the next transfer. Note: transfer_one and
 *                    transfer_one_message are mutually exclusive; when both
 *                    are set, the generic subsystem does not call your
 *                    transfer_one callback.
 * @handle_err: the subsystem calls the driver to handle an error that occurs
 *		in the generic implementation of transfer_one_message().
 * @unprepare_message: undo any work done by prepare_message().
 * @cs_gpios: Array of GPIOs to use as chip select lines; one per CS
 *	number. Any individual value may be -ENOENT for CS lines that
 *	are not GPIOs (driven by the SPI controller itself).
 * @statistics: statistics for the spi_master
 * @dma_tx: DMA transmit channel
 * @dma_rx: DMA receive channel
 * @dummy_rx: dummy receive buffer for full-duplex devices
 * @dummy_tx: dummy transmit buffer for full-duplex devices
 *
 * Each SPI master controller can communicate with one or more @spi_device
 * children.  These make a small bus, sharing MOSI, MISO and SCK signals
 * but not chip select signals.  Each device may be configured to use a
 * different clock rate, since those shared signals are ignored unless
 * the chip is selected.
 *
 * The driver for an SPI controller manages access to those devices through
 * a queue of spi_message transactions, copying data between CPU memory and
 * an SPI slave device.  For each such message it queues, it calls the
 * message's completion function when the transaction completes.
 */
struct spi_master {
	struct device	dev;

	struct list_head list;

	/* other than negative (== assign one dynamically), bus_num is fully
	 * board-specific.  usually that simplifies to being SOC-specific.
	 * example:  one SOC has three SPI controllers, numbered 0..2,
	 * and one board's schematics might show it using SPI-2.  software
	 * would normally use bus_num=2 for that controller.
	 */
	s16			bus_num;

	/* chipselects will be integral to many controllers; some others
	 * might use board-specific GPIOs.
	 */
	u16			num_chipselect;

	/* some SPI controllers pose alignment requirements on DMAable
	 * buffers; let protocol drivers know about these requirements.
	 */
	u16			dma_alignment;

	/* spi_device.mode flags understood by this controller driver */
	u16			mode_bits;

	/* bitmask of supported bits_per_word for transfers */
	u32			bits_per_word_mask;
#define SPI_BPW_MASK(bits) BIT((bits) - 1)
#define SPI_BIT_MASK(bits) (((bits) == 32) ? ~0U : (BIT(bits) - 1))
#define SPI_BPW_RANGE_MASK(min, max) (SPI_BIT_MASK(max) - SPI_BIT_MASK(min - 1))

	/* limits on transfer speed */
	u32			min_speed_hz;
	u32			max_speed_hz;

	/* other constraints relevant to this driver */
	u16			flags;
#define SPI_MASTER_HALF_DUPLEX	BIT(0)		/* can't do full duplex */
#define SPI_MASTER_NO_RX	BIT(1)		/* can't do buffer read */
#define SPI_MASTER_NO_TX	BIT(2)		/* can't do buffer write */
#define SPI_MASTER_MUST_RX      BIT(3)		/* requires rx */
#define SPI_MASTER_MUST_TX      BIT(4)		/* requires tx */

	/* lock and mutex for SPI bus locking */
	spinlock_t		bus_lock_spinlock;
	struct mutex		bus_lock_mutex;

	/* flag indicating that the SPI bus is locked for exclusive use */
	bool			bus_lock_flag;

	/* Setup mode and clock, etc (spi driver may call many times).
	 *
	 * IMPORTANT:  this may be called when transfers to another
	 * device are active.  DO NOT UPDATE SHARED REGISTERS in ways
	 * which could break those transfers.
	 */
	int			(*setup)(struct spi_device *spi);

	/* bidirectional bulk transfers
	 *
	 * + The transfer() method may not sleep; its main role is
	 *   just to add the message to the queue.
	 * + For now there's no remove-from-queue operation, or
	 *   any other request management
	 * + To a given spi_device, message queueing is pure fifo
	 *
	 * + The master's main job is to process its message queue,
	 *   selecting a chip then transferring data
	 * + If there are multiple spi_device children, the i/o queue
	 *   arbitration algorithm is unspecified (round robin, fifo,
	 *   priority, reservations, preemption, etc)
	 *
	 * + Chipselect stays active during the entire message
	 *   (unless modified by spi_transfer.cs_change != 0).
	 * + The message transfers use clock and SPI mode parameters
	 *   previously established by setup() for this device
	 */
	int			(*transfer)(struct spi_device *spi,
						struct spi_message *mesg);

	/* called on release() to free memory provided by spi_master */
	void			(*cleanup)(struct spi_device *spi);

	/*
	 * Used to enable core support for DMA handling, if can_dma()
	 * exists and returns true then the transfer will be mapped
	 * prior to transfer_one() being called.  The driver should
	 * not modify or store xfer and dma_tx and dma_rx must be set
	 * while the device is prepared.
	 */
	bool			(*can_dma)(struct spi_master *master,
					   struct spi_device *spi,
					   struct spi_transfer *xfer);

	/*
	 * These hooks are for drivers that want to use the generic
	 * master transfer queueing mechanism. If these are used, the
	 * transfer() function above must NOT be specified by the driver.
	 * Over time we expect SPI drivers to be phased over to this API.
	 */
	bool				queued;
	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;
	bool				idling;
	bool				busy;
	bool				running;
	bool				rt;
	bool				auto_runtime_pm;
	bool                            cur_msg_prepared;
	bool				cur_msg_mapped;
	struct completion               xfer_completion;
	size_t				max_dma_len;

	int (*prepare_transfer_hardware)(struct spi_master *master);
	int (*transfer_one_message)(struct spi_master *master,
				    struct spi_message *mesg);
	int (*unprepare_transfer_hardware)(struct spi_master *master);
	int (*prepare_message)(struct spi_master *master,
			       struct spi_message *message);
	int (*unprepare_message)(struct spi_master *master,
				 struct spi_message *message);

	/*
	 * These hooks are for drivers that use a generic implementation
	 * of transfer_one_message() provied by the core.
	 */
	void (*set_cs)(struct spi_device *spi, bool enable);
	int (*transfer_one)(struct spi_master *master, struct spi_device *spi,
			    struct spi_transfer *transfer);
	void (*handle_err)(struct spi_master *master,
			   struct spi_message *message);

	/* gpio chip select */
	int			*cs_gpios;

	/* statistics */
	struct spi_statistics	statistics;

	/* DMA channels for use with core dmaengine helpers */
	struct dma_chan		*dma_tx;
	struct dma_chan		*dma_rx;

	/* dummy data for full duplex devices */
	void			*dummy_rx;
	void			*dummy_tx;
};

参数含义如下:

  • dev:spi_master是一个device,所以包含了一个device的实例,设备模型使用;把spi_master看做是device的子类;
  • list:用于构建双向链表,链接到spi_controller list链表中;
  • bus_num:SPI控制器的编号,比如某SoC有3个SPI控制,那么这个结构描述的是第几个;
  • num_chipselect:片选数量,决定该控制器下面挂接多少个SPI设备,从设备的片选号不能大于这个数量;
  • mode_bits:SPI 控制器支持模式标志位,比如:
    • SPI_CPHA:支持时钟相位选择;
    • SPI_CPOL:支持时钟记性选择;
    • SPI_CS_HIGH:片选信号为高电平;
  • bits_per_word_mask:位掩码,指示驱动程序支持的bits_per_word值。设置了第n位表示支持bits_per_word n+1。如果设置了该位,SPI核将拒绝任何使用不受支持的bits_per_word进行的传输。如果未设置该位,则该值将被忽略,由各个驱动程序执行任何验证。
    • min_speed_hz/max_speed_hz:最大最小速率;
  • slave:是否是 slave;
  • bus_lock_spinlock:用于SPI总线锁定的自旋锁。
  • bus_lock_mutex:用于SPI总线锁定的互斥锁。
  • bus_lock_flag:指示SPI总线是否被独占使用的标志。
  • setup:SPI控制器初始化函数指针,用来设置SPI控制器和工作方式、clock等;
  • cleanup:在spidev_release函数中被调用;
  • transfer:添加消息到队列的方法。这个函数不可睡眠,它的职责是安排传送并且调用注册的回调函 complete()。这个不同的控制器要具体实现,传输数据最后都要调用这个函数;
  • queue:消息队列,用于连接挂载该控制器下的spi_message结构;控制器上可以同时被加入多个spi_message进行排队;
  • kworker:工作线程,负责执行工作的线程;
  • kworker_task:调用kthread_run为kworker创建并启动一个内核线程来处理工作,创建返回的task_struct结构体;
  • pump_messages:指的就是具体的工作(work),通过kthread_queue_work可以将工作挂到工作线程;
  • cur_msg:当前正在处理的spi_message
  • busy: message pump is busy:指定是当前SPI是否正在进行数据传输;
  • idling:内核工作线程是空闲的,即kworker没有工作需要执行;
  • auto_runtime_pm 用于指示 SPI 主控制器驱动程序在准备硬件时是否需要自动管理运行时电源管理(Runtime PM)。
  • cur_msg_prepared 用于表示当前消息(cur_msg)是否已经准备好进行传输。
  • cur_msg_mapped 表示当前的消息(cur_msg)是否已经被映射为 DMA 缓冲区。
  • max_dma_len 变量指定了设备在单个 DMA 传输中所支持的最大数据长度。它限制了每次 DMA 传输的数据量,以确保在硬件和系统限制范围内进行有效的数据传输。
  • prepare_transfer_hardware:在使用 DMA 进行数据传输时,SPI 主控制器需要在实际传输之前准备相关的硬件设置和配置。这包括配置 DMA 控制器、分配 DMA 缓冲区、设置传输方向和传输参数等。回调函数的目的是为传输做好准备工作,确保硬件和相关资源处于正确的状态以进行传输。该回调函数在每次传输开始之前被调用,以便准备传输所需的硬件资源。
  • unprepare_transfer_hardware 回调函数的目的是在传输完成后进行清理工作,释放传输过程中所使用的硬件资源,以便它们可以被其他传输所使用。
  • prepare_message :是一个函数指针,指向一个用于准备单个消息传输的回调函数。在使用 SPI 主控制器进行消息传输之前,需要进行一些准备工作,例如设置传输参数、映射 DMA 缓冲区等。prepare_message 回调函数的目的就是在实际传输之前执行这些准备操作。
  • unprepare_message 是一个函数指针,指向一个用于取消准备消息传输的回调函数。在使用 SPI 主控制器进行消息传输之后,可以使用 unprepare_message 回调函数执行一些清理操作,以撤消在消息传输准备阶段所做的配置和初始化。
  • set_cs:函数指针,可以用来实现SPI控制器片选信号,比如设置片选、取消片选,这个函数一般由SoC厂家的SPI控制器驱动程序提供;如果指定了cs_giopscs_gpio,该参数将无效;
  • cs_gpiod:片选GPIO描述符指针数组,如果一个SPI控制器外接了多个SPI从设备,这里存放的就是每个从设备的片选引脚GPIO描述符;
  • running: message pump is running:指的是消息队列是否启用,在spi_start_queue函数会将这个参数设置为true;不出意外的话,注册完SPI中断控制器后,这个参数时钟都是true;
  • transfer、transfer_one、transfer_one_message:用于SPI数据传输;其区别在于:
    • transfer:添加一个message到SPI控制器传输消息队列;如果未指定,由SPI核心默认初始化为spi_queued_transfer
    • transfer_one_message:传输一个spi_message,传输完成将会调用spi_finalize_current_message函数;由SPI核心默认初始化为spi_transfer_one_message
    • transfer_one:传输一个spi_transfer,0:传输完成,1:传输进行中,传输完成需要调用spi_finalize_current_transfer函数;这个函数一般由SoC厂家的SPI控制器驱动程序提供;
  • cs_gpio:片选GPIO编号数组,如果一个SPI控制器外接了多个SPI从设备,这里存放的就是每个从设备的片选引脚GPIO编号;
  • struct spi_statistics statistics 是一个用于记录 SPI 主控制器统计信息的结构体。
    • u32 transfer_count:记录执行的传输次数。
    • u32 rx_bytes:接收的总字节数。
    • u32 tx_bytes:发送的总字节数。
    • u32 rx_errors:接收期间发生的错误次数。
    • u32 tx_errors:发送期间发生的错误次数。
    • u32 rx_overruns:接收期间发生的溢出错误次数。
    • u32 tx_underruns:发送期间发生的欠流错误次数。
  • dma_tx:指向 DMA 传输通道的指针。DMA 传输通道用于处理 SPI 主控制器的发送数据部分。通过使用 DMA,可以实现在数据传输期间无需 CPU 干预,从而提高传输效率。
  • dma_rx:指向 DMA(直接内存访问)接收通道的指针。
  • dummy_rx:用于全双工设备的虚拟接收缓冲区。对于全双工的SPI设备,需要同时发送和接收数据。由于硬件限制,必须在发送数据时接收数据。但是,并非所有应用都需要实际接收数据,因此可以使用虚拟接收缓冲区 dummy_rx。在这种情况下,接收到的数据将被丢弃,而不会被处理或使用。
  • dummy_tx:用于全双工设备的虚拟发送缓冲区。

这里需要重点关注下transfer函数,根据上文注释中对transfer函数的描述,可以提炼出函数具备以下特性:

  • 支持双向批量传输
  • transfer()方法不能休眠;它的主要作用是将消息添加到队列中
  • 目前还没有从队列中删除操作,或者任何其他请求管理
  • 对于给定的spi_device,消息队列是单纯的FIFO方式
  • 选择一个芯片然后传输数据,master的主要工作是处理它的消息队列
  • 如果有多个spi_device子节点,i/o队列仲裁算法未指定具体方式,可以是轮询,fifo,优先、保留、优先等
  • 片选信号在整个消息期间保持活跃,除非被spi_transfer.cs_change != 0所修改
  • 消息传输使用时钟和SPI模式参数,这些参数是由setup()之前为这个设备建立

SPI设备信息结构体

struct spi_device抽象了连接到SPI总线上的SPI从设备,struct spi_device的成员变量很多来自于spi_board_info。它的定义在 include/linux/spi/spi.h 文件,如下:

c 复制代码
struct spi_device {
	struct device		dev;
	struct spi_master	*master;
	u32			max_speed_hz;
	u8			chip_select;
	u8			bits_per_word;
	u16			mode;
#define	SPI_CPHA	0x01			/* clock phase */
#define	SPI_CPOL	0x02			/* clock polarity */
#define	SPI_MODE_0	(0|0)			/* (original MicroWire) */
#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			/* chipselect active high? */
#define	SPI_LSB_FIRST	0x08			/* per-word bits-on-wire */
#define	SPI_3WIRE	0x10			/* SI/SO signals shared */
#define	SPI_LOOP	0x20			/* loopback mode */
#define	SPI_NO_CS	0x40			/* 1 dev/bus, no chipselect */
#define	SPI_READY	0x80			/* slave pulls low to pause */
#define	SPI_TX_DUAL	0x100			/* transmit with 2 wires */
#define	SPI_TX_QUAD	0x200			/* transmit with 4 wires */
#define	SPI_RX_DUAL	0x400			/* receive with 2 wires */
#define	SPI_RX_QUAD	0x800			/* receive with 4 wires */
	int			irq;
	void			*controller_state;
	void			*controller_data;
	char			modalias[SPI_NAME_SIZE];
	int			cs_gpio;	/* chip select gpio */

	/* the statistics */
	struct spi_statistics	statistics;

	/*
	 * likely need more hooks for more protocol options affecting how
	 * the controller talks to each chip, like:
	 *  - memory packing (12 bit samples into low bits, others zeroed)
	 *  - priority
	 *  - drop chipselect after each word
	 *  - chipselect delays
	 *  - ...
	 */
};
  • dev: 一个指向 struct device 的指针,表示该 SPI 设备所属的设备。
  • master: 一个指向 struct spi_master 的指针,表示该 SPI 设备所连接的 SPI 主控制器。
  • max_speed_hz: 一个无符号 32 位整数,表示该 SPI 设备的最大传输速率。
  • chip_select: 一个无符号 8 位整数,表示该 SPI 设备的芯片选择线编号。
  • bits_per_word: 一个无符号 8 位整数,表示每个数据传输字中的位数。
  • mode: 一个无符号 16 位整数,表示 SPI 设备的工作模式。它是通过按位与操作来组合以下定义的模式选项的值:
    • SPI_CPHA: 表示时钟相位(Clock Phase)。
    • SPI_CPOL: 表示时钟极性(Clock Polarity)。
    • SPI_MODE_0: SPI 模式 0。
    • SPI_MODE_1: SPI 模式 1。
    • SPI_MODE_2: SPI 模式 2。
    • SPI_MODE_3: SPI 模式 3。
    • SPI_CS_HIGH: 芯片选择信号是否为高电平有效。
    • SPI_LSB_FIRST: 数据传输的位顺序,是否为最低有效位(LSB)先传输。
    • SPI_3WIRE: 是否共享单线的串行输入和输出信号。
    • SPI_LOOP: 是否开启回环模式。
    • SPI_NO_CS: 是否禁用芯片选择信号。
    • SPI_READY: 从设备拉低该信号以暂停传输。
    • SPI_TX_DUAL: 使用两根线进行发送。
    • SPI_TX_QUAD: 使用四根线进行发送。
    • SPI_RX_DUAL: 使用两根线进行接收。
    • SPI_RX_QUAD: 使用四根线进行接收。
  • irq: 一个整数,表示与该 SPI 设备相关联的中断请求(IRQ)线。
  • controller_state: 一个指向控制器状态的指针,用于保存控制器的特定状态信息。
  • controller_data: 一个指向控制器数据的指针,用于保存控制器的特定配置数据。
  • modalias: 一个长度为 SPI_NAME_SIZE 的字符数组,表示 SPI 设备的模块别名。
  • cs_gpio: 一个整数,表示与该 SPI 设备相关联的芯片选择信号所连接的 GPIO 引脚编号。
  • statistics: 一个 struct spi_statistics 类型的变量,用于记录 SPI 设备的统计信息。

需要注意的是:

  • spi总线中,同一个时间,spi控制器只能跟一个从设备进行沟通。(这点类似I2C总线)
  • spi控制器是通过cs 片选引脚的高低来控制和那个设备进行沟通。片选号对应片选引脚。
  • struct spi_device ->mode 非常重要,主要是相位(CPHA)和极性(CPOL)的搭配方式。
  • struct spi_device ->bits_per_word,指定每次读写的字长,单位bit,如果该值为0,则默认使用8bit为字长。

SPI设备驱动结构体

struct spi_driver描述一个SPI设备驱动,与对应的 spi_device 结构进行匹配后调用 probe;定义在 include/linux/spi/spi.h文件,如下:

c 复制代码
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;
};

spi_driver为主机端协议驱动数据结构,其中支持的函数或结构体功能定义:

  • id_table:往往一个驱动可能能同时支持多个硬件,这些硬件的名字都放在该结构体数组中;
  • probe:当驱动和设备信息匹配成功之后,就会调用probe函数,驱动所有的资源的注册和初始化全部放在probe函数中;
  • remove:从spi设备解绑定该驱动程序
  • shutdown:在系统状态转换期间使用的标准关机回调函数,例如powerdown/halt和kexec
  • driver: SPI设备驱动程序应该初始化该结构的name和owner字段。

SPI传输信息结构体

struct spi_transfer代表一个读写缓冲对,包括接收缓冲区和发送缓冲区,spi_transfer的发送是通过构建spi_message实现,通过将spi_transfer中链表节点transfer_list链接到spi_message中的transfers,再以spi_message形式向底层发送数据。

实际上spi_transfer才是传输的最小单位,之所以又引进了spi_message进行打包,我觉得原因是:有时候希望往SPI从设备的多个不连续的地址(或寄存器)一次性写入,如果没有spi_message进行把这样的多个spi_transfer打包,因为通常真正的数据传送工作是在另一个内核线程(工作队列)中完成的,不打包的后果就是会造成更多的进程切换,效率降低,延迟增加,尤其对于多个不连续地址的小规模数据传送而言就更为明显。

每个spi_transfer都可以对传输的一些参数进行设备,使得spi_controller按照它要求的参数进行数据发送。

struct spi_transfer定义在 include/linux/spi/spi.h文件,如下:

c 复制代码
struct spi_transfer {
	/* it's ok if tx_buf == rx_buf (right?)
	 * for MicroWire, one buffer must be null
	 * buffers must work with dma_*map_single() calls, unless
	 *   spi_message.is_dma_mapped reports a pre-existing mapping
	 */
	const void	*tx_buf;
	void		*rx_buf;
	unsigned	len;

	dma_addr_t	tx_dma;
	dma_addr_t	rx_dma;
	struct sg_table tx_sg;
	struct sg_table rx_sg;

	unsigned	cs_change:1;
	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;
	u32		speed_hz;

	struct list_head transfer_list;
};
  • tx_buf: 要写入的数据缓冲区的指针,如果为 NULL,则在填充 rx_buf 时将输出零值。
  • rx_buf: 要读取的数据缓冲区的指针,如果为 NULL,则接收到的数据将被丢弃。
  • tx_dma: tx_buf 的 DMA 地址
  • rx_dma: rx_buf 的 DMA 地址
  • tx_nbits: 用于写入的位数。如果为 0,则使用默认值 SPI_NBITS_SINGLE
  • rx_nbits: 用于读取的位数。如果为 0,则使用默认值 SPI_NBITS_SINGLE
  • len: 读写缓冲区的大小(以字节为单位)。
  • speed_hz: 选择除设备默认速率以外的传输速率。如果为 0,则使用 spi_device 的默认速率。
  • bits_per_word: 选择除设备默认 bits_per_word 以外的传输位数。如果为 0,则使用 spi_device 的默认 bits_per_word
  • cs_change: 传输完成后是否影响芯片选择信号。
  • delay_usecs: 传输完成后延迟的微秒数,然后(可选地)更改芯片选择状态,然后开始下一个传输或完成当前的 spi_message
  • transfer_list: 用于在 spi_message 中顺序执行多个传输的链表。
  • tx_sg: 用于传输的散列表(Scatterlist)。
  • rx_sg: 用于接收的散列表(Scatterlist)。

struct spi_tranfer 表示一个读写缓存对。spi_transfer的传送是通过构建一个spi_message来实现的。
spi_transfer的特点如下:

  • 一个或多个spi_transfer组成一个spi_message.
  • 一个spi_message 就是一个完整的spi传输,也就是一个CS 由高到低,再由低到高的过程。
  • spi_transfer 代表一个读写缓冲对,包含接收缓冲区及发送缓冲区,其实,spi_transfer的发送是通过构建spi_message实现。
  • 通过将spi_transfer中的链表transfer_list链接到spi_message中的transfers,再以spi_message形势向底层发送数据。
  • 每个spi_transfer都可以对传输的一些参数进行设置,使得spi主控制器按照它要求的参数进行数据发送。
  • 每一个spi_transfer 都有自己的通讯速率,字宽 的要求

SPI 消息结构体

struct spi_message描述一个SPI传输的数据,spi_message用于执行数据传输的原子序列,由多个spi_transfer段组成,一旦控制器接收了一个spi_message,其中的spi_transfer应该按顺序被发送,并且不能被其它spi_message打断,所以我们认为spi_message就是一次SPI数据交换的原子操作。

spi_message定义在 include/linux/spi/spi.h文件,如下:

c 复制代码
/**
 * struct spi_message - one multi-segment SPI transaction
 * @transfers: list of transfer segments in this transaction
 * @spi: SPI device to which the transaction is queued
 * @is_dma_mapped: if true, the caller provided both dma and cpu virtual
 *	addresses for each transfer buffer
 * @complete: called to report transaction completions
 * @context: the argument to complete() when it's called
 * @frame_length: the total number of bytes in the message
 * @actual_length: the total number of bytes that were transferred in all
 *	successful segments
 * @status: zero for success, else negative errno
 * @queue: for use by whichever driver currently owns the message
 * @state: for use by whichever driver currently owns the message
 *
 * A @spi_message is used to execute an atomic sequence of data transfers,
 * each represented by a struct spi_transfer.  The sequence is "atomic"
 * in the sense that no other spi_message may use that SPI bus until that
 * sequence completes.  On some systems, many such sequences can execute as
 * as single programmed DMA transfer.  On all systems, these messages are
 * queued, and might complete after transactions to other devices.  Messages
 * sent to a given spi_device are always executed in FIFO order.
 *
 * The code that submits an spi_message (and its spi_transfers)
 * to the lower layers is responsible for managing its memory.
 * Zero-initialize every field you don't set up explicitly, to
 * insulate against future API updates.  After you submit a message
 * and its transfers, ignore them until its completion callback.
 */
struct spi_message {
	struct list_head	transfers;

	struct spi_device	*spi;

	unsigned		is_dma_mapped:1;

	/* REVISIT:  we might want a flag affecting the behavior of the
	 * last transfer ... allowing things like "read 16 bit length L"
	 * immediately followed by "read L bytes".  Basically imposing
	 * a specific message scheduling algorithm.
	 *
	 * Some controller drivers (message-at-a-time queue processing)
	 * could provide that as their default scheduling algorithm.  But
	 * others (with multi-message pipelines) could need a flag to
	 * tell them about such special cases.
	 */

	/* completion is reported through a callback */
	void			(*complete)(void *context);
	void			*context;
	unsigned		frame_length;
	unsigned		actual_length;
	int			status;

	/* for optional use by whatever driver currently owns the
	 * spi_message ...  between calls to spi_async and then later
	 * complete(), that's the spi_master controller driver.
	 */
	struct list_head	queue;
	void			*state;
};
  • transfers:一个链表,用于存储在该事务中的多个传输段。每个传输段都由结构体 struct spi_transfer 表示。可以通过遍历链表来访问每个传输段,以执行具体的数据传输操作。
  • spi:指向该事务所关联的SPI设备的指针。通过该指针,可以确定将要执行通信的特定SPI设备。
  • is_dma_mapped:这是一个标志位,如果设置为1,则表示对于每个传输缓冲区,调用者已经提供了DMA和CPU虚拟地址。这对于使用DMA进行高效数据传输非常有用。
  • complete:该字段是一个函数指针,用于指定当事务完成时要调用的回调函数。在事务完成时,将调用指定的回调函数来处理事务完成的事件。
  • context:一个指针,作为参数传递给 complete 回调函数。可以用来传递额外的上下文信息给回调函数,以便进行相关处理。
  • frame_length:表示该事务中的总字节数。它包括所有传输段的字节数之和。
  • actual_length:表示在所有成功传输的传输段中实际传输的总字节数。该字段用于记录实际传输的数据量。
  • status:表示事务的状态。如果为0,则表示事务成功完成;否则,表示发生了错误,其值为负的错误码(errno)。
  • queue:该字段是为当前拥有该消息的驱动程序保留的,可供驱动程序在处理消息时使用。
  • state:该字段也是为当前拥有该消息的驱动程序保留的,用于保存驱动程序内部的状态信息。

SPI从设备结构体

struct spi_board_info描述的是具体的SPI从设备,定义在include/linux/spi/spi.h文件中,该结构记录着SPI从设备使用的SPI控制器编号、片选信号、数据比特率、SPI传输模式等。如下:

c 复制代码
/**
 * struct spi_board_info - board-specific template for a SPI device
 * @modalias: Initializes spi_device.modalias; identifies the driver.
 * @platform_data: Initializes spi_device.platform_data; the particular
 *	data stored there is driver-specific.
 * @controller_data: Initializes spi_device.controller_data; some
 *	controllers need hints about hardware setup, e.g. for DMA.
 * @irq: Initializes spi_device.irq; depends on how the board is wired.
 * @max_speed_hz: Initializes spi_device.max_speed_hz; based on limits
 *	from the chip datasheet and board-specific signal quality issues.
 * @bus_num: Identifies which spi_master parents the spi_device; unused
 *	by spi_new_device(), and otherwise depends on board wiring.
 * @chip_select: Initializes spi_device.chip_select; depends on how
 *	the board is wired.
 * @mode: Initializes spi_device.mode; based on the chip datasheet, board
 *	wiring (some devices support both 3WIRE and standard modes), and
 *	possibly presence of an inverter in the chipselect path.
 *
 * When adding new SPI devices to the device tree, these structures serve
 * as a partial device template.  They hold information which can't always
 * be determined by drivers.  Information that probe() can establish (such
 * as the default transfer wordsize) is not included here.
 *
 * These structures are used in two places.  Their primary role is to
 * be stored in tables of board-specific device descriptors, which are
 * declared early in board initialization and then used (much later) to
 * populate a controller's device tree after the that controller's driver
 * initializes.  A secondary (and atypical) role is as a parameter to
 * spi_new_device() call, which happens after those controller drivers
 * are active in some dynamic board configuration models.
 */
struct spi_board_info {
	/* the device name and module name are coupled, like platform_bus;
	 * "modalias" is normally the driver name.
	 *
	 * platform_data goes to spi_device.dev.platform_data,
	 * controller_data goes to spi_device.controller_data,
	 * irq is copied too
	 */
	char		modalias[SPI_NAME_SIZE];
	const void	*platform_data;
	void		*controller_data;
	int		irq;

	/* slower signaling on noisy or low voltage boards */
	u32		max_speed_hz;


	/* bus_num is board specific and matches the bus_num of some
	 * spi_master that will probably be registered later.
	 *
	 * chip_select reflects how this chip is wired to that master;
	 * it's less than num_chipselect.
	 */
	u16		bus_num;
	u16		chip_select;

	/* mode becomes spi_device.mode, and is essential for chips
	 * where the default of SPI_CS_HIGH = 0 is wrong.
	 */
	u16		mode;

	/* ... may need additional spi_device chip config data here.
	 * avoid stuff protocol drivers can set; but include stuff
	 * needed to behave without being bound to a driver:
	 *  - quirks like clock rate mattering when not selected
	 */
};
  • modalias:该字段用于初始化 spi_device.modalias 字段,它是一个字符串,通常包含驱动程序的名称。这个字段在设备树中与设备关联起来,帮助内核识别并加载对应的驱动程序。
  • platform_data:这个字段用于初始化 spi_device.platform_data 字段,它是一个指向特定于驱动程序的数据的指针。驱动程序可以使用这个字段传递一些特定于设备的配置参数或其他数据。
  • controller_data:该字段用于初始化 spi_device.controller_data 字段,它是一个指向控制器相关数据的指针。一些控制器可能需要一些硬件设置的提示,例如关于 DMA 的设置,这个字段可以用来传递这些信息。
  • irq:这个字段初始化了 spi_device.irq 字段,用于指定与该设备相关的中断号。具体的中断号取决于板子的布线方式和硬件设计。
  • max_speed_hz:该字段初始化了 spi_device.max_speed_hz 字段,用于指定 SPI 设备的最大时钟速度。这个值通常基于芯片的数据手册以及板级特定的信号质量问题进行确定。
  • bus_num:这个字段用于标识所属的 spi_master,即 SPI 控制器的编号。它与 spi_master 结构体中的 bus_num 字段相对应,表示该设备所连接的 SPI 控制器。
  • chip_select:该字段初始化了 spi_device.chip_select 字段,用于指定与该设备相关的片选信号。具体的片选信号取决于板子的布线方式和硬件设计。
  • mode:这个字段初始化了 spi_device.mode 字段,用于指定 SPI 设备的通信模式。通信模式是根据芯片的数据手册、板子的布线方式以及片选路径中是否存在反相器等因素确定的。

spi_board_info 中的大部分成员都是通过解析设备树获得,而spi_board_info 将来会被转换成spi_device类型,spi_devicespi_board_info内部很多成员都是相似的,spi_device中的很多成员变量值都继承于spi_board_info

  • modalias将初始化 spi_device.modalias,最后会与从设备驱动中spi_device_idname做匹配;
  • flags 将初始化 spi_device.flags
  • platform_data 将初始化 spi_device.dev.platform_data
  • controller_data将初始化spi_device.controller_data
  • irq 将初始化 spi_device.irq
  • max_speed_hz将初始化 spi_device.max_speed_hz
  • chip_select将初始化 spi_device.chip_select
  • mode将初始化 spi_device.mode

需要注意的是,struct spi_board_info 结构体中的字段提供的是设备的静态信息,不能包含通过驱动程序的 probe() 函数动态确定的信息。

spi_bitbang

spi_bitbang结构体的主要用途是实现SPI总线的位操作(bit-banging)接口,用于控制SPI总线的传输和通信,以满足特定的应用需求。

首先,对于多数情况来说,我们所用的SPI,都是有对应的SPI的控制器的,其负责和外部SPI设备进行通信,负责两者通信时候的信号之间的同步,保证信号的timing都符合SPI协议,保证可以正常进行SPI通信。

但是有些时候,没有此对应的硬件上的SPI控制器,而还想要和SPI设备通信,那么就只能用GPIO端口去模拟对应的SPI接口的对应的pin:片选CS,数据输入Data In,数据输出Data Out,时钟Clock,去模拟SPI协议,和对应spi设备进行通信。

所以,此时你对每个端口的操作,作为编程者,你自己要去负责信号的同步,保证timing符合协议规定,才能正常进行SPI通信。

这样的SPI的bit-bang,优点是不需要SPI的控制器了,但是缺点很明显,除了要用户自己负责同步,timing等事情之外,相对来说,即使本身SPI设备支持以很高的频率运行,可以实现很好的性能,但是以bit-bang的方式去使用的话,实际性能往往很差。

最后,可以用一句话来解释,什么是SPI的bitbang/bit-bang:

Use software to control serial communication at general-purpose I/O pins

通过GPIO引脚,用软件来模拟串行通信(SPI/I2C)

c 复制代码
struct spi_bitbang {
	struct mutex		lock;
	u8			busy;
	u8			use_dma;
	u8			flags;		/* extra spi->mode support */

	struct spi_master	*master;

	/* setup_transfer() changes clock and/or wordsize to match settings
	 * for this transfer; zeroes restore defaults from spi_device.
	 */
	int	(*setup_transfer)(struct spi_device *spi,
			struct spi_transfer *t);

	void	(*chipselect)(struct spi_device *spi, int is_on);
#define	BITBANG_CS_ACTIVE	1	/* normally nCS, active low */
#define	BITBANG_CS_INACTIVE	0

	/* txrx_bufs() may handle dma mapping for transfers that don't
	 * already have one (transfer.{tx,rx}_dma is zero), or use PIO
	 */
	int	(*txrx_bufs)(struct spi_device *spi, struct spi_transfer *t);

	/* txrx_word[SPI_MODE_*]() just looks like a shift register */
	u32	(*txrx_word[4])(struct spi_device *spi,
			unsigned nsecs,
			u32 word, u8 bits);
};
  • struct mutex lock: 这是一个互斥锁(mutex),用于对SPI总线进行同步访问。在并发访问SPI总线时,通过获得该锁,可以确保每次只有一个线程或进程能够访问SPI总线,避免数据竞争和冲突。
  • u8 busy: 这个成员表示SPI总线的忙碌状态。当其值为1时,表示SPI总线正在执行传输操作;当其值为0时,表示SPI总线处于空闲状态。
  • u8 use_dma: 这个成员表示是否使用DMA(Direct Memory Access)进行数据传输。如果其值为1,表示使用DMA方式传输数据;如果其值为0,表示使用PIO(Programmed Input/Output)方式进行数据传输。
  • u8 flags: 这个成员是一些额外的标志,用于支持SPI模式的特定功能。
  • struct spi_master *master: 这是一个指向SPI主控制器(spi_master)的指针。通过该指针,可以与特定的SPI主控制器进行通信和交互,包括设置时钟频率、发送和接收数据等。
  • int (*setup_transfer)(struct spi_device *spi, struct spi_transfer *t): 这是一个函数指针,指向一个函数,用于设置SPI传输的参数。在每次传输之前,可以调用该函数来改变时钟频率、字大小等传输设置。该函数接收两个参数,spi表示SPI设备的指针,t表示SPI传输的参数结构体(spi_transfer)。
  • void (*chipselect)(struct spi_device *spi, int is_on): 这是一个函数指针,指向一个函数,用于控制SPI设备的片选信号。通过调用该函数,可以使能或禁用SPI设备的片选信号。函数的第一个参数是SPI设备的指针,第二个参数is_on表示片选信号的状态。宏定义BITBANG_CS_ACTIVE表示片选信号处于激活状态(通常为低电平),BITBANG_CS_INACTIVE表示片选信号处于非激活状态。
  • int (*txrx_bufs)(struct spi_device *spi, struct spi_transfer *t): 这是一个函数指针,指向一个函数,用于在SPI设备和主机之间传输数据。该函数可以处理没有进行DMA映射的传输(transfer.{tx,rx}_dma为零),或者使用PIO方式进行数据传输。函数的第一个参数是SPI设备的指针,第二个参数是SPI传输的参数结构体。
  • u32 (*txrx_word[4])(struct spi_device *spi, unsigned nsecs, u32 word, u8 bits): 这是一个函数指针数组,用于在SPI设备和主机之间以位移寄存
相关推荐
Lary_Rock34 分钟前
RK3576 LINUX RKNN SDK 测试
linux·运维·服务器
△曉風殘月〆37 分钟前
WPF MVVM入门系列教程(二、依赖属性)
c#·wpf·mvvm
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
云飞云共享云桌面2 小时前
8位机械工程师如何共享一台图形工作站算力?
linux·服务器·网络
励志成为嵌入式工程师2 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
逐·風3 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
捕鲸叉3 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer3 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq3 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
一坨阿亮4 小时前
Linux 使用中的问题
linux·运维