行情
HOME
行情
正文内容
利用spi写入nand 宏旺半导体解答什么是SPI Flash 运用于哪些领域
发布时间 : 2024-10-06
作者 : 小编
访问数量 : 23
扫码分享至微信

宏旺半导体解答什么是SPI Flash 运用于哪些领域

有关注宏旺半导体的小伙伴都知道,宏旺半导体是一家专注于存储芯片Design、研发、封装、测试、销售服务于一体的高科技企业。在之前宏旺半导体给大家带来了很多关于存储方面的文章,主要介绍了eMMC、UFS等Flash,但很少提到SPI闪存,作为闪存家族的重要一员,今天就和大家聊聊ICMAX SPI。

什么是SPI Flash

SPI Flash是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,一般智能电视、老式机顶盒、智能后视镜、投影仪、物联网、监控摄像机等产品会用到。由于体积小,它可以减少ASIC控制器引脚数,降低封装成本,缩小电路板空间,并降低系统成本。与并行闪存相比,SPI串行闪存功耗更低、连线更少,它是一种理想的高成本效益的数据传输解决方案。

如果采用SPI NAND Flash的方案,主控(MCU)内可以不需要带有传统NAND的控制器,只需要有SPI的接口,这样可以减少主控的成本。另外SPI NAND Flash的封装形式多采用 WSON的封装,尺寸比传统的NAND Flash TSOP的封装要小很多,充分节省了PCB板的空间,已经管脚的数量,从而可以减小PCB的尺寸及层数,既满足了小型化的需求也降低了产品的成本。

随着5G网络即将带来的数据狂潮,万物互联对存储芯片有了更高的需求,目前SPI SLC NAND在PON、网通模块、监控等领域也逐步普及。SPI NAND有更快的写入速度,且对于频繁擦写有着更高的稳定性。从上面我们了解到了 SPI NAND Flash的这么多优点,那么他的性能相比于传统的NAND Flash是否有打折扣呢?

我们以国产本土品牌ICMAX的2Gb SPI NAND Flash 型号为IMS2G083J1H2S-WN 为例来进行说明。SPI NAND Flash本质上是一个NAND Flash+ SPI controller,ICMAX为了提供给客户稳定可靠的ICMAX Flash,保证用户的程序代码的一致性,将NAND Flash和 SPI controller做了独特别设计,从而保证了所有ICMAX出厂的SPI NAND Flash都拥有一致的controller,客户在对于SPI 编程时也不会出现因为不同的controller导致驱动程序需要调整的问题。

另外ICMAX拥有完全自主的NAND Flash设计能力,在SPI NAND Flash中采用的 NAND 晶圆均为SLC规格的,擦写次数达到10万次,存储时间高达10年以上,硬件的ECC校验,更好的帮助客户管理好Flash。宏旺半导体ICMAX的SPI NAND具备体积小、集成度高等优势,从严格的封装、测试、认证等一步到位,大大缩短生产周期,为客户提供高性能到高性价比的解决方案,帮助客户抢占市场先机。

【驱动】SPI驱动分析(七)-SPI驱动常用调试方法

## 用户态

用户应用层使用spidev驱动的步骤如下:

1. 打开SPI设备文件:用户可以通过打开`/dev/spidevX.Y`文件来访问SPI设备,其中X是SPI控制器的编号,Y是SPI设备的编号。

2. 配置SPI参数:用户可以使用ioctl命令`SPI_IOC_WR_MODE`、`SPI_IOC_WR_BITS_PER_WORD`和`SPI_IOC_WR_MAX_SPEED_HZ`来设置SPI模式、数据位数和时钟速度等参数。

3. 发送和接收数据:用户可以使用read和write系统调用来发送和接收SPI数据。写入的数据将被传输到SPI设备,而从设备读取的数据将被存储在用户提供的缓冲区中。

4. 关闭SPI设备文件:当不再需要与SPI设备通信时,用户应该关闭SPI设备文件。

总结起来,spidev驱动提供了一种简单而灵活的方式来与SPI设备进行通信,使得用户可以轻松地在Linux系统上开发和控制SPI设备。

`spidev`驱动有现成的测试工具。其中一个常用的测试工具是`spi_test`,它是`spidev`驱动自带的测试工具,可以用于测试和调试SPI设备。`spi_test`可以通过命令行参数设置SPI设备的各种参数,如设备文件、传输速率、字节顺序等。使用spi_test可以发送和接收SPI数据,以验证spidev驱动的功能和性能。

在源码`Documentation\spi`路径下,有两个测试工具的源码文件,`spidev_fdx.c`和`spidev_test.c`文件。可以直接交叉编译为可执行文件使用。这些工具都基于`spidev`通用设备驱动以及对应的ioctl命令实现,可以方便的用来对spi的通用型驱动来进行测试。

`parse_opts`这段代码通过解析命令行选项,并根据选项的值设置相应的变量,实现了对命令行参数的解析和处理。

```c

static void parse_opts(int argc, char *argv[])

{

while (1) {

static const struct option lopts[] = {

{ "device", 1, 0, 'D' },

{ "speed", 1, 0, 's' },

{ "delay", 1, 0, 'd' },

{ "bpw", 1, 0, 'b' },

{ "loop", 0, 0, 'l' },

{ "cpha", 0, 0, 'H' },

{ "cpol", 0, 0, 'O' },

{ "lsb", 0, 0, 'L' },

{ "cs-high", 0, 0, 'C' },

{ "3wire", 0, 0, '3' },

{ "no-cs", 0, 0, 'N' },

{ "ready", 0, 0, 'R' },

{ "dual", 0, 0, '2' },

{ "verbose", 0, 0, 'v' },

{ "quad", 0, 0, '4' },

{ NULL, 0, 0, 0 },

};

int c;

c = getopt_long(argc, argv, "D:s:d:b:lHOLC3NR24p:v", lopts, NULL);

if (c == -1)

break;

switch (c) {

case 'D':

device = optarg;

break;

case 's':

speed = atoi(optarg);

break;

case 'd':

delay = atoi(optarg);

break;

case 'b':

bits = atoi(optarg);

break;

case 'l':

mode |= SPI_LOOP;

break;

case 'H':

mode |= SPI_CPHA;

break;

case 'O':

mode |= SPI_CPOL;

break;

case 'L':

mode |= SPI_LSB_FIRST;

break;

case 'C':

mode |= SPI_CS_HIGH;

break;

case '3':

mode |= SPI_3WIRE;

break;

case 'N':

mode |= SPI_NO_CS;

break;

case 'v':

verbose = 1;

break;

case 'R':

mode |= SPI_READY;

break;

case 'p':

input_tx = optarg;

break;

case '2':

mode |= SPI_TX_DUAL;

break;

case '4':

mode |= SPI_TX_QUAD;

break;

default:

print_usage(argv[0]);

break;

}

}

if (mode & SPI_LOOP) {

if (mode & SPI_TX_DUAL)

mode |= SPI_RX_DUAL;

if (mode & SPI_TX_QUAD)

mode |= SPI_RX_QUAD;

}

}

```

1. 声明一个静态的选项数组`lopts`,用于定义可接受的命令行选项。

2. 在循环内部,调用`getopt_long`函数来解析下一个选项。`getopt_long`函数会返回选项的短选项字符(c),如果没有更多选项则返回-1。

3. 使用switch语句根据选项的短选项字符进行分支处理。

