0%

Linux驱动学习日记(26) Linux SPI 驱动

Linux 下的 SPI 驱动框架简介

  SPI 驱动框架和 I2C 很类似,都分为主机控制器驱动和设备驱动,主机控制器也就是 SOC 的 SPI 控制器接口。

SPI 主机驱动

  SPI 主机驱动也就是 SOC 的 SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动。Linux 使用 spi_controller 表示 SPI 主机驱动,spi_controller 是个结构体,定义在 include/linux/spi/spi.h:

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
struct spi_controller {
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 */
u32 mode_bits;

/* spi_device.mode flags override flags for this controller */
u32 buswidth_override_bits;

/* Bitmask of supported bits_per_word for transfers */
u32 bits_per_word_mask;
······

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

  1、spi_master 申请与释放
  spi_alloc_master 函数用于申请 spi_master,函数原型如下:

1
static inline struct spi_controller *spi_alloc_master(struct device *host, unsigned int size)

  函数参数和返回值含义如下
  dev:设备,一般是 platform_device 中的 dev 成员变量。
  size:私有数据大小,可以通过 spi_maser_get_devdata 函数获取到这些私有数据。
  返回值:申请到的 spi_controller。

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

1
static inline void spi_controller_put(struct spi_controller *ctlr)

  其中,ctrl 就是要释放的 spi_master。

  2、spi_master 的注册与注销
  当 spi_master 初始化完成以后就要将其注册到 Linux 内核,spi_master 注册函数为 spi_register_controller,函数原型如下:

1
int spi_register_controller(struct spi_controller *ctlr);

  其中 ctlr 表示要注册的 spi_controller,返回值为 0 时成功,为负值时失败。
  如果要注销的话可以使用 spi_unregister_controller 函数,此函数原型如下

1
void spi_unregister_controller(struct spi_controller *ctlr);

  其中,ctlr 就是要注销的 spi_controller。

SPI 设备驱动

  spi 设备驱动和 i2c 设备驱动也很类似,Linux 内核使用 spi_driver 来表示 spi 设备驱动,其原型如下:

1
2
3
4
5
6
7
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
void (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
struct device_driver driver;
};

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

1
2
#define spi_register_driver(driver) \
__spi_register_driver(THIS_MODULE, driver)

  driver 就表示要注册的 spi_driver,返回值为 0 表示注册成功,为负值表示失败。
  注销 SPI 设备驱动以后也需要注销掉前面注册的 spi_driver,使用 spi_unregister_driver 函数完成 spi_driver 的注销,函数原型如下:

1
void spi_unregister_driver(struct spi_driver *sdrv)

  其中,sdrv 就表示需要注销的 spi_driver。

SPI 设备驱动编写流程

SPI 设备信息概述

  1、IO 的 pinctrl 子节点的创建与修改
  首先肯定是根据所使用的 IO 来创建和修改 pinctrl 子节点,一定要注意检查相应的 IO 有没有被其他设备所占用!!!

  2、SPI 设备节点的创建与修改
  采用设备树的情况下,SPI 设备信息描述就通过创建相应的设备子节点来完成,我们可以打开 imx6ull-14x14-evk.dtsi 文件,找到 spi 相关的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
spi-4 {
compatible = "spi-gpio";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_spi4>;
status = "okay";
gpio-sck = <&gpio5 11 0>; // 配置时钟线
gpio-mosi = <&gpio5 10 0>; // 配置数据线(主到从)
cs-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>; // 配置片选线,设置为低有效
num-chipselects = <1>; // 配置设备数量,表示只有 1 个设备
#address-cells = <1>;
#size-cells = <0>;

gpio_spi: gpio@0 { // 表示通道 0
compatible = "fairchild,74hc595"; // 设备类型
gpio-controller;
#gpio-cells = <2>;
reg = <0>;
registers-number = <1>;
spi-max-frequency = <100000>; // spi最大频率
enable-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>; // 使能控制引脚,低有效
};
};

SPI 设备数据收发处理流程

