SPI子系统

SPI驱动框架

SPI 驱动框架和 I2C 很类似 ,都分为主机控制器驱动和设备驱动。

SPI主机驱动

​ SPI 主机驱动就是 SOC 的 SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动。 Linux 内核
使用结构体spi_master 表示 SPI 主机驱动 。

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

​ 和 I2C 适配器驱动一样, SPI 主机驱动一般都是半导体厂商去编写的。

SPI 设备驱动

​ Linux 内核使用 spi_driver 结构体来表示 spi 设备驱动。 SPI 设备驱动的关键就是 spi_driver ,申请,设置,向内核注册 spi_driver

1
2
3
4
5
6
7
8
9
10
static struct spi_driver chip_spi_driver = {
.probe = chip_spi_probe,
.remove = chip_spi_remove,
.driver = {
.name = "chip",
.owner = THIS_MODULE,
.of_match_table = chip_of_match,
},
.id_table = chip_id,
};

spi_driver 注册

  1. 传统方法

在驱动入口init函数中,调用spi_register_driver 来注册 spi_driver。

在驱动出口exit函数中,调用spi_unregister_driver 来注销 spi_driver

  1. 使用宏定义module_spi_driver 来直接注册spi_driver

这个宏定义将 spi_register/unregister_driver() 与 module_init 和 module_exit 封装了起来。

注册完成,匹配成功就可调用probe函数

SPI device 与 driver 匹配

同样与I2C子系统非常相似,当匹配成功, probe 函数就会被调用。

常用compatible属性进行匹配。

spi_driver设置

spi设备驱动本质上任属于字符设备驱动范畴。

  • 在prob函数中

进行字符设备的注册,设备节点的创建,将file_operations结构体注册进内核,并初始化spi_device

1
2
3
4
static struct spi_device *spi; 

spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0*/
spi_setup(spi);
  • 在remove函数

进行与prob函数中顺序相反的注销。

  • 填充file_operations结构体的open, read, write,release等函数。

注意:platform_device 中如果不提供 release 函数 ,则在调用 platform_device_unregister 时会出现警告,

如果实在无事可做,可以提供一个空的release 函数。

SPI 设备树节点

设备树添加节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
&ecspi3 { 
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>;
cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>; /* 软件片选 */
status = "okay";


spidev: icm20608@0{
compatible = "invensense,icm20608";
interrupt-parent = <&gpio1>;
interrupts = <1 1>;
spi-max-frequency = <8000000>;
reg = <0>;
};
};

pinctrl子系统修改

1
2
3
4
5
6
7
8
9
10
pinctrl_ecspi3: ecspi3 {              
fsl,pins = <
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x000010B0
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x000010B0
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x000010B0
//MX6UL_PAD_UART2_TX_DATA__ECSPI3_SS0 0x000010B0//硬件片选
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x000010B0 //软件片选
>;
};

片选引脚使用软件片选,即使用一个GPIO引脚进行CS引脚模拟。

SPI 通信过程

spi通信步骤

  1. 申请并初始化 spi_transfer,设置 spi_transfer 的 tx_buf 成员变量, tx_buf 为要发送的数
    据。然后设置 rx_buf 成员变量, rx_buf 保存着接收到的数据。最后设置 len 成员变量,也就是
    要进行数据通信的长度。
  2. 使用 spi_message_init 函数初始化 spi_message。
  3. 使用spi_message_add_tail函数将前面设置好的spi_transfer添加到spi_message队列中。
  4. 使用 spi_sync 函数完成 SPI 数据同步传输。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* SPI 多字节发送 */
static int spi_send(struct spi_device *spi, u8 *buf, int len)
{
int ret;
struct spi_message message;
struct spi_transfer transfer = {
.tx_buf = buf,
.len = len,
};

spi_message_init(&message); /* 初始化 spi_message */
spi_message_add_tail(transfer, &message);/* 将 spi_transfer 添加到 spi_message 队列 */
ret = spi_sync(spi, &message); /* 同步传输 */

return ret;
}

/* SPI 多字节接收 */
static int spi_receive(struct spi_device *spi, u8 *buf, int len)
{
int ret;
struct spi_message message;
struct spi_transfer transfer = {
.rx_buf = buf,
.len = len,
};

spi_message_init(&message); /* 初始化 spi_message */
spi_message_add_tail(transfer, &message);/* 将 spi_transfer 添加到 spi_message 队列 */
ret = spi_sync(spi, &message); /* 同步传输 */

return ret;
}

​ SPI 数据传输也支持异步传输,异步传输不会阻塞地等到完成,异步传输需要设置 spi_message 中的 complete成员变量, complete 是一个回调函数,当 SPI 异步传输完成以后此函数就会被调用。

SPI 异步传输函数为 spi_async,函数原型如下:

1
int spi_async(struct spi_device *spi, struct spi_message *message)

spi通信函数封装

参考内核源码 /kernel/driver/spi/spi.c spi.h

实际还是 spi_transfer, spi_message进一步封装

1
2
3
4
5
6
7
8
9
10
11
12
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)