Linux SPI设备分析3(基于Linux6.6)---SPI master /device/driver介绍
一、spi驱动框架与i2c框架的区别
| 特性 | SPI 驱动框架 | I2C 驱动框架 |
|---|---|---|
| 通信方式 | 全双工(Full-duplex),同时支持数据的发送和接收 | 半双工(Half-duplex),一次只能发送或接收数据 |
| 信号线数量 | 4 根:MOSI(主设备发送)、MISO(主设备接收)、SCK(时钟)、CS(片选) | 2 根:SDA(数据线)、SCL(时钟线) |
| 总线结构 | 支持多从设备,通常每个从设备使用一个单独的片选(CS)信号 | 支持多设备,所有设备共享同一条总线,地址区分不同设备 |
| 通信速率 | 通常较高,支持更高的传输速率(几 Mbps 至几十 Mbps) | 通常较低,速度一般在 100 kbps、400 kbps 或 1 Mbps 左右 |
| 硬件复杂度 | 硬件支持较为复杂,需要多条信号线并且通常每个从设备需要独立的片选线 | 硬件支持较为简单,只需要两条信号线即可实现多设备通信 |
| 设备地址管理 | 没有统一的设备地址管理机制,通过片选(CS)线区分设备 | 每个设备都有唯一的 7 位或 10 位地址,用于在总线上进行寻址 |
| 传输模式 | 支持全双工,主设备与从设备可以同时进行数据交换 | 仅支持半双工,主设备发送数据后等待从设备响应(或反之) |
| 协议复杂度 | 相对较复杂,涉及时序、片选信号等多个参数的配置 | 协议简单,基于地址进行通信,时序由 I2C 协议定义 |
| 数据传输方式 | 由主设备通过 SCK 时钟同步控制数据传输,适用于高速数据交换 | 基于时钟拉高与拉低的同步传输,适用于慢速数据交换 |
| 数据校验 | 通常不支持硬件校验(需要应用层进行校验) | 支持硬件校验(如 CRC 校验),能够检测数据传输的错误 |
| 设备数量 | 支持的设备数量相对较少,通常受限于主设备的片选线数量 | 可以支持更多设备,设备地址是唯一标识,且支持多个设备同时存在 |
| 时序控制 | 时钟信号由主设备产生(SCK),从设备根据该时钟信号进行同步传输 | 由主设备生成时钟信号(SCL),从设备同步接收数据 |
| 支持的操作模式 | SPI 模式有 4 种(CPOL 和 CPHA 配置),需根据设备要求设置 | I2C 模式简单,主要分为标准模式(100 kbps)、快速模式(400 kbps)等 |
| 硬件资源使用 | 需要多条信号线,并且每个设备使用独立的片选信号线(CS),占用较多 GPIO 资源 | 只需要 2 根信号线 SDA 和 SCL,占用较少的硬件资源 |
| 数据帧格式 | 数据传输通常是字节或更大的块,帧的格式更灵活 | 数据传输是按字节进行,帧格式固定,通常是设备地址 + 数据 |
| 典型应用场景 | 用于高速通信、传感器、存储器、显示屏等设备间的通信 | 用于低速通信、传感器、EEPROM、RTC、微控制器间的通信等 |
二、spi驱动-总线-控制器-设备模块之间的关联
这四个子模块通过借助LINUX设备-总线-驱动模型提供的数据结构以及接口函数,完成了子模块间的关联(这四个子模块关联关系的建立以及解除,即通过这几个模块间的注册与注销接口完成的)。简要说下这几个模块间的关联,然后再分别对这几个模块进行分析说明。下面通过一张简略图描述这四者之间的关系。

