SPI 协议介绍

CPOL:表示 SPICLK 的初始电平,0 为低电平,1 为高电平

CPHA:表示相位,即第一个还是第二个时钟沿采样数据,0 为第一个时钟沿,1 为第二个时钟沿

CPOLCPHA模式含义
000SPICLK 初始电平为低电平,在第一个时钟沿采样数据
011SPICLK 初始电平为低电平,在第二个时钟沿采样数据
102SPICLK 初始电平为高电平,在第一个时钟沿采样数据
113SPICLK 初始电平为高电平,在第二个时钟沿采样数据

我们常用的是模式 0 和模式 3,因为它们都是在上升沿采样数据,不用去在乎时钟的初始电平是什么,只要在上升沿采集数据就行。

SPI driver

spi_controller

struct spi_controller {
	struct device	dev;
	struct list_head list;
	s16			bus_num;
	u16			num_chipselect;
	u16			dma_alignment;
	u32			mode_bits;
	u32			buswidth_override_bits;
	u32			bits_per_word_mask;
	u32			min_speed_hz;
	u32			max_speed_hz;
	u16			flags;
	bool			devm_allocated;
	union {
		bool			slave;
		bool			target;
	};
	size_t (*max_transfer_size)(struct spi_device *spi);
	size_t (*max_message_size)(struct spi_device *spi);
	struct mutex		io_mutex;
	struct mutex		add_lock;
	spinlock_t		bus_lock_spinlock;
	struct mutex		bus_lock_mutex;
	bool			bus_lock_flag;
	int			(*setup)(struct spi_device *spi);
	int (*set_cs_timing)(struct spi_device *spi);
	int			(*transfer)(struct spi_device *spi,
						struct spi_message *mesg);
	void			(*cleanup)(struct spi_device *spi);
	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;
	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;
	bool                            fallback;
	bool				last_cs_mode_high;
	s8				last_cs[SPI_CS_CNT_MAX];
	u32				last_cs_index_mask : SPI_CS_CNT_MAX;
	struct completion               xfer_completion;
	size_t				max_dma_len;
	int (*optimize_message)(struct spi_message *msg);
	int (*unoptimize_message)(struct spi_message *msg);
	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);
	};
	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);
	const struct spi_controller_mem_ops *mem_ops;
	const struct spi_controller_mem_caps *mem_caps;
	struct gpio_desc	**cs_gpiods;
	bool			use_gpio_descriptors;
	s8			unused_native_cs;
	s8			max_native_cs;
	struct spi_statistics __percpu	*pcpu_statistics;
	struct dma_chan		*dma_tx;
	struct dma_chan		*dma_rx;
	void			*dummy_rx;
	void			*dummy_tx;
	int (*fw_translate_cs)(struct spi_controller *ctlr, unsigned cs);
	bool			ptp_sts_supported;
	unsigned long		irq_flags;
	bool			queue_empty;
	bool			must_async;
	bool			defer_optimize_message;
};

list: spi controller global list。

bus_num: SoC 上某个 spi controller 的编号。

num_chipselect: 用来表示 spi slaves 的编号,每个 slave 都有一个 chipselect signal。

dma_alignment: 有些 spi controller 对 dma buffer 有 alignment 要求,vendor driver 设置。

mode_bits: 用来表示 spi controller 支持的 mode,在 uapi/linux/spi/spi.h 中定义,vendor driver 设置。

bits_per_word_mask: 用来表示 spi controller 支持的 bits_per_word,vendor driver 设置。

min/max_speed_hz: 用来表示 spi controller 支持的 min/max speed,vendor driver 设置。

flags: 额外的 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 */
#define SPI_CONTROLLER_MULTI_CS		BIT(7)

transfer: 向 queue 中增加 spi message, 由 spi core 提供,默认实现为 spi_queued_transfer。

can_dma: 是否支持 dma.

queued: 如果支持 spi core 的 queue 机制,transfer 函数由 spi core 提供,则 spi core 把 queued 置为 1.