4. 根据不同的选项,执行相应的操作。例如,对于选项'D',将其参数值赋给`device`变量;对于选项's',将其参数值转换为整数并赋给`speed`变量。

5. 如果遇到未知的选项,调用`print_usage`函数打印用法信息。

6. 循环结束后,根据设置的选项进行一些额外的逻辑处理。例如,如果设置了`SPI_LOOP`选项,则根据是否设置了`SPI_TX_DUAL`和`SPI_TX_QUAD`选项,设置相应的`SPI_RX_DUAL`和`SPI_RX_QUAD`选项。

`print_usage`打印spi_test的 使用方法。

```c

static void print_usage(const char *prog)

{

printf("Usage: %s [-DsbdlHOLC3]\n", prog);

puts(" -D --device device to use (default /dev/spidev1.1)\n"

" -s --speed max speed (Hz)\n"

" -d --delay delay (usec)\n"

" -b --bpw bits per word \n"

" -l --loop loopback\n"

" -H --cpha clock phase\n"

" -O --cpol clock polarity\n"

" -L --lsb least significant bit first\n"

" -C --cs-high chip select active high\n"

" -3 --3wire SI/SO signals shared\n"

" -v --verbose Verbose (show tx buffer)\n"

" -p Send data (e.g. \"1234\\xde\\xad\")\n"

" -N --no-cs no chip select\n"

" -R --ready slave pulls low to pause\n"

" -2 --dual dual transfer\n"

" -4 --quad quad transfer\n");

exit(1);

}

```

- `-D, --device <device>`:设置要使用的SPI设备,默认为`/dev/spidev1.0`。

- `-s, --speed <speed>`:设置SPI时钟速度,单位为Hz。

- `-d, --delay <delay>`:设置SPI传输之间的延迟时间,单位为微秒。

- `-b, --bits <bits>`:设置每个字的位数。

- `-l, --loop`:启用回环模式,将接收到的数据回送给发送方。

- `-H, --cpha`:将时钟相位设置为第二个边沿。

- `-O, --cpol`:将时钟极性设置为低电平活动。

- `-L, --lsb`:设置最低有效位(LSB)为先传输。

- `-C, --cs-high`:设置片选信号为高电平有效。

- `-3, --3wire`:设置3线SPI模式(共享SI/SO信号)。

- `-N, --no-cs`:禁用片选信号。

- `-v, --verbose`:启用详细输出模式,显示传输缓冲区的内容。

- `-t, --transfer <data>`:执行一个SPI传输,发送给定的数据字节。

- `-r, --read <count>`:执行一个SPI读传输,读取指定数量的字节。

- `-w, --write <data>`:执行一个SPI写传输,发送给定的数据字节。

- `-f, --file <file>`:从文件中读取数据并执行SPI传输。

- `-h, --help`:显示帮助信息。

`transfer`通过`ioctl`系统调用执行SPI数据传输操作。根据传入的参数和全局变量的设置,配置SPI传输的参数,并将发送和接收的数据进行打印。

```c

static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)

{

int ret;

struct spi_ioc_transfer tr = {

.tx_buf = (unsigned long)tx,

.rx_buf = (unsigned long)rx,

.len = len,

.delay_usecs = delay,

.speed_hz = speed,

.bits_per_word = bits,

};

if (mode & SPI_TX_QUAD)

tr.tx_nbits = 4;

else if (mode & SPI_TX_DUAL)

tr.tx_nbits = 2;

if (mode & SPI_RX_QUAD)

tr.rx_nbits = 4;

else if (mode & SPI_RX_DUAL)

tr.rx_nbits = 2;

if (!(mode & SPI_LOOP)) {

if (mode & (SPI_TX_QUAD | SPI_TX_DUAL))

tr.rx_buf = 0;

else if (mode & (SPI_RX_QUAD | SPI_RX_DUAL))

tr.tx_buf = 0;

}

ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);

if (ret < 1)

pabort("can't send spi message");

if (verbose)

hex_dump(tx, len, 32, "TX");

hex_dump(rx, len, 32, "RX");

}

```

1.

2. 声明一个`spi_ioc_transfer`结构体变量`tr`,用于设置SPI传输的参数。

3. 在`spi_ioc_transfer`结构体中设置以下字段:

- `tx_buf`:指向发送数据缓冲区的指针。

- `rx_buf`:指向接收数据缓冲区的指针。

- `len`:要传输的数据长度。

- `delay_usecs`:传输之间的延迟时间(以微秒为单位)。

- `speed_hz`:SPI时钟速度(以赫兹为单位)。

- `bits_per_word`:每个字的位数。

4. 根据变量`mode`的值设置`tr`结构体中的`tx_nbits`和`rx_nbits`字段。如果`mode`中包含`SPI_TX_QUAD`标志,则将`tx_nbits`设置为4;如果`mode`中包含`SPI_TX_DUAL`标志,则将`tx_nbits`设置为2。类似地,如果`mode`中包含`SPI_RX_QUAD`标志,则将`rx_nbits`设置为4;如果`mode`中包含`SPI_RX_DUAL`标志,则将`rx_nbits`设置为2。

5. 如果`mode`中不包含`SPI_LOOP`标志,则根据`mode`中的其他标志设置`tr`结构体中的`tx_buf`和`rx_buf`字段。如果`mode`中包含`SPI_TX_QUAD`或`SPI_TX_DUAL`标志,则将`rx_buf`设置为0,表示在非回环模式下不接收数据。类似地,如果`mode`中包含`SPI_RX_QUAD`或`SPI_RX_DUAL`标志,则将`tx_buf`设置为0,表示在非回环模式下不发送数据。

6. 使用`ioctl`系统调用发送SPI消息并执行SPI数据传输操作。`SPI_IOC_MESSAGE(1)`表示发送单个SPI消息。

7. 检查`ioctl`的返回值`ret`,如果小于1,则表示SPI消息发送失败,调用`pabort`函数打印错误消息并终止程序。

8. 如果`verbose`标志为真,则使用`hex_dump`函数打印发送和接收数据的十六进制表示。

这段代码用于将输入字符串中的转义序列`\x`转换为对应的字符,并将结果存储在目标字符串中。它通过遍历输入字符串的字符,并根据转义序列的位置和格式进行解析和转换。

```c

static int unescape(char *_dst, char *_src, size_t len)

{

int ret = 0;

char *src = _src;

char *dst = _dst;

unsigned int ch;

while (*src) {

if (*src == '\\' && *(src+1) == 'x') {

sscanf(src + 2, "%2x", &ch);

src += 4;

*dst++ = (unsigned char)ch;

} else {

*dst++ = *src++;

}

ret++;

}

return ret;

}

```

`main`函数通过设置SPI设备的参数并执行数据传输操作与SPI设备进行通信。具体的数据传输操作在`transfer`函数中实现。

