Linux下SPI设备驱动开发

发布于:2025-07-22 ⋅ 阅读:(22) ⋅ 点赞:(0)

一.SPI协议介绍

1.硬件连接介绍

        引脚含义:

        DO(MOSI):Master Output, Slave Input,SPI主控用来发出数据,SPI从设备用来接收数据。

        DI(MISO):Master Input, Slave Output,SPI主控用来发出数据,SPI从设备用来接收数据。

        SCK:Serial Clock,时钟。

        CS:Chip Select,芯片选择引脚。

2.SPI协议

a.传输示例

        设现在主控芯片要传输一个0x56数据给SPI FLASH,时序如下:

        首先我们要先拉低 CS0 选中 SPI Flash,0x56的二进制为 0b0101 0110,因此我们在每个 SCK 时钟周期,DO 输出对应的电平。SPI Flash 会在每个时钟周期的上升沿读取 DO 上的电平。 

b.SPI 模式

        在 SPI 协议中,有两个值来确定 SPI 的模式。CPOL:表示 SPICLK 的初始电平,0 为低电平,1 为高电平。CPHA:表示相位,即第一个还是第二个时钟沿采样数据,0 为第一个时钟沿,1 为第二个时钟沿。

        模式0:CPOL=0,CPHA=0,SPICLK初始电平为低电平,在第一个时钟沿采样数据。

        模式1:CPOL=0,CPHA=1,SPICLK初始电平为低电平,在第二个时钟沿采样数据

        模式2:CPOL=1,CPHA=0,SPICLK初始电平为高电平,在第一个时钟沿采样数据

        模式3:CPOL=1,CPHA=1,SPICLK初始电平为低电平,在第一个时钟沿采样数据。

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

3.特点

a.采用主-从模式(Master-Slave) 的控制方式

        SPI 规定了两个 SPI 设备之间通信必须由主设备 (Master) 来控制次设备 (Slave). 一个 Master 设备可以通过提供 Clock 以及对 Slave 设备进行片选 (Slave Select) 来控制多个 Slave 设备, SPI 协议还规定 Slave 设备的 Clock 由 Master 设备通过 SCK 管脚提供给 Slave 设备, Slave 设备本身不能产生或控制 Clock, 没有 Clock 则 Slave 设备不能正常工作。

c.采用同步方式(Synchronous)传输数据

        Master 设备会根据将要交换的数据来产生相应的时钟脉冲(Clock Pulse), 时钟脉冲组成了时钟信号(Clock Signal) , 时钟信号通过时钟极性 (CPOL) 和 时钟相位 (CPHA) 控制着两个 SPI 设备间何时数据交换以及何时对接收到的数据进行采样, 来保证数据在两个设备之间是同步传输的。

d.数据交换(Data Exchanges)

        SPI 设备间的数据传输之所以又被称为数据交换, 是因为 SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个 “发送者(Transmitter)” 或者 “接收者(Receiver)”. 在每个 Clock 周期内, SPI 设备都会发送并接收一个 bit 大小的数据, 相当于该设备有一个 bit 大小的数据被交换了。

​         一个 Slave 设备要想能够接收到 Master 发过来的控制信号, 必须在此之前能够被 Master 设备进行访问 (Access)。所以, Master 设备必须首先通过 SS/CS pin 对 Slave 设备进行片选, 把想要访问的 Slave 设备选上。

        ​ 在数据传输的过程中, 每次接收到的数据必须在下一次数据传输之前被采样.。如果之前接收到的数据没有被读取, 那么这些已经接收完成的数据将有可能会被丢弃, 导致 SPI 物理模块最终失效。因此, 在程序中一般都会在 SPI 传输完数据后, 去读取 SPI 设备里的数据, 即使这些数据(Dummy Data)在我们的程序里是无用的。

二.SPI总线设备驱动模型

1.SPI主机驱动

        SPI 主机驱动就是 SOC 的 SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动。 Linux 内核使用 spi_master 表示 SPI 主机驱动, spi_master 是个结构体,定义在 include/linux/spi/spi.h 文件
中,部分代码如下:

struct spi_master {
 
    struct device dev;
 
    struct list_head list;
.....
 
    int (*transfer)(struct spi_device *spi,struct spi_message *mesg);
.....
 