- spi总线通过其bus_kset成员,链接至系统的总线集bus_kset上;
- spi中心概念的klist_devices、klist_drivers链表,完成了总线与spi设备、spi驱动之间的关联;
- Spi master借助device类型变量中的父子链表成员,完成与spi device的关联(父子关系关联);
- Spi master与spi device借助其spi模块定义的成员,完成了spi master与spi device的绑定;
- Spi master、spi device借助其device类型,完成了与系统设备集devices_kset的关联;
- Spi master借助其device类型成员变量,完成与spi_master_class类的关联(此图未画出)。
基本上通过这些关联关系,即完成了spi框架中这几个子模块间的关联操作。
三、spi master device的注册与注销以及相关结构体说明
Spi master用于抽象一个spi控制器,该控制器提供spi总线的通信方法等信息,同时该spi控制器的device类型的成员变量是该控制器下所有spi device的父设备。下面分析下该控制器的注册与注销。
先分析下spi master的定义,对该结构体变量熟悉了以后,对它的的注册与注销也就基本上熟悉了。对主要成员进行说明:
- dev用于使用LINUX设备-总线-驱动模型;
- Bus_num、num_chipselect用于说明总线号及支持的cs个数;
- mode_bits表示该控制器支持的模式(即spi总线支持的四种模式);
- setup接口用于设置总线模式、时钟等内容;
- transfer说明该控制器提供的通信方法(目前在新的内核中已不推荐使用该接口,推荐使用队列方式,队列模式由spi核心提供,我们会在后面进行详细说明);
- kworker、kworker_task、queued、pump_messages、prepare_transfer_hardware、unprepare_transfer_hardware、unprepare_transfer_hardware这几个成员变量均是用于spi核心提供的队列通信模式。
- list成员用于将该spi_master链接至系统的spi_master_list链表上
include/linux/spi/spi.h
/* Compatibility layer */
#define spi_master spi_controller
struct spi_controller {
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 */
u32 mode_bits;
/* spi_device.mode flags override flags for this controller */
u32 buswidth_override_bits;
/* Bitmask of supported bits_per_word for transfers */
u32 bits_per_word_mask;
#define SPI_BPW_MASK(bits) BIT((bits) - 1)
#define SPI_BPW_RANGE_MASK(min, max) GENMASK((max) - 1, (min) - 1)
/* Limits on transfer speed */
u32 min_speed_hz;
u32 max_speed_hz;
/* Other constraints relevant to this driver */
u16 flags;
#define SPI_CONTROLLER_HALF_DUPLEX BIT(0) /* Can't do full duplex */
#define SPI_CONTROLLER_NO_RX BIT(1) /* Can't do buffer read */
#define SPI_CONTROLLER_NO_TX BIT(2) /* Can't do buffer write */
#define SPI_CONTROLLER_MUST_RX BIT(3) /* Requires rx */
#define SPI_CONTROLLER_MUST_TX BIT(4) /* Requires tx */
#define SPI_CONTROLLER_GPIO_SS BIT(5) /* GPIO CS must select slave */
#define SPI_CONTROLLER_SUSPENDED BIT(6) /* Currently suspended */
/* Flag indicating if the allocation of this struct is devres-managed */
bool devm_allocated;
union {
/* Flag indicating this is an SPI slave controller */
bool slave;
/* Flag indicating this is an SPI target controller */
bool target;
};
/*
* On some hardware transfer / message size may be constrained
* the limit may depend on device transfer settings.
*/
size_t (*max_transfer_size)(struct spi_device *spi);
size_t (*max_message_size)(struct spi_device *spi);
/* I/O mutex */
struct mutex io_mutex;
/* Used to avoid adding the same CS twice */
struct mutex add_lock;
/* 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);
/*
* set_cs_timing() method is for SPI controllers that supports
* configuring CS timing.
*
* This hook allows SPI client drivers to request SPI controllers
* to configure specific CS timing through spi_set_cs_timing() after
* spi_setup().
*/
int (*set_cs_timing)(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 controller's main job is to process its message queue,
* selecting a chip (for masters), 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_controller */
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_controller *ctlr,
struct spi_device *spi,
struct spi_transfer *xfer);
struct device *dma_map_dev;
struct device *cur_rx_dma_dev;
struct device *cur_tx_dma_dev;
/*
* These hooks are for drivers that want to use the generic
* controller 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 kthread_work pump_messages;
spinlock_t queue_lock;
struct list_head queue;
struct spi_message *cur_msg;
struct completion cur_msg_completion;
bool cur_msg_incomplete;
bool cur_msg_need_completion;
bool busy;
bool running;
bool rt;
bool auto_runtime_pm;
bool cur_msg_mapped;
char last_cs;
bool last_cs_mode_high;
bool fallback;
struct completion xfer_completion;
size_t max_dma_len;
int (*prepare_transfer_hardware)(struct spi_controller *ctlr);
int (*transfer_one_message)(struct spi_controller *ctlr,
struct spi_message *mesg);
int (*unprepare_transfer_hardware)(struct spi_controller *ctlr);
int (*prepare_message)(struct spi_controller *ctlr,
struct spi_message *message);
int (*unprepare_message)(struct spi_controller *ctlr,
struct spi_message *message);
union {
int (*slave_abort)(struct spi_controller *ctlr);
int (*target_abort)(struct spi_controller *ctlr);
};
/*
* These hooks are for drivers that use a generic implementation
* of transfer_one_message() provided by the core.
*/
void (*set_cs)(struct spi_device *spi, bool enable);
int (*transfer_one)(struct spi_controller *ctlr, struct spi_device *spi,
struct spi_transfer *transfer);
void (*handle_err)(struct spi_controller *ctlr,
struct spi_message *message);
/* Optimized handlers for SPI memory-like operations. */
const struct spi_controller_mem_ops *mem_ops;
const struct spi_controller_mem_caps *mem_caps;
/* GPIO chip select */
struct gpio_desc **cs_gpiods;
bool use_gpio_descriptors;
s8 unused_native_cs;
s8 max_native_cs;
/* Statistics */
struct spi_statistics __percpu *pcpu_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;
int (*fw_translate_cs)(struct spi_controller *ctlr, unsigned cs);
/*
* Driver sets this field to indicate it is able to snapshot SPI
* transfers (needed e.g. for reading the time of POSIX clocks)
*/
bool ptp_sts_supported;
/* Interrupt enable state during PTP system timestamping */
unsigned long irq_flags;
/* Flag for enabling opportunistic skipping of the queue in spi_sync */
bool queue_empty;
bool must_async;
};
3.1、Spi master的注册
上面分析了spi_master的数据结构定义,现在分析下其注册接口spi_register_master的实现。在之前的文档中,已经说明spi驱动框架是按照LINUX设备-总线-驱动模型进行实现的,并没有将spi master注册到spi总线上,而仅仅将spi master注册到系统的devices_kset中,隶属于LINUX系统设备集,同时隶属于spi_master_class类(spi master device并没有注册到spi总线上,而不像i2c模块,将i2c adapter也注册到i2c总线上,从这一点上而言,spi模块还是比较好的遵守了LINUX设备-总线-驱动模型的)。
Spi master注册接口的流程图如下,主要实现的功能如下:
- 调用device_add,将该master对应的device成员注册到系统的设备集devices_kset上,以及链接至spi_master_class类,此处主要是使用系统的设备模型对应的接口;
- 针对新版内核,spi模块提供队列传输模式,为该master创建worker kthread,并与master的进行绑定(该部分在后面会进行详细说明);
- 将该master链接到系统的spi_master_list链表上(该变量主要是在调用spi模块的设备注册接口时,根据传递的bus_num与该链表上的master进行匹配检测,从而实现将注册的spi device与对应的master的绑定);
- 对系统上board_list链表上注册的spi board info(尚未创建对应的spi device),若其属于该master,则为其创建spi device并注册到spi总线上,同时完成spi device与该master的绑定操作;
- 对于通过设备树创建的master,针对该master的of_node节点上所有的设备子节点,均创建对应的spi device,并将其注册到spi总线上,同时完成spi device与master的绑定操作。
针对spi device,其父设备均为其所绑定的spi master。