  SPI 设备驱动的核心时 spi_driver,这个我们之前学习过了。当我们向Linux内核注册成功以后就可以使用 SPI 核心层提供的 API 函数来对设备进行读写操作了。首先是 spi_transfer 结构体,此结构体用于描述 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
34
35
36
37
38
39
40
41
42
43
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 dummy_data:1;
unsigned cs_off:1;
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;
struct spi_delay delay;
struct spi_delay cs_change_delay;
struct spi_delay word_delay;
u32 speed_hz;

u32 effective_speed_hz;

unsigned int ptp_sts_word_pre;
unsigned int ptp_sts_word_post;

struct ptp_system_timestamp *ptp_sts;

bool timestamped;

struct list_head transfer_list;

#define SPI_TRANS_FAIL_NO_START BIT(0)
u16 error;
};

  其中,tx_buf 保存着要发送的数据,rx_buf 保存接收到的数据。len 是要进行传输的数据长度,由于 SPI 是全双工通信,因此在一次通信中接收的字节数都是一样的。
  spi_transfer 需要组织成 spi_message,spi_message 也是一个结构体,内容如下:

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
34
35
36
37
38
struct spi_message {
struct list_head transfers;

struct spi_device *spi;

unsigned is_dma_mapped:1;

/* REVISIT: we might want a flag affecting the behavior of the
* last transfer ... allowing things like "read 16 bit length L"
* immediately followed by "read L bytes". Basically imposing
* a specific message scheduling algorithm.
*
* Some controller drivers (message-at-a-time queue processing)
* could provide that as their default scheduling algorithm. But
* others (with multi-message pipelines) could need a flag to
* tell them about such special cases.
*/

/* Completion is reported through a callback */
void (*complete)(void *context);
void *context;
unsigned frame_length;
unsigned actual_length;
int status;

/* For optional use by whatever driver currently owns the
* spi_message ... between calls to spi_async and then later
* complete(), that's the spi_controller controller driver.
*/
struct list_head queue;
void *state;

/* List of spi_res reources when the spi message is processed */
struct list_head resources;

/* spi_prepare_message() was called for this message */
bool prepared;
};

  在使用 spi_message 之前需要对其进行初始化,spi_message 初始化函数为 spi_message_init,函数原型如下:

1
void spi_message_init(struct spi_message *m)

  其中,m 就表示要初始化的 spi_message。
  spi_message 准备好以后就可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待 SPI 数据传输完成,同步传输函数为 spi_sync,函数原型如下:

1
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)

  其中,t 表示要添加到队列中的 spi_transfer,m 表示要加入的 spi_message。

  spi_message 准备好以后就可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待 SPI 数据传输完成,同步传输函数为 spi_sync,函数原型如下:

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

  其中,spi 就是要进行数据传输的 spi_device,message 表示要传输的 spi_message。

  异步传输不会阻塞的等待 SPI 数据传输完成,异步传输需要设置 spi_message 中的 complete 成员变量,其是一个回调函数,当 SPI 异步传输完成以后此函数就会被调用。SPI 异步传输函数为 spi_async,函数原型如下:

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

  其中,spi 就是要进行数据传输的 spi_device,message 就是要传输的 spi_message。

  综上所述,SPI 数据传输步骤如下:
  ①、申请并初始化 spi_transfer,设置 spi_transfer 的 tx_buf 成员变量,然后设置 rx_buf 成员变量,最后设置 len 成员变量。
  ②、使用 spi_message_init 函数初始化 spi_message。
  ③、使用 spi_message_add_tail 函数将前面设置好的 spi_transfer 添加到 spi_message 队列中。
  ④、使用 spi_sync 函数完成 SPI 数据同步传输。

硬件分析

  我们使用 SPI 通信的 W25Q64 完成本次学习。查询原理图,得知可以将以下几个引脚复用为 SPI 功能。

图 1 spi原理图

代码编写

修改设备树

  打开 imx6ull-hcw-emmc.dtsi 文件,在 iomuxc 节点中添加一个新的子节点来描述 W25Q64 所使用的 SPI 引脚。内容如下:

1
2
3
4
5
6
7
8
pinctrl_ecspi3: w25q64{
fsl,pins = <
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1
>;
};

  接下来,我们在设备树文件中添加以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
&ecspi3 {
/delete-property/ dmas;
/delete-property/ dma-names;
fsl,spi-num-chipselects = <1>;
cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>;
status = "okay";

w25q64@0 {
compatible = "hcw,w25q64";
reg = <0>;
spi-max-frequency = <1000000>;
};
};

  检查引脚是否冲突,将冲突的设备设为 disabled,编译设备树,并使用新的 dtb 文件启动 Linux 内核。

编写代码

  新建文件,写入以下代码。
  1、设备结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define     W25Q64_CNT      1
