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

用户态

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

  1. 打开SPI设备文件:用户可以通过打开/dev/spidevX.Y文件来访问SPI设备,其中X是SPI控制器的编号,Y是SPI设备的编号。
  2. 配置SPI参数:用户可以使用ioctl命令SPI_IOC_WR_MODESPI_IOC_WR_BITS_PER_WORDSPI_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.cspidev_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;}
}
  1. 声明一个静态的选项数组lopts,用于定义可接受的命令行选项。
  2. 在循环内部,调用getopt_long函数来解析下一个选项。getopt_long函数会返回选项的短选项字符(c),如果没有更多选项则返回-1。
  3. 使用switch语句根据选项的短选项字符进行分支处理。
  4. 根据不同的选项,执行相应的操作。例如,对于选项’D’,将其参数值赋给device变量;对于选项’s’,将其参数值转换为整数并赋给speed变量。
  5. 如果遇到未知的选项,调用print_usage函数打印用法信息。
  6. 循环结束后,根据设置的选项进行一些额外的逻辑处理。例如,如果设置了SPI_LOOP选项,则根据是否设置了SPI_TX_DUALSPI_TX_QUAD选项,设置相应的SPI_RX_DUALSPI_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");
}
  1. 声明一个spi_ioc_transfer结构体变量tr,用于设置SPI传输的参数。
  2. spi_ioc_transfer结构体中设置以下字段:
    • tx_buf:指向发送数据缓冲区的指针。
    • rx_buf:指向接收数据缓冲区的指针。
    • len:要传输的数据长度。
    • delay_usecs:传输之间的延迟时间(以微秒为单位)。
    • speed_hz:SPI时钟速度(以赫兹为单位)。
    • bits_per_word:每个字的位数。
  3. 根据变量mode的值设置tr结构体中的tx_nbitsrx_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。
  4. 如果mode中不包含SPI_LOOP标志,则根据mode中的其他标志设置tr结构体中的tx_bufrx_buf字段。如果mode中包含SPI_TX_QUADSPI_TX_DUAL标志,则将rx_buf设置为0,表示在非回环模式下不接收数据。类似地,如果mode中包含SPI_RX_QUADSPI_RX_DUAL标志,则将tx_buf设置为0,表示在非回环模式下不发送数据。
  5. 使用ioctl系统调用发送SPI消息并执行SPI数据传输操作。SPI_IOC_MESSAGE(1)表示发送单个SPI消息。
  6. 检查ioctl的返回值ret,如果小于1,则表示SPI消息发送失败,调用pabort函数打印错误消息并终止程序。
  7. 如果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;
}
  1. 调用parse_opts函数,解析命令行参数并设置全局变量。
  2. 使用open函数打开SPI设备,以可读写方式打开。如果返回值小于0,则打印错误消息并终止程序。
  3. 使用ioctl系统调用设置SPI设备的模式(SPI_IOC_WR_MODE32SPI_IOC_RD_MODE32)、每字位数(SPI_IOC_WR_BITS_PER_WORDSPI_IOC_RD_BITS_PER_WORD)以及最大时钟速度(SPI_IOC_WR_MAX_SPEED_HZSPI_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这些函数只能执行完毕才可返回

完成代码如下

/** 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;
}
  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结构的成员变量spidev
  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设备的相关信息的调试信息。
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", &times);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", &times);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", &times);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. 使用memsettmp数组清零,然后使用copy_from_user从用户空间将数据拷贝到tmp数组中。如果拷贝失败,将返回错误码EFAULT
  2. 函数通过空格字符将命令和参数分隔开,并将它们存储在参数数组argv中。通过循环查找空格字符,并将空格替换为字符串结束符号,然后将下一个字符的地址存储在argv数组中。最后,将tmp数组的最后一个字符设置为字符串结束符号。
  3. 据解析得到的命令,函数执行相应的操作。如果命令是"setspeed",则设置SPI设备的速度。如果命令是"write",则向SPI设备写入数据。如果命令是"read",则从SPI设备读取数据。如果命令是"loop",则进行SPI设备的循环测试。如果命令不匹配上述任何一个条件,则打印命令使用说明。
  4. 当命令是"setspeed"时,代码会解析参数并设置指定的SPI设备的速度。
    • 使用sscanf函数从参数数组argv中读取idval的值,并将其存储在相应的变量中。
    • 检查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中读取idtimessize的值,并将其存储在相应的变量中。
    • 如果参数个数大于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中读取idtimessize的值,并将其存储在相应的变量中。
    • 将循环测试的数据填充到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设备的同步传输操作,将spim作为参数传入。该函数会阻塞直到传输完成。。

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。

常见问题

  1. 调试前确认驱动有跑起来
  2. 确保 SPI 4 个引脚的 IOMUX 配置无误
  3. 确认 TX 送时,TX 引脚有正常的波形,CLK 有正常的 CLOCK 信号,CS 信号有拉低
  4. 如果 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", &times);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", &times);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", &times);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");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/187079.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

trait 特征

trait&#xff08;特征&#xff09;RUST用来以一种抽象的方式来定义共享行为&#xff0c;还可以使用trait约束用来将泛型参数指定为实现了某些特征行为的类型。通过trait将特定方法签名组合起来&#xff0c;用来实现某种目的所必须的行为集合。 pub trait Summary {fn summari…

中国技协城市主产业职业技能(上海)联赛暨全 国网络与信息安全管理员职工职业技能竞赛—线上赛初赛a

目录 一、理论题 二、CTF 1.赛前测试:f12_me 2.WEB:VersionControl 3.MISC:SecretDocume 4.Reverse:pyc

基于单片机的排队叫号系统设计

1&#xff0e;设计任务 利用AT89C51单片机为核心控制元件,设计一个节日彩灯门&#xff0c;设计的系统实用性强、操作简单&#xff0c;实现了智能化、数字化。 基本要求&#xff1a;利用单片机AT89C51设计排队叫号机&#xff0c;能实现叫号功能。 创新&#xff1a;能显示叫号…

猫头虎分享ubuntu20.04下VSCode无法输入中文解决方法

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

qt 5.15.2 的编译部署quazip-0.7.3项目过程

qt 5.15.2 的编译部署quazip-0.7.3项目过程 &#xff08;一&#xff09;先下载quazip-0.7.3项目源代码 下载地址&#xff1a;https://sourceforge.net/projects/quazip/ 更新版本参考地址下载&#xff1a;https://github.com/stachenov/quazip (二) 用qt creater打开下载的q…

【网络控制】

网络控制 “网络控制”&#xff08;Network Control&#xff09;通常指的是通过网络对远程设备、系统或服务进行管理和监控的过程。这可以包括远程设备的配置、监测、故障排除、升级等操作。网络控制的目的是在分布式环境中实现对各种网络设备和资源的远程管理。 网络控制的关…

【EasyExcel】导出excel并支持自定义设置数据行背景颜色等

需求背景&#xff1a; 根据查询条件将列表数据导出&#xff0c;并筛选出满足某个条件的数据&#xff0c;将满足条件的数据的背景颜色设置成黄色。 &#xff08;本文例子如&#xff1a;name出现的次数大于等于2&#xff0c;将相关数据背景颜色都设置为黄色&#xff09; …

基于C++11实现通过/sys/class/net/eth1/operstate文件获取eth1网口

在Linux系统中&#xff0c;网口&#xff08;网络接口&#xff09;的状态信息可以在/sys/class/net/<interface>/operstate文件中查看。其中&#xff0c;<interface>是您要查看状态的具体网口名称。 例如&#xff0c;要查看eth1网口的状态&#xff0c;可以使用以下…

算法通关村第一关—链表高频面试题(白银)

链表高频面试题 一、五种方法解决两个链表的第一个公共子节点的问题 面试 02.07.链表相交1.首先想到的是暴力解&#xff0c;将第一个链表中的每一个结点依次与第二个链表的进行比较&#xff0c;当出现相等的结点指针时&#xff0c;即为相交结点。虽然简单&#xff0c;但是时间…

常见智力题汇总

常见智力题汇总 扔瓶子问题扑克牌问题出队问题烧绳子问题赛马问题求出前三名求出前五名 接水问题种树问题硬币问题宝石问题核酸检测问题 笔者最近面试遇到了好几道智力题&#xff0c;这些题目特点就是如果没有见过&#xff0c;很难第一时间思考得到答案&#xff0c;因此笔者面试…

VUE2+THREE.JS项目搭建

THREE项目搭建 简介学习文档推荐搭建1.下载three.js2.新建3DWorkShop.vue文件3.创建utils/three/tool.js4.创建components/three/draw.vue[重点]4.1 引入文件4.2 初始化场景4.3 初始化渲染器4.4 初始化光源4.5 初始化相机(人眼模式)4.6 初始化控制器4.7 初始化动画4.8 添加全局…

功率信号源简介及其应用有哪些内容

功率信号源是一种能够提供稳定输出功率信号的设备或电路。它在许多领域中都有广泛的应用。以下是一些关于功率信号源的内容&#xff1a; 功率信号源简介&#xff1a;功率信号源是一种电子设备或电路&#xff0c;它能够提供稳定的输出功率信号。功率信号源通常由放大器、稳压器、…

rest_framework_django学习笔记一(序列化器)

rest_framework_django学习笔记一(序列化器) 一、引入Django Rest Framework 1、安装 pip install djangorestframework2、引入 INSTALLED_APPS [...rest_framework, ]3、原始RESTful接口写法 models.py from django.db import models 测试数据 仅供参考 INSERT INTO de…

力扣:1419. 数青蛙

题目&#xff1a; 代码&#xff1a; class Solution { public:int minNumberOfFrogs(string croakOfFrogs){string s "croak";int ns.size();//首先创建一个哈希表来标明每个元素出现的次数&#xff01;vector<int>hash(n); //不用真的创建一个hash表用一个数…

如何使用高防CDN防护HTTPS 攻击?有什么优势?

随着互联网的普及&#xff0c;网络安全问题也日益凸显。其中&#xff0c;HTTPS 攻击是一种常见的网络安全威胁&#xff0c;它通过篡改网站数据、窃取用户信息等方式&#xff0c;给网站带来巨大的风险。为了有效防御 HTTPS 攻击&#xff0c;高防 CDN 成为了一个重要的工具。下面…

(项目已开源)社区求助 哪位大佬能不能帮我 将box1 audio 和 box2 slider滑块 和 box3 歌词滚动区域 进行联动

(项目已开源)社区求助 哪位大佬能不能帮我 将box1 audio 和 box2 slider滑块 和 box3 歌词滚动区域 进行联动 链接&#xff1a;https://pan.baidu.com/s/16lpEW6L5jrHfhsG7EXocLw?pwdkryy 提取码&#xff1a;kryy <!--社区求助 哪位大佬能不能帮我 将box1 audio 和 box2 s…

传统算法: Pygame 实现一个简单的二阶马尔可夫链的状态转移过程

使用 Pygame 演示了一个简单的二阶马尔可夫链的状态转移过程。以下是代码的主要步骤和原理解释: 1、初始化和基本设置 Pygame 初始化: 通过 pygame.init() 初始化 Pygame。 定义颜色和屏幕大小: 定义了一些颜色常量(WHITE, BLACK, RED, GREEN, BLUE)和屏幕的宽度和高度。…

Nginx实现(动静分离)

动静分离应该是听的次数较多的性能优化方案&#xff0c;那先思考一个问题&#xff1a;「「为什么需要做动静分离呢&#xff1f;它带来的好处是什么&#xff1f;」」 其实这个问题也并不难回答&#xff0c;当你搞懂了网站的本质后&#xff0c;自然就理解了动静分离的重要性。先来…

通过C#获取Windows设置的夏令时开关

C#获取Windows夏令时开关 // 获取所有的时区信息 var allTimeZones TimeZoneInfo.GetSystemTimeZones().ToList();通过接口可以看到, 字段SupportsDaylightSavingTime代表是否支持配置夏令时 // 获取当前Window设置的时区 var tmpLocal TimeZoneInfo.Local;但是取Local 信息…

Oracle 单表插入/多表插入(Single Table Insert/Multi-table Insert)

数据库应用中&#xff0c;我们经常需要向表中插入数据&#xff0c;insert语句是最常用的数据插入方式&#xff0c;根据目标表的数量&#xff0c;可以分为单表插入和多表插入。 目录 一、 单表插入&#xff08;Single Table Insert&#xff09; 二、 多表插入&#xff08;Multi-…