3.2、Spi master的注销
以上为spi master注册相关的处理流程分析,而针对spi master注销的流程,其主要流程与上
面的流程刚发相反,主要内容如下:
- 将该master从spi_master_list链表上移除;
- 针对该spi master上所有的子设备,调用device_unregister将其从spi总线上移除,并完成与spi driver的解绑;
- 若该spi master支持队列传输模式,则关闭该master对应的worker kthread。
- 针对该spi master对应的device,调用device_unregister,移除该设备(此处仅仅将该设备从系统设备集devices_kset上注销以及从spi_master_class类上解除对该设备的链接)
3.3、Spi device的注册与注销
上面分析了spi总线的注册与注销,此处分析spi设备的注册与注销,首先分析下spi device对应数据结构定义。主要包含该设备所依附的master、传输模式、传输速率、片选、device类型的成员变量用于实现LINUX设备模型,完成注册到spi总线上等。
struct spi_device {
struct device dev;
/*该设备所依附的master*/
struct spi_master *master;
/*传输速率*/
u32 max_speed_hz;
/*该设备对应的片选*/
u8 chip_select;
/*数据传输模式(4种模式之一)*/
u8 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 */
/*单次传输的bit数*/
u8 bits_per_word;
/*中断号*/
int irq;
/*spi控制器相关的私有状态与私有数据等信息*/
void *controller_state;
void *controller_data;
/*该设备的名称,用于设备与驱动的匹配检测*/
char modalias[SPI_NAME_SIZE];
/*若使用gpio作为cs,此处标注cs的序号,*/
int cs_gpio; /* chip select gpio */
/*
* 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
* - ...
*/
};
该结构体的定义还是比较明确的,通过上面总线的注册与注销的分析,spi device主要是借助设备模型的device_add接口,完成将该设备注册到spi总线上。spi设备的添加接口spi_add_device的流程图如下,也就是增加了spi_setup的调用设置该设备的通信模式与通信速率,然后就调用device_add完成spi设备的注册了。