#define W25Q64_NAME "w25q64"

struct w25q64_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int major;
void *private_data;
int cs_gpio;
};
static struct w25q64_dev w25q64dev;

  2、w25q64 spi 设备的注册与注销
  对于 spi 设备驱动,首先就是要初始化并向系统注册 spi_driver,w25q64 的 spi_driver 初始化、注册、注销代码如下:

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
static const struct of_device_id w25q64_of_match[] = {
{.compatible = "hcw,w25q64"},
{}
};

static struct spi_driver w25q64_drv = {
.driver = {
.name = "w25q64_drv",
.owner = THIS_MODULE,
.of_match_table = w25q64_of_match,
}
};

static int __init w25q64_init(void){
return spi_register_driver(&w25q64_drv);
}

static void __exit w25q64_exit(void){
spi_unregister_driver(&w25q64_drv);
}

module_init(w25q64_init);
module_exit(w25q64_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hcw");

  3、probe&remove 函数

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
34
35
36
37
38
39
40
static int w25q64_probe(struct spi_device *dev)
{
if(w25q64dev.major){
w25q64dev.devid = MKDEV(w25q64dev.major, 0);
register_chrdev_region(w25q64dev.devid, W25Q64_CNT, W25Q64_NAME);
}
else{
alloc_chrdev_region(&w25q64dev.devid, 0, W25Q64_CNT, W25Q64_NAME);
w25q64dev.major = MAJOR(w25q64dev.devid);
}

cdev_init(&w25q64dev.cdev, &w25q64_fops);
cdev_add(&w25q64dev.cdev, w25q64dev.devid, W25Q64_CNT);

w25q64dev.class = class_create(THIS_MODULE, W25Q64_NAME);
if(IS_ERR(w25q64dev.class))
{
return PTR_ERR(w25q64dev.class);
}

w25q64dev.device = device_create(w25q64dev.class, NULL, w25q64dev.devid, NULL, W25Q64_NAME);
if(IS_ERR(w25q64dev.device))
{
return PTR_ERR(w25q64dev.device);
}

dev->mode = SPI_MODE_0;
spi_setup(dev);
w25q64dev.private_data = dev;

return 0;
}

static void w25q64_remove(struct spi_device *dev)
{
cdev_del(&w25q64dev.cdev);
unregister_chrdev_region(w25q64dev.devid, W25Q64_CNT);
device_destroy(w25q64dev.class, w25q64dev.devid);
class_destroy(w25q64dev.class);
}

  4、w25q64 读取 ID 函数
  SPI 驱动最终是通过读写 w25q64 寄存器来实现的,因此需要编写对应的寄存器读写函数。

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
34
35
36
37
38
39
static int w25q64_readID(struct w25q64_dev *dev, void *buf)
{
int ret = -1;
unsigned char txdata[1];
unsigned char *rxdata;
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = (struct spi_device *)dev->private_data;

t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
if(!t)
{
return -ENOMEM;
}

rxdata = kzalloc(sizeof(char) * 4, GFP_KERNEL);
if(!rxdata)
goto out1;

txdata[0] = 0x90;
t->tx_buf = txdata;
t->rx_buf = rxdata;
t->len = 4;
spi_message_init(&m);
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);

if(ret)
goto out2;

memcpy(buf, rxdata + 1, 3); // 从 rxdata 的首地址往后偏移 1 个字节开始拷贝,用来过滤掉第一个无效数据

out2:
kfree(rxdata);
out1:
kfree(t);

return ret;
}

修复错误

  写好后,编译模块装载,运行测试程序,但是并没有如预想中的一样输出设备 ID。是怎么回事呢?我们查阅 STM32 平台下的标准代码可以得知,我们应该依次发送 90 00 00 00 ff ff。最终代码放在最后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