```c

int main(int argc, char *argv[])

{

int ret = 0;

int fd;

uint8_t *tx;

uint8_t *rx;

int size;

parse_opts(argc, argv);

fd = open(device, O_RDWR);

if (fd < 0)

pabort("can't open device");

/*

* spi mode

*/

ret = ioctl(fd, SPI_IOC_WR_MODE32, &mode);

if (ret == -1)

pabort("can't set spi mode");

ret = ioctl(fd, SPI_IOC_RD_MODE32, &mode);

if (ret == -1)

pabort("can't get spi mode");

/*

* bits per word

*/

ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);

if (ret == -1)

pabort("can't set bits per word");

ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);

if (ret == -1)

pabort("can't get bits per word");

/*

* max speed hz

*/

ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);

if (ret == -1)

pabort("can't set max speed hz");

ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);

if (ret == -1)

pabort("can't get max speed hz");

printf("spi mode: 0x%x\n", mode);

printf("bits per word: %d\n", bits);

printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);

if (input_tx) {

size = strlen(input_tx+1);

tx = malloc(size);

rx = malloc(size);

size = unescape((char *)tx, input_tx, size);

transfer(fd, tx, rx, size);

free(rx);

free(tx);

} else {

transfer(fd, default_tx, default_rx, sizeof(default_tx));

}

close(fd);

return ret;

}

```

1. 调用`parse_opts`函数,解析命令行参数并设置全局变量。

2. 使用`open`函数打开SPI设备,以可读写方式打开。如果返回值小于0,则打印错误消息并终止程序。

3. 使用`ioctl`系统调用设置SPI设备的模式(`SPI_IOC_WR_MODE32`和`SPI_IOC_RD_MODE32`)、每字位数(`SPI_IOC_WR_BITS_PER_WORD`和`SPI_IOC_RD_BITS_PER_WORD`)以及最大时钟速度(`SPI_IOC_WR_MAX_SPEED_HZ`和`SPI_IOC_RD_MAX_SPEED_HZ`)。如果返回值为-1,则打印错误消息并终止程序。

4. 使用`printf`函数打印设置的SPI设备参数:模式、每字位数和最大时钟速度。

5. 如果`input_tx`不为NULL,则表示存在输入的发送数据。

- 计算输入发送数据的大小(排除末尾的`\0`)。

- 分配相应大小的内存给发送和接收缓冲区。

- 调用`unescape`函数,将输入发送数据中的转义序列反转义,并返回处理的字符数量。

- 调用`transfer`函数,执行SPI数据传输操作,将反转义后的发送数据发送到SPI设备,并接收数据到接收缓冲区。

- 释放发送和接收缓冲区的内存。

6. 否则,表示使用默认的发送和接收数据进行传输。

- 调用`transfer`函数,执行SPI数据传输操作,将默认的发送数据发送到SPI设备,并接收数据到接收缓冲区。

### spidev的缺点

使用read、write函数时,只能读、写,之二十半双工方式 使用ioctl可以达到全双工的读写 但是spidev有2个缺点:

- 不支持中断

- 只支持同步操作,不支持异步操作:就是read/write/ioctl这些函数只能执行完毕才可返回

完成代码如下

```c

/*

* SPI testing utility (using spidev driver)

*

* Copyright (c) 2007 MontaVista Software, Inc.

* Copyright (c) 2007 Anton Vorontsov <avorontsov@ru.mvista.com>

*

* This program is free software; you can redistribute it and/or modify

* it under the terms of the GNU General Public License as published by

* the Free Software Foundation; either version 2 of the License.

*

* Cross-compile with cross-gcc -I/path/to/cross-kernel/include

*/

#include <stdint.h>

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <getopt.h>

#include <fcntl.h>

#include <sys/ioctl.h>

#include <linux/types.h>

#include <linux/spi/spidev.h>

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

static void pabort(const char *s)

{

perror(s);

abort();

}

static const char *device = "/dev/spidev1.1";

static uint32_t mode;

static uint8_t bits = 8;

static uint32_t speed = 500000;

static uint16_t delay;

static int verbose;

uint8_t default_tx[] = {

0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

0x40, 0x00, 0x00, 0x00, 0x00, 0x95,

0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

0xF0, 0x0D,

};

uint8_t default_rx[ARRAY_SIZE(default_tx)] = {0, };

char *input_tx;

static void hex_dump(const void *src, size_t length, size_t line_size, char *prefix)

{

int i = 0;

const unsigned char *address = src;

const unsigned char *line = address;

unsigned char c;

printf("%s | ", prefix);

while (length-- > 0) {

printf("%02X ", *address++);

if (!(++i % line_size) || (length == 0 && i % line_size)) {

if (length == 0) {

while (i++ % line_size)

printf("__ ");

}

printf(" | "); /* right close */

while (line < address) {

c = *line++;

printf("%c", (c < 33 || c == 255) ? 0x2E : c);

}

printf("\n");

if (length > 0)

printf("%s | ", prefix);

}

}

}

/*

* Unescape - process hexadecimal escape character

* converts shell input "\x23" -> 0x23

*/

static int unescape(char *_dst, char *_src, size_t len)

{

int ret = 0;

char *src = _src;

char *dst = _dst;

unsigned int ch;

while (*src) {

if (*src == '\\' && *(src+1) == 'x') {

sscanf(src + 2, "%2x", &ch);

src += 4;

*dst++ = (unsigned char)ch;

} else {

*dst++ = *src++;

}

ret++;

}

return ret;

}

static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)

{

int ret;

struct spi_ioc_transfer tr = {

.tx_buf = (unsigned long)tx,

.rx_buf = (unsigned long)rx,

.len = len,

.delay_usecs = delay,

.speed_hz = speed,

.bits_per_word = bits,

};

if (mode & SPI_TX_QUAD)

tr.tx_nbits = 4;

else if (mode & SPI_TX_DUAL)

tr.tx_nbits = 2;

if (mode & SPI_RX_QUAD)

tr.rx_nbits = 4;

else if (mode & SPI_RX_DUAL)

tr.rx_nbits = 2;

if (!(mode & SPI_LOOP)) {

if (mode & (SPI_TX_QUAD | SPI_TX_DUAL))

tr.rx_buf = 0;

else if (mode & (SPI_RX_QUAD | SPI_RX_DUAL))

tr.tx_buf = 0;

}

ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);

if (ret < 1)

pabort("can't send spi message");

if (verbose)

hex_dump(tx, len, 32, "TX");

hex_dump(rx, len, 32, "RX");

}

static void print_usage(const char *prog)

{

printf("Usage: %s [-DsbdlHOLC3]\n", prog);

puts(" -D --device device to use (default /dev/spidev1.1)\n"

" -s --speed max speed (Hz)\n"

" -d --delay delay (usec)\n"

" -b --bpw bits per word \n"

" -l --loop loopback\n"

" -H --cpha clock phase\n"

" -O --cpol clock polarity\n"

" -L --lsb least significant bit first\n"

" -C --cs-high chip select active high\n"

" -3 --3wire SI/SO signals shared\n"

" -v --verbose Verbose (show tx buffer)\n"

" -p Send data (e.g. \"1234\\xde\\xad\")\n"

" -N --no-cs no chip select\n"

" -R --ready slave pulls low to pause\n"

" -2 --dual dual transfer\n"

" -4 --quad quad transfer\n");

exit(1);

}

static void parse_opts(int argc, char *argv[])

{

while (1) {

static const struct option lopts[] = {

{ "device", 1, 0, 'D' },

{ "speed", 1, 0, 's' },

{ "delay", 1, 0, 'd' },

{ "bpw", 1, 0, 'b' },

{ "loop", 0, 0, 'l' },

{ "cpha", 0, 0, 'H' },

{ "cpol", 0, 0, 'O' },

{ "lsb", 0, 0, 'L' },

{ "cs-high", 0, 0, 'C' },

{ "3wire", 0, 0, '3' },

{ "no-cs", 0, 0, 'N' },

{ "ready", 0, 0, 'R' },

{ "dual", 0, 0, '2' },

{ "verbose", 0, 0, 'v' },

{ "quad", 0, 0, '4' },

{ NULL, 0, 0, 0 },

};

int c;

c = getopt_long(argc, argv, "D:s:d:b:lHOLC3NR24p:v", lopts, NULL);

if (c == -1)

break;

switch (c) {

case 'D':

device = optarg;

break;

case 's':

speed = atoi(optarg);

break;

case 'd':

delay = atoi(optarg);

break;

case 'b':

bits = atoi(optarg);

break;

case 'l':

mode |= SPI_LOOP;

break;

case 'H':

mode |= SPI_CPHA;

break;

case 'O':

mode |= SPI_CPOL;

break;

case 'L':

mode |= SPI_LSB_FIRST;

break;

case 'C':

mode |= SPI_CS_HIGH;

break;

case '3':

mode |= SPI_3WIRE;

break;

case 'N':

mode |= SPI_NO_CS;

break;

case 'v':

verbose = 1;

break;

case 'R':

mode |= SPI_READY;

break;

case 'p':

input_tx = optarg;

break;

case '2':

mode |= SPI_TX_DUAL;

break;

case '4':

mode |= SPI_TX_QUAD;

break;

default:

print_usage(argv[0]);

break;

}

}

if (mode & SPI_LOOP) {

if (mode & SPI_TX_DUAL)

mode |= SPI_RX_DUAL;

if (mode & SPI_TX_QUAD)

mode |= SPI_RX_QUAD;

}

}

int main(int argc, char *argv[])

{

int ret = 0;

int fd;

uint8_t *tx;

uint8_t *rx;

int size;

parse_opts(argc, argv);

fd = open(device, O_RDWR);

if (fd < 0)

pabort("can't open device");

/*

* spi mode

*/

ret = ioctl(fd, SPI_IOC_WR_MODE32, &mode);

if (ret == -1)

pabort("can't set spi mode");

ret = ioctl(fd, SPI_IOC_RD_MODE32, &mode);

if (ret == -1)

pabort("can't get spi mode");

/*

* bits per word

*/

ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);

if (ret == -1)

pabort("can't set bits per word");

ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);

if (ret == -1)

pabort("can't get bits per word");

/*

* max speed hz

*/

ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);

if (ret == -1)

pabort("can't set max speed hz");

ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);

if (ret == -1)

pabort("can't get max speed hz");

printf("spi mode: 0x%x\n", mode);

printf("bits per word: %d\n", bits);

printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);

if (input_tx) {

size = strlen(input_tx+1);

tx = malloc(size);

rx = malloc(size);

size = unescape((char *)tx, input_tx, size);

transfer(fd, tx, rx, size);

free(rx);

free(tx);

} else {

transfer(fd, default_tx, default_rx, sizeof(default_tx));

}

close(fd);

return ret;

}

```

