1 /* $NetBSD: scmdctl.c,v 1.3 2024/12/01 10:32:48 rillig 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.3 2024/12/01 10:32:48 rillig 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 170 while ((c = getopt(argc, argv, "db:s:lh")) != -1 ) { 171 switch (c) { 172 case 'd': 173 debug = true; 174 break; 175 case 'b': 176 baud_rate = (long)strtoi(optarg, NULL, 0, 1, LONG_MAX, &error); 177 if (error) 178 warnc(error, "Conversion of `%s' to a baud rate " 179 "failed, using %ld", optarg, baud_rate); 180 break; 181 case 's': 182 slave_a = (long)strtoi(optarg, NULL, 0, 0, LONG_MAX, &error); 183 if (error) 184 warnc(error, "Conversion of `%s' to a SPI slave address " 185 "failed, using %ld", optarg, slave_a); 186 break; 187 case 'l': 188 list_names = true; 189 break; 190 case 'h': 191 default: 192 usage(); 193 exit(0); 194 } 195 } 196 197 argc -= optind; 198 argv += optind; 199 200 if (debug) { 201 fprintf(stderr,"ARGC: %d\n", argc); 202 fprintf(stderr,"ARGV[0]: %s ; ARGV[1]: %s ; ARGV[2]: %s ; ARGV[3]: %s; ARGV[4]: %s; ARGV[5]: %s\n", 203 argv[0],argv[1],argv[2],argv[3],argv[4],argv[5]); 204 } 205 206 if (list_names) { 207 for(c = 0x00; c < SCMD_REG_SIZE;c++) 208 printf("Register %d (0x%02X): %s\n",c,c,scmdregisternames[c]); 209 exit(0); 210 } 211 212 if (argc <= 1) { 213 usage(); 214 exit(0); 215 } 216 217 fd = open(argv[0], O_RDWR, 0); 218 if (fd == -1) { 219 err(EXIT_FAILURE, "open %s", argv[0]); 220 } 221 222 /* Figure out what the device is. First try uart tty, 223 * then SPI userland and the if those two fail, assume 224 * scmd(4). 225 */ 226 ttyerror = ttysetup(fd,(speed_t)baud_rate); 227 228 if (ttyerror) { 229 ul_spierror = ul_spisetup(fd, slave_a); 230 if (ul_spierror) { 231 dev_is_uart = false; 232 } else { 233 uart_s = UART_IS_SPI_USERLAND; 234 } 235 } 236 uart_set_subtype(uart_s, slave_a); 237 238 if (debug) { 239 fprintf(stderr, "ttysetup: error return %d\n", ttyerror); 240 fprintf(stderr, "ul_spisetup: error return %d\n", ul_spierror); 241 } 242 243 /* A UART here is either a tty uart or a SPI userland device. 244 * They mostly end up working the same. 245 */ 246 if (dev_is_uart) { 247 func_block.func_clear = &uart_clear; 248 func_block.func_phy_read = &uart_read_register; 249 func_block.func_phy_write = &uart_write_register; 250 } else { 251 func_block.func_clear = &i2cspi_clear; 252 func_block.func_phy_read = &i2cspi_read_register; 253 func_block.func_phy_write = &i2cspi_write_register; 254 } 255 256 valid = valid_cmd(scmdcmds,__arraycount(scmdcmds),argv[1]); 257 258 if (valid != -1) { 259 switch (scmdcmds[valid].id) { 260 case SCMD_IDENTIFY: 261 module = 0; 262 if (argc == 3) { 263 module = (long)strtoi(argv[2], NULL, 10, 0, 16, &error); 264 if (error) 265 warnc(error, "Conversion of '%s' module failed," 266 " using %ld", argv[2], module); 267 } 268 error = common_identify(&func_block, fd, debug, module, &ir); 269 break; 270 case SCMD_DIAG: 271 module = 0; 272 if (argc == 3) { 273 module = (long)strtoi(argv[2], NULL, 10, 0, 16, &error); 274 if (error) 275 warnc(error, "Conversion of '%s' module failed," 276 " using %ld", argv[2], module); 277 } 278 error = common_diag(&func_block, fd, debug, module, &diag); 279 break; 280 case SCMD_MOTOR: 281 if (argc >= 3) { 282 validsub = valid_cmd(motorsubcmds,__arraycount(motorsubcmds),argv[2]); 283 if (validsub != -1) { 284 switch (motorsubcmds[validsub].id) { 285 case SCMD_SUBMOTORGET: 286 module = SCMD_ANY_MODULE; 287 if (argc == 4) { 288 module = (long)strtoi(argv[3], NULL, 10, 0, 16, &error); 289 if (error) 290 warnc(error, "Conversion of '%s' module failed," 291 " using %ld", argv[3], module); 292 } 293 error = common_get_motor(&func_block, fd, debug, (int)module, &motors); 294 break; 295 case SCMD_SUBMOTORSET: 296 if (argc == 6) { 297 module = (long)strtoi(argv[3], NULL, 10, 0, 16, &error); 298 if (error) 299 warnc(error, "Conversion of '%s' module failed," 300 " using %ld", argv[3], module); 301 motor = argv[4][0]; 302 reg_value = (int8_t)strtoi(argv[5], NULL, 0, -127, 127, &error); 303 if (error) 304 err(EXIT_FAILURE,"Bad conversion for set motor for reg_value: %s", argv[5]); 305 } else { 306 fprintf(stderr,"Missing arguments to set motor command\n\n"); 307 usage(); 308 exit(1); 309 } 310 error = common_set_motor(&func_block, fd, debug, (int)module, motor, reg_value); 311 break; 312 case SCMD_SUBMOTORINVERT: 313 if (argc == 5) { 314 module = (long)strtoi(argv[3], NULL, 10, 0, 16, &error); 315 if (error) 316 warnc(error, "Conversion of '%s' module failed," 317 " using %ld", argv[3], module); 318 motor = argv[4][0]; 319 } else { 320 fprintf(stderr,"Missing arguments to invert motor command\n\n"); 321 usage(); 322 exit(1); 323 } 324 error = common_invert_motor(&func_block, fd, debug, (int)module, motor); 325 break; 326 case SCMD_SUBMOTORBRIDGE: 327 if (argc == 4) { 328 module = (long)strtoi(argv[3], NULL, 10, 0, 16, &error); 329 if (error) 330 warnc(error, "Conversion of '%s' module failed," 331 " using %ld", argv[3], module); 332 } else { 333 fprintf(stderr,"Missing arguments to bridge motor command\n\n"); 334 usage(); 335 exit(1); 336 } 337 error = common_bridge_motor(&func_block, fd, debug, (int)module); 338 break; 339 case SCMD_SUBMOTORDISABLE: 340 error = common_enable_disable(&func_block, fd, debug, SCMD_DISABLE); 341 break; 342 case SCMD_SUBMOTORENABLE: 343 error = common_enable_disable(&func_block, fd, debug, SCMD_ENABLE); 344 break; 345 default: 346 fprintf(stderr,"Unhandled subcommand to motor: %s %d\n\n", argv[2], validsub); 347 usage(); 348 exit(1); 349 } 350 } else { 351 fprintf(stderr,"Unknown subcommand to motor: %s\n\n", argv[2]); 352 usage(); 353 exit(1); 354 } 355 } else { 356 fprintf(stderr,"Missing arguments to motor command\n\n"); 357 usage(); 358 exit(1); 359 } 360 break; 361 case SCMD_READ: 362 memset(register_shadow,SCMD_HOLE_VALUE + 1,SCMD_REG_SIZE); 363 if (argc >= 4) { 364 module = (long)strtoi(argv[2], NULL, 10, 0, 16, &error); 365 if (error) 366 warnc(error, "Conversion of '%s' module failed," 367 " using %ld", argv[2], module); 368 reg = (uint8_t)strtoi(argv[3], NULL, 0, 0, 0x7e, &error); 369 if (error) { 370 for(c = 0x00; c < SCMD_REG_SIZE;c++) 371 if (strncmp(argv[3],scmdregisternames[c],15) == 0) 372 break; 373 if (c == SCMD_REG_SIZE) { 374 fprintf(stderr,"Bad conversion for read register start: %s\n", argv[3]); 375 exit(1); 376 } 377 reg = c; 378 } 379 reg_e = reg; 380 if (argc == 5) { 381 reg_e = (uint8_t)strtoi(argv[4], NULL, 0, 0, 0x7e, &error); 382 if (error) { 383 for(c = 0x00; c < SCMD_REG_SIZE;c++) 384 if (strncmp(argv[4],scmdregisternames[c],15) == 0) 385 break; 386 if (c == SCMD_REG_SIZE) { 387 fprintf(stderr,"Bad conversion for read register end: %s\n", argv[4]); 388 exit(1); 389 } 390 reg_e = c; 391 } 392 } 393 if (reg_e < reg) { 394 fprintf(stderr,"Register end can not be less than register start: %d %d\n\n", reg, reg_e); 395 usage(); 396 exit(1); 397 } 398 if (dev_is_uart) { 399 error = uart_read_register(fd,debug,module,reg,reg_e,®ister_shadow[reg]); 400 } else { 401 error = i2cspi_read_register(fd,debug,module,reg,reg_e,®ister_shadow[reg]); 402 } 403 } else { 404 fprintf(stderr,"Missing arguments to read_register command\n\n"); 405 usage(); 406 exit(1); 407 } 408 break; 409 case SCMD_WRITE: 410 if (argc == 5) { 411 module = (long)strtoi(argv[2], NULL, 10, 0, 16, &error); 412 if (error) 413 warnc(error, "Conversion of '%s' module failed," 414 " using %ld", argv[2], module); 415 reg = (uint8_t)strtoi(argv[3], NULL, 0, 0, 0x7e, &error); 416 if (error) { 417 for(c = 0x00; c < SCMD_REG_SIZE;c++) 418 if (strncmp(argv[3],scmdregisternames[c],15) == 0) 419 break; 420 if (c == SCMD_REG_SIZE) { 421 fprintf(stderr,"Bad conversion for write register start: %s\n", argv[3]); 422 exit(1); 423 } 424 reg = c; 425 } 426 reg_value = (int8_t)strtoi(argv[4], NULL, 0, 0, 0xff, &error); 427 if (error) 428 err(EXIT_FAILURE,"Bad conversion for write register for reg_value: %s", argv[4]); 429 if (dev_is_uart) { 430 error = uart_write_register(fd,debug,module,reg,reg_value); 431 } else { 432 error = i2cspi_write_register(fd,debug,module,reg,reg_value); 433 } 434 } else { 435 fprintf(stderr,"Missing arguments to write_register command\n\n"); 436 usage(); 437 exit(1); 438 } 439 break; 440 case SCMD_RESTART: 441 case SCMD_ENUMERATE: 442 error = common_control_1(&func_block, fd, debug, scmdcmds[valid].id); 443 break; 444 case SCMD_UPDATERATE: 445 if (argc >= 3) { 446 validsub = valid_cmd(updateratesubcmds,__arraycount(updateratesubcmds),argv[2]); 447 if (validsub != -1) { 448 switch (updateratesubcmds[validsub].id) { 449 case SCMD_SUBURGET: 450 error = common_get_update_rate(&func_block, fd, debug, &ur); 451 break; 452 case SCMD_SUBURSET: 453 if (argc == 4) { 454 ur = (uint8_t)strtoi(argv[3], NULL, 0, 0, 0xff, &error); 455 if (error) 456 err(EXIT_FAILURE,"Bad conversion for update_rate: %s", argv[3]); 457 error = common_set_update_rate(&func_block, fd, debug, ur); 458 } else { 459 fprintf(stderr,"Missing arguments to set update_rate command\n\n"); 460 usage(); 461 exit(1); 462 } 463 break; 464 case SCMD_SUBURFORCE: 465 error = common_force_update(&func_block, fd, debug); 466 break; 467 default: 468 fprintf(stderr,"Unhandled subcommand to updaterate: %s %d\n\n", argv[2], validsub); 469 usage(); 470 exit(1); 471 } 472 } else { 473 fprintf(stderr,"Unknown subcommand to updaterate: %s\n\n", argv[2]); 474 usage(); 475 exit(1); 476 } 477 } else { 478 fprintf(stderr,"Missing arguments to update_rate command\n\n"); 479 usage(); 480 exit(1); 481 } 482 break; 483 case SCMD_EBUS: 484 if (argc >= 3) { 485 validsub = valid_cmd(ebussubcmds,__arraycount(ebussubcmds),argv[2]); 486 if (validsub != -1) { 487 switch (ebussubcmds[validsub].id) { 488 case SCMD_SUBEBUSGET: 489 error = common_get_ebus_speed(&func_block, fd, debug, &ebus_s); 490 break; 491 case SCMD_SUBEBUSSET: 492 if (argc == 4) { 493 for(ebus_s = 0; ebus_s < __arraycount(ebus_speeds);ebus_s++) 494 if (strncmp(argv[3],ebus_speeds[ebus_s],8) == 0) 495 break; 496 if (ebus_s == __arraycount(ebus_speeds)) { 497 fprintf(stderr,"Bad conversion for set expansion bus speed: %s\n", argv[3]); 498 exit(1); 499 } 500 error = common_set_ebus_speed(&func_block, fd, debug, ebus_s); 501 } else { 502 fprintf(stderr,"Missing arguments to set expansion_bus command\n\n"); 503 usage(); 504 exit(1); 505 } 506 break; 507 default: 508 fprintf(stderr,"Unhandled subcommand to expansion_bus: %s %d\n\n", argv[2], validsub); 509 usage(); 510 exit(1); 511 } 512 } else { 513 fprintf(stderr,"Unknown subcommand to expansion_bus: %s\n\n", argv[2]); 514 usage(); 515 exit(1); 516 } 517 } else { 518 fprintf(stderr,"Missing arguments to expansion_bus_speed command\n\n"); 519 usage(); 520 exit(1); 521 } 522 break; 523 case SCMD_LOCK: 524 if (argc == 4) { 525 validsub = valid_cmd(locksubcmds,__arraycount(locksubcmds),argv[2]); 526 if (validsub != -1) { 527 lock_type = valid_cmd(lockcmdtypes,__arraycount(lockcmdtypes),argv[3]); 528 if (lock_type == -1) { 529 fprintf(stderr,"Unknown lock type: %s\n\n", argv[3]); 530 usage(); 531 exit(1); 532 } 533 lock_type = lockcmdtypes[lock_type].id; 534 535 if (debug) 536 fprintf(stderr,"Lock type in lock command: %d\n",lock_type); 537 538 switch (locksubcmds[validsub].id) { 539 case SCMD_SUBLOCKGET: 540 error = common_get_lock_state(&func_block, fd, debug, lock_type, &lock_state); 541 break; 542 case SCMD_SUBLOCKLOCK: 543 error = common_set_lock_state(&func_block, fd, debug, lock_type, SCMD_LOCK_LOCKED); 544 break; 545 case SCMD_SUBLOCKUNLOCK: 546 error = common_set_lock_state(&func_block, fd, debug, lock_type, SCMD_LOCK_UNLOCK); 547 break; 548 default: 549 fprintf(stderr,"Unhandled subcommand to lock: %s %d\n\n", argv[2], validsub); 550 usage(); 551 exit(1); 552 } 553 } else { 554 fprintf(stderr,"Unknown subcommand to lock: %s\n\n", argv[2]); 555 usage(); 556 exit(1); 557 } 558 } else { 559 fprintf(stderr,"Missing arguments to lock command\n\n"); 560 usage(); 561 exit(1); 562 } 563 break; 564 case SCMD_SPIREADONE: 565 error = 0; 566 if (dev_is_uart && 567 uart_s == UART_IS_SPI_USERLAND) { 568 error = uart_ul_spi_read_one(fd,debug); 569 } 570 break; 571 default: 572 fprintf(stderr,"Unknown handling of command: %d\n",valid); 573 exit(2); 574 } 575 576 if (! error) { 577 switch (scmdcmds[valid].id) { 578 case SCMD_IDENTIFY: 579 print_identify(&ir); 580 break; 581 case SCMD_DIAG: 582 print_diag(&diag); 583 break; 584 case SCMD_MOTOR: 585 if (validsub != -1 && 586 motorsubcmds[validsub].id == SCMD_SUBMOTORGET) 587 print_motor(&motors); 588 break; 589 case SCMD_READ: 590 for(int g = reg; g <= reg_e; g++) 591 printf("Register %d (0x%02X) (%s): %d (0x%02X)\n",g,g,scmdregisternames[g],register_shadow[g],register_shadow[g]); 592 break; 593 case SCMD_UPDATERATE: 594 if (validsub != -1 && 595 updateratesubcmds[validsub].id == SCMD_SUBURGET) 596 printf("Update rate: %dms\n",ur); 597 break; 598 case SCMD_EBUS: 599 if (validsub != -1 && 600 ebussubcmds[validsub].id == SCMD_SUBEBUSGET) 601 printf("Expansion bus speed: %s (0x%02X)\n",(ebus_s <= 0x03) ? ebus_speeds[ebus_s] : "Unknown",ebus_s); 602 break; 603 case SCMD_LOCK: 604 if (validsub != -1 && 605 locksubcmds[validsub].id == SCMD_SUBLOCKGET) { 606 int x = SCMD_MASTER_LOCK_UNLOCKED; 607 608 if (lock_type == SCMD_LOCAL_USER_LOCK || 609 lock_type == SCMD_GLOBAL_USER_LOCK) 610 x = SCMD_USER_LOCK_UNLOCKED; 611 printf("Lock state: %s (0x%02X)\n",(lock_state == x ? "Unlocked" : "Locked"),lock_state); 612 } 613 break; 614 case SCMD_WRITE: 615 case SCMD_RESTART: 616 case SCMD_ENUMERATE: 617 case SCMD_SPIREADONE: 618 break; 619 default: 620 fprintf(stderr,"Unknown printing of command: %d\n",valid); 621 exit(2); 622 } 623 } else { 624 fprintf(stderr,"Error: %d\n", error); 625 exit(1); 626 } 627 } else { 628 fprintf(stderr,"Unknown command: %s\n\n",argv[1]); 629 usage(); 630 exit(1); 631 } 632 633 close(fd); 634 exit(0); 635 } 636