/* * This file is part of the flashrom project. * * Copyright (C) 2009, 2010, 2011, 2012 Carl-Daniel Hailfinger * Copyright 2025 Simon Arlott * * 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; version 2 of the License. * * 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. */ /* Website: https://spidriver.com/ * Firmware: https://github.com/jamesbowman/spidriver * Protocol: https://github.com/jamesbowman/spidriver/blob/master/protocol.md */ #include #include #include #include #include #include #include #include #include "flash.h" #include "programmer.h" #include "spi.h" static int spidriver_serialport_setup(char *dev) { /* 460800bps, 8 databits, no parity, 1 stopbit */ sp_fd = sp_openserport(dev, 460800); if (sp_fd == SER_INV_FD) return 1; return 0; } static int spidriver_sendrecv(unsigned char *buf, unsigned int writecnt, unsigned int readcnt) { unsigned int i; int ret = 0; msg_pspew("%s: write %i, read %i ", __func__, writecnt, readcnt); if (!writecnt && !readcnt) { msg_perr("Zero length command!\n"); return 1; } if (writecnt) msg_pspew("Sending"); for (i = 0; i < writecnt; i++) msg_pspew(" 0x%02x", buf[i]); if (writecnt) ret = serialport_write(buf, writecnt); if (ret) return ret; if (readcnt) ret = serialport_read(buf, readcnt); if (ret) return ret; if (readcnt) msg_pspew(", receiving"); for (i = 0; i < readcnt; i++) msg_pspew(" 0x%02x", buf[i]); msg_pspew("\n"); return 0; } /* Sending multiple commands too quickly usually fails, so use echo to wait for * each command to complete before sending the next one. */ static int spidriver_send_command(const struct flashctx *flash, unsigned int writecnt, unsigned int readcnt, const unsigned char *writearr, unsigned char *readarr) { int ret = 0; { unsigned char buf[1 + 2]; unsigned int i = 0; /* Assert CS# */ buf[i++] = 's'; /* Echo */ buf[i++] = 'e'; buf[i++] = 'S'; if ((ret = spidriver_sendrecv(buf, i, 1))) { msg_perr("Communication error after writing %u reading %u\n", writecnt, readcnt); return SPI_GENERIC_ERROR; } if (buf[0] != 'S') { msg_perr("Communication error, unexpected select echo response %u\n", buf[0]); return SPI_GENERIC_ERROR; } } while (writecnt > 0) { unsigned char buf[1 + 64 + 2]; unsigned int i = 0; unsigned int len = writecnt > 64 ? 64 : writecnt; /* Write */ i = 0; buf[i++] = 0xc0 - 1 + len; memcpy(&buf[i], writearr, len); i += len; writearr += len; writecnt -= len; /* Echo */ buf[i++] = 'e'; buf[i++] = 'W'; if ((ret = spidriver_sendrecv(buf, i, 1))) { msg_perr("Communication error writing %u\n", len); return SPI_GENERIC_ERROR; } if (buf[0] != 'W') { msg_perr("Communication error, unexpected write echo response %u\n", buf[0]); return SPI_GENERIC_ERROR; } } while (readcnt > 0) { unsigned char buf[1 + 64]; unsigned int i = 0; unsigned int len = readcnt > 64 ? 64 : readcnt; /* Read and write */ i = 0; buf[i++] = 0x80 - 1 + len; memset(&buf[i], 0, len); i += len; if ((ret = spidriver_sendrecv(buf, i, len))) { msg_perr("Communication error reading %u\n", len); return SPI_GENERIC_ERROR; } memcpy(readarr, buf, len); readarr += len; readcnt -= len; } { unsigned char buf[1 + 2]; unsigned int i = 0; /* De-assert CS# */ buf[i++] = 'u'; /* Echo */ buf[i++] = 'e'; buf[i++] = 'U'; if ((ret = spidriver_sendrecv(buf, i, 1))) { msg_perr("Communication error after writing %u reading %u\n", writecnt, readcnt); return SPI_GENERIC_ERROR; } if (buf[0] != 'U') { msg_perr("Communication error, unexpected unselect echo response %u\n", buf[0]); return SPI_GENERIC_ERROR; } } return ret; } static struct spi_master spi_master_spidriver = { .features = SPI_MASTER_4BA, .max_data_read = MAX_DATA_READ_UNLIMITED, .max_data_write = MAX_DATA_WRITE_UNLIMITED, .command = spidriver_send_command, .read = default_spi_read, .write_256 = default_spi_write_256, .shutdown = serialport_shutdown, }; static int spidriver_spi_init(const struct programmer_cfg *cfg) { char *tmp; char *dev; unsigned long fw_version = 0; int ret = 0; long mode = 0; bool a = true; bool b = true; size_t i; dev = extract_programmer_param_str(cfg, "dev"); if (dev && !strlen(dev)) { free(dev); dev = NULL; } if (!dev) { msg_perr("No serial device given. Use flashrom -p spidriver:dev=/dev/ttyUSB0\n"); return 1; } tmp = extract_programmer_param_str(cfg, "mode"); if (tmp) { mode = strtol(tmp, NULL, 10); if (mode < 0 || mode > 3) { msg_perr("Error: Invalid SPI mode %ld\nValid values are 0, 1, 2 or 3\n", mode); return 1; } } free(tmp); tmp = extract_programmer_param_str(cfg, "a"); if (tmp) { if (strcasecmp("high", tmp) == 0) { ; /* Default */ } else if (strcasecmp("low", tmp) == 0) { a = false; } else { msg_perr("Error: Invalid A state %s\nValid values are \"high\" or \"low\"\n", tmp); return 1; } } free(tmp); tmp = extract_programmer_param_str(cfg, "b"); if (tmp) { if (strcasecmp("high", tmp) == 0) { ; /* Default */ } else if (strcasecmp("low", tmp) == 0) { b = false; } else { msg_perr("Error: Invalid B state %s\nValid values are \"high\" or \"low\"\n", tmp); return 1; } } free(tmp); ret = spidriver_serialport_setup(dev); free(dev); if (ret) return ret; /* Largest message is: 1 byte command (tx), 80 byte response plus 1 for * string null termination (rx). */ unsigned char buf[80 + 1]; /* Flush any in-progress transfer with 64 zero bytes. */ i = 64; memset(buf, 0, i); if ((ret = spidriver_sendrecv(buf, i, 0))) goto init_err_cleanup_exit; default_delay(1400); /* Enough time to receive 64 bytes at 460800bps */ sp_flush_incoming(); memset(buf, 0, 81); i = 0; buf[i++] = '?'; if ((ret = spidriver_sendrecv(buf, i, 80))) goto init_err_cleanup_exit; /* [spidriver2 AAAAAAAA 000000002 5.190 000 21.9 1 1 1 ffff 0 ] */ /* ^^^^^ ^^^ ^^^^ ^ ^ ^ ^^^^ ^ */ /* (seconds) | | | | | | | | */ /* | | | | | | | ` SPI mode (0-3) */ /* | | | | | | ` CCITT CRC */ /* | | | | | ` Chip select */ /* | | | | ` "B" signal */ /* | | | ` "A" signal */ /* | | ` Temperature */ /* | ` Current */ /* ` Voltage */ if (buf[0] != '[' || buf[79] != ']' || !strcmp((char*)&buf[1], "spidriver")) { msg_perr("Invalid status response: %s\n", buf); ret = 1; goto init_err_cleanup_exit; } msg_pdbg("Status: %s\n", buf); msg_pdbg("Detected SPIDriver hardware "); if (!strchr("0123456789", buf[10])) { msg_pdbg("(unknown version number format)"); } else { fw_version = strtoul((char*)&buf[10], &tmp, 10); msg_pdbg("v%lu", fw_version); } msg_pdbg("\n"); /* De-assert CS#, configure A and B signals */ i = 0; buf[i++] = 'u'; buf[i++] = 'a'; buf[i++] = a ? 1 : 0; buf[i++] = 'b'; buf[i++] = b ? 1 : 0; msg_pdbg("Raising CS#\n"); msg_pdbg("Driving A %s\n", a ? "high" : "low"); msg_pdbg("Driving B %s\n", b ? "high" : "low"); if ((ret = spidriver_sendrecv(buf, i, 0))) goto init_err_cleanup_exit; if (fw_version >= 2) { /* Set SPI mode */ i = 0; buf[i++] = 'm'; buf[i++] = mode & 0xFF; if ((ret = spidriver_sendrecv(buf, i, 0))) goto init_err_cleanup_exit; } else if (mode != 0) { msg_perr("Error: SPI mode %ld not supported by version %lu hardware\n", mode, fw_version); ret = 1; goto init_err_cleanup_exit; } return register_spi_master(&spi_master_spidriver, NULL); init_err_cleanup_exit: serialport_shutdown(NULL); return ret; } const struct programmer_entry programmer_spidriver = { .name = "spidriver", .type = OTHER, .devs.note = "SPIDriver\n", .init = spidriver_spi_init, };