transfer_one_message: 如果 vendor driver 没实现,默认实现为 spi_transfer_one_message.

max_transfer_size/max_message_size: spi controller 支持的 max transfer size/max message size 回调,vendor driver 设置。

setup: 设定 spi controller mode, clk 等等。

set_cs_timing: optional, 设定 cs timing 的回调。

prepare_message/unprepare_message: optional, 为 spi message 传输前的准备工作,比如设定 dma mapping 等。

transfer_one: 传输一条 spi_transfer, 传输完成需要调用 spi_finalize_current_transfer,和 transfer_one_message 互斥。

spi_transfer, spi_message

在 SPI 子系统中,用 spi_transfer 结构体描述一个传输,用 spi_message 管理整个传输。可以构造多个 spi_transfer 结构体,把它们放入一个 spi_message 里面。

SPI 传输时,发出 N 个字节,就可以同时得到 N 个字节。

  • 即使只想读 N 个字节,也必须发出 N 个字节:可以发出 0xff
  • 即使只想发出 N 个字节,也会读到 N 个字节:可以忽略读到的数据。
struct spi_message {
	struct list_head	transfers;
	struct spi_device	*spi;
	unsigned		is_dma_mapped:1;
	bool			prepared;
	int			status;
	void			(*complete)(void *context);
	void			*context;
	unsigned		frame_length;
	unsigned		actual_length;
	struct list_head	queue;
	struct list_head        resources;
};

transfers: 一个 spi_message 可以包含多个 spi_transfer,组成一个链表。

spi: 传输 message 到某个 spi device。

is_dma_mapped: 找不到 spi core 哪里使用了该 field?? 是否

prepared: 是否调用了 ctrl->prepare_message()。

status: 当前 message 的 status,0 表示正常,负数表示异常。

complete: 传输完成后的回调。

context: 传递给 complete 的参数。

frame_length: 当前 message 的要传输的总字节数。

actual_length: 当前 message 实际已经传输的字节数。

struct spi_transfer {
	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 /* 1-bit transfer */
#define	SPI_NBITS_DUAL		0x02 /* 2-bit transfer */
#define	SPI_NBITS_QUAD		0x04 /* 4-bit transfer */
	u8		bits_per_word;
	u8		word_delay_usecs;
	u16		delay_usecs;
	u16		cs_change_delay;
	u8		cs_change_delay_unit;
	u32		speed_hz;
	u16		word_delay;
	u32		effective_speed_hz;
	struct list_head transfer_list;
};

tx_buf/rx_buf: 发送/接收数据缓冲区。

len: 发送/接收数据的长度。

tx_dma/rx_dma: 发送/接收数据缓冲区的 dma 地址。

tx_sg/rx_sg: 发送/接收数据缓冲区的 scatterlist。

cs_change: 传输完成后是否要改变 cs 信号,consumer 设定。

cs_change_delay: 当 cs_change 置起,传输完成后,cs 信号 deassert 到 assert 的 delay 时间,consumer 设定。

tx_nbits/rx_nbits: 每秒传输几 bits,有 SPI_NBITS_SINGLE, SPI_NBITS_DUAL, SPI_NBITS_QUAD 三种。

bits_per_word: 一次 SPI 传输中发送/接收的每个数据单元的大小。

delay_usecs: 传输开始前的 delay。

speed_hz: 传输速度。

SPI 设备树

SPI Master

必须的属性如下:

  • address-cells:这个 SPI Master 下的 SPI 设备,需要多少个 cell 来表述它的片选引脚
  • size-cells:必须设置为 0
  • compatible:根据它找到 SPI Master 驱动

可选的属性如下:

  • cs-gpios:SPI Master 可以使用多个 GPIO 当做片选,可以在这个属性列出那些 GPIO
  • num-cs:片选引脚总数

SPI Device

必须的属性如下:

  • compatible:根据它找到 SPI Device 驱动
  • reg:用来表示它使用哪个片选引脚
  • spi-max-frequency:必选,该 SPI 设备支持的最大 SPI 时钟

