Linux下SPI设备驱动开发
一.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_messagespi_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_messagespi_message_add_tail(t, &m); //3. 将 spi_transfer 添加到 spi_message 队列ret = spi_sync(spi, &m); //4. 同步传输return ret;
}