下面看下spi device添加的方式,主要包括如下几个方式:
- 在spi master注册时,完成spi 设备的创建与注册(1. 通过设备树模式,为该master下的设备完成创建与注册 2.通过注册到board_list链表上的设备信息,完成spi设备的创建与注册);
- 针对spi master已创建的情况,通过调用spi_register_board_info接口,实现spi device的创建与注册操作。
针对spi device,其注销接口则主要是调用device_unregister完成设备的注销操作。
四、Spi driver的注册与注销
进行spi driver的注册与注销操作,照例先分析spi driver的数据结构的定义:
include/linux/spi/spi.h
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
void (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
struct device_driver driver;
};
该接口基本上没有定义spi私有的相关信息,基本上是对device_driver类型的重载,因此spi driver的注册应该也不会复杂,基本上也就是调用driver_register实现驱动的注册。
4.1、spi_register_driver接口实现
如下为spi_register_driver的实现,设置该驱动属于spi总线、同时设置probe、remove等函数指针,最后调用driver_register完成spi总线的注册。
drivers/spi/spi.c
int __spi_register_driver(struct module *owner, struct spi_driver *sdrv)
{
sdrv->driver.owner = owner;
sdrv->driver.bus = &spi_bus_type;
/*
* For Really Good Reasons we use spi: modaliases not of:
* modaliases for DT so module autoloading won't work if we
* don't have a spi_device_id as well as a compatible string.
*/
if (sdrv->driver.of_match_table) {
const struct of_device_id *of_id;
for (of_id = sdrv->driver.of_match_table; of_id->compatible[0];
of_id++) {
const char *of_name;
/* Strip off any vendor prefix */
of_name = strnchr(of_id->compatible,
sizeof(of_id->compatible), ',');
if (of_name)
of_name++;
else
of_name = of_id->compatible;
if (sdrv->id_table) {
const struct spi_device_id *spi_id;
spi_id = spi_match_id(sdrv->id_table, of_name);
if (spi_id)
continue;
} else {
if (strcmp(sdrv->driver.name, of_name) == 0)
continue;
}
pr_warn("SPI driver %s has no spi_device_id for %s\n",
sdrv->driver.name, of_id->compatible);
}
}
return driver_register(&sdrv->driver);
}
EXPORT_SYMBOL_GPL(__spi_register_driver);
关于driver_register接口的实现流程,想详细了解的同学请查看之前写的设备驱动模型相关的文档,此处就不附上链接了,下图为driver_register以及device_register中实现设备与驱动匹配的简要流程(因spi总线未定义probe,因此当完成spi设备与驱动匹配检测后,则直接调用spi driver的probe进行探测操作)。

4.2、spi_unregister_driver接口
该接口直接调用driver_unregister,实现驱动的注销以及与device的解绑等操作,此接口为LINUX设备-总线-驱动模型中的接口。
以上即为spi driver的注册与注销,相对来说还是比较简单的。那我们在实现一个spi驱动时,应该注意哪些事情呢:
- 确认该spi设备支持的通信方式、片选;
- 确认该spi设备驱动在进行探测时,需要对哪些寄存器进行初始化(针对初始化操作,在driver的probe接口中实现);
- 为了应用层程序可以和spi设备进行通信,提供字符设备文件(read、write、ioctl),并实现相应的操作接口(在driver的probe接口中实现字符设备的注册)。
include/linux/spi/spi.h
/**
* spi_unregister_driver - reverse effect of spi_register_driver
* @sdrv: the driver to unregister
* Context: can sleep
*/
static inline void spi_unregister_driver(struct spi_driver *sdrv)
{
if (sdrv)
driver_unregister(&sdrv->driver);
}
2244

被折叠的 条评论
为什么被折叠?



