1 /* $NetBSD: scmdctl.c,v 1.1 2021/12/07 17:39:55 brad Exp $ */
2
3 /*
4 * Copyright (c) 2021 Brad Spencer <brad@anduin.eldar.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/cdefs.h>
20 #ifdef __RCSID
21 __RCSID("$NetBSD: scmdctl.c,v 1.1 2021/12/07 17:39:55 brad Exp $");
22 #endif
23
24 /* Main userland program that knows how to talk to the Sparkfun
25 * Serial Controlled Motor Driver (SCMD). The device provides
26 * 127 registers that are used to interact with the motors.
27 * This program provides some convience commands to work with most
28 * of the abilities of the SCMD device.
29 *
30 * This knows how to talk to a SCMD device via:
31 *
32 * 1) The uart tty interface that is provided by the SCMD device
33 * 2) Userland SPI talking to something like /dev/spi0 directly
34 * In most ways this acts like talking to the tty uart.
35 * 3) Using the scmd(4) i2c or spi driver. This is, by far, the
36 * fastest way to access the driver. The other methods have
37 * increased latency.
38 */
39
40 #include <inttypes.h>
41 #include <stdbool.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <unistd.h>
45 #include <err.h>
46 #include <fcntl.h>
47 #include <string.h>
48 #include <limits.h>
49 #include <termios.h>
50 #include <sys/ioctl.h>
51 #include <sys/time.h>
52 #include <dev/spi/spi_io.h>
53
54 #include <dev/ic/scmdreg.h>
55
56 #define EXTERN extern
57 #include "common.h"
58 #include "scmdctl.h"
59 #include "uart.h"
60 #include "i2cspi.h"
61 #include "printscmd.h"
62 #include "responses.h"
63 #include "scmdctlconst.h"
64
65 int ul_spisetup(int, int);
66 int ttysetup(int, speed_t);
67 int valid_cmd(const struct scmdcmd[], long unsigned int, char *);
68
69
70 static void
usage(void)71 usage(void)
72 {
73 const char *p = getprogname();
74
75 fprintf(stderr, "Usage: %s [-dlh] [-b baud rate] [-s SPI slave addr] device cmd args\n\n",
76 p);
77
78 for(long unsigned int i = 0;i < __arraycount(scmdcmds);i++) {
79 fprintf(stderr,"%s [-dlh] [-b baud rate] [-s SPI slave addr] device %s %s\n",
80 p,scmdcmds[i].cmd,scmdcmds[i].helpargs);
81 }
82 }
83
84 int
valid_cmd(const struct scmdcmd c[],long unsigned int csize,char * cmdtocheck)85 valid_cmd(const struct scmdcmd c[], long unsigned int csize, char *cmdtocheck)
86 {
87 int r = -1;
88
89 for(long unsigned int i = 0;i < csize;i++) {
90 if (strncmp(cmdtocheck,c[i].cmd,16) == 0) {
91 r = i;
92 break;
93 }
94 }
95
96 return r;
97 }
98
99 /* This is expected to fail if the device is not a classic tty */
100 int
ttysetup(int fd,speed_t spd)101 ttysetup(int fd, speed_t spd)
102 {
103 struct termios cntrl;
104
105 (void)tcgetattr(fd, &cntrl);
106 (void)cfsetospeed(&cntrl, spd);
107 (void)cfsetispeed(&cntrl, spd);
108 cntrl.c_cflag &= ~(CSIZE|PARENB);
109 cntrl.c_cflag |= CS8;
110 cntrl.c_cflag |= CLOCAL;
111 cntrl.c_iflag &= ~(ISTRIP|ICRNL);
112 cntrl.c_oflag &= ~OPOST;
113 cntrl.c_lflag &= ~(ICANON|ISIG|IEXTEN|ECHO);
114 cntrl.c_cc[VMIN] = 1;
115 cntrl.c_cc[VTIME] = 0;
116 cntrl.c_iflag &= ~(IXOFF|IXON);
117 return tcsetattr(fd, TCSAFLUSH, &cntrl);
118 }
119
120 /* This is for userland SPI and is expected to fail if the device is
121 * not a /dev/spiN
122 */
123 int
ul_spisetup(int fd,int slave_addr)124 ul_spisetup(int fd, int slave_addr)
125 {
126 struct timespec ts;
127 struct spi_ioctl_configure spi_c;
128 int e;
129
130 spi_c.sic_addr = slave_addr;
131 #define SPI_MODE_0 0
132 #define SPI_MODE_1 1
133 #define SPI_MODE_2 2
134 #define SPI_MODE_3 3
135 spi_c.sic_mode = SPI_MODE_0;
136 spi_c.sic_speed = 1000000;
137
138 e = ioctl(fd,SPI_IOCTL_CONFIGURE,&spi_c);
139 if (e != -1) {
140 ts.tv_sec = 0;
141 ts.tv_nsec = 50;
142 nanosleep(&ts,NULL);
143 }
144
145 return e;
146 }
147
148 int
main(int argc,char * argv[])149 main(int argc, char *argv[])
150 {
151 int c;
152 bool debug = false;
153 int fd = -1, error, ttyerror = 0, ul_spierror = 0, valid, validsub = -1;
154 long baud_rate = 9600;
155 long slave_a = 0;
156 bool dev_is_uart = true;
157 int uart_s = UART_IS_PURE_UART;
158 struct scmd_identify_response ir;
159 struct scmd_diag_response diag;
160 struct scmd_motor_response motors;
161 long module;
162 char motor;
163 int8_t reg_value;
164 uint8_t reg = 0, reg_e = 0, ur, ebus_s, lock_state;
165 uint8_t register_shadow[SCMD_REG_SIZE];
166 int lock_type = -1;
167 bool list_names = false;
168 struct function_block func_block;
169 extern char *optarg;
170 extern int optind;
171
172 while ((c = getopt(argc, argv, "db:s:lh")) != -1 ) {
173 switch (c) {
174 case 'd':
175 debug = true;
176 break;
177 case 'b':
178 baud_rate = (long)strtoi(optarg, NULL, 0, 1, LONG_MAX, &error);
179 if (error)
180 warnc(error, "Conversion of `%s' to a baud rate "
181 "failed, using %ld", optarg, baud_rate);
182 break;
183 case 's':
184 slave_a = (long)strtoi(optarg, NULL, 0, 0, LONG_MAX, &error);
185 if (error)
186 warnc(error, "Conversion of `%s' to a SPI slave address "
187 "failed, using %ld", optarg, slave_a);
188 break;
189 case 'l':
190 list_names = true;
191 break;
192 case 'h':
193 default:
194 usage();
195 exit(0);
196 }
197 }
198
199 argc -= optind;
200 argv += optind;
201
202 if (debug) {
203 fprintf(stderr,"ARGC: %d\n", argc);
204 fprintf(stderr,"ARGV[0]: %s ; ARGV[1]: %s ; ARGV[2]: %s ; ARGV[3]: %s; ARGV[4]: %s; ARGV[5]: %s\n",
205 argv[0],argv[1],argv[2],argv[3],argv[4],argv[5]);
206 }
207
208 if (list_names) {
209 for(c = 0x00; c < SCMD_REG_SIZE;c++)
210 printf("Register %d (0x%02X): %s\n",c,c,scmdregisternames[c]);
211 exit(0);
212 }
213
214 if (argc <= 1) {
215 usage();
216 exit(0);
217 }
218
219 fd = open(argv[0], O_RDWR, 0);
220 if (fd == -1) {
221 err(EXIT_FAILURE, "open %s", argv[0]);
222 }
223
224 /* Figure out what the device is. First try uart tty,
225 * then SPI userland and the if those two fail, assume
226 * scmd(4).
227 */
228 ttyerror = ttysetup(fd,(speed_t)baud_rate);
229
230 if (ttyerror) {
231 ul_spierror = ul_spisetup(fd, slave_a);
232 if (ul_spierror) {
233 dev_is_uart = false;
234 } else {
235 uart_s = UART_IS_SPI_USERLAND;
236 }
237 }
238 uart_set_subtype(uart_s, slave_a);
239
240 if (debug) {
241 fprintf(stderr, "ttysetup: error return %d\n", ttyerror);
242 fprintf(stderr, "ul_spisetup: error return %d\n", ul_spierror);
243 }
244
245 /* A UART here is either a tty uart or a SPI userland device.
246 * They mostly end up working the same.
247 */
248 if (dev_is_uart) {
249 func_block.func_clear = &uart_clear;
250 func_block.func_phy_read = &uart_read_register;
251 func_block.func_phy_write = &uart_write_register;
252 } else {
253 func_block.func_clear = &i2cspi_clear;
254 func_block.func_phy_read = &i2cspi_read_register;
255 func_block.func_phy_write = &i2cspi_write_register;
256 }
257
258 valid = valid_cmd(scmdcmds,__arraycount(scmdcmds),argv[1]);
259
260 if (valid != -1) {
261 switch (scmdcmds[valid].id) {
262 case SCMD_IDENTIFY:
263 module = 0;
264 if (argc == 3) {
265 module = (long)strtoi(argv[2], NULL, 10, 0, 16, &error);
266 if (error)
267 warnc(error, "Conversion of '%s' module failed,"
268 " using %ld", argv[2], module);
269 }
270 error = common_identify(&func_block, fd, debug, module, &ir);
271 break;
272 case SCMD_DIAG:
273 module = 0;
274 if (argc == 3) {
275 module = (long)strtoi(argv[2], NULL, 10, 0, 16, &error);
276 if (error)
277 warnc(error, "Conversion of '%s' module failed,"
278 " using %ld", argv[2], module);
279 }
280 error = common_diag(&func_block, fd, debug, module, &diag);
281 break;
282 case SCMD_MOTOR:
283 if (argc >= 3) {
284 validsub = valid_cmd(motorsubcmds,__arraycount(motorsubcmds),argv[2]);
285 if (validsub != -1) {
286 switch (motorsubcmds[validsub].id) {
287 case SCMD_SUBMOTORGET:
288 module = SCMD_ANY_MODULE;
289 if (argc == 4) {
290 module = (long)strtoi(argv[3], NULL, 10, 0, 16, &error);
291 if (error)
292 warnc(error, "Conversion of '%s' module failed,"
293 " using %ld", argv[3], module);
294 }
295 error = common_get_motor(&func_block, fd, debug, (int)module, &motors);
296 break;
297 case SCMD_SUBMOTORSET:
298 if (argc == 6) {
299 module = (long)strtoi(argv[3], NULL, 10, 0, 16, &error);
300 if (error)
301 warnc(error, "Conversion of '%s' module failed,"
302 " using %ld", argv[3], module);
303 motor = argv[4][0];
304 reg_value = (int8_t)strtoi(argv[5], NULL, 0, -127, 127, &error);
305 if (error)
306 err(EXIT_FAILURE,"Bad conversion for set motor for reg_value: %s", argv[5]);
307 } else {
308 fprintf(stderr,"Missing arguments to set motor command\n\n");
309 usage();
310 exit(1);
311 }
312 error = common_set_motor(&func_block, fd, debug, (int)module, motor, reg_value);
313 break;
314 case SCMD_SUBMOTORINVERT:
315 if (argc == 5) {
316 module = (long)strtoi(argv[3], NULL, 10, 0, 16, &error);
317 if (error)
318 warnc(error, "Conversion of '%s' module failed,"
319 " using %ld", argv[3], module);
320 motor = argv[4][0];
321 } else {
322 fprintf(stderr,"Missing arguments to invert motor command\n\n");
323 usage();
324 exit(1);
325 }
326 error = common_invert_motor(&func_block, fd, debug, (int)module, motor);
327 break;
328 case SCMD_SUBMOTORBRIDGE:
329 if (argc == 4) {
330 module = (long)strtoi(argv[3], NULL, 10, 0, 16, &error);
331 if (error)
332 warnc(error, "Conversion of '%s' module failed,"
333 " using %ld", argv[3], module);
334 } else {
335 fprintf(stderr,"Missing arguments to bridge motor command\n\n");
336 usage();
337 exit(1);
338 }
339 error = common_bridge_motor(&func_block, fd, debug, (int)module);
340 break;
341 case SCMD_SUBMOTORDISABLE:
342 error = common_enable_disable(&func_block, fd, debug, SCMD_DISABLE);
343 break;
344 case SCMD_SUBMOTORENABLE:
345 error = common_enable_disable(&func_block, fd, debug, SCMD_ENABLE);
346 break;
347 default:
348 fprintf(stderr,"Unhandled subcommand to motor: %s %d\n\n", argv[2], validsub);
349 usage();
350 exit(1);
351 break;
352 }
353 } else {
354 fprintf(stderr,"Unknown subcommand to motor: %s\n\n", argv[2]);
355 usage();
356 exit(1);
357 }
358 } else {
359 fprintf(stderr,"Missing arguments to motor command\n\n");
360 usage();
361 exit(1);
362 }
363 break;
364 case SCMD_READ:
365 memset(register_shadow,SCMD_HOLE_VALUE + 1,SCMD_REG_SIZE);
366 if (argc >= 4) {
367 module = (long)strtoi(argv[2], NULL, 10, 0, 16, &error);
368 if (error)
369 warnc(error, "Conversion of '%s' module failed,"
370 " using %ld", argv[2], module);
371 reg = (uint8_t)strtoi(argv[3], NULL, 0, 0, 0x7e, &error);
372 if (error) {
373 for(c = 0x00; c < SCMD_REG_SIZE;c++)
374 if (strncmp(argv[3],scmdregisternames[c],15) == 0)
375 break;
376 if (c == SCMD_REG_SIZE) {
377 fprintf(stderr,"Bad conversion for read register start: %s\n", argv[3]);
378 exit(1);
379 }
380 reg = c;
381 }
382 reg_e = reg;
383 if (argc == 5) {
384 reg_e = (uint8_t)strtoi(argv[4], NULL, 0, 0, 0x7e, &error);
385 if (error) {
386 for(c = 0x00; c < SCMD_REG_SIZE;c++)
387 if (strncmp(argv[4],scmdregisternames[c],15) == 0)
388 break;
389 if (c == SCMD_REG_SIZE) {
390 fprintf(stderr,"Bad conversion for read register end: %s\n", argv[4]);
391 exit(1);
392 }
393 reg_e = c;
394 }
395 }
396 if (reg_e < reg) {
397 fprintf(stderr,"Register end can not be less than register start: %d %d\n\n", reg, reg_e);
398 usage();
399 exit(1);
400 }
401 if (dev_is_uart) {
402 error = uart_read_register(fd,debug,module,reg,reg_e,®ister_shadow[reg]);
403 } else {
404 error = i2cspi_read_register(fd,debug,module,reg,reg_e,®ister_shadow[reg]);
405 }
406 } else {
407 fprintf(stderr,"Missing arguments to read_register command\n\n");
408 usage();
409 exit(1);
410 }
411 break;
412 case SCMD_WRITE:
413 if (argc == 5) {
414 module = (long)strtoi(argv[2], NULL, 10, 0, 16, &error);
415 if (error)
416 warnc(error, "Conversion of '%s' module failed,"
417 " using %ld", argv[2], module);
418 reg = (uint8_t)strtoi(argv[3], NULL, 0, 0, 0x7e, &error);
419 if (error) {
420 for(c = 0x00; c < SCMD_REG_SIZE;c++)
421 if (strncmp(argv[3],scmdregisternames[c],15) == 0)
422 break;
423 if (c == SCMD_REG_SIZE) {
424 fprintf(stderr,"Bad conversion for write register start: %s\n", argv[3]);
425 exit(1);
426 }
427 reg = c;
428 }
429 reg_value = (int8_t)strtoi(argv[4], NULL, 0, 0, 0xff, &error);
430 if (error)
431 err(EXIT_FAILURE,"Bad conversion for write register for reg_value: %s", argv[4]);
432 if (dev_is_uart) {
433 error = uart_write_register(fd,debug,module,reg,reg_value);
434 } else {
435 error = i2cspi_write_register(fd,debug,module,reg,reg_value);
436 }
437 } else {
438 fprintf(stderr,"Missing arguments to write_register command\n\n");
439 usage();
440 exit(1);
441 }
442 break;
443 case SCMD_RESTART:
444 case SCMD_ENUMERATE:
445 error = common_control_1(&func_block, fd, debug, scmdcmds[valid].id);
446 break;
447 case SCMD_UPDATERATE:
448 if (argc >= 3) {
449 validsub = valid_cmd(updateratesubcmds,__arraycount(updateratesubcmds),argv[2]);
450 if (validsub != -1) {
451 switch (updateratesubcmds[validsub].id) {
452 case SCMD_SUBURGET:
453 error = common_get_update_rate(&func_block, fd, debug, &ur);
454 break;
455 case SCMD_SUBURSET:
456 if (argc == 4) {
457 ur = (uint8_t)strtoi(argv[3], NULL, 0, 0, 0xff, &error);
458 if (error)
459 err(EXIT_FAILURE,"Bad conversion for update_rate: %s", argv[3]);
460 error = common_set_update_rate(&func_block, fd, debug, ur);
461 } else {
462 fprintf(stderr,"Missing arguments to set update_rate command\n\n");
463 usage();
464 exit(1);
465 }
466 break;
467 case SCMD_SUBURFORCE:
468 error = common_force_update(&func_block, fd, debug);
469 break;
470 default:
471 fprintf(stderr,"Unhandled subcommand to updaterate: %s %d\n\n", argv[2], validsub);
472 usage();
473 exit(1);
474 break;
475 }
476 } else {
477 fprintf(stderr,"Unknown subcommand to updaterate: %s\n\n", argv[2]);
478 usage();
479 exit(1);
480 }
481 } else {
482 fprintf(stderr,"Missing arguments to update_rate command\n\n");
483 usage();
484 exit(1);
485 }
486 break;
487 case SCMD_EBUS:
488 if (argc >= 3) {
489 validsub = valid_cmd(ebussubcmds,__arraycount(ebussubcmds),argv[2]);
490 if (validsub != -1) {
491 switch (ebussubcmds[validsub].id) {
492 case SCMD_SUBEBUSGET:
493 error = common_get_ebus_speed(&func_block, fd, debug, &ebus_s);
494 break;
495 case SCMD_SUBEBUSSET:
496 if (argc == 4) {
497 for(ebus_s = 0; ebus_s < __arraycount(ebus_speeds);ebus_s++)
498 if (strncmp(argv[3],ebus_speeds[ebus_s],8) == 0)
499 break;
500 if (ebus_s == __arraycount(ebus_speeds)) {
501 fprintf(stderr,"Bad conversion for set expansion bus speed: %s\n", argv[3]);
502 exit(1);
503 }
504 error = common_set_ebus_speed(&func_block, fd, debug, ebus_s);
505 } else {
506 fprintf(stderr,"Missing arguments to set expansion_bus command\n\n");
507 usage();
508 exit(1);
509 }
510 break;
511 default:
512 fprintf(stderr,"Unhandled subcommand to expansion_bus: %s %d\n\n", argv[2], validsub);
513 usage();
514 exit(1);
515 break;
516 }
517 } else {
518 fprintf(stderr,"Unknown subcommand to expansion_bus: %s\n\n", argv[2]);
519 usage();
520 exit(1);
521 }
522 } else {
523 fprintf(stderr,"Missing arguments to expansion_bus_speed command\n\n");
524 usage();
525 exit(1);
526 }
527 break;
528 case SCMD_LOCK:
529 if (argc == 4) {
530 validsub = valid_cmd(locksubcmds,__arraycount(locksubcmds),argv[2]);
531 if (validsub != -1) {
532 lock_type = valid_cmd(lockcmdtypes,__arraycount(lockcmdtypes),argv[3]);
533 if (lock_type == -1) {
534 fprintf(stderr,"Unknown lock type: %s\n\n", argv[3]);
535 usage();
536 exit(1);
537 }
538 lock_type = lockcmdtypes[lock_type].id;
539
540 if (debug)
541 fprintf(stderr,"Lock type in lock command: %d\n",lock_type);
542
543 switch (locksubcmds[validsub].id) {
544 case SCMD_SUBLOCKGET:
545 error = common_get_lock_state(&func_block, fd, debug, lock_type, &lock_state);
546 break;
547 case SCMD_SUBLOCKLOCK:
548 error = common_set_lock_state(&func_block, fd, debug, lock_type, SCMD_LOCK_LOCKED);
549 break;
550 case SCMD_SUBLOCKUNLOCK:
551 error = common_set_lock_state(&func_block, fd, debug, lock_type, SCMD_LOCK_UNLOCK);
552 break;
553 default:
554 fprintf(stderr,"Unhandled subcommand to lock: %s %d\n\n", argv[2], validsub);
555 usage();
556 exit(1);
557 break;
558 }
559 } else {
560 fprintf(stderr,"Unknown subcommand to lock: %s\n\n", argv[2]);
561 usage();
562 exit(1);
563 }
564 } else {
565 fprintf(stderr,"Missing arguments to lock command\n\n");
566 usage();
567 exit(1);
568 }
569 break;
570 case SCMD_SPIREADONE:
571 error = 0;
572 if (dev_is_uart &&
573 uart_s == UART_IS_SPI_USERLAND) {
574 error = uart_ul_spi_read_one(fd,debug);
575 }
576 break;
577 default:
578 fprintf(stderr,"Unknown handling of command: %d\n",valid);
579 exit(2);
580 break;
581 }
582
583 if (! error) {
584 switch (scmdcmds[valid].id) {
585 case SCMD_IDENTIFY:
586 print_identify(&ir);
587 break;
588 case SCMD_DIAG:
589 print_diag(&diag);
590 break;
591 case SCMD_MOTOR:
592 if (validsub != -1 &&
593 motorsubcmds[validsub].id == SCMD_SUBMOTORGET)
594 print_motor(&motors);
595 break;
596 case SCMD_READ:
597 for(int g = reg; g <= reg_e; g++)
598 printf("Register %d (0x%02X) (%s): %d (0x%02X)\n",g,g,scmdregisternames[g],register_shadow[g],register_shadow[g]);
599 break;
600 case SCMD_UPDATERATE:
601 if (validsub != -1 &&
602 updateratesubcmds[validsub].id == SCMD_SUBURGET)
603 printf("Update rate: %dms\n",ur);
604 break;
605 case SCMD_EBUS:
606 if (validsub != -1 &&
607 ebussubcmds[validsub].id == SCMD_SUBEBUSGET)
608 printf("Expansion bus speed: %s (0x%02X)\n",(ebus_s <= 0x03) ? ebus_speeds[ebus_s] : "Unknown",ebus_s);
609 break;
610 case SCMD_LOCK:
611 if (validsub != -1 &&
612 locksubcmds[validsub].id == SCMD_SUBLOCKGET) {
613 int x = SCMD_MASTER_LOCK_UNLOCKED;
614
615 if (lock_type == SCMD_LOCAL_USER_LOCK ||
616 lock_type == SCMD_GLOBAL_USER_LOCK)
617 x = SCMD_USER_LOCK_UNLOCKED;
618 printf("Lock state: %s (0x%02X)\n",(lock_state == x ? "Unlocked" : "Locked"),lock_state);
619 }
620 break;
621 case SCMD_WRITE:
622 case SCMD_RESTART:
623 case SCMD_ENUMERATE:
624 case SCMD_SPIREADONE:
625 break;
626 default:
627 fprintf(stderr,"Unknown printing of command: %d\n",valid);
628 exit(2);
629 break;
630 }
631 } else {
632 fprintf(stderr,"Error: %d\n", error);
633 exit(1);
634 }
635 } else {
636 fprintf(stderr,"Unknown command: %s\n\n",argv[1]);
637 usage();
638 exit(1);
639 }
640
641 close(fd);
642 exit(0);
643 }
644