mirror of
https://review.coreboot.org/flashrom.git
synced 2025-04-26 22:52:34 +02:00

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> Reviewed-on: https://review.coreboot.org/c/flashrom/+/86411 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Peter Marheine <pmarheine@chromium.org> Reviewed-by: Anastasia Klimchuk <aklm@chromium.org>
337 lines
8.5 KiB
C
337 lines
8.5 KiB
C
/*
|
|
* 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 <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 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 ] */
|
|
/* <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 */
|
|
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,
|
|
};
|