uint16_t w25qxx_read_id(void)
{
uint16_t id = 0;
//片选有效
SPI_CS = 0;

//发送0x90,读取厂商ID和设备ID
spi_read_writeByte(0x90);

//发送24位地址(3个字节) 前面两个字节可以任意,第三个字节必须是0x00
spi_read_writeByte(0x00);
spi_read_writeByte(0x00);
spi_read_writeByte(0x00);//一定是0x00


//随便发2个字节的数据
id |= spi_read_writeByte(0xFF)<<8; //id:0xEF17 厂商ID:0xEF
id |= spi_read_writeByte(0xFF); //设备ID:0x17

//片选无效
SPI_CS = 1;

return id;
}
图 2 读到ID
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/*
* @Author: 胡城玮
* @FilePath: w25q64.c
* @Date: 2026-03-22
* @Description:
* @Version: 0.1
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define W25Q64_CNT 1
#define W25Q64_NAME "w25q64"

struct w25q64_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int major;
void *private_data;
int cs_gpio;
};

static struct w25q64_dev w25q64dev;

static const struct of_device_id w25q64_of_match[] = {
{.compatible = "hcw,w25q64"},
{}
};

static const struct spi_device_id w25q64_id[] = {
{ "w25q64", 0 },
{ }
};

MODULE_DEVICE_TABLE(of, w25q64_of_match);

static int w25q64_readID(struct w25q64_dev *dev, void *buf)
{
int ret = -1;
unsigned char txdata[6] = {0x90, 0x00, 0x00, 0x00,0xff,0xff};
unsigned char *rxdata;
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = (struct spi_device *)dev->private_data;

t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
if(!t)
{
return -ENOMEM;
}

rxdata = kzalloc(sizeof(char) * 6, GFP_KERNEL);
if(!rxdata)
goto out1;

t->tx_buf = txdata;
t->rx_buf = rxdata;
t->len = 6;
spi_message_init(&m);
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);

if(ret)
goto out2;

memcpy(buf, rxdata + 4, 2); // 从 rxdata 的首地址往后偏移 4 个字节开始拷贝,用来过滤掉4个无效数据

out2:
kfree(rxdata);
out1:
kfree(t);

return ret;
}

static int w25q64_open(struct inode *nd, struct file *filp)
{
filp->private_data = &w25q64dev;
return 0;
}

static ssize_t w25q64_read(struct file *filp, char __user *buf, size_t cnt, loff_t *oft)
{
int ret = 0;
uint8_t data[3];
ret = w25q64_readID(&w25q64dev, data);
if(ret)
return ret;
printk("read id = %02x %02x %02x\n", data[0], data[1], data[2]);
int err = copy_to_user(buf, data, 3);

if(err)
{
return -EFAULT;
}
return 3;
}

static int w25q64_release(struct inode *inode, struct file *filp)
{
return 0;
}

static struct file_operations w25q64_fops = {
.owner = THIS_MODULE,
.read = w25q64_read,
.open = w25q64_open,
.release = w25q64_release
};



static int w25q64_probe(struct spi_device *dev)
{

printk("matched!!!\r\n");

if(w25q64dev.major){
w25q64dev.devid = MKDEV(w25q64dev.major, 0);
register_chrdev_region(w25q64dev.devid, W25Q64_CNT, W25Q64_NAME);
}
else{
alloc_chrdev_region(&w25q64dev.devid, 0, W25Q64_CNT, W25Q64_NAME);
w25q64dev.major = MAJOR(w25q64dev.devid);
}

cdev_init(&w25q64dev.cdev, &w25q64_fops);
cdev_add(&w25q64dev.cdev, w25q64dev.devid, W25Q64_CNT);

w25q64dev.class = class_create(THIS_MODULE, W25Q64_NAME);
if(IS_ERR(w25q64dev.class))
{
return PTR_ERR(w25q64dev.class);
}

w25q64dev.device = device_create(w25q64dev.class, NULL, w25q64dev.devid, NULL, W25Q64_NAME);
if(IS_ERR(w25q64dev.device))
{
return PTR_ERR(w25q64dev.device);
}

dev->mode = SPI_MODE_0;
spi_setup(dev);
w25q64dev.private_data = dev;

return 0;
}

static void w25q64_remove(struct spi_device *dev)
{
cdev_del(&w25q64dev.cdev);
unregister_chrdev_region(w25q64dev.devid, W25Q64_CNT);
device_destroy(w25q64dev.class, w25q64dev.devid);
class_destroy(w25q64dev.class);
}

static struct spi_driver w25q64_drv = {
.driver = {
.name = "w25q64_drv",
.owner = THIS_MODULE,
.of_match_table = w25q64_of_match,
},
.probe = w25q64_probe,
.remove = w25q64_remove,
.id_table = w25q64_id,
};

static int __init w25q64_init(void){
return spi_register_driver(&w25q64_drv);
}

static void __exit w25q64_exit(void){
spi_unregister_driver(&w25q64_drv);
}

module_init(w25q64_init);
module_exit(w25q64_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hcw");