## 内核态

### DTS配置

```c

&spi0 {

status = "okay";

max-freq = <48000000>; //spi internal clk, don't modify

//dma-names = "tx", "rx"; //enable dma

pinctrl-names = "default"; //pinctrl according to you board

pinctrl-0 = <&spi0_clk &spi0_tx &spi0_rx &spi0_cs0 &spi0_cs1>;

spi_test@00 {

compatible = "rockchip,spi_test_bus0_cs0";

reg = <0>; //chip select 0:cs0 1:cs1

id = <0>;

spi-max-frequency = <24000000>; //spi output clock

//spi-cpha; not support

//spi-cpol; //if the property is here it is 1:clk is high, else 0:clk is low when idle

};

spi_test@01 {

compatible = "rockchip,spi_test_bus0_cs1";

reg = <1>;

id = <1>;

spi-max-frequency = <24000000>;

spi-cpha;

spi-cpol;

};

};

```

### 代码分析

```C

static int __init spi_rockchip_test_init(void)

{

int ret = 0;

misc_register(&spi_test_misc);

ret = spi_register_driver(&spi_rockchip_test_driver);

return ret;

}

module_init(spi_rockchip_test_init);

```

`spi_rockchip_test_init`函数,作为内核模块的初始化函数。在这个函数内部,执行以下操作:调用`misc_register`函数,将`spi_test_misc`结构体注册为一个misc设备。调用`spi_register_driver`函数,将`spi_rockchip_test_driver`结构体注册为一个SPI总线驱动程序。

```c

static struct spi_driver spi_rockchip_test_driver = {

.driver = {

.name = "spi_test",

.owner = THIS_MODULE,

.of_match_table = of_match_ptr(rockchip_spi_test_dt_match),

},

.probe = rockchip_spi_test_probe,

.remove = rockchip_spi_test_remove,

};

```

`spi_rockchip_test_driver`的SPI总线驱动程序结构体(`struct spi_driver`)。在这个结构体中,设置了以下成员变量:

- `.driver.name`:驱动程序的名称,设置为`"spi_test"`。

- `.driver.owner`:指向当前内核模块的指针,用于标识驱动程序的所有者。

- `.driver.of_match_table`:指向一个设备树匹配表的指针,用于与设备树中的设备进行匹配。

- `.probe`:指向`rockchip_spi_test_probe`函数的指针,表示当设备被探测到时,将调用该函数进行初始化。

- `.remove`:指向`rockchip_spi_test_remove`函数的指针,表示当设备被移除时,将调用该函数进行清理。

```c

static struct miscdevice spi_test_misc = {

.minor = MISC_DYNAMIC_MINOR,

.name = "spi_misc_test",

.fops = &spi_test_fops,

};

```

定义了一个名为`spi_test_misc`的Misc设备结构体(`struct miscdevice`)。在这个结构体中,设置了以下成员变量:

- `.minor`:使用`MISC_DYNAMIC_MINOR`宏来动态分配一个未使用的次设备号。

- `.name`:设备的名称,设置为`"spi_misc_test"`。

- `.fops`:指向`spi_test_fops`的指针,将文件操作结构体与Misc设备关联起来。

```c

static const struct file_operations spi_test_fops = {

.write = spi_test_write,

};

```

首先,定义了一个名为`spi_test_fops`的文件操作结构体(`struct file_operations`)。在这个结构体中,只设置了其中的一个成员变量`.write`,将其指向了`spi_test_write`函数。这表明当文件被写入时,会调用`spi_test_write`函数来处理写操作。

```c

static int rockchip_spi_test_probe(struct spi_device *spi)

{

int ret;

int id = 0;

struct spi_test_data *spi_test_data = NULL;

if (!spi)

return -ENOMEM;

if (!spi->dev.of_node)

return -ENOMEM;

spi_test_data = (struct spi_test_data *)kzalloc(sizeof(struct spi_test_data), GFP_KERNEL);

if (!spi_test_data) {

dev_err(&spi->dev, "ERR: no memory for spi_test_data\n");

return -ENOMEM;

}

spi->bits_per_word = 8;

spi_test_data->spi = spi;

spi_test_data->dev = &spi->dev;

ret = spi_setup(spi);

if (ret < 0) {

dev_err(spi_test_data->dev, "ERR: fail to setup spi\n");

return -1;

}

if (of_property_read_u32(spi->dev.of_node, "id", &id)) {

dev_warn(&spi->dev, "fail to get id, default set 0\n");

id = 0;

}

g_spi_test_data[id] = spi_test_data;

printk("%s:name=%s,bus_num=%d,cs=%d,mode=%d,speed=%d\n", __func__, spi->modalias, spi->master->bus_num, spi->chip_select, spi->mode, spi->max_speed_hz);

return ret;

}

```