可选的属性如下:

  • spi-cpol:这是一个空属性 (没有值),表示 CPOL 为 1,即平时 SPI 时钟为低电平
  • spi-cpha:这是一个空属性 (没有值),表示 CPHA 为 1),即在时钟的第 2 个边沿采样数据
  • spi-cs-high:这是一个空属性 (没有值),表示片选引脚高电平有效
  • spi-3wire:这是一个空属性 (没有值),表示使用 SPI 三线模式
  • spi-lsb-first:这是一个空属性 (没有值),表示使用 SPI 传输数据时先传输最低位 (LSB)
  • spi-tx-bus-width:表示有几条 MOSI 引脚;没有这个属性时默认只有 1 条 MOSI 引脚
  • spi-rx-bus-width:表示有几条 MISO 引脚;没有这个属性时默认只有 1 条 MISO 引脚
  • spi-rx-delay-us:单位是毫秒,表示每次读传输后要延时多久
  • spi-tx-delay-us:单位是毫秒,表示每次写传输后要延时多久

示例:

spi@f00 {
		#address-cells = <1>;
		#size-cells = <0>;
		compatible = "fsl,mpc5200b-spi","fsl,mpc5200-spi";
		reg = <0xf00 0x20>;
		interrupts = <2 13 0 2 14 0>;
		interrupt-parent = <&mpc5200_pic>;

		ethernet-switch@0 {
			compatible = "micrel,ks8995m";
			spi-max-frequency = <1000000>;
			reg = <0>;
		};

		codec@1 {
			compatible = "ti,tlv320aic26";
			spi-max-frequency = <100000>;
			reg = <1>;
		};
	};

spidev 的使用 (SPI 用户态 API)

  • 内核驱动:drivers\spi\spidev.c
  • 内核提供的测试程序:tools\spi\spidev_fdx.c
  • 内核文档:Documentation\spi\spidev

spidev 驱动程序分析

内核驱动:drivers\spi\spidev.c

spidev_write();
	spidev_sync_write();
		spidev_sync();

spidev_read();
	spidev_sync_read();
		spidev_sync();

spidev_ioctl();
// 在应用层通过 ioctl(fd, SPI_IOC_MESSAGE(x), xfer) 来调用,进行 spi 传输。参考 spidev_fdx.c

spidev 应用程序分析

内核提供的测试程序:tools\spi\spidev_fdx.c

使用方法:

spidev_fdx [-h] [-m N] [-r N] /dev/spidevB.D

h: 打印用法

m N:先写 1 个字节 0xaa,再读 N 个字节,**注意:**不是同时写同时读

r N:读 N 个字节

spidev 缺点

使用 read、write 函数时,只能读、写,这是半双工方式。

使用 ioctl 可以达到全双工的读写。

但是 spidev 有 2 个缺点:

  • 不支持中断
  • 只支持同步操作,不支持异步操作:就是 read/write/ioctl 这些函数只能执行完毕才可返回

Data structure and APIs

// include/linux/spi/spi.h
static inline int spi_write(struct spi_device *spi, const void *buf, size_t len);
static inline int spi_read(struct spi_device *spi, void *buf, size_t len);
extern int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx);
static inline ssize_t spi_w8r8(struct spi_device *spi, u8 cmd);
static inline ssize_t spi_w8r16(struct spi_device *spi, u8 cmd);
static inline ssize_t spi_w8r16be(struct spi_device *spi, u8 cmd);
static inline int spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers, unsigned int num_xfers);
extern int spi_async(struct spi_device *spi, struct spi_message *message);
extern int spi_sync(struct spi_device *spi, struct spi_message *message);

所有的传输函数都是 spi_async 和 spi_sync 的封装。

流程分析

spi driver 初始化:

spi_alloc_host();

spi_register_controller();
    spi_controller_initialize_queue();
        ctlr->transfer = spi_queued_transfer;
	ctlr->transfer_one_message = spi_transfer_one_message;
        spi_init_queue();
	    kthread_init_work(&ctlr->pump_messages, spi_pump_messages);
	spi_start_queue();
	    // 进入 spi_pump_messages
	    kthread_queue_work(ctlr->kworker, &ctlr->pump_messages); 

