【驱动】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 :设置要使用的SPI设备,默认为/dev/spidev1.0
  • -s, --speed :设置SPI时钟速度,单位为Hz。
  • -d, --delay :设置SPI传输之间的延迟时间,单位为微秒。
  • -b, --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 :执行一个SPI传输,发送给定的数据字节。
  • -r, --read :执行一个SPI读传输,读取指定数量的字节。
  • -w, --write :执行一个SPI写传输,发送给定的数据字节。
  • -f, --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 
 *
 * 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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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 ");
MODULE_AUTHOR("Huibin Hong ");
MODULE_DESCRIPTION("ROCKCHIP SPI TEST Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("spi:spi_test");

你可能感兴趣的:(#,SPI驱动,嵌入式硬件,linux,c语言,嵌入式)