1. 首先,会做一个判空,传入的`spi`指针为空指针,表示没有有效的SPI设备,函数将返回错误码`ENOMEM`,表示内存不足。如果`spi`结构的`dev`成员中的`of_node`为空,表示设备没有有效的设备树节点,函数同样返回错误码`ENOMEM`。

2. 使用`kzalloc`分配了一块内存,大小为`struct spi_test_data`结构的大小。`kzalloc`是一个内核函数,它会将分配的内存区域清零。如果分配失败,将返回错误码`ENOMEM`。如果分配成功,将把指针赋给`spi_test_data`。如果分配失败,函数将打印错误信息,并返回错误码`ENOMEM`。

3. 将SPI设备的`bits_per_word`成员设置为8,表示每个字节使用8个位。

4. 将`spi`指针和`spi->dev`的地址分别赋给`spi_test_data`结构的成员变量`spi`和`dev`。

5. 调用`spi_setup`函数对SPI设备进行设置和初始化。如果返回值小于0,表示设置和初始化失败。函数将打印错误信息,并返回-1。

6. 这里使用`of_property_read_u32`函数从设备树节点中读取名为"id"的属性,并将其值存储在`id`变量中。如果读取失败,将打印警告信息,并将`id`设置为0。

7. 将`spi_test_data`指针存储在全局数组`g_spi_test_data`中的索引为`id`的位置。

8. 使用`printk`函数打印一条包含SPI设备的相关信息的调试信息。

```c

static ssize_t spi_test_write(struct file *file,

const char __user *buf, size_t n, loff_t *offset)

{

int argc = 0, i;

char tmp[64];

char *argv[16];

char *cmd, *data;

unsigned int id = 0, times = 0, size = 0;

unsigned long us = 0, bytes = 0;

char *txbuf = NULL, *rxbuf = NULL;

ktime_t start_time;

ktime_t end_time;

ktime_t cost_time;

memset(tmp, 0, sizeof(tmp));

if (copy_from_user(tmp, buf, n))

return -EFAULT;

cmd = tmp;

data = tmp;

while (data < (tmp + n)) {

data = strstr(data, " ");

if (!data)

break;

*data = 0;

argv[argc] = ++data;

argc++;

if (argc >= 16)

break;

}

tmp[n - 1] = 0;

if (!strcmp(cmd, "setspeed")) {

int id = 0, val;

struct spi_device *spi = NULL;

sscanf(argv[0], "%d", &id);

sscanf(argv[1], "%d", &val);

if (id >= MAX_SPI_DEV_NUM)

return n;

if (!g_spi_test_data[id]) {

pr_err("g_spi.%d is NULL\n", id);

return n;

} else {

spi = g_spi_test_data[id]->spi;

}

spi->max_speed_hz = val;

} else if (!strcmp(cmd, "write")) {

char name[64];

int fd;

mm_segment_t old_fs = get_fs();

sscanf(argv[0], "%d", &id);

sscanf(argv[1], "%d", ×);

sscanf(argv[2], "%d", &size);

if (argc > 3) {

sscanf(argv[3], "%s", name);

set_fs(KERNEL_DS);

}

txbuf = kzalloc(size, GFP_KERNEL);

if (!txbuf) {

printk("spi write alloc buf size %d fail\n", size);

return n;

}

if (argc > 3) {

fd = sys_open(name, O_RDONLY, 0);

if (fd < 0) {

printk("open %s fail\n", name);

} else {

sys_read(fd, (char __user *)txbuf, size);

sys_close(fd);

}

set_fs(old_fs);

} else {

for (i = 0; i < size; i++)

txbuf[i] = i % 256;

}

start_time = ktime_get();

for (i = 0; i < times; i++)

spi_write_slt(id, txbuf, size);

end_time = ktime_get();

cost_time = ktime_sub(end_time, start_time);

us = ktime_to_us(cost_time);

bytes = size * times * 1;

bytes = bytes * 1000 / us;

printk("spi write %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);

kfree(txbuf);

} else if (!strcmp(cmd, "read")) {

sscanf(argv[0], "%d", &id);

sscanf(argv[1], "%d", ×);

sscanf(argv[2], "%d", &size);

rxbuf = kzalloc(size, GFP_KERNEL);

if (!rxbuf) {

printk("spi read alloc buf size %d fail\n", size);

return n;

}

start_time = ktime_get();

for (i = 0; i < times; i++)

spi_read_slt(id, rxbuf, size);

end_time = ktime_get();

cost_time = ktime_sub(end_time, start_time);

us = ktime_to_us(cost_time);

bytes = size * times * 1;

bytes = bytes * 1000 / us;

printk("spi read %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);

kfree(rxbuf);

} else if (!strcmp(cmd, "loop")) {

sscanf(argv[0], "%d", &id);

sscanf(argv[1], "%d", ×);

sscanf(argv[2], "%d", &size);

txbuf = kzalloc(size, GFP_KERNEL);

if (!txbuf) {

printk("spi tx alloc buf size %d fail\n", size);

return n;

}

rxbuf = kzalloc(size, GFP_KERNEL);

if (!rxbuf) {

kfree(txbuf);

printk("spi rx alloc buf size %d fail\n", size);

return n;

}

for (i = 0; i < size; i++)

txbuf[i] = i % 256;

start_time = ktime_get();

for (i = 0; i < times; i++)

spi_write_and_read_slt(id, txbuf, rxbuf, size);

end_time = ktime_get();

cost_time = ktime_sub(end_time, start_time);

us = ktime_to_us(cost_time);

if (memcmp(txbuf, rxbuf, size))

printk("spi loop test fail\n");

bytes = size * times;

bytes = bytes * 1000 / us;

printk("spi loop %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);

kfree(txbuf);

kfree(rxbuf);

} else {

printk("echo id number size > /dev/spi_misc_test\n");

printk("echo write 0 10 255 > /dev/spi_misc_test\n");

printk("echo write 0 10 255 init.rc > /dev/spi_misc_test\n");

printk("echo read 0 10 255 > /dev/spi_misc_test\n");

printk("echo loop 0 10 255 > /dev/spi_misc_test\n");

printk("echo setspeed 0 1000000 > /dev/spi_misc_test\n");

}

return n;

}

```

1. 使用`memset`将`tmp`数组清零,然后使用`copy_from_user`从用户空间将数据拷贝到`tmp`数组中。如果拷贝失败,将返回错误码`EFAULT`。

2. 函数通过空格字符将命令和参数分隔开,并将它们存储在参数数组`argv`中。通过循环查找空格字符,并将空格替换为字符串结束符号,然后将下一个字符的地址存储在`argv`数组中。最后,将`tmp`数组的最后一个字符设置为字符串结束符号。

3. 据解析得到的命令,函数执行相应的操作。如果命令是"setspeed",则设置SPI设备的速度。如果命令是"write",则向SPI设备写入数据。如果命令是"read",则从SPI设备读取数据。如果命令是"loop",则进行SPI设备的循环测试。如果命令不匹配上述任何一个条件,则打印命令使用说明。

4. 当命令是"setspeed"时,代码会解析参数并设置指定的SPI设备的速度。

- 使用`sscanf`函数从参数数组`argv`中读取`id`和`val`的值,并将其存储在相应的变量中。

