528 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * SPI testing utility (using spidev driver)4 *5 * Copyright (c) 2007 MontaVista Software, Inc.6 * Copyright (c) 2007 Anton Vorontsov <avorontsov@ru.mvista.com>7 *8 * Cross-compile with cross-gcc -I/path/to/cross-kernel/include9 */10 11#include <stdint.h>12#include <unistd.h>13#include <stdio.h>14#include <stdlib.h>15#include <string.h>16#include <errno.h>17#include <getopt.h>18#include <fcntl.h>19#include <time.h>20#include <sys/ioctl.h>21#include <linux/ioctl.h>22#include <sys/stat.h>23#include <linux/types.h>24#include <linux/spi/spidev.h>25 26#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))27 28static void pabort(const char *s)29{30 if (errno != 0)31 perror(s);32 else33 printf("%s\n", s);34 35 abort();36}37 38static const char *device = "/dev/spidev1.1";39static uint32_t mode;40static uint8_t bits = 8;41static char *input_file;42static char *output_file;43static uint32_t speed = 500000;44static uint16_t delay;45static int verbose;46static int transfer_size;47static int iterations;48static int interval = 5; /* interval in seconds for showing transfer rate */49 50static uint8_t default_tx[] = {51 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,52 0x40, 0x00, 0x00, 0x00, 0x00, 0x95,53 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,54 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,55 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,56 0xF0, 0x0D,57};58 59static uint8_t default_rx[ARRAY_SIZE(default_tx)] = {0, };60static char *input_tx;61 62static void hex_dump(const void *src, size_t length, size_t line_size,63 char *prefix)64{65 int i = 0;66 const unsigned char *address = src;67 const unsigned char *line = address;68 unsigned char c;69 70 printf("%s | ", prefix);71 while (length-- > 0) {72 printf("%02X ", *address++);73 if (!(++i % line_size) || (length == 0 && i % line_size)) {74 if (length == 0) {75 while (i++ % line_size)76 printf("__ ");77 }78 printf(" |");79 while (line < address) {80 c = *line++;81 printf("%c", (c < 32 || c > 126) ? '.' : c);82 }83 printf("|\n");84 if (length > 0)85 printf("%s | ", prefix);86 }87 }88}89 90/*91 * Unescape - process hexadecimal escape character92 * converts shell input "\x23" -> 0x2393 */94static int unescape(char *_dst, char *_src, size_t len)95{96 int ret = 0;97 int match;98 char *src = _src;99 char *dst = _dst;100 unsigned int ch;101 102 while (*src) {103 if (*src == '\\' && *(src+1) == 'x') {104 match = sscanf(src + 2, "%2x", &ch);105 if (!match)106 pabort("malformed input string");107 108 src += 4;109 *dst++ = (unsigned char)ch;110 } else {111 *dst++ = *src++;112 }113 ret++;114 }115 return ret;116}117 118static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)119{120 int ret;121 int out_fd;122 struct spi_ioc_transfer tr = {123 .tx_buf = (unsigned long)tx,124 .rx_buf = (unsigned long)rx,125 .len = len,126 .delay_usecs = delay,127 .speed_hz = speed,128 .bits_per_word = bits,129 };130 131 if (mode & SPI_TX_OCTAL)132 tr.tx_nbits = 8;133 else if (mode & SPI_TX_QUAD)134 tr.tx_nbits = 4;135 else if (mode & SPI_TX_DUAL)136 tr.tx_nbits = 2;137 if (mode & SPI_RX_OCTAL)138 tr.rx_nbits = 8;139 else if (mode & SPI_RX_QUAD)140 tr.rx_nbits = 4;141 else if (mode & SPI_RX_DUAL)142 tr.rx_nbits = 2;143 if (!(mode & SPI_LOOP)) {144 if (mode & (SPI_TX_OCTAL | SPI_TX_QUAD | SPI_TX_DUAL))145 tr.rx_buf = 0;146 else if (mode & (SPI_RX_OCTAL | SPI_RX_QUAD | SPI_RX_DUAL))147 tr.tx_buf = 0;148 }149 150 ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);151 if (ret < 1)152 pabort("can't send spi message");153 154 if (verbose)155 hex_dump(tx, len, 32, "TX");156 157 if (output_file) {158 out_fd = open(output_file, O_WRONLY | O_CREAT | O_TRUNC, 0666);159 if (out_fd < 0)160 pabort("could not open output file");161 162 ret = write(out_fd, rx, len);163 if (ret != len)164 pabort("not all bytes written to output file");165 166 close(out_fd);167 }168 169 if (verbose)170 hex_dump(rx, len, 32, "RX");171}172 173static void print_usage(const char *prog)174{175 printf("Usage: %s [-2348CDFHILMNORSZbdilopsv]\n", prog);176 puts("general device settings:\n"177 " -D --device device to use (default /dev/spidev1.1)\n"178 " -s --speed max speed (Hz)\n"179 " -d --delay delay (usec)\n"180 " -l --loop loopback\n"181 "spi mode:\n"182 " -H --cpha clock phase\n"183 " -O --cpol clock polarity\n"184 " -F --rx-cpha-flip flip CPHA on Rx only xfer\n"185 "number of wires for transmission:\n"186 " -2 --dual dual transfer\n"187 " -4 --quad quad transfer\n"188 " -8 --octal octal transfer\n"189 " -3 --3wire SI/SO signals shared\n"190 " -Z --3wire-hiz high impedance turnaround\n"191 "data:\n"192 " -i --input input data from a file (e.g. \"test.bin\")\n"193 " -o --output output data to a file (e.g. \"results.bin\")\n"194 " -p Send data (e.g. \"1234\\xde\\xad\")\n"195 " -S --size transfer size\n"196 " -I --iter iterations\n"197 "additional parameters:\n"198 " -b --bpw bits per word\n"199 " -L --lsb least significant bit first\n"200 " -C --cs-high chip select active high\n"201 " -N --no-cs no chip select\n"202 " -R --ready slave pulls low to pause\n"203 " -M --mosi-idle-low leave mosi line low when idle\n"204 "misc:\n"205 " -v --verbose Verbose (show tx buffer)\n");206 exit(1);207}208 209static void parse_opts(int argc, char *argv[])210{211 while (1) {212 static const struct option lopts[] = {213 { "device", 1, 0, 'D' },214 { "speed", 1, 0, 's' },215 { "delay", 1, 0, 'd' },216 { "loop", 0, 0, 'l' },217 { "cpha", 0, 0, 'H' },218 { "cpol", 0, 0, 'O' },219 { "rx-cpha-flip", 0, 0, 'F' },220 { "dual", 0, 0, '2' },221 { "quad", 0, 0, '4' },222 { "octal", 0, 0, '8' },223 { "3wire", 0, 0, '3' },224 { "3wire-hiz", 0, 0, 'Z' },225 { "input", 1, 0, 'i' },226 { "output", 1, 0, 'o' },227 { "size", 1, 0, 'S' },228 { "iter", 1, 0, 'I' },229 { "bpw", 1, 0, 'b' },230 { "lsb", 0, 0, 'L' },231 { "cs-high", 0, 0, 'C' },232 { "no-cs", 0, 0, 'N' },233 { "ready", 0, 0, 'R' },234 { "mosi-idle-low", 0, 0, 'M' },235 { "verbose", 0, 0, 'v' },236 { NULL, 0, 0, 0 },237 };238 int c;239 240 c = getopt_long(argc, argv, "D:s:d:b:i:o:lHOLC3ZFMNR248p:vS:I:",241 lopts, NULL);242 243 if (c == -1)244 break;245 246 switch (c) {247 case 'D':248 device = optarg;249 break;250 case 's':251 speed = atoi(optarg);252 break;253 case 'd':254 delay = atoi(optarg);255 break;256 case 'b':257 bits = atoi(optarg);258 break;259 case 'i':260 input_file = optarg;261 break;262 case 'o':263 output_file = optarg;264 break;265 case 'l':266 mode |= SPI_LOOP;267 break;268 case 'H':269 mode |= SPI_CPHA;270 break;271 case 'O':272 mode |= SPI_CPOL;273 break;274 case 'L':275 mode |= SPI_LSB_FIRST;276 break;277 case 'C':278 mode |= SPI_CS_HIGH;279 break;280 case '3':281 mode |= SPI_3WIRE;282 break;283 case 'Z':284 mode |= SPI_3WIRE_HIZ;285 break;286 case 'F':287 mode |= SPI_RX_CPHA_FLIP;288 break;289 case 'M':290 mode |= SPI_MOSI_IDLE_LOW;291 break;292 case 'N':293 mode |= SPI_NO_CS;294 break;295 case 'v':296 verbose = 1;297 break;298 case 'R':299 mode |= SPI_READY;300 break;301 case 'p':302 input_tx = optarg;303 break;304 case '2':305 mode |= SPI_TX_DUAL;306 break;307 case '4':308 mode |= SPI_TX_QUAD;309 break;310 case '8':311 mode |= SPI_TX_OCTAL;312 break;313 case 'S':314 transfer_size = atoi(optarg);315 break;316 case 'I':317 iterations = atoi(optarg);318 break;319 default:320 print_usage(argv[0]);321 }322 }323 if (mode & SPI_LOOP) {324 if (mode & SPI_TX_DUAL)325 mode |= SPI_RX_DUAL;326 if (mode & SPI_TX_QUAD)327 mode |= SPI_RX_QUAD;328 if (mode & SPI_TX_OCTAL)329 mode |= SPI_RX_OCTAL;330 }331}332 333static void transfer_escaped_string(int fd, char *str)334{335 size_t size = strlen(str);336 uint8_t *tx;337 uint8_t *rx;338 339 tx = malloc(size);340 if (!tx)341 pabort("can't allocate tx buffer");342 343 rx = malloc(size);344 if (!rx)345 pabort("can't allocate rx buffer");346 347 size = unescape((char *)tx, str, size);348 transfer(fd, tx, rx, size);349 free(rx);350 free(tx);351}352 353static void transfer_file(int fd, char *filename)354{355 ssize_t bytes;356 struct stat sb;357 int tx_fd;358 uint8_t *tx;359 uint8_t *rx;360 361 if (stat(filename, &sb) == -1)362 pabort("can't stat input file");363 364 tx_fd = open(filename, O_RDONLY);365 if (tx_fd < 0)366 pabort("can't open input file");367 368 tx = malloc(sb.st_size);369 if (!tx)370 pabort("can't allocate tx buffer");371 372 rx = malloc(sb.st_size);373 if (!rx)374 pabort("can't allocate rx buffer");375 376 bytes = read(tx_fd, tx, sb.st_size);377 if (bytes != sb.st_size)378 pabort("failed to read input file");379 380 transfer(fd, tx, rx, sb.st_size);381 free(rx);382 free(tx);383 close(tx_fd);384}385 386static uint64_t _read_count;387static uint64_t _write_count;388 389static void show_transfer_rate(void)390{391 static uint64_t prev_read_count, prev_write_count;392 double rx_rate, tx_rate;393 394 rx_rate = ((_read_count - prev_read_count) * 8) / (interval*1000.0);395 tx_rate = ((_write_count - prev_write_count) * 8) / (interval*1000.0);396 397 printf("rate: tx %.1fkbps, rx %.1fkbps\n", rx_rate, tx_rate);398 399 prev_read_count = _read_count;400 prev_write_count = _write_count;401}402 403static void transfer_buf(int fd, int len)404{405 uint8_t *tx;406 uint8_t *rx;407 int i;408 409 tx = malloc(len);410 if (!tx)411 pabort("can't allocate tx buffer");412 for (i = 0; i < len; i++)413 tx[i] = random();414 415 rx = malloc(len);416 if (!rx)417 pabort("can't allocate rx buffer");418 419 transfer(fd, tx, rx, len);420 421 _write_count += len;422 _read_count += len;423 424 if (mode & SPI_LOOP) {425 if (memcmp(tx, rx, len)) {426 fprintf(stderr, "transfer error !\n");427 hex_dump(tx, len, 32, "TX");428 hex_dump(rx, len, 32, "RX");429 exit(1);430 }431 }432 433 free(rx);434 free(tx);435}436 437int main(int argc, char *argv[])438{439 int ret = 0;440 int fd;441 uint32_t request;442 443 parse_opts(argc, argv);444 445 if (input_tx && input_file)446 pabort("only one of -p and --input may be selected");447 448 fd = open(device, O_RDWR);449 if (fd < 0)450 pabort("can't open device");451 452 /*453 * spi mode454 */455 /* WR is make a request to assign 'mode' */456 request = mode;457 ret = ioctl(fd, SPI_IOC_WR_MODE32, &mode);458 if (ret == -1)459 pabort("can't set spi mode");460 461 /* RD is read what mode the device actually is in */462 ret = ioctl(fd, SPI_IOC_RD_MODE32, &mode);463 if (ret == -1)464 pabort("can't get spi mode");465 /* Drivers can reject some mode bits without returning an error.466 * Read the current value to identify what mode it is in, and if it467 * differs from the requested mode, warn the user.468 */469 if (request != mode)470 printf("WARNING device does not support requested mode 0x%x\n",471 request);472 473 /*474 * bits per word475 */476 ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);477 if (ret == -1)478 pabort("can't set bits per word");479 480 ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);481 if (ret == -1)482 pabort("can't get bits per word");483 484 /*485 * max speed hz486 */487 ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);488 if (ret == -1)489 pabort("can't set max speed hz");490 491 ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);492 if (ret == -1)493 pabort("can't get max speed hz");494 495 printf("spi mode: 0x%x\n", mode);496 printf("bits per word: %u\n", bits);497 printf("max speed: %u Hz (%u kHz)\n", speed, speed/1000);498 499 if (input_tx)500 transfer_escaped_string(fd, input_tx);501 else if (input_file)502 transfer_file(fd, input_file);503 else if (transfer_size) {504 struct timespec last_stat;505 506 clock_gettime(CLOCK_MONOTONIC, &last_stat);507 508 while (iterations-- > 0) {509 struct timespec current;510 511 transfer_buf(fd, transfer_size);512 513 clock_gettime(CLOCK_MONOTONIC, ¤t);514 if (current.tv_sec - last_stat.tv_sec > interval) {515 show_transfer_rate();516 last_stat = current;517 }518 }519 printf("total: tx %.1fKB, rx %.1fKB\n",520 _write_count/1024.0, _read_count/1024.0);521 } else522 transfer(fd, default_tx, default_rx, sizeof(default_tx));523 524 close(fd);525 526 return ret;527}528