    int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg);
}

        上述代码中,transfer 函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函数。transfer_one_message 函数,也用于 SPI 数据发送,用于发送一个 spi_message,SPI 的数据会打包成 spi_message,然后以队列方式发送出去。

        SPI 主机端最终会通过 transfer 函数与 SPI 设备进行通信,因此对于 SPI 主机控制器的驱
动编写者而言 transfer 函数是需要实现的,因为不同的 SOC 其 SPI 控制器不同,寄存器都不一
样。和 I2C 适配器驱动一样,SPI 主机驱动一般都是 SOC 厂商去编写的。

        SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册
spi_master。

        spi_master的申请与释放:

        spi_alloc_master函数用于申请spi_master,函数定义如下所示:

struct spi_master *spi_alloc_master(struct device *dev,
                                        unsigned size)

        dev:设备,一般是 platform_device 中的 dev 成员变量。

        size:私有数据大小,可以通过 spi_master_get_devdata 函数获取到这些私有数据。

         spi_master 的释放通过 spi_master_put 函数来完成,当我们删除一个 SPI 主机驱动的时候就需要释放掉前面申请的 spi_master,spi_master_put 函数原型如下:

void spi_master_put(struct spi_master *master)

        master:要释放的 spi_master。

        spi_master的注册与注销:

        当 spi_master 初始化完成以后就需要将其注册到 Linux 内核,spi_master 注册函数为
spi_register_master,函数原型如下:

int spi_register_master(struct spi_master *master)

        master:要注册的 spi_master。

        如果要注销 spi_master 的话可以使用 spi_unregister_master 函数,此函数原型为:

void spi_unregister_master(struct spi_master *master)

        master:要注销的 spi_master。

2.SPI设备驱动

        spi 设备驱动和 i2c 设备驱动也很类似,Linux 内核使用 spi_driver 结构体来表示 spi 设备驱动 , 我 们 在 编 写 SPI 设 备 驱 动 的 时 候 需 要 实 现 spi_driver 。 spi_driver 结 构 体 定 义 在include/linux/spi/spi.h 文件中,结构体内容如下:

    struct spi_driver {  
        int         (*probe)(struct spi_device *spi);  
        int         (*remove)(struct spi_device *spi);  
        void            (*shutdown)(struct spi_device *spi);  
        int         (*suspend)(struct spi_device *spi, pm_message_t mesg);  
        int         (*resume)(struct spi_device *spi);  
        struct device_driver    driver;  
    }; 

        可以看出,spi_driver 和 i2c_driver、platform_driver 基本一样,当 SPI 设备和驱动匹配成功
以后 probe 函数就会执行。spi_driver 初始化完成以后需要向 Linux 内核注册,spi_driver 注册函数为spi_register_driver,函数原型如下:

int spi_register_driver(struct spi_driver *sdrv)

        sdrv:要注册的 spi_driver。

        注销 SPI 设备驱动以后也需要注销掉前面注册的 spi_driver,使用 spi_unregister_driver 函
数完成 spi_driver 的注销,函数原型如下:

void spi_unregister_driver(struct spi_driver *sdrv)

        sdrv:要注销的 spi_driver。

        driver是为device服务的,spi_driver注册时会扫描SPI bus上的设备,进行驱动和设备的绑定,probe函数用于驱动和设备匹配时被调用。SPI的通信是通过消息队列机制,而不是像I2C那样通过与从设备进行对话的方式。

        spi_device结构体的内容如下:

    struct spi_device {  
        struct device       dev;  
        struct spi_master   *master;  
        u32         max_speed_hz;  
        u8          chip_select;  
        u8          mode;    
        u8          bits_per_word;  
        int         irq;  
        void            *controller_state;  
        void            *controller_data;  
        char            modalias[32];   
    }; 

        spi_driver 注册示例程序如下所示:

/* probe 函数 */
static int xxx_probe(struct spi_device *spi){
	/* 具体函数内容 */
	return 0;
}
/* remove 函数 */
static int xxx_remove(struct spi_device *spi){
	/* 具体函数内容 */
	return 0;
}
/* 传统匹配方式 ID 列表 */
static const struct spi_device_id xxx_id[] = {
	{"xxx", 0},
	{}
};
/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
	{ .compatible = "xxx" },
	{ /* Sentinel */ }
};
/* SPI 驱动结构体 */
static struct spi_driver xxx_driver = {
	.probe = xxx_probe,
	.remove = xxx_remove,
	.driver = {
		.owner = THIS_MODULE,
		.name = "xxx",
		.of_match_table = xxx_of_match,
	},
	.id_table = xxx_id,
};
/* 驱动入口函数 */
static int __init xxx_init(void){
	return spi_register_driver(&xxx_driver);
}
/* 驱动出口函数 */
static void __exit xxx_exit(void){
	spi_unregister_driver(&xxx_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);

3.SPI 设备和驱动匹配过程

        SPI 设备和驱动的匹配过程是由 SPI 总线来完成的,SPI 总线为 spi_bus_type,定义在 drivers/spi/spi.c 文件中,内容如下所示:

struct bus_type spi_bus_type = {
	.name = "spi",
	.dev_groups = spi_dev_groups,
	.match = spi_match_device,
	.uevent = spi_uevent,
};

        SPI 设备和驱动的匹配函数为 spi_match_device,其函数内容如下:

static int spi_match_device(struct device *dev, struct device_driver *drv) {
	const struct spi_device *spi = to_spi_device(dev);
	const struct spi_driver *sdrv = to_spi_driver(drv);

	/* 用于完成设备树设备和驱动匹配 */
	if (of_driver_match_device(dev, drv))
		return 1;
	/* 用于ACPI形式的匹配 */
	if (acpi_driver_match_device(dev, drv))
		return 1;
	/* 用于传统无设备树设备和驱动匹配 */
	if (sdrv->id_table)
		return !!spi_match_id(sdrv->id_table, spi);
	/* 比较modalias成员变量和name成员变量是否相等 */
	return strcmp(spi->modalias, drv->name) == 0;
}

4.SPI 设备数据收发处理

        当向 Linux 内核注册成功 spi_driver 后就可以使用 SPI 核心层提供的 API 函数来对设备进行读写操作了。首先是 spi_transfer 结构体,此结构体用于描述 SPI 传输信息,结构体内容如下:

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

        spi_transfer 需要组织成 spi_message, spi_message 也是一个结构体,内容如下:

struct spi_message {
	struct list_head transfers;	
	struct spi_device *spi;	
	unsigned is_dma_mapped:1;
	......
	/* completion is reported through a callback */
	void (*complete)(void *context);
	void *context;
	unsigned frame_length;
	unsigned actual_length;
	int status;	

	struct list_head queue;
	void *state;
};

        在使用spi_message之前需要对其进行初始化:

void spi_message_init(struct spi_message *m)
//m:要初始化的 spi_message

        spi_message 初始化完成以后需要将 spi_transfer 添加到 spi_message 队列中:

void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
//t:要添加到队列中的 spi_transfer
//m:spi_transfer 要加入的 spi_message

        spi_message 准备好后既可以进行数据传输,数据传输分为同步传输和异步传输:

/***同步传输会阻塞的等待 SPI 数据传输完成***/
int spi_sync(struct spi_device *spi, struct spi_message *message)
//spi:要进行数据传输的 spi_device
//message:要传输的 spi_message
/***异步传输不会阻塞等待,需设置spi_message中的
complete回调函数,当异步传输完成后此函数就会被调用***/
int spi_async(struct spi_device *spi, struct spi_message *message)
//spi:要进行数据传输的 spi_device
//message:要传输的 spi_message

        SPI 数据传输示例代码如下:

/********** SPI 多字节发送 **********/
static int spi_send(struct spi_device *spi, u8 *buf, int len){
	int ret;
	struct spi_message m;
	struct spi_transfer t = {	  //1. 定义一个spi_transfer结构体变量,并设置成员变量
		.tx_buf = buf,
		.len = len,
	};
	spi_message_init(&m); 		  //2. 初始化 spi_message
	spi_message_add_tail(t, &m);  //3. 将 spi_transfer 添加到 spi_message 队列
	ret = spi_sync(spi, &m); 	  //4. 同步传输
	return ret;
}
/********** SPI 多字节接收 **********/
static int spi_receive(struct spi_device *spi, u8 *buf, int len){
	int ret;
	struct spi_message m;
	struct spi_transfer t = {	  //1. 定义一个spi_transfer结构体变量,并设置成员变量
		.rx_buf = buf,
		.len = len,
	};
	spi_message_init(&m); 		  //2. 初始化 spi_message
	spi_message_add_tail(t, &m);  //3. 将 spi_transfer 添加到 spi_message 队列
	ret = spi_sync(spi, &m);	  //4. 同步传输
	return ret;
}

网站公告

今日签到

点亮在社区的每一天
去签到