- 检查`id`是否超出最大SPI设备数量的限制。如果超出限制,函数将返回处理的字节数`n`。

- 检查对应的`g_spi_test_data[id]`是否为空,如果为空,则打印错误信息并返回处理的字节数`n`。

- 如果`g_spi_test_data[id]`不为空,将其对应的`spi`设备指针赋值给变量`spi`。

- 将`spi->max_speed_hz`设置为`val`,即设置SPI设备的速度。

5. 当命令是"write"时,代码会向指定的SPI设备写入数据。

- 使用`sscanf`函数从参数数组`argv`中读取`id`、`times`和`size`的值,并将其存储在相应的变量中。

- 如果参数个数大于3,说明还有一个文件名参数,使用`sscanf`函数从参数数组`argv`中读取文件名,并将其存储在`name`数组中。

- 如果参数个数大于3,说明有文件名参数,打开该文件并读取数据到`txbuf`中。

- 调用`ktime_get`函数获取当前时间作为测试开始时间。

- 通过循环调用`spi_write_slt`函数向SPI设备写入数据,循环次数为`times`次,每次写入的数据为`txbuf`,数据大小为`size`。

- 调用`ktime_get`函数获取当前时间作为测试结束时间,并计算测试所花费的时间。

- 通过计算总的数据量和测试时间,计算出传输速度,并打印相关信息。

6. 当命令是"read"时,代码会从指定的SPI设备读取数据。具体步骤与"write"命令类似,不同之处在于使用`spi_read_slt`函数从SPI设备读取数据,并计算读取的速度。

7. 当命令是"loop"时,代码将执行SPI设备的循环测试。

- 使用`sscanf`函数从参数数组`argv`中读取`id`、`times`和`size`的值,并将其存储在相应的变量中。

- 将循环测试的数据填充到`txbuf`数组中,每个字节的值为`i % 256`。

- 调用`ktime_get`函数获取当前时间作为测试开始时间。

- 通过循环调用`spi_write_and_read_slt`函数进行循环测试,循环次数为`times`次,每次向SPI设备写入`txbuf`数据,然后从SPI设备读取`size`字节的数据存储到`rxbuf`中。

- 调用`ktime_get`函数获取当前时间作为测试结束时间,并计算测试时间。

- 通过计算总的数据量和测试时间,计算出传输速度,并打印相关信息。

```c

int spi_write_and_read_slt(int id, const void *tx_buf,

void *rx_buf, size_t len)

{

int ret = -1;

struct spi_device *spi = NULL;

struct spi_transfer t = {

.tx_buf = tx_buf,

.rx_buf = rx_buf,

.len = len,

};

struct spi_message m;

if (id >= MAX_SPI_DEV_NUM)

return ret;

if (!g_spi_test_data[id]) {

pr_err("g_spi.%d is NULL\n", id);

return ret;

} else {

spi = g_spi_test_data[id]->spi;

}

spi_message_init(&m);

spi_message_add_tail(&t, &m);

return spi_sync(spi, &m);

}

```

`spi_write_and_read_slt`通过SPI总线向指定的SPI设备进行同时写入和读取操作。它使用了`spi_transfer`结构体和`spi_message`结构体来描述数据传输的相关参数,并调用`spi_sync`函数执行SPI设备的同步传输操作,将`spi`和`m`作为参数传入。该函数会阻塞直到传输完成。。

```c

int spi_write_then_read_slt(int id, const void *txbuf, unsigned n_tx,

void *rxbuf, unsigned n_rx)

{

int ret = -1;

struct spi_device *spi = NULL;

if (id >= MAX_SPI_DEV_NUM)

return ret;

if (!g_spi_test_data[id]) {

pr_err("g_spi.%d is NULL\n", id);

return ret;

} else {

spi = g_spi_test_data[id]->spi;

}

ret = spi_write_then_read(spi, txbuf, n_tx, rxbuf, n_rx);

return ret;

}

```

这段代码通过SPI总线向指定的SPI设备进行先写后读的操作。它使用了`spi_write_then_read`函数来执行先写后读的操作,并将操作结果返回。

```c

int spi_read_slt(int id, void *rxbuf, size_t n)

{

int ret = -1;

struct spi_device *spi = NULL;

if (id >= MAX_SPI_DEV_NUM)

return ret;

if (!g_spi_test_data[id]) {

pr_err("g_spi.%d is NULL\n", id);

return ret;

} else {

spi = g_spi_test_data[id]->spi;

}

ret = spi_read(spi, rxbuf, n);

return ret;

}

```

`spi_read_slt`通过SPI总线从指定的SPI设备进行读取操作。它使用了`spi_read`函数来执行读取操作,并将操作结果返回。

```c

int spi_write_slt(int id, const void *txbuf, size_t n)

{

int ret = -1;

struct spi_device *spi = NULL;

if (id >= MAX_SPI_DEV_NUM)

return -1;

if (!g_spi_test_data[id]) {

pr_err("g_spi.%d is NULL\n", id);

return -1;

} else {

spi = g_spi_test_data[id]->spi;

}

ret = spi_write(spi, txbuf, n);

return ret;

}

```

spi_write_slt通过SPI总线向指定的SPI设备进行写入操作。它使用了`spi_write`函数来执行写入操作,并将操作结果返回。如果参数不合法或指定的SPI设备不存在,函数会直接返回-1。

### 测试命令

```c

echo write 0 10 255 > /dev/spi_misc_test

echo write 0 10 255 init.rc > /dev/spi_misc_test

echo read 0 10 255 > /dev/spi_misc_test

echo loop 0 10 255 > /dev/spi_misc_test

echo setspeed 0 1000000 > /dev/spi_misc_test

```

```bash

echo 类型 id 循环次数 传输长度 > /dev/spi_misc_test

echo setspeed id 频率(单位 Hz) > /dev/spi_misc_test

```

如果需要,可以自己修改测试 case。

### 常见问题

1. 调试前确认驱动有跑起来

2. 确保 SPI 4 个引脚的 IOMUX 配置无误

3. 确认 TX 送时,TX 引脚有正常的波形,CLK 有正常的 CLOCK 信号,CS 信号有拉低

4. 如果 clk 频率较高,可以考虑提高驱动强度来改善信号

### 完整代码

