用户态

用户应用层使用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 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;
}
  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 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", &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");

【驱动】SPI驱动分析(七)-SPI驱动常用调试方法的更多相关文章

  1. VC的常用调试方法

    前言 VS是非常强大的IDE,所以掌握VSVC的常用方法,将会使得我们找出问题解决问题事半功倍. 目录 VSVC的常用调试方法 前言 1. Watch窗口查看伪变量 2. 查看指针指向的一序列值 3. ...

  2. shell 脚本常用调试方法

    曾经我刚开始学习 shell 脚本时,除了知道用 echo 输出一些信息外,并不知道其他方法,仅仅依赖 echo 来查找错误,比较难调试且过程繁琐.效率低下.本文介绍下我常用的一些 shell 脚本调 ...

  3. keil 常用调试方法

    1.内存调试方法, 参考链接: https://blog.51cto.com/u_4029519/5423341 2.汇编调试方法 常用到bootlader和应用程序的调试 1.生成bin文件.汇编文 ...

  4. Makefile常用调试方法

    转载自 陈皓<跟我一起写 Makefile><GNU Make项目管理> GNU make 提供了若干可以协助调试的内置函数以及命令行选项. 1.warning函数 $(war ...

  5. 【转】 GDB 常用调试方法

    一.多线程调试 多线程调试可能是问得最多的.其实,重要就是下面几个命令: info thread 查看当前进程的线程. thread <ID> 切换调试的线程为指定ID的线程. break ...

  6. iPhone页面的常用调试方法

    在iPhone中调试,大体上与上文 安卓中的移动页面调试 类似,区别主要是iOS系统中的一些限制,导致某些工具无法使用. 本文基于此,简要介绍在iPhone中如何调试页面. 最终可以实现在Mac平台使 ...

  7. onvif规范的实现:onvif开发常用调试方法 和常见的segmentation fault错误

    在前几篇中,虽然已经实现了rtsp视频流的对接,但是还要做的工作还非常多,onvif本来就是一个覆盖面非常广的一个协议,每一个功能都要填充大量的函数.而且稍不注意就会出现segmentation fa ...

  8. sqlprofiler 常用调试方法

  9. shell常用调试方法

    检查语法 -n选项只做语法检查,而不执行脚本. sh -n script_name.sh 启动调试 sh -x script_name.s 进入调试模式后,Shell依次执行读入的语句,产生的输出中有 ...

  10. 基于S3C2440的嵌入式Linux驱动——看门狗(watchdog)驱动解读

    本文将介绍看门狗驱动的实现. 目标平台:TQ2440 CPU:s3c2440 内核版本:2.6.30 1. 看门狗概述 看门狗其实就是一个定时器,当该定时器溢出前必须对看门狗进行"喂狗“,如 ...

随机推荐

  1. Nginx根据Origin配置禁止跨域访问策略

    产品需要通过某所的安全测评扫描,其中提出一个关于跨域策略配置不当的问题,如下: 这个需要根据客户端传递的请求头中的Origin值,进行安全的跨站策略配置,目的是对非法的origin直接返回403错误页 ...

  2. [AGC59C] Guessing Permutation for as Long as Possible

    Problem Statement A teacher has a hidden permutation $P=(P_1,P_2,\ldots,P_N)$ of $(1,2,\cdots,N)$. Y ...

  3. Storm 集群的搭建及其Java编程进行简单统计计算

    一.Storm集群构建 编写storm 与 zookeeper的yml文件 storm yml文件的编写 具体如下: version: '2' services: zookeeper1: image: ...

  4. 数据智慧:C#中编程实现自定义计算的Excel数据透视表

    前言 数据透视表(Pivot Table)是一种数据分析工具,通常用于对大量数据进行汇总.分析和展示.它可以帮助用户从原始数据中提取关键信息.发现模式和趋势,并以可视化的方式呈现. 在数据透视表中,数 ...

  5. NLP复习之朴素贝叶斯

    朴素贝叶斯分类器和加一平滑计算每个单词的似然值 贝叶斯规则:c表示类别,d表示数据 \[P(c|d) = \frac{P(d|c)P(c)}{P(d)} \] 例题1 假设句子"I alwa ...

  6. cmake的安装方法

    最近参与一个新项目,这个项目使用cmake作为构建系统.作为Java程序员,平常都使用ant或者maven来构建,难得有机会接触cmake之类的工具,所以参与这个项目是个学习cmake的好机会. 但干 ...

  7. ASR项目实战-前处理

    本文深入探讨前处理环节. 首先介绍一些基本的名词,比如 文件名后缀 文件格式 音频格式 采样率和位深 预备知识 文件名后缀.文件格式和音频格式 常见的音频文件,比如.wav..mp3..m4a..wm ...

  8. ElasticSearch之cat health API

    命令样例如下: curl -X GET "https://localhost:9200/_cat/health?v=true&pretty" --cacert $ES_HO ...

  9. 从零玩转SpringSecurity+JWT整合前后端分离-从零玩转springsecurityjwt整合前后端分离

    title: 从零玩转SpringSecurity+JWT整合前后端分离 date: 2021-05-06 14:56:57.699 updated: 2021-12-26 17:43:19.478 ...

  10. Mybatis源码5 StatementHandler ,ParameterHandler

    Mybatis5 StatementHandler ,ParameterHandler 一丶概述 前面我们总结了SqlSession--->CachingExecutor--->BaseE ...