14-SPI协议及驱动讲解

从内核中最简单的驱动程序入手,描述Linux驱动开发,主要文章目录如下(持续更新中):
 01 - 第一个内核模块程序
 02 - 注册字符设备驱动
 03 - open & close 函数的应用
 04 - read & write 函数的应用
 05 - ioctl 的应用
 06 - ioctl LED灯硬件分析
 07 - ioctl 控制LED软件实现(寄存器操作)
 08 - ioctl 控制LED软件实现(库函数操作)
 09 - 注册字符设备的另一种方法(常用)
 10 - 一个cdev实现对多个设备的支持
 11 - 四个cdev控制四个LED设备
 12 - 虚拟串口驱动
 13 - I2C驱动
 14 - SPI协议及驱动讲解
 15 - SPI Linux驱动代码实现
 16 - 非阻塞型I/O
 17 - 阻塞型I/O
 18 - I/O多路复用之 select
 19 - I/O多路复用之 poll
 20 - I/O多路复用之 epoll
 21 - 异步通知

文章目录

  • 1. SPI协议简介
    • 1.1 SPI典型连接
    • 1.2 SPI时序
    • 1.3 SPI的四种工作模式
  • 2. Linux SPI驱动
    • 2.1 spi_device
    • 2.2 设备树
    • 2.3 spi_driver
    • 2.4 SPI驱动注册和注销函数
    • 2.5 驱动中SPI数据的传输
    • 2.6 SPI中的ioctl命令
  • 3 总结

1. SPI协议简介

1.1 SPI典型连接

 SPI是串行外设接口(Serial Peripheral Interface)的缩写,由Motorola公司开发,SPI是同步四线制高速的全双工同步通讯总线,目前速率最高可达50MHz,也属于主从式结构所有的传输都是通过主机发起的,但和I2C总线不一样的是主机上只能有主机控制器,各个从机通过不同的片选线来进行选择,典型的连接如下图所示:
14-SPI协议及驱动讲解_第1张图片
 图中Master是主机,有4个片选信号SS0,SS1,SS2,SS3(上面的横杠表示低电平有效)分别连接四个从机,由片选信号来决定哪个从机被选中,从而与之通信。
 SCLK:串行时钟线,由主机发出。
 MISO(SDI):Master input,Slave output,主入从出,即从机发送数据,主机接收数据。
 MOSI(SDO):Master output,Slave input,主出从入,即主机发送数据,从机接收数据。
 SS:Slave select,从机选择线,由主机发出低电平有效。 一个简单的增加片选的方法是使用GPIO来模拟SPI_CSn信号,在每传输一个数据之前,将相应的GPIO置低(假设从设备片选信号为低有效),选中对应的SPI从设备,传输结束后再将GPIO置高。

1.2 SPI时序

 因为主机通过MOSI发送数据的同时也可以通过MISO接收数据,所以SPI总线是全双工的。所有的数据通过SCLK进行同步,所以它也是同步的总线。SPI典型时序如下图所示:
14-SPI协议及驱动讲解_第2张图片
 图中 CPOL是用来决定SCK时钟信号空闲时的电平,CPOL=0,空闲电平为低电平,CPOL=1时,空闲电平为高电平。
 CPHA是用来决定采样时刻的,CPHA=0,在每个周期的第一个时钟沿采样,CPHA=1,在每个周期的第二个时钟沿采样。

1.3 SPI的四种工作模式

 spi四种模式SPI的相位(CPHA)和极性(CPOL)分别可以为0或1,对应的4种组合构成了SPI的4种模式(mode),其中使用的最为广泛的是SPI0和SPI3方式 。
 Mode0 CPOL=0, CPHA=0 SCLK平时为低电平,在SCLK的上升沿采样数据,下降沿输出数据 ;
 Mode1 CPOL=0, CPHA=1 SCLK平时为低电平,在SCLK的上升沿输出数据,下降沿采样数据;
 Mode2 CPOL=1, CPHA=0 SCLK平时为高电平,在SCLK的上升沿输出数据,下降沿采样数据;
 Mode3 CPOL=1, CPHA=1 SCLK平时为高电平,在SCLK的上升沿采样数据,下降沿输出数据。

 SPI有四种不同的工作模式,不同的从设备可能在出厂时就配置为某种模式,这是不能够改变的,但是通信双方必须工作在同一模式下,所以我们可以对属设备SPI模式进行配置。

2. Linux SPI驱动