```C

/*drivers/spi/spi-rockchip-test.c -spi test driver

*

*

* This program is distributed in the hope that it will be useful,

* but WITHOUT ANY WARRANTY; without even the implied warranty of

* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

* GNU General Public License for more details.

*/

/* dts config

&spi0 {

status = "okay";

max-freq = <48000000>; //spi internal clk, don't modify

//dma-names = "tx", "rx"; //enable dma

pinctrl-names = "default"; //pinctrl according to you board

pinctrl-0 = <&spi0_clk &spi0_tx &spi0_rx &spi0_cs0 &spi0_cs1>;

spi_test@00 {

compatible = "rockchip,spi_test_bus0_cs0";

reg = <0>; //chip select 0:cs0 1:cs1

id = <0>;

spi-max-frequency = <24000000>; //spi output clock

//spi-cpha; not support

//spi-cpol; //if the property is here it is 1:clk is high, else 0:clk is low when idle

};

spi_test@01 {

compatible = "rockchip,spi_test_bus0_cs1";

reg = <1>;

id = <1>;

spi-max-frequency = <24000000>;

spi-cpha;

spi-cpol;

};

};

*/

/* how to test spi

* echo write 0 10 255 > /dev/spi_misc_test

* echo write 0 10 255 init.rc > /dev/spi_misc_test

* echo read 0 10 255 > /dev/spi_misc_test

* echo loop 0 10 255 > /dev/spi_misc_test

* echo setspeed 0 1000000 > /dev/spi_misc_test

*/

#include <linux/interrupt.h>

#include <linux/slab.h>

#include <linux/init.h>

#include <linux/module.h>

#include <linux/workqueue.h>

#include <linux/interrupt.h>

#include <linux/delay.h>

#include <linux/clk.h>

#include <linux/fs.h>

#include <linux/dma-mapping.h>

#include <linux/dmaengine.h>

#include <linux/platform_device.h>

#include <linux/pm_runtime.h>

#include <linux/spi/spi.h>

#include <linux/gpio.h>

#include <linux/of.h>

#include <linux/of_gpio.h>

#include <linux/miscdevice.h>

#include <linux/hrtimer.h>

#include <linux/platform_data/spi-rockchip.h>

#include <asm/uaccess.h>

#include <linux/syscalls.h>

#define MAX_SPI_DEV_NUM 6

#define SPI_MAX_SPEED_HZ 12000000

struct spi_test_data {

struct device *dev;

struct spi_device *spi;

char *rx_buf;

int rx_len;

char *tx_buf;

int tx_len;

};

static struct spi_test_data *g_spi_test_data[MAX_SPI_DEV_NUM];

int spi_write_slt(int id, const void *txbuf, size_t n)

{

int ret = -1;

struct spi_device *spi = NULL;

if (id >= MAX_SPI_DEV_NUM)

return -1;

if (!g_spi_test_data[id]) {

pr_err("g_spi.%d is NULL\n", id);

return -1;

} else {

spi = g_spi_test_data[id]->spi;

}

ret = spi_write(spi, txbuf, n);

return ret;

}

int spi_read_slt(int id, void *rxbuf, size_t n)

{

int ret = -1;

struct spi_device *spi = NULL;

if (id >= MAX_SPI_DEV_NUM)

return ret;

if (!g_spi_test_data[id]) {

pr_err("g_spi.%d is NULL\n", id);

return ret;

} else {

spi = g_spi_test_data[id]->spi;

}

ret = spi_read(spi, rxbuf, n);

return ret;

}

int spi_write_then_read_slt(int id, const void *txbuf, unsigned n_tx,

void *rxbuf, unsigned n_rx)

{

int ret = -1;

struct spi_device *spi = NULL;

if (id >= MAX_SPI_DEV_NUM)

return ret;

if (!g_spi_test_data[id]) {

pr_err("g_spi.%d is NULL\n", id);

return ret;

} else {

spi = g_spi_test_data[id]->spi;

}

ret = spi_write_then_read(spi, txbuf, n_tx, rxbuf, n_rx);

return ret;

}

int spi_write_and_read_slt(int id, const void *tx_buf,

void *rx_buf, size_t len)

{

int ret = -1;

struct spi_device *spi = NULL;

struct spi_transfer t = {

.tx_buf = tx_buf,

.rx_buf = rx_buf,

.len = len,

};

struct spi_message m;

if (id >= MAX_SPI_DEV_NUM)

return ret;

if (!g_spi_test_data[id]) {

pr_err("g_spi.%d is NULL\n", id);

return ret;

} else {

spi = g_spi_test_data[id]->spi;

}

spi_message_init(&m);

spi_message_add_tail(&t, &m);

return spi_sync(spi, &m);

}

static ssize_t spi_test_write(struct file *file,

const char __user *buf, size_t n, loff_t *offset)

{

int argc = 0, i;

char tmp[64];

char *argv[16];

char *cmd, *data;

unsigned int id = 0, times = 0, size = 0;

unsigned long us = 0, bytes = 0;

char *txbuf = NULL, *rxbuf = NULL;

ktime_t start_time;

ktime_t end_time;

ktime_t cost_time;

memset(tmp, 0, sizeof(tmp));

if (copy_from_user(tmp, buf, n))

return -EFAULT;

cmd = tmp;

data = tmp;

while (data < (tmp + n)) {

data = strstr(data, " ");

if (!data)

break;

*data = 0;

argv[argc] = ++data;

argc++;

if (argc >= 16)

break;

}

tmp[n - 1] = 0;

if (!strcmp(cmd, "setspeed")) {

int id = 0, val;

struct spi_device *spi = NULL;

sscanf(argv[0], "%d", &id);

sscanf(argv[1], "%d", &val);

if (id >= MAX_SPI_DEV_NUM)

return n;

if (!g_spi_test_data[id]) {

pr_err("g_spi.%d is NULL\n", id);

return n;

} else {

spi = g_spi_test_data[id]->spi;

}

spi->max_speed_hz = val;

} else if (!strcmp(cmd, "write")) {

char name[64];

int fd;

mm_segment_t old_fs = get_fs();

sscanf(argv[0], "%d", &id);

sscanf(argv[1], "%d", ×);

sscanf(argv[2], "%d", &size);

if (argc > 3) {

sscanf(argv[3], "%s", name);

set_fs(KERNEL_DS);

}

txbuf = kzalloc(size, GFP_KERNEL);

if (!txbuf) {

printk("spi write alloc buf size %d fail\n", size);

return n;

}

if (argc > 3) {

fd = sys_open(name, O_RDONLY, 0);

if (fd < 0) {

printk("open %s fail\n", name);

} else {

sys_read(fd, (char __user *)txbuf, size);

sys_close(fd);

}

set_fs(old_fs);

} else {

for (i = 0; i < size; i++)

txbuf[i] = i % 256;

}

start_time = ktime_get();

for (i = 0; i < times; i++)

spi_write_slt(id, txbuf, size);

end_time = ktime_get();

cost_time = ktime_sub(end_time, start_time);

us = ktime_to_us(cost_time);

bytes = size * times * 1;

bytes = bytes * 1000 / us;

printk("spi write %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);

kfree(txbuf);

} else if (!strcmp(cmd, "read")) {

sscanf(argv[0], "%d", &id);

sscanf(argv[1], "%d", ×);

sscanf(argv[2], "%d", &size);

rxbuf = kzalloc(size, GFP_KERNEL);

if (!rxbuf) {

printk("spi read alloc buf size %d fail\n", size);

return n;

}

start_time = ktime_get();

for (i = 0; i < times; i++)

spi_read_slt(id, rxbuf, size);

end_time = ktime_get();

cost_time = ktime_sub(end_time, start_time);

us = ktime_to_us(cost_time);

bytes = size * times * 1;

bytes = bytes * 1000 / us;

printk("spi read %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);

kfree(rxbuf);

} else if (!strcmp(cmd, "loop")) {

sscanf(argv[0], "%d", &id);

sscanf(argv[1], "%d", ×);

sscanf(argv[2], "%d", &size);

txbuf = kzalloc(size, GFP_KERNEL);

if (!txbuf) {

printk("spi tx alloc buf size %d fail\n", size);

return n;

}

rxbuf = kzalloc(size, GFP_KERNEL);

if (!rxbuf) {

kfree(txbuf);

printk("spi rx alloc buf size %d fail\n", size);

return n;

}

for (i = 0; i < size; i++)

txbuf[i] = i % 256;

start_time = ktime_get();

for (i = 0; i < times; i++)

spi_write_and_read_slt(id, txbuf, rxbuf, size);

end_time = ktime_get();

cost_time = ktime_sub(end_time, start_time);

us = ktime_to_us(cost_time);

if (memcmp(txbuf, rxbuf, size))

printk("spi loop test fail\n");

bytes = size * times;

bytes = bytes * 1000 / us;

printk("spi loop %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);

kfree(txbuf);

kfree(rxbuf);

} else {

printk("echo id number size > /dev/spi_misc_test\n");

printk("echo write 0 10 255 > /dev/spi_misc_test\n");

printk("echo write 0 10 255 init.rc > /dev/spi_misc_test\n");

printk("echo read 0 10 255 > /dev/spi_misc_test\n");

printk("echo loop 0 10 255 > /dev/spi_misc_test\n");

printk("echo setspeed 0 1000000 > /dev/spi_misc_test\n");

}

return n;

}

static const struct file_operations spi_test_fops = {

.write = spi_test_write,

};

static struct miscdevice spi_test_misc = {

.minor = MISC_DYNAMIC_MINOR,

.name = "spi_misc_test",

.fops = &spi_test_fops,

};

static int rockchip_spi_test_probe(struct spi_device *spi)

{

int ret;

int id = 0;

struct spi_test_data *spi_test_data = NULL;

if (!spi)

return -ENOMEM;

if (!spi->dev.of_node)

return -ENOMEM;

spi_test_data = (struct spi_test_data *)kzalloc(sizeof(struct spi_test_data), GFP_KERNEL);

if (!spi_test_data) {

dev_err(&spi->dev, "ERR: no memory for spi_test_data\n");

return -ENOMEM;

}

spi->bits_per_word = 8;

spi_test_data->spi = spi;

spi_test_data->dev = &spi->dev;

ret = spi_setup(spi);

if (ret < 0) {

dev_err(spi_test_data->dev, "ERR: fail to setup spi\n");

return -1;

}

if (of_property_read_u32(spi->dev.of_node, "id", &id)) {

dev_warn(&spi->dev, "fail to get id, default set 0\n");

id = 0;

}

g_spi_test_data[id] = spi_test_data;

printk("%s:name=%s,bus_num=%d,cs=%d,mode=%d,speed=%d\n", __func__, spi->modalias, spi->master->bus_num, spi->chip_select, spi->mode, spi->max_speed_hz);

return ret;

}

static int rockchip_spi_test_remove(struct spi_device *spi)

{

printk("%s\n", __func__);

return 0;

}

#ifdef CONFIG_OF

static const struct of_device_id rockchip_spi_test_dt_match[] = {

{ .compatible = "rockchip,spi_test_bus0_cs0", },

{ .compatible = "rockchip,spi_test_bus0_cs1", },

{ .compatible = "rockchip,spi_test_bus1_cs0", },

{ .compatible = "rockchip,spi_test_bus1_cs1", },

{ .compatible = "rockchip,spi_test_bus2_cs0", },

{ .compatible = "rockchip,spi_test_bus2_cs1", },

{ .compatible = "rockchip,spi_test_bus3_cs0", },

{ .compatible = "rockchip,spi_test_bus3_cs1", },

{ .compatible = "rockchip,spi_test_bus4_cs0", },

{ .compatible = "rockchip,spi_test_bus4_cs1", },

{},

};

MODULE_DEVICE_TABLE(of, rockchip_spi_test_dt_match);

#endif /* CONFIG_OF */

static struct spi_driver spi_rockchip_test_driver = {

.driver = {

.name = "spi_test",

.owner = THIS_MODULE,

.of_match_table = of_match_ptr(rockchip_spi_test_dt_match),

},

.probe = rockchip_spi_test_probe,

.remove = rockchip_spi_test_remove,

};

static int __init spi_rockchip_test_init(void)

{

int ret = 0;

misc_register(&spi_test_misc);

ret = spi_register_driver(&spi_rockchip_test_driver);

return ret;

}

module_init(spi_rockchip_test_init);

static void __exit spi_rockchip_test_exit(void)

{

misc_deregister(&spi_test_misc);

return spi_unregister_driver(&spi_rockchip_test_driver);

}

module_exit(spi_rockchip_test_exit);

MODULE_AUTHOR("Luo Wei <lw@rock-chips.com>");

MODULE_AUTHOR("Huibin Hong <hhb@rock-chips.com>");

MODULE_DESCRIPTION("ROCKCHIP SPI TEST Driver");

MODULE_LICENSE("GPL");

MODULE_ALIAS("spi:spi_test");

```

