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