Simon Arlott has uploaded this change for review.

View Change

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

To view, visit change 86411. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newchange
Gerrit-Project: flashrom
Gerrit-Branch: main
Gerrit-Change-Id: I07b23c1146d4ad3606b54a1e8dc8030cf4ebf57b
Gerrit-Change-Number: 86411
Gerrit-PatchSet: 1
Gerrit-Owner: Simon Arlott