相关问答

请问如何将图片及字符烧进 SPI Flash并读取显示?

转换为bin再写入spiflash,人为定义spi的区域划分,不同的图片方法不同的起始地址。bin文件放在SD卡中,通过读SD卡再写入SPIFLASH转换为bin再写入spiflash,.....

大家嵌入式中常用什么接口呢?UART、 SPI 、IIC还是USB、CAN?

UART-超级终端上用得着,串口通信上用得着。SPI-读SD卡可用的,也可以用来读写一些使用这个接口的器件,如我现在用的MCP2515就是SPI接口的。IIC-好像有IIC...

spi 和i2c哪个值得入手?

只要搞单片机应用设计,SPI总线和I2C总线都必须掌握,因为单片机的外设有很多使用SPI总线或I2C总线。掌握这两个总线技术的应用,通常需要了解两方面的内容:第...

嵌入式Flash读写操作该如何进行设计?

Flash存储芯片的通讯方式以SPI居多,在实现flash读写时就是要实现SPI的通讯协议,与EEPROM不同的是,SPI在操作时是按照PAGE页进行整页擦除写入的,这一点需要注...

ch348芯片怎么接单片机?

通信协议,使用相应的...在编程时,需要了解ch348芯片的功能和通信协议,使用相应的库函数或驱动程序与单片机进行串口通信,实现对ch348芯片的控制和数据传输。...

一块开发板上的多个FPGA间如何通讯?

对于多个FPGA之间的通信,只要IO连接上了,对于高速的数据流需要同步时钟,对于低速的接口,只需要异步处理即可。下面介绍几种FPGA之间常用的数据通信方式。一...4...

uart模块使用?

UART使用示例1.UART发送与接收(非中断方式)#include'headfile.h'//包含头文件uint8uart_receive;uint8uart_se...

sd模式是什么?

用SD模式时,往往需要选择带有SD卡控制器接口的MCU,或者必须加入额外的SD卡控制单元以支持SD卡的读写。然而,很多51单片机没有集成SD卡控制器接口,若选用S...

gpio接口是干什么的?

GPIO(英语:General-purposeinput/output),通用型之输入输出的简称,功能类似8051的P0—P3,其接脚可以供使用者由程控自由使用,PIN脚依现实考量可作为通用......

为什么自己设计不出来单片机程序?该怎么做?

单片机其实就是一个“微型”电脑,它集成CPU、RAM、ROM、I/O,还集成PWM、AD、定时器、中断、UART、SPI等等的各种外设。如果你有一定的电子基础知识和C语言基...

 高级群  泰勒希罗 
王经理: 180-0000-0000(微信同号)
10086@qq.com
北京海淀区西三旗街道国际大厦08A座
©2024  上海羊羽卓进出口贸易有限公司  版权所有.All Rights Reserved.  |  程序由Z-BlogPHP强力驱动
网站首页
电话咨询
微信号

QQ

在线咨询真诚为您提供专业解答服务

热线

188-0000-0000
专属服务热线

微信

二维码扫一扫微信交流
顶部