【驱动】SPI驱动分析(七)-SPI驱动常用调试方法
用户态
用户应用层使用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 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; }; };
代码分析
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 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");
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库