2.1 spi_device

 SPI驱动包含主机控制器,SPI Core和SPI设备驱动。SPI主机控制器一般是由SCO芯片厂商来实现的,在实际开发中更多关注的是SPI的设备驱动,SPI的设备表示方法如下:(include\linux\spi\spi.h)

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

主要设备成员如下:

struct spi_master	*master;		// 所连接的SPI主机控制器
u32	max_speed_hz;					// 设备工作的最高频率
u8	chip_select;					// 片选信号
u8	bits_per_word;					// SPI的字长
u16	mode;							// SPI的工作模式

2.2 设备树

 内核中使用设备树来描述设备节点,本文以AM335x的开发版为例,讲解SPI设备的设备树的描写方法。
 首先查看AM3358的原理图和芯片手册,对SPI1的相关引脚设置为SPI功能,其中截取的部门原理图如下图所示:
14-SPI协议及驱动讲解_第3张图片
AM3358原理图和芯片手册下载链接见AM3358芯片手册和AM3358原理图
将相应的引脚设置为SPI功能,设备树中代码如下

&am33xx_pinmux {
    spi1_pins: pinmux_spi1_pins {
         pinctrl-single,pins = <
             AM33XX_IOPAD(0x990, PIN_INPUT_PULLDOWN | MUX_MODE3) /* mcasp0_aclkx.spi1_cslk */    
             AM33XX_IOPAD(0x994, PIN_INPUT_PULLDOWN | MUX_MODE3) /* mcasp0_fsx.spi1_d0     */
             AM33XX_IOPAD(0x998, PIN_INPUT_PULLDOWN | MUX_MODE3) /* mcasp0_axr0_spi1_d1    */
             AM33XX_IOPAD(0x99c, PIN_INPUT_PULLDOWN | MUX_MODE3) /* mcasp0_ahclkr.spi1.cs0 */
         >;
     };
};

 引脚复用功能设定好之后,建立SPI1主控制器的节点,pinctrl指定了主控制器所使用的管脚;spidev0: spidev0@0是子节点,表示在这个SPI主控制器上的设备,compatible用于匹配驱动

&spi1 {
    pinctrl-names = "default";
    pinctrl-0 = <&spi1_pins>;

    status = "okay";
 
    spidev0: spidev0@0{
        spi-max-frequency = <25000000>;
        reg = <0>;
        compatible = "ti,spidev0";
    };
};

2.3 spi_driver

 内核在启动过程中会自动把上面的SPI设备树节点解析为 struct spi_device,当有匹配的驱动时,会调用驱动中的probe函数,在内核中spi_driver结构体定义如下:(include\linux\spi\spi.h)

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;
};

2.4 SPI驱动注册和注销函数

 在驱动文件的最开始,应该首先实现SPI驱动的注册和注销函数,SPI驱动的注册函数定义如下(include\linux\spi\spi.h)

/* use a define to avoid include chaining to get THIS_MODULE */
#define spi_register_driver(driver)  __spi_register_driver(THIS_MODULE, driver)

&emap;SPI驱动的注销函数定义如下(include\linux\spi\spi.h)

static inline void spi_unregister_driver(struct spi_driver *sdrv)
{
	if (sdrv)
		driver_unregister(&sdrv->driver);
}

 除此之外内核中还定义了一个宏来简化SPI驱动注册和注销的函数,定义如下(include\linux\spi\spi.h)

#define module_spi_driver(__spi_driver) \
	module_driver(__spi_driver, spi_register_driver, \
			spi_unregister_driver)

其中module_drive定义在(include\linux\device.h)

#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
	return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
	__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);

 分析上面module_spi_driver宏可以发现这个宏可以同时实现SPI驱动的注册和注销函数,示例如下:

struct spi_driver my_spi_drv;	// 定义SPI驱动结构体
module_spi_driver(my_spi_drv);	// 注册和注销SPI驱动

2.5 驱动中SPI数据的传输

 SPI是全双工的,通常需要一边发送数据一边接收数据,并且发送和接收的数据通常字节数相等。为了描述这一对缓冲区,SPI定义了一个结构体如下:(include\linux\spi\spi.h),主要的变量已在结构体中注释。

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;
};

 struct spi_transfer结构对象构成一个传输事务,多个传输事务构成一条消息,消息中的传输事务以链表的形式组织在一起,消息的结构类型为struct spi_message,SPI消息传输中主要使用的函数接口如下:

static inline void spi_message_init(struct spi_message *m);	// 初始化消息m
static inline void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);	// 将传输事务t加入到消息m中的链表
int spi_sync(struct spi_device *spi, struct spi_message *message);	// 发起SPI设备上的事务传输,同步等待所有事务完成。成功返回0失败返回错误码
static inline void spi_transfer_del(struct spi_transfer *t);	// 从链表中删除事务t

 使用传输事务和消息进行传输的典型代码如下:

int ret;
struct spi_message m;
int rx, tx;
struct spi_transfer t = {
	.tx_buf = &tx,
	.rx_buf = &rx,
	.len = 4;
};
spi_message_init(&m);			// 初始化消息m
spi_message_add_tail(&t, &m);	// 将传输事务t加入到消息m的链表
ret = spi_sync(&my_spidev, &m);	// 发起传输,同步等待所有事件完成
if (ret < 0)
{
	printk("spi_sync failed.\n");
	return -EIO;	// I/O error
}

 内核中也提供了一下简化的SPI数据传输函数,如下:

// 将buf中的数据写len个字节到spi设备。成功返回0,失败返回错误码
static inline int spi_write(struct spi_device * spi, const void * buf, size_t len);

// 从spi设备中读len个字节到buf。成功返回0,失败返回错误码
static unsigned int spi_read(struct spi_device * spi, void * buf, size_t len);

// 将txbuf中的数据写n_tx个字节到spi设备,再从spi设备中读n_rx个字节数据到rxbuf。成功返回0,失败返回错误码
int spi_write_then_read(struct spi_device * spi, const void * txbuf, unsigned n_tx, void * rxbuf, unsigned n_rx);

 查看内核源码可以看出,简化的spi传输函数也采用了传输事务和消息,只是将其封装在简化的函数中,spi_write函数的内核中源码如下:

static inline int spi_write(struct spi_device *spi, const void *buf, size_t len)
{
	struct spi_transfer	t = {
			.tx_buf		= buf,
			.len		= len,
		};

	return spi_sync_transfer(spi, &t, 1);
}

static inline int spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers, unsigned int num_xfers)
{
	struct spi_message msg;

	spi_message_init_with_transfers(&msg, xfers, num_xfers);

	return spi_sync(spi, &msg);
}

static inline void spi_message_init_with_transfers(struct spi_message *m, struct spi_transfer *xfers, unsigned int num_xfers)
{
	unsigned int i;

	spi_message_init(m);
	for (i = 0; i < num_xfers; ++i)
		spi_message_add_tail(&xfers[i], m);
}

2.6 SPI中的ioctl命令

 spi应用层主要使用ioctl对SPI设备进行控制,内核中定义了常用的操作命令如下(include\uapi\linux\spi\spidev.h)

#define SPI_IOC_MAGIC 'k'

/* Read / Write of SPI mode (SPI_MODE_0..SPI_MODE_3) (limited to 8 bits) */
#define SPI_IOC_RD_MODE			_IOR(SPI_IOC_MAGIC, 1, __u8)
#define SPI_IOC_WR_MODE			_IOW(SPI_IOC_MAGIC, 1, __u8)

/* Read / Write SPI bit justification */
#define SPI_IOC_RD_LSB_FIRST		_IOR(SPI_IOC_MAGIC, 2, __u8)
#define SPI_IOC_WR_LSB_FIRST		_IOW(SPI_IOC_MAGIC, 2, __u8)

/* Read / Write SPI device word length (1..N) */
#define SPI_IOC_RD_BITS_PER_WORD	_IOR(SPI_IOC_MAGIC, 3, __u8)
#define SPI_IOC_WR_BITS_PER_WORD	_IOW(SPI_IOC_MAGIC, 3, __u8)

/* Read / Write SPI device default max speed hz */
#define SPI_IOC_RD_MAX_SPEED_HZ		_IOR(SPI_IOC_MAGIC, 4, __u32)
#define SPI_IOC_WR_MAX_SPEED_HZ		_IOW(SPI_IOC_MAGIC, 4, __u32)

/* Read / Write of the SPI mode field */
#define SPI_IOC_RD_MODE32		_IOR(SPI_IOC_MAGIC, 5, __u32)
#define SPI_IOC_WR_MODE32		_IOW(SPI_IOC_MAGIC, 5, __u32)

3 总结

 本节主要对SPI协议和内核中SPI相关的结构体,设备树和操作函数进行分析,具体实现代码将在下一节进行描述,链接如下SPI驱动代码实现

你可能感兴趣的:(Linux驱动,LInux,SPI协议,SPI驱动)