Simon Arlott has uploaded this change for review. ( https://review.coreboot.org/c/flashrom/+/86411?usp=email )
Change subject: spidriver: Add support for the Excamera Labs SPIDriver programmer ......................................................................
spidriver: Add support for the Excamera Labs SPIDriver programmer
This is a SPI hardware interface with a display (https://spidriver.com/), connected as an FT230X USB serial device at a fixed baud rate of 460800.
Firmware: https://github.com/jamesbowman/spidriver Protocol: https://github.com/jamesbowman/spidriver/blob/master/protocol.md
Most of the implementation is copied from the Bus Pirate programmer.
Tested with a SPIDriver v2 by reading FM25Q128A flash memory on Linux.
Change-Id: I07b23c1146d4ad3606b54a1e8dc8030cf4ebf57b Signed-off-by: Simon Arlott flashrom@octiron.net --- M doc/classic_cli_manpage.rst M include/programmer.h M meson.build M meson_options.txt M programmer_table.c A spidriver.c M test_build.sh 7 files changed, 364 insertions(+), 4 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/11/86411/1
diff --git a/doc/classic_cli_manpage.rst b/doc/classic_cli_manpage.rst index 4f2e68e..c23a0db 100644 --- a/doc/classic_cli_manpage.rst +++ b/doc/classic_cli_manpage.rst @@ -356,6 +356,7 @@ * ``mediatek_i2c_spi`` (for SPI flash ROMs attached to some Mediatek display devices accessible over I2C) * ``dirtyjtag_spi`` (for SPI flash ROMs attached to DirtyJTAG-compatible devices) * ``asm106x`` (for SPI flash ROMs attached to asm106x PCI SATA controllers) + * ``spidriver`` (for SPI flash ROMs attached to an Excamera Labs SPIDriver)
Some programmers have optional or mandatory parameters which are described in detail in the **PROGRAMMER-SPECIFIC INFORMATION** section. Support for some programmers can be disabled at compile time. @@ -1404,6 +1405,26 @@ mechanism in the driver to positively identify that a given I2C bus is actually connected to a supported device.
+spidriver programmer +^^^^^^^^^^^^^^^^^^^^ + +A required ``dev`` parameter specifies the SPIDriver device node and an optional ``mode`` parameter specifies the mode +of the SPI bus. The parameter delimiter is a comma. Syntax is:: + + flashrom -p spidriver:dev=/dev/device,mode=0 + +where ``mode`` can be ``0``, ``1``, ``2`` or ``3``. The default is mode 0. +Setting the SPI mode requires version 2 of the device firmware. + +An optional A and/or B parameter specifies the state of the SPIDriver A and B pins. +This may be used to drive the A or B pins high or low before a transfer. +Syntax is:: + + flashrom -p spidriver:a=state,b=state + +where ``state`` can be ``high`` or ``low``. The default ``state`` is ``high``. + + EXAMPLES --------
@@ -1482,7 +1503,7 @@
* needs TCP access to the network or userspace access to a serial port
-* buspirate_spi +* buspirate_spi, spidriver
* needs userspace access to a serial port
@@ -1503,7 +1524,7 @@ * have to be run as superuser/root * need raw access permission
-* serprog, buspirate_spi, dediprog, usbblaster_spi, ft2232_spi, pickit2_spi, ch341a_spi, digilent_spi, dirtyjtag_spi +* serprog, buspirate_spi, dediprog, usbblaster_spi, ft2232_spi, pickit2_spi, ch341a_spi, digilent_spi, dirtyjtag_spi, spidriver
* can be run as normal user on most operating systems if appropriate device permissions are set
diff --git a/include/programmer.h b/include/programmer.h index e0630b8..56165bb 100644 --- a/include/programmer.h +++ b/include/programmer.h @@ -98,6 +98,7 @@ extern const struct programmer_entry programmer_satasii; extern const struct programmer_entry programmer_serprog; extern const struct programmer_entry programmer_stlinkv3_spi; +extern const struct programmer_entry programmer_spidriver; extern const struct programmer_entry programmer_usbblaster_spi; extern const struct programmer_entry programmer_dirtyjtag_spi;
diff --git a/meson.build b/meson.build index 6c8d3d3..5a4062f 100644 --- a/meson.build +++ b/meson.build @@ -542,6 +542,12 @@ 'srcs' : files('serprog.c', 'serial.c', custom_baud_c), 'flags' : [ '-DCONFIG_SERPROG=1' ], }, + 'spidriver' : { + 'systems' : systems_serial, + 'groups' : [ group_serial, group_external ], + 'srcs' : files('spidriver.c', 'serial.c', custom_baud_c), + 'flags' : [ '-DCONFIG_SPIDRIVER=1' ], + }, 'stlinkv3_spi' : { 'deps' : [ libusb1 ], 'groups' : [ group_usb, group_external ], diff --git a/meson_options.txt b/meson_options.txt index 87456a9..3b9bea1 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -14,7 +14,8 @@ 'gfxnvidia', 'internal', 'it8212', 'jlink_spi', 'linux_mtd', 'linux_spi', 'mediatek_i2c_spi', 'mstarddc_spi', 'ni845x_spi', 'nic3com', 'nicintel', 'nicintel_eeprom', 'nicintel_spi', 'nicnatsemi', 'nicrealtek', 'ogp_spi', 'parade_lspcon', 'pickit2_spi', 'pony_spi', 'raiden_debug_spi', - 'rayer_spi', 'realtek_mst_i2c_spi', 'satamv', 'satasii', 'serprog', 'stlinkv3_spi', 'usbblaster_spi', + 'rayer_spi', 'realtek_mst_i2c_spi', 'satamv', 'satasii', 'serprog', 'spidriver', 'stlinkv3_spi', + 'usbblaster_spi', ], description: 'Active programmers') option('llvm_cov', type : 'feature', value : 'disabled', description : 'build for llvm code coverage') option('man-pages', type : 'feature', value : 'auto', description : 'build the man-page for classic_cli') diff --git a/programmer_table.c b/programmer_table.c index 79acd77..6d28dd1 100644 --- a/programmer_table.c +++ b/programmer_table.c @@ -179,6 +179,10 @@ #if CONFIG_DIRTYJTAG_SPI == 1 &programmer_dirtyjtag_spi, #endif + +#if CONFIG_SPIDRIVER == 1 + &programmer_spidriver, +#endif };
const size_t programmer_table_size = ARRAY_SIZE(programmer_table); diff --git a/spidriver.c b/spidriver.c new file mode 100644 index 0000000..bafc0aa --- /dev/null +++ b/spidriver.c @@ -0,0 +1,326 @@ +/* + * 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/ + * Protocol: https://github.com/jamesbowman/spidriver/blob/master/protocol.md + */ + +#include <assert.h> +#include <stdio.h> +#include <strings.h> +#include <string.h> +#include <stdbool.h> +#include <stdlib.h> +#include <ctype.h> +#include <unistd.h> +#include "flash.h" +#include "programmer.h" +#include "spi.h" + +static int buspirate_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; + + /* Echo */ + buf[i++] = 'e'; + buf[i++] = 'W'; + writearr += len; + writecnt -= len; + + 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 buspirate_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; + + 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("Invalid SPI mode %ld\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("Invalid A state, driving high by default\n"); + } + 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("Invalid B state, driving high by default\n"); + } + free(tmp); + + ret = buspirate_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. */ + memset(buf, 0, 64); + if ((ret = spidriver_sendrecv(buf, 64, 0))) + goto init_err_cleanup_exit; + + default_delay(1400); /* Enough time to receive 64 bytes at 460800bps */ + sp_flush_incoming(); + + memset(buf, 0, 81); + buf[0] = '?'; + if ((ret = spidriver_sendrecv(buf, 1, 80))) + goto init_err_cleanup_exit; + + /* [spidriver2 AAAAAAAA 000000002 5.190 000 21.9 1 1 1 ffff 0 ] */ + /* <version> <serial> <uptime> ^^^^^ ^^^ ^^^^ ^ ^ ^ ^^^^ ^ */ + /* (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 */ + buf[0] = 'u'; + buf[1] = 'a'; + buf[2] = a ? 1 : 0; + buf[3] = 'b'; + buf[4] = 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, 5, 0))) + goto init_err_cleanup_exit; + + if (fw_version >= 1) { + /* Set SPI mode */ + buf[0] = 'm'; + buf[1] = mode & 0xFF; + if ((ret = spidriver_sendrecv(buf, 1, 0))) + goto init_err_cleanup_exit; + } else if (mode != 0) { + msg_perr("SPI mode %ld not supported by version 1 hardware", mode); + 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 = buspirate_spi_init, +}; diff --git a/test_build.sh b/test_build.sh index 968f259..4619618 100755 --- a/test_build.sh +++ b/test_build.sh @@ -11,7 +11,8 @@ gfxnvidia internal it8212 jlink_spi linux_mtd linux_spi parade_lspcon \ mediatek_i2c_spi mstarddc_spi nic3com nicintel nicintel_eeprom nicintel_spi \ nicnatsemi nicrealtek ogp_spi pickit2_spi pony_spi raiden_debug_spi rayer_spi \ - realtek_mst_i2c_spi satamv satasii serprog stlinkv3_spi usbblaster_spi asm106x" + realtek_mst_i2c_spi satamv satasii serprog spidriver stlinkv3_spi usbblaster_spi\ + asm106x"
if [ "$(basename "${CC}")" = "ccc-analyzer" ] || [ -n "${COVERITY_OUTPUT}" ]; then