spi_pump_messages();
    // 此时 ctlr->queue 为空,进入 idle 状态返回。

传输函数流程分析:

spi_sync()
    // 这里根据 queue_empty 决定传输方式
    // 如果此时 queue 是 empty 的,那么直接传输
    // 如果此时 queue 中还有消息没有发完,那么将当前消息也放入 queue,并阻塞等待完成
    __spi_transfer_message_noqueue()/spi_async_locked()

// sync 处理
__spi_transfer_message_noqueue()
    __spi_pump_transfer_message()

// async 处理
spi_async_locked()
    ctlr->transfer()

spi_queued_transfer() // ctlr->transfer()
        list_add_tail(&msg->queue, &ctlr->queue)
	kthread_queue_work(ctlr->kworker, &ctlr->pump_messages) // 进入 spi_pump_messages
	// return

spi_pump_messages()
    __spi_pump_messages();
        // 取出第一条 message
        msg = list_first_entry(&ctlr->queue, struct spi_message, queue);
	// 这里开始 sync 和 async 的处理就一样了
        __spi_pump_transfer_message();
static int __spi_pump_transfer_message(struct spi_controller *ctlr,
				       struct spi_message *msg, bool was_busy)
{
	struct spi_transfer *xfer;
	int ret;

	/// 第一次传输 was_busy 为 false,后面都为 true, resume 唤醒
	if (!was_busy && ctlr->auto_runtime_pm)
		pm_runtime_get_sync(ctlr->dev.parent);

	if (!was_busy && ctlr->prepare_transfer_hardware)
		ret = ctlr->prepare_transfer_hardware(ctlr);

	/// 如果硬件对单次 transfer size 有限制,则需要将 transfers 拆分成多个 transfers
	spi_split_transfers_maxsize(ctlr, msg,
					  spi_max_transfer_size(msg->spi),
					  GFP_KERNEL | GFP_DMA);

	if (ctlr->prepare_message) {
		ctlr->prepare_message(ctlr, msg);
		msg->prepared = true;
	}

	spi_map_msg(ctlr, msg);

	WRITE_ONCE(ctlr->cur_msg_incomplete, true);
	WRITE_ONCE(ctlr->cur_msg_need_completion, false);
	reinit_completion(&ctlr->cur_msg_completion);

	ctlr->transfer_one_message(ctlr, msg);

	WRITE_ONCE(ctlr->cur_msg_need_completion, true);
	if (READ_ONCE(ctlr->cur_msg_incomplete))
		wait_for_completion(&ctlr->cur_msg_completion);

	return 0;
}
static int spi_transfer_one_message(struct spi_controller *ctlr,
				    struct spi_message *msg)
{
	struct spi_transfer *xfer;
	bool keep_cs = false;
	int ret = 0;

	xfer = list_first_entry(&msg->transfers, struct spi_transfer,
				transfer_list);
	spi_set_cs(msg->spi, !xfer->cs_off, false);

	list_for_each_entry(xfer, &msg->transfers, transfer_list) {
		if ((xfer->tx_buf || xfer->rx_buf) && xfer->len) {
			reinit_completion(&ctlr->xfer_completion);

fallback_pio:
			spi_dma_sync_for_device(ctlr, xfer);
			ret = ctlr->transfer_one(ctlr, msg->spi, xfer);

			/// 正常 transfer_one return 1, 在 spi_transfer_wait 中等待中断调用 spi_finalize_current_transfer
			if (ret > 0)
				spi_transfer_wait(ctlr, msg, xfer);

			spi_dma_sync_for_cpu(ctlr, xfer);
		}
		// ...
		msg->actual_length += xfer->len;
	}

out:
	if (ret != 0 || !keep_cs)
		spi_set_cs(msg->spi, false, false); // cs 拉低

	spi_finalize_current_message(ctlr);

	return ret;
}