Linux驱动开发:SPI驱动开发原理

前言

本文章是根据韦东山老师的教学视频整理的学习笔记https://video.100ask.net/page/1712503

SPI 通信协议采用同步全双工传输机制,拓扑架构支持一主多从连接模式,这种模式在实际应用场景中颇为高效。其有效传输距离大致为 10m ,传输速率处于 1 - 70Mbps 区间,能够满足多种场景的速率要求。SPI 采用大端传输方式,电气类型为 TTL 电平标准,在常见数字电路中具有良好的兼容性。

一、SPI硬件框架

SPI主要由四条信号线组成,分别是 SCK、MOSI、MISO 和 SS。

1、串行时钟线(SCK)

  • 功能:SCK 是由主设备产生的时钟信号,用于同步主设备和从设备之间的数据传输。主设备通过控制 SCK 的频率来决定数据传输的速率,每一个时钟脉冲对应一位数据的传输。
  • 工作原理:在时钟信号的上升沿或下降沿,主设备和从设备会进行数据的发送和接收操作。具体是上升沿还是下降沿进行数据操作,取决于 SPI 的工作模式(由时钟极性 CPOL 和时钟相位 CPHA 决定)。

2、主输出从输入线(MOSI)

  • 功能:MOSI用于主设备向从设备发送数据。。当主设备需要向从设备传输数据时,它会将数据通过 MOSI 线逐位发送出去,从设备在相应的时钟信号控制下接收这些数据。
  • 工作原理:在 SCK 的有效沿(上升沿或下降沿),主设备将待发送的数据位放到 MOSI 线上,从设备在同一时刻读取该线上的数据位。这样,随着 SCK 时钟信号的不断变化,主设备就可以将数据依次发送给从设备。

3、主输入从输出线(MISO)

  • 功能:MISO 用于从设备向主设备发送数据。当从设备需要将数据反馈给主设备时,它会将数据通过 MISO 线逐位发送出去,主设备在相应的时钟信号控制下接收这些数据。
  • 工作原理:与 MOSI 类似,在 SCK 的有效沿,从设备将待发送的数据位放到 MISO 线上,主设备在同一时刻读取该线上的数据位。通过这种方式,从设备可以将数据传输给主设备。

4、从设备选择线(SS)

  • 功能:SS 线用于主设备选择要与之通信的从设备。SPI 支持一主多从的拓扑结构,主设备通过拉低特定从设备的 SS 线来选中该从设备,使其进入工作状态,准备进行数据传输。
  • 工作原理:在 SPI 通信中,每个从设备都有一个独立的 SS 线,当主设备需要与某个从设备通信时,将该从设备的 SS 线拉低(通常为低电平有效),其他从设备的 SS 线保持高电平。被选中的从设备会响应主设备的时钟信号和数据传输请求,而未被选中的从设备则处于空闲状态,不参与数据传输。

二、SPI协议

SPI四种模式

时钟极性(CPOL)定义了时钟空闲状态电平

  • CPOL=0,SCK 信号在空闲状态(即没有数据传输时)保持低电平。在这种情况下,SCK 信号的有效沿为上升沿(从低电平到高电平的跳变),数据通常在上升沿被采样。
  • CPOL=1,SCK 信号在空闲状态保持高电平。此时,SCK 信号的有效沿为下降沿(从高电平到低电平的跳变),数据通常在下降沿被采样。

时钟相位(CPHA)定义了数据的采集时间

  • CPHA=0,在SCK的第一个跳变沿(上升沿或下降沿)进行数据采样。
  • CPHA=1,在SCK的第二个跳变沿(上升沿或下降沿)进行数据采样。

表1 SPI四种工作模式

|-------|------|------|----------------------------------------------------------------------------|
| 模式 | CPOL | CPHA | 描述 |
| mode0 | 0 | 0 | |
| mode1 | 0 | 1 | |
| mode2 | 1 | 0 | |
| mode3 | 1 | 1 | |

三、SPI总线设备驱动模型

1、平台总线驱动模型

先来回顾一下平台总线驱动模型。

平台总线驱动模型将驱动程序分成两部分:

一部分注册一个platform_driver结构体;

另一部分注册一个platform_device结构体

  • 可以使用C文件注册platform_device。
  • 也可以使用设备树创建一个节点,内核解析设备树时注册platform_device。

