用户态
用户应用层使用spidev驱动的步骤如下:
- 打开SPI设备文件:用户可以通过打开
/dev/spidevX.Y
文件来访问SPI设备,其中X是SPI控制器的编号,Y是SPI设备的编号。 - 配置SPI参数:用户可以使用ioctl命令
SPI_IOC_WR_MODE
、SPI_IOC_WR_BITS_PER_WORD
和SPI_IOC_WR_MAX_SPEED_HZ
来设置SPI模式、数据位数和时钟速度等参数。 - 发送和接收数据:用户可以使用read和write系统调用来发送和接收SPI数据。写入的数据将被传输到SPI设备,而从设备读取的数据将被存储在用户提供的缓冲区中。
- 关闭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
这段代码通过解析命令行选项,并根据选项的值设置相应的变量,实现了对命令行参数的解析和处理。
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;}
}
- 声明一个静态的选项数组
lopts
,用于定义可接受的命令行选项。 - 在循环内部,调用
getopt_long
函数来解析下一个选项。getopt_long
函数会返回选项的短选项字符(c),如果没有更多选项则返回-1。 - 使用switch语句根据选项的短选项字符进行分支处理。
- 根据不同的选项,执行相应的操作。例如,对于选项’D’,将其参数值赋给
device
变量;对于选项’s’,将其参数值转换为整数并赋给speed
变量。 - 如果遇到未知的选项,调用
print_usage
函数打印用法信息。 - 循环结束后,根据设置的选项进行一些额外的逻辑处理。例如,如果设置了
SPI_LOOP
选项,则根据是否设置了SPI_TX_DUAL
和SPI_TX_QUAD
选项,设置相应的SPI_RX_DUAL
和SPI_RX_QUAD
选项。
print_usage
打印spi_test的 使用方法。
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传输的参数,并将发送和接收的数据进行打印。
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");
}
- 声明一个
spi_ioc_transfer
结构体变量tr
,用于设置SPI传输的参数。 - 在
spi_ioc_transfer
结构体中设置以下字段:tx_buf
:指向发送数据缓冲区的指针。rx_buf
:指向接收数据缓冲区的指针。len
:要传输的数据长度。delay_usecs
:传输之间的延迟时间(以微秒为单位)。speed_hz
:SPI时钟速度(以赫兹为单位)。bits_per_word
:每个字的位数。
- 根据变量
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。 - 如果
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,表示在非回环模式下不发送数据。 - 使用
ioctl
系统调用发送SPI消息并执行SPI数据传输操作。SPI_IOC_MESSAGE(1)
表示发送单个SPI消息。 - 检查
ioctl
的返回值ret
,如果小于1,则表示SPI消息发送失败,调用pabort
函数打印错误消息并终止程序。 - 如果
verbose
标志为真,则使用hex_dump
函数打印发送和接收数据的十六进制表示。
这段代码用于将输入字符串中的转义序列\x
转换为对应的字符,并将结果存储在目标字符串中。它通过遍历输入字符串的字符,并根据转义序列的位置和格式进行解析和转换。
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
函数中实现。
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;
}
- 调用
parse_opts
函数,解析命令行参数并设置全局变量。 - 使用
open
函数打开SPI设备,以可读写方式打开。如果返回值小于0,则打印错误消息并终止程序。 - 使用
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,则打印错误消息并终止程序。 - 使用
printf
函数打印设置的SPI设备参数:模式、每字位数和最大时钟速度。 - 如果
input_tx
不为NULL,则表示存在输入的发送数据。- 计算输入发送数据的大小(排除末尾的
\0
)。 - 分配相应大小的内存给发送和接收缓冲区。
- 调用
unescape
函数,将输入发送数据中的转义序列反转义,并返回处理的字符数量。 - 调用
transfer
函数,执行SPI数据传输操作,将反转义后的发送数据发送到SPI设备,并接收数据到接收缓冲区。 - 释放发送和接收缓冲区的内存。
- 计算输入发送数据的大小(排除末尾的
- 否则,表示使用默认的发送和接收数据进行传输。
- 调用
transfer
函数,执行SPI数据传输操作,将默认的发送数据发送到SPI设备,并接收数据到接收缓冲区。
spidev的缺点
使用read、write函数时,只能读、写,之二十半双工方式 使用ioctl可以达到全双工的读写 但是spidev有2个缺点:
- 不支持中断
- 只支持同步操作,不支持异步操作:就是read/write/ioctl这些函数只能执行完毕才可返回
完成代码如下
/** 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配置
&spi0 {status = "okay";max-freq = <48000000>; //spi internal clk, don't modify//dma-names = "tx", "rx"; //enable dmapinctrl-names = "default"; //pinctrl according to you boardpinctrl-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:cs1id = <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;};
};
代码分析
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总线驱动程序。
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
函数的指针,表示当设备被移除时,将调用该函数进行清理。
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设备关联起来。
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
函数来处理写操作。
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;
}
- 首先,会做一个判空,传入的
spi
指针为空指针,表示没有有效的SPI设备,函数将返回错误码ENOMEM
,表示内存不足。如果spi
结构的dev
成员中的of_node
为空,表示设备没有有效的设备树节点,函数同样返回错误码ENOMEM
。 - 使用
kzalloc
分配了一块内存,大小为struct spi_test_data
结构的大小。kzalloc
是一个内核函数,它会将分配的内存区域清零。如果分配失败,将返回错误码ENOMEM
。如果分配成功,将把指针赋给spi_test_data
。如果分配失败,函数将打印错误信息,并返回错误码ENOMEM
。 - 将SPI设备的
bits_per_word
成员设置为8,表示每个字节使用8个位。 - 将
spi
指针和spi->dev
的地址分别赋给spi_test_data
结构的成员变量spi
和dev
。 - 调用
spi_setup
函数对SPI设备进行设置和初始化。如果返回值小于0,表示设置和初始化失败。函数将打印错误信息,并返回-1。 - 这里使用
of_property_read_u32
函数从设备树节点中读取名为"id"的属性,并将其值存储在id
变量中。如果读取失败,将打印警告信息,并将id
设置为0。 - 将
spi_test_data
指针存储在全局数组g_spi_test_data
中的索引为id
的位置。 - 使用
printk
函数打印一条包含SPI设备的相关信息的调试信息。
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;
}
- 使用
memset
将tmp
数组清零,然后使用copy_from_user
从用户空间将数据拷贝到tmp
数组中。如果拷贝失败,将返回错误码EFAULT
。 - 函数通过空格字符将命令和参数分隔开,并将它们存储在参数数组
argv
中。通过循环查找空格字符,并将空格替换为字符串结束符号,然后将下一个字符的地址存储在argv
数组中。最后,将tmp
数组的最后一个字符设置为字符串结束符号。 - 据解析得到的命令,函数执行相应的操作。如果命令是"setspeed",则设置SPI设备的速度。如果命令是"write",则向SPI设备写入数据。如果命令是"read",则从SPI设备读取数据。如果命令是"loop",则进行SPI设备的循环测试。如果命令不匹配上述任何一个条件,则打印命令使用说明。
- 当命令是"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设备的速度。
- 使用
- 当命令是"write"时,代码会向指定的SPI设备写入数据。
- 使用
sscanf
函数从参数数组argv
中读取id
、times
和size
的值,并将其存储在相应的变量中。 - 如果参数个数大于3,说明还有一个文件名参数,使用
sscanf
函数从参数数组argv
中读取文件名,并将其存储在name
数组中。 - 如果参数个数大于3,说明有文件名参数,打开该文件并读取数据到
txbuf
中。 - 调用
ktime_get
函数获取当前时间作为测试开始时间。 - 通过循环调用
spi_write_slt
函数向SPI设备写入数据,循环次数为times
次,每次写入的数据为txbuf
,数据大小为size
。 - 调用
ktime_get
函数获取当前时间作为测试结束时间,并计算测试所花费的时间。 - 通过计算总的数据量和测试时间,计算出传输速度,并打印相关信息。
- 使用
- 当命令是"read"时,代码会从指定的SPI设备读取数据。具体步骤与"write"命令类似,不同之处在于使用
spi_read_slt
函数从SPI设备读取数据,并计算读取的速度。 - 当命令是"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
函数获取当前时间作为测试结束时间,并计算测试时间。 - 通过计算总的数据量和测试时间,计算出传输速度,并打印相关信息。
- 使用
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
作为参数传入。该函数会阻塞直到传输完成。。
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
函数来执行先写后读的操作,并将操作结果返回。
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
函数来执行读取操作,并将操作结果返回。
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。
测试命令
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
echo 类型 id 循环次数 传输长度 > /dev/spi_misc_test
echo setspeed id 频率(单位 Hz) > /dev/spi_misc_test
如果需要,可以自己修改测试 case。
常见问题
- 调试前确认驱动有跑起来
- 确保 SPI 4 个引脚的 IOMUX 配置无误
- 确认 TX 送时,TX 引脚有正常的波形,CLK 有正常的 CLOCK 信号,CS 信号有拉低
- 如果 clk 频率较高,可以考虑提高驱动强度来改善信号
完整代码
/*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 dmapinctrl-names = "default"; //pinctrl according to you boardpinctrl-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:cs1id = <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 12000000struct 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");