【驱动】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_giops`、`cs_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_device`和`spi_board_info`内部很多成员都是相似的,`spi_device`中的很多成员变量值都继承于`spi_board_info`。
- `modalias`将初始化 `spi_device.modalias`,最后会与从设备驱动中`spi_device_id`的`name`做匹配;
- `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设备和主机之间以位移寄存
LINUX SPI模块之SPI 通信接口(spi_sync、spi_async)分析说明
上两篇文章完成了spi总线、设备、驱动、master的分析,下面我们分析下spi模块提供的通信方法,通过该通信方法,即可完成cpu与具体spi设备之间的通信(借助spi controller)。
其实,spi_sync、spi_async的实现也不是太复杂,但是由于在新版内核中,针对spi_sync,spi核心提供了基于worker线程的处理方式,基于该方式则所有spi master均不需要提供spi_transfer接口,而使用spi核心定义的spi_queued_transfer即可。而这两个接口也是与具体的spi master相关联的。
spi_sync、spi_async
Spi 核心提供两种通信方法spi_sync、spi_async(同步、异步),这两种方法最终均是通过调用__spi_async接口实现数据的通信。
这两个接口的处理流程图如下所示:
对于同步通信,设置本次传输的spi_message类型变量的complete函数指针指向spi_complete,借助完成量机制,完成spi的同步通信操作(主要借助完成量的complete、wait_for_completion这两个接口,其实这两个接口也就是借助内核的等待队列,根据等待队列的sleep、wakeup实现完成量);对于异步通信,则不必执行上述1中的内容;对本次通信的spi_message类型变量进行合法性检查,包括spi_message->transfer链表上所有需要输出的spi_transfer类型的数据进行合法性检测、传输速率等信息的合法性检测。调用spi控制器的transfer接口,进行数据传输操作。spi核心提供的spi queue处理方式
针对spi核心提供的spi_sync、spi_async接口,基本上完成上面的框架接口,然后每一个spi_master则通过实现spi_master->transfter接口,即实现了数据通信。但在新版的内核中,spi核心模块又提供了kthread_worker机制,通过为每一个spi控制器创建worker线程,由该worker线程处理该spi总线上所有需要处理的通信数据。
Spi 控制器的worker kthread的创建
当调用spi_register_master接口注册一个spi控制器,若该控制器支持队列模式,则调用spi_master_initialize_queue接口创建一个worker kthread,同时将master->kworker绑定到该线程生;初始化master->pump_messages,用于注册到master->kworker上。Spi 控制器queue机制的transfer接口
针对提供queue机制的spi控制器,其transfer接口均指向spi_queued_transfer,该接口的实现流程如下,主要实现功能如下:
将该spi_message类型的变量加入到spi->master的queue队列上;将master->pump_messages加入到master->kworker上。当spi_queued_transfer完成将master->pump_messages加入到master->kworker上后,worker kthread线程的处理函数kthread_worker_fn即会从master->kworker上取出一个kthread_work,并调用该变量的func接口,对于spi控制器而言,即为spi_pump_messages接口。该接口的实现功能如下:
然后调用该master->prepare_transfer_hardware、transfer_one_message接口,完成针对该spi_message类型变量的数据传输,这就由具体控制器的接口来实现相应功能。以上便是spi核心提供的基于队列模式(借助LINUX的worker kthread机制)相关的spi核心层提供的接口分析。
此篇文章简要说明了spi_sync、spi_async的实现方式,以及与spi master之间的调用关联。
相关问答
linux 怎么设置 spi 命令?linux可以打开系统应用设置功能设置spi命令linux可以打开系统应用设置功能设置spi命令
玩树莓派能学到什么?曾经极客君作为一名设备的追求者,天天琢磨着各种显卡、内存、机械键盘,以为有了好设备才能够成为真正的代码大神。直到看到这条新闻:美国宇航局(NASA)监察...首...
mt7621at是什么处理器?SPI,NANDFlash,SDXC1xUSB3.0,2XUSB2.0,3xPCIe主机5-端口10/100/1000SW/PHY和1个RGMIIGreen--智能时钟缩放(不包括)...
学习嵌入式开发的过程难吗?网友一根据我的了解,选择学习嵌入式linux,刚好我们学校也重视嵌入式linux,从实验室到课程安排都是关于嵌入式linux方面,天时地利!这里我把学习linux的经验...根...
前辈们有哪位知道! linux 软连接和硬连接有什么用?[回答]Linux链接概念Linux链接分两种,一种被称为硬链接(HardLink),另一种被称为符号链接(SymbolicLink)。默认情况下,ln命令产生硬链接。【硬连接】硬连...
在线的同志!楼主请问:哈尔滨连接器批发价,连接器什么品牌...[回答]有时钟信号(SCLK,经常在10MHz左右)和并行数据线带有“主出,从进(MOSI)”或是“主进,从出(MISO)”信号。数据交换的时候有四种时钟模式,模...数据交...
JAVA、C、C++、Python同样是高级语言,为什么只有C和C++可以编写单片机程序?作为一个诞生本是为系统开发的语言,它本身就是与单片机编程匹配的,只不过由于种种原因,C语言进一步发展,成为了一种广泛使用可上可下的编程语言,看起来与Jav...
自主研发十八年,国产龙芯处理器到底如何了?龙芯主频较低与工艺制程有很大的关系,龙芯3A3000仍然采用了28nm的制程,而intel、amd、国内的飞腾、兆芯已经使用了14nm工艺。对比intel即将发布的i9-9900ks,将...
arctic50带什么芯片?Arctic50采用了NordicSemiconductor的nRF52840芯片,这是一款功能强大的蓝牙5.0和蓝牙低功耗(BLE)芯片。nRF52840芯片集成了ArmCortex-M4处理器...
dcp计算机是什么?dcp可以在不同主机之间使用Dat对等网络复制文件。这可以让你在两个主机间传输文件时,无需操心所述主机之间互相访问的细节,以及这些主机是否使用了NAT。Li...