匹配机制概述

在 Linux 内核中,platform_bus_type负责管理平台设备(platform_device)和平台驱动(platform_driver)的匹配过程。

platform_bus_type 定义了一个 match 函数,当有新的平台设备或平台驱动注册到系统时,内核会调用这个 match 函数来判断设备和驱动是否匹配。如果匹配成功,内核会调用驱动的 probe 函数来初始化设备。

platform_match源码如下所示:

cpp 复制代码
static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}

platform_match函数会按照以下顺序来尝试匹配设备和驱动:

  1. 检查设备的driver_override字段;
  2. 尝试使用设备树(of)风格匹配;
  3. 尝试使用ACPI风格匹配;
  4. 尝试依据驱动的id_table进行匹配
  5. 若前面的匹配都失败了,就使用设备和驱动的名称进行匹配。

2、SPI数据结构

前面的硬件框架可以看出,SPI子系统涉及到2类硬件:SPI控制器、SPI设备。

在SPI总线设备驱动模型中,spi_master、spi_device和spi_driver是三个核心的数据结构和概念,它们共同协作实现对SPI设备的管理和驱动。

spi_master结构体

spi_master代表SPI主控制器,它是系统中负责管理SPI总线并与SPI从设备进行通信的硬件实体。在Linux内核中,它抽象魏一个spi_master结构体,用来描述和控制 SPI 主机控制器的各种属性与操作,里面最重要的成员是transfer函数指针。

cpp 复制代码
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 */

	/*
	 * 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;

	/* 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);
	int (*spi_flash_read)(struct  spi_device *spi,
			      struct spi_flash_read_message *msg);
	bool (*flash_read_supported)(struct spi_device *spi);

	/*
	 * 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;

	int (*fw_translate_cs)(struct spi_master *master, unsigned cs);
};

spi_device结构体

spi_device代表一个具体的 SPI 从设备,它连接到 SPI 总线上,是 SPI 通信的目标设备。在 Linux 内核中,它抽象为一个spi_device结构体,用于描述 SPI 从设备的各种属性。

cpp 复制代码
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
	 *  - ...
	 */
};

spi_driver结构体

spi_driver代表 SPI 设备的驱动程序,它实现了对 SPI 从设备的具体操作和功能。在 Linux 内核中,它抽象为一个spi_driver结构体,包含了驱动程序的各种回调函数。

cpp 复制代码
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_master负责管理 SPI 总线,为spi_device提供通信的基础;spi_device代表具体的 SPI 从设备,通过spi_master注册到系统中;spi_driver则负责驱动spi_device,通过与spi_device匹配后,实现对设备的具体操作和数据传输。它们共同构成了 Linux 内核中 SPI 总线设备驱动模型的核心部分。

3、SPI驱动框架

在 Linux 系统中,SPI 控制器通常是通过平台总线驱动模型进行创建和管理的,而 SPI 设备则是基于 SPI 总线驱动模型来创建和操作的。

相关推荐
188号安全攻城狮23 分钟前
【PWN】HappyNewYearCTF_8_ret2csu
linux·汇编·安全·网络安全·系统安全
Yana.nice2 小时前
openssl将证书从p7b转换为crt格式
java·linux
AI逐月2 小时前
tmux 常用命令总结:从入门到稳定使用的一篇实战博客
linux·服务器·ssh·php
小白跃升坊2 小时前
基于1Panel的AI运维
linux·运维·人工智能·ai大模型·教学·ai agent
跃渊Yuey2 小时前
【Linux】线程同步与互斥
linux·笔记
舰长1152 小时前
linux 实现文件共享的实现方式比较
linux·服务器·网络
zmjjdank1ng3 小时前
Linux 输出重定向
linux·运维
路由侠内网穿透.3 小时前
本地部署智能家居集成解决方案 ESPHome 并实现外部访问( Linux 版本)
linux·运维·服务器·网络协议·智能家居
VekiSon3 小时前
Linux内核驱动——基础概念与开发环境搭建
linux·运维·服务器·c语言·arm开发
zl_dfq3 小时前
Linux 之 【进程信号】(signal、kill、raise、abort、alarm、Core Dump核心转储机制)
linux